diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d9b4c23 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Kai + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index bcd2ef5..1c6a8b0 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,5 @@ # Java Projects -- 使用 [symbols-outline](https://github.com/simrat39/symbols-outline.nvim) 代码实现预览 -- [vscode-java-dependency](https://github.com/Microsoft/vscode-java-dependency) 提供数据支持 - -![java-deps](https://javahello.github.io/dev/nvim-lean/images/java-deps.png) - ## 安装 [English](https://github.com/JavaHello/java-deps.nvim/issues/2) @@ -24,7 +19,7 @@ ``` -- 手动编译 `vscode-java-dependency` +- 手动编译 `vscode-java-dependency` (可选) ```sh git clone https://github.com/microsoft/vscode-java-dependency.git @@ -33,7 +28,7 @@ npm install npm run build-server ``` -- 将 `vscode-java-dependency` 编译后的 `jar` 添加到 jdtls_config["init_options"].bundles 中 +- 将 `vscode-java-dependency` 的 `jar` 包添加到 jdtls_config["init_options"].bundles 中 ```lua local jdtls_config = {} @@ -56,11 +51,10 @@ jdtls_config["init_options"] = { } ``` -- 添加 attach +- 添加命令 ```lua jdtls_config["on_attach"] = function(client, buffer) - require("java-deps").attach(client, buffer) -- 添加命令 local create_command = vim.api.nvim_buf_create_user_command create_command(buffer, "JavaProjects", require("java-deps").toggle_outline, { @@ -76,3 +70,8 @@ end :lua require('java-deps').open_outline() :lua require('java-deps').close_outline() ``` + +## 参考实现 + +- 使用 [symbols-outline](https://github.com/simrat39/symbols-outline.nvim) 代码实现预览 +- [vscode-java-dependency](https://github.com/Microsoft/vscode-java-dependency) 提供数据支持 diff --git a/lua/java-deps.lua b/lua/java-deps.lua deleted file mode 100644 index 55f15d1..0000000 --- a/lua/java-deps.lua +++ /dev/null @@ -1,456 +0,0 @@ -local context = require("java-deps.context") -local parser = require("java-deps.parser") -local providers = require("java-deps.providers.init") -local lsp_command = require("java-deps.lsp-command") -local ui = require("java-deps.ui") -local writer = require("java-deps.writer") -local config = require("java-deps.config") -local utils = require("java-deps.utils.init") -local View = require("java-deps.view") -local folding = require("java-deps.folding") -local node_kind = require("java-deps.symbols").NodeKind -local t_utils = require("java-deps.utils.table") - -local M = { - view = nil, - ------------------------- - -- STATE - ------------------------- - state = { - preview_buf = nil, - preview_win = nil, - hover_buf = nil, - hover_win = nil, - flattened_outline_items = {}, - code_buf = nil, - code_win = nil, - root_items = nil, - current_node = nil, - }, -} - -local function setup_global_autocmd() - if config.options.highlight_hovered_item or config.options.auto_unfold_hover then - vim.api.nvim_create_autocmd("CursorHold", { - pattern = "*", - callback = function() - M._highlight_current_item(nil) - end, - }) - end - - vim.api.nvim_create_autocmd("WinEnter", { - pattern = "*", - callback = require("java-deps.preview").close, - }) -end - -local function setup_buffer_autocmd() - if config.options.auto_preview then - vim.api.nvim_create_autocmd("CursorHold", { - buffer = 0, - callback = require("java-deps.preview").show, - }) - else - vim.api.nvim_create_autocmd("CursorMoved", { - buffer = 0, - callback = require("java-deps.preview").close, - }) - end -end - -local function wipe_state() - M.state = { - preview_buf = nil, - preview_win = nil, - hover_buf = nil, - hover_win = nil, - flattened_outline_items = {}, - code_buf = nil, - code_win = nil, - root_items = nil, - current_node = nil, - } -end - -local function _update_lines() - M.state.flattened_outline_items = parser.flatten(M.state.root_items) - writer.parse_and_write(M.view.bufnr, M.state.flattened_outline_items) -end - -function M._current_node() - local current_line = vim.api.nvim_win_get_cursor(M.view.winnr)[1] - return M.state.flattened_outline_items[current_line] -end - -local function goto_location(change_focus) - local node = M._current_node() - vim.api.nvim_win_set_cursor(M.state.code_win, { node.line + 1, node.character }) - if change_focus then - vim.fn.win_gotoid(M.state.code_win) - end - if config.options.auto_close then - M.close_outline() - end -end - -local function reveal_paths(children, parent) - if children and #children < 1 then - return - end - for i = 1, #children, 1 do - local node = children[i] - if node == nil then - return - end - local perfix - node.parent = parent - if node.cname == nil and node.kind == node_kind.Package then - node.cname = node.name - local name = node.name:match(".+%.(%w+)$") - if name ~= nil then - node.name = name - end - perfix = node.cname .. "." - else - perfix = node.name .. "." - end - local j = i + 1 - local next_children = nil - while j < #children do - local next_node = children[j] - if next_node.cname == nil and node.kind == node_kind.Package then - next_node.cname = next_node.name - local name = next_node.name:match(".+%.(%w+)$") - if name ~= nil then - next_node.name = name - end - end - if node.kind == next_node.kind and vim.startswith(next_node.cname, perfix) then - if next_children == nil then - next_children = {} - end - table.insert(next_children, next_node) - table.remove(children, j) - else - j = j + 1 - end - end - node.children = next_children - if node.children ~= nil then - reveal_paths(node.children, node) - end - end -end - -local function node_eq(a, b) - if a == nil or b == nil then - return false - end - return a.kind == b.kind and a.name == b.name and a.path == b.path -end -local function find_pkg(node) - if node == nil then - return nil - end - if node.kind > node_kind.Package then - return nil - end - if node.kind == node_kind.Package then - return node - else - find_pkg(node.parent) - end -end - -function open_pkg(node) - if node.kind == node_kind.Package then - if node.children == nil then - node.children = {} - end - local c = lsp_command.get_package_data(M.state.code_buf, node) - if c ~= nil and type(c) == "table" and #c > 0 then - vim.list_extend(node.children, c) - end - end -end -function open_pkgs(node) - if node.kind == node_kind.Package then - if node.children == nil then - node.children = {} - else - open_pkgs(node.children[1]) - end - local c = lsp_command.get_package_data(M.state.code_buf, node) - if c ~= nil and type(c) == "table" and #c > 0 then - vim.list_extend(node.children, c) - end - end -end - -local function package_handler(node) - if not folding.is_foldable(node) then - return - end - if M.view:is_open() then - local response = lsp_command.get_package_data(M.state.code_buf, node) - if response == nil or type(response) ~= "table" or #response < 1 then - return - end - parser.sort_result(response) - local child_hir = t_utils.array_copy(node.hierarchy) - table.insert(child_hir, node.isLast) - node.children = parser.parse(response, node.depth + 1, child_hir, node) - return response - end -end - -local function open_file(node) - node = node or M._current_node() - -- open_file - local fname = node.uri - if vim.startswith(fname, "file://") or vim.startswith(fname, "jdt://") then - vim.fn.win_gotoid(M.state.code_win) - local bufnr = vim.uri_to_bufnr(fname) - vim.bo[bufnr].buflisted = true - vim.api.nvim_win_set_buf(M.state.code_win, bufnr) - - if config.options.auto_close then - M.close_outline() - end - end -end - -function M._set_folded_or_open(open, move_cursor, node_index) - local node = M.state.flattened_outline_items[node_index] or M._current_node() - M.state.current_node = node - local folded = false - if node.folded ~= nil then - folded = not node.folded - end - if node.kind == node_kind.File or node.kind == node_kind.PrimaryType or node.kind == node_kind.ClassFile then - if move_cursor then - vim.api.nvim_win_set_cursor(M.view.winnr, { node_index, 0 }) - end - if open then - open_file(node) - end - else - M._set_folded(folded, move_cursor, node_index) - end -end -function M._set_folded(folded, move_cursor, node_index) - local node = M.state.flattened_outline_items[node_index] or M._current_node() - M.state.current_node = node - if folding.is_foldable(node) then - node.folded = folded - - if move_cursor then - vim.api.nvim_win_set_cursor(M.view.winnr, { node_index, 0 }) - end - - package_handler(node) - _update_lines() - elseif node.parent then - local parent_node = M.state.flattened_outline_items[node.parent.line_in_outline] - - if parent_node then - M._set_folded(folded, not parent_node.folded and folded, parent_node.line_in_outline) - end - else - node.folded = false - end -end - -function M._set_all_folded(folded, nodes) - nodes = nodes or M.state.root_items - - for _, node in ipairs(nodes) do - node.folded = folded - if node.children then - M._set_all_folded(folded, node.children) - end - end - - _update_lines() -end - -function M._highlight_current_item(winnr) - local has_provider = providers.has_provider() - - local is_current_buffer_the_outline = M.view.bufnr == vim.api.nvim_get_current_buf() - - local doesnt_have_outline_buf = not M.view.bufnr - - local should_exit = not has_provider or doesnt_have_outline_buf or is_current_buffer_the_outline - - -- Make a special case if we have a window number - -- Because we might use this to manually focus so we dont want to quit this - -- function - if winnr then - should_exit = false - end - - if should_exit then - return - end - - local win = winnr or vim.api.nvim_get_current_win() - - local hovered_line = vim.api.nvim_win_get_cursor(win)[1] - 1 - - local leaf_node = nil - - local cb = function(value) - value.hovered = nil - - if value.line == hovered_line then - value.hovered = true - leaf_node = value - end - end - - utils.items_dfs(cb, M.state.root_items) - - _update_lines() - - if leaf_node then - for index, node in ipairs(M.state.flattened_outline_items) do - if node == leaf_node then - vim.api.nvim_win_set_cursor(M.view.winnr, { index, 1 }) - break - end - end - end -end - -local function setup_keymaps(bufnr) - local map = function(...) - utils.nmap(bufnr, ...) - end - -- show help - map(config.options.keymaps.show_help, require("java-deps.config").show_help) - -- close outline - map(config.options.keymaps.close, function() - M.view:close() - end) - -- open_file - map(config.options.keymaps.open_file, function() - M._set_folded_or_open(true) - end) - -- preview symbol - map(config.options.keymaps.toggle_preview, require("java-deps.preview").toggle) - -- fold selection - map(config.options.keymaps.fold, function() - M._set_folded(true) - end) - -- unfold selection - map(config.options.keymaps.unfold, function() - M._set_folded(false) - end) - -- fold all - map(config.options.keymaps.fold_all, function() - M._set_all_folded(true) - end) - -- unfold all - map(config.options.keymaps.unfold_all, function() - M._set_all_folded(false) - end) - -- fold reset - map(config.options.keymaps.fold_reset, function() - M._set_all_folded(nil) - end) -end - -local function handler(response) - if response == nil or type(response) ~= "table" then - return - end - - M.state.code_win = vim.api.nvim_get_current_win() - - M.view:setup_view() - -- clear state when buffer is closed - vim.api.nvim_buf_attach(M.view.bufnr, false, { - on_detach = function(_, _) - wipe_state() - end, - }) - - setup_keymaps(M.view.bufnr) - setup_buffer_autocmd() - - local items = parser.parse(response) - - M.state.root_items = items - M.state.flattened_outline_items = parser.flatten(items) - - writer.parse_and_write(M.view.bufnr, M.state.flattened_outline_items) - - M._highlight_current_item(M.state.code_win) -end - -function M.toggle_outline() - if M.view:is_open() then - M.close_outline() - else - M.open_outline() - end -end - -local function resolve_path(path) - local resp = lsp_command.resolve_path(M.state.code_buf, path) - local function find_root(node) - for _, value in ipairs(M.state.flattened_outline_items) do - if value.kind == node.kind then - if node.kind == node_kind.PrimaryType then - if value.name == node.name then - return value - end - elseif node.kind == node_kind.CompilationUnit then - if value.uri == node.uri then - return value - end - elseif node.path ~= nil and value.path == node.path then - return value - end - end - end - end - if resp ~= nil then - for _, value in ipairs(resp) do - local node = find_root(value) - if node ~= nil then - M._set_folded_or_open(false, true, node.line_in_outline) - end - end - end -end - -function M.open_outline() - if not M.view:is_open() then - M.state.code_buf = vim.api.nvim_get_current_buf() - local resp = lsp_command.get_projects(M.state.code_buf, context.current_config().root_uri) - local path = vim.uri_from_bufnr(M.state.code_buf) - handler(resp) - resolve_path(path) - end -end - -function M.close_outline() - M.view:close() -end - -function M.setup(opts) - config.setup(opts) - ui.setup_highlights() - - M.view = View:new() - setup_global_autocmd() -end - -M.attach = function(client, buf, root_dir) - context.attach(client, buf, root_dir) -end - -return M diff --git a/lua/java-deps/config.lua b/lua/java-deps/config.lua index feae772..698f341 100644 --- a/lua/java-deps/config.lua +++ b/lua/java-deps/config.lua @@ -1,96 +1,49 @@ local M = { - debug = false, - jdtls_name = "jdtls", - options = { - show_guides = true, - show_path_details = true, - auto_close = false, - width = 32, - relative_width = true, - show_numbers = false, - show_relative_numbers = false, - preview_bg_highlight = "Pmenu", - winblend = 0, - request_timeout = 3000, - autofold_depth = 99, - fold_markers = { "", "" }, - position = "right", - wrap = false, - hierarchical_view = true, - keymaps = { -- These keymaps can be a string or a table for multiple keys - open_file = "o", - close = { "", "q" }, - show_help = "?", - toggle_preview = "K", - fold = "h", - unfold = "l", - fold_all = "W", - unfold_all = "E", - fold_reset = "R", - }, - symbols = { - Workspace = { icon = "", hl = "@text.uri" }, - Project = { icon = "", hl = "@text.uri" }, - PackageRoot = { icon = "", hl = "@text.uri" }, - Package = { icon = "", hl = "@namespace" }, - PrimaryType = { icon = "󰠱", hl = "@type" }, - CompilationUnit = { icon = "", hl = "@text.uri" }, - ClassFile = { icon = "", hl = "@text.uri" }, - Container = { icon = "󰆧", hl = "@text.uri" }, - Folder = { icon = "󰉋", hl = "@method" }, - File = { icon = "󰈙", hl = "@method" }, - - CLASS = { icon = "󰠱", hl = "@class" }, - ENUM = { icon = "", hl = "@enum" }, - INTERFACE = { icon = "", hl = "@interface" }, - JAR = { icon = "", hl = "@conditional" }, - }, - symbol_blacklist = {}, - }, + jdtls_name = "jdtls", + options = { + show_guides = true, + auto_close = false, + width = 40, + show_numbers = false, + show_relative_numbers = false, + preview_bg_highlight = "Pmenu", + winblend = 0, + fold_markers = { "", "" }, + position = "right", + wrap = false, + hierarchical_view = true, + keymaps = { + close = "q", + toggle_fold = "o", + }, + symbols = { + icons = {}, + }, + }, } M.setup = function(config) - if config then - M = vim.tbl_extend("force", M, config) - end + if config then + M = vim.tbl_extend("force", M, config) + end end function M.has_numbers() - return M.options.show_numbers or M.options.show_relative_numbers -end -local function has_value(tab, val) - for _, value in ipairs(tab) do - if value == val then - return true - end - end - - return false -end - -function M.is_symbol_blacklisted(kind) - if kind == nil then - return false - end - return has_value(M.options.symbol_blacklist, kind) + return M.options.show_numbers or M.options.show_relative_numbers end function M.show_help() - print("Current keymaps:") - print(vim.inspect(M.options.keymaps)) + print("Current keymaps:") + print(vim.inspect(M.options.keymaps)) end function M.get_split_command() - if M.options.position == "left" then - return "topleft vs" - else - return "botright vs" - end + if M.options.position == "left" then + return "topleft vs" + else + return "botright vs" + end end function M.get_window_width() - if M.options.relative_width then - return math.ceil(vim.o.columns * (M.options.width / 100)) - else - return M.options.width - end + return M.options.width end return M diff --git a/lua/java-deps/context.lua b/lua/java-deps/context.lua deleted file mode 100644 index 4bd32ae..0000000 --- a/lua/java-deps/context.lua +++ /dev/null @@ -1,33 +0,0 @@ -local utils = require("java-deps.utils") -local config = require("java-deps.config") -local M = { - current_client = nil, - root_dir = nil, - root_uri = nil, -} -M.current_config = function() - if M.root_dir == nil then - M.attach(utils.get_client(config.jdtls_name)) - end - return M -end - -M.attach = function(client, _, root_dir) - if client == nil then - vim.notify(config.jdtls_name .. " client not found", vim.log.levels.ERROR) - return - end - M.current_client = client - M.root_dir = root_dir or client.config.root_dir - M.root_uri = "file://" .. M.root_dir - if M.current_config().root_dir == nil then - vim.notify(config.jdtls_name .. " client root_dir is empty", vim.log.levels.ERROR) - end -end - -M.clear = function() - M.current_client = nil - M.client_configs = {} -end - -return M diff --git a/lua/java-deps/folding.lua b/lua/java-deps/folding.lua deleted file mode 100644 index 9ca94b4..0000000 --- a/lua/java-deps/folding.lua +++ /dev/null @@ -1,44 +0,0 @@ -local M = {} -local config = require("java-deps.config") -local node_kind = require("java-deps.symbols").NodeKind - -local is_pkg = function(node) - if - node_kind.Workspace == node.kind - or node_kind.Container == node.kind - or node_kind.Project == node.kind - or node_kind.PackageRoot == node.kind - or node_kind.Package == node.kind - or node_kind.Folder == node.kind - then - return true - end - return false -end -M.is_foldable = function(node) - if node.children and #node.children > 0 then - return true - end - return is_pkg(node) -end - -local get_default_folded = function(depth) - local fold_past = config.options.autofold_depth - if not fold_past then - return false - else - return depth >= fold_past - end -end - -M.is_folded = function(node) - if node.folded ~= nil then - return node.folded - elseif node.hovered and config.options.auto_unfold_hover then - return false - else - return get_default_folded(node.depth) - end -end - -return M diff --git a/lua/java-deps/highlight.lua b/lua/java-deps/highlight.lua new file mode 100644 index 0000000..ce2af2a --- /dev/null +++ b/lua/java-deps/highlight.lua @@ -0,0 +1,34 @@ +local M = { + items = { + nsid = vim.api.nvim_create_namespace("java-deps-items"), + highlights = { + LineGuide = { link = "Comment" }, + }, + }, +} + +M.init_hl = function() + local ihlf = function(hls) + for name, hl in pairs(hls.highlights) do + if vim.fn.hlexists("JavaDeps" .. name) == 0 then + vim.api.nvim_set_hl(0, "JavaDeps" .. name, { link = hl.link }) + end + end + end + ihlf(M.items) +end +M.clear_all_ns = function(bufnr) + vim.api.nvim_buf_clear_namespace(bufnr, -1, 0, -1) +end + +---@param bufnr number +---@param hl_info table +---@param nodes TreeItem[] +function M.add_item_highlights(bufnr, hl_info, nodes) + for _, line_hl in ipairs(hl_info) do + local line, hl_start, hl_end, hl_type = unpack(line_hl) + vim.api.nvim_buf_add_highlight(bufnr, M.items.nsid, hl_type, line - 1, hl_start, hl_end) + end +end + +return M diff --git a/lua/java-deps/init.lua b/lua/java-deps/init.lua new file mode 100644 index 0000000..e579100 --- /dev/null +++ b/lua/java-deps/init.lua @@ -0,0 +1,42 @@ +local config = require("java-deps.config") +local View = require("java-deps.view") +local highlight = require("java-deps.highlight") +-- debug +vim.g.java_deps = { + debug = true, +} + +local M = { + view = nil, + state = { + code_buf = nil, + code_win = nil, + }, +} + +function M.toggle_outline() + if M.view:is_open() then + M.close_outline() + else + M.open_outline() + end +end + +function M.open_outline() + if not M.view:is_open() then + M.view:open() + M.view:revealPaths() + end +end + +function M.close_outline() + M.view:close() +end + +function M.setup(opts) + config.setup(opts) + M.view = View:new() + highlight.init_hl() +end + +return M diff --git a/lua/java-deps/java/ContainerEntryKind.lua b/lua/java-deps/java/ContainerEntryKind.lua new file mode 100644 index 0000000..059b3ac --- /dev/null +++ b/lua/java-deps/java/ContainerEntryKind.lua @@ -0,0 +1,22 @@ +local INodeData = require("java-deps.java.nodeData").INodeData +local M = {} + +---@enum ContainerEntryKind +M.ContainerEntryKind = { + CPE_LIBRARY = 1, + CPE_PROJECT = 2, + CPE_SOURCE = 3, + CPE_VARIABLE = 4, + CPE_CONTAINER = 5, +} +---@class IContainerNodeData: INodeData +---@field entryKind ContainerEntryKind +local IContainerNodeData = INodeData:new() +IContainerNodeData.__index = IContainerNodeData + +function IContainerNodeData:new() + return setmetatable(INodeData:new(), self) +end +M.IContainerNodeData = IContainerNodeData + +return M diff --git a/lua/java-deps/java/IPackageRootNodeData.lua b/lua/java-deps/java/IPackageRootNodeData.lua new file mode 100644 index 0000000..0c709d2 --- /dev/null +++ b/lua/java-deps/java/IPackageRootNodeData.lua @@ -0,0 +1,34 @@ +local INodeData = require("java-deps.java.nodeData").INodeData +local M = {} +---@enum PackageRootKind +M.PackageRootKind = { + K_SOURCE = 1, + K_BINARY = 2, +} + +---@class IPackageRootNodeData:INodeData +---@field entryKind PackageRootKind +---@field attributes table +local IPackageRootNodeData = INodeData:new() +IPackageRootNodeData.__index = IPackageRootNodeData +function IPackageRootNodeData:new() + return setmetatable(INodeData:new(), self) +end + +---@param node INodeData +---@return IPackageRootNodeData +function IPackageRootNodeData:form(node) + return setmetatable(node, self) +end + +function IPackageRootNodeData:getEntryKind() + return self.entryKind +end + +function IPackageRootNodeData:getAttributes() + return self.attributes +end + +M.IPackageRootNodeData = IPackageRootNodeData + +return M diff --git a/lua/java-deps/java/hieararchicalPackageNodeData.lua b/lua/java-deps/java/hieararchicalPackageNodeData.lua new file mode 100644 index 0000000..c46a1ca --- /dev/null +++ b/lua/java-deps/java/hieararchicalPackageNodeData.lua @@ -0,0 +1,120 @@ +local _n = require("java-deps.java.nodeData") +local _ipkg = require("java-deps.java.IPackageRootNodeData") +local INodeData = _n.INodeData +local IPackageRootNodeData = _ipkg.IPackageRootNodeData +local M = {} + +---@class HierarchicalPackageNodeData: INodeData +---@field displayName string +---@field name string +---@field _nodeData? INodeData +---@field children HierarchicalPackageNodeData[] +local HierarchicalPackageNodeData = INodeData:new() +HierarchicalPackageNodeData.__index = HierarchicalPackageNodeData + +---@param displayName string +---@param parentName? string +---@return HierarchicalPackageNodeData +function HierarchicalPackageNodeData:new(displayName, parentName) + local name = (parentName == nil or parentName == "") and displayName or parentName .. "." .. displayName + return setmetatable({ + displayName = displayName, + name = name, + children = {}, + }, self) +end + +function HierarchicalPackageNodeData:compressTree() + while self.name ~= "" and #self.children == 1 and not self:isPackage() do + local child = self.children[1] + self.name = self.name .. "." .. child.displayName + self.displayName = self.displayName .. "." .. child.displayName + self.children = child.children + self._nodeData = child._nodeData + end + for _, child in ipairs(self.children) do + child:compressTree() + end +end +---@param packages string[] +---@param nodeData INodeData +function HierarchicalPackageNodeData:addSubPackage(packages, nodeData) + if #packages == 0 then + self._nodeData = nodeData + return + end + local subPackageDisplayName = table.remove(packages, 1) + ---@type HierarchicalPackageNodeData? + local childNode + for _, child in ipairs(self.children) do + if child.displayName == subPackageDisplayName then + childNode = child + break + end + end + if childNode then + childNode:addSubPackage(packages, nodeData) + else + local newNode = HierarchicalPackageNodeData:new(subPackageDisplayName, self.name) + newNode:addSubPackage(packages, nodeData) + table.insert(self.children, newNode) + end +end + +function HierarchicalPackageNodeData:isPackage() + return self._nodeData ~= nil +end + +function HierarchicalPackageNodeData:getDisplayName() + return self.displayName +end + +function HierarchicalPackageNodeData:getName() + return self.name +end +function HierarchicalPackageNodeData:getModuleName() + return self._nodeData and self._nodeData.moduleName +end +function HierarchicalPackageNodeData:getPath() + return self._nodeData and self._nodeData.path +end + +function HierarchicalPackageNodeData:getHandlerIdentifier() + return self._nodeData and self._nodeData.handlerIdentifier +end + +function HierarchicalPackageNodeData:getUri() + return self._nodeData and self._nodeData.uri +end + +function HierarchicalPackageNodeData:getKind() + return self._nodeData and self._nodeData.kind +end + +function HierarchicalPackageNodeData:getChildren() + return self.children +end + +function HierarchicalPackageNodeData:getMetaData() + return self._nodeData and self._nodeData.metaData +end + +function HierarchicalPackageNodeData:getEntryKind() + return self._nodeData and IPackageRootNodeData:form(self._nodeData):getEntryKind() +end + +M.HierarchicalPackageNodeData = HierarchicalPackageNodeData + +---@param packageList INodeData[] +---@return HierarchicalPackageNodeData +M.createHierarchicalNodeDataByPackageList = function(packageList) + local result = HierarchicalPackageNodeData:new("", "") + for _, nodeData in ipairs(packageList) do + local packages = vim.split(nodeData.name, "%.") + result:addSubPackage(packages, nodeData) + end + result:compressTree() + return result +end + +return M diff --git a/lua/java-deps/java/jdtls.lua b/lua/java-deps/java/jdtls.lua new file mode 100644 index 0000000..dc245fc --- /dev/null +++ b/lua/java-deps/java/jdtls.lua @@ -0,0 +1,94 @@ +local lsp_command = require("java-deps.java.lsp-command") +local node_data = require("java-deps.java.nodeData") +local NodeKind = node_data.NodeKind +local M = {} + +---@param params string +---@return INodeData[] +M.getProjects = function(params) + local err, resp = lsp_command.execute_command({ + command = lsp_command.JAVA_PROJECT_LIST, + arguments = params, + }) + if err then + vim.notify(err.message or vim.inspect(err), vim.log.levels.WARN) + return {} + end + return node_data.generateNodeList(resp) +end +M.root_dir = function() + return lsp_command.get_client().root_dir +end + +M.getProjectUris = function() + local err, resp = lsp_command.execute_command({ + command = lsp_command.GET_ALL_PROJECTS, + }) + if err then + vim.notify(err.message or vim.inspect(err), vim.log.levels.WARN) + return {} + end + return resp or {} +end + +---@return INodeData[] +M.getPackageData = function(params) + local excludePatterns = nil + local err, resp = lsp_command.execute_command({ + command = lsp_command.JAVA_GETPACKAGEDATA, + arguments = params, + }) + if err then + vim.notify(err.message or vim.inspect(err), vim.log.levels.WARN) + return {} + end + ---@type INodeData[] + local nodeDatas = node_data.generateNodeList(resp) + -- Filter out non java resources + if true then + nodeDatas = vim.tbl_filter(function(data) + return data.kind ~= NodeKind.Folder and data.kind ~= NodeKind.File + end, nodeDatas) + end + + if excludePatterns and #nodeDatas > 0 then + local uriOfChildren = vim.tbl_map(function(node) + return node.uri + end, nodeDatas) + + local urisToExclude = {} + for _, pattern in pairs(excludePatterns) do + if excludePatterns[pattern] then + local toExclude = vim.tbl_filter(function(urio) + return string.match(urio, pattern) + end, uriOfChildren) + for _, uriToExclude in ipairs(toExclude) do + table.insert(urisToExclude, uriToExclude) + end + end + end + if #urisToExclude > 0 then + nodeDatas = vim.tbl_filter(function(node) + if not node.uri then + return true + end + return not vim.tbl_contains(urisToExclude, node.uri) + end, nodeDatas) + end + end + return nodeDatas +end + +---@return INodeData[] +M.resolvePath = function(params) + local err, resp = lsp_command.execute_command({ + command = lsp_command.JAVA_RESOLVEPATH, + arguments = params, + }) + if err then + vim.notify(err.message or vim.inspect(err), vim.log.levels.WARN) + return {} + end + return node_data.generateNodeList(resp) +end +return M diff --git a/lua/java-deps/java/lsp-command.lua b/lua/java-deps/java/lsp-command.lua new file mode 100644 index 0000000..653cf3c --- /dev/null +++ b/lua/java-deps/java/lsp-command.lua @@ -0,0 +1,53 @@ +local config = require("java-deps.config") +local M = {} + +M.JAVA_PROJECT_LIST = "java.project.list" +M.GET_ALL_PROJECTS = "java.project.getAll" +M.JAVA_PROJECT_REFRESH_LIB_SERVER = "java.project.refreshLib" +M.JAVA_GETPACKAGEDATA = "java.getPackageData" +M.JAVA_RESOLVEPATH = "java.resolvePath" +M.JAVA_PROJECT_GETMAINCLASSES = "java.project.getMainClasses" + +---@return vim.lsp.Client? +M.get_client = function() + local clients = vim.lsp.get_clients({ name = config.jdtls_name or "jdtls" }) + if not clients or #clients == 0 then + vim.notify("No jdtls client found", vim.log.levels.WARN) + return + end + return clients[1] +end + +-- 使用异步没有错误信输出 +M.execute_command_async = function(command, callback, bufnr) + local client = M.get_client() + if not client then + return + end + local co + if not callback then + co = coroutine.running() + if co then + callback = function(err, resp) + coroutine.resume(co, err, resp) + end + end + end + client.request("workspace/executeCommand", command, callback, bufnr) + if co then + return coroutine.yield() + end +end +M.execute_command = function(command, bufnr) + local client = M.get_client() + if not client then + return + end + local resp = client.request_sync("workspace/executeCommand", command, 20000, bufnr) + if not resp then + return "No response" + end + return nil, resp.result +end + +return M diff --git a/lua/java-deps/java/nodeData.lua b/lua/java-deps/java/nodeData.lua new file mode 100644 index 0000000..79dfe93 --- /dev/null +++ b/lua/java-deps/java/nodeData.lua @@ -0,0 +1,90 @@ +local M = {} +---@enum NodeKind +M.NodeKind = { + Workspace = 1, + Project = 2, + PackageRoot = 3, + Package = 4, + PrimaryType = 5, + CompilationUnit = 6, + ClassFile = 7, + Container = 8, + Folder = 9, + File = 10, +} + +---@enum TypeKind +M.TypeKind = { + Class = 1, + Interface = 2, + Enum = 3, +} + +---@class INodeData +---@field displayName? string +---@field name string +---@field moduleName? string +---@field path? string +---@field handlerIdentifier? string +---@field uri? string +---@field kind NodeKind +---@field children? any[] +---@field metaData? table +local INodeData = {} +INodeData.__index = INodeData +function INodeData:new() + return setmetatable({}, self) +end + +function INodeData:form(resp) + return setmetatable(resp, self) +end +---@param resp table? +---@return INodeData[] +M.generateNodeList = function(resp) + if not resp then + return {} + end + local nodes = {} + for _, node in ipairs(resp) do + table.insert(nodes, INodeData:form(node)) + end + return nodes +end +function INodeData:getDisplayName() + return self.displayName +end + +function INodeData:getName() + return self.name +end +function INodeData:getModuleName() + return self.moduleName +end +function INodeData:getPath() + return self.path +end + +function INodeData:getHandlerIdentifier() + return self.handlerIdentifier +end + +function INodeData:getUri() + return self.uri +end + +function INodeData:getKind() + return self.kind +end + +function INodeData:getChildren() + return self.children +end + +function INodeData:getMetaData() + return self.metaData +end + +M.INodeData = INodeData + +return M diff --git a/lua/java-deps/lsp-command.lua b/lua/java-deps/lsp-command.lua deleted file mode 100644 index bacd2ca..0000000 --- a/lua/java-deps/lsp-command.lua +++ /dev/null @@ -1,91 +0,0 @@ -local config = require("java-deps.config") -local context = require("java-deps.context") -local symbols = require("java-deps.symbols") -local M = {} - -local request = function(bufnr, method, params, handler) - local client = context.current_client - client.request(method, params, handler, bufnr) -end -local request_sync = function(bufnr, method, params, timeout) - timeout = timeout or config.options.request_timeout - local client = context.current_client - return client.request_sync(method, params, timeout, bufnr) -end - -M.command = function(buf, params, handler) - request(buf, "workspace/executeCommand", params, function(err, projects) - if err then - vim.notify(err.message, vim.log.levels.WARN) - elseif projects then - handler(projects) - end - end) -end - -M.command_sync = function(buf, command, arguments, timeout) - local params0 = {} - params0.command = command - params0.arguments = arguments - local resp, err = request_sync(buf, "workspace/executeCommand", params0, timeout) - if err then - vim.notify("executeCommand " .. command .. " error: " .. err) - return - end - if resp.result ~= nil then - return resp.result - elseif resp.error ~= nil then - vim.notify(vim.inspect(resp), vim.log.levels.WARN) - end -end - -local function root_project(node) - local root = node - while root ~= nil do - if root.kind == symbols.NodeKind.Project then - return root - end - root = root.parent - end -end - -M.get_package_data = function(buf, node) - local arguments = { - kind = node.kind, - } - if node.kind == symbols.NodeKind.Project then - arguments.projectUri = node.uri - elseif node.kind == symbols.NodeKind.Container then - arguments.projectUri = root_project(node).uri - arguments.path = node.path - elseif node.kind == symbols.NodeKind.PackageRoot then - arguments.projectUri = root_project(node).uri - arguments.rootPath = node.path - arguments.handlerIdentifier = node.handlerIdentifier - arguments.isHierarchicalView = config.options.hierarchical_view - elseif node.kind == symbols.NodeKind.Package then - arguments.projectUri = root_project(node).uri - arguments.path = node.name - arguments.handlerIdentifier = node.handlerIdentifier - else - arguments.projectUri = root_project(node).uri - arguments.path = node.path - end - return M.command_sync(buf, "java.getPackageData", arguments) -end - -M.get_projects = function(buf, rootUri) - rootUri = rootUri or context.root_uri - local arguments = { - rootUri, - } - return M.command_sync(buf, "java.project.list", arguments) -end - -M.resolve_path = function(buf, uri) - local arguments = { - uri, - } - return M.command_sync(buf, "java.resolvePath", arguments) -end -return M diff --git a/lua/java-deps/mappings.lua b/lua/java-deps/mappings.lua new file mode 100644 index 0000000..71c9483 --- /dev/null +++ b/lua/java-deps/mappings.lua @@ -0,0 +1,17 @@ +local config = require("java-deps.config") +local M = {} + +---@param view View +M.init_mappings = function(view) + vim.keymap.set("n", "h", function() end, { noremap = true, silent = true, buffer = view.bufnr }) + vim.keymap.set("n", "l", function() end, { noremap = true, silent = true, buffer = view.bufnr }) + vim.keymap.set("n", config.options.keymaps.toggle_fold or "o", function() + view:foldToggle() + end, { noremap = true, silent = true, buffer = view.bufnr }) + + vim.keymap.set("n", config.options.keymaps.close or "q", function() + view:close() + end, { noremap = true, silent = true, buffer = view.bufnr }) +end + +return M diff --git a/lua/java-deps/parser.lua b/lua/java-deps/parser.lua index ce05700..5b03c1a 100644 --- a/lua/java-deps/parser.lua +++ b/lua/java-deps/parser.lua @@ -1,176 +1,88 @@ local config = require("java-deps.config") -local symbols = require("java-deps.symbols") -local folding = require("java-deps.folding") -local t_utils = require("java-deps.utils.table") -local ui = require("java-deps.ui") +local icons = require("java-deps.views.icons") local M = {} -local function parse_result(result, depth, hierarchy, parent) - local ret = nil - - for index, value in pairs(result) do - if not config.is_symbol_blacklisted(symbols.kinds[value.kind]) then - -- the hierarchy is basically a table of booleans which tells whether - -- the parent was the last in its group or not - local hir = hierarchy 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 node = { - entryKind = value.entryKind, - metaData = value.metaData, - handlerIdentifier = value.handlerIdentifier, - kind = value.kind, - uri = value.uri, - path = value.path, - name = value.name, - icon = symbols.icon_from_kind(value), - depth = level, - isLast = isLast, - hierarchy = hir, - parent = parent, - } - if ret == nil then - ret = {} - end - - table.insert(ret, node) - - local children = nil - if value.children ~= nil then - -- copy by value because we dont want it messing with the hir table - local child_hir = t_utils.array_copy(hir) - table.insert(child_hir, isLast) - children = parse_result(value.children, level + 1, child_hir, node) - end - - node.children = children - end +local function str_to_table(str) + local t = {} + for i = 1, #str do + t[i] = str:sub(i, i) end - return ret -end - -M.sort_result = function(result) - table.sort(result, function(a, b) - if a.kind == b.kind then - return a.name < b.name - end - return a.kind < b.kind - end) - return result + return t end -function M.parse(response, depth, hierarchy, parent) - local sorted = M.sort_result(response) - return parse_result(sorted, depth, hierarchy, parent) -end - -function M.flatten(outline_items, ret) - ret = ret or {} - for _, value in ipairs(outline_items) do - table.insert(ret, value) - value.line_in_outline = #ret - if value.children ~= nil and not folding.is_folded(value) then - M.flatten(value.children, 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 guides = { + markers = { + bottom = "└", + middle = "├", + vertical = "│", + horizontal = "─", + }, +} +---@param flattened_outline_items TreeItem +---@return table +---@return table function M.get_lines(flattened_outline_items) local lines = {} local hl_info = {} for node_line, node in ipairs(flattened_outline_items) do local depth = node.depth - local marker_space = (config.options.fold_markers and 1) or 0 + local marker_space = config.options.fold_markers and 1 or 0 - local line = t_utils.str_to_table(string.rep(" ", depth + marker_space)) - local running_length = 1 - - local function add_guide_hl(from, to) - table.insert(hl_info, { - node_line, - from, - to, - "JavaDespOutlineConnector", - }) - end + local line = str_to_table(string.rep(" ", depth + marker_space)) + local folded = node:is_foldable() for index, _ in ipairs(line) do -- all items start with a space (or two) if config.options.show_guides then - -- makes the guides - if index == 1 then - line[index] = " " - -- i f index is last, add a bottom marker if current item is last, - -- else add a middle marker - elseif index == #line then + if index == #line then -- add fold markers - if config.options.fold_markers and folding.is_foldable(node) then - if folding.is_folded(node) then - line[index] = config.options.fold_markers[1] - else + if config.options.fold_markers and folded then + if node:is_expanded() then line[index] = config.options.fold_markers[2] + else + line[index] = config.options.fold_markers[1] end - - add_guide_hl(running_length, running_length + vim.fn.strlen(line[index]) - 1) - - -- the root level has no vertical markers elseif depth > 1 then if node.isLast then - line[index] = ui.markers.bottom - add_guide_hl(running_length, running_length + vim.fn.strlen(ui.markers.bottom) - 1) + line[index] = guides.markers.bottom else - line[index] = ui.markers.middle - add_guide_hl(running_length, running_length + vim.fn.strlen(ui.markers.middle) - 1) + line[index] = guides.markers.middle end 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 node.hierarchy[index] and depth > 1 then - line[index + marker_space] = ui.markers.vertical - add_guide_hl( - running_length - 1 + 2 * marker_space, - running_length + vim.fn.strlen(ui.markers.vertical) - 1 + 2 * marker_space - ) + line[index + marker_space] = guides.markers.vertical end end line[index] = line[index] .. " " - - running_length = running_length + vim.fn.strlen(line[index]) end - local final_prefix = line + local string_prefix = "" - local string_prefix = t_utils.table_to_str(final_prefix) + for _, value in ipairs(line) do + string_prefix = string_prefix .. tostring(value) + end - table.insert(lines, string_prefix .. node.icon .. " " .. node.name) + local hl_icon = icons.get_icon(node.data) + local icon = hl_icon.icon + table.insert(lines, string_prefix .. icon .. " " .. node.label) local hl_start = #string_prefix - local hl_end = #string_prefix + #node.icon - local hl_type = config.options.symbols[symbols.kinds[node.kind]].hl + local hl_end = #string_prefix + #icon + local hl_type = hl_icon.hl or "Type" table.insert(hl_info, { node_line, hl_start, hl_end, hl_type }) - - node.prefix_length = #string_prefix + #node.icon + 1 + node.prefix_length = #string_prefix + #icon + 1 end return lines, hl_info end -function M.get_details(flattened_outline_items) - local lines = {} - for _, value in ipairs(flattened_outline_items) do - local detail - if symbols.type_kind(value) == symbols.NodeKind.JAR then - detail = value.path - end - table.insert(lines, detail or "") - end - return lines -end return M diff --git a/lua/java-deps/preview.lua b/lua/java-deps/preview.lua index ea6ac15..3b08731 100644 --- a/lua/java-deps/preview.lua +++ b/lua/java-deps/preview.lua @@ -4,252 +4,228 @@ local jd = require("java-deps") local M = {} local state = { - preview_buf = nil, - preview_win = nil, - hover_buf = nil, - hover_win = nil, + preview_buf = nil, + preview_win = nil, + hover_buf = nil, + hover_win = nil, } local function is_current_win_outline() - local curwin = vim.api.nvim_get_current_win() - return curwin == jd.view.winnr + local curwin = vim.api.nvim_get_current_win() + return curwin == jd.view.winnr end local function has_code_win() - if jd.state.code_win == nil then - return false - end - local isWinValid = vim.api.nvim_win_is_valid(jd.state.code_win) - if not isWinValid then - return false - end - local bufnr = vim.api.nvim_win_get_buf(jd.state.code_win) - local isBufValid = vim.api.nvim_buf_is_valid(bufnr) - return isBufValid + if jd.state.code_win == nil then + return false + end + local isWinValid = vim.api.nvim_win_is_valid(jd.state.code_win) + if not isWinValid then + return false + end + local bufnr = vim.api.nvim_win_get_buf(jd.state.code_win) + local isBufValid = vim.api.nvim_buf_is_valid(bufnr) + return isBufValid end local function get_offset() - local outline_winnr = jd.view.winnr - local width = 53 - local height = 0 - - if config.has_numbers() then - width = width + 4 - end - - if config.options.position == "right" then - width = 0 - width - else - width = vim.api.nvim_win_get_width(outline_winnr) + 1 - end - return { height, width } + local outline_winnr = jd.view.winnr + local width = 53 + local height = 0 + + if config.has_numbers() then + width = width + 4 + end + + if config.options.position == "right" then + width = 0 - width + else + width = vim.api.nvim_win_get_width(outline_winnr) + 1 + end + return { height, width } end local function get_height() - local uis = vim.api.nvim_list_uis() - return math.ceil(uis[1].height / 3) + local uis = vim.api.nvim_list_uis() + return math.ceil(uis[1].height / 3) end local function get_hovered_node() - local hovered_line = vim.api.nvim_win_get_cursor(jd.view.winnr)[1] - local node = jd.state.flattened_outline_items[hovered_line] - return node + local hovered_line = vim.api.nvim_win_get_cursor(jd.view.winnr)[1] + local node = jd.state.flattened_outline_items[hovered_line] + return node end local function update_preview(code_buf) - code_buf = code_buf or vim.api.nvim_win_get_buf(jd.state.code_win) - - local node = get_hovered_node() - if not node then - return - end - local lines = vim.api.nvim_buf_get_lines(code_buf, 0, -1, false) - - if state.preview_buf ~= nil then - vim.api.nvim_buf_set_lines(state.preview_buf, 0, -1, 0, lines) - vim.api.nvim_win_set_cursor(state.preview_win, { node.line + 1, node.character }) - end + code_buf = code_buf or vim.api.nvim_win_get_buf(jd.state.code_win) + + local node = get_hovered_node() + if not node then + return + end + local lines = vim.api.nvim_buf_get_lines(code_buf, 0, -1, false) + + if state.preview_buf ~= nil then + vim.api.nvim_buf_set_lines(state.preview_buf, 0, -1, 0, lines) + vim.api.nvim_win_set_cursor(state.preview_win, { node.line + 1, node.character }) + end end local function setup_preview_buf() - local code_buf = vim.api.nvim_win_get_buf(jd.state.code_win) - local ft = vim.api.nvim_buf_get_option(code_buf, "filetype") + local code_buf = vim.api.nvim_win_get_buf(jd.state.code_win) + local ft = vim.api.nvim_buf_get_option(code_buf, "filetype") - local function treesitter_attach() - local ts_highlight = require("nvim-treesitter.highlight") + local function treesitter_attach() + local ts_highlight = require("nvim-treesitter.highlight") - ts_highlight.attach(state.preview_buf, ft) - end + ts_highlight.attach(state.preview_buf, ft) + end - -- user might not have tree sitter installed - pcall(treesitter_attach) + -- user might not have tree sitter installed + pcall(treesitter_attach) - vim.api.nvim_buf_set_option(state.preview_buf, "syntax", ft) - vim.api.nvim_buf_set_option(state.preview_buf, "bufhidden", "delete") - vim.api.nvim_win_set_option(state.preview_win, "cursorline", true) - update_preview(code_buf) + vim.api.nvim_buf_set_option(state.preview_buf, "syntax", ft) + vim.api.nvim_buf_set_option(state.preview_buf, "bufhidden", "delete") + vim.api.nvim_win_set_option(state.preview_win, "cursorline", true) + update_preview(code_buf) end local function get_hover_params(node, winnr) - local bufnr = vim.api.nvim_win_get_buf(winnr) - -- local uri = vim.uri_from_bufnr(bufnr) - return { - node = node, - bufnr = bufnr, - } + local bufnr = vim.api.nvim_win_get_buf(winnr) + -- local uri = vim.uri_from_bufnr(bufnr) + return { + node = node, + bufnr = bufnr, + } end local function update_hover() - if not has_code_win() then - return - end - - local node = get_hovered_node() - if not node then - return - end - - local content - if node.metaData and node.metaData["maven.groupId"] then - content = "```xml\n\n" - .. "\t" - .. node.metaData["maven.groupId"] - .. "\n" - .. "\t" - .. node.metaData["maven.artifactId"] - .. "\n" - .. "\t" - .. node.metaData["maven.version"] - .. "\n" - - if node.metaData["maven.scope"] then - content = content .. "\t" .. node.metaData["maven.scope"] .. "\n" - end - if node.metaData["maven.optionaldependency"] then - content = content .. "\t" .. node.metaData["maven.optionaldependency"] .. "\n" - end - content = content .. "\n```" - else - return - end - - local mdstring = { - { kind = "markdown", value = content }, - { kind = "markdown", value = "[" .. node.name .. "](" .. node.path .. ")" }, - } - local markdown_lines = vim.lsp.util.convert_input_to_markdown_lines(mdstring) - markdown_lines = vim.lsp.util.trim_empty_lines(markdown_lines) - if vim.tbl_isempty(markdown_lines) then - markdown_lines = { "###No info available!" } - end - - markdown_lines = vim.lsp.util.stylize_markdown(state.hover_buf, markdown_lines, {}) - - if state.hover_buf ~= nil then - vim.api.nvim_buf_set_lines(state.hover_buf, 0, -1, 0, markdown_lines) - end + if not has_code_win() then + return + end + + local node = get_hovered_node() + if not node then + return + end + + local content + if node.metaData and node.metaData["maven.groupId"] then + content = "```xml\n\n" + .. "\t" + .. node.metaData["maven.groupId"] + .. "\n" + .. "\t" + .. node.metaData["maven.artifactId"] + .. "\n" + .. "\t" + .. node.metaData["maven.version"] + .. "\n" + + if node.metaData["maven.scope"] then + content = content .. "\t" .. node.metaData["maven.scope"] .. "\n" + end + if node.metaData["maven.optionaldependency"] then + content = content .. "\t" .. node.metaData["maven.optionaldependency"] .. "\n" + end + content = content .. "\n```" + else + return + end + + local mdstring = { + { kind = "markdown", value = content }, + { kind = "markdown", value = "[" .. node.name .. "](" .. node.path .. ")" }, + } + local markdown_lines = vim.lsp.util.convert_input_to_markdown_lines(mdstring) + markdown_lines = vim.lsp.util.trim_empty_lines(markdown_lines) + if vim.tbl_isempty(markdown_lines) then + markdown_lines = { "###No info available!" } + end + + markdown_lines = vim.lsp.util.stylize_markdown(state.hover_buf, markdown_lines, {}) + + if state.hover_buf ~= nil then + vim.api.nvim_buf_set_lines(state.hover_buf, 0, -1, 0, markdown_lines) + end end local function setup_hover_buf() - if not has_code_win() then - return - end - -- local code_buf = vim.api.nvim_win_get_buf(jd.state.code_win) - -- local ft = vim.api.nvim_buf_get_option(code_buf, "filetype") - -- vim.api.nvim_buf_set_option(state.hover_buf, "syntax", "xml") - vim.api.nvim_buf_set_option(state.hover_buf, "bufhidden", "delete") - vim.api.nvim_win_set_option(state.hover_win, "wrap", true) - vim.api.nvim_win_set_option(state.hover_win, "cursorline", false) - update_hover() + if not has_code_win() then + return + end + -- local code_buf = vim.api.nvim_win_get_buf(jd.state.code_win) + -- local ft = vim.api.nvim_buf_get_option(code_buf, "filetype") + -- vim.api.nvim_buf_set_option(state.hover_buf, "syntax", "xml") + vim.api.nvim_buf_set_option(state.hover_buf, "bufhidden", "delete") + vim.api.nvim_win_set_option(state.hover_win, "wrap", true) + vim.api.nvim_win_set_option(state.hover_win, "cursorline", false) + update_hover() end local function set_bg_hl() - local winhi = "Normal:" .. config.options.preview_bg_highlight - -- vim.api.nvim_win_set_option(state.preview_win, "winhighlight", winhi) - vim.api.nvim_win_set_option(state.hover_win, "winhighlight", winhi) - local winblend = config.options.winblend - -- vim.api.nvim_win_set_option(state.preview_win, "winblend", winblend) - vim.api.nvim_win_set_option(state.hover_win, "winblend", winblend) + local winhi = "Normal:" .. config.options.preview_bg_highlight + -- vim.api.nvim_win_set_option(state.preview_win, "winhighlight", winhi) + vim.api.nvim_win_set_option(state.hover_win, "winhighlight", winhi) + local winblend = config.options.winblend + -- vim.api.nvim_win_set_option(state.preview_win, "winblend", winblend) + vim.api.nvim_win_set_option(state.hover_win, "winblend", winblend) end -local function show_preview() - if state.preview_win == nil and state.preview_buf == nil then - state.preview_buf = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_attach(state.preview_buf, false, { - on_detach = function() - state.preview_buf = nil - state.preview_win = nil - end, - }) - local offsets = get_offset() - state.preview_win = vim.api.nvim_open_win(state.preview_buf, false, { - relative = "win", - width = 50, - height = get_height(), - bufpos = { 0, 0 }, - row = offsets[1], - col = offsets[2], - border = config.options.border, - }) - setup_preview_buf() - else - update_preview() - end -end local function show_hover() - if state.hover_win == nil and state.hover_buf == nil then - state.hover_buf = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_attach(state.hover_buf, false, { - on_detach = function() - state.hover_buf = nil - state.hover_win = nil - end, - }) - local offsets = get_offset() - local height = get_height() - state.hover_win = vim.api.nvim_open_win(state.hover_buf, false, { - relative = "win", - width = 50, - height = height, - bufpos = { 0, 0 }, - row = offsets[1] + height + 2, - col = offsets[2], - border = config.options.border, - }) - setup_hover_buf() - else - update_hover() - end + if state.hover_win == nil and state.hover_buf == nil then + state.hover_buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_attach(state.hover_buf, false, { + on_detach = function() + state.hover_buf = nil + state.hover_win = nil + end, + }) + local offsets = get_offset() + local height = get_height() + state.hover_win = vim.api.nvim_open_win(state.hover_buf, false, { + relative = "win", + width = 50, + height = height, + bufpos = { 0, 0 }, + row = offsets[1] + height + 2, + col = offsets[2], + border = config.options.border, + }) + setup_hover_buf() + else + update_hover() + end end function M.show() - if not is_current_win_outline() or #vim.api.nvim_list_wins() < 2 then - return - end + if not is_current_win_outline() or #vim.api.nvim_list_wins() < 2 then + return + end - -- show_preview() - show_hover() - set_bg_hl() + -- show_preview() + show_hover() + set_bg_hl() end function M.close() - if has_code_win() then - if state.preview_win ~= nil and vim.api.nvim_win_is_valid(state.preview_win) then - vim.api.nvim_win_close(state.preview_win, true) - end - if state.hover_win ~= nil and vim.api.nvim_win_is_valid(state.hover_win) then - vim.api.nvim_win_close(state.hover_win, true) - end - end + if has_code_win() then + if state.preview_win ~= nil and vim.api.nvim_win_is_valid(state.preview_win) then + vim.api.nvim_win_close(state.preview_win, true) + end + if state.hover_win ~= nil and vim.api.nvim_win_is_valid(state.hover_win) then + vim.api.nvim_win_close(state.hover_win, true) + end + end end function M.toggle() - if state.preview_win ~= nil then - M.close() - else - M.show() - end + if state.preview_win ~= nil then + M.close() + else + M.show() + end end return M diff --git a/lua/java-deps/providers/init.lua b/lua/java-deps/providers/init.lua deleted file mode 100644 index 6a128b2..0000000 --- a/lua/java-deps/providers/init.lua +++ /dev/null @@ -1,32 +0,0 @@ -local M = {} - -local providers = { - "java-deps/providers/nvim-lsp", -} - -_G._java_deps_outline_current_provider = nil - -function M.has_provider() - local ret = false - for _, value in ipairs(providers) do - local provider = require(value) - if provider.should_use_provider(0) then - ret = true - break - end - end - return ret -end - -function M.request_symbols(bufnr, node, on_symbols) - for _, value in ipairs(providers) do - local provider = require(value) - if provider.should_use_provider(0) then - _G._java_deps_outline_current_provider = provider - provider.request_symbols(bufnr, node, on_symbols) - break - end - end -end - -return M diff --git a/lua/java-deps/providers/nvim-lsp.lua b/lua/java-deps/providers/nvim-lsp.lua deleted file mode 100644 index 2aa9b5d..0000000 --- a/lua/java-deps/providers/nvim-lsp.lua +++ /dev/null @@ -1,17 +0,0 @@ -local M = {} - -function M.hover_info(bufnr, params, on_info) - on_info(nil, { - contents = { - kind = "markdown", - content = { "No extra information availaible!" }, - }, - }) -end - --- probably change this -function M.should_use_provider(bufnr) - return true -end - -return M diff --git a/lua/java-deps/symbols.lua b/lua/java-deps/symbols.lua deleted file mode 100644 index 2e8b1aa..0000000 --- a/lua/java-deps/symbols.lua +++ /dev/null @@ -1,118 +0,0 @@ -local config = require("java-deps.config") -local M = {} -M.NodeKind = { - Workspace = 1, - Project = 2, - PackageRoot = 3, - Package = 4, - PrimaryType = 5, - CompilationUnit = 6, - ClassFile = 7, - Container = 8, - Folder = 9, - File = 10, - - -- metaData.TypeKind - CLASS = 11, - INTERFACE = 12, - ENUM = 13, - - JAR = 24, -} - -M.TypeKind = { - Class = 1, - Interface = 2, - Enum = 3, -} - -M.kinds = { - "Workspace", - "Project", - "PackageRoot", - "Package", - "PrimaryType", - "CompilationUnit", - "ClassFile", - "Container", - "Folder", - "File", - - -- metaData.TypeKind - [M.NodeKind.CLASS] = "CLASS", - [M.NodeKind.INTERFACE] = "INTERFACE", - [M.NodeKind.ENUM] = "ENUM", - - [M.NodeKind.JAR] = "JAR", -} - -M.ContainerEntryKind = { - CPE_LIBRARY = 1, - CPE_PROJECT = 2, - CPE_SOURCE = 3, - CPE_VARIABLE = 4, - CPE_CONTAINER = 5, -} -M.PackageRootKind = { - K_SOURCE = 1, - K_BINARY = 2, -} - -M.ContainerType = { - JRE = "jre", - Maven = "maven", - Gradle = "gradle", - ReferencedLibrary = "referencedLibrary", - Unknown = "", -} - -M.ContainerPath = { - JRE = "org.eclipse.jdt.launching.JRE_CONTAINER", - Maven = "org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER", - Gradle = "org.eclipse.buildship.core.gradleclasspathcontainer", - ReferencedLibrary = "REFERENCED_LIBRARIES_PATH", -} - -M.ContainerType = { - JRE = "jre", - Maven = "maven", - Gradle = "gradle", - ReferencedLibrary = "referencedLibrary", - Unknown = "", -} - -M.type_kind = function(node) - if node.metaData and node.metaData.TypeKind then - return node.metaData.TypeKind + 10 - end - if node.name and vim.endswith(node.name, ".jar") then - return M.NodeKind.JAR - end - return node.kind -end - -function M.icon_from_kind(node) - local symbols = config.options.symbols - - if type(node) == "string" then - return symbols[node].icon - end - - local kind = M.type_kind(node) - return symbols[M.kinds[kind]].icon -end - -function M.get_container_type(containerPath) - if vim.startswith(containerPath, M.ContainerPath.JRE) then - return M.ContainerType.JRE - elseif vim.startswith(containerPath, M.ContainerPath.Maven) then - return M.ContainerType.Maven - elseif vim.startswith(containerPath, M.ContainerPath.Gradle) then - return M.ContainerType.Gradle - elseif vim.startswith(containerPath, M.ContainerPath.ReferencedLibrary) then - return M.ContainerType.ReferencedLibrary - end - return M.ContainerType.Unknown -end - -return M diff --git a/lua/java-deps/ui.lua b/lua/java-deps/ui.lua deleted file mode 100644 index 032ff78..0000000 --- a/lua/java-deps/ui.lua +++ /dev/null @@ -1,58 +0,0 @@ -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 - -function M.setup_highlights() - -- Setup the FocusedSymbol highlight group if it hasn't been done already by - -- a theme or manually set - if vim.fn.hlexists 'FocusedSymbol' == 0 then - local cline_hl = vim.api.nvim_get_hl_by_name('CursorLine', true) - local string_hl = vim.api.nvim_get_hl_by_name('String', true) - - vim.api.nvim_set_hl( - 0, - 'FocusedSymbol', - { bg = cline_hl.background, fg = string_hl.foreground } - ) - end - - -- Some colorschemes do some funky things with the comment highlight, most - -- notably making them italic, which messes up the outline connector. Fix - -- this by copying the foreground color from the comment hl into a new - -- highlight. - local comment_fg_gui = vim.fn.synIDattr( - vim.fn.synIDtrans(vim.fn.hlID 'Comment'), - 'fg', - 'gui' - ) - - if vim.fn.hlexists 'JavaDespOutlineConnector' == 0 then - vim.cmd( - string.format('hi JavaDespOutlineConnector guifg=%s', comment_fg_gui) - ) - end -end - -return M diff --git a/lua/java-deps/utils/init.lua b/lua/java-deps/utils/init.lua deleted file mode 100644 index 00c66db..0000000 --- a/lua/java-deps/utils/init.lua +++ /dev/null @@ -1,122 +0,0 @@ -local M = {} - ----maps the table|string of keys to the action ----@param keys table|string ----@param action function|string -function M.nmap(bufnr, keys, action) - if type(keys) == 'string' then - keys = { keys } - end - - for _, lhs in ipairs(keys) do - vim.keymap.set( - 'n', - lhs, - action, - { silent = true, noremap = true, buffer = bufnr } - ) - end -end - ---- @param f function ---- @param delay number ---- @return function -function M.debounce(f, delay) - local timer = vim.loop.new_timer() - - return function(...) - local args = { ... } - - timer:start( - delay, - 0, - vim.schedule_wrap(function() - timer:stop() - f(unpack(args)) - end) - ) - end -end - -function M.items_dfs(callback, children) - for _, val in ipairs(children) do - callback(val) - - if val.children then - M.items_dfs(callback, val.children) - end - end -end - ----Merges a symbol tree recursively, only replacing nodes ----which have changed. This will maintain the folding ----status of any unchanged nodes. ----@param new_node table New node ----@param old_node table Old node ----@param index? number Index of old_item in parent ----@param parent? table Parent of old_item -M.merge_items_rec = function(new_node, old_node, index, parent) - local failed = false - - if not new_node or not old_node then - failed = true - else - for key, _ in pairs(new_node) do - if - vim.tbl_contains({ - 'parent', - 'children', - 'folded', - 'hovered', - 'line_in_outline', - 'hierarchy', - }, key) - then - goto continue - end - - if key == 'name' then - -- in the case of a rename, just rename the existing node - old_node['name'] = new_node['name'] - else - if not vim.deep_equal(new_node[key], old_node[key]) then - failed = true - break - end - end - - ::continue:: - end - end - - if failed then - if parent and index then - parent[index] = new_node - end - else - local next_new_item = new_node.children or {} - - -- in case new children are created on a node which - -- previously had no children - if #next_new_item > 0 and not old_node.children then - old_node.children = {} - end - - local next_old_item = old_node.children or {} - - for i = 1, math.max(#next_new_item, #next_old_item) do - M.merge_items_rec(next_new_item[i], next_old_item[i], i, next_old_item) - end - end -end - -M.get_client = function(server_name) - local clients = vim.lsp.get_active_clients() - for _, client in ipairs(clients) do - if client.name == server_name then - return client - end - end -end - -return M diff --git a/lua/java-deps/utils/table.lua b/lua/java-deps/utils/table.lua deleted file mode 100644 index 62246ff..0000000 --- a/lua/java-deps/utils/table.lua +++ /dev/null @@ -1,31 +0,0 @@ -local M = {} - -function M.table_to_str(t) - local ret = "" - for _, value in ipairs(t) do - ret = ret .. tostring(value) - end - return ret -end - -function M.str_to_table(str) - local t = {} - for i = 1, #str do - t[i] = str:sub(i, i) - end - return t -end - ---- Copies an array and returns it because lua usually does references ----@generic T ----@param t T[] ----@return T[] -function M.array_copy(t) - local ret = {} - for _, value in ipairs(t) do - table.insert(ret, value) - end - return ret -end - -return M diff --git a/lua/java-deps/view.lua b/lua/java-deps/view.lua index df78c39..8d88081 100644 --- a/lua/java-deps/view.lua +++ b/lua/java-deps/view.lua @@ -1,61 +1,163 @@ local config = require("java-deps.config") +local provider = require("java-deps.views.data_provider") +local writer = require("java-deps.writer") +local mappings = require("java-deps.mappings") +local jdtls = require("java-deps.java.jdtls") + +---@class View +---@field bufnr number +---@field winnr number +---@field code_buf number +---@field code_win number +---@field data_provider DataProvider +---@field _flatten_tree TreeItem[] +---@field _cursor_item TreeItem? local View = {} +View.__index = View function View:new() - return setmetatable({ bufnr = nil, winnr = nil }, { __index = View }) + return setmetatable({ bufnr = nil, winnr = nil }, self) end ---creates the outline window and sets it up function View:setup_view() - -- create a scratch unlisted buffer - self.bufnr = vim.api.nvim_create_buf(false, true) - - -- delete buffer when window is closed / buffer is hidden - vim.api.nvim_buf_set_option(self.bufnr, "bufhidden", "delete") - -- create a split - vim.cmd(config.get_split_command()) - -- resize to a % of the current window size - vim.cmd("vertical resize " .. config.get_window_width()) - - -- get current (outline) window and attach our buffer to it - self.winnr = vim.api.nvim_get_current_win() - vim.api.nvim_win_set_buf(self.winnr, self.bufnr) - - -- window stuff - vim.api.nvim_win_set_option(self.winnr, "number", false) - vim.api.nvim_win_set_option(self.winnr, "relativenumber", false) - vim.api.nvim_win_set_option(self.winnr, "winfixwidth", true) - vim.api.nvim_win_set_option(self.winnr, "list", false) - vim.api.nvim_win_set_option(self.winnr, "wrap", config.options.wrap) - vim.api.nvim_win_set_option(self.winnr, "linebreak", true) -- only has effect when wrap=true - vim.api.nvim_win_set_option(self.winnr, "breakindent", true) -- only has effect when wrap=true - -- Would be nice to use ui.markers.vertical as part of showbreak to keep - -- continuity of the tree UI, but there's currently no way to style the - -- color, apart from globally overriding hl-NonText, which will potentially - -- mess with other theme/user settings. So just use empty spaces for now. - vim.api.nvim_win_set_option(self.winnr, "showbreak", " ") -- only has effect when wrap=true. - -- buffer stuff - vim.api.nvim_buf_set_name(self.bufnr, "JavaProjects") - vim.api.nvim_buf_set_option(self.bufnr, "filetype", "JavaProjects") - vim.api.nvim_buf_set_option(self.bufnr, "modifiable", false) - - if config.options.show_numbers or config.options.show_relative_numbers then - vim.api.nvim_win_set_option(self.winnr, "nu", true) - end - - if config.options.show_relative_numbers then - vim.api.nvim_win_set_option(self.winnr, "rnu", true) - end + -- create a scratch unlisted buffer + self.bufnr = vim.api.nvim_create_buf(false, true) + + -- delete buffer when window is closed / buffer is hidden + vim.api.nvim_buf_set_option(self.bufnr, "bufhidden", "delete") + -- create a split + vim.cmd(config.get_split_command()) + -- resize to a % of the current window size + vim.cmd("vertical resize " .. config.get_window_width()) + + -- get current (outline) window and attach our buffer to it + self.winnr = vim.api.nvim_get_current_win() + vim.api.nvim_win_set_buf(self.winnr, self.bufnr) + + -- window stuff + vim.api.nvim_win_set_option(self.winnr, "spell", false) + vim.api.nvim_win_set_option(self.winnr, "signcolumn", "no") + vim.api.nvim_win_set_option(self.winnr, "foldcolumn", "0") + vim.api.nvim_win_set_option(self.winnr, "number", false) + vim.api.nvim_win_set_option(self.winnr, "relativenumber", false) + vim.api.nvim_win_set_option(self.winnr, "winfixwidth", true) + vim.api.nvim_win_set_option(self.winnr, "list", false) + vim.api.nvim_win_set_option(self.winnr, "wrap", config.options.wrap) + vim.api.nvim_win_set_option(self.winnr, "linebreak", true) -- only has effect when wrap=true + vim.api.nvim_win_set_option(self.winnr, "breakindent", true) -- only has effect when wrap=true + -- Would be nice to use ui.markers.vertical as part of showbreak to keep + -- continuity of the tree UI, but there's currently no way to style the + -- color, apart from globally overriding hl-NonText, which will potentially + -- mess with other theme/user settings. So just use empty spaces for now. + vim.api.nvim_win_set_option(self.winnr, "showbreak", " ") -- only has effect when wrap=true. + -- buffer stuff + vim.api.nvim_buf_set_name(self.bufnr, "JavaProjects") + vim.api.nvim_buf_set_option(self.bufnr, "filetype", "JavaProjects") + vim.api.nvim_buf_set_option(self.bufnr, "modifiable", false) + + if config.options.show_numbers or config.options.show_relative_numbers then + vim.api.nvim_win_set_option(self.winnr, "nu", true) + end + + if config.options.show_relative_numbers then + vim.api.nvim_win_set_option(self.winnr, "rnu", true) + end end function View:close() - vim.api.nvim_win_close(self.winnr, true) - self.winnr = nil - self.bufnr = nil + vim.api.nvim_win_close(self.winnr, true) + self.winnr = nil + self.bufnr = nil + self.data_provider = nil + self._flatten_tree = nil + self.code_buf = nil + self.code_win = nil +end + +function View:open() + self.code_buf = vim.api.nvim_get_current_buf() + self.code_win = vim.api.nvim_get_current_win() + self:setup_view() + mappings.init_mappings(self) + local uri = vim.uri_from_fname(jdtls.root_dir()) + self.data_provider = provider.DataProvider:new(uri) +end + +function View:foldToggle() + local item = self:cursorNode() + if item then + item:foldToggle() + self:refresh() + if item:canOpen() then + self:open_file(item) + end + end +end + +---@return TreeItem? +function View:cursorNode() + local c = vim.api.nvim_win_get_cursor(self.winnr) + if self._flatten_tree and self._flatten_tree[c[1]] then + local item = self._flatten_tree[c[1]] + self._cursor_item = item + end + return self._cursor_item +end + +function View:revealCursor() + local idx, item = self.data_provider:findRevealNode(self._flatten_tree) + if idx and item then + vim.api.nvim_win_set_cursor(self.winnr, { idx, 0 }) + end +end + +function View:flattenTree() + self._flatten_tree = self.data_provider:flattenTree() +end + +function View:_write() + if self._flatten_tree then + writer.parse_and_write(self.bufnr, self._flatten_tree) + end +end +---refresh the outline window +function View:refresh() + self:flattenTree() + self:_write() +end + +function View:revealPaths() + writer.write_outline(self.bufnr, { "Loading..." }) + self.data_provider:revealPaths(jdtls.resolvePath(vim.uri_from_bufnr(self.code_buf))) + self:refresh() + self:revealCursor() end function View:is_open() - return self.winnr and self.bufnr and vim.api.nvim_buf_is_valid(self.bufnr) and vim.api.nvim_win_is_valid(self.winnr) + return self.winnr and self.bufnr and vim.api.nvim_buf_is_valid(self.bufnr) and vim.api.nvim_win_is_valid(self.winnr) +end + +---@param node TreeItem? +function View:open_file(node) + node = node or self:cursorNode() + if not node then + return + end + -- open_file + local fname = node.resourceUri + if not fname or not node:canOpen() then + return + end + if vim.startswith(fname, "file:/") or vim.startswith(fname, "jdt:/") then + vim.fn.win_gotoid(self.code_win) + local bufnr = vim.uri_to_bufnr(fname) + vim.bo[bufnr].buflisted = true + vim.api.nvim_win_set_buf(self.code_win, bufnr) + if config.options.auto_close then + self:close() + end + end end return View diff --git a/lua/java-deps/view/node.lua b/lua/java-deps/view/node.lua deleted file mode 100644 index fa75179..0000000 --- a/lua/java-deps/view/node.lua +++ /dev/null @@ -1,19 +0,0 @@ -local M = {} - -DataNode = {} - -function DataNode:new(parent) - local o = parent or {} - setmetatable(o, self) - self.__index = self - return o -end -function DataNode:add_child(child) - if self.childrens == nil then - self.childrens = {} - end - table.insert(self.childrens, child) - child.parent = self -end - -return M diff --git a/lua/java-deps/views/data_node.lua b/lua/java-deps/views/data_node.lua new file mode 100644 index 0000000..232cae0 --- /dev/null +++ b/lua/java-deps/views/data_node.lua @@ -0,0 +1,646 @@ +local node_data = require("java-deps.java.nodeData") +local NodeKind = node_data.NodeKind +local jdtls = require("java-deps.java.jdtls") +local ExplorerNode = require("java-deps.views.explorer_node").ExplorerNode +local hieararchicalPackageNodeData = require("java-deps.java.hieararchicalPackageNodeData") +local icons = require("java-deps.views.icons") + +local M = {} +M.isHierarchicalView = true + +M.K_TYPE_KIND = "TypeKind" +M.NATURE_ID = "NatureId" + +M.NatureId = { + Maven = "org.eclipse.m2e.core.maven2Nature", + Gradle = "org.eclipse.buildship.core.gradleprojectnature", + BspGradle = "com.microsoft.gradle.bs.importer.GradleBuildServerProjectNature", + UnmanagedFolder = "org.eclipse.jdt.ls.core.unmanagedFolder", + Java = "org.eclipse.jdt.core.javanature", +} + +M.ReadableNature = { + Maven = "maven", + Gradle = "gradle", + BspGradle = "bsp-gradle", + UnmanagedFolder = "unmanagedFolder", + Java = "java", +} +M.NatureIdMap = { + [M.NatureId.Maven] = M.ReadableNature.Maven, + [M.NatureId.Gradle] = M.ReadableNature.Gradle, + [M.NatureId.BspGradle] = M.ReadableNature.BspGradle, + [M.NatureId.UnmanagedFolder] = M.ReadableNature.UnmanagedFolder, + [M.NatureId.Java] = M.ReadableNature.Java, +} + +M.ContainerType = { + JRE = "jre", + Maven = "maven", + Gradle = "gradle", + ReferencedLibrary = "referencedLibrary", + Unknown = "", +} + +M.ContainerPath = { + JRE = "org.eclipse.jdt.launching.JRE_CONTAINER", + Maven = "org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER", + Gradle = "org.eclipse.buildship.core.gradleclasspathcontainer", + ReferencedLibrary = "REFERENCED_LIBRARIES_PATH", +} + +---@enum TreeItemCollapsibleState +M.TreeItemCollapsibleState = { + None = 0, + Collapsed = 1, + Expanded = 2, +} + +---@param natureId string +---@return string +M.getProjectType = function(natureId) + return M.NatureIdMap[natureId] or "" +end + +---@class DataNode: ExplorerNode +---@field _childrenNodes ExplorerNode[] +---@field _nodeData INodeData +---@field _parent DataNode? +---@field _project DataNode? +---@field _rootNode DataNode? +---@field _hierarchicalPackageNode boolean +---@field _hierarchicalPackageRootNode boolean +---@field _collapsibleState TreeItemCollapsibleState +local DataNode = ExplorerNode:new() + +DataNode.__index = DataNode +DataNode._hierarchicalPackageNode = false +DataNode._hierarchicalPackageRootNode = false + +---@param nodeData INodeData +---@param parent DataNode? +---@param project DataNode? +---@param rootNode DataNode? +---@return DataNode +function DataNode:new(nodeData, parent, project, rootNode) + local data = setmetatable({}, self) + data._nodeData = nodeData + data._parent = parent + data._project = project + data._rootNode = rootNode + return data +end + +---@class TreeItem +---@field label? string +---@field id? string +---@field icon? string +---@field depth? number +---@field description? string +---@field resourceUri? string +---@field command? string +---@field isLast? boolean +---@field data? DataNode +---@field hierarchy? table +local TreeItem = {} + +TreeItem.__index = TreeItem + +function TreeItem:new() + return setmetatable({}, self) +end + +---@return DataNode[] +function DataNode:createHierarchicalPackageRootNode() + local result = {} + local packageData = {} + if self._nodeData.children then + for _, child in ipairs(self._nodeData.children) do + if child:getKind() == NodeKind.Package then + table.insert(packageData, child) + else + table.insert(result, M.createNode(child, self, self._project, self)) + end + end + if #packageData > 0 then + local data = hieararchicalPackageNodeData.createHierarchicalNodeDataByPackageList(packageData) + if data and data.children then + for _, child in ipairs(data.children) do + local node = M.createNode(child, self, self._project, self) + table.insert(result, node) + end + end + end + end + return result +end + +---@return DataNode[] +function DataNode:createHierarchicalPackageNode() + local result = {} + if self._nodeData.children then + for _, child in ipairs(self._nodeData.children) do + table.insert(result, M.createNode(child, self, self._project, self._rootNode)) + end + end + return result +end + +function DataNode:createChildNodeList() + local kind = self:kind() + if kind == NodeKind.Workspace then + local result = {} + if self._nodeData.children then + for _, child in ipairs(self._nodeData.children) do + table.insert(result, M.createNode(child, self, nil, nil)) + end + end + return result + elseif kind == NodeKind.Project then + local result = {} + local packageData = {} + if self._nodeData.children then + for _, child in ipairs(self._nodeData.children) do + if child:getKind() == NodeKind.Package then + table.insert(packageData, child) + else + table.insert(result, M.createNode(child, self, self, nil)) + end + end + + if #packageData > 0 then + if M.isHierarchicalView then + local data = hieararchicalPackageNodeData.createHierarchicalNodeDataByPackageList(packageData) + if data and data.children then + for _, child in ipairs(data.children) do + table.insert(result, M.createNode(child, self, self, self)) + end + end + else + for _, child in ipairs(packageData) do + table.insert(result, M.createNode(child, self, self, self)) + end + end + end + end + return result + elseif kind == NodeKind.Container then + local result = {} + if self._nodeData.children then + for _, child in ipairs(self._nodeData.children) do + table.insert(result, M.createNode(child, self, self._project, nil)) + end + end + return result + elseif kind == NodeKind.PackageRoot then + if M.isHierarchicalView then + return self:createHierarchicalPackageRootNode() + else + local result = {} + if self._nodeData.children then + for _, child in ipairs(self._nodeData.children) do + table.insert(result, M.createNode(child, self, self._project, self)) + end + end + return result + end + elseif kind == NodeKind.Package then + if M.isHierarchicalView then + return self:createHierarchicalPackageNode() + else + local result = {} + if self._nodeData.children then + for _, child in ipairs(self._nodeData.children) do + table.insert(result, M.createNode(child, self, self._project, self._rootNode)) + end + end + return result + end + elseif kind == NodeKind.Folder then + local result = {} + if self._nodeData.children then + for _, child in ipairs(self._nodeData.children) do + table.insert(result, M.createNode(child, self, self._project, self._rootNode)) + end + end + return result + elseif kind == NodeKind.PrimaryType then + return nil + else + return nil + end +end +function DataNode:loadData() + local kind = self:kind() + if kind == NodeKind.Workspace then + return jdtls.getProjects(self._nodeData:getUri()) + elseif kind == NodeKind.Project then + return jdtls.getPackageData({ + kind = NodeKind.Project, + projectUri = self._nodeData:getUri(), + }) + elseif kind == NodeKind.Container then + return jdtls.getPackageData({ + kind = NodeKind.Container, + projectUri = self._project._nodeData:getUri(), + path = self._nodeData:getPath(), + }) + elseif kind == NodeKind.PackageRoot then + return jdtls.getPackageData({ + kind = NodeKind.PackageRoot, + projectUri = self._project._nodeData:getUri(), + rootPath = self._nodeData:getPath(), + handlerIdentifier = self._nodeData:getHandlerIdentifier(), + isHierarchicalView = M.isHierarchicalView, + }) + elseif kind == NodeKind.Package then + return jdtls.getPackageData({ + kind = NodeKind.Package, + projectUri = self._project._nodeData:getUri(), + path = self._nodeData:getName(), + handlerIdentifier = self._nodeData:getHandlerIdentifier(), + }) + elseif kind == NodeKind.Folder then + return jdtls.getPackageData({ + kind = NodeKind.Folder, + projectUri = self._project._nodeData:getUri(), + path = self._nodeData:getPath(), + rootPath = self._rootNode and self._rootNode._nodeData:getPath() or nil, + handlerIdentifier = self._rootNode and self._rootNode._nodeData:getHandlerIdentifier(), + }) + elseif kind == NodeKind.PrimaryType then + return nil + elseif kind == NodeKind.Folder then + return jdtls.getPackageData({ + kind = NodeKind.Folder, + projectUri = self._project._nodeData:getUri(), + path = self._nodeData:getPath(), + rootPath = self._rootNode and self._rootNode._nodeData:getPath() or nil, + handlerIdentifier = self._rootNode and self._rootNode._nodeData:getHandlerIdentifier() or nil, + }) + else + return nil + end +end + +function DataNode:icon() + return icons.get_icon(self) +end +function DataNode:kind() + return self._nodeData:getKind() +end +function DataNode:typeKind() + return self._nodeData:getMetaData() and self._nodeData:getMetaData()[M.K_TYPE_KIND] or nil +end + +function DataNode:sort() + table.sort(self._childrenNodes, function(a, b) + ---@diagnostic disable: undefined-field + if a._nodeData:getKind() and b._nodeData:getKind() and a._nodeData:getName() and b._nodeData:getName() then + if a._nodeData:getKind() == b._nodeData:getKind() then + return a._nodeData:getName() < b._nodeData:getName() + else + return a._nodeData:getKind() < b._nodeData:getKind() + end + end + return false + end) +end + +---@param paths INodeData[] +function DataNode:baseRevealPaths(paths) + if #paths == 0 then + return self + end + ---@type DataNode[] + local childNodeData = table.remove(paths, 1) + ---@type DataNode[] + local children = self:getChildren() + ---@type DataNode[]? + local childNode = vim.tbl_filter(function(child) + return childNodeData:getName() == child._nodeData:getName() and childNodeData:getPath() == child._nodeData:getPath() + end, children) + childNode = (childNode and #childNode > 0) and childNode[1] or nil + return (childNode and #paths > 0) and childNode:revealPaths(paths) or childNode +end + +---@param uri string +---@return boolean +local function is_workspace_file(uri) + local rootPath = jdtls.root_dir() + if vim.startswith(uri, "file:/") then + local path = vim.uri_to_fname(uri) + return path == rootPath or vim.startswith(path, rootPath) + end + return false +end + +---@param paths INodeData[] +function DataNode:revealPaths(paths) + if #paths == 0 then + return self + end + self._collapsibleState = M.TreeItemCollapsibleState.Expanded + local kind = self:kind() + if kind == NodeKind.Project then + if not self._nodeData:getUri() then + return + end + + if is_workspace_file(self._nodeData:getUri()) then + return self:baseRevealPaths(paths) + end + + local childNodeData = paths[1] + ---@type DataNode[] + local children = self:getChildren() + ---@type DataNode[]? + local childNode = vim.tbl_filter(function(child) + return vim.startswith(childNodeData:getName(), child._nodeData:getName() .. ".") + or childNodeData:getName() == child._nodeData:getName() + end, children) + ---@type DataNode? + childNode = (childNode and #childNode > 0) and childNode[1] or nil + if childNode and childNode._hierarchicalPackageNode then + table.remove(paths, 1) + end + return (childNode and #paths > 0) and childNode:revealPaths(paths) or childNode + elseif kind == NodeKind.PackageRoot then + if self._hierarchicalPackageRootNode then + local hierarchicalNodeData = paths[1] + + ---@type DataNode[] + local children = self:getChildren() + ---@type DataNode[]? + local childNode = vim.tbl_filter(function(child) + return vim.startswith(hierarchicalNodeData:getName(), child._nodeData:getName() .. ".") + or hierarchicalNodeData:getName() == child._nodeData:getName() + end, children) + ---@type DataNode? + childNode = (childNode and #childNode > 0) and childNode[1] or nil + if childNode and not childNode._hierarchicalPackageNode then + table.remove(paths, 1) + end + return (childNode and #paths > 0) and childNode:revealPaths(paths) or childNode + else + return self:baseRevealPaths(paths) + end + elseif kind == NodeKind.Package and self._hierarchicalPackageNode then + local hierarchicalNodeData = paths[1] + if hierarchicalNodeData:getName() == self._nodeData:getName() then + table.remove(paths, 1) + return self:baseRevealPaths(paths) + else + ---@type DataNode[] + local children = self:getChildren() + ---@type DataNode[]? + local childNode = vim.tbl_filter(function(child) + return vim.startswith(hierarchicalNodeData:getName(), child._nodeData:getName() .. ".") + or hierarchicalNodeData:getName() == child._nodeData:getName() + end, children) + ---@type DataNode? + childNode = (childNode and #childNode > 0) and childNode[1] or nil + return (childNode and #paths > 0) and childNode:revealPaths(paths) or nil + end + else + return self:baseRevealPaths(paths) + end +end + +local function uniqBy(arr, fn) + local seen = {} + local result = {} + for _, value in ipairs(arr) do + local key = fn(value) + if not seen[key] then + seen[key] = true + table.insert(result, value) + end + end + return result +end +---@return ExplorerNode[] +function DataNode:getChildren() + if self:kind() == NodeKind.Package then + local data = self:loadData() + if data then + if self._nodeData.children then + for _, child in ipairs(data) do + table.insert(self._nodeData.children, child) + end + self._nodeData.children = uniqBy(self._nodeData.children, function(child) + return child:getPath() .. child:getName() + end) + else + self._nodeData.children = data + end + self._childrenNodes = self:createChildNodeList() or {} + self:sort() + end + return self._childrenNodes + else + if not self._nodeData.children then + local data = self:loadData() + self._nodeData.children = data + self._childrenNodes = self:createChildNodeList() or {} + self:sort() + end + return self._childrenNodes + end +end + +M.DataNode = DataNode + +---@param nodeData INodeData +---@param parent DataNode? +---@param project DataNode? +---@param rootNode DataNode? +M.createNode = function(nodeData, parent, project, rootNode) + local kind = nodeData:getKind() + if kind == NodeKind.Workspace then + return DataNode:new(nodeData, parent, project, rootNode) + elseif kind == NodeKind.Project then + return DataNode:new(nodeData, parent, project, rootNode) + elseif kind == NodeKind.Container then + if not parent or not project then + vim.notify("Container node must have parent and project", vim.log.levels.ERROR) + return nil + end + return DataNode:new(nodeData, parent, project, rootNode) + elseif kind == NodeKind.PackageRoot then + if not parent or not project then + vim.notify("Package root node must have parent and project", vim.log.levels.ERROR) + return nil + end + local data = DataNode:new(nodeData, parent, project, rootNode) + if M.isHierarchicalView then + data._hierarchicalPackageRootNode = true + end + return data + elseif kind == NodeKind.Package then + if not parent or not project or not rootNode then + vim.notify("Package node must have parent, project and root node", vim.log.levels.ERROR) + return nil + end + local data = DataNode:new(nodeData, parent, project, rootNode) + if M.isHierarchicalView then + data._hierarchicalPackageNode = true + end + return data + elseif kind == NodeKind.PrimaryType then + if nodeData:getMetaData() and nodeData:getMetaData()[M.K_TYPE_KIND] then + if not parent then + vim.notify("Primary type node must have parent", vim.log.levels.ERROR) + return nil + end + return DataNode:new(nodeData, parent, project, rootNode) + end + elseif kind == NodeKind.Folder then + if not parent or not project then + vim.notify("Folder node must have parent and project.", vim.log.levels.ERROR) + return nil + end + return DataNode:new(nodeData, parent, project, rootNode) + elseif kind == NodeKind.CompilationUnit or kind == NodeKind.ClassFile or kind == NodeKind.File then + if not parent then + vim.notify("File node must have parent", vim.log.levels.ERROR) + return nil + end + return DataNode:new(nodeData, parent, project, rootNode) + end +end + +function DataNode:isUnmanagedFolder() + local natureIds = self._nodeData:getMetaData() and self._nodeData:getMetaData()[M.NATURE_ID] or {} + for _, natureId in ipairs(natureIds) do + if natureId == M.NatureId.UnmanagedFolder then + return true + end + end + return false +end + +function DataNode:getContainerType() + local containerPath = self._nodeData:getPath() or "" + if containerPath.startsWith(M.ContainerPath.JRE) then + return M.ContainerType.JRE + elseif containerPath.startsWith(M.ContainerPath.Maven) then + return M.ContainerType.Maven + elseif containerPath.startsWith(M.ContainerPath.Gradle) then + return M.ContainerType.Gradle + elseif containerPath.startsWith(M.ContainerPath.ReferencedLibrary) and self._project:isUnmanagedFolder() then + return M.ContainerType.ReferencedLibrary + end + return M.ContainerType.Unknown +end + +function DataNode:description() + --TODO +end +function DataNode:command() + --TODO +end + +---@return boolean +function DataNode:hasChildren() + local kind = self:kind() + if + kind == NodeKind.CompilationUnit + or kind == NodeKind.ClassFile + or kind == NodeKind.File + or kind == NodeKind.PrimaryType + then + return false + end + return true +end + +---@return TreeItemCollapsibleState +function DataNode:collapsibleState() + if not self._collapsibleState then + if self:hasChildren() then + self._collapsibleState = M.TreeItemCollapsibleState.Collapsed + end + end + return self._collapsibleState +end + +function TreeItem:collapsibleState() + return self.data:collapsibleState() +end + +---@return boolean +function TreeItem:is_foldable() + return self:collapsibleState() == M.TreeItemCollapsibleState.Collapsed + or self:collapsibleState() == M.TreeItemCollapsibleState.Expanded +end +---@return boolean +function TreeItem:is_expanded() + return self:collapsibleState() == M.TreeItemCollapsibleState.Expanded +end +---@return boolean +function TreeItem:is_collapsed() + return self:collapsibleState() == M.TreeItemCollapsibleState.Collapsed +end + +function TreeItem:expanded() + if not self:is_foldable() then + return + end + if self:is_expanded() then + return + end + self.data._collapsibleState = M.TreeItemCollapsibleState.Expanded + self.data:getChildren() +end + +function TreeItem:collapsed() + if not self:is_foldable() then + return + end + if self:is_collapsed() then + return + end + self.data._collapsibleState = M.TreeItemCollapsibleState.Collapsed +end + +function TreeItem:foldToggle() + if self:is_expanded() then + self:collapsed() + else + self:expanded() + end +end + +---是否是文件可以打开 +function TreeItem:canOpen() + local kind = self.data:kind() + return kind == NodeKind.PrimaryType or kind == NodeKind.ClassFile or kind == NodeKind.File +end + +function DataNode:getTreeItem() + local item = TreeItem:new() + item.label = self._nodeData:getDisplayName() or self._nodeData:getName() + item.description = self:description() + -- item.icon = self:icon() + item.command = self:command() + item.data = self + if self._nodeData:getUri() then + local kind = self:kind() + if + kind == NodeKind.Project + or kind == NodeKind.PackageRoot + or kind == NodeKind.Package + or kind == NodeKind.PrimaryType + or kind == NodeKind.CompilationUnit + or kind == NodeKind.ClassFile + or kind == NodeKind.Folder + or kind == NodeKind.File + then + item.resourceUri = self._nodeData:getUri() + end + end + return item +end + +return M diff --git a/lua/java-deps/views/data_provider.lua b/lua/java-deps/views/data_provider.lua new file mode 100644 index 0000000..7a6b595 --- /dev/null +++ b/lua/java-deps/views/data_provider.lua @@ -0,0 +1,102 @@ +local jdtls = require("java-deps.java.jdtls") +local data_node = require("java-deps.views.data_node") +local M = {} + +---@class DataProvider +---@field rootPath string +---@field revealNode INodeData? +---@field _rootProjects DataNode[]? +local DataProvider = {} +DataProvider.__index = DataProvider + +function DataProvider:new(rootPath) + return setmetatable({ + rootPath = rootPath, + _rootProjects = {}, + }, self) +end + +---@return DataNode[] +function DataProvider:getRootProjects() + if self._rootProjects and #self._rootProjects > 0 then + return self._rootProjects + end + + local rootProjects = {} + for _, project in ipairs(jdtls.getProjects(self.rootPath)) do + if project then + local root = data_node.createNode(project) + if root then + table.insert(rootProjects, root) + end + end + end + self._rootProjects = rootProjects + return rootProjects +end + +function DataProvider:revealPaths(paths) + ---@type INodeData[] + local rpath = paths or {} + self.revealNode = rpath and #rpath > 0 and rpath[#rpath] or nil + ---@type INodeData + local cpath = (rpath and #rpath > 0) and table.remove(rpath, 1) or nil + + local projects = self:getRootProjects() + for _, root in ipairs(projects) do + if cpath and cpath:getName() == root._nodeData:getName() and cpath:getPath() == root._nodeData:getPath() then + root:revealPaths(rpath) + break + end + end +end + +---@param nodes DataNode[] +local function _flattenTree(result, nodes, level, hierarchy) + if not nodes or #nodes == 0 then + return + end + for idx, node in ipairs(nodes) do + local c = node:getTreeItem() + c.hierarchy = vim.deepcopy(hierarchy) + c.depth = level + if idx == #nodes then + -- 如果是最后一个节点, 子节点不需要再画竖线 + c.hierarchy[level] = true + c.isLast = true + end + table.insert(result, c) + if node._childrenNodes and #node._childrenNodes > 0 and c:is_expanded() then + _flattenTree(result, node._childrenNodes, level + 1, c.hierarchy) + end + end +end + +---@return TreeItem[] +function DataProvider:flattenTree() + ---@type TreeItem[] + local result = {} + _flattenTree(result, self:getRootProjects(), 0, {}) + return result +end + +---获取当前节点位置 +---@param treeItems TreeItem[] +function DataProvider:findRevealNode(treeItems) + if not treeItems or #treeItems == 0 then + return + end + for idx, item in ipairs(treeItems) do + if + item.data + and item.data._nodeData:getName() == self.revealNode:getName() + and item.data._nodeData:getPath() == self.revealNode:getPath() + then + return idx, item + end + end +end + +M.DataProvider = DataProvider + +return M diff --git a/lua/java-deps/views/explorer_node.lua b/lua/java-deps/views/explorer_node.lua new file mode 100644 index 0000000..cf1d272 --- /dev/null +++ b/lua/java-deps/views/explorer_node.lua @@ -0,0 +1,28 @@ +---@class ExplorerNode +---@field _parent? ExplorerNode + +local M = {} + +local ExplorerNode = {} +ExplorerNode.__index = ExplorerNode + +function ExplorerNode:new() + return setmetatable({}, self) +end + +---@param node ExplorerNode +---@param levelToCheck number +function ExplorerNode:isItselfOrAncestorOf(node, levelToCheck) + levelToCheck = levelToCheck or math.huge + while node and levelToCheck >= 0 do + if self == node then + return true + end + node = node._parent + levelToCheck = levelToCheck - 1 + end + return false +end + +M.ExplorerNode = ExplorerNode +return M diff --git a/lua/java-deps/views/icons.lua b/lua/java-deps/views/icons.lua new file mode 100644 index 0000000..7376675 --- /dev/null +++ b/lua/java-deps/views/icons.lua @@ -0,0 +1,45 @@ +local node_data = require("java-deps.java.nodeData") +local PackageRootKind = require("java-deps.java.IPackageRootNodeData").PackageRootKind +local NodeKind = node_data.NodeKind +local TypeKind = node_data.TypeKind + +---@class Icon +---@field icon string +---@field hl string? + +local M = { + NodeKind = { + [NodeKind.Workspace] = { icon = "", hl = "Type" }, + [NodeKind.Project] = { icon = "" }, + [NodeKind.PackageRoot] = { icon = "" }, + [NodeKind.Package] = { icon = "" }, + [NodeKind.PrimaryType] = { icon = "󰠱" }, + [NodeKind.CompilationUnit] = { icon = "" }, + [NodeKind.ClassFile] = { icon = "" }, + [NodeKind.Container] = { icon = "" }, + [NodeKind.Folder] = { icon = "" }, + [NodeKind.File] = { icon = "󰈙" }, + }, + TypeKind = { + [TypeKind.Class] = { icon = "󰠱" }, + [TypeKind.Interface] = { icon = "" }, + [TypeKind.Enum] = { icon = "" }, + }, + EntryKind = { + [PackageRootKind.K_SOURCE] = { icon = "" }, + [PackageRootKind.K_BINARY] = { icon = "" }, + }, +} + +---@param node DataNode +---@return Icon +M.get_icon = function(node) + local kind = node:kind() + if kind == node_data.NodeKind.PrimaryType then + return M.TypeKind[node:typeKind()] + else + return M.NodeKind[kind] + end +end + +return M diff --git a/lua/java-deps/writer.lua b/lua/java-deps/writer.lua index f65a87f..ce12ec7 100644 --- a/lua/java-deps/writer.lua +++ b/lua/java-deps/writer.lua @@ -1,98 +1,31 @@ -local parser = require 'java-deps.parser' -local config = require 'java-deps.config' -local ui = require 'java-deps.ui' +local parser = require("java-deps.parser") +local highlight = require("java-deps.highlight") local M = {} local function is_buffer_outline(bufnr) local isValid = vim.api.nvim_buf_is_valid(bufnr) local name = vim.api.nvim_buf_get_name(bufnr) - local ft = vim.api.nvim_buf_get_option(bufnr, 'filetype') - return string.match(name, 'JavaProjects') ~= nil and ft == 'JavaProjects' and isValid + local ft = vim.api.nvim_buf_get_option(bufnr, "filetype") + return string.match(name, "JavaProjects") ~= nil and ft == "JavaProjects" and isValid end -local hlns = vim.api.nvim_create_namespace 'java-deps-outline-icon-highlight' - function M.write_outline(bufnr, lines) if not is_buffer_outline(bufnr) then return end - vim.api.nvim_buf_set_option(bufnr, 'modifiable', true) + 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.add_highlights(bufnr, hl_info, nodes) - for _, line_hl in ipairs(hl_info) do - local line, hl_start, hl_end, hl_type = unpack(line_hl) - vim.api.nvim_buf_add_highlight( - bufnr, - hlns, - hl_type, - line - 1, - hl_start, - hl_end - ) - end - - M.add_hover_highlights(bufnr, nodes) -end - -local ns = vim.api.nvim_create_namespace 'java-deps-outline-virt-text' - -function M.write_details(bufnr, lines) - if not is_buffer_outline(bufnr) then - return - end - - for index, value in ipairs(lines) do - vim.api.nvim_buf_set_extmark(bufnr, ns, index - 1, -1, { - virt_text = { { value, 'Comment' } }, - virt_text_pos = 'eol', - hl_mode = 'combine', - }) - end -end - -local function clear_virt_text(bufnr) - vim.api.nvim_buf_clear_namespace(bufnr, -1, 0, -1) -end - -M.add_hover_highlights = function(bufnr, nodes) - if not config.options.highlight_hovered_item then - return - end - - -- clear old highlight - ui.clear_hover_highlight(bufnr) - for _, node in ipairs(nodes) do - if not node.hovered then - goto continue - end - - if node.prefix_length then - ui.add_hover_highlight( - bufnr, - node.line_in_outline - 1, - node.prefix_length - ) - end - ::continue:: - end + vim.api.nvim_buf_set_option(bufnr, "modifiable", false) end --- runs the whole writing routine where the text is cleared, new data is parsed --- and then written +---@param bufnr integer +---@param flattened_outline_items TreeItem function M.parse_and_write(bufnr, flattened_outline_items) local lines, hl_info = parser.get_lines(flattened_outline_items) + highlight.clear_all_ns(bufnr) M.write_outline(bufnr, lines) - - clear_virt_text(bufnr) - M.add_highlights(bufnr, hl_info, flattened_outline_items) - if config.options.show_path_details then - local details = parser.get_details(flattened_outline_items) - M.write_details(bufnr, details) - end + highlight.add_item_highlights(bufnr, hl_info, flattened_outline_items) end return M