LunarVim / lunarvim.org

🌐 Website for LunarVim
https://www.lunarvim.org
GNU General Public License v3.0
137 stars 204 forks source link

Better document extension points #251

Open danielo515 opened 2 years ago

danielo515 commented 2 years ago

Feature Description

Hello, first, thanks for this wonderful neovim preset. It looks outstanding and really lowers the entry barrier. However, when it's time to extend and customise it beyond the most basic (keymaps and just adding more basic plugins) it is not easy to understand. For example, each plugin documentation has it's own set of assumptions about how is your environment and the knowledge you have about it (basically your personal configuration), but in the case of lunar most people will have no idea if they can just follow the plugin instructions or they have to hook that into some lunar vim startup function. I will list the things that I think are worth documenting, but probably there are more

Describe the alternatives you have considered

Right now the only alternative is reading the source-code, but that is not very comfortable because you have to navigate the repository, have a good understanding of lua and a good understanding of neovim apis and vim in general. Those are too high of a entry barrier.

Support information

Some of the things I think are worth documenting are:

abzcoding commented 2 years ago

first off i do agree we should have a better documentation, but sadly we don't have enough manpower to do so, so feel free to contribute


  1. Startup order

here is the detailed order -> https://www.lunarvim.org/dev/02-under-the-hood.html


  1. What is available at a certain point

right now you can require them inside config because we have minimal lazy loading, though i would suggest using this syntax to be on the safe side

  local status_ok, sample_plugin = pcall(require, "sample_plugin")
  if not status_ok then
    return
  end

  -- do stuff with `sample_plugin`

and also you can require them inside on_config_done


  1. Available setup-hooks/callback

pretty much all plugins have this, and they all work the same way, they provide you with an object so you can do anything you want with them and override lunarvim settings and we are planning to enable users to change the load event of plugins as well ( e.g. InsertEnter ... )


  1. How to extend specific plugin configurations

