Skip to content

9 tmux Neovim

Julien Chappuis edited this page Mar 28, 2024 · 7 revisions

Introduction

Table of Contents

The goal is to reach a fully functionnal tmux + Neovim environment, primarily for Python / Rust usage.

tmux

Base install

$ paru -S tmux tmux-plugin-manager

Base config

Reference :

Create ~/.config/tmux/tmux.conf

set-option -sa terminal-overrides ",xterm*:Tc"
set -g mouse on

unbind C-b
set -g prefix C-Space
bind C-Space send-prefix

# Vim style pane selection
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R

# Start windows and panes at 1, not 0
set -g base-index 1
set -g pane-base-index 1
set-window-option -g pane-base-index 1
set-option -g renumber-windows on

# Use Alt-arrow keys without prefix key to switch panes
bind -n M-Left select-pane -L
bind -n M-Right select-pane -R
bind -n M-Up select-pane -U
bind -n M-Down select-pane -D

# Shift arrow to switch windows
bind -n S-Left  previous-window
bind -n S-Right next-window

# Shift Alt vim keys to switch windows
bind -n M-H previous-window
bind -n M-L next-window

set -g @catppuccin_flavour 'mocha'

set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-sensible'
set -g @plugin 'christoomey/vim-tmux-navigator'
set -g @plugin 'dreamsofcode-io/catppuccin-tmux'
set -g @plugin 'tmux-plugins/tmux-yank'

run '/usr/share/tmux-plugin-manager/tpm'

# set vi-mode
set-window-option -g mode-keys vi
# keybindings
bind-key -T copy-mode-vi v send-keys -X begin-selection
bind-key -T copy-mode-vi C-v send-keys -X rectangle-toggle
bind-key -T copy-mode-vi y send-keys -X copy-selection-and-cancel

bind '"' split-window -v -c "#{pane_current_path}"
bind % split-window -h -c "#{pane_current_path}"

Source the file using tmux :

tmux source ~/.config/tmux/tmux.conf
tmux

Type CTRL+SPACE then I to install the plugins. (this is capital i)

Neovim

Base Install

$ paru -S neovim xclip npm ripgrep
mkdir -p ~/.config/nvim
mkdir -p ~/.config/nvim/lua/jubi
cd ~/.config/nvim

Custom remaps

Edit ~/.config/nvim/lua/jubi/remap.lua

vim.g.mapleader = " "
vim.keymap.set("n", "<leader>pv", vim.cmd.Ex)

-- Allows to move selected text up and down
vim.keymap.set("v", "J", ":m '>+1<CR>gv=gv")
vim.keymap.set("v", "K", ":m '<-2<CR>gv=gv")

-- cursor stays in place when appending the line below
vim.keymap.set("n", "J", "mzJ`z")
-- cursor stays in the middle during half page jumps
vim.keymap.set("n", "<C-d>", "<C-d>zz")
vim.keymap.set("n", "<C-u>", "<C-u>zz")
-- search term stays in the middle during searches
vim.keymap.set("n", "n", "nzzzv")
vim.keymap.set("n", "N", "Nzzzv")

