Skip to content
This repository has been archived by the owner on Apr 16, 2024. It is now read-only.

Feature: restructure modules and config #277

Merged

Conversation

LuigiPiucco
Copy link
Contributor

@LuigiPiucco LuigiPiucco commented Dec 31, 2021

For more info on this, see #233 and the commit messages, but essentially this separates concerns about the various functionalities: each module has its directory, toggling a module just entails listing it in doom-nvim/modules.lua, everything else can be overridden and configured in doom-nvim/config.lua. Packages can be added per module (in a packages.lua file) or by the user in the doom.packages table, they can also override module packages however desired.
The config files changed names as well, the doom_ prefix was removed, for simplicity, and we have the luxury of changing this because the old files wouldn't work anyway.

These are big, breaking changes, which will be documented in later work. The next big thing to do here is add a README.md per module explaining all options, bindings and autocmds, plus whatever extra things they enable. Another extra piece of work is to create LSP wrapper for each language, with appropriate packages and config. Also, doom.norg should probably be rewritten, I imagine most of it is outdated now.

Also, I'd like to thank @connorgmeehan, @caligian and @NTBBloodbath for support and ideas.

Closes #233 #251
(and probably many others, but I'll list only direct fixes)

These changes have been discussed in doom-neovim#233.

As a side note, I ran stylua and luacheck over the repo.

* From everyone's perpective

- The module structure has been flattened out, removing the category
  grouping. Beyond less iteration scopes, this may help with, in the
  future, allowing the user to write and enable custom modules in
  $DOOMDIR.

* From the user's perpective

- `doom_config.lua` works via overrides now, it can change defaults
  on the global `doom` before everything gets actually configured.
- `doom_modules.lua`  just returns the actual thing it's supposed to,
  without {return value}.source. More on that next.
- Instead of each config file returning a source key with its file
  location, the handlers for each config file actively search for them.
  This is described in the respective files inside `lua/doom/core/config`,
  but it's basicaly a check for two special paths falling back to
  runtimepath.
- `doom_userplugins.lua` is removed in favor of having its
  functionality in `doom_config.lua`. To add a standalone package, add
  its packer spec to `doom.packages` (this is a map-like table of
  package names to packer specs).
- To override the spec of a module package, change the keys in
  `doom.packages[package_name_without_author]`.
  Special attrs: `config` is run after the built-in config.
- To change settings and behavior, override keys in
  `doom[module]`, or just `doom.*` for some core features.
- To add standalone bindings, add them to `doom.binds`. This is
  passed to nest along with the modules' binds. `doom.binds` overrides
  anything you don't like in modules, you can safely replace the bind.
- To add standalone autocmds, add them to `doom.autocmds[augroup_name]`. The
  structure is as passed to `utils.create_augroups`, so:
  ```lua
  doom.autocmds[group_name] = {
    { event1, pattern1, cmd1 },
    ...
  }
  ```
- You shouldn't override any tables, like `doom.autocmds` or `doom.packages`,
  only the leaves, else you remove all that is already there. We could
  use metatables to avoid this in the future.
- The `config.nvim` table is no longer used, most of its functionality is spread
  elsewhere, like autocmds. For variables and options, just use
  vim.opt.*, vim.g.* etc. in `doom_config.lua`
- Settings can also be appended to in `doom_config.lua`, because
  defaults are prepopulated.

* From a maintainer's perpective

- Most things are grouped under the `doom` global, which is populated
  and overriden early in `init.lua`. The exception is enabled modules,
  which you still need to require `doom.core.config.modules` to get.
  However, do so sparingly, and only if you truly mean to iterate over
  enabled modules disregarding user overrides (they may have removed a
  particular package or reset binds).
- Modules are defined in the following folder structure inside
  `lua/doom/modules`:
  `<module>/{init.lua,packages.lua,binds.lua,autocmds.lua}`.
  init.lua and packages.lua are required, even if empty.
- `init.lua` returns: defaults under a `defaults` key; config functions
  under `packer_config[package_name]`. It can access the doom global
  only inside the configs.
- `packages.lua` returns: a map of package names to package specs, with no
  `config` or `disable` keys present. Most things should be lazy-loaded,
  preferably with the `cmd` and `module` keys. It cannot access the doom global.
- `autocmds.lua` returns: a list of autocmds to be applied under the group
  `doom_{module}`. It can use the `doom` global to add things
  conditionally.
- `binds.lua` returns: the keyconfig to be passed to nest. It can use the
  `doom` global to add things conditionally.

What's left:

- Implement lsp wrapping.
- Document the individual modules.
- Write a migration script.
This adds wrapping of lsp for one language: lua.
It adds two modules, `auto_install` and `lua`. The former toggles auto
installation, and is sort of a marker module. Beyond installing and
configuring nvim-lsp-installer and DAPInstall.nvim, it doesn't really do
anything. It's purpose is to be checked on the lua module which will use
the installers if it is enabled. Otherwise just falls back to $PATH. Since
I'm on NixOS, I can only test the non-auto installation, but it seems to be
downloading properly for the auto version. The lua module also installs
additional packages, namely lua-dev.nvim, which used to be part of the lsp
module. It could add keybinds, but I couldn't think of any. It has a
special autocommand that `dofile`s the config.lua, which actually sets
up lsp (and possibly dap) lazily and only after the first lua file is
opened. This commit is meant as a template/prototype to other language
wrappings.
Also remove the built-in modules in favor of putting them inside utils
(reloader and async) or implementing them later with the new module
structure (others).
Now supports lua functions. The ideas come from @caligian and his repo, though
modified to use a unique index instead of requiring a name.
@LuigiPiucco
Copy link
Contributor Author

The linter and style checker should be pleased now.

@NTBBloodbath NTBBloodbath added the scope: enhancement New feature or request label Jan 1, 2022
@connorgmeehan
Copy link
Collaborator

Hey sorry for the delay reviewing, getting around to it now. These are some smaller issues I found on a first read through. Before I get more into it I just want to confirm I understand how everything works and make sure I fully understand the lifecycle of a module. I'll write my understanding below, could you confirm if I'm right or correct me if I'm wrong?

By the way, some really cool optimisations here, it's awesome to see.

First doom.config.core's `load function is called which:

  1. Set's default vim options, loads default config options
  2. populates doom global object with empty tables for autocommands, binds,
  3. Loads each enabled module and adds packer dependencies to packages table and settings to the doom object.
  4. Executes the user's config.lua file allowing the user to override/modify settings.
  5. Loads and populates autocommands and `binds

Then doom/core/init is loaded, just applies various settings, for colourschemes, sets up some commands etc.

Then doom/modules/init

  1. Packer is initialised
  2. All of the packer config functions are flattened into a table.
  3. We iterate along the packer dependencies (from doom.config.core) and merge the packer spec with the relevant packer config function.
    a. Can the user can provide a post config hook to packages by setting the config field of the packer spec in config.lua?
  4. Autocommands are iterated upon and built.

@LuigiPiucco
Copy link
Contributor Author

I'll write my understanding below, could you confirm if I'm right or correct me if I'm wrong?

That's correct, the only mistake in the explanation is a typo in the module name (it's doom.core.config).

a. Can the user can provide a post config hook to packages by setting the config field of the packer spec in config.lua?

Yes and, in fact, it's done automatically if the user tries to override config in doom.packages[package] spec. That's why config is separated from packages.lua into init.lua. doom/modules/init.lua passes to packer both what's in doom.packages[package].config (this is the user override, or post config, if you will) and what's in the flattened table of configs acquired from iterating the init.luas of each module (this is the built-in config, and it's run first).

The exact method for running the two of them is simple, even though it took me a while to figure out. I first tried to create an anonymous function that just runs both in sequence, but them I ran into a packer issue that prohibits from having captures in config. I hit my head on the wall for a while, until I discovered that passing both functions in a table works, even though its undocumented.

@connorgmeehan
Copy link
Collaborator

Ok, I noticed you implemented the lua language using a config.lua file in the module. Could you point out where it's being executed from and when in the lifecycle it occurs?

@LuigiPiucco
Copy link
Contributor Author

LuigiPiucco commented Jan 5, 2022

In doom/modules/lua/autocmds.lua, there is an autocmd to run dofile on config.lua upon entering a lua file. It's a once command, and is meant to run things like require("lspconfig")[server].setup(), lsp/dap install commands (if the autoinstall module is on) and treesitter parser configs.

I made an exception for the installation of treesitter parsers, they don't need the autoinstall module, because they aren't packaged in distros, at least that I know of. Additionally, the module breaks lsp and dap on NixOS, since the downloaded executables don't work on NixOS. An alternative solution would be to add three autoinstall modules, one for each of lsp, dap and treesitter.

@NTBBloodbath NTBBloodbath added priority: high High priority stuff scope: breaking changes Internal breaking changes to our codebase scope: docs Improvements or additions to documentation labels Jan 13, 2022
@NTBBloodbath NTBBloodbath linked an issue Jan 13, 2022 that may be closed by this pull request
@connorgmeehan
Copy link
Collaborator

connorgmeehan commented Jan 14, 2022

It looks like there are some issues with inter-module dependencies (lsp depends on snippets, autoinstall depends on dap). Additionally I think the way you override a modules defaults can be improved. I have some changes to the order of execution that I think would solve all of this.

Firstly I think the enabled modules should be available before parsing the packages.lua of each module. That way the auto_install module can disable dap-install if dap is not enabled.

Secondly I think all overrides should be editable the same, logical way. I think autocommands, packer specs, keybinds, configure function should be under the same doom.module.module_name object allowing the user to do something as follows:

local lsp_module` = doom.modules.lsp
lsp_module.packages['lspconfig'].commit = '...'
lsp_module.keybinds = {...}
tbl.insert(lsp_module.autocmds, { ... })

Lastly I think this doom.modules.lsp object should be populated, including the autocommands, packer spec, keybinds etc, before the config.lua is parsed. I know that this means we have to iterate over the same data twice and isn't as efficient but I think there's huge value for newcomers to able to print(vim.inspect(lsp_module.keybinds)) and see a valid config. Looking at the source code of a module to see what kind of data it returns isn't always super clear, especially for someone new to nvim.

This is just one possible solution and I'm interested in hearing alternatives to either of these issues. I think that this new architecture needs to be a bit clearer in how it functions and I think these changes would make a very clear mental modal of:

  1. Parse modules.lua
  2. Populate defaults in doom object
  3. Allow user to inspect and override defaults via config.lua
  4. Start doom-nvim using the defaults from the doom object.

Edit: Again, great work! Sorry for the delay, I've had a very lazy new years and I'm now back at work but slowly chipping through reviewing this PR :).

