diff --git a/configuration.nix b/configuration.nix index 515b715c..5b7c5787 100644 --- a/configuration.nix +++ b/configuration.nix @@ -54,7 +54,10 @@ inputs: let nix.enable = true; html.enable = isMaximal; - clang.enable = isMaximal; + clang = { + enable = isMaximal; + lsp.server = "clangd"; + }; sql.enable = isMaximal; rust = { enable = isMaximal; @@ -74,7 +77,7 @@ inputs: let nvimWebDevicons.enable = true; scrollBar.enable = true; smoothScroll.enable = true; - cellularAutomaton.enable = true; + cellularAutomaton.enable = isMaximal; fidget-nvim.enable = true; indentBlankline = { enable = true; @@ -155,7 +158,7 @@ inputs: let }; vim.projects = { - project-nvim.enable = true; + project-nvim.enable = isMaximal; }; vim.utility = { @@ -173,7 +176,7 @@ inputs: let vim.notes = { obsidian.enable = false; # FIXME neovim fails to build if obsidian is enabled orgmode.enable = false; - mind-nvim.enable = true; + mind-nvim.enable = isMaximal; todo-comments.enable = true; }; @@ -190,6 +193,10 @@ inputs: let colorizer.enable = true; modes-nvim.enable = false; # the theme looks terrible with catppuccin illuminate.enable = true; + breadcrumbs = { + enable = isMaximal; + navbuddy.enable = isMaximal; + }; smartcolumn = { enable = true; columnAt.languages = { diff --git a/docs/release-notes/rl-0.5.adoc b/docs/release-notes/rl-0.5.adoc index cde1d4b8..3da3c739 100644 --- a/docs/release-notes/rl-0.5.adoc +++ b/docs/release-notes/rl-0.5.adoc @@ -12,7 +12,7 @@ https://github.com/horriblename[horriblename]: * Fixed a bug where cmp's close and scrollDocs mappings wasn't working. -* Streamlined and simplified extra plugin API with the addition of <>. +* Streamlined and simplified extra plugin API with the addition of <> https://github.com/amanse[amanse]: @@ -23,3 +23,7 @@ https://github.com/notashelf[notashelf]: * Added GitHub Copilot to completion sources. * Added <> for global and individual plugin border configuration. + +* LSP integrated breadcrumbs with <> through nvim-navic + +* LSP navigation helper with nvim-navbuddy, depends on nvim-navic (automatically enabled) diff --git a/flake.lock b/flake.lock index 14f41693..c3b6a384 100644 --- a/flake.lock +++ b/flake.lock @@ -1092,6 +1092,38 @@ "type": "github" } }, + "nvim-navbuddy": { + "flake": false, + "locked": { + "lastModified": 1688569844, + "narHash": "sha256-011RT/wnQdBR1vMrXFwxbicBAgdcd4eQYPbok/o3CIE=", + "owner": "SmiteshP", + "repo": "nvim-navbuddy", + "rev": "244a4cded6f2b568403684131d148048efe4e8af", + "type": "github" + }, + "original": { + "owner": "SmiteshP", + "repo": "nvim-navbuddy", + "type": "github" + } + }, + "nvim-navic": { + "flake": false, + "locked": { + "lastModified": 1689447657, + "narHash": "sha256-fcSer6l6oX0qnOcRfNSZImmtkqjdH0WSUkptjaFj0AI=", + "owner": "SmiteshP", + "repo": "nvim-navic", + "rev": "e6da6f74d89de65258ea7e98e22103ff5de6dcf5", + "type": "github" + }, + "original": { + "owner": "SmiteshP", + "repo": "nvim-navic", + "type": "github" + } + }, "nvim-neoclip": { "flake": false, "locked": { @@ -1413,6 +1445,8 @@ "nvim-dap-ui": "nvim-dap-ui", "nvim-lightbulb": "nvim-lightbulb", "nvim-lspconfig": "nvim-lspconfig", + "nvim-navbuddy": "nvim-navbuddy", + "nvim-navic": "nvim-navic", "nvim-neoclip": "nvim-neoclip", "nvim-notify": "nvim-notify", "nvim-session-manager": "nvim-session-manager", diff --git a/flake.nix b/flake.nix index 85d4bfee..4de26355 100644 --- a/flake.nix +++ b/flake.nix @@ -143,6 +143,7 @@ url = "github:tversteeg/registers.nvim"; flake = false; }; + nvim-neoclip = { url = "github:AckslD/nvim-neoclip.lua"; flake = false; @@ -248,6 +249,7 @@ url = "github:b3nj5m1n/kommentary"; flake = false; }; + comment-nvim = { url = "github:numToStr/Comment.nvim"; flake = false; @@ -455,6 +457,16 @@ }; # UI + nvim-navbuddy = { + url = "github:SmiteshP/nvim-navbuddy"; + flake = false; + }; + + nvim-navic = { + url = "github:SmiteshP/nvim-navic"; + flake = false; + }; + noice-nvim = { url = "github:folke/noice.nvim"; flake = false; diff --git a/lib/types/plugins.nix b/lib/types/plugins.nix index 47b9ab2e..ccbde555 100644 --- a/lib/types/plugins.nix +++ b/lib/types/plugins.nix @@ -87,6 +87,8 @@ with lib; let "nvim-surround" "nvim-dap" "nvim-dap-ui" + "nvim-navic" + "nvim-navbuddy" "copilot-cmp" ]; # You can either use the name of the plugin or a package. diff --git a/modules/lsp/config.nix b/modules/lsp/config.nix index 10851187..83da30b2 100644 --- a/modules/lsp/config.nix +++ b/modules/lsp/config.nix @@ -81,9 +81,16 @@ in { end end + ${optionalString (config.vim.ui.breadcrumbs.enable) ''local navic = require("nvim-navic")''} default_on_attach = function(client, bufnr) attach_keymaps(client, bufnr) format_callback(client, bufnr) + ${optionalString (config.vim.ui.breadcrumbs.enable) '' + -- let navic attach to buffers + if client.server_capabilities.documentSymbolProvider then + navic.attach(client, bufnr) + end + ''} end local capabilities = vim.lsp.protocol.make_client_capabilities() diff --git a/modules/statusline/lualine/config.nix b/modules/statusline/lualine/config.nix index 385e139d..cff8596c 100644 --- a/modules/statusline/lualine/config.nix +++ b/modules/statusline/lualine/config.nix @@ -49,6 +49,17 @@ in { lualine_z = ${cfg.inactiveSection.z}, }, tabline = {}, + + ${optionalString (config.vim.ui.breadcrumbs.source == "nvim-navic") '' + winbar = { + lualine_c = { + { + "navic", + draw_empty = ${toString config.vim.ui.breadcrumbs.alwaysRender} + } + } + }, + ''} extensions = {${ if (config.vim.filetree.nvimTreeLua.enable) then "\"nvim-tree\"" diff --git a/modules/ui/breadcrumbs/breadcrumbs.nix b/modules/ui/breadcrumbs/breadcrumbs.nix new file mode 100644 index 00000000..d54328ff --- /dev/null +++ b/modules/ui/breadcrumbs/breadcrumbs.nix @@ -0,0 +1,478 @@ +{ + lib, + config, + ... +}: let + inherit (lib) mkEnableOption mkOption types; +in { + options.vim.ui.breadcrumbs = { + enable = lib.mkEnableOption "breadcrumbs"; + source = mkOption { + type = with types; nullOr (enum ["nvim-navic"]); # TODO: lspsaga and dropbar + default = "nvim-navic"; + description = '' + The source to be used for breadcrumbs component. Null means no breadcrumbs. + ''; + }; + + # maybe this should be an option to *disable* alwaysRender optionally but oh well + # too late + alwaysRender = mkOption { + type = types.bool; + default = true; + description = "Whether to always display the breadcrumbs component on winbar (always renders winbar)"; + }; + + navbuddy = { + enable = mkEnableOption "navbuddy LSP helper UI. Enabling this option automatically loads and enables nvim-navic"; + + # this option is interpreted as null if mkEnableOption is used, and therefore cannot be converted to a string in config.nix + useDefaultMappings = mkOption { + type = types.bool; + default = true; + description = "use default Navbuddy keybindings (disables user-specified keybinds)"; + }; + + mappings = { + close = mkOption { + type = types.str; + default = ""; + description = "keybinding to close Navbuddy UI"; + }; + + nextSibling = mkOption { + type = types.str; + default = "j"; + description = "keybinding to navigate to the next sibling node"; + }; + + previousSibling = mkOption { + type = types.str; + default = "k"; + description = "keybinding to navigate to the previous sibling node"; + }; + + parent = mkOption { + type = types.str; + default = "h"; + description = "keybinding to navigate to the parent node"; + }; + + children = mkOption { + type = types.str; + default = "h"; + description = "keybinding to navigate to the child node"; + }; + + root = mkOption { + type = types.str; + default = "0"; + description = "keybinding to navigate to the root node"; + }; + + visualName = mkOption { + type = types.str; + default = "v"; + description = "visual selection of name"; + }; + + visualScope = mkOption { + type = types.str; + default = "V"; + description = "visual selection of scope"; + }; + + yankName = mkOption { + type = types.str; + default = "y"; + description = "yank the name to system clipboard"; + }; + + yankScope = mkOption { + type = types.str; + default = "Y"; + description = "yank the scope to system clipboard"; + }; + + insertName = mkOption { + type = types.str; + default = "i"; + description = "insert at start of name"; + }; + + insertScope = mkOption { + type = types.str; + default = "I"; + description = "insert at start of scope"; + }; + + appendName = mkOption { + type = types.str; + default = "a"; + description = "insert at end of name"; + }; + + appendScope = mkOption { + type = types.str; + default = "A"; + description = "insert at end of scope"; + }; + + rename = mkOption { + type = types.str; + default = "r"; + description = "rename the node"; + }; + + delete = mkOption { + type = types.str; + default = "d"; + description = "delete the node"; + }; + + foldCreate = mkOption { + type = types.str; + default = "f"; + description = "create a new fold"; + }; + + foldDelete = mkOption { + type = types.str; + default = "F"; + description = "delete the current fold"; + }; + + comment = mkOption { + type = types.str; + default = "c"; + description = "comment the node"; + }; + + select = mkOption { + type = types.str; + default = ""; + description = "goto selected symbol"; + }; + + moveDown = mkOption { + type = types.str; + default = "J"; + description = "move focused node down"; + }; + + moveUp = mkOption { + type = types.str; + default = "K"; + description = "move focused node up"; + }; + + telescope = mkOption { + type = types.str; + default = "t"; + description = "fuzzy finder at current level"; + }; + + help = mkOption { + type = types.str; + default = "g?"; + description = "open mapping help window"; + }; + }; + + window = { + # size = {} + # position = {} + + border = mkOption { + # TODO: let this type accept a custom string + type = types.enum ["single" "rounded" "double" "solid" "none"]; + default = config.vim.ui.borders.globalStyle; + description = "border style to use"; + }; + + scrolloff = mkOption { + type = with types; nullOr int; + default = null; + description = "Scrolloff value within navbuddy window"; + }; + + sections = { + # left section + left = { + /* + size = { + type = with types; nullOr (intBetween 0 100); + default = null; + description = "size of the left section of Navbuddy UI in percentage (0-100)"; + }; + */ + + border = mkOption { + # TODO: let this type accept a custom string + type = with types; nullOr (enum ["single" "rounded" "double" "solid" "none"]); + default = config.vim.ui.borders.globalStyle; + description = "border style to use for the left section of Navbuddy UI"; + }; + }; + + # middle section + mid = { + /* + size = { + type = with types; nullOr (intBetween 0 100); + default = null; + description = "size of the left section of Navbuddy UI in percentage (0-100)"; + }; + */ + + border = mkOption { + # TODO: let this type accept a custom string + type = with types; nullOr (enum ["single" "rounded" "double" "solid" "none"]); + default = config.vim.ui.borders.globalStyle; + description = "border style to use for the middle section of Navbuddy UI"; + }; + }; + + # right section + # there is no size option for the right section, it fills the remaining space + right = { + border = mkOption { + # TODO: let this type accept a custom string + type = with types; nullOr (enum ["single" "rounded" "double" "solid" "none"]); + default = config.vim.ui.borders.globalStyle; + description = "border style to use for the right section of Navbuddy UI"; + }; + + preview = mkOption { + type = types.enum ["leaf" "always" "never"]; + default = "leaf"; + description = "display mode of the preview on the right section"; + }; + }; + }; + }; + + nodeMarkers = { + enable = mkEnableOption "node markers"; + icons = { + leaf = mkOption { + type = types.str; + default = " "; + description = ""; + }; + + leafSelected = mkOption { + type = types.str; + default = " → "; + description = ""; + }; + + branch = mkOption { + type = types.str; + default = " "; + description = ""; + }; + }; + }; + + lsp = { + autoAttach = mkOption { + type = types.bool; + default = true; + description = "Whether to attach to LSP server manually"; + }; + + preference = mkOption { + type = with types; nullOr (listOf str); + default = null; + description = "list of lsp server names in order of preference"; + }; + }; + + sourceBuffer = { + followNode = mkOption { + type = types.bool; + default = true; + description = "keep the current node in focus on the source buffer"; + }; + + highlight = mkOption { + type = types.bool; + default = true; + description = "highlight the currently focused node"; + }; + + reorient = mkOption { + type = types.enum ["smart" "top" "mid" "none"]; + default = "smart"; + }; + + scrolloff = mkOption { + type = with types; nullOr int; + default = null; + description = "scrolloff value when navbuddy is open"; + }; + }; + + # there probably is a better way to do this + # alas, I am not a nix wizard + icons = { + file = mkOption { + type = types.str; + default = "󰈙 "; + description = ""; + }; + + module = mkOption { + type = types.str; + default = " "; + description = ""; + }; + + namespace = mkOption { + type = types.str; + default = "󰌗 "; + description = ""; + }; + + package = mkOption { + type = types.str; + default = " "; + description = ""; + }; + + class = mkOption { + type = types.str; + default = "󰌗 "; + description = ""; + }; + + property = mkOption { + type = types.str; + default = " "; + description = ""; + }; + + field = mkOption { + type = types.str; + default = " "; + description = ""; + }; + + constructor = mkOption { + type = types.str; + default = " "; + description = ""; + }; + + enum = mkOption { + type = types.str; + default = "󰕘"; + description = ""; + }; + + interface = mkOption { + type = types.str; + default = "󰕘"; + description = ""; + }; + + function = mkOption { + type = types.str; + default = "󰊕 "; + description = ""; + }; + + variable = mkOption { + type = types.str; + default = "󰆧 "; + description = ""; + }; + + constant = mkOption { + type = types.str; + default = "󰏿 "; + description = ""; + }; + + string = mkOption { + type = types.str; + default = " "; + description = ""; + }; + + number = mkOption { + type = types.str; + default = "󰎠 "; + description = ""; + }; + + boolean = mkOption { + type = types.str; + default = "◩ "; + description = ""; + }; + + array = mkOption { + type = types.str; + default = "󰅪 "; + description = ""; + }; + + object = mkOption { + type = types.str; + default = "󰅩 "; + description = ""; + }; + + method = mkOption { + type = types.str; + default = "󰆧 "; + description = ""; + }; + + key = mkOption { + type = types.str; + default = "󰌋 "; + description = ""; + }; + + null = mkOption { + type = types.str; + default = "󰟢 "; + description = ""; + }; + + enumMember = mkOption { + type = types.str; + default = "󰕘 "; + description = ""; + }; + + struct = mkOption { + type = types.str; + default = "󰌗 "; + description = ""; + }; + + event = mkOption { + type = types.str; + default = " "; + description = ""; + }; + + operator = mkOption { + type = types.str; + default = "󰆕 "; + description = ""; + }; + + typeParameter = mkOption { + type = types.str; + default = "󰊄 "; + description = ""; + }; + }; + }; + }; +} diff --git a/modules/ui/breadcrumbs/config.nix b/modules/ui/breadcrumbs/config.nix new file mode 100644 index 00000000..7bf7dc1f --- /dev/null +++ b/modules/ui/breadcrumbs/config.nix @@ -0,0 +1,159 @@ +{ + config, + lib, + ... +}: +with lib; +with builtins; let + cfg = config.vim.ui.breadcrumbs; + nb = cfg.navbuddy; + + nilOrStr = v: + if v == null + then "nil" + else toString v; +in { + config = mkIf cfg.enable { + vim.startPlugins = + [ + "nvim-lspconfig" + ] + ++ lib.optionals (config.vim.lsp.lspsaga.enable && cfg.source == "lspsaga") [ + "lspsaga" + ] + ++ lib.optionals (cfg.navbuddy.enable || cfg.source == "nvim-navic") [ + "nvim-navbuddy" + "nvim-navic" + ]; + + vim.luaConfigRC.breadcrumbs = nvim.dag.entryAfter ["lspconfig"] '' + local navbuddy = require("nvim-navbuddy") + local navic = require("nvim-navic") + local actions = require("nvim-navbuddy.actions") + + -- TODO: wrap this in an optional string with navbuddy as the enable condition + navbuddy.setup { + window = { + border = "${nb.window.border}", -- "rounded", "double", "solid", "none" + size = "60%", + position = "50%", + scrolloff = ${(nilOrStr nb.window.scrolloff)}, + sections = { + left = { + size = "20%", + border = ${(nilOrStr nb.window.sections.left.border)}, + }, + + mid = { + size = "40%", + border = ${(nilOrStr nb.window.sections.mid.border)}, + }, + + right = { + border = ${(nilOrStr nb.window.sections.right.border)}, + preview = "leaf", + } + }, + }, + node_markers = { + enabled = ${boolToString nb.nodeMarkers.enable}, + icons = { + leaf = "${nb.nodeMarkers.icons.leaf}", + leaf_selected = "${nb.nodeMarkers.icons.leafSelected}", + branch = "${nb.nodeMarkers.icons.branch}", + }, + }, + + lsp = { + auto_attach = ${boolToString nb.lsp.autoAttach}, + -- preference = nil, -- TODO: convert list to lua table if not null + }, + + source_buffer = { + follow_node = ${boolToString nb.sourceBuffer.followNode}, + highlight = ${boolToString nb.sourceBuffer.highlight}, + reorient = "${nb.sourceBuffer.reorient}", + scrolloff = ${nilOrStr nb.sourceBuffer.scrolloff} + }, + + icons = { + File = "${cfg.navbuddy.icons.file}", + Module = "${cfg.navbuddy.icons.module}", + Namespace = "${cfg.navbuddy.icons.namespace}", + Package = "${cfg.navbuddy.icons.package}", + Class = "${cfg.navbuddy.icons.class}", + Method = "${cfg.navbuddy.icons.method}", + Property = "${cfg.navbuddy.icons.property}", + Field = "${cfg.navbuddy.icons.field}", + Constructor = "${cfg.navbuddy.icons.constructor}", + Enum = "${cfg.navbuddy.icons.enum}", + Interface = "${cfg.navbuddy.icons.interface}", + Function = "${cfg.navbuddy.icons.function}", + Variable = "${cfg.navbuddy.icons.variable}", + Constant = "${cfg.navbuddy.icons.constant}", + String = "${cfg.navbuddy.icons.string}", + Number = "${cfg.navbuddy.icons.number}", + Boolean = "${cfg.navbuddy.icons.boolean}", + Array = "${cfg.navbuddy.icons.array}", + Object = "${cfg.navbuddy.icons.object}", + Key = "${cfg.navbuddy.icons.key}", + Null = "${cfg.navbuddy.icons.null}", + EnumMember = "${cfg.navbuddy.icons.enumMember}", + Struct = "${cfg.navbuddy.icons.struct}", + Event = "${cfg.navbuddy.icons.event}", + Operator = "${cfg.navbuddy.icons.operator}", + TypeParameter = "${cfg.navbuddy.icons.typeParameter}" + }, + + -- make those configurable + use_default_mappings = ${toString (cfg.navbuddy.useDefaultMappings)}, + mappings = { + ["${cfg.navbuddy.mappings.close}"] = actions.close(), + ["${cfg.navbuddy.mappings.nextSibling}"] = actions.next_sibling(), + ["${cfg.navbuddy.mappings.previousSibling}"] = actions.previous_sibling(), + ["${cfg.navbuddy.mappings.close}"] = actions.parent(), + ["${cfg.navbuddy.mappings.children}"] = actions.children(), + ["${cfg.navbuddy.mappings.root}"] = actions.root(), + + ["${cfg.navbuddy.mappings.visualName}"] = actions.visual_name(), + ["${cfg.navbuddy.mappings.visualScope}"] = actions.visual_scope(), + + ["${cfg.navbuddy.mappings.yankName}"] = actions.yank_name(), + ["${cfg.navbuddy.mappings.yankScope}"] = actions.yank_scope(), + + ["${cfg.navbuddy.mappings.insertName}"] = actions.insert_name(), + ["${cfg.navbuddy.mappings.insertScope}"] = actions.insert_scope(), + + ["${cfg.navbuddy.mappings.appendName}"] = actions.append_name(), + ["${cfg.navbuddy.mappings.appendScope}"] = actions.append_scope(), + + ["${cfg.navbuddy.mappings.rename}"] = actions.rename(), + + ["${cfg.navbuddy.mappings.delete}"] = actions.delete(), + + ["${cfg.navbuddy.mappings.foldCreate}"] = actions.fold_create(), + ["${cfg.navbuddy.mappings.foldDelete}"] = actions.fold_delete(), + + ["${cfg.navbuddy.mappings.comment}"] = actions.comment(), + + ["${cfg.navbuddy.mappings.select}"] = actions.select(), + + ["${cfg.navbuddy.mappings.moveDown}"] = actions.move_down(), + ["${cfg.navbuddy.mappings.moveUp}"] = actions.move_up(), + + ["${cfg.navbuddy.mappings.telescope}"] = actions.telescope({ + layout_strategy = "horizontal", + layout_config = { + height = 0.60, + width = 0.75, + prompt_position = "top", + preview_width = 0.50 + }, + }), + + ["${cfg.navbuddy.mappings.help}"] = actions.help(), -- Open mappings help window + }, + } + ''; + }; +} diff --git a/modules/ui/breadcrumbs/default.nix b/modules/ui/breadcrumbs/default.nix new file mode 100644 index 00000000..02c63515 --- /dev/null +++ b/modules/ui/breadcrumbs/default.nix @@ -0,0 +1,6 @@ +_: { + imports = [ + ./config.nix + ./breadcrumbs.nix + ]; +} diff --git a/modules/ui/default.nix b/modules/ui/default.nix index 757dd469..bbcb9d20 100644 --- a/modules/ui/default.nix +++ b/modules/ui/default.nix @@ -6,6 +6,7 @@ _: { ./smartcolumn ./colorizer ./illuminate + ./breadcrumbs ./borders ]; } diff --git a/modules/visuals/config.nix b/modules/visuals/config.nix index aaad23ba..79105201 100644 --- a/modules/visuals/config.nix +++ b/modules/visuals/config.nix @@ -54,7 +54,8 @@ in { 'alpha', 'code-action-menu-menu', 'code-action-menu-warning-message', - 'notify' + 'notify', + 'Navbuddy' }, } '';