move stuff around

This commit is contained in:
2026-04-27 18:57:10 -04:00
parent e57257a3a5
commit 4c6caec1d7
6 changed files with 665 additions and 74 deletions

View File

@@ -5,17 +5,22 @@
;; Your init file should contain only one such instance.
;; If there is more than one, they won't work right.
'(package-selected-packages
'(aider browse-kill-ring caddyfile-mode cape cider company corfu
dockerfile-mode dumb-jump dumber-jump emacs-lldb emmet
emmet-mode exec-path-from-shell flycheck fsharp-mode
haskell-mode haskell-ts-mode helm htmlize idris-mode
'(aider browse-kill-ring caddyfile-mode cape cider cmake-mode company
corfu dart-mode dockerfile-mode dumb-jump dumber-jump
emmet-mode exec-path-from-shell expand-region flutter
flycheck fsharp-mode gptel haskell-mode haskell-ts-mode
helm htmlize idris-mode llm-tool-collection llm-tools
marginalia markdown-ts-mode move-text multiple-cursors
odin-mode orderless org-mode parinfer-rust-mode
realgud-lldb rg sly terraform-mode tree-sitter-langs
ocaml-ts-mode orderless parinfer-rust-mode realgud-lldb
reason-mode rg sly terraform-mode tree-sitter-langs tuareg
vertico visual-fill-column yasnippet-snippets zig-mode))
'(package-vc-selected-packages
'((llm-tool-collection :url
"https://github.com/skissue/llm-tool-collection")))
'(safe-local-variable-directories '("/Users/grant/programming/project/edit/"))
'(safe-local-variable-values
'((Package . CL-USER) (Syntax . ANSI-Common-Lisp) (Base . 10)
'((checkdoc-allow-quoting-nil-and-t . t) (Package . CL-USER)
(Syntax . ANSI-Common-Lisp) (Base . 10)
(flycheck-clang-includes . "SDL3-3.2.26/include"))))
(custom-set-faces
;; custom-set-faces was added by Custom.

257
init.el
View File

@@ -1,5 +1,7 @@
;;; -*- lexical-binding: t; -*-
(setq gc-cons-threshold (* 800000 100))
(require 'package)
(add-to-list 'package-archives
'("melpa" . "https://melpa.org/packages/") t)
@@ -10,10 +12,13 @@
;; If startup times are slow
;; (setq use-package-verbose t)
;; (setq use-package-compute-statistics t)
(setq use-package-compute-statistics t)
(add-to-list 'load-path (concat user-emacs-directory "local-lisp/"))
(add-to-list 'load-path (concat user-emacs-directory "user-lisp/"))
(add-to-list 'load-path "/opt/homebrew/Cellar/mu/1.12.14/share/emacs/site-lisp/mu/mu4e")
(global-auto-revert-mode 1)
(recentf-mode 1)
(setq tab-bar-show nil)
(setq inhibit-startup-message t)
(setq initial-scratch-message nil)
@@ -39,6 +44,13 @@
(pixel-scroll-precision-mode 1)
(kill-ring-deindent-mode 1)
(setopt auto-revert-avoid-polling t)
(setopt auto-revert-interval 5)
(setopt auto-revert-check-vc-info t)
(global-auto-revert-mode 1)
(setq global-auto-revert-non-file-buffers t)
(setq-default grep-command "rg")
(setq use-package-always-ensure t)
(setq mac-command-modifier 'control)
(setq is-mac (string= system-type "darwin"))
@@ -51,13 +63,14 @@
(defun hgh/enable-cursor-blink ()
(blink-cursor-mode 1))
(add-hook 'activate-mark-hook 'hgh/disable-cursor-blink)
(add-hook 'activate-mark-hook 'hgh/disable-cursor-blink)
(add-hook 'deactivate-mark-hook 'hgh/enable-cursor-blink)
(when is-mac
(setq dired-use-ls-dired t
insert-directory-program "gls"
dired-listing-switches "-aBhl --group-directories-first"))
dired-listing-switches "-aBhl --group-directories-first")
(setq-default manual-program "gman"))
;; Add .asdf to exec-path
(when (file-exists-p (file-truename "~/.asdf"))
@@ -74,6 +87,11 @@
(when exec-path
(setenv "PATH" (string-join exec-path ":")))
(defun hgh/duplicate-dwim ()
(interactive)
(duplicate-dwim)
(next-line))
(defun hgh/visit-init-file ()
(interactive)
(find-file user-init-file))
@@ -110,7 +128,11 @@
(keymap-global-set "M-1" #'delete-other-windows)
(keymap-global-set "M-2" #'split-window-below)
(keymap-global-set "M-3" #'split-window-right)
(keymap-global-set "C-," #'duplicate-dwim)
(keymap-global-set "C-," #'hgh/duplicate-dwim)
(keymap-global-set "C-c f n" #'flymake-goto-next-error)
(keymap-global-set "C-c f p" #'flymake-goto-prev-error)
(keymap-global-set "C-c f b" #'flymake-show-buffer-diagnostics)
(keymap-set completion-list-mode-map "M-n" #'minibuffer-next-completion)
(keymap-set completion-list-mode-map "M-p" #'minibuffer-previous-completion)
@@ -123,34 +145,18 @@
(load-theme 'modus-vivendi t)
;; Let's prefer completion-preview for now if it's available
(if (version<= "30.1" emacs-version)
(use-package completion-preview
:ensure nil
:demand t
:bind
(:map completion-preview-active-mode-map
("M-n" . completion-preview-next-candidate)
("M-p" . completion-preview-preview-candidate))
:config
(global-completion-preview-mode t))
(use-package corfu
:custom
(corfu-auto t)
(corfu-cycle t)
:config
(global-corfu-mode 1)))
(use-package corfu
:custom
(corfu-auto t)
(corfu-cycle t)
:config
(global-corfu-mode 1))
(use-package exec-path-from-shell
:when is-mac
:config
(exec-path-from-shell-initialize))
(use-package man
:when is-mac
:custom
(manual-program "gman"))
(use-package magit
:defer t)
@@ -164,7 +170,7 @@
("\\.jsx\\'" . tsx-ts-mode)
("\\.json\\'" . json-ts-mode))
:custom
(treesit-extra-load-path '("~/repos/tree-sitter-module/dist")))
(treesit-extra-load-path `("~/repos/tree-sitter-module/dist" ,(concat user-emacs-directory "/tree-sitter"))))
(use-package tree-sitter-langs
:defer t)
@@ -175,6 +181,7 @@
(use-package company)
(defun enable-parinfer ()
(require 's)
(let ((buf (or (buffer-file-name) (buffer-name) "")))
(when (and
(not (s-contains? "sbcl" buf))
@@ -191,13 +198,46 @@
(parinfer-rust-disable-troublesome-modes t))
(use-package sly
:mode "\\.lisp\\'"
:commands (sly)
:custom
(inferior-lisp-program "/opt/homebrew/bin/sbcl"))
(defun hgh/--project-execute (command name)
(require 'project)
(when (not command)
(user-error (concat "No command specified for project-" name)))
(let ((default-directory (project-root (project-current t))))
(compilation-start command
nil
(lambda (mode-name) (concat "*" (project-name (project-current)) " " name "*")))))
(defmacro hgh/define-project-command (name)
"Define a project command with NAME.
Creates a variable `hgh/project-NAME-command' and function `hgh/project-NAME'."
(let ((var-sym (intern (format "hgh/project-%s-command" name)))
(fn-sym (intern (format "hgh/project-%s" name))))
`(progn
(defvar ,var-sym nil
,(format "Command to %s the project." name))
(put ',var-sym 'safe-local-variable #'stringp)
(defun ,fn-sym ()
,(format "Execute the project %s command." name)
(interactive)
(hgh/--project-execute ,var-sym ,name)))))
(hgh/define-project-command "run")
(hgh/define-project-command "test")
(hgh/define-project-command "lint")
(use-package project
:bind (("C-x p s" . hgh/project-ripgrep)
("C-x p S" . project-shell)))
("C-x p S" . project-shell)
("C-x p t" . hgh/project-test)
("C-x p r" . hgh/project-run)
("C-x p l" . hgh/project-lint)))
(use-package vertico
:custom
@@ -222,9 +262,6 @@
:init
(add-to-list 'completion-at-point-functions #'cape-dabbrev))
(use-package browse-kill-ring
:bind (("C-c y" . browse-kill-ring)))
(defun hgh/org-mode-visual-fill ()
(setq visual-fill-column-width 120
visual-fill-column-center-text t)
@@ -245,7 +282,10 @@
(svelte-mode . eglot-ensure)
(haskell-mode . eglot-ensure)
(terraform-mode . eglot-ensure)
(odin-mode . eglot-ensure))
(odin-mode . eglot-ensure)
(tuareg-mode . eglot-ensure)
(ruby-mode . eglot-ensure)
(dart-mode . eglot-ensure))
:bind
(:map eglot-mode-map
("C-c r" . eglot-rename)
@@ -257,21 +297,39 @@
:config
;; Set up using clippy with rust analyzer
(setf eglot-server-programs
(cl-remove-if (lambda (c) (equal (car c) 'rust-mode))
(cl-remove-if (lambda (c) (equal (car c) (or 'rust-mode 'tuareg-mode '(dart-mode dart-ts-mode))))
eglot-server-programs))
(setf eglot-server-programs
(append (list
(list '(rust-ts-mode rust-mode) "rust-analyzer" :initializationOptions '(:checkOnSave (:command "clippy")))
'(svelte-mode "svelteserver" "--stdio")
'(haskell-mode "haskell-language-server-wrapper" "--lsp"))
eglot-server-programs)))
'(haskell-mode "haskell-language-server-wrapper" "--lsp")
'(tuareg-mode "dune" "exec" "ocamllsp")
'(reason-mode "dune" "exec" "ocamllsp")
'(ruby-mode "solargraph" "socket" "--port" :autoport)
'(dart-mode "dart" "language-server" "--client-id" "emacs.eglot-dart")
eglot-server-programs))))
(setenv "DOTNET_ROOT" "~/.local/share/mise/installs/dotnet/9")
(push '("\\.csproj$" . xml-mode) auto-mode-alist)
(push '("\\.vs$" . c-mode) auto-mode-alist)
(push '("\\.fs$" . c-mode) auto-mode-alist)
(push '("\\.sbclrc" . lisp-mode) auto-mode-alist)
(push '("\\.csproj$" . xml-mode) auto-mode-alist)
(push '("\\.vs$" . c-mode) auto-mode-alist)
(push '("\\.fs$" . c-mode) auto-mode-alist)
(push '("\\.sbclrc" . lisp-mode) auto-mode-alist)
(push '("dune-project\\'" . lisp-mode) auto-mode-alist)
(push '("dune\\'" . lisp-mode) auto-mode-alist)
(push '("\\.ml\\'" . tuareg-mode) auto-mode-alist)
(setf auto-mode-alist
(cl-remove-if
(lambda (tup)
(or
(and
(string-equal (car tup) "\\.ml\\'")
(eq (cdr tup) 'ocaml-ts-mode))
(and
(string-equal (car tup) "\\.ml\\'")
(eq (cdr tup) 'lisp-mode))))
auto-mode-alist))
;; paredit
@@ -285,16 +343,10 @@
(define-key paredit-mode-map (kbd "M-:") #'paredit-backward-barf-sexp)
(enable-paredit-mode))
(defvar lisp-mode-hooks nil)
(setf lisp-mode-hooks '((emacs-lisp-mode-hook emacs-lisp-mode)
(clojure-mode-hook clojure-mode)
(lisp-mode-hook lisp-mode)
(sly-mode-hook sly-mode)))
(defun hgh/rg (search)
(interactive "sSearch: ")
(compilation-start
(concat "rg --no-heading '" search "'")
(concat "rg -S --no-heading '" search "'")
'compilation-mode
(lambda (s)
(concat
@@ -317,7 +369,6 @@
(advice-add 'mc/mark-next-like-this :before #'hgh/set-cursor-type-box)
(advice-add 'mc/mark-previous-like-this :before #'hgh/set-cursor-type-box))
(use-package yasnippet
:diminish
:defer t
@@ -365,26 +416,27 @@
(org-html-head-include-default-style nil)
(org-html-head-include-scripts nil)
(org-html-head "<link rel=\"stylesheet\" href=\"https://cdn.simplecss.org/simple.min.css\" />")
(org-capture-templates
'(("t" "Task" entry (file+headline "~/org/inbox.org" "Tasks")
"* TODO %?\n%U\n%i\n%a")
("n" "Note" entry (file+headline "~/org/inbox.org" "Notes")
"* %?\n%U\n%i\n%a")
("i" "Idea" entry (file+headline "~/org/inbox.org" "Ideas")
"* %?\n%U\n%i\n%a")))
'(("t" "Task" entry (file+headline "~/org/inbox.org" "Tasks")
"* TODO %?\n%U\n%i\n%a")
("n" "Note" entry (file+headline "~/org/inbox.org" "Notes")
"* %?\n%U\n%i\n%a")
("i" "Idea" entry (file+headline "~/org/inbox.org" "Ideas")
"* %?\n%U\n%i\n%a")))
(org-publish-use-timestamps-flag nil)
(org-publish-project-alist
(list
(list "writings"
:base-directory "~/Documents/writings/content"
:publishing-directory "~/Documents/writings/public"
:exclude "~/Documents/writings/notes"
:recursive t
:time-stamp-file nil
:section-numbers nil
:with-creator t
:with-author nil)))
(list
(list "writings"
:base-directory "~/Documents/writings/content"
:publishing-directory "~/Documents/writings/public"
:exclude "~/Documents/writings/notes"
:recursive t
:time-stamp-file nil
:section-numbers nil
:with-creator t
:with-author nil)))
:config
(require 'ox-publish)
@@ -394,10 +446,12 @@
'((shell . t))))
(use-package dumber-jump
:defer t
:init
(add-hook 'xref-backend-functions #'dumber-jump-xref-activate))
(use-package idris-mode)
(use-package idris-mode
:mode "\\.idr\\'")
(use-package htmlize)
@@ -444,3 +498,74 @@
js-mode
;; Need to add this
tsx-ts-mode)))
(use-package mu4e
:ensure nil
:commands (mu4e)
:custom
(mu4e-maildir "~/Mail")
(mu4e-get-mail-command "mbsync -a")
(mu4e-update-interval 300)
(mu4e-change-filenames-when-moving t)
(mu4e-context-policy 'pick-first)
(mu4e-compose-context-policy 'ask-if-none)
(mu4e-contexts
(list
(make-mu4e-context
:name "Grant"
:match-func
(lambda (msg)
(when msg
(string-prefix-p "/grant" (mu4e-message-field msg :maildir))))
:vars '((user-mail-address . "grant@granthorner.dev")
(user-full-name . "Grant Horner")
(mu4e-drafts-folder . "/grant/Drafts")
(mu4e-sent-folder . "/grant/Sent Mail")
(mu4e-trash-folder . "/grant/Trash")
(mu4e-rstrash-folder . "/grant/Trash")))
(make-mu4e-context
:name "Gmail"
:match-func
(lambda (msg)
(when msg
(string-prefix-p "/gmail" (mu4e-message-field msg :maildir))))
:vars '((user-mail-address . "gmail@granthorner.dev")
(user-full-name . "Grant Horner")
(mu4e-drafts-folder . "/gmail/Drafts")
(mu4e-sent-folder . "/gmail/Sent Mail")
(mu4e-trash-folder . "/gmail/Trash")
(mu4e-rstrash-folder . "/gmail/Trash"))))))
(use-package tuareg
:mode "\\.ml\\'")
(defun hgh/eglot-restart ()
(interactive)
(require 'eglot)
(when eglot--managed-mode
(message "Shutting down eglot servers...")
(eglot-shutdown-all)
(message "Restarting eglot for current buffer...")
(eglot)))
(use-package reason-mode
:mode "\\.re\\'")
(use-package expand-region
:bind ("C-=" . er/expand-region))
(use-package ediff
:defer t
:custom
(ediff-window-setup-function 'ediff-setup-windows-plain)
(ediff-split-window-function 'split-window-horizontally)
(ediff-diff-options "-w")
:config
(set-face-attribute 'ediff-odd-diff-A nil :background "#75383b")
(set-face-attribute 'ediff-even-diff-A nil :background "#75383b")
(set-face-attribute 'ediff-odd-diff-B nil :background "#265441")
(set-face-attribute 'ediff-even-diff-B nil :background "#265441"))
(use-package dart-mode)
(use-package flutter)

View File

@@ -83,7 +83,7 @@ http_headers = {
:group 'codex)
(defcustom codex-sessions-file (concat user-emacs-directory "codex-sessions")
"What the default name should be fore the codex chat buffer."
"What the default name should be for the codex chat buffer."
:type 'string
:group 'codex)

346
user-lisp/llm-tools.el Normal file
View File

@@ -0,0 +1,346 @@
;;; llm-tools.el --- Emacs tools for GPTel models -*- lexical-binding: t -*-
;; Copyright (C) 2024
;; Author: Your Name
;; Keywords: tools, convenience, gptel
;; This file is NOT part of GNU Emacs.
;;; Commentary:
;; This file provides GPTel tools that allow LLM models to look up
;; information in Emacs, including function names and documentation,
;; variable names and documentation, key bindings, and more.
;;; Code:
(require 'gptel)
;; ============================================================================
;; Documentation Lookup Tools
;; ============================================================================
(gptel-make-tool
:name "get_function_doc"
:category "emacs"
:description "Get the documentation string for an Emacs Lisp function. Returns the function's docstring, argument list, and source file location."
:function (lambda (function-name)
(let ((sym (intern-soft function-name)))
(if (and sym (fboundp sym))
(let* ((doc (documentation sym t))
(arglist (help-function-arglist sym))
(file (find-lisp-object-file-name sym 'defun)))
(format "Function: %s\nArguments: %s\nFile: %s\n\nDocumentation:\n%s"
sym
(if arglist (prin1-to-string arglist) "(unknown)")
(or file "built-in/unknown")
(or doc "No documentation available")))
(format "Error: Function '%s' not found" function-name))))
:args '((:name "function-name"
:type string
:description "The name of the function (symbol) to look up"
:optional nil)))
(gptel-make-tool
:name "get_variable_doc"
:category "emacs"
:description "Get the documentation string for an Emacs Lisp variable. Returns the variable's docstring, current value (if safe to display), and source file location."
:function (lambda (variable-name)
(let ((sym (intern-soft variable-name)))
(if (and sym (boundp sym))
(let* ((doc (documentation-property sym 'variable-documentation))
(file (find-lisp-object-file-name sym 'defvar))
(value (bound-and-true-p sym))
(value-str (if (and value (or (numberp value)
(stringp value)
(symbolp value)
(and (listp value)
(< (length (format "%s" value)) 200))))
(format "%S" value)
"(complex value omitted)")))
(format "Variable: %s\nCurrent Value: %s\nFile: %s\n\nDocumentation:\n%s"
sym
value-str
(or file "built-in/unknown")
(or doc "No documentation available")))
(format "Error: Variable '%s' not found" variable-name))))
:args '((:name "variable-name"
:type string
:description "The name of the variable (symbol) to look up"
:optional nil)))
(gptel-make-tool
:name "get_face_doc"
:category "emacs"
:description "Get information about an Emacs face including its documentation and attributes."
:function (lambda (face-name)
(let ((sym (intern-soft face-name)))
(if (and sym (facep sym))
(let* ((doc (documentation-property sym 'face-documentation))
(attrs (face-all-attributes sym (selected-frame))))
(format "Face: %s\n\nDocumentation:\n%s\n\nAttributes:\n%s"
sym
(or doc "No documentation available")
(mapconcat (lambda (attr)
(format " %s: %S" (car attr) (cdr attr)))
attrs
"\n")))
(format "Error: Face '%s' not found" face-name))))
:args '((:name "face-name"
:type string
:description "The name of the face to look up"
:optional nil)))
(gptel-make-tool
:name "describe_key"
:category "emacs"
:description "Get the command bound to a specific key sequence in Emacs."
:function (lambda (key-sequence)
(condition-case err
(let* ((keys (read-kbd-macro key-sequence))
(binding (key-binding keys))
(cmd (if (arrayp binding)
(format "Keymap: %S" binding)
binding)))
(if (and cmd (not (eq cmd 'nil)))
(format "Key: %s\nBound to: %s\n\nDocumentation:\n%s"
key-sequence
(if (symbolp cmd) cmd (format "%S" cmd))
(if (symbolp cmd)
(or (documentation cmd t) "No documentation")
"Not a command"))
(format "Key '%s' is not bound to any command" key-sequence)))
(error (format "Error parsing key sequence '%s': %s"
key-sequence
(error-message-string err)))))
:args '((:name "key-sequence"
:type string
:description "The key sequence as a string (e.g., 'C-x C-f')"
:optional nil)))
(gptel-make-tool
:name "apropos_lookup"
:category "emacs"
:description "Look up Emacs symbols using apropos. Searches for symbols matching a pattern and returns their names, types, and documentation snippets."
:function (lambda (pattern type)
(let* ((symbols (apropos-internal pattern (lambda (s) (fboundp s))))
(vars (apropos-internal pattern (lambda (s) (boundp s))))
(faces (apropos-internal pattern (lambda (s) (facep s))))
(filter-fn (cond
((string= type "functions") (lambda (s) (memq s symbols)))
((string= type "variables") (lambda (s) (memq s vars)))
((string= type "faces") (lambda (s) (memq s faces)))
(t (lambda (s) t))))
(all-symbols (delete-dups (append symbols vars faces)))
(filtered (seq-filter filter-fn all-symbols)))
(if filtered
(format "Found %d symbols matching '%s':\n\n%s"
(length filtered)
pattern
(mapconcat (lambda (sym)
(format "%s (%s)\n %s"
sym
(cond ((fboundp sym) "function")
((boundp sym) "variable")
((facep sym) "face")
(t "symbol"))
(let ((doc (or (documentation sym t)
(documentation-property sym 'variable-documentation)
(documentation-property sym 'face-documentation)
"No documentation")))
(if (> (length doc) 100)
(concat (substring doc 0 100) "...")
doc))))
filtered
"\n\n"))
(format "No symbols matching pattern '%s' found" pattern))))
:args '((:name "pattern"
:type string
:description "The regex pattern to search for in symbol names"
:optional nil)
(:name "type"
:type string
:description "Filter by type: 'functions', 'variables', 'faces', or 'all' (default 'all')"
:optional t
:default "all")))
;; ============================================================================
;; Buffer/List Tools
;; ============================================================================
(gptel-make-tool
:name "list_modes"
:category "emacs"
:description "List major and minor modes available in Emacs."
:function (lambda (mode-type pattern)
(let ((major-modes (apropos-internal "-mode\\'" (lambda (s)
(and (boundp s)
(get s 'derived-mode-parent)))))
(minor-modes (apropos-internal "-mode\\'" (lambda (s)
(and (boundp s)
(get s 'minor-mode-function)))))
(filter (when pattern
(lambda (s) (string-match-p pattern (symbol-name s))))))
(when filter
(setq major-modes (seq-filter filter major-modes))
(setq minor-modes (seq-filter filter minor-modes)))
(let ((results
(cond
((string= mode-type "major")
(cons "Major Modes" major-modes))
((string= mode-type "minor")
(cons "Minor Modes" minor-modes))
(t
`(("Major Modes" . ,major-modes)
("Minor Modes" . ,minor-modes))))))
(if (consp (car results))
(format "%s\n\n%s"
(format "%s (%d):\n%s"
(caar results)
(length (cdar results))
(mapconcat (lambda (m) (format " - %s" m))
(cdar results)
"\n"))
(format "%s (%d):\n%s"
(cadr results)
(length (cddr results))
(mapconcat (lambda (m) (format " - %s" m))
(cddr results)
"\n")))
(format "%s (%d):\n%s"
(car results)
(length (cdr results))
(mapconcat (lambda (m) (format " - %s" m))
(cdr results)
"\n"))))))
:args '((:name "mode-type"
:type string
:description "Type of mode: 'major', 'minor', or 'all' (default 'all')"
:optional t
:default "all")
(:name "pattern"
:type string
:description "Optional regex pattern to filter mode names"
:optional t)))
;; ============================================================================
;; Source Location Tools
;; ============================================================================
(gptel-make-tool
:name "find_function_source"
:category "emacs"
:description "Find the file where an Emacs Lisp function is defined. Returns the file path and line number if available."
:function (lambda (function-name)
(let ((sym (intern-soft function-name)))
(if (and sym (fboundp sym))
(let* ((file (find-lisp-object-file-name sym 'defun))
(source (when file
(with-temp-buffer
(insert-file-contents file)
(goto-char (point-min))
(re-search-forward (format "^(defun\\s +%s" (regexp-quote function-name)) nil t)
(when (re-search-backward "^(defun\\s +" nil t)
(cons file (line-number-at-pos)))))))
(if source
(format "Function '%s' is defined in:\n %s:%d"
function-name (car source) (cdr source))
(format "Function '%s' source location: %s"
function-name (or file "built-in/unknown"))))
(format "Error: Function '%s' not found" function-name))))
:args '((:name "function-name"
:type string
:description "The name of the function to locate"
:optional nil)))
(gptel-make-tool
:name "find_variable_source"
:category "emacs"
:description "Find the file where an Emacs Lisp variable is defined. Returns the file path if available."
:function (lambda (variable-name)
(let ((sym (intern-soft variable-name)))
(if (and sym (boundp sym))
(let ((file (or (find-lisp-object-file-name sym 'defvar)
(find-lisp-object-file-name sym 'defcustom)
(find-lisp-object-file-name sym 'defconst))))
(format "Variable '%s' is defined in: %s"
variable-name (or file "built-in/unknown")))
(format "Error: Variable '%s' not found" variable-name))))
:args '((:name "variable-name"
:type string
:description "The name of the variable to locate"
:optional nil)))
;; ============================================================================
;; Current Context Tools
;; ============================================================================
(gptel-make-tool
:name "get_buffer_context"
:category "emacs"
:description "Get information about the current buffer including its name, major mode, minor modes, and file path if applicable."
:function (lambda ()
(format "Buffer Context:\n Name: %s\n File: %s\n Major Mode: %s\n Minor Modes: %s\n Read-only: %s\n Modified: %s"
(buffer-name (current-buffer))
(or (buffer-file-name (current-buffer)) "N/A")
major-mode
(mapconcat (lambda (m) (format "%s" m))
(seq-filter (lambda (m) (and (boundp m) (symbol-value m)))
minor-mode-list)
", ")
(if buffer-read-only "yes" "no")
(if (buffer-modified-p) "yes" "no")))
:args nil)
(gptel-make-tool
:name "get_active_features"
:category "emacs"
:description "Get information about active Emacs features and packages."
:function (lambda ()
(format "Active Features (loaded packages/modules):\n%s"
(mapconcat (lambda (f) (format " - %s" f))
features
"\n")))
:args nil)
;; ============================================================================
;; Tool Registry for Easy Enable/Disable
;; ============================================================================
(defconst llm-tools-tool-list
'("get_function_doc"
"get_variable_doc"
"get_face_doc"
"describe_key"
"apropos_lookup"
"list_modes"
"find_function_source"
"find_variable_source"
"get_buffer_context"
"get_active_features")
"List of all LLM tool symbols defined in this file.")
;;;###autoload
(defun llm-tools-enable-all ()
"Enable all LLM Emacs tools for GPTel."
(interactive)
(dolist (tool llm-tools-tool-list)
(when (and tool (gptel-get-tool tool))
(add-to-list 'gptel-tools (gptel-get-tool tool))))
(message "LLM Emacs tools enabled for GPTel (%d tools)"
(length gptel-tools)))
;;;###autoload
(defun llm-tools-disable-all ()
"Disable all LLM Emacs tools from GPTel."
(interactive)
(dolist (tool llm-tools-tool-list)
(let ((tool-obj (gptel-get-tool tool)))
(when tool-obj
(setq gptel-tools (delete tool-obj gptel-tools)))))
(message "LLM Emacs tools disabled from GPTel"))
(provide 'llm-tools)
;;; llm-tools.el ends here

View File

@@ -0,0 +1,115 @@
;;; magit-ediff-all.el --- Compare all changed files between revisions using Ediff -*- lexical-binding:t -*-
;; Copyright (C) 2025 The Magit Project Contributors
;; Author: [Your Name]
;; Maintainer: [Your Name]
;; SPDX-License-Identifier: GPL-3.0-or-later
;;; Commentary:
;; This library extends Magit's ediff support by adding a command to
;; compare all changed files between two revisions using an ediff
;; session group.
;;; Code:
(require 'magit)
(require 'magit-ediff)
(require 'ediff)
(require 'cl-lib)
;; Define the new suffix key and description
(defvar magit-ediff-all-key "A"
"Key binding for the ediff all command in the magit-ediff transient.")
(defvar magit-ediff-all-description "All changed files (between revisions)"
"Description for the ediff all command in the magit-ediff transient.")
(defun magit-ediff-all--write-file (temp-files temp-buffers rev dir file)
(let* ((rel-path (if (file-name-absolute-p file)
(file-relative-name file (magit-toplevel))
file))
(rel-file (expand-file-name rel-path dir))
;; Handle files that are added or removed - use empty buffer
(buf (or (magit-find-file-noselect rev file)
(generate-new-buffer " *empty*"))))
;; Create necessary subdirectories
(let ((dir-part (file-name-directory rel-file)))
(when dir-part
(make-directory dir-part t)))
(push rel-file temp-files)
(push buf temp-buffers)
(with-current-buffer buf
(write-region (point-min) (point-max) rel-file nil 'quiet)
(add-hook 'ediff-prepare-buffer-hook
(lambda ()
(rename-buffer (format "%s~%s~" rel-file rev)))
0 t))))
;;;###autoload
(defun magit-ediff-all-files (revA revB)
"Compare all changed files between REVA and REVB using Ediff.
This creates an Ediff session group that allows you to navigate
through all changed files between the two revisions and compare
them one at a time."
(interactive
(pcase-let ((`(,a ,b) (magit-ediff-compare--read-revisions nil)))
(list a b)))
(when (or (not revA) (not revB))
(user-error "Both revisions must be specified"))
(magit-with-toplevel
(let* ((changed-files (magit-changed-files revA revB))
(dirA (make-temp-file (format "magit-ediff-%s-" revA) t))
(dirB (make-temp-file (format "magit-ediff-%s-" revB) t))
temp-files
temp-buffers)
(message "Preparing %d files for comparison..." (length changed-files))
;; Create temporary files for each revision
(dolist (file changed-files)
(magit-ediff-all--write-file temp-files temp-buffers revA dirA file)
(magit-ediff-all--write-file temp-files temp-buffers revB dirB file))
;; Define cleanup function
(cl-labels
((magit-ediff-all--cleanup ()
(dolist (file temp-files)
(when (file-exists-p file)
(delete-file file)))
;; Kill any buffers visiting files in the temp directories
(dolist (buf (buffer-list))
(when-let ((filename (buffer-file-name buf)))
(when (or (string-prefix-p (expand-file-name dirA) filename)
(string-prefix-p (expand-file-name dirB) filename))
(when (buffer-live-p buf)
(kill-buffer buf)))))
(when (file-exists-p dirA)
(delete-directory dirA t))
(when (file-exists-p dirB)
(delete-directory dirB t))
(dolist (buf temp-buffers)
(when (buffer-live-p buf)
(kill-buffer buf)))
(remove-hook 'ediff-quit-session-group-hook #'magit-ediff-all--cleanup)))
;; Register hooks
(add-hook 'ediff-quit-session-group-hook #'magit-ediff-all--cleanup)
;; Open ediff on the two directories
(message "Opening Ediff session group...")
(ediff-directories dirA dirB nil)))))
;; Add the command to the magit-ediff transient
(with-eval-after-load 'magit-ediff
(transient-append-suffix 'magit-ediff "r"
`(,magit-ediff-all-key ,magit-ediff-all-description magit-ediff-all-files)))
(provide 'magit-ediff-all)
;;; magit-ediff-all.el ends here