@connorgmeehan
Copy link
Collaborator

connorgmeehan commented Jan 14, 2022

I've branched off your changes here and I've pushed some fixes/changes to some of the issues outlined here. If you're happy with my code, feel free to cherry pick from my branch. I'd like to get this PR to a point where I can use it for work and keep providing bugs and changes from more intense usage.

I wouldn't cherry pick the new languages just yet, that can be done at the end. Also any commit with the temp: prefix is just to patch things over so I can keep working, they can be ignored.

@LuigiPiucco
Copy link
Contributor Author

It looks like there are some issues with inter-module dependencies (lsp depends on snippets, autoinstall depends on dap).

This is true and, because I had already opened the PR when I noticed, I left it there to allow you to review without stuff changing under your feet. However, I think the solution is even simpler: is_plugin_disabled(module_name) works everywhere, since it doesn't depend on the doom global, instead it checks the modules.lua file directly. As such, we can use it to require packages conditionally based on whether a module has been enabled. So this bit is probably not needed, or rather, it's already satisfied:

Firstly I think the enabled modules should be available before parsing the packages.lua of each module.


Secondly I think all overrides should be editable the same, logical way. I think autocommands, packer specs, keybinds, configure function should be under the same doom.module.module_name object

This is more of a stylistic choice, and while it does look a bit better, it incurs iteration cost and complexity. I can do it, but it would be a big force-push, since all the module looping would change. I will let us discuss some more before tackling this.


