Added or corrected Commentary sections
[bpt/emacs.git] / lisp / textmodes / picture.el
CommitLineData
6594deb0
ER
1;;; picture.el --- "Picture mode" -- editing using quarter-plane screen model.
2
3a801d0c
ER
3;; Copyright (C) 1985 Free Software Foundation, Inc.
4
e5167999
ER
5;; Author: K. Shane Hartman
6;; Maintainer: FSF
695d13c7
BP
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software; you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
e5167999 12;; the Free Software Foundation; either version 2, or (at your option)
695d13c7
BP
13;; any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs; see the file COPYING. If not, write to
22;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23
e5167999 24;;; Code:
695d13c7 25
695d13c7
BP
26(defun move-to-column-force (column)
27 "Move to column COLUMN in current line.
28Differs from `move-to-column' in that it creates or modifies whitespace
29if necessary to attain exactly the specified column."
30 (move-to-column column)
31 (let ((col (current-column)))
32 (if (< col column)
33 (indent-to column)
34 (if (and (/= col column)
35 (= (preceding-char) ?\t))
36 (let (indent-tabs-mode)
37 (delete-char -1)
38 (indent-to col)
39 (move-to-column column))))))
40
41\f
42;; Picture Movement Commands
43
44(defun picture-end-of-line (&optional arg)
45 "Position point after last non-blank character on current line.
46With ARG not nil, move forward ARG - 1 lines first.
47If scan reaches end of buffer, stop there without error."
48 (interactive "P")
49 (if arg (forward-line (1- (prefix-numeric-value arg))))
50 (beginning-of-line)
51 (skip-chars-backward " \t" (prog1 (point) (end-of-line))))
52
53(defun picture-forward-column (arg)
54 "Move cursor right, making whitespace if necessary.
55With argument, move that many columns."
56 (interactive "p")
57 (move-to-column-force (+ (current-column) arg)))
58
59(defun picture-backward-column (arg)
60 "Move cursor left, making whitespace if necessary.
61With argument, move that many columns."
62 (interactive "p")
63 (move-to-column-force (- (current-column) arg)))
64
65(defun picture-move-down (arg)
66 "Move vertically down, making whitespace if necessary.
67With argument, move that many lines."
68 (interactive "p")
69 (let ((col (current-column)))
70 (picture-newline arg)
71 (move-to-column-force col)))
72
73(defconst picture-vertical-step 0
74 "Amount to move vertically after text character in Picture mode.")
75
76(defconst picture-horizontal-step 1
77 "Amount to move horizontally after text character in Picture mode.")
78
79(defun picture-move-up (arg)
80 "Move vertically up, making whitespace if necessary.
81With argument, move that many lines."
82 (interactive "p")
83 (picture-move-down (- arg)))
84
85(defun picture-movement-right ()
86 "Move right after self-inserting character in Picture mode."
87 (interactive)
88 (picture-set-motion 0 1))
89
90(defun picture-movement-left ()
91 "Move left after self-inserting character in Picture mode."
92 (interactive)
93 (picture-set-motion 0 -1))
94
95(defun picture-movement-up ()
96 "Move up after self-inserting character in Picture mode."
97 (interactive)
98 (picture-set-motion -1 0))
99
100(defun picture-movement-down ()
101 "Move down after self-inserting character in Picture mode."
102 (interactive)
103 (picture-set-motion 1 0))
104
105(defun picture-movement-nw ()
106 "Move up and left after self-inserting character in Picture mode."
107 (interactive)
108 (picture-set-motion -1 -1))
109
110(defun picture-movement-ne ()
111 "Move up and right after self-inserting character in Picture mode."
112 (interactive)
113 (picture-set-motion -1 1))
114
115(defun picture-movement-sw ()
116 "Move down and left after self-inserting character in Picture mode."
117 (interactive)
118 (picture-set-motion 1 -1))
119
120(defun picture-movement-se ()
121 "Move down and right after self-inserting character in Picture mode."
122 (interactive)
123 (picture-set-motion 1 1))
124
125(defun picture-set-motion (vert horiz)
126 "Set VERTICAL and HORIZONTAL increments for movement in Picture mode.
127The mode line is updated to reflect the current direction."
128 (setq picture-vertical-step vert
129 picture-horizontal-step horiz)
130 (setq mode-name
131 (format "Picture:%s"
132 (car (nthcdr (+ 1 (% horiz 2) (* 3 (1+ (% vert 2))))
133 '(nw up ne left none right sw down se)))))
134 ;; Kludge - force the mode line to be updated. Is there a better
135 ;; way to this?
136 (set-buffer-modified-p (buffer-modified-p))
137 (message ""))
138
139(defun picture-move ()
140 "Move in direction of `picture-vertical-step' and `picture-horizontal-step'."
141 (picture-move-down picture-vertical-step)
142 (picture-forward-column picture-horizontal-step))
143
144(defun picture-motion (arg)
145 "Move point in direction of current picture motion in Picture mode.
146With ARG do it that many times. Useful for delineating rectangles in
147conjunction with diagonal picture motion.
148Do \\[command-apropos] picture-movement to see commands which control motion."
149 (interactive "p")
150 (picture-move-down (* arg picture-vertical-step))
151 (picture-forward-column (* arg picture-horizontal-step)))
152
153(defun picture-motion-reverse (arg)
154 "Move point in direction opposite of current picture motion in Picture mode.
155With ARG do it that many times. Useful for delineating rectangles in
156conjunction with diagonal picture motion.
157Do \\[command-apropos] `picture-movement' to see commands which control motion."
158 (interactive "p")
159 (picture-motion (- arg)))
160
161\f
162;; Picture insertion and deletion.
163
164(defun picture-self-insert (arg)
165 "Insert this character in place of character previously at the cursor.
166The cursor then moves in the direction you previously specified
167with the commands `picture-movement-right', `picture-movement-up', etc.
168Do \\[command-apropos] `picture-movement' to see those commands."
169 (interactive "p")
170 (while (> arg 0)
171 (setq arg (1- arg))
172 (move-to-column-force (1+ (current-column)))
173 (delete-char -1)
174 (insert last-input-char)
175 (forward-char -1)
176 (picture-move)))
177
178(defun picture-clear-column (arg)
179 "Clear out ARG columns after point without moving."
180 (interactive "p")
181 (let* ((opoint (point))
182 (original-col (current-column))
183 (target-col (+ original-col arg)))
184 (move-to-column-force target-col)
185 (delete-region opoint (point))
186 (save-excursion
187 (indent-to (max target-col original-col)))))
188
189(defun picture-backward-clear-column (arg)
190 "Clear out ARG columns before point, moving back over them."
191 (interactive "p")
192 (picture-clear-column (- arg)))
193
194(defun picture-clear-line (arg)
195 "Clear out rest of line; if at end of line, advance to next line.
196Cleared-out line text goes into the kill ring, as do newlines that are
197advanced over. With argument, clear out (and save in kill ring) that
198many lines."
199 (interactive "P")
200 (if arg
201 (progn
202 (setq arg (prefix-numeric-value arg))
203 (kill-line arg)
204 (newline (if (> arg 0) arg (- arg))))
205 (if (looking-at "[ \t]*$")
206 (kill-ring-save (point) (progn (forward-line 1) (point)))
207 (kill-region (point) (progn (end-of-line) (point))))))
208
209(defun picture-newline (arg)
210 "Move to the beginning of the following line.
211With argument, moves that many lines (up, if negative argument);
212always moves to the beginning of a line."
213 (interactive "p")
214 (if (< arg 0)
215 (forward-line arg)
216 (while (> arg 0)
217 (end-of-line)
218 (if (eobp) (newline) (forward-char 1))
219 (setq arg (1- arg)))))
220
221(defun picture-open-line (arg)
222 "Insert an empty line after the current line.
223With positive argument insert that many lines."
224 (interactive "p")
225 (save-excursion
226 (end-of-line)
227 (open-line arg)))
228
229(defun picture-duplicate-line ()
230 "Insert a duplicate of the current line, below it."
231 (interactive)
232 (save-excursion
233 (let ((contents
234 (buffer-substring
235 (progn (beginning-of-line) (point))
236 (progn (picture-newline 1) (point)))))
237 (forward-line -1)
238 (insert contents))))
239
240\f
241;; Picture Tabs
242
243(defvar picture-tab-chars "!-~"
244 "*A character set which controls behavior of commands
245\\[picture-set-tab-stops] and \\[picture-tab-search]. It is NOT a
246regular expression, any regexp special characters will be quoted.
247It defines a set of \"interesting characters\" to look for when setting
248\(or searching for) tab stops, initially \"!-~\" (all printing characters).
249For example, suppose that you are editing a table which is formatted thus:
250| foo | bar + baz | 23 *
251| bubbles | and + etc | 97 *
252and that `picture-tab-chars' is \"|+*\". Then invoking
253\\[picture-set-tab-stops] on either of the previous lines would result
254in the following tab stops
255 : : : :
256Another example - \"A-Za-z0-9\" would produce the tab stops
257 : : : :
258
259Note that if you want the character `-' to be in the set, it must be
260included in a range or else appear in a context where it cannot be
261taken for indicating a range (e.g. \"-A-Z\" declares the set to be the
262letters `A' through `Z' and the character `-'). If you want the
263character `\\' in the set it must be preceded by itself: \"\\\\\".
264
265The command \\[picture-tab-search] is defined to move beneath (or to) a
266character belonging to this set independent of the tab stops list.")
267
268(defun picture-set-tab-stops (&optional arg)
269 "Set value of `tab-stop-list' according to context of this line.
270This controls the behavior of \\[picture-tab]. A tab stop is set at
271every column occupied by an \"interesting character\" that is preceded
272by whitespace. Interesting characters are defined by the variable
273`picture-tab-chars', see its documentation for an example of usage.
274With ARG, just (re)set `tab-stop-list' to its default value. The tab
275stops computed are displayed in the minibuffer with `:' at each stop."
276 (interactive "P")
277 (save-excursion
278 (let (tabs)
279 (if arg
280 (setq tabs (default-value 'tab-stop-list))
281 (let ((regexp (concat "[ \t]+[" (regexp-quote picture-tab-chars) "]")))
282 (beginning-of-line)
283 (let ((bol (point)))
284 (end-of-line)
285 (while (re-search-backward regexp bol t)
286 (skip-chars-forward " \t")
287 (setq tabs (cons (current-column) tabs)))
288 (if (null tabs)
289 (error "No characters in set %s on this line."
290 (regexp-quote picture-tab-chars))))))
291 (setq tab-stop-list tabs)
292 (let ((blurb (make-string (1+ (nth (1- (length tabs)) tabs)) ?\ )))
293 (while tabs
294 (aset blurb (car tabs) ?:)
295 (setq tabs (cdr tabs)))
296 (message blurb)))))
297
298(defun picture-tab-search (&optional arg)
299 "Move to column beneath next interesting char in previous line.
300With ARG move to column occupied by next interesting character in this
301line. The character must be preceded by whitespace.
302\"interesting characters\" are defined by variable `picture-tab-chars'.
303If no such character is found, move to beginning of line."
304 (interactive "P")
305 (let ((target (current-column)))
306 (save-excursion
307 (if (and (not arg)
308 (progn
309 (beginning-of-line)
310 (skip-chars-backward
311 (concat "^" (regexp-quote picture-tab-chars))
312 (point-min))
313 (not (bobp))))
314 (move-to-column target))
315 (if (re-search-forward
316 (concat "[ \t]+[" (regexp-quote picture-tab-chars) "]")
317 (save-excursion (end-of-line) (point))
318 'move)
319 (setq target (1- (current-column)))
320 (setq target nil)))
321 (if target
322 (move-to-column-force target)
323 (beginning-of-line))))
324
325(defun picture-tab (&optional arg)
326 "Tab transparently (just move point) to next tab stop.
327With prefix arg, overwrite the traversed text with spaces. The tab stop
328list can be changed by \\[picture-set-tab-stops] and \\[edit-tab-stops].
329See also documentation for variable `picture-tab-chars'."
330 (interactive "P")
331 (let* ((opoint (point)))
332 (move-to-tab-stop)
333 (if arg
334 (let (indent-tabs-mode
335 (column (current-column)))
336 (delete-region opoint (point))
337 (indent-to column)))))
338\f
339;; Picture Rectangles
340
341(defconst picture-killed-rectangle nil
342 "Rectangle killed or copied by \\[picture-clear-rectangle] in Picture mode.
343The contents can be retrieved by \\[picture-yank-rectangle]")
344
345(defun picture-clear-rectangle (start end &optional killp)
346 "Clear and save rectangle delineated by point and mark.
347The rectangle is saved for yanking by \\[picture-yank-rectangle] and replaced
348with whitespace. The previously saved rectangle, if any, is lost. With
349prefix argument, the rectangle is actually killed, shifting remaining text."
350 (interactive "r\nP")
351 (setq picture-killed-rectangle (picture-snarf-rectangle start end killp)))
352
353(defun picture-clear-rectangle-to-register (start end register &optional killp)
354 "Clear rectangle delineated by point and mark into REGISTER.
355The rectangle is saved in REGISTER and replaced with whitespace. With
356prefix argument, the rectangle is actually killed, shifting remaining text."
357 (interactive "r\ncRectangle to register: \nP")
358 (set-register register (picture-snarf-rectangle start end killp)))
359
360(defun picture-snarf-rectangle (start end &optional killp)
361 (let ((column (current-column))
362 (indent-tabs-mode nil))
363 (prog1 (save-excursion
364 (if killp
365 (delete-extract-rectangle start end)
366 (prog1 (extract-rectangle start end)
367 (clear-rectangle start end))))
368 (move-to-column-force column))))
369
370(defun picture-yank-rectangle (&optional insertp)
371 "Overlay rectangle saved by \\[picture-clear-rectangle]
372The rectangle is positioned with upper left corner at point, overwriting
373existing text. With prefix argument, the rectangle is inserted instead,
374shifting existing text. Leaves mark at one corner of rectangle and
375point at the other (diagonally opposed) corner."
376 (interactive "P")
377 (if (not (consp picture-killed-rectangle))
378 (error "No rectangle saved.")
379 (picture-insert-rectangle picture-killed-rectangle insertp)))
380
381(defun picture-yank-rectangle-from-register (register &optional insertp)
382 "Overlay rectangle saved in REGISTER.
383The rectangle is positioned with upper left corner at point, overwriting
384existing text. With prefix argument, the rectangle is
385inserted instead, shifting existing text. Leaves mark at one corner
386of rectangle and point at the other (diagonally opposed) corner."
387 (interactive "cRectangle from register: \nP")
388 (let ((rectangle (get-register register)))
389 (if (not (consp rectangle))
390 (error "Register %c does not contain a rectangle." register)
391 (picture-insert-rectangle rectangle insertp))))
392
393(defun picture-insert-rectangle (rectangle &optional insertp)
394 "Overlay RECTANGLE with upper left corner at point.
395Optional argument INSERTP, if non-nil causes RECTANGLE to be inserted.
396Leaves the region surrounding the rectangle."
397 (let ((indent-tabs-mode nil))
398 (if (not insertp)
399 (save-excursion
400 (delete-rectangle (point)
401 (progn
402 (picture-forward-column (length (car rectangle)))
403 (picture-move-down (1- (length rectangle)))
404 (point)))))
405 (push-mark)
406 (insert-rectangle rectangle)))
407
408\f
409;; Picture Keymap, entry and exit points.
410
411(defconst picture-mode-map nil)
412
413(if (not picture-mode-map)
414 (let ((i ?\ ))
415 (setq picture-mode-map (make-keymap))
416 (while (< i ?\177)
c4fc49b6 417 (define-key picture-mode-map (make-string 1 i) 'picture-self-insert)
695d13c7
BP
418 (setq i (1+ i)))
419 (define-key picture-mode-map "\C-f" 'picture-forward-column)
420 (define-key picture-mode-map "\C-b" 'picture-backward-column)
421 (define-key picture-mode-map "\C-d" 'picture-clear-column)
422 (define-key picture-mode-map "\C-c\C-d" 'delete-char)
423 (define-key picture-mode-map "\177" 'picture-backward-clear-column)
424 (define-key picture-mode-map "\C-k" 'picture-clear-line)
425 (define-key picture-mode-map "\C-o" 'picture-open-line)
426 (define-key picture-mode-map "\C-m" 'picture-newline)
427 (define-key picture-mode-map "\C-j" 'picture-duplicate-line)
428 (define-key picture-mode-map "\C-n" 'picture-move-down)
429 (define-key picture-mode-map "\C-p" 'picture-move-up)
430 (define-key picture-mode-map "\C-e" 'picture-end-of-line)
431 (define-key picture-mode-map "\e\t" 'picture-toggle-tab-state)
432 (define-key picture-mode-map "\t" 'picture-tab)
433 (define-key picture-mode-map "\e\t" 'picture-tab-search)
434 (define-key picture-mode-map "\C-c\t" 'picture-set-tab-stops)
435 (define-key picture-mode-map "\C-c\C-k" 'picture-clear-rectangle)
436 (define-key picture-mode-map "\C-c\C-w" 'picture-clear-rectangle-to-register)
437 (define-key picture-mode-map "\C-c\C-y" 'picture-yank-rectangle)
438 (define-key picture-mode-map "\C-c\C-x" 'picture-yank-rectangle-from-register)
439 (define-key picture-mode-map "\C-c\C-c" 'picture-mode-exit)
440 (define-key picture-mode-map "\C-c\C-f" 'picture-motion)
441 (define-key picture-mode-map "\C-c\C-b" 'picture-motion-reverse)
442 (define-key picture-mode-map "\C-c<" 'picture-movement-left)
443 (define-key picture-mode-map "\C-c>" 'picture-movement-right)
444 (define-key picture-mode-map "\C-c^" 'picture-movement-up)
445 (define-key picture-mode-map "\C-c." 'picture-movement-down)
446 (define-key picture-mode-map "\C-c`" 'picture-movement-nw)
447 (define-key picture-mode-map "\C-c'" 'picture-movement-ne)
448 (define-key picture-mode-map "\C-c/" 'picture-movement-sw)
449 (define-key picture-mode-map "\C-c\\" 'picture-movement-se)))
450
451(defvar edit-picture-hook nil
452 "If non-nil, it's value is called on entry to Picture mode.
453Picture mode is invoked by the command \\[edit-picture].")
454
c4fc49b6
RS
455(defvar picture-mode-old-local-map)
456(defvar picture-mode-old-mode-name)
457(defvar picture-mode-old-major-mode)
458
7229064d 459;;;###autoload
695d13c7
BP
460(defun edit-picture ()
461 "Switch to Picture mode, in which a quarter-plane screen model is used.
462Printing characters replace instead of inserting themselves with motion
463afterwards settable by these commands:
464 C-c < Move left after insertion.
465 C-c > Move right after insertion.
466 C-c ^ Move up after insertion.
467 C-c . Move down after insertion.
468 C-c ` Move northwest (nw) after insertion.
469 C-c ' Move northeast (ne) after insertion.
470 C-c / Move southwest (sw) after insertion.
471 C-c \\ Move southeast (se) after insertion.
472The current direction is displayed in the mode line. The initial
473direction is right. Whitespace is inserted and tabs are changed to
474spaces when required by movement. You can move around in the buffer
475with these commands:
476 C-p Move vertically to SAME column in previous line.
477 C-n Move vertically to SAME column in next line.
478 C-e Move to column following last non-whitespace character.
479 C-f Move right inserting spaces if required.
480 C-b Move left changing tabs to spaces if required.
481 C-c C-f Move in direction of current picture motion.
482 C-c C-b Move in opposite direction of current picture motion.
483 Return Move to beginning of next line.
484You can edit tabular text with these commands:
485 M-Tab Move to column beneath (or at) next interesting character.
486 `Indents' relative to a previous line.
487 Tab Move to next stop in tab stop list.
488 C-c Tab Set tab stops according to context of this line.
489 With ARG resets tab stops to default (global) value.
490 See also documentation of variable picture-tab-chars
491 which defines \"interesting character\". You can manually
492 change the tab stop list with command \\[edit-tab-stops].
493You can manipulate text with these commands:
494 C-d Clear (replace) ARG columns after point without moving.
495 C-c C-d Delete char at point - the command normally assigned to C-d.
496 Delete Clear (replace) ARG columns before point, moving back over them.
497 C-k Clear ARG lines, advancing over them. The cleared
498 text is saved in the kill ring.
499 C-o Open blank line(s) beneath current line.
500You can manipulate rectangles with these commands:
501 C-c C-k Clear (or kill) a rectangle and save it.
502 C-c C-w Like C-c C-k except rectangle is saved in named register.
503 C-c C-y Overlay (or insert) currently saved rectangle at point.
504 C-c C-x Like C-c C-y except rectangle is taken from named register.
505 \\[copy-rectangle-to-register] Copies a rectangle to a register.
506 \\[advertised-undo] Can undo effects of rectangle overlay commands
507 commands if invoked soon enough.
508You can return to the previous mode with:
509 C-c C-c Which also strips trailing whitespace from every line.
510 Stripping is suppressed by supplying an argument.
511
512Entry to this mode calls the value of edit-picture-hook if non-nil.
513
514Note that Picture mode commands will work outside of Picture mode, but
515they are not defaultly assigned to keys."
516 (interactive)
517 (if (eq major-mode 'edit-picture)
518 (error "You are already editing a Picture.")
519 (make-local-variable 'picture-mode-old-local-map)
520 (setq picture-mode-old-local-map (current-local-map))
521 (use-local-map picture-mode-map)
522 (make-local-variable 'picture-mode-old-mode-name)
523 (setq picture-mode-old-mode-name mode-name)
524 (make-local-variable 'picture-mode-old-major-mode)
525 (setq picture-mode-old-major-mode major-mode)
526 (setq major-mode 'edit-picture)
527 (make-local-variable 'picture-killed-rectangle)
528 (setq picture-killed-rectangle nil)
529 (make-local-variable 'tab-stop-list)
530 (setq tab-stop-list (default-value 'tab-stop-list))
531 (make-local-variable 'picture-tab-chars)
532 (setq picture-tab-chars (default-value 'picture-tab-chars))
533 (make-local-variable 'picture-vertical-step)
534 (make-local-variable 'picture-horizontal-step)
535 (picture-set-motion 0 1)
e0dad66e
RS
536 ;; edit-picture-hook is what we used to run, picture-mode-hook is in doc.
537 (run-hooks 'edit-picture-hook 'picture-mode-hook)
695d13c7
BP
538 (message
539 (substitute-command-keys
540 "Type \\[picture-mode-exit] in this buffer to return it to %s mode.")
541 picture-mode-old-mode-name)))
542
6503cec3
JB
543;;;###autoload
544(fset 'picture-mode 'edit-picture)
695d13c7
BP
545
546(defun picture-mode-exit (&optional nostrip)
547 "Undo edit-picture and return to previous major mode.
548With no argument strips whitespace from end of every line in Picture buffer
549 otherwise just return to previous mode."
550 (interactive "P")
551 (if (not (eq major-mode 'edit-picture))
552 (error "You aren't editing a Picture.")
553 (if (not nostrip) (picture-clean))
554 (setq mode-name picture-mode-old-mode-name)
555 (use-local-map picture-mode-old-local-map)
556 (setq major-mode picture-mode-old-major-mode)
557 (kill-local-variable 'tab-stop-list)
558 ;; Kludge - force the mode line to be updated. Is there a better
559 ;; way to do this?
560 (set-buffer-modified-p (buffer-modified-p))))
561
562(defun picture-clean ()
563 "Eliminate whitespace at ends of lines."
564 (save-excursion
565 (goto-char (point-min))
566 (while (re-search-forward "[ \t][ \t]*$" nil t)
567 (delete-region (match-beginning 0) (point)))))
49116ac0
JB
568
569(provide 'picture)
570
6594deb0 571;;; picture.el ends here