blob: fb25c13796c67e116af0ebd243927b75791c83af (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
;;; ai-rewrite.el --- Directive-picker wrappers for gptel-rewrite -*- lexical-binding: t; coding: utf-8; -*-
;; Author: Craig Jennings <c@cjennings.net>
;;; Commentary:
;; Adds two ergonomic wrappers around `gptel-rewrite':
;;
;; cj/gptel-rewrite-with-directive Pick a named directive,
;; then rewrite the region.
;; cj/gptel-rewrite-redo-with-different-directive
;; Re-run the previous region
;; with a different directive.
;;
;; A directive is a short system-message snippet attached to a name
;; (e.g. "terse", "fix-grammar"). The directive body is injected
;; into the rewrite via `gptel-rewrite-directives-hook' just for that
;; call -- no global state changes.
;;; Code:
;; Declare the hook variable special so our `let'-binding below is
;; dynamic (visible across the `call-interactively' that follows)
;; rather than lexical when this file is byte-compiled.
(defvar gptel-rewrite-directives-hook)
(defcustom cj/gptel-rewrite-directives
'(("terse"
. "Rewrite the text to be as terse as possible without losing meaning.\nDo not add commentary. Return only the rewritten text.")
("fix-grammar"
. "Fix grammar and spelling errors only. Do not rephrase, restructure,\nor change tone. Return only the corrected text.")
("refactor-readability"
. "Refactor the code for readability. Improve naming, split long\nfunctions when appropriate, remove unnecessary complexity, and preserve\nbehavior exactly. Return only the refactored code.")
("add-docstring"
. "Add or improve docstrings for every function in the region. Use the\nidiomatic docstring style for the language. Do not change executable\ncode. Return the whole region with the updated docstrings.")
("explain-as-comment"
. "Replace the region with the original code, preceded by a concise\nblock comment explaining what the code does. Use the language's\nidiomatic comment syntax. Return code + comment, nothing else.")
("shorten"
. "Shorten the text while preserving meaning, technical accuracy, and\nthe author's voice. Remove rhetorical padding. Return only the\nshortened text."))
"Named system-message directives for `cj/gptel-rewrite-with-directive'.
Each entry is a (NAME . BODY) pair where NAME is the directive label
presented in the completing-read prompt and BODY is the system
message injected into the next `gptel-rewrite' call."
:type '(alist :key-type string :value-type string)
:group 'cj)
(defvar-local cj/gptel-rewrite--last-region nil
"Cons (BEG-MARKER . END-MARKER) of the last directive-driven rewrite.")
(defvar-local cj/gptel-rewrite--last-directive nil
"Name of the directive used in the last directive-driven rewrite.")
(defun cj/gptel-rewrite--call-with-directive (directive-name beg end)
"Run `gptel-rewrite' over BEG..END with DIRECTIVE-NAME's system message.
Stores the region (as markers) and directive name on buffer-local
variables so `cj/gptel-rewrite-redo-with-different-directive' can
revisit them."
(let ((body (alist-get directive-name cj/gptel-rewrite-directives
nil nil #'equal)))
(unless body
(user-error "Unknown rewrite directive: %s" directive-name))
(setq-local cj/gptel-rewrite--last-region
(cons (copy-marker beg) (copy-marker end)))
(setq-local cj/gptel-rewrite--last-directive directive-name)
(let ((gptel-rewrite-directives-hook
(cons (lambda () body) gptel-rewrite-directives-hook)))
(save-excursion
(goto-char beg)
(push-mark end t t)
(call-interactively #'gptel-rewrite)))))
;;;###autoload
(defun cj/gptel-rewrite-with-directive (directive-name)
"Pick DIRECTIVE-NAME from `cj/gptel-rewrite-directives' and rewrite the region.
Requires an active region. The directive is applied only to this
call -- it does not modify global `gptel-directives'."
(interactive
(progn
(unless (use-region-p)
(user-error "No region selected"))
(list (completing-read
"Rewrite directive: "
(mapcar #'car cj/gptel-rewrite-directives) nil t))))
(cj/gptel-rewrite--call-with-directive
directive-name (region-beginning) (region-end)))
;;;###autoload
(defun cj/gptel-rewrite-redo-with-different-directive ()
"Re-run the previous directive-driven rewrite with a different directive.
The region is restored from the markers captured at the last call;
the user picks a new directive from the remaining choices."
(interactive)
(unless cj/gptel-rewrite--last-region
(user-error "No previous rewrite to redo in this buffer"))
(let* ((beg-mk (car cj/gptel-rewrite--last-region))
(end-mk (cdr cj/gptel-rewrite--last-region))
(current cj/gptel-rewrite--last-directive)
(others (cl-remove
current
(mapcar #'car cj/gptel-rewrite-directives)
:test #'equal))
(chosen (completing-read
(format "Re-rewrite with (was %s): " current)
others nil t)))
(cj/gptel-rewrite--call-with-directive
chosen (marker-position beg-mk) (marker-position end-mk))))
(provide 'ai-rewrite)
;;; ai-rewrite.el ends here
|