R-nvim / cmp-r

Completion source for nvim-cmp using R.nvim as backend.
GNU General Public License v3.0
19 stars 1 forks source link

E5108: Error executing lua: [string ":lua"]:20: attempt to call global 'has_words_before' (a nil value) #5

Closed maitra closed 8 months ago

maitra commented 8 months ago

I have the following in my .config/nvim/init.vim


call plug#begin('~/.vim/plugged')

Plug 'bluz71/vim-nightfly-colors'
Plug 'ryanoasis/vim-devicons'
Plug 'tpope/vim-rsi'
Plug 'mhartington/formatter.nvim'

Plug 'nvim-treesitter/nvim-treesitter', {'do': ':TSUpdate'}
Plug 'hrsh7th/nvim-cmp'
Plug 'hrsh7th/cmp-nvim-lsp'
Plug 'R-nvim/R.nvim'
Plug 'R-nvim/cmp-r'

"https://jdhao.github.io/2019/03/26/nvim_latex_write_preview/
if has('nvim')
  Plug 'Shougo/deoplete.nvim', { 'do': ':UpdateRemotePlugins' }
else
 Plug 'Shougo/deoplete.nvim'
 Plug 'roxma/nvim-yarp'
 Plug 'roxma/vim-hug-neovim-rpc'
endif
let g:deoplete#enable_at_startup = 1

Plug 'lervag/vimtex'
" Track the engine.
Plug 'SirVer/ultisnips'

" Snippets are separated from the engine. Add this if you want them:
Plug 'honza/vim-snippets'

call plug#end()

" Trigger configuration. You need to change this to something other than <tab> if you use one of the following:
" - https://github.com/Valloric/YouCompleteMe
" - https://github.com/nvim-lua/completion-nvim
let g:UltiSnipsExpandTrigger="<tab>"
let g:UltiSnipsJumpForwardTrigger="<c-b>"
let g:UltiSnipsJumpBackwardTrigger="<c-z>"

" If you want :UltiSnipsEdit to split your window.
let g:UltiSnipsEditSplit="vertical"

:lua << EOF
require("nvim-treesitter.configs").setup({
  sync_install = true,
  ensure_installed = {
    "r",
    "markdown",
    "markdown_inline",
    "rnoweb",
  },
})

local cmp = require("cmp")
cmp.setup({
  sources = {{ name = "cmp_r" }},
  mapping = cmp.mapping.preset.insert({
    ['<CR>'] = cmp.mapping.confirm({ select = false }),
    -- During auto-completion, press <Tab> to select the next item.
    ['<Tab>'] = cmp.mapping(function(fallback)
      if cmp.visible() then
        cmp.select_next_item({ behavior = cmp.SelectBehavior.Insert })
      elseif has_words_before() then
        cmp.complete()
      else
        fallback()
      end
    end, { 'i', 's' }),
    ['<S-Tab>'] = cmp.mapping(function(fallback)
      if cmp.visible() then
        cmp.select_prev_item({ behavior = cmp.SelectBehavior.Insert })
      else
        fallback()
      end
    end, { 'i', 's' }),
  }),
})

require("cmp_r").setup({ })
require("r").setup({ assign_map = "_" })
EOF

However, I get the following error in nvim mode, whenever I press TAB in the insert mode of nvim.

E5108: Error executing lua: [string ":lua"]:20: attempt to call global 'has_words_before' (a nil value)
stack traceback:
        [string ":lua"]:20: in function 'on_keymap'
        /home/maitra/.vim/plugged/nvim-cmp/lua/cmp/core.lua:145: in function 'callback'
        /home/maitra/.vim/plugged/nvim-cmp/lua/cmp/utils/keymap.lua:133: in function </home/maitra/.
vim/plugged/nvim-cmp/lua/cmp/utils/keymap.lua:127>

What is going wrong here? Thanks!

jalvesaq commented 8 months ago

