SmiteshP / nvim-navic

Simple winbar/statusline plugin that shows your current code context
Apache License 2.0
1.35k stars 49 forks source link
document-symbols language-server-protocol lsp lua neovim neovim-plugin nvim nvim-plugin

🛰️ nvim-navic

A simple statusline/winbar component that uses LSP to show your current code context. Named after the Indian satellite navigation system.

2022-06-11 17-02-33

You might also be interested in nvim-navbuddy. Paired with nvim-navic, it will give you complete breadcrumbs experience like in an IDE!

⚡️ Requirements

📦 Installation

Install the plugin with your preferred package manager:

packer

use {
    "SmiteshP/nvim-navic",
    requires = "neovim/nvim-lspconfig"
}

vim-plug

Plug "neovim/nvim-lspconfig"
Plug "SmiteshP/nvim-navic"

⚙️ Setup

For nvim-navic to work, it needs attach to the lsp server. You can pass the nvim-navic's attach function as on_attach while setting up the lsp server. You can skip this step if you have enabled auto_attach option during setup.

Note: nvim-navic can attach to only one server per buffer.

Example:

local navic = require("nvim-navic")

require("lspconfig").clangd.setup {
    on_attach = function(client, bufnr)
        navic.attach(client, bufnr)
    end
}

If you're sharing your on-attach function between lspconfigs, better wrap nvim-navic's attach function to make sure documentSymbolProvider is enabled:

Example:

local on_attach = function(client, bufnr)
    ...
    if client.server_capabilities.documentSymbolProvider then
        navic.attach(client, bufnr)
    end
    ...
end

require("lspconfig").clangd.setup {
    on_attach = on_attach
}

NOTE: You can set vim.g.navic_silence = true to supress error messages thrown by nvim-navic. However this is not recommended as the error messages indicate that there is problem in your setup. That is, you are attaching nvim-navic to servers that don't support documentSymbol or are attaching navic to multiple servers for a single buffer.

NOTE: You can set vim.b.navic_lazy_update_context = true for specific buffers, where you want the the updates to not occur on every CursorMoved event. It should help if you are facing performance issues in large files. Read the docs for example usage of this variable. Alternatively, you can pass lazy_update_context=true to the setup function to turn off context updates on the CursorMoved event completely for all buffers. It's useful when you just want context updates to happen only on CursorHold events and not on CursorMoved.

🪄 Customise

Use the setup function to modify default parameters.

navic.setup {
    icons = {
        File          = "󰈙 ",
        Module        = " ",
        Namespace     = "󰌗 ",
        Package       = " ",
        Class         = "󰌗 ",
        Method        = "󰆧 ",
        Property      = " ",
        Field         = " ",
        Constructor   = " ",
        Enum          = "󰕘",
        Interface     = "󰕘",
        Function      = "󰊕 ",
        Variable      = "󰆧 ",
        Constant      = "󰏿 ",
        String        = "󰀬 ",
        Number        = "󰎠 ",
        Boolean       = "◩ ",
        Array         = "󰅪 ",
        Object        = "󰅩 ",
        Key           = "󰌋 ",
        Null          = "󰟢 ",
        EnumMember    = " ",
        Struct        = "󰌗 ",
        Event         = " ",
        Operator      = "󰆕 ",
        TypeParameter = "󰊄 ",
    },
    lsp = {
        auto_attach = false,
        preference = nil,
    },
    highlight = false,
    separator = " > ",
    depth_limit = 0,
    depth_limit_indicator = "..",
    safe_output = true,
    lazy_update_context = false,
    click = false,
    format_text = function(text)
        return text
    end,
}

For highlights to work, highlight groups must be defined. These may be defined in your colourscheme, if not you can define them yourself too as shown in below code snippet.

Example highlight definitions ```lua vim.api.nvim_set_hl(0, "NavicIconsFile", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsModule", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsNamespace", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsPackage", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsClass", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsMethod", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsProperty", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsField", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsConstructor", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsEnum", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsInterface", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsFunction", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsVariable", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsConstant", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsString", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsNumber", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsBoolean", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsArray", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsObject", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsKey", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsNull", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsEnumMember", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsStruct", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsEvent", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsOperator", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicIconsTypeParameter", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicText", {default = true, bg = "#000000", fg = "#ffffff"}) vim.api.nvim_set_hl(0, "NavicSeparator", {default = true, bg = "#000000", fg = "#ffffff"}) ```

If you have a font patched with codicon.ttf, you can replicate the look of VSCode breadcrumbs using the following icons

