vim.g.mapleader = ' ' vim.g.maplocalleader = ' ' vim.g.loaded_netrw = 1 vim.g.loaded_netrwPlugin = 1 -- Set highlight on search vim.o.hlsearch = false -- Make line numbers default vim.wo.number = true -- Enable mouse mode vim.o.mouse = 'a' -- Enable break indent vim.o.breakindent = true -- Save undo history vim.o.undofile = true -- Case insensitive searching UNLESS /C or capital in search vim.o.ignorecase = true vim.o.smartcase = true -- Keep signcolumn on by default vim.wo.signcolumn = 'yes' -- Set completeopt to have a better completion experience vim.o.completeopt = 'menuone,noselect,noinsert' -- Use terminal colors vim.o.termguicolors = true -- Tabs vim.o.shiftwidth = 4 vim.o.smarttab = true vim.o.tabstop = 4 vim.o.expandtab = true vim.o.softtabstop = 4 -- Show cursor line vim.o.cursorline = true vim.o.pumheight = 10 vim.o.relativenumber = true vim.o.scrolloff = 8 vim.o.showmode = false vim.o.showtabline = 0 vim.o.wrap = true vim.o.foldmethod = "expr" vim.o.foldexpr = "nvim_treesitter#foldexpr()" vim.o.foldlevelstart = 99 -- show cursor line only in active window local cursorLineGrp = vim.api.nvim_create_augroup("CursorLine", { clear = true }) vim.api.nvim_create_autocmd( { "InsertLeave", "WinEnter" }, { pattern = "*", command = "set cursorline", group = cursorLineGrp } ) vim.api.nvim_create_autocmd( { "InsertEnter", "WinLeave" }, { pattern = "*", command = "set nocursorline", group = cursorLineGrp } ) -- Auto format on save vim.cmd [[autocmd BufWritePre * lua vim.lsp.buf.format()]] -- Auto update on file changes vim.o.autoread = true vim.api.nvim_create_autocmd({ "BufEnter", "CursorHold", "CursorHoldI", "FocusGained" }, { command = "if mode() != 'c' | checktime | endif", pattern = { "*" }, }) vim.api.nvim_create_autocmd({ "FileChangedShellPost" }, { command = 'echohl WarningMsg | echo "File changed on disk. Buffer reloaded." | echohl None', pattern = { "*" }, }) ------------------------------------------------------------------- -- Plugin management ------------------------------------------------------------------- local lazypath = vim.fn.stdpath 'data' .. '/lazy/lazy.nvim' 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) ------------------------------------------------------------------- -- Plugin installation ------------------------------------------------------------------- require('lazy').setup({ -- Color scheme ------------------------------------------ { dir = "~/linuxbox/nightly_cm.nvim", lazy = false, priority = 1000, config = function() vim.cmd.colorscheme 'nightly_cm' end, }, -- LSP ---------------------------------------------------- { "neovim/nvim-lspconfig" }, { "L3MON4D3/LuaSnip", -- follow latest release. version = "v2.*", -- Replace by the latest released major (first number of latest release) -- install jsregexp (optional!). build = "make install_jsregexp" }, { 'hrsh7th/nvim-cmp', dependencies = { 'saadparwaiz1/cmp_luasnip', 'hrsh7th/cmp-nvim-lsp', 'rafamadriz/friendly-snippets', 'hrsh7th/cmp-buffer', 'hrsh7th/cmp-path', 'hrsh7th/cmp-cmdline', }, }, { "ahmedkhalf/project.nvim", config = function() require("project_nvim").setup { sync_root_with_cwd = true, respect_buf_cwd = true, update_focused_file = { enable = true, update_root = true }, } end }, { -- Highlight, edit, and navigate code 'nvim-treesitter/nvim-treesitter', dependencies = { 'nvim-treesitter/nvim-treesitter-textobjects', }, build = ':TSUpdate', }, { "simrat39/rust-tools.nvim", }, { "ray-x/lsp_signature.nvim", config = function() require "lsp_signature".setup { bind = true, -- This is mandatory, otherwise border config won't get registered. handler_opts = { border = "solid" } } end }, -- File explorer ------------------------------------------- { "ibhagwan/fzf-lua", -- optional for icon support dependencies = { "nvim-tree/nvim-web-devicons" }, config = function() -- calling `setup` is optional for customization require("fzf-lua").setup({}) end }, { "nvim-neo-tree/neo-tree.nvim", branch = "v3.x", dependencies = { "nvim-lua/plenary.nvim", "nvim-tree/nvim-web-devicons", "MunifTanjim/nui.nvim", }, config = function() require("neo-tree").setup { close_if_last_window = true, popup_border_style = "rounded", enable_git_status = true, enable_diagnostics = true, open_files_do_not_replace_types = { "terminal", "trouble", "qf" }, -- when opening files, do not use windows containing these filetypes or buftypes sort_case_insensitive = false, -- used when sorting files and directories in the tree sort_function = nil, -- use a custom function for sorting files and directories in the tree default_component_configs = { container = { enable_character_fade = true }, indent = { indent_size = 2, padding = 1, -- extra padding on left hand side -- indent guides with_markers = true, indent_marker = "│", last_indent_marker = "└", highlight = "NeoTreeIndentMarker", -- expander config, needed for nesting files with_expanders = nil, -- if nil and file nesting is enabled, will enable expanders expander_collapsed = "", expander_expanded = "", expander_highlight = "NeoTreeExpander", }, icon = { folder_closed = "", folder_open = "", folder_empty = "󰜌", provider = function(icon, node, state) -- default icon provider utilizes nvim-web-devicons if available if node.type == "file" or node.type == "terminal" then local success, web_devicons = pcall(require, "nvim-web-devicons") local name = node.type == "terminal" and "terminal" or node.name if success then local devicon, hl = web_devicons.get_icon(name) icon.text = devicon or icon.text icon.highlight = hl or icon.highlight end end end, -- The next two settings are only a fallback, if you use nvim-web-devicons and configure default icons there -- then these will never be used. default = "*", highlight = "NeoTreeFileIcon" }, modified = { symbol = "[+]", highlight = "NeoTreeModified", }, name = { trailing_slash = false, use_git_status_colors = true, highlight = "NeoTreeFileName", }, git_status = { symbols = { -- Change type added = "", -- or "✚", but this is redundant info if you use git_status_colors on the name modified = "", -- or "", but this is redundant info if you use git_status_colors on the name deleted = "✖", -- this can only be used in the git_status source renamed = "󰁕", -- this can only be used in the git_status source -- Status type untracked = "", ignored = "", unstaged = "󰄱", staged = "", conflict = "", } }, -- If you don't want to use these columns, you can set `enabled = false` for each of them individually file_size = { enabled = true, required_width = 64, -- min width of window required to show this column }, type = { enabled = true, required_width = 122, -- min width of window required to show this column }, last_modified = { enabled = true, required_width = 88, -- min width of window required to show this column }, created = { enabled = true, required_width = 110, -- min width of window required to show this column }, symlink_target = { enabled = false, }, }, commands = { parent_or_close = function(state) local node = state.tree:get_node() if (node.type == "directory" or node:has_children()) and node:is_expanded() then state.commands.toggle_node(state) else require("neo-tree.ui.renderer").focus_node(state, node:get_parent_id()) end end, child_or_open = function(state) local node = state.tree:get_node() if node.type == "directory" or node:has_children() then if not node:is_expanded() then -- if unexpanded, expand state.commands.toggle_node(state) else -- if expanded and has children, seleect the next child require("neo-tree.ui.renderer").focus_node(state, node:get_child_ids()[1]) end else -- if not a directory just open it state.commands.open(state) end end, copy_selector = function(state) local node = state.tree:get_node() local filepath = node:get_id() local filename = node.name local modify = vim.fn.fnamemodify local results = { e = { val = modify(filename, ":e"), msg = "Extension only" }, f = { val = filename, msg = "Filename" }, F = { val = modify(filename, ":r"), msg = "Filename w/o extension" }, h = { val = modify(filepath, ":~"), msg = "Path relative to Home" }, p = { val = modify(filepath, ":."), msg = "Path relative to CWD" }, P = { val = filepath, msg = "Absolute path" }, } local messages = { { "\nChoose to copy to clipboard:\n", "Normal" }, } for i, result in pairs(results) do if result.val and result.val ~= "" then vim.list_extend(messages, { { ("%s."):format(i), "Identifier" }, { (" %s: "):format(result.msg) }, { result.val, "String" }, { "\n" }, }) end end vim.api.nvim_echo(messages, false, {}) local result = results[vim.fn.getcharstr()] if result and result.val and result.val ~= "" then vim.notify("Copied: " .. result.val) vim.fn.setreg("+", result.val) end end, }, window = { position = "float", width = 40, mappings = { [""] = false, -- disable space until we figure out which-key disabling h = "parent_or_close", l = "child_or_open", Y = "copy_selector", }, }, } end, }, -- GUI ------------------------------------------------------ { 'mrjones2014/smart-splits.nvim', opts = { at_edge = 'stop', } }, { -- Set lualine as statusline 'nvim-lualine/lualine.nvim', opts = { options = { icons_enabled = true, theme = 'nightly_cm', component_separators = '', section_separators = '', globalstatus = true, }, sections = { lualine_a = { 'mode' }, lualine_b = { 'branch', 'diff' }, lualine_c = { 'diagnostics', 'filename' }, lualine_x = { { -- Lsp server name . function() local msg = '' local clients = vim.lsp.get_clients({ bufnr = 0 }) if next(clients) == nil then return msg end for _, client in ipairs(clients) do msg = msg .. client.name .. ", " end if msg == '' then return msg end return msg:sub(1, -3) end, color = { fg = '#c6c6c6' }, }, }, lualine_y = { 'filetype' }, lualine_z = { 'progress' } }, }, }, { -- Adds git releated signs to the gutter, as well as utilities for managing changes 'lewis6991/gitsigns.nvim', opts = { signs = { add = { text = "▎" }, change = { text = "▎" }, delete = { text = "▎" }, topdelete = { text = "▎" }, changedelete = { text = "▎" }, untracked = { text = "▎" }, }, preview_config = { -- Options passed to nvim_open_win border = 'solid', style = 'minimal', relative = 'cursor', row = 0, col = 1 }, on_attach = function(bufnr) vim.keymap.set('n', 'gp', require('gitsigns').prev_hunk, { buffer = bufnr, desc = '[G]o to [P]revious Hunk' }) vim.keymap.set('n', 'gn', require('gitsigns').next_hunk, { buffer = bufnr, desc = '[G]o to [N]ext Hunk' }) vim.keymap.set('n', 'ph', require('gitsigns').preview_hunk, { buffer = bufnr, desc = '[P]review [H]unk' }) end, }, }, { -- Add indentation guides even on blank lines 'lukas-reineke/indent-blankline.nvim', main = "ibl", opts = { scope = { show_end = false, } } }, { "norcalli/nvim-colorizer.lua", config = function() require("colorizer").setup() end, }, { "folke/lazydev.nvim", ft = "lua", -- only load on lua files opts = { library = { -- See the configuration section for more details -- Load luvit types when the `vim.uv` word is found { path = "${3rd}/luv/library", words = { "vim%.uv" } }, }, }, }, { "folke/which-key.nvim", event = "VeryLazy", init = function() vim.o.timeout = true vim.o.timeoutlen = 300 end, opts = { -- your configuration comes here -- or leave it empty to use the default settings -- refer to the configuration section below } }, { 'MeanderingProgrammer/render-markdown.nvim', dependencies = { 'nvim-treesitter/nvim-treesitter', 'echasnovski/mini.nvim' }, -- if you use the mini.nvim suite -- dependencies = { 'nvim-treesitter/nvim-treesitter', 'echasnovski/mini.icons' }, -- if you use standalone mini plugins -- dependencies = { 'nvim-treesitter/nvim-treesitter', 'nvim-tree/nvim-web-devicons' }, -- if you prefer nvim-web-devicons ---@module 'render-markdown' ---@type render.md.UserConfig opts = { code = { enabled = true, sign = true, style = 'full', position = 'left', language_pad = 0, language_name = true, disable_background = { 'diff' }, width = 'full', left_margin = 0, left_pad = 0, right_pad = 0, min_width = 0, border = 'thick', above = '▄', below = '▀', highlight = 'RenderMarkdownCode', highlight_inline = 'RenderMarkdownCodeInline', highlight_language = nil, }, }, }, -- Utils --------------------------------------------------- { "max397574/better-escape.nvim", opts = { timeout = 300 } }, { 'numToStr/Comment.nvim', opts = {} }, { "folke/todo-comments.nvim", dependencies = { "nvim-lua/plenary.nvim" }, opts = { } }, { 'phaazon/hop.nvim', branch = 'v2', -- optional but strongly recommended config = function() require 'hop'.setup { keys = 'etovxqpdygfblzhckisuran' } end }, { "folke/trouble.nvim", dependencies = { "nvim-tree/nvim-web-devicons" }, opts = { }, }, }, {}) ------------------------------------------------------------------- -- LSP Configurations ------------------------------------------------------------------- local lspconfig = require('lspconfig') -- Lua lspconfig.lua_ls.setup {} -- Rust lspconfig.rust_analyzer.setup { -- Server-specific settings. See `:help lspconfig-setup` settings = { ['rust-analyzer'] = {}, }, } -- [[ Configure nvim-cmp ]] local cmp = require 'cmp' local luasnip = require 'luasnip' require('luasnip.loaders.from_vscode').lazy_load() luasnip.config.setup {} local cmp_opts = { border = "solid", winhighlight = 'Normal:Pmenu,FloatBorder:FloatBorder,Search:NONE,CursorLine:PmenuSel', } 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 cmp.setup { preselect = cmp.PreselectMode.None, view = { entries = "custom" -- can be "custom", "wildmenu" or "native" }, completion = { completion = { completeopt = 'menu,menuone,noinsert,noselect' }, }, window = { completion = cmp.config.window.bordered(cmp_opts), documentation = cmp.config.window.bordered(cmp_opts), }, snippet = { expand = function(args) luasnip.lsp_expand(args.body) end, }, duplicates = { nvim_lsp = 1, luasnip = 1, cmp_tabnine = 1, buffer = 1, path = 1, }, mapping = cmp.mapping.preset.insert { [''] = cmp.mapping.complete {}, [''] = cmp.mapping.confirm { behavior = cmp.ConfirmBehavior.Replace, select = false, }, [''] = cmp.mapping(function(fallback) if cmp.visible() and has_words_before() 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 = { -- { name = 'copilot', priority = 2 }, { name = 'nvim_lsp', priority = 2 }, { name = 'buffer', priority = 2 }, { name = 'path', priority = 2 }, { name = 'luasnip', priority = 2 }, }, } cmp.setup.cmdline({ '/', '?' }, { mapping = cmp.mapping.preset.cmdline(), view = { entries = { name = 'wildmenu', separator = '|' } }, window = { completion = cmp.config.window.bordered(cmp_opts), documentation = cmp.config.window.bordered(cmp_opts), }, sources = { { name = 'buffer' } } }) cmp.setup.cmdline(':', { mapping = cmp.mapping.preset.cmdline(), view = { entries = { name = 'wildmenu', separator = '|' } }, window = { completion = cmp.config.window.bordered(cmp_opts), documentation = cmp.config.window.bordered(cmp_opts), }, sources = cmp.config.sources({ { name = 'path' } }, { { name = 'cmdline' } }) }) -- [[ Configure Treesitter ]] require('nvim-treesitter.configs').setup { -- Add languages to be installed here that you want installed for treesitter auto_install = true, highlight = { enable = true }, indent = { enable = true }, incremental_selection = { enable = true, keymaps = { init_selection = '', node_incremental = '', scope_incremental = '', node_decremental = '', }, }, textobjects = { select = { enable = true, lookahead = true, -- Automatically jump forward to textobj, similar to targets.vim keymaps = { -- You can use the capture groups defined in textobjects.scm ['aa'] = '@parameter.outer', ['ia'] = '@parameter.inner', ['af'] = '@function.outer', ['if'] = '@function.inner', ['ac'] = '@class.outer', ['ic'] = '@class.inner', }, }, move = { enable = true, set_jumps = true, -- whether to set jumps in the jumplist goto_next_start = { [']m'] = '@function.outer', [']]'] = '@class.outer', }, goto_previous_start = { ['[m'] = '@function.outer', ['[['] = '@class.outer', }, goto_next_end = { [']M'] = '@function.outer', [']['] = '@class.outer', }, goto_previous_end = { ['[M'] = '@function.outer', ['[]'] = '@class.outer', }, }, swap = { enable = true, swap_next = { ['a'] = '@parameter.inner', }, swap_previous = { ['A'] = '@parameter.inner', }, }, }, } -- resizing splits vim.keymap.set({ 'n', 't' }, '', require('smart-splits').resize_left, { desc = 'Resize left' }) vim.keymap.set({ 'n', 't' }, '', require('smart-splits').resize_down, { desc = 'Resize down' }) vim.keymap.set({ 'n', 't' }, '', require('smart-splits').resize_up, { desc = 'Resize up' }) vim.keymap.set({ 'n', 't' }, '', require('smart-splits').resize_right, { desc = 'Resize right' }) -- moving between splits vim.keymap.set({ 'n', 't' }, '', require('smart-splits').move_cursor_left, { desc = 'Move cursor left' }) vim.keymap.set({ 'n', 't' }, '', require('smart-splits').move_cursor_down, { desc = 'Move cursor down' }) vim.keymap.set({ 'n', 't' }, '', require('smart-splits').move_cursor_up, { desc = 'Move cursor up' }) vim.keymap.set({ 'n', 't' }, '', require('smart-splits').move_cursor_right, { desc = 'Move cursor right' }) -- swapping buffers between windows vim.keymap.set('n', 'h', require('smart-splits').swap_buf_left, { desc = 'Swap buffer left' }) vim.keymap.set('n', 'j', require('smart-splits').swap_buf_down, { desc = 'Swap buffer down' }) vim.keymap.set('n', 'k', require('smart-splits').swap_buf_up, { desc = 'Swap buffer up' }) vim.keymap.set('n', 'l', require('smart-splits').swap_buf_right, { desc = 'Swap buffer right' }) -- Yank, delete, and paste always use system clipboard vim.keymap.set({ 'n', 'v' }, 'y', '"+y', { noremap = true, silent = true }) vim.keymap.set({ 'n', 'v' }, 'Y', '"+Y', { noremap = true, silent = true }) vim.keymap.set({ 'n', 'v' }, 'd', '"+d', { noremap = true, silent = true }) -- vim.keymap.set({ 'n', 'v' }, 'x', '"+x', { noremap = true, silent = true }) vim.keymap.set({ 'n', 'v' }, 'p', '"+p', { noremap = true, silent = true }) vim.keymap.set({ 'n', 'v' }, 'P', '"+P', { noremap = true, silent = true }) --Other vim.keymap.set('n', 's', ":HopWord", { desc = 'hop', silent = true }) vim.keymap.set('n', '', ":tabNext", { desc = 'Next tab', silent = true }) vim.keymap.set('n', '', ":Format", { desc = 'Format code', silent = true }) vim.keymap.set('n', '', ":make", { desc = 'Format code', silent = true }) vim.keymap.set('n', '', ":make clean", { desc = 'Format code', silent = true }) vim.keymap.set('n', '', ":write", { desc = 'Save', silent = true }) vim.keymap.set('n', '', ":quit", { desc = 'Quit', silent = true }) vim.keymap.set('n', 'e', ":Neotree filesystem reveal float toggle", { desc = 'File explorer', silent = true }) vim.keymap.set('n', 'd', ":Trouble diagnostics toggle", { desc = 'Diagnostic view', silent = true }) vim.keymap.set('n', 'f', ":FzfLua files", { desc = 'Find file', silent = true })