DEV Community

Cover image for Neovim for beginners
Samriddha Chattopadhyay
Samriddha Chattopadhyay

Posted on

Neovim for beginners

So Here's the thing. I had planned to write one blog a week, but due to some reasons, I couldn't do much research on the thing I wanted to write about. So instead of writing a half-assed blog about a topic I don't know much about, I am writing a blog on something that I do know some things about. And yes, that's Neovim. It's a text editor I have been using in the past, and have always come back to even after I thought I would never return to it at some point.

My Neovim Journey

Before going into the actual content, I would like to share my neovim journey first.
I first started using neovim three years back. I was in the second year of my college at the time and was learning linux. Due to some issues (both hardware and software), VS Code wasn't running properly and I was having to use Sublime text (which is a fantastic tool imo). However, I was just not content with using Sublime. Something felt wrong and so, I started searching for other editors. I also tried Brackets, Atom and Eclipse in the process and none of them suited my taste. That was when I discovered vim (yes vim, not neovim).

The baby steps

I started using vim and started learning the shortcuts and keybinds. Eventually, I also wrote a config file (init.vim.....Ah.....this brings back memories) and was pretty content with it. But something still felt lacking. I had started watching Primeagen and in one of his videos, I heard about neovim. I was curious, so I checked it out. I had heard that it's a fork of vim and has a lot of extra stuff (like native lsp support and all), but I wasn't too happy with it as it felt mostly same to me. However, all that changed when I discovered something new.

The world of linux ricing

As I mentioned earlier, I was learning linux and to no surprise, I came across linux ricing soon. At the time, I was using a ubuntu installation on my HP pavilion laptop, and I was pretty bored with the stock gnome look. I had tried to customize with some plugins and stuff, but I still didn't like it at the time. That was when I came across Distrotube channel and learnt about linux ricing. I started installing different window managers and bars and run launchers and started tinkering with config files. One of the first window managers I used was the awesome window manager. It's configured in lua, the same language used to configure neovim. That was when I learnt lua properly. However, I wasn't content with just my desktop looking good. I wanted my editor to match the theme and that was when I started using Lunar vim.

The first ever neovim distro

For people who don't know, Lunarvim is a distro for neovim. That is, it's a specific set of config made by @chrisatmachine. At the time, it was in it's very early stage and I got to see the exact configs without much abstractions. Being a nerd, I dug into it and within a month, figured out how things worked. Once that happened, I made my very first config file (custom one) and started daily driving it.

Making my own demon

We create our own demons

- Tony Stark

After I made my own config, I started daily driving it. However, I started to notice that my productivity had gone down instead of going up.
The reason was that, I was always updating my config. Everytime I stareted working, I would come across some issue and I would start tinkering with my config and I would spend a lot of time in the process. And this was not restricted to neovim. I would also make changes to my window manager config, my polybar config and rofi configs from time to time, but these were still rare. Neovim was something I used frequently, and I noticed a lot of issues with it (yeah I was a noob at the time, and still am xD) and started fixing them. This made me lose a lot of time in a day and it was starting to affect my productivity and work as well. So, I shifted to Nvchad.

The Chad config

Nvchad was the setup of my dreams. It looked so similar to VS Code, but was in vim and I loved it. I started daily driving it, and I used it for the longest ( I think 5-6 months). Again, being a nerd, I used to go through it's code and figure out how things worked and I would change some things here and there to see how it affected the setup. These 6 months, I learned the most about Neovim. I learnt about buffers, windows, tabs and the differences between those. I learnt how the keybindings actually worked (yeah earlier I mostly copy pasted, so I wasn't sure how things were actually working), I learned about neovim auto commands, and the list goes on.
There was another thing I leant from this, and that is actually what this blog is about. I learnt what I actually needed to have an usable setup.

Wrting a config from scratch

