Files
outline.nvim/README.md
2023-11-12 13:18:25 +08:00

23 KiB
Raw Blame History

A note on fork status

This is a fork of the original symbols-outline.nvim which fixes a lot of bugs from the original repo, and also adds many more features (see #12 on github).

It started out as a personal fork which I maintain for my personal use-cases, soon more and more features found their way in and I started introducing significant changes. However, due to simrat's status with the original plugin, I eventually decided to rename and detach the fork, starting to work on this as a new plugin.

You can see all the changes from the original plugin before fork detach in #12 on github.


outline.nvim

A sidebar with a tree-like outline of symbols from your code, powered by LSP.

demo

Table of contents

Prerequisites

  • Neovim 0.7+
  • Properly configured Neovim LSP client

Installation

Packer:

use 'hedyhli/outline.nvim'

Lazy:

{
  "hedyhli/outline.nvim",
  config = function()
    -- Example mapping to toggle outline
    vim.keymap.set("n", "<leader>tt", "<cmd>Outline<CR>",
      { desc = "Toggle Outline" })

    require("outline").setup {
      -- Your setup opts here (leave empty to use defaults)
    }
  end,
},

Lazy with lazy-loading:

{
  "hedyhli/outline.nvim",
  cmd = { "Outline", "OutlineOpen" },
  keys = {
    -- Example mapping to toggle outline
    { "<leader>tt", "<cmd>Outline<CR>", desc = "Toggle outline" },
  },
  opts = {
    -- Your setup opts here
  },
},

This allows Lazy.nvim to lazy load the plugin on commands Outline, OutlineOpen, and your keybindings.

Setup

Call the setup function with your configuration options.

Note that a call to .setup() is required for this plugin to work (simrat39/symbols-outline.nvim#213).

require("outline").setup({})

Configuration

The configuration structure has been heavily improved and refactored in this plugin. For details and reasoning, see breaking changes.

Terminology

Check this list if you there's any confusion with the terms used in the configuration.

  • Provider: Source of the items in the outline view. Could be LSP, CoC, etc.
  • Node: An item in the outline view
  • Fold: Collapse a collapsible node
  • Location: Where in the source file a node is from
  • Preview: Show the location of a node in code using a floating window
  • Peek: Go to corresponding location in code without leaving outline window
  • Hover: Cursor currently on the line of a node
  • Hover symbol: Displaying a floating window to show symbol information provided by provider.
  • Focus: Which window the cursor is in

Default options

Pass a table to the setup call with your configuration options.

Default values are shown:

{
  outline_window = {
    -- Where to open the split window: right/left
    position = 'right',
    -- Only in this fork:
    -- The default split commands used are 'topleft vs' and 'botright vs'
    -- depending on `position`. You can change this by providing your own
    -- `split_command`.
    -- `position` will not be considered if `split_command` is non-nil.
    -- This should be a valid vim command used for opening the split for the
    -- outline window. Eg, 'rightbelow vsplit'.
    split_command = nil,

    -- Percentage or integer of columns
    width = 25, 
    -- Whether width is relative to the total width of nvim
    -- When relative_width = true, this means take 25% of the total
    -- screen width for outline window.
    relative_width = true,

    -- Behaviour changed in this fork:
    -- Auto close the outline window if goto_location is triggered and not for
    -- peek_location
    auto_close = false,
    -- Automatically go to location in code when navigating outline window.
    -- Only in this fork
    auto_goto = false,

    -- Vim options for the outline window
    show_numbers = false,
    show_relative_numbers = false,

    -- Only in this fork (this and the one below)
    show_cursorline = true,
    -- Enable this when you enabled cursorline so your cursor color can
    -- blend with the cursorline, in effect, as if your cursor is hidden
    -- in the outline window.
    -- This is useful because with cursorline, there isn't really a need
    -- to know the vertical column position of the cursor and it may even
    -- be distracting, rendering lineno/guides/icons unreadable.
    -- This makes your line of cursor look the same as if the cursor wasn't
    -- focused on the outline window.
    hide_cursor = false,

    -- Whether to wrap long lines, or let them flow off the window
    wrap = false,
    -- Only in this fork:
    -- Whether to focus on the outline window when it is opened.
    -- Set to false to remain focus on your previous buffer when opening
    -- outline.
    focus_on_open = true,
    -- Only in this fork:
    -- Winhighlight option for outline window.
    -- See :help 'winhl'
    -- To change background color to "CustomHl" for example, append "Normal:CustomHl".
    -- Note that if you're adding highlight changes, you should append to this
    -- default value, otherwise details/lineno will not have highlights.
    winhl = "OutlineDetails:Comment,OutlineLineno:LineNr",
  },

  outline_items = {
    -- Whether to highlight the currently hovered symbol (high cpu usage)
    highlight_hovered_item = true,
    -- Show extra details with the symbols (lsp dependent)
    show_symbol_details = true,
    -- Only in this fork.
    -- Show line numbers of each symbol next to them.
    -- Why? See this comment:
    -- https://github.com/simrat39/symbols-outline.nvim/issues/212#issuecomment-1793503563
    show_symbol_lineno = false,
  },

  -- Options for outline guides.
  -- Only in this fork
  guides = {
    enabled = true,
    markers = {
      bottom = '└',
      middle = '├',
      vertical = '│',
      horizontal = '─',
    },
  },

  symbol_folding = {
    -- Depth past which nodes will be folded by default
    autofold_depth = nil,
    -- Automatically unfold hovered symbol
    auto_unfold_hover = true,
    markers = { '', '' },
  },

  preview_window = {
    -- Automatically open preview of code location when navigating outline window
    auto_preview = false,
    -- Automatically open hover_symbol when opening preview (see keymaps for
    -- hover_symbol).
    -- If you disable this you can still open hover_symbol using your keymap
    -- below.
    -- Only in this fork
    open_hover_on_preview = true,
    -- Only in this fork:
    width = 50,     -- Percentage or integer of columns
    min_width = 50, -- This is the number of columns
    -- Whether width is relative to the total width of nvim.
    -- When relative_width = true, this means take 50% of the total
    -- screen width for preview window, ensure the result width is at least 50
    -- characters wide.
    relative_width = true,
    -- Border option for floating preview window.
    -- Options include: single/double/rounded/solid/shadow or an array of border
    -- characters.
    -- See :help nvim_open_win() and search for "border" option.
    border = 'single',
    -- winhl options for the preview window, see ':h winhl'
    winhl = '',
    -- Pseudo-transparency of the preview window, see ':h winblend'
    winblend = 0
  },

  -- These keymaps can be a string or a table for multiple keys
  keymaps = { 
    show_help = '?',
    close = {"<Esc>", "q"},
    -- Jump to symbol under cursor.
    -- It can auto close the outline window when triggered, see
    -- 'auto_close' option above.
    goto_location = "<Cr>",
    -- Jump to symbol under cursor but keep focus on outline window.
    -- Renamed in this fork!
    peek_location = "o",
    -- Only in this fork (next 2):
    -- Visit location in code and close outline immediately
    goto_and_close = "<S-Cr>"
    -- Change cursor position of outline window to the current location in code.
    -- "Opposite" of goto/peek_location.
    restore_location = "<C-g>",
    -- Open LSP/provider-dependent symbol hover information
    hover_symbol = "<C-space>",
    -- Preview location code of the symbol under cursor
    toggle_preview = "K",
    -- Symbol actions
    rename_symbol = "r",
    code_actions = "a",
    -- These fold actions are collapsing tree nodes, not code folding
    fold = "h",
    unfold = "l",
    fold_toggle = "<Tab>",       -- Only in this fork
    -- Toggle folds for all nodes.
    -- If at least one node is folded, this action will fold all nodes.
    -- If all nodes are folded, this action will unfold all nodes.
    fold_toggle_all = "<S-Tab>", -- Only in this fork
    fold_all = "W",
    unfold_all = "E",
    fold_reset = "R",
    -- Only in this fork:
    -- Move down/up by one line and peek_location immediately.
    down_and_goto = "<C-j>",
    up_and_goto = "<C-k>",
  },

  providers = {
    lsp = {
      -- Lsp client names to ignore
      blacklist_clients = {},
    },
  },

  symbols = {
    -- Symbols to ignore.
    -- Possible values are the Keys in the icons table below.
    blacklist = {},
    -- Added in this fork:
    -- You can use a custom function that returns the icon for each symbol kind.
    -- This function takes a kind (string) as parameter and should return an
    -- icon.
    icon_fetcher = nil,
    -- 3rd party source for fetching icons. Fallback if icon_fetcher returned
    -- empty string. Currently supported values: 'lspkind'
    icon_source = nil,
    -- The next fall back if both icon_fetcher and icon_source has failed, is
    -- the custom mapping of icons specified below. The icons table is also
    -- needed for specifying hl group.
    -- Changed in this fork to fix deprecated icons not showing.
    icons = {
      File = { icon = "󰈔", hl = "@text.uri" },
      Module = { icon = "󰆧", hl = "@namespace" },
      Namespace = { icon = "󰅪", hl = "@namespace" },
      Package = { icon = "󰏗", hl = "@namespace" },
      Class = { icon = "𝓒", hl = "@type" },
      Method = { icon = "ƒ", hl = "@method" },
      Property = { icon = "", hl = "@method" },
      Field = { icon = "󰆨", hl = "@field" },
      Constructor = { icon = "", hl = "@constructor" },
      Enum = { icon = "", hl = "@type" },
      Interface = { icon = "󰜰", hl = "@type" },
      Function = { icon = "", hl = "@function" },
      Variable = { icon = "", hl = "@constant" },
      Constant = { icon = "", hl = "@constant" },
      String = { icon = "𝓐", hl = "@string" },
      Number = { icon = "#", hl = "@number" },
      Boolean = { icon = "⊨", hl = "@boolean" },
      Array = { icon = "󰅪", hl = "@constant" },
      Object = { icon = "⦿", hl = "@type" },
      Key = { icon = "🔐", hl = "@type" },
      Null = { icon = "NULL", hl = "@type" },
      EnumMember = { icon = "", hl = "@field" },
      Struct = { icon = "𝓢", hl = "@type" },
      Event = { icon = "🗲", hl = "@type" },
      Operator = { icon = "+", hl = "@operator" },
      TypeParameter = { icon = "𝙏", hl = "@parameter" },
      Component = { icon = "󰅴", hl = "@function" },
      Fragment = { icon = "󰅴", hl = "@constant" },
      -- Added ccls symbols in this fork
      TypeAlias =  { icon = ' ', hl = '@type' },
      Parameter = { icon = ' ', hl = '@parameter' },
      StaticMethod = { icon = ' ', hl = '@function' },
      Macro = { icon = ' ', hl = '@macro' },
    },
  },
}

To find out exactly what some of the options do, please see the recipes section of the readme at the bottom for screen-recordings.

The order in which the sources for icons are checked is:

  1. Icon fetcher function
  2. Icon source (only lspkind is supported for this option as of now)
  3. Icons table

A fallback is always used if the previous candidate returned either an empty string or a falsey value.

Commands

  • :Outline[!]

    Toggle outline. With bang (!) the cursor focus stays in your original window after opening the outline window. Set focus_on_win = true to always use this behaviour.

  • :OutlineOpen[!]

    Open outline. With bang (!) the cursor focus stays in your original window after opening the outline window. Set focus_on_win = true to always use this behaviour.

  • :OutlineClose

    Close outline

  • :OutlineFocus

    Toggle focus on outline

  • :OutlineFocusOutline

    Focus on outline

  • :OutlineFocusCode

    Focus on source window

  • :OutlineStatus

    Display current provider and outline window status in the messages area.

  • :OutlineFollow[!]

    Go to corresponding node in outline based on cursor position in code, and focus on the outline window.

    With bang, retain focus on the code window.

    This can be understood as the converse of goto_location (see keymaps). goto_location sets cursor of code window to the position of outline window, whereas this command sets position in outline window to the cursor position of code window.

    With bang, it can be understood as the converse of focus_location.

Default keymaps

These mappings are active for the outline window.

Key Action
Esc / q Close outline
? Show help
Enter Go to symbol location in code
o Go to symbol location in code without losing focus
Shift+Enter Go to symbol location in code and close outline
Ctrl+k Go up and goto location
Ctrl+j Go down and goto location
Ctrl+g Update outline window to focus on code location
Ctrl+Space Hover current symbol (provider action)
K Toggles the current symbol preview
r Rename symbol
a Code actions
h Fold symbol or parent symbol
Tab Toggle fold under cursor
Shift+Tab Toggle all folds
l Unfold symbol
W Fold all symbols
E Unfold all symbols
R Reset all folding

Highlights

Outline window

Default:

outline_window = {
  winhl = "OutlineDetails:Comment,OutlineLineno:LineNr",
},

Possible highlight groups provided by outline.nvim to customize:

Highlight Purpose
OutlineCurrent Highlight of the focused symbol
OutlineConnector Highlight of the table connectors
OutlineDetails Highlight of the details info virtual text
OutlineLineno Highlight of the lineno column

You can customize any other highlight groups using winhl too, this option is passed directly to the winhl vim option unprocessed.

To customize colors of the symbol icons, use the symbols.icons table. See config.

Preview window

preview_window = {
  winhl = "",
},

Lua API

Outline.nvim provides the following public API for use in lua.

require'outline'
  • setup(opts)

  • toggle_outline(opts)

    Toggle opening/closing of outline window.

    If opts.focus_outline=false, keep focus on previous window.

  • open_outline(opts)

    Open the outline window.

    If opts.focus_outline=false, keep focus on previous window.

  • close_outline()

    Close the outline window.

  • focus_toggle()

    Toggle cursor focus between code and outline window.

  • focus_outline()

    Focus cursor on the outline window.

  • focus_code()

    Focus cursor on the window which the outline is derived from.

  • is_open()

    Return whether the outline window is open.

  • show_status()

    Display current provider and outline window status in the messages area.

  • has_provider()

    Returns whether a provider is available for current window.

  • follow_cursor(opts)

    Go to corresponding node in outline based on cursor position in code, and focus on the outline window.

    With opts.focus_outline=false, cursor focus will remain on code window.

Tips

  • To open the outline but don't focus on it, you can use :Outline! or :OutlineOpen!.

    This is useful in autocmds, say you have a filetype that, whenever a buffer with that filetype is opened you want to open the outline.

  • After navigating around in the outline window, you can use <C-g> (default mapping for restore_location) to go back to the corresponding outline location based on the code location.

  • To customize the background colors, text colors, and borders, you can use outline_window.winhl for the outline window or preview_window.winhl for the preview floating window. See highlights.

  • To fix symbol icon related issues, there are several options. If you use lspkind, you can set symbols.icon_source = 'lspkind' to use lspkind for fetching icons. You can also use your own function symbols.icon_fetcher that takes a string and should return an icon. Otherwise, you can edit the symbols.icons table for specifying icons.

    The order in which the sources of icons are checked is:

    1. Icon fetcher function
    2. Icon source
    3. Icons table

    A fallback is always used if the previous candidate returned either an empty string or a falsey value.

  • You can customize the split command used for creating the outline window split using outline_window.split_command, such as "topleft vsp". See :h windows

Recipes

Behaviour you may want to achieve and the combination of configuration options to achieve it.

Code snippets in this section are to be placed in .setup({ <HERE> }) directly unless specified otherwise.

  • Unfold all others except currently hovered item
symbol_folding = {
  autofold_depth = 1,
  auto_unfold_hover = true,
},
outline window showing auto fold depth
  • Use outline window as a quick-jump window
preview_window = {
  auto_preview = true,
},

https://github.com/hedyhli/outline.nvim/assets/50042066/a473d791-d1b9-48e9-917f-b816b564a645

Note that in the recording I have preview_window.open_hover_on_preview = false.

Alternatively, if you want to automatically navigate to the corresponding code location directly and not use the preview window:

outline_window = {
  auto_goto = true,
},

This feature was added by @stickperson in an upstream PR 🙌

https://github.com/hedyhli/outline.nvim/assets/50042066/3d06e342-97ac-400c-8598-97a9235de66c

Or, you can use keys <C-j> and <C-k> to achieve the same effect, whilst not having auto_goto on by default.

This feature is newly added in this fork.

  • Hide the extra details after each symbol name
outline_items = {
  show_symbol_details = false,
},
  • Show line numbers next to each symbol to jump to that symbol quickly

This feature is newly added in this fork.

outline_items = {
  show_symbol_lineno = false,
},

The default highlight group for the line numbers is LineNr, you can customize it using outline_window.winhl: please see highlights.

outline window showing lineno
  • Single cursorline
outline_window = {
  show_cursorline = true,
  hide_cursor = true,
}

This will be how the outline window looks like when focused:

outline window showing hide_cursor

Note that in the screenshot, outline_items.show_symbol_lineno is also enabled.

Some may find this unhelpful, but one may argue that elements in each row of the outline becomes more readable this way, hence this is an option.

This feature is newly added in this fork, and is currently experimental (may be unstable).

  • Custom icons

You can write your own function for fetching icons. Here is one such example that simply returns in plain text, the first letter of the given kind.

symbols = {
  icon_fetcher = function(kind) return kind:sub(1,1) end
}

The fetcher function, if provided, is checked first before using icon_source and icons as fallback.

outline with plain text icons

Any other recipes you think others may also find useful? Feel free to open a PR.

TODO

Key:

-     : Idea
- [ ] : Planned
- [/] : WIP
- ❌  : Was idea, found usable workaround
- ✅  : Implemented
  • Folds

    • [ ] Org-like shift+tab behavior: Open folds level-by-level
    • [ ] Optionally not opening all child nodes when opening parent node
    • Fold siblings and siblings of parent on startup
  • Navigation

    • Go to parent (as of now you can press hl to achieve the same effect)
    • Cycle siblings (as of now when reached the last sibling, you can use hlj to go back to first sibling)
  • [ ] simrat39/symbols-outline.nvim#75: Handling of the outline window when attached buffer is closed.

    Maybe it should continue working, so that pressing enter can open a split to the correct location, and pressing q can properly close the buffer.

  • Preview / Hover

  • View

  • nvim-navic
  • nvim-navbuddy
  • dropdown.nvim
  • treesitter (inspect/edit)
  • lspsaga
  • navigator.lua