diff --git a/laptop/.config/nvim/init.lua b/laptop/.config/nvim/init.lua new file mode 100644 index 0000000..a93f0a9 --- /dev/null +++ b/laptop/.config/nvim/init.lua @@ -0,0 +1,622 @@ +-- init.lua +-- Based on kickstart.nvim — mirrors emacs setup + +vim.g.mapleader = ' ' +vim.g.maplocalleader = '\\' -- vimtex uses \ll, \lv, etc. +vim.g.have_nerd_font = true + +------------------------------------------------------------------------------- +-- Options +------------------------------------------------------------------------------- + +vim.opt.number = true +vim.opt.relativenumber = true +vim.opt.mouse = 'a' +vim.opt.showmode = false +vim.opt.clipboard = 'unnamedplus' +vim.opt.breakindent = true +vim.opt.undofile = true +vim.opt.ignorecase = true +vim.opt.smartcase = true +vim.opt.signcolumn = 'yes' +vim.opt.updatetime = 250 +vim.opt.timeoutlen = 300 +vim.opt.splitright = true +vim.opt.splitbelow = true +vim.opt.inccommand = 'split' +vim.opt.cursorline = true +vim.opt.scrolloff = 8 +vim.opt.backup = false +vim.opt.swapfile = false +vim.opt.writebackup = false +vim.opt.expandtab = true +vim.opt.shiftwidth = 4 +vim.opt.tabstop = 4 +vim.opt.smartindent = true +vim.opt.list = true +vim.opt.listchars = { tab = '» ', trail = '·', nbsp = '␣' } + +------------------------------------------------------------------------------- +-- Keymaps +------------------------------------------------------------------------------- + +vim.keymap.set('n', '', 'nohlsearch') +vim.keymap.set('n', '[d', vim.diagnostic.goto_prev, { desc = 'Prev diagnostic' }) +vim.keymap.set('n', ']d', vim.diagnostic.goto_next, { desc = 'Next diagnostic' }) +vim.keymap.set('n', 'e', vim.diagnostic.open_float, { desc = 'Show diagnostic' }) + +vim.keymap.set('n', '', 'h', { desc = 'Move to left window' }) +vim.keymap.set('n', '', 'l', { desc = 'Move to right window' }) +vim.keymap.set('n', '', 'j', { desc = 'Move to lower window' }) +vim.keymap.set('n', '', 'k', { desc = 'Move to upper window' }) + +vim.keymap.set('t', '', '', { desc = 'Exit terminal mode' }) + +------------------------------------------------------------------------------- +-- Bootstrap lazy.nvim +------------------------------------------------------------------------------- + +local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim' +if not (vim.uv or vim.loop).fs_stat(lazypath) then + local out = vim.fn.system({ + 'git', 'clone', '--filter=blob:none', '--branch=stable', + 'https://github.com/folke/lazy.nvim.git', lazypath, + }) + if vim.v.shell_error ~= 0 then + error('Error cloning lazy.nvim:\n' .. out) + end +end +vim.opt.rtp:prepend(lazypath) + +------------------------------------------------------------------------------- +-- Plugins +------------------------------------------------------------------------------- + +require('lazy').setup({ + + -------------------------------------------------------------------------- + -- Theme + -------------------------------------------------------------------------- + { + 'catppuccin/nvim', + name = 'catppuccin', + priority = 1000, + opts = { + flavour = 'mocha', + integrations = { + cmp = true, + gitsigns = true, + telescope = { enabled = true }, + treesitter = true, + which_key = true, + mason = true, + rainbow_delimiters = true, + indent_blankline = { enabled = true }, + dap = true, + dap_ui = true, + native_lsp = { + enabled = true, + underlines = { + errors = { 'underline' }, + hints = { 'underline' }, + warnings = { 'underline' }, + information = { 'underline' }, + }, + }, + }, + }, + config = function(_, opts) + require('catppuccin').setup(opts) + vim.cmd.colorscheme('catppuccin') + end, + }, + + -------------------------------------------------------------------------- + -- Status line (doom-modeline equivalent) + -------------------------------------------------------------------------- + { + 'nvim-lualine/lualine.nvim', + dependencies = { 'nvim-tree/nvim-web-devicons' }, + opts = { + options = { + theme = 'catppuccin', + component_separators = '|', + section_separators = '', + }, + }, + }, + + -------------------------------------------------------------------------- + -- UI + -------------------------------------------------------------------------- + { + 'HiPhish/rainbow-delimiters.nvim', + config = function() + local rainbow = require('rainbow-delimiters') + vim.g.rainbow_delimiters = { + strategy = { [''] = rainbow.strategy['global'] }, + query = { [''] = 'rainbow-delimiters', lua = 'rainbow-blocks' }, + highlight = { + 'RainbowDelimiterRed', 'RainbowDelimiterYellow', + 'RainbowDelimiterBlue', 'RainbowDelimiterOrange', + 'RainbowDelimiterGreen', 'RainbowDelimiterViolet', + 'RainbowDelimiterCyan', + }, + } + end, + }, + + { + 'lukas-reineke/indent-blankline.nvim', + main = 'ibl', + opts = {}, + }, + + -------------------------------------------------------------------------- + -- Which-key (identical to emacs which-key) + -------------------------------------------------------------------------- + { + 'folke/which-key.nvim', + event = 'VimEnter', + opts = { + delay = 1000, + icons = { mappings = vim.g.have_nerd_font }, + spec = { + { 'f', group = 'find' }, + { 'l', group = 'lsp' }, + { 'd', group = 'debug' }, + { 'g', group = 'git' }, + { 'b', group = 'buffer' }, + { 'x', group = 'latex' }, + { 'h', group = 'harpoon' }, + }, + }, + }, + + -------------------------------------------------------------------------- + -- Telescope (vertico + consult + projectile) + -------------------------------------------------------------------------- + { + 'nvim-telescope/telescope.nvim', + event = 'VimEnter', + branch = '0.1.x', + dependencies = { + 'nvim-lua/plenary.nvim', + { + 'nvim-telescope/telescope-fzf-native.nvim', + build = 'make', + cond = function() return vim.fn.executable('make') == 1 end, + }, + 'nvim-tree/nvim-web-devicons', + }, + config = function() + require('telescope').setup({ + defaults = { + file_ignore_patterns = { 'node_modules', '.git/' }, + }, + }) + pcall(require('telescope').load_extension, 'fzf') + + local b = require('telescope.builtin') + vim.keymap.set('n', '', b.find_files, { desc = 'Find files' }) + vim.keymap.set('n', 'ff', b.find_files, { desc = 'Find files' }) + vim.keymap.set('n', 'fg', b.live_grep, { desc = 'Live grep' }) + vim.keymap.set('n', 'fb', b.buffers, { desc = 'Buffers' }) + vim.keymap.set('n', 'fh', b.help_tags, { desc = 'Help tags' }) + vim.keymap.set('n', 'fr', b.oldfiles, { desc = 'Recent files' }) + vim.keymap.set('n', 'fd', b.diagnostics, { desc = 'Diagnostics' }) + vim.keymap.set('n', 'fs', b.grep_string, { desc = 'Grep word under cursor' }) + vim.keymap.set('n', '/', b.current_buffer_fuzzy_find, { desc = 'Search buffer' }) + end, + }, + + -------------------------------------------------------------------------- + -- Git — lazygit float + gutter signs + -------------------------------------------------------------------------- + { + 'folke/snacks.nvim', + priority = 1000, + lazy = false, + opts = { lazygit = { enabled = true } }, + keys = { + { 'gg', function() Snacks.lazygit() end, desc = 'Lazygit' }, + }, + }, + + { + 'lewis6991/gitsigns.nvim', + opts = { + signs = { + add = { text = '+' }, + change = { text = '~' }, + delete = { text = '_' }, + topdelete = { text = '‾' }, + changedelete = { text = '~' }, + }, + on_attach = function(bufnr) + local gs = require('gitsigns') + local map = function(l, r, desc) + vim.keymap.set('n', l, r, { buffer = bufnr, desc = desc }) + end + map(']h', gs.next_hunk, 'Next hunk') + map('[h', gs.prev_hunk, 'Prev hunk') + map('gs', gs.stage_hunk, 'Stage hunk') + map('gr', gs.reset_hunk, 'Reset hunk') + map('gS', gs.stage_buffer, 'Stage buffer') + map('gp', gs.preview_hunk, 'Preview hunk') + map('gb', gs.blame_line, 'Blame line') + map('gd', gs.diffthis, 'Diff this') + end, + }, + }, + + -------------------------------------------------------------------------- + -- Treesitter (treesit-auto equivalent) + -------------------------------------------------------------------------- + { + 'nvim-treesitter/nvim-treesitter', + build = ':TSUpdate', + main = 'nvim-treesitter.configs', + opts = { + ensure_installed = { + 'bash', 'c', 'cpp', 'css', 'go', 'html', 'javascript', + 'json', 'lua', 'luadoc', 'markdown', 'markdown_inline', + 'rust', 'tsx', 'typescript', 'vim', 'vimdoc', 'yaml', 'latex', + 'c_sharp', + }, + auto_install = true, + highlight = { enable = true }, + indent = { enable = true }, + }, + }, + + -------------------------------------------------------------------------- + -- Snippets (yasnippet + yasnippet-snippets equivalent) + -------------------------------------------------------------------------- + { + 'L3MON4D3/LuaSnip', + build = 'make install_jsregexp', + dependencies = { 'rafamadriz/friendly-snippets' }, + config = function() + require('luasnip.loaders.from_vscode').lazy_load() + end, + }, + + -------------------------------------------------------------------------- + -- Completion (company equivalent) + -------------------------------------------------------------------------- + { + 'hrsh7th/nvim-cmp', + event = 'InsertEnter', + dependencies = { + 'saadparwaiz1/cmp_luasnip', + 'hrsh7th/cmp-nvim-lsp', + 'hrsh7th/cmp-path', + 'hrsh7th/cmp-buffer', + }, + config = function() + local cmp = require('cmp') + local luasnip = require('luasnip') + + cmp.setup({ + snippet = { + expand = function(args) luasnip.lsp_expand(args.body) end, + }, + completion = { completeopt = 'menu,menuone,noinsert' }, + mapping = cmp.mapping.preset.insert({ + [''] = cmp.mapping.select_next_item(), + [''] = cmp.mapping.select_prev_item(), + [''] = cmp.mapping.scroll_docs(-4), + [''] = cmp.mapping.scroll_docs(4), + [''] = cmp.mapping.complete(), + [''] = cmp.mapping.confirm({ select = true }), + [''] = cmp.mapping(function(fallback) + if cmp.visible() then + cmp.select_next_item() + elseif luasnip.expand_or_locally_jumpable() then + luasnip.expand_or_jump() + else + fallback() + end + end, { 'i', 's' }), + [''] = cmp.mapping(function(fallback) + if cmp.visible() then + cmp.select_prev_item() + elseif luasnip.locally_jumpable(-1) then + luasnip.jump(-1) + else + fallback() + end + end, { 'i', 's' }), + }), + sources = cmp.config.sources( + { { name = 'nvim_lsp' }, { name = 'luasnip' }, { name = 'path' } }, + { { name = 'buffer' } } + ), + formatting = { + format = function(entry, item) + item.menu = ({ + nvim_lsp = '[LSP]', luasnip = '[Snip]', + buffer = '[Buf]', path = '[Path]', + })[entry.source.name] + return item + end, + }, + }) + end, + }, + + -------------------------------------------------------------------------- + -- Autopairs (smartparens equivalent) + -------------------------------------------------------------------------- + { + 'windwp/nvim-autopairs', + event = 'InsertEnter', + config = function() + require('nvim-autopairs').setup({}) + require('cmp').event:on('confirm_done', + require('nvim-autopairs.completion.cmp').on_confirm_done() + ) + end, + }, + + -------------------------------------------------------------------------- + -- Formatting (prettier, stylua, clang-format, etc.) + -------------------------------------------------------------------------- + { + 'stevearc/conform.nvim', + event = { 'BufWritePre' }, + cmd = { 'ConformInfo' }, + keys = { + { + 'lf', + function() require('conform').format({ async = true }) end, + mode = { 'n', 'v' }, + desc = 'Format buffer', + }, + }, + opts = { + formatters_by_ft = { + lua = { 'stylua' }, + go = { 'goimports', 'gofmt' }, + rust = { 'rustfmt' }, + c = { 'clang_format' }, + cpp = { 'clang_format' }, + javascript = { 'prettier' }, + javascriptreact = { 'prettier' }, + typescript = { 'prettier' }, + typescriptreact = { 'prettier' }, + css = { 'prettier' }, + html = { 'prettier' }, + json = { 'prettier' }, + }, + format_on_save = { timeout_ms = 500, lsp_fallback = true }, + }, + }, + + -------------------------------------------------------------------------- + -- LSP (eglot equivalent — nvim-lspconfig + mason) + -------------------------------------------------------------------------- + { + 'neovim/nvim-lspconfig', + dependencies = { + { 'williamboman/mason.nvim', opts = {} }, + '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('lsp-attach', { clear = true }), + callback = function(event) + local map = function(keys, func, desc) + vim.keymap.set('n', keys, func, { buffer = event.buf, desc = desc }) + end + local b = require('telescope.builtin') + + -- mirrors emacs C-c l bindings + map('gd', b.lsp_definitions, 'Go to definition') + map('gr', b.lsp_references, 'Find references') + map('gi', b.lsp_implementations, 'Go to implementation') + map('lt', b.lsp_type_definitions, 'Type definition') + map('ls', b.lsp_document_symbols, 'Document symbols') + map('K', vim.lsp.buf.hover, 'Hover documentation') + map('la', vim.lsp.buf.code_action, 'Code action') + map('ln', vim.lsp.buf.rename, 'Rename') + map('le', vim.diagnostic.open_float, 'Show diagnostics') + map('lh', vim.lsp.buf.signature_help, 'Signature help') + + local client = vim.lsp.get_client_by_id(event.data.client_id) + if client then + -- highlight references to word under cursor + if client.supports_method('textDocument/documentHighlight') then + local grp = vim.api.nvim_create_augroup('lsp-highlight', { clear = false }) + vim.api.nvim_create_autocmd({ 'CursorHold', 'CursorHoldI' }, { + buffer = event.buf, group = grp, + callback = vim.lsp.buf.document_highlight, + }) + vim.api.nvim_create_autocmd({ 'CursorMoved', 'CursorMovedI' }, { + buffer = event.buf, group = grp, + callback = vim.lsp.buf.clear_references, + }) + end + -- inlay hints toggle + if client.supports_method('textDocument/inlayHint') then + map('li', function() + vim.lsp.inlay_hint.enable( + not vim.lsp.inlay_hint.is_enabled({ bufnr = event.buf }) + ) + end, 'Toggle inlay hints') + end + end + end, + }) + + local capabilities = vim.tbl_deep_extend('force', + vim.lsp.protocol.make_client_capabilities(), + require('cmp_nvim_lsp').default_capabilities() + ) + + local servers = { + clangd = {}, + gopls = {}, + rust_analyzer = {}, + ts_ls = {}, + cssls = {}, + html = {}, + csharp_ls = {}, + lua_ls = { + settings = { + Lua = { + completion = { callSnippet = 'Replace' }, + diagnostics = { globals = { 'vim', 'Snacks' } }, + workspace = { + library = vim.api.nvim_get_runtime_file('', true), + checkThirdParty = false, + }, + }, + }, + }, + } + + require('mason-tool-installer').setup({ + ensure_installed = { 'stylua', 'prettier', 'clang-format' }, + }) + + require('mason-lspconfig').setup({ + ensure_installed = vim.tbl_keys(servers), + handlers = { + function(server_name) + local cfg = servers[server_name] or {} + cfg.capabilities = vim.tbl_deep_extend('force', capabilities, cfg.capabilities or {}) + require('lspconfig')[server_name].setup(cfg) + end, + }, + }) + end, + }, + + -------------------------------------------------------------------------- + -- Debugging (dap-mode equivalent) + -------------------------------------------------------------------------- + { + 'mfussenegger/nvim-dap', + dependencies = { + { + 'rcarriga/nvim-dap-ui', + dependencies = { 'nvim-neotest/nvim-nio' }, + config = function() + local dap, dapui = require('dap'), require('dapui') + dapui.setup() + -- auto-open/close UI with debug sessions + dap.listeners.after.event_initialized['dapui_config'] = dapui.open + dap.listeners.before.event_terminated['dapui_config'] = dapui.close + dap.listeners.before.event_exited['dapui_config'] = dapui.close + end, + }, + { 'theHamsta/nvim-dap-virtual-text', opts = {} }, + { 'leoluz/nvim-dap-go', config = function() require('dap-go').setup() end }, + }, + config = function() + local dap = require('dap') + + -- codelldb for C / C++ / Rust (installed via mason) + dap.adapters.codelldb = { + type = 'server', + port = '${port}', + executable = { + command = vim.fn.stdpath('data') .. '/mason/bin/codelldb', + args = { '--port', '${port}' }, + }, + } + local codelldb_cfg = {{ + name = 'Launch', + type = 'codelldb', + request = 'launch', + program = function() + return vim.fn.input('Executable: ', vim.fn.getcwd() .. '/', 'file') + end, + cwd = '${workspaceFolder}', + stopOnEntry = false, + }} + dap.configurations.c = codelldb_cfg + dap.configurations.cpp = codelldb_cfg + dap.configurations.rust = codelldb_cfg + end, + keys = { + { 'dd', function() require('dap').continue() end, desc = 'Debug: start/continue' }, + { 'db', function() require('dap').toggle_breakpoint() end, desc = 'Debug: toggle breakpoint' }, + { 'dn', function() require('dap').step_over() end, desc = 'Debug: step over' }, + { 'di', function() require('dap').step_into() end, desc = 'Debug: step into' }, + { 'do', function() require('dap').step_out() end, desc = 'Debug: step out' }, + { 'dr', function() require('dap').restart() end, desc = 'Debug: restart' }, + { 'dq', function() require('dap').disconnect() end, desc = 'Debug: disconnect' }, + { 'du', function() require('dapui').toggle() end, desc = 'Debug: toggle UI' }, + { 'de', function() require('dapui').eval(vim.fn.input('Eval: ')) end, desc = 'Debug: eval expression' }, + }, + }, + + -- install codelldb via mason + { + 'williamboman/mason.nvim', + opts = function(_, opts) + opts.ensure_installed = opts.ensure_installed or {} + vim.list_extend(opts.ensure_installed, { 'codelldb' }) + end, + }, + + -------------------------------------------------------------------------- + -- LaTeX (AUCTeX + latexmk + zathura equivalent) + -------------------------------------------------------------------------- + { + 'lervag/vimtex', + lazy = false, + init = function() + vim.g.vimtex_view_method = 'zathura' + vim.g.vimtex_compiler_method = 'latexmk' + vim.g.vimtex_compiler_latexmk = { + options = { '-pdf', '-interaction=nonstopmode', '-synctex=1' }, + } + end, + }, + + -------------------------------------------------------------------------- + -- Harpoon + -------------------------------------------------------------------------- + { + 'ThePrimeagen/harpoon', + branch = 'harpoon2', + dependencies = { 'nvim-lua/plenary.nvim' }, + config = function() + local harpoon = require('harpoon') + harpoon:setup() + + vim.keymap.set('n', 'a', function() harpoon:list():add() end, { desc = 'Harpoon: add file' }) + vim.keymap.set('n', 'h', function() harpoon.ui:toggle_quick_menu(harpoon:list()) end, { desc = 'Harpoon: menu' }) + vim.keymap.set('n', '1', function() harpoon:list():select(1) end, { desc = 'Harpoon: file 1' }) + vim.keymap.set('n', '2', function() harpoon:list():select(2) end, { desc = 'Harpoon: file 2' }) + vim.keymap.set('n', '3', function() harpoon:list():select(3) end, { desc = 'Harpoon: file 3' }) + vim.keymap.set('n', '4', function() harpoon:list():select(4) end, { desc = 'Harpoon: file 4' }) + end, + }, + + -------------------------------------------------------------------------- + -- Misc + -------------------------------------------------------------------------- + { 'tpope/vim-sleuth' }, -- automatic indent detection + { + 'folke/todo-comments.nvim', + event = 'VimEnter', + dependencies = { 'nvim-lua/plenary.nvim' }, + opts = { signs = false }, + }, + +}, { + ui = { + icons = vim.g.have_nerd_font and {} or { + cmd = '⌘', config = '🛠', event = '📅', ft = '📂', init = '⚙', + keys = '🗝', plugin = '🔌', runtime = '💻', source = '📄', + start = '🚀', task = '📌', lazy = '💤', + }, + }, +}) diff --git a/laptop/.config/tmux/tmux.conf b/laptop/.config/tmux/tmux.conf index 10f64d3..ed027b7 100644 --- a/laptop/.config/tmux/tmux.conf +++ b/laptop/.config/tmux/tmux.conf @@ -1,36 +1,32 @@ -# tmux.conf — keybindings mirror emacs window/windmove conventions +# tmux.conf — vim-style keybindings -# Emacs-style splits (mirrors C-x 2 / C-x 3) +# Splits unbind '"' unbind % -bind 2 split-window -v -c "#{pane_current_path}" # horizontal split (below) -bind 3 split-window -h -c "#{pane_current_path}" # vertical split (right) +bind s split-window -v -c "#{pane_current_path}" +bind v split-window -h -c "#{pane_current_path}" -# Emacs-style close (mirrors C-x 0 / C-x 1) -bind 0 kill-pane -bind 1 kill-pane -a +# Close +bind x kill-pane -# Emacs-style cycle (mirrors C-x o) -bind o select-pane -t :.+ +# Pane navigation: prefix+hjkl and M-hjkl without prefix +bind h select-pane -L +bind j select-pane -D +bind k select-pane -U +bind l select-pane -R -# Pane navigation: prefix+Arrow everywhere, M-S-Arrow without prefix -bind Left select-pane -L -bind Right select-pane -R -bind Up select-pane -U -bind Down select-pane -D +bind -n M-h select-pane -L +bind -n M-j select-pane -D +bind -n M-k select-pane -U +bind -n M-l select-pane -R -bind -n M-S-Left select-pane -L -bind -n M-S-Right select-pane -R -bind -n M-S-Up select-pane -U -bind -n M-S-Down select-pane -D - -# Copy mode with emacs keys -set-window-option -g mode-keys emacs +# Copy mode with vi keys +set-window-option -g mode-keys vi # General set -g mouse on set -g history-limit 10000 -set -sg escape-time 0 # no delay for escape — important for emacs +set -sg escape-time 0 # no delay for escape — important for vim set -g focus-events on set -g default-terminal "tmux-256color" set -as terminal-overrides ',*:Tc' # true color passthrough