diff --git a/ai/agents.el b/ai/agents.el new file mode 100644 index 0000000..267ea0e --- /dev/null +++ b/ai/agents.el @@ -0,0 +1,133 @@ +;;; ai/agents.el -*- lexical-binding: t; -*- + +;;; ============================================================================ +;;; Claude Code Configuration +;;; ============================================================================ + +(use-package! claude-code + :defer t + :config + ;; Unset CLAUDECODE to allow running Claude Code inside Emacs sub-processes + (setenv "CLAUDECODE" nil) + (setq claude-code-program "ccr" + claude-code-program-switches '("code" "--dangerously-skip-permissions")) + + ;; Use Eat backend + (setq claude-code-terminal-backend 'eat + ;; Emacs-style editing in Claude buffer: RET inserts newline, S-RET sends + claude-code-newline-keybinding-style 'shift-return-to-send) + + ;; Enable global mode + (claude-code-mode 1) + + ;; Emacs-style prefix key (works without Doom/Evil leader keys) + (global-set-key (kbd "C-c c") claude-code-command-map) + + ;; Set Doom leader keybindings + (map! :leader + (:prefix ("C" . "claude-code") + :desc "Start Claude" "c" #'claude-code + :desc "Start in directory" "d" #'claude-code-start-in-directory + :desc "Continue conversation" "C" #'claude-code-continue + :desc "Resume session" "R" #'claude-code-resume + :desc "New instance" "i" #'claude-code-new-instance + :desc "Kill Claude" "k" #'claude-code-kill + :desc "Kill all instances" "K" #'claude-code-kill-all + :desc "Send command" "s" #'claude-code-send-command + :desc "Send command with context" "x" #'claude-code-send-command-with-context + :desc "Send region/buffer" "r" #'claude-code-send-region + :desc "Send buffer file" "o" #'claude-code-send-buffer-file + :desc "Fix error at point" "e" #'claude-code-fix-error-at-point + :desc "Toggle window" "t" #'claude-code-toggle + :desc "Switch to buffer" "b" #'claude-code-switch-to-buffer + :desc "Select buffer" "B" #'claude-code-select-buffer + :desc "Toggle read-only" "z" #'claude-code-toggle-read-only-mode + :desc "Cycle mode" "M" #'claude-code-cycle-mode + :desc "Transient menu" "m" #'claude-code-transient + :desc "Slash commands" "/" #'claude-code-slash-commands + :desc "Fork conversation" "f" #'claude-code-fork + :desc "Send return" "y" #'claude-code-send-return + :desc "Send escape" "n" #'claude-code-send-escape + :desc "Send 1" "1" #'claude-code-send-1 + :desc "Send 2" "2" #'claude-code-send-2 + :desc "Send 3" "3" #'claude-code-send-3)) + + ;; Optional: Configure window display (side window on right) + (add-to-list 'display-buffer-alist + '("^\\*claude" + (display-buffer-in-side-window) + (side . right) + (window-width . 90))) + + ;; Optional: Enable auto-revert for files modified by Claude + (global-auto-revert-mode 1) + + ;; Optional: Increase vterm scrollback for long conversations + (add-hook 'claude-code-start-hook + (lambda () + (when (eq claude-code-terminal-backend 'vterm) + (setq-local vterm-max-scrollback 100000))))) + +;;; ============================================================================ +;;; Gemini CLI Configuration +;;; ============================================================================ + +(use-package! gemini-cli + :defer t + :config + ;; Set the Gemini executable + (setq gemini-cli-program "gemini" + gemini-cli-program-switches nil) + + ;; Use Eat backend + (setq gemini-cli-terminal-backend 'eat + ;; Emacs-style editing in Gemini buffer: RET inserts newline, S-RET sends + gemini-cli-newline-keybinding-style 'shift-return-to-send) + + ;; Enable global mode + (gemini-cli-mode 1) + + ;; Emacs-style prefix key + (global-set-key (kbd "C-c g") gemini-cli-command-map) + + ;; Set Doom leader keybindings under SPC G + (map! :leader + (:prefix ("G" . "gemini-cli") + :desc "Start Gemini" "c" #'gemini-cli + :desc "Start in directory" "d" #'gemini-cli-start-in-directory + :desc "Continue conversation" "C" #'gemini-cli-continue + :desc "Resume session" "R" #'gemini-cli-resume + :desc "New instance" "i" #'gemini-cli-new-instance + :desc "Kill Gemini" "k" #'gemini-cli-kill + :desc "Kill all instances" "K" #'gemini-cli-kill-all + :desc "Send command" "s" #'gemini-cli-send-command + :desc "Send command with context" "x" #'gemini-cli-send-command-with-context + :desc "Send region/buffer" "r" #'gemini-cli-send-region + :desc "Send buffer file" "o" #'gemini-cli-send-buffer-file + :desc "Fix error at point" "e" #'gemini-cli-fix-error-at-point + :desc "Toggle window" "t" #'gemini-cli-toggle + :desc "Switch to buffer" "b" #'gemini-cli-switch-to-buffer + :desc "Select buffer" "B" #'gemini-cli-select-buffer + :desc "Toggle read-only" "z" #'gemini-cli-toggle-read-only-mode + :desc "Cycle mode" "M" #'gemini-cli-cycle-mode + :desc "Transient menu" "m" #'gemini-cli-transient + :desc "Slash commands" "/" #'gemini-cli-slash-commands + :desc "Fork conversation" "f" #'gemini-cli-fork + :desc "Send return" "y" #'gemini-cli-send-return + :desc "Send escape" "n" #'gemini-cli-send-escape + :desc "Send 1" "1" #'gemini-cli-send-1 + :desc "Send 2" "2" #'gemini-cli-send-2 + :desc "Send 3" "3" #'gemini-cli-send-3)) + + ;; Optional: Configure window display (side window on right) + (add-to-list 'display-buffer-alist + '("^\\*gemini" + (display-buffer-in-side-window) + (side . right) + (window-width . 90))) + + ;; Optional: Increase vterm scrollback for long conversations + (add-hook 'gemini-cli-start-hook + (lambda () + (when (eq gemini-cli-terminal-backend 'vterm) + (setq-local vterm-max-scrollback 100000))))) diff --git a/ai.el b/ai/gptel.el similarity index 51% rename from ai.el rename to ai/gptel.el index 14ee4c9..682c63d 100644 --- a/ai.el +++ b/ai/gptel.el @@ -1,4 +1,4 @@ -;;; ai.el -*- lexical-binding: t; -*- +;;; ai/gptel.el -*- lexical-binding: t; -*- ;;; ============================================================================ ;;; gptel Configuration @@ -130,31 +130,6 @@ "C-c TAB" #'gptel-complete "C-c C-a" #'gptel-accept-completion)) -;;; ============================================================================ -;;; Optional: Add Ollama for local models (commented out by default) -;;; ============================================================================ - -;; Uncomment to add Ollama support for local models -;; (after! gptel -;; (gptel-make-ollama "Ollama" -;; :host "localhost:11434" -;; :stream t -;; :models '(qwen2.5-coder:latest -;; mistral:latest -;; llama3.2:latest))) - -;; To make Ollama default, uncomment: -;; (setq gptel-model 'qwen2.5-coder:latest -;; gptel-backend (gptel-make-ollama "Ollama" -;; :host "localhost:11434" -;; :stream t -;; :models '(qwen2.5-coder:latest -;; mistral:latest))) - -;;; ============================================================================ -;;; Helper functions -;;; ============================================================================ - (defun my/gptel-quick-explain-region () "Quickly explain selected code region in the gptel buffer." (interactive) @@ -228,159 +203,3 @@ :desc "Proofread text" "p" #'my/gptel-proofread-region :desc "Copy path tag (relative)" "t" #'my/copy-region-as-path-tag :desc "Copy path tag (absolute)" "T" #'my/copy-region-as-path-tag-absolute)) - -;;; Claude Code Configuration - -(use-package! claude-code - :defer t - :config - ;; Unset CLAUDECODE to allow running Claude Code inside Emacs sub-processes - (setenv "CLAUDECODE" nil) - (setq claude-code-program "ccr" - claude-code-program-switches '("code" "--dangerously-skip-permissions")) - - ;; Use Eat backend - (setq claude-code-terminal-backend 'eat - ;; Emacs-style editing in Claude buffer: RET inserts newline, S-RET sends - claude-code-newline-keybinding-style 'shift-return-to-send) - - ;; Enable global mode - (claude-code-mode 1) - - ;; Emacs-style prefix key (works without Doom/Evil leader keys) - (global-set-key (kbd "C-c c") claude-code-command-map) - - ;; Set Doom leader keybindings - (map! :leader - (:prefix ("C" . "claude-code") - :desc "Start Claude" "c" #'claude-code - :desc "Start in directory" "d" #'claude-code-start-in-directory - :desc "Continue conversation" "C" #'claude-code-continue - :desc "Resume session" "R" #'claude-code-resume - :desc "New instance" "i" #'claude-code-new-instance - :desc "Kill Claude" "k" #'claude-code-kill - :desc "Kill all instances" "K" #'claude-code-kill-all - :desc "Send command" "s" #'claude-code-send-command - :desc "Send command with context" "x" #'claude-code-send-command-with-context - :desc "Send region/buffer" "r" #'claude-code-send-region - :desc "Send buffer file" "o" #'claude-code-send-buffer-file - :desc "Fix error at point" "e" #'claude-code-fix-error-at-point - :desc "Toggle window" "t" #'claude-code-toggle - :desc "Switch to buffer" "b" #'claude-code-switch-to-buffer - :desc "Select buffer" "B" #'claude-code-select-buffer - :desc "Toggle read-only" "z" #'claude-code-toggle-read-only-mode - :desc "Cycle mode" "M" #'claude-code-cycle-mode - :desc "Transient menu" "m" #'claude-code-transient - :desc "Slash commands" "/" #'claude-code-slash-commands - :desc "Fork conversation" "f" #'claude-code-fork - :desc "Send return" "y" #'claude-code-send-return - :desc "Send escape" "n" #'claude-code-send-escape - :desc "Send 1" "1" #'claude-code-send-1 - :desc "Send 2" "2" #'claude-code-send-2 - :desc "Send 3" "3" #'claude-code-send-3)) - - ;; Optional: Configure window display (side window on right) - (add-to-list 'display-buffer-alist - '("^\\*claude" - (display-buffer-in-side-window) - (side . right) - (window-width . 90))) - - ;; Optional: Enable auto-revert for files modified by Claude - (global-auto-revert-mode 1) - - ;; Optional: Increase vterm scrollback for long conversations - (add-hook 'claude-code-start-hook - (lambda () - (when (eq claude-code-terminal-backend 'vterm) - (setq-local vterm-max-scrollback 100000))))) - -;;; Gemini CLI Configuration - -(use-package! gemini-cli - :defer t - :config - ;; Set the Gemini executable - (setq gemini-cli-program "gemini" - gemini-cli-program-switches nil) - - ;; Use Eat backend - (setq gemini-cli-terminal-backend 'eat - ;; Emacs-style editing in Gemini buffer: RET inserts newline, S-RET sends - gemini-cli-newline-keybinding-style 'shift-return-to-send) - - ;; Enable global mode - (gemini-cli-mode 1) - - ;; Emacs-style prefix key - (global-set-key (kbd "C-c g") gemini-cli-command-map) - - ;; Set Doom leader keybindings under SPC G - (map! :leader - (:prefix ("G" . "gemini-cli") - :desc "Start Gemini" "c" #'gemini-cli - :desc "Start in directory" "d" #'gemini-cli-start-in-directory - :desc "Continue conversation" "C" #'gemini-cli-continue - :desc "Resume session" "R" #'gemini-cli-resume - :desc "New instance" "i" #'gemini-cli-new-instance - :desc "Kill Gemini" "k" #'gemini-cli-kill - :desc "Kill all instances" "K" #'gemini-cli-kill-all - :desc "Send command" "s" #'gemini-cli-send-command - :desc "Send command with context" "x" #'gemini-cli-send-command-with-context - :desc "Send region/buffer" "r" #'gemini-cli-send-region - :desc "Send buffer file" "o" #'gemini-cli-send-buffer-file - :desc "Fix error at point" "e" #'gemini-cli-fix-error-at-point - :desc "Toggle window" "t" #'gemini-cli-toggle - :desc "Switch to buffer" "b" #'gemini-cli-switch-to-buffer - :desc "Select buffer" "B" #'gemini-cli-select-buffer - :desc "Toggle read-only" "z" #'gemini-cli-toggle-read-only-mode - :desc "Cycle mode" "M" #'gemini-cli-cycle-mode - :desc "Transient menu" "m" #'gemini-cli-transient - :desc "Slash commands" "/" #'gemini-cli-slash-commands - :desc "Fork conversation" "f" #'gemini-cli-fork - :desc "Send return" "y" #'gemini-cli-send-return - :desc "Send escape" "n" #'gemini-cli-send-escape - :desc "Send 1" "1" #'gemini-cli-send-1 - :desc "Send 2" "2" #'gemini-cli-send-2 - :desc "Send 3" "3" #'gemini-cli-send-3)) - - ;; Optional: Configure window display (side window on right) - (add-to-list 'display-buffer-alist - '("^\\*gemini" - (display-buffer-in-side-window) - (side . right) - (window-width . 90))) - - ;; Optional: Enable auto-revert for files modified by Gemini - ;; (global-auto-revert-mode 1) - - ;; Optional: Increase vterm scrollback for long conversations - (add-hook 'gemini-cli-start-hook - (lambda () - (when (eq gemini-cli-terminal-backend 'vterm) - (setq-local vterm-max-scrollback 100000))))) - -;; (use-package! claudemacs -;; :defer t -;; :commands (claudemacs claudemacs-transient-menu) -;; :init -;; (setq claudemacs-program "ccr" -;; claudemacs-program-switches '("code" "--dangerously-skip-permissions") -;; claudemacs-default-tool 'claude -;; claudemacs-prefer-projectile-root t) -;; :config -;; (map! :map prog-mode-map -;; "C-c C-e" #'claudemacs-transient-menu) -;; (map! :map emacs-lisp-mode-map -;; "C-c C-e" #'claudemacs-transient-menu) -;; (map! :map text-mode-map -;; "C-c C-e" #'claudemacs-transient-menu) -;; (after! python -;; (map! :map python-base-mode-map -;; "C-c C-e" #'claudemacs-transient-menu)) -;; (after! eat -;; (setq eat-term-scrollback-size 400000) -;; (add-hook 'eat-mode-hook -;; (lambda () -;; (setq-local show-trailing-whitespace nil)))) -;; :desc "Claudemacs menu" "E" #'claudemacs-transient-menu))) diff --git a/config.el b/config.el index 6856f48..c07f90e 100644 --- a/config.el +++ b/config.el @@ -3,9 +3,12 @@ ;; Load modular configuration files (load! "security") (load! "core") -(load! "development") +(load! "development/lsp") +(load! "development/nix") +(load! "development/misc") (load! "org") -(load! "ai") +(load! "ai/gptel") +(load! "ai/agents") (load! "inbox") (load! "metrics") (load! "theme") diff --git a/development.el b/development.el deleted file mode 100644 index 33617b6..0000000 --- a/development.el +++ /dev/null @@ -1,151 +0,0 @@ -;;; development.el -*- lexical-binding: t; -*- - -(after! lsp-ui - (setq lsp-ui-doc-include-signature t ;; Show method signatures in docs - lsp-ui-sideline-show-symbol t ;; Show symbols in the sideline - lsp-ui-doc-show-with-cursor t ;; Show docs when cursor is on symbol - lsp-ui-doc-max-height 30 ;; Increase doc height to show more info - lsp-ui-sideline-show-hover t ;; Show hover information in sideline - lsp-ui-doc-enable t ;; Enable documentation - lsp-signature-auto-activate t ;; Show signature help automatically - lsp-signature-render-documentation t)) ;; Include docs in signature help - -(after! eldoc - (setq eldoc-echo-area-use-multiline-p t - eldoc-echo-area-prefer-doc-buffer t)) - -(use-package! corfu-popupinfo - :after corfu - :hook (corfu-mode . corfu-popupinfo-mode) - :custom - (corfu-popupinfo-delay '(0.25 . 0.1)) ; Faster popup display - (corfu-popupinfo-hide nil) ; Don't hide automatically - ) - -;; Configure Corfu for richer display -(after! corfu - (setq corfu-auto t ; Enable auto completion - corfu-auto-prefix 2 ; Complete after 2 characters - corfu-preview-current t ; Preview current candidate - corfu-show-documentation t ; Show documentation in popup - corfu-doc-max-height 20 ; Max height for doc popup - corfu-doc-delay 0.2 ; Faster doc popup - corfu-quit-no-match t) ; Quit if no match - - ;; Show more detailed annotations - (setq corfu-show-annotations t - corfu-annotate-max-width 50) - - ) - -;; If using LSP, configure to show more details -(after! lsp-mode - (setq lsp-completion-show-detail t ; Show method signatures - lsp-completion-show-kind t ; Show completion item kind - lsp-completion-show-label-description t ; Show more details in label - )) - -;; Haskell LSP uses HLS by default in Doom; removed outdated fourmolu config - -(use-package! bnf-mode - :mode "\\.bnf\\'" - :config - ;; Optional: Add any custom configuration here - (setq bnf-mode-hook - (lambda () - ;; Enable line numbers - (display-line-numbers-mode) - ;; Enable auto-pairing of angle brackets - (modify-syntax-entry ?< "(>" bnf-mode-syntax-table) - (modify-syntax-entry ?> ")<" bnf-mode-syntax-table)))) - -(use-package! ebnf-mode - :mode "\\.ebnf\\'" - :config - ;; Optional customizations: - (setq ebnf-mode-indent-offset 4) ; Default is 8 spaces - (setq ebnf-mode-indent-by-production t) ; Align with the '=' in productions - (setq ebnf-mode-comment-char ?\;) ; Use ; for comments - (setq ebnf-mode-eop-char ?\.)) ; Use . for end of production - -(use-package! nix-mode - :mode "\\.nix\\'" - :hook (nix-mode . lsp-deferred) - :config - ;; Configure nil LSP server - (after! lsp-mode - (add-to-list 'lsp-language-id-configuration '(nix-mode . "nix")) - (lsp-register-client - (make-lsp-client :new-connection (lsp-stdio-connection "nil") - :major-modes '(nix-mode) - :server-id 'nil-ls - :priority 0))) - - ;; Function to check if we're in a nixpkgs repository - (defun in-nixpkgs-repo-p () - (when-let ((root (or (locate-dominating-file default-directory ".git") - (vc-root-dir)))) - (or (string-match-p "/nixpkgs/?$" root) - (file-exists-p (expand-file-name "pkgs/top-level/all-packages.nix" root)) - (file-exists-p (expand-file-name ".nixpkgs-version.json" root))))) - - ;; Set up format-all-mode to use our custom formatter selection - (set-formatter! 'nixfmt - '("nixfmt") - :modes '(nix-mode)) - - (set-formatter! 'alejandra - '("alejandra" "--quiet") - :modes '(nix-mode)) - - ;; Add hooks to handle all formatting scenarios - (add-hook! 'nix-mode-hook - ;; Disable LSP formatting - (setq-local +format-with-lsp nil) - - ;; Set formatter for format-all-mode (+format/buffer) - (setq-local +format-with - (if (in-nixpkgs-repo-p) - 'nixfmt - 'alejandra)) - - ;; Handle format-on-save - (when (bound-and-true-p format-all-mode) - (format-all-mode -1)) - (add-hook! 'before-save-hook :local - (when (eq major-mode 'nix-mode) - (if (in-nixpkgs-repo-p) - (format-all-buffer 'nixfmt) - (format-all-buffer 'alejandra)))) - - ;; Override lsp-format-buffer - (advice-add 'lsp-format-buffer :around - (lambda (orig-fun &rest args) - (if (eq major-mode 'nix-mode) - (let ((fmt (if (in-nixpkgs-repo-p) - (executable-find "nixfmt") - (executable-find "alejandra")))) - (when fmt - (format-all-buffer (intern (file-name-nondirectory fmt))))) - (apply orig-fun args)))))) - -;; Enable format-on-save globally if desired -(setq +format-on-save-enabled-modes '(nix-mode)) - -(use-package! llvm-ts-mode - :mode "\\.ll\\'" ; LLVM IR files - :mode "\\.td\\'" ; TableGen files - :hook (llvm-ts-mode . tree-sitter-mode)) - -(after! tree-sitter - (add-to-list 'tree-sitter-major-mode-language-alist '(llvm-ts-mode . llvm))) - -(after! lsp-clangd - (setq lsp-clients-clangd-executable "clangd") - (setq lsp-clients--clangd-default-executable "clangd") - (setq lsp-clangd-binary-path "clangd")) - -;; Justfile mode (psibi/justl.el) -(use-package! justl - :config - (map! :n "e" 'justl-exec-recipe)) diff --git a/development/lsp.el b/development/lsp.el new file mode 100644 index 0000000..e0dc61d --- /dev/null +++ b/development/lsp.el @@ -0,0 +1,50 @@ +;;; development/lsp.el -*- lexical-binding: t; -*- + +(after! lsp-ui + (setq lsp-ui-doc-include-signature t ;; Show method signatures in docs + lsp-ui-sideline-show-symbol t ;; Show symbols in the sideline + lsp-ui-doc-show-with-cursor t ;; Show docs when cursor is on symbol + lsp-ui-doc-max-height 30 ;; Increase doc height to show more info + lsp-ui-sideline-show-hover t ;; Show hover information in sideline + lsp-ui-doc-enable t ;; Enable documentation + lsp-signature-auto-activate t ;; Show signature help automatically + lsp-signature-render-documentation t)) ;; Include docs in signature help + +(after! eldoc + (setq eldoc-echo-area-use-multiline-p t + eldoc-echo-area-prefer-doc-buffer t)) + +(use-package! corfu-popupinfo + :after corfu + :hook (corfu-mode . corfu-popupinfo-mode) + :custom + (corfu-popupinfo-delay '(0.25 . 0.1)) ; Faster popup display + (corfu-popupinfo-hide nil) ; Don't hide automatically + ) + +;; Configure Corfu for richer display +(after! corfu + (setq corfu-auto t ; Enable auto completion + corfu-auto-prefix 2 ; Complete after 2 characters + corfu-preview-current t ; Preview current candidate + corfu-show-documentation t ; Show documentation in popup + corfu-doc-max-height 20 ; Max height for doc popup + corfu-doc-delay 0.2 ; Faster doc popup + corfu-quit-no-match t) ; Quit if no match + + ;; Show more detailed annotations + (setq corfu-show-annotations t + corfu-annotate-max-width 50) + ) + +;; If using LSP, configure to show more details +(after! lsp-mode + (setq lsp-completion-show-detail t ; Show method signatures + lsp-completion-show-kind t ; Show completion item kind + lsp-completion-show-label-description t ; Show more details in label + )) + +(after! lsp-clangd + (setq lsp-clients-clangd-executable "clangd") + (setq lsp-clients--clangd-default-executable "clangd") + (setq lsp-clangd-binary-path "clangd")) diff --git a/development/misc.el b/development/misc.el new file mode 100644 index 0000000..c1320a0 --- /dev/null +++ b/development/misc.el @@ -0,0 +1,27 @@ +;;; development/misc.el -*- lexical-binding: t; -*- + +(use-package! bnf-mode + :mode "\\.bnf\\'" + :config + ;; Optional: Add any custom configuration here + (setq bnf-mode-hook + (lambda () + ;; Enable line numbers + (display-line-numbers-mode) + ;; Enable auto-pairing of angle brackets + (modify-syntax-entry ?< "(>" bnf-mode-syntax-table) + (modify-syntax-entry ?> ")<" bnf-mode-syntax-table)))) + +(use-package! ebnf-mode + :mode "\\.ebnf\\'" + :config + ;; Optional customizations: + (setq ebnf-mode-indent-offset 4) ; Default is 8 spaces + (setq ebnf-mode-indent-by-production t) ; Align with the '=' in productions + (setq ebnf-mode-comment-char ?\;) ; Use ; for comments + (setq ebnf-mode-eop-char ?\.)) ; Use . for end of production + +;; Justfile mode (psibi/justl.el) +(use-package! justl + :config + (map! :n "e" 'justl-exec-recipe)) diff --git a/development/nix.el b/development/nix.el new file mode 100644 index 0000000..6841375 --- /dev/null +++ b/development/nix.el @@ -0,0 +1,73 @@ +;;; development/nix.el -*- lexical-binding: t; -*- + +(use-package! nix-mode + :mode "\\.nix\\'" + :hook (nix-mode . lsp-deferred) + :config + ;; Configure nil LSP server + (after! lsp-mode + (add-to-list 'lsp-language-id-configuration '(nix-mode . "nix")) + (lsp-register-client + (make-lsp-client :new-connection (lsp-stdio-connection "nil") + :major-modes '(nix-mode) + :server-id 'nil-ls + :priority 0))) + + ;; Function to check if we're in a nixpkgs repository + (defun in-nixpkgs-repo-p () + (when-let ((root (or (locate-dominating-file default-directory ".git") + (vc-root-dir)))) + (or (string-match-p "/nixpkgs/?$" root) + (file-exists-p (expand-file-name "pkgs/top-level/all-packages.nix" root)) + (file-exists-p (expand-file-name ".nixpkgs-version.json" root))))) + + ;; Set up format-all-mode to use our custom formatter selection + (set-formatter! 'nixfmt + '("nixfmt") + :modes '(nix-mode)) + + (set-formatter! 'alejandra + '("alejandra" "--quiet") + :modes '(nix-mode)) + + ;; Add hooks to handle all formatting scenarios + (add-hook! 'nix-mode-hook + ;; Disable LSP formatting + (setq-local +format-with-lsp nil) + + ;; Set formatter for format-all-mode (+format/buffer) + (setq-local +format-with + (if (in-nixpkgs-repo-p) + 'nixfmt + 'alejandra)) + + ;; Handle format-on-save + (when (bound-and-true-p format-all-mode) + (format-all-mode -1)) + (add-hook! 'before-save-hook :local + (when (eq major-mode 'nix-mode) + (if (in-nixpkgs-repo-p) + (format-all-buffer 'nixfmt) + (format-all-buffer 'alejandra)))) + + ;; Override lsp-format-buffer + (advice-add 'lsp-format-buffer :around + (lambda (orig-fun &rest args) + (if (eq major-mode 'nix-mode) + (let ((fmt (if (in-nixpkgs-repo-p) + (executable-find "nixfmt") + (executable-find "alejandra")))) + (when fmt + (format-all-buffer (intern (file-name-nondirectory fmt))))) + (apply orig-fun args)))))) + +;; Enable format-on-save globally if desired +(setq +format-on-save-enabled-modes '(nix-mode)) + +(use-package! llvm-ts-mode + :mode "\\.ll\\'" ; LLVM IR files + :mode "\\.td\\'" ; TableGen files + :hook (llvm-ts-mode . tree-sitter-mode)) + +(after! tree-sitter + (add-to-list 'tree-sitter-major-mode-language-alist '(llvm-ts-mode . llvm)))