-- pasting foo over bar keeps foo in the buffer
vim.keymap.set("x", "<leader>p", [["_dP]])

-- Copies to system clipboard
vim.keymap.set({ "n", "v" }, "<leader>y", [["+y]])
vim.keymap.set("n", "<leader>Y", [["+Y]])

-- Quickfix navigation
vim.keymap.set("n", "<C-k>", "<cmd>cnext<CR>zz")
vim.keymap.set("n", "<C-j>", "<cmd>cprev<CR>zz")
vim.keymap.set("n", "<leader>k", "<cmd>lnext<CR>zz")
vim.keymap.set("n", "<leader>j", "<cmd>lprev<CR>zz")

Custom options

Edit ~/.config/nvim/lua/jubi/set.lua

vim.opt.nu = true
vim.opt.relativenumber = true
vim.opt.cursorline = true

vim.opt.tabstop = 4
vim.opt.softtabstop = 4
vim.opt.shiftwidth = 4
vim.opt.expandtab = true

vim.opt.smartindent = true

vim.opt.swapfile = false
vim.opt.backup = false
vim.opt.undodir = os.getenv("HOME") .. "/.vim/undodir"
vim.opt.undofile = true

vim.opt.hlsearch = false
vim.opt.incsearch = true

vim.opt.termguicolors = true

vim.opt.scrolloff = 8
vim.opt.signcolumn = "yes"

vim.opt.updatetime = 50

vim.opt.colorcolumn = "80"

Plugin managers

Lazy (General plugin manager)

Reference : https://github.com/folke/lazy.nvim Edit ~/.config/nvim/lua/jubi/init.lua

require("jubi.remap")
require("jubi.set")

local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" (1)
if not vim.loop.fs_stat(lazypath) then
    vim.fn.system({
        "git",
        "clone",
        "--filter=blob:none",
        "https://github.com/folke/lazy.nvim.git",
        "--branch=stable", -- latest stable release
        lazypath,
    })
end
vim.opt.rtp:prepend(lazypath)

require("lazy").setup("jubi.plugins") (2)
  1. This block installs Lazy if not already present

  2. Instructs Lazy to load all the plugin configs in the ~/config/jubi/nvim/lua/jubi/plugins directory

Note
For all plugins below, install them by running :Lazy then I to install

Mason (LSP servers/DAP servers/linters/formatters plugin manager)

return {
    "williamboman/mason.nvim",
    "williamboman/mason-lspconfig.nvim",
    build = ":MasonUpdate" -- :MasonUpdate updates registry contents
}

Telescope (Fuzzy finder)

References :

Create ~/.config/nvim/lua/jubi/plugins/telescope.lua

return {
    'nvim-telescope/telescope.nvim',
    tag = '0.1.4',
    dependencies = {
        'nvim-lua/plenary.nvim',
        'nvim-tree/nvim-web-devicons'}
}

Create ~/.config/nvim/lua/jubi/plugins/telescope-fzf-native.lua

return {
    'nvim-telescope/telescope-fzf-native.nvim',
    build = 'cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release && cmake --build build --config Release && cmake --install build --prefix build'
}

Create ~/.config/nvim/after/plugin/telescope.lua

local builtin = require('telescope.builtin')
vim.keymap.set('n', '<leader>pf', builtin.find_files, {})
vim.keymap.set('n', '<C-p>', builtin.git_files, {})
vim.keymap.set('n', '<leader>ps', builtin.live_grep, {})
vim.keymap.set('n', '<leader>pb', builtin.buffers, {})

Harpoon (Fast switcher between a set of files)

Create ~/.config/jubi/nvim/lua/jubi/plugins/harpoon.lua

return {
	"ThePrimeagen/harpoon",
}

Create ~/.config/nvim/after/plugin/harpoon.lua

local mark = require("harpoon.mark")
local ui = require("harpoon.ui")

vim.keymap.set("n", "<leader>a", mark.add_file)
vim.keymap.set("n", "<C-e>", ui.toggle_quick_menu)

vim.keymap.set("n", "<C-h>", function () ui.nav_file(1) end)
vim.keymap.set("n", "<C-t>", function () ui.nav_file(2) end)
vim.keymap.set("n", "<C-n>", function () ui.nav_file(3) end)
vim.keymap.set("n", "<C-s>", function () ui.nav_file(4) end)

Vim-illuminate (Highlights similar words)

Create ~/.config/nvim/lua/jubi/plugins/vim-illuminate.lua

return {
    'Rrethy/vim-illuminate'
}

Create ~/.config/nvim/after/plugin/vim-illuminate.lua

local highlights = {
  IlluminatedWord = { bg = "#6e738d" },
  IlluminatedCurWord = { bg = "#6e738d" },
  IlluminatedWordText = { bg = "#6e738d" },
  IlluminatedWordRead = { bg = "#6e738d" },
  IlluminatedWordWrite = { bg = "#6e738d" },
}

for group, value in pairs(highlights) do
  vim.api.nvim_set_hl(0, group, value)
end

LSP - Parsers - Highlighters - Debuggers - Formatters

Tree-sitter (Syntax highlighter)

Create ~/config/jubi/nvim/lua/jubi/plugins/treesitter.lua

return {
	"nvim-treesitter/nvim-treesitter",
	build = ":TSUpdate"
}

Create ~/config/jubi/nvim/after/plugin/treesitter.lua

require'nvim-treesitter.configs'.setup {
	-- A list of parser names, or "all" (the five listed parsers should always be installed)
	ensure_installed = { "bash", "c", "dockerfile", "fish", "git_config", "git_rebase", "html", "javascript", "json", "lua", "markdown", "python", "query", "rust", "toml", "typescript", "vim", "vimdoc"},

	-- Install parsers synchronously (only applied to `ensure_installed`)
	sync_install = false,

	-- Automatically install missing parsers when entering buffer
	-- Recommendation: set to false if you don't have `tree-sitter` CLI installed locally
	auto_install = true,

	-- Setting this to true will run `:h syntax` and tree-sitter at the same time.
	-- Set this to `true` if you depend on 'syntax' being enabled (like for indentation).
	-- Using this option may slow down your editor, and you may see some duplicate highlights.
	-- Instead of true it can also be a list of languages
	additional_vim_regex_highlighting = false,
}

Save and type :so to start installing the parsers.

nvim-lspconfig (LSP base plugin + configuration)

Note
I do not use LSP zero, in order to have full control on the LSP configuration. This setup uses the base Neovim nvim-lspconfig, and builds from there.

Usage: This is the base plugin enabling Language Server Protocol usage in Neovim, bringing it on par with Visual Studio Code.

Create ~/.config/nvim/lua/jubi/plugins/nvim-lspconfig.lua

return {
    'neovim/nvim-lspconfig',
}

Create ~/.config/nvim/after/plugin/lspconfig_meta.lua

-- This fully configures nvim-lsp, mason-lspconfig, nvim-cmp and nvim-dap

vim.opt.completeopt = { "menu", "menuone", "noselect" }

-- Keybindings
vim.api.nvim_create_autocmd('LspAttach', {
    desc = 'LSP actions',
    callback = function(event)
        local opts = { buffer = event.buf }
        vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, opts)
        vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts)
        vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts)
        vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, opts)
        vim.keymap.set('n', '<C-h>', vim.lsp.buf.signature_help, opts)
        vim.keymap.set('n', '<leader>wa', vim.lsp.buf.add_workspace_folder, opts)
        vim.keymap.set('n', '<leader>wr', vim.lsp.buf.remove_workspace_folder, opts)
        vim.keymap.set('n', '<leader>wl', function()
            print(vim.inspect(vim.lsp.buf.list_workspace_folders()))
        end, opts)
        vim.keymap.set('n', '<leader>D', vim.lsp.buf.type_definition, opts)
        vim.keymap.set('n', '<leader>rn', vim.lsp.buf.rename, opts)
        vim.keymap.set({ 'n', 'v' }, '<leader>ca', vim.lsp.buf.code_action, opts)
        vim.keymap.set('n', 'gr', vim.lsp.buf.references, opts)
        vim.keymap.set('n', '<leader>ds', vim.lsp.buf.document_symbol, opts)
        vim.keymap.set('n', '<leader>f', function()
            vim.lsp.buf.format { async = true }
        end, opts)
        vim.keymap.set('n', '<leader>dl', function() vim.diagnostic.open_float() end, opts)
        vim.keymap.set('n', '[d', function() vim.diagnostic.goto_next() end, opts)
        vim.keymap.set('n', ']d', function() vim.diagnostic.goto_prev() end, opts)
    end
})

-- Mason configuration
require("mason").setup()
require("mason-lspconfig").setup {}

local lspconfig = require('lspconfig')

-- autocomplete configuration
local cmp = require('cmp')
cmp.setup {
    mapping = {
        ["<C-p>"] = cmp.mapping.select_prev_item(),
        ["<C-n>"] = cmp.mapping.select_next_item(),
        ["<C-y>"] = cmp.mapping.confirm({ select = true }),
        ["<C-Space>"] = cmp.mapping.complete(),
    },
    window = {
        completion = {
            winhighlight = "Normal:Pmenu,FloatBorder:Pmenu,Search:None",
            col_offset = -3,
            side_padding = 0,
        },
    },
    sources = {
        { name = 'nvim_lsp' }, -- should probably be first
        { name = 'buffer' },
        { name = 'emoji' },
        { name = 'luasnip' },
        { name = 'nvim_lsp_signature_help' },
        { name = 'path' },
    },
    formatting = {
        fields = { "kind", "abbr", "menu" },
        format = function(entry, vim_item)
            local kind = require("lspkind").cmp_format({ mode = "symbol_text", maxwidth = 50 })(entry, vim_item)
            local strings = vim.split(kind.kind, "%s", { trimempty = true })
            kind.kind = " " .. (strings[1] or "") .. " "
            kind.menu = "    (" .. (strings[2] or "") .. ")"

            return kind
        end,
    },
    snippet = {
        expand = function(args)
            require 'luasnip'.lsp_expand(args.body)
        end
    },
}

-- defines autocomplete categories colors using Catppuccin theme
vim.api.nvim_set_hl(0, "PmenuSel", { bg = "#6e738d", fg = "NONE" })
vim.api.nvim_set_hl(0, "Pmenu", { fg = "#CAD3F5", bg = "#181926" })

vim.api.nvim_set_hl(0, "CmpItemAbbrDeprecated", { fg = "#B8C0E0", bg = "NONE", strikethrough = true })
vim.api.nvim_set_hl(0, "CmpItemAbbrMatch", { fg = "#7DC4E4", bg = "NONE", bold = true })
vim.api.nvim_set_hl(0, "CmpItemAbbrMatchFuzzy", { fg = "#7DC4E4", bg = "NONE", bold = true })
vim.api.nvim_set_hl(0, "CmpItemMenu", { fg = "#C6A0F6", bg = "NONE", italic = true })

vim.api.nvim_set_hl(0, "CmpItemKindField", { fg = "#24273A", bg = "#ED8796" })
vim.api.nvim_set_hl(0, "CmpItemKindProperty", { fg = "#24273A", bg = "#ED8796" })
vim.api.nvim_set_hl(0, "CmpItemKindEvent", { fg = "#24273A", bg = "#ED8796" })

vim.api.nvim_set_hl(0, "CmpItemKindText", { fg = "#24273A", bg = "#A6DA95" })
vim.api.nvim_set_hl(0, "CmpItemKindEnum", { fg = "#24273A", bg = "#A6DA95" })
vim.api.nvim_set_hl(0, "CmpItemKindKeyword", { fg = "#24273A", bg = "#A6DA95" })

vim.api.nvim_set_hl(0, "CmpItemKindConstant", { fg = "#24273A", bg = "#EED49F" })
vim.api.nvim_set_hl(0, "CmpItemKindConstructor", { fg = "#24273A", bg = "#EED49F" })
vim.api.nvim_set_hl(0, "CmpItemKindReference", { fg = "#24273A", bg = "#EED49F" })

vim.api.nvim_set_hl(0, "CmpItemKindFunction", { fg = "#24273A", bg = "#F5BDE6" })
vim.api.nvim_set_hl(0, "CmpItemKindStruct", { fg = "#24273A", bg = "#F5BDE6" })
vim.api.nvim_set_hl(0, "CmpItemKindClass", { fg = "#24273A", bg = "#F5BDE6" })
vim.api.nvim_set_hl(0, "CmpItemKindModule", { fg = "#24273A", bg = "#F5BDE6" })
vim.api.nvim_set_hl(0, "CmpItemKindOperator", { fg = "#24273A", bg = "#F5BDE6" })

vim.api.nvim_set_hl(0, "CmpItemKindVariable", { fg = "#24273A", bg = "#8AADF4" })
vim.api.nvim_set_hl(0, "CmpItemKindFile", { fg = "#24273A", bg = "#8AADF4" })

vim.api.nvim_set_hl(0, "CmpItemKindUnit", { fg = "#24273A", bg = "#D4A959" })
vim.api.nvim_set_hl(0, "CmpItemKindSnippet", { fg = "#24273A", bg = "#D4A959" })
vim.api.nvim_set_hl(0, "CmpItemKindFolder", { fg = "#24273A", bg = "#D4A959" })

vim.api.nvim_set_hl(0, "CmpItemKindMethod", { fg = "#24273A", bg = "#91D7E3" })
vim.api.nvim_set_hl(0, "CmpItemKindValue", { fg = "#24273A", bg = "#91D7E3" })
vim.api.nvim_set_hl(0, "CmpItemKindEnumMember", { fg = "#24273A", bg = "#91D7E3" })

vim.api.nvim_set_hl(0, "CmpItemKindInterface", { fg = "#24273A", bg = "#8BD5CA" })
vim.api.nvim_set_hl(0, "CmpItemKindColor", { fg = "#24273A", bg = "#8BD5CA" })
vim.api.nvim_set_hl(0, "CmpItemKindTypeParameter", { fg = "#24273A", bg = "#8BD5CA" })


------------------------------------------------------------------------------
-- LSP servers configuration
------------------------------------------------------------------------------

local lsp_capabilities = require('cmp_nvim_lsp').default_capabilities()
local get_servers = require('mason-lspconfig').get_installed_servers

for _, server_name in ipairs(get_servers()) do
    lspconfig[server_name].setup({
        capabilities = lsp_capabilities,
    })
end

require('lspconfig').pylsp.setup {
    settings = {
        pylsp = {
            plugins = {
                jedi_completion = {
                    enabled = true,
                    eager = true,
                    cache_for = { "numpy", "scipy" }
                },
                jedi_definition = {
                    enabled = true,
                    follow_imports = true,
                    follow_builtin_imports = true,
                },
                jedi_hover = { enabled = true },
                jedi_references = { enabled = true },
                jedi_signature_help = { enabled = true },
                jedi_symbols = { enabled = true, all_scopes = true, include_import_symbols = true },
                preload = { enabled = true, modules = { "numpy", "scipy" } },
                isort = { enabled = false },
                spyder = { enabled = false },
                memestra = { enabled = false },
                pycodestyle = { enabled = false }, -- not work
                flake8 = { enabled = false },
                pyflakes = { enabled = false },
                yapf = { enabled = true },
                pylint = { enabled = false },
                ruff = {
                    enabled = true,
                    extendSelect = { "I" },
                },
                black = { enabled = true }
            }
        }
    }
}

require('lspconfig').htmx.setup {}

require('lspconfig').lua_ls.setup {
    on_init = function(client)
        local path = client.workspace_folders[1].name
        if not vim.loop.fs_stat(path .. '/.luarc.json') and not vim.loop.fs_stat(path .. '/.luarc.jsonc') then
            client.config.settings.Lua = vim.tbl_deep_extend('force', client.config.settings.Lua, {
                runtime = {
                    -- Tell the language server which version of Lua you're using (most likely LuaJIT in the case of Neovim)
                    version = 'LuaJIT'
                },
                -- Make the server aware of Neovim runtime files
                workspace = {
                    library = { vim.env.VIMRUNTIME }
                    -- or pull in all of 'runtimepath'. NOTE: this is a lot slower
                    -- library = vim.api.nvim_get_runtime_file("", true)
                }
            })

            client.notify("workspace/didChangeConfiguration", { settings = client.config.settings })
        end
        return true
    end,
    settings = {
        Lua = {
            completion = {
                callSnippet = "Replace"
            },
            diagnostics = {
                globals = { "vim" }
            },
        }
    }
}

nvim-dap (Debugger base plugin)

Usage: This is the base plugin enabling debugging.

Create ~/config/jubi/nvim/lua/jubi/plugins/nvim-dap.lua

return {
    'mfussenegger/nvim-dap',
    dependencies = {
        'rcarriga/nvim-dap-ui',
        'nvim-neotest/nvim-nio',
        'mfussenegger/nvim-dap-python',
    }
}
require("dapui").setup()
require("dap-python").setup()

local dap, dapui = require("dap"), require("dapui")

dap.listeners.after.event_initialized["dapui_config"] = function()
    dapui.open()
end
dap.listeners.before.event_terminated["dapui_config"] = function()
    dapui.close()
end
dap.listeners.before.event_exited["dapui_config"] = function()
    dapui.close()
end

local sign = vim.fn.sign_define

-- Insert language specific configuration here

-- These are to override the default highlight groups for catppuccin (see https://github.com/catppuccin/nvim/#special-integrations)
sign("DapBreakpoint", { text = "", texthl = "DapBreakpoint", linehl = "", numhl = "" })
sign("DapBreakpointCondition", { text = "", texthl = "DapBreakpointCondition", linehl = "", numhl = "" })
sign("DapLogPoint", { text = "", texthl = "DapLogPoint", linehl = "", numhl = "" })


vim.keymap.set("n", "<F9>", ':DapToggleBreakpoint<CR>')
vim.keymap.set("n", "<s-F5>", ':DapTerminate<CR>')
vim.keymap.set("n", "<F5>", ':DapContinue<CR>')
vim.keymap.set("n", "<F10>", ':DapStepOver<CR>')
vim.keymap.set("n", "<F11>", ':DapStepInto<CR>')
vim.keymap.set("n", "<s-F11>", ':DapStepOut<CR>')

conform-nvim (Formatter base plugin)

Usage: successor of null-ls, makes clever use of the appropriate formatters, also enables format on save.

Create ~/config/jubi/nvim/lua/jubi/plugins/conform-nvim.lua

return {
  'stevearc/conform.nvim',
  opts = {},
}
Create `~/.config/nvim/after/plugin/conform-nvim.lua`

require("conform").setup({
    formatters_by_ft = {
        lua = { "stylua" },
        -- Conform will run multiple formatters sequentially
        python = { "ruff_format" },
        -- Use a sub-list to run only the first available formatter
        javascript = { { "prettierd", "prettier" } },
    },
    format_on_save = {
        -- I recommend these options. See :help conform.format for details.
        lsp_fallback = true,
        timeout_ms = 500,
    },
})

Copilot

Reference:

Make sure to have nonicons font installed (.ttf file is in the dest folder), and your terminal backup font configured In kitty.conf

symbol_map U+f101-U+f25c nonicons

Create ~/.config/nvim/lua/jubi/plugins/copilot.lua

return {
    "zbirenbaum/copilot.lua",
    lazy = true
}

Create ~/.config/nvim/lua/jubi/plugins/copilot-status.lua

return {
    "jonahgoldwastaken/copilot-status.nvim",
    dependencies = { "copilot.lua" },
    lazy = true,
    event = "BufReadPost",
}

Create ~/.config/nvim/after/plugin/copilot.lua

require('copilot').setup({
    panel = {
        enabled = false
    },
    suggestion = {
        enabled = true,
        auto_trigger = true,
        debounce = 75,
        keymap = {
            accept = "<M-y>",
            accept_line = "<M-u>",
            accept_word = "<M-i>",
            next = "<M-]>",
            prev = "<M-[>",
            dismiss = "<C-]>",
        }
    }
})

Create ~/.config/nvim/after/plugin/copilot-status.lua

require('copilot_status').setup({
    icons = {
        idle = "", --\uF254 Copilot head symbol in nonicons font
        error = "", --\uf256 Copilot head symbol with an exclamation mark in nonicons font
        offline = "", --\uf255 Copilot head strike through
        warning = "",
        loading = "",
    },
    debug = false,
})

To verify the codes, use this quick script

#!/usr/bin/env python

start_code, stop_code = 'F101', 'F25C'  # (CJK Unified Ideographs)
start_idx, stop_idx = [int(code, 16) for code in (start_code, stop_code)]  # from hexadecimal to unicode code point
for unicode_idx in range(start_idx, stop_idx+1):
    print(f"{hex(unicode_idx)} - {chr(unicode_idx)}")  # from unicode code point to character

Then enter the characters using kitty unicode assistant CTRL+SHIFT+U and the code point.W

Language specific config

Lua

Usage: configures LuaLSP to autocomplete vim functions / objects like vim, which makes typing lua neovim configs a lot easier and removes warnings such as Undefined global 'vim'.

Create ~/.config/nvim/lua/jubi/plugins/neodev-nvim.lua

return {
    "folke/neodev.nvim",
    opts = {}
}

Python LSP

Reference: Details on Python pylsp config here

pylsp is installed via Mason. The pylsp plugin is installed via :PylspInstall <tab> to install the following plugins : * * python-lsp-mypy : config details here

Usage: I use Python-LSP metapackage, which uses : * Jedi for autocompletion, static code analysis * Ruff for linting and import sorting (formatting is also done by Ruff, but via Conform)

Append to ~/.config/nvim/after/plugin/lspconfig_meta.lua, in the LSP Server configuration section.

require('lspconfig').pylsp.setup {
    settings = {
        pylsp = {
            plugins = {
                jedi_completion = {
                    enabled = true,
                    eager = true,
                    cache_for = { "numpy", "scipy" }
                },
                jedi_definition = {
                    enabled = true,
                    follow_imports = true,
                    follow_builtin_imports = true,
                },
                jedi_hover = { enabled = true },
                jedi_references = { enabled = true },
                jedi_signature_help = { enabled = true },
                jedi_symbols = { enabled = true, all_scopes = true, include_import_symbols = true },
                preload = { enabled = true, modules = { "numpy", "scipy" } },
                isort = { enabled = false },
                spyder = { enabled = false },
                memestra = { enabled = false },
                pycodestyle = { enabled = false }, -- not work
                flake8 = { enabled = false },
                pyflakes = { enabled = false },
                yapf = { enabled = true },
                pylint = { enabled = false },
                ruff = {
                    enabled = true,
                    extendSelect = { "I" },
                },
                black = { enabled = true }
            }
        }
    }
}

Python Debug

  • Install debugpy as advised (I tried with installing it in the project venv, it works too)

Rust-tools

Reference :

Warning
You need to install rust-analyzer and codelldb manually, by launching :Mason, go to (1) LSP and (3) DAP menu (respectively), and install it.

Create ~/.config/nvim/lua/jubi/plugins/rust-tools.lua

return {
    'simrat39/rust-tools.nvim',
    dependencies = {
        'nvim-lua/plenary.nvim',
        'mfussenegger/nvim-dap',
        'rcarriga/nvim-dap-ui'
    },
}

Create ~/.config/nvim/after/plugin/rust-tools.lua

local rt = require("rust-tools")
local mason_registry = require("mason-registry")
local codelldb = mason_registry.get_package("codelldb")
local codelldb_root = codelldb:get_install_path() .. "/extension/"
local codelldb_path = codelldb_root .. "adapter/codelldb"
local liblldb_path = codelldb_root .. "lldb/lib/liblldb.so"

rt.setup({
    dap = {
        adapter = require("rust-tools.dap").get_codelldb_adapter(codelldb_path, liblldb_path),
    },
    server = {
        on_attach = function(_, bufnr)
            -- Hover actions
            vim.keymap.set("n", "<Leader>k", rt.hover_actions.hover_actions, { buffer = bufnr })
            -- Code action groups
            vim.keymap.set("n", "<Leader>a", rt.code_action_group.code_action_group, { buffer = bufnr })
        end,
    },
    tools = {
        hover_actions = {
            auto_focus = true,
        },
    },
})

Add to ~/.config/nvim/after/plugin/nvim-dap.lua, before the key mapping section

local mason_registry = require("mason-registry")
local codelldb = mason_registry.get_package("codelldb")
local codelldb_root = codelldb:get_install_path() .. "/extension/"
local codelldb_path = codelldb_root .. "adapter/codelldb"
local liblldb_path = codelldb_root .. "lldb/lib/liblldb.so"

dap.adapters.codelldb = {
    type = 'server',
    port = "${port}",
    executable = {
        -- CHANGE THIS to your path!
        command = codelldb_path,
        args = { "--port", "${port}" },
    }
}

dap.configurations.rust = {
    {
        name = "Launch file",
        type = "codelldb",
        request = "launch",
        -- program = function()
        --     return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/', 'file')
        -- end,
        program = codelldb_path,
        cwd = '${workspaceFolder}',
        stopOnEntry = false,
    },
}

Versioning

UndoTree (Undo history navigator)

Create ~/config/jubi/nvim/lua/jubi/plugins/undotree.lua

return {
	"mbbill/undotree"
}

Create ~/config/jubi/nvim/after/plugin/undotree.lua

vim.keymap.set("n", "<leader>u", vim.cmd.UndotreeToggle)

Fugitive (Git plugin)

Create ~/config/jubi/nvim/lua/jubi/plugins/vim-fugitive.lua

return {
	"tpope/vim-fugitive"
}

Create ~/config/jubi/nvim/after/plugin/vim-fugitive.lua

vim.keymap.set("n", "<leader>gs", vim.cmd.Git)

Git signs (git decorations in buffers)

Create ~/config/jubi/nvim/lua/jubi/plugins/gitsigns.lua

return {
    "lewis6991/gitsigns.nvim",
}

Create ~/.config/nvim/after/plugin/gitsigns.lua

require('gitsigns').setup {
      signs = {
    add          = { text = '' },
    change       = { text = '' },
    delete       = { text = '_' },
    topdelete    = { text = '' },
    changedelete = { text = '~' },
    untracked    = { text = '' },
  },
  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
  },
  attach_to_untracked = true,
  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 = '<author>, <author_time:%Y-%m-%d> - <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
  },
  yadm = {
    enable = false
  },
    on_attach                    = function(bufnr)
        local gs = package.loaded.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 return ']c' end
            vim.schedule(function() gs.next_hunk() end)
            return '<Ignore>'
        end, { expr = true })

        map('n', '[c', function()
            if vim.wo.diff then return '[c' end
            vim.schedule(function() gs.prev_hunk() end)
            return '<Ignore>'
        end, { expr = true })

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

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

Quality of life

nvim-autopairs (autocomplete pairs of characters)

Create ~/.config/nvim/lua/jubi/plugins/nvim-autopairs.lua

return {
    'windwp/nvim-autopairs',
    event = "InsertEnter",
    opts = {}
}

vim-commentary (easier commenting)

Create ~/.config/nvim/lua/jubi/plugins/vim-commentary.lua

return {
    'tpope/vim-commentary',
}

Themes - Visual appearance

Catppuccin (Colorscheme)

Create ~/config/jubi/nvim/lua/jubi/plugins/catppuccin.lua

return {
	"catppuccin/nvim",
	name = "catppuccin",
	lazy = false,
	priority = 1000,
	config = function()
		require("catppuccin").setup {
			flavour = "mocha"
		}
		vim.cmd.colorscheme "catppuccin"
	end,
}

Ident-blankLines (indentation / scope highlighter)

Usage: * shows thin vertical lines representing each tab stop (same as VS Code) * highlights the scope of variables / functions

Create ~/config/jubi/nvim/lua/jubi/plugins/ident-blanklines.lua

return {
    "lukas-reineke/indent-blankline.nvim",
    main = "ibl",
    opts = {}
}

Create ~/.config/nvim/after/plugin/indent-blankline.lua

vim.opt.list = true

require("ibl").setup {
}

LuaLine (Pretty status line)

Usage: prettier vim command line

Create ~/config/jubi/nvim/lua/jubi/plugins/lualine.lua

return {
    'nvim-lualine/lualine.nvim',
    dependencies = {
        'nvim-tree/nvim-web-devicons'
    }
}

Create ~/.config/nvim/after/plugin/lualine.lua

require('lualine').setup {
    options = {
        icons_enabled = true,
        theme = "catppuccin",
        component_separators = '|',
        disabled_filetypes = {
            statusline = {},
            winbar = {},
        },
        ignore_focus = {},
        always_divide_middle = true,
        globalstatus = false,
        refresh = {
            statusline = 1000,
            tabline = 1000,
            winbar = 1000,
        }
    },
    sections = {
        lualine_a = {
            { 'mode', right_padding = 2 }
        },
        lualine_b = { 'branch', 'diff', 'diagnostics' },
        lualine_c = { 'filename' },
        lualine_x = {
            {
                function() return require("copilot_status").status_string() end,
                cnd = function() return require("copilot_status").enabled() end,
            },
            'encoding',
            'fileformat',
            'filetype' },
        lualine_y = { 'progress' },
        lualine_z = {
            { 'location', left_padding = 2 },
        },
    },
    inactive_sections = {
        lualine_a = {},
        lualine_b = {},
        lualine_c = { 'filename' },
        lualine_x = { 'location' },
        lualine_y = {},
        lualine_z = {}
    },
    tabline = {},
    winbar = {},
    inactive_winbar = {},
    extensions = {}
}