nvim migration

30 Jan 2025

I avoided Neovim for a long time because of backwards compatibility fear, even from Vim 7x to 8x compatibility is an issue, however this was unfunded, NeoVim could be more compatible with Vim than Vim itself, no need to learn Lua (at the beginning), took a day (a long one) to convert my 10 years old vim configuration to an almost identical setup in NeoVim with increased performance and better functionality.

Pros

Cons

Windows 10/11

Neovim on Windows doesn’t require WSL, it works great if the proper environment is set, a good terminal emulator, fonts, and a gcc compiler to run the heavier plugins at the speed of though.

Dependencies

Chocolatey, a package manager that allows to install the other dependencies in a single command line, from an Admistrator PowerShell prompt:

Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))

Latest version of Powershell to have a better experience within the CLI in general:

choco install powershell-core

A better Terminal emulator:

choco install microsoft-windows-terminal

Fonts, go to Nerd Fonts and download your favorite, Caskaydia Cove is a good one, this would allow to have emojics in the terminal, once downloaded the archive unzip it and install the OTF files to “all users”

Install gcc , git and make to compile plugins and supercharge Nvim (optional):

choco install make
choco install mingw
choco install git

Install nvm and nodejs to setup Github Copilot integration (optional):

Dependencies configuration

Configure the new Terminal to use the latest Powershell and the downloaded fonts:

Installation

Go to neovim/neovim and install the latest version for windows: nvim-win64.msi

NeoVim configuration

Due to Lua support Neovim init.lua configuration is cross platform compatible, you can set / unset preference based on the environment, it can also read vanilla vimscript therefore you could use your same vimrc settings, however unless you’re in a hurry, it’s recommended to migrate to lua at the earneast opportunity.

$ nvim .\AppData\Local\nvim\init.lua

Nvim and Vim support distributions, bundles that provide an advanced and uniformed experience, in terms of programming languages it would be comparable to Ruby on Rails, Django, CakePhp, etc, these distributions are ok if you’re looking for a battery included experience or if you’ve previous experience with Nvim, however as a learning exercise it’s a better to use a minimalist approach and enable only the interested features, this also allows you to customize your usage cases. Some good distribution examples are: lazyvim, nvchad, astronvim.

On this article, the focus would be in starting from a minimalist, single page, documentated approach. Good configurations are personal, built over time with a lot of polish. Get the content of kickstart.nvim/init.lua to .\AppData\Local\nvim\init.lua

Boostrapping

The 1st step is to create a mechanism to download a Nvim plugin manager in case none is locally installed and retrieve the configured plugins with their dependencies, NOTE: This code is already included in the kickstart.nvim file referenced above, it’s shown here only for analysis / understanding. There are several plugins manager but on this article we would refer to lazy.nvim

local lazypath = vim.fn.stdpath 'data' .. '/lazy/lazy.nvim'
if not (vim.uv or vim.loop).fs_stat(lazypath) then
    local lazyrepo = 'https://github.com/folke/lazy.nvim.git'
    local out = vim.fn.system { 'git', 'clone', '--filter=blob:none', '--branch=stable', lazyrepo, lazypath }
    if vim.v.shell_error ~= 0 then
        error('Error cloning lazy.nvim:\n' .. out)
    end
end

Vim traditional configuration

Vim settings are available via lua:

vim.g.mapleader = ","
vim.g.maplocalleader = ","

Or they can be read as vimscripting files:

vim.cmd('source my_vim_script.vim')

Plugin installation

Lazy.nvim support from simple to quite complex escenarios, eg:

require("lazy").setup({
    "javier-lopez/my-autoloads.vim", -- the most simple case, plugins hosted in https://github.com/javier-lopez/my-autoloads.vim
    "javier-lopez/my-mappings.vim",
    { "javier-lopez/my-sensible.vim", -- plugins with dependencies
        dependencies = {
            "javier-lopez/cutils.vim",
        },
    },
    { "javier-lopez/colors.vim", -- plugins with priorization + initial customization
        priority = 1000, -- Make sure to load this before all the other start plugins.
        init = function()
            vim.cmd.colorscheme("hemisu")
        end,
    },
    -- Highlight todo, notes, etc in comments
    { "folke/todo-comments.nvim", -- plugins with lazy loading, plugin would wait until UI is drawn to load
        event = "VimEnter",
        dependencies = { "nvim-lua/plenary.nvim" },
        opts = { signs = false },
    },
)}

Github Copilot integration

Within the hrsh7th/nvim-cmp depency section add zbirenbaum/copilot-cmp and zbirenbaum/copilot.lua:

Once installed run :Copilot auth:

{ "hrsh7th/nvim-cmp", -- Autocompletion plugin
    event = "InsertEnter", -- Lazy load until user enters Insert mode
    dependencies = {
        { "zbirenbaum/copilot-cmp",
            event = "InsertEnter",
            config = function () require("copilot_cmp").setup() end,
            dependencies = { "zbirenbaum/copilot.lua", -- core package that interacts with gh copilot api
                cmd = "Copilot",
                config = function()
                require("copilot").setup({
                    -- It is recommended to disable copilot.lua's suggestion and panel modules, as they can interfere with completions properly appearing in copilot-cmp.
                    suggestion = { enabled = false },
                    panel = { enabled = false },
                    copilot_node_command = vim.fn.expand("$HOME") .. "/AppData/Local/nvm/v23.6.1/node.exe",
                })
                end,
            },
        },
    },
},

Within the nvim-cmp configuration set the <Enter> and <Tabs> keys as suggestion selectors and include the copilot source:

cmp.setup({
    mapping = cmp.mapping.preset.insert({
    --["<C-y>"] = cmp.mapping.confirm({ select = true }),
    ['<CR>'] = cmp.mapping.confirm { select = true }, -- <Enter> accepts the suggestion
    ['<Tab>'] = cmp.mapping.select_next_item(), -- Iterate over suggestions
    ['<S-Tab>'] = cmp.mapping.select_prev_item(), -- <Shift><Tab> Iterate backwards
    ...
    }),
    sources = {
        { name = "path" },
        { name = "calc" },
        { name = "copilot" },
    }
})

CopilotC-Nvim/CopilotChat.nvim integrates Copilot Chat:

{ "CopilotC-Nvim/CopilotChat.nvim",
    -- press <Ctrl-s> to apply instructions
    dependencies = {
        { "zbirenbaum/copilot.lua" },
        { "nvim-lua/plenary.nvim", branch = "master" }, -- for curl, log and async functions
    },
    -- build = "make tiktoken", -- Only on MacOS or Linux
    keys = {
        {
            "<leader>h",
            function()
                return require("CopilotChat").toggle()
            end,
            desc = "CopilotChat - Toggle Main Window",
            mode = { "n", "v" },
        },
        {
            "<leader>oo",
            function()
                return require("CopilotChat").toggle()
            end,
            desc = "CopilotChat - Toggle Main Window",
            mode = { "n", "v" },
        },
        {
            "<leader>ol",
            function()
                return require("CopilotChat").reset()
            end,
            desc = "CopilotChat - Clear Main Window",
            mode = { "n", "v" },
        },
        {
            "<leader>oa",
            function()
                local input = vim.fn.input("Action Over File: ")
                if input ~= "" then
                    require("CopilotChat").ask(input, { selection = require("CopilotChat.select").buffer })
                end
            end,
            desc = "CopilotChat - Action Over File",
        },
    },
},

Happy hacking!