I'm sorry! Trying to simplify the example, I suppressed the function has_words_before. Below is my complete cmp configuration, but I didn't create it; I copied it from somewhere some time ago:

    -- Snippet Engine written in Lua
    {
        'L3MON4D3/LuaSnip',
        dependencies = {
            -- Preconfigured snippets for different languages
            'rafamadriz/friendly-snippets',
            config = function()
                require('luasnip.loaders.from_vscode').lazy_load()
                require('luasnip.loaders.from_lua').load({ paths = { './snippets' } })
            end,
        },
        -- stylua: ignore
        keys = {
            { '<C-l>', function() require('luasnip').expand_or_jump() end, mode = { 'i', 's' } },
        },
        opts = {
            history = true,
            delete_check_events = 'TextChanged',
            -- ft_func = function()
                --  return vim.split(vim.bo.filetype, '.', { plain = true })
                -- end,
            },
            config = function(_, opts)
                require('luasnip').setup(opts)
                vim.api.nvim_create_user_command('LuaSnipEdit', function()
                    require('luasnip.loaders').edit_snippet_files()
                end, {})
            end,
        },

        -----------------------------------------------------------------------------
        -- Completion plugin for neovim written in Lua
        {
            'hrsh7th/nvim-cmp',
            version = false, -- last release is way too old
            event = 'InsertEnter',
            dependencies = {
                -- nvim-cmp source for neovim builtin LSP client
                'hrsh7th/cmp-nvim-lsp',
                -- nvim-cmp source for buffer words
                'hrsh7th/cmp-buffer',
                'hrsh7th/cmp-cmdline',
                -- nvim-cmp source for path
                'hrsh7th/cmp-path',
                -- Luasnip completion source for nvim-cmp
                'saadparwaiz1/cmp_luasnip',
                -- Tmux completion source for nvim-cmp
                -- 'andersevenrud/cmp-tmux',
                { dir = '~/src/cmp-zotcite' },

                { dir = "~/src/cmp-r", },
                'codybuell/cmp-lbdb',

            },
            opts = function()
                local cmp = require('cmp')
                local defaults = require('cmp.config.default')()
                local luasnip = require('luasnip')

                local function has_words_before()
                    if vim.bo.buftype == 'prompt' then
                        return false
                    end
                    local line, col = unpack(vim.api.nvim_win_get_cursor(0))
                    -- stylua: ignore
                    return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match('%s') == nil
                end

                local kind_icons = {
                    Text = "",
                    Method = "󰆧",
                    Function = "󰊕",
                    Constructor = "󱢌",
                    Field = "󰽐",
                    Variable = "α",
                    Class = "󰠱",
                    Interface = "",
                    Module = "",
                    Property = "󰜢",
                    Unit = "",
                    Value = "󰎠",
                    Enum = "",
                    Keyword = "󰌋",
                    Snippet = "",
                    Color = "󰏘",
                    File = "󰈙",
                    Reference = "",
                    Folder = "󰉋",
                    EnumMember = "",
                    Constant = "󰏿",
                    Struct = "",
                    Event = "",
                    Operator = "󰆕",
                    TypeParameter = "",
                }

                return {
                    sorting = defaults.sorting,
                    snippet = {
                        expand = function(args)
                            require('luasnip').lsp_expand(args.body)
                        end,
                    },
                    sources = cmp.config.sources({
                        { name = 'cmp_zotcite' },
                        { name = 'cmp_r' },
                        { name = 'otter' },
                        { name = 'nvim_lsp', priority = 50 },
                        { name = 'path', priority = 40 },
                        { name = 'luasnip', priority = 30 },
                        { name = 'lbdb', priority = 20, filetypes = { 'mail' }},
                    }),

                    formatting = {
                        fields = {'abbr', 'kind', 'menu'},
                        format = function(entry, vim_item)
                            -- Kind icons
                            vim_item.kind = string.format('%s', kind_icons[vim_item.kind])
                            -- Source
                            vim_item.menu = ({
                                latex_symbols = "",
                                lbdb = "lb",
                                otter = "o",
                                nvim_lsp = '',
                                nvim_lua = 'L',
                                luasnip = '',
                                buffer = '﬘',
                                cmdline = ":",
                                path = '',
                                cmp_zotcite = 'Z',
                                cmp_r = 'R'
                            })[entry.source.name] or entry.source.name
                            return vim_item
                        end
                    },

                    mapping = cmp.mapping.preset.insert({
                        -- <CR> accepts currently selected item.
                        -- Set `select` to `false` to only confirm explicitly selected items.
                        ['<CR>'] = cmp.mapping.confirm({ select = false }),
                        ['<S-CR>'] = cmp.mapping.confirm({
                            behavior = cmp.ConfirmBehavior.Replace,
                            select = false,
                        }),
                        ['<C-Space>'] = cmp.mapping.complete(),
                        ['<C-n>'] = cmp.mapping.select_next_item({
                            behavior = cmp.SelectBehavior.Insert,
                        }),
                        ['<C-p>'] = cmp.mapping.select_prev_item({
                            behavior = cmp.SelectBehavior.Insert,
                        }),
                        ['<C-d>'] = cmp.mapping.select_next_item({ count = 5 }),
                        ['<C-u>'] = cmp.mapping.select_prev_item({ count = 5 }),
                        ['<C-f>'] = cmp.mapping.scroll_docs(4),
                        ['<C-b>'] = cmp.mapping.scroll_docs(-4),
                        ['<C-c>'] = function(fallback)
                            cmp.close()
                            fallback()
                        end,
                        ['<Tab>'] = cmp.mapping(function(fallback)
                            if cmp.visible() then
                                cmp.select_next_item({ behavior = cmp.SelectBehavior.Insert })
                            elseif luasnip.locally_jumpable(1) then
                                luasnip.jump(1)
                            elseif has_words_before() then
                                cmp.complete()
                            else
                                fallback()
                            end
                        end, { 'i', 's' }),
                        ['<S-Tab>'] = cmp.mapping(function(fallback)
                            if cmp.visible() then
                                cmp.select_prev_item({ behavior = cmp.SelectBehavior.Insert })
                            elseif luasnip.locally_jumpable(-1) then
                                luasnip.jump(-1)
                            else
                                fallback()
                            end
                        end, { 'i', 's' }),
                    }),
                }
            end,
            config = function(_, opts)
                for _, source in ipairs(opts.sources) do
                    source.group_index = source.group_index or 1
                end

                local cmp = require("cmp")
                cmp.setup(opts)

                -- `/` cmdline setup.
                cmp.setup.cmdline('/', {
                    mapping = cmp.mapping.preset.cmdline(),
                    -- sources = { { name = 'buffer' } }
                })

                -- `:` cmdline setup.
                cmp.setup.cmdline(':', {
                    mapping = cmp.mapping.preset.cmdline(),
                    sources = cmp.config.sources({
                        { name = 'path' },
                        { name = 'cmdline' }
                    })
                })

                require("cmp_r").setup({ })
            end,
        },
maitra commented 8 months ago

No problem. So you do not need to have the treesitter plugin? And I guess that these are the plugins that are needed for your configuration.

