Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
marvel
GitHub Repository: marvel/qnf
Path: blob/master/elisp/boxquote.el
987 views
1
;;; boxquote.el --- Quote text with a semi-box.
2
;; Copyright 1999-2009 by Dave Pearson <[email protected]>
3
;; $Revision: 1.23 $
4
5
;; boxquote.el is free software distributed under the terms of the GNU
6
;; General Public Licence, version 2 or (at your option) any later version.
7
;; For details see the file COPYING.
8
9
;;; Commentary:
10
11
;; boxquote provides a set of functions for using a text quoting style that
12
;; partially boxes in the left hand side of an area of text, such a marking
13
;; style might be used to show externally included text or example code.
14
;;
15
;; ,----
16
;; | The default style looks like this.
17
;; `----
18
;;
19
;; A number of functions are provided for quoting a region, a buffer, a
20
;; paragraph and a defun. There are also functions for quoting text while
21
;; pulling it in, either by inserting the contents of another file or by
22
;; yanking text into the current buffer.
23
;;
24
;; The latest version of boxquote.el can be found at:
25
;;
26
;; <URL:http://www.davep.org/emacs/#boxquote.el>
27
28
;;; Thanks:
29
30
;; Kai Grossjohann for inspiring the idea of boxquote. I wrote this code to
31
;; mimic the "inclusion quoting" style in his Usenet posts. I could have
32
;; hassled him for his code but it was far more fun to write it myself.
33
;;
34
;; Mark Milhollan for providing a patch that helped me get the help quoting
35
;; functions working with XEmacs.
36
;;
37
;; Oliver Much for suggesting the idea of having a `boxquote-kill-ring-save'
38
;; function.
39
;;
40
;; Reiner Steib for suggesting `boxquote-where-is' and the idea of letting
41
;; `boxquote-describe-key' describe key bindings from other buffers. Also
42
;; thanks go to Reiner for suggesting `boxquote-insert-buffer'.
43
44
;;; Code:
45
46
;; Things we need:
47
48
(eval-when-compile
49
(require 'cl))
50
(require 'rect)
51
52
;; Attempt to handle older/other emacs.
53
(eval-and-compile
54
55
;; If customize isn't available just use defvar instead.
56
(unless (fboundp 'defgroup)
57
(defmacro defgroup (&rest rest) nil)
58
(defmacro defcustom (symbol init docstring &rest rest)
59
`(defvar ,symbol ,init ,docstring)))
60
61
;; If `line-beginning-position' isn't available provide one.
62
(unless (fboundp 'line-beginning-position)
63
(defun line-beginning-position (&optional n)
64
"Return the `point' of the beginning of the current line."
65
(save-excursion
66
(beginning-of-line n)
67
(point))))
68
69
;; If `line-end-position' isn't available provide one.
70
(unless (fboundp 'line-end-position)
71
(defun line-end-position (&optional n)
72
"Return the `point' of the end of the current line."
73
(save-excursion
74
(end-of-line n)
75
(point)))))
76
77
;; Customize options.
78
79
(defgroup boxquote nil
80
"Mark regions of text with a half-box."
81
:group 'editing
82
:prefix "boxquote-")
83
84
(defcustom boxquote-top-and-tail "----"
85
"*Text that will be used at the top and tail of the box."
86
:type 'string
87
:group 'boxquote)
88
89
(defcustom boxquote-top-corner ","
90
"*Text used for the top corner of the box."
91
:type 'string
92
:group 'boxquote)
93
94
(defcustom boxquote-bottom-corner "`"
95
"*Text used for the bottom corner of the box."
96
:type 'string
97
:group 'boxquote)
98
99
(defcustom boxquote-side "| "
100
"*Text used for the side of the box."
101
:type 'string
102
:group 'boxquote)
103
104
(defcustom boxquote-title-format "[ %s ]"
105
"*Format string to use when creating a box title."
106
:type 'string
107
:group 'boxquote)
108
109
(defcustom boxquote-title-files t
110
"*Should a `boxquote-insert-file' title the box with the file name?"
111
:type '(choice
112
(const :tag "Title the box with the file name" t)
113
(const :tag "Don't title the box with the file name" nil))
114
:group 'boxquote)
115
116
(defcustom boxquote-file-title-function #'file-name-nondirectory
117
"*Function to apply to a file's name when using it to title a box."
118
:type 'function
119
:group 'boxquote)
120
121
(defcustom boxquote-title-buffers t
122
"*Should a `boxquote-insert-buffer' title the box with the buffer name?"
123
:type '(choice
124
(const :tag "Title the box with the buffer name" t)
125
(const :tag "Don't title the box with the buffer name" nil))
126
:group 'boxquote)
127
128
(defcustom boxquote-buffer-title-function #'identity
129
"*Function to apply to a buffer's name when using it to title a box."
130
:type 'function
131
:group 'boxquote)
132
133
(defcustom boxquote-region-hook nil
134
"*Hooks to perform when on a region prior to boxquoting.
135
136
Note that all forms of boxquoting use `boxquote-region' to create the
137
boxquote. Because of this any hook you place here will be invoked by any of
138
the boxquoting functions."
139
:type 'hook
140
:group 'boxquote)
141
142
(defcustom boxquote-yank-hook nil
143
"*Hooks to perform on the yanked text prior to boxquoting."
144
:type 'hook
145
:group 'boxquote)
146
147
(defcustom boxquote-insert-file-hook nil
148
"*Hooks to perform on the text from an inserted file prior to boxquoting."
149
:type 'hook
150
:group 'boxquote)
151
152
(defcustom boxquote-kill-ring-save-title #'buffer-name
153
"*Function for working out the title for a `boxquote-kill-ring-save'.
154
155
The string returned from this function will be used as the title for a
156
boxquote when the saved text is yanked into a buffer with \\[boxquote-yank].
157
158
An example of a non-trivial value for this variable might be:
159
160
(lambda ()
161
(if (string= mode-name \"Article\")
162
(aref gnus-current-headers 4)
163
(buffer-name)))
164
165
In this case, if you are a `gnus' user, \\[boxquote-kill-ring-save] could be
166
used to copy text from an article buffer and, when it is yanked into another
167
buffer using \\[boxquote-yank], the title of the boxquote would be the ID of
168
the article you'd copied the text from."
169
:type 'function
170
:group 'boxquote)
171
172
(defcustom boxquote-describe-function-title-format "C-h f %s RET"
173
"*Format string to use when formatting a function description box title"
174
:type 'string
175
:group 'boxquote)
176
177
(defcustom boxquote-describe-variable-title-format "C-h v %s RET"
178
"*Format string to use when formatting a variable description box title"
179
:type 'string
180
:group 'boxquote)
181
182
(defcustom boxquote-describe-key-title-format "C-h k %s"
183
"*Format string to use when formatting a key description box title"
184
:type 'string
185
:group 'boxquote)
186
187
(defcustom boxquote-where-is-title-format "C-h w %s RET"
188
"*Format string to use when formatting a `where-is' description box title"
189
:type 'string
190
:group 'boxquote)
191
192
(defcustom boxquote-where-is-body-format "%s is on %s"
193
"*Format string to use when formatting a `where-is' description."
194
:type 'string
195
:group 'boxquote)
196
197
;; Main code:
198
199
(defun boxquote-xemacs-p ()
200
"Are we running in XEmacs?"
201
(and (boundp 'running-xemacs) (symbol-value 'running-xemacs)))
202
203
(defun boxquote-points ()
204
"Find the start and end points of a boxquote.
205
206
If `point' is inside a boxquote then a cons is returned, the `car' is the
207
start `point' and the `cdr' is the end `point'. NIL is returned if no
208
boxquote is found."
209
(save-excursion
210
(beginning-of-line)
211
(let* ((re-top (concat "^" (regexp-quote boxquote-top-corner)
212
(regexp-quote boxquote-top-and-tail)))
213
(re-left (concat "^" (regexp-quote boxquote-side)))
214
(re-bottom (concat "^" (regexp-quote boxquote-bottom-corner)
215
(regexp-quote boxquote-top-and-tail)))
216
(points
217
(flet ((find-box-end (re &optional back)
218
(save-excursion
219
(when (if back
220
(search-backward-regexp re nil t)
221
(search-forward-regexp re nil t))
222
(point)))))
223
(cond ((looking-at re-top)
224
(cons (point) (find-box-end re-bottom)))
225
((looking-at re-left)
226
(cons (find-box-end re-top t) (find-box-end re-bottom)))
227
((looking-at re-bottom)
228
(cons (find-box-end re-top t) (line-end-position)))))))
229
(when (and (car points) (cdr points))
230
points))))
231
232
(defun boxquote-quoted-p ()
233
"Is `point' inside a boxquote?"
234
(not (null (boxquote-points))))
235
236
(defun boxquote-points-with-check ()
237
"Get the `boxquote-points' and flag an error of no box was found."
238
(or (boxquote-points) (error "I can't see a box here")))
239
240
(defun boxquote-title-format-as-regexp ()
241
"Return a regular expression to match the title."
242
(with-temp-buffer
243
(insert (regexp-quote boxquote-title-format))
244
(setf (point) (point-min))
245
(when (search-forward "%s" nil t)
246
(replace-match ".*" nil t))
247
(buffer-string)))
248
249
(defun boxquote-get-title ()
250
"Get the title for the current boxquote."
251
(multiple-value-bind (prefix-len suffix-len)
252
(with-temp-buffer
253
(let ((look-for "%s"))
254
(insert boxquote-title-format)
255
(setf (point) (point-min))
256
(search-forward look-for)
257
(list (- (point) (length look-for) 1) (- (point-max) (point)))))
258
(save-excursion
259
(save-restriction
260
(boxquote-narrow-to-boxquote)
261
(setf (point) (+ (point-min)
262
(length (concat boxquote-top-corner
263
boxquote-top-and-tail))))
264
(if (looking-at (boxquote-title-format-as-regexp))
265
(buffer-substring-no-properties (+ (point) prefix-len)
266
(- (line-end-position) suffix-len))
267
"")))))
268
269
;;;###autoload
270
(defun boxquote-title (title)
271
"Set the title of the current boxquote to TITLE.
272
273
If TITLE is an empty string the title is removed. Note that the title will
274
be formatted using `boxquote-title-format'."
275
(interactive (list (read-from-minibuffer "Title: " (boxquote-get-title))))
276
(save-excursion
277
(save-restriction
278
(boxquote-narrow-to-boxquote)
279
(setf (point) (+ (point-min)
280
(length (concat boxquote-top-corner
281
boxquote-top-and-tail))))
282
(unless (eolp)
283
(kill-line))
284
(unless (zerop (length title))
285
(insert (format boxquote-title-format title))))))
286
287
;;;###autoload
288
(defun boxquote-region (start end)
289
"Draw a box around the left hand side of a region bounding START and END."
290
(interactive "r")
291
(save-excursion
292
(save-restriction
293
(flet ((bol-at-p (n)
294
(setf (point) n)
295
(bolp))
296
(insert-corner (corner pre-break)
297
(insert (concat (if pre-break "\n" "")
298
corner boxquote-top-and-tail "\n"))))
299
(let ((break-start (not (bol-at-p start)))
300
(break-end (not (bol-at-p end))))
301
(narrow-to-region start end)
302
(run-hooks 'boxquote-region-hook)
303
(setf (point) (point-min))
304
(insert-corner boxquote-top-corner break-start)
305
(let ((start-point (line-beginning-position)))
306
(setf (point) (point-max))
307
(insert-corner boxquote-bottom-corner break-end)
308
(string-rectangle start-point
309
(progn
310
(setf (point) (point-max))
311
(forward-line -2)
312
(line-beginning-position))
313
boxquote-side)))))))
314
315
;;;###autoload
316
(defun boxquote-buffer ()
317
"Apply `boxquote-region' to a whole buffer."
318
(interactive)
319
(boxquote-region (point-min) (point-max)))
320
321
;;;###autoload
322
(defun boxquote-insert-file (filename)
323
"Insert the contents of a file, boxed with `boxquote-region'.
324
325
If `boxquote-title-files' is non-nil the boxquote will be given a title that
326
is the result of applying `boxquote-file-title-function' to FILENAME."
327
(interactive "fInsert file: ")
328
(insert (with-temp-buffer
329
(insert-file-contents filename nil)
330
(run-hooks 'boxquote-insert-file-hook)
331
(boxquote-buffer)
332
(when boxquote-title-files
333
(boxquote-title (funcall boxquote-file-title-function filename)))
334
(buffer-string))))
335
336
;;;###autoload
337
(defun boxquote-insert-buffer (buffer)
338
"Insert the contents of a buffer, boxes with `boxquote-region'.
339
340
If `boxquote-title-buffers' is non-nil the boxquote will be given a title that
341
is the result of applying `boxquote-buffer-title-function' to BUFFER."
342
(interactive "bInsert Buffer: ")
343
(boxquote-text
344
(with-current-buffer buffer
345
(buffer-substring-no-properties (point-min) (point-max))))
346
(when boxquote-title-buffers
347
(boxquote-title (funcall boxquote-buffer-title-function buffer))))
348
349
;;;###autoload
350
(defun boxquote-kill-ring-save ()
351
"Like `kill-ring-save' but remembers a title if possible.
352
353
The title is acquired by calling `boxquote-kill-ring-save-title'. The title
354
will be used by `boxquote-yank'."
355
(interactive)
356
(call-interactively #'kill-ring-save)
357
(setf (car kill-ring-yank-pointer)
358
(format "%S" (list
359
'boxquote-yank-marker
360
(funcall boxquote-kill-ring-save-title)
361
(car kill-ring-yank-pointer)))))
362
363
;;;###autoload
364
(defun boxquote-yank ()
365
"Do a `yank' and box it in with `boxquote-region'.
366
367
If the yanked entry was placed on the kill ring with
368
`boxquote-kill-ring-save' the resulting boxquote will be titled with
369
whatever `boxquote-kill-ring-save-title' returned at the time."
370
(interactive)
371
(save-excursion
372
(insert (with-temp-buffer
373
(yank)
374
(setf (point) (point-min))
375
(let ((title
376
(let ((yanked (condition-case nil
377
(read (current-buffer))
378
(error nil))))
379
(when (listp yanked)
380
(when (eq (car yanked) 'boxquote-yank-marker)
381
(setf (buffer-string) (nth 2 yanked))
382
(nth 1 yanked))))))
383
(run-hooks 'boxquote-yank-hook)
384
(boxquote-buffer)
385
(when title
386
(boxquote-title title))
387
(buffer-string))))))
388
389
;;;###autoload
390
(defun boxquote-defun ()
391
"Apply `boxquote-region' the current defun."
392
(interactive)
393
(mark-defun)
394
(boxquote-region (region-beginning) (region-end)))
395
396
;;;###autoload
397
(defun boxquote-paragraph ()
398
"Apply `boxquote-region' to the current paragraph."
399
(interactive)
400
(mark-paragraph)
401
(boxquote-region (region-beginning) (region-end)))
402
403
;;;###autoload
404
(defun boxquote-boxquote ()
405
"Apply `boxquote-region' to the current boxquote."
406
(interactive)
407
(let ((box (boxquote-points-with-check)))
408
(boxquote-region (car box) (1+ (cdr box)))))
409
410
(defun boxquote-help-buffer-name (item)
411
"Return the name of the help buffer associated with ITEM."
412
(if (boxquote-xemacs-p)
413
(loop for buffer in (symbol-value 'help-buffer-list)
414
when (string-match (concat "^*Help:.*`" item "'") buffer)
415
return buffer)
416
"*Help*"))
417
418
(defun boxquote-quote-help-buffer (help-call title-format item)
419
"Perform a help command and boxquote the output.
420
421
HELP-CALL is a function that calls the help command.
422
423
TITLE-FORMAT is the `format' string to use to product the boxquote title.
424
425
ITEM is a function for retrieving the item to get help on."
426
(let ((one-window-p (one-window-p)))
427
(boxquote-text
428
(save-window-excursion
429
(funcall help-call)
430
(with-current-buffer (boxquote-help-buffer-name (funcall item))
431
(buffer-substring-no-properties (point-min) (point-max)))))
432
(boxquote-title (format title-format (funcall item)))
433
(when one-window-p
434
(delete-other-windows))))
435
436
;;;###autoload
437
(defun boxquote-describe-function ()
438
"Call `describe-function' and boxquote the output into the current buffer."
439
(interactive)
440
(boxquote-quote-help-buffer
441
#'(lambda ()
442
(call-interactively #'describe-function))
443
boxquote-describe-function-title-format
444
#'(lambda ()
445
(car (if (boxquote-xemacs-p)
446
(symbol-value 'function-history)
447
minibuffer-history)))))
448
449
;;;###autoload
450
(defun boxquote-describe-variable ()
451
"Call `describe-variable' and boxquote the output into the current buffer."
452
(interactive)
453
(boxquote-quote-help-buffer
454
#'(lambda ()
455
(call-interactively #'describe-variable))
456
boxquote-describe-variable-title-format
457
#'(lambda ()
458
(car (if (boxquote-xemacs-p)
459
(symbol-value 'variable-history)
460
minibuffer-history)))))
461
462
;;;###autoload
463
(defun boxquote-describe-key (key)
464
"Call `describe-key' and boxquote the output into the current buffer.
465
466
If the call to this command is prefixed with \\[universal-argument] you will also be
467
prompted for a buffer. The key defintion used will be taken from that buffer."
468
(interactive "kDescribe key: ")
469
(let ((from-buffer (if current-prefix-arg
470
(read-buffer "Buffer: " (current-buffer) t)
471
(current-buffer))))
472
(let ((binding
473
(with-current-buffer from-buffer
474
(key-binding key))))
475
(if (or (null binding) (integerp binding))
476
(message "%s is undefined" (with-current-buffer from-buffer
477
(key-description key)))
478
(boxquote-quote-help-buffer
479
#'(lambda ()
480
(with-current-buffer from-buffer
481
(describe-key key)))
482
boxquote-describe-key-title-format
483
#'(lambda ()
484
(with-current-buffer from-buffer
485
(key-description key))))))))
486
487
;;;###autoload
488
(defun boxquote-shell-command (command)
489
"Call `shell-command' with COMMAND and boxquote the output."
490
(interactive (list (read-from-minibuffer "Shell command: " nil nil nil 'shell-command-history)))
491
(boxquote-text (with-temp-buffer
492
(shell-command command t)
493
(buffer-string)))
494
(boxquote-title command))
495
496
;;;###autoload
497
(defun boxquote-where-is (definition)
498
"Call `where-is' with DEFINITION and boxquote the result."
499
(interactive "CCommand: ")
500
(boxquote-text (with-temp-buffer
501
(where-is definition t)
502
(format boxquote-where-is-body-format definition (buffer-string))))
503
(boxquote-title (format boxquote-where-is-title-format definition)))
504
505
;;;###autoload
506
(defun boxquote-text (text)
507
"Insert TEXT, boxquoted."
508
(interactive "sText: ")
509
(save-excursion
510
(unless (bolp)
511
(insert "\n"))
512
(insert
513
(with-temp-buffer
514
(insert text)
515
(boxquote-buffer)
516
(buffer-string)))))
517
518
;;;###autoload
519
(defun boxquote-narrow-to-boxquote ()
520
"Narrow the buffer to the current boxquote."
521
(interactive)
522
(let ((box (boxquote-points-with-check)))
523
(narrow-to-region (car box) (cdr box))))
524
525
;;;###autoload
526
(defun boxquote-narrow-to-boxquote-content ()
527
"Narrow the buffer to the content of the current boxquote."
528
(interactive)
529
(let ((box (boxquote-points-with-check)))
530
(narrow-to-region (save-excursion
531
(setf (point) (car box))
532
(forward-line 1)
533
(point))
534
(save-excursion
535
(setf (point) (cdr box))
536
(line-beginning-position)))))
537
538
;;;###autoload
539
(defun boxquote-kill ()
540
"Kill the boxquote and its contents."
541
(interactive)
542
(let ((box (boxquote-points-with-check)))
543
(kill-region (car box) (1+ (cdr box)))))
544
545
;;;###autoload
546
(defun boxquote-fill-paragraph (arg)
547
"Perform a `fill-paragraph' inside a boxquote."
548
(interactive "P")
549
(if (boxquote-quoted-p)
550
(save-restriction
551
(boxquote-narrow-to-boxquote-content)
552
(let ((fill-prefix boxquote-side))
553
(fill-paragraph arg)))
554
(fill-paragraph arg)))
555
556
;;;###autoload
557
(defun boxquote-unbox-region (start end)
558
"Remove a box created with `boxquote-region'."
559
(interactive "r")
560
(save-excursion
561
(save-restriction
562
(narrow-to-region start end)
563
(setf (point) (point-min))
564
(if (looking-at (concat "^" (regexp-quote boxquote-top-corner)
565
(regexp-quote boxquote-top-and-tail)))
566
(let ((ends (concat "^[" (regexp-quote boxquote-top-corner)
567
(regexp-quote boxquote-bottom-corner)
568
"]" boxquote-top-and-tail))
569
(lines (concat "^" (regexp-quote boxquote-side))))
570
(loop while (< (point) (point-max))
571
if (looking-at ends) do (kill-line t)
572
if (looking-at lines) do (delete-char 2)
573
do (forward-line)))
574
(error "I can't see a box here")))))
575
576
;;;###autoload
577
(defun boxquote-unbox ()
578
"Remove the boxquote that contains `point'."
579
(interactive)
580
(let ((box (boxquote-points-with-check)))
581
(boxquote-unbox-region (car box) (1+ (cdr box)))))
582
583
(provide 'boxquote)
584
585
;;; boxquote.el ends here.
586
587