dotfiles/nvim/init.lua
2025-06-14 17:17:10 -05:00

651 lines
17 KiB
Lua

local vim = vim
local Plug = vim.fn['plug#']
local o = vim.opt
local g = vim.g
o.title = true
g.mapleader = ' '
g.maplocalleader = ' '
g.toggle_theme_icon = ""
g.have_nerd_font = true
-- Line numbers
o.number = true
-- o.spell = true
o.spelllang="en_us,es"
-- Configure how new splits should be opened
o.splitright = true
o.splitbelow = true
o.inccommand = 'split'
-- Tabs
o.shiftwidth = 2
o.tabstop = 2
o.undofile = true
o.undodir = vim.fn.stdpath("data") .. "/undo//"
-- https://www.reddit.com/r/neovim/comments/wlkq0e/neovim_configuration_to_backup_files_with/
o.backup = true
o.backupdir = vim.fn.stdpath("data") .. "/backup//"
-- o.clipboard = "unnamedplus"
-- Statuscolumn
-- Set sign column to be 4.
-- So, if I have a new diff, It'll not feel weird, by the signcolumn expanding
o.signcolumn = "yes:1"
-- Statusline
o.cmdheight = 0
o.laststatus = 3
-- Folds
o.foldcolumn = "1"
o.foldlevel = 99
o.foldlevelstart = 99
o.foldenable = true
-- Enable break indent
o.breakindent = true
o.list = true
o.listchars = { tab = '» ', trail = '·', nbsp = '' }
-- Scrolloff
o.scrolloff = 15
-- Show which line your cursor is on
o.cursorline = true
-- Searching
o.ignorecase = true
o.smartcase = true
o.pumblend = 10 -- Popup blend
o.pumheight = 10 -- Maximum number of entries in a popup
vim.opt.hlsearch = true
vim.keymap.set('n', '<Esc>', '<cmd>nohlsearch<CR>')
local map = vim.keymap.set
map('v', "<leader>y", '"+y')
map('n', '<C-j>', ':Treewalker Down<CR>', { noremap = true })
map('n', '<C-k>', ':Treewalker Up<CR>', { noremap = true })
map('n', '<C-h>', ':Treewalker Left<CR>', { noremap = true })
map('n', '<C-l>', ':Treewalker Right<CR>', { noremap = true })
-- Diagnostic keymaps
map('n', '[d', vim.diagnostic.goto_prev, { desc = 'Go to previous [D]iagnostic message' })
map('n', ']d', vim.diagnostic.goto_next, { desc = 'Go to next [D]iagnostic message' })
map('n', '<leader>e', vim.diagnostic.open_float, { desc = 'Show diagnostic [E]rror messages' })
map('v', '<leader>q', vim.diagnostic.setloclist, { desc = 'Open diagnostic [Q]uickfix list' })
-- Prevent CapsLock from interfering with movement keys
map('n', 'J', 'j', { noremap = true })
map('n', 'K', 'k', { noremap = true })
map('n', 'H', 'h', { noremap = true })
map('n', 'L', 'l', { noremap = true })
map('n', 'U', 'u', { noremap = true })
-- Better window navigation (works across splits)
map('n', '<C-h>', '<C-w>h', { desc = 'Move to left window' })
map('n', '<C-j>', '<C-w>j', { desc = 'Move to lower window' })
map('n', '<C-k>', '<C-w>k', { desc = 'Move to upper window' })
map('n', '<C-l>', '<C-w>l', { desc = 'Move to right window' })
-- Keep cursor centered during searches and jumps
map('n', 'n', 'nzzzv', { desc = 'Next search result (centered)' })
map('n', 'N', 'Nzzzv', { desc = 'Prev search result (centered)' })
map('n', 'G', 'Gzz', { desc = 'Jump to line (centered)' })
-- Clear last search highlight
map('n', '<leader>nh', ':nohlsearch<CR>', { desc = 'Clear search highlights' })
-- Half-page jumping with centered cursor
map('n', '<C-d>', '<C-d>zz')
map('n', '<C-u>', '<C-u>zz')
-- Move lines up/down in visual mode
map('v', 'J', ":m '>+1<CR>gv=gv", { desc = 'Move selection down' })
map('v', 'K', ":m '<-2<CR>gv=gv", { desc = 'Move selection up' })
-- Quick fix list navigation
map('n', '<C-q>', ':cnext<CR>', { desc = 'Next quickfix item' })
map('n', '<C-Q>', ':cprev<CR>', { desc = 'Previous quickfix item' })
-- System clipboard integration
map('n', '<leader>p', '"+p', { desc = 'Paste from system clipboard' })
map('n', '<leader>P', '"+P', { desc = 'Paste from system clipboard (before)' })
-- Better indentation
map('v', '<', '<gv', { desc = 'Unindent and stay in visual mode' })
map('v', '>', '>gv', { desc = 'Indent and stay in visual mode' })
-- Buffer navigation
map('n', '<leader>bn', ':bnext<CR>', { desc = 'Next buffer' })
map('n', '<leader>bp', ':bprevious<CR>', { desc = 'Previous buffer' })
map('n', '<leader>bd', ':bdelete<CR>', { desc = 'Delete buffer' })
-- Replace current word
map('n', '<leader>s', [[:%s/\<<C-r><C-w>\>/<C-r><C-w>/gI<Left><Left><Left>]],
{ desc = 'Replace current word' })
-- Keep search matches in middle
map('n', '*', '*zz', { desc = 'Search word under cursor (centered)' })
map('n', '#', '#zz', { desc = 'Search backward word (centered)' })
-- Quick save/quit
map('n', '<leader>w', ':w<CR>', { desc = 'Save file' })
map('n', '<leader>q', ':q<CR>', { desc = 'Quit window' })
-- Disable hell
map('n', 'Q', 'gq')
-- Toggle terminal
map('n', '<leader>tt', ':terminal<CR>',
{ desc = '[T]oggle [T]erminal' })
-- Escape terminal mode
map('t', '<Esc>', '<C-\\><C-n>',
{ desc = 'Exit terminal mode' })
-- Open chad tree
map('n', '<leader>.', ':CHADopen<CR>', { noremap = true })
-- Quick escape from insert mode
map('i', 'jk', '<Esc>', { desc = 'Exit insert mode' })
-- Clear search highlights
map('n', '<Esc>', '<cmd>nohlsearch<CR>',
{ desc = 'Clear search highlights' })
-- Toggle spellcheck
map('n', '<leader>ts', ':set spell!<CR>',
{ desc = '[T]oggle [S]pellcheck' })
-- Epic Golang JSON metadata macro, piss off pussies
map('n', '<leader>j', '_ywA<Space><Esc>pbeld$bguwciw"<Esc>pA"<Esc>F"<Ignore>ijson:<Esc>bi`<Esc>A`<Esc>_j', { noremap = true })
map('n', '<leader>_', 'ebcw_<Esc>', { noremap = true, desc = 'Replace word with _ (to ignore variable)' })
vim.call('plug#begin')
-- Style
Plug 'folke/tokyonight.nvim'
Plug 'olimorris/onedarkpro.nvim'
Plug 'xiyaowong/transparent.nvim'
Plug 'alexblackie/lunarised'
Plug 'starryleo/starry-vim-colorschemes'
-- lsp
Plug('williamboman/mason.nvim')
Plug('williamboman/mason-lspconfig.nvim')
-- more lsp
Plug('neovim/nvim-lspconfig')
Plug('L3MON4D3/LuaSnip', {
['tag'] = 'v2.*',
})
Plug 'marilari88/twoslash-queries.nvim'
Plug 'neovim/nvim-lspconfig'
-- auto complete
Plug 'hrsh7th/cmp-nvim-lsp'
Plug 'hrsh7th/cmp-buffer'
Plug 'hrsh7th/cmp-path'
Plug 'hrsh7th/cmp-cmdline'
Plug 'hrsh7th/nvim-cmp'
Plug 'zbirenbaum/copilot-cmp' -- copilot
Plug 'L3MON4D3/LuaSnip'
Plug 'saadparwaiz1/cmp_luasnip'
-- useful stuff
Plug 'echasnovski/mini.nvim'
Plug 'zbirenbaum/copilot.lua'
Plug 'ms-jpq/chadtree'
Plug 'ryanoasis/vim-devicons'
Plug 'kyazdani42/nvim-web-devicons'
Plug 'jetzig-framework/zmpl.vim'
Plug 'folke/which-key.nvim'
-- Personalization
Plug 'nvim-lualine/lualine.nvim'
Plug 'goolord/alpha-nvim'
Plug 'lukas-reineke/indent-blankline.nvim'
Plug 'karb94/neoscroll.nvim'
Plug 'folke/noice.nvim'
Plug 'MunifTanjim/nui.nvim'
-- More stuff
Plug 'SmiteshP/nvim-navic'
Plug 'kr40/nvim-macros'
-- tree sitter
Plug 'nvim-treesitter/nvim-treesitter'
Plug('aaronik/treewalker.nvim', {
opts = {
highlight = true
}
})
vim.call('plug#end')
vim.cmd('set background=dark')
-- Better Around/Inside textobjects
--
-- Examples:
-- - va) - [V]isually select [A]round [)]paren
-- - yinq - [Y]ank [I]nside [N]ext [']quote
-- - ci' - [C]hange [I]nside [']quote
require('mini.ai').setup { n_lines = 500 }
-- Add/delete/replace surroundings (brackets, quotes, etc.)
--
-- - saiw) - [S]urround [A]dd [I]nner [W]ord [)]Paren
-- - sd' - [S]urround [D]elete [']quotes
-- - sr)' - [S]urround [R]eplace [)] [']
require('mini.surround').setup()
-- Simple and easy statusline.
-- You could remove this setup call if you don't like it,
-- and try some other statusline plugin
local statusline = require 'mini.statusline'
-- set use_icons to true if you have a Nerd Font
statusline.setup { use_icons = vim.g.have_nerd_font }
-- You can configure sections in the statusline by overriding their
-- default behavior. For example, here we set the section for
-- cursor location to LINE:COLUMN
---@diagnostic disable-next-line: duplicate-set-field
statusline.section_location = function()
return '%2l:%-2v'
end
-- ... and there is more!
-- Check out: https://github.com/echasnovski/mini.nvim
require('mini.pairs').setup()
require('nvim-treesitter.configs').setup({
-- A list of parser names, or "all" (the listed parsers MUST always be installed)
ensure_installed = {'bash', 'c', 'html', 'lua', 'luadoc', 'markdown', 'vim', 'vimdoc'},
sync_install = false,
highlight = {
enable = true,
additional_vim_regex_highlighting = false,
},
})
require("mason").setup()
local lspconfig = require('lspconfig')
lspconfig.zls.setup({
{cmd = '~/.zig/zls'}
})
local lsp_cmds = vim.api.nvim_create_augroup('lsp_cmds', {clear = true})
vim.api.nvim_create_autocmd('LspAttach', {
group = lsp_cmds,
desc = 'LSP actions',
callback = function()
local bufmap = function(mode, lhs, rhs)
vim.keymap.set(mode, lhs, rhs, {buffer = true})
end
bufmap('n', 'K', '<cmd>lua vim.lsp.buf.hover()<cr>')
bufmap('n', 'gd', '<cmd>lua vim.lsp.buf.definition()<cr>')
bufmap('n', 'gD', '<cmd>lua vim.lsp.buf.declaration()<cr>')
bufmap('n', 'gi', '<cmd>lua vim.lsp.buf.implementation()<cr>')
bufmap('n', 'go', '<cmd>lua vim.lsp.buf.type_definition()<cr>')
bufmap('n', 'gr', '<cmd>lua vim.lsp.buf.references()<cr>')
bufmap('n', 'gs', '<cmd>lua vim.lsp.buf.signature_help()<cr>')
bufmap('n', '<F2>', '<cmd>lua vim.lsp.buf.rename()<cr>')
bufmap({'n', 'x'}, '<F3>', '<cmd>lua vim.lsp.buf.format({async = true})<cr>')
bufmap('n', '<F4>', '<cmd>lua vim.lsp.buf.code_action()<cr>')
bufmap('n', 'gl', '<cmd>lua vim.diagnostic.open_float()<cr>')
bufmap('n', '[d', '<cmd>lua vim.diagnostic.goto_prev()<cr>')
bufmap('n', ']d', '<cmd>lua vim.diagnostic.goto_next()<cr>')
end
})
require('mason-lspconfig').setup({
ensure_installed = {},
handlers = {
function(server)
lspconfig[server].setup({
capabilities = lsp_capabilities,
})
end,
}
})
vim.opt.completeopt = {'menu', 'menuone', 'noselect'}
require("copilot").setup({
suggestion = { enabled = false },
panel = { enabled = false },
})
require("copilot_cmp").setup()
local cmp = require('cmp')
local luasnip = require('luasnip')
local select_opts = {behavior = cmp.SelectBehavior.Select}
local has_words_before = function()
if vim.api.nvim_buf_get_option(0, "buftype") == "prompt" then return false end
local line, col = unpack(vim.api.nvim_win_get_cursor(0))
return col ~= 0 and vim.api.nvim_buf_get_text(0, line-1, 0, line-1, col, {})[1]:match("^%s*$") == nil
end
-- See :help cmp-config
cmp.setup({
snippet = {
expand = function(args)
luasnip.lsp_expand(args.body)
end
},
sources = {
{name = 'path'},
{name = 'nvim_lsp', keyword_length = 3},
{name = 'buffer', keyword_length = 3},
{name = 'luasnip', keyword_length = 2},
{name = 'copilot', group_index = 2}
},
window = {
documentation = cmp.config.window.bordered()
},
formatting = {
fields = {'menu', 'abbr', 'kind'},
format = function(entry, item)
local menu_icon = {
nvim_lsp = 'λ',
luasnip = '',
buffer = 'Ω',
path = '🖫',
}
item.menu = menu_icon[entry.source.name]
return item
end,
},
-- See :help cmp-mapping
mapping = {
['<Up>'] = cmp.mapping.select_prev_item(select_opts),
['<Down>'] = cmp.mapping.select_next_item(select_opts),
['<C-p>'] = cmp.mapping.select_prev_item(select_opts),
['<C-n>'] = cmp.mapping.select_next_item(select_opts),
['<C-u>'] = cmp.mapping.scroll_docs(-4),
['<C-f>'] = cmp.mapping.scroll_docs(4),
['<C-e>'] = cmp.mapping.abort(),
['<CR>'] = cmp.mapping.confirm({select = false}),
['<C-d>'] = cmp.mapping(function(fallback)
if luasnip.jumpable(1) then
luasnip.jump(1)
else
fallback()
end
end, {'i', 's'}),
['<C-b>'] = cmp.mapping(function(fallback)
if luasnip.jumpable(-1) then
luasnip.jump(-1)
else
fallback()
end
end, {'i', 's'}),
['<Tab>'] = cmp.mapping(function(fallback)
local col = vim.fn.col('.') - 1
if cmp.visible() and has_words_before() then
cmp.select_next_item({ behavior = cmp.SelectBehavior.Select })
elseif cmp.visible() then
cmp.select_next_item(select_opts)
elseif col == 0 or vim.fn.getline('.'):sub(col, col):match('%s') then
fallback()
else
cmp.complete()
end
end, {'i', 's'}),
['<S-Tab>'] = cmp.mapping(function(fallback)
if cmp.visible() then
cmp.select_prev_item(select_opts)
else
fallback()
end
end, {'i', 's'}),
},
})
-- auto save
vim.api.nvim_create_autocmd("BufWritePost", {
pattern = "hyprland.conf",
callback = function()
os.execute("hyprctl reload")
vim.cmd.edit()
end
})
vim.api.nvim_create_autocmd("BufWritePost", {
pattern = "init.lua",
callback = function()
-- Reload config without restarting Neovim
vim.cmd("source $MYVIMRC")
end
})
-- Execute go tidy on save because nico told me so
vim.api.nvim_create_autocmd("BufWritePost", {
pattern = "*.go",
callback = function()
vim.cmd("silent! !go mod tidy")
vim.cmd("silent! edit")
end
})
-- Format on save
vim.api.nvim_create_autocmd('BufWritePre', {
pattern = '*',
callback = function() vim.lsp.buf.format() end
})
-- Code actions menu
map('n', '<leader>ca', vim.lsp.buf.code_action,
{ desc = '[C]ode [A]ctions' })
-- Find references
map('n', '<leader>gr', vim.lsp.buf.references,
{ desc = '[G]oto [R]eferences' })
-- files to the right
local chadtree_settings = {
['view.open_direction'] = "right",
['view.width'] = 20
}
vim.api.nvim_set_var("chadtree_settings", chadtree_settings)
-- Customization
require('lualine').setup {
options = {
theme = 'tokyonight',
component_separators = { left = '', right = ''},
section_separators = { left = '', right = ''},
globalstatus = true,
icons_enabled = true
},
sections = {
lualine_a = {'mode'},
lualine_b = {'branch', 'diff', 'diagnostics'},
lualine_c = {
{
'filename',
path = 1, -- Show relative path
symbols = { modified = '', readonly = '' }
}
},
lualine_x = {
'encoding',
{
'fileformat',
symbols = { unix = ' LF' }
},
{
'filetype',
icons_enabled = true,
icon = { align = 'right' }
}
},
lualine_y = {'progress'},
lualine_z = {'location'}
},
extensions = {'chadtree', 'fugitive'}
}
local alpha = require'alpha'
local dashboard = require'alpha.themes.dashboard'
dashboard.section.header.val = {
[[ __ __ __ __ ______ __ __ ______ __ __ ]],
[[/\ \_\ \ /\ \/\ \ /\___ \ /\ \/\ \ /\ == \ /\ \/\ \ ]],
[[\ \____ \\ \ \_\ \\/_/ /__\ \ \_\ \\ \ __- \ \ \_\ \ ]],
[[ \/\_____\\ \_____\ /\_____\\ \_____\\ \_\ \_\\ \_____\]],
[[ \/_____/ \/_____/ \/_____/ \/_____/ \/_/ /_/ \/_____/]]
}
dashboard.section.buttons.val = {
dashboard.button("e", " New file", ":ene <BAR> startinsert <CR>"),
dashboard.button("f", "󰈞 Find file", ":CHADopen<CR>"),
dashboard.button("c", " Config", ":e ~/.config/nvim/init.lua<CR>"),
dashboard.button("q", " Quit", ":qa<CR>"),
}
alpha.setup(dashboard.config)
local highlight = {
"CursorColumn",
-- "Whitespace",
}
-- Indent guides
require('ibl').setup {
indent = { highlight = highlight, char = "" },
scope = { enabled = false },
}
-- Smooth scrolling
require('neoscroll').setup {
mappings = {'<C-u>', '<C-d>', 'zz', 'zt', 'zb'},
hide_cursor = true,
stop_eof = true,
respect_scrolloff = false,
}
require('nvim-navic').setup {
icons = {
File = "󰈙 ",
Module = "",
Namespace = "󰌗 ",
Package = "",
Class = "󰌗 ",
Method = "󰆧 ",
Property = "",
Field = "",
Constructor = "",
Enum = "󰕘",
Interface = "󰕘",
Function = "󰊕 ",
Variable = "󰆧 ",
Constant = "󰏿 ",
String = "󰀬 ",
Number = "󰎠 ",
Boolean = "",
Array = "󰅪 ",
Object = "󰅩 ",
Key = "󰌋 ",
Null = "󰟢 ",
EnumMember = "",
Struct = "󰌗 ",
Event = "",
Operator = "󰆕 ",
TypeParameter = "󰊄 ",
}
}
vim.o.winbar = "%{%v:lua.require'nvim-navic'.get_location()%}"
map('n', '<leader>tt', function()
local height = math.ceil(vim.o.lines * 0.3)
vim.api.nvim_command('botright split | resize ' .. height)
vim.api.nvim_command('terminal')
end, { desc = 'Open floating terminal' })
-- Better theme configuration
require('tokyonight').setup {
style = 'night',
transparent = true,
styles = {
floats = 'transparent',
sidebars = 'transparent'
},
on_highlights = function(hl, c)
hl.TelescopeBorder = { fg = c.bg_dark, bg = c.bg_dark }
hl.TelescopePromptBorder = { fg = c.orange, bg = c.bg }
end
}
vim.cmd.colorscheme('tokyonight')
-- Etc
require('noice').setup {
cmdline = {
view = 'cmdline_popup',
format = {
cmdline = { pattern = '^:', icon = '', lang = 'vim' },
search_down = { kind = 'search', pattern = '^/', icon = ' ', lang = 'regex' },
search_up = { kind = 'search', pattern = '^%?', icon = ' ', lang = 'regex' },
}
},
popupmenu = {
backend = 'nui',
},
views = {
cmdline_popup = {
position = { row = 5, col = '50%' },
size = { width = 60, height = 'auto' },
}
}
}
require('which-key').setup {
plugins = {
marks = true,
registers = true,
spelling = { enabled = true },
presets = {
operators = true,
motions = true,
text_objects = true,
windows = true,
nav = true,
z = true,
g = true
}
},
}