- nvim-cmp source for neovim builtin LSP client
                'hrsh7th/cmp-nvim-lsp',
                -- nvim-cmp source for buffer words
                'hrsh7th/cmp-buffer',
                'hrsh7th/cmp-cmdline',
                -- nvim-cmp source for path
                'hrsh7th/cmp-path',
                -- Luasnip completion source for nvim-cmp
                'saadparwaiz1/cmp_luasnip',
                -- Tmux completion source for nvim-cmp
                -- 'andersevenrud/cmp-tmux',
                { dir = '~/src/cmp-zotcite' },

                { dir = "~/src/cmp-r", },
                'codybuell/cmp-lbdb',

I am a few months into nvim and so am learning also at the same time. Thank you for your help!

jalvesaq commented 8 months ago

This is only the cmp part of my config. I split it into 5 files. I may paste my complete lazy.nvim config here if you want, but you will find better examples on the internet:

https://github.com/nvim-lua/kickstart.nvim https://github.com/rafi/vim-config https://astronvim.com/ https://www.lazyvim.org/ https://www.lunarvim.org/

maitra commented 8 months ago

Thanks, I did the following to the previous example of mine:


local cmp = require("cmp")

local function has_words_before()
                    if vim.bo.buftype == 'prompt' then
                        return false
                    end
                    local line, col = unpack(vim.api.nvim_win_get_cursor(0))
                    -- stylua: ignore
                    return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match('%s') == nil
                end

cmp.setup({
  sources = {{ name = "cmp_r" }},
  mapping = cmp.mapping.preset.insert({
    ['<CR>'] = cmp.mapping.confirm({ select = false }),
    -- During auto-completion, press <Tab> to select the next item.
    ['<Tab>'] = cmp.mapping(function(fallback)
      if cmp.visible() then
        cmp.select_next_item({ behavior = cmp.SelectBehavior.Insert })
      elseif has_words_before() then
        cmp.complete()
      else
        fallback()
      end
    end, { 'i', 's' }),
    ['<S-Tab>'] = cmp.mapping(function(fallback)
      if cmp.visible() then
        cmp.select_prev_item({ behavior = cmp.SelectBehavior.Insert })
      else
        fallback()
      end
    end, { 'i', 's' }),
  }),
})

require("cmp_r").setup({ })
require("r").setup({ assign_map = "_" })
EOF

But now while the error goes away, I get: home/maitra/R/x86_64-redhat-linux-gnu-library/4.3" is not writable. Create it now? which I should not be getting since I managed to get nvimcom installed systemwide on my machine. Can I get rid of this? Thanks!

jalvesaq commented 8 months ago

I increased nvimcom version two hours ago: https://github.com/R-nvim/R.nvim/commit/ffc1d52a2b08ea542d2c3c484eea3c7fd9ee4b11 You will get this message whenever I fix any bug in the nvimcom code because, then, I increase its version number to force the update.

maitra commented 8 months ago

Thank you! At least this message does not show up after updating! I will try and look into why the assignment action does not work.

jalvesaq commented 8 months ago

Did you do this?

require("r").setup({ assign_map = "_" })
jalvesaq commented 8 months ago

Note: vim-plug is probably the best plugin manager for Vim, but it seems that everyone is using lazy.nvim on Neovim. Even the author of packer switched to lazy.nvim. You will find many examples of Neovim configuration if you switch to lazy.nvim too.

srearl commented 8 months ago

This is only the cmp part of my config. I split it into 5 files. I may paste my complete lazy.nvim config here if you want, but you will find better examples on the internet:

https://github.com/nvim-lua/kickstart.nvim https://github.com/rafi/vim-config https://astronvim.com/ https://www.lazyvim.org/ https://www.lunarvim.org/

Hi @jalvesaq - Sorry to jump back into this closed thread but it would be very welcome if you would not mind posting your complete configuration. I just started the transition to R.nvim, and it is going generally well with all the major pieces working but I am struggling to get some of the smaller bits, such as viewing data frames with TMUX + Visidata and, even, ensuring that the R window always opens horizontally at the bottom of the screen to work. Thank you, as always, for your tremendous work!

jalvesaq commented 8 months ago

My config:

~/.config/nvim/init.lua:

--- NON PLUGIN OPTIONS ABOVE THIS LINE ---

-- Disable some built-in plugins
vim.g.editorconfig = false
vim.g.loaded_spellfile_plugin = true
vim.g.loaded_2html_plugin = true
vim.g.loaded_tarPlugin = true
vim.g.loaded_tutor_mode_plugin = true
vim.g.loaded_netrwPlugin = true
vim.g.loaded_gzip = true
vim.g.loaded_matchit = true
-- vim.g.loaded_matchparen = true
vim.g.loaded_zipPlugin = true

-- LAZY.NVIM:
-- keys: if defined, the plugin is loaded only when the key binding is pressed, not if called by another plugin.
-- config: if true (default), lazy.nvim will run require'plugin'.setup({})
-- opts: if defined, lazy.nvim will run require'plugin'setup(opts)
-- init: should be a function; if defined, is run before loading the plugin (useful for vimscript plugins relying on global variables).

vim.opt.rtp:prepend("/home/your_user_name/.local/share/nvim/lazy/lazy.nvim")
require("lazy").setup("plugins", {
      change_detection = {
        enabled = false, -- automatically check for config file changes and reload the ui
        notify = false, -- get a notification when changes are found
      },
})

~/.config/nvim/lua/plugins/cmp.lua:

