-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathevedel-utilities.el
350 lines (316 loc) · 14.8 KB
/
evedel-utilities.el
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
;;; -*- lexical-binding: t; -*-
(require 'cl-lib)
(defcustom e-descriptive-mode-roles
'((emacs-lisp-mode . "an Emacs Lisp programmer")
(js-mode . "a JavaScript programmer")
(c-mode . "a C programmer")
(c++-mode . "a C++ programmer")
(lisp-mode . "a Common Lisp programmer")
(web-mode . "a web developer"))
"Assciation list between major modes and model roles.
Answers the question \"who is the model?\""
:type 'list
:group 'evedel)
(defun e--cycle-list-around (element list)
"Cycle list LIST around ELEMENT.
If ELEMENT is found in LIST, returns a list with ELEMENT as the head and the
rest of the list rotated around it. Otherwise, returns the LIST."
(if-let ((element-tail (member element list)))
(append element-tail
(cl-loop for elt in list
while (not (eq elt element))
collect elt))
list))
(defun e--replace-text (start end text)
"Replace the text in the region from START to END with TEXT."
(save-excursion
(goto-char start)
(insert text)
(delete-region (point) (+ (point) (- end start)))))
(defun e--tint (source-color-name tint-color-name &optional intensity)
"Return hex string color of SOURCE-COLOR-NAME tinted with TINT-COLOR-NAME.
INTENSITY controls the tinting intensity, where 0 means no tinting and 1 means
that the resulting color is the same as the TINT-COLOR-NAME color."
(let* ((tint (color-name-to-rgb tint-color-name))
(color (color-name-to-rgb source-color-name))
(result (cl-mapcar (lambda (color tint)
(+ (* (- 1.0 intensity) color)
(* intensity tint)))
color
tint)))
(apply 'color-rgb-to-hex `(,@result 2))))
(defun e--pos-bol-p (pos buffer)
"Return nil if POS is not a beginning of a line in BUFFER."
(with-current-buffer buffer
(save-excursion
(goto-char pos)
(= pos (pos-bol)))))
(defun e--fill-label-string (string &optional prefix-string padding buffer)
"Fill STRING into its label.
If PREFIX-STRING is not nil, whitespace padding is added at the start of
every newline in STRING so that it aligns visually under PREFIX-STRING.
If PADDING is non-nil, then pad the entire string from the left with it.
If BUFFER is provided, STRING will be wrapped to not overflow the fill column
of BUFFER. Wrapping will attempt to respect word boundaries and only hyphenate
words as a last resort if a word is too long to fit on a line by itself."
(let* ((paragraph-padding (if prefix-string
(make-string (length prefix-string) ? )
""))
(padding-fill-column (if buffer
(- (with-current-buffer buffer
fill-column)
(if (null padding) 0 (length padding))
(length paragraph-padding))
nil)))
(when (< padding-fill-column (length prefix-string))
(setq padding-fill-column nil))
(with-temp-buffer
(when fill-column
(let ((fill-column padding-fill-column))
(insert string " ") ; The whitespace is so that large words at the EOB will be wrapped.
(goto-char (point-min))
(catch 'search-end
(while t
(beginning-of-line)
(let ((beg (point)))
(let (best-col-pos
(lineno (line-number-at-pos beg)))
(while (and (= (line-number-at-pos (point)) lineno)
(< (current-column) fill-column))
(setq best-col-pos (point))
(condition-case nil
(re-search-forward "\\s-+")
(error
(throw 'search-end nil))))
(goto-char best-col-pos)
(let ((eol-col (save-excursion (end-of-line) (current-column))))
(if (>= eol-col fill-column)
(progn
(when (bolp)
(forward-char (1- fill-column))
(insert "-"))
(save-excursion
(end-of-line)
(unless (>= (current-column) fill-column)
(delete-char 1)
(insert " ")))
(insert "\n"))
(forward-line)))))))))
(goto-char (point-min))
(insert prefix-string)
(forward-line)
(beginning-of-line)
(while (not (eobp))
(when padding
(insert padding))
(insert paragraph-padding)
(beginning-of-line)
(forward-line))
(string-trim (buffer-string)))))
(defun e--apply-face-to-match (regex string face)
"Apply FACE as a text property to the REGEX match in STRING.
If FACE is nil, removes the face property from the REGEX match in STRING."
(with-temp-buffer
(insert string)
(goto-char (point-min))
(while (re-search-forward regex nil t)
(if face
(add-text-properties (match-beginning 0) (match-end 0) `(face ,face))
(remove-text-properties (match-beginning 0) (match-end 0) '(face nil))))
(buffer-string)))
(defun e--restore-overlay (buffer overlay-start overlay-end properties)
"Helper function to restore an instruction overlay in BUFFER.
Uses PROPERTIES, OVERLAY-START, and OVERLAY-END to recreate the overlay."
(let ((new-ov (make-overlay overlay-start overlay-end buffer)))
(mapc (lambda (prop)
(overlay-put new-ov prop (plist-get properties prop)))
properties)
new-ov))
(defun e--descriptive-llm-mode-role (mode)
"Derive the descriptive major mode role name from the major MODE.
Defaults to \"a helpful assistant\" if no appropriate role has been found in
the `evedel-descriptive-mode-roles' variable.
The role will default to \"a careful programmer\" if the major mode is not
listed in `evedel-descriptive-mode-roles' but is derivative from `prog-mode'."
(if-let ((role (alist-get mode e-descriptive-mode-roles)))
role
(if (provided-mode-derived-p mode 'prog-mode)
"a careful programmer"
"a helpful assistant")))
(defun e--directive-llm-system-message (directive)
"Craft the system message for the LLM model associated with the DIRECTIVE.
Returns the message as a string."
(with-current-buffer (overlay-buffer directive)
(concat "You are " (e--descriptive-llm-mode-role major-mode) ". Follow user directive.")))
(defun e--delimiting-markdown-backticks (string)
"Return a string containing the appropriate code block backticks for STRING."
(let ((backticks "```"))
(while (string-match-p backticks string)
(setq backticks (concat backticks "`")))
backticks))
(defun e--overlay-region-info (overlay)
"Return region span information of OVERLAY in its buffer.
Returns three values, first being the region line & column span string in the
buffer, and the second being the content of the span itself."
(let ((beg (overlay-start overlay))
(end (overlay-end overlay)))
(cl-labels ((pos-bol-p (pos)
(save-excursion
(goto-char pos)
(bolp)))
(pos-eol-p (pos)
(save-excursion
(goto-char pos)
(eolp)))
(pos-lineno (pos)
(line-number-at-pos pos))
(pos-colno (pos)
(save-excursion
(goto-char pos)
(current-column))))
(with-current-buffer (overlay-buffer overlay)
(without-restriction
(unless (= beg end)
(when (pos-eol-p beg)
(cl-incf beg))
(when (pos-bol-p end)
(cl-decf end)))
(if (= beg end (point-min))
(cl-values "beginning of the buffer" "")
(let ((beg-lineno (pos-lineno beg))
(end-lineno (pos-lineno end))
(beg-colno (pos-colno beg))
(end-colno (pos-colno end)))
(cl-values (format "line%s %s"
(if (/= beg-lineno end-lineno) "s" "")
(if (/= beg-lineno end-lineno)
(format "%d%s-%d%s"
beg-lineno
(if (pos-bol-p beg)
""
(format ":%d" beg-colno))
end-lineno
(if (pos-eol-p end)
""
(format ":%d" end-colno)))
(format "%s%s"
beg-lineno
(if (and (pos-bol-p beg) (pos-eol-p end))
""
(if (= beg-colno end-colno)
(format ", column %d" beg-colno)
(format ", columns %d-%s"
beg-colno
(if (pos-eol-p end)
"eol"
(format "%d" end-colno))))))))
(buffer-substring-no-properties beg end)))))))))
(defun e--multiline-string-p (str)
"Check if STR contains multiple lines."
(string-match-p "\n" str))
(defun e--tag-query-prefix-from-infix (query)
"Transform the tag QUERY to prefix notation for Lisp.
Signals an error when the query is malformed."
(cl-labels ((operatorp (elm)
(member elm '(and or not)))
(unary-op-p (elm)
(eq elm 'not))
(binary-op-p (elm)
(member elm '(and or)))
(expressionp (elm)
(or (atomp elm) (listp elm)))
(atomp (elm)
(and (not (listp elm)) (not (operatorp elm))))
(expand-implicit-and-ops (expr)
(let ((result '()))
(dolist (elm expr)
(let ((prev (car result)))
(cond
((binary-op-p elm)
(cond
((binary-op-p prev)
(error "Consecutive binary operators: %s, %s" prev elm))
((not (expressionp prev))
(error "Binary operator follows operator: %s, %s" prev elm))))
((unary-op-p elm)
(cond
((unary-op-p prev)
(error "Consecutive unary operator: %s" prev)))))
(when (and (not (binary-op-p elm)) prev (not (operatorp prev)))
(push 'and result))
(push elm result)))
(cond
((operatorp (car result))
(error "Operator not followed by any expression: %s" (car result)))
((binary-op-p (car (last result)))
(error "Binary operator not following any expression: %s" (car (last result)))))
(nreverse result)))
(aux (elm)
(pcase elm
((pred atomp) elm)
((pred expressionp)
(let ((expanded-expr (expand-implicit-and-ops elm))
(toplevel-op nil)
(operator nil)
(multiplicative-exprs ())
(operatorless-arg nil)
(args ())
(negate-next-expr nil))
(dolist (elm expanded-expr)
(pcase elm
((pred expressionp)
(if (null operator)
(if (not negate-next-expr)
(setq operatorless-arg (aux elm))
(setq operatorless-arg `(not ,(aux elm)))
(setq negate-next-expr nil))
(cl-symbol-macrolet ((dst (if (eq operator 'and)
multiplicative-exprs
args)))
(when operatorless-arg
(push operatorless-arg dst)
(setq operatorless-arg nil))
(if (not negate-next-expr)
(push (aux elm) dst)
(push `(not ,(aux elm)) dst)
(setq negate-next-expr nil)))))
((pred operatorp)
(if (unary-op-p elm)
(setq negate-next-expr t)
(unless (eq toplevel-op 'or)
(setq toplevel-op elm))
(setq operator elm)
(unless (eq operator 'and)
(when multiplicative-exprs
(push `(and ,@(nreverse multiplicative-exprs)) args)
(setq multiplicative-exprs ())))))))
(if operatorless-arg
operatorless-arg
(if args
(progn
(when multiplicative-exprs
(push `(and ,@multiplicative-exprs) args))
`(,toplevel-op ,@(nreverse args)))
(when multiplicative-exprs
`(and ,@(nreverse multiplicative-exprs))))))))))
(aux query)))
(defun e--markdown-enquote (input-string)
"Add Markdown blockquote to each line in INPUT-STRING."
(let ((lines (split-string input-string "\n")))
(mapconcat (lambda (line) (concat "> " line)) lines "\n")))
(defun e--markdown-code-blocks (text)
"Extract Markdown code block contents from TEXT.
Returns a list with the blocks in the order they were found."
(let ((blocks '())
(pos 0)
(regex "```\\(.*\\)?\n\\([[:ascii:][:nonascii:]]*?\\)\n```"))
(while (string-match regex text pos)
(let ((block (match-string 2 text)))
(setq blocks (append blocks (list block)))
(setq pos (match-end 0))))
blocks))
(provide 'evedel-utilities)
;; Local Variables:
;; read-symbol-shorthands: (("e-" . "evedel-"))
;; End:
;;; evedel-utilities.el ends here.