VSCode like icons ```lua navic.setup { icons = { File = ' ', Module = ' ', Namespace = ' ', Package = ' ', Class = ' ', Method = ' ', Property = ' ', Field = ' ', Constructor = ' ', Enum = ' ', Interface = ' ', Function = ' ', Variable = ' ', Constant = ' ', String = ' ', Number = ' ', Boolean = ' ', Array = ' ', Object = ' ', Key = ' ', Null = ' ', EnumMember = ' ', Struct = ' ', Event = ' ', Operator = ' ', TypeParameter = ' ' } } ```

🚀 Usage

nvim-navic does not alter your statusline or winbar on its own. Instead, you are provided with these two functions and its left up to you how you want to incorporate this into your setup.

Examples

### Native method
Lua ```lua vim.o.statusline = "%{%v:lua.require'nvim-navic'.get_location()%}" -- OR vim.o.winbar = "%{%v:lua.require'nvim-navic'.get_location()%}" ```
Vimscript ```vim set statusline+=%{%v:lua.require'nvim-navic'.get_location()%} " OR set winbar+=%{%v:lua.require'nvim-navic'.get_location()%} ```
### [feline](https://github.com/feline-nvim/feline.nvim)
An example feline setup ```lua local navic = require("nvim-navic") table.insert(components.active[1], { provider = function() return navic.get_location() end, enabled = function() return navic.is_available() end }) require("feline").setup({components = components}) -- OR require("feline").winbar.setup({components = components}) ```
### [lualine](https://github.com/nvim-lualine/lualine.nvim)
An example lualine setup ```lua local navic = require("nvim-navic") require("lualine").setup({ sections = { lualine_c = { { "navic", -- Component specific options color_correction = nil, -- Can be nil, "static" or "dynamic". This option is useful only when you have highlights enabled. -- Many colorschemes don't define same backgroud for nvim-navic as their lualine statusline backgroud. -- Setting it to "static" will perform a adjustment once when the component is being setup. This should -- be enough when the lualine section isn't changing colors based on the mode. -- Setting it to "dynamic" will keep updating the highlights according to the current modes colors for -- the current section. navic_opts = nil -- lua table with same format as setup's option. All options except "lsp" options take effect when set here. } } }, -- OR in winbar winbar = { lualine_c = { { "navic", color_correction = nil, navic_opts = nil } } } }) -- OR a more hands on approach require("lualine").setup({ sections = { lualine_c = { { function() return navic.get_location() end, cond = function() return navic.is_available() end }, } }, -- OR in winbar winbar = { lualine_c = { { function() return navic.get_location() end, cond = function() return navic.is_available() end }, } } }) ```
### [galaxyline](https://github.com/glepnir/galaxyline.nvim)
An example galaxyline setup ```lua local navic = require("nvim-navic") local gl = require("galaxyline") gl.section.right[1]= { nvimNavic = { provider = function() return navic.get_location() end, condition = function() return navic.is_available() end } } ```

If you have a creative use case and want the raw context data to work with, you can use the following function

An example output of get_data function: ```lua { { name = "myclass", type = "Class", icon = "󰌗 ", kind = 5, scope = { start = { line = 1, character = 0 }, end = { line = 10, character = 0 } } }, { name = "mymethod", type = "Method", icon = "󰆧 ", kind = 6, scope = { start = { line = 2, character = 4 }, end = { line = 5, character = 4 } } } } ```

If you work with raw context data, you may want to render a modified version of it. In order to ensure a consistent format with get_location, you may use the following function:

An example usage of format_data: Consider the scenario of working in deeply nested namespaces. Typically, just the namespace names will occupy quite some space in your statusline. With the following snippet, nested namespace names are truncated and combined into a single component: ```lua -- Customized navic.get_location() that combines namespaces into a single string. -- Example: `adam::bob::charlie > foo` is transformed into `a::b::charlie > foo` function() local navic = require("nvim-navic") local old_data = navic.get_data() local new_data = {} local cur_ns = nil local ns_comps = {} for _, comp in ipairs(old_data) do if comp.type == "Namespace" then cur_ns = comp table.insert(ns_comps, comp.name) else -- On the first non-namespace component $c$, collect -- previous NS components into a single one and -- insert it in front of $c$. if cur_ns ~= nil then -- Concatenate name and insert local num_comps = #ns_comps local comb_name = "" for idx = 1, num_comps do local ns_name = ns_comps[idx] -- No "::" in front of first component local join = (idx == 1) and "" or "::" if idx ~= num_comps then comb_name = comb_name .. join .. ns_name:sub(1, 1) else comb_name = comb_name .. join .. ns_name end end cur_ns.name = comb_name table.insert(new_data, cur_ns) cur_ns = nil end table.insert(new_data, comp) end end return navic.format_data(new_data) end ```