return {

    -- Snippet Engine written in Lua
    {
        'L3MON4D3/LuaSnip',
        dependencies = {
            -- Preconfigured snippets for different languages
            'rafamadriz/friendly-snippets',
            config = function()
                require('luasnip.loaders.from_vscode').lazy_load()
                require('luasnip.loaders.from_lua').load({ paths = { './snippets' } })
            end,
        },
        -- stylua: ignore
        keys = {
            { '<C-l>', function() require('luasnip').expand_or_jump() end, mode = { 'i', 's' } },
        },
        opts = {
            history = true,
            delete_check_events = 'TextChanged',
            -- ft_func = function()
                --  return vim.split(vim.bo.filetype, '.', { plain = true })
                -- end,
            },
            config = function(_, opts)
                require('luasnip').setup(opts)
                vim.api.nvim_create_user_command('LuaSnipEdit', function()
                    require('luasnip.loaders').edit_snippet_files()
                end, {})
            end,
        },

        -----------------------------------------------------------------------------
        -- Completion plugin for neovim written in Lua
        {
            'hrsh7th/nvim-cmp',
            version = false, -- last release is way too old
            event = 'InsertEnter',
            dependencies = {
                'hrsh7th/cmp-nvim-lsp',
                'hrsh7th/cmp-buffer',
                'hrsh7th/cmp-cmdline',
                'hrsh7th/cmp-path',
                -- Luasnip completion source for nvim-cmp
                'saadparwaiz1/cmp_luasnip',
                { dir = '~/src/cmp-zotcite' },

                { dir = "~/src/cmp-r", },
                'codybuell/cmp-lbdb',
            },
            opts = function()
                local cmp = require('cmp')
                local defaults = require('cmp.config.default')()
                local luasnip = require('luasnip')

                local function has_words_before()
                    if vim.bo.buftype == 'prompt' then
                        return false
                    end
                    local line, col = unpack(vim.api.nvim_win_get_cursor(0))
                    -- stylua: ignore
                    return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match('%s') == nil
                end

                local kind_icons = {
                    Text = "",
                    Method = "󰆧",
                    Function = "󰊕",
                    Constructor = "󱢌",
                    Field = "󰽐",
                    Variable = "α",
                    Class = "󰠱",
                    Interface = "",
                    Module = "",
                    Property = "󰜢",
                    Unit = "",
                    Value = "󰎠",
                    Enum = "",
                    Keyword = "󰌋",
                    Snippet = "",
                    Color = "󰏘",
                    File = "󰈙",
                    Reference = "",
                    Folder = "󰉋",
                    EnumMember = "",
                    Constant = "󰏿",
                    Struct = "",
                    Event = "",
                    Operator = "󰆕",
                    TypeParameter = "",
                }

                return {
                    sorting = defaults.sorting,
                    snippet = {
                        expand = function(args)
                            require('luasnip').lsp_expand(args.body)
                        end,
                    },
                    sources = cmp.config.sources({
                        { name = 'cmp_zotcite' },
                        { name = 'cmp_r' },
                        { name = 'otter' },
                        { name = 'nvim_lsp', priority = 50 },
                        { name = 'path', priority = 40 },
                        { name = 'luasnip', priority = 30 },
                        { name = 'lbdb', priority = 20, filetypes = { 'mail' }},
                    }),

                    formatting = {
                        fields = {'abbr', 'kind', 'menu'},
                        format = function(entry, vim_item)
                            -- Kind icons
                            vim_item.kind = string.format('%s', kind_icons[vim_item.kind])
                            -- Source
                            vim_item.menu = ({
                                latex_symbols = "",
                                lbdb = "lb",
                                otter = "o",
                                nvim_lsp = '',
                                nvim_lua = 'L',
                                luasnip = '',
                                buffer = '﬘',
                                cmdline = ":",
                                path = '',
                                cmp_zotcite = 'Z',
                                cmp_r = 'R'
                            })[entry.source.name] or entry.source.name
                            return vim_item
                        end
                    },

                    mapping = cmp.mapping.preset.insert({
                        -- <CR> accepts currently selected item.
                        -- Set `select` to `false` to only confirm explicitly selected items.
                        ['<CR>'] = cmp.mapping.confirm({ select = false }),
                        ['<S-CR>'] = cmp.mapping.confirm({
                            behavior = cmp.ConfirmBehavior.Replace,
                            select = false,
                        }),
                        ['<C-Space>'] = cmp.mapping.complete(),
                        ['<C-n>'] = cmp.mapping.select_next_item({
                            behavior = cmp.SelectBehavior.Insert,
                        }),
                        ['<C-p>'] = cmp.mapping.select_prev_item({
                            behavior = cmp.SelectBehavior.Insert,
                        }),
                        ['<C-d>'] = cmp.mapping.select_next_item({ count = 5 }),
                        ['<C-u>'] = cmp.mapping.select_prev_item({ count = 5 }),
                        ['<C-f>'] = cmp.mapping.scroll_docs(4),
                        ['<C-b>'] = cmp.mapping.scroll_docs(-4),
                        ['<C-c>'] = function(fallback)
                            cmp.close()
                            fallback()
                        end,
                        ['<Tab>'] = cmp.mapping(function(fallback)
                            if cmp.visible() then
                                cmp.select_next_item({ behavior = cmp.SelectBehavior.Insert })
                            elseif luasnip.locally_jumpable(1) then
                                luasnip.jump(1)
                            elseif has_words_before() then
                                cmp.complete()
                            else
                                fallback()
                            end
                        end, { 'i', 's' }),
                        ['<S-Tab>'] = cmp.mapping(function(fallback)
                            if cmp.visible() then
                                cmp.select_prev_item({ behavior = cmp.SelectBehavior.Insert })
                            elseif luasnip.locally_jumpable(-1) then
                                luasnip.jump(-1)
                            else
                                fallback()
                            end
                        end, { 'i', 's' }),
                    }),
                }
            end,
            config = function(_, opts)
                for _, source in ipairs(opts.sources) do
                    source.group_index = source.group_index or 1
                end

                local cmp = require("cmp")
                cmp.setup(opts)

                -- `/` cmdline setup.
                cmp.setup.cmdline('/', {
                    mapping = cmp.mapping.preset.cmdline(),
                    -- sources = { { name = 'buffer' } }
                })

                -- `:` cmdline setup.
                cmp.setup.cmdline(':', {
                    mapping = cmp.mapping.preset.cmdline(),
                    sources = cmp.config.sources({
                        { name = 'path' },
                        { name = 'cmdline' }
                    })
                })

                require("cmp_r").setup({ })
            end,
        },

    }

