diff --git a/.github/workflows/lua.yml b/.github/workflows/lua.yml new file mode 100644 index 000000000..ae3ba508b --- /dev/null +++ b/.github/workflows/lua.yml @@ -0,0 +1,54 @@ +name: Lua +on: + push: + branches: + - main + pull_request: + branches: + - main +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.sha }} + cancel-in-progress: true +jobs: + lua-neovim: + runs-on: ubuntu-latest + timeout-minutes: 300 + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Free Disk Space (Ubuntu) + uses: jlumbroso/free-disk-space@v1.3.1 + with: + tool-cache: false + - name: Install Nix + uses: cachix/install-nix-action@v31 + with: + github_access_token: ${{ secrets.GITHUB_TOKEN }} + - name: Install Neovim + run: nix profile install nixpkgs#neovim + - name: Validate Neovim Configuration + run: make lua-check-neovim + lua-hammerspoon: + runs-on: macos-latest + timeout-minutes: 300 + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Install Nix + uses: cachix/install-nix-action@v31 + with: + github_access_token: ${{ secrets.GITHUB_TOKEN }} + - name: Validate Hammerspoon Configuration + run: make lua-check-hammerspoon + lua-check: + if: always() + needs: + - lua-neovim + - lua-hammerspoon + runs-on: ubuntu-latest + timeout-minutes: 3 + steps: + - name: Alls Green + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/.gitignore b/.gitignore index 5a154206b..31e02e131 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # AI .claude +# Nix +*.hm-backup + # General .DS_Store .AppleDouble diff --git a/Makefile b/Makefile index 738b9b43e..964d875df 100644 --- a/Makefile +++ b/Makefile @@ -88,66 +88,61 @@ DARWIN_REBUILD := $(shell command -v darwin-rebuild 2>/dev/null || echo "./resul # Default target .PHONY: default -default: help +default: help ## Default target (shows help). -# Help target +# ==================================================================================== +# HELP +# ==================================================================================== .PHONY: help -help: - @echo "Available targets:" - @echo " install - Set up full environment" - @echo " setup - Basic Nix setup" - @echo " setup-dev - Set up local development environment (Nix + submodules + shell)" - @echo " dev - Enter the Nix dev shell" - @echo " build - Build Nix configuration" - @echo " switch - Apply Nix configuration" - @echo " update - Update Nix flake and configurations" - @echo " format - Format Nix files" - @echo " format-check - Check Nix formatting" - @echo " docker-build - Build the Docker image" - @echo " switch-HOST - Switch to a named host configuration (e.g., make switch-galactica)" - @echo " encrypt-key-HOST - Encrypt a key for a named host (e.g., make encrypt-key-galactica KEY_FILE=~/.ssh/id_ed25519)" - @echo " decrypt-key-HOST - Decrypt a key for a named host (e.g., make decrypt-key-galactica KEY_FILE=id_ed25519)" - @echo " rekey-HOST - Rekey all secrets for a named host (e.g., make rekey-galactica)" +help: ## Show this help message. + @echo "Usage: make " + @echo + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z0-9_-]+:.*?## / {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) ##@ General .PHONY: install -install: setup update shell-install +install: setup nix-flake-update nix-build nix-switch shell-install ## Set up full environment (setup, flake-update, build, switch, shell-install). .PHONY: build -build: nix-build +build: nix-build ## Build Nix configuration. .PHONY: check -check: nix-check +check: ## Run all validation checks (nix, format, lua). + @echo "๐Ÿ” Running all validation checks..." + @$(MAKE) nix-flake-check + @$(MAKE) nix-format-check + @$(MAKE) lua-check + @echo "โœ… All checks passed" .PHONY: flake-check -flake-check: nix-flake-check +flake-check: nix-flake-check ## Check Nix flake configuration (alias for nix-flake-check). .PHONY: format -format: nix-format +format: nix-format ## Format Nix files (alias for nix-format). .PHONY: setup -setup: nix-setup +setup: nix-setup ## Basic Nix setup (alias for nix-setup). .PHONY: setup-dev -setup-dev: nix-setup git-submodule-sync shell-install +setup-dev: nix-setup git-submodule-sync shell-install ## Set up local development environment (Nix + submodules + shell). .PHONY: switch -switch: nix-switch +switch: nix-switch ## Apply Nix configuration (alias for nix-switch). .PHONY: update -update: nix-update shell-update +update: nix-update shell-update neovim-update ## Update Nix flake and configurations. .PHONY: dev -dev: nix-develop +dev: nix-develop ## Enter the Nix dev shell (alias for nix-develop). ##@ Nix Setup .PHONY: nix-setup -nix-setup: nix-install nix-check nix-connect +nix-setup: nix-install nix-check nix-connect ## Set up Nix environment (install, check, connect). .PHONY: nix-connect -nix-connect: +nix-connect: ## Ensure Nix daemon is running. @echo "๐Ÿ”Œ Ensuring Nix daemon is running for $(NIX_CONFIG_TYPE) on $(OS) $(ARCH) for USER=$(NIX_USERNAME)" @if [ "$(OS)" = "Darwin" ]; then \ sudo launchctl unload /Library/LaunchDaemons/org.nixos.nix-daemon.plist 2>/dev/null || true; \ @@ -176,7 +171,7 @@ nix-connect: @echo "โœ… Nix daemon should now be active!" .PHONY: nix-check -nix-check: +nix-check: ## Verify Nix environment setup. @echo "๐Ÿ” Verifying Nix environment setup for $(NIX_CONFIG_TYPE) on $(OS) $(ARCH) for USER=$(NIX_USERNAME)" @if [ "$(NIX_ENV)" = "not_found" ]; then \ echo "โŒ Nix environment not found. Please ensure Nix is installed and run:"; \ @@ -185,11 +180,11 @@ nix-check: @echo "โœ… Nix environment found!" .PHONY: nix-develop -nix-develop: +nix-develop: ## Enter the Nix development shell. $(NIX_ALLOW_UNFREE) $(NIX_EXEC) develop $(NIX_FLAGS) .PHONY: nix-install -nix-install: +nix-install: ## Install Nix if not already installed. @if [ "$(NIX_ENV)" = "not_found" ]; then \ echo "๐Ÿš€ Installing Nix environment for $(NIX_CONFIG_TYPE) on $(OS) $(ARCH) for USER=$(NIX_USERNAME)"; \ curl -L https://nixos.org/nix/install | sh; \ @@ -199,10 +194,10 @@ nix-install: ##@ Nix .PHONY: nix-update -nix-update: nix-flake-update nix-build nix-switch +nix-update: nix-flake-update nix-build nix-switch ## Update Nix flake, build, and switch. .PHONY: nix-backup -nix-backup: +nix-backup: ## Backup configuration files. @echo "๐Ÿ—„๏ธ Backing up configuration files for $(NIX_CONFIG_TYPE) on $(OS) $(ARCH) for USER=$(NIX_USERNAME)" @backup_dir="$$HOME/.config/backups/$(shell date +%Y%m%d_%H%M%S)"; \ mkdir -p "$$backup_dir"; \ @@ -212,7 +207,7 @@ nix-backup: fi .PHONY: nix-build -nix-build: nix-connect +nix-build: nix-connect ## Build Nix configuration. @echo "๐Ÿ—๏ธ Building Nix configuration for $(NIX_CONFIG_TYPE) on $(OS) $(ARCH) for USER=$(NIX_USERNAME)" @if [ "$$CI" = "true" ] || [ "$$IN_DOCKER" = "true" ]; then \ echo "๐Ÿค– Running in CI/Docker environment"; \ @@ -244,13 +239,13 @@ nix-build: nix-connect @echo "โœ… Nix configuration built successfully!" .PHONY: nix-flake-check -nix-flake-check: +nix-flake-check: ## Check Nix flake configuration. @echo "๐Ÿ” Checking Nix flake configuration..." @$(NIX_ALLOW_UNFREE) $(NIX_EXEC) flake check --all-systems --impure $(NIX_FLAGS) @echo "โœ… Nix flake check completed successfully" .PHONY: nix-flake-update -nix-flake-update: nix-connect +nix-flake-update: nix-connect ## Update flake.lock file. @echo "โ™ป๏ธ Refreshing flake.lock file..." @if [ "$$CI" = "true" ] || [ "$$IN_DOCKER" = "true" ]; then \ echo "Bypassing flake update in CI/Docker"; \ @@ -260,25 +255,25 @@ nix-flake-update: nix-connect @echo "โœ… flake.lock updated!" .PHONY: nix-format -nix-format: nix-format-clear-cache +nix-format: nix-format-clear-cache ## Format Nix files. @echo "๐Ÿงน Formatting Nix files..." @$(NIX_EXEC) fmt -- --clear-cache @echo "โœ… Formatting complete" .PHONY: nix-format-clear-cache -nix-format-clear-cache: +nix-format-clear-cache: ## Clear Nix format cache. @echo "๐Ÿงน Clearing Nix cache..." @$(NIX_EXEC) fmt -- --clear-cache @echo "โœ… Cache cleared" .PHONY: nix-format-check -nix-format-check: nix-format-clear-cache +nix-format-check: nix-format-clear-cache ## Check Nix file formatting. @echo "๐Ÿ” Checking Nix file formatting..." @$(NIX_EXEC) fmt -- --fail-on-change @echo "โœ… All Nix files are properly formatted" .PHONY: nix-switch -nix-switch: +nix-switch: ## Activate Nix configuration. @echo "๐Ÿ”ง Activating Nix configuration for $(NIX_CONFIG_TYPE) on $(OS) $(ARCH) for USER=$(NIX_USERNAME)" @if [ "$$CI" = "true" ] || [ "$$IN_DOCKER" = "true" ]; then \ if [ "$(OS)" = "Darwin" ]; then \ @@ -318,7 +313,7 @@ nix-switch: @echo "โœ… Nix configuration activated successfully!" .PHONY: nix-switch-vm -nix-switch-vm: +nix-switch-vm: ## Switch NixOS configuration in VM. @if [ ! -f "./result/bin/run-nixos-vm" ]; then \ echo "โŒ VM binary not found at ./result/bin/run-nixos-vm"; \ exit 0; \ @@ -328,28 +323,68 @@ nix-switch-vm: timeout 600 ./result/bin/run-nixos-vm -nographic < vm_commands.txt || exit 1; \ rm -f vm_commands.txt +##@ Nix Offline Mode + +.PHONY: nix-build-offline +nix-build-offline: ## Build Nix configuration in offline mode. + @echo "๐Ÿ—๏ธ Building Nix configuration in offline mode" + @if [ "$(OS)" = "Darwin" ]; then \ + NIX_OFFLINE=1 $(NIX_ALLOW_UNFREE) $(NIX_EXEC) build .#darwinConfigurations.galactica.system $(NIX_FLAGS) --impure --no-update-lock-file --offline --show-trace; \ + elif [ "$(NIX_CONFIG_TYPE)" = "nixosConfigurations" ]; then \ + NIX_OFFLINE=1 $(NIX_ALLOW_UNFREE) $(NIX_EXEC) build .#nixosConfigurations.runner.config.system.build.toplevel $(NIX_FLAGS) --impure --no-update-lock-file --offline --show-trace; \ + elif [ "$(NIX_CONFIG_TYPE)" = "homeConfigurations" ]; then \ + NIX_OFFLINE=1 $(NIX_ALLOW_UNFREE) $(NIX_EXEC) build .#$(NIX_CONFIG_TYPE)."$(NIX_USERNAME)@$(NIX_SYSTEM)".activationPackage $(NIX_FLAGS) --impure --no-update-lock-file --offline --show-trace; \ + else \ + echo "Unsupported OS $(OS) for offline build"; \ + exit 1; \ + fi + @echo "โœ… Nix configuration built successfully in offline mode!" + +.PHONY: nix-switch-offline +nix-switch-offline: ## Activate Nix configuration in offline mode. + @echo "๐Ÿ”ง Activating Nix configuration in offline mode" + @if [ "$(OS)" = "Darwin" ]; then \ + NIX_OFFLINE=1 sudo $(NIX_ALLOW_UNFREE) $(DARWIN_REBUILD) switch --flake .#galactica --impure --offline; \ + elif [ "$(NIX_CONFIG_TYPE)" = "nixosConfigurations" ]; then \ + NIX_OFFLINE=1 sudo $(NIX_ALLOW_UNFREE) $(NIX_EXEC) run $(NIX_FLAGS) --impure nixpkgs#nixos-rebuild -- switch --flake .#runner --no-update-lock-file --offline || exit 0; \ + elif [ "$(NIX_CONFIG_TYPE)" = "homeConfigurations" ]; then \ + NIX_OFFLINE=1 USER=$(NIX_USERNAME) sudo $(NIX_ALLOW_UNFREE) $(NIX_EXEC) run $(NIX_FLAGS) --impure .#$(NIX_CONFIG_TYPE)."$(NIX_USERNAME)@$(NIX_SYSTEM)".activationPackage --offline; \ + else \ + echo "Unsupported OS $(OS) for offline switch"; \ + exit 1; \ + fi + @echo "โœ… Nix configuration activated successfully in offline mode!" + +.PHONY: nix-setup-offline +nix-setup-offline: ## Set up offline environment. + @echo "๐Ÿ”ง Setting up offline environment" + @mkdir -p ~/.cache/nix + @echo "โœ… Offline environment setup complete" + +##@ Offline Mode + ##@ Named Hosts Specific Targets .PHONY: switch-% -switch-%: +switch-%: ## Switch to a named host configuration (e.g., make switch-galactica). @$(MAKE) nix-switch HOST=$* .PHONY: encrypt-key-% -encrypt-key-%: +encrypt-key-%: ## Encrypt a key for a named host (e.g., make encrypt-key-galactica KEY_FILE=~/.ssh/id_ed25519). @$(MAKE) encrypt-key HOST=$* KEY_FILE=$(KEY_FILE) .PHONY: decrypt-key-% -decrypt-key-%: +decrypt-key-%: ## Decrypt a key for a named host (e.g., make decrypt-key-galactica KEY_FILE=id_ed25519). @$(MAKE) decrypt-key HOST=$* KEY_FILE=$(KEY_FILE) .PHONY: rekey-% -rekey-%: +rekey-%: ## Rekey all secrets for a named host (e.g., make rekey-galactica). @$(MAKE) rekey HOST=$* ##@ Agenix Secrets Management .PHONY: encrypt-key -encrypt-key: +encrypt-key: ## Encrypt a key file for a host (requires HOST and KEY_FILE variables). @if [ -z "$(HOST)" ]; then \ echo "โŒ HOST variable is not set. Usage: make encrypt-key HOST= KEY_FILE="; \ exit 1; \ @@ -363,7 +398,7 @@ encrypt-key: @echo "โœ… Key encrypted to named-hosts/$(HOST)/keys/$(shell basename $(KEY_FILE)).age" .PHONY: decrypt-key -decrypt-key: +decrypt-key: ## Decrypt a key file for a host (requires HOST variable, optional KEY_FILE). @if [ -z "$(HOST)" ]; then \ echo "โŒ HOST variable is not set. Usage: make decrypt-key HOST= KEY_FILE="; \ exit 1; \ @@ -375,7 +410,7 @@ decrypt-key: @cd named-hosts/$(HOST) && agenix -d keys/$(KEY_FILE).age .PHONY: rekey -rekey: +rekey: ## Rekey all secrets for a host (requires HOST variable). @if [ -z "$(HOST)" ]; then \ echo "โŒ HOST variable is not set. Usage: make rekey HOST="; \ exit 1; \ @@ -387,7 +422,7 @@ rekey: ##@ Shell Installation .PHONY: shell-install -shell-install: +shell-install: ## Set up Fish shell as default shell. @echo "๐Ÿ  Setting up Fish shell..." @if command -v fish > /dev/null; then \ fish_path=$$(command -v fish); \ @@ -407,20 +442,109 @@ shell-install: fi .PHONY: shell-update -shell-update: shell-install +shell-update: shell-install ## Update Fish shell setup (alias for shell-install). ##@ Docker .PHONY: docker-build -docker-build: +docker-build: ## Build Docker image. @echo "๐Ÿณ Building Docker image: $(DOCKER_IMAGE_LATEST) and $(DOCKER_IMAGE_TAGGED)..." @docker build -t $(DOCKER_IMAGE_LATEST) -t $(DOCKER_IMAGE_TAGGED) -f Dockerfile . @echo "โœ… Docker image built: $(DOCKER_IMAGE_LATEST) and $(DOCKER_IMAGE_TAGGED)" +##@ Neovim + +.PHONY: neovim-dev +neovim-dev: ## Set up local Neovim development environment. + @echo "๐Ÿ”ง Setting up local Neovim development environment..." + @if [ -L "$(HOME)/.config/nvim" ]; then \ + rm "$(HOME)/.config/nvim"; \ + fi + @mkdir -p "$(HOME)/.config/nvim" + @ln -sf "$(PWD)/config/nvim/init.lua" "$(HOME)/.config/nvim/init.lua" + @ln -sf "$(PWD)/config/nvim/nvim-pack-lock.json" "$(HOME)/.config/nvim/nvim-pack-lock.json" + @echo "โœ… Local Neovim development environment ready" + @echo "๐Ÿšง To restore the Nix-managed version, run 'make switch'" + +.PHONY: neovim-update +neovim-update: ## Update Neovim plugins. + @echo "๐Ÿ“ฆ Updating neovim plugins..." + @nvim --headless +"lua vim.pack.update()" +qa + @echo "โœ… Neovim plugins updated" + +.PHONY: neovim-sync +neovim-sync: ## Sync Neovim plugins. + @echo "๐Ÿ”„ Syncing neovim plugins..." + @nvim --headless +"lua vim.cmd('source ' .. vim.fn.stdpath('config') .. '/init.lua')" +qa + @if [ -f "$(PWD)/config/nvim/nvim-pack-lock.json" ]; then \ + if [ "$$(uname)" = "Darwin" ]; then \ + sed -i '' -e '$$ { /^$$/d; }' "$(PWD)/config/nvim/nvim-pack-lock.json"; \ + else \ + sed -i -e '$$ { /^$$/d; }' "$(PWD)/config/nvim/nvim-pack-lock.json"; \ + fi && \ + printf '\n' >> "$(PWD)/config/nvim/nvim-pack-lock.json"; \ + fi + @echo "โœ… Neovim plugins synced" + +##@ Lua + +.PHONY: lua-check +lua-check: lua-check-neovim lua-check-hammerspoon ## Check all Lua configurations (Neovim and Hammerspoon). + @echo "โœ… All Lua configurations validated" + +.PHONY: lua-check-neovim +lua-check-neovim: ## Check Neovim configuration. + @echo "๐Ÿ” Checking Neovim configuration..." + @if ! command -v nvim >/dev/null 2>&1; then \ + echo "โš ๏ธ Neovim is not installed or not in PATH"; \ + exit 1; \ + fi + @NVIM_CONFIG="$(PWD)/config/nvim/init.lua"; \ + if [ ! -f "$$NVIM_CONFIG" ]; then \ + echo "โš ๏ธ Could not find Neovim configuration at $$NVIM_CONFIG"; \ + exit 1; \ + fi + @echo "๐Ÿ“ Validating Neovim configuration syntax..." + @mkdir -p ~/.config/nvim + @ln -sf "$(PWD)/config/nvim/init.lua" ~/.config/nvim/init.lua + @if [ -f "$(PWD)/config/nvim/nvim-pack-lock.json" ]; then \ + ln -sf "$(PWD)/config/nvim/nvim-pack-lock.json" ~/.config/nvim/nvim-pack-lock.json; \ + fi + @nvim --headless -c "lua dofile('$(PWD)/config/nvim/init.lua')" -c "qa" 2>&1; \ + EXIT_CODE=$$?; \ + if [ $$EXIT_CODE -eq 0 ]; then \ + echo "โœ… Neovim configuration is valid"; \ + else \ + echo "โŒ Neovim configuration has errors (exit code: $$EXIT_CODE)"; \ + exit $$EXIT_CODE; \ + fi + +.PHONY: lua-check-hammerspoon +lua-check-hammerspoon: ## Check Hammerspoon configuration. + @echo "๐Ÿ” Checking Hammerspoon configuration..." + @HAMMERSPOON_CONFIG="$(PWD)/config/hammerspoon/init.lua"; \ + if [ ! -f "$$HAMMERSPOON_CONFIG" ]; then \ + echo "โš ๏ธ Could not find Hammerspoon configuration at $$HAMMERSPOON_CONFIG"; \ + exit 1; \ + fi + @echo "๐Ÿ“ Validating Hammerspoon configuration syntax..." + @if command -v lua >/dev/null 2>&1; then \ + lua -e "assert(loadfile('$(PWD)/config/hammerspoon/init.lua'))" && \ + echo "โœ… Hammerspoon configuration is valid" || \ + (echo "โŒ Hammerspoon configuration has syntax errors" && exit 1); \ + elif command -v nix >/dev/null 2>&1; then \ + nix run nixpkgs#lua -- -e "assert(loadfile('$(PWD)/config/hammerspoon/init.lua'))" && \ + echo "โœ… Hammerspoon configuration is valid" || \ + (echo "โŒ Hammerspoon configuration has syntax errors" && exit 1); \ + else \ + echo "โš ๏ธ Neither lua nor nix is available for syntax checking"; \ + exit 1; \ + fi + ##@ Git Submodule .PHONY: git-submodule-sync -git-submodule-sync: +git-submodule-sync: ## Sync and update git submodules. @echo "๐Ÿ” Syncing and updating git submodules..." @git submodule sync @git submodule update --init --recursive diff --git a/bun.lock b/bun.lock index 20c78f9b2..f4533a5f4 100644 --- a/bun.lock +++ b/bun.lock @@ -12,6 +12,9 @@ "cline": "^1.0.5", "open-composer": "^0.8.23", }, + "devDependencies": { + "@biomejs/biome": "^2.3.7", + }, }, }, "trustedDependencies": [ @@ -37,6 +40,24 @@ "@asamuzakjp/css-color": ["@asamuzakjp/css-color@3.2.0", "", { "dependencies": { "@csstools/css-calc": "^2.1.3", "@csstools/css-color-parser": "^3.0.9", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", "lru-cache": "^10.4.3" } }, "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw=="], + "@biomejs/biome": ["@biomejs/biome@2.3.7", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.7", "@biomejs/cli-darwin-x64": "2.3.7", "@biomejs/cli-linux-arm64": "2.3.7", "@biomejs/cli-linux-arm64-musl": "2.3.7", "@biomejs/cli-linux-x64": "2.3.7", "@biomejs/cli-linux-x64-musl": "2.3.7", "@biomejs/cli-win32-arm64": "2.3.7", "@biomejs/cli-win32-x64": "2.3.7" }, "bin": { "biome": "bin/biome" } }, "sha512-CTbAS/jNAiUc6rcq94BrTB8z83O9+BsgWj2sBCQg9rD6Wkh2gjfR87usjx0Ncx0zGXP1NKgT7JNglay5Zfs9jw=="], + + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LirkamEwzIUULhXcf2D5b+NatXKeqhOwilM+5eRkbrnr6daKz9rsBL0kNZ16Hcy4b8RFq22SG4tcLwM+yx/wFA=="], + + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-Q4TO633kvrMQkKIV7wmf8HXwF0dhdTD9S458LGE24TYgBjSRbuhvio4D5eOQzirEYg6eqxfs53ga/rbdd8nBKg=="], + + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-inHOTdlstUBzgjDcx0ge71U4SVTbwAljmkfi3MC5WzsYCRhancqfeL+sa4Ke6v2ND53WIwCFD5hGsYExoI3EZQ=="], + + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-/afy8lto4CB8scWfMdt+NoCZtatBUF62Tk3ilWH2w8ENd5spLhM77zKlFZEvsKJv9AFNHknMl03zO67CiklL2Q=="], + + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.7", "", { "os": "linux", "cpu": "x64" }, "sha512-fJMc3ZEuo/NaMYo5rvoWjdSS5/uVSW+HPRQujucpZqm2ZCq71b8MKJ9U4th9yrv2L5+5NjPF0nqqILCl8HY/fg=="], + + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.7", "", { "os": "linux", "cpu": "x64" }, "sha512-CQUtgH1tIN6e5wiYSJqzSwJumHYolNtaj1dwZGCnZXm2PZU1jOJof9TsyiP3bXNDb+VOR7oo7ZvY01If0W3iFQ=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-aJAE8eCNyRpcfx2JJAtsPtISnELJ0H4xVVSwnxm13bzI8RwbXMyVtxy2r5DV1xT3WiSP+7LxORcApWw0LM8HiA=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.7", "", { "os": "win32", "cpu": "x64" }, "sha512-pulzUshqv9Ed//MiE8MOUeeEkbkSHVDVY5Cz5wVAnH1DUqliCQG3j6s1POaITTFqFfo7AVIx2sWdKpx/GS+Nqw=="], + "@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="], "@csstools/color-helpers": ["@csstools/color-helpers@5.1.0", "", {}, "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA=="], diff --git a/config/default.nix b/config/default.nix index 538175682..01f73d9cb 100644 --- a/config/default.nix +++ b/config/default.nix @@ -3,5 +3,6 @@ ./direnv ./hammerspoon ./karabiner + ./nvim ./starship ] diff --git a/config/nvim/.gitignore b/config/nvim/.gitignore new file mode 100644 index 000000000..937aa8833 --- /dev/null +++ b/config/nvim/.gitignore @@ -0,0 +1 @@ +init.lua.backup.* \ No newline at end of file diff --git a/config/nvim/default.nix b/config/nvim/default.nix new file mode 100644 index 000000000..09aea663f --- /dev/null +++ b/config/nvim/default.nix @@ -0,0 +1,18 @@ +{ config, lib, ... }: +let + nvimPackLockJson = ./nvim-pack-lock.json; +in +{ + home.file.".config/nvim/init.lua" = { + source = config.lib.file.mkOutOfStoreSymlink ./init.lua; + force = true; + }; + # Copy nvim-pack-lock.json via activation script instead of symlinking + # so Neovim can write to it. Home Manager always creates symlinks for + # managed files, so we handle this file separately. + home.activation.copyNvimPackLock = lib.hm.dag.entryAfter [ "writeBoundary" ] '' + $DRY_RUN_CMD mkdir -p "$HOME/.config/nvim" + $DRY_RUN_CMD cp -f ${nvimPackLockJson} "$HOME/.config/nvim/nvim-pack-lock.json" + $DRY_RUN_CMD chmod 644 "$HOME/.config/nvim/nvim-pack-lock.json" + ''; +} diff --git a/config/nvim/init.lua b/config/nvim/init.lua new file mode 100644 index 000000000..17f24ee99 --- /dev/null +++ b/config/nvim/init.lua @@ -0,0 +1,1330 @@ +-- ==================================================================================== +-- VIM OPTIONS +-- ==================================================================================== +vim.opt.compatible = false +vim.opt.termsync = true +vim.opt.hidden = true +vim.opt.updatetime = 300 +vim.opt.mouse = "" +vim.opt.inccommand = "nosplit" +vim.opt.splitbelow = true +vim.opt.splitright = true +vim.opt.wrap = true +vim.opt.textwidth = 0 +vim.opt.expandtab = true +vim.opt.smartindent = true +vim.opt.shiftwidth = 2 +vim.opt.softtabstop = 2 +vim.opt.tabstop = 2 +vim.opt.signcolumn = "yes" +vim.opt.scrolloff = 10 +vim.opt.sidescrolloff = 10 +vim.opt.number = true +vim.opt.relativenumber = true +vim.opt.smoothscroll = true +vim.opt.swapfile = false +vim.opt.backup = true +vim.opt.undofile = true +vim.opt.undodir = vim.fn.stdpath("data") .. "undodir" +vim.opt.hlsearch = false +vim.opt.ignorecase = true +vim.opt.incsearch = true +vim.opt.ruler = true +vim.opt.wildmenu = true +vim.opt.autoread = true +vim.opt.completeopt = { "menu", "menuone", "noselect" } +vim.opt.colorcolumn = "80" +vim.opt.backspace = { "indent", "eol", "start" } +vim.opt.spelllang = { "en_us" } +vim.opt.laststatus = 2 +vim.opt.cursorline = true +vim.opt.grepprg = "rg --vimgrep --smart-case --follow" +-- Background will be set automatically based on system theme +vim.opt.termguicolors = true +vim.opt.shortmess:append("c") +vim.opt.timeoutlen = 300 +vim.opt.winborder = "none" + +vim.hl.priorities.semantic_tokens = 10 +vim.g.fugitive_legacy_commands = 0 + +-- ==================================================================================== +-- PLUGIN INSTALLATION +-- ==================================================================================== +vim.pack.add({ + -- UI + { src = "https://github.com/nvim-tree/nvim-web-devicons" }, + { src = "https://github.com/Mofiqul/dracula.nvim" }, + { + src = "https://github.com/f-person/auto-dark-mode.nvim", + priority = 1000, + }, + { src = "https://github.com/folke/sidekick.nvim" }, + { src = "https://github.com/nvim-tree/nvim-tree.lua" }, + { src = "https://github.com/nvim-lualine/lualine.nvim" }, + { src = "https://github.com/stevearc/dressing.nvim" }, + { src = "https://github.com/rcarriga/nvim-notify" }, + { src = "https://github.com/christoomey/vim-tmux-navigator" }, + { src = "https://github.com/asiryk/auto-hlsearch.nvim" }, + { src = "https://github.com/famiu/bufdelete.nvim" }, + { src = "https://github.com/norcalli/nvim-colorizer.lua" }, + { src = "https://github.com/lewis6991/gitsigns.nvim" }, + { src = "https://github.com/akinsho/git-conflict.nvim" }, + + -- TELESCOPE + { src = "https://github.com/nvim-lua/plenary.nvim" }, + { src = "https://github.com/nvim-telescope/telescope-github.nvim" }, + { src = "https://github.com/nvim-telescope/telescope.nvim" }, + + -- CODING + { src = "https://github.com/rgroli/other.nvim" }, + { src = "https://github.com/danymat/neogen" }, + { src = "https://github.com/stevearc/conform.nvim" }, + { src = "https://github.com/zbirenbaum/copilot.lua" }, + { src = "https://github.com/rafamadriz/friendly-snippets" }, + { src = "https://github.com/giuxtaposition/blink-cmp-copilot" }, + { + src = "https://github.com/saghen/blink.cmp", + version = vim.version.range("1.*"), + }, + + -- TPOPE + { src = "https://github.com/tpope/vim-fugitive" }, + { src = "https://github.com/tpope/vim-rhubarb" }, + { src = "https://github.com/tpope/vim-abolish" }, + { src = "https://github.com/tpope/vim-repeat" }, + { src = "https://github.com/tpope/vim-eunuch" }, + { src = "https://github.com/tpope/vim-sleuth" }, + { src = "https://github.com/tpope/vim-speeddating" }, + + -- TREESITTER + { src = "https://github.com/nvim-treesitter/nvim-treesitter" }, + { src = "https://github.com/lukas-reineke/indent-blankline.nvim" }, + { src = "https://github.com/wansmer/treesj" }, + { src = "https://github.com/windwp/nvim-autopairs" }, + { src = "https://github.com/windwp/nvim-ts-autotag" }, + { src = "https://github.com/kylechui/nvim-surround" }, + { src = "https://github.com/folke/todo-comments.nvim" }, + { src = "https://github.com/nvim-treesitter/nvim-treesitter-context" }, + { src = "https://github.com/nvim-treesitter/nvim-treesitter-textobjects" }, + { src = "https://github.com/RRethy/nvim-treesitter-endwise" }, +}) + +-- ==================================================================================== +-- KEYMAPS +-- ==================================================================================== +local opts = { noremap = true, silent = true } +local keymap = vim.keymap.set + +-- @keymap : Set as leader key +--Remap space as leader key +keymap("", "", "", opts) +vim.g.mapleader = " " +vim.g.maplocalleader = " " + +-- ==================================================================================== +-- QUICKFIX LISTS +-- ==================================================================================== +-- @keymap co: Open quickfix list +keymap("n", "co", ":copen", opts) +-- @keymap cc: Close quickfix list +keymap("n", "cc", ":cclose", opts) + +-- ==================================================================================== +-- BUFFER AND FILE OPERATIONS +-- ==================================================================================== +-- Helper function to cycle buffers (includes unlisted buffers like nvim-tree) +local function cycle_buffer(direction) + local current_buf = vim.api.nvim_get_current_buf() + + -- Get all loaded buffers (this includes buffers in windows and loaded but not visible) + local buffer_set = {} + local buffers = {} + + -- First, get buffers from windows (includes nvim-tree) + for _, win in ipairs(vim.api.nvim_list_wins()) do + local buf = vim.api.nvim_win_get_buf(win) + if vim.api.nvim_buf_is_valid(buf) and vim.api.nvim_buf_is_loaded(buf) and not buffer_set[buf] then + buffer_set[buf] = true + table.insert(buffers, buf) + end + end + + -- Then add all other loaded buffers (files that aren't in windows) + for _, buf in ipairs(vim.api.nvim_list_bufs()) do + if vim.api.nvim_buf_is_valid(buf) and vim.api.nvim_buf_is_loaded(buf) and not buffer_set[buf] then + buffer_set[buf] = true + table.insert(buffers, buf) + end + end + + -- Ensure current buffer is in the list + if not buffer_set[current_buf] then + table.insert(buffers, current_buf) + buffer_set[current_buf] = true + end + + -- Need at least 2 buffers to cycle + if #buffers < 2 then + return + end + + -- Find current buffer index + local current_idx = nil + for i, buf in ipairs(buffers) do + if buf == current_buf then + current_idx = i + break + end + end + + -- This should never happen now, but safety check + if not current_idx then + current_idx = 1 + end + + -- Calculate next/previous index with wrapping + local next_idx + if direction == "next" then + next_idx = current_idx + 1 + if next_idx > #buffers then + next_idx = 1 + end + else -- prev + next_idx = current_idx - 1 + if next_idx < 1 then + next_idx = #buffers + end + end + + -- Get target buffer + local target_buf = buffers[next_idx] + + -- Safety check + if not vim.api.nvim_buf_is_valid(target_buf) then + return + end + + -- Find window containing target buffer + local target_win = nil + for _, win in ipairs(vim.api.nvim_list_wins()) do + if vim.api.nvim_win_is_valid(win) and vim.api.nvim_win_get_buf(win) == target_buf then + target_win = win + break + end + end + + if target_win then + -- Buffer is in a window, switch to that window + vim.api.nvim_set_current_win(target_win) + else + -- Buffer not in any window, switch to it in current window + vim.api.nvim_set_current_buf(target_buf) + end +end + +-- @keymap : Cycle to next buffer +vim.keymap.set("n", "", function() + cycle_buffer("next") +end, { noremap = true, silent = true }) + +-- @keymap : Cycle to previous buffer +vim.keymap.set("n", "", function() + cycle_buffer("prev") +end, { noremap = true, silent = true }) + +-- Alternative: ]b and [b for buffer navigation (vim convention) +-- @keymap ]b: Cycle to next buffer +keymap("n", "]b", function() + cycle_buffer("next") +end, opts) + +-- @keymap [b: Cycle to previous buffer +keymap("n", "[b", function() + cycle_buffer("prev") +end, opts) +-- @keymap q: Close current buffer +keymap("n", "q", ":Bdelete", opts) +-- @keymap bad: Wipe all buffers +keymap("n", "bad", ":%bwipeout!:intro", opts) +-- @keymap w: Write file +keymap("n", "w", ":write", opts) +-- @keymap r: Reload Neovim configuration +keymap("n", "r", function() + vim.cmd("source $MYVIMRC") +end, opts) +-- @keymap h: Show help (all keymaps and commands) +keymap("n", "h", ":Help", opts) +-- @keymap ZZ: Save all buffers and quit +keymap("n", "ZZ", ":wa:q", opts) + +-- ==================================================================================== +-- TERMINAL +-- ==================================================================================== +-- Terminal toggle state +local terminal_bufnr = nil +local terminal_winid = nil + +-- Toggle terminal function +local function toggle_terminal() + -- Check if terminal window is currently visible + if terminal_winid and vim.api.nvim_win_is_valid(terminal_winid) then + -- Terminal is visible, hide it (but keep the buffer) + vim.api.nvim_win_hide(terminal_winid) + terminal_winid = nil + return + end + + -- Check if terminal buffer exists + if terminal_bufnr and vim.api.nvim_buf_is_valid(terminal_bufnr) then + -- Check if buffer is already displayed in any window + local existing_winid = nil + for _, winid in ipairs(vim.api.nvim_list_wins()) do + if vim.api.nvim_win_get_buf(winid) == terminal_bufnr then + existing_winid = winid + break + end + end + + if existing_winid then + -- Buffer is already displayed, focus that window + vim.api.nvim_set_current_win(existing_winid) + terminal_winid = existing_winid + else + -- Terminal buffer exists but window is hidden, show it + vim.cmd("10split") + vim.api.nvim_set_current_buf(terminal_bufnr) + terminal_winid = vim.api.nvim_get_current_win() + end + return + end + + -- Terminal doesn't exist, create it + vim.cmd("10split | terminal") + terminal_bufnr = vim.api.nvim_get_current_buf() + terminal_winid = vim.api.nvim_get_current_win() +end + +-- @keymap j: Toggle terminal (show/hide) +keymap("n", "j", toggle_terminal, opts) + +-- ==================================================================================== +-- NAVIGATION +-- ==================================================================================== +-- @keymap n: Next search result and center screen +keymap("n", "n", "nzzzv", opts) +-- @keymap N: Previous search result and center screen +keymap("n", "N", "Nzzzv", opts) +-- @keymap : Scroll up and center screen +keymap("n", "", "zz", opts) +-- @keymap : Scroll down and center screen +keymap("n", "", "zz", opts) +-- @keymap : Jump to older position and center screen +keymap("n", "", "zz", opts) +-- @keymap : Jump to newer position and center screen +keymap("n", "", "zz", opts) + +-- ==================================================================================== +-- CLIPBOARD OPERATIONS +-- ==================================================================================== +-- @keymap y: Yank to system clipboard +-- system clipboard integration +keymap({ "n", "v" }, "y", '"+y', opts) + +-- @keymap py: Copy current file path to clipboard +-- copy the current file path +keymap("n", "py", ':let @" = expand("%:p")', opts) + +-- @keymap d: Delete to blackhole register +-- delete to blackhole +keymap({ "n", "v" }, "d", '"_d', opts) + +-- ==================================================================================== +-- GIT OPERATIONS +-- ==================================================================================== +-- @keymap gs: Open Git status in new tab +keymap("n", "gs", ":tab Git", opts) +-- @keymap : Open Git mergetool in new tab +keymap("n", "", ":tab Git mergetool", opts) +-- @keymap gd: Preview hunk inline +keymap("n", "gd", ":Gitsign preview_hunk_inline", opts) + +-- ==================================================================================== +-- VISUAL MODE OPERATIONS +-- ==================================================================================== +-- @keymap <: Decrease indent (visual mode) +keymap("v", "<", ": Increase indent (visual mode) +keymap("v", ">", ">gv", opts) + +-- @keymap p: Paste without replacing clipboard (visual mode) +-- If I visually select words and paste from clipboard, don't replace my +-- clipboard with the selected word, instead keep my old word in the +-- clipboard +keymap("v", "p", '"_dP', opts) + +-- @keymap K: Move selected text up (visual mode) +keymap("x", "K", ":move '<-2gv-gv", opts) +-- @keymap J: Move selected text down (visual mode) +keymap("x", "J", ":move '>+1gv-gv", opts) + +-- ==================================================================================== +-- INSERT MODE OPERATIONS +-- ==================================================================================== +-- @keymap -/_/,/./;/:/!/?: Add undo breakpoints after punctuation +-- in insert mode, adds new undo points after more some chars: +for _, lhs in ipairs({ "-", "_", ",", ".", ";", ":", "/", "!", "?" }) do + keymap("i", lhs, lhs .. "u", opts) +end + +-- @keymap jj: Exit insert mode +-- exit insert mode with jj +keymap("i", "jj", "", opts) + +-- ==================================================================================== +-- UI CONFIGURATION +-- ==================================================================================== + +vim.api.nvim_set_hl(0, "StatusLine", { reverse = false }) +vim.api.nvim_set_hl(0, "StatusLineNC", { reverse = false }) + +require("dressing").setup({ input = { insert_only = true } }) + +local notify = require("notify") +notify.setup({ + render = "compact", + stages = "static", +}) +vim.notify = notify + +local section_b = { "branch", "diff", { "diagnostics", sources = { "nvim_workspace_diagnostic" } } } +local section_c = { "%=", { "filename", file_status = false, path = 1 } } +local lualine_config = { + options = { + theme = function() + -- Dynamically determine theme based on background + return vim.o.background == "dark" and "dracula" or "auto" + end, + component_separators = "", + section_separators = "", + }, + sections = { + lualine_b = section_b, + lualine_c = section_c, + }, + inactive_sections = { + lualine_c = section_c, + lualine_x = { "location" }, + }, +} +require("lualine").setup(lualine_config) + +-- Auto theme switching based on system appearance +require("auto-dark-mode").setup({ + update_interval = 1000, -- Check for theme changes every second + set_dark_mode = function() + vim.api.nvim_set_option_value("background", "dark", {}) + vim.cmd("colorscheme dracula") + -- Refresh lualine to update theme + if package.loaded["lualine"] then + require("lualine").setup(lualine_config) + end + end, + set_light_mode = function() + vim.api.nvim_set_option_value("background", "light", {}) + vim.cmd("colorscheme default") + -- Refresh lualine to update theme + if package.loaded["lualine"] then + require("lualine").setup(lualine_config) + end + end, +}) + +require("auto-hlsearch").setup({}) +require("gitsigns").setup({}) +require("git-conflict").setup({}) + +-- ==================================================================================== +-- DIAGNOSTICS +-- ==================================================================================== +-- setup diagnostics +vim.diagnostic.config({ + underline = true, + update_in_insert = false, + severity_sort = true, +}) + +-- @keymap xx: Open diagnostics in quickfix +keymap("n", "xx", vim.diagnostic.setqflist, opts) + +-- set up diagnostic signs +local severity = vim.diagnostic.severity +local signs = { + [severity.ERROR] = "๎ช‡", + [severity.WARN] = "๎ฉฌ", + [severity.INFO] = "๎ฉด", + [severity.HINT] = "๓ฐŒถ", +} +vim.diagnostic.config({ + signs = { + text = signs, + }, +}) + +-- ==================================================================================== +-- CODE NAVIGATION +-- ==================================================================================== +require("other-nvim").setup({ mappings = { "golang" } }) +-- @keymap oo: Navigate to related file (other.nvim) +keymap("n", "oo", ":Other", opts) +-- @keymap ov: Navigate to related file in vertical split +keymap("n", "ov", ":OtherVSplit", opts) +-- @keymap os: Navigate to related file in horizontal split +keymap("n", "os", ":OtherSplit", opts) + +-- @keymap gco: Generate code annotation (neogen) +require("neogen").setup() +keymap("n", "gco", ":Neogen", opts) + +-- ==================================================================================== +-- COMPLETION AND COPILOT +-- ==================================================================================== +require("copilot").setup({ + suggestion = { enabled = false }, + panel = { enabled = false }, +}) + +---@module 'blink.cmp' +---@type blink.cmp.Config +local cmp = require("blink.cmp") +cmp.setup({ + appearance = { + nerd_font_variant = "mono", + kind_icons = { + Array = "๎ชŠ", + Boolean = "๎ช", + Class = "๎ญ›", + Color = "๎ญœ", + Constant = "๎ญ", + Constructor = "๎ชŒ", + Copilot = "๏„“", + Enum = "๎ช•", + EnumMember = "๎ญž", + Event = "๎ช†", + Field = "๎ญŸ", + File = "๎ฉป", + Folder = "๓ฐ‰‹", + Function = "๎ชŒ", + Interface = "๎ญก", + Key = "๎ช“", + Keyword = "๎ญข", + Method = "๎ชŒ", + Module = "๎ฌฉ", + Namespace = "๎ช‹", + Null = "๓ฐŸข", + Number = "๎ช", + Object = "๎ช‹", + Operator = "๎ญค", + Package = "๎ฌฉ", + Property = "๎ญฅ", + Reference = "๎ฌถ", + Snippet = "๎ญฆ", + String = "๎ฎ", + Struct = "๎ช‘", + Text = "๎ช“", + TypeParameter = "๎ช’", + Unit = "๎ช–", + Value = "๎ช“", + Variable = "๎ชˆ", + }, + }, + cmdline = { + sources = function() + local type = vim.fn.getcmdtype() + if type == "/" or type == "?" then + return { "buffer" } + end + if type == ":" then + return { "cmdline" } + end + return {} + end, + completion = { + menu = { + draw = { + columns = { + { "kind_icon", "label", gap = 1 }, + { "kind" }, + }, + }, + }, + }, + }, + sources = { + default = { "lsp", "path", "snippets", "buffer", "copilot" }, + providers = { + lsp = { + min_keyword_length = 0, + score_offset = 0, + }, + path = { + min_keyword_length = 0, + }, + snippets = { + min_keyword_length = 2, + }, + buffer = { + min_keyword_length = 5, + max_items = 5, + }, + copilot = { + name = "copilot", + module = "blink-cmp-copilot", + score_offset = 1000, + min_keyword_length = 0, + async = true, + override = { + -- copilot complete on space, new line, etc as well... + get_trigger_characters = function(self) + local trigger_characters = self:get_trigger_characters() + vim.list_extend(trigger_characters, { "\n", "\t", " " }) + return trigger_characters + end, + }, + transform_items = function(_, items) + local CompletionItemKind = require("blink.cmp.types").CompletionItemKind + local kind_idx = #CompletionItemKind + 1 + CompletionItemKind[kind_idx] = "Copilot" + for _, item in ipairs(items) do + item.kind = kind_idx + end + return items + end, + }, + }, + }, + completion = { + accept = { auto_brackets = { enabled = true } }, + keyword = { + range = "full", + }, + menu = { + draw = { + columns = { + { "kind_icon", "label", gap = 1 }, + { "kind" }, + }, + }, + }, + }, +}) + +-- @keymap : Accept copilot/completion (selects first if none selected) +-- Use Tab to accept copilot/completions +keymap("i", "", function() + -- Check if completion menu is visible (works for blink.cmp and built-in) + if vim.fn.pumvisible() == 1 then + -- Since noselect is enabled, first select the first item, then accept + -- This ensures we accept the top suggestion (or navigate to copilot if needed) + local current_selection = vim.fn.complete_info({ "selected" }).selected + if current_selection == -1 then + -- Nothing selected, select first item + return vim.api.nvim_replace_termcodes("", true, false, true) + else + -- Something is selected, just accept it + return vim.api.nvim_replace_termcodes("", true, false, true) + end + end + -- No completion menu visible, insert tab normally + return vim.api.nvim_replace_termcodes("", true, false, true) +end, { expr = true, noremap = true, silent = true }) + +-- ==================================================================================== +-- UTILITY FUNCTIONS +-- ==================================================================================== +local function copen() + if vim.fn.getqflist({ size = 0 }).size > 1 then + vim.cmd("copen") + else + vim.cmd("cclose") + end +end + +local function cclear() + vim.fn.setqflist({}, "r") +end + +-- @command Finder: Open current file directory in Finder/file explorer +-- Opens the directory of the current file in Finder/file explorer. +vim.api.nvim_create_user_command("Finder", "!open %:h", {}) + +-- @command ThemeRefresh: Refresh theme based on system appearance +-- Refreshes the colorscheme based on current system theme (dark/light). +vim.api.nvim_create_user_command("ThemeRefresh", function() + require("auto-dark-mode").update() +end, { desc = "Refresh theme based on system appearance" }) + +-- ==================================================================================== +-- AUTOCMDS +-- ==================================================================================== +-- Check for file changes on BufEnter, CursorHold, CursorHoldI, and FocusGained +vim.api.nvim_create_autocmd({ + "BufEnter", + "CursorHold", + "CursorHoldI", + "FocusGained", +}, { + pattern = "*", + command = "if mode() != 'c' | checktime | endif", +}) + +-- Resize splits if window got resized +vim.api.nvim_create_autocmd({ "VimResized" }, { + callback = function() + vim.cmd("tabdo wincmd =") + end, +}) + +-- Start in insert mode when opening git commit messages +vim.api.nvim_create_autocmd("FileType", { + pattern = "gitcommit", + command = "startinsert", +}) + +-- Ensure the parent folder exists when creating new files (for LSP context) +vim.api.nvim_create_autocmd("BufNewFile", { + pattern = "*", + callback = function() + local dir = vim.fn.expand(":p:h") + if vim.fn.isdirectory(dir) == 0 then + vim.fn.mkdir(dir, "p") + vim.cmd([[ :e % ]]) + end + end, +}) + +-- Highlight on yank (briefly highlight yanked text) +-- See `:help vim.highlight.on_yank()` +vim.api.nvim_create_autocmd("TextYankPost", { + pattern = "*", + callback = function() + vim.hl.on_yank() + end, +}) + +-- Open help window in a vertical split to the right +vim.api.nvim_create_autocmd("BufWinEnter", { + pattern = { "*.txt" }, + callback = function() + if vim.o.filetype == "help" then + vim.cmd.wincmd("L") + end + end, +}) + +-- Add keymaps for git filetype buffers +vim.api.nvim_create_autocmd("FileType", { + pattern = "git", + callback = function() + local bufnr = vim.api.nvim_get_current_buf() + local buf_opts = { noremap = true, silent = true, buffer = bufnr } + -- @keymap gq: Close git buffer + keymap("n", "gq", ":silent! close", buf_opts) + end, +}) + +-- Configure fugitive buffers with git keymaps (push, pull, commit, etc.) +vim.api.nvim_create_autocmd("FileType", { + pattern = "fugitive", + callback = function() + local bufnr = vim.api.nvim_get_current_buf() + + local function async_git(args, success_msg, error_msg) + vim.system({ "git", unpack(args) }, {}, function(obj) + vim.schedule(function() + if obj.code == 0 then + vim.notify(success_msg, vim.log.levels.INFO) + else + vim.notify(error_msg, vim.log.levels.ERROR) + end + end) + end) + end + + vim.cmd("normal )k=") + + local buf_opts = { noremap = true, silent = true, buffer = bufnr } + -- @keymap gp: Git push (fugitive buffer) + keymap("n", "gp", function() + async_git({ "push", "--quiet" }, "Pushed!", "Push failed!") + vim.cmd("silent! close") + end, buf_opts) + + -- @keymap gP: Git pull with rebase (fugitive buffer) + keymap("n", "gP", function() + async_git({ "pull", "--rebase" }, "Pulled!", "Pull failed!") + vim.cmd("silent! close") + end, buf_opts) + + -- @keymap go: Git push and open PR (fugitive buffer) + keymap("n", "go", function() + async_git({ "ppr" }, "Pushed and opened PR URL!", "Failed to push or open PR") + vim.cmd("silent! close") + end, buf_opts) + + -- @keymap cc: Git commit with sign-off (fugitive buffer) + -- @keymap gq: Close fugitive buffer + keymap("n", "cc", ":silent! Git commit -s", buf_opts) + keymap("n", "gq", ":silent! close", buf_opts) + end, +}) + +-- Enable spell check and set textwidth for git commit messages +vim.api.nvim_create_autocmd("FileType", { + pattern = "gitcommit", + callback = function() + vim.opt_local.spell = true + vim.opt_local.textwidth = 72 + end, +}) + +-- Add q keymap to close quickfix and help buffers +vim.api.nvim_create_autocmd("FileType", { + pattern = { "qf", "help" }, + callback = function() + -- @keymap q: Close quickfix/help buffer + keymap("n", "q", ":bdelete", { + buffer = vim.api.nvim_get_current_buf(), + noremap = true, + silent = true, + }) + end, +}) + +-- Track terminal buffer and window, and set up terminal mode keymap +vim.api.nvim_create_autocmd("TermOpen", { + callback = function() + local bufnr = vim.api.nvim_get_current_buf() + local winid = vim.api.nvim_get_current_win() + + -- Update terminal tracking if this is our managed terminal + if terminal_bufnr == nil or terminal_bufnr == bufnr then + terminal_bufnr = bufnr + terminal_winid = winid + end + + -- @keymap j: Exit terminal mode and hide terminal + keymap("t", "j", function() + -- Exit terminal mode properly + vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("", true, false, true), "n", false) + -- Hide the terminal window but keep the buffer + vim.schedule(function() + if vim.api.nvim_win_is_valid(terminal_winid) then + vim.api.nvim_win_hide(terminal_winid) + terminal_winid = nil + end + end) + end, { + buffer = bufnr, + noremap = true, + silent = true, + }) + + -- @keymap : Exit terminal mode (keeps terminal visible) + -- Standard Neovim way: also works + keymap("t", "", vim.api.nvim_replace_termcodes("", true, false, true), { + buffer = bufnr, + noremap = true, + silent = true, + }) + + -- @keymap : Trigger completion in terminal mode + -- Note: Terminal completion works differently - you may need to use for file completion + -- or configure your shell's completion (e.g., fish, zsh with completion plugins) + end, +}) + +-- Clean up terminal tracking when terminal buffer is closed +vim.api.nvim_create_autocmd("BufDelete", { + callback = function(args) + if args.buf == terminal_bufnr then + terminal_bufnr = nil + terminal_winid = nil + end + end, +}) + +-- Enable spell check and set textwidth for markdown files +vim.api.nvim_create_autocmd("FileType", { + pattern = "markdown", + callback = function() + vim.opt_local.spell = true + vim.opt_local.textwidth = 80 + vim.opt_local.formatoptions:remove("ct") + end, +}) + +-- ==================================================================================== +-- TREESITTER AND SYNTAX +-- ==================================================================================== +-- syntax, indentation, treesitter, etc. +require("ibl").setup({ + indent = { char = "โ”‚" }, + exclude = { filetypes = { "help" } }, + scope = { enabled = false }, +}) + +require("nvim-autopairs").setup() + +require("nvim-ts-autotag").setup() +require("nvim-surround").setup() +require("todo-comments").setup() + +require("treesitter-context").setup() + +-- ==================================================================================== +-- TREESITTER CONFIGURATION +-- ==================================================================================== +---@diagnostic disable-next-line: missing-fields +require("nvim-treesitter.configs").setup({ + ensure_installed = { + "arduino", + "awk", + "bash", + "cpp", + "css", + "csv", + "diff", + "dockerfile", + "fish", + "git_config", + "git_rebase", + "gitattributes", + "gitcommit", + "gitignore", + "go", + "gomod", + "gosum", + "gowork", + "graphql", + "hcl", + "html", + "http", + "http", + "ini", + "javascript", + "jq", + "json", + "lua", + "make", + "markdown", + "markdown_inline", + "mermaid", + "python", + "query", + "regex", + "ruby", + "scss", + "sql", + "ssh_config", + "templ", + "terraform", + "toml", + "vhs", + "vim", + "vimdoc", + "yaml", + "zig", + }, + highlight = { + enable = true, + }, + indent = { + enable = true, + }, + endwise = { + enable = true, + }, + autopairs = { + enable = true, + }, + context_commentstring = { + enable = true, + enable_autocmd = false, + }, + incremental_selection = { + enable = true, + keymaps = { + init_selection = "", + node_incremental = "", + node_decremental = "", + scope_incremental = "", + }, + }, + auto_install = true, + textobjects = { + enable = true, + lookahead = true, + swap = { + enable = true, + swap_next = { + -- @keymap a: Swap next parameter (treesitter) + ["a"] = "@parameter.inner", + }, + swap_previous = { + -- @keymap A: Swap previous parameter (treesitter) + ["A"] = "@parameter.inner", + }, + }, + move = { + enable = true, + set_jumps = true, + goto_next_start = { + -- @keymap ]f: Go to next function start (treesitter) + -- @keymap ]c: Go to next class start (treesitter) + -- @keymap ]a: Go to next parameter start (treesitter) + ["]f"] = "@function.inner", + ["]c"] = "@class.inner", + ["]a"] = "@parameter.inner", + }, + goto_next_end = { + -- @keymap ]F: Go to next function end (treesitter) + -- @keymap ]C: Go to next class end (treesitter) + -- @keymap ]A: Go to next parameter end (treesitter) + ["]F"] = "@function.inner", + ["]C"] = "@class.inner", + ["]A"] = "@parameter.inner", + }, + goto_previous_start = { + -- @keymap [f: Go to previous function start (treesitter) + -- @keymap [c: Go to previous class start (treesitter) + -- @keymap [a: Go to previous parameter start (treesitter) + ["[f"] = "@function.inner", + ["[c"] = "@class.inner", + ["[a"] = "@parameter.inner", + }, + goto_previous_end = { + -- @keymap [F: Go to previous function end (treesitter) + -- @keymap [C: Go to previous class end (treesitter) + -- @keymap [A: Go to previous parameter end (treesitter) + ["[F"] = "@function.inner", + ["[C"] = "@class.inner", + ["[A"] = "@parameter.inner", + }, + }, + select = { + enable = true, + keymaps = { + -- @keymap af: Select function outer (treesitter) + -- @keymap if: Select function inner (treesitter) + -- @keymap ac: Select conditional outer (treesitter) + -- @keymap ic: Select conditional inner (treesitter) + -- @keymap aa: Select parameter outer (treesitter) + -- @keymap ia: Select parameter inner (treesitter) + -- @keymap av: Select variable outer (treesitter) + -- @keymap iv: Select variable inner (treesitter) + ["af"] = "@function.outer", + ["if"] = "@function.inner", + + ["ac"] = "@conditional.outer", + ["ic"] = "@conditional.inner", + + ["aa"] = "@parameter.outer", + ["ia"] = "@parameter.inner", + + ["av"] = "@variable.outer", + ["iv"] = "@variable.inner", + }, + }, + }, +}) + +-- @keymap st: Toggle treesitter split/join +local treesj = require("treesj") +treesj.setup({ use_default_keymaps = false }) +keymap("n", "st", treesj.toggle, opts) + +-- ==================================================================================== +-- TELESCOPE CONFIGURATION +-- ==================================================================================== +local telescope = require("telescope") +telescope.setup({ + defaults = { + pickers = { + find_files = { + theme = "ivy", + }, + }, + prompt_prefix = " ๏€‚ ", + selection_caret = " โฏ ", + entry_prefix = " ", + multi_icon = "+ ", + path_display = { "filename_first" }, + vimgrep_arguments = { + "rg", + "--color=never", + "--no-heading", + "--with-filename", + "--line-number", + "--column", + "--smart-case", + "--sort=path", + }, + }, +}) +telescope.load_extension("gh") + +local function ivy(iopts) + return require("telescope.themes").get_ivy(iopts) +end + +local builtin = require("telescope.builtin") +-- @keymap : Find files (Telescope) +vim.keymap.set("n", "", function() + builtin.find_files(ivy({ + find_command = { + "fd", + "--type", + "f", + "--strip-cwd-prefix", + "--hidden", + }, + })) +end, opts) + +-- @keymap of: Open old files (Telescope) +keymap("n", "of", function() + builtin.oldfiles(ivy({ + only_cwd = true, + })) +end, opts) + +-- @keymap lg: Live grep (Telescope) +keymap("n", "lg", function() + builtin.live_grep(ivy()) +end, opts) + +-- @keymap fb: Find buffers (Telescope) +keymap("n", "fb", function() + builtin.buffers(ivy()) +end, opts) + +-- @keymap fh: Find help tags (Telescope) +keymap("n", "fh", function() + builtin.help_tags(ivy()) +end, opts) + +-- @keymap fc: Find commands (Telescope) +keymap("n", "fc", function() + builtin.commands(ivy()) +end, opts) + +-- @keymap fr: Resume last Telescope search +keymap("n", "fr", function() + builtin.resume(ivy()) +end, opts) + +-- @keymap fq: Find in quickfix (Telescope) +keymap("n", "fq", function() + builtin.quickfix(ivy()) +end, opts) + +-- @keymap /: Fuzzy find in current buffer (Telescope) +keymap("n", "/", function() + builtin.current_buffer_fuzzy_find(ivy()) +end, opts) + +-- @keymap ghi: Find GitHub issues (Telescope) +keymap("n", "ghi", function() + telescope.extensions.gh.issues(ivy()) +end, opts) + +-- ==================================================================================== +-- FILE TREE +-- ==================================================================================== +-- @keymap b: Toggle file tree (nvim-tree) +require("nvim-tree").setup({ + view = { + width = 30, + }, + actions = { + open_file = { + quit_on_open = false, + }, + }, + on_attach = function(bufnr) + local api = require("nvim-tree.api") + + local function opts(desc) + return { desc = "nvim-tree: " .. desc, buffer = bufnr, noremap = true, silent = true, nowait = true } + end + + -- Default mappings. You can override them in the mappings table below + -- @keymap l: Open file/folder (nvim-tree) + vim.keymap.set("n", "l", api.node.open.edit, opts("Open")) + -- @keymap : Open file/folder (nvim-tree) + vim.keymap.set("n", "", api.node.open.edit, opts("Open")) + -- @keymap o: Open file/folder (nvim-tree) + vim.keymap.set("n", "o", api.node.open.edit, opts("Open")) + -- @keymap h: Close folder (nvim-tree) + vim.keymap.set("n", "h", api.node.navigate.parent_close, opts("Close Directory")) + -- @keymap v: Open in vertical split (nvim-tree) + vim.keymap.set("n", "v", api.node.open.vertical, opts("Open: Vertical Split")) + -- @keymap s: Open in horizontal split (nvim-tree) + vim.keymap.set("n", "s", api.node.open.horizontal, opts("Open: Horizontal Split")) + -- @keymap t: Open in new tab (nvim-tree) + vim.keymap.set("n", "t", api.node.open.tab, opts("Open: New Tab")) + -- @keymap i: Open in horizontal split (nvim-tree) + vim.keymap.set("n", "i", api.node.open.horizontal, opts("Open: Horizontal Split")) + -- @keymap a: Add file/folder (nvim-tree) + vim.keymap.set("n", "a", api.fs.create, opts("Create")) + -- @keymap d: Delete file/folder (nvim-tree) + vim.keymap.set("n", "d", api.fs.remove, opts("Delete")) + -- @keymap r: Rename file/folder (nvim-tree) + vim.keymap.set("n", "r", api.fs.rename, opts("Rename")) + -- @keymap y: Copy absolute path (nvim-tree) + vim.keymap.set("n", "y", api.fs.copy.absolute_path, opts("Copy Absolute Path")) + -- @keymap Y: Copy relative path (nvim-tree) + vim.keymap.set("n", "Y", api.fs.copy.relative_path, opts("Copy Relative Path")) + -- @keymap c: Change directory (nvim-tree) + vim.keymap.set("n", "c", api.tree.change_root_to_node, opts("CD")) + -- @keymap u: Go to parent directory (nvim-tree) + vim.keymap.set("n", "u", api.tree.change_root_to_parent, opts("Up")) + -- @keymap q: Close tree (nvim-tree) + vim.keymap.set("n", "q", api.tree.close, opts("Close")) + -- @keymap R: Refresh tree (nvim-tree) + vim.keymap.set("n", "R", api.tree.reload, opts("Refresh")) + -- @keymap ?: Toggle help (nvim-tree) + vim.keymap.set("n", "?", api.tree.toggle_help, opts("Help")) + -- @keymap .: Run file command (nvim-tree) + vim.keymap.set("n", ".", api.node.run.cmd, opts("Run Command")) + -- @keymap : Rename file/folder (nvim-tree) + vim.keymap.set("n", "", api.fs.rename_basename, opts("Rename: Basename")) + -- @keymap : Cut file/folder (nvim-tree) + vim.keymap.set("n", "", api.fs.cut, opts("Cut")) + -- @keymap : Copy file/folder (nvim-tree) + vim.keymap.set("n", "", api.fs.copy.node, opts("Copy")) + -- @keymap : Paste file/folder (nvim-tree) + vim.keymap.set("n", "", api.fs.paste, opts("Paste")) + -- @keymap [c: Navigate to previous sibling (nvim-tree) + vim.keymap.set("n", "[c", api.node.navigate.sibling.prev, opts("Prev Sibling")) + -- @keymap ]c: Navigate to next sibling (nvim-tree) + vim.keymap.set("n", "]c", api.node.navigate.sibling.next, opts("Next Sibling")) + -- @keymap p: Preview file (nvim-tree) + vim.keymap.set("n", "p", api.node.open.preview, opts("Open Preview")) + -- @keymap : Close folder (nvim-tree) + vim.keymap.set("n", "", api.node.navigate.parent_close, opts("Close Directory")) + -- @keymap : Open preview and keep focus (nvim-tree) + vim.keymap.set("n", "", api.node.open.preview, opts("Open Preview")) + -- @keymap H: Toggle dotfiles (nvim-tree) + vim.keymap.set("n", "H", api.tree.toggle_gitignore_filter, opts("Toggle Git Ignore")) + -- @keymap I: Toggle dotfiles (nvim-tree) + vim.keymap.set("n", "I", api.tree.toggle_hidden_filter, opts("Toggle Dotfiles")) + end, +}) +keymap("n", "b", ":NvimTreeToggle", opts) + +-- ==================================================================================== +-- HELP COMMAND +-- ==================================================================================== +-- @command Help: Show all keymaps and commands from init.lua +vim.api.nvim_create_user_command("Help", function() + local config_file = vim.fn.stdpath("config") .. "/init.lua" + local file = io.open(config_file, "r") + if not file then + vim.notify("Could not read config file: " .. config_file, vim.log.levels.ERROR) + return + end + + local keymaps = {} + local commands = {} + local plugins = {} + local current_section = "General" + + for line in file:lines() do + -- Parse @keymap comments + local key, desc = line:match("^%s*%-%-%s*@keymap%s+(.+):%s*(.+)$") + if key and desc then + table.insert(keymaps, { key = key, desc = desc, section = current_section }) + end + + -- Parse @command comments + local cmd, cmd_desc = line:match("^%s*%-%-%s*@command%s+(.+):%s*(.+)$") + if cmd and cmd_desc then + table.insert(commands, { cmd = cmd, desc = cmd_desc }) + end + + -- Parse @plugin comments + local plugin, plugin_desc = line:match("^%s*%-%-%s*@plugin%s+(.+):%s*(.+)$") + if plugin and plugin_desc then + table.insert(plugins, { plugin = plugin, desc = plugin_desc }) + end + + -- Track sections + if line:match("^%-%-%s*@section") or line:match("^%-%-%s*%-%-%-") then + current_section = line:match("UI") and "UI" + or line:match("CODING") and "Coding" + or line:match("TPOPE") and "Plugins" + or line:match("treesitter") and "Treesitter" + or "General" + end + end + + file:close() + + -- Create help buffer + local buf = vim.api.nvim_create_buf(false, true) + local lines = {} + + table.insert(lines, "Neovim Configuration Help") + table.insert(lines, "=" .. string.rep("=", 50)) + table.insert(lines, "") + + -- Keymaps section + table.insert(lines, "KEYMAPS") + table.insert(lines, string.rep("-", 50)) + table.insert(lines, "") + for _, km in ipairs(keymaps) do + local key = km.key:gsub("", "") + table.insert(lines, string.format(" %-25s %s", key, km.desc)) + end + table.insert(lines, "") + + -- Commands section + if #commands > 0 then + table.insert(lines, "COMMANDS") + table.insert(lines, string.rep("-", 50)) + table.insert(lines, "") + for _, cmd in ipairs(commands) do + table.insert(lines, string.format(" :%-24s %s", cmd.cmd, cmd.desc)) + end + table.insert(lines, "") + end + + -- Plugins section + if #plugins > 0 then + table.insert(lines, "PLUGINS") + table.insert(lines, string.rep("-", 50)) + table.insert(lines, "") + for _, plugin in ipairs(plugins) do + table.insert(lines, string.format(" %-25s %s", plugin.plugin, plugin.desc)) + end + table.insert(lines, "") + end + + table.insert(lines, "=" .. string.rep("=", 50)) + table.insert(lines, "Press 'q' to close") + + vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) + vim.api.nvim_buf_set_option(buf, "filetype", "help") + vim.api.nvim_buf_set_option(buf, "modifiable", false) + vim.api.nvim_buf_set_name(buf, "neovim-config-help") + + -- Open in split + vim.cmd("split") + vim.api.nvim_set_current_buf(buf) + + -- @keymap q: Close help buffer + -- Map q to close + vim.keymap.set("n", "q", ":close", { buffer = buf, silent = true }) +end, { desc = "Show all keymaps and commands from init.lua" }) diff --git a/config/nvim/nvim-pack-lock.json b/config/nvim/nvim-pack-lock.json new file mode 100644 index 000000000..75f97bbd5 --- /dev/null +++ b/config/nvim/nvim-pack-lock.json @@ -0,0 +1,177 @@ +{ + "plugins": { + "auto-dark-mode.nvim": { + "rev": "e300259ec777a40b4b9e3c8e6ade203e78d15881", + "src": "https://github.com/f-person/auto-dark-mode.nvim" + }, + "auto-hlsearch.nvim": { + "rev": "77502069b22e48ab7736fe9cf53e1fdac1923bca", + "src": "https://github.com/asiryk/auto-hlsearch.nvim" + }, + "blink-cmp-copilot": { + "rev": "439cff78780c033aa23cf061d7315314b347e3c1", + "src": "https://github.com/giuxtaposition/blink-cmp-copilot" + }, + "blink.cmp": { + "rev": "b19413d214068f316c78978b08264ed1c41830ec", + "src": "https://github.com/saghen/blink.cmp", + "version": "1.0.0 - 2.0.0" + }, + "bufdelete.nvim": { + "rev": "f6bcea78afb3060b198125256f897040538bcb81", + "src": "https://github.com/famiu/bufdelete.nvim" + }, + "conform.nvim": { + "rev": "afce105eb47b0869dfafdac90d82f02ef2419d61", + "src": "https://github.com/stevearc/conform.nvim" + }, + "copilot.lua": { + "rev": "4383e05a47493d7ff77b058c0548129eb38ec7fb", + "src": "https://github.com/zbirenbaum/copilot.lua" + }, + "dracula.nvim": { + "rev": "ae752c13e95fb7c5f58da4b5123cb804ea7568ee", + "src": "https://github.com/Mofiqul/dracula.nvim" + }, + "dressing.nvim": { + "rev": "2d7c2db2507fa3c4956142ee607431ddb2828639", + "src": "https://github.com/stevearc/dressing.nvim" + }, + "friendly-snippets": { + "rev": "572f5660cf05f8cd8834e096d7b4c921ba18e175", + "src": "https://github.com/rafamadriz/friendly-snippets" + }, + "git-conflict.nvim": { + "rev": "a1badcd070d176172940eb55d9d59029dad1c5a6", + "src": "https://github.com/akinsho/git-conflict.nvim" + }, + "gitsigns.nvim": { + "rev": "cdafc320f03f2572c40ab93a4eecb733d4016d07", + "src": "https://github.com/lewis6991/gitsigns.nvim" + }, + "gruvbox.nvim": { + "rev": "5e0a460d8e0f7f669c158dedd5f9ae2bcac31437", + "src": "https://github.com/ellisonleao/gruvbox.nvim" + }, + "indent-blankline.nvim": { + "rev": "005b56001b2cb30bfa61b7986bc50657816ba4ba", + "src": "https://github.com/lukas-reineke/indent-blankline.nvim" + }, + "lualine.nvim": { + "rev": "3946f0122255bc377d14a59b27b609fb3ab25768", + "src": "https://github.com/nvim-lualine/lualine.nvim" + }, + "neogen": { + "rev": "d7f9461727751fb07f82011051338a9aba07581d", + "src": "https://github.com/danymat/neogen" + }, + "nvim-autopairs": { + "rev": "7a2c97cccd60abc559344042fefb1d5a85b3e33b", + "src": "https://github.com/windwp/nvim-autopairs" + }, + "nvim-colorizer.lua": { + "rev": "a065833f35a3a7cc3ef137ac88b5381da2ba302e", + "src": "https://github.com/norcalli/nvim-colorizer.lua" + }, + "nvim-lspconfig": { + "rev": "a2a277114dbb93e98f04b5087a9b832918b7da15", + "src": "https://github.com/neovim/nvim-lspconfig" + }, + "nvim-notify": { + "rev": "8701bece920b38ea289b457f902e2ad184131a5d", + "src": "https://github.com/rcarriga/nvim-notify" + }, + "nvim-surround": { + "rev": "fcfa7e02323d57bfacc3a141f8a74498e1522064", + "src": "https://github.com/kylechui/nvim-surround" + }, + "nvim-tree.lua": { + "rev": "3fb91e18a727ecc0385637895ec397dea90be42a", + "src": "https://github.com/nvim-tree/nvim-tree.lua" + }, + "nvim-treesitter": { + "rev": "42fc28ba918343ebfd5565147a42a26580579482", + "src": "https://github.com/nvim-treesitter/nvim-treesitter" + }, + "nvim-treesitter-context": { + "rev": "660861b1849256398f70450afdf93908d28dc945", + "src": "https://github.com/nvim-treesitter/nvim-treesitter-context" + }, + "nvim-treesitter-endwise": { + "rev": "a61a9de7965324d4019fb1637b66bfacdcb01f51", + "src": "https://github.com/RRethy/nvim-treesitter-endwise" + }, + "nvim-treesitter-textobjects": { + "rev": "5ca4aaa6efdcc59be46b95a3e876300cfead05ef", + "src": "https://github.com/nvim-treesitter/nvim-treesitter-textobjects" + }, + "nvim-ts-autotag": { + "rev": "c4ca798ab95b316a768d51eaaaee48f64a4a46bc", + "src": "https://github.com/windwp/nvim-ts-autotag" + }, + "nvim-web-devicons": { + "rev": "8dcb311b0c92d460fac00eac706abd43d94d68af", + "src": "https://github.com/nvim-tree/nvim-web-devicons" + }, + "other.nvim": { + "rev": "1d48e090f6d1d53dda9fb5094af3f2006ebbb858", + "src": "https://github.com/rgroli/other.nvim" + }, + "plenary.nvim": { + "rev": "b9fd5226c2f76c951fc8ed5923d85e4de065e509", + "src": "https://github.com/nvim-lua/plenary.nvim" + }, + "sidekick.nvim": { + "rev": "c2bdf8cfcd87a6be5f8b84322c1b5052e78e302e", + "src": "https://github.com/folke/sidekick.nvim" + }, + "telescope-github.nvim": { + "rev": "ee95c509901c3357679e9f2f9eaac3561c811736", + "src": "https://github.com/nvim-telescope/telescope-github.nvim" + }, + "telescope.nvim": { + "rev": "83a3a713d6b2d2a408491a1b959e55a7fa8678e8", + "src": "https://github.com/nvim-telescope/telescope.nvim" + }, + "todo-comments.nvim": { + "rev": "31e3c38ce9b29781e4422fc0322eb0a21f4e8668", + "src": "https://github.com/folke/todo-comments.nvim" + }, + "treesj": { + "rev": "4770492244ccecdc9b01e0e81fa8f2f6b4a23841", + "src": "https://github.com/wansmer/treesj" + }, + "vim-abolish": { + "rev": "dcbfe065297d31823561ba787f51056c147aa682", + "src": "https://github.com/tpope/vim-abolish" + }, + "vim-eunuch": { + "rev": "e86bb794a1c10a2edac130feb0ea590a00d03f1e", + "src": "https://github.com/tpope/vim-eunuch" + }, + "vim-fugitive": { + "rev": "61b51c09b7c9ce04e821f6cf76ea4f6f903e3cf4", + "src": "https://github.com/tpope/vim-fugitive" + }, + "vim-repeat": { + "rev": "65846025c15494983dafe5e3b46c8f88ab2e9635", + "src": "https://github.com/tpope/vim-repeat" + }, + "vim-rhubarb": { + "rev": "5496d7c94581c4c9ad7430357449bb57fc59f501", + "src": "https://github.com/tpope/vim-rhubarb" + }, + "vim-sleuth": { + "rev": "be69bff86754b1aa5adcbb527d7fcd1635a84080", + "src": "https://github.com/tpope/vim-sleuth" + }, + "vim-speeddating": { + "rev": "c17eb01ebf5aaf766c53bab1f6592710e5ffb796", + "src": "https://github.com/tpope/vim-speeddating" + }, + "vim-tmux-navigator": { + "rev": "c45243dc1f32ac6bcf6068e5300f3b2b237e576a", + "src": "https://github.com/christoomey/vim-tmux-navigator" + } + } +} diff --git a/flake.lock b/flake.lock index 09c45c1cf..22f4a567a 100644 --- a/flake.lock +++ b/flake.lock @@ -66,6 +66,27 @@ } }, "flake-parts_2": { + "inputs": { + "nixpkgs-lib": [ + "neovim-nightly-overlay", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1763759067, + "narHash": "sha256-LlLt2Jo/gMNYAwOgdRQBrsRoOz7BPRkzvNaI/fzXi2Q=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "2cccadc7357c0ba201788ae99c4dfa90728ef5e0", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_3": { "inputs": { "nixpkgs-lib": [ "nur", @@ -106,6 +127,44 @@ "type": "github" } }, + "neovim-nightly-overlay": { + "inputs": { + "flake-parts": "flake-parts_2", + "neovim-src": "neovim-src", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1763769887, + "narHash": "sha256-Viyww4Atra7fD4IuhUwFGBtkyIgzuG3lLXu9gdGdEaU=", + "owner": "nix-community", + "repo": "neovim-nightly-overlay", + "rev": "34efe16d64996dca36f9bae9fb954a867f9d2680", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "neovim-nightly-overlay", + "type": "github" + } + }, + "neovim-src": { + "flake": false, + "locked": { + "lastModified": 1763766790, + "narHash": "sha256-GLflfd2+d4KEwvwapZGvjVuYUFAsbpDxEkgmOni89JM=", + "owner": "neovim", + "repo": "neovim", + "rev": "1c12073db6c64eb365748f153f96be9b0fe61070", + "type": "github" + }, + "original": { + "owner": "neovim", + "repo": "neovim", + "type": "github" + } + }, "nix-darwin": { "inputs": { "nixpkgs": [ @@ -159,7 +218,7 @@ }, "nur": { "inputs": { - "flake-parts": "flake-parts_2", + "flake-parts": "flake-parts_3", "nixpkgs": [ "nixpkgs" ] @@ -183,6 +242,7 @@ "agenix": "agenix", "flake-parts": "flake-parts", "home-manager": "home-manager", + "neovim-nightly-overlay": "neovim-nightly-overlay", "nix-darwin": "nix-darwin", "nixpkgs": "nixpkgs", "nur": "nur", diff --git a/flake.nix b/flake.nix index 6d29d35c3..7051f2e8a 100644 --- a/flake.nix +++ b/flake.nix @@ -29,6 +29,10 @@ inputs.nixpkgs.follows = "nixpkgs"; inputs.home-manager.follows = "home-manager"; }; + neovim-nightly-overlay = { + url = "github:nix-community/neovim-nightly-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = @@ -86,34 +90,29 @@ "ubuntu@x86_64-linux" = import ./hosts/linux { inherit inputs; username = "ubuntu"; - pkgs = inputs.nixpkgs.legacyPackages."x86_64-linux"; - lib = inputs.nixpkgs.legacyPackages."x86_64-linux".lib; + system = "x86_64-linux"; }; "root@x86_64-linux" = import ./hosts/linux { inherit inputs; username = "root"; - pkgs = inputs.nixpkgs.legacyPackages."x86_64-linux"; - lib = inputs.nixpkgs.legacyPackages."x86_64-linux".lib; + system = "x86_64-linux"; }; "root@aarch64-linux" = import ./hosts/linux { inherit inputs; username = "root"; - pkgs = inputs.nixpkgs.legacyPackages."aarch64-linux"; - lib = inputs.nixpkgs.legacyPackages."aarch64-linux".lib; + system = "aarch64-linux"; }; "runner@x86_64-linux" = import ./hosts/linux { inherit inputs; isRunner = true; username = "runner"; - pkgs = inputs.nixpkgs.legacyPackages."x86_64-linux"; - lib = inputs.nixpkgs.legacyPackages."x86_64-linux".lib; + system = "x86_64-linux"; }; "runner@aarch64-linux" = import ./hosts/linux { inherit inputs; isRunner = true; username = "runner"; - pkgs = inputs.nixpkgs.legacyPackages."aarch64-linux"; - lib = inputs.nixpkgs.legacyPackages."aarch64-linux".lib; + system = "aarch64-linux"; }; }; }; diff --git a/home-manager/default.nix b/home-manager/default.nix index aaabbc42c..d321a8bc6 100644 --- a/home-manager/default.nix +++ b/home-manager/default.nix @@ -30,17 +30,6 @@ in inputs.agenix.homeManagerModules.default ]; - nixpkgs.config = { - allowUnfree = true; - allowUnfreePredicate = - pkg: - builtins.elem (lib.getName pkg) [ - "claude-code" - "qwen-code" - "crush" - ]; - }; - home.username = username; home.homeDirectory = lib.mkIf pkgs.stdenv.isLinux "/home/${username}"; home.packages = packages; diff --git a/home-manager/modules/codex/default.nix b/home-manager/modules/codex/default.nix index 9daa0a939..a3336e7ba 100644 --- a/home-manager/modules/codex/default.nix +++ b/home-manager/modules/codex/default.nix @@ -2,5 +2,6 @@ { home.file.".codex/config.toml" = { source = config.lib.file.mkOutOfStoreSymlink ./config.toml; + force = true; }; } diff --git a/home-manager/modules/opencode/opencode.jsonc b/home-manager/modules/opencode/opencode.jsonc index 743d18542..65dfd0aff 100644 --- a/home-manager/modules/opencode/opencode.jsonc +++ b/home-manager/modules/opencode/opencode.jsonc @@ -24,6 +24,12 @@ "baseURL": "http://127.0.0.1:1234/v1" }, "models": { + "openai/gpt-oss-20b": { + "name": "OpenAI: gpt-oss-20b" + }, + "openai/gpt-oss-120b": { + "name": "OpenAI: gpt-oss-120b" + }, "qwen/qwen3-coder-30b": { "name": "Qwen: Qwen3 Coder 30B A3B Instruct" } diff --git a/home-manager/packages/default.nix b/home-manager/packages/default.nix index c40957318..e77d45b84 100644 --- a/home-manager/packages/default.nix +++ b/home-manager/packages/default.nix @@ -8,7 +8,7 @@ in with pkgs; [ inputs.agenix.packages.${stdenv.hostPlatform.system}.default - inputs.nur.legacyPackages.${stdenv.hostPlatform.system}.repos.charmbracelet.crush + pkgs.nur.repos.charmbracelet.crush age aichat # aider-chat # FIXME: pyarrow 20.0.0 has malloc issues on macOS, uncomment when fixed diff --git a/home-manager/programs/bash/default.nix b/home-manager/programs/bash/default.nix index d3b0befe7..97a511edd 100644 --- a/home-manager/programs/bash/default.nix +++ b/home-manager/programs/bash/default.nix @@ -32,11 +32,17 @@ }; bashrcExtra = '' - # Add Go bin to PATH - export PATH="$PATH:$GOPATH/bin" + # Go configuration + export GOPATH="$HOME/go" - # Add Bun bin to PATH + # Add additional bin paths + export PATH="$PATH:$GOPATH/bin" export PATH="$HOME/.bun/bin:$PATH" + export PATH="$HOME/.foundry/bin:$PATH" + export PATH="$HOME/.local/bin:$PATH" + export PATH="$HOME/.nix-profile/bin:$PATH" + export PATH="/nix/var/nix/profiles/default/bin:$PATH" + export PATH="/opt/homebrew/bin:$PATH" ''; profileExtra = '' diff --git a/home-manager/programs/neovim/config/init.lua b/home-manager/programs/neovim/config/init.lua deleted file mode 100644 index 4a4590bbf..000000000 --- a/home-manager/programs/neovim/config/init.lua +++ /dev/null @@ -1 +0,0 @@ -require("colorizer").setup() diff --git a/home-manager/programs/neovim/default.nix b/home-manager/programs/neovim/default.nix index fc4441c3b..193f64d5b 100644 --- a/home-manager/programs/neovim/default.nix +++ b/home-manager/programs/neovim/default.nix @@ -1,19 +1,10 @@ -{ - config, - pkgs, - ... -}: +{ pkgs, ... }: { programs.neovim = { enable = true; + package = pkgs.neovim; defaultEditor = true; vimAlias = true; vimdiffAlias = true; - plugins = with pkgs.vimPlugins; [ - nvim-colorizer-lua - ]; - }; - xdg.configFile."nvim" = { - source = config.lib.file.mkOutOfStoreSymlink ./config; }; } diff --git a/home-manager/programs/zsh/default.nix b/home-manager/programs/zsh/default.nix index 5d18c5171..2f55f7cdc 100644 --- a/home-manager/programs/zsh/default.nix +++ b/home-manager/programs/zsh/default.nix @@ -20,10 +20,15 @@ initContent = '' # Go configuration export GOPATH="$HOME/go" - export PATH="$PATH:$GOPATH/bin" - # Bun configuration + # Additional bin paths + export PATH="$PATH:$GOPATH/bin" export PATH="$HOME/.bun/bin:$PATH" + export PATH="$HOME/.foundry/bin:$PATH" + export PATH="$HOME/.local/bin:$PATH" + export PATH="$HOME/.nix-profile/bin:$PATH" + export PATH="/nix/var/nix/profiles/default/bin:$PATH" + export PATH="/opt/homebrew/bin:$PATH" # FNM (Fast Node Manager) configuration export FNM_DIR="$HOME/Library/Application Support/fnm" diff --git a/home-manager/services/code-syncer/sync.sh b/home-manager/services/code-syncer/sync.sh index aacef3d87..2545b0bcf 100755 --- a/home-manager/services/code-syncer/sync.sh +++ b/home-manager/services/code-syncer/sync.sh @@ -22,6 +22,7 @@ PROPRIETARY_EXTENSIONS=( "github.codespaces" "github.copilot" "github.copilot-chat" + "github.vscode-github-actions" "ms-python.debugpy" "ms-python.python" "ms-python.vscode-pylance" diff --git a/hosts/darwin/default.nix b/hosts/darwin/default.nix index d342eeb8a..e85e63498 100644 --- a/hosts/darwin/default.nix +++ b/hosts/darwin/default.nix @@ -5,8 +5,20 @@ isRunner ? false, }: let - inherit (inputs) nix-darwin home-manager agenix; + inherit (inputs) + nix-darwin + home-manager + agenix + ; system = "aarch64-darwin"; + overlays = import ../../overlays { inherit inputs; }; + nixpkgsConfig = import ../../lib/nixpkgs-config.nix { + nixpkgsLib = inputs.nixpkgs.lib; + }; + pkgs = import inputs.nixpkgs { + inherit system overlays; + config = nixpkgsConfig; + }; configuration = { ... }: { @@ -25,8 +37,10 @@ in home-manager.darwinModules.home-manager agenix.darwinModules.default { + nixpkgs.pkgs = pkgs; home-manager.backupFileExtension = "hm-backup"; home-manager.extraSpecialArgs = { inherit inputs; }; + home-manager.useGlobalPkgs = true; home-manager.sharedModules = [ { home.activation.removeBackups = { @@ -40,9 +54,8 @@ in ]; home-manager.useUserPackages = true; home-manager.users."${username}" = import ../../home-manager { - inherit inputs username; - lib = inputs.nixpkgs.legacyPackages.${system}.lib; - pkgs = inputs.nixpkgs.legacyPackages.${system}; + inherit inputs username pkgs; + lib = pkgs.lib; config = { }; }; } diff --git a/hosts/linux/default.nix b/hosts/linux/default.nix index 2fb39ff48..d72c7078a 100644 --- a/hosts/linux/default.nix +++ b/hosts/linux/default.nix @@ -1,12 +1,20 @@ { inputs, username, - pkgs, - lib, + system ? "x86_64-linux", isRunner ? false, }: let inherit (inputs) home-manager; + overlays = import ../../overlays { inherit inputs; }; + nixpkgsConfig = import ../../lib/nixpkgs-config.nix { + nixpkgsLib = inputs.nixpkgs.lib; + }; + pkgs = import inputs.nixpkgs { + inherit system overlays; + config = nixpkgsConfig; + }; + lib = pkgs.lib; in home-manager.lib.homeManagerConfiguration { inherit pkgs; diff --git a/hosts/nixos/default.nix b/hosts/nixos/default.nix index 30225e3c8..50ddab6d7 100644 --- a/hosts/nixos/default.nix +++ b/hosts/nixos/default.nix @@ -7,9 +7,13 @@ let inherit (inputs) nixpkgs home-manager; system = "x86_64-linux"; + nixpkgsConfig = import ../../lib/nixpkgs-config.nix { + nixpkgsLib = nixpkgs.lib; + }; + overlays = import ../../overlays { inherit inputs; }; pkgs = import nixpkgs { - inherit system; - config.allowUnfree = true; + inherit system overlays; + config = nixpkgsConfig; }; configuration = { config, lib, ... }: @@ -116,6 +120,7 @@ nixpkgs.lib.nixosSystem { home-manager.nixosModules.home-manager { home-manager.backupFileExtension = "hm-backup"; + home-manager.useGlobalPkgs = true; home-manager.useUserPackages = true; home-manager.users."${username}" = import ../../home-manager { inherit inputs username; diff --git a/lib/nixpkgs-config.nix b/lib/nixpkgs-config.nix new file mode 100644 index 000000000..da9d28d9f --- /dev/null +++ b/lib/nixpkgs-config.nix @@ -0,0 +1,11 @@ +{ nixpkgsLib }: +{ + allowUnfree = true; + allowUnfreePredicate = + pkg: + builtins.elem (nixpkgsLib.getName pkg) [ + "claude-code" + "qwen-code" + "crush" + ]; +} diff --git a/nix-darwin/config/homebrew.nix b/nix-darwin/config/homebrew.nix index c91fb5928..ab56b3c06 100644 --- a/nix-darwin/config/homebrew.nix +++ b/nix-darwin/config/homebrew.nix @@ -1,10 +1,10 @@ { isRunner }: { homebrew = { - enable = !isRunner; + enable = !isRunner && (builtins.getEnv "NIX_OFFLINE" != "1"); onActivation = { - autoUpdate = true; - upgrade = true; + autoUpdate = builtins.getEnv "NIX_OFFLINE" != "1"; + upgrade = builtins.getEnv "NIX_OFFLINE" != "1"; cleanup = "zap"; extraFlags = [ "--force" ]; }; @@ -78,6 +78,7 @@ "sf-symbols" "slack" "visual-studio-code" + "vscodium" "warp" "wezterm" "windsurf" diff --git a/nix-darwin/config/nix.nix b/nix-darwin/config/nix.nix index acaf57546..802454173 100644 --- a/nix-darwin/config/nix.nix +++ b/nix-darwin/config/nix.nix @@ -7,5 +7,4 @@ }; }; - nixpkgs.config.allowUnfree = true; } diff --git a/nix-darwin/config/system.nix b/nix-darwin/config/system.nix index 57e4b2b11..aeaecf6cd 100644 --- a/nix-darwin/config/system.nix +++ b/nix-darwin/config/system.nix @@ -62,7 +62,7 @@ }; }; activationScripts.extraActivation.text = builtins.toString ( - if !isRunner then + if !isRunner && (builtins.getEnv "NIX_OFFLINE" != "1") then '' softwareupdate --all --install; '' diff --git a/overlays/default.nix b/overlays/default.nix new file mode 100644 index 000000000..39265f679 --- /dev/null +++ b/overlays/default.nix @@ -0,0 +1,21 @@ +{ inputs }: +[ + inputs.nur.overlays.default + inputs.neovim-nightly-overlay.overlays.default + (final: prev: { + # Ensure neovim-unwrapped exposes a lua attribute for wrapper consumers (e.g., home-manager) + neovim-unwrapped = + (prev.neovim-unwrapped.overrideAttrs (oldAttrs: { + passthru = (oldAttrs.passthru or { }) // { + lua = prev.lua5_4; + }; + })) + // { + lua = prev.lua5_4; + }; + }) + (final: prev: { + # Provide non-deprecated alias so upstream modules using pkgs.system don't emit warnings. + system = prev.stdenv.hostPlatform.system; + }) +] diff --git a/package.json b/package.json index dc6ce4391..b894db217 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "open-composer": "^0.8.23" }, "trustedDependencies": [ + "@biomejs/biome", "@github/copilot", "@google/jules", "@getgrit/cli",