{ inputs, ... }: { perSystem = { system, pkgs, ... }: { packages.nvim = inputs.nixvim.legacyPackages.${system}.makeNixvim { # The options-reference manpage builds via nixvim's docs/pkgs.nix, which # patches nixos-render-docs with nixpkgs PR#538502 (admonition support). # That PR is already merged in our nixpkgs, so the patch fails to apply # and breaks the build. We follow a single nixpkgs, so we can't feed # nixvim an older one — just drop the manpage (irrelevant to the editor). enableMan = false; vimAlias = true; # Closure budget: ~100 MB. Everything below that isn't the editor or a # plugin comes from PATH (project devshell / system) instead of being # baked in: no python/ruby/node providers (no remote plugins are used), # no git/bat/wl-clipboard on the wrapper PATH (desktops install # wl-clipboard themselves; xdg-utils behind it costs 120 MB of perl), # language servers unset (taken from the project devshell), and only # the grammars we edit. withPython3 = false; withRuby = false; withNodeJs = false; waylandSupport = false; enablePrintInit = false; dependencies.git.enable = false; dependencies.bat.enable = false; colorschemes = { ayu = { enable = true; settings = { mirage = false; dark = true; }; settings.overrides = { EoLSpace = { bg = "#883333"; }; }; }; everforest = { enable = true; settings.overrides = { EoLSpace = { bg = "#FF2222"; # doesn't work }; }; }; }; extraConfigLua = '' -- Cross-platform background detection and colorscheme switching local last_theme = nil local function detect_and_set_theme() local is_dark = false if vim.fn.has("mac") == 1 then -- macOS: check system appearance local result = vim.fn.system("defaults read -g AppleInterfaceStyle 2>/dev/null") is_dark = result:match("Dark") ~= nil elseif vim.fn.has("unix") == 1 then -- Linux: check if running under a dark theme -- Check NixOS theme system first (always check this fresh) local f = io.open("/var/theme/current_theme", "r") if f then local content = f:read("*all"):gsub("^%s+", ""):gsub("%s+$", "") f:close() is_dark = (content == "dark") else -- Fallback to other methods if theme file doesn't exist local methods = { -- GNOME/GTK "gsettings get org.gnome.desktop.interface gtk-theme 2>/dev/null", "gsettings get org.gnome.desktop.interface color-scheme 2>/dev/null", -- KDE "kreadconfig5 --file kdeglobals --group General --key ColorScheme 2>/dev/null", -- Check if COLORFGBG suggests dark background function() local colorfgbg = vim.env.COLORFGBG if colorfgbg then local fg, bg = colorfgbg:match("(%d+);(%d+)") if fg and bg then -- If foreground is lighter than background, likely dark theme return tonumber(fg) > tonumber(bg) end end return false end } for _, method in ipairs(methods) do if type(method) == "function" then is_dark = method() if is_dark then break end else local result = vim.fn.system(method) if result:match("[Dd]ark") or result:match("prefer%-dark") then is_dark = true break end end end end end local new_theme = is_dark and "dark" or "light" if new_theme ~= last_theme then last_theme = new_theme vim.o.background = new_theme if is_dark then vim.cmd("colorscheme ayu") else vim.cmd("colorscheme everforest") end end end -- Set initial theme vim.defer_fn(detect_and_set_theme, 100) -- Check for theme changes every 3 seconds (less frequent to be less resource intensive) local timer = vim.loop.new_timer() timer:start(3000, 3000, vim.schedule_wrap(detect_and_set_theme)) -- Create a command to manually refresh theme vim.api.nvim_create_user_command("RefreshTheme", detect_and_set_theme, {}) -- Configure lsp_lines to always show and disable default virtual_text vim.diagnostic.config({ virtual_text = false, virtual_lines = true, }) ''; globals = { mapleader = " "; }; opts = { mouse = "a"; number = true; shiftwidth = 2; clipboard = "unnamedplus"; # undo stuff undofile = true; undolevels = 10000; undoreload = 10000; listchars = "tab:⇥ ,extends:❯,precedes:❮,nbsp:␣,trail:·,eol:↲"; }; extraConfigVim = '' set et " show special characters set list " match trailing whitespace match EoLSpace /\s\+$/ " need to use lua to expand $HOME lua vim.o.undodir = vim.fs.normalize('$HOME/.vim/undodir') ''; extraPlugins = [ pkgs.vimPlugins.vim-fetch ]; keymaps = [ { mode = "i"; key = ""; action = ""; } { mode = "i"; key = ""; action = ""; } { mode = "n"; key = ""; action = ":q"; } { mode = "n"; key = ""; action = ""; } { mode = [ "n" "v" ]; key = "cc"; action = ":CopilotChatOpen"; } { mode = [ "n" "v" ]; key = "ce"; action = ":CopilotChatExplain"; } { mode = [ "n" "v" ]; key = "cr"; action = ":CopilotChatReview"; } { mode = [ "n" "v" ]; key = "cf"; action = ":CopilotChatFix"; } { mode = [ "n" "v" ]; key = "cd"; action = ":CopilotChatDocs"; } { mode = [ "n" "x" ]; key = "p"; action = "(YankyPutAfter)"; } { mode = [ "n" "x" ]; key = "P"; action = "(YankyPutBefore)"; } { mode = [ "c" "i" "n" "x" ]; key = ""; action = ":lua vim.lsp.buf.code_action()"; } { mode = [ "n" ]; key = "gd"; action = ":lua vim.lsp.buf.definition()"; } { mode = [ "n" ]; key = "gD"; action = ":lua vim.lsp.buf.declaration()"; } { mode = "n"; key = "ll"; action = ":lua require('lsp_lines').toggle()"; options.desc = "Toggle lsp_lines"; } ]; # autoCmd = [ # { # event = "BufWritePost"; # desc = "run treefmt on save"; # # https://stackoverflow.com/questions/77466697/how-to-automatically-format-on-save # callback.__raw = '' # function() # vim.cmd("silent !treefmt %") # vim.cmd("edit") # end # ''; # } # ]; plugins.none-ls = { enable = true; # sources.formatting.treefmt = { # enable = true; # package = pkgs.emptyDirectory; # }; # package = pkgs.vimPlugins.none-ls-nvim.overrideAttrs (_old: { # src = pkgs.fetchFromGitHub { # owner = "lassulus"; # repo = "none-ls.nvim"; # rev = "292c2b5eadea0a097b9a01804aea70f5c8125200"; # sha256 = "sha256-nbzqXsxmYfxngdJH3NXMLmGgygh6reSEnruoc6l8t/s="; # }; # }); sources.formatting.nix_flake_fmt.enable = true; }; plugins.web-devicons.enable = true; plugins.telescope.extensions.undo.enable = true; plugins.telescope = { enable = true; keymaps = { "b" = "buffers"; "u" = "undo"; "ff" = "find_files"; "fg" = "live_grep"; "fh" = "help_tags"; "fp" = "yank_history"; }; }; plugins.lsp-format.enable = true; plugins.lsp = { enable = true; servers = { bashls = { enable = true; # bash package = null; }; nixd = { enable = true; # nix package = null; extraOptions.offset_encoding = "utf-8"; # workaround https://github.com/nix-community/nixvim/issues/2390#issuecomment-2408101568 }; ruff = { enable = true; # python package = null; }; pyright = { enable = true; # python package = null; }; jsonls = { enable = true; # json package = null; }; }; }; plugins.treesitter = { enable = true; grammarPackages = with pkgs.vimPlugins.nvim-treesitter.builtGrammars; [ bash json lua markdown markdown_inline nix python toml vim vimdoc yaml ]; }; plugins.cmp_yanky.enable = true; plugins.cmp = { enable = true; autoEnableSources = true; settings = { experimental = { ghost_text = true; }; sources = [ { name = "nvim_lsp"; } { name = "buffer"; # text within current buffer option.get_bufnrs.__raw = "vim.api.nvim_list_bufs"; keywordLength = 2; } { name = "path"; # file system paths keywordLength = 3; } { name = "cmp_yanky"; } ]; mapping = { "" = "cmp.mapping.select_next_item()"; "" = "cmp.mapping.select_prev_item()"; "" = "cmp.mapping.select_next_item()"; "" = "cmp.mapping.select_prev_item()"; "" = "cmp.mapping.abort()"; "" = "cmp.mapping.scroll_docs(-4)"; "" = "cmp.mapping.scroll_docs(4)"; "" = '' cmp.mapping({ i = function(fallback) if cmp.visible() and cmp.get_active_entry() then cmp.confirm({ behavior = cmp.ConfirmBehavior.Replace, select = false }) else fallback() end end, s = cmp.mapping.confirm({ select = true }), c = cmp.mapping.confirm({ behavior = cmp.ConfirmBehavior.Replace, select = true }), }) ''; }; }; }; plugins.bufferline.enable = true; plugins.which-key.enable = true; plugins.yanky = { enable = true; enableTelescope = true; }; plugins.comment.enable = true; plugins.undotree.enable = true; # seems to be needed for undo history in telescope plugins.gitsigns.enable = true; }; }; }