~/.config/nvim/lua/plugins/lsp.lua:

return {

    {
        -- Must be before lspconfig
        'folke/neodev.nvim', -- completion of vim module and standard lua functions
        config = function ()
            require("neodev").setup({})
        end
    },

    {
        'ray-x/lsp_signature.nvim',
        config = function ()
            require("lsp_signature").setup()
        end
    },

    {
        "williamboman/mason.nvim",
        config = true
    },

    {
        'neovim/nvim-lspconfig',
        config = function ()
            -- Setup lspconfig.
            local cmpcapablts = require('cmp_nvim_lsp').default_capabilities(vim.lsp.protocol.make_client_capabilities())

            local lspcfg = require('lspconfig')

            -- Use an on_attach function to only map the following keys
            -- after the language server attaches to the current buffer
            local on_attach = function(_, bufnr)
                local function buf_set_keymap(...)
                    vim.api.nvim_buf_set_keymap(bufnr, ...)
                end
                -- local function buf_set_option(...) vim.api.nvim_buf_set_option(bufnr, ...) end

                -- Enable language server completion triggered by <c-x><c-o> if not enabling autocompletion
                vim.bo.omnifunc = 'v:lua.vim.lsp.omnifunc'

                -- Mappings.
                local opts = { noremap = true, silent = true }

                -- See `:help vim.lsp.*` for documentation on any of the below functions
                buf_set_keymap('n', 'gD', '<cmd>lua vim.lsp.buf.declaration()<CR>', opts)
                buf_set_keymap('n', 'gd', '<cmd>lua vim.lsp.buf.definition()<CR>', opts)
                buf_set_keymap('n', 'K', '<cmd>lua vim.lsp.buf.hover()<CR>', opts)
                buf_set_keymap('n', 'gi', '<cmd>lua vim.lsp.buf.implementation()<CR>', opts)
                buf_set_keymap('n', '<Leader>wa', '<cmd>lua vim.lsp.buf.add_workspace_folder()<CR>', opts)
                buf_set_keymap('n', '<Leader>wr', '<cmd>lua vim.lsp.buf.remove_workspace_folder()<CR>', opts)
                buf_set_keymap('n', '<Leader>wl', '<cmd>lua print(vim.inspect(vim.lsp.buf.list_workspace_folders()))<CR>', opts)
                buf_set_keymap('n', '<Leader>D', '<cmd>lua vim.lsp.buf.type_definition()<CR>', opts)
                buf_set_keymap('n', '<Leader>rn', '<cmd>lua vim.lsp.buf.rename()<CR>', opts)
                buf_set_keymap('n', '<Leader>ca', '<cmd>lua vim.lsp.buf.code_action()<CR>', opts)
                buf_set_keymap('n', 'gr', '<cmd>lua vim.lsp.buf.references()<CR>', opts)
                buf_set_keymap('n', '<Leader>e', '<cmd>lua vim.diagnostic.open_float()<CR>', opts)
                buf_set_keymap('n', '[d', '<cmd>lua vim.diagnostic.goto_prev()<CR>', opts)
                buf_set_keymap('n', ']d', '<cmd>lua vim.diagnostic.goto_next()<CR>', opts)
                buf_set_keymap('n', '<Leader>q', '<cmd>lua vim.diagnostic.setloclist()<CR>', opts)
                buf_set_keymap('n', '<Leader>f', '<cmd>lua vim.lsp.buf.formatting()<CR>', opts)

            end

            vim.diagnostic.config({
                severity_sort = true,
            })

            -- map buffer local keybindings when the language server attaches
            lspcfg['pyright'].setup { on_attach = on_attach, flags = { debounce_text_changes = 150 }, capabilities = cmpcapablts }
            lspcfg['clangd'].setup { on_attach = on_attach, flags = { debounce_text_changes = 150 }, capabilities = cmpcapablts }
            lspcfg['vimls'].setup { on_attach = on_attach, flags = { debounce_text_changes = 150 }, capabilities = cmpcapablts }

            -- Grammarly requires node 16:
            -- https://github.com/znck/grammarly/issues/334
            lspcfg['grammarly'].setup {
                on_attach = on_attach,
                flags = { debounce_text_changes = 150 },
                capabilities = cmpcapablts,
                cmd = { "n", "run", "16", "/usr/local/bin/grammarly-languageserver", "--stdio" },
                filetypes = {"grammar"},
                init_options = { clientId = 'client_BaDkMgx4X19X9UxxYRCXZo', }, -- default client id from grammarly extension
            }
            -- lspcfg['r_language_server'].setup { on_attach = on_attach, flags = { debounce_text_changes = 150 }, capabilities = cmpcapablts }

            -- Segundo comentário na internet, ltex com n-grams é tão bom
            -- quanto grammarly para correção gramatical.
            -- Não instalar porque tem memory leak
            -- lspcfg['ltex'].setup { }

            -- local runtime_path = vim.split(package.path, ';')
            -- table.insert(runtime_path, "lua/?.lua")
            -- table.insert(runtime_path, "lua/?/init.lua")

            lspcfg['lua_ls'].setup {
                on_attach = on_attach,
                flags = { debounce_text_changes = 150 },
                capabilities = cmpcapablts,
                settings = {
                    Lua = {
                        workspace = {
                            -- Make the server aware of Neovim runtime files
                            library = {
                                -- for neovim plugins:
                                vim.env.VIMRUNTIME,
                                -- "~/.local/share/nvim/lazy/neodev.nvim/types/stable",
                                "~/.local/share/nvim/lazy/neodev.nvim/types/nightly",
                            },
                            -- Diable the message "Do you need to configure your environment as luassert"
                            -- See: https://github.com/sumneko/lua-language-server/discussions/1688
                            -- checkThirdParty = false,

                            -- Required by obslua.lua
                            -- preloadFileSize = 500000,
                        },
                        runtime = {
                            -- Tell the language server which version of Lua you're using (most likely LuaJIT in the case of Neovim)
                            version = 'LuaJIT',
                            -- Setup your lua path
                            -- path = runtime_path,
                            path = {
                                "lua/?.lua",
                                "lua/?/init.lua",
                            }
                        },
                        completion = { callSnippet = "Replace" },
                        telemetry = {
                            enable = false,
                        },
                        diagnostics = { globals = { 'vim' }} },
                },
            }

            vim.fn.sign_define('DiagnosticSignError',
                { text = '✘', texthl = 'LspDiagnosticsDefaultError' })

            vim.fn.sign_define('DiagnosticSignWarn',
                { text = '▲', texthl = 'LspDiagnosticsDefaultWarning' })

            vim.fn.sign_define('DiagnosticSignInfo',
                { text = 'I', texthl = 'LspDiagnosticsDefaultInformation' })

            vim.fn.sign_define('DiagnosticSignHint',
                { text = '⚑', texthl = 'LspDiagnosticsDefaultHint' })
        end
    },

    {
        'nvimtools/none-ls.nvim',
    dependencies = 'nvim-lua/plenary.nvim',
        config = function ()
            local null_ls = require("null-ls")
            null_ls.setup({
                sources = {
                    null_ls.builtins.formatting.stylua,
                    null_ls.builtins.diagnostics.selene,
                },
            })
        end
    },
}

