stevearc / aerial.nvim

Neovim plugin for a code outline window
MIT License
1.74k stars 85 forks source link

feature request: Support for enabling multiple groups of SymbolKind filters in the user config file #423

Open shaneish opened 3 days ago

shaneish commented 3 days ago

Did you check existing requests?

Describe the feature

I'd like to see support added to allow users to specify multiple named groups of filter_kinds so that separate keybinds can be set to navigate within different "hierarchies" of symbols.

To illustrate further, assume instead of the default filter_kind configuration with a single group of symbol filters, I have the following defined in my setup configuration instead. Note there are two separate proposed configuration methods for this feature request, please refer to the comments in the code block to see a description of each:

-- option 1) this first example shows each kind group to filter on as ordered elements in a table

-- i think this would be the easiest to implement and the less confusing way to reference
-- a specific group as the "default" group, but would be slightly more confusing when setting
-- keybinds as users would need to refer back to the group and count the index out 
-- for whichever group they want to use.

filter_kind = {
  {
    "Class",
    "Constructor",
    "Enum",
    "Function",
    "Interface",
    "Module",
    "Method",
    "Struct",
  }, {
    "Field",
    "Variable",
    "Constant",
    "String",
    "Number",
    "Boolean",
    "Array",
    "Object",
    "Key",
    "Null",
    "EnumMember",
    "Operator",
    "TypeParameter",
    "Property",
  }, {
    "File",
    "Module",
    "Namespace",
    "Package",
    "Class",
    "Function",
    "Struct",
    "Component",
  },
}

-- option 2) this second example shows them being set as named elements in the table

-- i think this would make it easier for users to specify exactly which one
-- they want to use in their keybinds, but it would also muddy the water a bit
-- by no longer having a true `default` option

filter_kind = {
  ["primary"] = {
    "Class",
    "Constructor",
    "Enum",
    "Function",
    "Interface",
    "Module",
    "Method",
    "Struct",
  }, 
  ["secondary"] = {
    "Field",
    "Variable",
    "Constant",
    "String",
    "Number",
    "Boolean",
    "Array",
    "Object",
    "Key",
    "Null",
    "EnumMember",
    "Operator",
    "TypeParameter",
    "Property",
  },
  ["overview"] = {
    "File",
    "Module",
    "Namespace",
    "Package",
    "Class",
    "Function",
    "Struct",
    "Component",
  },
}

Now, when a user sets their keybinds they can refer to the specific group they want to use like this:

-- option 1) this setup shows how keybinds would be set using the ordered `kind_filters` table

require("aerial").setup({
  on_attach = function(bufnr)
    vim.keymap.set("n", "<C-k>", "<cmd>AerialPrev(1)<CR>", { buffer = bufnr })
    vim.keymap.set("n", "<C-j>", "<cmd>AerialNext(1)<CR>", { buffer = bufnr })
    vim.keymap.set("n", "<C-h>", "<cmd>AerialPrev(2)<CR>", { buffer = bufnr })
    vim.keymap.set("n", "<C-l>", "<cmd>AerialNext(2)<CR>", { buffer = bufnr })
    vim.keymap.set("n", "<C-l>", "<cmd>AerialNavToggle(3)<CR>", { buffer = bufnr })
  end,
})

-- option 2) this setup corresponds to the implementation where `kind_filters` maps a
-- a specified name to a given filter group

require("aerial").setup({
  on_attach = function(bufnr)
    vim.keymap.set("n", "<C-k>", "<cmd>AerialPrev('primary')<CR>", { buffer = bufnr })
    vim.keymap.set("n", "<C-j>", "<cmd>AerialNext('primary')<CR>", { buffer = bufnr })
    vim.keymap.set("n", "<C-h>", "<cmd>AerialPrev('secondary')<CR>", { buffer = bufnr })
    vim.keymap.set("n", "<C-l>", "<cmd>AerialNext('secondary')<CR>", { buffer = bufnr })
    vim.keymap.set("n", "<C-l>", "<cmd>AerialNavToggle('overview')<CR>", { buffer = bufnr })
  end,
})

Provide background

The reasons I'd like to do this are two-fold:

1) I love being able to navigate up and down via code context with the current implementation of the plugin, but would like a way to navigate left and write within lines by code context as well. So in the example I gave above, the first entry would be the current method that I could navigate up/down with via and , while the 2nd entry would enable me to jump between the different principal elements of code within the lines using and as well.

2) I would also love a way to set a overview group to view and jump to the highest-level symbols in a project and navigate between them. This is represented by the 3rd entry in the example above. For this functionality, I simply mapped one button to toggle the ArialNavigator. Think of this as the difference between navigating with ]] & [[ vs ]m and [m in the default Neovim keymaps.

What is the significance of this feature?

nice to have

Additional details

I've reviewed the code a bit and this appears (on the surface, at least) to be a relatively simple addition. If others agree, I can implement the feature myself and submit a PR for this issue, no worries.

Honestly, if the maintainers don't think this is a good addition I will probably just fork the plugin, implement the changes, and point my Neovim configs to the forked plugin. I'd prefer not to do this for obvious reasons, I'm just saying there's no expectation here so if you hate it let me know lol.

Would not be the first time I've forked a Neovim plugin and started using it instead, but those plugins around Neovim features that aren't moving as fast as treesitter stuff like this so I didn't have to worry about breaking changes from the Neovim team.

stevearc commented 3 days ago

This would be a neat feature, but I do not think it would be a relatively simple addition. At the moment, the filter_kind is applied directly after we get symbols back from the source (LSP, treesitter). This means that we're storing them pre-filtered. If you want to make this change, you'll need to store all symbols and apply the correct filter for every operation on those symbols. That means refactoring a large amount of the code and also introducing the possibility for inconsistencies if two related operations end up using different filters.

The other main difficulty I see is coming up with a good configuration API and making it backwards compatible. At the moment you can define filter_kind as a list, or as a filetype map like

filter_kind = {
  lua = {
    "Class",
    "Function",
    "Module",
  },
  markdown = {
    "Interface",
  }
}

I think it will be a challenge to come up with a design that allows for specifying multiple or named symbol groups, possibly per-filetype, without being difficult to read and understand.

But feel free to make the attempt! I can't promise that I'll merge it back in, but I'll definitely review it.