diff lisp/hyperbole/kotl/kotl-mode.el @ 0:376386a54a3c r19-14

Import from CVS: tag r19-14
author cvs
date Mon, 13 Aug 2007 08:45:50 +0200
parents
children 4103f0995bd7
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/hyperbole/kotl/kotl-mode.el	Mon Aug 13 08:45:50 2007 +0200
@@ -0,0 +1,2871 @@
+;;!emacs
+;;
+;; FILE:         kotl-mode.el
+;; SUMMARY:      Major mode for editing koutlines and associated commands.
+;; USAGE:        GNU Emacs Lisp Library
+;; KEYWORDS:     data, hypermedia, outlines, wp
+;;
+;; AUTHOR:       Bob Weiner & Kellie Clark
+;;
+;; ORIG-DATE:    6/30/93
+;; LAST-MOD:      3-Nov-95 at 19:25:57 by Bob Weiner
+;;
+;; This file is part of Hyperbole.
+;; Available for use and distribution under the same terms as GNU Emacs.
+;;
+;; Copyright (C) 1993, 1994, 1995  Free Software Foundation, Inc.
+;; Developed with support from Motorola Inc.
+;;
+;; DESCRIPTION:  
+;; DESCRIP-END.
+
+;;; ************************************************************************
+;;; Other required Lisp Libraries
+;;; ************************************************************************
+
+(mapcar 'require '(hsite hmail kview kimport kvspec kotl))
+
+;;; ************************************************************************
+;;; Public variables
+;;; ************************************************************************
+
+(defvar kotl-mode:refill-flag nil
+  "*Automatically refill cells during move, copy, promotion and demotion operations when non-nil.
+Default value is nil.  Cells with a `no-fill' attribute are never refilled
+during such operations, regardless of the value of this flag.")
+
+;;; ************************************************************************
+;;; Public functions
+;;; ************************************************************************
+
+;;;###autoload
+(defun kotl-mode ()
+  "The major mode used to edit and view koutlines.
+It provides the following keys:
+\\{kotl-mode-map}"
+  (interactive)
+  (use-local-map kotl-mode-map)
+  (set-syntax-table text-mode-syntax-table)
+  ;; Turn off filladapt minor mode if on, so that it does not interfere with
+  ;; the filling code in "kfill.el".
+  (and (boundp 'filladapt-mode) filladapt-mode (filladapt-mode -1))
+  (if (/= 3 (length (action:params (symbol-function 'fill-paragraph))))
+      ;; Some package such as filladapt has overwritten the primitives
+      ;; defined in kfill.el, so reload it.
+      (load "kfill"))
+  ;; Ensure that outline structure data is saved when save-buffer is called
+  ;; from save-some-buffers, {C-x s}.
+  (add-hook 'local-write-file-hooks 'kotl-mode:update-buffer)
+  (mapcar 'make-local-variable
+	  '(kotl-previous-mode indent-line-function indent-region-function
+            minor-mode-alist selective-display-ellipses))
+  ;; Used by kimport.el functions.
+  (if (and (boundp 'kotl-previous-mode) kotl-previous-mode)
+      nil
+    (setq kotl-previous-mode major-mode
+	  ;; Remove outline indication due to selective-display.
+	  minor-mode-alist (copy-sequence minor-mode-alist)
+	  minor-mode-alist (set:remove '(selective-display " Outline")
+				       minor-mode-alist)
+	  minor-mode-alist (set:remove '(selective-display " Otl")
+				       minor-mode-alist)
+	;; Remove indication that buffer is ;; narrowed.
+	mode-line-format (copy-sequence mode-line-format)
+	mode-line-format (set:remove "%n" mode-line-format)))
+  ;;
+  (setq indent-line-function 'kotl-mode:indent-line
+	indent-region-function 'kotl-mode:indent-region
+	local-abbrev-table text-mode-abbrev-table
+	selective-display t
+	selective-display-ellipses t
+	paragraph-start "^[ \t]*$\\|^\^L"
+	paragraph-separate "^[ \t]*$\\|^\^L")
+  ;;
+  ;; This major-mode setting must come after the local variable settings but
+  ;; before the koutline is formatted.
+  (setq major-mode 'kotl-mode
+	mode-name "Kotl"
+	indent-tabs-mode nil)
+  ;; If buffer has not yet been formatted for editing, format it.
+  (cond
+   ;; Koutline file that has been loaded and formatted for editing.
+   ((kview:is-p kview)
+    ;; The buffer might have been widened for inspection, so narrow to cells
+    ;; only.
+    (kfile:narrow-to-kcells))
+   ;; Koutline file that has been loaded but not yet formatted for editing.
+   ((kfile:is-p)
+    (kfile:read
+     (current-buffer)
+     (and buffer-file-name (file-exists-p buffer-file-name)))
+    (kvspec:activate))
+   ;; New koutline buffer or a foreign text buffer that must be converted to
+   ;; koutline format.
+   (t
+    (kfile:create (current-buffer))
+    (kvspec:activate)))
+  ;; We have been converting a buffer from a foreign format to a koutline.
+  ;; Now that it is converted, ensure that kotl-previous-mode is set to
+  ;; koutline now.
+  (setq kotl-previous-mode 'kotl-mode)
+  (run-hooks 'kotl-mode-hook))
+
+(defun kotl-mode:find-file-hook ()
+  (if (kview:is-p kview)
+      (kotl-mode:to-valid-position))
+  nil)
+
+;;; Ensure that point ends up at a valid position whenever a find-file
+;;; is done on a kotl-file.
+(add-hook 'find-file-hooks 'kotl-mode:find-file-hook)
+
+;;; Ensure that outline structure data is hidden from view after a file save.
+(add-hook 'after-save-hook 'kfile:narrow-to-kcells)
+
+;;; ------------------------------------------------------------------------
+;;; Editing within a single kotl
+;;; ------------------------------------------------------------------------
+
+(fset 'kotl-mode:backward-delete-char-untabify
+      'kotl-mode:delete-backward-char)
+(fset 'kotl-mode:backward-delete-char
+      'kotl-mode:delete-backward-char)
+
+(defun kotl-mode:backward-kill-word (arg)
+  "Kill up to prefix ARG words preceding point within a single cell."
+  (interactive "*p")
+  (or arg (setq arg 1))
+  (cond ((< arg 0)
+	 (if (kotl-mode:eocp)
+	     (error "(kotl-mode:backward-kill-word): End of cell")))
+	((> arg 0)
+	 (if (kotl-mode:bocp)
+	     (error "(kotl-mode:backward-kill-word): Beginning of cell"))))
+  (if (= arg 0)
+      nil
+    (save-restriction
+      (narrow-to-region (kcell-view:start) (kcell-view:end-contents))
+      (backward-kill-word arg))))
+
+(defun kotl-mode:center-line ()
+  "Center the line point is on, within the width specified by `fill-column'.
+This means adjusting the indentation so that it equals the distance between
+the end of the text and `fill-column'."
+  (interactive "*")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (let ((indent (kcell-view:indent))
+	(opoint (point-marker))
+	(bocp)
+	start)
+    (setq start (kotl-mode:beginning-of-line))
+    (if (setq bocp (kotl-mode:bocp))
+	(progn
+	  ;; Add a temporary fill-prefix since this is the 1st line of the cell
+	  ;; where label could interfere with centering.
+	  (insert "\n\n") (insert-char ?\  indent)))
+    (center-line)
+    (if bocp
+	;; Delete temporary fill prefix.
+	(delete-region start (+ start indent 2)))
+    (goto-char opoint)
+    ;; Move to editable point if need be.
+    (kotl-mode:to-valid-position)))
+
+(defun kotl-mode:center-paragraph ()
+  "Center each nonblank line in the paragraph at or after point.
+See `center-line' for more info."
+  (interactive "*")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (let ((indent (kcell-view:indent))
+	(opoint (point-marker))
+	start)
+    (backward-paragraph)
+    (kotl-mode:to-valid-position)
+    (setq start (point))
+    ;; Add a temporary fill-prefix for 1st line in cell which contains a
+    ;; label, so is centered properly.
+    (insert "\n\n") (insert-char ?\  indent)
+    (kcell-view:operate 'center-paragraph)
+    ;; Delete temporary fill prefix.
+    (delete-region start (+ start indent 2))
+    ;; Return to original point.
+    (goto-char (min opoint (kcell-view:end-contents)))
+    ;; Move to editable point if need be.
+    (kotl-mode:to-valid-position)))
+
+(defun kotl-mode:copy-region-as-kill (start end)
+  "Copy region between START and END within a single kcell to kill ring."
+  (interactive "r")
+  (kotl-mode:kill-region start end t))
+
+(defun kotl-mode:copy-to-register (register start end &optional delete-flag)
+  "Copy into REGISTER the region START to END.
+With optional prefix arg DELETE-FLAG, delete region."
+  (interactive "cCopy to register: \nr\nP")
+  (let ((indent (kcell-view:indent)))
+    (set-register register
+		  (hypb:replace-match-string
+		   (concat "^" (make-string indent ?\ ))
+		   (buffer-substring start end)
+		   "" t)))
+  (if delete-flag (delete-region start end)))
+
+(defun kotl-mode:delete-backward-char (arg &optional kill-flag)
+  "Delete up to the preceding prefix ARG characters.
+Return number of characters deleted.
+Optional KILL-FLAG non-nil means save in kill ring instead of deleting.
+Does not delete across cell boundaries."
+  (interactive "*P")
+  (if (interactive-p)
+      (if current-prefix-arg
+	  (setq kill-flag t
+		arg (prefix-numeric-value current-prefix-arg))))
+  (or arg (setq arg 1))
+  (kotl-mode:delete-char (- arg) kill-flag))
+
+(defun kotl-mode:delete-blank-lines ()
+  "On blank line within a cell, delete all surrounding blank lines, leaving just one.
+On isolated blank line, delete that one.
+On nonblank line, delete all blank lines that follow it.
+
+If nothing but whitespace follows point until the end of a cell, delete all
+whitespace at the end of the cell."
+  (interactive "*")
+  ;; If nothing but whitespace from point until the end of cell, remove all
+  ;; cell trailing whitespace.
+  (let ((end (kcell-view:end-contents))
+	start)
+    (if (save-excursion
+	  (skip-chars-forward " \t\n\r" end)
+	  (not (kotl-mode:eocp)))
+	(kcell-view:operate (function (lambda () (delete-blank-lines))))
+      (setq start (kcell-view:start))
+      (goto-char end)
+      ;; delete any preceding whitespace
+      (skip-chars-backward " \t\n\r" start)
+      (delete-region (max start (point)) end)))
+  (kotl-mode:to-valid-position))
+
+(defun kotl-mode:delete-char (arg &optional kill-flag)
+  "Delete up to prefix ARG characters following point.
+Return number of characters deleted.
+Optional KILL-FLAG non-nil means save in kill ring instead of deleting.
+Does not delete across cell boundaries."
+  (interactive "*P")
+  (if (interactive-p)
+      (if current-prefix-arg
+	  (setq kill-flag t
+		arg (prefix-numeric-value current-prefix-arg))))
+  (or arg (setq arg 1))
+  (let ((del-count 0)
+	(indent (kcell-view:indent))
+	count start end)
+    (cond ((> arg 0)
+	   (if (kotl-mode:eocp)
+	       (error "(kotl-mode:delete-char): End of cell")
+	     (setq end (kcell-view:end)
+		   arg (min arg (- end (point))))
+	     (while (> arg 0)
+	       (if (kotl-mode:eolp)
+		   (if (/= ?\ (char-syntax (following-char)))
+		       (setq arg 0
+			     del-count (1- del-count))
+		     (delete-char 1 kill-flag)
+		     ;; There may be non-whitespace characters in the
+		     ;; indent area.  Don't delete them.
+		     (setq count indent)
+		     (while (and (> count 0)
+				 (= ?\ (char-syntax (following-char))))
+		       (delete-char 1)
+		       (setq count (1- count))))
+		 (delete-char 1 kill-flag))
+	       (setq arg (1- arg)
+		     del-count (1+ del-count)))
+	     ))
+	  ((< arg 0)
+	   (if (kotl-mode:bocp)
+	       (error "(kotl-mode:delete-char): Beginning of cell")
+	     (setq start (kcell-view:start)
+		   arg (max arg (- start (point))))
+	     (while (< arg 0)
+	       (if (kotl-mode:bolp)
+		   (if (/= ?\ (char-syntax (preceding-char)))
+		       (setq arg 0
+			     del-count (1- del-count))
+		     ;; There may be non-whitespace characters in the
+		     ;; indent area.  Don't delete them.
+		     (setq count indent)
+		     (while (and (> count 0)
+				 (= ?\ (char-syntax (preceding-char))))
+		       (delete-char -1)
+		       (setq count (1- count)))
+		     (if (zerop count)
+			 (delete-char -1 kill-flag)))
+		 (delete-char -1 kill-flag))
+	       (setq arg (1+ arg)
+		     del-count (1+ del-count))))))
+    del-count))
+
+(defun kotl-mode:delete-horizontal-space ()
+  "Delete all spaces and tabs around point."
+  (interactive "*")
+  (save-restriction
+    (narrow-to-region
+     (save-excursion
+       (kotl-mode:start-of-line))
+     (save-excursion
+       (kotl-mode:finish-of-line)))
+    (delete-horizontal-space)))
+
+(defun kotl-mode:delete-indentation (&optional arg)
+  "Join this line to previous and fix up whitespace at join.
+If there is a fill prefix, delete it from the beginning of this line.
+With argument, join this line to following line."
+  (interactive "*P")
+  (kcell-view:operate
+   (function
+    (lambda ()
+      (let ((opoint (point)))
+	(beginning-of-line)
+	(if arg (forward-line 1))
+	(if (eq (preceding-char) ?\n)
+	    (progn
+	      (delete-region (point) (1- (point)))
+	      ;; If the second line started with the fill prefix,
+	      ;; delete the prefix.
+	      (if (and fill-prefix
+		       (<= (+ (point) (length fill-prefix)) (point-max))
+		       (string= fill-prefix
+				(buffer-substring
+				 (point) (+ (point) (length fill-prefix)))))
+		  (delete-region (point) (+ (point) (length fill-prefix))))
+	      (fixup-whitespace))
+	  (goto-char opoint)))))))
+
+(defun kotl-mode:fill-cell (&optional justify ignore-collapsed-p)
+  "Fill current cell within current view if it does not have a non-nil `no-fill' attribute.
+With optional JUSTIFY, justify cell as well.
+IGNORE-COLLAPSED-P is used when caller has already expanded cell, indicating
+it is not collapsed."
+  (interactive "*P")
+  (cond ((kcell-view:get-attr 'no-fill)
+	 (if (interactive-p)
+	     (progn (beep)
+		    (message "Current cell has a `do not fill' attribute.")
+		    nil)))
+	((string-match "\\`[ \t\n\r]*\\'" (kcell-view:contents))
+	  ;; Cell content is all whitespace.
+	 nil)
+	(t (let* ((indent (kcell-view:indent))
+		  (opoint (set-marker (make-marker) (point)))
+		  (start  (kcell-view:start))
+		  (collapsed-p)
+		  (end (kcell-view:end-contents))
+		  temp-prefix prev-point)
+	     (goto-char start)
+	     ;; Expand cell if collapsed so that filling is done properly.
+	     (if (and (not ignore-collapsed-p)
+		      (setq collapsed-p (search-forward "\r" end t)))
+		 (subst-char-in-region start end ?\r ?\n t))
+	     (goto-char start)
+	     ;; Add a temporary fill-prefix for first labeled line, so is
+	     ;; filled properly.
+	     (insert (setq temp-prefix
+			   (concat "\n\n" (make-string indent ?\ ))))
+	     (while (progn (fill-paragraph justify)
+			   (setq prev-point (point))
+			   (forward-paragraph)
+			   (and (/= (point) prev-point)
+				(< (point) (kcell-view:end-contents))
+				(if (memq (preceding-char) '(?\n ?\r))
+				    (not (looking-at "[\n\r]"))
+				  t))))
+	     ;; Delete temporary fill prefix.
+	     (goto-char start)
+	     (if (looking-at temp-prefix)
+		 (replace-match "" t t))
+	     ;; Return to original point.
+	     (setq end (kcell-view:end-contents))
+	     (goto-char (min opoint end))
+	     ;;
+	     ;; If cell was collapsed before filling, collapse it again.
+	     (if collapsed-p
+		 (subst-char-in-region start end ?\n ?\r t))
+	     ;;
+	     ;; Remove markers
+	     (set-marker opoint nil))
+	   ;; Move to editable point if need be.
+	   (kotl-mode:to-valid-position))))
+
+(defun kotl-mode:fill-paragraph (&optional justify)
+  "Fill the current paragraph within the cell.
+With optional JUSTIFY, justify the paragraph as well.
+Ignore any non-nil no-fill attribute attached to the cell."
+  (interactive "*P")
+  (let ((indent (kcell-view:indent))
+	(opoint (point-marker))
+	start end)
+    (backward-paragraph)
+    (kotl-mode:to-valid-position)
+    (setq start (point-marker))
+    ;; Add a temporary fill-prefix for 1st line in cell which contains a
+    ;; label, so is filled properly.
+    (insert "\n\n") (insert-char ?\  indent)
+    (setq end (point-marker))
+    ;; Return to original paragraph point.  This is the correct formula,
+    ;; considering the fill prefix that was just added.
+    (goto-char (min (max opoint (point)) (kcell-view:end-contents)))
+    (fill-paragraph justify)
+    ;; Delete temporary fill prefix.
+    (delete-region start end)
+    ;; Return to original point.
+    (goto-char (min opoint (kcell-view:end-contents)))
+    ;; Move to editable point if need be.
+    (kotl-mode:to-valid-position)
+    ;; Remove markers
+    (set-marker opoint nil)
+    (set-marker start nil)
+    (set-marker end nil)))
+
+;; XEmacs binds this to {M-q}.
+(fset 'kotl-mode:fill-paragraph-or-region 'kotl-mode:fill-paragraph)
+
+(defun kotl-mode:fill-tree (&optional top-p)
+  "Refill each cell within the tree whose root is at point.
+Skip cells with a non-nil no-fill attribute.
+With optional prefix argument TOP-P non-nil, refill all cells in the outline."
+  (interactive "P")
+  ;; Store list of which cells are presently collapsed.
+  (let ((collapsed-cells
+	 (kview:map-tree
+	  (function (lambda (view)
+		      ;; Use free variable label-sep-len bound in
+		      ;; kview:map-tree for speed.
+		      (kcell-view:collapsed-p nil label-sep-len)))
+	  kview top-p)))
+    ;;
+    ;; Expand all cells in tree.
+    (if top-p
+	(subst-char-in-region (point-min) (point-max) ?\r ?\n t)
+      (save-excursion
+	(kotl-mode:end-of-tree)
+	(subst-char-in-region
+	 (point) (kcell-view:end-contents) ?\r ?\n t)))
+    ;;
+    ;; Refill cells without no-fill property.
+    (kview:map-tree (function (lambda (view) (kotl-mode:fill-cell)))
+		    kview top-p)
+    ;;
+    ;; Collapse temporarily expanded cells.
+    (if (delq nil collapsed-cells)
+	(kview:map-tree
+	 (function
+	  (lambda (view)
+	    (if (car collapsed-cells)
+		;; Use free variable label-sep-len bound in
+		;; kview:map-tree for speed.
+		(kcell-view:collapse nil label-sep-len))
+	    (setq collapsed-cells (cdr collapsed-cells))))
+	 kview top-p))))
+
+(defun kotl-mode:insert-buffer (buffer)
+  "Insert after point the contents of BUFFER.
+Puts mark after the inserted text.
+BUFFER may be a buffer or a buffer name."
+  (interactive "*bInsert buffer: ")
+  (insert-buffer buffer)
+  (kotl-mode:add-indent-to-region))
+
+(defun kotl-mode:insert-file (import-from children-p)
+  "Insert each element in IMPORT-FROM as a separate cell in the current view.
+Insert as sibling cells following the current cell unless prefix arg,
+CHILDREN-P is non-nil, then insert as the initial children of the current
+cell.
+
+IMPORT-FROM may be a buffer name or file name (file name completion is
+provided).
+
+See documentation for `kimport:file' for information on how the type of
+importation is determined."
+  (interactive
+   (list (read-file-name
+	  (if current-prefix-arg
+	      "Buffer or file to insert as children of current cell: "
+	    "Buffer or file to insert as siblings of current cell: "))
+	 current-prefix-arg))
+  (kimport:file import-from (current-buffer) children-p))
+
+(defun kotl-mode:insert-file-contents (filename)
+  "Insert contents of file FILENAME into current cell after point.
+Set mark after the inserted text."
+  (interactive "*fInsert file: ")
+  (let ((tem (insert-file-contents filename)))
+    (push-mark (+ (point) (car (cdr tem)))))
+  (kotl-mode:add-indent-to-region))
+
+(defun kotl-mode:insert-register (register &optional arg)
+  "Insert contents of register REGISTER at point in current cell.
+REGISTER is a character naming the register to insert.
+Normally puts point before and mark after the inserted text.
+If optional second arg is non-nil, puts mark before and point after.
+Interactively, second arg is non-nil if prefix arg is supplied."
+  (interactive "*cInsert register: \nP")
+  (push-mark)
+  (let ((val (get-register register)))
+    (cond ((consp val)
+           (insert-rectangle val))
+          ((stringp val)
+	   (insert val)
+	   (kotl-mode:add-indent-to-region))
+          ((integerp val)
+           (princ val (current-buffer)))
+          ((and (markerp val) (marker-position val))
+           (princ (marker-position val) (current-buffer)))
+          (t
+           (error "Register '%c' does not contain text" register))))
+  (if (not arg) (exchange-point-and-mark)))
+
+(defun kotl-mode:just-one-space ()
+  "Delete all spaces and tabs around point and leave one space."
+  (interactive "*")
+  (save-restriction
+    (narrow-to-region
+     (save-excursion
+       (kotl-mode:start-of-line))
+     (save-excursion
+       (kotl-mode:finish-of-line)))
+    (just-one-space)))
+
+(defun kotl-mode:kill-line (&optional arg)
+  "Kill ARG lines from point."
+  (interactive "*P")
+  (if (and (null arg)
+	   (kotl-mode:bolp)
+	   (boundp 'kill-whole-line) kill-whole-line)
+      (let ((indent (kcell-view:indent)))
+	;; Kill whole line including newline, if any.
+	(kcell-view:operate
+	 (function
+	  (lambda ()
+	    (let ((no-newline))
+	      (kill-region (point)
+			   (progn (setq no-newline
+					(not (search-forward "\n" nil 'stay)))
+				  (point)))
+	      (or no-newline (delete-char indent)))))))
+    ;; Kill part of a line or multiple lines.
+    (let ((num-arg (prefix-numeric-value arg)))
+      (cond
+       ((and (null arg) (not (kotl-mode:eolp)))
+	;; kill to eol but not newline
+	(kill-region (point) (setq arg (kotl-mode:finish-of-line))))
+       ((= num-arg 0)
+	;; kill to bol
+	(kill-region (point) (setq arg (kotl-mode:start-of-line))))
+       (t;; (/= num-arg 0)
+	;; Find start and end of region to kill
+	(let ((start (point))
+	      (end (min (kcell-view:end-contents)
+			(save-excursion (forward-line num-arg) (point)))))
+	  (kotl-mode:kill-region start end))))))
+  (setq last-command 'kill-region))
+
+(defun kotl-mode:kill-region (start end &optional copy-p)
+  "Kill region between START and END within a single kcell.
+With optional COPY-P equal to 't, copy region to kill ring but does not
+kill it.  With COPY-P any other non-nil value, return region as a
+string without affecting kill ring.
+
+If the buffer is read-only and COPY-P is nil, the region will not be deleted
+but it will be copied to the kill ring and then an error will be signaled."
+  (interactive "*r")
+  (let ((read-only (and (not copy-p) buffer-read-only)))
+    (if read-only (setq copy-p t))
+    (if (and (number-or-marker-p start)
+	     (number-or-marker-p end)
+	     (eq (kcell-view:cell start)
+		 (kcell-view:cell end)))
+	(save-excursion
+	  (goto-char start)
+	  (let ((indent (kcell-view:indent))
+		killed subst-str)
+	    ;; Convert region to string
+	    ;; Convert all occurrences of newline + indent
+	    ;; to just newline, eliminating indent.
+	    ;; Then save to kill ring.
+	    (setq subst-str (concat "\\([\n\r]\\)" (make-string indent ?\ ))
+		  killed
+		  (hypb:replace-match-string
+		   subst-str (buffer-substring start end) "\\1"))
+	    (if copy-p
+		nil
+	      ;; If last char of region is a newline, then delete indent in
+	      ;; following line.
+	      (delete-region
+	       start (+ end (if (memq (char-after (1- (max start end)))
+				      '(?\n ?\r))
+				indent
+			      0))))
+	    (if (and copy-p (not (eq copy-p t)))
+		;; Return killed region as a string.
+		killed
+	      (if (eq last-command 'kill-region)
+		  (kill-append killed (< end start))
+		(kill-new killed))
+	      (setq this-command 'kill-region)
+	      (if read-only (barf-if-buffer-read-only))
+	      )))
+      (error
+       "(kotl-mode:kill-region): Bad region or not within a single kcell."))))
+
+(fset 'kotl-mode:kill-ring-save 'kotl-mode:copy-region-as-kill)
+
+(defun kotl-mode:kill-sentence (&optional arg)
+  "Kill up to prefix ARG (or 1) sentences following point within a single cell."
+  (interactive "*p")
+  (or arg (setq arg 1))
+  (cond ((> arg 0)
+	 (if (kotl-mode:eocp)
+	     (error "(kotl-mode:kill-sentence): End of cell")))
+	((< arg 0)
+	 (if (kotl-mode:bocp)
+	     (error "(kotl-mode:kill-sentence): Beginning of cell"))))
+  (if (= arg 0)
+      nil
+    (kotl-mode:kill-region (point)
+			   (save-excursion
+			     (kotl-mode:forward-sentence arg)))))
+
+(defun kotl-mode:kill-word (arg)
+  "Kill up to prefix ARG words following point within a single cell."
+  (interactive "*p")
+  (or arg (setq arg 1))
+  (cond ((> arg 0)
+	 (if (kotl-mode:eocp)
+	     (error "(kotl-mode:kill-word): End of cell")))
+	((< arg 0)
+	 (if (kotl-mode:bocp)
+	     (error "(kotl-mode:kill-word): Beginning of cell"))))
+  (if (= arg 0)
+      nil
+    (save-restriction
+      (narrow-to-region (kcell-view:start) (kcell-view:end-contents))
+      (kill-word arg))))
+
+(defun kotl-mode:newline (arg)
+  "Insert a newline.  With ARG, insert ARG newlines.
+In Auto Fill mode, if no numeric arg, break the preceding line if it is
+too long."
+  (interactive "*p")
+  (let ((indent (kcell-view:indent)))
+    (if (equal arg 1)
+	(progn
+	  (save-excursion
+	    (insert ?\n)
+	    (insert-char ?\  indent))
+	  (do-auto-fill)
+	  (forward-line 1)
+	  (kotl-mode:start-of-line)
+	  )
+      (while (> arg 0)
+	(insert ?\n)
+	(insert-char ?\  indent)
+	(setq arg (1- arg))))))
+
+(fset 'kotl-mode:newline-and-indent 'kotl-mode:newline)
+
+(defun kotl-mode:open-line (arg)
+  "Insert a newline and leave point before it.
+With arg N, insert N newlines."
+  (interactive "*p")
+  (let* ((bolp (and (kotl-mode:bolp) (not (kotl-mode:bocp))))
+	 (indent (kcell-view:indent)))
+    (while (> arg 0)
+      (save-excursion
+        (insert ?\n)
+	(if (and (not bolp) fill-prefix)
+	    (insert fill-prefix)
+	  (insert-char ?\  indent)))
+      (setq arg (1- arg)))
+    (if (and bolp fill-prefix)
+	(progn (delete-horizontal-space)
+	       (insert fill-prefix)))
+    ))
+
+(defun kotl-mode:set-fill-prefix (turn-off)
+  "Sets fill prefix to line up to point.
+With prefix arg TURN-OFF or at begin of line, turns fill prefix off."
+  (interactive "P")
+  (set-fill-prefix (or turn-off (kotl-mode:bolp))))
+
+(defun kotl-mode:transpose-chars (arg)
+  "Interchange characters around point, moving forward one character.
+With prefix ARG, take character before point and drag it forward past ARG
+other characters (backward if ARG negative).
+If no prefix ARG and at end of line, the previous two characters are
+exchanged."
+  (interactive "*P")
+  (and (null arg) (kotl-mode:eolp) (kotl-mode:forward-char -1))
+  (transpose-subr 'kotl-mode:forward-char (prefix-numeric-value arg)))
+
+(defun kotl-mode:transpose-lines (arg)
+  "Exchange current line and previous line, leaving point after both.
+If no previous line, exchange current with next line.
+With prefix ARG, take previous line and move it past ARG lines.
+With prefix ARG = 0, interchange the line that contains point with the line
+that contains mark."
+  (interactive "*p")
+  (cond
+   ((and (kotl-mode:first-line-p) (kotl-mode:last-line-p))
+    (error "(kotl-mode:transpose-lines): Only one line in outline"))
+   ;;
+   ;; Transpose current and previous lines or current and next lines, if no
+   ;; previous line.  Leave point after both exchanged lines.
+   ((= arg 1)
+    (let* ((point (point-marker))
+	   (mark (set-marker (make-marker)
+			     (if (kotl-mode:first-line-p)
+				 (kotl-mode:next-line 1)
+			       (kotl-mode:previous-line 1)))))
+      (kotl-mode:transpose-lines-internal point mark)
+      (goto-char (max point mark))
+      (kotl-mode:next-line 1)
+      (set-marker mark nil)))
+   ;;
+   ;; Transpose point and mark lines, leaving point on the line of text that
+   ;; originally contained point.
+   ((= arg 0)
+    (kotl-mode:transpose-lines-internal (point-marker) (hypb:mark-marker t))
+    ;; This is like exchange-point-and-mark, but doesn't activate the
+    ;; mark.
+    (goto-char (prog1 (hypb:mark t)
+		 (set-marker (hypb:mark-marker t) (point)))))
+   ;;
+   ;; Move previous line past ARG next lines and leave point after previous
+   ;; line text.
+   (t
+    (if (kotl-mode:first-line-p)
+	(error "(kotl-mode:transpose-lines): No previous line to transpose"))
+    (kotl-mode:previous-line 1)
+    (let* ((mark (set-marker (make-marker)
+			     (save-excursion (kotl-mode:next-line arg))))
+	   (line-to-move (kotl-mode:delete-line)))
+      (condition-case ()
+	  ;; Delete trailing newline if any, ignoring error.
+	  (kotl-mode:delete-char 1)
+	(error nil))
+      (goto-char mark)
+      (set-marker mark nil)
+      (kotl-mode:finish-of-line)
+      (insert "\n")
+      (insert-char ?\  (kcell-view:indent))
+      (insert line-to-move)
+      (kotl-mode:start-of-line)))))
+
+(defun kotl-mode:transpose-paragraphs (arg)
+  "Interchange this (or next) paragraph with previous one."
+  (interactive "*p")
+  (transpose-subr 'kotl-mode:forward-paragraph (prefix-numeric-value arg)))
+
+(defun kotl-mode:transpose-sentences (arg)
+  "Interchange this (next) and previous sentence."
+  (interactive "*p")
+  (transpose-subr 'kotl-mode:forward-sentence (prefix-numeric-value arg)))
+
+(defun kotl-mode:transpose-words (arg)
+  "Interchange words around point, leaving point after both words.
+With prefix ARG, take word before or around point and drag it forward past
+ARG other words (backward if ARG negative).  If ARG is zero, the words around
+or after point and around or after mark are interchanged."
+  (interactive "*p")
+  (transpose-subr 'kotl-mode:forward-word (prefix-numeric-value arg)))
+
+(defun kotl-mode:zap-to-char (arg char)
+  "Kill up to and including prefix ARG'th occurrence of CHAR.
+Goes backward if ARG is negative; error if CHAR not found."
+  (interactive "*p\ncZap to char within current cell: ")
+  (kcell-view:operate 
+   (function (lambda () (zap-to-char arg char)))))
+
+;;; ------------------------------------------------------------------------
+;;; Editing across kotls
+;;; ------------------------------------------------------------------------
+
+(defun kotl-mode:append-cell (contents-cell append-to-cell)
+  "Append CONTENTS-CELL to APPEND-TO-CELL.
+APPEND-TO-CELL is refilled if neither cell has a no-fill property and
+kotl-mode:refill-flag is enabled."
+  (interactive
+   (let* ((label (kcell-view:label))
+	  (hargs:defaults (list label label)))
+     (hargs:iform-read
+      '(interactive
+	"*+KAppend contents of cell: \n+KAppend contents of cell <%s> to cell: "))))
+  (save-excursion
+    (kotl-mode:goto-cell contents-cell)
+    (let ((contents (kcell-view:contents))
+	  (no-fill (kcell-view:get-attr 'no-fill)))
+      (kotl-mode:goto-cell append-to-cell)
+      (if no-fill nil (setq no-fill (kcell-view:get-attr 'no-fill)))
+      (goto-char (kcell-view:end-contents))
+      (let ((fill-prefix (make-string (kcell-view:indent) ?\ )))
+	(if (kotl-mode:bolp)
+	    nil
+	  ;; Append contents of cell beginning on its own line.
+	  (insert "\n" fill-prefix))
+	(kview:insert-contents (kcell-view:cell) contents
+			       (or no-fill (null kotl-mode:refill-flag))
+			       fill-prefix)))))
+
+(defun kotl-mode:copy-after (from-cell-ref to-cell-ref child-p)
+  "Copy tree rooted at FROM-CELL-REF to follow tree rooted at TO-CELL-REF.
+If prefix arg CHILD-P is non-nil, make FROM-CELL-REF the first child of
+TO-CELL-REF, otherwise make it the sibling following TO-CELL-REF.
+
+Leave point at the start of the root cell of the new tree."
+  (interactive
+   (let* ((label (kcell-view:label))
+	  (hargs:defaults (list label label)))
+     (append
+      (hargs:iform-read
+       (list
+	'interactive
+	(format "*+KCopy tree: \n+KCopy tree <%%s> to follow as %s of cell: "
+		(if current-prefix-arg "child" "sibling"))))
+      (list current-prefix-arg))))
+  ;;
+  ;; Copy tree in current view and leave point at the start of the copy.
+  (goto-char (kotl-mode:move-after from-cell-ref to-cell-ref child-p t))
+  ;; Alter the copied tree so each cell appears to be newly created.
+  (kview:map-tree
+   (function
+    (lambda (view)
+      (kcell-view:set-cell
+       (kcell:create nil (kview:id-increment view)))))
+   kview))
+
+(defun kotl-mode:copy-before (from-cell-ref to-cell-ref parent-p)
+  "Copy tree rooted at FROM-CELL-REF to precede tree rooted at TO-CELL-REF.
+If prefix arg PARENT-P is non-nil, make FROM-CELL-REF the first child of
+TO-CELL-REF's parent, otherwise make it the preceding sibling of TO-CELL-REF.
+
+Leave point at the start of the root cell of the new tree."
+  (interactive
+   (let* ((label (kcell-view:label))
+	  (hargs:defaults (list label label)))
+     (append
+      (hargs:iform-read
+       (list 'interactive
+	     (format "*+KCopy tree: \n+KCopy tree <%%s> to be %s of cell: "
+		     (if current-prefix-arg "first child of parent"
+		       "preceding sibling"))))
+      (list current-prefix-arg))))
+  ;;
+  ;; Copy tree in current view and leave point at the start of the copy.
+  (goto-char (kotl-mode:move-before from-cell-ref to-cell-ref parent-p t))
+  ;; Alter the copied tree so each cell appears to be newly created.
+  (kview:map-tree
+   (function
+    (lambda (view)
+      (kcell-view:set-cell
+       (kcell:create nil (kview:id-increment view)))))
+   kview))
+
+(defun kotl-mode:copy-to-buffer (cell-ref buffer invisible-flag)
+  "Copy outline tree rooted at CELL-REF to a non-koutline BUFFER.
+Invisible text is expanded and included in the copy only if INVISIBLE-FLAG is
+non-nil.  The tree is inserted before point in BUFFER.  Use \"0\" to copy the
+whole outline buffer."
+  (interactive
+   (let ((label-default (kcell-view:label)))
+     (hargs:iform-read
+      '(interactive
+	(list
+	 (hargs:read "Copy tree without attributes: (0 for whole outline) "
+		     nil label-default nil 'kcell)
+	 (read-buffer "To buffer: "
+		      (save-window-excursion
+			(if (one-window-p)
+			    (select-frame (next-frame))
+			  (other-window 1))
+			(buffer-name))
+		      t)
+	 (y-or-n-p "Copy invisible text? "))))))
+  (message "") ;; Erase last interactive prompt, if any.
+  (setq buffer (get-buffer-create buffer))
+  (if (equal cell-ref "0")
+      (hypb:insert-region buffer (point-min) (point-max) invisible-flag)
+    (let (start end)
+      (save-excursion
+	(kotl-mode:goto-cell cell-ref t)
+	(save-excursion (beginning-of-line) (setq start (point)))
+	(setq end (kotl-mode:tree-end)))
+      (hypb:insert-region buffer start end invisible-flag))))
+
+(defun kotl-mode:move-after (from-cell-ref to-cell-ref child-p
+			     &optional copy-p fill-p)
+  "Move tree rooted at FROM-CELL-REF to follow tree rooted at TO-CELL-REF.
+If prefix arg CHILD-P is non-nil, make FROM-CELL-REF the first child of
+TO-CELL-REF, otherwise make it the sibling following TO-CELL-REF.
+With optional COPY-P, copies tree rather than moving it.
+
+Leave point at original location but return the tree's new start point."
+  (interactive
+   (let* ((label (kcell-view:label))
+	  (hargs:defaults (list label label)))
+     (append
+      (hargs:iform-read
+       (list
+	'interactive
+	(format "*+KMove tree: \n+KMove tree <%%s> to follow as %s of cell: "
+		(if current-prefix-arg "child" "sibling"))))
+      (list current-prefix-arg))))
+  (if (and (not copy-p) (equal from-cell-ref to-cell-ref))
+      (error "(kotl-mode:move-after): Can't move tree after itself"))
+  (let* ((orig (set-marker (make-marker) (point)))
+	 (label-sep-len (kview:label-separator-length kview))
+	 (move-to-point (set-marker
+			 (make-marker)
+			 (kotl-mode:goto-cell to-cell-ref t)))
+	 (to-label (kcell-view:label))
+	 (to-indent (kcell-view:indent nil label-sep-len))
+	 (from-label (progn (kotl-mode:goto-cell from-cell-ref t)
+			    (kcell-view:label)))
+	 (from-indent (kcell-view:indent nil label-sep-len))
+	 (start (kotl-mode:tree-start))
+	 (end   (kotl-mode:tree-end))
+	 (sib-id (if (= 0 (kotl-mode:forward-cell 1))
+		     (kcell-view:idstamp)))
+	 new-tree-start)
+    ;;
+    ;; We can't move a tree to a point within itself, so if that is the case
+    ;; and this is not a copy operation, signal an error.
+    (if (and (not copy-p) (>= move-to-point start) (<= move-to-point end))
+	(error "(kotl-mode:move-after): Can't move tree <%s> to within itself"
+	       from-label))
+    ;;
+    ;; If tree to move has a sibling, point is now at the start of the
+    ;; sibling cell.  Mark its label with a property which will be deleted
+    ;; whenever the cell label is renumbered.  This tells us whether or not
+    ;; to renumber the sibling separately from the tree to move.
+    (if sib-id
+	;; Move to middle of label and insert klabel-original temp property.
+	(progn (goto-char (- (point) label-sep-len 3))
+	       (kproperty:set 'klabel-original t)))
+    ;;
+    ;; Position for insertion before deletion of tree-to-move from old
+    ;; position, in case old position precedes new one.
+    ;; Skip past either cell or tree at move-to-point.
+    (goto-char move-to-point)
+    (if child-p
+	;; Move to insert position for first child of to-cell-ref.
+	(progn (goto-char (kcell-view:end))
+	       (setq to-label (klabel:child to-label)
+		     to-indent (+ to-indent (kview:level-indent kview))))
+      ;; Move to after to-cell-ref's tree for insertion as following sibling.
+      (goto-char (kotl-mode:tree-end))
+      (setq to-label (klabel:increment to-label)))
+    ;;
+    ;; Insert tree-to-move at new location
+    ;;
+    (kview:move start end (point) from-indent to-indent copy-p
+		(or fill-p kotl-mode:refill-flag))
+    ;;
+    ;; Ensure that point is within editable region of cell with to-label.
+    (kotl-mode:to-valid-position)
+    (setq new-tree-start (point))
+    ;;
+    ;; Update current cell and new siblings' labels within view.
+    (klabel-type:update-labels to-label)
+    ;;
+    (if copy-p
+	nil
+      ;;
+      ;; Move to sibling of tree-to-move within view and update labels within
+      ;; view of tree-to-move's original siblings.
+      (if sib-id
+	  (progn (kotl-mode:goto-cell sib-id t)
+		 ;; Sibling labels may have already been updated if tree was
+		 ;; moved somewhere preceding its siblings.
+		 (let ((label-middle (- (point) label-sep-len 2)))
+		   (if (kproperty:get label-middle 'klabel-original)
+		       (klabel-type:update-labels from-label))))))
+    ;;
+    (goto-char orig)
+    ;;
+    ;; Ensure that point is within editable region of a cell.
+    (kotl-mode:to-valid-position)
+    ;;
+    (set-marker orig nil)
+    (set-marker move-to-point nil)
+    new-tree-start))
+
+(defun kotl-mode:move-before (from-cell-ref to-cell-ref parent-p
+			      &optional copy-p fill-p)
+  "Move tree rooted at FROM-CELL-REF to precede tree rooted at TO-CELL-REF.
+If prefix arg PARENT-P is non-nil, make FROM-CELL-REF the first child of
+TO-CELL-REF's parent, otherwise make it the preceding sibling of TO-CELL-REF.
+With optional COPY-P, copies tree rather than moving it.
+
+Leave point at original location but return the tree's new start point."
+  (interactive
+   (let* ((label (kcell-view:label))
+	  (hargs:defaults (list label label)))
+     (append
+      (hargs:iform-read
+       (list 'interactive
+	     (format "*+KMove tree: \n+KMove tree <%%s> to be %s of cell: "
+		     (if current-prefix-arg "first child of parent"
+		       "preceding sibling"))))
+      (list current-prefix-arg))))
+  (if (and (not copy-p) (equal from-cell-ref to-cell-ref))
+      (error "(kotl-mode:move-before): Can't move tree before itself"))
+  (let* ((orig (set-marker (make-marker) (point)))
+	 (label-sep-len (kview:label-separator-length kview))
+	 (move-to-point (set-marker
+			 (make-marker)
+			 (kotl-mode:goto-cell to-cell-ref t)))
+	 (to-label (kcell-view:label))
+	 (to-indent (kcell-view:indent nil label-sep-len))
+	 (from-label (progn (kotl-mode:goto-cell from-cell-ref t)
+			    (kcell-view:label)))
+	 (from-indent (kcell-view:indent nil label-sep-len))
+	 (start (kotl-mode:tree-start))
+	 (end   (kotl-mode:tree-end))
+	 (sib-id (if (= 0 (kotl-mode:forward-cell 1))
+		     (kcell-view:idstamp)))
+	 new-tree-start)
+    ;;
+    ;; We can't move a tree to a point within itself, so if that is the case
+    ;; and this is not a copy operation, signal an error.
+    (if (and (not copy-p) (>= move-to-point start) (<= move-to-point end))
+	(error "(kotl-mode:move-before): Can't move tree <%s> to within itself"
+	       from-label))
+    ;;
+    ;; If tree to move has a sibling, point is now at the start of the
+    ;; sibling cell.  Mark its label with a property which will be deleted
+    ;; whenever the cell label is renumbered.  This tells us whether or not
+    ;; to renumber the sibling separately from the tree to move.
+    (if sib-id
+	;; Move to middle of label and insert klabel-original temp property.
+	(progn (goto-char (- (point) label-sep-len 3))
+	       (kproperty:set 'klabel-original t)))
+    ;;
+    ;; Position for insertion at succeeding-tree, before deletion of
+    ;; tree-to-move from old position, in case old position precedes new one.
+    (goto-char move-to-point)
+    (if parent-p
+	;; Move to insert position for first child of to-cell-ref's parent.
+	(if (kcell-view:parent nil label-sep-len)
+	    (progn (setq to-label (klabel:child (kcell-view:label)))
+		   (goto-char (kcell-view:end)))
+	  (error "(kotl-mode:move-before): to-cell-ref's parent not in current view"))
+      ;; Move to before to-cell-ref for insertion as preceding sibling.
+      (goto-char (kotl-mode:tree-start)))
+    ;;
+    ;; Insert tree-to-move at new location
+    ;;
+    (kview:move start end (point) from-indent to-indent copy-p
+		(or fill-p kotl-mode:refill-flag))
+    ;;
+    ;; Ensure that point is within editable region of root of tree just moved.
+    (kotl-mode:to-valid-position)
+    (setq new-tree-start (point))
+    ;;
+    ;; Update current cell and new siblings' labels within view.
+    (klabel-type:update-labels to-label)
+    ;;
+    (if copy-p
+	nil
+      ;;
+      ;; Move to sibling of tree-to-move within view and update labels within
+      ;; view of tree-to-move's original siblings.
+      (if sib-id
+	  (progn
+	    (kotl-mode:goto-cell sib-id t)
+	    ;; Sibling labels may have already been updated if tree was
+	    ;; moved somewhere preceding its siblings.
+	    (let ((label-middle (- (point) label-sep-len 2)))
+	      (if (kproperty:get label-middle 'klabel-original)
+		  (klabel-type:update-labels from-label))))))
+    ;;
+    (goto-char orig)
+    ;;
+    ;; Ensure that point is within editable region of a cell.
+    (kotl-mode:to-valid-position)
+    ;;
+    (set-marker orig nil)
+    (set-marker move-to-point nil)
+    new-tree-start))
+
+(defun kotl-mode:yank (&optional arg)
+  "Reinsert the last stretch of killed text.
+More precisely, reinsert the stretch of killed text most recently
+killed OR yanked.  Put point at end, and set mark at beginning.
+With just C-u as argument, same but put point at beginning (and mark at end).
+With argument N, reinsert the Nth most recently killed stretch of killed
+text.
+See also the command \\[kotl-mode:yank-pop]."
+  (interactive "*P")
+  (push-mark (point))
+  (let* ((yank-text (current-kill (cond
+				   ((listp arg) 0)
+				   ((eq arg '-) -1)
+				   (t (1- arg)))))
+	 (indent (kcell-view:indent))
+	 (indent-str (make-string indent ?\ )))
+    ;; Convert all occurrences of newline to newline + cell indent.
+    ;; Then insert into buffer.
+    (insert (hypb:replace-match-string
+	     "[\n\r]" yank-text (concat "\\0" indent-str))))
+  (if (consp arg)
+      ;; This is like exchange-point-and-mark, but doesn't activate the mark.
+      ;; It is cleaner to avoid activation, even though the command
+      ;; loop would deactivate the mark because we inserted text.
+      (goto-char (prog1 (mark t)
+		   (set-marker (hypb:mark-marker t) (point)))))
+  nil)
+
+(defun kotl-mode:yank-pop (arg)
+  "Replace just-yanked stretch of killed text with a different stretch.
+This command is allowed only immediately after a `yank' or a `yank-pop'.
+At such a time, the region contains a stretch of reinserted
+previously-killed text.  `yank-pop' deletes that text and inserts in its
+place a different stretch of killed text.
+
+With no argument, the previous kill is inserted.
+With argument N, insert the Nth previous kill.
+If N is negative, this is a more recent kill.
+
+The sequence of kills wraps around, so that after the oldest one
+comes the newest one."
+  (interactive "*p")
+  (if (not (eq last-command 'kotl-mode:yank))
+      (error "Previous command was not a yank"))
+  (setq this-command 'kotl-mode:yank)
+  (let ((before (< (point) (mark t))))
+    (delete-region (point) (mark t))
+    (set-marker (hypb:mark-marker t) (point) (current-buffer))
+    (let* ((yank-text (current-kill arg))
+	   (indent (kcell-view:indent))
+	   (indent-str (make-string indent ?\ )))
+      ;; Convert all occurrences of newline to newline + cell indent.
+      ;; Then insert into buffer.
+      (insert (hypb:replace-match-string
+	       "[\n\r]" yank-text (concat "\\0" indent-str))))
+    (if before
+	;; This is like exchange-point-and-mark, but doesn't activate the mark.
+	;; It is cleaner to avoid activation, even though the command
+	;; loop would deactivate the mark because we inserted text.
+	(goto-char (prog1 (mark t)
+		     (set-marker (hypb:mark-marker t) (point) (current-buffer))))))
+  nil)
+
+;;; ------------------------------------------------------------------------
+;;; Movement
+;;; ------------------------------------------------------------------------
+
+;;; Cursor and keypad key functions aliases for XEmacs.
+(if (not (string-match "XEmacs\\|Lucid" emacs-version))
+    nil
+  (fset 'kotl-mode:fkey-backward-char 'kotl-mode:backward-char)
+  (fset 'kotl-mode:fkey-forward-char  'kotl-mode:forward-char)
+  (fset 'kotl-mode:fkey-next-line     'kotl-mode:next-line)
+  (fset 'kotl-mode:fkey-previous-line 'kotl-mode:previous-line)
+  (fset 'kotl-mode:deprecated-scroll-down 'kotl-mode:scroll-down)
+  (fset 'kotl-mode:deprecated-scroll-up 'kotl-mode:scroll-up)
+  (fset 'kotl-mode:deprecated-bob     'kotl-mode:beginning-of-buffer)
+  (fset 'kotl-mode:deprecated-eob     'kotl-mode:end-of-buffer))
+
+(defun kotl-mode:back-to-indentation ()
+  "Move point to the first non-read-only non-whitespace character on this line."
+  (interactive)
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (back-to-indentation)
+  (kotl-mode:to-valid-position))
+
+(defun kotl-mode:backward-cell (arg)
+  "Move to prefix ARGth prior visible cell (same level) within current view.
+Return number of cells left to move."
+  (interactive "p")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (if (< arg 0)
+      (kotl-mode:forward-cell (- arg))
+    (let ((prior (= arg 0))
+	  (label-sep-len (kview:label-separator-length kview)))
+      (while (and (> arg 0) (setq prior (kcell-view:backward t label-sep-len)))
+	(setq arg (1- arg)))
+      (if (or prior (not (interactive-p)))
+	  arg
+	(error "(kotl-mode:backward-cell): No prior cell at same level")))))
+
+(defun kotl-mode:backward-char (&optional arg)
+  "Move point backward ARG (or 1) characters and return point."
+  (interactive "p")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (or arg (setq arg 1))
+  (if (>= arg 0)
+      (while (> arg 0)
+	(cond ((kotl-mode:bobp)
+	       (error "(kotl-mode:backward-char): Beginning of buffer"))
+	      ((kotl-mode:bocp)
+	       (if (kcell-view:previous)
+		   (kotl-mode:end-of-cell)))
+	      ((kotl-mode:bolp)
+	       (if (re-search-backward "[\n\r]" nil t)
+		   (kotl-mode:to-valid-position t)))
+	      (t (backward-char)))
+	(setq arg (1- arg)))
+    (kotl-mode:forward-char (- arg)))
+  (point))
+
+(defun kotl-mode:backward-paragraph (&optional arg)
+  "Move backward to start of paragraph.
+With arg N, do it N times; negative arg -N means move forward N paragraphs.
+Return point.
+
+A paragraph start is the beginning of a line which is a
+`first-line-of-paragraph' or which is ordinary text and follows a
+paragraph-separating line.
+
+See `forward-paragraph' for more information."
+  (interactive "p")
+  (setq arg (prefix-numeric-value arg)
+	zmacs-region-stays t);; Maintain region highlight for XEmacs.
+  (kotl-mode:forward-paragraph (- arg)))
+
+(fset 'kotl-mode:backward-para 'kotl-mode:backward-paragraph)
+
+(defun kotl-mode:backward-sentence (&optional arg)
+  "Move point backward ARG (or 1) sentences and return point."
+  (interactive "p")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (let* ((label-sep-len (kview:label-separator-length kview))
+	 ;; Setting fill prefix makes sentence commands properly recognize
+	 ;; indented paragraphs.
+	 (fill-prefix (make-string (kcell-view:indent nil label-sep-len) ?\ )))
+    (if (kotl-mode:bobp)
+	(error "(kotl-mode:backward-sentence): First sentence")
+      (if (and (kotl-mode:bocp) (kcell-view:previous nil label-sep-len))
+	  (goto-char (kcell-view:end-contents)))
+      (or arg (setq arg 1))
+      (save-restriction
+	(if (= arg 1)
+	    (narrow-to-region
+	     (- (kcell-view:start nil label-sep-len)
+		(kcell-view:indent nil label-sep-len))
+	     (kcell-view:end-contents)))
+	(unwind-protect
+	    (let ((opoint (point)))
+	      (backward-sentence arg)
+	      (if (= opoint (point))
+		  (progn (kcell-view:previous nil label-sep-len)
+			 (backward-sentence arg))))
+	  (kotl-mode:to-valid-position t)))))
+  (point))
+
+(defun kotl-mode:backward-word (&optional arg)
+  "Move point backward ARG (or 1) words and return point."
+  (interactive "p")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (or arg (setq arg 1))
+  (if (>= arg 0)
+      (while (> arg 0)
+	(cond ((kotl-mode:bobp) (setq arg 0))
+	      ((kotl-mode:bocp)
+	       (beginning-of-line)
+	       (kotl-mode:to-valid-position t)))
+	(unwind-protect
+	    (backward-word 1)
+	  (kotl-mode:to-valid-position t))
+	(setq arg (1- arg)))
+    (kotl-mode:forward-word (- arg)))
+  (point))
+
+(defun kotl-mode:beginning-of-buffer ()
+  "Move point to beginning of buffer and return point."
+  (interactive)
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (goto-char (point-min))
+  ;; To move to cell start.
+  (goto-char (kcell-view:start)))
+
+(defun kotl-mode:beginning-of-cell (&optional arg)
+  "Move point to beginning of current or ARGth - 1 prior cell and return point."
+  (interactive "p")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (or arg (setq arg 1))
+  (or (integer-or-marker-p arg)
+      (error "(kotl-mode:beginning-of-cell): Wrong type arg, integer-or-marker, '%s'" arg))
+  (if (= arg 1)
+      (goto-char (kcell-view:start))
+    (kotl-mode:backward-cell (1- arg)))
+  (point))
+
+;;; Avoid XEmacs byte-compiler bug which inserts nil for calls to this
+;;; function if named kotl-mode:beginning-of-line.
+;;;
+(defun kotl-mode:start-of-line (&optional arg)
+  "Move point to beginning of current or ARGth - 1 line and return point."
+  (interactive "p")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (or arg (setq arg 1))
+  (or (integer-or-marker-p arg)
+      (error "(kotl-mode:start-of-line): Wrong type arg, integer-or-marker, '%s'" arg))
+  (forward-line (1- arg))
+  (if (eolp)
+      nil
+    (forward-char (prog1 (kcell-view:indent)
+		    (beginning-of-line))))
+  (point))
+
+(defalias 'kotl-mode:beginning-of-line 'kotl-mode:start-of-line)
+
+(defun kotl-mode:beginning-of-tree ()
+  "Move point to the level 1 root of the current cell's tree.
+Leave point at the start of the cell."
+  (interactive)
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (let ((label-sep-len (kview:label-separator-length kview)))
+    (if (/= (kcell-view:level nil label-sep-len) 1)
+	;; Enable user to return to this previous position if desired.
+	(push-mark nil 'no-msg))
+    (while (and (/= (kcell-view:level nil label-sep-len) 1)
+		(kcell-view:parent nil label-sep-len)))
+    (kotl-mode:beginning-of-cell)))
+
+(defun kotl-mode:down-level (arg)
+  "Move down prefix ARG levels lower within current tree."
+  (interactive "p")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (if (< arg 0)
+      (kotl-mode:up-level (- arg))
+    ;; Enable user to return to this previous position if desired.
+    (push-mark nil 'no-msg)
+    (let ((child))
+      (while (and (> arg 0) (kcell-view:child))
+	(or child (setq child t))
+	(setq arg (1- arg)))
+      ;; Signal an error if couldn't move down at least 1 child level.
+      (or child
+	  (progn
+	    (goto-char (hypb:mark t))
+	    (pop-mark)
+	    (error "(kotl-mode:down-level): No child level to which to move")
+	    )))))
+
+(defun kotl-mode:end-of-buffer ()
+  "Move point to end of buffer and return point."
+  (interactive)
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (goto-char (point-max))
+  ;; To move to cell end.
+  (kotl-mode:to-valid-position t)
+  (point))
+
+(defun kotl-mode:end-of-cell (&optional arg)
+  "Move point to end of current or ARGth - 1 succeeding cell and return point."
+  (interactive "p")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (or arg (setq arg 1))
+  (or (integer-or-marker-p arg)
+      (error "(kotl-mode:end-of-cell): Wrong type arg, integer-or-marker, '%s'" arg))
+  (if (= arg 1)
+      (goto-char (kcell-view:end-contents))
+    (kotl-mode:forward-cell (1- arg)))
+  (point))
+
+;;; Avoid XEmacs byte-compiler bug which inserts nil for calls to this
+;;; function if named kotl-mode:end-of-line.
+;;;
+(defun kotl-mode:finish-of-line (&optional arg)
+  "Move point to end of current or ARGth - 1 line and return point."
+  (interactive "p")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (or arg (setq arg 1))
+  (or (integer-or-marker-p arg)
+      (error "(kotl-mode:finish-of-line): Wrong type arg, integer-or-marker, '%s'" arg))
+  (forward-line (1- arg))
+  (end-of-line)
+  ;; May have to move backwards to before label if support labels
+  ;; at end of cells.
+  (point))
+
+(defalias 'kotl-mode:end-of-line 'kotl-mode:finish-of-line)
+
+(defun kotl-mode:end-of-tree ()
+  "Move point to the last cell in tree rooted at the current cell.
+Leave point at the start of the cell."
+  (interactive)
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  ;; Enable user to return to this previous position if desired.
+  (push-mark nil 'no-msg)
+  (let ((label-sep-len (kview:label-separator-length kview)))
+    (if (kcell-view:forward nil label-sep-len)
+	;; Move to cell preceding start of next tree.
+	(kcell-view:previous nil label-sep-len)
+      ;; Otherwise, no next tree, so move until find last cell in tree.
+      (let ((cell-indent (kcell-view:indent nil label-sep-len))
+	    (end-point (point)))
+	;; Terminate when no further cells or when reach a cell at an equal
+	;; or higher level in the outline than the first cell that we
+	;; processed.
+	(while (and (kcell-view:next nil label-sep-len)
+		    (> (kcell-view:indent nil label-sep-len) cell-indent))
+	  (setq end-point (point)))
+	(goto-char end-point)))
+    (kotl-mode:beginning-of-cell)))
+
+(defun kotl-mode:first-sibling ()
+  "Move point to the first sibling of the present cell.
+Leave point at the start of the cell or at its present position if it is
+already within the first sibling cell."
+  (interactive)
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (let ((label-sep-len (kview:label-separator-length kview)))
+    (if (save-excursion (kcell-view:backward nil label-sep-len))
+	;; Enable user to return to this previous position if desired.
+	(push-mark nil 'no-msg))
+    (while (kcell-view:backward nil label-sep-len))))
+
+(defun kotl-mode:forward-cell (arg)
+  "Move to prefix ARGth following cell (same level) within current view.
+Return number of cells left to move."
+  (interactive "p")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (if (< arg 0)
+      (kotl-mode:backward-cell (- arg))
+    (let ((next (= arg 0))
+	  (label-sep-len (kview:label-separator-length kview)))
+      (while (and (> arg 0) (setq next (kcell-view:forward t label-sep-len)))
+	(setq arg (1- arg)))
+      (if (or next (not (interactive-p)))
+	  arg
+	(error "(kotl-mode:forward-cell): No following cell at same level")))))
+
+(defun kotl-mode:forward-char (&optional arg)
+  "Move point forward ARG (or 1) characters and return point."
+  (interactive "p")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (or arg (setq arg 1))
+  (if (>= arg 0)
+      (while (> arg 0)
+	(cond ((and (kotl-mode:eolp) (kotl-mode:last-line-p))
+	       (error "(kotl-mode:forward-char): End of buffer"))
+	      ((kotl-mode:eocp)
+	       (skip-chars-forward "\n\r")
+	       (kotl-mode:start-of-line))
+	      ((kotl-mode:eolp)
+	       (forward-char)
+	       (kotl-mode:start-of-line))
+	      (t (forward-char)))
+	(setq arg (1- arg)))
+    (kotl-mode:backward-char (- arg)))
+  (point))
+
+(defun kotl-mode:forward-paragraph (&optional arg)
+  "Move point forward until after the last character of the current paragraph.
+With arg N, do it N times; negative arg -N means move backward N paragraphs.
+Return point.
+
+A line which `paragraph-start' matches either separates paragraphs
+\(if `paragraph-separate' matches it also) or is the first line of a paragraph.
+A paragraph end is one character before the beginning of a line which is not
+part of the paragraph, or the end of the buffer."
+  (interactive "p")
+  (setq arg (prefix-numeric-value arg)
+	zmacs-region-stays t);; Maintain region highlight for XEmacs.
+  (if (< arg 0)
+      (progn
+	(if (kotl-mode:bocp) (setq arg (1- arg)))
+	(while (< arg 0)
+	  (start-of-paragraph-text)
+	  (setq arg (1+ arg))))
+    (while (> arg 0) 
+      (end-of-paragraph-text)
+      (setq arg (1- arg))))
+  (kotl-mode:to-valid-position)
+  (point))
+
+(fset 'kotl-mode:forward-para 'kotl-mode:forward-paragraph)
+
+(defun kotl-mode:forward-sentence (&optional arg)
+  "Move point forward ARG (or 1) sentences and return point."
+  (interactive "P")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (let* ((label-sep-len (kview:label-separator-length kview))
+	 ;; Setting fill prefix makes sentence commands properly recognize
+	 ;; indented paragraphs.
+	 (fill-prefix (make-string (kcell-view:indent nil label-sep-len) ?\ )))
+    (if (kotl-mode:eobp)
+	(error "(kotl-mode:forward-sentence): Last sentence")
+      (if (kotl-mode:eocp) (kcell-view:next nil label-sep-len))
+      (or arg (setq arg 1))
+      (save-restriction
+	(if (= arg 1)
+	    (narrow-to-region
+	     (- (kcell-view:start nil label-sep-len)
+		(kcell-view:indent nil label-sep-len))
+	     (kcell-view:end-contents)))
+	(unwind-protect
+	    (let ((opoint (point)))
+	      (forward-sentence arg)
+	      (if (= opoint (point))
+		  (progn (kcell-view:next nil label-sep-len)
+			 (forward-sentence arg))))
+	  (kotl-mode:to-valid-position)))))
+  (point))
+
+(defun kotl-mode:forward-word (&optional arg)
+  "Move point forward ARG (or 1) words and return point."
+  (interactive "p")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (or arg (setq arg 1))
+  (if (>= arg 0)
+      (while (> arg 0)
+	(cond ((kotl-mode:eobp) (setq arg 0))
+	      ((kotl-mode:eocp)
+	       (skip-chars-forward "\n\r")
+	       (kotl-mode:start-of-line)))
+	(unwind-protect
+	    (forward-word 1)
+	  (kotl-mode:to-valid-position))
+	;; If point is at beginning of a cell after moving forward a word,
+	;; then we moved over something other than a word (some
+	;; punctuation or an outline autonumber); therefore, leave counter as
+	;; is in order to move forward over next word.
+	(or (kotl-mode:bocp)
+	    (setq arg (1- arg))))
+    (kotl-mode:backward-word (- arg)))
+  (point))
+
+(defun kotl-mode:goto-cell (cell-ref &optional error-p)
+  "Move point to start of cell given by CELL-REF.  (See 'kcell:ref-to-id'.)
+Return point iff CELL-REF is found within current view.
+With a prefix argument, CELL-REF is assigned the argument value for use
+as an idstamp.
+
+Optional second arg, ERROR-P, non-nil means signal an error if CELL-REF is
+not found within current view.  Will signal same error if called
+interactively when CELL-REF is not found."
+  (interactive
+   (list (if current-prefix-arg
+	     (format "0%d" (prefix-numeric-value current-prefix-arg))
+	   (read-string "Goto cell label or id: "))))
+  (setq cell-ref
+	(or (kcell:ref-to-id cell-ref)
+	    (error "(kotl-mode:goto-cell): Invalid cell reference, '%s'" cell-ref)))
+  (let* ((opoint (point))
+	 (found)
+	 cell-id kvspec)
+    (if (= ?| (aref cell-ref 0))
+	;; This is a standalone view spec, not a cell reference.
+	(progn (kvspec:activate cell-ref) (setq found (point)))
+
+      ;; !! Remove any relative specs and view specs from
+      ;; cell-ref to form cell-id.  Really should account for relative
+      ;; specs here, but we don't yet support them.
+      (if (string-match "\\(\\.[a-zA-Z]+\\)?\\([|:].*\\)\\|\\.[a-zA-Z]+"
+			cell-ref)
+	  (setq cell-id (substring cell-ref 0 (match-beginning 0))
+		kvspec  (if (match-beginning 2)
+			    (substring
+			     cell-ref (match-beginning 2) (match-end 2))))
+	(setq cell-id cell-ref kvspec nil))
+
+      (goto-char (point-min))
+      (cond ((= ?0 (aref cell-id 0))
+	     ;; is an idstamp
+	     (if (kview:goto-cell-id cell-id)
+		 (setq found (point))))
+	    ;; is a label
+	    ((re-search-forward
+	      (format "\\([\n\r][\n\r]\\|\\`\\)[ ]*%s%s"
+		      (regexp-quote cell-id)
+		      (regexp-quote (kview:label-separator kview)))
+	      nil t)
+	     (setq found (point)))
+	    ;; no match
+	    (t (goto-char opoint)
+	       nil))
+      (if (and (not found) (or error-p (interactive-p)))
+	  (error "(kotl-mode:goto-cell): No '%s' cell in this view" cell-ref)
+	;; Activate any viewspec associated with cell-ref.
+	(if kvspec (kvspec:activate kvspec))))
+    found))
+
+(defun kotl-mode:head-cell ()
+  "Move point to the start of the first visible cell at the same level as current cell.
+If at head cell already, do nothing and return nil."
+  (interactive "p")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (let ((moved)
+	(label-sep-len (kview:label-separator-length kview)))
+    (while (kcell-view:backward t label-sep-len)
+      (setq moved t))
+    moved))
+
+(defun kotl-mode:last-sibling ()
+  "Move point to the last sibling of the present cell.
+Leave point at the start of the cell or at its present position if it is
+already within the last sibling cell."
+  (interactive)
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (let ((label-sep-len (kview:label-separator-length kview)))
+    (if (save-excursion (kcell-view:forward nil label-sep-len))
+	;; Enable user to return to this previous position if desired.
+	(push-mark nil 'no-msg))
+    (while (kcell-view:forward nil label-sep-len))))
+
+(defun kotl-mode:mark-paragraph ()
+  "Put point at beginning of this paragraph, mark at end.
+The paragraph marked is the one that contains point or follows point."
+  (interactive)
+  (forward-paragraph 1)
+  (kotl-mode:to-valid-position t)
+  (hypb:push-mark nil t t)
+  (backward-paragraph 1)
+  (kotl-mode:to-valid-position))
+
+(defun kotl-mode:mark-whole-buffer ()
+  "Put point at first editable character in buffer and mark at the last such character."
+  (interactive)
+  (hypb:push-mark (point))
+  (kotl-mode:end-of-buffer)
+  (hypb:push-mark (point) nil t)
+  (kotl-mode:beginning-of-buffer))
+
+(defun kotl-mode:next-cell (arg)
+  "Move to prefix ARGth next cell (any level) within current view."
+  (interactive "p")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (if (< arg 0)
+      (kotl-mode:previous-cell (- arg))
+    (let ((next (= arg 0))
+	  (label-sep-len (kview:label-separator-length kview)))
+      (while (and (> arg 0) (setq next (kcell-view:next t label-sep-len)))
+	(setq arg (1- arg)))
+      (if next
+	  arg
+	(error "(kotl-mode:next-cell): Last cell")))))
+
+(defun kotl-mode:next-line (arg)
+  "Move point to ARGth next line and return point."
+  (interactive "p")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (kotl-mode:set-temp-goal-column)
+  (let ((orig-arg arg))
+    (cond ((> arg 0)
+	   (while (and (> arg 0) (= 0 (forward-line 1)))
+	     (cond ((kotl-mode:eobp)
+		    (forward-line -1)
+		    (goto-char (kcell-view:end-contents))
+		    (and (interactive-p) (= orig-arg arg)
+			 (message "(kotl-mode:next-line): Last line") (beep))
+		    (setq arg 0)
+		    )
+		   ((looking-at "^$");; blank line between cells
+		    nil);; Don't count this line.
+		   (t (setq arg (1- arg)))))
+	   (kotl-mode:line-move 0)
+	   (kotl-mode:to-valid-position)
+	   )
+	  ((< arg 0)
+	   (kotl-mode:previous-line (- arg)))
+	  (t)))
+  (setq this-command 'next-line)
+  (point))
+
+(defun kotl-mode:next-tree ()
+  "Move past current tree to next tree, or to last cell in tree if no next tree.
+Return non-nil iff there is a next tree within this koutline."
+  (let ((start-indent (kcell-view:indent))
+	(label-sep-len (kview:label-separator-length kview))
+	(same-tree t))
+      (while (and (kcell-view:next nil label-sep-len)
+		  (setq same-tree (< start-indent
+				     (kcell-view:indent nil label-sep-len)))))
+      (not same-tree)))
+
+(defun kotl-mode:previous-line (arg)
+  "Move point to ARGth previous line and return point."
+  (interactive "p")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (kotl-mode:set-temp-goal-column)
+  (cond ((> arg 0)
+         (while (and (> arg 0) (= 0 (forward-line -1)))
+           (cond ((kotl-mode:bobp)
+                  (kotl-mode:beginning-of-cell)
+                  (setq arg 0))
+                 ((looking-at "^$") ;; blank line between cells
+                  nil) ;; Don't count this line.
+                 (t (setq arg (1- arg)))))
+	 (kotl-mode:line-move 0)
+	 (kotl-mode:to-valid-position)
+	 )
+        ((< arg 0)
+         (kotl-mode:next-line (- arg)))
+        (t))
+  (setq this-command 'previous-line)
+  (point))
+
+(defun kotl-mode:previous-cell (arg)
+  "Move to prefix ARGth previous cell (any level) within current view."
+  (interactive "p")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (if (< arg 0)
+      (kotl-mode:next-cell (- arg))
+    (let ((previous (= arg 0))
+	  (label-sep-len (kview:label-separator-length kview)))
+      (while (and (> arg 0) (setq previous
+				  (kcell-view:previous t label-sep-len)))
+	(setq arg (1- arg)))
+      (if previous
+	  arg
+	(error "(kotl-mode:previous-cell): First cell")))))
+
+(defun kotl-mode:scroll-down (arg)
+  "Scroll text of current window downward ARG lines; or a windowful if no ARG."
+  (interactive "P")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (scroll-down arg)
+  (kotl-mode:to-valid-position t))
+
+(defun kotl-mode:scroll-up (arg)
+  "Scroll text of current window upward ARG lines; or a windowful if no ARG."
+  (interactive "P")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (scroll-up arg)
+  (kotl-mode:to-valid-position))
+
+(defun kotl-mode:tail-cell ()
+  "Move point to the start of the last visible cell at the same level as current cell and return t.
+If at tail cell already, do nothing and return nil."
+  (interactive "p")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (let ((moved)
+	(label-sep-len (kview:label-separator-length kview)))
+    (while (kcell-view:forward t label-sep-len)
+      (setq moved t))
+    moved))
+
+(defun kotl-mode:up-level (arg)
+  "Move up prefix ARG levels higher in current outline view."
+  (interactive "p")
+  (setq zmacs-region-stays t) ;; Maintain region highlight for XEmacs.
+  (if (< arg 0)
+      (kotl-mode:down-level (- arg))
+    ;; Enable user to return to this previous position if desired.
+    (push-mark nil 'no-msg)
+    (let ((parent)
+	  (label-sep-len (kview:label-separator-length kview))
+	  result)
+      (while (and (> arg 0) (setq result (kcell-view:parent t label-sep-len)))
+	(or parent (setq parent result))
+	(setq arg (if (eq result 0) 0 (1- arg))))
+      ;; Signal an error if couldn't move up at least 1 parent level.
+      (or (and parent (not (eq parent 0)))
+	  (progn
+	    (goto-char (hypb:mark t))
+	    (pop-mark)
+	    (error "(kotl-mode:up-level): No parent level to which to move")
+	    )))))
+
+;;; ------------------------------------------------------------------------
+;;; Predicates
+;;; ------------------------------------------------------------------------
+
+(defun kotl-mode:bobp ()
+  "Return point if at the start of the first cell in kview, else nil."
+  (interactive)
+  (or (bobp)
+      (and (not (save-excursion (re-search-backward "[\n\r]" nil t)))
+	   (kotl-mode:bolp))))
+
+(defun kotl-mode:bocp ()
+  "Return point if at beginning of a kcell, else nil."
+  (and (kotl-mode:bolp)
+       (let ((begin-point (kcell-view:plist-point))
+	     (bol))
+	 (and begin-point
+	      (save-excursion
+		;; If first line-begin is less than cell begin point,
+		;; then we know we are on the first line of the cell.
+		(if (setq bol (re-search-backward "^" nil t))
+		    (<= bol begin-point)))))
+       (point)))
+
+(defun kotl-mode:bolp ()
+  "Return point if at beginning of a kview line, else nil."
+  (if (= (current-column) (kcell-view:indent))
+      (point)))
+
+(defun kotl-mode:buffer-empty-p ()
+  "Return non-nil iff there are no outline cells within current buffer."
+  (save-excursion
+    (goto-char (point-min))
+    (looking-at "[\n\r]*\\'")))
+
+(defun kotl-mode:eobp ()
+  "Return point if after the end of the last cell in kview, else nil."
+  (interactive)
+  (if (looking-at "^[\n\r]*\\'") (point)))
+
+(defun kotl-mode:eocp ()
+  "Return point if at the end of a kview cell, else nil."
+  (or (eobp)
+      (looking-at "[\n\r]+\\'")
+      (and (eolp)
+	   (save-excursion
+	     (skip-chars-forward "\n\r")
+	     (kotl-mode:start-of-line)
+	     (kotl-mode:bocp)))))
+
+(fset 'kotl-mode:eolp 'eolp)
+
+(defun kotl-mode:first-cell-p ()
+  "Return t iff point is on the first cell of the outline."
+  (save-excursion (not (kcell-view:previous))))
+
+(fset 'kotl-mode:first-line-p 'first-line-p)
+
+(defun kotl-mode:last-cell-p ()
+  "Return t iff point is on the last cell of the outline."
+  (save-excursion (not (kcell-view:next))))
+
+(defun kotl-mode:last-line-p ()
+  "Return t iff point is on the last line of the outline."
+  (save-excursion
+    (kotl-mode:finish-of-line)
+    (looking-at "\n*\\'")))
+
+;;; ------------------------------------------------------------------------
+;;; Smart Key Support
+;;; ------------------------------------------------------------------------
+
+
+(defun kotl-mode:action-key ()
+  "Collapses, expands, links to, and scrolls through koutline cells.
+Invoked via a key press when in kotl-mode.  It assumes that its caller has
+already checked that the key was pressed in an appropriate buffer and has
+moved the cursor to the selected buffer.
+
+If key is pressed:
+ (1) at the end of buffer, uncollapse and unhide all cells in view;
+ (2) within a cell, if its subtree is hidden then show it,
+     otherwise hide it;
+ (3) between cells or within the read-only indentation region to the left of
+     a cell, then move point to prior location and begin creation of a
+     klink to some other outline cell; hit the Action Key twice to select the
+     link referent cell;
+ (4) anywhere else, scroll up a windowful."
+  (interactive)
+  (cond	((kotl-mode:eobp) (kotl-mode:show-all))
+	((kotl-mode:eolp) (smart-scroll-up))
+	((not (kview:valid-position-p))
+	 (if (markerp action-key-depress-prev-point)
+	     (progn (select-window
+		     (get-buffer-window
+		      (marker-buffer action-key-depress-prev-point)))
+		    (goto-char (marker-position action-key-depress-prev-point))
+		    (call-interactively 'klink:create))
+	   (kotl-mode:to-valid-position)
+	   (error "(kotl-mode:action-key): Action Key released at invalid position")))
+	(t ;; On a cell line (not at the end of line).
+	 (if (smart-outline-subtree-hidden-p)
+	     (kotl-mode:show-tree (kcell-view:label))
+	   (kotl-mode:hide-tree (kcell-view:label)))))
+  (kotl-mode:to-valid-position))
+
+(defun kotl-mode:help-key ()
+  "Displays properties of koutline cells, collapses all cells, and scrolls back.
+Invoked via an assist-key press when in kotl-mode.  It assumes that its caller
+has already checked that the assist-key was pressed in an appropriate buffer
+and has moved the cursor to the selected buffer.
+
+If assist-key is pressed:
+ (1) at the end of buffer, collapse all cells and hide all non-level-one
+     cells;
+ (2) on a header line but not at the beginning or end, display properties of
+     each cell in tree beginning at point;
+ (3) between cells or within the read-only indentation region to the left of
+     a cell, then move point to prior location and prompt to move one tree to
+     a new location in the outline; hit the Action Key twice to select the
+     tree to move and where to move it;
+ (4) anywhere else, scroll down a windowful."
+  (interactive)
+  (cond ((kotl-mode:eobp) (kotl-mode:overview))
+	((kotl-mode:eolp) (smart-scroll-down))
+	((not (kview:valid-position-p))
+	 (if (markerp assist-key-depress-prev-point)
+	     (progn (select-window
+		     (get-buffer-window
+		      (marker-buffer assist-key-depress-prev-point)))
+		    (goto-char (marker-position
+				assist-key-depress-prev-point))
+		    (call-interactively 'kotl-mode:move-after))
+	   (kotl-mode:to-valid-position)
+	   (error "(kotl-mode:help-key): Help Key released at invalid position")))
+	((not (bolp))
+	 ;; On an outline header line but not at the start/end of line,
+	 ;; show attributes for tree at point.
+	 (kotl-mode:cell-help (kcell-view:label) (or current-prefix-arg 2)))
+	((smart-scroll-down)))
+  (kotl-mode:to-valid-position))
+
+;;; ------------------------------------------------------------------------
+;;; Structure Editing
+;;; ------------------------------------------------------------------------
+
+(defun kotl-mode:add-child ()
+  "Add a new cell to current kview as first child of current cell."
+  (interactive "*")
+  (kotl-mode:add-cell '(4)))
+
+(defun kotl-mode:add-parent ()
+  "Add a new cell to current kview as sibling of current cell's parent."
+  (interactive "*")
+  (kotl-mode:add-cell -1))
+
+(defun kotl-mode:add-cell (&optional relative-level contents plist no-fill)
+  "Add a cell following current cell at optional RELATIVE-LEVEL with CONTENTS string, attributes in PLIST, a property list, and NO-FILL flag to prevent any filling of CONTENTS. 
+
+Optional prefix arg RELATIVE-LEVEL means add as sibling if nil or >= 0, as
+child if equal to universal argument, {C-u}, and as sibling of current cell's
+parent, otherwise.  If added as sibling of current level, RELATIVE-LEVEL is
+used as a repeat count for the number of cells to add.
+
+Return last newly added cell."
+  (interactive "*P")
+  (or (stringp contents) (setq contents nil))
+  (let ((klabel (kcell-view:label))
+	(label-sep-len (kview:label-separator-length kview))
+	cell-level new-cell sibling-p child-p start parent
+	cells-to-add)
+    (setq cell-level (kcell-view:level nil label-sep-len)
+	  child-p (equal relative-level '(4))
+	  sibling-p (and (not child-p)
+			 (cond ((not relative-level) 1)
+			       ((>= (prefix-numeric-value relative-level) 0)
+				(prefix-numeric-value relative-level))))
+	  cells-to-add (or sibling-p 1))
+    (if child-p
+	(setq cell-level (1+ cell-level))
+      (if sibling-p
+	  nil
+	;; Add as following sibling of current cell's parent.
+	;; Move to parent.
+	(setq cell-level (1- cell-level)
+	      start (point)
+	      parent (kcell-view:parent nil label-sep-len))
+	(if (not (eq parent t))
+	    (progn
+	      (goto-char start)
+	      (error
+	       "(kotl-mode:add-cell): No higher level at which to add cell.")
+	      )))
+      ;; Skip from point past any children to next cell.
+      (if (kotl-mode:next-tree)
+	  ;; If found a new tree, then move back to prior cell so can add
+	  ;; new cell after it.
+	  (kcell-view:previous nil label-sep-len)))
+    (goto-char (kcell-view:end))
+    ;;
+    ;; Insert new cells into view.
+    (if (= cells-to-add 1)
+	(setq klabel
+	      (cond (sibling-p
+		     (klabel:increment klabel))
+		    (child-p (klabel:child klabel))
+		    ;; add as sibling of parent of current cell
+		    (t (klabel:increment (klabel:parent klabel))))
+	      new-cell (kview:add-cell klabel cell-level contents plist
+				       (or no-fill sibling-p
+					   (not kotl-mode:refill-flag))))
+      ;;
+      ;; sibling-p must be true if we are looping here so there is no need to
+      ;; conditionalize how to increment the labels.
+      (while (>= (setq cells-to-add (1- cells-to-add)) 0)
+	(setq klabel (klabel:increment klabel)
+	      ;; Since new cells are at the same level as old one, don't fill
+	      ;; any of their intial contents.
+	      new-cell (kview:add-cell klabel cell-level contents plist t))))
+    ;;
+    ;; Move back to last inserted cell and then move to its following
+    ;; sibling if any.
+    (kotl-mode:to-valid-position t)
+    (save-excursion
+      (if (kcell-view:forward t label-sep-len)
+	  ;; Update the labels of these siblings and their subtrees.
+	  (klabel-type:update-labels (klabel:increment klabel))))
+    ;;
+    ;; Leave point within last newly added cell and return this cell.
+    (kotl-mode:beginning-of-cell)
+    new-cell))
+
+(defun kotl-mode:demote-tree (arg)
+  "Move current tree a maximum of prefix ARG levels lower in current view.
+Each cell is refilled iff its `no-fill' attribute is nil and
+kotl-mode:refill-flag is non-nil.  With prefix ARG = 0, cells are demoted up
+to one level and kotl-mode:refill-flag is treated as true."
+  (interactive "*p")
+  (if (< arg 0)
+      (kotl-mode:promote-tree (- arg))
+    (let* ((label-sep-len (kview:label-separator-length kview))
+	   (orig-level (kcell-view:level nil label-sep-len))
+	   (orig-point (point))
+	   (orig-id (kcell-view:idstamp))
+	   (fill-p (= arg 0))
+	   (orig-pos-in-cell
+	    (- (point) (kcell-view:start nil label-sep-len)))
+	   prev prev-level)
+      (if fill-p (setq arg 1))
+      (unwind-protect
+	  (progn
+	    (backward-char 1)
+	    (while (and (> arg 0)
+			(setq prev
+			      (kcell-view:previous nil label-sep-len)))
+	      (if prev
+		  (progn (setq prev-level
+			       (kcell-view:level nil label-sep-len))
+			 (cond ((> prev-level (+ orig-level arg))
+				;; Don't want to demote this far
+				;; so keep looking at prior nodes.
+				nil)
+			       ((= arg (- prev-level orig-level))
+				;; Demote to be sibling of this kcell.
+				(setq arg -1))
+			       ((< prev-level orig-level)
+				;; prev is at higher level then
+				;; orig, so can't demote
+				(setq prev nil
+				      arg 0))
+			       (t
+				;; Demote below this kcell.  This is
+				;; as far we can demote, though it may
+				;; not be the full amount of arg.
+				(setq arg 0))))))
+	    (if prev
+		(kotl-mode:move-after
+		 (kcell-view:label orig-point)
+		 (kcell-view:label) (= arg 0)
+		 nil fill-p)))
+	;; Move to start of original cell
+	(kotl-mode:goto-cell orig-id)
+	;; Move to original pos within cell
+	(forward-char orig-pos-in-cell)
+	(kotl-mode:to-valid-position))
+      (if (not prev)
+	  (error "(kotl-mode:demote-tree): Cannot demote any further")))))
+
+(defun kotl-mode:exchange-cells (cell-ref-1 cell-ref-2)
+  "Exchange CELL-REF-1 with CELL-REF-2 in current view.  Don't move point."
+  (interactive
+   (let ((hargs:defaults
+	  (save-excursion
+	    (list (kcell-view:label)
+		  (cond
+		   ((kcell-view:previous t)
+		    (kcell-view:label))
+		   ((kcell-view:next t)
+		    (kcell-view:label))
+		   (t (error
+		       "(kotl-mode:exchange-cells): No 2 visible cells")))))))
+     (hargs:iform-read
+      '(interactive "*+KExchange cell: \n+KExchange cell <%s> with cell: "))))
+  (save-excursion
+    (let (kcell-1 contents-1
+	  kcell-2 contents-2)
+      ;;
+      ;; Save cell-1 attributes
+      (kotl-mode:goto-cell cell-ref-1 t)
+      (setq kcell-1 (kcell-view:cell)
+	    contents-1 (kcell-view:contents))
+      ;;
+      ;; Save cell-2 attributes
+      (kotl-mode:goto-cell cell-ref-2 t)
+      (setq kcell-2 (kcell-view:cell)
+	    contents-2 (kcell-view:contents))
+      ;;
+      ;; Substitute cell-1 attributes into cell-2 location.
+      ;;
+      ;; Set kcell properties.
+      (kcell-view:set-cell kcell-1)
+      ;; If idstamp labels are on, then must exchange labels in view.
+      (if (eq (kview:label-type kview) 'id)
+	  (klabel:set (kcell-view:idstamp)))
+      ;; Exchange cell contents.
+      (delete-region (kcell-view:start) (kcell-view:end-contents))
+      (insert
+       (hypb:replace-match-string
+	"\\([\n\r]\\)"
+	contents-1 (concat "\\1" (make-string (kcell-view:indent) ?\ ))))
+      (if kotl-mode:refill-flag (kotl-mode:fill-cell))
+      ;;
+      ;; Substitute cell-2 attributes into cell-1 location.
+      ;;
+      ;; Set kcell properties.
+      (kotl-mode:goto-cell cell-ref-1 t)
+      (kcell-view:set-cell kcell-2)
+      ;; If idstamp labels are on, then must exchange labels in view.
+      (if (eq (kview:label-type kview) 'id)
+	  (klabel:set (kcell-view:idstamp)))
+      ;; Exchange cell contents.
+      (delete-region (kcell-view:start) (kcell-view:end-contents))
+      ;; Add indentation to all but first line.
+      (insert
+       (hypb:replace-match-string
+	"\\([\n\r]\\)"
+	contents-2 (concat "\\1" (make-string (kcell-view:indent) ?\ ))))
+      (if kotl-mode:refill-flag (kotl-mode:fill-cell)))))
+
+(defun kotl-mode:kill-contents (arg)
+  "Kill contents of cell from point to cell end.
+With prefix ARG, kill entire cell contents."
+  (interactive "*P")
+  (kotl-mode:kill-region
+   (if arg (kcell-view:start) (point))
+   (kcell-view:end-contents)))
+
+(defun kotl-mode:kill-tree (&optional arg)
+  "Kill ARG following trees starting with tree rooted at point.
+If ARG is not a non-positive number, nothing is done."
+  (interactive "*p")
+  (or (integerp arg) (setq arg 1))
+  (let ((killed) (label (kcell-view:label))
+	(label-sep-len (kview:label-separator-length kview))
+	start end sib)
+    (while (> arg 0)
+      (setq start (kotl-mode:tree-start)
+	    end   (kotl-mode:tree-end)
+	    sib   (kcell-view:sibling-p nil nil label-sep-len)
+	    arg (1- arg)
+	    killed t)
+      ;; Don't want to delete any prior cells, so if on last cell, ensure
+      ;; this is the last one killed.
+      (if (kotl-mode:last-cell-p)
+	  (progn (setq arg 0)
+		 (kview:delete-region start end))
+	(kview:delete-region start end)
+	(kotl-mode:to-valid-position)))
+    (if killed
+	(progn
+	  (cond (sib (klabel-type:update-labels label))
+		((kotl-mode:buffer-empty-p)
+		 ;; Always leave at least 1 visible cell within a view.
+		 (kview:add-cell "1" 1)))
+	  (kotl-mode:to-valid-position)))))
+
+(defun kotl-mode:mail-tree (cell-ref invisible-flag)
+  "Mail outline tree rooted at CELL-REF.  Use \"0\" for whole outline buffer.
+Invisible text is expanded and included in the mail only if INVISIBLE-FLAG is
+non-nil."
+  (interactive
+   (let ((label-default (kcell-view:label)))
+     (hargs:iform-read
+      '(interactive
+	(list 
+	 (hargs:read "Mail tree: (0 for whole outline) "
+		     nil label-default nil 'kcell)
+	 (y-or-n-p "Include invisible text? "))))))
+  (if (equal cell-ref "0")
+      (hmail:buffer nil invisible-flag)
+    (let (start end)
+      (save-excursion
+	(kotl-mode:goto-cell cell-ref t)
+	(beginning-of-line)
+	(setq start (point))
+	(or (= (kotl-mode:forward-cell 1) 0) (goto-char (point-max)))
+	(forward-line -1)
+	(setq end (point)))
+      (hmail:region start end nil invisible-flag))))
+
+(defun kotl-mode:promote-tree (arg)
+  "Move current tree a maximum of prefix ARG levels higher in current view.
+Each cell is refilled iff its `no-fill' attribute is nil and
+kotl-mode:refill-flag is non-nil.  With prefix ARG = 0, cells are promoted up
+to one level and kotl-mode:refill-flag is treated as true."
+  (interactive "*p")
+  (if (< arg 0)
+      (kotl-mode:demote-tree (- arg))
+    (let* ((parent) (result)
+	   (label-sep-len (kview:label-separator-length kview))
+	   (orig-point (point))
+	   (orig-id (kcell-view:idstamp))
+	   (fill-p (= arg 0))
+	   (orig-pos-in-cell
+	    (- (point) (kcell-view:start nil label-sep-len))))
+      (if fill-p (setq arg 1))
+      (unwind-protect
+	  (progn
+	    (backward-char 1)
+	    (while (and (> arg 0)
+			(setq result (kcell-view:parent
+				      nil label-sep-len))
+			(not (eq result 0)))
+	      (setq parent result
+		    arg (1- arg)))
+	    (if parent
+		(kotl-mode:move-after
+		 (kcell-view:label orig-point)
+		 (kcell-view:label) nil
+		 nil fill-p)))
+	;; Move to start of original cell
+	(kotl-mode:goto-cell orig-id)
+	;; Move to original pos within cell
+	(forward-char orig-pos-in-cell)
+	(kotl-mode:to-valid-position))
+      (if (not parent)
+	  (error "(kotl-mode:promote-tree): Cannot promote any further")))))
+
+(defun kotl-mode:set-cell-attribute (attribute value &optional pos)
+  "Include ATTRIBUTE VALUE with the current cell or the cell at optional POS.
+Replaces any existing value that ATTRIBUTE has.
+When called interactively, it displays the setting in the minibuffer as
+confirmation."
+  (interactive
+   (let* ((plist (copy-sequence (kcell-view:plist)))
+	  (existing-attributes plist)
+	  attribute value)
+     (barf-if-buffer-read-only)
+     ;; Remove attribute values
+     (while plist
+       (setcdr plist (cdr (cdr plist)))
+       (setq plist (cdr plist)))
+     ;; Remove read-only attributes
+     (setq existing-attributes (set:create existing-attributes)
+	   existing-attributes (set:difference
+				existing-attributes
+				kcell:read-only-attributes))
+
+     (while (zerop (length (setq attribute
+				 (completing-read
+				  "Current cell attribute to set: "
+				  (mapcar 'list
+					  (mapcar 'symbol-name
+						  existing-attributes))))))
+       (beep))
+     (setq attribute (intern attribute)
+	   value (kcell-view:get-attr attribute))
+     (if value
+	 (setq value (read-expression
+		      (format "Change value of \"%s\" to: " attribute)
+		      (prin1-to-string value)))
+       (setq value (read-expression
+		    (format "Set value of \"%s\" to: " attribute))))
+     (list attribute value nil)))
+  (kcell-view:set-attr attribute value pos)
+  ;; Note that buffer needs to be saved to store new attribute value.
+  (set-buffer-modified-p t)
+  (if (interactive-p)
+      (message "Attribute \"%s\" set to `%s' in cell <%s>."
+	       attribute value (kcell-view:label pos))))
+
+(defun kotl-mode:split-cell (&optional arg)
+  "Split the current cell into two cells and move to the new cell.
+The cell contents after point become part of the newly created cell.
+The default is to create the new cell as a sibling of the current cell.
+With optional universal ARG, {C-u}, the new cell is added as the child of
+the current cell."
+  (interactive "*P")
+  (let ((new-cell-contents (kotl-mode:kill-region
+			    (point) (kcell-view:end-contents) 'string))
+	(start (kcell-view:start)))
+    ;; delete any preceding whitespace
+    (skip-chars-backward " \t\n\r" start)
+    (delete-region (max start (point)) (kcell-view:end-contents))
+    (kotl-mode:add-cell arg new-cell-contents (kcell-view:plist))))
+
+(defun kotl-mode:transpose-cells (arg)
+  "Exchange current and previous visible cells, leaving point after both.
+If no previous cell, exchange current with next cell.
+With prefix ARG, take current tree and move it past ARG visible cells.
+With prefix ARG = 0, interchange the cell that contains point with the cell
+that contains mark."
+  (interactive "*p")
+  (let ((label-sep-len (kview:label-separator-length kview)))
+    (cond
+     ((save-excursion (not (or (kcell-view:next t label-sep-len)
+			       (kcell-view:previous t label-sep-len))))
+      (error "(kotl-mode:transpose-cells): Only one visible cell in outline"))
+     ;;
+     ;; Transpose current and previous cells or current and next cells, if no
+     ;; previous cell.  Leave point after both exchanged cells or within last
+     ;; visible cell.
+     ((= arg 1)
+      (let ((label-1 (kcell-view:label))
+	    (prev (kcell-view:previous t label-sep-len))
+	    label-2)
+	(or prev (kcell-view:next t label-sep-len))
+	(setq label-2 (kcell-view:label))
+	(kotl-mode:exchange-cells label-1 label-2)
+	(kcell-view:next t label-sep-len)
+	(if prev (kcell-view:next t label-sep-len))))
+     ;;
+     ;; Transpose point and mark cells, moving point to the new location of the
+     ;; cell which originally contained point.
+     ((= arg 0)
+      (let ((label-1 (kcell-view:label))
+	    label-2)
+	;; This is like exchange-point-and-mark, but doesn't activate the
+	;; mark.
+	(goto-char (prog1 (hypb:mark t)
+		     (set-marker (hypb:mark-marker t) (point))))
+	(setq label-2 (kcell-view:label))
+	(kotl-mode:exchange-cells label-1 label-2)))
+     ;;
+     ;; Move current tree past ARG next visible cells and leave point after
+     ;; original cell text.
+     (t
+      (let ((mark (set-marker (make-marker)
+			      (save-excursion (kotl-mode:next-line arg)))))
+	(kotl-mode:move-after
+	 (kcell-view:label)
+	 (progn (while (and (> arg 0) (kcell-view:next t label-sep-len))
+		  (setq arg (1- arg)))
+		(kcell-view:label))
+	 nil)
+	(goto-char mark)
+	(set-marker mark nil))))))
+
+;;; ------------------------------------------------------------------------
+;;; Structure Viewing
+;;; ------------------------------------------------------------------------
+
+(defun kotl-mode:collapse-tree (&optional all-flag)
+  "Collapse to one line each visible cell of tree rooted at point.
+With optional ALL-FLAG non-nil, collapse all cells visible within the current
+view."
+  (interactive "P")
+  (kotl-mode:is-p)
+  (let (buffer-read-only)
+    (if all-flag
+	(progn (kvspec:show-lines-per-cell 1)
+	       (kvspec:update t))
+      (kview:map-tree
+       (function
+	(lambda (kview)
+	  ;; Use free variable label-sep-len bound in kview:map-tree for speed.
+	  (goto-char (kcell-view:start nil label-sep-len))
+	  (subst-char-in-region (point) (kcell-view:end-contents) ?\n ?\r t)))
+       kview nil t))))
+
+(defun kotl-mode:expand-tree (&optional all-flag)
+  "Expand each visible cell of tree rooted at point.
+With optional ALL-FLAG non-nil, expand all cells visible within the current
+view."
+  (interactive "P")
+  (kotl-mode:is-p)
+  (let (buffer-read-only)
+    (if all-flag
+	(progn (kvspec:show-lines-per-cell 0)
+	       (kvspec:update t))
+      (kview:map-tree
+       (function
+	(lambda (kview)
+	  ;; Use free variable label-sep-len bound in kview:map-tree for speed.
+	  (goto-char (kcell-view:start nil label-sep-len))
+	  (subst-char-in-region (point) (kcell-view:end-contents) ?\r ?\n t)))
+       kview nil t))))
+
+(defun kotl-mode:toggle-tree-expansion (&optional all-flag)
+  "Collapse or expand each cell of tree rooted at point or all visible cells if optional prefix arg ALL-FLAG is given.
+If current cell is collapsed, cells will be expanded, otherwise they will be
+collapsed."
+  (interactive "P")
+  (if (kcell-view:collapsed-p)
+       ;; expand cells
+      (kotl-mode:expand-tree all-flag)
+    (kotl-mode:collapse-tree all-flag)))
+
+;;; 
+(defun kotl-mode:overview ()
+  "Show the first line of each cell without blank line separators."
+  (interactive)
+  (kotl-mode:show-all)
+  (if (string-match "b" kvspec:current)
+      (kvspec:toggle-blank-lines))
+  (kotl-mode:collapse-tree t))
+
+(defun kotl-mode:show-all ()
+  "Show (expand) all cells in current view."
+  (interactive)
+  (if (kotl-mode:is-p)
+      (progn (kview:set-attr kview 'levels-to-show 0)
+	     (kview:set-attr kview 'lines-to-show 0)
+	     (show-all)
+	     (kvspec:update t))))
+
+(defun kotl-mode:top-cells ()
+  "Collapse all level 1 cells in view and hide any deeper sublevels."
+  (interactive)
+  (kotl-mode:is-p)
+  (let ((modified-p (buffer-modified-p))
+	(buffer-read-only))
+    (kvspec:levels-to-show 1)
+    (kvspec:show-lines-per-cell 1)
+    (kvspec:update t)
+    ;; Restore buffer modification status
+    (set-buffer-modified-p modified-p)))
+
+;;; 
+(defun kotl-mode:hide-sublevels (levels-to-keep)
+  "Hide all cells in outline at levels deeper than LEVELS-TO-KEEP (a number).
+Shows any hidden cells within LEVELS-TO-KEEP.  1 is the first level.  0 means
+display all levels of cells."
+  (interactive "P")
+  (kvspec:levels-to-show levels-to-keep)
+  ;; The prior call might have shown more lines per cell than the current
+  ;; viewspec supports, so reset lines per cell.
+  (kvspec:lines-to-show)
+  (kvspec:update t))
+
+(defun kotl-mode:hide-subtree (&optional cell-ref show-flag)
+  "Hide subtree, ignoring root, at optional CELL-REF (defaults to cell at point)."
+  (interactive)
+  (kotl-mode:is-p)
+  (save-excursion
+    (if cell-ref
+	(kotl-mode:goto-cell cell-ref t)
+      (kotl-mode:beginning-of-cell))
+    (let ((start (kcell-view:end-contents))
+	  (end (kotl-mode:tree-end t))
+	  (buffer-read-only))
+      (if show-flag
+	  (subst-char-in-region start end ?\r ?\n t)
+	(subst-char-in-region start end ?\n ?\r t)))))
+
+(defun kotl-mode:show-subtree (&optional cell-ref)
+  "Show subtree, ignoring root, at optional CELL-REF (defaults to cell at point)."
+  (interactive)
+  (kotl-mode:hide-subtree cell-ref t))
+
+(defun kotl-mode:hide-tree (&optional cell-ref show-flag)
+  "Collapse tree rooted at optional CELL-REF (defaults to cell at point)."
+  (interactive)
+  (kotl-mode:is-p)
+  (save-excursion
+    (let ((start (if cell-ref
+		     (kotl-mode:goto-cell cell-ref t)
+		   (kotl-mode:beginning-of-cell)))
+	  (end (kotl-mode:tree-end t))
+	  (buffer-read-only))
+      (if show-flag
+	  (subst-char-in-region start end ?\r ?\n t)
+	(subst-char-in-region start end ?\n ?\r t)))))
+
+(defun kotl-mode:show-tree (&optional cell-ref)
+  "Display fully expanded tree rooted at CELL-REF."
+  (interactive)
+  (kotl-mode:hide-tree cell-ref t))
+
+;;; 
+(defun kotl-mode:cell-attributes (all-flag)
+  "Display a temporary buffer with the attributes of the current kcell.
+With prefix arg ALL-FLAG non-nil, display the attributes of all visible
+kcells in the current buffer.
+
+See also the documentation for `kotl-mode:cell-help'."
+  (interactive "P")
+  (with-output-to-temp-buffer
+      (hypb:help-buf-name "Kotl")
+    (save-excursion
+      (if (not all-flag)
+	  (kotl-mode:print-attributes kview)
+	(let ((label-sep-len (kview:label-separator-length kview)))
+	  (kotl-mode:beginning-of-buffer)
+	  (while (progn (kotl-mode:print-attributes kview)
+			(kcell-view:next t label-sep-len))))))))
+
+(defun kotl-mode:cell-help (&optional cell-ref cells-flag)
+  "Display a temporary buffer with CELL-REF's attributes.
+CELL-REF defaults to current cell.
+Optional prefix arg CELLS-FLAG selects the cells to print:
+  If = 1, print CELL-REF's cell only;
+  If > 1, print CELL-REF's visible tree (the tree rooted at CELL-REF);
+  If < 1, print all visible cells in current view (CELL-REF is not used).
+
+See also the documentation for `kotl-mode:cell-attributes'."
+  (interactive
+   (let* ((label (kcell-view:label))
+	  (hargs:defaults (list label label)))
+     (append
+      (let ((arg (prefix-numeric-value current-prefix-arg)))
+	(if (< arg 1)
+	    0
+	  (hargs:iform-read
+	   (list 'interactive
+		 (format "+KDisplay properties of koutline %s: "
+			 (if (= arg 1) "cell" "tree"))))))
+      (list current-prefix-arg))))
+  (or (integerp cells-flag)
+      (setq cells-flag (prefix-numeric-value cells-flag)))
+  (or (stringp cell-ref) (setq cell-ref (kcell-view:label)))
+  (with-output-to-temp-buffer
+      (hypb:help-buf-name "Koutline")
+    (save-excursion
+      (if (equal cell-ref "0")
+	  (progn
+	    (hattr:report (kcell:plist (kview:top-cell kview)))
+	    (terpri)
+	    (cond ((= cells-flag 1) nil)
+		  ((> cells-flag 1)
+		   (kview:map-tree 'kotl-mode:print-attributes kview t t))
+		  ;; (< cells-flag 1)
+		  (t (kotl-mode:cell-attributes t))))
+	(cond ((= cells-flag 1)
+	       (kotl-mode:goto-cell cell-ref)
+	       (kotl-mode:print-attributes kview))
+	      ((> cells-flag 1)
+	       (kotl-mode:goto-cell cell-ref)
+	       (kview:map-tree 'kotl-mode:print-attributes kview nil t))
+	      ;; (< cells-flag 1)
+	      (t (kotl-mode:cell-attributes t)))))))
+
+(defun kotl-mode:get-cell-attribute (attribute &optional pos)
+  "Return ATTRIBUTE's value for the current cell or the cell at optional POS.
+When called interactively, it displays the value in the minibuffer."
+  (interactive "SCurrent cell attribute to get: ")
+  (let ((value (kcell-view:get-attr attribute pos)))
+    (if (interactive-p)
+	(message "Attribute \"%s\" = `%s' in cell <%s>."
+		 attribute value (kcell-view:label pos)))
+    value))
+
+;;; ************************************************************************
+;;; Private functions
+;;; ************************************************************************
+
+(defun kotl-mode:add-indent-to-region (&optional indent start end)
+  "Add current cell's indent to current region.
+Optionally, INDENT and region START and END may be given."
+  (or (integerp indent) (setq indent (kcell-view:indent)))
+  (save-excursion
+    (save-restriction
+      (narrow-to-region (or start (point)) (or end (hypb:mark t)))
+      (goto-char (point-min))
+      (replace-regexp "\n" (concat "\n" (make-string indent ?\ ))))))
+
+(defun kotl-mode:delete-line (&optional pos)
+  "Delete and return contents of cell line at point or optional POS as a string.
+Does not delete newline at end of line."
+  (save-excursion
+    (if pos (goto-char pos))
+    (if (kview:valid-position-p)
+	(let ((bol (kotl-mode:start-of-line))
+	      (eol (kotl-mode:finish-of-line)))
+	  (prog1
+	      (buffer-substring bol eol)
+	    (delete-region bol eol)))
+      (error "(kotl-mode:delete-line): Invalid position, '%d'" (point)))))
+
+(defun kotl-mode:indent-line (arg)
+  ;; Disallow the indent-line command.
+  (error "(kotl-mode:indent-line): Insert spaces to indent each line."))
+
+(defun kotl-mode:indent-region (start end)
+  ;; User might try to indent across cells.  This would be bad, so disallow
+  ;; the indent-region command.
+  (error "(kotl-mode:indent-region): Insert spaces to indent each line."))
+
+(defun kotl-mode:is-p ()
+  "Signal an error if current buffer is not a Hyperbole outline, else return t."
+  (if (kview:is-p kview)
+      t
+    (hypb:error
+     "(kotl-mode:is-p): Command requires a current Hyperbole outline.")))
+
+(defun kotl-mode:tree-end (&optional omit-end-newlines)
+  "Return end point of current cell's tree within this view.
+If optional OMIT-END-NEWLINES is non-nil, point returned precedes any
+newlines at end of tree."
+  (let* ((label-sep-len (kview:label-separator-length kview))
+	 (start-indent (kcell-view:indent nil label-sep-len))
+	 (next))
+    (save-excursion
+      (while (and (setq next (kcell-view:next nil label-sep-len))
+		  (< start-indent (kcell-view:indent nil label-sep-len))))
+      (cond (next
+	     (goto-char (progn (kcell-view:previous nil label-sep-len)
+			       (kcell-view:end))))
+	    ;; Avoid skipping too far at end of file.
+	    ((re-search-forward "[\n\r][\n\r]" nil t))
+	    (t (goto-char (point-max))))
+      (if omit-end-newlines (skip-chars-backward "\n\r"))
+      (point))))
+
+(defun kotl-mode:tree-start ()
+  "Return beginning of line position preceding current cell's start point."
+  (save-excursion (goto-char (kcell-view:start)) (beginning-of-line)
+		  (point)))
+
+(defun kotl-mode:line-move (arg)
+  "Move point ARG visible lines forward within an outline."
+  (if (not (integerp selective-display))
+      (forward-line arg)
+    ;; Move by arg lines, but ignore invisible ones.
+    (while (> arg 0)
+      (vertical-motion 1)
+      (forward-char -1)
+      (forward-line 1)
+      (setq arg (1- arg)))
+    (while (< arg 0)
+      (vertical-motion -1)
+      (beginning-of-line)
+      (setq arg (1+ arg))))
+  (move-to-column (or goal-column temporary-goal-column))
+  nil)
+
+(defun kotl-mode:print-attributes (kview)
+  "Print to the `standard-output' stream the attributes of the current visible kcell. 
+Takes argument KVIEW (so it can be used with 'kview:map-tree' and so that
+KVIEW is bound correctly) but always operates upon the current view."
+  ;; Move to start of visible cell to avoid printing attributes for an
+  ;; invisible kcell which point may be over.
+  ;; Print first line of cell for reference.
+  (save-excursion
+    (princ
+     (buffer-substring (progn (beginning-of-line) (point))
+		       (progn (kview:end-of-actual-line)
+			      (point)))))
+  (terpri)
+  (hattr:report (kcell:plist (kcell-view:cell)))
+  (terpri))
+
+(defun kotl-mode:set-temp-goal-column ()
+  (if (not (or (eq last-command 'next-line)
+	       (eq last-command 'previous-line)))
+      (setq temporary-goal-column
+	    (if (and track-eol (eolp)
+		     ;; Don't count beg of empty line as end of line
+		     ;; unless we just did explicit end-of-line.
+		     (or (not (bolp)) (eq last-command 'end-of-line)))
+		9999
+	      (current-column)))))
+
+(defun kotl-mode:to-valid-position (&optional backward-p)
+  "Move point to the nearest non-read-only position within current koutline view.
+With optional BACKWARD-P, move backward if possible to get to valid position."
+  (if (kview:valid-position-p)
+      nil
+    (let ((label-sep-len (kview:label-separator-length kview)))
+      (cond ((kotl-mode:bobp)
+	     (goto-char (kcell-view:start nil label-sep-len)))
+	    ((kotl-mode:eobp)
+	     (skip-chars-backward "\n\r"))
+	    (t (let ((indent (kcell-view:indent nil label-sep-len)))
+		 (if (bolp)
+		     (if backward-p
+			 (skip-chars-backward "\n\r")
+		       (skip-chars-forward "\n\r")))
+		 (setq indent (kcell-view:indent nil label-sep-len))
+		 (if (< (current-column) indent)
+		     (move-to-column indent))))))))
+
+(defun kotl-mode:transpose-lines-internal (start end)
+  "Transpose lines at START and END markers within an outline.
+Leave point at end of line now residing at START."
+  (if (and start end
+	   (kview:valid-position-p start)
+	   (kview:valid-position-p end))
+      (let* ((pline (kotl-mode:delete-line start))
+	     mline)
+	(goto-char end)
+	(setq mline (kotl-mode:delete-line))
+	(insert pline)
+	(goto-char start)
+	(insert mline))
+    ;; Set non-point and non-mark markers to point nowhere before signalling
+    ;; an error.
+    (or (eq start (point-marker))
+	(eq start (hypb:mark-marker t))
+	(set-marker start nil))
+    (or (eq end (point-marker))
+	(eq end (hypb:mark-marker t))
+	(set-marker start nil))
+    (error "(kotl-mode:transpose-lines): Point or mark is at an invalid position")))
+
+(defun kotl-mode:update-buffer ()
+  "Update current view buffer in preparation for saving."
+  (if (kview:is-p kview)
+      (let ((mod-p (buffer-modified-p))
+	    (start (window-start)))
+	(save-excursion
+	  (kfile:update)
+	  (set-buffer-modified-p mod-p))
+	(set-window-start nil (max (point-min) start) t)
+	nil)))
+
+;;; ------------------------------------------------------------------------
+
+(defvar kotl-mode-map nil
+  "Keymap containing koutliner editing and viewing commands.")
+(if kotl-mode-map
+    nil
+  (setq kotl-mode-map
+	(if (string-match "XEmacs\\|Lucid" emacs-version)
+	    (make-keymap)
+	  (copy-keymap indented-text-mode-map)))
+  ;; Overload edit keys to deal with structure and labels.
+  (let (local-cmd)
+    (mapcar 
+     (if (string-match "XEmacs\\|Lucid" emacs-version)
+	 ;; XEmacs
+	 (function
+	  (lambda (cmd)
+	    (setq local-cmd (intern-soft
+			     (concat "kotl-mode:" (symbol-name cmd))))
+	    ;; Only bind key locally if kotl-mode local-cmd has already
+	    ;; been defined and cmd is a valid function.
+	    (if (and local-cmd (fboundp cmd))
+		(progn
+		  ;; Make local-cmd have the same property list as cmd,
+		  ;; e.g. so pending-delete property is the same.
+		  (setplist local-cmd (symbol-plist cmd)) 
+		  (mapcar
+		   (function
+		    (lambda (key) (define-key kotl-mode-map key local-cmd)))
+		   (where-is-internal cmd))))))
+       ;; GNU Emacs 19
+       (function
+	(lambda (cmd)
+	  (setq local-cmd (intern-soft
+			   (concat "kotl-mode:" (symbol-name cmd))))
+	  ;; Only bind key locally if kotl-mode local-cmd has already
+	  ;; been defined and cmd is a valid function.
+	  (if (and local-cmd (fboundp cmd))
+	      (progn
+		;; Make local-cmd have the same property list as cmd,
+		;; e.g. so pending-delete property is the same.
+		(setplist local-cmd (symbol-plist cmd)) 
+		(substitute-key-definition
+		 cmd local-cmd kotl-mode-map global-map))))))
+     '(
+       back-to-indentation
+       backward-char
+       backward-delete-char
+       backward-delete-char-untabify
+       backward-kill-word
+       backward-para
+       backward-paragraph
+       backward-sentence
+       backward-word
+       beginning-of-buffer
+       beginning-of-line
+       copy-region-as-kill
+       copy-to-register
+       delete-blank-lines
+       delete-backward-char
+       delete-char
+       delete-horizontal-space
+       delete-indentation
+       end-of-buffer
+       end-of-line
+       fill-paragraph
+       fill-paragraph-or-region
+       ;; cursor keys
+       fkey-backward-char
+       fkey-forward-char
+       fkey-next-line
+       fkey-previous-line
+       ;;
+       forward-char
+       forward-word
+       forward-para
+       forward-paragraph
+       forward-sentence
+       insert-buffer
+       insert-file
+       insert-register
+       just-one-space
+       kill-word
+       kill-line
+       kill-region
+       kill-ring-save
+       kill-sentence
+       mark-paragraph
+       mark-whole-buffer
+       newline
+       newline-and-indent
+       next-line
+       open-line
+       previous-line
+       scroll-down
+       scroll-up
+       set-fill-prefix
+       transpose-chars
+       transpose-lines
+       transpose-paragraphs
+       transpose-sentences
+       transpose-words
+       yank
+       yank-pop
+       zap-to-char
+       )))
+
+
+  ;; kotl-mode keys
+  (define-key kotl-mode-map "\C-c@"     'kotl-mode:mail-tree)
+  (define-key kotl-mode-map "\C-c+"     'kotl-mode:append-cell)
+  (define-key kotl-mode-map "\C-c,"     'kotl-mode:beginning-of-cell)
+  (define-key kotl-mode-map "\C-c."     'kotl-mode:end-of-cell)
+  (define-key kotl-mode-map "\C-c<"     'kotl-mode:first-sibling)
+  (define-key kotl-mode-map "\C-c>"     'kotl-mode:last-sibling)
+  (define-key kotl-mode-map "\C-c^"     'kotl-mode:beginning-of-tree)
+  (define-key kotl-mode-map "\C-c$"     'kotl-mode:end-of-tree)
+  (define-key kotl-mode-map "\C-ca"     'kotl-mode:add-child)
+  (define-key kotl-mode-map "\C-c\C-a"  'kotl-mode:show-all)
+  (define-key kotl-mode-map "\C-cb"     'kvspec:toggle-blank-lines)
+  (define-key kotl-mode-map "\C-c\C-b"  'kotl-mode:backward-cell)
+  (define-key kotl-mode-map "\C-cc"     'kotl-mode:copy-after)
+  (define-key kotl-mode-map "\C-c\C-c"  'kotl-mode:copy-before)
+  (define-key kotl-mode-map "\C-c\M-c"  'kotl-mode:copy-to-buffer)
+  (define-key kotl-mode-map "\C-cd"     'kotl-mode:down-level)
+  (define-key kotl-mode-map "\C-c\C-d"  'kotl-mode:down-level)
+  (define-key kotl-mode-map "\C-ce"     'kotl-mode:exchange-cells)
+  (define-key kotl-mode-map "\C-c\C-f"  'kotl-mode:forward-cell)
+  (define-key kotl-mode-map "\C-cg"     'kotl-mode:goto-cell)
+  (define-key kotl-mode-map "\C-ch"     'kotl-mode:cell-help)
+  (define-key kotl-mode-map "\C-c\C-h"  'kotl-mode:hide-tree)
+  (define-key kotl-mode-map "\M-\C-h"   'kotl-mode:hide-subtree)
+  ;; Override this global binding for set-selective-display with a similar
+  ;; function appropriate for kotl-mode.
+  (define-key kotl-mode-map "\C-x$"     'kotl-mode:hide-sublevels)
+  (define-key kotl-mode-map "\C-i"      'kotl-mode:demote-tree)
+  (define-key kotl-mode-map "\M-\C-i"   'kotl-mode:promote-tree)
+  (define-key kotl-mode-map "\C-j"      'kotl-mode:add-cell)
+  (define-key kotl-mode-map "\M-j"      'kotl-mode:fill-paragraph)
+  (define-key kotl-mode-map "\C-c\M-j"  'kotl-mode:fill-cell)
+  (define-key kotl-mode-map "\M-\C-j"   'kotl-mode:fill-tree)
+  (define-key kotl-mode-map "\C-c\C-k"  'kotl-mode:kill-tree)
+  (define-key kotl-mode-map "\C-ck"     'kotl-mode:kill-contents)
+  (define-key kotl-mode-map "\C-c\C-i"  'kotl-mode:set-cell-attribute)
+  (define-key kotl-mode-map "\C-cl"     'klink:create)
+  (define-key kotl-mode-map "\C-c\C-l"  'kview:set-label-type)
+  (define-key kotl-mode-map "\C-c\M-l"  'kview:set-label-separator)
+  (define-key kotl-mode-map "\C-m"      'kotl-mode:newline)
+  (define-key kotl-mode-map "\C-cm"     'kotl-mode:move-after)
+  (define-key kotl-mode-map "\C-c\C-m"  'kotl-mode:move-before)
+  (define-key kotl-mode-map "\C-c\C-n"  'kotl-mode:next-cell)
+  (define-key kotl-mode-map "\C-c\C-o"  'kotl-mode:overview)
+  (define-key kotl-mode-map "\C-c\C-p"  'kotl-mode:previous-cell)
+  (define-key kotl-mode-map "\C-cp"     'kotl-mode:add-parent)
+  (if (memq (global-key-binding "\M-q") '(fill-paragraph
+					  fill-paragraph-or-region))
+      (progn
+	(define-key kotl-mode-map "\C-c\M-q" 'kotl-mode:fill-cell)
+	(define-key kotl-mode-map "\M-\C-q"  'kotl-mode:fill-tree)))
+  (define-key kotl-mode-map "\C-cs"     'kotl-mode:split-cell)
+  (define-key kotl-mode-map "\C-c\C-s"  'kotl-mode:show-tree)
+  (define-key kotl-mode-map "\C-c\C-\\" 'kotl-mode:show-tree)
+  (define-key kotl-mode-map "\M-s"      'kotl-mode:center-line)
+  (define-key kotl-mode-map "\M-S"      'kotl-mode:center-paragraph)
+  (define-key kotl-mode-map "\C-ct"     'kotl-mode:transpose-cells)
+  (define-key kotl-mode-map "\C-c\C-t"  'kotl-mode:top-cells)
+  (define-key kotl-mode-map "\C-cu"     'kotl-mode:up-level)
+  (define-key kotl-mode-map "\C-c\C-u"  'kotl-mode:up-level)
+  (define-key kotl-mode-map "\C-c\C-v"  'kvspec:activate)
+  (define-key kotl-mode-map "\C-x\C-w"  'kfile:write))
+
+(provide 'kotl-mode)