~/.config/nvim/lua/plugins/edit.lua:

return {

    {
        'windwp/nvim-autopairs',
        event = "InsertEnter",
        config = function ()
            require('nvim-autopairs').setup({ check_ts = true })
            require("nvim-autopairs").remove_rule("`")
            -- Make nvim-autopairs compatible with Rmd and Quarto
        end
    },

    {
        "sbdchd/neoformat",
    },

    {
        "danymat/neogen",
        dependencies = "nvim-treesitter/nvim-treesitter",
        config = true,
    },

    -- Disable slow features when opening big files
    "LunarVim/bigfile.nvim",

    {
        "numToStr/Comment.nvim",
        config = function ()
            require('Comment').setup({
                ignore = '^$',
                mappings = { basic = true, extra = false },
            })
        end
    },

    {
        "nvim-treesitter/nvim-treesitter",
        tag = nil,
        branch = "master",
        run = ":TSUpdate",
        config = function ()
            require("nvim-treesitter.configs").setup({
                sync_install = true,
                ensure_installed = {
                    "c",
                    "r",
                    "python",
                    "markdown",
                    "markdown_inline",
                    "bash",
                    "yaml",
                    "json",
                    "lua",
                    "vim",
                    "query",
                    "vimdoc",
                    "latex",
                    "rnoweb",
                    "html",
                    "css"
                },
                highlight = {
                    enable = true,
                    -- disable = { "rnoweb", "help" }
                },
                -- indent = {
                --     enable = true,
                --     -- disable = { "r", "markdown" },
                -- },
                incremental_selection = {
                    enable = true,
                    keymaps = {
                        init_selection = "gnn",
                        node_incremental = "grn",
                        scope_incremental = "grc",
                        node_decremental = "grm",
                    },
                },
            })
            vim.o.foldmethod = "expr"
            vim.o.foldexpr = "nvim_treesitter#foldexpr()"
            vim.o.foldenable = false
        end
    },

    {
        "FabijanZulj/blame.nvim",
        init = function ()
            vim.api.nvim_create_user_command("GitBlameToggle", function(args)
                require("blame").toggle(args)
            end, { nargs = "*" })
        end
    },

    "sindrets/diffview.nvim",

    -- { 'tpope/vim-fugitive', config = false},

    {
        'echasnovski/mini.align',
        version = '*',
        config = function ()
            require('mini.align').setup({ mappings = { start = '', start_with_preview = 'A' }})
            vim.cmd("nunmap A")
        end
    },

    -- vd is better:
    -- 'vim-scripts/csv.vim',

    -- Super powerful color picker/colorizer plugin
    {
    'uga-rosa/ccc.nvim',
    event = 'FileType',
    keys = {
        { '<Leader>tc', '<cmd>CccPick<CR>', desc = 'Color-picker' },
    },
    opts = {
        highlighter = {
        auto_enable = true,
        lsp = true,
        excludes = { 'lazy', 'mason', 'help', 'neo-tree' },
        },
    },
    },

    -- {
    --     --  Colorize color code such as #87d7ff
    --     'norcalli/nvim-colorizer.lua',
    --     config = function ()
    --         if vim.o.termguicolors then
    --             require'colorizer'.setup {
    --                 'css',
    --                 'html',
    --                 html = { mode = 'foreground' }
    --             }
    --         end
    --     end
    -- },

    { 'jamessan/vim-gnupg', config = false},

    {
        'vim-scripts/CheckAttach.vim',
        init = function ()
            vim.g.checkattach_once = 'y'
            vim.g.attach_check_keywords = 'segue,anex'
        end,
        config = false
    },

    -- Syntax highlighting and auto template for fish functions
    { 'dag/vim-fish', config = false},

    {
        dir = '~/src/vimcmdline',
        ft = {"python", "lua", "sh"},
        init = function ()
            vim.g.cmdline_app = {
                sh = 'bash',
                -- python = '~/.py3env/bin/python3',
            }
            vim.g.cmdline_outhl = { r = 0 }
            vim.g.cmdline_map_send           = '<Enter>'
            vim.g.cmdline_map_send_and_stay  = '<LocalLeader><Enter>'
        end,
        config = false
    },

    -- Não existe nenhum outro plugin usando Wordnet do dictd. Na minha avaliação,
    -- o wordnet se mostrou a melhor combinação de dicionário+thesaurus e o dictd, por
    -- ser um daemon, a forma mais rápida de obter resultados.
    {
        dir = '~/src/dict.nvim',
        keys = {
            {
                '<Leader>d',
                '<Cmd>lua require("dict").lookup()<CR>',
                desc = "Dict: look up the word under cursor"
            },
        },
    },
}

