From 289b80b2b579b437d49ff35ae088b2f74304350a Mon Sep 17 00:00:00 2001 From: ZockerKatze Date: Wed, 11 Jun 2025 10:24:57 +0200 Subject: [PATCH] initial --- init.lua | 1259 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1259 insertions(+) create mode 100644 init.lua diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..9cfd1bb --- /dev/null +++ b/init.lua @@ -0,0 +1,1259 @@ +-- Bootstrap Lazy.nvim +local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" +if not vim.loop.fs_stat(lazypath) then + local result = vim.fn.system({ + "git", "clone", "--filter=blob:none", + "https://github.com/folke/lazy.nvim.git", + "--branch=stable", -- latest stable release + lazypath + }) + if vim.v.shell_error ~= 0 then + vim.api.nvim_echo({ + { "Failed to clone lazy.nvim:\n", "ErrorMsg" }, + { result, "WarningMsg" }, + { "\nPress any key to exit...", "MoreMsg" }, + }, true, {}) + vim.fn.getchar() + os.exit(1) + end +end +vim.opt.rtp:prepend(lazypath) + +-- Leader key must be set before lazy setup +vim.g.mapleader = " " +vim.g.maplocalleader = "\\" + +-- Core vim settings +vim.opt.number = true +vim.opt.relativenumber = true +vim.opt.wrap = false +vim.opt.expandtab = true +vim.opt.shiftwidth = 2 +vim.opt.tabstop = 2 +vim.opt.softtabstop = 2 +vim.opt.smartindent = true +vim.opt.cursorline = true +vim.opt.termguicolors = true +vim.opt.signcolumn = "yes" +vim.opt.updatetime = 250 +vim.opt.timeoutlen = 300 +vim.opt.splitright = true +vim.opt.splitbelow = true +vim.opt.undofile = true +vim.opt.ignorecase = true +vim.opt.smartcase = true +vim.opt.mouse = "a" +vim.opt.clipboard = "unnamedplus" +vim.o.shortmess = vim.o.shortmess .. "I" + +-- Plugin specifications +require("lazy").setup({ + -- LSP and completion setup + { + "neovim/nvim-lspconfig", + event = { "BufReadPre", "BufNewFile" }, + dependencies = { + "williamboman/mason.nvim", + "williamboman/mason-lspconfig.nvim", + "hrsh7th/cmp-nvim-lsp", + "folke/neodev.nvim", + }, + config = function() + -- Setup neodev before lspconfig + require("neodev").setup() + + -- Diagnostic configuration + vim.diagnostic.config({ + virtual_text = { + prefix = "●", + }, + signs = true, + underline = true, + update_in_insert = false, + severity_sort = true, + }) + + -- Diagnostic signs + local signs = { Error = " ", Warn = " ", Hint = "󰠠 ", Info = " " } + for type, icon in pairs(signs) do + local hl = "DiagnosticSign" .. type + vim.fn.sign_define(hl, { text = icon, texthl = hl, numhl = "" }) + end + + -- LSP keymaps + local on_attach = function(client, bufnr) + local opts = { buffer = bufnr, silent = true } + local keymap = vim.keymap.set + + keymap("n", "gD", vim.lsp.buf.declaration, opts) + keymap("n", "gd", vim.lsp.buf.definition, opts) + keymap("n", "K", vim.lsp.buf.hover, opts) + keymap("n", "gi", vim.lsp.buf.implementation, opts) + keymap("n", "", vim.lsp.buf.signature_help, opts) + keymap("n", "wa", vim.lsp.buf.add_workspace_folder, opts) + keymap("n", "wr", vim.lsp.buf.remove_workspace_folder, opts) + keymap("n", "wl", function() + print(vim.inspect(vim.lsp.buf.list_workspace_folders())) + end, opts) + keymap("n", "D", vim.lsp.buf.type_definition, opts) + keymap("n", "rn", vim.lsp.buf.rename, opts) + keymap("n", "ca", vim.lsp.buf.code_action, opts) + keymap("n", "gr", vim.lsp.buf.references, opts) + keymap("n", "f", function() + vim.lsp.buf.format({ async = true }) + end, opts) + end + + local capabilities = require("cmp_nvim_lsp").default_capabilities() + local lspconfig = require("lspconfig") + + -- Setup Mason + require("mason").setup({ + ui = { + icons = { + package_installed = "✓", + package_pending = "➜", + package_uninstalled = "✗", + }, + }, + }) + + require("mason-lspconfig").setup({ + ensure_installed = { + "lua_ls", + "pyright", + "clangd", + "html", + "cssls", + "ts_ls", + }, + automatic_installation = true, + }) + + -- LSP server configurations + local servers = { + lua_ls = { + settings = { + Lua = { + workspace = { + checkThirdParty = false, + }, + completion = { + callSnippet = "Replace", + }, + }, + }, + }, + pyright = {}, + clangd = { + cmd = { "clangd", "--background-index", "--clang-tidy" }, + }, + html = { + filetypes = { "html", "htmldjango" }, + }, + cssls = {}, + ts_ls = {}, + } + + require("mason-lspconfig").setup_handlers({ + function(server_name) + local server = servers[server_name] or {} + server.capabilities = vim.tbl_deep_extend("force", {}, capabilities, server.capabilities or {}) + server.on_attach = on_attach + lspconfig[server_name].setup(server) + end, + }) + end, + }, + + -- Mason for managing external tools + { + "williamboman/mason.nvim", + cmd = "Mason", + keys = { { "cm", "Mason", desc = "Mason" } }, + }, + + -- Autocompletion + { + "hrsh7th/nvim-cmp", + version = false, + event = "InsertEnter", + dependencies = { + "hrsh7th/cmp-nvim-lsp", + "hrsh7th/cmp-buffer", + "hrsh7th/cmp-path", + "saadparwaiz1/cmp_luasnip", + }, + opts = function() + vim.api.nvim_set_hl(0, "CmpGhostText", { link = "Comment", default = true }) + local cmp = require("cmp") + local luasnip = require("luasnip") + + return { + completion = { + completeopt = "menu,menuone,noinsert", + }, + snippet = { + expand = function(args) + luasnip.lsp_expand(args.body) + end, + }, + mapping = cmp.mapping.preset.insert({ + [""] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Insert }), + [""] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Insert }), + [""] = cmp.mapping.scroll_docs(-4), + [""] = cmp.mapping.scroll_docs(4), + [""] = cmp.mapping.complete(), + [""] = cmp.mapping.abort(), + [""] = cmp.mapping.confirm({ select = true }), + [""] = cmp.mapping(function(fallback) + if cmp.visible() then + cmp.select_next_item() + elseif luasnip.expand_or_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.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) + local icons = { + nvim_lsp = "", + luasnip = "", + buffer = "", + path = "", + } + item.menu = icons[entry.source.name] + return item + end, + }, + experimental = { + ghost_text = { + hl_group = "CmpGhostText", + }, + }, + } + end, + }, + + -- Snippets + { + "L3MON4D3/LuaSnip", + build = (function() + if vim.fn.has("win32") == 1 or vim.fn.executable("make") == 0 then + return + end + return "make install_jsregexp" + end)(), + dependencies = { + "rafamadriz/friendly-snippets", + config = function() + require("luasnip.loaders.from_vscode").lazy_load() + end, + }, + opts = { + history = true, + delete_check_events = "TextChanged", + }, + }, + + -- Neodev for better lua development + { "folke/neodev.nvim", opts = {} }, + + -- Treesitter + { + "nvim-treesitter/nvim-treesitter", + version = false, + build = ":TSUpdate", + event = { "BufReadPost", "BufNewFile" }, + cmd = { "TSUpdateSync" }, + opts = { + ensure_installed = { + "bash", + "c", + "cpp", + "css", + "html", + "javascript", + "json", + "lua", + "markdown", + "markdown_inline", + "python", + "query", + "regex", + "tsx", + "typescript", + "vim", + "yaml", + }, + highlight = { enable = true }, + indent = { enable = true }, + incremental_selection = { + enable = true, + keymaps = { + init_selection = "", + node_incremental = "", + scope_incremental = false, + node_decremental = "", + }, + }, + }, + config = function(_, opts) + require("nvim-treesitter.configs").setup(opts) + end, + }, + + -- Auto tag + { + "windwp/nvim-ts-autotag", + event = "InsertEnter", + opts = {}, + }, + + -- Emmet + { + "mattn/emmet-vim", + ft = { "html", "css", "javascript", "typescript", "javascriptreact", "typescriptreact" }, + init = function() + vim.g.user_emmet_leader_key = "" + end, + }, + + -- Colorscheme + { + "folke/tokyonight.nvim", + lazy = false, + priority = 1000, + opts = { + style = "night", + transparent = false, + terminal_colors = true, + styles = { + comments = { italic = true }, + keywords = { italic = true }, + functions = {}, + variables = {}, + sidebars = "dark", + floats = "dark", + }, + }, + config = function(_, opts) + require("tokyonight").setup(opts) + vim.cmd.colorscheme("tokyonight") + end, + }, + + -- Statusline + { + "nvim-lualine/lualine.nvim", + event = "VeryLazy", + opts = function() + return { + options = { + theme = "tokyonight", + globalstatus = true, + disabled_filetypes = { statusline = { "dashboard", "alpha" } }, + }, + sections = { + lualine_a = { "mode" }, + lualine_b = { "branch" }, + lualine_c = { + { + "diagnostics", + symbols = { + error = " ", + warn = " ", + info = " ", + hint = "󰠠 ", + }, + }, + { "filetype", icon_only = true, separator = "", padding = { left = 1, right = 0 } }, + { "filename", path = 1, symbols = { modified = " ", readonly = "", unnamed = "" } }, + }, + lualine_x = { + { + function() return require("noice").api.status.command.get() end, + cond = function() return package.loaded["noice"] and require("noice").api.status.command.has() end, + color = { fg = "#ff9e64" }, + }, + { + function() return require("noice").api.status.mode.get() end, + cond = function() return package.loaded["noice"] and require("noice").api.status.mode.has() end, + color = { fg = "#ff9e64" }, + }, + { "encoding" }, + { "fileformat" }, + }, + lualine_y = { "progress" }, + lualine_z = { "location" }, + }, + extensions = { "neo-tree", "lazy" }, + } + end, + }, + + -- File explorer + { + "nvim-neo-tree/neo-tree.nvim", + branch = "v3.x", + cmd = "Neotree", + keys = { + { "fe", function() require("neo-tree.command").execute({ toggle = true, dir = vim.loop.cwd() }) end, desc = "Explorer NeoTree (cwd)" }, + { "fE", function() require("neo-tree.command").execute({ toggle = true, dir = vim.fn.expand("%:p:h") }) end, desc = "Explorer NeoTree (current file)" }, + { "e", "fe", desc = "Explorer NeoTree (cwd)", remap = true }, + { "E", "fE", desc = "Explorer NeoTree (current file)", remap = true }, + }, + deactivate = function() + vim.cmd([[Neotree close]]) + end, + init = function() + if vim.fn.argc(-1) == 1 then + local stat = vim.loop.fs_stat(vim.fn.argv(0)) + if stat and stat.type == "directory" then + require("neo-tree") + end + end + end, + opts = { + sources = { "filesystem", "buffers", "git_status", "document_symbols" }, + open_files_do_not_replace_types = { "terminal", "Trouble", "qf", "Outline" }, + filesystem = { + bind_to_cwd = false, + follow_current_file = { enabled = true }, + use_libuv_file_watcher = true, + filtered_items = { + visible = true, + hide_dotfiles = false, + hide_gitignored = false, + }, + }, + window = { + position = "left", + width = 30, + mappings = { + [""] = "none", + }, + }, + default_component_configs = { + indent = { + with_expanders = true, + expander_collapsed = "", + expander_expanded = "", + expander_highlight = "NeoTreeExpander", + }, + }, + }, + }, + + -- Fuzzy finder + { + "nvim-telescope/telescope.nvim", + cmd = "Telescope", + version = false, + dependencies = { + "nvim-lua/plenary.nvim", + { + "nvim-telescope/telescope-fzf-native.nvim", + build = "make", + enabled = vim.fn.executable("make") == 1, + config = function() + require("telescope").load_extension("fzf") + end, + }, + }, + keys = { + { ",", "Telescope buffers show_all_buffers=true", desc = "Switch Buffer" }, + { "/", "Telescope live_grep", desc = "Grep (root dir)" }, + { ":", "Telescope command_history", desc = "Command History" }, + { "", "Telescope find_files", desc = "Find Files (root dir)" }, + { "fb", "Telescope buffers", desc = "Buffers" }, + { "ff", "Telescope find_files", desc = "Find Files (root dir)" }, + { "fF", "Telescope find_files hidden=true no_ignore=true", desc = "Find Files (all)" }, + { "fg", "Telescope live_grep", desc = "Grep (root dir)" }, + { "fh", "Telescope help_tags", desc = "Help Pages" }, + { "fH", "Telescope highlights", desc = "Search Highlight Groups" }, + { "fk", "Telescope keymaps", desc = "Key Maps" }, + { "fl", "Telescope loclist", desc = "Location List" }, + { "fM", "Telescope man_pages", desc = "Man Pages" }, + { "fm", "Telescope marks", desc = "Jump to Mark" }, + { "fo", "Telescope vim_options", desc = "Options" }, + { "fR", "Telescope resume", desc = "Resume" }, + { "fq", "Telescope quickfix", desc = "Quickfix List" }, + { "fw", "Telescope grep_string word_match=-w", desc = "Word (root dir)" }, + { "fW", "Telescope grep_string", desc = "Selection (root dir)", mode = "v" }, + { "uC", "Telescope colorscheme enable_preview=true", desc = "Colorscheme with preview" }, + }, + opts = function() + return { + defaults = { + file_ignore_patterns = { "node_modules", ".git/" }, + vimgrep_arguments = { + "rg", + "-L", + "--color=never", + "--no-heading", + "--with-filename", + "--line-number", + "--column", + "--smart-case", + }, + prompt_prefix = " ", + selection_caret = " ", + entry_prefix = " ", + initial_mode = "insert", + selection_strategy = "reset", + sorting_strategy = "ascending", + layout_strategy = "horizontal", + layout_config = { + horizontal = { + prompt_position = "top", + preview_width = 0.55, + results_width = 0.8, + }, + vertical = { + mirror = false, + }, + width = 0.87, + height = 0.80, + preview_cutoff = 120, + }, + path_display = { "truncate" }, + winblend = 0, + border = {}, + borderchars = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" }, + color_devicons = true, + set_env = { ["COLORTERM"] = "truecolor" }, + }, + pickers = { + live_grep = { + theme = "dropdown", + }, + grep_string = { + theme = "dropdown", + }, + find_files = { + theme = "dropdown", + previewer = false, + }, + buffers = { + theme = "dropdown", + previewer = false, + initial_mode = "normal", + mappings = { + i = { + [""] = function(...) + return require("telescope.actions").delete_buffer(...) + end, + }, + n = { + ["dd"] = function(...) + return require("telescope.actions").delete_buffer(...) + end, + }, + }, + }, + planets = { + show_pluto = true, + show_moon = true, + }, + colorscheme = { + enable_preview = true, + }, + }, + } + end, + }, + + -- Better vim.ui + { + "stevearc/dressing.nvim", + lazy = true, + init = function() + vim.ui.select = function(...) + require("lazy").load({ plugins = { "dressing.nvim" } }) + return vim.ui.select(...) + end + vim.ui.input = function(...) + require("lazy").load({ plugins = { "dressing.nvim" } }) + return vim.ui.input(...) + end + end, + }, + + -- Auto pairs + { + "windwp/nvim-autopairs", + event = "InsertEnter", + opts = {}, + }, + + -- Comments + { + "numToStr/Comment.nvim", + opts = {}, + lazy = false, + }, + + -- Which key + { + "folke/which-key.nvim", + event = "VeryLazy", + opts = { + plugins = { spelling = true }, + defaults = { + mode = { "n", "v" }, + ["g"] = { name = "+goto" }, + ["gz"] = { name = "+surround" }, + ["]"] = { name = "+next" }, + ["["] = { name = "+prev" }, + [""] = { name = "+tabs" }, + ["b"] = { name = "+buffer" }, + ["c"] = { name = "+code" }, + ["f"] = { name = "+file/find" }, + ["g"] = { name = "+git" }, + ["gh"] = { name = "+hunks" }, + ["q"] = { name = "+quit/session" }, + ["s"] = { name = "+search" }, + ["u"] = { name = "+ui" }, + ["w"] = { name = "+windows" }, + ["x"] = { name = "+diagnostics/quickfix" }, + }, + }, + config = function(_, opts) + local wk = require("which-key") + wk.setup(opts) + wk.register(opts.defaults) + end, + }, + + -- Git signs + { + "lewis6991/gitsigns.nvim", + event = { "BufReadPre", "BufNewFile" }, + opts = { + signs = { + add = { text = "▎" }, + change = { text = "▎" }, + delete = { text = "" }, + topdelete = { text = "" }, + changedelete = { text = "▎" }, + untracked = { text = "▎" }, + }, + on_attach = function(buffer) + local gs = package.loaded.gitsigns + + local function map(mode, l, r, desc) + vim.keymap.set(mode, l, r, { buffer = buffer, desc = desc }) + end + + map("n", "]h", gs.next_hunk, "Next Hunk") + map("n", "[h", gs.prev_hunk, "Prev Hunk") + map({ "n", "v" }, "ghs", ":Gitsigns stage_hunk", "Stage Hunk") + map({ "n", "v" }, "ghr", ":Gitsigns reset_hunk", "Reset Hunk") + map("n", "ghS", gs.stage_buffer, "Stage Buffer") + map("n", "ghu", gs.undo_stage_hunk, "Undo Stage Hunk") + map("n", "ghR", gs.reset_buffer, "Reset Buffer") + map("n", "ghp", gs.preview_hunk, "Preview Hunk") + map("n", "ghb", function() gs.blame_line({ full = true }) end, "Blame Line") + map("n", "ghd", gs.diffthis, "Diff This") + map("n", "ghD", function() gs.diffthis("~") end, "Diff This ~") + map({ "o", "x" }, "ih", ":Gitsigns select_hunk", "GitSigns Select Hunk") + end, + }, + }, + + -- Dashboard + { + "nvimdev/dashboard-nvim", + event = "VimEnter", + opts = function() + local logo = [[ + ███╗ ██╗███████╗ ██████╗ ██╗ ██╗██╗███╗ ███╗ + ████╗ ██║██╔════╝██╔═══██╗██║ ██║██║████╗ ████║ + ██╔██╗ ██║█████╗ ██║ ██║██║ ██║██║██╔████╔██║ + ██║╚██╗██║██╔══╝ ██║ ██║╚██╗ ██╔╝██║██║╚██╔╝██║ + ██║ ╚████║███████╗╚██████╔╝ ╚████╔╝ ██║██║ ╚═╝ ██║ + ╚═╝ ╚═══╝╚══════╝ ╚═════╝ ╚═══╝ ╚═╝╚═╝ ╚═╝ + ]] + + logo = string.rep("\n", 8) .. logo .. "\n\n" + + local opts = { + theme = "doom", + hide = { + statusline = false, + }, + config = { + header = vim.split(logo, "\n"), + center = { + { action = "Telescope find_files", desc = " Find file", icon = " ", key = "f" }, + { action = "ene | startinsert", desc = " New file", icon = " ", key = "n" }, + { action = "Telescope oldfiles", desc = " Recent files", icon = " ", key = "r" }, + { action = "Telescope live_grep", desc = " Find text", icon = " ", key = "g" }, + { action = "e $MYVIMRC", desc = " Config", icon = " ", key = "c" }, + { action = "Lazy", desc = " Lazy", icon = "󰒲 ", key = "l" }, + { action = "qa", desc = " Quit", icon = " ", key = "q" }, + }, + footer = function() + local stats = require("lazy").stats() + local ms = (math.floor(stats.startuptime * 100 + 0.5) / 100) + return { "⚡ Neovim loaded " .. stats.loaded .. "/" .. stats.count .. " plugins in " .. ms .. "ms" } + end, + }, + } + + for _, button in ipairs(opts.config.center) do + button.desc = button.desc .. string.rep(" ", 43 - #button.desc) + button.key_format = " %s" + end + + if vim.o.filetype == "lazy" then + vim.cmd.close() + vim.api.nvim_create_autocmd("User", { + pattern = "DashboardLoaded", + callback = function() + require("lazy").show() + end, + }) + end + + return opts + end, + }, + + -- Formatting + { + "stevearc/conform.nvim", + dependencies = { "mason.nvim" }, + lazy = true, + cmd = "ConformInfo", + keys = { + { + "cF", + function() + require("conform").format({ formatters = { "injected" } }) + end, + mode = { "n", "v" }, + desc = "Format Injected Langs", + }, + }, + init = function() + vim.o.formatexpr = "v:lua.require'conform'.formatexpr()" + end, + opts = { + formatters_by_ft = { + lua = { "stylua" }, + python = { "isort", "black" }, + javascript = { { "prettierd", "prettier" } }, + typescript = { { "prettierd", "prettier" } }, + html = { { "prettierd", "prettier" } }, + css = { { "prettierd", "prettier" } }, + json = { { "prettierd", "prettier" } }, + yaml = { { "prettierd", "prettier" } }, + markdown = { { "prettierd", "prettier" } }, + cpp = { "clang-format" }, + c = { "clang-format" }, + }, + format_on_save = function(bufnr) + if vim.g.disable_autoformat or vim.b[bufnr].disable_autoformat then + return + end + local filetype = vim.bo[bufnr].filetype + if filetype == "c" or filetype == "cpp" then + return { timeout_ms = 500 } + end + return { timeout_ms = 500, lsp_fallback = true } + end, + formatters = { + injected = { options = { ignore_errors = true } }, + }, + }, + }, + + -- Linting + { + "mfussenegger/nvim-lint", + event = { "BufReadPre", "BufNewFile" }, + opts = { + events = { "BufWritePost", "BufReadPost", "InsertLeave" }, + linters_by_ft = { + python = { "flake8" }, + cpp = { "cpplint" }, + c = { "cpplint" }, + javascript = { "eslint_d" }, + typescript = { "eslint_d" }, + }, + }, + config = function(_, opts) + local M = {} + + local lint = require("lint") + for name, linter in pairs(opts.linters or {}) do + if type(linter) == "table" and type(lint.linters[name]) == "table" then + lint.linters[name] = vim.tbl_deep_extend("force", lint.linters[name], linter) + else + lint.linters[name] = linter + end + end + lint.linters_by_ft = opts.linters_by_ft + + function M.debounce(ms, fn) + local timer = vim.loop.new_timer() + return function(...) + local argv = { ... } + timer:start(ms, 0, function() + timer:stop() + vim.schedule_wrap(fn)(unpack(argv)) + end) + end + end + + function M.lint() + local names = lint._resolve_linter_by_ft(vim.bo.filetype) + names = vim.list_extend({}, names) + + if #names == 0 then + vim.list_extend(names, lint.linters_by_ft["_"] or {}) + end + + vim.list_extend(names, lint.linters_by_ft["*"] or {}) + + local ctx = { filename = vim.api.nvim_buf_get_name(0) } + ctx.dirname = vim.fn.fnamemodify(ctx.filename, ":h") + names = vim.tbl_filter(function(name) + local linter = lint.linters[name] + if not linter then + return false + end + + return not (type(linter) == "table" and linter.condition and not linter.condition(ctx)) + end, names) + + if #names > 0 then + lint.try_lint(names) + end + end + + vim.api.nvim_create_autocmd(opts.events, { + group = vim.api.nvim_create_augroup("nvim-lint", { clear = true }), + callback = M.debounce(100, M.lint), + }) + end, + }, + + -- Live server for web development + { + "barrett-ruth/live-server.nvim", + build = "npm install -g live-server", + cmd = { "LiveServerStart", "LiveServerStop" }, + config = true, + }, + + -- Debugging + { + "mfussenegger/nvim-dap", + dependencies = { + "rcarriga/nvim-dap-ui", + "nvim-neotest/nvim-nio", + "williamboman/mason.nvim", + "jay-babu/mason-nvim-dap.nvim", + }, + keys = function() + local dap = require("dap") + local dapui = require("dapui") + return { + { "dB", function() dap.set_breakpoint(vim.fn.input('Breakpoint condition: ')) end, desc = "Breakpoint Condition" }, + { "db", function() dap.toggle_breakpoint() end, desc = "Toggle Breakpoint" }, + { "dc", function() dap.continue() end, desc = "Continue" }, + { "dC", function() dap.run_to_cursor() end, desc = "Run to Cursor" }, + { "dg", function() dap.goto_() end, desc = "Go to line (no execute)" }, + { "di", function() dap.step_into() end, desc = "Step Into" }, + { "dj", function() dap.down() end, desc = "Down" }, + { "dk", function() dap.up() end, desc = "Up" }, + { "dl", function() dap.run_last() end, desc = "Run Last" }, + { "do", function() dap.step_out() end, desc = "Step Out" }, + { "dO", function() dap.step_over() end, desc = "Step Over" }, + { "dp", function() dap.pause() end, desc = "Pause" }, + { "dr", function() dap.repl.toggle() end, desc = "Toggle REPL" }, + { "ds", function() dap.session() end, desc = "Session" }, + { "dt", function() dap.terminate() end, desc = "Terminate" }, + { "dw", function() require("dap.ui.widgets").hover() end, desc = "Widgets" }, + { "", function() dap.continue() end, desc = "Debug: Start/Continue" }, + { "", function() dap.step_over() end, desc = "Debug: Step Over" }, + { "", function() dap.step_into() end, desc = "Debug: Step Into" }, + { "", function() dap.step_out() end, desc = "Debug: Step Out" }, + } + end, + config = function() + local dap = require("dap") + local dapui = require("dapui") + + require("mason-nvim-dap").setup({ + automatic_setup = true, + handlers = {}, + ensure_installed = { + "python", + "codelldb", + }, + }) + + dapui.setup({ + icons = { expanded = "▾", collapsed = "▸", current_frame = "*" }, + controls = { + icons = { + pause = "⏸", + play = "▶", + step_into = "⏎", + step_over = "⏭", + step_out = "⏮", + step_back = "b", + run_last = "▶▶", + terminate = "⏹", + disconnect = "⏏", + }, + }, + }) + + dap.listeners.after.event_initialized["dapui_config"] = function() + dapui.open({}) + end + dap.listeners.before.event_terminated["dapui_config"] = function() + dapui.close({}) + end + dap.listeners.before.event_exited["dapui_config"] = function() + dapui.close({}) + end + + -- Install language specific config + require("dap-python").setup("~/.virtualenvs/debugpy/bin/python") + end, + }, + + -- Python debugging + { + "mfussenegger/nvim-dap-python", + ft = "python", + dependencies = { + "mfussenegger/nvim-dap", + "rcarriga/nvim-dap-ui", + }, + config = function(_, opts) + local path = require("mason-registry").get_package("debugpy"):get_install_path() + require("dap-python").setup(path .. "/venv/bin/python") + end, + }, + + -- Better notifications + { + "rcarriga/nvim-notify", + keys = { + { + "un", + function() + require("notify").dismiss({ silent = true, pending = true }) + end, + desc = "Dismiss all Notifications", + }, + }, + opts = { + timeout = 3000, + max_height = function() + return math.floor(vim.o.lines * 0.75) + end, + max_width = function() + return math.floor(vim.o.columns * 0.75) + end, + }, + init = function() + vim.notify = require("notify") + end, + }, + + -- Better vim.ui + { + "folke/noice.nvim", + event = "VeryLazy", + opts = { + lsp = { + override = { + ["vim.lsp.util.convert_input_to_markdown_lines"] = true, + ["vim.lsp.util.stylize_markdown"] = true, + ["cmp.entry.get_documentation"] = true, + }, + }, + routes = { + { + filter = { + event = "msg_show", + any = { + { find = "%d+L, %d+B" }, + { find = "; after #%d+" }, + { find = "; before #%d+" }, + }, + }, + view = "mini", + }, + }, + presets = { + bottom_search = true, + command_palette = true, + long_message_to_split = true, + inc_rename = true, + lsp_doc_border = false, + }, + }, + dependencies = { + "MunifTanjim/nui.nvim", + "rcarriga/nvim-notify", + }, + }, + + -- Buffer remove + { + "echasnovski/mini.bufremove", + keys = { + { "bd", function() require("mini.bufremove").delete(0, false) end, desc = "Delete Buffer" }, + { "bD", function() require("mini.bufremove").delete(0, true) end, desc = "Delete Buffer (Force)" }, + }, + }, + + -- Session management + { + "folke/persistence.nvim", + event = "BufReadPre", + opts = { options = vim.opt.sessionoptions:get() }, + keys = { + { "qs", function() require("persistence").load() end, desc = "Restore Session" }, + { "ql", function() require("persistence").load({ last = true }) end, desc = "Restore Last Session" }, + { "qd", function() require("persistence").stop() end, desc = "Don't Save Current Session" }, + }, + }, +}, { + defaults = { + lazy = false, + version = false, + }, + install = { colorscheme = { "tokyonight", "habamax" } }, + checker = { enabled = true }, + performance = { + rtp = { + disabled_plugins = { + "gzip", + "tarPlugin", + "tohtml", + "tutor", + "zipPlugin", + }, + }, + }, +}) + +-- Additional keymaps +local keymap = vim.keymap.set +local opts = { silent = true } + +-- Better up/down +keymap({ "n", "x" }, "j", "v:count == 0 ? 'gj' : 'j'", { expr = true, silent = true }) +keymap({ "n", "x" }, "k", "v:count == 0 ? 'gk' : 'k'", { expr = true, silent = true }) + +-- Move to window using the hjkl keys +keymap("n", "", "h", { desc = "Go to left window", remap = true }) +keymap("n", "", "j", { desc = "Go to lower window", remap = true }) +keymap("n", "", "k", { desc = "Go to upper window", remap = true }) +keymap("n", "", "l", { desc = "Go to right window", remap = true }) + +-- Resize window using arrow keys +keymap("n", "", "resize +2", { desc = "Increase window height" }) +keymap("n", "", "resize -2", { desc = "Decrease window height" }) +keymap("n", "", "vertical resize -2", { desc = "Decrease window width" }) +keymap("n", "", "vertical resize +2", { desc = "Increase window width" }) + +-- Move Lines +keymap("n", "", "m .+1==", { desc = "Move down" }) +keymap("n", "", "m .-2==", { desc = "Move up" }) +keymap("i", "", "m .+1==gi", { desc = "Move down" }) +keymap("i", "", "m .-2==gi", { desc = "Move up" }) +keymap("v", "", ":m '>+1gv=gv", { desc = "Move down" }) +keymap("v", "", ":m '<-2gv=gv", { desc = "Move up" }) + +-- Buffers +keymap("n", "", "bprevious", { desc = "Prev buffer" }) +keymap("n", "", "bnext", { desc = "Next buffer" }) +keymap("n", "[b", "bprevious", { desc = "Prev buffer" }) +keymap("n", "]b", "bnext", { desc = "Next buffer" }) + +-- Clear search with +keymap({ "i", "n" }, "", "noh", { desc = "Escape and clear hlsearch" }) + +-- Save file +keymap({ "i", "x", "n", "s" }, "", "w", { desc = "Save file" }) + +-- Better indenting +keymap("v", "<", "", ">gv") + +-- Lazy +keymap("n", "l", "Lazy", { desc = "Lazy" }) + +-- New file +keymap("n", "fn", "enew", { desc = "New File" }) + +-- Quit +keymap("n", "qq", "qa", { desc = "Quit all" }) + +-- Highlights under cursor +keymap("n", "ui", vim.show_pos, { desc = "Inspect Pos" }) + +-- Python run +keymap("n", "r", function() + if vim.bo.filetype == "python" then + vim.cmd("w") + vim.cmd("!python3 %") + else + print("Not a Python file") + end +end, { desc = "Run Python file" }) + +-- Toggle options +keymap("n", "uf", function() + vim.g.disable_autoformat = not vim.g.disable_autoformat + if vim.g.disable_autoformat then + print("Disabled format on save") + else + print("Enabled format on save") + end +end, { desc = "Toggle auto format (global)" }) + +keymap("n", "uF", function() + vim.b.disable_autoformat = not vim.b.disable_autoformat + if vim.b.disable_autoformat then + print("Disabled format on save (buffer)") + else + print("Enabled format on save (buffer)") + end +end, { desc = "Toggle auto format (buffer)" }) + +-- Auto commands +local function augroup(name) + return vim.api.nvim_create_augroup("lazyvim_" .. name, { clear = true }) +end + +-- Check if we need to reload the file when it changed +vim.api.nvim_create_autocmd({ "FocusGained", "TermClose", "TermLeave" }, { + group = augroup("checktime"), + command = "checktime", +}) + +-- Highlight on yank +vim.api.nvim_create_autocmd("TextYankPost", { + group = augroup("highlight_yank"), + callback = function() + vim.highlight.on_yank() + end, +}) + +-- Resize splits if window got resized +vim.api.nvim_create_autocmd({ "VimResized" }, { + group = augroup("resize_splits"), + callback = function() + local current_tab = vim.fn.tabpagenr() + vim.cmd("tabdo wincmd =") + vim.cmd("tabnext " .. current_tab) + end, +}) + +-- Go to last loc when opening a buffer +vim.api.nvim_create_autocmd("BufReadPost", { + group = augroup("last_loc"), + callback = function(event) + local exclude = { "gitcommit" } + local buf = event.buf + if vim.tbl_contains(exclude, vim.bo[buf].filetype) or vim.b[buf].lazyvim_last_loc then + return + end + vim.b[buf].lazyvim_last_loc = true + local mark = vim.api.nvim_buf_get_mark(buf, '"') + local lcount = vim.api.nvim_buf_line_count(buf) + if mark[1] > 0 and mark[1] <= lcount then + pcall(vim.api.nvim_win_set_cursor, 0, mark) + end + end, +}) + +-- Close some filetypes with +vim.api.nvim_create_autocmd("FileType", { + group = augroup("close_with_q"), + pattern = { + "PlenaryTestPopup", + "help", + "lspinfo", + "man", + "notify", + "qf", + "query", + "spectre_panel", + "startuptime", + "tsplayground", + "neotest-output", + "checkhealth", + "neotest-summary", + "neotest-output-panel", + }, + callback = function(event) + vim.bo[event.buf].buflisted = false + vim.keymap.set("n", "q", "close", { buffer = event.buf, silent = true }) + end, +}) + +-- Wrap and check for spell in text filetypes +vim.api.nvim_create_autocmd("FileType", { + group = augroup("wrap_spell"), + pattern = { "gitcommit", "markdown" }, + callback = function() + vim.opt_local.wrap = true + vim.opt_local.spell = true + end, +}) + +-- Auto create dir when saving a file, in case some intermediate directory does not exist +vim.api.nvim_create_autocmd({ "BufWritePre" }, { + group = augroup("auto_create_dir"), + callback = function(event) + if event.match:match("^%w%w+://") then + return + end + local file = vim.loop.fs_realpath(event.match) or event.match + vim.fn.mkdir(vim.fn.fnamemodify(file, ":p:h"), "p") + end, +}) + +-- Format on save for web files +vim.api.nvim_create_autocmd("BufWritePre", { + group = augroup("format_web_files"), + pattern = { "*.html", "*.css", "*.js", "*.ts", "*.json" }, + callback = function() + if vim.g.disable_autoformat or vim.b.disable_autoformat then + return + end + local conform = require("conform") + conform.format({ async = false, lsp_fallback = true }) + end, +})