diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..2d12fcb --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,90 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Overview + +This is a nix-darwin and home-manager configuration for managing a macOS system (Apple Silicon M3 Max) declaratively. The configuration uses Nix flakes and manages both system-level packages (via nix-darwin) and user-level packages and dotfiles (via home-manager). + +## Architecture + +### Flake Structure + +The repository follows a modular architecture: + +- **`flake.nix`**: Entry point defining inputs (nixpkgs, nix-darwin, home-manager, nur) and outputs. Defines the main darwin configuration `salarm3max` for aarch64-darwin. +- **`home.nix`**: Main home-manager configuration importing all program modules and defining system-wide packages. +- **`programs/default.nix`**: List of program module imports (each program has its own subdirectory with `default.nix`). +- **`programs/*/default.nix`**: Individual program configurations (fish, git, jujutsu, ghostty, wezterm, zed, etc.). + +### Key Design Patterns + +1. **Modular Program Configuration**: Each program (fish, git, ghostty, etc.) has its own directory under `programs/` with a `default.nix` file. +2. **Allowlist for Unfree Packages**: Unfree packages are explicitly allowlisted in `home.nix` using `allowUnfreePredicate`. +3. **External Config Fetching**: Neovim config is fetched from external GitHub repo (`softinio/nvim-config`) rather than managed in this repo. +4. **Integration Through Imports**: `programs/default.nix` is a simple list that gets imported into `home.nix`, making it easy to enable/disable programs. + +## Common Commands + +**Note**: User uses Fish shell, not Bash. All commands below work in Fish. + +### Building and Activating Configuration + +```fish +# Build and switch to new configuration (PREFERRED - use this alias) +nixre + +# Full command (if needed) +darwin-rebuild switch --flake ~/.config/nixpkgs#salarm3max + +# Build without switching +darwin-rebuild build --flake ~/.config/nixpkgs#salarm3max +``` + +### Updating Dependencies + +```fish +# Update all flake inputs +nix flake update + +# Update specific input +nix flake lock --update-input nixpkgs +``` + +### Testing Changes + +```fish +# Build configuration locally to test before switching +nix build .#darwinConfigurations.salarm3max.system +``` + +### Maintenance + +```fish +# Garbage collection +nix-collect-garbage -d # Also aliased as: nixgc + +# Repair Nix store +nix-store --repair --verify --check-contents # Also aliased as: nixstorerepair + +# Check system info +nix-shell -p nix-info --run "nix-info -m" # Also aliased as: nixinfo +``` + +## Adding New Programs + +To add a new program configuration: + +1. Create directory: `programs/newprogram/` +2. Create `programs/newprogram/default.nix` with configuration +3. Add `./newprogram` to the list in `programs/default.nix` +4. Run `nixre` to apply changes + +## Important Notes + +- **Platform**: This configuration is specifically for `aarch64-darwin` (Apple Silicon Macs). +- **User**: Configuration is for user `salar` with home directory `/Users/salar`. +- **Shell**: Fish is the default shell with extensive customizations. +- **Nix Features**: Experimental features `nix-command` and `flakes` are enabled. +- **Version Control**: Uses jujutsu (jj) as the primary VCS alongside git. +- **Editor**: Neovim is the default editor (`$EDITOR` and `$VISUAL` environment variables). diff --git a/flake.lock b/flake.lock index ccb7503..d5d00b4 100644 --- a/flake.lock +++ b/flake.lock @@ -1,6 +1,27 @@ { "nodes": { "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "nixvim", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1759362264, + "narHash": "sha256-wfG0S7pltlYyZTM+qqlhJ7GMw2fTF4mLKCIVhLii/4M=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "758cf7296bee11f1706a574c77d072b8a7baa881", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_2": { "inputs": { "nixpkgs-lib": [ "nur", @@ -21,6 +42,24 @@ "type": "github" } }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, "home-manager": { "inputs": { "nixpkgs": [ @@ -41,6 +80,34 @@ "type": "github" } }, + "ixx": { + "inputs": { + "flake-utils": [ + "nixvim", + "nuschtosSearch", + "flake-utils" + ], + "nixpkgs": [ + "nixvim", + "nuschtosSearch", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1754860581, + "narHash": "sha256-EM0IE63OHxXCOpDHXaTyHIOk2cNvMCGPqLt/IdtVxgk=", + "owner": "NuschtOS", + "repo": "ixx", + "rev": "babfe85a876162c4acc9ab6fb4483df88fa1f281", + "type": "github" + }, + "original": { + "owner": "NuschtOS", + "ref": "v0.1.1", + "repo": "ixx", + "type": "github" + } + }, "nix-darwin": { "inputs": { "nixpkgs": [ @@ -93,9 +160,32 @@ "type": "github" } }, - "nur": { + "nixvim": { "inputs": { "flake-parts": "flake-parts", + "nixpkgs": [ + "nixpkgs" + ], + "nuschtosSearch": "nuschtosSearch", + "systems": "systems_2" + }, + "locked": { + "lastModified": 1759527752, + "narHash": "sha256-+sncyvy1dkwRBeq7vw/YpsKqB18UuaKuW2lKRRv6/EI=", + "owner": "nix-community", + "repo": "nixvim", + "rev": "42d87fd4d8f51ea8c591b0b72af76b0aba991f54", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixvim", + "type": "github" + } + }, + "nur": { + "inputs": { + "flake-parts": "flake-parts_2", "nixpkgs": "nixpkgs_2" }, "locked": { @@ -112,13 +202,67 @@ "type": "github" } }, + "nuschtosSearch": { + "inputs": { + "flake-utils": "flake-utils", + "ixx": "ixx", + "nixpkgs": [ + "nixvim", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1758662783, + "narHash": "sha256-igrxT+/MnmcftPOHEb+XDwAMq3Xg1Xy7kVYQaHhPlAg=", + "owner": "NuschtOS", + "repo": "search", + "rev": "7d4c0fc4ffe3bd64e5630417162e9e04e64b27a4", + "type": "github" + }, + "original": { + "owner": "NuschtOS", + "repo": "search", + "type": "github" + } + }, "root": { "inputs": { "home-manager": "home-manager", "nix-darwin": "nix-darwin", "nixpkgs": "nixpkgs", + "nixvim": "nixvim", "nur": "nur" } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 6908110..aca228b 100644 --- a/flake.nix +++ b/flake.nix @@ -7,6 +7,10 @@ url = "github:LnL7/nix-darwin"; inputs.nixpkgs.follows = "nixpkgs"; }; + nixvim = { + url = "github:nix-community/nixvim"; + inputs.nixpkgs.follows = "nixpkgs"; + }; nur.url = "github:nix-community/nur"; home-manager = { url = "github:nix-community/home-manager"; @@ -18,6 +22,7 @@ { self, nix-darwin, + nixvim, home-manager, nur, nixpkgs, @@ -86,6 +91,11 @@ home-manager.backupFileExtension = "backup"; home-manager.useUserPackages = true; home-manager.users.salar = homeManagerConfFor ./home.nix; + home-manager.extraSpecialArgs = { + inputs = { + inherit nixvim; + }; + }; } ]; specialArgs = { diff --git a/home.nix b/home.nix index c288088..2163a53 100644 --- a/home.nix +++ b/home.nix @@ -1,4 +1,5 @@ { + inputs, lib, pkgs, ... @@ -8,11 +9,15 @@ # Let Home Manager install and manage itself. programs.home-manager.enable = true; - imports = (import ./programs); + imports = [ + inputs.nixvim.homeModules.nixvim + ] + ++ (import ./programs); nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ + "copilot.vim" "discord" "gh-copilot" "slack" @@ -62,7 +67,6 @@ metals multimarkdown mypy - neovim nerd-fonts.fira-code nil niv @@ -218,10 +222,10 @@ }; # Neovim Configuration - xdg.configFile."nvim".source = pkgs.fetchFromGitHub { - owner = "softinio"; - repo = "nvim-config"; - rev = "e89dc85e3a4116796d4393d37c12c31737e6f17c"; - sha256 = "sha256-HCLF7vM4pUbW2OlOuat1isnuyfqfPxjJ4wWYLcNcu0M="; - }; + # xdg.configFile."nvim".source = pkgs.fetchFromGitHub { + # owner = "softinio"; + # repo = "nvim-config"; + # rev = "e89dc85e3a4116796d4393d37c12c31737e6f17c"; + # sha256 = "sha256-HCLF7vM4pUbW2OlOuat1isnuyfqfPxjJ4wWYLcNcu0M="; + # }; } diff --git a/programs/default.nix b/programs/default.nix index 05aa5dd..3d8075e 100644 --- a/programs/default.nix +++ b/programs/default.nix @@ -6,6 +6,7 @@ ./git ./jujutsu # ./kitty + ./nixvim ./tmux ./wezterm ./vscode diff --git a/programs/nixvim/_plugins/avante.nix b/programs/nixvim/_plugins/avante.nix new file mode 100644 index 0000000..2034464 --- /dev/null +++ b/programs/nixvim/_plugins/avante.nix @@ -0,0 +1,17 @@ +{ ... }: + +{ + programs.nixvim = { + plugins.avante = { + enable = true; + settings = { + provider = "claude"; + providers = { + claude = { + model = "claude-sonnet-4-5-20250929"; + }; + }; + }; + }; + }; +} diff --git a/programs/nixvim/_plugins/conform.nix b/programs/nixvim/_plugins/conform.nix new file mode 100644 index 0000000..a3a7c1d --- /dev/null +++ b/programs/nixvim/_plugins/conform.nix @@ -0,0 +1,36 @@ +{ ... }: + +{ + programs.nixvim = { + plugins.conform-nvim = { + enable = true; + settings = { + formatters_by_ft = { + lua = [ "stylua" ]; + nix = [ "nixfmt" ]; + python = '' + function(bufnr) + if require("conform").get_formatter_info("ruff_format", bufnr).available then + return { "ruff_fix", "ruff_format" } + else + return { "isort", "black", "flake8" } + end + end + ''; + scala = [ "scalafmt" ]; + swift = [ "swift_format" ]; + "*" = [ "trim_whitespace" "trim_newlines" ]; + }; + format_on_save = '' + function(bufnr) + -- Disable with a global or buffer-local variable + if vim.g.disable_autoformat or vim.b[bufnr].disable_autoformat then + return + end + return { timeout_ms = 500, lsp_fallback = true } + end + ''; + }; + }; + }; +} diff --git a/programs/nixvim/_plugins/default.nix b/programs/nixvim/_plugins/default.nix new file mode 100644 index 0000000..c384695 --- /dev/null +++ b/programs/nixvim/_plugins/default.nix @@ -0,0 +1,105 @@ +{ ... }: + +{ + imports = [ + ./avante.nix + ./conform.nix + ./floaterm.nix + ./lsp.nix + ./lualine.nix + ./neo-tree.nix + ./telescope.nix + ./treesitter.nix + ]; + + # Global plugin configurations can go here + programs.nixvim = { + colorschemes.tokyonight = { + enable = true; + settings = { + style = "night"; + on_colors.__raw = "function(colors) colors.bg = \"#000000\" end"; + }; + }; + + plugins = { + # Lazy loading + lz-n.enable = true; + + web-devicons.enable = true; + + gitsigns = { + enable = true; + settings.signs = { + add.text = "+"; + change.text = "~"; + delete.text = "_"; + topdelete.text = "‾"; + changedelete.text = "~"; + }; + }; + + nvim-autopairs.enable = true; + + colorizer = { + enable = true; + settings.user_default_options.names = false; + }; + + copilot-vim.enable = true; + + fidget.enable = true; + + flash.enable = true; + + img-clip = { + enable = true; + settings = { + default = { + embed_image_as_base64 = false; + prompt_for_file_name = false; + drag_and_drop = { + insert_mode = true; + }; + use_absolute_path = true; + }; + }; + }; + + indent-blankline.enable = true; + + markview.enable = true; + + nui.enable = true; + + oil = { + enable = true; + lazyLoad.settings.cmd = "Oil"; + }; + + snacks.enable = true; + + todo-comments = { + enable = true; + keymaps.todoTelescope.key = "t"; + }; + + trim = { + enable = true; + settings = { + highlight = true; + ft_blocklist = [ + "checkhealth" + "floaterm" + "lspinfo" + "neo-tree" + "TelescopePrompt" + ]; + }; + }; + + trouble.enable = true; + which-key.enable = true; + }; + }; +} diff --git a/programs/nixvim/_plugins/floaterm.nix b/programs/nixvim/_plugins/floaterm.nix new file mode 100644 index 0000000..a8cc99d --- /dev/null +++ b/programs/nixvim/_plugins/floaterm.nix @@ -0,0 +1,14 @@ +{ ... }: + +{ + programs.nixvim.plugins.floaterm = { + enable = true; + + settings = { + width = 0.8; + height = 0.8; + title = ""; + keymap_toggle = ","; + }; + }; +} diff --git a/programs/nixvim/_plugins/lsp.nix b/programs/nixvim/_plugins/lsp.nix new file mode 100644 index 0000000..ed030e5 --- /dev/null +++ b/programs/nixvim/_plugins/lsp.nix @@ -0,0 +1,61 @@ +{ lib, ... }: + +{ + programs.nixvim = { + diagnostic.settings.virtual_text = true; + + lsp = { + inlayHints.enable = true; + servers = { + bashls.enable = true; + html.enable = true; + jqls.enable = true; + jsonls.enable = true; + lua_ls = { + enable = true; + settings.settings.diagnostics.globals = [ "vim" ]; + }; + marksman.enable = true; + metals.enable = true; + nil_ls.enable = true; + nixd.enable = true; + pyrefly.enable = true; + rust_analyzer.enable = true; + sourcekit.enable = true; + ts_ls.enable = true; + yamlls.enable = true; + }; + + keymaps = + lib.mapAttrsToList + ( + key: props: + { + inherit key; + options.silent = true; + } + // props + ) + { + "k".action.__raw = "function() vim.diagnostic.jump({ count=-1, float=true }) end"; + "j".action.__raw = "function() vim.diagnostic.jump({ count=1, float=true }) end"; + gd.lspBufAction = "definition"; + gD.lspBufAction = "references"; + gt.lspBufAction = "type_definition"; + gi.lspBufAction = "implementation"; + K.lspBufAction = "hover"; + "".lspBufAction = "rename"; + }; + }; + + plugins = { + lsp-format = { + enable = true; + lspServersToEnable = "all"; + }; + + # Sane defaults for all servers + lspconfig.enable = true; + }; + }; +} diff --git a/programs/nixvim/_plugins/lualine.nix b/programs/nixvim/_plugins/lualine.nix new file mode 100644 index 0000000..53945e7 --- /dev/null +++ b/programs/nixvim/_plugins/lualine.nix @@ -0,0 +1,49 @@ +{ ... }: + +{ + programs.nixvim.plugins.lualine = { + enable = true; + + settings = { + options.globalstatus = true; + + sections = { + lualine_a = [ "mode" ]; + lualine_b = [ "branch" ]; + lualine_c = [ + "filename" + "diff" + ]; + + lualine_x = [ + "diagnostics" + + { + __unkeyed.__raw = '' + function() + local msg = "" + local buf_ft = vim.api.nvim_buf_get_option(0, 'filetype') + local clients = vim.lsp.get_clients() + if next(clients) == nil then + return msg + end + for _, client in ipairs(clients) do + local filetypes = client.config.filetypes + if filetypes and vim.fn.index(filetypes, buf_ft) ~= -1 then + return client.name + end + end + return msg + end + ''; + icon = ""; + color.fg = "#ffffff"; + } + "encoding" + "fileformat" + "filetype" + ]; + }; + }; + }; +} diff --git a/programs/nixvim/_plugins/neo-tree.nix b/programs/nixvim/_plugins/neo-tree.nix new file mode 100644 index 0000000..73c1ee6 --- /dev/null +++ b/programs/nixvim/_plugins/neo-tree.nix @@ -0,0 +1,25 @@ +{ ... }: + +{ + programs.nixvim = { + keymaps = [ + { + mode = "n"; + key = "m"; + action = ":Neotree action=focus reveal toggle"; + options.silent = true; + } + ]; + + plugins.neo-tree = { + enable = true; + + closeIfLastWindow = true; + window = { + width = 30; + autoExpandWidth = true; + position = "right"; + }; + }; + }; +} diff --git a/programs/nixvim/_plugins/telescope.nix b/programs/nixvim/_plugins/telescope.nix new file mode 100644 index 0000000..a04aefb --- /dev/null +++ b/programs/nixvim/_plugins/telescope.nix @@ -0,0 +1,55 @@ +{ ... }: + +{ + programs.nixvim = { + plugins.telescope = { + enable = true; + + extensions = { + fzf-native.enable = true; + ui-select.enable = true; + undo.enable = true; + }; + + keymaps = { + "ff" = "find_files"; + "fg" = "live_grep"; + "b" = "buffers"; + "fh" = "help_tags"; + "fd" = "diagnostics"; + + "" = "git_files"; + "p" = "oldfiles"; + "" = "live_grep"; + }; + + settings.defaults = { + file_ignore_patterns = [ + "^.git/" + "^.mypy_cache/" + "^__pycache__/" + "^output/" + "^data/" + "%.ipynb" + ]; + set_env.COLORTERM = "truecolor"; + }; + }; + + keymaps = [ + { + mode = "n"; + key = ""; + action.__raw = '' + function() + require('telescope.builtin').live_grep({ + default_text="TODO", + initial_mode="normal" + }) + end + ''; + options.silent = true; + } + ]; + }; +} diff --git a/programs/nixvim/_plugins/treesitter.nix b/programs/nixvim/_plugins/treesitter.nix new file mode 100644 index 0000000..950c461 --- /dev/null +++ b/programs/nixvim/_plugins/treesitter.nix @@ -0,0 +1,69 @@ +{ pkgs, ... }: + +{ + programs.nixvim.plugins = { + treesitter = { + enable = true; + + grammarPackages = with pkgs.vimPlugins.nvim-treesitter.builtGrammars; [ + awk + bash + c + cpp + css + dockerfile + fish + git-rebase + gitattributes + gitcommit + gitignore + go + haskell + hocon + html + http + java + javascript + json + json5 + jsonc + lua + make + markdown + markdown-inline + nix + proto + python + rust + scala + sql + swift + terraform + toml + typescript + vim + xml + yaml + ]; + + nixvimInjections = true; + + settings = { + highlight.enable = true; + indent.enable = true; + }; + folding = false; + }; + + treesitter-refactor = { + enable = true; + highlightDefinitions = { + enable = true; + # Set to false if you have an `updatetime` of ~100. + clearOnCursorMove = false; + }; + }; + + hmts.enable = true; + }; +} diff --git a/programs/nixvim/completion.nix b/programs/nixvim/completion.nix new file mode 100644 index 0000000..18b1ce3 --- /dev/null +++ b/programs/nixvim/completion.nix @@ -0,0 +1,65 @@ +{ ... }: + +{ + programs.nixvim = { + opts.completeopt = [ + "menu" + "menuone" + "noselect" + ]; + + plugins = { + luasnip.enable = true; + + lspkind = { + enable = true; + + settings = { + cmp = { + enable = true; + menu = { + nvim_lsp = "[LSP]"; + nvim_lua = "[api]"; + path = "[path]"; + luasnip = "[snip]"; + buffer = "[buffer]"; + cmp_tabby = "[Tabby]"; + }; + }; + }; + }; + + cmp = { + enable = true; + + settings = { + snippet.expand = '' + function(args) + require('luasnip').lsp_expand(args.body) + end + ''; + + mapping = { + "" = "cmp.mapping.scroll_docs(-4)"; + "" = "cmp.mapping.scroll_docs(4)"; + "" = "cmp.mapping.complete()"; + "" = "cmp.mapping.close()"; + "" = "cmp.mapping(cmp.mapping.select_next_item(), {'i', 's'})"; + "" = "cmp.mapping(cmp.mapping.select_prev_item(), {'i', 's'})"; + "" = "cmp.mapping.confirm({ select = true })"; + }; + + sources = [ + { name = "path"; } + { name = "nvim_lsp"; } + { name = "luasnip"; } + { + name = "buffer"; + option.get_bufnrs.__raw = "vim.api.nvim_list_bufs"; + } + ]; + }; + }; + }; + }; +} diff --git a/programs/nixvim/default.nix b/programs/nixvim/default.nix new file mode 100644 index 0000000..8ad3bef --- /dev/null +++ b/programs/nixvim/default.nix @@ -0,0 +1,21 @@ +{ pkgs, ... }: + +{ + imports = [ + ./options.nix + ./keymappings.nix + ./completion.nix + ./_plugins + ]; + + programs.nixvim = { + enable = true; + + defaultEditor = true; + + nixpkgs.useGlobalPackages = true; + + viAlias = true; + vimAlias = true; + }; +} diff --git a/programs/nixvim/keymappings.nix b/programs/nixvim/keymappings.nix new file mode 100644 index 0000000..1977905 --- /dev/null +++ b/programs/nixvim/keymappings.nix @@ -0,0 +1,53 @@ +{ lib, ... }: + +{ + programs.nixvim = { + keymaps = + let + normal = + lib.mapAttrsToList + ( + key: action: { + mode = "n"; + inherit action key; + } + ) + { + "" = ""; + "" = ":noh"; + "Y" = "y$"; + "" = ":b#"; + "" = ":close"; + "s" = ":w"; + "" = ":w"; + "h" = "h"; + "l" = "l"; + "L" = "$"; + "H" = "^"; + "" = ":resize -2"; + "" = ":resize +2"; + "" = ":vertical resize +2"; + "" = ":vertical resize -2"; + "" = ":move-2"; + "" = ":move+"; + }; + visual = + lib.mapAttrsToList + ( + key: action: { + mode = "v"; + inherit action key; + } + ) + { + ">" = ">gv"; + "<" = "" = ">gv"; + "" = "