just add the settings you like( and we don't have), for example in bufferline options, we don't have mode but you can easily add it using

lvim.builtin.bufferline.options.mode = "buffers", -- set to "tabs" to only show tabpages instead

basically, just extend the settings or even completely override them


let me know if you need extra info

kylo252 commented 2 years ago

Another relevant issue to this is that the site only targets the stable branch, this makes it harder to update information when a PR is done.

Also related https://github.com/LunarVim/LunarVim/projects/1#card-67485925

danielo515 commented 2 years ago

first off i do agree we should have a better documentation, but sadly we don't have enough manpower to do so, so feel free to contribute

I will be happy to contribute, but I want to first get to an agreement of what is needed so I don't try to do useless PRs.

Here are some things that, even after reading your answer and what you suggested, are still not clear to me.

right now you can require them inside config because we have minimal lazy loading, though i would suggest using this syntax to be on the safe side

It have happened to me several times that I could not load a plugin from my configuration. At first I thought it was because I have configured the plugins before trying to require them, but even when I moved the plugin require to the config function of packer I was not able to require them. Explaining at what part of your local config you can add configs that rely on plugin imports will be a nice thing to clarify. Even using the format you suggested does not help, because the result will be that you don't have an error, but you will not load your configurations either.

pretty much all plugins have this, and they all work the same way, they provide you with an object so you can do anything you want with them and override lunarvim settings and we are planning to enable users to change the load event of plugins as well ( e.g. InsertEnter ... )

This is another thing that may look obvious when you have familiarity with the code-base, but that it is not that simple From the outside. "Pretty much all plugins" means that I have to go to the code, see if such a reference exist and it's exact naming, because I may become with a different naming idea than yours. Having a section of "config-hooks" or "plugin-configuration" with all the available ones specifically listed will be a nice thing to have. Also, I still don't know what the thing you get is exactly. Is the instance of the plugin? Is the configuration object? Is it equivalent to do require"plugin-name"? Does it change from plugin to plugin?

just add the settings you like( and we don't have), for example in bufferline options, we don't have mode but you can easily add it using

lvim.builtin.bufferline.options.mode = "buffers", -- set to "tabs" to only show tabpages instead

basically, just extend the settings or even completely override them

Again, having specific instructions, ideally per plugin, about how to extend lvim settings will be amazing. To me it is still not clear when this configurations are applied: Do they happen after my entire config.lua it's sourced? If that is the case, that means that I can not do anything that relies on those configs inside my config.lua unless is a function that is lazy loaded. Another thing that is not clear is what extend mean. If you are a seasoned lua developer you probably have your own ideas, but as a newbie is hard to figure it out, specially because it is not clear the way configs are applied by lunar vim. What does extend mean exactly? Do I need to call vim.tbl_deep_extend ? Or is lunar vim just applying whatever I put on the global configuration with some kind of internal default config? As I said, this may look like silly questions once you read the code but ideally you should not be reading the code.

To give you an example on this last point, this is something I tried to configure telescope:

lvim.builtin.telescope.pickers = {
    {
        lsp_workspace_symbols = {
            attach_mappings = function(_, map)
                local onSelect = function(prompt_bufnr)
                    local selection = require("telescope.actions.state").get_selected_entry()
                    print(selection)
                end
                map("i", "<CR>", onSelect)
            end,
                mappings = {
                    i = {
                        ["<cr>"] = function(prompt_bufnr)
                            local selection = require("telescope.actions.state").get_selected_entry()
                            print(vim.inspect(selection))
                        end,
                    },
                },
        },
    },
}

However, it is not working and I have no idea why. Is this the supposed way to extend telescope? Should I be doing something different? However, If I move that very same piece of code to the callback function of telescope like this:

lvim.builtin.telescope.on_config_done = function(tele)
    tele.load_extension("frecency")
    tele.load_extension("command_palette")
    tele.load_extension("notify")
    tele.load_extension("file_browser")
    local opts = {
        pickers = {
            lsp_workspace_symbols = {
                mappings = {
                    i = {
                        ["<cr>"] = function(prompt_bufnr)
                            local selection = require("telescope.actions.state").get_selected_entry()
                            print(vim.inspect(selection))
                        end,
                    },
                },
            },
        },
    }

    notify(vim.inspect(opts))
    require("telescope").setup(opts)
end

Then it is loaded into telescope and kind of works. Does not work as I wanted because the which key function is not triggering with this mappings, however if I manually run :Telescope lsp_workspace_symbols it works as I expect. So advanced configurations are not trivial.

danielo515 commented 2 years ago

To give you another good example most users will have to face: adding snippets. This is a trivial task in almost all editors, but when it's time to do it on lunar-vim it's very confusing for most people which is made obvious by reading how many questions about this are on the chat about this. Of course it is not a matter of replicating the docs of luasnip, but, specially becasue lunar-vim uses a quite old version of it, some guidance should be added. Things like the following will be nice to have them documented:

abzcoding commented 2 years ago

I will be happy to contribute, but I want to first get to an agreement of what is needed so I don't try to do useless PRs.

thank you for taking time and trying to improve lunarvim <3

It have happened to me several times that I could not load a plugin from my configuration. At first I thought it was because I have configured the plugins before trying to require them, but even when I moved the plugin require to the config function of packer I was not able to require them. Explaining at what part of your local config you can add configs that rely on plugin imports will be a nice thing to clarify.

can you give a sample? ( to clarify this part for me )

This is another thing that may look obvious when you have familiarity with the code-base, but that it is not that simple From the outside. "Pretty much all plugins" means that I have to go to the code, see if such a reference exist and it's exact naming, because I may become with a different naming idea than yours.

you don't need to read the code, the auto-completion should be enough

  1. alpha doesn't have on_config_done Screen Shot 2022-04-09 at 1 19 48 AM
  2. telescope does Screen Shot 2022-04-09 at 1 20 02 AM

Having a section of "config-hooks" or "plugin-configuration" with all the available ones specifically listed will be a nice thing to have

that is a good idea, we should add somewhere here probably

Also, I still don't know what the thing you get is exactly. Is the instance of the plugin? Is the configuration object? Is it equivalent to do require"plugin-name"? Does it change from plugin to plugin?

it is equivalent to do require"plugin-name"

Again, having specific instructions, ideally per plugin, about how to extend lvim settings will be amazing. To me it is still not clear when this configurations are applied: Do they happen after my entire config.lua it's sourced? If that is the case, that means that I can not do anything that relies on those configs inside my config.lua unless is a function that is lazy l

Screen Shot 2022-04-09 at 1 25 19 AM

To give you an example on this last point, this is something I tried to configure telescope:

pickers are under lvim.builtin.telescope.defaults.pickers you forgot the defaults, this should be visible via auto-complete

Screen Shot 2022-04-09 at 1 35 29 AM Screen Shot 2022-04-09 at 1 35 20 AM

What does extend mean exactly? Do I need to call vim.tbl_deep_extend ? Or is lunar vim just applying whatever I put on the global configuration with some kind of internal default config

this is sadly inconsistent, for example in keybindings you can override the entire config ( we don't extend, we just override) but for example in plugin config, we do sth like this

  lvim.builtin.telescope = vim.tbl_extend("force", lvim.builtin.telescope, {

we should address this 🤞

but, specially becasue lunar-vim uses a quite old version of it, some guidance should be added.

what do you mean an old version?

abzcoding commented 2 years ago

also please feel free to do any PR that would improve our documentation, we do welcome any PR that would help users an easier transition into lunarvim, also tyvm for trying to help us improve lunarvim ❤️

danielo515 commented 2 years ago

can you give a sample? ( to clarify this part for me )

So, for example. I have a configuration for neoclip plugin. I want this plugin to self-register it's own which key configs, but at the time of calling the config function, which-key is never available. here is a slimmed down version of this scenario:

local log = require("lvim.core.log")
local M = {}

M.config = function()
    local status_ok, neoclip = pcall(require, "neoclip")
    if not status_ok then
        log:error("Could not load neoclip config")
        return
    end

    neoclip.setup({
        history = 50,
        enable_persistent_history = true,
        db_path = vim.fn.stdpath("data") .. "/neoclip.sqlite3",
    })
    local function clip()
        local opts = {  bla bla }
        local dropdown = require("telescope.themes").get_dropdown(opts)
        require("telescope").extensions.neoclip.default(dropdown)
    end
    local whk_status, whk = pcall(require, "which-key")
    if not whk_status then
        log:error("Could not load which-key")
        return
    end
    whk.register({
        ["<leader>y"] = { clip, "neoclip: open yank history" },
    })
end

return M

Then, on plugins.lua, which is required from config.lua

    {
        "AckslD/nvim-neoclip.lua",
        requires = { "tami5/sqlite.lua" },
        config = function()
            require("user.neoclip").config()
        end,
    },

Most of the times I see the warning about which-key not being available. But maybe this is an issue related to how packer loads plugins and not something lunar-vim specific? At this point, I am a bit confused about how and when plugins are loaded and if I'm supposed to being able to require them on my config files. The graph you have linked twice makes things even worse, because it specifically states that user config is loaded before plugins are setup? So now I'm wondering how it is possible that any of my configs works since many of them load some plugins almost at the root of the config file.

you don't need to read the code, the auto-completion should be enough

That is nice once you realise such kind of functions exist, but I didn't saw them until guess what, I read the code 😛 Having a dedicated section about configuring plugins on the docs, mentioning things like this on_config_done callbacks will be awesome.

pickers are under lvim.builtin.telescope.defaults.pickers you forgot the defaults, this should be visible via auto-complete

See how it is confusing? I did it correctly. Pickers are not,or at least should not, be under defaults key. They are just like extensions, and the telescope documentation states them at the root of the config. In fact it works perfectly fine for me doing it inside the setup complete callback:

lvim.builtin.telescope.on_config_done = function(tele)
    tele.load_extension("frecency")
    tele.load_extension("command_palette")
    tele.load_extension("notify")
    tele.load_extension("file_browser")
    tele.load_extension("lazygit")
    tele.load_extension("packer")
    local opts = {
        pickers = {
            lsp_workspace_symbols = {
                mappings = {
                    i = {
                        ["<cr>"] = function(prompt_bufnr)
                            local selection = require("telescope.actions.state").get_selected_entry()
                            print(vim.inspect(selection))
                        end,
                    },
                },
            },
        },
    }

    tele.setup(opts)
end

this is sadly inconsistent, for example in keybindings you can override the entire config ( we don't extend, we just override) but for example in plugin config, we do sth like this

While I understand the reasoning (if the user wants to completely wipe out lunar defaults) I think it should not be this implicit. Maybe providing configuration functions to add your own configs with clear options about merge/replace will be a more clear API?

  lvim.builtin.telescope = vim.tbl_extend("force", lvim.builtin.telescope, {

Is the bit you wrote like that in the actual code? If I get it correctly, tbl_extend gives precedence to the right, meaning you will be overriding any configuration provided by the user in case of conflict.

what do you mean an old version?

Forget about it, I was confusing by reading an old comment on the chat about the luasnips version and the lack of certain method.

abzcoding commented 2 years ago

Most of the time I see the warning about which-key not being available. But maybe this is an issue related to how packer loads plugins and not something lunar-vim specific?

ignore the warning, does the <leader>y command work? cause i have the exact settings and the key registers everytime

Screen Shot 2022-04-11 at 1 45 33 PM

because it specifically states that user config is loaded before plugins are setup? So now I'm wondering how it is possible that any of my configs works since many of them load some plugins almost at the root of the config file

packer first reads all of your configs to be able to create a compiled version of it under ~/.config/lvim/plugin/packer_compiled.lua then we create a cached version of it under ~/.cache/nvim/luacache, that is why all of your config works

Having a dedicated section about configuring plugins on the docs, and mentioning things like this on_config_done callbacks will be awesome.

sounds good to me 👍

See how it is confusing? I did it correctly. Pickers are not,or at least should not, be under defaults key. They are just like extensions, and the telescope documentation states them

you are right, I think that is a bug on our side, we should move out pickers from defaults as we did with extensions https://github.com/LunarVim/LunarVim/blob/36361e1107d80e2eb2d8d90afebcff40f1198764/lua/lvim/core/telescope.lua#L73-L80

Is the bit you wrote like that in the actual code? If I get it correctly, tbl_extend gives precedence to the right, meaning you will be overriding any configuration provided by the user in case of conflict.

that part is under config which is called way before user config, so it will not override user config, the setup part which is called after using config will respect your config 😅 the reason being -> -- Define this minimal config so that it's available if telescope is not yet available.


anyway, I think we have a couple of steps to take:

  1. move out pickers from defaults
  2. create a section in lunarvim.org explaining on_config_done and a simple explanation of how things work
  3. any more suggestions that you have
danielo515 commented 2 years ago

that sounds like a plan, I'll be drafting some PRs whenever I can. As an additional note, here is another pattern that I tried that didn't worked because how lunar internals are:

local M = {}

M.plugin = {
    "ThePrimeagen/harpoon",
    requires = "nvim-lua/plenary.nvim",
    after = "telescope.nvim",
    config = function()
        require("telescope").load_extension("harpoon")
        -- set harpoon keymaps
        lvim.keys.normal_mode["tu"] = "<cmd>lua require('harpoon.term').gotoTerminal(1)<CR>"
        lvim.keys.normal_mode["te"] = "<cmd>lua require('harpoon.term').gotoTerminal(2)<CR>"
        lvim.keys.normal_mode["cu"] = "<cmd>lua require('harpoon.term').sendCommand(1, 1)<CR>"
        lvim.keys.normal_mode["ce"] = "<cmd>lua require('harpoon.term').sendCommand(1, 2)<CR>"
        lvim.builtin.which_key.mappings["a"] = { "<cmd>lua require('harpoon.mark').add_file()<CR>", "ÔóÑ Add Mark" }
        lvim.builtin.which_key.mappings["<leader>"] = {
            "<cmd>Telescope harpoon marks<CR>",
            "ÔÄ¢ Harpoon",
        }

        local whk_status, whk = pcall(require, "which-key")
        if not whk_status then
            return
        end
        whk.register({
            ["<leader>1"] = { "<CMD>lua require('harpoon.ui').nav_file(1)<CR>", "Ô¢£ goto1" },
            ["<leader>2"] = { "<CMD>lua require('harpoon.ui').nav_file(2)<CR>", " goto2" },
            ["<leader>3"] = { "<CMD>lua require('harpoon.ui').nav_file(3)<CR>", " goto3" },
            ["<leader>4"] = { "<CMD>lua require('harpoon.ui').nav_file(4)<CR>", " goto4" },
        })
    end,
}

return M

The leader mappings worked as expected, but the normal mappings for lunar vim didn't. Why? Well, my guess is that which key register works at any time, while registering keymaps for lunar vim only works during startup, and you can't add more once all plugins and everything has finished loading.

abzcoding commented 2 years ago

while registering keymaps for lunar vim only works during startup, and you can't add more once all plugins and everything has finished loading

yes 😅 using this function

https://github.com/LunarVim/LunarVim/blob/b4d5f093a5607d3f14cbf90aebfb25036c3ee272/lua/lvim/keymappings.lua#L144-L160

seanrmurphy commented 2 years ago

I'm also tying to add neoclip to my telescope config - I'm not familiar with lua and my understanding of the way that Lunarvim all fits together is less than comprehensive. (I'd also like to add telescope-github).

I tried adding the following to lvim.plugins

  {
    "AckslD/nvim-neoclip.lua",
    requires = {
      -- you'll need at least one of these
      { 'nvim-telescope/telescope.nvim' },
      -- {'ibhagwan/fzf-lua'},
    },
    config = function()
      require('neoclip').setup()
    end,
  },

I can see that Lunarvim modifies the Packer config to load this module. It is not, however, registered with Telescope - I am not able to run

:Telescope neoclip

If I do this, however

:lua require('telescope').load_extension('neoclip')

I am then able to use neoclip in Telescope (but I'm not sure if it's really configured correctly).

From the above discussion, I'm not clear on the best approach - do I need to modify lvim.builtin.telescope.extensions - is there a way to extend this, rather than overwrite this?

danielo515 commented 2 years ago

@seanrmurphy I was about to open a PR to explain this in more detail, because this is one of the problems I struggle with when I started. I think this is what can be considered an actionable item, no @abzcoding ? 😄 My recommendation, and what I will be adding to the docs under the section of Extra Plugins > Telescope extensions (here) is:

Add your extension as usual to the list of plugins. There are several ways to register extensions within telescope, but generally the safer is explained below. Create the callback function that will be called when telescope has finished loading. Then register your extension within that callback to make sure telescope is available and can register extensions like this:

lvim.builtin.telescope.on_config_done = function(telescope)
  telescope.load_extension "frecency"
  telescope.load_extension "neoclip"
  ...
end
seanrmurphy commented 2 years ago

Thanks for that @danielo515 - so, to give a concrete example, this is the content that I have in my config.lua which seems to work fine - it is preceded by some over stuff (languages installed, theme to use etc).

<SNIP>...

lvim.plugins = {
  -- { "fatih/vim-go" },
  { "WhoIsSethDaniel/goldsmith.nvim" },
  { "nvim-treesitter/nvim-treesitter-textobjects" },
  { "preservim/vim-markdown" },
  {
    "tzachar/cmp-tabnine",
    run = "./install.sh",
    requires = "hrsh7th/nvim-cmp",
    config = function()
      local tabnine = require "cmp_tabnine.config"
      tabnine:setup {
        max_lines = 1000,
        max_num_results = 10,
        sort = true,
      }
    end,
    opt = true,
    event = "InsertEnter",
  },
  {
    "AckslD/nvim-neoclip.lua",
    requires = {
      -- you'll need at least one of these
      { 'nvim-telescope/telescope.nvim' },
      -- {'ibhagwan/fzf-lua'},
    },
    config = function()
      require('neoclip').setup({
        keys = {
          telescope = {
            i = {
              select = '<cr>',
              paste = '<c-p>',
              paste_behind = '<c-k>',
              replay = '<c-q>', -- replay a macro
              delete = '<c-d>', -- delete an entry
              custom = {},
            },
            n = {
              select = '<cr>',
              paste = 'p',
              paste_behind = 'P',
              replay = 'q',
              delete = 'd',
              custom = {},
            }
          }
        }
      })
    end,
  },

}

lvim.builtin.telescope.on_config_done = function(telescope)
  telescope.load_extension "neoclip"
end
danielo515 commented 2 years ago

Yes,that looks perfect.

El lun., 30 may. 2022 17:24, Seán Murphy @.***> escribió:

Thanks for that @danielo515 https://github.com/danielo515 - so, to give a concrete example, this is the content that I have in my config.lua which seems to work fine - it is preceded by some over stuff (languages installed, theme to use etc).

... lvim.plugins = { -- { "fatih/vim-go" }, { "WhoIsSethDaniel/goldsmith.nvim" }, { "nvim-treesitter/nvim-treesitter-textobjects" }, { "preservim/vim-markdown" }, { "tzachar/cmp-tabnine", run = "./install.sh", requires = "hrsh7th/nvim-cmp", config = function() local tabnine = require "cmp_tabnine.config" tabnine:setup { max_lines = 1000, max_num_results = 10, sort = true, } end, opt = true, event = "InsertEnter", }, { "AckslD/nvim-neoclip.lua", requires = { -- you'll need at least one of these { 'nvim-telescope/telescope.nvim' }, -- {'ibhagwan/fzf-lua'}, }, config = function() require('neoclip').setup({ keys = { telescope = { i = { select = '', paste = '', paste_behind = '', replay = '', -- replay a macro delete = '', -- delete an entry custom = {}, }, n = { select = '', paste = 'p', paste_behind = 'P', replay = 'q', delete = 'd', custom = {}, } } } }) end, }, } lvim.builtin.telescope.on_config_done = function(telescope) telescope.load_extension "neoclip" end — Reply to this email directly, view it on GitHub , or unsubscribe . You are receiving this because you were mentioned.Message ID: ***@***.***>
BartOtten commented 2 years ago

The instructions do not seem to work reliably with telescope-project. Have pasted the plugin snippet and added project in the callback, ran :PackerSync a few times to be sure but it rarely works (sometimes is does….)

baipaiha commented 1 year ago

Thanks for lunarvim work ! Since documentation need a lot of man power, maybe a listing of user configuration repository could help ? Currently, i use https://github.com/ChristianChiarulli/lvim as configuration example,

adamwojt commented 4 months ago

The extension points are pain for core plugins. I can't seem to find a way to do it for LuaSnip. The only way is to have dirty git source with custom patches or completely override config for it. I just wanted to add require("luasnip").config.setup({store_selection_keys="<Tab>"}) so that I can use selection snippets.