From e1980dc21b0ced8f581a1e13949093e8b6186614 Mon Sep 17 00:00:00 2001 From: reze-dev <25588579+reze-dev@users.noreply.github.com> Date: Sat, 19 Sep 2026 01:05:06 +0530 Subject: [PATCH 01/12] refactor(scripts): remove scripts directory and clean up contributing guide --- CONTRIBUTING.md | 1 - scripts/format | 43 ---------------------------------------- scripts/test-plymouth.sh | 15 -------------- 3 files changed, 59 deletions(-) delete mode 100755 scripts/format delete mode 100755 scripts/test-plymouth.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6a7483ca..db2da8e9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -116,7 +116,6 @@ ctos/ │ ├── features/ # Vertical feature slices (core, desktop, dev, shell, terminals, tools) │ ├── hardware/ # Hardware drivers (NVIDIA) │ └── profiles/ # Composable feature bundles (base, desktop, workstation, gaming) -├── scripts/ # Repository and maintenance utility scripts ├── shell/ # ctOS Quickshell desktop environment and greeter ├── tests/ # End-to-end and boundary test suites └── assets/ # Themes and media diff --git a/scripts/format b/scripts/format deleted file mode 100755 index 6eba6f51..00000000 --- a/scripts/format +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -format_file() { - local file="$1" - - [[ -f "$file" ]] || return 0 - - case "$file" in - *.nix) - nixfmt "$file" - ;; - - *.qml) - qmlformat -i "$file" - ;; - - *.sh | *.bash) - shfmt -w "$file" - ;; - esac -} - -if [[ "$#" -eq 0 ]]; then - exit 0 -fi - -for arg in "$@"; do - if [[ "$arg" == "." ]]; then - while IFS= read -r -d '' file; do - format_file "$file" - done < <( - command git ls-files \ - --modified \ - --others \ - --exclude-standard \ - -z - ) - elif [[ -f "$arg" ]]; then - format_file "$arg" - fi -done diff --git a/scripts/test-plymouth.sh b/scripts/test-plymouth.sh deleted file mode 100755 index dd4a1d4c..00000000 --- a/scripts/test-plymouth.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -# WARNING: This script launches an interactive Plymouth daemon using /dev/tty. -# Do NOT execute during an active Wayland/X11 session as it may disrupt the display server. -mkdir -p /tmp/plymouth-test/themes -cp -r assets/ctos-plymouth /tmp/plymouth-test/themes/ctos -sed -i 's|/usr/share/plymouth/themes/ctos|/tmp/plymouth-test/themes/ctos|g' /tmp/plymouth-test/themes/ctos/ctos.plymouth - -export PLYMOUTH_THEME_PATH=/tmp/plymouth-test/themes -export PLYMOUTH_DATADIR=/tmp/plymouth-test - -plymouthd --debug --debug-file=/tmp/plymouth-debug.log --tty=/dev/tty -plymouth --show-splash -sleep 2 -plymouth quit -cat /tmp/plymouth-debug.log | grep -i "error\|parse\|fail\|script" From d2941ba12dc9ed033d22203d361ab9f057b486c8 Mon Sep 17 00:00:00 2001 From: reze-dev <25588579+reze-dev@users.noreply.github.com> Date: Sat, 19 Sep 2026 01:05:08 +0530 Subject: [PATCH 02/12] refactor(shell): remove git format hooks and unshadow vim alias in zsh and fish --- modules/features/shell/fish.nix | 12 ------------ modules/features/shell/zsh.nix | 13 ------------- 2 files changed, 25 deletions(-) diff --git a/modules/features/shell/fish.nix b/modules/features/shell/fish.nix index 18ade991..d908c20d 100644 --- a/modules/features/shell/fish.nix +++ b/modules/features/shell/fish.nix @@ -43,17 +43,6 @@ in set -x TERM xterm-256color ''; functions = { - git = '' - if test "$argv[1]" = add - set -l git_root (command git rev-parse --show-toplevel) - or return - - "$git_root/scripts/format" $argv[2..-1] - command git add $argv[2..-1] - else - command git $argv - end - ''; starship_transient_prompt_func = "starship module character"; }; shellAliases = { @@ -63,7 +52,6 @@ in tree = "eza -T --icons"; la = "eza -la --icons"; lo = "eza -l -o --icons"; - vim = "nvim"; tmux = "tmux -u"; butt = "but"; }; diff --git a/modules/features/shell/zsh.nix b/modules/features/shell/zsh.nix index 722dc187..9a831503 100644 --- a/modules/features/shell/zsh.nix +++ b/modules/features/shell/zsh.nix @@ -48,18 +48,6 @@ in zstyle ':fzf-tab:complete:cd:*' fzf-preview 'eza --color=always --icons always $realpath' zstyle ':fzf-tab:complete:__zoxide_z:*' fzf-preview 'eza --color=always --icons always $realpath' '') - (lib.mkOrder 1200 '' - git() { - if [[ "$1" == "add" ]]; then - local git_root - git_root=$(command git rev-parse --show-toplevel) || return - "$git_root/scripts/format" "''${@:2}" - command git add "''${@:2}" - else - command git "$@" - fi - } - '') (lib.mkOrder 1500 '' TRANSIENT_PROMPT_PROMPT='$(starship prompt --terminal-width="$COLUMNS" --keymap="''${KEYMAP:-}" --status="''${STARSHIP_CMD_STATUS}" --pipestatus="''${STARSHIP_PIPE_STATUS[*]}" --cmd-duration="''${STARSHIP_DURATION:-}" --jobs="''${STARSHIP_JOBS_COUNT}")' TRANSIENT_PROMPT_RPROMPT='$(starship prompt --right --terminal-width="$COLUMNS" --keymap="''${KEYMAP:-}" --status="''${STARSHIP_CMD_STATUS}" --pipestatus="''${STARSHIP_PIPE_STATUS[*]}" --cmd-duration="''${STARSHIP_DURATION:-}" --jobs="''${STARSHIP_JOBS_COUNT}")' @@ -95,7 +83,6 @@ in tree = "eza -T --icons"; la = "eza -la --icons"; lo = "eza -l -o --icons"; - vim = "nvim"; butt = "but"; }; }; From 68d6e463ca27039d850ba71c97be15bfaa2ac659 Mon Sep 17 00:00:00 2001 From: reze-dev <25588579+reze-dev@users.noreply.github.com> Date: Sat, 19 Sep 2026 01:05:12 +0530 Subject: [PATCH 03/12] feat(nvf): import nvf configuration wired via nvf.nix --- nvf/config/languages.nix | 36 + nvf/config/lua_config.nix | 2506 +++++++++++++++++++++++++++++++++++++ nvf/config/options.nix | 51 + nvf/config/plugins.nix | 211 ++++ nvf/config/theme.nix | 31 + nvf/nvf.nix | 10 + 6 files changed, 2845 insertions(+) create mode 100644 nvf/config/languages.nix create mode 100644 nvf/config/lua_config.nix create mode 100644 nvf/config/options.nix create mode 100644 nvf/config/plugins.nix create mode 100644 nvf/config/theme.nix create mode 100644 nvf/nvf.nix diff --git a/nvf/config/languages.nix b/nvf/config/languages.nix new file mode 100644 index 00000000..457dfb54 --- /dev/null +++ b/nvf/config/languages.nix @@ -0,0 +1,36 @@ +{ pkgs, ... }: { + config.vim = { + lsp.enable = true; + treesitter.enable = true; + languages = { + enableFormat = true; + enableTreesitter = true; + enableDAP = true; + clang.enable = true; + rust = { + enable = true; + lsp.enable = false; + dap.enable = false; + extensions = { + rustaceanvim.enable = true; + crates-nvim.enable = true; + }; + }; + go.enable = true; + python.enable = true; + lua.enable = true; + html.enable = true; + css.enable = true; + typescript.enable = true; + markdown.enable = true; + nix.enable = true; + bash.enable = true; + json.enable = true; + yaml.enable = true; + toml.enable = true; + docker.enable = true; + cmake.enable = true; + sql.enable = true; + }; + }; +} diff --git a/nvf/config/lua_config.nix b/nvf/config/lua_config.nix new file mode 100644 index 00000000..aafcc01b --- /dev/null +++ b/nvf/config/lua_config.nix @@ -0,0 +1,2506 @@ +{ pkgs, ... }: { + config.vim = { + luaConfigRC.core_options = '' + -- ─── System Environment & PATH ─────────────────────────────── + local home = vim.env.HOME or vim.fn.expand("~") + local mason_bin = vim.fn.stdpath("data") .. "/mason/bin" + local go_bin = home .. "/go/bin" + local cargo_bin = home .. "/.cargo/bin" + + vim.env.PATH = mason_bin .. ":" .. go_bin .. ":" .. cargo_bin .. ":" .. (vim.env.PATH or "") + + local opt = vim.opt + opt.listchars = { tab = "» ", trail = "·", nbsp = "␣" } + opt.fillchars = { eob = " ", fold = " ", foldopen = "v", foldsep = " ", foldclose = ">" } + opt.shortmess:append("sI") + opt.inccommand = "split" + opt.jumpoptions = "view" + opt.virtualedit = "block" + opt.smoothscroll = true + opt.completeopt = { "menu", "menuone", "noselect" } + + local undo_dir = vim.fn.stdpath("data") .. "/undo" + opt.undodir = undo_dir + pcall(vim.fn.mkdir, undo_dir, "p") + + vim.g.loaded_python3_provider = 0 + vim.g.loaded_ruby_provider = 0 + vim.g.loaded_perl_provider = 0 + vim.g.loaded_node_provider = 0 + ''; + luaConfigRC.core_keymaps = '' + -- ─── Global Keymaps ─────────────────────────────────────────── + -- Plugin-specific keymaps live in their respective plugin files. + local map = vim.keymap.set + + -- Better escape + map("i", "jk", "", { desc = "Exit insert mode" }) + + -- Save & quit + map("n", "w", "w", { desc = "Save file" }) + map("n", "q", "q", { desc = "Quit" }) + map("n", "Q", "qa!", { desc = "Force quit all" }) + + -- Clear search highlights + map("n", "nh", "nohlsearch", { desc = "Clear search highlights" }) + + -- Better window navigation (overridden by tmux-navigator when in tmux) + map("n", "", "h", { desc = "Move to left window" }) + map("n", "", "j", { desc = "Move to lower window" }) + map("n", "", "k", { desc = "Move to upper window" }) + map("n", "", "l", { desc = "Move to right window" }) + + -- Resize windows with arrows + map("n", "", "resize +2", { desc = "Increase window height" }) + map("n", "", "resize -2", { desc = "Decrease window height" }) + map("n", "", "vertical resize -2", { desc = "Decrease window width" }) + map("n", "", "vertical resize +2", { desc = "Increase window width" }) + + -- Buffer navigation + map("n", "", "bprevious", { desc = "Previous buffer" }) + map("n", "", "bnext", { desc = "Next buffer" }) + map("n", "bd", "bdelete", { desc = "Close buffer" }) + map("n", "bD", "bdelete!", { desc = "Force close buffer" }) + + -- Move lines up/down in visual mode + map("v", "J", ":m '>+1gv=gv", { desc = "Move selection down" }) + map("v", "K", ":m '<-2gv=gv", { desc = "Move selection up" }) + + -- Stay in visual mode when indenting + map("v", "<", "", ">gv", { desc = "Indent right" }) + + -- Keep cursor centered when scrolling + map("n", "", "zz", { desc = "Scroll down (centered)" }) + map("n", "", "zz", { desc = "Scroll up (centered)" }) + + -- Keep search terms centered + map("n", "n", "nzzzv", { desc = "Next search result (centered)" }) + map("n", "N", "Nzzzv", { desc = "Previous search result (centered)" }) + + -- Join lines without moving cursor + map("n", "J", "mzJ`z", { desc = "Join lines" }) + + -- Paste without losing register content + map("x", "p", [["_dP]], { desc = "Paste without overwriting register" }) + + -- Delete without yanking (visual mode only, keeping normal d free for DAP) + map("x", "d", [["_d]], { desc = "Delete without yanking" }) + + -- Better terminal navigation + map("t", "", "wincmd h", { desc = "Move to left window (terminal)" }) + map("t", "", "wincmd j", { desc = "Move to lower window (terminal)" }) + map("t", "", "wincmd k", { desc = "Move to upper window (terminal)" }) + map("t", "", "wincmd l", { desc = "Move to right window (terminal)" }) + map("t", "", "", { desc = "Exit terminal mode" }) + + -- Quickfix navigation + map("n", "]q", "cnextzz", { desc = "Next quickfix item" }) + map("n", "[q", "cprevzz", { desc = "Previous quickfix item" }) + + -- Select all + map("n", "", "ggG", { desc = "Select all" }) + + -- Package manager / configuration info + map("n", "L", function() + vim.notify("Configured declaratively via Nix / nvf flake", vim.log.levels.INFO, { title = "Package Manager" }) + end, { desc = "Open Lazy plugin manager / Nix info" }) + ''; + luaConfigRC.core_autocmds = '' + -- ─── Autocommands ───────────────────────────────────────────── + local augroup = vim.api.nvim_create_augroup + local autocmd = vim.api.nvim_create_autocmd + + -- Highlight on yank + autocmd("TextYankPost", { + group = augroup("YankHighlight", { clear = true }), + callback = function() + vim.hl.on_yank({ higroup = "IncSearch", timeout = 200 }) + end, + }) + + -- Resize splits when window is resized + autocmd("VimResized", { + group = augroup("ResizeSplits", { clear = true }), + callback = function() + local current_tab = vim.fn.tabpagenr() + vim.cmd("tabdo wincmd =") + vim.cmd("tabnext " .. current_tab) + end, + }) + + -- Go to last cursor position when opening a file + autocmd("BufReadPost", { + group = augroup("LastPosition", { clear = true }), + 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 certain filetypes with q + autocmd("FileType", { + group = augroup("CloseWithQ", { clear = true }), + pattern = { + "PlenaryTestPopup", + "checkhealth", + "dbout", + "gitsigns.blame", + "help", + "lspinfo", + "neotest-output", + "neotest-output-panel", + "neotest-summary", + "notify", + "qf", + "spectre_panel", + "startuptime", + "tsplayground", + }, + callback = function(event) + vim.bo[event.buf].buflisted = false + vim.keymap.set("n", "q", "close", { buffer = event.buf, silent = true }) + end, + }) + + -- Auto create directories when saving a file + autocmd("BufWritePre", { + group = augroup("AutoCreateDir", { clear = true }), + callback = function(event) + if event.match:match("^%w%w+:[\\/][\\/]") then + return + end + local file = vim.uv.fs_realpath(event.match) or event.match + vim.fn.mkdir(vim.fn.fnamemodify(file, ":p:h"), "p") + end, + }) + + -- Remove trailing whitespace on save + autocmd("BufWritePre", { + group = augroup("TrimWhitespace", { clear = true }), + pattern = "*", + callback = function() + local save_cursor = vim.fn.getpos(".") + pcall(function() + vim.cmd([[%s/\s\+$//e]]) + end) + vim.fn.setpos(".", save_cursor) + end, + }) + + -- Check if file changed when its window is focused + autocmd({ "FocusGained", "TermClose", "TermLeave" }, { + group = augroup("Checktime", { clear = true }), + callback = function() + if vim.o.buftype ~= "nofile" then + vim.cmd("checktime") + end + end, + }) + + -- Wrap and spell check in text filetypes + autocmd("FileType", { + group = augroup("WrapSpell", { clear = true }), + pattern = { "text", "plaintex", "typst", "gitcommit", "markdown" }, + callback = function() + vim.opt_local.wrap = true + vim.opt_local.spell = true + end, + }) + + -- Fix conceallevel for json files + autocmd("FileType", { + group = augroup("JsonConceal", { clear = true }), + pattern = { "json", "jsonc", "json5" }, + callback = function() + vim.opt_local.conceallevel = 0 + end, + }) + + ''; + luaConfigRC.plugin_lualine_snacks = '' + -- Snacks + require("snacks").setup({ + -- ── Dashboard ── + dashboard = { + enabled = true, + preset = { + keys = { + { icon = " ", key = "f", desc = "Find File", action = ":lua Snacks.dashboard.pick('files')" }, + { icon = " ", key = "n", desc = "New File", action = ":ene | startinsert" }, + { icon = "󰱼 ", key = "g", desc = "Find Text", action = ":lua Snacks.dashboard.pick('live_grep')" }, + { icon = " ", key = "r", desc = "Recent Files", action = ":lua Snacks.dashboard.pick('oldfiles')" }, + { icon = " ", key = "p", desc = "Projects", action = ":lua Snacks.picker.projects()" }, + { icon = " ", key = "c", desc = "Config", action = ":lua Snacks.dashboard.pick('files', {cwd = vim.fn.stdpath('config')})" }, + + { icon = " ", key = "q", desc = "Quit", action = ":qa" }, + }, + header = [[ + + +  + ████ ██████ █████ ██ + ███████████ █████  + █████████ ███████████████████ ███ ███████████ + █████████ ███ █████████████ █████ ██████████████ + █████████ ██████████ █████████ █████ █████ ████ █████ + ███████████ ███ ███ █████████ █████ █████ ████ █████ + ██████ █████████████████████ ████ █████ █████ ████ ██████ + + + + + + ]], + }, + sections = { + { pane = 1, section = "header", padding = 2 }, + + { pane = 2, section = "keys", gap = 1, padding = 1 }, + }, + }, + + -- ── Notifier ── + notifier = { + enabled = true, + timeout = 3000, + style = "compact", + }, + + -- ── Quick file ── (fast file opening) + quickfile = { enabled = true }, + + -- ── Status column ── + statuscolumn = { enabled = true }, + + -- ── Words ── (highlight/navigate references) + words = { enabled = true }, + + -- ── Styles ── + styles = { + notification = { + wo = { wrap = true }, + }, + }, + }) + + vim.keymap.set("n", "un", function() Snacks.notifier.hide() end, { desc = "Dismiss all notifications" }) + vim.keymap.set("n", "gg", function() Snacks.lazygit() end, { desc = "Lazygit" }) + vim.keymap.set("n", "gl", function() Snacks.lazygit.log() end, { desc = "Lazygit log (cwd)" }) + vim.keymap.set({ "n", "t" }, "]]", function() Snacks.words.jump(vim.v.count1) end, { desc = "Next reference" }) + vim.keymap.set({ "n", "t" }, "[[", function() Snacks.words.jump(-vim.v.count1) end, { desc = "Previous reference" }) + + -- Lualine + local lualine = require("lualine") + + local function hl(group, attr, fallback) + local ok, value = pcall(vim.api.nvim_get_hl, 0, { name = group, link = true }) + if not ok or not value[attr] then + return fallback + end + return string.format("#%06x", value[attr]) + end + + local function base46_palette() + local ok, base46 = pcall(require, "base46") + if not ok then + return nil, nil + end + + local theme_name = base46.current_theme + if not theme_name and vim.g.colors_name then + theme_name = vim.g.colors_name:match("^base46%-(.+)$") + end + + local theme = theme_name and base46.theme_tables[theme_name] + if not theme and theme_name and base46.get_builtin_theme then + theme = base46.get_builtin_theme(theme_name) + end + + if not theme then + return nil, nil + end + return theme.base_30 or {}, theme.base_16 or {} + end + + local function colors() + local base30, base16 = base46_palette() + if base30 then + return { + bg = base30.statusline_bg or base30.black or base16.base00 or "#202328", + fg = base30.white or base16.base05 or "#bbc2cf", + muted = base30.grey_fg or base16.base03 or "#5c6370", + yellow = base30.yellow or base30.sun or base16.base0A or "#ECBE7B", + cyan = base30.cyan or base30.teal or base16.base0C or "#008080", + green = base30.green or base30.vibrant_green or base16.base0B or "#98be65", + orange = base30.orange or base16.base09 or "#FF8800", + violet = base30.purple or base30.dark_purple or base16.base0E or "#a9a1e1", + magenta = base30.pink or base30.baby_pink or base30.purple or base16.base0E or "#c678dd", + blue = base30.blue or base30.nord_blue or base16.base0D or "#51afef", + red = base30.red or base16.base08 or "#ec5f67", + } + end + + return { + bg = hl("StatusLine", "bg", hl("Normal", "bg", "#202328")), + fg = hl("StatusLine", "fg", hl("Normal", "fg", "#bbc2cf")), + muted = hl("Comment", "fg", "#5c6370"), + yellow = hl("DiagnosticWarn", "fg", "#ECBE7B"), + cyan = hl("DiagnosticInfo", "fg", "#008080"), + green = hl("String", "fg", "#98be65"), + orange = hl("Number", "fg", "#FF8800"), + violet = hl("DiagnosticHint", "fg", "#a9a1e1"), + magenta = hl("Keyword", "fg", "#c678dd"), + blue = hl("Function", "fg", "#51afef"), + red = hl("DiagnosticError", "fg", "#ec5f67"), + } + end + + local conditions = { + buffer_not_empty = function() + return vim.fn.empty(vim.fn.expand("%:t")) ~= 1 + end, + hide_in_width = function() + return vim.fn.winwidth(0) > 80 + end, + check_git_workspace = function() + local filepath = vim.fn.expand("%:p:h") + local gitdir = vim.fn.finddir(".git", filepath .. ";") + return gitdir and #gitdir > 0 and #gitdir < #filepath + end, + } + + local function mode_color() + local c = colors() + local mode_colors = { + n = c.red, + no = c.red, + nov = c.red, + noV = c.red, + ["no\22"] = c.red, + niI = c.red, + niR = c.red, + niV = c.red, + nt = c.red, + i = c.green, + ic = c.yellow, + ix = c.yellow, + v = c.blue, + V = c.blue, + ["\22"] = c.blue, + s = c.orange, + S = c.orange, + ["\19"] = c.orange, + c = c.magenta, + cv = c.red, + ce = c.red, + R = c.violet, + Rc = c.violet, + Rx = c.violet, + Rv = c.violet, + Rvc = c.violet, + Rvx = c.violet, + r = c.cyan, + rm = c.cyan, + ["r?"] = c.cyan, + ["!"] = c.red, + t = c.red, + } + return mode_colors[vim.fn.mode()] or c.red + end + + local function lsp_name() + local clients = vim.lsp.get_clients({ bufnr = 0 }) + if #clients == 0 then + return "No Active Lsp" + end + + local names = {} + for _, client in ipairs(clients) do + names[#names + 1] = client.name + end + return table.concat(names, ", ") + end + + local function setup() + local c = colors() + local config = { + options = { + component_separators = "", + section_separators = "", + globalstatus = true, + disabled_filetypes = { + statusline = { "dashboard", "snacks_dashboard", "lazy", "mason" }, + }, + theme = { + normal = { c = { fg = c.fg, bg = c.bg } }, + inactive = { c = { fg = c.fg, bg = c.bg } }, + }, + refresh = { + statusline = 100, + }, + }, + sections = { + lualine_a = {}, + lualine_b = {}, + lualine_c = {}, + lualine_x = {}, + lualine_y = {}, + lualine_z = {}, + }, + inactive_sections = { + lualine_a = {}, + lualine_b = {}, + lualine_c = {}, + lualine_x = {}, + lualine_y = {}, + lualine_z = {}, + }, + } + + local function ins_left(component) + table.insert(config.sections.lualine_c, component) + end + + local function ins_right(component) + table.insert(config.sections.lualine_x, component) + end + + ins_left({ + function() + return "▊" + end, + color = function() + return { fg = mode_color() } + end, + padding = { left = 0, right = 1 }, + }) + + ins_left({ + function() + return "" + end, + color = function() + return { fg = mode_color(), gui = "bold" } + end, + padding = { right = 1 }, + }) + + ins_left({ + "filesize", + cond = conditions.buffer_not_empty, + color = { fg = c.cyan, gui = "bold" }, + }) + + ins_left({ + "filename", + cond = conditions.buffer_not_empty, + color = { fg = c.magenta, gui = "bold" }, + symbols = { modified = " ●", readonly = " 󰌾", unnamed = "[No Name]", newfile = "[New]" }, + }) + + ins_left({ "location", color = { fg = c.blue, gui = "bold" } }) + ins_left({ "progress", color = { fg = c.violet, gui = "bold" } }) + + ins_left({ + "diagnostics", + sources = { "nvim_diagnostic" }, + symbols = { error = " ", warn = " ", info = " ", hint = "󰌵 " }, + diagnostics_color = { + error = { fg = c.red }, + warn = { fg = c.yellow }, + info = { fg = c.cyan }, + hint = { fg = c.violet }, + }, + }) + + ins_left({ + function() + return "%=" + end, + }) + + ins_left({ + lsp_name, + icon = " LSP:", + cond = conditions.hide_in_width, + color = { fg = c.green, gui = "bold" }, + }) + + ins_right({ + "o:encoding", + fmt = string.upper, + cond = conditions.hide_in_width, + color = { fg = c.yellow, gui = "bold" }, + }) + + ins_right({ + "fileformat", + fmt = string.upper, + icons_enabled = false, + cond = conditions.hide_in_width, + color = { fg = c.cyan, gui = "bold" }, + }) + + ins_right({ + "branch", + icon = " ", + cond = conditions.check_git_workspace, + color = { fg = c.violet, gui = "bold" }, + }) + + ins_right({ + "diff", + symbols = { added = " ", modified = "󰝤 ", removed = " " }, + diff_color = { + added = { fg = c.green }, + modified = { fg = c.orange }, + removed = { fg = c.red }, + }, + cond = conditions.hide_in_width, + }) + + ins_right({ + function() + return "▊" + end, + color = function() + return { fg = mode_color() } + end, + padding = { left = 1 }, + }) + + lualine.setup(config) + end + + setup() + vim.api.nvim_create_autocmd("ColorScheme", { + group = vim.api.nvim_create_augroup("EvilineThemeRefresh", { clear = true }), + callback = setup, + }) + + ''; + luaConfigRC.plugin_ui = '' + -- ============================================================================ + -- Migrated Standalone UI Configurations + -- Extracted from lua/plugins/ui.lua + -- ============================================================================ + + -- 1. Web Devicons (dependency for UI icons) + pcall(function() + require("nvim-web-devicons").setup({}) + end) + + -- 2. Indent Blankline (lukas-reineke/indent-blankline.nvim) + pcall(function() + require("ibl").setup({ + indent = { + char = "│", + tab_char = "│", + }, + scope = { + show_start = false, + show_end = false, + include = { + node_type = { + ["*"] = { + "argument_list", + "arguments", + "assignment_statement", + "block", + "chunk", + "class", + "do_block", + "element", + "except", + "for", + "function", + "if_statement", + "method", + "object", + "return_statement", + "table", + "try", + "while", + }, + }, + }, + }, + exclude = { + filetypes = { + "help", + "dashboard", + "snacks_dashboard", + "lazy", + "mason", + "notify", + "oil", + "toggleterm", + }, + }, + }) + end) + + -- 3. Notify (rcarriga/nvim-notify) + pcall(function() + local notify = require("notify") + notify.setup({ + stages = "fade_in_slide_out", + 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, + on_open = function(win) + vim.api.nvim_win_set_config(win, { zindex = 100 }) + end, + render = "wrapped-compact", + top_down = true, + }) + vim.notify = notify + end) + + -- 4. Noice (folke/noice.nvim) + pcall(function() + require("noice").setup({ + lsp = { + override = { + ["vim.lsp.util.convert_input_to_markdown_lines"] = true, + ["vim.lsp.util.stylize_markdown"] = true, + ["cmp.entry.get_documentation"] = true, + }, + hover = { enabled = true }, + signature = { enabled = true }, + }, + presets = { + bottom_search = true, + command_palette = true, + long_message_to_split = true, + inc_rename = true, + lsp_doc_border = true, + }, + routes = { + -- Hide "written" messages + { + filter = { + event = "msg_show", + kind = "", + find = "written", + }, + opts = { skip = true }, + }, + }, + }) + + -- Noice keymaps + vim.keymap.set("c", "", function() + require("noice").redirect(vim.fn.getcmdline()) + end, { desc = "Redirect cmdline" }) + vim.keymap.set("n", "snl", function() require("noice").cmd("last") end, { desc = "Noice last message" }) + vim.keymap.set("n", "snh", function() require("noice").cmd("history") end, { desc = "Noice history" }) + vim.keymap.set("n", "sna", function() require("noice").cmd("all") end, { desc = "Noice all" }) + vim.keymap.set("n", "snd", function() require("noice").cmd("dismiss") end, { desc = "Dismiss all" }) + end) + + -- 5. Dressing (stevearc/dressing.nvim) + pcall(function() + require("dressing").setup({}) + end) + + -- 6. Illuminate (RRethy/vim-illuminate) + pcall(function() + require("illuminate").configure({ + delay = 200, + large_file_cutoff = 2000, + large_file_overrides = { + providers = { "lsp" }, + }, + filetypes_denylist = { + "oil", + "dashboard", + "snacks_dashboard", + "lazy", + "mason", + "TelescopePrompt", + }, + }) + end) + + -- 7. Colorizer (NvChad/nvim-colorizer.lua) + pcall(function() + require("colorizer").setup({ + filetypes = { "*" }, + user_default_options = { + RGB = true, + RRGGBB = true, + names = false, + RRGGBBAA = true, + AARRGGBB = true, + rgb_fn = true, + hsl_fn = true, + css = true, + css_fn = true, + mode = "background", + tailwind = false, + virtualtext = "■", + always_update = false, + }, + }) + end) + + ''; + luaConfigRC.plugin_remaining = '' + local function safe_setup(plugin, setup_fn) + local ok, m = pcall(require, plugin) + if not ok then + vim.notify("Failed to load " .. plugin, vim.log.levels.WARN) + return + end + setup_fn(m) + end + + -- ─── Conform — Autoformatter ────────────────────────────────── + safe_setup("conform", function(conform) + conform.setup({ + formatters_by_ft = { + c = { "clang-format" }, + cpp = { "clang-format" }, + rust = { "rustfmt" }, + go = { "goimports", "gofumpt" }, + lua = { "stylua" }, + python = { "ruff_format", "ruff_organize_imports" }, + toml = { "taplo" }, + javascript = { "prettier" }, + typescript = { "prettier" }, + javascriptreact = { "prettier" }, + typescriptreact = { "prettier" }, + html = { "prettier" }, + css = { "prettier" }, + scss = { "prettier" }, + json = { "prettier" }, + jsonc = { "prettier" }, + yaml = { "prettier" }, + markdown = { "prettier" }, + graphql = { "prettier" }, + nix = { "nixfmt" }, + fish = { "fish_indent" }, + sh = { "shfmt" }, + bash = { "shfmt" }, + ["_"] = { "trim_whitespace" }, + }, + format_on_save = function(bufnr) + if vim.b[bufnr].disable_autoformat or vim.g.disable_autoformat then + return + end + return { + timeout_ms = 500, + lsp_format = "fallback", + } + end, + formatters = { + shfmt = { + prepend_args = { "-i", "2" }, + }, + stylua = { + prepend_args = { "--indent-type", "Spaces", "--indent-width", "2" }, + }, + ["clang-format"] = { + prepend_args = { "-fallback-style=LLVM" }, + }, + }, + }) + + vim.keymap.set({ "n", "v" }, "cf", function() + conform.format({ + async = false, + timeout_ms = 3000, + lsp_format = "fallback", + }) + end, { desc = "Format buffer/selection" }) + + vim.api.nvim_create_user_command("FormatToggle", function() + vim.g.disable_autoformat = not vim.g.disable_autoformat + local state = vim.g.disable_autoformat and "disabled" or "enabled" + vim.notify("Autoformat " .. state, vim.log.levels.INFO) + end, { desc = "Toggle autoformat on save" }) + + vim.api.nvim_create_user_command("FormatToggleBuf", function() + vim.b.disable_autoformat = not vim.b.disable_autoformat + local state = vim.b.disable_autoformat and "disabled" or "enabled" + vim.notify("Autoformat (buffer) " .. state, vim.log.levels.INFO) + end, { desc = "Toggle autoformat on save (buffer)" }) + end) + + -- ─── DAP — Debug Adapter Protocol ───────────────────────────── + safe_setup("dap", function(dap) + -- Signs + vim.fn.sign_define("DapBreakpoint", { text = " ", texthl = "DiagnosticError", linehl = "", numhl = "" }) + vim.fn.sign_define("DapBreakpointCondition", { text = " ", texthl = "DiagnosticWarn", linehl = "", numhl = "" }) + vim.fn.sign_define("DapBreakpointRejected", { text = " ", texthl = "DiagnosticError", linehl = "", numhl = "" }) + vim.fn.sign_define("DapLogPoint", { text = " ", texthl = "DiagnosticInfo", linehl = "", numhl = "" }) + vim.fn.sign_define("DapStopped", { text = "▶ ", texthl = "DiagnosticOk", linehl = "DapStoppedLine", numhl = "" }) + vim.api.nvim_set_hl(0, "DapStoppedLine", { default = true, link = "Visual" }) + + -- DAP UI + local ok_dapui, dapui = pcall(require, "dapui") + if ok_dapui then + dapui.setup({ + icons = { expanded = "▾", collapsed = "▸", current_frame = "▸" }, + layouts = { + { + elements = { + { id = "scopes", size = 0.35 }, + { id = "breakpoints", size = 0.15 }, + { id = "stacks", size = 0.25 }, + { id = "watches", size = 0.25 }, + }, + size = 40, + position = "left", + }, + { + elements = { + { id = "repl", size = 0.5 }, + { id = "console", size = 0.5 }, + }, + size = 0.25, + position = "bottom", + }, + }, + floating = { + border = "rounded", + mappings = { + close = { "q", "" }, + }, + }, + }) + + dap.listeners.after.event_initialized["dapui_config"] = function() + pcall(function() dapui.open({}) end) + end + dap.listeners.before.event_terminated["dapui_config"] = function() + pcall(function() dapui.close({}) end) + end + dap.listeners.before.event_exited["dapui_config"] = function() + pcall(function() dapui.close({}) end) + end + + vim.keymap.set("n", "du", function() dapui.toggle({}) end, { desc = "DAP: toggle UI" }) + vim.keymap.set({ "n", "v" }, "de", function() dapui.eval() end, { desc = "DAP: eval" }) + end + + -- DAP Virtual Text + local ok_dap_vt, dap_vt = pcall(require, "nvim-dap-virtual-text") + if ok_dap_vt then + dap_vt.setup({ + enabled = true, + enabled_commands = true, + highlight_changed_variables = true, + highlight_new_as_changed = false, + show_stop_reason = true, + commented = false, + only_first_definition = true, + all_references = false, + clear_on_continue = false, + display_callback = function(variable, buf, stackframe, node, options) + if options.virt_text_pos == "inline" then + return " = " .. variable.value + else + return variable.name .. " = " .. variable.value + end + end, + virt_text_pos = "eol", + all_frames = false, + virt_lines = false, + virt_text_win_col = nil, + }) + end + + -- DAP Go + local ok_dap_go, dap_go = pcall(require, "dap-go") + if ok_dap_go then + dap_go.setup({}) + end + + -- DAP Python + local ok_dap_py, dap_py = pcall(require, "dap-python") + if ok_dap_py then + local debugpy_path = vim.fn.exepath("python3") + dap_py.setup(debugpy_path) + end + + -- Keymaps + local map = vim.keymap.set + map("n", "db", function() dap.toggle_breakpoint() end, { desc = "DAP: toggle breakpoint" }) + map("n", "dB", function() dap.set_breakpoint(vim.fn.input("Breakpoint condition: ")) end, { desc = "DAP: conditional breakpoint" }) + map("n", "dc", function() dap.continue() end, { desc = "DAP: continue" }) + map("n", "dC", function() dap.run_to_cursor() end, { desc = "DAP: run to cursor" }) + map("n", "di", function() dap.step_into() end, { desc = "DAP: step into" }) + map("n", "do", function() dap.step_over() end, { desc = "DAP: step over" }) + map("n", "dO", function() dap.step_out() end, { desc = "DAP: step out" }) + map("n", "dp", function() dap.pause() end, { desc = "DAP: pause" }) + map("n", "dr", function() dap.restart() end, { desc = "DAP: restart" }) + map("n", "dl", function() dap.run_last() end, { desc = "DAP: run last" }) + map("n", "dt", function() dap.terminate() end, { desc = "DAP: terminate" }) + map("n", "dw", function() require("dap.ui.widgets").hover() end, { desc = "DAP: widgets" }) + + -- CodeLLDB Discovery & Adapter + local mason_path = vim.fn.stdpath("data") .. "/mason/packages/codelldb" + local codelldb_mason = mason_path .. "/extension/adapter/codelldb" + local codelldb_cmd = "codelldb" + + if vim.fn.filereadable(codelldb_mason) == 1 or vim.fn.executable(codelldb_mason) == 1 then + codelldb_cmd = codelldb_mason + elseif vim.fn.executable("codelldb") == 1 then + codelldb_cmd = vim.fn.exepath("codelldb") + end + + dap.adapters.codelldb = { + type = "server", + port = "''${port}", + executable = { + command = codelldb_cmd, + args = { "--port", "''${port}" }, + }, + } + dap.adapters.lldb = dap.adapters.codelldb + + local function get_binary() + local cwd = vim.fn.getcwd() + local candidates = {} + local seen = {} + + local search_dirs = { + "target/debug", "target/release", "build", "build/bin", + "bin", "out", "cmake-build-debug", "cmake-build-release", + } + local ignored_exts = { + o = true, a = true, so = true, dylib = true, dll = true, + d = true, rlib = true, rmeta = true, pdb = true, + txt = true, log = true, cmake = true, json = true, + ninja = true, lock = true, + } + local ignored_script_exts = { + sh = true, bash = true, zsh = true, lua = true, py = true, + pl = true, rb = true, js = true, ts = true, md = true, + } + + for _, dir in ipairs(search_dirs) do + local full_dir = cwd .. "/" .. dir + if vim.fn.isdirectory(full_dir) == 1 then + local files = vim.fn.glob(full_dir .. "/*", false, true) + for _, file in ipairs(files) do + if vim.fn.isdirectory(file) == 0 and vim.fn.executable(file) == 1 then + local ext = vim.fn.fnamemodify(file, ":e"):lower() + if not ignored_exts[ext] and not ignored_script_exts[ext] and not seen[file] then + seen[file] = true + table.insert(candidates, file) + end + end + end + end + end + + local root_files = vim.fn.glob(cwd .. "/*", false, true) + for _, file in ipairs(root_files) do + if vim.fn.isdirectory(file) == 0 and vim.fn.executable(file) == 1 then + local ext = vim.fn.fnamemodify(file, ":e"):lower() + if not ignored_exts[ext] and not ignored_script_exts[ext] and not file:match("/%.") and not seen[file] then + seen[file] = true + table.insert(candidates, file) + end + end + end + + local co = coroutine.running() + if co and #candidates > 0 then + local items = {} + for _, c in ipairs(candidates) do + table.insert(items, c) + end + table.insert(items, "Manually enter path...") + + vim.ui.select(items, { + prompt = "Select executable: ", + format_item = function(item) + if item == "Manually enter path..." then + return item + end + return vim.fn.fnamemodify(item, ":.") + end, + }, function(choice) + if not choice then + coroutine.resume(co, dap.ABORT) + elseif choice == "Manually enter path..." then + local manual = vim.fn.input("Path to executable: ", cwd .. "/", "file") + coroutine.resume(co, manual ~= "" and manual or dap.ABORT) + else + coroutine.resume(co, choice) + end + end) + return coroutine.yield() + elseif #candidates == 1 and not co then + return candidates[1] + else + local input = vim.fn.input("Path to executable: ", cwd .. "/", "file") + if input == "" then + return dap.ABORT or nil + end + return input + end + end + + local codelldb_configurations = { + { + name = "Launch Binary (Smart Picker)", + type = "codelldb", + request = "launch", + program = get_binary, + cwd = "''${workspaceFolder}", + stopOnEntry = false, + args = {}, + }, + { + name = "Launch Binary with Arguments", + type = "codelldb", + request = "launch", + program = get_binary, + cwd = "''${workspaceFolder}", + stopOnEntry = false, + args = function() + local args_str = vim.fn.input("Arguments: ") + if args_str == "" then + return {} + end + return vim.split(args_str, "%s+", { trimempty = true }) + end, + }, + { + name = "Attach to Process", + type = "codelldb", + request = "attach", + pid = function() + return require("dap.utils").pick_process() + end, + cwd = "''${workspaceFolder}", + }, + { + name = "Load Core Dump", + type = "codelldb", + request = "attach", + program = get_binary, + coreDumpPath = function() + local core_path = vim.fn.input("Path to core dump: ", vim.fn.getcwd() .. "/", "file") + return core_path ~= "" and core_path or nil + end, + cwd = "''${workspaceFolder}", + }, + } + + dap.configurations.cpp = codelldb_configurations + dap.configurations.c = codelldb_configurations + dap.configurations.rust = codelldb_configurations + + -- Delve (Go) + local dlv_path = "dlv" + local mason_dlv = vim.fn.stdpath("data") .. "/mason/packages/delve/dlv" + if vim.fn.filereadable(mason_dlv) == 1 or vim.fn.executable(mason_dlv) == 1 then + dlv_path = mason_dlv + elseif vim.fn.executable("dlv") == 1 then + dlv_path = vim.fn.exepath("dlv") + end + + dap.adapters.delve = { + type = "server", + port = "''${port}", + executable = { + command = dlv_path, + args = { "dap", "-l", "127.0.0.1:''${port}" }, + }, + } + dap.adapters.go = dap.adapters.delve + + dap.configurations.go = { + { + type = "delve", + name = "Debug (Smart Picker / Current File)", + request = "launch", + program = "''${file}", + }, + { + type = "delve", + name = "Debug Package", + request = "launch", + program = "''${fileDirname}", + }, + { + type = "delve", + name = "Debug with Arguments", + request = "launch", + program = "''${file}", + args = function() + local args_str = vim.fn.input("Arguments: ") + if args_str == "" then + return {} + end + return vim.split(args_str, "%s+", { trimempty = true }) + end, + }, + { + type = "delve", + name = "Debug Test", + request = "launch", + mode = "test", + program = "''${file}", + }, + { + type = "delve", + name = "Debug Test (Package)", + request = "launch", + mode = "test", + program = "./''${relativeFileDirname}", + }, + { + type = "delve", + name = "Attach to Process", + request = "attach", + mode = "local", + processId = function() + return require("dap.utils").pick_process() + end, + }, + } + end) + + -- ─── Colorscheme (rose-pine & monoglow) ─────────────────────── + safe_setup("rose-pine", function(rose_pine) + rose_pine.setup({ + variant = "main", + dark_variant = "main", + dim_inactive_windows = false, + extend_background_behind_borders = true, + styles = { + bold = true, + italic = true, + transparency = true, + }, + highlight_groups = { + TelescopeBorder = { fg = "highlight_high", bg = "none" }, + TelescopeNormal = { bg = "none" }, + TelescopePromptNormal = { bg = "base" }, + TelescopeResultsNormal = { fg = "subtle", bg = "none" }, + TelescopeSelection = { fg = "text", bg = "base" }, + TelescopeSelectionCaret = { fg = "rose", bg = "rose" }, + FloatBorder = { fg = "highlight_high", bg = "surface" }, + NormalFloat = { bg = "surface" }, + CursorLine = { bg = "highlight_low" }, + CursorLineNr = { fg = "gold" }, + StatusLine = { fg = "love", bg = "love", blend = 10 }, + StatusLineNC = { fg = "subtle", bg = "surface" }, + WhichKeyFloat = { bg = "surface" }, + }, + }) + local ok_base46, base46 = pcall(require, "base46") + if ok_base46 then + base46.setup({ + transparency = true, + set_background = true, + term_colors = true, + integrations = { + defaults = true, + syntax = true, + treesitter = true, + lsp = true, + telescope = true, + whichkey = true, + gitsigns = true, + bufferline = true, + devicons = true, + blink = true, + statusline = false, + neotest = true, + }, + }) + end + + package.preload["core.theme"] = function() + local base46_state_file = vim.fn.stdpath("state") .. "/base46-theme" + return { + base46_state_file = base46_state_file, + read_base46_theme = function() + if vim.fn.filereadable(base46_state_file) ~= 1 then return nil end + local lines = vim.fn.readfile(base46_state_file) + local theme = lines[1] + return theme and theme ~= "" and theme or nil + end, + write_base46_theme = function(theme) + vim.fn.mkdir(vim.fn.fnamemodify(base46_state_file, ":h"), "p") + vim.fn.writefile({ theme }, base46_state_file) + end, + } + end + + local theme_state = require("core.theme") + local saved_theme = theme_state.read_base46_theme() + + if saved_theme then + if saved_theme == "monoglow" or saved_theme == "rose-pine" then + vim.cmd.colorscheme(saved_theme) + else + if ok_base46 then + base46.load(saved_theme) + vim.api.nvim_exec_autocmds("ColorScheme", {}) + else + vim.cmd.colorscheme("monoglow") + end + end + else + vim.cmd.colorscheme("monoglow") + end + + local function pick_theme() + local themes = { "monoglow", "rose-pine" } + for _, path in ipairs(vim.api.nvim_get_runtime_file("lua/base46/themes/*.lua", true)) do + local name = vim.fn.fnamemodify(path, ":t:r") + themes[#themes + 1] = name + end + table.sort(themes) + + vim.ui.select(themes, { prompt = "Select theme: " }, function(choice) + if choice then + require("core.theme").write_base46_theme(choice) + if choice == "monoglow" or choice == "rose-pine" then + vim.cmd.colorscheme(choice) + else + local ok, b46 = pcall(require, "base46") + if ok then + b46.load(choice) + vim.api.nvim_exec_autocmds("ColorScheme", {}) + end + end + vim.notify("Theme: " .. choice, vim.log.levels.INFO) + end + end) + end + + vim.keymap.set("n", "ut", pick_theme, { desc = "Themes: choose theme" }) + vim.api.nvim_create_user_command("NvChadTheme", pick_theme, { desc = "Choose colorscheme" }) + package.preload["base46-themes"] = function() + return { + pick = pick_theme, + } + end + end) + + -- ─── LSP Configuration & Keymaps ───────────────────────────── + vim.diagnostic.config({ + underline = true, + update_in_insert = false, + virtual_text = { + spacing = 4, + source = "if_many", + prefix = "●", + }, + severity_sort = true, + float = { + focusable = true, + style = "minimal", + border = "rounded", + source = true, + header = "", + prefix = "", + }, + signs = { + text = { + [vim.diagnostic.severity.ERROR] = " ", + [vim.diagnostic.severity.WARN] = " ", + [vim.diagnostic.severity.HINT] = "󰌵 ", + [vim.diagnostic.severity.INFO] = " ", + }, + }, + }) + + vim.api.nvim_create_autocmd("LspAttach", { + group = vim.api.nvim_create_augroup("UserLspConfig", { clear = true }), + callback = function(ev) + local bufnr = ev.buf + local client = vim.lsp.get_client_by_id(ev.data.client_id) + if not client then + return + end + + local map = function(keys, func, desc, mode) + mode = mode or "n" + vim.keymap.set(mode, keys, func, { buffer = bufnr, desc = "LSP: " .. desc }) + end + + map("gd", vim.lsp.buf.definition, "Go to definition") + map("gD", vim.lsp.buf.declaration, "Go to declaration") + map("gr", vim.lsp.buf.references, "Go to references") + map("gi", vim.lsp.buf.implementation, "Go to implementation") + map("D", vim.lsp.buf.type_definition, "Type definition") + map("li", vim.lsp.buf.incoming_calls, "Incoming calls") + map("lo", vim.lsp.buf.outgoing_calls, "Outgoing calls") + + map("K", vim.lsp.buf.hover, "Hover documentation") + map("", vim.lsp.buf.signature_help, "Signature help") + map("", vim.lsp.buf.signature_help, "Signature help", "i") + + map("ca", vim.lsp.buf.code_action, "Code action") + map("ca", vim.lsp.buf.code_action, "Code action", "v") + map("rn", vim.lsp.buf.rename, "Rename symbol") + + map("ld", vim.diagnostic.open_float, "Line diagnostics") + map("[d", function() vim.diagnostic.jump({ count = -1, float = true }) end, "Previous diagnostic") + map("]d", function() vim.diagnostic.jump({ count = 1, float = true }) end, "Next diagnostic") + map("[e", function() vim.diagnostic.jump({ count = -1, float = true, severity = vim.diagnostic.severity.ERROR }) end, "Previous error") + map("]e", function() vim.diagnostic.jump({ count = 1, float = true, severity = vim.diagnostic.severity.ERROR }) end, "Next error") + + map("lwa", vim.lsp.buf.add_workspace_folder, "Add workspace folder") + map("lwr", vim.lsp.buf.remove_workspace_folder, "Remove workspace folder") + map("lwl", function() + print(vim.inspect(vim.lsp.buf.list_workspace_folders())) + end, "List workspace folders") + + if client:supports_method("textDocument/inlayHint", bufnr) then + vim.lsp.inlay_hint.enable(true, { bufnr = bufnr }) + map("lh", function() + vim.lsp.inlay_hint.enable(not vim.lsp.inlay_hint.is_enabled({ bufnr = bufnr }), { bufnr = bufnr }) + end, "Toggle inlay hints") + end + + if client:supports_method("textDocument/codeLens", bufnr) then + map("ll", vim.lsp.codelens.run, "Run codelens") + map("lL", vim.lsp.codelens.refresh, "Refresh codelens") + end + + if client:supports_method("textDocument/documentHighlight", bufnr) then + local group = vim.api.nvim_create_augroup("LspDocumentHighlight", { clear = false }) + vim.api.nvim_clear_autocmds({ group = group, buffer = bufnr }) + vim.api.nvim_create_autocmd({ "CursorHold", "CursorHoldI" }, { + group = group, + buffer = bufnr, + callback = vim.lsp.buf.document_highlight, + }) + vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI", "BufLeave" }, { + group = group, + buffer = bufnr, + callback = vim.lsp.buf.clear_references, + }) + end + end, + }) + + -- ─── Bufferline — Tab/Buffer Line ───────────────────────────── + safe_setup("bufferline", function(bufferline) + bufferline.setup({ + options = { + mode = "buffers", + themable = true, + numbers = "none", + close_command = "bdelete! %d", + right_mouse_command = "bdelete! %d", + left_mouse_command = "buffer %d", + middle_mouse_command = nil, + indicator = { + icon = "▎", + style = "icon", + }, + buffer_close_icon = "󰅖", + modified_icon = "●", + close_icon = "", + left_trunc_marker = "", + right_trunc_marker = "", + max_name_length = 30, + max_prefix_length = 30, + truncate_names = true, + tab_size = 21, + diagnostics = "nvim_lsp", + diagnostics_update_in_insert = false, + diagnostics_indicator = function(count, level) + local icon = level:match("error") and " " or " " + return " " .. icon .. count + end, + offsets = { + { + filetype = "oil", + text = " File Explorer", + highlight = "Directory", + text_align = "left", + separator = true, + }, + }, + color_icons = true, + show_buffer_icons = true, + show_buffer_close_icons = true, + show_close_icon = false, + show_tab_indicators = true, + show_duplicate_prefix = true, + persist_buffer_sort = true, + separator_style = "thin", + enforce_regular_tabs = false, + always_show_bufferline = true, + hover = { + enabled = true, + delay = 200, + reveal = { "close" }, + }, + }, + }) + + vim.keymap.set("n", "bp", "BufferLineTogglePin", { desc = "Pin buffer" }) + vim.keymap.set("n", "bx", "BufferLineCloseOthers", { desc = "Close other buffers" }) + vim.keymap.set("n", "br", "BufferLineCloseRight", { desc = "Close buffers to the right" }) + vim.keymap.set("n", "bl", "BufferLineCloseLeft", { desc = "Close buffers to the left" }) + vim.keymap.set("n", "", "BufferLineCyclePrev", { desc = "Previous buffer" }) + vim.keymap.set("n", "", "BufferLineCycleNext", { desc = "Next buffer" }) + vim.keymap.set("n", "[b", "BufferLineCyclePrev", { desc = "Previous buffer" }) + vim.keymap.set("n", "]b", "BufferLineCycleNext", { desc = "Next buffer" }) + end) + + + -- ─── Extra Editing Plugins ──────────────────────────────────── + safe_setup("nvim-autopairs", function(autopairs) + autopairs.setup({ + check_ts = true, + ts_config = { + lua = { "string", "source" }, + javascript = { "string", "template_string" }, + java = false, + }, + disable_filetype = { "TelescopePrompt", "spectre_panel" }, + fast_wrap = { + map = "", + chars = { "{", "[", "(", '"', "'" }, + pattern = [=[[%'%"%>%]%)%}%,]]=], + end_key = "$", + before_key = "h", + after_key = "l", + cursor_pos_before = true, + keys = "qwertyuiopzxcvbnmasdfghjkl", + manual_position = true, + highlight = "Search", + highlight_grey = "Comment", + }, + }) + end) + + safe_setup("nvim-surround", function(surround) + surround.setup({}) + end) + + safe_setup("trouble", function(trouble) + trouble.setup({ + use_diagnostic_signs = true, + }) + + vim.keymap.set("n", "xx", "Trouble diagnostics toggle", { desc = "Diagnostics (Trouble)" }) + vim.keymap.set("n", "xX", "Trouble diagnostics toggle filter.buf=0", { desc = "Buffer diagnostics (Trouble)" }) + vim.keymap.set("n", "xs", "Trouble symbols toggle focus=false", { desc = "Symbols (Trouble)" }) + vim.keymap.set("n", "xl", "Trouble lsp toggle focus=false win.position=right", { desc = "LSP references (Trouble)" }) + vim.keymap.set("n", "xL", "Trouble loclist toggle", { desc = "Location list (Trouble)" }) + vim.keymap.set("n", "xQ", "Trouble qflist toggle", { desc = "Quickfix list (Trouble)" }) + end) + + safe_setup("mini.ai", function(ai) + ai.setup({ + n_lines = 500, + custom_textobjects = { + o = ai.gen_spec.treesitter({ + a = { "@block.outer", "@conditional.outer", "@loop.outer" }, + i = { "@block.inner", "@conditional.inner", "@loop.inner" }, + }, {}), + f = ai.gen_spec.treesitter({ a = "@function.outer", i = "@function.inner" }, {}), + c = ai.gen_spec.treesitter({ a = "@class.outer", i = "@class.inner" }, {}), + }, + }) + end) + + safe_setup("better_escape", function(be) + be.setup({ + timeout = 200, + default_mappings = false, + mappings = { + i = { + j = { k = "" }, + }, + c = { + j = { k = "" }, + }, + }, + }) + end) + + safe_setup("persistence", function(persistence) + persistence.setup({ + options = { "buffers", "curdir", "tabpages", "winsize", "help", "globals", "skiprtp" }, + }) + + vim.keymap.set("n", "qs", function() persistence.load() end, { desc = "Restore session" }) + vim.keymap.set("n", "ql", function() persistence.load({ last = true }) end, { desc = "Restore last session" }) + vim.keymap.set("n", "qd", function() persistence.stop() end, { desc = "Don't save current session" }) + end) + + + -- ─── Git Integration ────────────────────────────────────────── + safe_setup("gitsigns", function(gitsigns) + gitsigns.setup({ + signs = { + add = { text = "▎" }, + change = { text = "▎" }, + delete = { text = "" }, + topdelete = { text = "" }, + changedelete = { text = "▎" }, + untracked = { text = "▎" }, + }, + signs_staged = { + add = { text = "▎" }, + change = { text = "▎" }, + delete = { text = "" }, + topdelete = { text = "" }, + changedelete = { text = "▎" }, + }, + current_line_blame = false, + current_line_blame_opts = { + virt_text = true, + virt_text_pos = "eol", + delay = 500, + ignore_whitespace = false, + }, + current_line_blame_formatter = ", - ", + preview_config = { + border = "rounded", + style = "minimal", + relative = "cursor", + row = 0, + col = 1, + }, + on_attach = function(bufnr) + local gs = package.loaded.gitsigns + + local function map(mode, l, r, opts) + opts = opts or {} + opts.buffer = bufnr + vim.keymap.set(mode, l, r, opts) + end + + -- Navigation + map("n", "]g", function() + if vim.wo.diff then + vim.cmd.normal({ "]c", bang = true }) + else + gs.nav_hunk("next") + end + end, { desc = "Next git hunk" }) + + map("n", "[g", function() + if vim.wo.diff then + vim.cmd.normal({ "[c", bang = true }) + else + gs.nav_hunk("prev") + end + end, { desc = "Previous git hunk" }) + + -- Actions + map("n", "gs", gs.stage_hunk, { desc = "Git: stage hunk" }) + map("n", "gr", gs.reset_hunk, { desc = "Git: reset hunk" }) + map("v", "gs", function() gs.stage_hunk({ vim.fn.line("."), vim.fn.line("v") }) end, { desc = "Git: stage hunk" }) + map("v", "gr", function() gs.reset_hunk({ vim.fn.line("."), vim.fn.line("v") }) end, { desc = "Git: reset hunk" }) + map("n", "gS", gs.stage_buffer, { desc = "Git: stage buffer" }) + map("n", "gR", gs.reset_buffer, { desc = "Git: reset buffer" }) + map("n", "gu", gs.undo_stage_hunk, { desc = "Git: undo stage hunk" }) + map("n", "gp", gs.preview_hunk, { desc = "Git: preview hunk" }) + map("n", "gb", function() gs.blame_line({ full = true }) end, { desc = "Git: blame line" }) + map("n", "gB", gs.toggle_current_line_blame, { desc = "Git: toggle line blame" }) + map("n", "gd", gs.diffthis, { desc = "Git: diff this" }) + map("n", "gD", function() gs.diffthis("~") end, { desc = "Git: diff this ~" }) + + -- Text object + map({ "o", "x" }, "ih", ":Gitsigns select_hunk", { desc = "Select git hunk" }) + end, + }) + end) + + + -- ─── Harpoon 2 — Fast File Switching ────────────────────────── + safe_setup("harpoon", function(harpoon) + harpoon:setup({ + settings = { + save_on_toggle = true, + sync_on_ui_close = true, + key = function() + return vim.uv.cwd() + end, + }, + }) + + vim.keymap.set("n", "a", function() harpoon:list():add() end, { desc = "Harpoon: add file" }) + vim.keymap.set("n", "", function() harpoon.ui:toggle_quick_menu(harpoon:list()) end, { desc = "Harpoon: toggle 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" }) + vim.keymap.set("n", "5", function() harpoon:list():select(5) end, { desc = "Harpoon: file 5" }) + vim.keymap.set("n", "[h", function() harpoon:list():prev() end, { desc = "Harpoon: previous file" }) + vim.keymap.set("n", "]h", function() harpoon:list():next() end, { desc = "Harpoon: next file" }) + end) + + + -- ─── Motion Training & Navigation Tools ─────────────────────────── + safe_setup("hardtime", function(hardtime) + hardtime.setup({ + max_count = 3, + disabled_filetypes = { + "qf", "netrw", "NvimTree", "lazy", "mason", "oil", + "snacks_dashboard", "dashboard", "trouble", "harpoon", + "help", "undotree", "dapui_scopes", "dapui_breakpoints", + "dapui_stacks", "dapui_watches", "dap-repl", "dapui_console", + }, + disabled_buftypes = { "nofile", "prompt", "quickfix", "terminal" }, + }) + + vim.keymap.set("n", "uh", function() hardtime.toggle() end, { desc = "Toggle Hardtime (motion trainer)" }) + end) + + safe_setup("precognition", function(precognition) + precognition.setup({ + start_visible = false, + show_blank = true, + }) + + vim.keymap.set("n", "up", function() precognition.toggle() end, { desc = "Toggle Precognition (motion guide)" }) + end) + + safe_setup("flash", function(flash) + flash.setup({}) + + vim.keymap.set({ "n", "x", "o" }, "s", function() flash.jump() end, { desc = "Flash jump" }) + vim.keymap.set({ "n", "x", "o" }, "S", function() flash.treesitter() end, { desc = "Flash treesitter" }) + vim.keymap.set("o", "r", function() flash.remote() end, { desc = "Remote Flash" }) + vim.keymap.set({ "o", "x" }, "R", function() flash.treesitter_search() end, { desc = "Treesitter search" }) + vim.keymap.set("c", "", function() flash.toggle() end, { desc = "Toggle Flash search" }) + end) + + + -- ─── Oil — File Explorer as a Buffer ────────────────────────── + safe_setup("oil", function(oil) + oil.setup({ + default_file_explorer = true, + columns = { + "icon", + "size", + }, + buf_options = { + buflisted = false, + bufhidden = "hide", + }, + win_options = { + wrap = false, + signcolumn = "no", + cursorcolumn = false, + foldcolumn = "0", + spell = false, + list = false, + conceallevel = 3, + concealcursor = "nvic", + }, + delete_to_trash = true, + skip_confirm_for_simple_edits = true, + prompt_save_on_select_new_entry = true, + cleanup_delay_ms = 2000, + lsp_file_methods = { + timeout_ms = 1000, + autosave_changes = false, + }, + constrain_cursor = "editable", + watch_for_changes = true, + keymaps = { + ["g?"] = "actions.show_help", + [""] = "actions.select", + [""] = { "actions.select", opts = { vertical = true }, desc = "Open in vsplit" }, + [""] = { "actions.select", opts = { horizontal = true }, desc = "Open in hsplit" }, + [""] = { "actions.select", opts = { tab = true }, desc = "Open in new tab" }, + [""] = "actions.preview", + [""] = "actions.close", + [""] = "actions.refresh", + ["-"] = "actions.parent", + ["_"] = "actions.open_cwd", + ["`"] = "actions.cd", + ["~"] = { "actions.cd", opts = { scope = "tab" }, desc = ":tcd to the directory" }, + ["gs"] = "actions.change_sort", + ["gx"] = "actions.open_external", + ["g."] = "actions.toggle_hidden", + ["g\\"] = "actions.toggle_trash", + }, + use_default_keymaps = false, + view_options = { + show_hidden = true, + is_hidden_file = function(name, _) + return vim.startswith(name, ".") + end, + is_always_hidden = function(name, _) + return name == ".." or name == ".git" + end, + natural_order = true, + case_insensitive = false, + sort = { + { "type", "asc" }, + { "name", "asc" }, + }, + }, + float = { + padding = 2, + max_width = 90, + max_height = 0, + border = "rounded", + win_options = { + winblend = 0, + }, + }, + }) + + vim.keymap.set("n", "e", "Oil", { desc = "Open file explorer (Oil)" }) + vim.keymap.set("n", "-", "Oil", { desc = "Open parent directory" }) + end) + + + -- ─── Testing — one workflow for Go, Rust, and C++ projects ──────── + safe_setup("neotest", function(neotest) + local adapters = {} + + -- Go Adapter + local ok_golang, neotest_golang = pcall(require, "neotest-golang") + if ok_golang then + table.insert( + adapters, + neotest_golang({ + runner = "go", + go_test_args = { "-v", "-race", "-count=1" }, + dap_go_opts = {}, + }) + ) + else + local ok_go, neotest_go = pcall(require, "neotest-go") + if ok_go then + table.insert( + adapters, + neotest_go({ + experimental = { test_table = true }, + args = { "-race", "-v", "-count=1" }, + }) + ) + end + end + + -- Rust Adapter + local ok_rustacean, rustacean_neotest = pcall(require, "rustaceanvim.neotest") + if ok_rustacean then + table.insert(adapters, rustacean_neotest) + else + local ok_rust, neotest_rust = pcall(require, "neotest-rust") + if ok_rust then + table.insert( + adapters, + neotest_rust({ + args = { "--no-capture" }, + dap_adapter = "codelldb", + }) + ) + end + end + + -- C++ Adapter + local ok_gtest, neotest_gtest = pcall(require, "neotest-gtest") + if ok_gtest then + if type(neotest_gtest) == "function" then + table.insert(adapters, neotest_gtest({})) + elseif type(neotest_gtest.setup) == "function" then + table.insert(adapters, neotest_gtest.setup({})) + else + table.insert(adapters, neotest_gtest) + end + end + + neotest.setup({ + adapters = adapters, + status = { virtual_text = true, signs = true }, + output = { open_on_run = true }, + output_panel = { open_on_run = false }, + quickfix = { open = false }, + }) + + vim.keymap.set("n", "tt", function() neotest.run.run() end, { desc = "Test: run nearest" }) + vim.keymap.set("n", "tf", function() neotest.run.run(vim.fn.expand("%")) end, { desc = "Test: run current file" }) + vim.keymap.set("n", "ta", function() neotest.run.run(vim.fn.getcwd()) end, { desc = "Test: run all (project)" }) + vim.keymap.set("n", "tw", function() neotest.watch.toggle() end, { desc = "Test: toggle watch mode" }) + vim.keymap.set("n", "td", function() neotest.run.run({ strategy = "dap" }) end, { desc = "Test: debug nearest (DAP)" }) + vim.keymap.set("n", "ts", function() neotest.summary.toggle() end, { desc = "Test: toggle summary" }) + vim.keymap.set("n", "to", function() neotest.output.open({ enter = true }) end, { desc = "Test: open output window" }) + vim.keymap.set("n", "tO", function() neotest.output_panel.toggle() end, { desc = "Test: toggle output panel" }) + vim.keymap.set("n", "tS", function() neotest.run.stop() end, { desc = "Test: stop running test" }) + vim.keymap.set("n", "]T", function() neotest.jump.next({ status = "failed" }) end, { desc = "Test: jump next failed" }) + vim.keymap.set("n", "[T", function() neotest.jump.prev({ status = "failed" }) end, { desc = "Test: jump previous failed" }) + end) + + + -- ─── Vim-Tmux Navigator ─────────────────────────────────────── + -- Init (runs before loaded typically, so doing it first) + vim.g.tmux_navigator_no_mappings = 1 + vim.g.tmux_navigator_save_on_switch = 2 + vim.g.tmux_navigator_disable_when_zoomed = 1 + vim.g.tmux_navigator_preserve_zoom = 1 + + vim.keymap.set("n", "", "TmuxNavigateLeft", { desc = "Navigate left (tmux-aware)" }) + vim.keymap.set("n", "", "TmuxNavigateDown", { desc = "Navigate down (tmux-aware)" }) + vim.keymap.set("n", "", "TmuxNavigateUp", { desc = "Navigate up (tmux-aware)" }) + vim.keymap.set("n", "", "TmuxNavigateRight", { desc = "Navigate right (tmux-aware)" }) + + + -- ─── TODO Comments ──────────────────────────────────────────── + safe_setup("todo-comments", function(todo) + todo.setup({ + signs = true, + sign_priority = 8, + keywords = { + FIX = { icon = " ", color = "error", alt = { "FIXME", "BUG", "FIXIT", "ISSUE" } }, + TODO = { icon = " ", color = "info" }, + HACK = { icon = " ", color = "warning" }, + WARN = { icon = " ", color = "warning", alt = { "WARNING", "XXX" } }, + PERF = { icon = "󰅒 ", alt = { "OPTIM", "PERFORMANCE", "OPTIMIZE" } }, + NOTE = { icon = "󰍨 ", color = "hint", alt = { "INFO" } }, + TEST = { icon = "⏲ ", color = "test", alt = { "TESTING", "PASSED", "FAILED" } }, + }, + merge_keywords = true, + highlight = { + multiline = true, + multiline_pattern = "^.", + multiline_context = 10, + before = "", + keyword = "wide", + after = "fg", + pattern = [[.*<(KEYWORDS)\s*:]], + comments_only = true, + max_line_len = 400, + exclude = {}, + }, + search = { + command = "rg", + args = { + "--color=never", + "--no-heading", + "--with-filename", + "--line-number", + "--column", + }, + pattern = [[\b(KEYWORDS):]], + }, + }) + + vim.keymap.set("n", "]t", function() todo.jump_next() end, { desc = "Next TODO" }) + vim.keymap.set("n", "[t", function() todo.jump_prev() end, { desc = "Previous TODO" }) + vim.keymap.set("n", "ft", "TodoTelescope", { desc = "Find TODOs" }) + vim.keymap.set("n", "fT", "TodoTelescope keywords=TODO,FIX,FIXME", { desc = "Find TODO/FIX/FIXME" }) + end) + + -- ─── Clangd Extensions ────────────────────────────────────────── + safe_setup("clangd_extensions", function(clangd_ext) + clangd_ext.setup({ + inlay_hints = { + inline = false, + only_current_line = false, + only_current_line_autocmd = { "CursorHold" }, + show_parameter_hints = true, + parameter_hints_prefix = "<- ", + other_hints_prefix = "=> ", + max_len_align = false, + max_len_align_padding = 1, + right_align = false, + right_align_padding = 7, + highlight = "Comment", + priority = 100, + }, + ast = { + role_icons = { + type = "🄣", + declaration = "🄓", + expression = "🄔", + statement = ";", + specifier = "🄢", + ["template argument"] = "🆃", + }, + kind_icons = { + Compound = "🄲", + Recovery = "🅁", + TranslationUnit = "🅄", + PackExpansion = "🄿", + TemplateTypeParm = "🅃", + TemplateTemplateParm = "🅃", + TemplateParamObject = "🅃", + }, + highlights = { + detail = "Comment", + }, + }, + memory_usage = { + border = "rounded", + }, + symbol_info = { + border = "rounded", + }, + type_hierarchy = { + border = "rounded", + }, + }) + + vim.api.nvim_create_autocmd("FileType", { + group = vim.api.nvim_create_augroup("ClangdKeymaps", { clear = true }), + pattern = { "c", "cpp", "objc", "objcpp", "cuda" }, + callback = function(event) + local map = function(keys, func, desc) + vim.keymap.set("n", keys, func, { buffer = event.buf, desc = "Clangd: " .. desc }) + end + + map("ch", "ClangdSwitchSourceHeader", "Switch source/header") + map("ct", "ClangdTypeHierarchy", "Type hierarchy") + map("cm", "ClangdMemoryUsage", "Memory usage") + map("cs", "ClangdSymbolInfo", "Symbol info") + map("cA", "ClangdAST", "View AST") + end, + }) + end) + + -- ─── Go.nvim ──────────────────────────────────────────────────── + safe_setup("go", function(go) + go.setup({ + lsp_cfg = false, + lsp_on_attach = false, + gofmt = "gofumpt", + goimports = "gopls", + linter = "golangci-lint", + test_runner = "go", + run_in_floaterm = true, + floaterm = { + position = "bottom", + width = 0.98, + height = 0.35, + }, + dap_debug = true, + dap_debug_gui = true, + tag_transform = "snakecase", + tag_options = "json=omitempty", + verbose = false, + log_path = vim.fn.stdpath("cache") .. "/gonvim.log", + icons = { breakpoint = "🔴", currentpos = "🏃" }, + }) + + vim.api.nvim_create_autocmd("FileType", { + pattern = { "go" }, + callback = function(event) + local map = function(keys, func, desc) + vim.keymap.set("n", keys, func, { buffer = event.buf, desc = "Go: " .. desc }) + end + + map("Gt", "GoTest", "Run tests") + map("Gf", "GoTestFunc", "Test function") + map("Gc", "GoCoverage", "Toggle coverage") + map("Ga", "GoAddTag", "Add struct tags") + map("Gr", "GoRmTag", "Remove struct tags") + map("Gi", "GoImpl", "Implement interface") + map("Ge", "GoIfErr", "Generate if err") + map("Gd", "GoDebug", "Start debugger") + map("GD", "GoDebug -s", "Stop debugger") + map("Gl", "GoLint", "Lint") + map("Gv", "GoVet", "Vet") + map("Gg", "GoGenerate", "Go generate") + map("Gm", "GoModTidy", "Go mod tidy") + end, + }) + end) + + -- ─── TS Context Commentstring & Comment.nvim ─────────────────── + safe_setup("ts_context_commentstring", function(ts_cc) + ts_cc.setup({ + enable_autocmd = false, + }) + local ok_comm, comm = pcall(require, "Comment") + if ok_comm then + local ok_ts_hook, ts_hook = pcall(require, "ts_context_commentstring.integrations.comment_nvim") + local pre_hook = ok_ts_hook and ts_hook.create_pre_hook() or nil + comm.setup({ + padding = true, + sticky = true, + ignore = "^$", + toggler = { + line = "gcc", + block = "gbc", + }, + opleader = { + line = "gc", + block = "gb", + }, + extra = { + above = "gcO", + below = "gco", + eol = "gcA", + }, + mappings = { + basic = true, + extra = true, + }, + pre_hook = pre_hook, + }) + end + end) + + -- ─── nvim-lint ──────────────────────────────────────────────── + safe_setup("lint", function(lint) + lint.linters["clang-tidy"] = lint.linters.clangtidy + lint.linters["golangci-lint"] = lint.linters.golangcilint + + lint.linters_by_ft = { + c = { "clang-tidy" }, + cpp = { "clang-tidy" }, + go = { "golangci-lint" }, + python = { "ruff" }, + } + + local lint_augroup = vim.api.nvim_create_augroup("NvimLintUser", { clear = true }) + + local function run_lint() + local bufnr = vim.api.nvim_get_current_buf() + if not vim.api.nvim_buf_is_valid(bufnr) then + return + end + local ft = vim.bo[bufnr].filetype + if not ft or ft == "" then + return + end + local linters = lint.linters_by_ft[ft] or {} + if type(linters) ~= "table" or #linters == 0 then + return + end + + local valid_linters = {} + for _, name in ipairs(linters) do + local linter = lint.linters[name] + if linter then + local cmd = type(linter) == "table" and linter.cmd or name + if type(cmd) == "function" then + cmd = cmd() + end + if type(cmd) == "string" and vim.fn.executable(cmd) == 1 then + table.insert(valid_linters, name) + end + end + end + + if #valid_linters > 0 then + lint.try_lint(valid_linters) + end + end + + vim.api.nvim_create_autocmd({ "BufWritePost", "BufReadPost", "InsertLeave" }, { + group = lint_augroup, + callback = run_lint, + }) + + vim.api.nvim_create_user_command("Lint", function() + run_lint() + end, { desc = "Trigger linting for current buffer" }) + + vim.keymap.set("n", "cl", function() + run_lint() + end, { desc = "Lint current buffer" }) + end) + + -- ─── Rustaceanvim ────────────────────────────────────────────── + local capabilities = vim.lsp.protocol.make_client_capabilities() + local ok_blink, blink = pcall(require, "blink.cmp") + if ok_blink then + capabilities = blink.get_lsp_capabilities(capabilities) + end + + vim.g.rustaceanvim = { + tools = { + hover_actions = { + replace_builtin_hover = false, + }, + float_win_config = { + border = "rounded", + }, + }, + server = { + capabilities = capabilities, + on_attach = function(client, bufnr) + local map = function(keys, func, desc) + vim.keymap.set("n", keys, func, { buffer = bufnr, desc = "Rust: " .. desc }) + end + + map("re", "RustLsp expandMacro", "Expand macro") + map("rc", "RustLsp openCargo", "Open Cargo.toml") + map("rp", "RustLsp parentModule", "Parent module") + map("rd", "RustLsp renderDiagnostic", "Render diagnostic") + map("rr", "RustLsp runnables", "Runnables") + map("rt", "RustLsp testables", "Testables") + map("rm", "RustLsp rebuildProcMacros", "Rebuild proc macros") + map("rk", "RustLsp moveItem up", "Move item up") + map("rj", "RustLsp moveItem down", "Move item down") + map("ra", "RustLsp codeAction", "Code action (rust)") + map("rh", "RustLsp hover actions", "Hover actions") + map("J", "RustLsp joinLines", "Join lines (Rust)") + map("rD", "RustLsp debuggables", "Debuggables") + end, + default_settings = { + ["rust-analyzer"] = { + cargo = { + allFeatures = true, + loadOutDirsFromCheck = true, + buildScripts = { enable = true }, + }, + checkOnSave = true, + check = { + command = "clippy", + extraArgs = { "--no-deps" }, + }, + procMacro = { + enable = true, + ignored = { + ["async-trait"] = { "async_trait" }, + ["napi-derive"] = { "napi" }, + ["async-recursion"] = { "async_recursion" }, + }, + }, + inlayHints = { + bindingModeHints = { enable = false }, + chainingHints = { enable = true }, + closingBraceHints = { enable = true, minLines = 25 }, + closureReturnTypeHints = { enable = "with_block" }, + lifetimeElisionHints = { enable = "never" }, + maxLength = 25, + parameterHints = { enable = true }, + reborrowHints = { enable = "never" }, + renderColons = true, + typeHints = { + enable = true, + hideClosureInitialization = false, + hideNamedConstructor = false, + }, + }, + }, + }, + }, + dap = { + adapter = function() + local ok_cfg, cfg = pcall(require, "rustaceanvim.config") + if not ok_cfg then return nil end + local mason_path = vim.fn.stdpath("data") .. "/mason/packages/codelldb" + local codelldb_path = mason_path .. "/extension/adapter/codelldb" + local liblldb_path = mason_path .. "/extension/lldb/lib/liblldb.so" + + if vim.fn.filereadable(codelldb_path) == 1 then + return cfg.get_codelldb_adapter(codelldb_path, liblldb_path) + else + return cfg.get_codelldb_adapter("codelldb", "") + end + end, + }, + } + + -- ─── Which-Key Specifications ────────────────────────────────── + local ok_wk, wk = pcall(require, "which-key") + if ok_wk then + wk.setup({ + preset = "modern", + delay = 300, + win = { + border = "rounded", + padding = { 1, 2 }, + title = true, + title_pos = "center", + zindex = 1000, + }, + layout = { + width = { min = 20 }, + spacing = 3, + }, + keys = { + scroll_down = "", + scroll_up = "", + }, + show_help = true, + show_keys = true, + triggers = { + { "", mode = "nxsot" }, + }, + }) + wk.add({ + { "f", group = "Find", icon = " " }, + { "e", group = "Explorer", icon = "󰙅 " }, + { "a", group = "Harpoon", icon = "󰛢 " }, + { "b", group = "Buffer", icon = "󰓩 " }, + { "l", group = "LSP", icon = " " }, + { "c", group = "Code", icon = "󰅩 " }, + { "C", group = "CMake/Build", icon = " " }, + { "r", group = "Rust", icon = "󱘗 " }, + { "G", group = "Go", icon = " " }, + { "t", group = "Test", icon = "󰙨 " }, + { "d", group = "Debug", icon = " " }, + { "u", group = "UI/Toggles", icon = "󱄄 " }, + { "g", group = "Git", icon = "󰊢 " }, + { "s", group = "Swap/Search", icon = "󰔡 " }, + { "x", group = "Trouble/Diagnostics", icon = "󰅙 " }, + { "n", group = "Noice", icon = "󰎟 " }, + { "w", icon = "󰆓 " }, + { "q", icon = "󰩈 " }, + { "L", icon = "󰒲 " }, + }) + + vim.keymap.set("n", "?", function() + wk.show({ global = false }) + end, { desc = "Buffer-local keymaps (which-key)" }) + end + + -- ─── Telescope Configuration & Keymaps ──────────────────────── + local ok_telescope, telescope = pcall(require, "telescope") + if ok_telescope then + local ok_actions, actions = pcall(require, "telescope.actions") + local mappings_i = {} + local mappings_n = {} + if ok_actions then + mappings_i = { + [""] = actions.move_selection_next, + [""] = actions.move_selection_previous, + [""] = actions.send_selected_to_qflist + actions.open_qflist, + [""] = actions.delete_buffer, + [""] = actions.close, + } + mappings_n = { + ["q"] = actions.close, + [""] = actions.send_selected_to_qflist + actions.open_qflist, + } + end + + local ok_themes, themes = pcall(require, "telescope.themes") + local ui_select_ext = ok_themes and { themes.get_dropdown() } or {} + + telescope.setup({ + defaults = { + 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" }, + preview = { + treesitter = false, + }, + winblend = 0, + border = {}, + borderchars = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" }, + color_devicons = true, + set_env = { ["COLORTERM"] = "truecolor" }, + file_ignore_patterns = { + "node_modules", + ".git/", + "target/", + "vendor/", + "__pycache__", + "%.lock", + }, + vimgrep_arguments = { + "rg", + "-L", + "--color=never", + "--no-heading", + "--with-filename", + "--line-number", + "--column", + "--smart-case", + }, + mappings = { + i = mappings_i, + n = mappings_n, + }, + }, + pickers = { + find_files = { + hidden = true, + find_command = { "rg", "--files", "--hidden", "--glob", "!**/.git/*" }, + }, + buffers = { + sort_mru = true, + sort_lastused = true, + }, + live_grep = { + additional_args = { "--hidden" }, + }, + }, + extensions = { + fzf = { + fuzzy = true, + override_generic_sorter = true, + override_file_sorter = true, + case_mode = "smart_case", + }, + ["ui-select"] = ui_select_ext, + }, + }) + + pcall(telescope.load_extension, "fzf") + pcall(telescope.load_extension, "ui-select") + + local map = vim.keymap.set + map("n", "ff", "Telescope find_files", { desc = "Find files" }) + map("n", "fg", "Telescope live_grep", { desc = "Live grep" }) + map("n", "fb", "Telescope buffers", { desc = "Find buffers" }) + map("n", "fh", "Telescope help_tags", { desc = "Help tags" }) + map("n", "fr", "Telescope oldfiles", { desc = "Recent files" }) + map("n", "fd", "Telescope diagnostics", { desc = "Diagnostics" }) + map("n", "fw", "Telescope grep_string", { desc = "Find word under cursor" }) + map("n", "fk", "Telescope keymaps", { desc = "Keymaps" }) + map("n", "fc", "Telescope commands", { desc = "Commands" }) + map("n", "fm", "Telescope marks", { desc = "Marks" }) + map("n", "fs", "Telescope lsp_document_symbols", { desc = "Document symbols" }) + map("n", "fS", "Telescope lsp_workspace_symbols", { desc = "Workspace symbols" }) + map("n", "gc", "Telescope git_commits", { desc = "Git commits" }) + map("n", "gC", "Telescope git_bcommits", { desc = "Git buffer commits" }) + map("n", "gt", "Telescope git_status", { desc = "Git status" }) + map("n", "", "Telescope resume", { desc = "Resume last search" }) + end + + -- ─── Treesitter Textobjects ─────────────────────────────────── + local ok_ts_configs, ts_configs = pcall(require, "nvim-treesitter.configs") + if ok_ts_configs then + ts_configs.setup({ + textobjects = { + select = { + enable = true, + lookahead = true, + keymaps = { + ["af"] = { query = "@function.outer", desc = "Around function" }, + ["if"] = { query = "@function.inner", desc = "Inside function" }, + ["ac"] = { query = "@class.outer", desc = "Around class" }, + ["ic"] = { query = "@class.inner", desc = "Inside class" }, + ["aa"] = { query = "@parameter.outer", desc = "Around argument" }, + ["ia"] = { query = "@parameter.inner", desc = "Inside argument" }, + ["al"] = { query = "@loop.outer", desc = "Around loop" }, + ["il"] = { query = "@loop.inner", desc = "Inside loop" }, + ["ai"] = { query = "@conditional.outer", desc = "Around conditional" }, + ["ii"] = { query = "@conditional.inner", desc = "Inside conditional" }, + ["ab"] = { query = "@block.outer", desc = "Around block" }, + ["ib"] = { query = "@block.inner", desc = "Inside block" }, + }, + }, + move = { + enable = true, + goto_next_start = { + ["]f"] = { query = "@function.outer", desc = "Next function start" }, + ["]c"] = { query = "@class.outer", desc = "Next class start" }, + ["]a"] = { query = "@parameter.inner", desc = "Next argument" }, + }, + goto_next_end = { + ["]F"] = { query = "@function.outer", desc = "Next function end" }, + ["]C"] = { query = "@class.outer", desc = "Next class end" }, + }, + goto_previous_start = { + ["[f"] = { query = "@function.outer", desc = "Previous function start" }, + ["[c"] = { query = "@class.outer", desc = "Previous class start" }, + ["[a"] = { query = "@parameter.inner", desc = "Previous argument" }, + }, + goto_previous_end = { + ["[F"] = { query = "@function.outer", desc = "Previous function end" }, + ["[C"] = { query = "@class.outer", desc = "Previous class end" }, + }, + }, + swap = { + enable = true, + swap_next = { + ["cx"] = { query = "@parameter.inner", desc = "Swap with next parameter" }, + }, + swap_previous = { + ["cX"] = { query = "@parameter.inner", desc = "Swap with previous parameter" }, + }, + }, + }, + }) + else + local ok_ts_to, ts_to = pcall(require, "nvim-treesitter-textobjects") + if ok_ts_to then + ts_to.setup({ + select = { + lookahead = true, + }, + move = { + set_jumps = true, + }, + }) + + local map = vim.keymap.set + local sel = require("nvim-treesitter-textobjects.select") + local mov = require("nvim-treesitter-textobjects.move") + local swp = require("nvim-treesitter-textobjects.swap") + + local select_maps = { + ["af"] = { query = "@function.outer", desc = "Around function" }, + ["if"] = { query = "@function.inner", desc = "Inside function" }, + ["ac"] = { query = "@class.outer", desc = "Around class" }, + ["ic"] = { query = "@class.inner", desc = "Inside class" }, + ["aa"] = { query = "@parameter.outer", desc = "Around argument" }, + ["ia"] = { query = "@parameter.inner", desc = "Inside argument" }, + ["al"] = { query = "@loop.outer", desc = "Around loop" }, + ["il"] = { query = "@loop.inner", desc = "Inside loop" }, + ["ai"] = { query = "@conditional.outer", desc = "Around conditional" }, + ["ii"] = { query = "@conditional.inner", desc = "Inside conditional" }, + ["ab"] = { query = "@block.outer", desc = "Around block" }, + ["ib"] = { query = "@block.inner", desc = "Inside block" }, + } + for k, v in pairs(select_maps) do + map({ "x", "o" }, k, function() + sel.select_textobject(v.query) + end, { desc = v.desc }) + end + + map({ "n", "x", "o" }, "]f", function() mov.goto_next_start("@function.outer") end, { desc = "Next function start" }) + map({ "n", "x", "o" }, "]c", function() mov.goto_next_start("@class.outer") end, { desc = "Next class start" }) + map({ "n", "x", "o" }, "]a", function() mov.goto_next_start("@parameter.inner") end, { desc = "Next argument" }) + map({ "n", "x", "o" }, "]F", function() mov.goto_next_end("@function.outer") end, { desc = "Next function end" }) + map({ "n", "x", "o" }, "]C", function() mov.goto_next_end("@class.outer") end, { desc = "Next class end" }) + map({ "n", "x", "o" }, "[f", function() mov.goto_previous_start("@function.outer") end, { desc = "Previous function start" }) + map({ "n", "x", "o" }, "[c", function() mov.goto_previous_start("@class.outer") end, { desc = "Previous class start" }) + map({ "n", "x", "o" }, "[a", function() mov.goto_previous_start("@parameter.inner") end, { desc = "Previous argument" }) + map({ "n", "x", "o" }, "[F", function() mov.goto_previous_end("@function.outer") end, { desc = "Previous function end" }) + map({ "n", "x", "o" }, "[C", function() mov.goto_previous_end("@class.outer") end, { desc = "Previous class end" }) + + map("n", "cx", function() swp.swap_next("@parameter.inner") end, { desc = "Swap with next parameter" }) + map("n", "cX", function() swp.swap_previous("@parameter.inner") end, { desc = "Swap with previous parameter" }) + end + end + + ''; + }; +} diff --git a/nvf/config/options.nix b/nvf/config/options.nix new file mode 100644 index 00000000..bf97e207 --- /dev/null +++ b/nvf/config/options.nix @@ -0,0 +1,51 @@ +{ pkgs, ... }: { + config.vim = { + options = { + number = true; + relativenumber = true; + signcolumn = "yes"; + cursorline = true; + tabstop = 2; + shiftwidth = 2; + softtabstop = 2; + expandtab = true; + smartindent = true; + autoindent = true; + breakindent = true; + ignorecase = true; + smartcase = true; + hlsearch = true; + incsearch = true; + termguicolors = true; + background = "dark"; + showmode = false; + pumheight = 10; + pumblend = 10; + winblend = 0; + conceallevel = 0; + cmdheight = 1; + laststatus = 3; + list = true; + mouse = "a"; + clipboard = "unnamedplus"; + wrap = false; + linebreak = true; + scrolloff = 8; + sidescrolloff = 8; + splitright = true; + splitbelow = true; + swapfile = false; + backup = false; + undofile = true; + updatetime = 200; + timeoutlen = 300; + confirm = true; + shell = "bash"; + foldmethod = "expr"; + foldexpr = "v:lua.vim.treesitter.foldexpr()"; + foldlevel = 99; + foldlevelstart = 99; + foldenable = true; + }; + }; +} diff --git a/nvf/config/plugins.nix b/nvf/config/plugins.nix new file mode 100644 index 00000000..2a684250 --- /dev/null +++ b/nvf/config/plugins.nix @@ -0,0 +1,211 @@ +{ pkgs, ... }: { + config.vim = { + autocomplete = { + nvim-cmp.enable = false; + blink-cmp = { + enable = true; + friendly-snippets.enable = true; + setupOpts = { + keymap = { + preset = "default"; + "" = [ + "show" + "show_documentation" + "hide_documentation" + ]; + "" = [ "hide" ]; + "" = [ + "accept" + "fallback" + ]; + "" = [ + "select_next" + "snippet_forward" + "fallback" + ]; + "" = [ + "select_prev" + "snippet_backward" + "fallback" + ]; + "" = [ + "select_next" + "fallback" + ]; + "" = [ + "select_prev" + "fallback" + ]; + "" = [ + "scroll_documentation_up" + "fallback" + ]; + "" = [ + "scroll_documentation_down" + "fallback" + ]; + }; + appearance = { + nerd_font_variant = "mono"; + }; + completion = { + accept = { + auto_brackets = { + enabled = true; + }; + }; + menu = { + border = "rounded"; + winhighlight = "Normal:NormalFloat,FloatBorder:FloatBorder,CursorLine:PmenuSel,Search:None"; + }; + documentation = { + auto_show = true; + auto_show_delay_ms = 200; + window = { + border = "rounded"; + winhighlight = "Normal:NormalFloat,FloatBorder:FloatBorder"; + }; + }; + ghost_text = { + enabled = true; + }; + }; + signature = { + enabled = true; + window = { + border = "rounded"; + }; + }; + fuzzy = { + implementation = "prefer_rust"; + }; + }; + }; + }; + + ui = { + noice.enable = true; + colorizer.enable = true; + illuminate.enable = true; + fastaction.enable = true; + }; + + statusline.lualine.enable = true; + telescope.enable = true; + filetree.neo-tree.enable = false; # We use oil-nvim instead + binds.whichKey.enable = true; + + # Natively supported plugins mapped from your original config + git.gitsigns.enable = true; + tabline.nvimBufferline.enable = true; + comments.comment-nvim.enable = true; + notes.todo-comments.enable = true; + navigation.harpoon.enable = true; + utility.oil-nvim.enable = true; + utility.motion.flash-nvim.enable = true; + utility.motion.precognition.enable = true; + binds.hardtime-nvim.enable = true; + lsp.trouble.enable = true; + + extraPlugins = with pkgs.vimPlugins; { + snacks-nvim = { + package = snacks-nvim; + }; + dressing-nvim = { + package = dressing-nvim; + }; + vim-tmux-navigator = { + package = vim-tmux-navigator; + }; + neotest = { + package = neotest; + }; + neotest-go = { + package = neotest-go; + }; + neotest-rust = { + package = neotest-rust; + }; + neotest-gtest = { + package = neotest-gtest; + }; + neotest-golang = { + package = neotest-golang; + }; + fixcursorhold-nvim = { + package = FixCursorHold-nvim; + }; + persistence-nvim = { + package = persistence-nvim; + }; + + # Migrated plugins from lua config + conform-nvim = { + package = conform-nvim; + }; + nvim-dap-ui = { + package = nvim-dap-ui; + }; + nvim-nio = { + package = nvim-nio; + }; + nvim-dap-virtual-text = { + package = nvim-dap-virtual-text; + }; + nvim-dap-go = { + package = nvim-dap-go; + }; + nvim-dap-python = { + package = nvim-dap-python; + }; + better-escape-nvim = { + package = better-escape-nvim; + }; + mini-ai = { + package = mini-ai; + }; + indent-blankline-nvim = { + package = indent-blankline-nvim; + }; + nvim-notify = { + package = nvim-notify; + }; + nvim-autopairs = { + package = nvim-autopairs; + }; + nvim-surround = { + package = nvim-surround; + }; + clangd_extensions-nvim = { + package = clangd_extensions-nvim; + }; + go-nvim = { + package = go-nvim; + }; + guihua-lua = { + package = guihua-lua; + }; + nvim-ts-context-commentstring = { + package = nvim-ts-context-commentstring; + }; + nvim-treesitter-textobjects = { + package = nvim-treesitter-textobjects; + }; + nvim-lint = { + package = nvim-lint; + }; + telescope-fzf-native-nvim = { + package = telescope-fzf-native-nvim; + }; + telescope-ui-select-nvim = { + package = telescope-ui-select-nvim; + }; + rose-pine = { + package = rose-pine; + }; + nvim-lspconfig = { + package = nvim-lspconfig; + }; + }; + }; +} diff --git a/nvf/config/theme.nix b/nvf/config/theme.nix new file mode 100644 index 00000000..341c86af --- /dev/null +++ b/nvf/config/theme.nix @@ -0,0 +1,31 @@ +{ pkgs, ... }: { + config.vim = { + extraPlugins = with pkgs.vimPlugins; { + base46 = { + package = pkgs.vimUtils.buildVimPlugin { + name = "base46"; + src = pkgs.fetchFromGitHub { + owner = "AvengeMedia"; + repo = "base46"; + rev = "83522e02c6c3b4ea901c4bffd9e0a5e0371c1fe6"; + hash = "sha256-kwDMC6rYzJYECmGnwn8JiAbffUq7hAXcUH6gPSkk2uI="; + }; + doCheck = false; + }; + }; + monoglow-nvim = { + package = pkgs.vimUtils.buildVimPlugin { + name = "monoglow.nvim"; + src = pkgs.fetchFromGitHub { + owner = "wnkz"; + repo = "monoglow.nvim"; + rev = "a249b1f55bfe9171e2f8aff7acf140f78ca4b2bb"; + hash = "sha256-EIslqnOIOLfQ7e7L1FvwfVfel6h+UPFIUcSgvp8zf0E="; + }; + doCheck = false; + }; + }; + }; + luaConfigRC.monoglow = "vim.cmd.colorscheme('monoglow')"; + }; +} diff --git a/nvf/nvf.nix b/nvf/nvf.nix new file mode 100644 index 00000000..b06214bc --- /dev/null +++ b/nvf/nvf.nix @@ -0,0 +1,10 @@ +{ ... }: +{ + imports = [ + ./config/options.nix + ./config/languages.nix + ./config/plugins.nix + ./config/theme.nix + ./config/lua_config.nix + ]; +} From 31603b230dff0d3305912b4ce1fb19855fd09657 Mon Sep 17 00:00:00 2001 From: reze-dev <25588579+reze-dev@users.noreply.github.com> Date: Sat, 19 Sep 2026 01:05:16 +0530 Subject: [PATCH 04/12] feat(flake): add nvf input and expose nvf-vim package --- flake.lock | 54 +++++++++++++++++++++++++++++++++++++++++++++++ flake.nix | 4 ++++ flake/modules.nix | 1 + flake/nvf.nix | 24 +++++++++++++++++++++ 4 files changed, 83 insertions(+) create mode 100644 flake/nvf.nix diff --git a/flake.lock b/flake.lock index 0ff21ffa..1f786c6f 100644 --- a/flake.lock +++ b/flake.lock @@ -164,6 +164,22 @@ "type": "github" } }, + "flake-compat_3": { + "flake": false, + "locked": { + "lastModified": 1777699697, + "narHash": "sha256-Eg9b/rq/ECYwNwEXs5i9wHyhxNI0JrYx2srdI2uZMaQ=", + "ref": "refs/heads/main", + "rev": "382052b74656a369c5408822af3f2501e9b1af81", + "revCount": 94, + "type": "git", + "url": "https://git.lix.systems/lix-project/flake-compat.git" + }, + "original": { + "type": "git", + "url": "https://git.lix.systems/lix-project/flake-compat.git" + } + }, "flake-parts": { "inputs": { "nixpkgs-lib": [ @@ -569,6 +585,21 @@ "type": "github" } }, + "mnw": { + "locked": { + "lastModified": 1780772958, + "narHash": "sha256-VKKe8r4pwCGWZ3Yr9CPN129R4S3CKLSrlYqdYz3vKpM=", + "owner": "Gerg-L", + "repo": "mnw", + "rev": "0871dbf63a53610c95db04439ed8ea4d6ec9c160", + "type": "github" + }, + "original": { + "owner": "Gerg-L", + "repo": "mnw", + "type": "github" + } + }, "niri": { "inputs": { "niri-stable": "niri-stable", @@ -748,6 +779,28 @@ "type": "github" } }, + "nvf": { + "inputs": { + "flake-compat": "flake-compat_3", + "mnw": "mnw", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1789510188, + "narHash": "sha256-umec9LKkWjK89Yx4XpvQStkgfKvhK7GjVf5m21bjzKU=", + "owner": "notashelf", + "repo": "nvf", + "rev": "cdc7e7aaa7eb521bba1b38f54c3228d52cc31703", + "type": "github" + }, + "original": { + "owner": "notashelf", + "repo": "nvf", + "type": "github" + } + }, "pre-commit-hooks": { "inputs": { "flake-compat": "flake-compat_2", @@ -781,6 +834,7 @@ "niri": "niri", "nix-index-database": "nix-index-database", "nixpkgs": "nixpkgs_3", + "nvf": "nvf", "sops-nix": "sops-nix", "tmux-powerkit": "tmux-powerkit", "zen-browser": "zen-browser" diff --git a/flake.nix b/flake.nix index 68246e9f..3debfb4c 100644 --- a/flake.nix +++ b/flake.nix @@ -39,6 +39,10 @@ url = "github:nix-community/nix-index-database"; inputs.nixpkgs.follows = "nixpkgs"; }; + nvf = { + url = "github:notashelf/nvf"; + inputs.nixpkgs.follows = "nixpkgs"; + }; # ── Desktop & Wayland Ecosystem ───────────────────────────────────── home-manager = { diff --git a/flake/modules.nix b/flake/modules.nix index 83ae536e..afc92997 100644 --- a/flake/modules.nix +++ b/flake/modules.nix @@ -6,5 +6,6 @@ ./devshell.nix ./formatter.nix ./shell.nix + ./nvf.nix ]; } diff --git a/flake/nvf.nix b/flake/nvf.nix new file mode 100644 index 00000000..fc94a32c --- /dev/null +++ b/flake/nvf.nix @@ -0,0 +1,24 @@ +{ inputs, ... }: + +{ + perSystem = + { pkgs, ... }: + let + customNeovim = inputs.nvf.lib.neovimConfiguration { + inherit pkgs; + modules = [ ../nvf/nvf.nix ]; + }; + nvf-vim = + pkgs.runCommand "nvf-vim" + { + meta.mainProgram = "vim"; + } + '' + mkdir -p $out/bin + ln -s ${customNeovim.neovim}/bin/nvim $out/bin/vim + ''; + in + { + packages.nvf-vim = nvf-vim; + }; +} From e43b17f052e82272245cb9726be374692a2daa58 Mon Sep 17 00:00:00 2001 From: reze-dev <25588579+reze-dev@users.noreply.github.com> Date: Sat, 19 Sep 2026 01:05:19 +0530 Subject: [PATCH 05/12] feat(development): introduce ctos.features.nvf module and enable for workstation and Makima --- hosts/Makima/default.nix | 1 + modules/features/development/nvf.nix | 34 ++++++++++++++++++++++++++++ modules/profiles/workstation.nix | 1 + 3 files changed, 36 insertions(+) create mode 100644 modules/features/development/nvf.nix diff --git a/hosts/Makima/default.nix b/hosts/Makima/default.nix index c6697cba..b05beadc 100644 --- a/hosts/Makima/default.nix +++ b/hosts/Makima/default.nix @@ -49,6 +49,7 @@ greeter.enable = true; fish.enable = true; emacs.enable = true; + nvf.enable = true; }; # NVIDIA GPU diff --git a/modules/features/development/nvf.nix b/modules/features/development/nvf.nix new file mode 100644 index 00000000..eca638dc --- /dev/null +++ b/modules/features/development/nvf.nix @@ -0,0 +1,34 @@ +{ + config, + inputs, + lib, + pkgs, + ... +}: +let + cfg = config.ctos.features.nvf; +in +{ + options.ctos.features.nvf.enable = lib.mkEnableOption "nvf Neovim configuration exposed as vim"; + + config = lib.mkIf cfg.enable ( + let + customNeovim = inputs.nvf.lib.neovimConfiguration { + inherit pkgs; + modules = [ ../../../nvf/nvf.nix ]; + }; + nvf-vim = + pkgs.runCommand "nvf-vim" + { + meta.mainProgram = "vim"; + } + '' + mkdir -p $out/bin + ln -s ${customNeovim.neovim}/bin/nvim $out/bin/vim + ''; + in + { + environment.systemPackages = [ nvf-vim ]; + } + ); +} diff --git a/modules/profiles/workstation.nix b/modules/profiles/workstation.nix index 3591f529..41921f1c 100644 --- a/modules/profiles/workstation.nix +++ b/modules/profiles/workstation.nix @@ -11,6 +11,7 @@ let "fish" "fzf" "git" + "nvf" "starship" "tmux" "virtualization" From a7c1b814eb39b49189a65687540844607ad14708 Mon Sep 17 00:00:00 2001 From: reze-dev <25588579+reze-dev@users.noreply.github.com> Date: Sat, 19 Sep 2026 01:10:07 +0530 Subject: [PATCH 06/12] chore(flake.lock): bump flake.lock inputs --- flake.lock | 70 +++++++++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/flake.lock b/flake.lock index 1f786c6f..0771cf80 100644 --- a/flake.lock +++ b/flake.lock @@ -64,12 +64,12 @@ ] }, "locked": { - "lastModified": 1788427446, - "narHash": "sha256-3JntNU/vT11eljUs+NKvVea8u+TTA3d+pGBZOD9KZBI=", - "rev": "cb76ac22754f6b36c008a3c39477c174a146dd6b", - "revCount": 436, + "lastModified": 1789681713, + "narHash": "sha256-6KPsQyUeKKlTJmI4VXijP+B4+zi5FCEXOscFxMm8zHQ=", + "rev": "35e914102d4992b10bac4c12e94ec606d109633d", + "revCount": 438, "type": "tarball", - "url": "https://api.flakehub.com/f/pinned/DeterminateSystems/determinate/3.22.3/01a06698-e4aa-7ed0-adb3-2be018fc0c1f/source.tar.gz" + "url": "https://api.flakehub.com/f/pinned/DeterminateSystems/determinate/3.22.5/01a0b15b-1d43-7be1-beb0-8aa22a4d3d93/source.tar.gz" }, "original": { "type": "tarball", @@ -79,37 +79,37 @@ "determinate-nixd-aarch64-darwin": { "flake": false, "locked": { - "narHash": "sha256-hG3WJH3ox9F4jzQX6Tm4kkuu5Jld5ahSHWWLkLvqptg=", + "narHash": "sha256-QuBUghdOXgnIXX25fUNvBnpjeSgIebz7uCHK6E5K1qc=", "type": "file", - "url": "https://install.determinate.systems/determinate-nixd/tag/v3.22.3/macOS" + "url": "https://install.determinate.systems/determinate-nixd/tag/v3.22.5/macOS" }, "original": { "type": "file", - "url": "https://install.determinate.systems/determinate-nixd/tag/v3.22.3/macOS" + "url": "https://install.determinate.systems/determinate-nixd/tag/v3.22.5/macOS" } }, "determinate-nixd-aarch64-linux": { "flake": false, "locked": { - "narHash": "sha256-JD6kygFDs/JIlFOe45IgeI5r/UjQOE0QyX90EB0s5mg=", + "narHash": "sha256-XT6fkS0rMzQErYmxw0kMb4bQCgLat0v0OgcMidj0Srw=", "type": "file", - "url": "https://install.determinate.systems/determinate-nixd/tag/v3.22.3/aarch64-linux" + "url": "https://install.determinate.systems/determinate-nixd/tag/v3.22.5/aarch64-linux" }, "original": { "type": "file", - "url": "https://install.determinate.systems/determinate-nixd/tag/v3.22.3/aarch64-linux" + "url": "https://install.determinate.systems/determinate-nixd/tag/v3.22.5/aarch64-linux" } }, "determinate-nixd-x86_64-linux": { "flake": false, "locked": { - "narHash": "sha256-njskQtmrE+ynHHz1TOP1Ws0tgL9XJgDdQIM1KA/kW7E=", + "narHash": "sha256-JlMPuS0nwupTaJ4fvr14R519hUBupHteDNshGhCdMM8=", "type": "file", - "url": "https://install.determinate.systems/determinate-nixd/tag/v3.22.3/x86_64-linux" + "url": "https://install.determinate.systems/determinate-nixd/tag/v3.22.5/x86_64-linux" }, "original": { "type": "file", - "url": "https://install.determinate.systems/determinate-nixd/tag/v3.22.3/x86_64-linux" + "url": "https://install.determinate.systems/determinate-nixd/tag/v3.22.5/x86_64-linux" } }, "disko": { @@ -250,11 +250,11 @@ ] }, "locked": { - "lastModified": 1789482455, - "narHash": "sha256-pD3qVlQ4mUCFoTWrYtxecpSitQMsgJvJIN0FUIDZoEU=", + "lastModified": 1789695075, + "narHash": "sha256-HREFWgmCTp28ZdMkSFOW2QW8uI17v5CNc04W46h6aPo=", "owner": "nix-community", "repo": "home-manager", - "rev": "efa3ccb4c3cc90d832eab232976379058fa75aa3", + "rev": "4ac5a2ae9025eab1ebece4f9df8c315fd84a738a", "type": "github" }, "original": { @@ -339,11 +339,11 @@ "xdph": "xdph" }, "locked": { - "lastModified": 1789498188, - "narHash": "sha256-H2MA6B3g0HAP+2aED9OQuepoFbubVadV0S7Mp+W5HZo=", + "lastModified": 1789653023, + "narHash": "sha256-kop6M/IMYCCdvOQTWrnYEFViP8jeLVjZRWHCkC2nuwY=", "ref": "refs/heads/main", - "rev": "92b82c0c1e4168d93903ec42a2276843bbd84821", - "revCount": 7818, + "rev": "d50ca8950ac8753c54e50b6d44f4461df14bfabb", + "revCount": 7819, "submodules": true, "type": "git", "url": "https://github.com/hyprwm/Hyprland" @@ -669,12 +669,12 @@ "nixpkgs-regression": "nixpkgs-regression" }, "locked": { - "lastModified": 1788380291, - "narHash": "sha256-bv8yt03cVAPdAc6/9nntqW2/QJeFsvuuYK0bbT99MDA=", - "rev": "3ed5caa3bbde51ab87977c8a80e27c6ed0ea8534", - "revCount": 27315, + "lastModified": 1789679063, + "narHash": "sha256-5rX9rK1y8NpOaYyL+cn1P2O5re+ON2Wpo39J8/LLCXI=", + "rev": "6468ca430b298865100b753ec21687aba457a05c", + "revCount": 27345, "type": "tarball", - "url": "https://api.flakehub.com/f/pinned/DeterminateSystems/nix-src/3.22.3/01a063f1-9def-7051-9003-4691ca024a72/source.tar.gz" + "url": "https://api.flakehub.com/f/pinned/DeterminateSystems/nix-src/3.22.5/01a0b14c-a282-7291-953e-6e8ddd04304b/source.tar.gz" }, "original": { "type": "tarball", @@ -765,11 +765,11 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1789426278, - "narHash": "sha256-GnotcvKtbTMnoVx6G4E7ZdUX6487Dky74dqq3pU7iRk=", + "lastModified": 1789684949, + "narHash": "sha256-ZKhUe/2IJUq1JhKxKMu8rbkgSGmPP2ZCqlIPn40aGCM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "efe6f071ede9d21c37462d2d6682d5e670099684", + "rev": "e554fab72f81915600f3f449b786fd9af40439a5", "type": "github" }, "original": { @@ -847,11 +847,11 @@ ] }, "locked": { - "lastModified": 1788914643, - "narHash": "sha256-4GuMPW90JSxXWDPUB9M+1m7fYbe3H0apOd86/zBQ2Kw=", + "lastModified": 1789691124, + "narHash": "sha256-k+I+R6uwHX3VcJ7326qLV6vCahZUgsVl+i8sSU/Stxk=", "owner": "Mic92", "repo": "sops-nix", - "rev": "13616fff713a9f94055c66f15687ebdc17a335df", + "rev": "1e73e8f7176d65e1b55e324de099bbfff4b2c574", "type": "github" }, "original": { @@ -979,11 +979,11 @@ ] }, "locked": { - "lastModified": 1789533592, - "narHash": "sha256-8oEUwQ3QKYIMPDPDS8b/IiQg4jR6Fbp/7l/IFopRYc0=", + "lastModified": 1789702344, + "narHash": "sha256-DfevaOxGu6NKUF+q5hUAg7kFscWQvuC9Yd/CgR4qpgc=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "5bc359ec4e967a852cb3723dd2f56eb2f4414bc2", + "rev": "ef5fe624be04120db77c08c5df6f75a6a4201517", "type": "github" }, "original": { From 41dd646e735bd4e155a6f7a9a8154f8f1fbe5da4 Mon Sep 17 00:00:00 2001 From: reze-dev <25588579+reze-dev@users.noreply.github.com> Date: Sat, 19 Sep 2026 02:58:13 +0530 Subject: [PATCH 07/12] refactor(nvf): modularize monolithic lua config into native nix modules --- nvf/config/core/autocmds.nix | 209 +++ nvf/config/core/globals.nix | 10 + nvf/config/core/keymaps.nix | 260 +++ nvf/config/{ => core}/options.nix | 30 + nvf/config/editor/comments.nix | 153 ++ nvf/config/editor/completion.nix | 86 + nvf/config/editor/editing.nix | 121 ++ nvf/config/editor/format.nix | 98 ++ nvf/config/editor/lint.nix | 76 + nvf/config/editor/whichkey.nix | 64 + nvf/config/languages.nix | 36 - nvf/config/languages/languages.nix | 258 +++ nvf/config/languages/lsp.nix | 152 ++ nvf/config/languages/treesitter.nix | 134 ++ nvf/config/lua_config.nix | 2506 --------------------------- nvf/config/plugins.nix | 211 --- nvf/config/theme.nix | 31 - nvf/config/tools/debugger.nix | 391 +++++ nvf/config/tools/git.nix | 108 ++ nvf/config/tools/navigation.nix | 358 ++++ nvf/config/tools/telescope.nix | 219 +++ nvf/config/tools/testing.nix | 105 ++ nvf/config/ui/noice.nix | 99 ++ nvf/config/ui/statusline.nix | 295 ++++ nvf/config/ui/tabline.nix | 114 ++ nvf/config/ui/theme.nix | 156 ++ nvf/config/ui/visuals.nix | 251 +++ nvf/nvf.nix | 37 +- 28 files changed, 3779 insertions(+), 2789 deletions(-) create mode 100644 nvf/config/core/autocmds.nix create mode 100644 nvf/config/core/globals.nix create mode 100644 nvf/config/core/keymaps.nix rename nvf/config/{ => core}/options.nix (53%) create mode 100644 nvf/config/editor/comments.nix create mode 100644 nvf/config/editor/completion.nix create mode 100644 nvf/config/editor/editing.nix create mode 100644 nvf/config/editor/format.nix create mode 100644 nvf/config/editor/lint.nix create mode 100644 nvf/config/editor/whichkey.nix delete mode 100644 nvf/config/languages.nix create mode 100644 nvf/config/languages/languages.nix create mode 100644 nvf/config/languages/lsp.nix create mode 100644 nvf/config/languages/treesitter.nix delete mode 100644 nvf/config/lua_config.nix delete mode 100644 nvf/config/plugins.nix delete mode 100644 nvf/config/theme.nix create mode 100644 nvf/config/tools/debugger.nix create mode 100644 nvf/config/tools/git.nix create mode 100644 nvf/config/tools/navigation.nix create mode 100644 nvf/config/tools/telescope.nix create mode 100644 nvf/config/tools/testing.nix create mode 100644 nvf/config/ui/noice.nix create mode 100644 nvf/config/ui/statusline.nix create mode 100644 nvf/config/ui/tabline.nix create mode 100644 nvf/config/ui/theme.nix create mode 100644 nvf/config/ui/visuals.nix diff --git a/nvf/config/core/autocmds.nix b/nvf/config/core/autocmds.nix new file mode 100644 index 00000000..e6cb6370 --- /dev/null +++ b/nvf/config/core/autocmds.nix @@ -0,0 +1,209 @@ +{ lib, ... }: { + config.vim = { + augroups = [ + { + name = "YankHighlight"; + clear = true; + } + { + name = "ResizeSplits"; + clear = true; + } + { + name = "LastPosition"; + clear = true; + } + { + name = "CloseWithQ"; + clear = true; + } + { + name = "AutoCreateDir"; + clear = true; + } + { + name = "TrimWhitespace"; + clear = true; + } + { + name = "Checktime"; + clear = true; + } + { + name = "WrapSpell"; + clear = true; + } + { + name = "JsonConceal"; + clear = true; + } + ]; + + autocmds = [ + # Highlight on yank + { + event = [ "TextYankPost" ]; + group = "YankHighlight"; + callback = lib.mkLuaInline '' + function() + vim.hl.on_yank({ higroup = "IncSearch", timeout = 200 }) + end + ''; + desc = "Highlight on yank"; + } + + # Resize splits when window is resized + { + event = [ "VimResized" ]; + group = "ResizeSplits"; + callback = lib.mkLuaInline '' + function() + local current_tab = vim.fn.tabpagenr() + vim.cmd("tabdo wincmd =") + vim.cmd("tabnext " .. current_tab) + end + ''; + desc = "Resize splits on window resize"; + } + + # Go to last cursor position when opening a file + { + event = [ "BufReadPost" ]; + group = "LastPosition"; + callback = lib.mkLuaInline '' + 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 + ''; + desc = "Go to last cursor position"; + } + + # Close certain filetypes with q + { + event = [ "FileType" ]; + group = "CloseWithQ"; + pattern = [ + "PlenaryTestPopup" + "checkhealth" + "dbout" + "gitsigns.blame" + "help" + "lspinfo" + "neotest-output" + "neotest-output-panel" + "neotest-summary" + "notify" + "qf" + "spectre_panel" + "startuptime" + "tsplayground" + ]; + callback = lib.mkLuaInline '' + function(event) + vim.bo[event.buf].buflisted = false + vim.keymap.set("n", "q", "close", { buffer = event.buf, silent = true }) + end + ''; + desc = "Close window with q for specific filetypes"; + } + + # Auto create directories when saving a file + { + event = [ "BufWritePre" ]; + group = "AutoCreateDir"; + callback = lib.mkLuaInline '' + function(event) + if event.match:match("^%w%w+:[\\/][\\/]") then + return + end + local file = vim.uv.fs_realpath(event.match) or event.match + vim.fn.mkdir(vim.fn.fnamemodify(file, ":p:h"), "p") + end + ''; + desc = "Auto create directory when saving a file"; + } + + # Remove trailing whitespace on save + { + event = [ "BufWritePre" ]; + group = "TrimWhitespace"; + pattern = [ "*" ]; + callback = lib.mkLuaInline '' + function() + local save_cursor = vim.fn.getpos(".") + pcall(function() + vim.cmd([[%s/\s\+$//e]]) + end) + vim.fn.setpos(".", save_cursor) + end + ''; + desc = "Remove trailing whitespace on save"; + } + + # Check if file changed when window is focused + { + event = [ + "FocusGained" + "TermClose" + "TermLeave" + ]; + group = "Checktime"; + callback = lib.mkLuaInline '' + function() + if vim.o.buftype ~= "nofile" then + vim.cmd("checktime") + end + end + ''; + desc = "Checktime on focus gained"; + } + + # Wrap and spell check in text filetypes + { + event = [ "FileType" ]; + group = "WrapSpell"; + pattern = [ + "text" + "plaintex" + "typst" + "gitcommit" + "markdown" + ]; + callback = lib.mkLuaInline '' + function() + vim.opt_local.wrap = true + vim.opt_local.spell = true + end + ''; + desc = "Enable wrap and spell for text filetypes"; + } + + # Fix conceallevel for json files + { + event = [ "FileType" ]; + group = "JsonConceal"; + pattern = [ + "json" + "jsonc" + "json5" + ]; + callback = lib.mkLuaInline '' + function() + vim.opt_local.conceallevel = 0 + end + ''; + desc = "Disable conceal for json files"; + } + ]; + }; +} diff --git a/nvf/config/core/globals.nix b/nvf/config/core/globals.nix new file mode 100644 index 00000000..1f02576f --- /dev/null +++ b/nvf/config/core/globals.nix @@ -0,0 +1,10 @@ +{ ... }: { + config.vim.globals = { + mapleader = " "; + maplocalleader = " "; + loaded_python3_provider = 0; + loaded_ruby_provider = 0; + loaded_perl_provider = 0; + loaded_node_provider = 0; + }; +} diff --git a/nvf/config/core/keymaps.nix b/nvf/config/core/keymaps.nix new file mode 100644 index 00000000..c902bd84 --- /dev/null +++ b/nvf/config/core/keymaps.nix @@ -0,0 +1,260 @@ +{ ... }: { + config.vim.keymaps = [ + # Better escape + { + key = "jk"; + mode = "i"; + action = ""; + desc = "Exit insert mode"; + } + + # Save & quit + { + key = "w"; + mode = "n"; + action = "w"; + desc = "Save file"; + } + { + key = "q"; + mode = "n"; + action = "q"; + desc = "Quit"; + } + { + key = "Q"; + mode = "n"; + action = "qa!"; + desc = "Force quit all"; + } + + # Clear search highlights + { + key = "nh"; + mode = "n"; + action = "nohlsearch"; + desc = "Clear search highlights"; + } + + # Better window navigation (overridden by tmux-navigator when in tmux) + { + key = ""; + mode = "n"; + action = "h"; + desc = "Move to left window"; + } + { + key = ""; + mode = "n"; + action = "j"; + desc = "Move to lower window"; + } + { + key = ""; + mode = "n"; + action = "k"; + desc = "Move to upper window"; + } + { + key = ""; + mode = "n"; + action = "l"; + desc = "Move to right window"; + } + + # Resize windows with arrows + { + key = ""; + mode = "n"; + action = "resize +2"; + desc = "Increase window height"; + } + { + key = ""; + mode = "n"; + action = "resize -2"; + desc = "Decrease window height"; + } + { + key = ""; + mode = "n"; + action = "vertical resize -2"; + desc = "Decrease window width"; + } + { + key = ""; + mode = "n"; + action = "vertical resize +2"; + desc = "Increase window width"; + } + + # Buffer navigation + { + key = ""; + mode = "n"; + action = "bprevious"; + desc = "Previous buffer"; + } + { + key = ""; + mode = "n"; + action = "bnext"; + desc = "Next buffer"; + } + { + key = "bd"; + mode = "n"; + action = "bdelete"; + desc = "Close buffer"; + } + { + key = "bD"; + mode = "n"; + action = "bdelete!"; + desc = "Force close buffer"; + } + + # Move lines up/down in visual mode + { + key = "J"; + mode = "v"; + action = ":m '>+1gv=gv"; + desc = "Move selection down"; + } + { + key = "K"; + mode = "v"; + action = ":m '<-2gv=gv"; + desc = "Move selection up"; + } + + # Stay in visual mode when indenting + { + key = "<"; + mode = "v"; + action = "''; + desc = "Exit terminal mode"; + } + + # Quickfix navigation + { + key = "]q"; + mode = "n"; + action = "cnextzz"; + desc = "Next quickfix item"; + } + { + key = "[q"; + mode = "n"; + action = "cprevzz"; + desc = "Previous quickfix item"; + } + + # Select all + { + key = ""; + mode = "n"; + action = "ggG"; + desc = "Select all"; + } + + # Package manager / configuration info + { + key = "L"; + mode = "n"; + action = "function() vim.notify('Configured declaratively via Nix / nvf flake', vim.log.levels.INFO, { title = 'Package Manager' }) end"; + lua = true; + desc = "Open Lazy plugin manager / Nix info"; + } + ]; +} diff --git a/nvf/config/options.nix b/nvf/config/core/options.nix similarity index 53% rename from nvf/config/options.nix rename to nvf/config/core/options.nix index bf97e207..89b73af7 100644 --- a/nvf/config/options.nix +++ b/nvf/config/core/options.nix @@ -1,5 +1,14 @@ { pkgs, ... }: { config.vim = { + # System Environment & PATH fallback + luaConfigRC.env = '' + local home = vim.env.HOME or vim.fn.expand("~") + local mason_bin = vim.fn.stdpath("data") .. "/mason/bin" + local go_bin = home .. "/go/bin" + local cargo_bin = home .. "/.cargo/bin" + vim.env.PATH = mason_bin .. ":" .. go_bin .. ":" .. cargo_bin .. ":" .. (vim.env.PATH or "") + ''; + options = { number = true; relativenumber = true; @@ -46,6 +55,27 @@ foldlevel = 99; foldlevelstart = 99; foldenable = true; + + # Migrated from lua_config.nix + inccommand = "split"; + jumpoptions = "view"; + virtualedit = "block"; + smoothscroll = true; + completeopt = [ + "menu" + "menuone" + "noselect" + ]; }; + + luaConfigRC.core_opt_extras = '' + vim.opt.listchars = { tab = "» ", trail = "·", nbsp = "␣" } + vim.opt.fillchars = { eob = " ", fold = " ", foldopen = "v", foldsep = " ", foldclose = ">" } + vim.opt.shortmess:append("sI") + + local undo_dir = vim.fn.stdpath("data") .. "/undo" + vim.opt.undodir = undo_dir + pcall(vim.fn.mkdir, undo_dir, "p") + ''; }; } diff --git a/nvf/config/editor/comments.nix b/nvf/config/editor/comments.nix new file mode 100644 index 00000000..da820bbd --- /dev/null +++ b/nvf/config/editor/comments.nix @@ -0,0 +1,153 @@ +{ pkgs, lib, ... }: { + config.vim = { + comments.comment-nvim = { + enable = true; + setupOpts = { + padding = true; + sticky = true; + ignore = "^$"; + toggler = { + line = "gcc"; + block = "gbc"; + }; + opleader = { + line = "gc"; + block = "gb"; + }; + extra = { + above = "gcO"; + below = "gco"; + eol = "gcA"; + }; + mappings = { + basic = true; + extra = true; + }; + pre_hook = lib.mkLuaInline '' + function() + local ok_hook, ts_hook = pcall(require, "ts_context_commentstring.integrations.comment_nvim") + return ok_hook and ts_hook.create_pre_hook() or nil + end + ''; + }; + }; + + extraPlugins = with pkgs.vimPlugins; { + nvim-ts-context-commentstring = { + package = nvim-ts-context-commentstring; + setup = "require('ts_context_commentstring').setup({ enable_autocmd = false })"; + }; + }; + + notes.todo-comments = { + enable = true; + setupOpts = { + signs = true; + sign_priority = 8; + keywords = { + FIX = { + icon = " "; + color = "error"; + alt = [ + "FIXME" + "BUG" + "FIXIT" + "ISSUE" + ]; + }; + TODO = { + icon = " "; + color = "info"; + }; + HACK = { + icon = " "; + color = "warning"; + }; + WARN = { + icon = " "; + color = "warning"; + alt = [ + "WARNING" + "XXX" + ]; + }; + PERF = { + icon = "󰅒 "; + alt = [ + "OPTIM" + "PERFORMANCE" + "OPTIMIZE" + ]; + }; + NOTE = { + icon = "󰍨 "; + color = "hint"; + alt = [ "INFO" ]; + }; + TEST = { + icon = "⏲ "; + color = "test"; + alt = [ + "TESTING" + "PASSED" + "FAILED" + ]; + }; + }; + merge_keywords = true; + highlight = { + multiline = true; + multiline_pattern = "^."; + multiline_context = 10; + before = ""; + keyword = "wide"; + after = "fg"; + pattern = ''.*<(KEYWORDS)\s*:''; + comments_only = true; + max_line_len = 400; + exclude = [ ]; + }; + search = { + command = "rg"; + args = [ + "--color=never" + "--no-heading" + "--with-filename" + "--line-number" + "--column" + ]; + pattern = ''\b(KEYWORDS):''; + }; + }; + }; + + keymaps = [ + { + key = "]t"; + mode = "n"; + action = "function() require('todo-comments').jump_next() end"; + lua = true; + desc = "Next TODO"; + } + { + key = "[t"; + mode = "n"; + action = "function() require('todo-comments').jump_prev() end"; + lua = true; + desc = "Previous TODO"; + } + { + key = "ft"; + mode = "n"; + action = "TodoTelescope"; + desc = "Find TODOs"; + } + { + key = "fT"; + mode = "n"; + action = "TodoTelescope keywords=TODO,FIX,FIXME"; + desc = "Find TODO/FIX/FIXME"; + } + ]; + }; +} diff --git a/nvf/config/editor/completion.nix b/nvf/config/editor/completion.nix new file mode 100644 index 00000000..d809af8c --- /dev/null +++ b/nvf/config/editor/completion.nix @@ -0,0 +1,86 @@ +{ ... }: { + config.vim = { + autocomplete = { + nvim-cmp.enable = false; + blink-cmp = { + enable = true; + friendly-snippets.enable = true; + setupOpts = { + keymap = { + preset = "default"; + "" = [ + "show" + "show_documentation" + "hide_documentation" + ]; + "" = [ "hide" ]; + "" = [ + "accept" + "fallback" + ]; + "" = [ + "select_next" + "snippet_forward" + "fallback" + ]; + "" = [ + "select_prev" + "snippet_backward" + "fallback" + ]; + "" = [ + "select_next" + "fallback" + ]; + "" = [ + "select_prev" + "fallback" + ]; + "" = [ + "scroll_documentation_up" + "fallback" + ]; + "" = [ + "scroll_documentation_down" + "fallback" + ]; + }; + appearance = { + nerd_font_variant = "mono"; + }; + completion = { + accept = { + auto_brackets = { + enabled = true; + }; + }; + menu = { + border = "rounded"; + winhighlight = "Normal:NormalFloat,FloatBorder:FloatBorder,CursorLine:PmenuSel,Search:None"; + }; + documentation = { + auto_show = true; + auto_show_delay_ms = 200; + window = { + border = "rounded"; + winhighlight = "Normal:NormalFloat,FloatBorder:FloatBorder"; + }; + }; + ghost_text = { + enabled = true; + }; + }; + signature = { + enabled = true; + window = { + border = "rounded"; + }; + }; + fuzzy = { + implementation = "prefer_rust"; + }; + }; + }; + }; + }; +} diff --git a/nvf/config/editor/editing.nix b/nvf/config/editor/editing.nix new file mode 100644 index 00000000..cbae6213 --- /dev/null +++ b/nvf/config/editor/editing.nix @@ -0,0 +1,121 @@ +{ pkgs, lib, ... }: { + config.vim = { + autopairs.nvim-autopairs = { + enable = true; + setupOpts = { + check_ts = true; + ts_config = { + lua = [ + "string" + "source" + ]; + javascript = [ + "string" + "template_string" + ]; + java = false; + }; + disable_filetype = [ + "TelescopePrompt" + "spectre_panel" + ]; + fast_wrap = { + map = ""; + chars = [ + "{" + "[" + "(" + "\"" + "'" + ]; + pattern = ''[%'%"%>%]%)%}%,]''; + end_key = "$"; + before_key = "h"; + after_key = "l"; + cursor_pos_before = true; + keys = "qwertyuiopzxcvbnmasdfghjkl"; + manual_position = true; + highlight = "Search"; + highlight_grey = "Comment"; + }; + }; + }; + + utility.surround = { + enable = true; + setupOpts = { }; + }; + + mini.ai = { + enable = true; + setupOpts = { + n_lines = 500; + custom_textobjects = lib.mkLuaInline '' + (function() + local ai = require("mini.ai") + return { + o = ai.gen_spec.treesitter({ + a = { "@block.outer", "@conditional.outer", "@loop.outer" }, + i = { "@block.inner", "@conditional.inner", "@loop.inner" }, + }, {}), + f = ai.gen_spec.treesitter({ a = "@function.outer", i = "@function.inner" }, {}), + c = ai.gen_spec.treesitter({ a = "@class.outer", i = "@class.inner" }, {}), + } + end)() + ''; + }; + }; + + extraPlugins = with pkgs.vimPlugins; { + better-escape-nvim = { + package = better-escape-nvim; + setup = '' + require("better_escape").setup({ + timeout = 200, + default_mappings = false, + mappings = { + i = { + j = { k = "" }, + }, + c = { + j = { k = "" }, + }, + }, + }) + ''; + }; + persistence-nvim = { + package = persistence-nvim; + setup = '' + require("persistence").setup({ + options = { "buffers", "curdir", "tabpages", "winsize", "help", "globals", "skiprtp" }, + }) + ''; + }; + }; + + keymaps = [ + { + key = "qs"; + mode = "n"; + action = "function() require('persistence').load() end"; + lua = true; + desc = "Restore session"; + } + { + key = "ql"; + mode = "n"; + action = "function() require('persistence').load({ last = true }) end"; + lua = true; + desc = "Restore last session"; + } + { + key = "qd"; + mode = "n"; + action = "function() require('persistence').stop() end"; + lua = true; + desc = "Don't save current session"; + } + ]; + }; +} diff --git a/nvf/config/editor/format.nix b/nvf/config/editor/format.nix new file mode 100644 index 00000000..d89a0f73 --- /dev/null +++ b/nvf/config/editor/format.nix @@ -0,0 +1,98 @@ +{ lib, ... }: { + config.vim = { + formatter.conform-nvim = { + enable = true; + setupOpts = { + formatters_by_ft = { + c = [ "clang-format" ]; + cpp = [ "clang-format" ]; + rust = [ "rustfmt" ]; + go = [ + "goimports" + "gofumpt" + ]; + lua = [ "stylua" ]; + python = [ + "ruff_format" + "ruff_organize_imports" + ]; + toml = [ "taplo" ]; + javascript = [ "prettier" ]; + typescript = [ "prettier" ]; + javascriptreact = [ "prettier" ]; + typescriptreact = [ "prettier" ]; + html = [ "prettier" ]; + css = [ "prettier" ]; + scss = [ "prettier" ]; + json = [ "prettier" ]; + jsonc = [ "prettier" ]; + yaml = [ "prettier" ]; + markdown = [ "prettier" ]; + graphql = [ "prettier" ]; + nix = [ "nixfmt" ]; + fish = [ "fish_indent" ]; + sh = [ "shfmt" ]; + bash = [ "shfmt" ]; + "_" = [ "trim_whitespace" ]; + }; + format_on_save = lib.mkLuaInline '' + function(bufnr) + if vim.b[bufnr].disable_autoformat or vim.g.disable_autoformat then + return + end + return { + timeout_ms = 500, + lsp_format = "fallback", + } + end + ''; + formatters = { + shfmt = { + prepend_args = [ + "-i" + "2" + ]; + }; + stylua = { + prepend_args = [ + "--indent-type" + "Spaces" + "--indent-width" + "2" + ]; + }; + clang-format = { + prepend_args = [ "-fallback-style=LLVM" ]; + }; + }; + }; + }; + + keymaps = [ + { + key = "cf"; + mode = [ + "n" + "v" + ]; + action = "function() require('conform').format({ async = false, timeout_ms = 3000, lsp_format = 'fallback' }) end"; + lua = true; + desc = "Format buffer/selection"; + } + ]; + + luaConfigRC.conform_commands = '' + vim.api.nvim_create_user_command("FormatToggle", function() + vim.g.disable_autoformat = not vim.g.disable_autoformat + local state = vim.g.disable_autoformat and "disabled" or "enabled" + vim.notify("Autoformat " .. state, vim.log.levels.INFO) + end, { desc = "Toggle autoformat on save" }) + + vim.api.nvim_create_user_command("FormatToggleBuf", function() + vim.b.disable_autoformat = not vim.b.disable_autoformat + local state = vim.b.disable_autoformat and "disabled" or "enabled" + vim.notify("Autoformat (buffer) " .. state, vim.log.levels.INFO) + end, { desc = "Toggle autoformat on save (buffer)" }) + ''; + }; +} diff --git a/nvf/config/editor/lint.nix b/nvf/config/editor/lint.nix new file mode 100644 index 00000000..62a63a78 --- /dev/null +++ b/nvf/config/editor/lint.nix @@ -0,0 +1,76 @@ +{ pkgs, ... }: { + config.vim = { + diagnostics.nvim-lint = { + enable = true; + linters_by_ft = { + c = [ "clang-tidy" ]; + cpp = [ "clang-tidy" ]; + go = [ "golangci-lint" ]; + python = [ "ruff" ]; + }; + }; + + luaConfigRC.lint_setup = '' + local ok, lint = pcall(require, "lint") + if ok then + lint.linters["clang-tidy"] = lint.linters.clangtidy + lint.linters["golangci-lint"] = lint.linters.golangcilint + + lint.linters_by_ft = { + c = { "clang-tidy" }, + cpp = { "clang-tidy" }, + go = { "golangci-lint" }, + python = { "ruff" }, + } + + local lint_augroup = vim.api.nvim_create_augroup("NvimLintUser", { clear = true }) + + local function run_lint() + local bufnr = vim.api.nvim_get_current_buf() + if not vim.api.nvim_buf_is_valid(bufnr) then + return + end + local ft = vim.bo[bufnr].filetype + if not ft or ft == "" then + return + end + local linters = lint.linters_by_ft[ft] or {} + if type(linters) ~= "table" or #linters == 0 then + return + end + + local valid_linters = {} + for _, name in ipairs(linters) do + local linter = lint.linters[name] + if linter then + local cmd = type(linter) == "table" and linter.cmd or name + if type(cmd) == "function" then + cmd = cmd() + end + if type(cmd) == "string" and vim.fn.executable(cmd) == 1 then + table.insert(valid_linters, name) + end + end + end + + if #valid_linters > 0 then + lint.try_lint(valid_linters) + end + end + + vim.api.nvim_create_autocmd({ "BufWritePost", "BufReadPost", "InsertLeave" }, { + group = lint_augroup, + callback = run_lint, + }) + + vim.api.nvim_create_user_command("Lint", function() + run_lint() + end, { desc = "Trigger linting for current buffer" }) + + vim.keymap.set("n", "cl", function() + run_lint() + end, { desc = "Lint current buffer" }) + end + ''; + }; +} diff --git a/nvf/config/editor/whichkey.nix b/nvf/config/editor/whichkey.nix new file mode 100644 index 00000000..4d7c3fba --- /dev/null +++ b/nvf/config/editor/whichkey.nix @@ -0,0 +1,64 @@ +{ ... }: { + config.vim = { + binds.whichKey = { + enable = true; + setupOpts = { + preset = "modern"; + delay = 300; + win = { + border = "rounded"; + padding = [ + 1 + 2 + ]; + title = true; + title_pos = "center"; + zindex = 1000; + }; + layout = { + width = { + min = 20; + }; + spacing = 3; + }; + keys = { + scroll_down = ""; + scroll_up = ""; + }; + show_help = true; + show_keys = true; + }; + }; + + luaConfigRC.whichkey_spec = '' + local ok_wk, wk = pcall(require, "which-key") + if ok_wk then + wk.add({ + { "f", group = "Find", icon = " " }, + { "e", group = "Explorer", icon = "󰙅 " }, + { "a", group = "Harpoon", icon = "󰛢 " }, + { "b", group = "Buffer", icon = "󰓩 " }, + { "l", group = "LSP", icon = " " }, + { "c", group = "Code", icon = "󰅩 " }, + { "C", group = "CMake/Build", icon = " " }, + { "r", group = "Rust", icon = "󱘗 " }, + { "G", group = "Go", icon = " " }, + { "t", group = "Test", icon = "󰙨 " }, + { "d", group = "Debug", icon = " " }, + { "u", group = "UI/Toggles", icon = "󱄄 " }, + { "g", group = "Git", icon = "󰊢 " }, + { "s", group = "Swap/Search", icon = "󰔡 " }, + { "x", group = "Trouble/Diagnostics", icon = "󰅙 " }, + { "n", group = "Noice", icon = "󰎟 " }, + { "w", icon = "󰆓 " }, + { "q", icon = "󰩈 " }, + { "L", icon = "󰒲 " }, + }) + + vim.keymap.set("n", "?", function() + wk.show({ global = false }) + end, { desc = "Buffer-local keymaps (which-key)" }) + end + ''; + }; +} diff --git a/nvf/config/languages.nix b/nvf/config/languages.nix deleted file mode 100644 index 457dfb54..00000000 --- a/nvf/config/languages.nix +++ /dev/null @@ -1,36 +0,0 @@ -{ pkgs, ... }: { - config.vim = { - lsp.enable = true; - treesitter.enable = true; - languages = { - enableFormat = true; - enableTreesitter = true; - enableDAP = true; - clang.enable = true; - rust = { - enable = true; - lsp.enable = false; - dap.enable = false; - extensions = { - rustaceanvim.enable = true; - crates-nvim.enable = true; - }; - }; - go.enable = true; - python.enable = true; - lua.enable = true; - html.enable = true; - css.enable = true; - typescript.enable = true; - markdown.enable = true; - nix.enable = true; - bash.enable = true; - json.enable = true; - yaml.enable = true; - toml.enable = true; - docker.enable = true; - cmake.enable = true; - sql.enable = true; - }; - }; -} diff --git a/nvf/config/languages/languages.nix b/nvf/config/languages/languages.nix new file mode 100644 index 00000000..57ffa5d3 --- /dev/null +++ b/nvf/config/languages/languages.nix @@ -0,0 +1,258 @@ +{ pkgs, ... }: { + config.vim = { + languages = { + enableFormat = true; + enableTreesitter = true; + enableDAP = true; + clang.enable = true; + rust = { + enable = true; + lsp.enable = false; + dap.enable = false; + extensions = { + rustaceanvim.enable = true; + crates-nvim.enable = true; + }; + }; + go.enable = true; + python.enable = true; + lua.enable = true; + html.enable = true; + css.enable = true; + typescript.enable = true; + markdown.enable = true; + nix.enable = true; + bash.enable = true; + json.enable = true; + yaml.enable = true; + toml.enable = true; + docker.enable = true; + cmake.enable = true; + sql.enable = true; + }; + + extraPlugins = with pkgs.vimPlugins; { + clangd_extensions-nvim = { + package = clangd_extensions-nvim; + }; + go-nvim = { + package = go-nvim; + }; + guihua-lua = { + package = guihua-lua; + }; + }; + + luaConfigRC.language_tools = '' + -- Clangd Extensions + local ok_clangd, clangd_ext = pcall(require, "clangd_extensions") + if ok_clangd then + clangd_ext.setup({ + inlay_hints = { + inline = false, + only_current_line = false, + only_current_line_autocmd = { "CursorHold" }, + show_parameter_hints = true, + parameter_hints_prefix = "<- ", + other_hints_prefix = "=> ", + max_len_align = false, + max_len_align_padding = 1, + right_align = false, + right_align_padding = 7, + highlight = "Comment", + priority = 100, + }, + ast = { + role_icons = { + type = "🄣", + declaration = "🄓", + expression = "🄔", + statement = ";", + specifier = "🄢", + ["template argument"] = "🆃", + }, + kind_icons = { + Compound = "🄲", + Recovery = "🅁", + TranslationUnit = "🅄", + PackExpansion = "🄿", + TemplateTypeParm = "🅃", + TemplateTemplateParm = "🅃", + TemplateParamObject = "🅃", + }, + highlights = { + detail = "Comment", + }, + }, + memory_usage = { + border = "rounded", + }, + symbol_info = { + border = "rounded", + }, + type_hierarchy = { + border = "rounded", + }, + }) + + vim.api.nvim_create_autocmd("FileType", { + group = vim.api.nvim_create_augroup("ClangdKeymaps", { clear = true }), + pattern = { "c", "cpp", "objc", "objcpp", "cuda" }, + callback = function(event) + local map = function(keys, func, desc) + vim.keymap.set("n", keys, func, { buffer = event.buf, desc = "Clangd: " .. desc }) + end + + map("ch", "ClangdSwitchSourceHeader", "Switch source/header") + map("ct", "ClangdTypeHierarchy", "Type hierarchy") + map("cm", "ClangdMemoryUsage", "Memory usage") + map("cs", "ClangdSymbolInfo", "Symbol info") + map("cA", "ClangdAST", "View AST") + end, + }) + end + + -- Go.nvim + local ok_go, gonvim = pcall(require, "go") + if ok_go then + gonvim.setup({ + lsp_cfg = false, + lsp_on_attach = false, + gofmt = "gofumpt", + goimports = "gopls", + linter = "golangci-lint", + test_runner = "go", + run_in_floaterm = true, + floaterm = { + position = "bottom", + width = 0.98, + height = 0.35, + }, + dap_debug = true, + dap_debug_gui = true, + tag_transform = "snakecase", + tag_options = "json=omitempty", + verbose = false, + log_path = vim.fn.stdpath("cache") .. "/gonvim.log", + icons = { breakpoint = "🔴", currentpos = "🏃" }, + }) + + vim.api.nvim_create_autocmd("FileType", { + pattern = { "go" }, + callback = function(event) + local map = function(keys, func, desc) + vim.keymap.set("n", keys, func, { buffer = event.buf, desc = "Go: " .. desc }) + end + + map("Gt", "GoTest", "Run tests") + map("Gf", "GoTestFunc", "Test function") + map("Gc", "GoCoverage", "Toggle coverage") + map("Ga", "GoAddTag", "Add struct tags") + map("Gr", "GoRmTag", "Remove struct tags") + map("Gi", "GoImpl", "Implement interface") + map("Ge", "GoIfErr", "Generate if err") + map("Gd", "GoDebug", "Start debugger") + map("GD", "GoDebug -s", "Stop debugger") + map("Gl", "GoLint", "Lint") + map("Gv", "GoVet", "Vet") + map("Gg", "GoGenerate", "Go generate") + map("Gm", "GoModTidy", "Go mod tidy") + end, + }) + end + + -- Rustaceanvim + local capabilities = vim.lsp.protocol.make_client_capabilities() + local ok_blink, blink = pcall(require, "blink.cmp") + if ok_blink then + capabilities = blink.get_lsp_capabilities(capabilities) + end + + vim.g.rustaceanvim = { + tools = { + hover_actions = { + replace_builtin_hover = false, + }, + float_win_config = { + border = "rounded", + }, + }, + server = { + capabilities = capabilities, + on_attach = function(client, bufnr) + local map = function(keys, func, desc) + vim.keymap.set("n", keys, func, { buffer = bufnr, desc = "Rust: " .. desc }) + end + + map("re", "RustLsp expandMacro", "Expand macro") + map("rc", "RustLsp openCargo", "Open Cargo.toml") + map("rp", "RustLsp parentModule", "Parent module") + map("rd", "RustLsp renderDiagnostic", "Render diagnostic") + map("rr", "RustLsp runnables", "Runnables") + map("rt", "RustLsp testables", "Testables") + map("rm", "RustLsp rebuildProcMacros", "Rebuild proc macros") + map("rk", "RustLsp moveItem up", "Move item up") + map("rj", "RustLsp moveItem down", "Move item down") + map("ra", "RustLsp codeAction", "Code action (rust)") + map("rh", "RustLsp hover actions", "Hover actions") + map("J", "RustLsp joinLines", "Join lines (Rust)") + map("rD", "RustLsp debuggables", "Debuggables") + end, + default_settings = { + ["rust-analyzer"] = { + cargo = { + allFeatures = true, + loadOutDirsFromCheck = true, + buildScripts = { enable = true }, + }, + checkOnSave = true, + check = { + command = "clippy", + extraArgs = { "--no-deps" }, + }, + procMacro = { + enable = true, + ignored = { + ["async-trait"] = { "async_trait" }, + ["napi-derive"] = { "napi" }, + ["async-recursion"] = { "async_recursion" }, + }, + }, + inlayHints = { + bindingModeHints = { enable = false }, + chainingHints = { enable = true }, + closingBraceHints = { enable = true, minLines = 25 }, + closureReturnTypeHints = { enable = "with_block" }, + lifetimeElisionHints = { enable = "never" }, + maxLength = 25, + parameterHints = { enable = true }, + reborrowHints = { enable = "never" }, + renderColons = true, + typeHints = { + enable = true, + hideClosureInitialization = false, + hideNamedConstructor = false, + }, + }, + }, + }, + }, + dap = { + adapter = function() + local ok_cfg, cfg = pcall(require, "rustaceanvim.config") + if not ok_cfg then return nil end + local mason_path = vim.fn.stdpath("data") .. "/mason/packages/codelldb" + local codelldb_path = mason_path .. "/extension/adapter/codelldb" + local liblldb_path = mason_path .. "/extension/lldb/lib/liblldb.so" + + if vim.fn.filereadable(codelldb_path) == 1 then + return cfg.get_codelldb_adapter(codelldb_path, liblldb_path) + else + return cfg.get_codelldb_adapter("codelldb", "") + end + end, + }, + } + ''; + }; +} diff --git a/nvf/config/languages/lsp.nix b/nvf/config/languages/lsp.nix new file mode 100644 index 00000000..bdba2030 --- /dev/null +++ b/nvf/config/languages/lsp.nix @@ -0,0 +1,152 @@ +{ ... }: { + config.vim = { + lsp = { + enable = true; + trouble = { + enable = true; + setupOpts = { + use_diagnostic_signs = true; + }; + }; + }; + + luaConfigRC.lsp_config = '' + vim.diagnostic.config({ + underline = true, + update_in_insert = false, + virtual_text = { + spacing = 4, + source = "if_many", + prefix = "●", + }, + severity_sort = true, + float = { + focusable = true, + style = "minimal", + border = "rounded", + source = true, + header = "", + prefix = "", + }, + signs = { + text = { + [vim.diagnostic.severity.ERROR] = " ", + [vim.diagnostic.severity.WARN] = " ", + [vim.diagnostic.severity.HINT] = "󰌵 ", + [vim.diagnostic.severity.INFO] = " ", + }, + }, + }) + + vim.api.nvim_create_autocmd("LspAttach", { + group = vim.api.nvim_create_augroup("UserLspConfig", { clear = true }), + callback = function(ev) + local bufnr = ev.buf + local client = vim.lsp.get_client_by_id(ev.data.client_id) + if not client then + return + end + + local map = function(keys, func, desc, mode) + mode = mode or "n" + vim.keymap.set(mode, keys, func, { buffer = bufnr, desc = "LSP: " .. desc }) + end + + map("gd", vim.lsp.buf.definition, "Go to definition") + map("gD", vim.lsp.buf.declaration, "Go to declaration") + map("gr", vim.lsp.buf.references, "Go to references") + map("gi", vim.lsp.buf.implementation, "Go to implementation") + map("D", vim.lsp.buf.type_definition, "Type definition") + map("li", vim.lsp.buf.incoming_calls, "Incoming calls") + map("lo", vim.lsp.buf.outgoing_calls, "Outgoing calls") + + map("K", vim.lsp.buf.hover, "Hover documentation") + map("", vim.lsp.buf.signature_help, "Signature help") + map("", vim.lsp.buf.signature_help, "Signature help", "i") + + map("ca", vim.lsp.buf.code_action, "Code action") + map("ca", vim.lsp.buf.code_action, "Code action", "v") + map("rn", vim.lsp.buf.rename, "Rename symbol") + + map("ld", vim.diagnostic.open_float, "Line diagnostics") + map("[d", function() vim.diagnostic.jump({ count = -1, float = true }) end, "Previous diagnostic") + map("]d", function() vim.diagnostic.jump({ count = 1, float = true }) end, "Next diagnostic") + map("[e", function() vim.diagnostic.jump({ count = -1, float = true, severity = vim.diagnostic.severity.ERROR }) end, "Previous error") + map("]e", function() vim.diagnostic.jump({ count = 1, float = true, severity = vim.diagnostic.severity.ERROR }) end, "Next error") + + map("lwa", vim.lsp.buf.add_workspace_folder, "Add workspace folder") + map("lwr", vim.lsp.buf.remove_workspace_folder, "Remove workspace folder") + map("lwl", function() + print(vim.inspect(vim.lsp.buf.list_workspace_folders())) + end, "List workspace folders") + + if client:supports_method("textDocument/inlayHint", bufnr) then + vim.lsp.inlay_hint.enable(true, { bufnr = bufnr }) + map("lh", function() + vim.lsp.inlay_hint.enable(not vim.lsp.inlay_hint.is_enabled({ bufnr = bufnr }), { bufnr = bufnr }) + end, "Toggle inlay hints") + end + + if client:supports_method("textDocument/codeLens", bufnr) then + map("ll", vim.lsp.codelens.run, "Run codelens") + map("lL", vim.lsp.codelens.refresh, "Refresh codelens") + end + + if client:supports_method("textDocument/documentHighlight", bufnr) then + local group = vim.api.nvim_create_augroup("LspDocumentHighlight", { clear = false }) + vim.api.nvim_clear_autocmds({ group = group, buffer = bufnr }) + vim.api.nvim_create_autocmd({ "CursorHold", "CursorHoldI" }, { + group = group, + buffer = bufnr, + callback = vim.lsp.buf.document_highlight, + }) + vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI", "BufLeave" }, { + group = group, + buffer = bufnr, + callback = vim.lsp.buf.clear_references, + }) + end + end, + }) + ''; + + keymaps = [ + { + key = "xx"; + mode = "n"; + action = "Trouble diagnostics toggle"; + desc = "Diagnostics (Trouble)"; + } + { + key = "xX"; + mode = "n"; + action = "Trouble diagnostics toggle filter.buf=0"; + desc = "Buffer diagnostics (Trouble)"; + } + { + key = "xs"; + mode = "n"; + action = "Trouble symbols toggle focus=false"; + desc = "Symbols (Trouble)"; + } + { + key = "xl"; + mode = "n"; + action = "Trouble lsp toggle focus=false win.position=right"; + desc = "LSP references (Trouble)"; + } + { + key = "xL"; + mode = "n"; + action = "Trouble loclist toggle"; + desc = "Location list (Trouble)"; + } + { + key = "xQ"; + mode = "n"; + action = "Trouble qflist toggle"; + desc = "Quickfix list (Trouble)"; + } + ]; + }; +} diff --git a/nvf/config/languages/treesitter.nix b/nvf/config/languages/treesitter.nix new file mode 100644 index 00000000..98e86190 --- /dev/null +++ b/nvf/config/languages/treesitter.nix @@ -0,0 +1,134 @@ +{ ... }: { + config.vim = { + treesitter = { + enable = true; + autotagHtml = true; + context.enable = true; + textobjects = { + enable = true; + setupOpts = { + select = { + enable = true; + lookahead = true; + keymaps = { + "af" = { + query = "@function.outer"; + desc = "Around function"; + }; + "if" = { + query = "@function.inner"; + desc = "Inside function"; + }; + "ac" = { + query = "@class.outer"; + desc = "Around class"; + }; + "ic" = { + query = "@class.inner"; + desc = "Inside class"; + }; + "aa" = { + query = "@parameter.outer"; + desc = "Around argument"; + }; + "ia" = { + query = "@parameter.inner"; + desc = "Inside argument"; + }; + "al" = { + query = "@loop.outer"; + desc = "Around loop"; + }; + "il" = { + query = "@loop.inner"; + desc = "Inside loop"; + }; + "ai" = { + query = "@conditional.outer"; + desc = "Around conditional"; + }; + "ii" = { + query = "@conditional.inner"; + desc = "Inside conditional"; + }; + "ab" = { + query = "@block.outer"; + desc = "Around block"; + }; + "ib" = { + query = "@block.inner"; + desc = "Inside block"; + }; + }; + }; + move = { + enable = true; + goto_next_start = { + "]f" = { + query = "@function.outer"; + desc = "Next function start"; + }; + "]c" = { + query = "@class.outer"; + desc = "Next class start"; + }; + "]a" = { + query = "@parameter.inner"; + desc = "Next argument"; + }; + }; + goto_next_end = { + "]F" = { + query = "@function.outer"; + desc = "Next function end"; + }; + "]C" = { + query = "@class.outer"; + desc = "Next class end"; + }; + }; + goto_previous_start = { + "[f" = { + query = "@function.outer"; + desc = "Previous function start"; + }; + "[c" = { + query = "@class.outer"; + desc = "Previous class start"; + }; + "[a" = { + query = "@parameter.inner"; + desc = "Previous argument"; + }; + }; + goto_previous_end = { + "[F" = { + query = "@function.outer"; + desc = "Previous function end"; + }; + "[C" = { + query = "@class.outer"; + desc = "Previous class end"; + }; + }; + }; + swap = { + enable = true; + swap_next = { + "cx" = { + query = "@parameter.inner"; + desc = "Swap with next parameter"; + }; + }; + swap_previous = { + "cX" = { + query = "@parameter.inner"; + desc = "Swap with previous parameter"; + }; + }; + }; + }; + }; + }; + }; +} diff --git a/nvf/config/lua_config.nix b/nvf/config/lua_config.nix deleted file mode 100644 index aafcc01b..00000000 --- a/nvf/config/lua_config.nix +++ /dev/null @@ -1,2506 +0,0 @@ -{ pkgs, ... }: { - config.vim = { - luaConfigRC.core_options = '' - -- ─── System Environment & PATH ─────────────────────────────── - local home = vim.env.HOME or vim.fn.expand("~") - local mason_bin = vim.fn.stdpath("data") .. "/mason/bin" - local go_bin = home .. "/go/bin" - local cargo_bin = home .. "/.cargo/bin" - - vim.env.PATH = mason_bin .. ":" .. go_bin .. ":" .. cargo_bin .. ":" .. (vim.env.PATH or "") - - local opt = vim.opt - opt.listchars = { tab = "» ", trail = "·", nbsp = "␣" } - opt.fillchars = { eob = " ", fold = " ", foldopen = "v", foldsep = " ", foldclose = ">" } - opt.shortmess:append("sI") - opt.inccommand = "split" - opt.jumpoptions = "view" - opt.virtualedit = "block" - opt.smoothscroll = true - opt.completeopt = { "menu", "menuone", "noselect" } - - local undo_dir = vim.fn.stdpath("data") .. "/undo" - opt.undodir = undo_dir - pcall(vim.fn.mkdir, undo_dir, "p") - - vim.g.loaded_python3_provider = 0 - vim.g.loaded_ruby_provider = 0 - vim.g.loaded_perl_provider = 0 - vim.g.loaded_node_provider = 0 - ''; - luaConfigRC.core_keymaps = '' - -- ─── Global Keymaps ─────────────────────────────────────────── - -- Plugin-specific keymaps live in their respective plugin files. - local map = vim.keymap.set - - -- Better escape - map("i", "jk", "", { desc = "Exit insert mode" }) - - -- Save & quit - map("n", "w", "w", { desc = "Save file" }) - map("n", "q", "q", { desc = "Quit" }) - map("n", "Q", "qa!", { desc = "Force quit all" }) - - -- Clear search highlights - map("n", "nh", "nohlsearch", { desc = "Clear search highlights" }) - - -- Better window navigation (overridden by tmux-navigator when in tmux) - map("n", "", "h", { desc = "Move to left window" }) - map("n", "", "j", { desc = "Move to lower window" }) - map("n", "", "k", { desc = "Move to upper window" }) - map("n", "", "l", { desc = "Move to right window" }) - - -- Resize windows with arrows - map("n", "", "resize +2", { desc = "Increase window height" }) - map("n", "", "resize -2", { desc = "Decrease window height" }) - map("n", "", "vertical resize -2", { desc = "Decrease window width" }) - map("n", "", "vertical resize +2", { desc = "Increase window width" }) - - -- Buffer navigation - map("n", "", "bprevious", { desc = "Previous buffer" }) - map("n", "", "bnext", { desc = "Next buffer" }) - map("n", "bd", "bdelete", { desc = "Close buffer" }) - map("n", "bD", "bdelete!", { desc = "Force close buffer" }) - - -- Move lines up/down in visual mode - map("v", "J", ":m '>+1gv=gv", { desc = "Move selection down" }) - map("v", "K", ":m '<-2gv=gv", { desc = "Move selection up" }) - - -- Stay in visual mode when indenting - map("v", "<", "", ">gv", { desc = "Indent right" }) - - -- Keep cursor centered when scrolling - map("n", "", "zz", { desc = "Scroll down (centered)" }) - map("n", "", "zz", { desc = "Scroll up (centered)" }) - - -- Keep search terms centered - map("n", "n", "nzzzv", { desc = "Next search result (centered)" }) - map("n", "N", "Nzzzv", { desc = "Previous search result (centered)" }) - - -- Join lines without moving cursor - map("n", "J", "mzJ`z", { desc = "Join lines" }) - - -- Paste without losing register content - map("x", "p", [["_dP]], { desc = "Paste without overwriting register" }) - - -- Delete without yanking (visual mode only, keeping normal d free for DAP) - map("x", "d", [["_d]], { desc = "Delete without yanking" }) - - -- Better terminal navigation - map("t", "", "wincmd h", { desc = "Move to left window (terminal)" }) - map("t", "", "wincmd j", { desc = "Move to lower window (terminal)" }) - map("t", "", "wincmd k", { desc = "Move to upper window (terminal)" }) - map("t", "", "wincmd l", { desc = "Move to right window (terminal)" }) - map("t", "", "", { desc = "Exit terminal mode" }) - - -- Quickfix navigation - map("n", "]q", "cnextzz", { desc = "Next quickfix item" }) - map("n", "[q", "cprevzz", { desc = "Previous quickfix item" }) - - -- Select all - map("n", "", "ggG", { desc = "Select all" }) - - -- Package manager / configuration info - map("n", "L", function() - vim.notify("Configured declaratively via Nix / nvf flake", vim.log.levels.INFO, { title = "Package Manager" }) - end, { desc = "Open Lazy plugin manager / Nix info" }) - ''; - luaConfigRC.core_autocmds = '' - -- ─── Autocommands ───────────────────────────────────────────── - local augroup = vim.api.nvim_create_augroup - local autocmd = vim.api.nvim_create_autocmd - - -- Highlight on yank - autocmd("TextYankPost", { - group = augroup("YankHighlight", { clear = true }), - callback = function() - vim.hl.on_yank({ higroup = "IncSearch", timeout = 200 }) - end, - }) - - -- Resize splits when window is resized - autocmd("VimResized", { - group = augroup("ResizeSplits", { clear = true }), - callback = function() - local current_tab = vim.fn.tabpagenr() - vim.cmd("tabdo wincmd =") - vim.cmd("tabnext " .. current_tab) - end, - }) - - -- Go to last cursor position when opening a file - autocmd("BufReadPost", { - group = augroup("LastPosition", { clear = true }), - 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 certain filetypes with q - autocmd("FileType", { - group = augroup("CloseWithQ", { clear = true }), - pattern = { - "PlenaryTestPopup", - "checkhealth", - "dbout", - "gitsigns.blame", - "help", - "lspinfo", - "neotest-output", - "neotest-output-panel", - "neotest-summary", - "notify", - "qf", - "spectre_panel", - "startuptime", - "tsplayground", - }, - callback = function(event) - vim.bo[event.buf].buflisted = false - vim.keymap.set("n", "q", "close", { buffer = event.buf, silent = true }) - end, - }) - - -- Auto create directories when saving a file - autocmd("BufWritePre", { - group = augroup("AutoCreateDir", { clear = true }), - callback = function(event) - if event.match:match("^%w%w+:[\\/][\\/]") then - return - end - local file = vim.uv.fs_realpath(event.match) or event.match - vim.fn.mkdir(vim.fn.fnamemodify(file, ":p:h"), "p") - end, - }) - - -- Remove trailing whitespace on save - autocmd("BufWritePre", { - group = augroup("TrimWhitespace", { clear = true }), - pattern = "*", - callback = function() - local save_cursor = vim.fn.getpos(".") - pcall(function() - vim.cmd([[%s/\s\+$//e]]) - end) - vim.fn.setpos(".", save_cursor) - end, - }) - - -- Check if file changed when its window is focused - autocmd({ "FocusGained", "TermClose", "TermLeave" }, { - group = augroup("Checktime", { clear = true }), - callback = function() - if vim.o.buftype ~= "nofile" then - vim.cmd("checktime") - end - end, - }) - - -- Wrap and spell check in text filetypes - autocmd("FileType", { - group = augroup("WrapSpell", { clear = true }), - pattern = { "text", "plaintex", "typst", "gitcommit", "markdown" }, - callback = function() - vim.opt_local.wrap = true - vim.opt_local.spell = true - end, - }) - - -- Fix conceallevel for json files - autocmd("FileType", { - group = augroup("JsonConceal", { clear = true }), - pattern = { "json", "jsonc", "json5" }, - callback = function() - vim.opt_local.conceallevel = 0 - end, - }) - - ''; - luaConfigRC.plugin_lualine_snacks = '' - -- Snacks - require("snacks").setup({ - -- ── Dashboard ── - dashboard = { - enabled = true, - preset = { - keys = { - { icon = " ", key = "f", desc = "Find File", action = ":lua Snacks.dashboard.pick('files')" }, - { icon = " ", key = "n", desc = "New File", action = ":ene | startinsert" }, - { icon = "󰱼 ", key = "g", desc = "Find Text", action = ":lua Snacks.dashboard.pick('live_grep')" }, - { icon = " ", key = "r", desc = "Recent Files", action = ":lua Snacks.dashboard.pick('oldfiles')" }, - { icon = " ", key = "p", desc = "Projects", action = ":lua Snacks.picker.projects()" }, - { icon = " ", key = "c", desc = "Config", action = ":lua Snacks.dashboard.pick('files', {cwd = vim.fn.stdpath('config')})" }, - - { icon = " ", key = "q", desc = "Quit", action = ":qa" }, - }, - header = [[ - - -  - ████ ██████ █████ ██ - ███████████ █████  - █████████ ███████████████████ ███ ███████████ - █████████ ███ █████████████ █████ ██████████████ - █████████ ██████████ █████████ █████ █████ ████ █████ - ███████████ ███ ███ █████████ █████ █████ ████ █████ - ██████ █████████████████████ ████ █████ █████ ████ ██████ - - - - - - ]], - }, - sections = { - { pane = 1, section = "header", padding = 2 }, - - { pane = 2, section = "keys", gap = 1, padding = 1 }, - }, - }, - - -- ── Notifier ── - notifier = { - enabled = true, - timeout = 3000, - style = "compact", - }, - - -- ── Quick file ── (fast file opening) - quickfile = { enabled = true }, - - -- ── Status column ── - statuscolumn = { enabled = true }, - - -- ── Words ── (highlight/navigate references) - words = { enabled = true }, - - -- ── Styles ── - styles = { - notification = { - wo = { wrap = true }, - }, - }, - }) - - vim.keymap.set("n", "un", function() Snacks.notifier.hide() end, { desc = "Dismiss all notifications" }) - vim.keymap.set("n", "gg", function() Snacks.lazygit() end, { desc = "Lazygit" }) - vim.keymap.set("n", "gl", function() Snacks.lazygit.log() end, { desc = "Lazygit log (cwd)" }) - vim.keymap.set({ "n", "t" }, "]]", function() Snacks.words.jump(vim.v.count1) end, { desc = "Next reference" }) - vim.keymap.set({ "n", "t" }, "[[", function() Snacks.words.jump(-vim.v.count1) end, { desc = "Previous reference" }) - - -- Lualine - local lualine = require("lualine") - - local function hl(group, attr, fallback) - local ok, value = pcall(vim.api.nvim_get_hl, 0, { name = group, link = true }) - if not ok or not value[attr] then - return fallback - end - return string.format("#%06x", value[attr]) - end - - local function base46_palette() - local ok, base46 = pcall(require, "base46") - if not ok then - return nil, nil - end - - local theme_name = base46.current_theme - if not theme_name and vim.g.colors_name then - theme_name = vim.g.colors_name:match("^base46%-(.+)$") - end - - local theme = theme_name and base46.theme_tables[theme_name] - if not theme and theme_name and base46.get_builtin_theme then - theme = base46.get_builtin_theme(theme_name) - end - - if not theme then - return nil, nil - end - return theme.base_30 or {}, theme.base_16 or {} - end - - local function colors() - local base30, base16 = base46_palette() - if base30 then - return { - bg = base30.statusline_bg or base30.black or base16.base00 or "#202328", - fg = base30.white or base16.base05 or "#bbc2cf", - muted = base30.grey_fg or base16.base03 or "#5c6370", - yellow = base30.yellow or base30.sun or base16.base0A or "#ECBE7B", - cyan = base30.cyan or base30.teal or base16.base0C or "#008080", - green = base30.green or base30.vibrant_green or base16.base0B or "#98be65", - orange = base30.orange or base16.base09 or "#FF8800", - violet = base30.purple or base30.dark_purple or base16.base0E or "#a9a1e1", - magenta = base30.pink or base30.baby_pink or base30.purple or base16.base0E or "#c678dd", - blue = base30.blue or base30.nord_blue or base16.base0D or "#51afef", - red = base30.red or base16.base08 or "#ec5f67", - } - end - - return { - bg = hl("StatusLine", "bg", hl("Normal", "bg", "#202328")), - fg = hl("StatusLine", "fg", hl("Normal", "fg", "#bbc2cf")), - muted = hl("Comment", "fg", "#5c6370"), - yellow = hl("DiagnosticWarn", "fg", "#ECBE7B"), - cyan = hl("DiagnosticInfo", "fg", "#008080"), - green = hl("String", "fg", "#98be65"), - orange = hl("Number", "fg", "#FF8800"), - violet = hl("DiagnosticHint", "fg", "#a9a1e1"), - magenta = hl("Keyword", "fg", "#c678dd"), - blue = hl("Function", "fg", "#51afef"), - red = hl("DiagnosticError", "fg", "#ec5f67"), - } - end - - local conditions = { - buffer_not_empty = function() - return vim.fn.empty(vim.fn.expand("%:t")) ~= 1 - end, - hide_in_width = function() - return vim.fn.winwidth(0) > 80 - end, - check_git_workspace = function() - local filepath = vim.fn.expand("%:p:h") - local gitdir = vim.fn.finddir(".git", filepath .. ";") - return gitdir and #gitdir > 0 and #gitdir < #filepath - end, - } - - local function mode_color() - local c = colors() - local mode_colors = { - n = c.red, - no = c.red, - nov = c.red, - noV = c.red, - ["no\22"] = c.red, - niI = c.red, - niR = c.red, - niV = c.red, - nt = c.red, - i = c.green, - ic = c.yellow, - ix = c.yellow, - v = c.blue, - V = c.blue, - ["\22"] = c.blue, - s = c.orange, - S = c.orange, - ["\19"] = c.orange, - c = c.magenta, - cv = c.red, - ce = c.red, - R = c.violet, - Rc = c.violet, - Rx = c.violet, - Rv = c.violet, - Rvc = c.violet, - Rvx = c.violet, - r = c.cyan, - rm = c.cyan, - ["r?"] = c.cyan, - ["!"] = c.red, - t = c.red, - } - return mode_colors[vim.fn.mode()] or c.red - end - - local function lsp_name() - local clients = vim.lsp.get_clients({ bufnr = 0 }) - if #clients == 0 then - return "No Active Lsp" - end - - local names = {} - for _, client in ipairs(clients) do - names[#names + 1] = client.name - end - return table.concat(names, ", ") - end - - local function setup() - local c = colors() - local config = { - options = { - component_separators = "", - section_separators = "", - globalstatus = true, - disabled_filetypes = { - statusline = { "dashboard", "snacks_dashboard", "lazy", "mason" }, - }, - theme = { - normal = { c = { fg = c.fg, bg = c.bg } }, - inactive = { c = { fg = c.fg, bg = c.bg } }, - }, - refresh = { - statusline = 100, - }, - }, - sections = { - lualine_a = {}, - lualine_b = {}, - lualine_c = {}, - lualine_x = {}, - lualine_y = {}, - lualine_z = {}, - }, - inactive_sections = { - lualine_a = {}, - lualine_b = {}, - lualine_c = {}, - lualine_x = {}, - lualine_y = {}, - lualine_z = {}, - }, - } - - local function ins_left(component) - table.insert(config.sections.lualine_c, component) - end - - local function ins_right(component) - table.insert(config.sections.lualine_x, component) - end - - ins_left({ - function() - return "▊" - end, - color = function() - return { fg = mode_color() } - end, - padding = { left = 0, right = 1 }, - }) - - ins_left({ - function() - return "" - end, - color = function() - return { fg = mode_color(), gui = "bold" } - end, - padding = { right = 1 }, - }) - - ins_left({ - "filesize", - cond = conditions.buffer_not_empty, - color = { fg = c.cyan, gui = "bold" }, - }) - - ins_left({ - "filename", - cond = conditions.buffer_not_empty, - color = { fg = c.magenta, gui = "bold" }, - symbols = { modified = " ●", readonly = " 󰌾", unnamed = "[No Name]", newfile = "[New]" }, - }) - - ins_left({ "location", color = { fg = c.blue, gui = "bold" } }) - ins_left({ "progress", color = { fg = c.violet, gui = "bold" } }) - - ins_left({ - "diagnostics", - sources = { "nvim_diagnostic" }, - symbols = { error = " ", warn = " ", info = " ", hint = "󰌵 " }, - diagnostics_color = { - error = { fg = c.red }, - warn = { fg = c.yellow }, - info = { fg = c.cyan }, - hint = { fg = c.violet }, - }, - }) - - ins_left({ - function() - return "%=" - end, - }) - - ins_left({ - lsp_name, - icon = " LSP:", - cond = conditions.hide_in_width, - color = { fg = c.green, gui = "bold" }, - }) - - ins_right({ - "o:encoding", - fmt = string.upper, - cond = conditions.hide_in_width, - color = { fg = c.yellow, gui = "bold" }, - }) - - ins_right({ - "fileformat", - fmt = string.upper, - icons_enabled = false, - cond = conditions.hide_in_width, - color = { fg = c.cyan, gui = "bold" }, - }) - - ins_right({ - "branch", - icon = " ", - cond = conditions.check_git_workspace, - color = { fg = c.violet, gui = "bold" }, - }) - - ins_right({ - "diff", - symbols = { added = " ", modified = "󰝤 ", removed = " " }, - diff_color = { - added = { fg = c.green }, - modified = { fg = c.orange }, - removed = { fg = c.red }, - }, - cond = conditions.hide_in_width, - }) - - ins_right({ - function() - return "▊" - end, - color = function() - return { fg = mode_color() } - end, - padding = { left = 1 }, - }) - - lualine.setup(config) - end - - setup() - vim.api.nvim_create_autocmd("ColorScheme", { - group = vim.api.nvim_create_augroup("EvilineThemeRefresh", { clear = true }), - callback = setup, - }) - - ''; - luaConfigRC.plugin_ui = '' - -- ============================================================================ - -- Migrated Standalone UI Configurations - -- Extracted from lua/plugins/ui.lua - -- ============================================================================ - - -- 1. Web Devicons (dependency for UI icons) - pcall(function() - require("nvim-web-devicons").setup({}) - end) - - -- 2. Indent Blankline (lukas-reineke/indent-blankline.nvim) - pcall(function() - require("ibl").setup({ - indent = { - char = "│", - tab_char = "│", - }, - scope = { - show_start = false, - show_end = false, - include = { - node_type = { - ["*"] = { - "argument_list", - "arguments", - "assignment_statement", - "block", - "chunk", - "class", - "do_block", - "element", - "except", - "for", - "function", - "if_statement", - "method", - "object", - "return_statement", - "table", - "try", - "while", - }, - }, - }, - }, - exclude = { - filetypes = { - "help", - "dashboard", - "snacks_dashboard", - "lazy", - "mason", - "notify", - "oil", - "toggleterm", - }, - }, - }) - end) - - -- 3. Notify (rcarriga/nvim-notify) - pcall(function() - local notify = require("notify") - notify.setup({ - stages = "fade_in_slide_out", - 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, - on_open = function(win) - vim.api.nvim_win_set_config(win, { zindex = 100 }) - end, - render = "wrapped-compact", - top_down = true, - }) - vim.notify = notify - end) - - -- 4. Noice (folke/noice.nvim) - pcall(function() - require("noice").setup({ - lsp = { - override = { - ["vim.lsp.util.convert_input_to_markdown_lines"] = true, - ["vim.lsp.util.stylize_markdown"] = true, - ["cmp.entry.get_documentation"] = true, - }, - hover = { enabled = true }, - signature = { enabled = true }, - }, - presets = { - bottom_search = true, - command_palette = true, - long_message_to_split = true, - inc_rename = true, - lsp_doc_border = true, - }, - routes = { - -- Hide "written" messages - { - filter = { - event = "msg_show", - kind = "", - find = "written", - }, - opts = { skip = true }, - }, - }, - }) - - -- Noice keymaps - vim.keymap.set("c", "", function() - require("noice").redirect(vim.fn.getcmdline()) - end, { desc = "Redirect cmdline" }) - vim.keymap.set("n", "snl", function() require("noice").cmd("last") end, { desc = "Noice last message" }) - vim.keymap.set("n", "snh", function() require("noice").cmd("history") end, { desc = "Noice history" }) - vim.keymap.set("n", "sna", function() require("noice").cmd("all") end, { desc = "Noice all" }) - vim.keymap.set("n", "snd", function() require("noice").cmd("dismiss") end, { desc = "Dismiss all" }) - end) - - -- 5. Dressing (stevearc/dressing.nvim) - pcall(function() - require("dressing").setup({}) - end) - - -- 6. Illuminate (RRethy/vim-illuminate) - pcall(function() - require("illuminate").configure({ - delay = 200, - large_file_cutoff = 2000, - large_file_overrides = { - providers = { "lsp" }, - }, - filetypes_denylist = { - "oil", - "dashboard", - "snacks_dashboard", - "lazy", - "mason", - "TelescopePrompt", - }, - }) - end) - - -- 7. Colorizer (NvChad/nvim-colorizer.lua) - pcall(function() - require("colorizer").setup({ - filetypes = { "*" }, - user_default_options = { - RGB = true, - RRGGBB = true, - names = false, - RRGGBBAA = true, - AARRGGBB = true, - rgb_fn = true, - hsl_fn = true, - css = true, - css_fn = true, - mode = "background", - tailwind = false, - virtualtext = "■", - always_update = false, - }, - }) - end) - - ''; - luaConfigRC.plugin_remaining = '' - local function safe_setup(plugin, setup_fn) - local ok, m = pcall(require, plugin) - if not ok then - vim.notify("Failed to load " .. plugin, vim.log.levels.WARN) - return - end - setup_fn(m) - end - - -- ─── Conform — Autoformatter ────────────────────────────────── - safe_setup("conform", function(conform) - conform.setup({ - formatters_by_ft = { - c = { "clang-format" }, - cpp = { "clang-format" }, - rust = { "rustfmt" }, - go = { "goimports", "gofumpt" }, - lua = { "stylua" }, - python = { "ruff_format", "ruff_organize_imports" }, - toml = { "taplo" }, - javascript = { "prettier" }, - typescript = { "prettier" }, - javascriptreact = { "prettier" }, - typescriptreact = { "prettier" }, - html = { "prettier" }, - css = { "prettier" }, - scss = { "prettier" }, - json = { "prettier" }, - jsonc = { "prettier" }, - yaml = { "prettier" }, - markdown = { "prettier" }, - graphql = { "prettier" }, - nix = { "nixfmt" }, - fish = { "fish_indent" }, - sh = { "shfmt" }, - bash = { "shfmt" }, - ["_"] = { "trim_whitespace" }, - }, - format_on_save = function(bufnr) - if vim.b[bufnr].disable_autoformat or vim.g.disable_autoformat then - return - end - return { - timeout_ms = 500, - lsp_format = "fallback", - } - end, - formatters = { - shfmt = { - prepend_args = { "-i", "2" }, - }, - stylua = { - prepend_args = { "--indent-type", "Spaces", "--indent-width", "2" }, - }, - ["clang-format"] = { - prepend_args = { "-fallback-style=LLVM" }, - }, - }, - }) - - vim.keymap.set({ "n", "v" }, "cf", function() - conform.format({ - async = false, - timeout_ms = 3000, - lsp_format = "fallback", - }) - end, { desc = "Format buffer/selection" }) - - vim.api.nvim_create_user_command("FormatToggle", function() - vim.g.disable_autoformat = not vim.g.disable_autoformat - local state = vim.g.disable_autoformat and "disabled" or "enabled" - vim.notify("Autoformat " .. state, vim.log.levels.INFO) - end, { desc = "Toggle autoformat on save" }) - - vim.api.nvim_create_user_command("FormatToggleBuf", function() - vim.b.disable_autoformat = not vim.b.disable_autoformat - local state = vim.b.disable_autoformat and "disabled" or "enabled" - vim.notify("Autoformat (buffer) " .. state, vim.log.levels.INFO) - end, { desc = "Toggle autoformat on save (buffer)" }) - end) - - -- ─── DAP — Debug Adapter Protocol ───────────────────────────── - safe_setup("dap", function(dap) - -- Signs - vim.fn.sign_define("DapBreakpoint", { text = " ", texthl = "DiagnosticError", linehl = "", numhl = "" }) - vim.fn.sign_define("DapBreakpointCondition", { text = " ", texthl = "DiagnosticWarn", linehl = "", numhl = "" }) - vim.fn.sign_define("DapBreakpointRejected", { text = " ", texthl = "DiagnosticError", linehl = "", numhl = "" }) - vim.fn.sign_define("DapLogPoint", { text = " ", texthl = "DiagnosticInfo", linehl = "", numhl = "" }) - vim.fn.sign_define("DapStopped", { text = "▶ ", texthl = "DiagnosticOk", linehl = "DapStoppedLine", numhl = "" }) - vim.api.nvim_set_hl(0, "DapStoppedLine", { default = true, link = "Visual" }) - - -- DAP UI - local ok_dapui, dapui = pcall(require, "dapui") - if ok_dapui then - dapui.setup({ - icons = { expanded = "▾", collapsed = "▸", current_frame = "▸" }, - layouts = { - { - elements = { - { id = "scopes", size = 0.35 }, - { id = "breakpoints", size = 0.15 }, - { id = "stacks", size = 0.25 }, - { id = "watches", size = 0.25 }, - }, - size = 40, - position = "left", - }, - { - elements = { - { id = "repl", size = 0.5 }, - { id = "console", size = 0.5 }, - }, - size = 0.25, - position = "bottom", - }, - }, - floating = { - border = "rounded", - mappings = { - close = { "q", "" }, - }, - }, - }) - - dap.listeners.after.event_initialized["dapui_config"] = function() - pcall(function() dapui.open({}) end) - end - dap.listeners.before.event_terminated["dapui_config"] = function() - pcall(function() dapui.close({}) end) - end - dap.listeners.before.event_exited["dapui_config"] = function() - pcall(function() dapui.close({}) end) - end - - vim.keymap.set("n", "du", function() dapui.toggle({}) end, { desc = "DAP: toggle UI" }) - vim.keymap.set({ "n", "v" }, "de", function() dapui.eval() end, { desc = "DAP: eval" }) - end - - -- DAP Virtual Text - local ok_dap_vt, dap_vt = pcall(require, "nvim-dap-virtual-text") - if ok_dap_vt then - dap_vt.setup({ - enabled = true, - enabled_commands = true, - highlight_changed_variables = true, - highlight_new_as_changed = false, - show_stop_reason = true, - commented = false, - only_first_definition = true, - all_references = false, - clear_on_continue = false, - display_callback = function(variable, buf, stackframe, node, options) - if options.virt_text_pos == "inline" then - return " = " .. variable.value - else - return variable.name .. " = " .. variable.value - end - end, - virt_text_pos = "eol", - all_frames = false, - virt_lines = false, - virt_text_win_col = nil, - }) - end - - -- DAP Go - local ok_dap_go, dap_go = pcall(require, "dap-go") - if ok_dap_go then - dap_go.setup({}) - end - - -- DAP Python - local ok_dap_py, dap_py = pcall(require, "dap-python") - if ok_dap_py then - local debugpy_path = vim.fn.exepath("python3") - dap_py.setup(debugpy_path) - end - - -- Keymaps - local map = vim.keymap.set - map("n", "db", function() dap.toggle_breakpoint() end, { desc = "DAP: toggle breakpoint" }) - map("n", "dB", function() dap.set_breakpoint(vim.fn.input("Breakpoint condition: ")) end, { desc = "DAP: conditional breakpoint" }) - map("n", "dc", function() dap.continue() end, { desc = "DAP: continue" }) - map("n", "dC", function() dap.run_to_cursor() end, { desc = "DAP: run to cursor" }) - map("n", "di", function() dap.step_into() end, { desc = "DAP: step into" }) - map("n", "do", function() dap.step_over() end, { desc = "DAP: step over" }) - map("n", "dO", function() dap.step_out() end, { desc = "DAP: step out" }) - map("n", "dp", function() dap.pause() end, { desc = "DAP: pause" }) - map("n", "dr", function() dap.restart() end, { desc = "DAP: restart" }) - map("n", "dl", function() dap.run_last() end, { desc = "DAP: run last" }) - map("n", "dt", function() dap.terminate() end, { desc = "DAP: terminate" }) - map("n", "dw", function() require("dap.ui.widgets").hover() end, { desc = "DAP: widgets" }) - - -- CodeLLDB Discovery & Adapter - local mason_path = vim.fn.stdpath("data") .. "/mason/packages/codelldb" - local codelldb_mason = mason_path .. "/extension/adapter/codelldb" - local codelldb_cmd = "codelldb" - - if vim.fn.filereadable(codelldb_mason) == 1 or vim.fn.executable(codelldb_mason) == 1 then - codelldb_cmd = codelldb_mason - elseif vim.fn.executable("codelldb") == 1 then - codelldb_cmd = vim.fn.exepath("codelldb") - end - - dap.adapters.codelldb = { - type = "server", - port = "''${port}", - executable = { - command = codelldb_cmd, - args = { "--port", "''${port}" }, - }, - } - dap.adapters.lldb = dap.adapters.codelldb - - local function get_binary() - local cwd = vim.fn.getcwd() - local candidates = {} - local seen = {} - - local search_dirs = { - "target/debug", "target/release", "build", "build/bin", - "bin", "out", "cmake-build-debug", "cmake-build-release", - } - local ignored_exts = { - o = true, a = true, so = true, dylib = true, dll = true, - d = true, rlib = true, rmeta = true, pdb = true, - txt = true, log = true, cmake = true, json = true, - ninja = true, lock = true, - } - local ignored_script_exts = { - sh = true, bash = true, zsh = true, lua = true, py = true, - pl = true, rb = true, js = true, ts = true, md = true, - } - - for _, dir in ipairs(search_dirs) do - local full_dir = cwd .. "/" .. dir - if vim.fn.isdirectory(full_dir) == 1 then - local files = vim.fn.glob(full_dir .. "/*", false, true) - for _, file in ipairs(files) do - if vim.fn.isdirectory(file) == 0 and vim.fn.executable(file) == 1 then - local ext = vim.fn.fnamemodify(file, ":e"):lower() - if not ignored_exts[ext] and not ignored_script_exts[ext] and not seen[file] then - seen[file] = true - table.insert(candidates, file) - end - end - end - end - end - - local root_files = vim.fn.glob(cwd .. "/*", false, true) - for _, file in ipairs(root_files) do - if vim.fn.isdirectory(file) == 0 and vim.fn.executable(file) == 1 then - local ext = vim.fn.fnamemodify(file, ":e"):lower() - if not ignored_exts[ext] and not ignored_script_exts[ext] and not file:match("/%.") and not seen[file] then - seen[file] = true - table.insert(candidates, file) - end - end - end - - local co = coroutine.running() - if co and #candidates > 0 then - local items = {} - for _, c in ipairs(candidates) do - table.insert(items, c) - end - table.insert(items, "Manually enter path...") - - vim.ui.select(items, { - prompt = "Select executable: ", - format_item = function(item) - if item == "Manually enter path..." then - return item - end - return vim.fn.fnamemodify(item, ":.") - end, - }, function(choice) - if not choice then - coroutine.resume(co, dap.ABORT) - elseif choice == "Manually enter path..." then - local manual = vim.fn.input("Path to executable: ", cwd .. "/", "file") - coroutine.resume(co, manual ~= "" and manual or dap.ABORT) - else - coroutine.resume(co, choice) - end - end) - return coroutine.yield() - elseif #candidates == 1 and not co then - return candidates[1] - else - local input = vim.fn.input("Path to executable: ", cwd .. "/", "file") - if input == "" then - return dap.ABORT or nil - end - return input - end - end - - local codelldb_configurations = { - { - name = "Launch Binary (Smart Picker)", - type = "codelldb", - request = "launch", - program = get_binary, - cwd = "''${workspaceFolder}", - stopOnEntry = false, - args = {}, - }, - { - name = "Launch Binary with Arguments", - type = "codelldb", - request = "launch", - program = get_binary, - cwd = "''${workspaceFolder}", - stopOnEntry = false, - args = function() - local args_str = vim.fn.input("Arguments: ") - if args_str == "" then - return {} - end - return vim.split(args_str, "%s+", { trimempty = true }) - end, - }, - { - name = "Attach to Process", - type = "codelldb", - request = "attach", - pid = function() - return require("dap.utils").pick_process() - end, - cwd = "''${workspaceFolder}", - }, - { - name = "Load Core Dump", - type = "codelldb", - request = "attach", - program = get_binary, - coreDumpPath = function() - local core_path = vim.fn.input("Path to core dump: ", vim.fn.getcwd() .. "/", "file") - return core_path ~= "" and core_path or nil - end, - cwd = "''${workspaceFolder}", - }, - } - - dap.configurations.cpp = codelldb_configurations - dap.configurations.c = codelldb_configurations - dap.configurations.rust = codelldb_configurations - - -- Delve (Go) - local dlv_path = "dlv" - local mason_dlv = vim.fn.stdpath("data") .. "/mason/packages/delve/dlv" - if vim.fn.filereadable(mason_dlv) == 1 or vim.fn.executable(mason_dlv) == 1 then - dlv_path = mason_dlv - elseif vim.fn.executable("dlv") == 1 then - dlv_path = vim.fn.exepath("dlv") - end - - dap.adapters.delve = { - type = "server", - port = "''${port}", - executable = { - command = dlv_path, - args = { "dap", "-l", "127.0.0.1:''${port}" }, - }, - } - dap.adapters.go = dap.adapters.delve - - dap.configurations.go = { - { - type = "delve", - name = "Debug (Smart Picker / Current File)", - request = "launch", - program = "''${file}", - }, - { - type = "delve", - name = "Debug Package", - request = "launch", - program = "''${fileDirname}", - }, - { - type = "delve", - name = "Debug with Arguments", - request = "launch", - program = "''${file}", - args = function() - local args_str = vim.fn.input("Arguments: ") - if args_str == "" then - return {} - end - return vim.split(args_str, "%s+", { trimempty = true }) - end, - }, - { - type = "delve", - name = "Debug Test", - request = "launch", - mode = "test", - program = "''${file}", - }, - { - type = "delve", - name = "Debug Test (Package)", - request = "launch", - mode = "test", - program = "./''${relativeFileDirname}", - }, - { - type = "delve", - name = "Attach to Process", - request = "attach", - mode = "local", - processId = function() - return require("dap.utils").pick_process() - end, - }, - } - end) - - -- ─── Colorscheme (rose-pine & monoglow) ─────────────────────── - safe_setup("rose-pine", function(rose_pine) - rose_pine.setup({ - variant = "main", - dark_variant = "main", - dim_inactive_windows = false, - extend_background_behind_borders = true, - styles = { - bold = true, - italic = true, - transparency = true, - }, - highlight_groups = { - TelescopeBorder = { fg = "highlight_high", bg = "none" }, - TelescopeNormal = { bg = "none" }, - TelescopePromptNormal = { bg = "base" }, - TelescopeResultsNormal = { fg = "subtle", bg = "none" }, - TelescopeSelection = { fg = "text", bg = "base" }, - TelescopeSelectionCaret = { fg = "rose", bg = "rose" }, - FloatBorder = { fg = "highlight_high", bg = "surface" }, - NormalFloat = { bg = "surface" }, - CursorLine = { bg = "highlight_low" }, - CursorLineNr = { fg = "gold" }, - StatusLine = { fg = "love", bg = "love", blend = 10 }, - StatusLineNC = { fg = "subtle", bg = "surface" }, - WhichKeyFloat = { bg = "surface" }, - }, - }) - local ok_base46, base46 = pcall(require, "base46") - if ok_base46 then - base46.setup({ - transparency = true, - set_background = true, - term_colors = true, - integrations = { - defaults = true, - syntax = true, - treesitter = true, - lsp = true, - telescope = true, - whichkey = true, - gitsigns = true, - bufferline = true, - devicons = true, - blink = true, - statusline = false, - neotest = true, - }, - }) - end - - package.preload["core.theme"] = function() - local base46_state_file = vim.fn.stdpath("state") .. "/base46-theme" - return { - base46_state_file = base46_state_file, - read_base46_theme = function() - if vim.fn.filereadable(base46_state_file) ~= 1 then return nil end - local lines = vim.fn.readfile(base46_state_file) - local theme = lines[1] - return theme and theme ~= "" and theme or nil - end, - write_base46_theme = function(theme) - vim.fn.mkdir(vim.fn.fnamemodify(base46_state_file, ":h"), "p") - vim.fn.writefile({ theme }, base46_state_file) - end, - } - end - - local theme_state = require("core.theme") - local saved_theme = theme_state.read_base46_theme() - - if saved_theme then - if saved_theme == "monoglow" or saved_theme == "rose-pine" then - vim.cmd.colorscheme(saved_theme) - else - if ok_base46 then - base46.load(saved_theme) - vim.api.nvim_exec_autocmds("ColorScheme", {}) - else - vim.cmd.colorscheme("monoglow") - end - end - else - vim.cmd.colorscheme("monoglow") - end - - local function pick_theme() - local themes = { "monoglow", "rose-pine" } - for _, path in ipairs(vim.api.nvim_get_runtime_file("lua/base46/themes/*.lua", true)) do - local name = vim.fn.fnamemodify(path, ":t:r") - themes[#themes + 1] = name - end - table.sort(themes) - - vim.ui.select(themes, { prompt = "Select theme: " }, function(choice) - if choice then - require("core.theme").write_base46_theme(choice) - if choice == "monoglow" or choice == "rose-pine" then - vim.cmd.colorscheme(choice) - else - local ok, b46 = pcall(require, "base46") - if ok then - b46.load(choice) - vim.api.nvim_exec_autocmds("ColorScheme", {}) - end - end - vim.notify("Theme: " .. choice, vim.log.levels.INFO) - end - end) - end - - vim.keymap.set("n", "ut", pick_theme, { desc = "Themes: choose theme" }) - vim.api.nvim_create_user_command("NvChadTheme", pick_theme, { desc = "Choose colorscheme" }) - package.preload["base46-themes"] = function() - return { - pick = pick_theme, - } - end - end) - - -- ─── LSP Configuration & Keymaps ───────────────────────────── - vim.diagnostic.config({ - underline = true, - update_in_insert = false, - virtual_text = { - spacing = 4, - source = "if_many", - prefix = "●", - }, - severity_sort = true, - float = { - focusable = true, - style = "minimal", - border = "rounded", - source = true, - header = "", - prefix = "", - }, - signs = { - text = { - [vim.diagnostic.severity.ERROR] = " ", - [vim.diagnostic.severity.WARN] = " ", - [vim.diagnostic.severity.HINT] = "󰌵 ", - [vim.diagnostic.severity.INFO] = " ", - }, - }, - }) - - vim.api.nvim_create_autocmd("LspAttach", { - group = vim.api.nvim_create_augroup("UserLspConfig", { clear = true }), - callback = function(ev) - local bufnr = ev.buf - local client = vim.lsp.get_client_by_id(ev.data.client_id) - if not client then - return - end - - local map = function(keys, func, desc, mode) - mode = mode or "n" - vim.keymap.set(mode, keys, func, { buffer = bufnr, desc = "LSP: " .. desc }) - end - - map("gd", vim.lsp.buf.definition, "Go to definition") - map("gD", vim.lsp.buf.declaration, "Go to declaration") - map("gr", vim.lsp.buf.references, "Go to references") - map("gi", vim.lsp.buf.implementation, "Go to implementation") - map("D", vim.lsp.buf.type_definition, "Type definition") - map("li", vim.lsp.buf.incoming_calls, "Incoming calls") - map("lo", vim.lsp.buf.outgoing_calls, "Outgoing calls") - - map("K", vim.lsp.buf.hover, "Hover documentation") - map("", vim.lsp.buf.signature_help, "Signature help") - map("", vim.lsp.buf.signature_help, "Signature help", "i") - - map("ca", vim.lsp.buf.code_action, "Code action") - map("ca", vim.lsp.buf.code_action, "Code action", "v") - map("rn", vim.lsp.buf.rename, "Rename symbol") - - map("ld", vim.diagnostic.open_float, "Line diagnostics") - map("[d", function() vim.diagnostic.jump({ count = -1, float = true }) end, "Previous diagnostic") - map("]d", function() vim.diagnostic.jump({ count = 1, float = true }) end, "Next diagnostic") - map("[e", function() vim.diagnostic.jump({ count = -1, float = true, severity = vim.diagnostic.severity.ERROR }) end, "Previous error") - map("]e", function() vim.diagnostic.jump({ count = 1, float = true, severity = vim.diagnostic.severity.ERROR }) end, "Next error") - - map("lwa", vim.lsp.buf.add_workspace_folder, "Add workspace folder") - map("lwr", vim.lsp.buf.remove_workspace_folder, "Remove workspace folder") - map("lwl", function() - print(vim.inspect(vim.lsp.buf.list_workspace_folders())) - end, "List workspace folders") - - if client:supports_method("textDocument/inlayHint", bufnr) then - vim.lsp.inlay_hint.enable(true, { bufnr = bufnr }) - map("lh", function() - vim.lsp.inlay_hint.enable(not vim.lsp.inlay_hint.is_enabled({ bufnr = bufnr }), { bufnr = bufnr }) - end, "Toggle inlay hints") - end - - if client:supports_method("textDocument/codeLens", bufnr) then - map("ll", vim.lsp.codelens.run, "Run codelens") - map("lL", vim.lsp.codelens.refresh, "Refresh codelens") - end - - if client:supports_method("textDocument/documentHighlight", bufnr) then - local group = vim.api.nvim_create_augroup("LspDocumentHighlight", { clear = false }) - vim.api.nvim_clear_autocmds({ group = group, buffer = bufnr }) - vim.api.nvim_create_autocmd({ "CursorHold", "CursorHoldI" }, { - group = group, - buffer = bufnr, - callback = vim.lsp.buf.document_highlight, - }) - vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI", "BufLeave" }, { - group = group, - buffer = bufnr, - callback = vim.lsp.buf.clear_references, - }) - end - end, - }) - - -- ─── Bufferline — Tab/Buffer Line ───────────────────────────── - safe_setup("bufferline", function(bufferline) - bufferline.setup({ - options = { - mode = "buffers", - themable = true, - numbers = "none", - close_command = "bdelete! %d", - right_mouse_command = "bdelete! %d", - left_mouse_command = "buffer %d", - middle_mouse_command = nil, - indicator = { - icon = "▎", - style = "icon", - }, - buffer_close_icon = "󰅖", - modified_icon = "●", - close_icon = "", - left_trunc_marker = "", - right_trunc_marker = "", - max_name_length = 30, - max_prefix_length = 30, - truncate_names = true, - tab_size = 21, - diagnostics = "nvim_lsp", - diagnostics_update_in_insert = false, - diagnostics_indicator = function(count, level) - local icon = level:match("error") and " " or " " - return " " .. icon .. count - end, - offsets = { - { - filetype = "oil", - text = " File Explorer", - highlight = "Directory", - text_align = "left", - separator = true, - }, - }, - color_icons = true, - show_buffer_icons = true, - show_buffer_close_icons = true, - show_close_icon = false, - show_tab_indicators = true, - show_duplicate_prefix = true, - persist_buffer_sort = true, - separator_style = "thin", - enforce_regular_tabs = false, - always_show_bufferline = true, - hover = { - enabled = true, - delay = 200, - reveal = { "close" }, - }, - }, - }) - - vim.keymap.set("n", "bp", "BufferLineTogglePin", { desc = "Pin buffer" }) - vim.keymap.set("n", "bx", "BufferLineCloseOthers", { desc = "Close other buffers" }) - vim.keymap.set("n", "br", "BufferLineCloseRight", { desc = "Close buffers to the right" }) - vim.keymap.set("n", "bl", "BufferLineCloseLeft", { desc = "Close buffers to the left" }) - vim.keymap.set("n", "", "BufferLineCyclePrev", { desc = "Previous buffer" }) - vim.keymap.set("n", "", "BufferLineCycleNext", { desc = "Next buffer" }) - vim.keymap.set("n", "[b", "BufferLineCyclePrev", { desc = "Previous buffer" }) - vim.keymap.set("n", "]b", "BufferLineCycleNext", { desc = "Next buffer" }) - end) - - - -- ─── Extra Editing Plugins ──────────────────────────────────── - safe_setup("nvim-autopairs", function(autopairs) - autopairs.setup({ - check_ts = true, - ts_config = { - lua = { "string", "source" }, - javascript = { "string", "template_string" }, - java = false, - }, - disable_filetype = { "TelescopePrompt", "spectre_panel" }, - fast_wrap = { - map = "", - chars = { "{", "[", "(", '"', "'" }, - pattern = [=[[%'%"%>%]%)%}%,]]=], - end_key = "$", - before_key = "h", - after_key = "l", - cursor_pos_before = true, - keys = "qwertyuiopzxcvbnmasdfghjkl", - manual_position = true, - highlight = "Search", - highlight_grey = "Comment", - }, - }) - end) - - safe_setup("nvim-surround", function(surround) - surround.setup({}) - end) - - safe_setup("trouble", function(trouble) - trouble.setup({ - use_diagnostic_signs = true, - }) - - vim.keymap.set("n", "xx", "Trouble diagnostics toggle", { desc = "Diagnostics (Trouble)" }) - vim.keymap.set("n", "xX", "Trouble diagnostics toggle filter.buf=0", { desc = "Buffer diagnostics (Trouble)" }) - vim.keymap.set("n", "xs", "Trouble symbols toggle focus=false", { desc = "Symbols (Trouble)" }) - vim.keymap.set("n", "xl", "Trouble lsp toggle focus=false win.position=right", { desc = "LSP references (Trouble)" }) - vim.keymap.set("n", "xL", "Trouble loclist toggle", { desc = "Location list (Trouble)" }) - vim.keymap.set("n", "xQ", "Trouble qflist toggle", { desc = "Quickfix list (Trouble)" }) - end) - - safe_setup("mini.ai", function(ai) - ai.setup({ - n_lines = 500, - custom_textobjects = { - o = ai.gen_spec.treesitter({ - a = { "@block.outer", "@conditional.outer", "@loop.outer" }, - i = { "@block.inner", "@conditional.inner", "@loop.inner" }, - }, {}), - f = ai.gen_spec.treesitter({ a = "@function.outer", i = "@function.inner" }, {}), - c = ai.gen_spec.treesitter({ a = "@class.outer", i = "@class.inner" }, {}), - }, - }) - end) - - safe_setup("better_escape", function(be) - be.setup({ - timeout = 200, - default_mappings = false, - mappings = { - i = { - j = { k = "" }, - }, - c = { - j = { k = "" }, - }, - }, - }) - end) - - safe_setup("persistence", function(persistence) - persistence.setup({ - options = { "buffers", "curdir", "tabpages", "winsize", "help", "globals", "skiprtp" }, - }) - - vim.keymap.set("n", "qs", function() persistence.load() end, { desc = "Restore session" }) - vim.keymap.set("n", "ql", function() persistence.load({ last = true }) end, { desc = "Restore last session" }) - vim.keymap.set("n", "qd", function() persistence.stop() end, { desc = "Don't save current session" }) - end) - - - -- ─── Git Integration ────────────────────────────────────────── - safe_setup("gitsigns", function(gitsigns) - gitsigns.setup({ - signs = { - add = { text = "▎" }, - change = { text = "▎" }, - delete = { text = "" }, - topdelete = { text = "" }, - changedelete = { text = "▎" }, - untracked = { text = "▎" }, - }, - signs_staged = { - add = { text = "▎" }, - change = { text = "▎" }, - delete = { text = "" }, - topdelete = { text = "" }, - changedelete = { text = "▎" }, - }, - current_line_blame = false, - current_line_blame_opts = { - virt_text = true, - virt_text_pos = "eol", - delay = 500, - ignore_whitespace = false, - }, - current_line_blame_formatter = ", - ", - preview_config = { - border = "rounded", - style = "minimal", - relative = "cursor", - row = 0, - col = 1, - }, - on_attach = function(bufnr) - local gs = package.loaded.gitsigns - - local function map(mode, l, r, opts) - opts = opts or {} - opts.buffer = bufnr - vim.keymap.set(mode, l, r, opts) - end - - -- Navigation - map("n", "]g", function() - if vim.wo.diff then - vim.cmd.normal({ "]c", bang = true }) - else - gs.nav_hunk("next") - end - end, { desc = "Next git hunk" }) - - map("n", "[g", function() - if vim.wo.diff then - vim.cmd.normal({ "[c", bang = true }) - else - gs.nav_hunk("prev") - end - end, { desc = "Previous git hunk" }) - - -- Actions - map("n", "gs", gs.stage_hunk, { desc = "Git: stage hunk" }) - map("n", "gr", gs.reset_hunk, { desc = "Git: reset hunk" }) - map("v", "gs", function() gs.stage_hunk({ vim.fn.line("."), vim.fn.line("v") }) end, { desc = "Git: stage hunk" }) - map("v", "gr", function() gs.reset_hunk({ vim.fn.line("."), vim.fn.line("v") }) end, { desc = "Git: reset hunk" }) - map("n", "gS", gs.stage_buffer, { desc = "Git: stage buffer" }) - map("n", "gR", gs.reset_buffer, { desc = "Git: reset buffer" }) - map("n", "gu", gs.undo_stage_hunk, { desc = "Git: undo stage hunk" }) - map("n", "gp", gs.preview_hunk, { desc = "Git: preview hunk" }) - map("n", "gb", function() gs.blame_line({ full = true }) end, { desc = "Git: blame line" }) - map("n", "gB", gs.toggle_current_line_blame, { desc = "Git: toggle line blame" }) - map("n", "gd", gs.diffthis, { desc = "Git: diff this" }) - map("n", "gD", function() gs.diffthis("~") end, { desc = "Git: diff this ~" }) - - -- Text object - map({ "o", "x" }, "ih", ":Gitsigns select_hunk", { desc = "Select git hunk" }) - end, - }) - end) - - - -- ─── Harpoon 2 — Fast File Switching ────────────────────────── - safe_setup("harpoon", function(harpoon) - harpoon:setup({ - settings = { - save_on_toggle = true, - sync_on_ui_close = true, - key = function() - return vim.uv.cwd() - end, - }, - }) - - vim.keymap.set("n", "a", function() harpoon:list():add() end, { desc = "Harpoon: add file" }) - vim.keymap.set("n", "", function() harpoon.ui:toggle_quick_menu(harpoon:list()) end, { desc = "Harpoon: toggle 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" }) - vim.keymap.set("n", "5", function() harpoon:list():select(5) end, { desc = "Harpoon: file 5" }) - vim.keymap.set("n", "[h", function() harpoon:list():prev() end, { desc = "Harpoon: previous file" }) - vim.keymap.set("n", "]h", function() harpoon:list():next() end, { desc = "Harpoon: next file" }) - end) - - - -- ─── Motion Training & Navigation Tools ─────────────────────────── - safe_setup("hardtime", function(hardtime) - hardtime.setup({ - max_count = 3, - disabled_filetypes = { - "qf", "netrw", "NvimTree", "lazy", "mason", "oil", - "snacks_dashboard", "dashboard", "trouble", "harpoon", - "help", "undotree", "dapui_scopes", "dapui_breakpoints", - "dapui_stacks", "dapui_watches", "dap-repl", "dapui_console", - }, - disabled_buftypes = { "nofile", "prompt", "quickfix", "terminal" }, - }) - - vim.keymap.set("n", "uh", function() hardtime.toggle() end, { desc = "Toggle Hardtime (motion trainer)" }) - end) - - safe_setup("precognition", function(precognition) - precognition.setup({ - start_visible = false, - show_blank = true, - }) - - vim.keymap.set("n", "up", function() precognition.toggle() end, { desc = "Toggle Precognition (motion guide)" }) - end) - - safe_setup("flash", function(flash) - flash.setup({}) - - vim.keymap.set({ "n", "x", "o" }, "s", function() flash.jump() end, { desc = "Flash jump" }) - vim.keymap.set({ "n", "x", "o" }, "S", function() flash.treesitter() end, { desc = "Flash treesitter" }) - vim.keymap.set("o", "r", function() flash.remote() end, { desc = "Remote Flash" }) - vim.keymap.set({ "o", "x" }, "R", function() flash.treesitter_search() end, { desc = "Treesitter search" }) - vim.keymap.set("c", "", function() flash.toggle() end, { desc = "Toggle Flash search" }) - end) - - - -- ─── Oil — File Explorer as a Buffer ────────────────────────── - safe_setup("oil", function(oil) - oil.setup({ - default_file_explorer = true, - columns = { - "icon", - "size", - }, - buf_options = { - buflisted = false, - bufhidden = "hide", - }, - win_options = { - wrap = false, - signcolumn = "no", - cursorcolumn = false, - foldcolumn = "0", - spell = false, - list = false, - conceallevel = 3, - concealcursor = "nvic", - }, - delete_to_trash = true, - skip_confirm_for_simple_edits = true, - prompt_save_on_select_new_entry = true, - cleanup_delay_ms = 2000, - lsp_file_methods = { - timeout_ms = 1000, - autosave_changes = false, - }, - constrain_cursor = "editable", - watch_for_changes = true, - keymaps = { - ["g?"] = "actions.show_help", - [""] = "actions.select", - [""] = { "actions.select", opts = { vertical = true }, desc = "Open in vsplit" }, - [""] = { "actions.select", opts = { horizontal = true }, desc = "Open in hsplit" }, - [""] = { "actions.select", opts = { tab = true }, desc = "Open in new tab" }, - [""] = "actions.preview", - [""] = "actions.close", - [""] = "actions.refresh", - ["-"] = "actions.parent", - ["_"] = "actions.open_cwd", - ["`"] = "actions.cd", - ["~"] = { "actions.cd", opts = { scope = "tab" }, desc = ":tcd to the directory" }, - ["gs"] = "actions.change_sort", - ["gx"] = "actions.open_external", - ["g."] = "actions.toggle_hidden", - ["g\\"] = "actions.toggle_trash", - }, - use_default_keymaps = false, - view_options = { - show_hidden = true, - is_hidden_file = function(name, _) - return vim.startswith(name, ".") - end, - is_always_hidden = function(name, _) - return name == ".." or name == ".git" - end, - natural_order = true, - case_insensitive = false, - sort = { - { "type", "asc" }, - { "name", "asc" }, - }, - }, - float = { - padding = 2, - max_width = 90, - max_height = 0, - border = "rounded", - win_options = { - winblend = 0, - }, - }, - }) - - vim.keymap.set("n", "e", "Oil", { desc = "Open file explorer (Oil)" }) - vim.keymap.set("n", "-", "Oil", { desc = "Open parent directory" }) - end) - - - -- ─── Testing — one workflow for Go, Rust, and C++ projects ──────── - safe_setup("neotest", function(neotest) - local adapters = {} - - -- Go Adapter - local ok_golang, neotest_golang = pcall(require, "neotest-golang") - if ok_golang then - table.insert( - adapters, - neotest_golang({ - runner = "go", - go_test_args = { "-v", "-race", "-count=1" }, - dap_go_opts = {}, - }) - ) - else - local ok_go, neotest_go = pcall(require, "neotest-go") - if ok_go then - table.insert( - adapters, - neotest_go({ - experimental = { test_table = true }, - args = { "-race", "-v", "-count=1" }, - }) - ) - end - end - - -- Rust Adapter - local ok_rustacean, rustacean_neotest = pcall(require, "rustaceanvim.neotest") - if ok_rustacean then - table.insert(adapters, rustacean_neotest) - else - local ok_rust, neotest_rust = pcall(require, "neotest-rust") - if ok_rust then - table.insert( - adapters, - neotest_rust({ - args = { "--no-capture" }, - dap_adapter = "codelldb", - }) - ) - end - end - - -- C++ Adapter - local ok_gtest, neotest_gtest = pcall(require, "neotest-gtest") - if ok_gtest then - if type(neotest_gtest) == "function" then - table.insert(adapters, neotest_gtest({})) - elseif type(neotest_gtest.setup) == "function" then - table.insert(adapters, neotest_gtest.setup({})) - else - table.insert(adapters, neotest_gtest) - end - end - - neotest.setup({ - adapters = adapters, - status = { virtual_text = true, signs = true }, - output = { open_on_run = true }, - output_panel = { open_on_run = false }, - quickfix = { open = false }, - }) - - vim.keymap.set("n", "tt", function() neotest.run.run() end, { desc = "Test: run nearest" }) - vim.keymap.set("n", "tf", function() neotest.run.run(vim.fn.expand("%")) end, { desc = "Test: run current file" }) - vim.keymap.set("n", "ta", function() neotest.run.run(vim.fn.getcwd()) end, { desc = "Test: run all (project)" }) - vim.keymap.set("n", "tw", function() neotest.watch.toggle() end, { desc = "Test: toggle watch mode" }) - vim.keymap.set("n", "td", function() neotest.run.run({ strategy = "dap" }) end, { desc = "Test: debug nearest (DAP)" }) - vim.keymap.set("n", "ts", function() neotest.summary.toggle() end, { desc = "Test: toggle summary" }) - vim.keymap.set("n", "to", function() neotest.output.open({ enter = true }) end, { desc = "Test: open output window" }) - vim.keymap.set("n", "tO", function() neotest.output_panel.toggle() end, { desc = "Test: toggle output panel" }) - vim.keymap.set("n", "tS", function() neotest.run.stop() end, { desc = "Test: stop running test" }) - vim.keymap.set("n", "]T", function() neotest.jump.next({ status = "failed" }) end, { desc = "Test: jump next failed" }) - vim.keymap.set("n", "[T", function() neotest.jump.prev({ status = "failed" }) end, { desc = "Test: jump previous failed" }) - end) - - - -- ─── Vim-Tmux Navigator ─────────────────────────────────────── - -- Init (runs before loaded typically, so doing it first) - vim.g.tmux_navigator_no_mappings = 1 - vim.g.tmux_navigator_save_on_switch = 2 - vim.g.tmux_navigator_disable_when_zoomed = 1 - vim.g.tmux_navigator_preserve_zoom = 1 - - vim.keymap.set("n", "", "TmuxNavigateLeft", { desc = "Navigate left (tmux-aware)" }) - vim.keymap.set("n", "", "TmuxNavigateDown", { desc = "Navigate down (tmux-aware)" }) - vim.keymap.set("n", "", "TmuxNavigateUp", { desc = "Navigate up (tmux-aware)" }) - vim.keymap.set("n", "", "TmuxNavigateRight", { desc = "Navigate right (tmux-aware)" }) - - - -- ─── TODO Comments ──────────────────────────────────────────── - safe_setup("todo-comments", function(todo) - todo.setup({ - signs = true, - sign_priority = 8, - keywords = { - FIX = { icon = " ", color = "error", alt = { "FIXME", "BUG", "FIXIT", "ISSUE" } }, - TODO = { icon = " ", color = "info" }, - HACK = { icon = " ", color = "warning" }, - WARN = { icon = " ", color = "warning", alt = { "WARNING", "XXX" } }, - PERF = { icon = "󰅒 ", alt = { "OPTIM", "PERFORMANCE", "OPTIMIZE" } }, - NOTE = { icon = "󰍨 ", color = "hint", alt = { "INFO" } }, - TEST = { icon = "⏲ ", color = "test", alt = { "TESTING", "PASSED", "FAILED" } }, - }, - merge_keywords = true, - highlight = { - multiline = true, - multiline_pattern = "^.", - multiline_context = 10, - before = "", - keyword = "wide", - after = "fg", - pattern = [[.*<(KEYWORDS)\s*:]], - comments_only = true, - max_line_len = 400, - exclude = {}, - }, - search = { - command = "rg", - args = { - "--color=never", - "--no-heading", - "--with-filename", - "--line-number", - "--column", - }, - pattern = [[\b(KEYWORDS):]], - }, - }) - - vim.keymap.set("n", "]t", function() todo.jump_next() end, { desc = "Next TODO" }) - vim.keymap.set("n", "[t", function() todo.jump_prev() end, { desc = "Previous TODO" }) - vim.keymap.set("n", "ft", "TodoTelescope", { desc = "Find TODOs" }) - vim.keymap.set("n", "fT", "TodoTelescope keywords=TODO,FIX,FIXME", { desc = "Find TODO/FIX/FIXME" }) - end) - - -- ─── Clangd Extensions ────────────────────────────────────────── - safe_setup("clangd_extensions", function(clangd_ext) - clangd_ext.setup({ - inlay_hints = { - inline = false, - only_current_line = false, - only_current_line_autocmd = { "CursorHold" }, - show_parameter_hints = true, - parameter_hints_prefix = "<- ", - other_hints_prefix = "=> ", - max_len_align = false, - max_len_align_padding = 1, - right_align = false, - right_align_padding = 7, - highlight = "Comment", - priority = 100, - }, - ast = { - role_icons = { - type = "🄣", - declaration = "🄓", - expression = "🄔", - statement = ";", - specifier = "🄢", - ["template argument"] = "🆃", - }, - kind_icons = { - Compound = "🄲", - Recovery = "🅁", - TranslationUnit = "🅄", - PackExpansion = "🄿", - TemplateTypeParm = "🅃", - TemplateTemplateParm = "🅃", - TemplateParamObject = "🅃", - }, - highlights = { - detail = "Comment", - }, - }, - memory_usage = { - border = "rounded", - }, - symbol_info = { - border = "rounded", - }, - type_hierarchy = { - border = "rounded", - }, - }) - - vim.api.nvim_create_autocmd("FileType", { - group = vim.api.nvim_create_augroup("ClangdKeymaps", { clear = true }), - pattern = { "c", "cpp", "objc", "objcpp", "cuda" }, - callback = function(event) - local map = function(keys, func, desc) - vim.keymap.set("n", keys, func, { buffer = event.buf, desc = "Clangd: " .. desc }) - end - - map("ch", "ClangdSwitchSourceHeader", "Switch source/header") - map("ct", "ClangdTypeHierarchy", "Type hierarchy") - map("cm", "ClangdMemoryUsage", "Memory usage") - map("cs", "ClangdSymbolInfo", "Symbol info") - map("cA", "ClangdAST", "View AST") - end, - }) - end) - - -- ─── Go.nvim ──────────────────────────────────────────────────── - safe_setup("go", function(go) - go.setup({ - lsp_cfg = false, - lsp_on_attach = false, - gofmt = "gofumpt", - goimports = "gopls", - linter = "golangci-lint", - test_runner = "go", - run_in_floaterm = true, - floaterm = { - position = "bottom", - width = 0.98, - height = 0.35, - }, - dap_debug = true, - dap_debug_gui = true, - tag_transform = "snakecase", - tag_options = "json=omitempty", - verbose = false, - log_path = vim.fn.stdpath("cache") .. "/gonvim.log", - icons = { breakpoint = "🔴", currentpos = "🏃" }, - }) - - vim.api.nvim_create_autocmd("FileType", { - pattern = { "go" }, - callback = function(event) - local map = function(keys, func, desc) - vim.keymap.set("n", keys, func, { buffer = event.buf, desc = "Go: " .. desc }) - end - - map("Gt", "GoTest", "Run tests") - map("Gf", "GoTestFunc", "Test function") - map("Gc", "GoCoverage", "Toggle coverage") - map("Ga", "GoAddTag", "Add struct tags") - map("Gr", "GoRmTag", "Remove struct tags") - map("Gi", "GoImpl", "Implement interface") - map("Ge", "GoIfErr", "Generate if err") - map("Gd", "GoDebug", "Start debugger") - map("GD", "GoDebug -s", "Stop debugger") - map("Gl", "GoLint", "Lint") - map("Gv", "GoVet", "Vet") - map("Gg", "GoGenerate", "Go generate") - map("Gm", "GoModTidy", "Go mod tidy") - end, - }) - end) - - -- ─── TS Context Commentstring & Comment.nvim ─────────────────── - safe_setup("ts_context_commentstring", function(ts_cc) - ts_cc.setup({ - enable_autocmd = false, - }) - local ok_comm, comm = pcall(require, "Comment") - if ok_comm then - local ok_ts_hook, ts_hook = pcall(require, "ts_context_commentstring.integrations.comment_nvim") - local pre_hook = ok_ts_hook and ts_hook.create_pre_hook() or nil - comm.setup({ - padding = true, - sticky = true, - ignore = "^$", - toggler = { - line = "gcc", - block = "gbc", - }, - opleader = { - line = "gc", - block = "gb", - }, - extra = { - above = "gcO", - below = "gco", - eol = "gcA", - }, - mappings = { - basic = true, - extra = true, - }, - pre_hook = pre_hook, - }) - end - end) - - -- ─── nvim-lint ──────────────────────────────────────────────── - safe_setup("lint", function(lint) - lint.linters["clang-tidy"] = lint.linters.clangtidy - lint.linters["golangci-lint"] = lint.linters.golangcilint - - lint.linters_by_ft = { - c = { "clang-tidy" }, - cpp = { "clang-tidy" }, - go = { "golangci-lint" }, - python = { "ruff" }, - } - - local lint_augroup = vim.api.nvim_create_augroup("NvimLintUser", { clear = true }) - - local function run_lint() - local bufnr = vim.api.nvim_get_current_buf() - if not vim.api.nvim_buf_is_valid(bufnr) then - return - end - local ft = vim.bo[bufnr].filetype - if not ft or ft == "" then - return - end - local linters = lint.linters_by_ft[ft] or {} - if type(linters) ~= "table" or #linters == 0 then - return - end - - local valid_linters = {} - for _, name in ipairs(linters) do - local linter = lint.linters[name] - if linter then - local cmd = type(linter) == "table" and linter.cmd or name - if type(cmd) == "function" then - cmd = cmd() - end - if type(cmd) == "string" and vim.fn.executable(cmd) == 1 then - table.insert(valid_linters, name) - end - end - end - - if #valid_linters > 0 then - lint.try_lint(valid_linters) - end - end - - vim.api.nvim_create_autocmd({ "BufWritePost", "BufReadPost", "InsertLeave" }, { - group = lint_augroup, - callback = run_lint, - }) - - vim.api.nvim_create_user_command("Lint", function() - run_lint() - end, { desc = "Trigger linting for current buffer" }) - - vim.keymap.set("n", "cl", function() - run_lint() - end, { desc = "Lint current buffer" }) - end) - - -- ─── Rustaceanvim ────────────────────────────────────────────── - local capabilities = vim.lsp.protocol.make_client_capabilities() - local ok_blink, blink = pcall(require, "blink.cmp") - if ok_blink then - capabilities = blink.get_lsp_capabilities(capabilities) - end - - vim.g.rustaceanvim = { - tools = { - hover_actions = { - replace_builtin_hover = false, - }, - float_win_config = { - border = "rounded", - }, - }, - server = { - capabilities = capabilities, - on_attach = function(client, bufnr) - local map = function(keys, func, desc) - vim.keymap.set("n", keys, func, { buffer = bufnr, desc = "Rust: " .. desc }) - end - - map("re", "RustLsp expandMacro", "Expand macro") - map("rc", "RustLsp openCargo", "Open Cargo.toml") - map("rp", "RustLsp parentModule", "Parent module") - map("rd", "RustLsp renderDiagnostic", "Render diagnostic") - map("rr", "RustLsp runnables", "Runnables") - map("rt", "RustLsp testables", "Testables") - map("rm", "RustLsp rebuildProcMacros", "Rebuild proc macros") - map("rk", "RustLsp moveItem up", "Move item up") - map("rj", "RustLsp moveItem down", "Move item down") - map("ra", "RustLsp codeAction", "Code action (rust)") - map("rh", "RustLsp hover actions", "Hover actions") - map("J", "RustLsp joinLines", "Join lines (Rust)") - map("rD", "RustLsp debuggables", "Debuggables") - end, - default_settings = { - ["rust-analyzer"] = { - cargo = { - allFeatures = true, - loadOutDirsFromCheck = true, - buildScripts = { enable = true }, - }, - checkOnSave = true, - check = { - command = "clippy", - extraArgs = { "--no-deps" }, - }, - procMacro = { - enable = true, - ignored = { - ["async-trait"] = { "async_trait" }, - ["napi-derive"] = { "napi" }, - ["async-recursion"] = { "async_recursion" }, - }, - }, - inlayHints = { - bindingModeHints = { enable = false }, - chainingHints = { enable = true }, - closingBraceHints = { enable = true, minLines = 25 }, - closureReturnTypeHints = { enable = "with_block" }, - lifetimeElisionHints = { enable = "never" }, - maxLength = 25, - parameterHints = { enable = true }, - reborrowHints = { enable = "never" }, - renderColons = true, - typeHints = { - enable = true, - hideClosureInitialization = false, - hideNamedConstructor = false, - }, - }, - }, - }, - }, - dap = { - adapter = function() - local ok_cfg, cfg = pcall(require, "rustaceanvim.config") - if not ok_cfg then return nil end - local mason_path = vim.fn.stdpath("data") .. "/mason/packages/codelldb" - local codelldb_path = mason_path .. "/extension/adapter/codelldb" - local liblldb_path = mason_path .. "/extension/lldb/lib/liblldb.so" - - if vim.fn.filereadable(codelldb_path) == 1 then - return cfg.get_codelldb_adapter(codelldb_path, liblldb_path) - else - return cfg.get_codelldb_adapter("codelldb", "") - end - end, - }, - } - - -- ─── Which-Key Specifications ────────────────────────────────── - local ok_wk, wk = pcall(require, "which-key") - if ok_wk then - wk.setup({ - preset = "modern", - delay = 300, - win = { - border = "rounded", - padding = { 1, 2 }, - title = true, - title_pos = "center", - zindex = 1000, - }, - layout = { - width = { min = 20 }, - spacing = 3, - }, - keys = { - scroll_down = "", - scroll_up = "", - }, - show_help = true, - show_keys = true, - triggers = { - { "", mode = "nxsot" }, - }, - }) - wk.add({ - { "f", group = "Find", icon = " " }, - { "e", group = "Explorer", icon = "󰙅 " }, - { "a", group = "Harpoon", icon = "󰛢 " }, - { "b", group = "Buffer", icon = "󰓩 " }, - { "l", group = "LSP", icon = " " }, - { "c", group = "Code", icon = "󰅩 " }, - { "C", group = "CMake/Build", icon = " " }, - { "r", group = "Rust", icon = "󱘗 " }, - { "G", group = "Go", icon = " " }, - { "t", group = "Test", icon = "󰙨 " }, - { "d", group = "Debug", icon = " " }, - { "u", group = "UI/Toggles", icon = "󱄄 " }, - { "g", group = "Git", icon = "󰊢 " }, - { "s", group = "Swap/Search", icon = "󰔡 " }, - { "x", group = "Trouble/Diagnostics", icon = "󰅙 " }, - { "n", group = "Noice", icon = "󰎟 " }, - { "w", icon = "󰆓 " }, - { "q", icon = "󰩈 " }, - { "L", icon = "󰒲 " }, - }) - - vim.keymap.set("n", "?", function() - wk.show({ global = false }) - end, { desc = "Buffer-local keymaps (which-key)" }) - end - - -- ─── Telescope Configuration & Keymaps ──────────────────────── - local ok_telescope, telescope = pcall(require, "telescope") - if ok_telescope then - local ok_actions, actions = pcall(require, "telescope.actions") - local mappings_i = {} - local mappings_n = {} - if ok_actions then - mappings_i = { - [""] = actions.move_selection_next, - [""] = actions.move_selection_previous, - [""] = actions.send_selected_to_qflist + actions.open_qflist, - [""] = actions.delete_buffer, - [""] = actions.close, - } - mappings_n = { - ["q"] = actions.close, - [""] = actions.send_selected_to_qflist + actions.open_qflist, - } - end - - local ok_themes, themes = pcall(require, "telescope.themes") - local ui_select_ext = ok_themes and { themes.get_dropdown() } or {} - - telescope.setup({ - defaults = { - 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" }, - preview = { - treesitter = false, - }, - winblend = 0, - border = {}, - borderchars = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" }, - color_devicons = true, - set_env = { ["COLORTERM"] = "truecolor" }, - file_ignore_patterns = { - "node_modules", - ".git/", - "target/", - "vendor/", - "__pycache__", - "%.lock", - }, - vimgrep_arguments = { - "rg", - "-L", - "--color=never", - "--no-heading", - "--with-filename", - "--line-number", - "--column", - "--smart-case", - }, - mappings = { - i = mappings_i, - n = mappings_n, - }, - }, - pickers = { - find_files = { - hidden = true, - find_command = { "rg", "--files", "--hidden", "--glob", "!**/.git/*" }, - }, - buffers = { - sort_mru = true, - sort_lastused = true, - }, - live_grep = { - additional_args = { "--hidden" }, - }, - }, - extensions = { - fzf = { - fuzzy = true, - override_generic_sorter = true, - override_file_sorter = true, - case_mode = "smart_case", - }, - ["ui-select"] = ui_select_ext, - }, - }) - - pcall(telescope.load_extension, "fzf") - pcall(telescope.load_extension, "ui-select") - - local map = vim.keymap.set - map("n", "ff", "Telescope find_files", { desc = "Find files" }) - map("n", "fg", "Telescope live_grep", { desc = "Live grep" }) - map("n", "fb", "Telescope buffers", { desc = "Find buffers" }) - map("n", "fh", "Telescope help_tags", { desc = "Help tags" }) - map("n", "fr", "Telescope oldfiles", { desc = "Recent files" }) - map("n", "fd", "Telescope diagnostics", { desc = "Diagnostics" }) - map("n", "fw", "Telescope grep_string", { desc = "Find word under cursor" }) - map("n", "fk", "Telescope keymaps", { desc = "Keymaps" }) - map("n", "fc", "Telescope commands", { desc = "Commands" }) - map("n", "fm", "Telescope marks", { desc = "Marks" }) - map("n", "fs", "Telescope lsp_document_symbols", { desc = "Document symbols" }) - map("n", "fS", "Telescope lsp_workspace_symbols", { desc = "Workspace symbols" }) - map("n", "gc", "Telescope git_commits", { desc = "Git commits" }) - map("n", "gC", "Telescope git_bcommits", { desc = "Git buffer commits" }) - map("n", "gt", "Telescope git_status", { desc = "Git status" }) - map("n", "", "Telescope resume", { desc = "Resume last search" }) - end - - -- ─── Treesitter Textobjects ─────────────────────────────────── - local ok_ts_configs, ts_configs = pcall(require, "nvim-treesitter.configs") - if ok_ts_configs then - ts_configs.setup({ - textobjects = { - select = { - enable = true, - lookahead = true, - keymaps = { - ["af"] = { query = "@function.outer", desc = "Around function" }, - ["if"] = { query = "@function.inner", desc = "Inside function" }, - ["ac"] = { query = "@class.outer", desc = "Around class" }, - ["ic"] = { query = "@class.inner", desc = "Inside class" }, - ["aa"] = { query = "@parameter.outer", desc = "Around argument" }, - ["ia"] = { query = "@parameter.inner", desc = "Inside argument" }, - ["al"] = { query = "@loop.outer", desc = "Around loop" }, - ["il"] = { query = "@loop.inner", desc = "Inside loop" }, - ["ai"] = { query = "@conditional.outer", desc = "Around conditional" }, - ["ii"] = { query = "@conditional.inner", desc = "Inside conditional" }, - ["ab"] = { query = "@block.outer", desc = "Around block" }, - ["ib"] = { query = "@block.inner", desc = "Inside block" }, - }, - }, - move = { - enable = true, - goto_next_start = { - ["]f"] = { query = "@function.outer", desc = "Next function start" }, - ["]c"] = { query = "@class.outer", desc = "Next class start" }, - ["]a"] = { query = "@parameter.inner", desc = "Next argument" }, - }, - goto_next_end = { - ["]F"] = { query = "@function.outer", desc = "Next function end" }, - ["]C"] = { query = "@class.outer", desc = "Next class end" }, - }, - goto_previous_start = { - ["[f"] = { query = "@function.outer", desc = "Previous function start" }, - ["[c"] = { query = "@class.outer", desc = "Previous class start" }, - ["[a"] = { query = "@parameter.inner", desc = "Previous argument" }, - }, - goto_previous_end = { - ["[F"] = { query = "@function.outer", desc = "Previous function end" }, - ["[C"] = { query = "@class.outer", desc = "Previous class end" }, - }, - }, - swap = { - enable = true, - swap_next = { - ["cx"] = { query = "@parameter.inner", desc = "Swap with next parameter" }, - }, - swap_previous = { - ["cX"] = { query = "@parameter.inner", desc = "Swap with previous parameter" }, - }, - }, - }, - }) - else - local ok_ts_to, ts_to = pcall(require, "nvim-treesitter-textobjects") - if ok_ts_to then - ts_to.setup({ - select = { - lookahead = true, - }, - move = { - set_jumps = true, - }, - }) - - local map = vim.keymap.set - local sel = require("nvim-treesitter-textobjects.select") - local mov = require("nvim-treesitter-textobjects.move") - local swp = require("nvim-treesitter-textobjects.swap") - - local select_maps = { - ["af"] = { query = "@function.outer", desc = "Around function" }, - ["if"] = { query = "@function.inner", desc = "Inside function" }, - ["ac"] = { query = "@class.outer", desc = "Around class" }, - ["ic"] = { query = "@class.inner", desc = "Inside class" }, - ["aa"] = { query = "@parameter.outer", desc = "Around argument" }, - ["ia"] = { query = "@parameter.inner", desc = "Inside argument" }, - ["al"] = { query = "@loop.outer", desc = "Around loop" }, - ["il"] = { query = "@loop.inner", desc = "Inside loop" }, - ["ai"] = { query = "@conditional.outer", desc = "Around conditional" }, - ["ii"] = { query = "@conditional.inner", desc = "Inside conditional" }, - ["ab"] = { query = "@block.outer", desc = "Around block" }, - ["ib"] = { query = "@block.inner", desc = "Inside block" }, - } - for k, v in pairs(select_maps) do - map({ "x", "o" }, k, function() - sel.select_textobject(v.query) - end, { desc = v.desc }) - end - - map({ "n", "x", "o" }, "]f", function() mov.goto_next_start("@function.outer") end, { desc = "Next function start" }) - map({ "n", "x", "o" }, "]c", function() mov.goto_next_start("@class.outer") end, { desc = "Next class start" }) - map({ "n", "x", "o" }, "]a", function() mov.goto_next_start("@parameter.inner") end, { desc = "Next argument" }) - map({ "n", "x", "o" }, "]F", function() mov.goto_next_end("@function.outer") end, { desc = "Next function end" }) - map({ "n", "x", "o" }, "]C", function() mov.goto_next_end("@class.outer") end, { desc = "Next class end" }) - map({ "n", "x", "o" }, "[f", function() mov.goto_previous_start("@function.outer") end, { desc = "Previous function start" }) - map({ "n", "x", "o" }, "[c", function() mov.goto_previous_start("@class.outer") end, { desc = "Previous class start" }) - map({ "n", "x", "o" }, "[a", function() mov.goto_previous_start("@parameter.inner") end, { desc = "Previous argument" }) - map({ "n", "x", "o" }, "[F", function() mov.goto_previous_end("@function.outer") end, { desc = "Previous function end" }) - map({ "n", "x", "o" }, "[C", function() mov.goto_previous_end("@class.outer") end, { desc = "Previous class end" }) - - map("n", "cx", function() swp.swap_next("@parameter.inner") end, { desc = "Swap with next parameter" }) - map("n", "cX", function() swp.swap_previous("@parameter.inner") end, { desc = "Swap with previous parameter" }) - end - end - - ''; - }; -} diff --git a/nvf/config/plugins.nix b/nvf/config/plugins.nix deleted file mode 100644 index 2a684250..00000000 --- a/nvf/config/plugins.nix +++ /dev/null @@ -1,211 +0,0 @@ -{ pkgs, ... }: { - config.vim = { - autocomplete = { - nvim-cmp.enable = false; - blink-cmp = { - enable = true; - friendly-snippets.enable = true; - setupOpts = { - keymap = { - preset = "default"; - "" = [ - "show" - "show_documentation" - "hide_documentation" - ]; - "" = [ "hide" ]; - "" = [ - "accept" - "fallback" - ]; - "" = [ - "select_next" - "snippet_forward" - "fallback" - ]; - "" = [ - "select_prev" - "snippet_backward" - "fallback" - ]; - "" = [ - "select_next" - "fallback" - ]; - "" = [ - "select_prev" - "fallback" - ]; - "" = [ - "scroll_documentation_up" - "fallback" - ]; - "" = [ - "scroll_documentation_down" - "fallback" - ]; - }; - appearance = { - nerd_font_variant = "mono"; - }; - completion = { - accept = { - auto_brackets = { - enabled = true; - }; - }; - menu = { - border = "rounded"; - winhighlight = "Normal:NormalFloat,FloatBorder:FloatBorder,CursorLine:PmenuSel,Search:None"; - }; - documentation = { - auto_show = true; - auto_show_delay_ms = 200; - window = { - border = "rounded"; - winhighlight = "Normal:NormalFloat,FloatBorder:FloatBorder"; - }; - }; - ghost_text = { - enabled = true; - }; - }; - signature = { - enabled = true; - window = { - border = "rounded"; - }; - }; - fuzzy = { - implementation = "prefer_rust"; - }; - }; - }; - }; - - ui = { - noice.enable = true; - colorizer.enable = true; - illuminate.enable = true; - fastaction.enable = true; - }; - - statusline.lualine.enable = true; - telescope.enable = true; - filetree.neo-tree.enable = false; # We use oil-nvim instead - binds.whichKey.enable = true; - - # Natively supported plugins mapped from your original config - git.gitsigns.enable = true; - tabline.nvimBufferline.enable = true; - comments.comment-nvim.enable = true; - notes.todo-comments.enable = true; - navigation.harpoon.enable = true; - utility.oil-nvim.enable = true; - utility.motion.flash-nvim.enable = true; - utility.motion.precognition.enable = true; - binds.hardtime-nvim.enable = true; - lsp.trouble.enable = true; - - extraPlugins = with pkgs.vimPlugins; { - snacks-nvim = { - package = snacks-nvim; - }; - dressing-nvim = { - package = dressing-nvim; - }; - vim-tmux-navigator = { - package = vim-tmux-navigator; - }; - neotest = { - package = neotest; - }; - neotest-go = { - package = neotest-go; - }; - neotest-rust = { - package = neotest-rust; - }; - neotest-gtest = { - package = neotest-gtest; - }; - neotest-golang = { - package = neotest-golang; - }; - fixcursorhold-nvim = { - package = FixCursorHold-nvim; - }; - persistence-nvim = { - package = persistence-nvim; - }; - - # Migrated plugins from lua config - conform-nvim = { - package = conform-nvim; - }; - nvim-dap-ui = { - package = nvim-dap-ui; - }; - nvim-nio = { - package = nvim-nio; - }; - nvim-dap-virtual-text = { - package = nvim-dap-virtual-text; - }; - nvim-dap-go = { - package = nvim-dap-go; - }; - nvim-dap-python = { - package = nvim-dap-python; - }; - better-escape-nvim = { - package = better-escape-nvim; - }; - mini-ai = { - package = mini-ai; - }; - indent-blankline-nvim = { - package = indent-blankline-nvim; - }; - nvim-notify = { - package = nvim-notify; - }; - nvim-autopairs = { - package = nvim-autopairs; - }; - nvim-surround = { - package = nvim-surround; - }; - clangd_extensions-nvim = { - package = clangd_extensions-nvim; - }; - go-nvim = { - package = go-nvim; - }; - guihua-lua = { - package = guihua-lua; - }; - nvim-ts-context-commentstring = { - package = nvim-ts-context-commentstring; - }; - nvim-treesitter-textobjects = { - package = nvim-treesitter-textobjects; - }; - nvim-lint = { - package = nvim-lint; - }; - telescope-fzf-native-nvim = { - package = telescope-fzf-native-nvim; - }; - telescope-ui-select-nvim = { - package = telescope-ui-select-nvim; - }; - rose-pine = { - package = rose-pine; - }; - nvim-lspconfig = { - package = nvim-lspconfig; - }; - }; - }; -} diff --git a/nvf/config/theme.nix b/nvf/config/theme.nix deleted file mode 100644 index 341c86af..00000000 --- a/nvf/config/theme.nix +++ /dev/null @@ -1,31 +0,0 @@ -{ pkgs, ... }: { - config.vim = { - extraPlugins = with pkgs.vimPlugins; { - base46 = { - package = pkgs.vimUtils.buildVimPlugin { - name = "base46"; - src = pkgs.fetchFromGitHub { - owner = "AvengeMedia"; - repo = "base46"; - rev = "83522e02c6c3b4ea901c4bffd9e0a5e0371c1fe6"; - hash = "sha256-kwDMC6rYzJYECmGnwn8JiAbffUq7hAXcUH6gPSkk2uI="; - }; - doCheck = false; - }; - }; - monoglow-nvim = { - package = pkgs.vimUtils.buildVimPlugin { - name = "monoglow.nvim"; - src = pkgs.fetchFromGitHub { - owner = "wnkz"; - repo = "monoglow.nvim"; - rev = "a249b1f55bfe9171e2f8aff7acf140f78ca4b2bb"; - hash = "sha256-EIslqnOIOLfQ7e7L1FvwfVfel6h+UPFIUcSgvp8zf0E="; - }; - doCheck = false; - }; - }; - }; - luaConfigRC.monoglow = "vim.cmd.colorscheme('monoglow')"; - }; -} diff --git a/nvf/config/tools/debugger.nix b/nvf/config/tools/debugger.nix new file mode 100644 index 00000000..03997dcc --- /dev/null +++ b/nvf/config/tools/debugger.nix @@ -0,0 +1,391 @@ +{ pkgs, ... }: { + config.vim = { + debugger.nvim-dap = { + enable = true; + ui = { + enable = true; + setupOpts = { + icons = { + expanded = "▾"; + collapsed = "▸"; + current_frame = "▸"; + }; + layouts = [ + { + elements = [ + { + id = "scopes"; + size = 0.35; + } + { + id = "breakpoints"; + size = 0.15; + } + { + id = "stacks"; + size = 0.25; + } + { + id = "watches"; + size = 0.25; + } + ]; + size = 40; + position = "left"; + } + { + elements = [ + { + id = "repl"; + size = 0.5; + } + { + id = "console"; + size = 0.5; + } + ]; + size = 0.25; + position = "bottom"; + } + ]; + floating = { + border = "rounded"; + mappings = { + close = [ + "q" + "" + ]; + }; + }; + }; + }; + }; + + extraPlugins = with pkgs.vimPlugins; { + nvim-nio = { + package = nvim-nio; + }; + nvim-dap-virtual-text = { + package = nvim-dap-virtual-text; + }; + nvim-dap-go = { + package = nvim-dap-go; + }; + nvim-dap-python = { + package = nvim-dap-python; + }; + }; + + luaConfigRC.dap_setup = '' + local ok_dap, dap = pcall(require, "dap") + if ok_dap then + -- Signs + vim.fn.sign_define("DapBreakpoint", { text = " ", texthl = "DiagnosticError", linehl = "", numhl = "" }) + vim.fn.sign_define("DapBreakpointCondition", { text = " ", texthl = "DiagnosticWarn", linehl = "", numhl = "" }) + vim.fn.sign_define("DapBreakpointRejected", { text = " ", texthl = "DiagnosticError", linehl = "", numhl = "" }) + vim.fn.sign_define("DapLogPoint", { text = " ", texthl = "DiagnosticInfo", linehl = "", numhl = "" }) + vim.fn.sign_define("DapStopped", { text = "▶ ", texthl = "DiagnosticOk", linehl = "DapStoppedLine", numhl = "" }) + vim.api.nvim_set_hl(0, "DapStoppedLine", { default = true, link = "Visual" }) + + -- DAP UI Listeners + local ok_dapui, dapui = pcall(require, "dapui") + if ok_dapui then + dap.listeners.after.event_initialized["dapui_config"] = function() + pcall(function() dapui.open({}) end) + end + dap.listeners.before.event_terminated["dapui_config"] = function() + pcall(function() dapui.close({}) end) + end + dap.listeners.before.event_exited["dapui_config"] = function() + pcall(function() dapui.close({}) end) + end + + vim.keymap.set("n", "du", function() dapui.toggle({}) end, { desc = "DAP: toggle UI" }) + vim.keymap.set({ "n", "v" }, "de", function() dapui.eval() end, { desc = "DAP: eval" }) + end + + -- DAP Virtual Text + local ok_dap_vt, dap_vt = pcall(require, "nvim-dap-virtual-text") + if ok_dap_vt then + dap_vt.setup({ + enabled = true, + enabled_commands = true, + highlight_changed_variables = true, + highlight_new_as_changed = false, + show_stop_reason = true, + commented = false, + only_first_definition = true, + all_references = false, + clear_on_continue = false, + display_callback = function(variable, buf, stackframe, node, options) + if options.virt_text_pos == "inline" then + return " = " .. variable.value + else + return variable.name .. " = " .. variable.value + end + end, + virt_text_pos = "eol", + all_frames = false, + virt_lines = false, + virt_text_win_col = nil, + }) + end + + -- DAP Go + local ok_dap_go, dap_go = pcall(require, "dap-go") + if ok_dap_go then + dap_go.setup({}) + end + + -- DAP Python + local ok_dap_py, dap_py = pcall(require, "dap-python") + if ok_dap_py then + local debugpy_path = vim.fn.exepath("python3") + dap_py.setup(debugpy_path) + end + + -- Keymaps + local map = vim.keymap.set + map("n", "db", function() dap.toggle_breakpoint() end, { desc = "DAP: toggle breakpoint" }) + map("n", "dB", function() dap.set_breakpoint(vim.fn.input("Breakpoint condition: ")) end, { desc = "DAP: conditional breakpoint" }) + map("n", "dc", function() dap.continue() end, { desc = "DAP: continue" }) + map("n", "dC", function() dap.run_to_cursor() end, { desc = "DAP: run to cursor" }) + map("n", "di", function() dap.step_into() end, { desc = "DAP: step into" }) + map("n", "do", function() dap.step_over() end, { desc = "DAP: step over" }) + map("n", "dO", function() dap.step_out() end, { desc = "DAP: step out" }) + map("n", "dp", function() dap.pause() end, { desc = "DAP: pause" }) + map("n", "dr", function() dap.restart() end, { desc = "DAP: restart" }) + map("n", "dl", function() dap.run_last() end, { desc = "DAP: run last" }) + map("n", "dt", function() dap.terminate() end, { desc = "DAP: terminate" }) + map("n", "dw", function() require("dap.ui.widgets").hover() end, { desc = "DAP: widgets" }) + + -- CodeLLDB Discovery & Adapter + local mason_path = vim.fn.stdpath("data") .. "/mason/packages/codelldb" + local codelldb_mason = mason_path .. "/extension/adapter/codelldb" + local codelldb_cmd = "codelldb" + + if vim.fn.filereadable(codelldb_mason) == 1 or vim.fn.executable(codelldb_mason) == 1 then + codelldb_cmd = codelldb_mason + elseif vim.fn.executable("codelldb") == 1 then + codelldb_cmd = vim.fn.exepath("codelldb") + end + + dap.adapters.codelldb = { + type = "server", + port = "''${port}", + executable = { + command = codelldb_cmd, + args = { "--port", "''${port}" }, + }, + } + dap.adapters.lldb = dap.adapters.codelldb + + local function get_binary() + local cwd = vim.fn.getcwd() + local candidates = {} + local seen = {} + + local search_dirs = { + "target/debug", "target/release", "build", "build/bin", + "bin", "out", "cmake-build-debug", "cmake-build-release", + } + local ignored_exts = { + o = true, a = true, so = true, dylib = true, dll = true, + d = true, rlib = true, rmeta = true, pdb = true, + txt = true, log = true, cmake = true, json = true, + ninja = true, lock = true, + } + local ignored_script_exts = { + sh = true, bash = true, zsh = true, lua = true, py = true, + pl = true, rb = true, js = true, ts = true, md = true, + } + + for _, dir in ipairs(search_dirs) do + local full_dir = cwd .. "/" .. dir + if vim.fn.isdirectory(full_dir) == 1 then + local files = vim.fn.glob(full_dir .. "/*", false, true) + for _, file in ipairs(files) do + if vim.fn.isdirectory(file) == 0 and vim.fn.executable(file) == 1 then + local ext = vim.fn.fnamemodify(file, ":e"):lower() + if not ignored_exts[ext] and not ignored_script_exts[ext] and not seen[file] then + seen[file] = true + table.insert(candidates, file) + end + end + end + end + end + + local root_files = vim.fn.glob(cwd .. "/*", false, true) + for _, file in ipairs(root_files) do + if vim.fn.isdirectory(file) == 0 and vim.fn.executable(file) == 1 then + local ext = vim.fn.fnamemodify(file, ":e"):lower() + if not ignored_exts[ext] and not ignored_script_exts[ext] and not file:match("/%.") and not seen[file] then + seen[file] = true + table.insert(candidates, file) + end + end + end + + local co = coroutine.running() + if co and #candidates > 0 then + local items = {} + for _, c in ipairs(candidates) do + table.insert(items, c) + end + table.insert(items, "Manually enter path...") + + vim.ui.select(items, { + prompt = "Select executable: ", + format_item = function(item) + if item == "Manually enter path..." then + return item + end + return vim.fn.fnamemodify(item, ":.") + end, + }, function(choice) + if not choice then + coroutine.resume(co, dap.ABORT) + elseif choice == "Manually enter path..." then + local manual = vim.fn.input("Path to executable: ", cwd .. "/", "file") + coroutine.resume(co, manual ~= "" and manual or dap.ABORT) + else + coroutine.resume(co, choice) + end + end) + return coroutine.yield() + elseif #candidates == 1 and not co then + return candidates[1] + else + local input = vim.fn.input("Path to executable: ", cwd .. "/", "file") + if input == "" then + return dap.ABORT or nil + end + return input + end + end + + local codelldb_configurations = { + { + name = "Launch Binary (Smart Picker)", + type = "codelldb", + request = "launch", + program = get_binary, + cwd = "''${workspaceFolder}", + stopOnEntry = false, + args = {}, + }, + { + name = "Launch Binary with Arguments", + type = "codelldb", + request = "launch", + program = get_binary, + cwd = "''${workspaceFolder}", + stopOnEntry = false, + args = function() + local args_str = vim.fn.input("Arguments: ") + if args_str == "" then + return {} + end + return vim.split(args_str, "%s+", { trimempty = true }) + end, + }, + { + name = "Attach to Process", + type = "codelldb", + request = "attach", + pid = function() + return require("dap.utils").pick_process() + end, + cwd = "''${workspaceFolder}", + }, + { + name = "Load Core Dump", + type = "codelldb", + request = "attach", + program = get_binary, + coreDumpPath = function() + local core_path = vim.fn.input("Path to core dump: ", vim.fn.getcwd() .. "/", "file") + return core_path ~= "" and core_path or nil + end, + cwd = "''${workspaceFolder}", + }, + } + + dap.configurations.cpp = codelldb_configurations + dap.configurations.c = codelldb_configurations + dap.configurations.rust = codelldb_configurations + + -- Delve (Go) + local dlv_path = "dlv" + local mason_dlv = vim.fn.stdpath("data") .. "/mason/packages/delve/dlv" + if vim.fn.filereadable(mason_dlv) == 1 or vim.fn.executable(mason_dlv) == 1 then + dlv_path = mason_dlv + elseif vim.fn.executable("dlv") == 1 then + dlv_path = vim.fn.exepath("dlv") + end + + dap.adapters.delve = { + type = "server", + port = "''${port}", + executable = { + command = dlv_path, + args = { "dap", "-l", "127.0.0.1:''${port}" }, + }, + } + dap.adapters.go = dap.adapters.delve + + dap.configurations.go = { + { + type = "delve", + name = "Debug (Smart Picker / Current File)", + request = "launch", + program = "''${file}", + }, + { + type = "delve", + name = "Debug Package", + request = "launch", + program = "''${fileDirname}", + }, + { + type = "delve", + name = "Debug with Arguments", + request = "launch", + program = "''${file}", + args = function() + local args_str = vim.fn.input("Arguments: ") + if args_str == "" then + return {} + end + return vim.split(args_str, "%s+", { trimempty = true }) + end, + }, + { + type = "delve", + name = "Debug Test", + request = "launch", + mode = "test", + program = "''${file}", + }, + { + type = "delve", + name = "Debug Test (Package)", + request = "launch", + mode = "test", + program = "./''${relativeFileDirname}", + }, + { + type = "delve", + name = "Attach to Process", + request = "attach", + mode = "local", + processId = function() + return require("dap.utils").pick_process() + end, + }, + } + end + ''; + }; +} diff --git a/nvf/config/tools/git.nix b/nvf/config/tools/git.nix new file mode 100644 index 00000000..c74ca5fd --- /dev/null +++ b/nvf/config/tools/git.nix @@ -0,0 +1,108 @@ +{ lib, ... }: { + config.vim = { + git.gitsigns = { + enable = true; + setupOpts = { + signs = { + add = { + text = "▎"; + }; + change = { + text = "▎"; + }; + delete = { + text = ""; + }; + topdelete = { + text = ""; + }; + changedelete = { + text = "▎"; + }; + untracked = { + text = "▎"; + }; + }; + signs_staged = { + add = { + text = "▎"; + }; + change = { + text = "▎"; + }; + delete = { + text = ""; + }; + topdelete = { + text = ""; + }; + changedelete = { + text = "▎"; + }; + }; + current_line_blame = false; + current_line_blame_opts = { + virt_text = true; + virt_text_pos = "eol"; + delay = 500; + ignore_whitespace = false; + }; + current_line_blame_formatter = ", - "; + preview_config = { + border = "rounded"; + style = "minimal"; + relative = "cursor"; + row = 0; + col = 1; + }; + on_attach = lib.mkLuaInline '' + function(bufnr) + local gs = package.loaded.gitsigns + + local function map(mode, l, r, opts) + opts = opts or {} + opts.buffer = bufnr + vim.keymap.set(mode, l, r, opts) + end + + -- Navigation + map("n", "]g", function() + if vim.wo.diff then + vim.cmd.normal({ "]c", bang = true }) + else + gs.nav_hunk("next") + end + end, { desc = "Next git hunk" }) + + map("n", "[g", function() + if vim.wo.diff then + vim.cmd.normal({ "[c", bang = true }) + else + gs.nav_hunk("prev") + end + end, { desc = "Previous git hunk" }) + + -- Actions + map("n", "gs", gs.stage_hunk, { desc = "Git: stage hunk" }) + map("n", "gr", gs.reset_hunk, { desc = "Git: reset hunk" }) + map("v", "gs", function() gs.stage_hunk({ vim.fn.line("."), vim.fn.line("v") }) end, { desc = "Git: stage hunk" }) + map("v", "gr", function() gs.reset_hunk({ vim.fn.line("."), vim.fn.line("v") }) end, { desc = "Git: reset hunk" }) + map("n", "gS", gs.stage_buffer, { desc = "Git: stage buffer" }) + map("n", "gR", gs.reset_buffer, { desc = "Git: reset buffer" }) + map("n", "gu", gs.undo_stage_hunk, { desc = "Git: undo stage hunk" }) + map("n", "gp", gs.preview_hunk, { desc = "Git: preview hunk" }) + map("n", "gb", function() gs.blame_line({ full = true }) end, { desc = "Git: blame line" }) + map("n", "gB", gs.toggle_current_line_blame, { desc = "Git: toggle line blame" }) + map("n", "gd", gs.diffthis, { desc = "Git: diff this" }) + map("n", "gD", function() gs.diffthis("~") end, { desc = "Git: diff this ~" }) + + -- Text object + map({ "o", "x" }, "ih", ":Gitsigns select_hunk", { desc = "Select git hunk" }) + end + ''; + }; + }; + + utility.diffview-nvim.enable = true; + }; +} diff --git a/nvf/config/tools/navigation.nix b/nvf/config/tools/navigation.nix new file mode 100644 index 00000000..0a9afddc --- /dev/null +++ b/nvf/config/tools/navigation.nix @@ -0,0 +1,358 @@ +{ pkgs, lib, ... }: { + config.vim = { + utility.oil-nvim = { + enable = true; + setupOpts = { + default_file_explorer = true; + columns = [ + "icon" + "size" + ]; + buf_options = { + buflisted = false; + bufhidden = "hide"; + }; + win_options = { + wrap = false; + signcolumn = "no"; + cursorcolumn = false; + foldcolumn = "0"; + spell = false; + list = false; + conceallevel = 3; + concealcursor = "nvic"; + }; + delete_to_trash = true; + skip_confirm_for_simple_edits = true; + prompt_save_on_select_new_entry = true; + cleanup_delay_ms = 2000; + lsp_file_methods = { + timeout_ms = 1000; + autosave_changes = false; + }; + constrain_cursor = "editable"; + watch_for_changes = true; + keymaps = { + "g?" = "actions.show_help"; + "" = "actions.select"; + "" = { + "@1" = "actions.select"; + opts = { + vertical = true; + }; + desc = "Open in vsplit"; + }; + "" = { + "@1" = "actions.select"; + opts = { + horizontal = true; + }; + desc = "Open in hsplit"; + }; + "" = { + "@1" = "actions.select"; + opts = { + tab = true; + }; + desc = "Open in new tab"; + }; + "" = "actions.preview"; + "" = "actions.close"; + "" = "actions.refresh"; + "-" = "actions.parent"; + "_" = "actions.open_cwd"; + "`" = "actions.cd"; + "~" = { + "@1" = "actions.cd"; + opts = { + scope = "tab"; + }; + desc = ":tcd to the directory"; + }; + "gs" = "actions.change_sort"; + "gx" = "actions.open_external"; + "g." = "actions.toggle_hidden"; + "g\\" = "actions.toggle_trash"; + }; + use_default_keymaps = false; + view_options = { + show_hidden = true; + is_hidden_file = lib.mkLuaInline '' + function(name, _) + return vim.startswith(name, ".") + end + ''; + is_always_hidden = lib.mkLuaInline '' + function(name, _) + return name == ".." or name == ".git" + end + ''; + natural_order = true; + case_insensitive = false; + sort = [ + { + "@1" = "type"; + "@2" = "asc"; + } + { + "@1" = "name"; + "@2" = "asc"; + } + ]; + }; + float = { + padding = 2; + max_width = 90; + max_height = 0; + border = "rounded"; + win_options = { + winblend = 0; + }; + }; + }; + }; + + navigation.harpoon = { + enable = true; + setupOpts = { + settings = { + save_on_toggle = true; + sync_on_ui_close = true; + key = lib.mkLuaInline '' + function() + return vim.uv.cwd() + end + ''; + }; + }; + }; + + utility.motion = { + flash-nvim.enable = true; + precognition = { + enable = true; + setupOpts = { + start_visible = false; + show_blank = true; + }; + }; + }; + + binds.hardtime-nvim = { + enable = true; + setupOpts = { + max_count = 3; + disabled_filetypes = [ + "qf" + "netrw" + "NvimTree" + "lazy" + "mason" + "oil" + "snacks_dashboard" + "dashboard" + "trouble" + "harpoon" + "help" + "undotree" + "dapui_scopes" + "dapui_breakpoints" + "dapui_stacks" + "dapui_watches" + "dap-repl" + "dapui_console" + ]; + disabled_buftypes = [ + "nofile" + "prompt" + "quickfix" + "terminal" + ]; + }; + }; + + extraPlugins = with pkgs.vimPlugins; { + vim-tmux-navigator = { + package = vim-tmux-navigator; + }; + }; + + globals = { + tmux_navigator_no_mappings = 1; + tmux_navigator_save_on_switch = 2; + tmux_navigator_disable_when_zoomed = 1; + tmux_navigator_preserve_zoom = 1; + }; + + keymaps = [ + # Oil + { + key = "e"; + mode = "n"; + action = "Oil"; + desc = "Open file explorer (Oil)"; + } + { + key = "-"; + mode = "n"; + action = "Oil"; + desc = "Open parent directory"; + } + + # Harpoon + { + key = "a"; + mode = "n"; + action = "function() require('harpoon'):list():add() end"; + lua = true; + desc = "Harpoon: add file"; + } + { + key = ""; + mode = "n"; + action = "function() local h = require('harpoon'); h.ui:toggle_quick_menu(h:list()) end"; + lua = true; + desc = "Harpoon: toggle menu"; + } + { + key = "1"; + mode = "n"; + action = "function() require('harpoon'):list():select(1) end"; + lua = true; + desc = "Harpoon: file 1"; + } + { + key = "2"; + mode = "n"; + action = "function() require('harpoon'):list():select(2) end"; + lua = true; + desc = "Harpoon: file 2"; + } + { + key = "3"; + mode = "n"; + action = "function() require('harpoon'):list():select(3) end"; + lua = true; + desc = "Harpoon: file 3"; + } + { + key = "4"; + mode = "n"; + action = "function() require('harpoon'):list():select(4) end"; + lua = true; + desc = "Harpoon: file 4"; + } + { + key = "5"; + mode = "n"; + action = "function() require('harpoon'):list():select(5) end"; + lua = true; + desc = "Harpoon: file 5"; + } + { + key = "[h"; + mode = "n"; + action = "function() require('harpoon'):list():prev() end"; + lua = true; + desc = "Harpoon: previous file"; + } + { + key = "]h"; + mode = "n"; + action = "function() require('harpoon'):list():next() end"; + lua = true; + desc = "Harpoon: next file"; + } + + # Flash + { + key = "s"; + mode = [ + "n" + "x" + "o" + ]; + action = "function() require('flash').jump() end"; + lua = true; + desc = "Flash jump"; + } + { + key = "S"; + mode = [ + "n" + "x" + "o" + ]; + action = "function() require('flash').treesitter() end"; + lua = true; + desc = "Flash treesitter"; + } + { + key = "r"; + mode = "o"; + action = "function() require('flash').remote() end"; + lua = true; + desc = "Remote Flash"; + } + { + key = "R"; + mode = [ + "o" + "x" + ]; + action = "function() require('flash').treesitter_search() end"; + lua = true; + desc = "Treesitter search"; + } + { + key = ""; + mode = "c"; + action = "function() require('flash').toggle() end"; + lua = true; + desc = "Toggle Flash search"; + } + + # Hardtime & Precognition toggles + { + key = "uh"; + mode = "n"; + action = "function() require('hardtime').toggle() end"; + lua = true; + desc = "Toggle Hardtime (motion trainer)"; + } + { + key = "up"; + mode = "n"; + action = "function() require('precognition').toggle() end"; + lua = true; + desc = "Toggle Precognition (motion guide)"; + } + + # Tmux Navigator + { + key = ""; + mode = "n"; + action = "TmuxNavigateLeft"; + desc = "Navigate left (tmux-aware)"; + } + { + key = ""; + mode = "n"; + action = "TmuxNavigateDown"; + desc = "Navigate down (tmux-aware)"; + } + { + key = ""; + mode = "n"; + action = "TmuxNavigateUp"; + desc = "Navigate up (tmux-aware)"; + } + { + key = ""; + mode = "n"; + action = "TmuxNavigateRight"; + desc = "Navigate right (tmux-aware)"; + } + ]; + }; +} diff --git a/nvf/config/tools/telescope.nix b/nvf/config/tools/telescope.nix new file mode 100644 index 00000000..fa0443d2 --- /dev/null +++ b/nvf/config/tools/telescope.nix @@ -0,0 +1,219 @@ +{ pkgs, lib, ... }: { + config.vim = { + telescope.enable = true; + + extraPlugins = with pkgs.vimPlugins; { + telescope-fzf-native-nvim = { + package = telescope-fzf-native-nvim; + }; + telescope-ui-select-nvim = { + package = telescope-ui-select-nvim; + }; + }; + + luaConfigRC.telescope_config = '' + local ok_telescope, telescope = pcall(require, "telescope") + if ok_telescope then + local ok_actions, actions = pcall(require, "telescope.actions") + local mappings_i = {} + local mappings_n = {} + if ok_actions then + mappings_i = { + [""] = actions.move_selection_next, + [""] = actions.move_selection_previous, + [""] = actions.send_selected_to_qflist + actions.open_qflist, + [""] = actions.delete_buffer, + [""] = actions.close, + } + mappings_n = { + ["q"] = actions.close, + [""] = actions.send_selected_to_qflist + actions.open_qflist, + } + end + + local ok_themes, themes = pcall(require, "telescope.themes") + local ui_select_ext = ok_themes and { themes.get_dropdown() } or {} + + telescope.setup({ + defaults = { + 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" }, + preview = { + treesitter = false, + }, + winblend = 0, + border = {}, + borderchars = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" }, + color_devicons = true, + set_env = { ["COLORTERM"] = "truecolor" }, + file_ignore_patterns = { + "node_modules", + ".git/", + "target/", + "vendor/", + "__pycache__", + "%.lock", + }, + vimgrep_arguments = { + "rg", + "-L", + "--color=never", + "--no-heading", + "--with-filename", + "--line-number", + "--column", + "--smart-case", + }, + mappings = { + i = mappings_i, + n = mappings_n, + }, + }, + pickers = { + find_files = { + hidden = true, + find_command = { "rg", "--files", "--hidden", "--glob", "!**/.git/*" }, + }, + buffers = { + sort_mru = true, + sort_lastused = true, + }, + live_grep = { + additional_args = { "--hidden" }, + }, + }, + extensions = { + fzf = { + fuzzy = true, + override_generic_sorter = true, + override_file_sorter = true, + case_mode = "smart_case", + }, + ["ui-select"] = ui_select_ext, + }, + }) + + pcall(telescope.load_extension, "fzf") + pcall(telescope.load_extension, "ui-select") + end + ''; + + keymaps = [ + { + key = "ff"; + mode = "n"; + action = "Telescope find_files"; + desc = "Find files"; + } + { + key = "fg"; + mode = "n"; + action = "Telescope live_grep"; + desc = "Live grep"; + } + { + key = "fb"; + mode = "n"; + action = "Telescope buffers"; + desc = "Find buffers"; + } + { + key = "fh"; + mode = "n"; + action = "Telescope help_tags"; + desc = "Help tags"; + } + { + key = "fr"; + mode = "n"; + action = "Telescope oldfiles"; + desc = "Recent files"; + } + { + key = "fd"; + mode = "n"; + action = "Telescope diagnostics"; + desc = "Diagnostics"; + } + { + key = "fw"; + mode = "n"; + action = "Telescope grep_string"; + desc = "Find word under cursor"; + } + { + key = "fk"; + mode = "n"; + action = "Telescope keymaps"; + desc = "Keymaps"; + } + { + key = "fc"; + mode = "n"; + action = "Telescope commands"; + desc = "Commands"; + } + { + key = "fm"; + mode = "n"; + action = "Telescope marks"; + desc = "Marks"; + } + { + key = "fs"; + mode = "n"; + action = "Telescope lsp_document_symbols"; + desc = "Document symbols"; + } + { + key = "fS"; + mode = "n"; + action = "Telescope lsp_workspace_symbols"; + desc = "Workspace symbols"; + } + { + key = "gc"; + mode = "n"; + action = "Telescope git_commits"; + desc = "Git commits"; + } + { + key = "gC"; + mode = "n"; + action = "Telescope git_bcommits"; + desc = "Git buffer commits"; + } + { + key = "gt"; + mode = "n"; + action = "Telescope git_status"; + desc = "Git status"; + } + { + key = ""; + mode = "n"; + action = "Telescope resume"; + desc = "Resume last search"; + } + ]; + }; +} diff --git a/nvf/config/tools/testing.nix b/nvf/config/tools/testing.nix new file mode 100644 index 00000000..9f055393 --- /dev/null +++ b/nvf/config/tools/testing.nix @@ -0,0 +1,105 @@ +{ pkgs, ... }: { + config.vim = { + extraPlugins = with pkgs.vimPlugins; { + neotest = { + package = neotest; + }; + neotest-go = { + package = neotest-go; + }; + neotest-rust = { + package = neotest-rust; + }; + neotest-gtest = { + package = neotest-gtest; + }; + neotest-golang = { + package = neotest-golang; + }; + fixcursorhold-nvim = { + package = FixCursorHold-nvim; + }; + }; + + luaConfigRC.neotest_setup = '' + local ok_neotest, neotest = pcall(require, "neotest") + if ok_neotest then + local adapters = {} + + -- Go Adapter + local ok_golang, neotest_golang = pcall(require, "neotest-golang") + if ok_golang then + table.insert( + adapters, + neotest_golang({ + runner = "go", + go_test_args = { "-v", "-race", "-count=1" }, + dap_go_opts = {}, + }) + ) + else + local ok_go, neotest_go = pcall(require, "neotest-go") + if ok_go then + table.insert( + adapters, + neotest_go({ + experimental = { test_table = true }, + args = { "-race", "-v", "-count=1" }, + }) + ) + end + end + + -- Rust Adapter + local ok_rustacean, rustacean_neotest = pcall(require, "rustaceanvim.neotest") + if ok_rustacean then + table.insert(adapters, rustacean_neotest) + else + local ok_rust, neotest_rust = pcall(require, "neotest-rust") + if ok_rust then + table.insert( + adapters, + neotest_rust({ + args = { "--no-capture" }, + dap_adapter = "codelldb", + }) + ) + end + end + + -- C++ Adapter + local ok_gtest, neotest_gtest = pcall(require, "neotest-gtest") + if ok_gtest then + if type(neotest_gtest) == "function" then + table.insert(adapters, neotest_gtest({})) + elseif type(neotest_gtest.setup) == "function" then + table.insert(adapters, neotest_gtest.setup({})) + else + table.insert(adapters, neotest_gtest) + end + end + + neotest.setup({ + adapters = adapters, + status = { virtual_text = true, signs = true }, + output = { open_on_run = true }, + output_panel = { open_on_run = false }, + quickfix = { open = false }, + }) + + local map = vim.keymap.set + map("n", "tt", function() neotest.run.run() end, { desc = "Test: run nearest" }) + map("n", "tf", function() neotest.run.run(vim.fn.expand("%")) end, { desc = "Test: run current file" }) + map("n", "ta", function() neotest.run.run(vim.fn.getcwd()) end, { desc = "Test: run all (project)" }) + map("n", "tw", function() neotest.watch.toggle() end, { desc = "Test: toggle watch mode" }) + map("n", "td", function() neotest.run.run({ strategy = "dap" }) end, { desc = "Test: debug nearest (DAP)" }) + map("n", "ts", function() neotest.summary.toggle() end, { desc = "Test: toggle summary" }) + map("n", "to", function() neotest.output.open({ enter = true }) end, { desc = "Test: open output window" }) + map("n", "tO", function() neotest.output_panel.toggle() end, { desc = "Test: toggle output panel" }) + map("n", "tS", function() neotest.run.stop() end, { desc = "Test: stop running test" }) + map("n", "]T", function() neotest.jump.next({ status = "failed" }) end, { desc = "Test: jump next failed" }) + map("n", "[T", function() neotest.jump.prev({ status = "failed" }) end, { desc = "Test: jump previous failed" }) + end + ''; + }; +} diff --git a/nvf/config/ui/noice.nix b/nvf/config/ui/noice.nix new file mode 100644 index 00000000..9b49dd66 --- /dev/null +++ b/nvf/config/ui/noice.nix @@ -0,0 +1,99 @@ +{ pkgs, lib, ... }: { + config.vim = { + ui.noice = { + enable = true; + setupOpts = { + lsp = { + override = { + "vim.lsp.util.convert_input_to_markdown_lines" = true; + "vim.lsp.util.stylize_markdown" = true; + "cmp.entry.get_documentation" = true; + }; + hover = { + enabled = true; + }; + signature = { + enabled = true; + }; + }; + presets = { + bottom_search = true; + command_palette = true; + long_message_to_split = true; + inc_rename = true; + lsp_doc_border = true; + }; + routes = [ + { + filter = { + event = "msg_show"; + kind = ""; + find = "written"; + }; + opts = { + skip = true; + }; + } + ]; + }; + }; + + notify.nvim-notify = { + enable = true; + setupOpts = { + stages = "fade_in_slide_out"; + timeout = 3000; + max_height = lib.mkLuaInline "function() return math.floor(vim.o.lines * 0.75) end"; + max_width = lib.mkLuaInline "function() return math.floor(vim.o.columns * 0.75) end"; + on_open = lib.mkLuaInline "function(win) vim.api.nvim_win_set_config(win, { zindex = 100 }) end"; + render = "wrapped-compact"; + top_down = true; + }; + }; + + extraPlugins = with pkgs.vimPlugins; { + dressing-nvim = { + package = dressing-nvim; + setup = "require('dressing').setup({})"; + }; + }; + + keymaps = [ + { + key = ""; + mode = "c"; + action = "function() require('noice').redirect(vim.fn.getcmdline()) end"; + lua = true; + desc = "Redirect cmdline"; + } + { + key = "snl"; + mode = "n"; + action = "function() require('noice').cmd('last') end"; + lua = true; + desc = "Noice last message"; + } + { + key = "snh"; + mode = "n"; + action = "function() require('noice').cmd('history') end"; + lua = true; + desc = "Noice history"; + } + { + key = "sna"; + mode = "n"; + action = "function() require('noice').cmd('all') end"; + lua = true; + desc = "Noice all"; + } + { + key = "snd"; + mode = "n"; + action = "function() require('noice').cmd('dismiss') end"; + lua = true; + desc = "Dismiss all"; + } + ]; + }; +} diff --git a/nvf/config/ui/statusline.nix b/nvf/config/ui/statusline.nix new file mode 100644 index 00000000..2e906a17 --- /dev/null +++ b/nvf/config/ui/statusline.nix @@ -0,0 +1,295 @@ +{ ... }: { + config.vim = { + statusline.lualine.enable = true; + + luaConfigRC.statusline_eviline = '' + local lualine = require("lualine") + + local function hl(group, attr, fallback) + local ok, value = pcall(vim.api.nvim_get_hl, 0, { name = group, link = true }) + if not ok or not value[attr] then + return fallback + end + return string.format("#%06x", value[attr]) + end + + local function base46_palette() + local ok, base46 = pcall(require, "base46") + if not ok then + return nil, nil + end + + local theme_name = base46.current_theme + if not theme_name and vim.g.colors_name then + theme_name = vim.g.colors_name:match("^base46%-(.+)$") + end + + local theme = theme_name and base46.theme_tables[theme_name] + if not theme and theme_name and base46.get_builtin_theme then + theme = base46.get_builtin_theme(theme_name) + end + + if not theme then + return nil, nil + end + return theme.base_30 or {}, theme.base_16 or {} + end + + local function colors() + local base30, base16 = base46_palette() + if base30 then + return { + bg = base30.statusline_bg or base30.black or base16.base00 or "#202328", + fg = base30.white or base16.base05 or "#bbc2cf", + muted = base30.grey_fg or base16.base03 or "#5c6370", + yellow = base30.yellow or base30.sun or base16.base0A or "#ECBE7B", + cyan = base30.cyan or base30.teal or base16.base0C or "#008080", + green = base30.green or base30.vibrant_green or base16.base0B or "#98be65", + orange = base30.orange or base16.base09 or "#FF8800", + violet = base30.purple or base30.dark_purple or base16.base0E or "#a9a1e1", + magenta = base30.pink or base30.baby_pink or base30.purple or base16.base0E or "#c678dd", + blue = base30.blue or base30.nord_blue or base16.base0D or "#51afef", + red = base30.red or base16.base08 or "#ec5f67", + } + end + + return { + bg = hl("StatusLine", "bg", hl("Normal", "bg", "#202328")), + fg = hl("StatusLine", "fg", hl("Normal", "fg", "#bbc2cf")), + muted = hl("Comment", "fg", "#5c6370"), + yellow = hl("DiagnosticWarn", "fg", "#ECBE7B"), + cyan = hl("DiagnosticInfo", "fg", "#008080"), + green = hl("String", "fg", "#98be65"), + orange = hl("Number", "fg", "#FF8800"), + violet = hl("DiagnosticHint", "fg", "#a9a1e1"), + magenta = hl("Keyword", "fg", "#c678dd"), + blue = hl("Function", "fg", "#51afef"), + red = hl("DiagnosticError", "fg", "#ec5f67"), + } + end + + local conditions = { + buffer_not_empty = function() + return vim.fn.empty(vim.fn.expand("%:t")) ~= 1 + end, + hide_in_width = function() + return vim.fn.winwidth(0) > 80 + end, + check_git_workspace = function() + local filepath = vim.fn.expand("%:p:h") + local gitdir = vim.fn.finddir(".git", filepath .. ";") + return gitdir and #gitdir > 0 and #gitdir < #filepath + end, + } + + local function mode_color() + local c = colors() + local mode_colors = { + n = c.red, + no = c.red, + nov = c.red, + noV = c.red, + ["no\22"] = c.red, + niI = c.red, + niR = c.red, + niV = c.red, + nt = c.red, + i = c.green, + ic = c.yellow, + ix = c.yellow, + v = c.blue, + V = c.blue, + ["\22"] = c.blue, + s = c.orange, + S = c.orange, + ["\19"] = c.orange, + c = c.magenta, + cv = c.red, + ce = c.red, + R = c.violet, + Rc = c.violet, + Rx = c.violet, + Rv = c.violet, + Rvc = c.violet, + Rvx = c.violet, + r = c.cyan, + rm = c.cyan, + ["r?"] = c.cyan, + ["!"] = c.red, + t = c.red, + } + return mode_colors[vim.fn.mode()] or c.red + end + + local function lsp_name() + local clients = vim.lsp.get_clients({ bufnr = 0 }) + if #clients == 0 then + return "No Active Lsp" + end + + local names = {} + for _, client in ipairs(clients) do + names[#names + 1] = client.name + end + return table.concat(names, ", ") + end + + local function setup_eviline() + local c = colors() + local config = { + options = { + component_separators = "", + section_separators = "", + globalstatus = true, + disabled_filetypes = { + statusline = { "dashboard", "snacks_dashboard", "lazy", "mason" }, + }, + theme = { + normal = { c = { fg = c.fg, bg = c.bg } }, + inactive = { c = { fg = c.fg, bg = c.bg } }, + }, + refresh = { + statusline = 100, + }, + }, + sections = { + lualine_a = {}, + lualine_b = {}, + lualine_c = {}, + lualine_x = {}, + lualine_y = {}, + lualine_z = {}, + }, + inactive_sections = { + lualine_a = {}, + lualine_b = {}, + lualine_c = {}, + lualine_x = {}, + lualine_y = {}, + lualine_z = {}, + }, + } + + local function ins_left(component) + table.insert(config.sections.lualine_c, component) + end + + local function ins_right(component) + table.insert(config.sections.lualine_x, component) + end + + ins_left({ + function() + return "▊" + end, + color = function() + return { fg = mode_color() } + end, + padding = { left = 0, right = 1 }, + }) + + ins_left({ + function() + return "" + end, + color = function() + return { fg = mode_color(), gui = "bold" } + end, + padding = { right = 1 }, + }) + + ins_left({ + "filesize", + cond = conditions.buffer_not_empty, + color = { fg = c.cyan, gui = "bold" }, + }) + + ins_left({ + "filename", + cond = conditions.buffer_not_empty, + color = { fg = c.magenta, gui = "bold" }, + symbols = { modified = " ●", readonly = " 󰌾", unnamed = "[No Name]", newfile = "[New]" }, + }) + + ins_left({ "location", color = { fg = c.blue, gui = "bold" } }) + ins_left({ "progress", color = { fg = c.violet, gui = "bold" } }) + + ins_left({ + "diagnostics", + sources = { "nvim_diagnostic" }, + symbols = { error = " ", warn = " ", info = " ", hint = "󰌵 " }, + diagnostics_color = { + error = { fg = c.red }, + warn = { fg = c.yellow }, + info = { fg = c.cyan }, + hint = { fg = c.violet }, + }, + }) + + ins_left({ + function() + return "%=" + end, + }) + + ins_left({ + lsp_name, + icon = " LSP:", + cond = conditions.hide_in_width, + color = { fg = c.green, gui = "bold" }, + }) + + ins_right({ + "o:encoding", + fmt = string.upper, + cond = conditions.hide_in_width, + color = { fg = c.yellow, gui = "bold" }, + }) + + ins_right({ + "fileformat", + fmt = string.upper, + icons_enabled = false, + cond = conditions.hide_in_width, + color = { fg = c.cyan, gui = "bold" }, + }) + + ins_right({ + "branch", + icon = " ", + cond = conditions.check_git_workspace, + color = { fg = c.violet, gui = "bold" }, + }) + + ins_right({ + "diff", + symbols = { added = " ", modified = "󰝤 ", removed = " " }, + diff_color = { + added = { fg = c.green }, + modified = { fg = c.orange }, + removed = { fg = c.red }, + }, + cond = conditions.hide_in_width, + }) + + ins_right({ + function() + return "▊" + end, + color = function() + return { fg = mode_color() } + end, + padding = { left = 1 }, + }) + + lualine.setup(config) + end + + setup_eviline() + vim.api.nvim_create_autocmd("ColorScheme", { + group = vim.api.nvim_create_augroup("EvilineThemeRefresh", { clear = true }), + callback = setup_eviline, + }) + ''; + }; +} diff --git a/nvf/config/ui/tabline.nix b/nvf/config/ui/tabline.nix new file mode 100644 index 00000000..75286697 --- /dev/null +++ b/nvf/config/ui/tabline.nix @@ -0,0 +1,114 @@ +{ lib, ... }: { + config.vim = { + tabline.nvimBufferline = { + enable = true; + setupOpts = { + options = { + mode = "buffers"; + themable = true; + numbers = "none"; + close_command = "bdelete! %d"; + right_mouse_command = "bdelete! %d"; + left_mouse_command = "buffer %d"; + middle_mouse_command = null; + indicator = { + icon = "▎"; + style = "icon"; + }; + buffer_close_icon = "󰅖"; + modified_icon = "●"; + close_icon = ""; + left_trunc_marker = ""; + right_trunc_marker = ""; + max_name_length = 30; + max_prefix_length = 30; + truncate_names = true; + tab_size = 21; + diagnostics = "nvim_lsp"; + diagnostics_update_in_insert = false; + diagnostics_indicator = lib.mkLuaInline '' + function(count, level) + local icon = level:match("error") and " " or " " + return " " .. icon .. count + end + ''; + offsets = [ + { + filetype = "oil"; + text = " File Explorer"; + highlight = "Directory"; + text_align = "left"; + separator = true; + } + ]; + color_icons = true; + show_buffer_icons = true; + show_buffer_close_icons = true; + show_close_icon = false; + show_tab_indicators = true; + show_duplicate_prefix = true; + persist_buffer_sort = true; + separator_style = "thin"; + enforce_regular_tabs = false; + always_show_bufferline = true; + hover = { + enabled = true; + delay = 200; + reveal = [ "close" ]; + }; + }; + }; + }; + + keymaps = [ + { + key = "bp"; + mode = "n"; + action = "BufferLineTogglePin"; + desc = "Pin buffer"; + } + { + key = "bx"; + mode = "n"; + action = "BufferLineCloseOthers"; + desc = "Close other buffers"; + } + { + key = "br"; + mode = "n"; + action = "BufferLineCloseRight"; + desc = "Close buffers to the right"; + } + { + key = "bl"; + mode = "n"; + action = "BufferLineCloseLeft"; + desc = "Close buffers to the left"; + } + { + key = ""; + mode = "n"; + action = "BufferLineCyclePrev"; + desc = "Previous buffer"; + } + { + key = ""; + mode = "n"; + action = "BufferLineCycleNext"; + desc = "Next buffer"; + } + { + key = "[b"; + mode = "n"; + action = "BufferLineCyclePrev"; + desc = "Previous buffer"; + } + { + key = "]b"; + mode = "n"; + action = "BufferLineCycleNext"; + desc = "Next buffer"; + } + ]; + }; +} diff --git a/nvf/config/ui/theme.nix b/nvf/config/ui/theme.nix new file mode 100644 index 00000000..d5787657 --- /dev/null +++ b/nvf/config/ui/theme.nix @@ -0,0 +1,156 @@ +{ pkgs, ... }: { + config.vim = { + extraPlugins = with pkgs.vimPlugins; { + base46 = { + package = pkgs.vimUtils.buildVimPlugin { + name = "base46"; + src = pkgs.fetchFromGitHub { + owner = "AvengeMedia"; + repo = "base46"; + rev = "83522e02c6c3b4ea901c4bffd9e0a5e0371c1fe6"; + hash = "sha256-kwDMC6rYzJYECmGnwn8JiAbffUq7hAXcUH6gPSkk2uI="; + }; + doCheck = false; + }; + }; + monoglow-nvim = { + package = pkgs.vimUtils.buildVimPlugin { + name = "monoglow.nvim"; + src = pkgs.fetchFromGitHub { + owner = "wnkz"; + repo = "monoglow.nvim"; + rev = "a249b1f55bfe9171e2f8aff7acf140f78ca4b2bb"; + hash = "sha256-EIslqnOIOLfQ7e7L1FvwfVfel6h+UPFIUcSgvp8zf0E="; + }; + doCheck = false; + }; + }; + rose-pine = { + package = rose-pine; + }; + }; + + luaConfigRC.theme_setup = '' + local ok_rp, rose_pine = pcall(require, "rose-pine") + if ok_rp then + rose_pine.setup({ + variant = "main", + dark_variant = "main", + dim_inactive_windows = false, + extend_background_behind_borders = true, + styles = { + bold = true, + italic = true, + transparency = true, + }, + highlight_groups = { + TelescopeBorder = { fg = "highlight_high", bg = "none" }, + TelescopeNormal = { bg = "none" }, + TelescopePromptNormal = { bg = "base" }, + TelescopeResultsNormal = { fg = "subtle", bg = "none" }, + TelescopeSelection = { fg = "text", bg = "base" }, + TelescopeSelectionCaret = { fg = "rose", bg = "rose" }, + FloatBorder = { fg = "highlight_high", bg = "surface" }, + NormalFloat = { bg = "surface" }, + CursorLine = { bg = "highlight_low" }, + CursorLineNr = { fg = "gold" }, + StatusLine = { fg = "love", bg = "love", blend = 10 }, + StatusLineNC = { fg = "subtle", bg = "surface" }, + WhichKeyFloat = { bg = "surface" }, + }, + }) + end + + local ok_base46, base46 = pcall(require, "base46") + if ok_base46 then + base46.setup({ + transparency = true, + set_background = true, + term_colors = true, + integrations = { + defaults = true, + syntax = true, + treesitter = true, + lsp = true, + telescope = true, + whichkey = true, + gitsigns = true, + bufferline = true, + devicons = true, + blink = true, + statusline = false, + neotest = true, + }, + }) + end + + package.preload["core.theme"] = function() + local base46_state_file = vim.fn.stdpath("state") .. "/base46-theme" + return { + base46_state_file = base46_state_file, + read_base46_theme = function() + if vim.fn.filereadable(base46_state_file) ~= 1 then return nil end + local lines = vim.fn.readfile(base46_state_file) + local theme = lines[1] + return theme and theme ~= "" and theme or nil + end, + write_base46_theme = function(theme) + vim.fn.mkdir(vim.fn.fnamemodify(base46_state_file, ":h"), "p") + vim.fn.writefile({ theme }, base46_state_file) + end, + } + end + + local theme_state = require("core.theme") + local saved_theme = theme_state.read_base46_theme() + + if saved_theme then + if saved_theme == "monoglow" or saved_theme == "rose-pine" then + vim.cmd.colorscheme(saved_theme) + else + if ok_base46 then + base46.load(saved_theme) + vim.api.nvim_exec_autocmds("ColorScheme", {}) + else + vim.cmd.colorscheme("monoglow") + end + end + else + vim.cmd.colorscheme("monoglow") + end + + local function pick_theme() + local themes = { "monoglow", "rose-pine" } + for _, path in ipairs(vim.api.nvim_get_runtime_file("lua/base46/themes/*.lua", true)) do + local name = vim.fn.fnamemodify(path, ":t:r") + themes[#themes + 1] = name + end + table.sort(themes) + + vim.ui.select(themes, { prompt = "Select theme: " }, function(choice) + if choice then + require("core.theme").write_base46_theme(choice) + if choice == "monoglow" or choice == "rose-pine" then + vim.cmd.colorscheme(choice) + else + local ok, b46 = pcall(require, "base46") + if ok then + b46.load(choice) + vim.api.nvim_exec_autocmds("ColorScheme", {}) + end + end + vim.notify("Theme: " .. choice, vim.log.levels.INFO) + end + end) + end + + vim.keymap.set("n", "ut", pick_theme, { desc = "Themes: choose theme" }) + vim.api.nvim_create_user_command("NvChadTheme", pick_theme, { desc = "Choose colorscheme" }) + package.preload["base46-themes"] = function() + return { + pick = pick_theme, + } + end + ''; + }; +} diff --git a/nvf/config/ui/visuals.nix b/nvf/config/ui/visuals.nix new file mode 100644 index 00000000..e92a1741 --- /dev/null +++ b/nvf/config/ui/visuals.nix @@ -0,0 +1,251 @@ +{ ... }: { + config.vim = { + visuals = { + nvim-web-devicons.enable = true; + indent-blankline = { + enable = true; + setupOpts = { + indent = { + char = "│"; + tab_char = "│"; + }; + scope = { + show_start = false; + show_end = false; + include = { + node_type = { + "*" = [ + "argument_list" + "arguments" + "assignment_statement" + "block" + "chunk" + "class" + "do_block" + "element" + "except" + "for" + "function" + "if_statement" + "method" + "object" + "return_statement" + "table" + "try" + "while" + ]; + }; + }; + }; + exclude = { + filetypes = [ + "help" + "dashboard" + "snacks_dashboard" + "lazy" + "mason" + "notify" + "oil" + "toggleterm" + ]; + }; + }; + }; + }; + + ui = { + fastaction.enable = true; + colorizer = { + enable = true; + setupOpts = { + filetypes = { + "*" = { }; + }; + user_default_options = { + RGB = true; + RRGGBB = true; + names = false; + RRGGBBAA = true; + AARRGGBB = true; + rgb_fn = true; + hsl_fn = true; + css = true; + css_fn = true; + mode = "background"; + tailwind = false; + virtualtext = "■"; + always_update = false; + }; + }; + }; + illuminate = { + enable = true; + setupOpts = { + delay = 200; + large_file_cutoff = 2000; + large_file_overrides = { + providers = [ "lsp" ]; + }; + filetypes_denylist = [ + "oil" + "dashboard" + "snacks_dashboard" + "lazy" + "mason" + "TelescopePrompt" + ]; + }; + }; + }; + + utility.snacks-nvim = { + enable = true; + setupOpts = { + dashboard = { + enabled = true; + preset = { + keys = [ + { + icon = " "; + key = "f"; + desc = "Find File"; + action = ":lua Snacks.dashboard.pick('files')"; + } + { + icon = " "; + key = "n"; + desc = "New File"; + action = ":ene | startinsert"; + } + { + icon = "󰱼 "; + key = "g"; + desc = "Find Text"; + action = ":lua Snacks.dashboard.pick('live_grep')"; + } + { + icon = " "; + key = "r"; + desc = "Recent Files"; + action = ":lua Snacks.dashboard.pick('oldfiles')"; + } + { + icon = " "; + key = "p"; + desc = "Projects"; + action = ":lua Snacks.picker.projects()"; + } + { + icon = " "; + key = "c"; + desc = "Config"; + action = ":lua Snacks.dashboard.pick('files', {cwd = vim.fn.stdpath('config')})"; + } + { + icon = " "; + key = "q"; + desc = "Quit"; + action = ":qa"; + } + ]; + header = '' + + +  + ████ ██████ █████ ██ + ███████████ █████  + █████████ ███████████████████ ███ ███████████ + █████████ ███ █████████████ █████ ██████████████ + █████████ ██████████ █████████ █████ █████ ████ █████ + ███████████ ███ ███ █████████ █████ █████ ████ █████ + ██████ █████████████████████ ████ █████ █████ ████ ██████ + + + + + + ''; + }; + sections = [ + { + pane = 1; + section = "header"; + padding = 2; + } + { + pane = 2; + section = "keys"; + gap = 1; + padding = 1; + } + ]; + }; + notifier = { + enabled = true; + timeout = 3000; + style = "compact"; + }; + quickfile = { + enabled = true; + }; + statuscolumn = { + enabled = true; + }; + words = { + enabled = true; + }; + styles = { + notification = { + wo = { + wrap = true; + }; + }; + }; + }; + }; + + keymaps = [ + { + key = "un"; + mode = "n"; + action = "function() Snacks.notifier.hide() end"; + lua = true; + desc = "Dismiss all notifications"; + } + { + key = "gg"; + mode = "n"; + action = "function() Snacks.lazygit() end"; + lua = true; + desc = "Lazygit"; + } + { + key = "gl"; + mode = "n"; + action = "function() Snacks.lazygit.log() end"; + lua = true; + desc = "Lazygit log (cwd)"; + } + { + key = "]]"; + mode = [ + "n" + "t" + ]; + action = "function() Snacks.words.jump(vim.v.count1) end"; + lua = true; + desc = "Next reference"; + } + { + key = "[["; + mode = [ + "n" + "t" + ]; + action = "function() Snacks.words.jump(-vim.v.count1) end"; + lua = true; + desc = "Previous reference"; + } + ]; + }; +} diff --git a/nvf/nvf.nix b/nvf/nvf.nix index b06214bc..23e7376d 100644 --- a/nvf/nvf.nix +++ b/nvf/nvf.nix @@ -1,10 +1,37 @@ { ... }: { imports = [ - ./config/options.nix - ./config/languages.nix - ./config/plugins.nix - ./config/theme.nix - ./config/lua_config.nix + # Core + ./config/core/options.nix + ./config/core/globals.nix + ./config/core/keymaps.nix + ./config/core/autocmds.nix + + # UI + ./config/ui/theme.nix + ./config/ui/statusline.nix + ./config/ui/tabline.nix + ./config/ui/noice.nix + ./config/ui/visuals.nix + + # Editor + ./config/editor/completion.nix + ./config/editor/format.nix + ./config/editor/lint.nix + ./config/editor/comments.nix + ./config/editor/editing.nix + ./config/editor/whichkey.nix + + # Tools + ./config/tools/telescope.nix + ./config/tools/navigation.nix + ./config/tools/git.nix + ./config/tools/debugger.nix + ./config/tools/testing.nix + + # Languages + ./config/languages/treesitter.nix + ./config/languages/lsp.nix + ./config/languages/languages.nix ]; } From 672e572df0f5746353a1f9131d128b92a901f906 Mon Sep 17 00:00:00 2001 From: reze-dev <25588579+reze-dev@users.noreply.github.com> Date: Sat, 19 Sep 2026 19:21:38 +0530 Subject: [PATCH 08/12] feat(nvf): eliminate mason and declare developer tools via native nix options --- nvf/config/core/options.nix | 3 +- nvf/config/editor/format.nix | 16 ++++++++ nvf/config/editor/lint.nix | 20 +++++---- nvf/config/languages/languages.nix | 7 ++-- nvf/config/tools/debugger.nix | 22 ++++++---- nvf/config/tools/navigation.nix | 1 - nvf/config/tools/tooling.nix | 65 ++++++++++++++++++++++++++++++ nvf/config/ui/statusline.nix | 2 +- nvf/config/ui/visuals.nix | 2 - nvf/nvf.nix | 1 + 10 files changed, 115 insertions(+), 24 deletions(-) create mode 100644 nvf/config/tools/tooling.nix diff --git a/nvf/config/core/options.nix b/nvf/config/core/options.nix index 89b73af7..674b3422 100644 --- a/nvf/config/core/options.nix +++ b/nvf/config/core/options.nix @@ -3,10 +3,9 @@ # System Environment & PATH fallback luaConfigRC.env = '' local home = vim.env.HOME or vim.fn.expand("~") - local mason_bin = vim.fn.stdpath("data") .. "/mason/bin" local go_bin = home .. "/go/bin" local cargo_bin = home .. "/.cargo/bin" - vim.env.PATH = mason_bin .. ":" .. go_bin .. ":" .. cargo_bin .. ":" .. (vim.env.PATH or "") + vim.env.PATH = go_bin .. ":" .. cargo_bin .. ":" .. (vim.env.PATH or "") ''; options = { diff --git a/nvf/config/editor/format.nix b/nvf/config/editor/format.nix index d89a0f73..48597660 100644 --- a/nvf/config/editor/format.nix +++ b/nvf/config/editor/format.nix @@ -2,6 +2,22 @@ config.vim = { formatter.conform-nvim = { enable = true; + presets = { + clang-format.enable = true; + rustfmt.enable = true; + goimports.enable = true; + gofumpt.enable = true; + stylua.enable = true; + ruff.enable = true; + ruff-organize-imports.enable = true; + taplo.enable = true; + prettier.enable = true; + nixfmt.enable = true; + shfmt.enable = true; + fish-indent.enable = true; + gersemi.enable = true; + sqlfluff.enable = true; + }; setupOpts = { formatters_by_ft = { c = [ "clang-format" ]; diff --git a/nvf/config/editor/lint.nix b/nvf/config/editor/lint.nix index 62a63a78..0e6ee1f7 100644 --- a/nvf/config/editor/lint.nix +++ b/nvf/config/editor/lint.nix @@ -1,12 +1,18 @@ { pkgs, ... }: { config.vim = { - diagnostics.nvim-lint = { - enable = true; - linters_by_ft = { - c = [ "clang-tidy" ]; - cpp = [ "clang-tidy" ]; - go = [ "golangci-lint" ]; - python = [ "ruff" ]; + diagnostics = { + presets = { + clangtidy.enable = true; + golangci-lint.enable = true; + }; + nvim-lint = { + enable = true; + linters_by_ft = { + c = [ "clang-tidy" ]; + cpp = [ "clang-tidy" ]; + go = [ "golangci-lint" ]; + python = [ "ruff" ]; + }; }; }; diff --git a/nvf/config/languages/languages.nix b/nvf/config/languages/languages.nix index 57ffa5d3..5d7b6213 100644 --- a/nvf/config/languages/languages.nix +++ b/nvf/config/languages/languages.nix @@ -241,12 +241,13 @@ adapter = function() local ok_cfg, cfg = pcall(require, "rustaceanvim.config") if not ok_cfg then return nil end - local mason_path = vim.fn.stdpath("data") .. "/mason/packages/codelldb" - local codelldb_path = mason_path .. "/extension/adapter/codelldb" - local liblldb_path = mason_path .. "/extension/lldb/lib/liblldb.so" + local codelldb_path = "${pkgs.vscode-extensions.vadimcn.vscode-lldb.adapter}/bin/codelldb" + local liblldb_path = "${pkgs.vscode-extensions.vadimcn.vscode-lldb.adapter}/share/lldb/lib/liblldb.so" if vim.fn.filereadable(codelldb_path) == 1 then return cfg.get_codelldb_adapter(codelldb_path, liblldb_path) + elseif vim.fn.executable("codelldb") == 1 then + return cfg.get_codelldb_adapter(vim.fn.exepath("codelldb"), "") else return cfg.get_codelldb_adapter("codelldb", "") end diff --git a/nvf/config/tools/debugger.nix b/nvf/config/tools/debugger.nix index 03997dcc..37f7f73e 100644 --- a/nvf/config/tools/debugger.nix +++ b/nvf/config/tools/debugger.nix @@ -160,22 +160,28 @@ map("n", "dw", function() require("dap.ui.widgets").hover() end, { desc = "DAP: widgets" }) -- CodeLLDB Discovery & Adapter - local mason_path = vim.fn.stdpath("data") .. "/mason/packages/codelldb" - local codelldb_mason = mason_path .. "/extension/adapter/codelldb" + local codelldb_pkg = "${pkgs.vscode-extensions.vadimcn.vscode-lldb.adapter}/bin/codelldb" + local liblldb_pkg = "${pkgs.vscode-extensions.vadimcn.vscode-lldb.adapter}/share/lldb/lib/liblldb.so" local codelldb_cmd = "codelldb" - if vim.fn.filereadable(codelldb_mason) == 1 or vim.fn.executable(codelldb_mason) == 1 then - codelldb_cmd = codelldb_mason + if vim.fn.executable(codelldb_pkg) == 1 then + codelldb_cmd = codelldb_pkg elseif vim.fn.executable("codelldb") == 1 then codelldb_cmd = vim.fn.exepath("codelldb") end + local codelldb_args = { "--port", "''${port}" } + if vim.fn.filereadable(liblldb_pkg) == 1 then + table.insert(codelldb_args, 1, "--liblldb") + table.insert(codelldb_args, 2, liblldb_pkg) + end + dap.adapters.codelldb = { type = "server", port = "''${port}", executable = { command = codelldb_cmd, - args = { "--port", "''${port}" }, + args = codelldb_args, }, } dap.adapters.lldb = dap.adapters.codelldb @@ -317,10 +323,10 @@ dap.configurations.rust = codelldb_configurations -- Delve (Go) + local dlv_pkg = "${pkgs.delve}/bin/dlv" local dlv_path = "dlv" - local mason_dlv = vim.fn.stdpath("data") .. "/mason/packages/delve/dlv" - if vim.fn.filereadable(mason_dlv) == 1 or vim.fn.executable(mason_dlv) == 1 then - dlv_path = mason_dlv + if vim.fn.executable(dlv_pkg) == 1 then + dlv_path = dlv_pkg elseif vim.fn.executable("dlv") == 1 then dlv_path = vim.fn.exepath("dlv") end diff --git a/nvf/config/tools/navigation.nix b/nvf/config/tools/navigation.nix index 0a9afddc..d87e5aad 100644 --- a/nvf/config/tools/navigation.nix +++ b/nvf/config/tools/navigation.nix @@ -147,7 +147,6 @@ "netrw" "NvimTree" "lazy" - "mason" "oil" "snacks_dashboard" "dashboard" diff --git a/nvf/config/tools/tooling.nix b/nvf/config/tools/tooling.nix new file mode 100644 index 00000000..5373ef43 --- /dev/null +++ b/nvf/config/tools/tooling.nix @@ -0,0 +1,65 @@ +{ pkgs, ... }: { + config.vim = { + # Declarative external developer tools via Nix + # Eliminates any dependency on Mason / mason-tool-installer + extraPackages = with pkgs; [ + # Language Servers (LSP) + clang-tools # clangd, clang-format, clang-tidy + rust-analyzer # rust-analyzer + gopls # gopls + basedpyright # basedpyright + lua-language-server # lua-language-server + nil # nil (Nix LSP) + marksman # marksman (Markdown LSP) + bash-language-server # bash-language-server + dockerfile-language-server # dockerfile-language-server + vscode-langservers-extracted # html, cssls, jsonls + yaml-language-server # yaml-language-server + taplo # taplo (TOML LSP & formatter) + sqls # sqls (SQL LSP) + superhtml # superhtml + + # Formatters + rustfmt # rustfmt + gotools # goimports + gofumpt # gofumpt + stylua # stylua + ruff # ruff format / ruff linter + prettier # prettier + nixfmt # nixfmt (Nix RFC style) + shfmt # shfmt + gersemi # gersemi (CMake formatter) + sqlfluff # sqlfluff (SQL formatter/linter) + + # Linters + golangci-lint # golangci-lint + + # Debuggers & DAP Adapters + vscode-extensions.vadimcn.vscode-lldb.adapter # codelldb + lldb # lldb, lldb-dap + delve # dlv + python3Packages.debugpy # debugpy + + # Go Ecosystem Tooling + go # go compiler & toolchain + gomodifytags # gomodifytags + impl # impl + iferr # iferr + gotests # gotests + + # Rust Ecosystem Tooling + cargo # cargo + + # Search & CLI Utilities + ripgrep # rg (used by Telescope) + fd # fd (used by Telescope) + fzf # fzf + ]; + + # Enable native DAP presets + debugger.nvim-dap.presets = { + codelldb.enable = true; + debugpy.enable = true; + }; + }; +} diff --git a/nvf/config/ui/statusline.nix b/nvf/config/ui/statusline.nix index 2e906a17..83562553 100644 --- a/nvf/config/ui/statusline.nix +++ b/nvf/config/ui/statusline.nix @@ -142,7 +142,7 @@ section_separators = "", globalstatus = true, disabled_filetypes = { - statusline = { "dashboard", "snacks_dashboard", "lazy", "mason" }, + statusline = { "dashboard", "snacks_dashboard", "lazy" }, }, theme = { normal = { c = { fg = c.fg, bg = c.bg } }, diff --git a/nvf/config/ui/visuals.nix b/nvf/config/ui/visuals.nix index e92a1741..25f0a4ac 100644 --- a/nvf/config/ui/visuals.nix +++ b/nvf/config/ui/visuals.nix @@ -43,7 +43,6 @@ "dashboard" "snacks_dashboard" "lazy" - "mason" "notify" "oil" "toggleterm" @@ -91,7 +90,6 @@ "dashboard" "snacks_dashboard" "lazy" - "mason" "TelescopePrompt" ]; }; diff --git a/nvf/nvf.nix b/nvf/nvf.nix index 23e7376d..bcb55c85 100644 --- a/nvf/nvf.nix +++ b/nvf/nvf.nix @@ -23,6 +23,7 @@ ./config/editor/whichkey.nix # Tools + ./config/tools/tooling.nix ./config/tools/telescope.nix ./config/tools/navigation.nix ./config/tools/git.nix From 5d157e9b41b1e7b23388fb6e7f29650bc95675a9 Mon Sep 17 00:00:00 2001 From: reze-dev <25588579+reze-dev@users.noreply.github.com> Date: Sat, 19 Sep 2026 19:31:46 +0530 Subject: [PATCH 09/12] fix(nvf): align conform and lint tool names with nix store paths and nvf presets --- nvf/config/editor/format.nix | 19 +++++++++++++++---- nvf/config/editor/lint.nix | 23 +++++++++++++++++++++-- nvf/config/languages/languages.nix | 2 +- nvf/config/tools/tooling.nix | 1 + 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/nvf/config/editor/format.nix b/nvf/config/editor/format.nix index 48597660..fe847f90 100644 --- a/nvf/config/editor/format.nix +++ b/nvf/config/editor/format.nix @@ -1,4 +1,4 @@ -{ lib, ... }: { +{ pkgs, lib, ... }: { config.vim = { formatter.conform-nvim = { enable = true; @@ -29,10 +29,12 @@ ]; lua = [ "stylua" ]; python = [ - "ruff_format" - "ruff_organize_imports" + "ruff" + "ruff-organize-imports" ]; toml = [ "taplo" ]; + cmake = [ "gersemi" ]; + sql = [ "sqlfluff" ]; javascript = [ "prettier" ]; typescript = [ "prettier" ]; javascriptreact = [ "prettier" ]; @@ -46,7 +48,7 @@ markdown = [ "prettier" ]; graphql = [ "prettier" ]; nix = [ "nixfmt" ]; - fish = [ "fish_indent" ]; + fish = [ "fish-indent" ]; sh = [ "shfmt" ]; bash = [ "shfmt" ]; "_" = [ "trim_whitespace" ]; @@ -80,6 +82,15 @@ clang-format = { prepend_args = [ "-fallback-style=LLVM" ]; }; + ruff_format = { + command = "${pkgs.ruff}/bin/ruff"; + }; + ruff_organize_imports = { + command = "${pkgs.ruff}/bin/ruff"; + }; + fish_indent = { + command = "${pkgs.fish}/bin/fish_indent"; + }; }; }; }; diff --git a/nvf/config/editor/lint.nix b/nvf/config/editor/lint.nix index 0e6ee1f7..e9291e42 100644 --- a/nvf/config/editor/lint.nix +++ b/nvf/config/editor/lint.nix @@ -7,6 +7,14 @@ }; nvim-lint = { enable = true; + linters = { + clang-tidy = { + cmd = "${pkgs.clang-tools}/bin/clang-tidy"; + }; + ruff = { + cmd = "${pkgs.ruff}/bin/ruff"; + }; + }; linters_by_ft = { c = [ "clang-tidy" ]; cpp = [ "clang-tidy" ]; @@ -19,8 +27,19 @@ luaConfigRC.lint_setup = '' local ok, lint = pcall(require, "lint") if ok then - lint.linters["clang-tidy"] = lint.linters.clangtidy - lint.linters["golangci-lint"] = lint.linters.golangcilint + if lint.linters.clangtidy and not lint.linters["clang-tidy"] then + lint.linters["clang-tidy"] = lint.linters.clangtidy + end + if lint.linters.golangcilint and not lint.linters["golangci-lint"] then + lint.linters["golangci-lint"] = lint.linters.golangcilint + end + if not lint.linters["ruff"] then + lint.linters["ruff"] = { + cmd = "${pkgs.ruff}/bin/ruff", + stdin = true, + args = { "check", "--force-exclude", "--stdin-filename", "$FILENAME", "-" }, + } + end lint.linters_by_ft = { c = { "clang-tidy" }, diff --git a/nvf/config/languages/languages.nix b/nvf/config/languages/languages.nix index 5d7b6213..d7509fc1 100644 --- a/nvf/config/languages/languages.nix +++ b/nvf/config/languages/languages.nix @@ -244,7 +244,7 @@ local codelldb_path = "${pkgs.vscode-extensions.vadimcn.vscode-lldb.adapter}/bin/codelldb" local liblldb_path = "${pkgs.vscode-extensions.vadimcn.vscode-lldb.adapter}/share/lldb/lib/liblldb.so" - if vim.fn.filereadable(codelldb_path) == 1 then + if vim.fn.filereadable(codelldb_path) == 1 or vim.fn.executable(codelldb_path) == 1 then return cfg.get_codelldb_adapter(codelldb_path, liblldb_path) elseif vim.fn.executable("codelldb") == 1 then return cfg.get_codelldb_adapter(vim.fn.exepath("codelldb"), "") diff --git a/nvf/config/tools/tooling.nix b/nvf/config/tools/tooling.nix index 5373ef43..2375a509 100644 --- a/nvf/config/tools/tooling.nix +++ b/nvf/config/tools/tooling.nix @@ -30,6 +30,7 @@ shfmt # shfmt gersemi # gersemi (CMake formatter) sqlfluff # sqlfluff (SQL formatter/linter) + fish # fish_indent # Linters golangci-lint # golangci-lint From ad261e0d028c716b8370428954b03821f4bf264e Mon Sep 17 00:00:00 2001 From: reze-dev <25588579+reze-dev@users.noreply.github.com> Date: Sat, 19 Sep 2026 19:55:41 +0530 Subject: [PATCH 10/12] refactor(nvf): replace unicode icons with equivalent nerdfont icons --- nvf/config/editor/comments.nix | 2 +- nvf/config/languages/languages.nix | 28 ++++++++++++++-------------- nvf/config/languages/lsp.nix | 2 +- nvf/config/tools/debugger.nix | 8 ++++---- nvf/config/ui/statusline.nix | 2 +- nvf/config/ui/tabline.nix | 2 +- nvf/config/ui/visuals.nix | 2 +- 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/nvf/config/editor/comments.nix b/nvf/config/editor/comments.nix index da820bbd..ab060f3f 100644 --- a/nvf/config/editor/comments.nix +++ b/nvf/config/editor/comments.nix @@ -85,7 +85,7 @@ alt = [ "INFO" ]; }; TEST = { - icon = "⏲ "; + icon = "󰙨 "; color = "test"; alt = [ "TESTING" diff --git a/nvf/config/languages/languages.nix b/nvf/config/languages/languages.nix index d7509fc1..82662d32 100644 --- a/nvf/config/languages/languages.nix +++ b/nvf/config/languages/languages.nix @@ -64,21 +64,21 @@ }, ast = { role_icons = { - type = "🄣", - declaration = "🄓", - expression = "🄔", - statement = ";", - specifier = "🄢", - ["template argument"] = "🆃", + type = "", + declaration = "", + expression = "", + statement = "", + specifier = "", + ["template argument"] = "", }, kind_icons = { - Compound = "🄲", - Recovery = "🅁", - TranslationUnit = "🅄", - PackExpansion = "🄿", - TemplateTypeParm = "🅃", - TemplateTemplateParm = "🅃", - TemplateParamObject = "🅃", + Compound = "", + Recovery = "", + TranslationUnit = "", + PackExpansion = "", + TemplateTypeParm = "", + TemplateTemplateParm = "", + TemplateParamObject = "", }, highlights = { detail = "Comment", @@ -134,7 +134,7 @@ tag_options = "json=omitempty", verbose = false, log_path = vim.fn.stdpath("cache") .. "/gonvim.log", - icons = { breakpoint = "🔴", currentpos = "🏃" }, + icons = { breakpoint = "", currentpos = "" }, }) vim.api.nvim_create_autocmd("FileType", { diff --git a/nvf/config/languages/lsp.nix b/nvf/config/languages/lsp.nix index bdba2030..a840ed24 100644 --- a/nvf/config/languages/lsp.nix +++ b/nvf/config/languages/lsp.nix @@ -17,7 +17,7 @@ virtual_text = { spacing = 4, source = "if_many", - prefix = "●", + prefix = "", }, severity_sort = true, float = { diff --git a/nvf/config/tools/debugger.nix b/nvf/config/tools/debugger.nix index 37f7f73e..708e7d9a 100644 --- a/nvf/config/tools/debugger.nix +++ b/nvf/config/tools/debugger.nix @@ -6,9 +6,9 @@ enable = true; setupOpts = { icons = { - expanded = "▾"; - collapsed = "▸"; - current_frame = "▸"; + expanded = ""; + collapsed = ""; + current_frame = ""; }; layouts = [ { @@ -84,7 +84,7 @@ vim.fn.sign_define("DapBreakpointCondition", { text = " ", texthl = "DiagnosticWarn", linehl = "", numhl = "" }) vim.fn.sign_define("DapBreakpointRejected", { text = " ", texthl = "DiagnosticError", linehl = "", numhl = "" }) vim.fn.sign_define("DapLogPoint", { text = " ", texthl = "DiagnosticInfo", linehl = "", numhl = "" }) - vim.fn.sign_define("DapStopped", { text = "▶ ", texthl = "DiagnosticOk", linehl = "DapStoppedLine", numhl = "" }) + vim.fn.sign_define("DapStopped", { text = " ", texthl = "DiagnosticOk", linehl = "DapStoppedLine", numhl = "" }) vim.api.nvim_set_hl(0, "DapStoppedLine", { default = true, link = "Visual" }) -- DAP UI Listeners diff --git a/nvf/config/ui/statusline.nix b/nvf/config/ui/statusline.nix index 83562553..4c813151 100644 --- a/nvf/config/ui/statusline.nix +++ b/nvf/config/ui/statusline.nix @@ -208,7 +208,7 @@ "filename", cond = conditions.buffer_not_empty, color = { fg = c.magenta, gui = "bold" }, - symbols = { modified = " ●", readonly = " 󰌾", unnamed = "[No Name]", newfile = "[New]" }, + symbols = { modified = " ", readonly = " 󰌾", unnamed = "[No Name]", newfile = "[New]" }, }) ins_left({ "location", color = { fg = c.blue, gui = "bold" } }) diff --git a/nvf/config/ui/tabline.nix b/nvf/config/ui/tabline.nix index 75286697..6280e33b 100644 --- a/nvf/config/ui/tabline.nix +++ b/nvf/config/ui/tabline.nix @@ -16,7 +16,7 @@ style = "icon"; }; buffer_close_icon = "󰅖"; - modified_icon = "●"; + modified_icon = ""; close_icon = ""; left_trunc_marker = ""; right_trunc_marker = ""; diff --git a/nvf/config/ui/visuals.nix b/nvf/config/ui/visuals.nix index 25f0a4ac..e6f71035 100644 --- a/nvf/config/ui/visuals.nix +++ b/nvf/config/ui/visuals.nix @@ -72,7 +72,7 @@ css_fn = true; mode = "background"; tailwind = false; - virtualtext = "■"; + virtualtext = "󱓻"; always_update = false; }; }; From f834b6a6f1035407e68071c7f3d45cb89e8c3009 Mon Sep 17 00:00:00 2001 From: reze-dev <25588579+reze-dev@users.noreply.github.com> Date: Sat, 19 Sep 2026 20:01:50 +0530 Subject: [PATCH 11/12] fix(nvf): correct clangd_extensions statement AST icon to codicon-symbol-statement --- nvf/config/languages/languages.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nvf/config/languages/languages.nix b/nvf/config/languages/languages.nix index 82662d32..d59df66f 100644 --- a/nvf/config/languages/languages.nix +++ b/nvf/config/languages/languages.nix @@ -67,7 +67,7 @@ type = "", declaration = "", expression = "", - statement = "", + statement = "", specifier = "", ["template argument"] = "", }, From 8f3d872890b4dec80cd881824b3ad34ced40d46d Mon Sep 17 00:00:00 2001 From: reze-dev <25588579+reze-dev@users.noreply.github.com> Date: Sat, 19 Sep 2026 20:14:30 +0530 Subject: [PATCH 12/12] ci: fix formatting to pass ci tests --- nvf/config/tools/tooling.nix | 76 ++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/nvf/config/tools/tooling.nix b/nvf/config/tools/tooling.nix index 2375a509..d7f1bc52 100644 --- a/nvf/config/tools/tooling.nix +++ b/nvf/config/tools/tooling.nix @@ -4,57 +4,57 @@ # Eliminates any dependency on Mason / mason-tool-installer extraPackages = with pkgs; [ # Language Servers (LSP) - clang-tools # clangd, clang-format, clang-tidy - rust-analyzer # rust-analyzer - gopls # gopls - basedpyright # basedpyright - lua-language-server # lua-language-server - nil # nil (Nix LSP) - marksman # marksman (Markdown LSP) - bash-language-server # bash-language-server - dockerfile-language-server # dockerfile-language-server - vscode-langservers-extracted # html, cssls, jsonls - yaml-language-server # yaml-language-server - taplo # taplo (TOML LSP & formatter) - sqls # sqls (SQL LSP) - superhtml # superhtml + clang-tools # clangd, clang-format, clang-tidy + rust-analyzer # rust-analyzer + gopls # gopls + basedpyright # basedpyright + lua-language-server # lua-language-server + nil # nil (Nix LSP) + marksman # marksman (Markdown LSP) + bash-language-server # bash-language-server + dockerfile-language-server # dockerfile-language-server + vscode-langservers-extracted # html, cssls, jsonls + yaml-language-server # yaml-language-server + taplo # taplo (TOML LSP & formatter) + sqls # sqls (SQL LSP) + superhtml # superhtml # Formatters - rustfmt # rustfmt - gotools # goimports - gofumpt # gofumpt - stylua # stylua - ruff # ruff format / ruff linter - prettier # prettier - nixfmt # nixfmt (Nix RFC style) - shfmt # shfmt - gersemi # gersemi (CMake formatter) - sqlfluff # sqlfluff (SQL formatter/linter) - fish # fish_indent + rustfmt # rustfmt + gotools # goimports + gofumpt # gofumpt + stylua # stylua + ruff # ruff format / ruff linter + prettier # prettier + nixfmt # nixfmt (Nix RFC style) + shfmt # shfmt + gersemi # gersemi (CMake formatter) + sqlfluff # sqlfluff (SQL formatter/linter) + fish # fish_indent # Linters - golangci-lint # golangci-lint + golangci-lint # golangci-lint # Debuggers & DAP Adapters vscode-extensions.vadimcn.vscode-lldb.adapter # codelldb - lldb # lldb, lldb-dap - delve # dlv - python3Packages.debugpy # debugpy + lldb # lldb, lldb-dap + delve # dlv + python3Packages.debugpy # debugpy # Go Ecosystem Tooling - go # go compiler & toolchain - gomodifytags # gomodifytags - impl # impl - iferr # iferr - gotests # gotests + go # go compiler & toolchain + gomodifytags # gomodifytags + impl # impl + iferr # iferr + gotests # gotests # Rust Ecosystem Tooling - cargo # cargo + cargo # cargo # Search & CLI Utilities - ripgrep # rg (used by Telescope) - fd # fd (used by Telescope) - fzf # fzf + ripgrep # rg (used by Telescope) + fd # fd (used by Telescope) + fzf # fzf ]; # Enable native DAP presets