--- /dev/null
+;; "Picture mode" -- editing using quarter-plane screen model.
+;; Copyright (C) 1985 Free Software Foundation, Inc.
+;; Principal author K. Shane Hartman
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 1, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to
+;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+
+(provide 'picture)
+
+(defun move-to-column-force (column)
+ "Move to column COLUMN in current line.
+Differs from `move-to-column' in that it creates or modifies whitespace
+if necessary to attain exactly the specified column."
+ (move-to-column column)
+ (let ((col (current-column)))
+ (if (< col column)
+ (indent-to column)
+ (if (and (/= col column)
+ (= (preceding-char) ?\t))
+ (let (indent-tabs-mode)
+ (delete-char -1)
+ (indent-to col)
+ (move-to-column column))))))
+
+\f
+;; Picture Movement Commands
+
+(defun picture-end-of-line (&optional arg)
+ "Position point after last non-blank character on current line.
+With ARG not nil, move forward ARG - 1 lines first.
+If scan reaches end of buffer, stop there without error."
+ (interactive "P")
+ (if arg (forward-line (1- (prefix-numeric-value arg))))
+ (beginning-of-line)
+ (skip-chars-backward " \t" (prog1 (point) (end-of-line))))
+
+(defun picture-forward-column (arg)
+ "Move cursor right, making whitespace if necessary.
+With argument, move that many columns."
+ (interactive "p")
+ (move-to-column-force (+ (current-column) arg)))
+
+(defun picture-backward-column (arg)
+ "Move cursor left, making whitespace if necessary.
+With argument, move that many columns."
+ (interactive "p")
+ (move-to-column-force (- (current-column) arg)))
+
+(defun picture-move-down (arg)
+ "Move vertically down, making whitespace if necessary.
+With argument, move that many lines."
+ (interactive "p")
+ (let ((col (current-column)))
+ (picture-newline arg)
+ (move-to-column-force col)))
+
+(defconst picture-vertical-step 0
+ "Amount to move vertically after text character in Picture mode.")
+
+(defconst picture-horizontal-step 1
+ "Amount to move horizontally after text character in Picture mode.")
+
+(defun picture-move-up (arg)
+ "Move vertically up, making whitespace if necessary.
+With argument, move that many lines."
+ (interactive "p")
+ (picture-move-down (- arg)))
+
+(defun picture-movement-right ()
+ "Move right after self-inserting character in Picture mode."
+ (interactive)
+ (picture-set-motion 0 1))
+
+(defun picture-movement-left ()
+ "Move left after self-inserting character in Picture mode."
+ (interactive)
+ (picture-set-motion 0 -1))
+
+(defun picture-movement-up ()
+ "Move up after self-inserting character in Picture mode."
+ (interactive)
+ (picture-set-motion -1 0))
+
+(defun picture-movement-down ()
+ "Move down after self-inserting character in Picture mode."
+ (interactive)
+ (picture-set-motion 1 0))
+
+(defun picture-movement-nw ()
+ "Move up and left after self-inserting character in Picture mode."
+ (interactive)
+ (picture-set-motion -1 -1))
+
+(defun picture-movement-ne ()
+ "Move up and right after self-inserting character in Picture mode."
+ (interactive)
+ (picture-set-motion -1 1))
+
+(defun picture-movement-sw ()
+ "Move down and left after self-inserting character in Picture mode."
+ (interactive)
+ (picture-set-motion 1 -1))
+
+(defun picture-movement-se ()
+ "Move down and right after self-inserting character in Picture mode."
+ (interactive)
+ (picture-set-motion 1 1))
+
+(defun picture-set-motion (vert horiz)
+ "Set VERTICAL and HORIZONTAL increments for movement in Picture mode.
+The mode line is updated to reflect the current direction."
+ (setq picture-vertical-step vert
+ picture-horizontal-step horiz)
+ (setq mode-name
+ (format "Picture:%s"
+ (car (nthcdr (+ 1 (% horiz 2) (* 3 (1+ (% vert 2))))
+ '(nw up ne left none right sw down se)))))
+ ;; Kludge - force the mode line to be updated. Is there a better
+ ;; way to this?
+ (set-buffer-modified-p (buffer-modified-p))
+ (message ""))
+
+(defun picture-move ()
+ "Move in direction of `picture-vertical-step' and `picture-horizontal-step'."
+ (picture-move-down picture-vertical-step)
+ (picture-forward-column picture-horizontal-step))
+
+(defun picture-motion (arg)
+ "Move point in direction of current picture motion in Picture mode.
+With ARG do it that many times. Useful for delineating rectangles in
+conjunction with diagonal picture motion.
+Do \\[command-apropos] picture-movement to see commands which control motion."
+ (interactive "p")
+ (picture-move-down (* arg picture-vertical-step))
+ (picture-forward-column (* arg picture-horizontal-step)))
+
+(defun picture-motion-reverse (arg)
+ "Move point in direction opposite of current picture motion in Picture mode.
+With ARG do it that many times. Useful for delineating rectangles in
+conjunction with diagonal picture motion.
+Do \\[command-apropos] `picture-movement' to see commands which control motion."
+ (interactive "p")
+ (picture-motion (- arg)))
+
+\f
+;; Picture insertion and deletion.
+
+(defun picture-self-insert (arg)
+ "Insert this character in place of character previously at the cursor.
+The cursor then moves in the direction you previously specified
+with the commands `picture-movement-right', `picture-movement-up', etc.
+Do \\[command-apropos] `picture-movement' to see those commands."
+ (interactive "p")
+ (while (> arg 0)
+ (setq arg (1- arg))
+ (move-to-column-force (1+ (current-column)))
+ (delete-char -1)
+ (insert last-input-char)
+ (forward-char -1)
+ (picture-move)))
+
+(defun picture-clear-column (arg)
+ "Clear out ARG columns after point without moving."
+ (interactive "p")
+ (let* ((opoint (point))
+ (original-col (current-column))
+ (target-col (+ original-col arg)))
+ (move-to-column-force target-col)
+ (delete-region opoint (point))
+ (save-excursion
+ (indent-to (max target-col original-col)))))
+
+(defun picture-backward-clear-column (arg)
+ "Clear out ARG columns before point, moving back over them."
+ (interactive "p")
+ (picture-clear-column (- arg)))
+
+(defun picture-clear-line (arg)
+ "Clear out rest of line; if at end of line, advance to next line.
+Cleared-out line text goes into the kill ring, as do newlines that are
+advanced over. With argument, clear out (and save in kill ring) that
+many lines."
+ (interactive "P")
+ (if arg
+ (progn
+ (setq arg (prefix-numeric-value arg))
+ (kill-line arg)
+ (newline (if (> arg 0) arg (- arg))))
+ (if (looking-at "[ \t]*$")
+ (kill-ring-save (point) (progn (forward-line 1) (point)))
+ (kill-region (point) (progn (end-of-line) (point))))))
+
+(defun picture-newline (arg)
+ "Move to the beginning of the following line.
+With argument, moves that many lines (up, if negative argument);
+always moves to the beginning of a line."
+ (interactive "p")
+ (if (< arg 0)
+ (forward-line arg)
+ (while (> arg 0)
+ (end-of-line)
+ (if (eobp) (newline) (forward-char 1))
+ (setq arg (1- arg)))))
+
+(defun picture-open-line (arg)
+ "Insert an empty line after the current line.
+With positive argument insert that many lines."
+ (interactive "p")
+ (save-excursion
+ (end-of-line)
+ (open-line arg)))
+
+(defun picture-duplicate-line ()
+ "Insert a duplicate of the current line, below it."
+ (interactive)
+ (save-excursion
+ (let ((contents
+ (buffer-substring
+ (progn (beginning-of-line) (point))
+ (progn (picture-newline 1) (point)))))
+ (forward-line -1)
+ (insert contents))))
+
+\f
+;; Picture Tabs
+
+(defvar picture-tab-chars "!-~"
+ "*A character set which controls behavior of commands
+\\[picture-set-tab-stops] and \\[picture-tab-search]. It is NOT a
+regular expression, any regexp special characters will be quoted.
+It defines a set of \"interesting characters\" to look for when setting
+\(or searching for) tab stops, initially \"!-~\" (all printing characters).
+For example, suppose that you are editing a table which is formatted thus:
+| foo | bar + baz | 23 *
+| bubbles | and + etc | 97 *
+and that `picture-tab-chars' is \"|+*\". Then invoking
+\\[picture-set-tab-stops] on either of the previous lines would result
+in the following tab stops
+ : : : :
+Another example - \"A-Za-z0-9\" would produce the tab stops
+ : : : :
+
+Note that if you want the character `-' to be in the set, it must be
+included in a range or else appear in a context where it cannot be
+taken for indicating a range (e.g. \"-A-Z\" declares the set to be the
+letters `A' through `Z' and the character `-'). If you want the
+character `\\' in the set it must be preceded by itself: \"\\\\\".
+
+The command \\[picture-tab-search] is defined to move beneath (or to) a
+character belonging to this set independent of the tab stops list.")
+
+(defun picture-set-tab-stops (&optional arg)
+ "Set value of `tab-stop-list' according to context of this line.
+This controls the behavior of \\[picture-tab]. A tab stop is set at
+every column occupied by an \"interesting character\" that is preceded
+by whitespace. Interesting characters are defined by the variable
+`picture-tab-chars', see its documentation for an example of usage.
+With ARG, just (re)set `tab-stop-list' to its default value. The tab
+stops computed are displayed in the minibuffer with `:' at each stop."
+ (interactive "P")
+ (save-excursion
+ (let (tabs)
+ (if arg
+ (setq tabs (default-value 'tab-stop-list))
+ (let ((regexp (concat "[ \t]+[" (regexp-quote picture-tab-chars) "]")))
+ (beginning-of-line)
+ (let ((bol (point)))
+ (end-of-line)
+ (while (re-search-backward regexp bol t)
+ (skip-chars-forward " \t")
+ (setq tabs (cons (current-column) tabs)))
+ (if (null tabs)
+ (error "No characters in set %s on this line."
+ (regexp-quote picture-tab-chars))))))
+ (setq tab-stop-list tabs)
+ (let ((blurb (make-string (1+ (nth (1- (length tabs)) tabs)) ?\ )))
+ (while tabs
+ (aset blurb (car tabs) ?:)
+ (setq tabs (cdr tabs)))
+ (message blurb)))))
+
+(defun picture-tab-search (&optional arg)
+ "Move to column beneath next interesting char in previous line.
+With ARG move to column occupied by next interesting character in this
+line. The character must be preceded by whitespace.
+\"interesting characters\" are defined by variable `picture-tab-chars'.
+If no such character is found, move to beginning of line."
+ (interactive "P")
+ (let ((target (current-column)))
+ (save-excursion
+ (if (and (not arg)
+ (progn
+ (beginning-of-line)
+ (skip-chars-backward
+ (concat "^" (regexp-quote picture-tab-chars))
+ (point-min))
+ (not (bobp))))
+ (move-to-column target))
+ (if (re-search-forward
+ (concat "[ \t]+[" (regexp-quote picture-tab-chars) "]")
+ (save-excursion (end-of-line) (point))
+ 'move)
+ (setq target (1- (current-column)))
+ (setq target nil)))
+ (if target
+ (move-to-column-force target)
+ (beginning-of-line))))
+
+(defun picture-tab (&optional arg)
+ "Tab transparently (just move point) to next tab stop.
+With prefix arg, overwrite the traversed text with spaces. The tab stop
+list can be changed by \\[picture-set-tab-stops] and \\[edit-tab-stops].
+See also documentation for variable `picture-tab-chars'."
+ (interactive "P")
+ (let* ((opoint (point)))
+ (move-to-tab-stop)
+ (if arg
+ (let (indent-tabs-mode
+ (column (current-column)))
+ (delete-region opoint (point))
+ (indent-to column)))))
+\f
+;; Picture Rectangles
+
+(defconst picture-killed-rectangle nil
+ "Rectangle killed or copied by \\[picture-clear-rectangle] in Picture mode.
+The contents can be retrieved by \\[picture-yank-rectangle]")
+
+(defun picture-clear-rectangle (start end &optional killp)
+ "Clear and save rectangle delineated by point and mark.
+The rectangle is saved for yanking by \\[picture-yank-rectangle] and replaced
+with whitespace. The previously saved rectangle, if any, is lost. With
+prefix argument, the rectangle is actually killed, shifting remaining text."
+ (interactive "r\nP")
+ (setq picture-killed-rectangle (picture-snarf-rectangle start end killp)))
+
+(defun picture-clear-rectangle-to-register (start end register &optional killp)
+ "Clear rectangle delineated by point and mark into REGISTER.
+The rectangle is saved in REGISTER and replaced with whitespace. With
+prefix argument, the rectangle is actually killed, shifting remaining text."
+ (interactive "r\ncRectangle to register: \nP")
+ (set-register register (picture-snarf-rectangle start end killp)))
+
+(defun picture-snarf-rectangle (start end &optional killp)
+ (let ((column (current-column))
+ (indent-tabs-mode nil))
+ (prog1 (save-excursion
+ (if killp
+ (delete-extract-rectangle start end)
+ (prog1 (extract-rectangle start end)
+ (clear-rectangle start end))))
+ (move-to-column-force column))))
+
+(defun picture-yank-rectangle (&optional insertp)
+ "Overlay rectangle saved by \\[picture-clear-rectangle]
+The rectangle is positioned with upper left corner at point, overwriting
+existing text. With prefix argument, the rectangle is inserted instead,
+shifting existing text. Leaves mark at one corner of rectangle and
+point at the other (diagonally opposed) corner."
+ (interactive "P")
+ (if (not (consp picture-killed-rectangle))
+ (error "No rectangle saved.")
+ (picture-insert-rectangle picture-killed-rectangle insertp)))
+
+(defun picture-yank-rectangle-from-register (register &optional insertp)
+ "Overlay rectangle saved in REGISTER.
+The rectangle is positioned with upper left corner at point, overwriting
+existing text. With prefix argument, the rectangle is
+inserted instead, shifting existing text. Leaves mark at one corner
+of rectangle and point at the other (diagonally opposed) corner."
+ (interactive "cRectangle from register: \nP")
+ (let ((rectangle (get-register register)))
+ (if (not (consp rectangle))
+ (error "Register %c does not contain a rectangle." register)
+ (picture-insert-rectangle rectangle insertp))))
+
+(defun picture-insert-rectangle (rectangle &optional insertp)
+ "Overlay RECTANGLE with upper left corner at point.
+Optional argument INSERTP, if non-nil causes RECTANGLE to be inserted.
+Leaves the region surrounding the rectangle."
+ (let ((indent-tabs-mode nil))
+ (if (not insertp)
+ (save-excursion
+ (delete-rectangle (point)
+ (progn
+ (picture-forward-column (length (car rectangle)))
+ (picture-move-down (1- (length rectangle)))
+ (point)))))
+ (push-mark)
+ (insert-rectangle rectangle)))
+
+\f
+;; Picture Keymap, entry and exit points.
+
+(defconst picture-mode-map nil)
+
+(if (not picture-mode-map)
+ (let ((i ?\ ))
+ (setq picture-mode-map (make-keymap))
+ (while (< i ?\177)
+ (aset picture-mode-map i 'picture-self-insert)
+ (setq i (1+ i)))
+ (define-key picture-mode-map "\C-f" 'picture-forward-column)
+ (define-key picture-mode-map "\C-b" 'picture-backward-column)
+ (define-key picture-mode-map "\C-d" 'picture-clear-column)
+ (define-key picture-mode-map "\C-c\C-d" 'delete-char)
+ (define-key picture-mode-map "\177" 'picture-backward-clear-column)
+ (define-key picture-mode-map "\C-k" 'picture-clear-line)
+ (define-key picture-mode-map "\C-o" 'picture-open-line)
+ (define-key picture-mode-map "\C-m" 'picture-newline)
+ (define-key picture-mode-map "\C-j" 'picture-duplicate-line)
+ (define-key picture-mode-map "\C-n" 'picture-move-down)
+ (define-key picture-mode-map "\C-p" 'picture-move-up)
+ (define-key picture-mode-map "\C-e" 'picture-end-of-line)
+ (define-key picture-mode-map "\e\t" 'picture-toggle-tab-state)
+ (define-key picture-mode-map "\t" 'picture-tab)
+ (define-key picture-mode-map "\e\t" 'picture-tab-search)
+ (define-key picture-mode-map "\C-c\t" 'picture-set-tab-stops)
+ (define-key picture-mode-map "\C-c\C-k" 'picture-clear-rectangle)
+ (define-key picture-mode-map "\C-c\C-w" 'picture-clear-rectangle-to-register)
+ (define-key picture-mode-map "\C-c\C-y" 'picture-yank-rectangle)
+ (define-key picture-mode-map "\C-c\C-x" 'picture-yank-rectangle-from-register)
+ (define-key picture-mode-map "\C-c\C-c" 'picture-mode-exit)
+ (define-key picture-mode-map "\C-c\C-f" 'picture-motion)
+ (define-key picture-mode-map "\C-c\C-b" 'picture-motion-reverse)
+ (define-key picture-mode-map "\C-c<" 'picture-movement-left)
+ (define-key picture-mode-map "\C-c>" 'picture-movement-right)
+ (define-key picture-mode-map "\C-c^" 'picture-movement-up)
+ (define-key picture-mode-map "\C-c." 'picture-movement-down)
+ (define-key picture-mode-map "\C-c`" 'picture-movement-nw)
+ (define-key picture-mode-map "\C-c'" 'picture-movement-ne)
+ (define-key picture-mode-map "\C-c/" 'picture-movement-sw)
+ (define-key picture-mode-map "\C-c\\" 'picture-movement-se)))
+
+(defvar edit-picture-hook nil
+ "If non-nil, it's value is called on entry to Picture mode.
+Picture mode is invoked by the command \\[edit-picture].")
+
+(defun edit-picture ()
+ "Switch to Picture mode, in which a quarter-plane screen model is used.
+Printing characters replace instead of inserting themselves with motion
+afterwards settable by these commands:
+ C-c < Move left after insertion.
+ C-c > Move right after insertion.
+ C-c ^ Move up after insertion.
+ C-c . Move down after insertion.
+ C-c ` Move northwest (nw) after insertion.
+ C-c ' Move northeast (ne) after insertion.
+ C-c / Move southwest (sw) after insertion.
+ C-c \\ Move southeast (se) after insertion.
+The current direction is displayed in the mode line. The initial
+direction is right. Whitespace is inserted and tabs are changed to
+spaces when required by movement. You can move around in the buffer
+with these commands:
+ C-p Move vertically to SAME column in previous line.
+ C-n Move vertically to SAME column in next line.
+ C-e Move to column following last non-whitespace character.
+ C-f Move right inserting spaces if required.
+ C-b Move left changing tabs to spaces if required.
+ C-c C-f Move in direction of current picture motion.
+ C-c C-b Move in opposite direction of current picture motion.
+ Return Move to beginning of next line.
+You can edit tabular text with these commands:
+ M-Tab Move to column beneath (or at) next interesting character.
+ `Indents' relative to a previous line.
+ Tab Move to next stop in tab stop list.
+ C-c Tab Set tab stops according to context of this line.
+ With ARG resets tab stops to default (global) value.
+ See also documentation of variable picture-tab-chars
+ which defines \"interesting character\". You can manually
+ change the tab stop list with command \\[edit-tab-stops].
+You can manipulate text with these commands:
+ C-d Clear (replace) ARG columns after point without moving.
+ C-c C-d Delete char at point - the command normally assigned to C-d.
+ Delete Clear (replace) ARG columns before point, moving back over them.
+ C-k Clear ARG lines, advancing over them. The cleared
+ text is saved in the kill ring.
+ C-o Open blank line(s) beneath current line.
+You can manipulate rectangles with these commands:
+ C-c C-k Clear (or kill) a rectangle and save it.
+ C-c C-w Like C-c C-k except rectangle is saved in named register.
+ C-c C-y Overlay (or insert) currently saved rectangle at point.
+ C-c C-x Like C-c C-y except rectangle is taken from named register.
+ \\[copy-rectangle-to-register] Copies a rectangle to a register.
+ \\[advertised-undo] Can undo effects of rectangle overlay commands
+ commands if invoked soon enough.
+You can return to the previous mode with:
+ C-c C-c Which also strips trailing whitespace from every line.
+ Stripping is suppressed by supplying an argument.
+
+Entry to this mode calls the value of edit-picture-hook if non-nil.
+
+Note that Picture mode commands will work outside of Picture mode, but
+they are not defaultly assigned to keys."
+ (interactive)
+ (if (eq major-mode 'edit-picture)
+ (error "You are already editing a Picture.")
+ (make-local-variable 'picture-mode-old-local-map)
+ (setq picture-mode-old-local-map (current-local-map))
+ (use-local-map picture-mode-map)
+ (make-local-variable 'picture-mode-old-mode-name)
+ (setq picture-mode-old-mode-name mode-name)
+ (make-local-variable 'picture-mode-old-major-mode)
+ (setq picture-mode-old-major-mode major-mode)
+ (setq major-mode 'edit-picture)
+ (make-local-variable 'picture-killed-rectangle)
+ (setq picture-killed-rectangle nil)
+ (make-local-variable 'tab-stop-list)
+ (setq tab-stop-list (default-value 'tab-stop-list))
+ (make-local-variable 'picture-tab-chars)
+ (setq picture-tab-chars (default-value 'picture-tab-chars))
+ (make-local-variable 'picture-vertical-step)
+ (make-local-variable 'picture-horizontal-step)
+ (picture-set-motion 0 1)
+ (run-hooks 'edit-picture-hook)
+ (message
+ (substitute-command-keys
+ "Type \\[picture-mode-exit] in this buffer to return it to %s mode.")
+ picture-mode-old-mode-name)))
+
+(fset 'picture-mode 'edit-picture) ; for the confused
+
+(defun picture-mode-exit (&optional nostrip)
+ "Undo edit-picture and return to previous major mode.
+With no argument strips whitespace from end of every line in Picture buffer
+ otherwise just return to previous mode."
+ (interactive "P")
+ (if (not (eq major-mode 'edit-picture))
+ (error "You aren't editing a Picture.")
+ (if (not nostrip) (picture-clean))
+ (setq mode-name picture-mode-old-mode-name)
+ (use-local-map picture-mode-old-local-map)
+ (setq major-mode picture-mode-old-major-mode)
+ (kill-local-variable 'tab-stop-list)
+ ;; Kludge - force the mode line to be updated. Is there a better
+ ;; way to do this?
+ (set-buffer-modified-p (buffer-modified-p))))
+
+(defun picture-clean ()
+ "Eliminate whitespace at ends of lines."
+ (save-excursion
+ (goto-char (point-min))
+ (while (re-search-forward "[ \t][ \t]*$" nil t)
+ (delete-region (match-beginning 0) (point)))))