vim.g.mapleader = ' ' vim.g.maplocalleader = ' ' vim.opt.number = 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.list = true vim.opt.listchars = { tab = '» ', trail = '·', nbsp = '␣' } vim.opt.inccommand = 'split' vim.opt.autoread = true vim.opt.cursorline = true vim.opt.scrolloff = 10 local path_package = vim.fn.stdpath('data') .. '/site/' local mini_path = path_package .. 'pack/deps/start/mini.nvim' if not vim.uv.fs_stat(mini_path) then vim.cmd('echo "Installing mini.nvim" | redraw') vim.fn.system({ 'git', 'clone', '--filter=blob:none', 'https://github.com/echasnovski/mini.nvim', mini_path }) vim.cmd('packadd mini.nvim | helptags ALL') end require('mini.deps').setup({ path = { package = path_package } }) local add, now, later = MiniDeps.add, MiniDeps.now, MiniDeps.later add('folke/snacks.nvim') add('stevearc/quicker.nvim') add('stevearc/oil.nvim') add('A7Lavinraj/fyler.nvim') add('EdenEast/nightfox.nvim') add('williamboman/mason.nvim') add('williamboman/mason-lspconfig.nvim') add('neovim/nvim-lspconfig') add('folke/lazydev.nvim') add('Bilal2453/luvit-meta') add('NeogitOrg/neogit') add('nvim-lua/plenary.nvim') add('sindrets/diffview.nvim') add('folke/which-key.nvim') add({ source = 'saghen/blink.cmp', checkout = 'v1.8.0', }) add('stevearc/conform.nvim') add('coder/claudecode.nvim') now(function() require('mini.icons').setup({ extension = { lua = { glyph = '󰢱', hl = 'MiniIconsAzure' }, }, file = { ['init.lua'] = { glyph = '󰢱', hl = 'MiniIconsAzure' }, }, default = { file = { glyph = '󰈔', hl = 'MiniIconsGrey' }, directory = { glyph = '󰉋', hl = 'MiniIconsBlue' }, }, }) require('nightfox').setup({}) vim.cmd('colorscheme nightfox') end) later(function() require('snacks').setup({ picker = { prompt = '󰍉 ', icons = { enabled = true, }, win = { input = { keys = { [''] = false, }, }, }, }, }) require('quicker').setup({ keys = { { '>', function() require('quicker').expand({ before = 2, after = 2, add_to_existing = true }) end, desc = 'Expand quickfix context', }, { '<', function() require('quicker').collapse() end, desc = 'Collapse quickfix context', }, }, }) -- Function to show current directory in oil winbar _G.get_oil_winbar = function() local bufnr = vim.api.nvim_win_get_buf(vim.g.statusline_winid or 0) local dir = require('oil').get_current_dir(bufnr) if dir then return vim.fn.fnamemodify(dir, ':~') else return vim.api.nvim_buf_get_name(bufnr) end end require('oil').setup({ view_options = { show_hidden = true }, columns = { 'icon', 'permissions', 'size', 'mtime', }, win_options = { winbar = '%!v:lua.get_oil_winbar()', }, float = { padding = 2, max_width = 0, max_height = 10, border = 'rounded', win_options = { winblend = 0, }, override = function(conf) conf.anchor = 'SW' conf.row = vim.o.lines - vim.o.cmdheight - 1 conf.col = 0 conf.width = vim.o.columns return conf end, }, keymaps = { ['q'] = 'actions.close', [''] = 'actions.close', -- Emacs-like navigation (up/down) [''] = 'k', [''] = 'j', -- Preview with Ctrl+Space [''] = 'actions.preview', -- Copy current directory path to clipboard ['gy'] = { callback = function() local oil = require('oil') local dir = oil.get_current_dir() if dir then vim.fn.setreg('+', dir) vim.notify('Copied: ' .. dir, vim.log.levels.INFO) end end, desc = 'Copy current directory path', }, }, }) require('fyler').setup({ views = { finder = { confirm_simple = true, close_on_select = false, }, }, }) require('emacs-find-file').setup({ max_matches = 8, on_directory_enter = function(dir) require('oil').open(dir) end, }) require('neogit').setup({ integrations = { diffview = true, snacks = true, }, }) require('diffview').setup({ enhanced_diff_hl = true, }) require('which-key').setup({ preset = 'modern', icons = { separator = '→', group = '', }, win = { border = 'rounded', }, }) require('claudecode').setup({}) require('conform').setup({ formatters_by_ft = { lua = { 'stylua' }, python = { 'isort', 'black' }, rust = { 'rustfmt' }, javascript = { 'prettierd', 'prettier', stop_after_first = true }, typescript = { 'prettierd', 'prettier', stop_after_first = true }, javascriptreact = { 'prettierd', 'prettier', stop_after_first = true }, typescriptreact = { 'prettierd', 'prettier', stop_after_first = true }, json = { 'prettierd', 'prettier', stop_after_first = true }, yaml = { 'prettierd', 'prettier', stop_after_first = true }, markdown = { 'prettierd', 'prettier', stop_after_first = true }, html = { 'prettierd', 'prettier', stop_after_first = true }, css = { 'prettierd', 'prettier', stop_after_first = true }, c = { 'clang_format' }, cpp = { 'clang_format' }, }, }) -- Format command vim.api.nvim_create_user_command('Format', function(_) require('conform').format({ async = true, lsp_format = 'fallback' }) end, { desc = 'Format current buffer' }) end) later(function() local has_words_before = function() local col = vim.api.nvim_win_get_cursor(0)[2] if col == 0 then return false end local line = vim.api.nvim_get_current_line() return line:sub(col, col):match("%s") == nil end require('blink.cmp').setup({ keymap = { preset = 'none', [''] = { 'show', 'show_documentation', 'hide_documentation' }, [''] = { 'hide' }, [''] = { 'accept', 'fallback' }, [''] = { function(cmp) if has_words_before() then return cmp.insert_next() end end, 'fallback', }, [''] = { 'insert_prev' }, [''] = { 'select_next', 'fallback' }, [''] = { 'select_prev', 'fallback' }, }, appearance = { use_nvim_cmp_as_default = true, nerd_font_variant = 'mono', }, sources = { default = { 'lsp', 'path', 'buffer' }, }, completion = { menu = { enabled = true, border = 'rounded', }, list = { selection = { preselect = false, }, cycle = { from_top = false, }, }, documentation = { window = { border = 'rounded', }, auto_show = true, auto_show_delay_ms = 200, }, }, signature = { enabled = true, window = { border = 'rounded', }, }, }) end) later(function() require('mason').setup({ ui = { border = 'rounded', icons = { package_installed = '✓', package_pending = '➜', package_uninstalled = '✗', }, }, }) require('mason-lspconfig').setup({ ensure_installed = { 'lua_ls', -- Lua 'pyright', -- Python 'rust_analyzer', -- Rust 'clangd', -- C/C++ }, automatic_installation = true, }) -- LSP keybindings and options vim.api.nvim_create_autocmd('LspAttach', { group = vim.api.nvim_create_augroup('UserLspConfig', {}), callback = function(ev) local opts = { buffer = ev.buf } vim.keymap.set('n', 'gd', function() require('snacks').picker.lsp_definitions() end, vim.tbl_extend('force', opts, { desc = 'Goto Definition' })) vim.keymap.set('n', 'gD', function() require('snacks').picker.lsp_declarations() end, vim.tbl_extend('force', opts, { desc = 'Goto Declaration' })) vim.keymap.set('n', 'gr', function() require('snacks').picker.lsp_references() end, vim.tbl_extend('force', opts, { desc = 'References', nowait = true })) vim.keymap.set('n', 'gI', function() require('snacks').picker.lsp_implementations() end, vim.tbl_extend('force', opts, { desc = 'Goto Implementation' })) vim.keymap.set('n', 'gy', function() require('snacks').picker.lsp_type_definitions() end, vim.tbl_extend('force', opts, { desc = 'Goto T[y]pe Definition' })) vim.keymap.set('n', 'gai', function() require('snacks').picker.lsp_incoming_calls() end, vim.tbl_extend('force', opts, { desc = 'C[a]lls Incoming' })) vim.keymap.set('n', 'gao', function() require('snacks').picker.lsp_outgoing_calls() end, vim.tbl_extend('force', opts, { desc = 'C[a]lls Outgoing' })) vim.keymap.set('n', 'K', vim.lsp.buf.hover, vim.tbl_extend('force', opts, { desc = 'Hover documentation (press K again to focus)' })) vim.keymap.set('n', '', vim.lsp.buf.signature_help, vim.tbl_extend('force', opts, { desc = 'Signature help' })) vim.keymap.set('n', 'rn', vim.lsp.buf.rename, vim.tbl_extend('force', opts, { desc = 'Rename symbol' })) vim.keymap.set({ 'n', 'v' }, 'ca', vim.lsp.buf.code_action, vim.tbl_extend('force', opts, { desc = 'Code action' })) vim.keymap.set('n', '[d', vim.diagnostic.goto_prev, vim.tbl_extend('force', opts, { desc = 'Previous diagnostic' })) vim.keymap.set('n', ']d', vim.diagnostic.goto_next, vim.tbl_extend('force', opts, { desc = 'Next diagnostic' })) vim.keymap.set('n', 'd', vim.diagnostic.open_float, vim.tbl_extend('force', opts, { desc = 'Show diagnostic' })) end, }) -- Diagnostic UI improvements vim.diagnostic.config({ virtual_text = true, signs = true, underline = true, update_in_insert = false, severity_sort = true, float = { border = 'rounded', source = 'always', }, }) -- Add borders to hover windows local orig_util_open_floating_preview = vim.lsp.util.open_floating_preview function vim.lsp.util.open_floating_preview(contents, syntax, opts, ...) opts = opts or {} opts.border = opts.border or 'rounded' return orig_util_open_floating_preview(contents, syntax, opts, ...) end -- Setup lazydev for better plugin type support require('lazydev').setup({ library = { { path = 'luvit-meta/library', words = { 'vim%.uv' } }, }, }) -- Setup language servers using vim.lsp.config -- Lua local runtime_path = vim.split(package.path, ';') table.insert(runtime_path, 'lua/?.lua') table.insert(runtime_path, 'lua/?/init.lua') vim.lsp.config.lua_ls = { cmd = { 'lua-language-server' }, root_markers = { '.luarc.json', '.luarc.jsonc', '.luacheckrc', '.stylua.toml', 'stylua.toml', 'selene.toml', 'selene.yml', '.git' }, settings = { Lua = { runtime = { version = 'LuaJIT', path = runtime_path, }, diagnostics = { globals = { 'vim' }, }, workspace = { library = { vim.env.VIMRUNTIME, '${3rd}/luv/library', vim.fn.stdpath('data') .. '/site/pack/deps/start', }, checkThirdParty = false, }, completion = { callSnippet = 'Replace', }, telemetry = { enable = false }, }, }, } -- TypeScript/JavaScript vim.lsp.config.ts_ls = { cmd = { 'typescript-language-server', '--stdio' }, root_markers = { 'package.json', 'tsconfig.json', 'jsconfig.json', '.git' }, } -- Python vim.lsp.config.pyright = { cmd = { 'pyright-langserver', '--stdio' }, root_markers = { 'pyproject.toml', 'setup.py', 'setup.cfg', 'requirements.txt', 'Pipfile', '.git' }, } -- Rust vim.lsp.config.rust_analyzer = { cmd = { 'rust-analyzer' }, root_markers = { 'Cargo.toml', 'rust-project.json', '.git' }, } -- C/C++ vim.lsp.config.clangd = { cmd = { 'clangd', '--background-index', '--clang-tidy', '--header-insertion=iwyu' }, root_markers = { 'compile_commands.json', 'compile_flags.txt', '.clangd', '.git' }, } -- Enable language servers vim.lsp.enable({ 'lua_ls', 'ts_ls', 'pyright', 'rust_analyzer', 'clangd' }) end) local map = function(mode, lhs, rhs, opts) if type(opts) == 'string' then opts = { desc = opts } end require('snacks').keymap.set(mode, lhs, rhs, opts) end map('n', 'ex', function() require('fyler').toggle({ kind = 'split_left_most' }) end, 'Toggle file tree') map('n', 'fs', 'w', 'Save file') map('n', 'ff', function() require('emacs-find-file').open() end, 'Find file (emacs-style)') map('n', 'bb', function() require('snacks').picker.buffers() end, 'List buffers') map('n', 'bd', function() require('snacks').bufdelete() end, 'Buffer delete') map('n', '.', function() require('snacks').scratch() end, 'Scratch buffer') map('n', 'sf', function() require('snacks').picker.files() end, 'Search file') map('n', 'sg', function() require('snacks').picker.grep() end, 'Search grep') map('n', 'sw', function() require('snacks').picker.grep_word() end, 'Search grep word') map('n', 'ls', function() require('snacks').picker.lsp_symbols() end, 'LSP Symbols') map('n', 'lS', function() require('snacks').picker.lsp_workspace_symbols() end, 'LSP Workspace Symbols') map('n', 'zz', function() require('snacks').zen() end, 'Toggle zen mode') map('n', 'zm', function() require('snacks').zen.zoom() end, 'Toggle Zoom') map('n', 'qq', function() require('quicker').open({ focus = true }) end, 'Open/focus quickfix') map('n', 'ql', function() require('quicker').toggle({ loclist = true }) end, 'Toggle loclist') map('n', 'cr', 'source $MYVIMRC', 'Reload config') map('n', 'ce', 'edit $MYVIMRC', 'Edit config') map('n', 'ch', 'checkhealth', 'Check health') map('n', 'cf', 'Format', 'Format buffer') map('n', 'gg', function() require('neogit').open() end, 'Open Neogit') -- Claude Code keymaps map('n', 'ac', 'ClaudeCode', 'Toggle Claude') map('n', 'af', 'ClaudeCodeFocus', 'Focus Claude') map('n', 'ar', 'ClaudeCode --resume', 'Resume Claude') map('n', 'aC', 'ClaudeCode --continue', 'Continue Claude') map('n', 'am', 'ClaudeCodeSelectModel', 'Select Claude model') map('n', 'ab', 'ClaudeCodeAdd %', 'Add current buffer') map('v', 'as', 'ClaudeCodeSend', 'Send to Claude') map('n', 'aa', 'ClaudeCodeDiffAccept', 'Accept diff') map('n', 'ad', 'ClaudeCodeDiffDeny', 'Deny diff') -- File tree specific keymap for Claude Code vim.api.nvim_create_autocmd('FileType', { pattern = { 'oil' }, callback = function() vim.keymap.set('n', 'as', 'ClaudeCodeTreeAdd', { buffer = true, desc = 'Add file to Claude' }) end, }) map('n', 'wh', 'h', 'Window left') map('n', 'wj', 'j', 'Window down') map('n', 'wk', 'k', 'Window up') map('n', 'wl', 'l', 'Window right') map('n', 'ws', 'split', 'Split window horizontally') map('n', 'wv', 'vsplit', 'Split window vertically') map('n', 'wq', 'q', 'Close window') vim.api.nvim_create_autocmd('QuickFixCmdPost', { callback = function() vim.cmd('redraw!') vim.schedule(function() require('quicker').open({ focus = true }) end) end, }) vim.opt.shortmess:append('c')