Lastly I think this doom.modules.lsp object should be populated, including the autocommands, packer spec, keybinds etc, before the config.lua is parsed. I know that this means we have to iterate over the same data twice and isn't as efficient

I don't think this is doable, not because of efficiency, but because we need doom do be overridden before keybinds and autocmds can be added, since some of them are conditional. At best, we could populate these two based on defaults first, but since the user may change them, it would not reflect what actually ends up being the config.

I think there's huge value for newcomers to able to print(vim.inspect(lsp_module.keybinds)) and see a valid config.

It is certainly valuable, but I'm assuming here you mean adding a print in config.lua? If so, this is not doable, or rather, it is doable but the print will show the current state of doom, which may very well be changed later and is probably not what the user wants. We should instead direct them to run the command :lua print(vim.inspect(doom.keybinds)) or the like, once they've gotten into doom, because then they can be sure of the final config. We may even add that as a bind.


I think that this new architecture needs to be a bit clearer in how it functions

Yes, I've come to this conclusion as well, and I'm now thinking if I should add a documentation commit in this PR instead of saving it for later. I may push it along with the other stuff.


I've branched off your changes here

Most of these look great (I've not looked at the temp and lang ones yet), I can add them once I push with the other changes mentioned.

@connorgmeehan
Copy link
Collaborator

I think it's best not to rebase from here on out just to maintain the commit messages and show the changes a little clearer. The only reason we'd want to rebase is to keep semantic commits but for such a large change I think it's better we just write the changelog ourselves.

(Regarding inter-module dependencies)

Oh great! I had a go fixing some of these but ran into some errors. Might have another go soon.

This is more of a stylistic choice...discuss later

Yep that sounds good

I don't think this is doable, not because of efficiency, but because we need doom do be overridden before keybinds and autocmds can be added, since some of them are conditional. At best, we could populate these two based on defaults first, but since the user may change them, it would not reflect what actually ends up being the config.

Ahhh that is a problem, the only conditional binds are in modules/core/binds.lua and modules/core/autocmds.lua right? Maybe there's a way we can resolve that.

it would not reflect what actually ends up being the config

I think that's ok because users will be modifying the same data that they're vim.inspecting. I think it will still be clear to the user because they're changing the data and they control the order of execution.

print(vim.inspect(doom.modules.lsp.binds)) -- Initial config
doom.module.lsp.binds = { ... custom binds ... }
print(vim.inspect(doom.modules.lsp.binds)) -- Modified config

Another option could be a telescope view that allows users to search through current module configs and open the vim.inspected data in a new buffer. Maybe it could even prepend the data with the code required to set the data, like if you telescoped for telescope keybinds you'd get.

-- This shows the current keybind settings for the telescope module.
-- Copy and paste this into your `config.lua` file to modify these.
doom.modules.telescope.keybinds = {
    -- <default keybinds>
}

Yes, I've come to this conclusion as well, and I'm now thinking if I should add a documentation commit in this PR instead of saving it for later. I may push it along with the other stuff.

Yeah would love to see some documentation, might even help me with my understanding of how everything works. I think short clear documentation is indicative of a logical, good API so the documentation will probably also help us make API decisions. If you do a first pass I can come in and edit this documentation as well :).