~/.config/nvim/lua/plugins/r.lua:

return {

    -- R related
    -- 'mllg/vim-devtools-plugin',

    {
        "quarto-dev/quarto-nvim",
        dev = false,
        dependencies = {
            { "nvim-treesitter/nvim-treesitter-textobjects" },
            {
                "jmbuhr/otter.nvim",
                dev = false,
                dependencies = { { "neovim/nvim-lspconfig" } },
                opts = {
                    buffers = {
                        -- if set to true, the filetype of the otterbuffers will be set.
                        -- otherwise only the autocommand of lspconfig that attaches
                        -- the language server will be executed without setting the filetype
                        set_filetype = true,
                    },
                },
            },
        },
        config = function ()
            require('quarto').setup({
                lspFeatures = { languages = { "r", "python" } },
                keymap = { },
            })
        end
    },

    -- Background of code blocks in Quarto documents
    {
        "lukas-reineke/headlines.nvim",
        dependencies = "nvim-treesitter/nvim-treesitter",
        config = function ()
            require("headlines").setup({
                quarto = {
                    query = vim.treesitter.query.parse(
                    "markdown",
                    [[ (fenced_code_block) @codeblock ]]
                    ),
                    treesitter_language = "markdown",
                    codeblock_highlight = "CodeBlock",
                },
            })
        end
    },

    { dir = '~/src/zotcite', config = false },

    {
        dir = '~/src/R.nvim',
        dependencies = 'windwp/nvim-autopairs',
        config = function ()
            local opts = {
                hook = {
                    after_config = function ()
                        if vim.o.syntax ~= "rbrowser" then
                            vim.api.nvim_buf_set_keymap(0, "n", "<Enter>", "<Plug>RDSendLine", {})
                            vim.api.nvim_buf_set_keymap(0, "v", "<Enter>", "<Plug>RSendSelection", {})
                        end
                    end,
                },
                min_editor_width = 78,
                external_term = "foot",
                rconsole_width = 78,
                listmethods = true,
                R_args = {'--quiet', '--no-save'},
                -- R_args = {"-d", "'valgrind --log-file=/dev/shm/valgrind_log_R'"},
                disable_cmds = {
                        "RClearConsole",
                        "RCustomStart",
                        "RDputObj",
                        "RInsertLineOutput",
                        "RMakeHTML",
                        "RMakeODT",
                        "RMakePDFK",
                        "RMakePDFKb",
                        "RMakeWord",
                        "RSPlot",
                        "RSaveClose",
                        "RViewDFa",
                        "RViewDFs",
                        "RViewDFv",
                    },
                }
                if vim.env.R_AUTO_START == "true" then
                    opts.auto_start = "on startup"
                    opts.objbr_auto_start = false
                end
                require("r").setup(opts)
            end,
            lazy = false
        },
    }

~/.config/nvim/lua/plugins/ui.lua:

