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/flake.lock b/flake.lock index 0ff21ffa..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": { @@ -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": [ @@ -234,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": { @@ -323,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" @@ -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", @@ -638,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", @@ -734,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": { @@ -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" @@ -793,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": { @@ -925,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": { 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; + }; +} 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/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"; }; }; 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" 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/core/options.nix b/nvf/config/core/options.nix new file mode 100644 index 00000000..674b3422 --- /dev/null +++ b/nvf/config/core/options.nix @@ -0,0 +1,80 @@ +{ pkgs, ... }: { + config.vim = { + # System Environment & PATH fallback + luaConfigRC.env = '' + local home = vim.env.HOME or vim.fn.expand("~") + local go_bin = home .. "/go/bin" + local cargo_bin = home .. "/.cargo/bin" + vim.env.PATH = go_bin .. ":" .. cargo_bin .. ":" .. (vim.env.PATH or "") + ''; + + 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; + + # 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..ab060f3f --- /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..fe847f90 --- /dev/null +++ b/nvf/config/editor/format.nix @@ -0,0 +1,125 @@ +{ pkgs, lib, ... }: { + 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" ]; + cpp = [ "clang-format" ]; + rust = [ "rustfmt" ]; + go = [ + "goimports" + "gofumpt" + ]; + lua = [ "stylua" ]; + python = [ + "ruff" + "ruff-organize-imports" + ]; + toml = [ "taplo" ]; + cmake = [ "gersemi" ]; + sql = [ "sqlfluff" ]; + 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" ]; + }; + ruff_format = { + command = "${pkgs.ruff}/bin/ruff"; + }; + ruff_organize_imports = { + command = "${pkgs.ruff}/bin/ruff"; + }; + fish_indent = { + command = "${pkgs.fish}/bin/fish_indent"; + }; + }; + }; + }; + + 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..e9291e42 --- /dev/null +++ b/nvf/config/editor/lint.nix @@ -0,0 +1,101 @@ +{ pkgs, ... }: { + config.vim = { + diagnostics = { + presets = { + clangtidy.enable = true; + golangci-lint.enable = true; + }; + 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" ]; + go = [ "golangci-lint" ]; + python = [ "ruff" ]; + }; + }; + }; + + luaConfigRC.lint_setup = '' + local ok, lint = pcall(require, "lint") + if ok then + 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" }, + 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/languages.nix b/nvf/config/languages/languages.nix new file mode 100644 index 00000000..d59df66f --- /dev/null +++ b/nvf/config/languages/languages.nix @@ -0,0 +1,259 @@ +{ 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 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 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"), "") + 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..a840ed24 --- /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/tools/debugger.nix b/nvf/config/tools/debugger.nix new file mode 100644 index 00000000..708e7d9a --- /dev/null +++ b/nvf/config/tools/debugger.nix @@ -0,0 +1,397 @@ +{ 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 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.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 = codelldb_args, + }, + } + 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_pkg = "${pkgs.delve}/bin/dlv" + local dlv_path = "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 + + 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..d87e5aad --- /dev/null +++ b/nvf/config/tools/navigation.nix @@ -0,0 +1,357 @@ +{ 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" + "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/tools/tooling.nix b/nvf/config/tools/tooling.nix new file mode 100644 index 00000000..d7f1bc52 --- /dev/null +++ b/nvf/config/tools/tooling.nix @@ -0,0 +1,66 @@ +{ 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) + fish # fish_indent + + # 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/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..4c813151 --- /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" }, + }, + 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..6280e33b --- /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..e6f71035 --- /dev/null +++ b/nvf/config/ui/visuals.nix @@ -0,0 +1,249 @@ +{ ... }: { + 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" + "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" + "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 new file mode 100644 index 00000000..bcb55c85 --- /dev/null +++ b/nvf/nvf.nix @@ -0,0 +1,38 @@ +{ ... }: +{ + imports = [ + # 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/tooling.nix + ./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 + ]; +} 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"