Finally modularize neovim configuration

This commit is contained in:
Alex Selimov 2025-09-03 09:05:52 -04:00
parent d4ca4668c4
commit e2697f61fd
No known key found for this signature in database
GPG key ID: BC03C17FF5CFFFD2
16 changed files with 872 additions and 1004 deletions

35
lua/config/autocmds.lua Normal file
View file

@ -0,0 +1,35 @@
-- Autocmds configuration
-- Disable semantic tokens
vim.api.nvim_create_autocmd("LspAttach", {
callback = function(args)
local client = vim.lsp.get_client_by_id(args.data.client_id)
client.server_capabilities.semanticTokensProvider = nil
end,
})
-- Go back to last edited line when reopening file
vim.api.nvim_create_autocmd("BufRead", {
callback = function(opts)
vim.api.nvim_create_autocmd("BufWinEnter", {
once = true,
buffer = opts.buf,
callback = function()
local ft = vim.bo[opts.buf].filetype
local last_known_line = vim.api.nvim_buf_get_mark(opts.buf, '"')[1]
if
not (ft:match("gitcommit") and ft:match("gitrebase"))
and last_known_line > 1
and last_known_line <= vim.api.nvim_buf_line_count(opts.buf)
then
vim.api.nvim_feedkeys([[g`"]], "nx", false)
end
end,
})
end,
})
-- Disable semantic highlights
for _, group in ipairs(vim.fn.getcompletion("@lsp", "highlight")) do
vim.api.nvim_set_hl(0, group, {})
end

34
lua/config/formatting.lua Normal file
View file

@ -0,0 +1,34 @@
-- Formatting configuration
-- Set rustfmt command
vim.g["rustfmt_command"] = "rustfmt +nightly"
-- Commands to disable/enable formatting
require("conform").setup({
format_on_save = function(bufnr)
-- Disable with a global or buffer-local variable
if vim.g.disable_autoformat or vim.b[bufnr].disable_autoformat then
return
end
return { timeout_ms = 500, lsp_fallback = true }
end,
})
vim.api.nvim_create_user_command("FormatDisable", function(args)
if args.bang then
-- FormatDisable! will disable formatting just for this buffer
vim.b.disable_autoformat = true
else
vim.g.disable_autoformat = true
end
end, {
desc = "Disable autoformat-on-save",
bang = true,
})
vim.api.nvim_create_user_command("FormatEnable", function()
vim.b.disable_autoformat = false
vim.g.disable_autoformat = false
end, {
desc = "Re-enable autoformat-on-save",
})

19
lua/config/keymaps.lua Normal file
View file

@ -0,0 +1,19 @@
-- Keymaps configuration
-- Clear search highlight
vim.keymap.set("n", "<Esc>", "<cmd>nohlsearch<CR>")
-- Diagnostic keymaps
vim.keymap.set("n", "[d", vim.diagnostic.goto_prev, { desc = "Go to previous [D]iagnostic message" })
vim.keymap.set("n", "]d", vim.diagnostic.goto_next, { desc = "Go to next [D]iagnostic message" })
vim.keymap.set("n", "<leader>e", vim.diagnostic.open_float, { desc = "Show diagnostic [E]rror messages" })
vim.keymap.set("n", "<leader>q", vim.diagnostic.setloclist, { desc = "Open diagnostic [Q]uickfix list" })
-- Window navigation
vim.keymap.set("n", "<C-h>", "<C-w><C-h>", { desc = "Move focus to the left window" })
vim.keymap.set("n", "<C-l>", "<C-w><C-l>", { desc = "Move focus to the right window" })
vim.keymap.set("n", "<C-j>", "<C-w><C-j>", { desc = "Move focus to the lower window" })
vim.keymap.set("n", "<C-k>", "<C-w><C-k>", { desc = "Move focus to the upper window" })
-- LSP keymaps
vim.keymap.set("n", "gd", vim.lsp.buf.definition, { desc = "Goto Definition" })

16
lua/config/lazy.lua Normal file
View file

@ -0,0 +1,16 @@
-- Lazy.nvim plugin manager setup
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
local lazyrepo = "https://github.com/folke/lazy.nvim.git"
vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
end
vim.opt.rtp:prepend(lazypath)
-- User dictionary for spell checking
local path = vim.fn.stdpath("config") .. "/spell/en.utf-8.add"
local words = {}
for word in io.open(path, "r"):lines() do
table.insert(words, word)
end

55
lua/config/options.lua Normal file
View file

@ -0,0 +1,55 @@
-- Options configuration
vim.g.mapleader = ";"
vim.g.maplocalleader = ";"
-- Line numbers
vim.opt.number = true
vim.opt.relativenumber = true
-- Mouse
vim.opt.mouse = ""
-- Mode display
vim.opt.showmode = false
-- Search
vim.opt.ignorecase = true
vim.opt.smartcase = true
vim.opt.hlsearch = true
-- Sign column
vim.opt.signcolumn = "yes"
-- Splits
vim.opt.splitright = true
vim.opt.splitbelow = true
-- Whitespace display
vim.opt.list = true
vim.opt.listchars = { tab = "» ", trail = "·", nbsp = "" }
-- Live substitution preview
vim.opt.inccommand = "split"
-- Cursor line
vim.opt.cursorline = true
-- General settings
vim.opt.shiftwidth = 4
vim.opt.tabstop = 4
vim.opt.expandtab = true
vim.opt.tw = 100
vim.opt.colorcolumn = "+1"
vim.opt.termguicolors = true
vim.opt.pumheight = 5
-- Light mode check
local function file_exists(name)
local f = io.open(name, "r")
return f ~= nil and io.close(f)
end
local home = os.getenv("HOME")
if file_exists(home .. "/.config/nvim/light_mode") then
vim.opt.background = "light"
end

59
lua/plugins/autopairs.lua Normal file
View file

@ -0,0 +1,59 @@
-- Autopairs plugin configuration
return {
"windwp/nvim-autopairs",
config = function()
local remap = vim.api.nvim_set_keymap
local npairs = require("nvim-autopairs")
npairs.setup({ map_bs = false, map_cr = false })
_G.MUtils = {}
MUtils.CR = function()
if vim.fn.pumvisible() ~= 0 then
if vim.fn.complete_info({ "selected" }).selected ~= -1 then
return npairs.esc("<c-y>")
else
return npairs.esc("<c-e>") .. npairs.autopairs_cr()
end
else
return npairs.autopairs_cr()
end
end
remap("i", "<cr>", "v:lua.MUtils.CR()", { expr = true, noremap = true })
MUtils.BS = function()
if vim.fn.pumvisible() ~= 0 and vim.fn.complete_info({ "mode" }).mode == "eval" then
return npairs.esc("<c-e>") .. npairs.autopairs_bs()
else
return npairs.autopairs_bs()
end
end
remap("i", "<bs>", "v:lua.MUtils.BS()", { expr = true, noremap = true })
-- put this to setup function and press <a-e> to use fast_wrap
npairs.setup({
fast_wrap = {},
})
-- change default fast_wrap
npairs.setup({
fast_wrap = {
map = "<M-e>",
chars = { "{", "[", "(", '"', "'" },
pattern = [=[[%'%"%>%]%)%}%,]]=],
end_key = "$",
before_key = "h",
after_key = "l",
cursor_pos_before = true,
keys = "qwertyuiopzxcvbnmasdfghjkl",
manual_position = true,
highlight = "Search",
highlight_grey = "Comment",
},
})
npairs.remove_rule("`")
end,
}

136
lua/plugins/completion.lua Normal file
View file

@ -0,0 +1,136 @@
-- Completion and snippets configuration
return {
{
"hrsh7th/nvim-cmp",
event = "InsertEnter",
dependencies = {
{
"L3MON4D3/LuaSnip",
build = (function()
if vim.fn.has("win32") == 1 or vim.fn.executable("make") == 0 then
return
end
return "make install_jsregexp"
end)(),
config = function()
require("luasnip.loaders.from_snipmate").lazy_load({ paths = "./snippets" })
local ls = require("luasnip")
local snip = ls.snippet
local node = ls.snippet_node
local text = ls.text_node
local insert = ls.insert_node
local func = ls.function_node
local choice = ls.choice_node
local dynamicn = ls.dynamic_node
ls.add_snippets(nil, {
python = {
snip({
trig = "imp",
namr = "Imports",
dscr = "Comments for imports",
}, {
text({ "# Core modules", "" }),
insert(1),
text({ "", "# Non-core modules", "" }),
insert(2),
text({ "", "# SEI modules", "" }),
insert(3),
}),
},
tex = {
snip({
trig = "input",
namr = "Input Cell",
dscr = "Cell for SEIInputTable",
}, {
text({ "\\hypertarget{" }),
insert(1),
text({ "}{" }),
insert(2),
text({ "} & \\SEICell{", "\t" }),
insert(3),
text({ "", "}\\\\", "" }),
}),
},
markdown = {
snip({
trig = "img",
namr = "image",
dscr = "Markdown img",
}, {
text({ "![" }),
insert(1),
text("]("),
insert(2),
text(")"),
}),
snip({
trig = "header",
namr = "header",
dscr = "Yaml header for markdown notes",
}, {
text({ "---", "" }),
text("title: "),
insert(1),
text({ "", "author: Alex Selimov", "" }),
text("tags: ["),
insert(2),
text({ "]", "", "" }),
text({ "---", "" }),
}),
},
})
end,
},
"saadparwaiz1/cmp_luasnip",
"hrsh7th/cmp-nvim-lsp",
"hrsh7th/cmp-path",
},
config = function()
local cmp = require("cmp")
local luasnip = require("luasnip")
luasnip.config.setup({})
cmp.setup({
snippet = {
expand = function(args)
luasnip.lsp_expand(args.body)
end,
},
completion = { completeopt = "menu,menuone,noinsert" },
mapping = cmp.mapping.preset.insert({
["<C-n>"] = cmp.mapping.select_next_item(),
["<C-p>"] = cmp.mapping.select_prev_item(),
["<C-y>"] = cmp.mapping.confirm({ select = true }),
["<leader>j"] = cmp.mapping(function()
if luasnip.expand_or_locally_jumpable() then
luasnip.expand_or_jump()
end
end, { "i", "s" }),
["<leader>k"] = cmp.mapping(function()
if luasnip.locally_jumpable(-1) then
luasnip.jump(-1)
end
end, { "i", "s" }),
}),
sources = {
{ name = "nvim_lsp" },
{ name = "luasnip" },
{ name = "path" },
},
})
end,
snippet = {
expand = function(args)
local luasnip = require("luasnip")
if not luasnip then
return
end
luasnip.lsp_expand(args.body)
end,
},
},
}

30
lua/plugins/dev-tools.lua Normal file
View file

@ -0,0 +1,30 @@
-- Development tools and utilities
return {
{
"codersauce/runst.nvim",
lazy = false,
opts = {},
config = function()
require("runst").setup()
end,
},
{
"Exafunction/windsurf.vim",
event = "BufEnter",
},
{
"danymat/neogen",
setup = {
snippet_engine = "luasnip",
},
config = function()
require("neogen").setup({})
local opts = { noremap = true, silent = true }
vim.api.nvim_set_keymap("n", "<Leader>dg", ":lua require('neogen').generate()<CR>", opts)
end,
version = "*",
},
}

22
lua/plugins/editor.lua Normal file
View file

@ -0,0 +1,22 @@
-- Editor enhancement plugins
return {
"christoomey/vim-tmux-navigator",
"kana/vim-textobj-user",
"mechatroner/rainbow_csv",
{
"GCBallesteros/vim-textobj-hydrogen",
dependencies = {
"kana/vim-textobj-user",
},
},
"godlygeek/tabular",
"tpope/vim-sleuth",
"norcalli/nvim-colorizer.lua",
"ixru/nvim-markdown",
"KeitaNakamura/tex-conceal.vim",
"skywind3000/asyncrun.vim",
"airblade/vim-gitgutter",
"tpope/vim-abolish",
"dhruvasagar/vim-table-mode",
}

View file

@ -0,0 +1,31 @@
-- Formatting plugins
return {
{
"stevearc/conform.nvim",
setup = {
formatters = {
black = {
command = "black",
prepend_args = { "--line-length", "100" },
},
},
},
opts = {
notify_on_error = false,
format_on_save = {
timeout_ms = 500,
lsp_fallback = false,
},
formatters_by_ft = {
lua = { "stylua" },
python = { "black" },
cpp = { "clang-format" },
c = { "clang-format" },
sh = { "beautysh" },
tex = { "latexindent" },
java = { "java" },
},
},
},
}

51
lua/plugins/iron.lua Normal file
View file

@ -0,0 +1,51 @@
-- Iron REPL plugin configuration
return {
"Vigemus/iron.nvim",
ft = { "python" },
config = function()
local iron = require("iron.core")
local view = require("iron.view")
iron.setup({
config = {
-- Whether a repl should be discarded or not
scratch_repl = true,
-- Your repl definitions come here
repl_definition = {
python = {
-- Can be a table or a function that
-- returns a table (see below)
command = "ipython --no-autoindent",
},
},
-- How the repl window will be displayed
-- See below for more information
repl_open_cmd = require("iron.view").bottom(20),
},
-- Iron doesn't set keymaps by default anymore.
-- You can set them here or manually add keymaps to the functions in iron.core
keymaps = {
visual_send = "<leader>sc",
send_file = "<leader>sf",
send_line = "<leader>sl",
cr = "<leader>s<cr>",
interrupt = "<leader>s<space>",
exit = "<leader>sq",
clear = "<leader>cl",
},
-- If the highlight is on, you can change how it looks
-- For the available options, check nvim_set_hl
highlight = {
italic = true,
},
ignore_blank_lines = false, -- ignore blank lines when sending visual select lines
})
vim.keymap.set("n", ";rs", "<cmd>IronRepl<cr>")
vim.keymap.set("n", ";rr", "<cmd>IronRestart<cr>")
vim.keymap.set("n", ";rf", "<cmd>IronFocus<cr>")
vim.keymap.set("n", ";rh", "<cmd>IronHide<cr>")
repl_open_cmd = "horizontal bot 20 split"
end,
}

193
lua/plugins/lsp.lua Normal file
View file

@ -0,0 +1,193 @@
-- LSP configuration
return {
{
"mfussenegger/nvim-jdtls",
config = function()
local jdtls = require("jdtls")
-- Find project root
local root_dir = require("jdtls.setup").find_root({ ".git", "mvnw", "gradlew" })
-- Path to your exported Eclipse/IntelliJ style xml
local style_path = vim.fn.expand("~/.config/nvim/GoogleStyle.xml")
local config = {
cmd = { "jdtls" }, -- or path to your startup script
root_dir = root_dir,
settings = {
java = {
format = {
settings = {
url = "file://" .. style_path,
profile = "GoogleStyle", -- must match the profile inside the xml
},
},
},
},
}
jdtls.start_or_attach(config)
end,
},
{
"neovim/nvim-lspconfig",
opts = {
autoformat = false,
},
dependencies = {
"williamboman/mason.nvim",
"williamboman/mason-lspconfig.nvim",
"WhoIsSethDaniel/mason-tool-installer.nvim",
{ "j-hui/fidget.nvim", opts = {} },
},
config = function()
vim.api.nvim_create_autocmd("LspAttach", {
group = vim.api.nvim_create_augroup("kickstart-lsp-attach", { clear = true }),
callback = function(event)
local map = function(keys, func, desc)
vim.keymap.set("n", keys, func, { buffer = event.buf, desc = "LSP: " .. desc })
end
map("gr", require("telescope.builtin").lsp_references, "[G]oto [R]eferences")
map("<leader>I", require("telescope.builtin").lsp_implementations, "[G]oto [I]mplementation")
map("<leader>D", require("telescope.builtin").lsp_type_definitions, "Type [D]efinition")
map("<leader>ds", require("telescope.builtin").lsp_document_symbols, "[D]ocument [S]ymbols")
map(
"<leader>ws",
require("telescope.builtin").lsp_dynamic_workspace_symbols,
"[W]orkspace [S]ymbols"
)
map("<leader>rn", vim.lsp.buf.rename, "[R]e[n]ame")
map("<leader>ca", vim.lsp.buf.code_action, "[C]ode [A]ction")
map("K", vim.lsp.buf.hover, "Hover Documentation")
map("gD", vim.lsp.buf.declaration, "[G]oto [D]eclaration")
local client = vim.lsp.get_client_by_id(event.data.client_id)
if client and client.server_capabilities.documentHighlightProvider then
vim.api.nvim_create_autocmd({ "CursorHold", "CursorHoldI" }, {
buffer = event.buf,
callback = vim.lsp.buf.document_highlight,
})
vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI" }, {
buffer = event.buf,
callback = vim.lsp.buf.clear_references,
})
end
end,
})
local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities = vim.tbl_deep_extend("force", capabilities, require("cmp_nvim_lsp").default_capabilities())
local servers = {
clangd = {
filetypes = {
"c",
"cpp",
},
},
taplo = {},
yamlls = { settings = { yaml = { format = { enable = false } } } },
pyright = {},
fortls = {},
jsonls = {},
bashls = { dependencies = "shellcheck" },
kotlin_language_server = {},
ts_ls = {},
rust_analyzer = {
settings = {
["rust-analyzer"] = {
check = {
command = "clippy",
},
rustfmt = {
extraArgs = { "+nightly" },
},
},
},
},
arduino_language_server = {},
ltex = {
settings = {
ltex = {
enabled = { "latex", "tex", "bib", "markdown" },
language = "auto",
diagnosticSeverity = "information",
sentenceCacheSize = 2000,
latex = {
commands = {
["\\hypertarget"] = "dummy",
},
},
dictionary = (function()
local files = {}
for _, file in ipairs(vim.api.nvim_get_runtime_file("dict/*", true)) do
local lang = vim.fn.fnamemodify(file, ":t:r")
local fullpath = vim.fs.normalize(file, ":p")
files[lang] = { ":" .. fullpath }
end
if files.default then
for lang, _ in pairs(files) do
if lang ~= "default" then
vim.list_extend(files[lang], files.default)
end
end
files.default = nil
end
return files
end)(),
},
},
},
lua_ls = {
settings = {
Lua = {
runtime = { version = "LuaJIT" },
workspace = {
checkThirdParty = false,
library = {
"${3rd}/luv/library",
unpack(vim.api.nvim_get_runtime_file("", true)),
},
},
},
},
},
}
require("mason").setup()
local ensure_installed = vim.tbl_keys(servers or {})
vim.list_extend(ensure_installed, {
"stylua",
"black",
"clang-format",
"beautysh",
"latexindent",
"prettier",
})
require("mason-tool-installer").setup({ ensure_installed = ensure_installed })
require("mason-lspconfig").setup({
handlers = {
function(server_name)
local server = servers[server_name] or {}
require("lspconfig")[server_name].setup({
cmd = server.cmd,
on_init = function(client)
client.offset_encoding = "utf-8"
end,
settings = server.settings,
filetypes = server.filetypes,
capabilities = vim.tbl_deep_extend("force", {}, capabilities, server.capabilities or {}),
})
end,
},
})
end,
},
}

29
lua/plugins/telescope.lua Normal file
View file

@ -0,0 +1,29 @@
-- Telescope fuzzy finder configuration
return {
"nvim-telescope/telescope.nvim",
event = "VeryLazy",
branch = "0.1.x",
dependencies = {
"nvim-lua/plenary.nvim",
{ "nvim-tree/nvim-web-devicons" },
{ "nvim-telescope/telescope-live-grep-args.nvim" },
},
config = function()
require("telescope").setup({
defaults = vim.tbl_extend("force", require("telescope.themes").get_ivy(), {
path_display = { "smart" },
}),
})
pcall(require("telescope").load_extension("live_grep_args"))
local builtin = require("telescope.builtin")
vim.keymap.set("n", "<C-g>", require("telescope").extensions.live_grep_args.live_grep_args)
vim.keymap.set(
"x",
"<C-g>",
"\"zy:lua require('telescope').extensions.live_grep_args.live_grep_args(require('telescope.themes').get_ivy({}))<cr><c-r>z"
)
vim.keymap.set("n", "<C-f>", builtin.find_files)
end,
}

135
lua/plugins/ui.lua Normal file
View file

@ -0,0 +1,135 @@
-- UI and appearance plugins
return {
{
"zenbones-theme/zenbones.nvim",
lazy = false,
priority = 1000,
dependencies = "rktjmp/lush.nvim",
config = function()
vim.cmd.colorscheme("zenwritten")
end,
},
{ "folke/todo-comments.nvim", dependencies = { "nvim-lua/plenary.nvim" }, opts = { signs = false } },
{
"echasnovski/mini.nvim",
keys = {
{
"<leader>m",
function()
require("mini.files").open(vim.api.nvim_buf_get_name(0), true)
end,
desc = "Open mini.files (Directory of Current File)",
},
},
config = function()
require("mini.files").setup()
local getWords = require("utils.statusline").getWords
require("mini.statusline").setup({
content = {
active = function()
local mode, mode_hl = MiniStatusline.section_mode({ trunc_width = 120 })
local git = MiniStatusline.section_git({ trunc_width = 75 })
local diagnostics = MiniStatusline.section_diagnostics({ trunc_width = 75 })
local filename = MiniStatusline.section_filename({ trunc_width = 140 })
local fileinfo = MiniStatusline.section_fileinfo({ trunc_width = 120 })
local location = MiniStatusline.section_location({ trunc_width = 75 })
local search = MiniStatusline.section_searchcount({ trunc_width = 75 })
local words = getWords()
return MiniStatusline.combine_groups({
{ hl = mode_hl, strings = { mode } },
{ hl = "MiniStatuslineDevinfo", strings = { git, diagnostics } },
"%<",
{ hl = "MiniStatuslineFilename", strings = { filename } },
"%=",
{ hl = "MiniStatuslineFileinfo", strings = { fileinfo } },
{ hl = "MiniStatuslineFileinfo", strings = { words } },
{ hl = mode_hl, strings = { search, location } },
})
end,
},
})
end,
},
{
"nvim-treesitter/nvim-treesitter",
build = ":TSUpdate",
config = function()
require("nvim-treesitter.configs").setup({
ensure_installed = { "bash", "c", "html", "lua", "markdown", "vim", "vimdoc" },
auto_install = true,
highlight = { enable = false },
indent = { enable = false },
})
end,
},
{
"folke/snacks.nvim",
priority = 1000,
lazy = false,
opts = {
dashboard = { enabled = true },
input = { enabled = true },
terminal = { enabled = true },
notify = { enabled = true },
},
keys = {
{
"<leader>T",
function()
Snacks.terminal()
end,
desc = "Toggle Terminal",
},
},
},
{
"MeanderingProgrammer/render-markdown.nvim",
dependencies = { "nvim-treesitter/nvim-treesitter", "echasnovski/mini.nvim" },
opts = {},
},
{
"folke/trouble.nvim",
opts = {},
cmd = "Trouble",
keys = {
{
"<leader>xx",
"<cmd>Trouble diagnostics toggle<cr>",
desc = "Diagnostics (Trouble)",
},
{
"<leader>xX",
"<cmd>Trouble diagnostics toggle filter.buf=0<cr>",
desc = "Buffer Diagnostics (Trouble)",
},
{
"<leader>cs",
"<cmd>Trouble symbols toggle focus=false<cr>",
desc = "Symbols (Trouble)",
},
{
"<leader>cl",
"<cmd>Trouble lsp toggle focus=false win.position=right<cr>",
desc = "LSP Definitions / references / ... (Trouble)",
},
{
"<leader>xL",
"<cmd>Trouble loclist toggle<cr>",
desc = "Location List (Trouble)",
},
{
"<leader>xQ",
"<cmd>Trouble qflist toggle<cr>",
desc = "Quickfix List (Trouble)",
},
},
},
}

15
lua/utils/statusline.lua Normal file
View file

@ -0,0 +1,15 @@
-- Statusline utilities
local M = {}
-- Function to get word count in status line
function M.getWords()
-- the third string here is the string for visual-block mode (^V)
if vim.fn.mode() == "v" or vim.fn.mode() == "V" or vim.fn.mode() == "" then
return vim.fn.wordcount().visual_words .. ""
else
return vim.fn.wordcount().words .. ""
end
end
return M