return {
    {
        "goolord/alpha-nvim",
        dependencies = { "nvim-tree/nvim-web-devicons" },
        config = function()
            local alpha = require("alpha")
            local dashboard = require("alpha.themes.dashboard")

            -- Set header
            dashboard.section.header.val = {
                [[##    ## ########  #######  ##     ## #### ##     ##]],
                [[###   ## ##       ##     ## ##     ##  ##  ###   ###]],
                [[####  ## ##       ##     ## ##     ##  ##  #### ####]],
                [[## ## ## ######   ##     ## ##     ##  ##  ## ### ##]],
                [[##  #### ##       ##     ##  ##   ##   ##  ##     ##]],
                [[##   ### ##       ##     ##   ## ##    ##  ##     ##]],
                [[##    ## ########  #######     ###    #### ##     ##]],
            }

            -- Set menu
            dashboard.section.buttons.val = {
                dashboard.button("e", "  > New file",  "<Cmd>ene <BAR> startinsert <CR>"),
                dashboard.button("r", "  > Recent",    "<Cmd>Telescope oldfiles<CR>"),
                dashboard.button("f", "  > Find file", "<Cmd>Telescope find_files<CR>"),
                dashboard.button("t", "  > Find Text", "<Cmd>Telescope live_grep<CR>" ),
                dashboard.button("s", "  > Settings",  "<Cmd>e $MYVIMRC<CR>"),
                dashboard.button("p", "  > Plugins",   "<Cmd>e ~/.config/nvim/lua/plugins<CR>"),
                dashboard.button("R", "󰟔  > R.nvim",    "<Cmd>e ~/.config/nvim/lua/plugins/r.lua<CR>"),
                dashboard.button("q", "  > Quit NVIM", "<Cmd>qa<CR>"),
            }

            -- Send config to alpha
            alpha.setup(dashboard.opts)
            vim.keymap.set('n', '<Leader>a', '<Cmd>Alpha<cr>')
        end
    },

    'nvim-telescope/telescope-ui-select.nvim',

    {
        'nvim-telescope/telescope.nvim',
        config = function ()
            vim.keymap.set('n', '<Leader>ff', '<Cmd>Telescope find_files<cr>')
            vim.keymap.set('n', '<Leader>fb', '<Cmd>Telescope buffers<cr>')
            vim.keymap.set('n', '<Leader>fh', '<Cmd>Telescope help_tags<cr>')
            require("telescope").setup {
                extensions = {
                    ["ui-select"] = {
                        require("telescope.themes").get_dropdown {
                            -- even more opts
                        }
                    }
                }
            }
            require("telescope").load_extension("ui-select")
        end
    },

    {
        'rcarriga/nvim-notify',
        config = function ()
            -- Overriding vim.notify with fancy notify
            local notify = require("notify")
            vim.notify = notify
            notify.setup()
            vim.keymap.set('',  '<Esc>', "<ESC>:noh<CR>:lua require('notify').dismiss()<CR>", {silent = true})
        end
    },

    {
        "nvim-tree/nvim-tree.lua",
        config = function ()
            require("nvim-tree").setup()
            vim.keymap.set('n', '<Leader>m', '<Cmd>NvimTreeToggle<CR>')
        end,
    },

    {
        "nvim-lualine/lualine.nvim",
        config = function()
            local rstt =
            {
                { "-", "#aaaaaa" }, -- 1: ftplugin/* sourced, but nclientserver not started yet.
                { "S", "#757755" }, -- 2: nclientserver started, but not ready yet.
                { "S", "#118811" }, -- 3: nclientserver is ready.
                { "S", "#ff8833" }, -- 4: nclientserver started the TCP server
                { "S", "#3388ff" }, -- 5: TCP server is ready
                { "R", "#ff8833" }, -- 6: R started, but nvimcom was not loaded yet.
                { "R", "#3388ff" }, -- 7: nvimcom is loaded.
            }

            local rstatus = function ()
                if not vim.g.R_Nvim_status or vim.g.R_Nvim_status == 0 then
                    -- No R file type (R, Quarto, Rmd, Rhelp) opened yet
                    return ""
                end
                return rstt[vim.g.R_Nvim_status][1]
            end

            local rsttcolor = function ()
                if not vim.g.R_Nvim_status or vim.g.R_Nvim_status == 0 then
                    -- No R file type (R, Quarto, Rmd, Rhelp) opened yet
                    return { fg = "#000000" }
                end
                return { fg = rstt[vim.g.R_Nvim_status][2] }
            end

            local selectionCount = function ()
                local m = vim.fn.mode()
                if m == "v" or m == "V" then
                    local c = vim.fn.wordcount()
                    return tostring(c.visual_words) .. " Words, " .. tostring(c.visual_chars) .. " Chars"
                end
                return ""
            end

            require("lualine").setup({
                options = {
                    section_separators = "",
                    component_separators = "",
                    globalstatus = true,
                },
                sections = {
                    lualine_a = { "mode" },
                    lualine_b = { {'branch', icon = ''}, "diagnostics" },
                    lualine_c = { "filename", "searchcount",  },
                    lualine_x = { selectionCount },
                    lualine_y = { {rstatus, color = rsttcolor }},
                    lualine_z = { "progress", "location" },
                },
                extensions = { "nvim-tree" },
            })
        end,
    },

    -- 'kshenoy/vim-signature',
    -- There is a neovim bug that requires :wshada! to persistently delete the marks
    {
        'chentoast/marks.nvim',
        -- NOTE: Devido a bug do neovim (https://github.com/neovim/neovim/issues/4295) é
        -- preciso executar o comando :wshada! para deletar as marcas permanentemente.
        opts = {
            default_mappings = false,
            force_write_shada = false,
            refresh_interval = 250,
            mappings = {}
        }
    },

    -----------------------------------------------------------------------------
    -- Create key bindings that stick
    {
    'folke/which-key.nvim',
    event = 'VeryLazy',
    opts = {
        icons = { separator = ' 󰁔 ' },
        plugins = { spelling = true },
        defaults = {
        mode = { 'n', 'v' },
        ['<Leader>t'] = { name = '+tools' },
        },
    },
    config = function(_, opts)
        local wk = require('which-key')
        wk.setup(opts)
        wk.register(opts.defaults)
    end,
    },

    -- Outliner
    {
    'stevearc/aerial.nvim',
    opts = {
        layout = {
        -- These control the width of the aerial window.
        -- They can be integers or a float between 0 and 1 (e.g. 0.4 for 40%)
        -- min_width and max_width can be a list of mixed types.
        -- max_width = {40, 0.2} means "the lesser of 40 columns or 20% of total"
        max_width = { 60, 0.4 },
        -- When the symbols change, resize the aerial window (within min/max constraints) to fit
        resize_to_content = true,
        }
    },
    -- Optional dependencies
    dependencies = {
        "nvim-treesitter/nvim-treesitter",
        "nvim-tree/nvim-web-devicons"
    },
    keys = {
        { '<leader>o', '<Cmd>AerialToggle!<CR>', desc = "Toggle Aerial outliner" }
    },
    },
}