Yes, I made another config (I somehow managed to lose all the code for the config, though ๐Ÿ˜…) and this time, it helped increase my productivity by a lot. "But Samy, I thought you liked Nvchad. Then why did you bother writing your own config ?" is what you'd like to ask, and here's my answer:

  • Nvchad was too much similar to vs code and was bloated with stuff that I didn't use. In fact, it reminded me of VS Code and for some reason, I wasn't able to focus on my work at one point (yeah that's kind of a lame reason)
  • I think neovim is a personalized editor, and I would like something that actually catered to my needs (minimal, fast and reliable). So I went on and made my own custom config and I used it for the rest of the time I was using linux. Yes. I am on windows now, and I have another config at the moment.

Getting my very own laptop

"Wait I think you said you had your laptop!" is what careful readers would say. Yes, I had one, but I had stored up enough money to buy a gaming laptop (the former one being more of an office laptop, couldn't run games smoothly) and yes I like gaming. So I got myself a gaming laptop, and yeah I had to use windows in it because linux gaming sucked (it still does imo, but that's a conversation for later). I loved my new laptop but I hated windows and I cursed it for around 2-3 months as there were not many tools that I used to use which were available on windows. However, at some point, I realized that I could just make some if I wanted to use them. If you're interested, please let me know in the comments and I would love to share some stuff I did to get back my linux setup (or at least something close to it) on windows. I installed neovim and made my new setup.

Basic parts of a neovim config

Okay, that's enough of backstory. Let's get to the point now.
To have a basic setup, you need the following:

  1. A terminal and a neovim installation (duh)
  2. Some sensible settings (details below)
  3. Some sensible keybinds (details below)
  4. Treesitter
  5. A fuzzy finder (Telescope, for example)
  6. Your required plugins (this depends on the user, but I will share the ones I use below and some essential ones)

As you can see, I didn't mention a colorscheme and a file tree. This is because, I believe a file tree is not much important when you have a fuzzy finder, however, you can have one if you want (even I use one and I have included that under 6). So let's see how to configure each of these. I will discuss how I configured them and I will link documentations for each of them. I highly suggest reading the documentation for details. This blog will simply cover the surface so that it's simple to understand, however, to really understand how things work, reading the docs is a must.

A terminal and a neovim installation

This should be a no brainer. Neovim is a terminal text editor, and so, having a terminal is a must. There are some GUI apps to load neovim as well, and you are free to use them too. It's just that my experience with them have been a little lacking as sometimes, they tend to slow down a lot (In my case, it happened when I opened a lot of files and had a lot of plugins) or sometimes, they have certain configs diabled (for example, transparent background would sometimes be disabled for some reason, etc).
My choice of terminal for my current windows workflow is the OG Windows Terminal. I am daily driving my neovim in it for about 6 months now and I have yet to face any issue. Of course, I had to change some keybinds in it to make things work (For example, I disabled the Ctrl+V as it was conflicting with neovim's visual block mode).
To set up neovim in windows, first, install neovim. I used winget to do that:

$ winget install Neovim.Neovim
Enter fullscreen mode Exit fullscreen mode

You can also use chocolatey or scoop or any other package manager you use. Once neovim is installed, you need to write your config in C:\Users\_username_\AppData\Local\nvim folder. If the folder doesn't exist, you can create one. All your config will reside inside the init.lua file within the folder. This is the folder structure I use:

โ”œโ”€โ”€โ”€lua
โ”‚   โ”œโ”€โ”€โ”€config
โ”‚   โ”‚   โ”œโ”€โ”€โ”€comments.lua
โ”‚   โ”‚   โ”œโ”€โ”€โ”€context.lua
โ”‚   โ”‚   โ”œโ”€โ”€โ”€git.lua
โ”‚   โ”‚   โ”œโ”€โ”€โ”€harpoon.lua
โ”‚   โ”‚   โ”œโ”€โ”€โ”€illuminate.lua
โ”‚   โ”‚   โ”œโ”€โ”€โ”€init.lua
โ”‚   โ”‚   โ”œโ”€โ”€โ”€lazy.lua
โ”‚   โ”‚   โ”œโ”€โ”€โ”€lsp.lua
โ”‚   โ”‚   โ”œโ”€โ”€โ”€lualine.lua
โ”‚   โ”‚   โ”œโ”€โ”€โ”€telescope.lua
โ”‚   โ”‚   โ”œโ”€โ”€โ”€treesitter.lua
โ”‚   โ”‚   โ”œโ”€โ”€โ”€trouble.lua
โ”‚   โ”‚   โ””โ”€โ”€โ”€undotree.lua
โ”‚   โ””โ”€โ”€โ”€samy
โ”‚   โ”‚   โ”œโ”€โ”€โ”€autocommands.lua
โ”‚   โ”‚   โ”œโ”€โ”€โ”€git.lua
โ”‚   โ”‚   โ”œโ”€โ”€โ”€init.lua
โ”‚   โ”‚   โ”œโ”€โ”€โ”€keybinds.lua
โ”‚   โ”‚   โ””โ”€โ”€โ”€settings.lua
โ””โ”€โ”€โ”€undodir
Enter fullscreen mode Exit fullscreen mode

Please ignore all the different files for now. As a general rule, this is what I follow:
Any plugin config goes into config/.
Any of my own config goes into samy/.
This helps me keep the code clean (it's not that clean, though ๐Ÿ˜…)

Some sensible settings

I shall divide this into some sub-sections to make things easier to understand.

Line numbers

There are three types for line numbers in neovim:

  1. No line numbers (default) no-line-numbers-demo
  2. Regular line numbers regular-line-numbers-demo
  3. Relative line numbers relative-line-numbers-demo I personally use a mixture of regular and relative line numbers. This is how I achieve it:
local opts = vim.opt

-- Line Numbers
opts.nu = true
opts.relativenumber = true
Enter fullscreen mode Exit fullscreen mode

And this is how it looks:
mixed-line-numbers-demo
Notice how the actual line number on the actual line is visible and the rest are numbered on the basis of the active line. This helps in navigating code within the file.
Let's say, I need to go to General settings portion. Instead of hitting j multiple times, I can hit 13j and go there directly. I know it's 13j and not 25j or 6j because I can see the relative line number for the line is 13!

Indentation

This should be straightforward. Indenting code is a part of our daily lives and let's be honest, indented code looks kinda sexy (Just kidding I don;t get turned on my code indentation). Personally, I like 4 space indenting as it tells me when I should made a modular function for the logic or when I am writing bad code (trust me, it's easier to spot bad code with a 4 space indenting). So these are the setting I use for specifying my indentation:

-- Indenting
opts.tabstop = 4
opts.softtabstop = 4
opts.shiftwidth = 4
opts.expandtab = true
opts.smartindent = true
Enter fullscreen mode Exit fullscreen mode

Search and other general settings

Please note that everything I say under this section is a personal opinion. You might not agree with me, and that's fine, so feel free to change things here according to your taste.
I like my code wrapped, hence, this line:

-- General Settings
opts.wrap = false
Enter fullscreen mode Exit fullscreen mode

I like to keep swapfile off. What this does is, it makes neovim to not store the buffers in case they are not closed properly. So, I add these lines:

opts.swapfile = false
opts.backup = false
Enter fullscreen mode Exit fullscreen mode

The next two lines are related to a plugin called undotree, and I would recommend using this one (details further down):

opts.undodir = "C:\\Users\\samy3\\AppData\\Local\\nvim\\undodir"
opts.undofile = true
Enter fullscreen mode Exit fullscreen mode

I like the current line to be highlighted and hence, I add this:

opts.cursorline = true
Enter fullscreen mode Exit fullscreen mode

The following two lines configure highlights after search and incremental search:

-- Searching
opts.hlsearch = false
opts.incsearch = true
Enter fullscreen mode Exit fullscreen mode

hlsearch = false makes sure that the searches are not highlighted after the search is complete (this just looks weird, so I keep it off. Please try keeping it on and see for yourself if it looks good).
incsearch = true ensures that the searches are highlighted while the search is happening. For example, when I hit / and write sea, all instances of sea will be highlighted. This is not a default behavior and needs to be enabled (yeah this was a shock to me too).

Some sensible keybinds

I won't say much here. Keybinds and shortcuts are personal stuff, so I would simply share what I use and some tips to customize them.
These are what I use:

vim.g.mapleader = " "

local keymap = vim.keymap

-- keymap.set("n", "<leader>pv", vim.cmd.Ex)

-- VS Code style shifting
keymap.set("v", "J", ":m '>=1<CR>gv=gv")
keymap.set("v", "K", ":m '<-2<CR>gv=gv")

-- Keybindings to make life easy
keymap.set("i", "jj", "<Esc>")
keymap.set("n", "Y", "yg$")
keymap.set("n", "J", "mzJ`z")
keymap.set("n", "<C-d>", "<C-d>zz")
keymap.set("n", "<C-u>", "<C-u>zz")
keymap.set("n", "n", "nzzzv")
keymap.set("n", "N", "Nzzzv")

-- Copy paste Keybindings for various utilities
keymap.set("x", "<leader>p", "\"_dP")
keymap.set("n", "<leader>y", "\"+y")
keymap.set("v", "<leader>y", "\"+y")
keymap.set("n", "<leader>Y", "\"+Y")
keymap.set("n", "<leader>d", "\"_d")
keymap.set("v", "<leader>d", "\"_d")

-- More life enhancement Keybindings
keymap.set("n", "Q", "<nop>")
keymap.set("n", "<leader>f", function ()
    vim.lsp.buf.format({ async = true })
end)
keymap.set("n", "<leader>x", "<cmd>!chmod +x %<CR>", { silent = true })

-- Error navigation
keymap.set("n", "<C-k>", "<cmd>cprev<CR>", { silent = true })
keymap.set("n", "<C-j>", "<cmd>cnext<CR>", { silent = true })

-- Buffer navigation
keymap.set("n", "<leader>bx", "<cmd>bdelete<CR>", { silent = true })
keymap.set("n", "<leader>bb", "<cmd>bnext<CR>", { silent = true })
keymap.set("n", "<leader>bB", "<cmd>bprev<CR>", { silent = true })
Enter fullscreen mode Exit fullscreen mode

Disclaimer: Some of these are copied from Primeagen's config, so if you're a viewer and were wondering why they looked familiar, you know now.

Here's an explanation of what's going on:

  1. vim.g.mapleader sets the leader key. This is similar to a mod key, but for neovim. SUPER, META, ALT, CTRL and SHIFT are also available, but it's generally not a good idea to set these as leader keys. A lot of people use SPACE as leader key, and so do I.
  2. keymap.set sets a keybind. To do so, all you need to do is specify the mode, the key (or skystorkes) you want to bind and the key or function or command you want to bind it to. We use n for Normal Mode, i for Insert mode, v or x for different visual modes and t for terminal mode.

If you want to copy my keybinds, here's a summary of what you're going to have:

Mode Key Effect
Normal Y Yank till end of line
Normal J Join the current and the next line and keep the cursor on the last line
Normal Ctrl + d Scroll down half a page and keep the cursor at the center of the page
Normal Ctrl + u Scroll up half a page and keep the cursor at the center of the page
Normal n Go to next search result
Normal N Go to previous search result
Normal Leader + y Copy to clipboard
Normal Leader + y Copy entire line to clipboard
Normal Leader + d Delete and remove from yank buffer
Normal Leader + f Format document using the active LSP
Normal Leader + x Make current script executable (works only in linux)
Normal Ctrl + j Go to next error
Normal Ctrl + k Go to previous error
Normal Ctrl + bx Close buffer
Normal Ctrl + bb Go to next buffer
Normal Ctrl + bB Go to previous buffer
Insert1 jj Exit insert mode
Visual J Move lines down
Visual K Move lines up
Visual Leader + y Copy selection to clipboard
Visual Leader + d Delete selection and remove from yank buffer
Visual Leader + x Paste without copying the selection

Treesitter

Treesitter is what makes your neovim colorful. It uses the LSP and colorscheme and colors your variables, keywords and other stuff. To understand how treesitter works and configure treesitter please read the docs. Here is my configuration for treesitter:

require('nvim-treesitter.install').prefer_git = true
require 'nvim-treesitter.configs'.setup {
    ensure_installed = { 'bash', 'c', 'diff', 'html', 'lua', 'luadoc', 'markdown', 'vim', 'vimdoc', 'javascript', 'rust', 'typescript', "comment" },
    -- Autoinstall languages that are not installed
    auto_install = true,
    highlight = {
        enable = true,
        -- Some languages depend on vim's regex highlighting system (such as Ruby) for indent rules.
        --  If you are experiencing weird indenting issues, add the language to
        --  the list of additional_vim_regex_highlighting and disabled languages for indent.
        additional_vim_regex_highlighting = { 'ruby' },
    },
    indent = { enable = true, disable = { 'ruby' } },
}
Enter fullscreen mode Exit fullscreen mode

A fuzzy finder

I personally use Telescope as my fuzzy finder. Again, here's the docs for telescope and here's my config:

local builtin = require("telescope.builtin")

local keymap = vim.keymap

keymap.set("n", "<leader>pf", builtin.find_files, {})
keymap.set("n", "<leader>bl", builtin.buffers, {})
keymap.set("n", "<leader>pp", builtin.git_files, {})
keymap.set("n", "<leader>ps", function ()
    builtin.grep_string({ search = vim.fn.input("Search: ") });
end)
Enter fullscreen mode Exit fullscreen mode

Here's an explanation about what is going on:
Firstly, as soon as we install the telescope plugin and start it when our neovim session starts, we can call the find_files, buffers, git_files, etc. anytime we want. There are some commands as well to trigger them and the docs will mention everything in detail. Here, we are triggering them using lua functions.
So builtin contains all the functions and stuff within the telescope package and we simply call the individual functions (this is a higher level explanation for any library in neovim btw).
Now, here, we bind it to some keystokes:

Mode Key Effect
Normal Leader + pf Search within all the files (this includes files ignored by git, like node_modules)
Normal Leader + pp Search within all the git files (this excludes files ignored by git, like node_modules)
Normal Leader + bl List all the active buffers (this lets you navigate between opened buffers or files)
Normal Leader + ps Project wide search for a specific term (Similar to what we have on VS Code)

Now, you might be confused about two things:

  1. How do we even install this telescope or the treesitter mentioned above ?
  2. How are buffers and files related ?

The answer to the first is provided in the next section, where we discuss the required plugins, so please hold your horses.
The answer to the second one ? Let's understand that.

Bonus: Opening files (and folders) in neovim

For people who have experience with IDEs or even notepad, opening files is something simple. You click on a file (or double click in some cases) and it opens up in an editor for you to edit. However, as with everything else, neovim takes this a step ahead.
In neovim, there are these three concepts relating to opening files:

  1. Buffers
  2. Window and Panes
  3. Tabs

Let's get down to each of these at once, starting with windows. We will take an example of VS Code to make things easier to understand.
In VS Code, when you open a file, it opens in a tab. Think of the current VS Code session as the window (I'm 99.99% sure that is what people call it as well). The concept of window is the same in neovim. You start a session and a window appears. Now, if you've used split views in VS Code, you'd know that windows can be split between panes. What this does is, it opens the file again (side by side) in another tab.

In neovim, panes and windows work in a similar way. A window is a neovim window and a pane is a split of the window (vertical or horizontal). What is different, though is the content being shown (or at least how it's being shown).
You see, neovim opens the file only once and then just shows it to you side by side.
This is done with the help of Buffers. Neovim always opens a file or folder in a buffer and shows the buffer in separate panes. This way, less memory is used for doing the same task (This is why VS Code doesn't suit me anymore).

So let's make things clear. You hit enter on a file (or open it in some way) and neovim creates a buffer for the file and shows it in a window. You can split the window and create panes, but the same buffer will be shown instead of opening the file again. Tabs takes this one step further. A tab is like a superset for the window. When you open multiple tabs, you get multiple windows in the same neovim session. So you can have your different microservices open in different tabs on the same session and keep working without worrying about anything else.

Now that this is clear, let's see some essential plugins you'd need for your basic setup.

Your required plugins

The first thing you need is a plugin manager. This works in the same way any package manager works in an OS.
There are couple of popular ones, like Packer, Plug, Lazy, etc.
I use Lazy (because I am Lazy lol). It really lives up to it's name, and let's you install packages in the most lazy way. Simply mention the git repo of the package and it will take care of the rest. Of course, you need to write the configs, but there is no other hastle in the installation part at least. This also enabled lazy loading by default, which makes neovim load faster than is the packages were all loaded at once. Here is what you need to do to install Lazy:

-- Bootstrap 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
        vim.api.nvim_echo({
            { "Failed to clone lazy.nvim:\n", "ErrorMsg" },
            { out,                            "WarningMsg" },
            { "\nPress any key to exit..." },
        }, true, {})
        vim.fn.getchar()
        os.exit(1)
    end
end
vim.opt.rtp:prepend(lazypath)

-- Make sure to setup `mapleader` and `maplocalleader` before
-- loading lazy.nvim so that mappings are correct.
-- This is also a good place to setup other settings (vim.opt)
vim.g.mapleader = " "
vim.g.maplocalleader = "\\"

-- Setup lazy.nvim
require("lazy").setup({
    spec = {
        -- All the plugins go here
    },
    -- Configure any other settings here. See the documentation for more details.
    -- colorscheme that will be used when installing plugins.
    install = { missing = true, colorscheme = { "catppuccin-mocha" } },
    -- automatically check for plugin updates
    checker = { enabled = true, notify = false },
}, {
    ui = {
        -- If you are using a Nerd Font: set icons to an empty table which will use the
        -- default lazy.nvim defined Nerd Font icons, otherwise define a unicode icons table
        icons = vim.g.have_nerd_font and {} or {
            cmd = 'โŒ˜',
            config = '๐Ÿ› ',
            event = '๐Ÿ“…',
            ft = '๐Ÿ“‚',
            init = 'โš™',
            keys = '๐Ÿ—',
            plugin = '๐Ÿ”Œ',
            runtime = '๐Ÿ’ป',
            require = '๐ŸŒ™',
            source = '๐Ÿ“„',
            start = '๐Ÿš€',
            task = '๐Ÿ“Œ',
            lazy = '๐Ÿ’ค ',
        },
    },
})

Enter fullscreen mode Exit fullscreen mode

This is a boilerplate and you can get the same along with the docs from here.
To install a package, you need to provide the name in the spec object and Lazy will take care of the rest. here are some examples for that:

  1. Using just a package name:
"nvim-lua/plenary.nvim",
Enter fullscreen mode Exit fullscreen mode
  1. Using a name and options:
{
    'nvim-telescope/telescope.nvim',
    tag = '0.1.8',
    dependencies = { 'nvim-lua/plenary.nvim' }
},
{
    "nvim-treesitter/nvim-treesitter",
    build = ":TSUpdate",
    opts = {
        ensure_installed = { 'bash', 'c', 'diff', 'html', 'lua', 'luadoc', 'markdown', 'vim', 'vimdoc', 'javascript', 'rust', 'typescript' },
        -- Autoinstall languages that are not installed
        auto_install = true,
        highlight = {
            enable = true,
            -- Some languages depend on vim's regex highlighting system (such as Ruby) for indent rules.
            --  If you are experiencing weird indenting issues, add the language to
            --  the list of additional_vim_regex_highlighting and disabled languages for indent.
            additional_vim_regex_highlighting = { 'ruby' },
        },
        indent = { enable = true, disable = { 'ruby' } },
    },
},
Enter fullscreen mode Exit fullscreen mode

As you can see, it's pretty simple to install packages and the same can be found on the docs for each package (well, at least mostly).

Alongside this, there are some other packages that any developer would like to have:

  1. Undotree
  2. Gitsigns
  3. LSP
  4. Comment
  5. File tree (optional)
  6. Colorscheme (optional)

I'll drop the docs and my configs for each of these here without much explanation as one good look at the docs will explain everything here. If something is difficult to understand, please feel free to put that up in the comments and I shall answer each of them:

Undotree

Docs: https://github.com/mbbill/undotree
Config:

vim.keymap.set("n", "<leader>u", vim.cmd.UndotreeToggle)
Enter fullscreen mode Exit fullscreen mode

Gitsigns

Docs: https://github.com/lewis6991/gitsigns.nvim
Config:

[vim.keymap.set("n", "<leader>u", vim.cmd.UndotreeToggle)](<local neogit = require('neogit')
neogit.setup {}

require('gitsigns').setup {
    signs = {
        add          = { text = 'โ”ƒ' },
        change       = { text = 'โ”ƒ' },
        delete       = { text = '_' },
        topdelete    = { text = 'โ€พ' },
        changedelete = { text = '~' },
        untracked    = { text = 'โ”†' },
    },
    signs_staged = {
        add          = { text = 'โ”ƒ' },
        change       = { text = 'โ”ƒ' },
        delete       = { text = '_' },
        topdelete    = { text = 'โ€พ' },
        changedelete = { text = '~' },
        untracked    = { text = 'โ”†' },
    },
    signs_staged_enable = true,
    signcolumn = true,  -- Toggle with `:Gitsigns toggle_signs`
    numhl      = false, -- Toggle with `:Gitsigns toggle_numhl`
    linehl     = false, -- Toggle with `:Gitsigns toggle_linehl`
    word_diff  = false, -- Toggle with `:Gitsigns toggle_word_diff`
    watch_gitdir = {
        follow_files = true
    },
    auto_attach = true,
    attach_to_untracked = false,
    current_line_blame = false, -- Toggle with `:Gitsigns toggle_current_line_blame`
    current_line_blame_opts = {
        virt_text = true,
        virt_text_pos = 'eol', -- 'eol' | 'overlay' | 'right_align'
        delay = 1000,
        ignore_whitespace = false,
        virt_text_priority = 100,
    },
    current_line_blame_formatter = '%3Cauthor%3E, <author_time:%R> - <summary>',
    sign_priority = 6,
    update_debounce = 100,
    status_formatter = nil, -- Use default
    max_file_length = 40000, -- Disable if file is longer than this (in lines)
    preview_config = {
        -- Options passed to nvim_open_win
        border = 'single',
        style = 'minimal',
        relative = 'cursor',
        row = 0,
        col = 1
    },
    on_attach = function(bufnr)
        local gitsigns = require('gitsigns')

        local function map(mode, l, r, opts)
            opts = opts or {}
            opts.buffer = bufnr
            vim.keymap.set(mode, l, r, opts)
        end

        -- Navigation
        map('n', ']c', function()
            if vim.wo.diff then
                vim.cmd.normal({']c', bang = true})
            else
                gitsigns.nav_hunk('next')
            end
        end)

        map('n', '[c', function()
            if vim.wo.diff then
                vim.cmd.normal({'[c', bang = true})
            else
                gitsigns.nav_hunk('prev')
            end
        end)

        -- Actions
        map('n', '<leader>hs', gitsigns.stage_hunk)
        map('n', '<leader>hr', gitsigns.reset_hunk)
        map('v', '<leader>hs', function() gitsigns.stage_hunk {vim.fn.line('.'), vim.fn.line('v')} end)
        map('v', '<leader>hr', function() gitsigns.reset_hunk {vim.fn.line('.'), vim.fn.line('v')} end)
        map('n', '<leader>hS', gitsigns.stage_buffer)
        map('n', '<leader>hu', gitsigns.undo_stage_hunk)
        map('n', '<leader>hR', gitsigns.reset_buffer)
        map('n', '<leader>hp', gitsigns.preview_hunk)
        map('n', '<leader>hb', function() gitsigns.blame_line{full=true} end)
        map('n', '<leader>tb', gitsigns.toggle_current_line_blame)
        map('n', '<leader>hd', gitsigns.diffthis)
        map('n', '<leader>hD', function() gitsigns.diffthis('~') end)
        map('n', '<leader>td', gitsigns.toggle_deleted)

        -- Text object
        map({'o', 'x'}, 'ih', ':<C-U>Gitsigns select_hunk<CR>')
    end
}>
Enter fullscreen mode Exit fullscreen mode

LSP

Docs: https://github.com/VonHeikemen/lsp-zero.nvim
Config:

local lsp_zero = require('lsp-zero')
local cmp_lsp = require("cmp_nvim_lsp")

lsp_zero.on_attach(function(client, bufnr)
    -- see :help lsp-zero-keybindings
    -- to learn the available actions
    if client.name == "tsserver" then
        client.server_capabilities.document_formatting = false
    end
    lsp_zero.default_keymaps({ buffer = bufnr })
end)

local capabilities = vim.tbl_deep_extend(
    "force",
    {},
    vim.lsp.protocol.make_client_capabilities(),
    cmp_lsp.default_capabilities()
)

-- to learn how to use mason.nvim
-- read this: https://github.com/VonHeikemen/lsp-zero.nvim/blob/v3.x/doc/md/guide/integrate-with-mason-nvim.md
require('mason').setup({})
require('mason-lspconfig').setup({
    ensure_installed = {
        "lua_ls",
        "tsserver",
        "eslint",
        "rust_analyzer"
    },
    handlers = {
        function(server_name)
            local opts = {}
            if server_name == "tsserver" then
                opts.settings = {
                    implicitProjectConfiguration = {
                        checkJs = true
                    },
                }
            end

            opts.capabilities = capabilities

            require('lspconfig')[server_name].setup(opts)
        end,
    },
})

local cmp = require("cmp")
local luasnip = require('luasnip')
luasnip.config.setup {}
local cmp_mappings = cmp.mapping.preset.insert({
    ["<C-Space>"] = cmp.mapping.complete(),
    ["<C-k>"] = cmp.mapping.select_prev_item(),
    ["<C-j>"] = cmp.mapping.select_next_item(),
    ["<C-b>"] = cmp.mapping(cmp.mapping.scroll_docs(-1), { "i", "c" }),
    ["<C-f>"] = cmp.mapping(cmp.mapping.scroll_docs(1), { "i", "c" }),
    ["<C-y>"] = cmp.config.disable, -- Specify `cmp.config.disable` if you want to remove the default `<C-y>` mapping.
    ["<C-e>"] = cmp.mapping({
        i = cmp.mapping.abort(),
        c = cmp.mapping.close(),
    }),
    -- Accept currently selected item. If none selected, `select` first item.
    -- Set `select` to `false` to only confirm explicitly selected items.
    ["<CR>"] = cmp.mapping.confirm({ select = true }),
    ["<Tab>"] = cmp.mapping(function(fallback)
        if cmp.visible() then
            cmp.select_next_item()
        else
            fallback()
        end
    end, { "i", "s" }),
    ["<S-Tab>"] = cmp.mapping(function(fallback)
        if cmp.visible() then
            cmp.select_prev_item()
        else
            fallback()
        end
    end, { "i", "s" }),
})

cmp.setup({
    snippet = {
        expand = function(args)
            luasnip.lsp_expand(args.body)
        end,
    },
    completion = { completeopt = 'menu,menuone,noinsert' },
    mapping = cmp_mappings,
    sources = {
        { name = 'nvim_lsp' },
        { name = 'luasnip' },
        { name = 'path' },
    },
})

local status, prettier = pcall(require, "prettier")
if not status then
    return
end

prettier.setup({
    bin = "prettierd",
    filetypes = {
        "css",
        "javascript",
        "javascriptreact",
        "typescript",
        "typescriptreact",
        "json",
        "scss",
        "less",
    },
})
Enter fullscreen mode Exit fullscreen mode

Comment

This has two parts to it:

  1. Comments (To add comments) Docs: https://github.com/numToStr/Comment.nvim Config:
local status_ok, comment = pcall(require, "Comment")
if not status_ok then
    return
end

comment.setup({
    pre_hook = function(ctx)
        local U = require("Comment.utils")

        local location = nil
        if ctx.ctype == U.ctype.block then
            location = require("ts_context_commentstring.utils").get_cursor_location()
        elseif ctx.cmotion == U.cmotion.v or ctx.cmotion == U.cmotion.V then
            location = require("ts_context_commentstring.utils").get_visual_start_location()
        end

        return require("ts_context_commentstring.internal").calculate_commentstring({
            key = ctx.ctype == U.ctype.line and "__default" or "__multiline",
            location = location,
        })
    end,
})
Enter fullscreen mode Exit fullscreen mode
  1. Treesitter Context (To give treesitter context of which language it is) Docs: https://github.com/nvim-treesitter/nvim-treesitter-context Config:
require 'treesitter-context'.setup {
    enable = true,            -- Enable this plugin (Can be enabled/disabled later via commands)
    max_lines = 0,            -- How many lines the window should span. Values <= 0 mean no limit.
    min_window_height = 0,    -- Minimum editor window height to enable context. Values <= 0 mean no limit.
    line_numbers = true,
    multiline_threshold = 20, -- Maximum number of lines to show for a single context
    trim_scope = 'outer',     -- Which context lines to discard if `max_lines` is exceeded. Choices: 'inner', 'outer'
    mode = 'cursor',          -- Line used to calculate context. Choices: 'cursor', 'topline'
    -- Separator between context and content. Should be a single character string, like '-'.
    -- When separator is set, the context will only show up when there are at least 2 lines above cursorline.
    separator = nil,
    zindex = 20,     -- The Z-index of the context window
    on_attach = nil, -- (fun(buf: integer): boolean) return false to disable attaching
}
Enter fullscreen mode Exit fullscreen mode

File tree (optional)

This is a little subjective and varies for user to user. There are many good file tree plugins, some of the popular ones being nvim-tree, neotree, nerdtree, etc.
personally, I hate file explorers (takes up a lot of space and shifts focus, and more importantly, reminds me of VS Code), so I use yazi.nvim

Docs: https://github.com/mikavilpas/yazi.nvim
Config:
This doesn't require any separate config. If you use yazi, the same config will be used here. Only the keybinds need to be configured while installing:

{
    "mikavilpas/yazi.nvim",
    event = "VeryLazy",
    keys = {
        -- ๐Ÿ‘‡ in this section, choose your own keymappings!
        {
            "<leader>pv",
            function()
                require("yazi").yazi()
            end,
            desc = "Open the file manager",
        },
        {
            -- Open in the current working directory
            "<leader>cw",
            function()
                require("yazi").yazi(nil, vim.fn.getcwd())
            end,
            desc = "Open the file manager in nvim's working directory",
        },
    },
    opts = {
        -- if you want to open yazi instead of netrw, see below for more info
        open_for_directories = false,
    },
},
Enter fullscreen mode Exit fullscreen mode

Colorscheme (optional)

This is again subjective, and depends on the user. I personally use catppuccin.
Docs: https://github.com/catppuccin/nvim
Config:

{
    "catppuccin/nvim",
    name = "catppuccin",
    priority = 1000
},
Enter fullscreen mode Exit fullscreen mode

Bonus: Some quality of life plugins

Here are some more plugins that I use personally:

  1. Harpoon
  2. Neogit
  3. TODO Domments
  4. Mini.nvim
  5. Noice.nvim
  6. Lualine
  7. Autopairs
  8. Indent Blankline
  9. nvim-ts-context-commentstring
  10. vim-css-color
  11. vim-closetag
  12. vim-illuminate
  13. goto-preview
  14. Trouble
  15. Codeium If you think these are a lot, then yes they are but this gives me the most basic neovim setup that I need. You can add more plugins and make it even more feature rich, and it's completely fine but I like my setup to be fast and reliable, so the fewer plugins I have, the less I need to worry about them going out of date xD

Conclusion

With that, we come to the end of this long blog. I hope this was informative (except foy my backstory) and you learnt somethigng from it. If you reached this far, then I'd like to thank you for your time and patience and I'd like it if you could drop a comment on anything related to the blog or my writing style or some detail that I covered or something I missed out on. I am looking to actively give back to this community that gave me so many resources to learn and upskill and I'd like to get some suggestions on that as well.
See you in the next blog this weekend.

Top comments (0)