It is more up to date and well maintained than my own, plus includes
what I had implemented.
This is sometimes allowed, but here it would try and get things that
weren't initialized.
@NextAlone
Copy link
Contributor

Will this be merged? Looking forward to these changes

@connorgmeehan
Copy link
Collaborator

connorgmeehan commented May 1, 2022

Hey NextAlone, I have been expanding on these changes, adding language support, fixing bugs and writing documentation and I should be merging to origin/next soon. I know it's been quite a delay but once this is done development should continue as normal.

The PR is here but it's a bit out of date (I just did some major refactoring to simplify the internal workings) #313

I will merge my changes, do some optimisations (addressing long packer sequencing time) make sure the documentation is good and then release it. Can I ask for some feedback on the documentation later on?

@connorgmeehan connorgmeehan merged commit fbb6360 into doom-neovim:next May 7, 2022
@NextAlone
Copy link
Contributor

Hey NextAlone, I have been expanding on these changes, adding language support, fixing bugs and writing documentation and I should be merging to origin/next soon. I know it's been quite a delay but once this is done development should continue as normal.

The PR is here but it's a bit out of date (I just did some major refactoring to simplify the internal workings) #313

I will merge my changes, do some optimisations (addressing long packer sequencing time) make sure the documentation is good and then release it. Can I ask for some feedback on the documentation later on?

Ok, you can, and it works fine

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
priority: high High priority stuff scope: breaking changes Internal breaking changes to our codebase scope: docs Improvements or additions to documentation scope: enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature Proposal: New module architecture
4 participants