From b3d41f9a0c63b13ad497a48677de933a3fb5a5cf Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Mon, 18 May 2026 02:24:30 -0400 Subject: refactor(ai-config): switch gptel to local fork, drop tab-width advice I switched the gptel use-package form to `:load-path "~/code/gptel"` with `:ensure nil` so Emacs loads from the fork instead of the MELPA release. The fork now carries the narrow `tab-width' copy in `gptel-org--create-prompt' that karthink redirected the upstream PR to, which replaces the local `:around' advice on `gptel--with-buffer-copy-internal' I'd been carrying. I also dropped the stale test file `tests/test-ai-config-gptel-prompt-tab-width.el' and the matching stub in `tests/testutil-ai-config.el'. Both existed only to test the advice I removed. --- modules/ai-config.el | 24 +----- tests/test-ai-config-gptel-prompt-tab-width.el | 106 ------------------------- tests/testutil-ai-config.el | 15 ---- 3 files changed, 2 insertions(+), 143 deletions(-) delete mode 100644 tests/test-ai-config-gptel-prompt-tab-width.el diff --git a/modules/ai-config.el b/modules/ai-config.el index 050efbd4..ddbe91b8 100644 --- a/modules/ai-config.el +++ b/modules/ai-config.el @@ -361,6 +361,8 @@ Works for any buffer, whether it's visiting a file or not." ;;; ---------------------------- GPTel Configuration ---------------------------- (use-package gptel + :load-path "~/code/gptel" + :ensure nil :defer t :commands (gptel gptel-send gptel-menu) :bind @@ -391,28 +393,6 @@ Works for any buffer, whether it's visiting a file or not." (advice-add 'gptel-send :before #'cj/gptel--refresh-org-prefix) (add-hook 'gptel-post-response-functions #'cj/gptel-insert-model-heading)) -;; Workaround: gptel's `gptel--with-buffer-copy-internal' copies the -;; source buffer's `major-mode' symbol into the prompt buffer but does -;; not run mode hooks, so `org-mode-hook' never fires there. In this -;; config the global `tab-width' default is 4, while `org-mode-hook' -;; sets it to 8 — so an inherited-org-mode prompt buffer keeps -;; `tab-width=4', and Org's `org-element--list-struct' guard raises -;; "Tab width in Org files must be 8" when gptel later parses it. -;; -;; Triggered in practice by `gptel-magit-generate-message' run from -;; COMMIT_EDITMSG with `git-commit-major-mode' set to `org-mode' (see -;; modules/vc-config.el). Force `tab-width=8' before `body-thunk' -;; runs so the prompt buffer satisfies Org's invariant. -(define-advice gptel--with-buffer-copy-internal - (:around (orig buf start end body-thunk) cj/fix-org-tab-width) - "Force `tab-width=8' in the gptel prompt buffer when its inherited -`major-mode' is `org-mode'." - (funcall orig buf start end - (lambda () - (when (eq major-mode 'org-mode) - (setq-local tab-width 8)) - (funcall body-thunk)))) - ;;; ---------------------------- Toggle GPTel Window ---------------------------- (defun cj/toggle-gptel () diff --git a/tests/test-ai-config-gptel-prompt-tab-width.el b/tests/test-ai-config-gptel-prompt-tab-width.el deleted file mode 100644 index 6f857cdc..00000000 --- a/tests/test-ai-config-gptel-prompt-tab-width.el +++ /dev/null @@ -1,106 +0,0 @@ -;;; test-ai-config-gptel-prompt-tab-width.el --- Tests for gptel prompt-buffer tab-width fix -*- lexical-binding: t; -*- - -;;; Commentary: -;; Regression tests for the `org-element--list-struct: Tab width in Org -;; files must be 8, not 4' error that fires when `gptel-magit' is -;; invoked from a `COMMIT_EDITMSG' buffer where `git-commit-major-mode' -;; is `org-mode'. -;; -;; Root cause: gptel's `gptel--with-buffer-copy-internal' -;; (gptel-request.el:945) sets the prompt buffer's `major-mode' to the -;; source buffer's mode as a *symbol*, without running mode hooks. So -;; an inherited `org-mode' prompt buffer keeps the global default -;; `tab-width' (4 in this config) — `org-mode-hook' never runs to set -;; 8. When gptel later parses the prompt buffer with `org-element', -;; Org's `tab-width=8' guard raises. -;; -;; Fix: an `:around' advice on `gptel--with-buffer-copy-internal' in -;; ai-config.el sets `tab-width=8' inside the prompt buffer when its -;; inherited `major-mode' is `org-mode'. -;; -;; These tests verify the advice fires for org-mode source buffers and -;; stays out of the way for other modes. Each test pins -;; `default-value' of `tab-width' to 4 — matching Craig's real config -;; — so the advice's effect is observable in batch tests (where the -;; default would otherwise be Emacs's built-in 8). - -;;; Code: - -(require 'ert) - -(add-to-list 'load-path (expand-file-name "tests" user-emacs-directory)) -(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) - -;; testutil-ai-config provides the gptel--with-buffer-copy-internal stub -;; and the gptel stubs ai-config needs to load. -(require 'testutil-ai-config) -(require 'ai-config) - -(defmacro test-ai-config--with-default-tab-width (sentinel &rest body) - "Bind `default-value' of `tab-width' to SENTINEL during BODY. -Restores the prior default on unwind. New buffers created inside BODY -inherit SENTINEL as their `tab-width'." - (declare (indent 1)) - `(let ((saved (default-value 'tab-width))) - (unwind-protect - (progn (setq-default tab-width ,sentinel) - ,@body) - (setq-default tab-width saved)))) - -;;; Normal Cases - -(ert-deftest test-ai-config-gptel-prompt-tab-width-org-mode-source-sets-8 () - "Normal: source buffer in `org-mode' → prompt buffer has `tab-width=8'. -Regression net for the `org-element--list-struct: Tab width must be 8' -error when gptel-magit runs from COMMIT_EDITMSG with -`git-commit-major-mode=org-mode'." - (test-ai-config--with-default-tab-width 4 - (let ((recorded nil)) - (with-temp-buffer - (setq major-mode 'org-mode) - (gptel--with-buffer-copy-internal - (current-buffer) nil nil - (lambda () (setq recorded tab-width)))) - (should (= recorded 8))))) - -;;; Boundary Cases - -(ert-deftest test-ai-config-gptel-prompt-tab-width-text-mode-source-not-overridden () - "Boundary: source buffer in `text-mode' → advice does NOT set `tab-width=8'. -Only `org-mode' source triggers the override; other modes keep the -inherited default." - (test-ai-config--with-default-tab-width 4 - (let ((recorded nil)) - (with-temp-buffer - (setq major-mode 'text-mode) - (gptel--with-buffer-copy-internal - (current-buffer) nil nil - (lambda () (setq recorded tab-width)))) - (should (= recorded 4))))) - -(ert-deftest test-ai-config-gptel-prompt-tab-width-fundamental-mode-source-not-overridden () - "Boundary: source buffer in `fundamental-mode' → no override. -Confirms the advice's `(eq major-mode 'org-mode)' gate is strict." - (test-ai-config--with-default-tab-width 4 - (let ((recorded nil)) - (with-temp-buffer - (setq major-mode 'fundamental-mode) - (gptel--with-buffer-copy-internal - (current-buffer) nil nil - (lambda () (setq recorded tab-width)))) - (should (= recorded 4))))) - -(ert-deftest test-ai-config-gptel-prompt-tab-width-advice-does-not-leak () - "Boundary: advice's `setq-local' stays buffer-local to the prompt buffer. -After body-thunk completes and the prompt buffer is killed, the source -buffer's `tab-width' is unchanged." - (with-temp-buffer - (setq major-mode 'org-mode) - (setq-local tab-width 4) - (gptel--with-buffer-copy-internal - (current-buffer) nil nil - (lambda () nil)) - (should (= tab-width 4)))) - -(provide 'test-ai-config-gptel-prompt-tab-width) -;;; test-ai-config-gptel-prompt-tab-width.el ends here diff --git a/tests/testutil-ai-config.el b/tests/testutil-ai-config.el index f56a38e1..c7486222 100644 --- a/tests/testutil-ai-config.el +++ b/tests/testutil-ai-config.el @@ -74,21 +74,6 @@ ;; so the magit integration only activates when magit is provided. ;; See test-ai-config-gptel-magit-lazy-loading.el for magit stub tests. -;; Stub `gptel--with-buffer-copy-internal' so the advice attached to it -;; in ai-config.el can be exercised without loading real gptel-request. -;; Mirrors the salient bits of the real function (gptel-request.el:945): -;; create a temp buffer, copy major-mode as a symbol (no hooks), call -;; body-thunk inside. See test-ai-config-gptel-prompt-tab-width.el. -(unless (fboundp 'gptel--with-buffer-copy-internal) - (defun gptel--with-buffer-copy-internal (buf _start _end body-thunk) - "Stub: create temp buffer, copy major-mode from BUF, run BODY-THUNK." - (let ((temp (generate-new-buffer " *gptel-prompt-stub*" t))) - (unwind-protect - (with-current-buffer temp - (setq major-mode (buffer-local-value 'major-mode buf)) - (funcall body-thunk)) - (kill-buffer temp))))) - ;; Stub ai-conversations (provide 'ai-conversations) -- cgit v1.2.3