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

Import from CVS: tag r19-14
author cvs
date Mon, 13 Aug 2007 08:45:50 +0200
parents
children c53a95d3c46d
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/hyperbole/kotl/kview.el	Mon Aug 13 08:45:50 2007 +0200
@@ -0,0 +1,1048 @@
+;;!emacs
+;;
+;; FILE:         kview.el
+;; SUMMARY:      Display handling of koutlines.
+;; USAGE:        GNU Emacs Lisp Library
+;; KEYWORDS:     outlines, wp
+;;
+;; AUTHOR:       Bob Weiner & Kellie Clark
+;;
+;; ORIG-DATE:    6/30/93
+;; LAST-MOD:      2-Nov-95 at 00:52:52 by Bob Weiner
+;;; ************************************************************************
+;;; Other required Lisp Libraries
+;;; ************************************************************************
+(mapcar 'require '(klabel kfill hypb))
+
+;;; ************************************************************************
+;;; Public variables
+;;; ************************************************************************
+
+(set-default 'kview nil)
+
+(defvar kview:default-blank-lines t
+  "*Default setting of whether to show blank lines between koutline cells.
+T means show them, nil means don't show them.")
+
+(defvar kview:default-levels-to-show 0
+  "*Default number of cell levels to show.  0 means all levels.")
+
+(defvar kview:default-lines-to-show 0
+  "*Default number of lines per cell to show.  0 means all lines.")
+
+
+(defvar kview:default-label-min-width 4
+  "*Minimum width to which to pad labels in a kotl view.
+Labels are padded with spaces on the left.")
+
+(defvar kview:default-label-separator "  "
+  "*Default string of characters to insert between label and contents of a koutline cell.")
+
+(defvar kview:default-label-type 'alpha
+  "*Default label-type to use for new koutlines.
+It must be one of the following symbols:
+  no              for no labels
+  id              for permanent idstamp labels, e.g. 001, 002, etc.
+  alpha           for '1a2' full alphanumeric labels
+  legal           for '1.1.2' labels
+  partial-alpha   for partial alphanumeric labels, e.g. '2' for node '1a2'
+  star            for multi-star labeling, e.g. '***'.")
+
+(defvar kview:default-level-indent 3
+  "*Default number of spaces to indent each succeeding level in koutlines.")
+
+;;; ************************************************************************
+;;; Public functions
+;;; ************************************************************************
+
+;;;
+;;; kcell-view
+;;;
+
+(defun kcell-view:backward (&optional visible-p label-sep-len)
+  "Move to start of the prior cell at the same level as the current cell.
+With optional VISIBLE-P, consider only visible cells.
+Return t unless no such cell."
+  (or label-sep-len (setq label-sep-len
+			  (kview:label-separator-length kview)))
+  (let ((opoint (point))
+	(found) (done)
+	(curr-indent 0)
+	(start-indent (kcell-view:indent nil label-sep-len)))
+    (while (and (not (or found done))
+		(kcell-view:previous visible-p label-sep-len))
+      (if (bobp)
+	  (progn (setq done t)
+		 (goto-char opoint))
+	(setq curr-indent (kcell-view:indent nil label-sep-len))
+	(cond ((= curr-indent start-indent)
+	       (goto-char (kcell-view:start nil label-sep-len))
+	       (setq found t))
+	      ((< curr-indent start-indent)
+	       ;; Went past start of this tree without a match.
+	       (setq done t)
+	       (goto-char opoint))
+	      ;; else go to prior node
+	      )))
+    found))
+
+(defun kview:beginning-of-actual-line ()
+  "Go to the beginning of the current line whether collapsed or not."
+  (if (re-search-backward "[\n\r]" nil 'move)
+      (forward-char 1)))
+
+(defun kcell-view:cell (&optional pos)
+  "Return kcell at optional POS or point."
+  (kproperty:get (kcell-view:plist-point pos) 'kcell))
+
+(defun kcell-view:child (&optional visible-p label-sep-len)
+  "Move to start of current cell's child.
+With optional VISIBLE-P, consider only visible children.
+Return t unless cell has no matching child.
+Optional LABEL-SEP-LEN is the length of the separation between
+a cell's label and the start of its contents."
+  (let* ((opoint (point))
+	 (prev-indent (kcell-view:indent nil label-sep-len))
+	 (next (kcell-view:next visible-p label-sep-len)))
+    (or label-sep-len (setq label-sep-len
+			    (kview:label-separator-length kview)))
+    ;; Since kcell-view:next leaves point at the start of a cell, the cell's
+    ;; indent is just the current-column of point.
+    (if (and next (> (current-column) prev-indent))
+	t
+      ;; Move back to previous point and return nil.
+      (goto-char opoint)
+      nil)))
+
+(defun kcell-view:child-p (&optional pos visible-p label-sep-len)
+  "Return t if cell at optional POS or point has a child.
+With optional VISIBLE-P, consider only visible children.
+Optional LABEL-SEP-LEN is the length of the separation between
+a cell's label and the start of its contents."
+  (save-excursion
+    (if pos (goto-char pos))
+    (kcell-view:child visible-p label-sep-len)))
+
+(defun kcell-view:collapse (&optional pos label-sep-len)
+  "Collapse cell at optional POS or point within the current view."
+  (save-excursion
+    (goto-char (kcell-view:start pos label-sep-len))
+    (subst-char-in-region (point) (kcell-view:end-contents) ?\n ?\r t)))
+
+(defun kcell-view:collapsed-p (&optional pos label-sep-len)
+  "Return t if cell at optional POS or point is collapsed within the current view."
+  (save-excursion
+    (goto-char (kcell-view:start pos label-sep-len))
+    (if (search-forward "\r" (kcell-view:end-contents) t)
+	t)))
+
+(defun kcell-view:contents (&optional pos)
+  "Return contents of cell at optional POS or point."
+  (save-excursion
+    (if pos (goto-char pos))
+    (let ((indent (kcell-view:indent))
+	  (start (kcell-view:start))
+	  (end (kcell-view:end-contents)))
+      ;; Remove indentation from all but first line.
+      (hypb:replace-match-string
+       (concat "\\([\n\r]\\)" (make-string indent ?\ ))
+       (buffer-substring start end) "\\1"))))
+
+(defun kcell-view:create (kview cell level klabel &optional no-fill)
+  "Insert into KVIEW at point, CELL at LEVEL (1 = first level) with KLABEL.
+Optional NO-FILL non-nil suppresses filling of cell's contents upon insertion
+or movement."
+  (if (= (kcell:idstamp cell) 0)
+      nil
+    (or no-fill (setq no-fill (kcell:get-attr cell 'no-fill)))
+    (let* ((label-min-width (kview:label-min-width kview))
+	   (label-fmt (format "%%%ds" label-min-width))
+	   (label (if (string= klabel "") "" (format label-fmt klabel)))
+	   (label-separator (if (string= klabel "") " "
+			      (kview:label-separator kview)))
+	   (mult-line-indent (* (1- level) (kview:level-indent kview)))
+	   (thru-label (+ mult-line-indent label-min-width
+			  (length label-separator)))
+	   (old-point (point))
+	   (fill-prefix (make-string thru-label ?\ ))
+	   contents
+	   new-point)
+      (if no-fill (kcell:set-attr cell 'no-fill t))
+      (insert fill-prefix)
+      (setq contents (kview:insert-contents cell nil no-fill fill-prefix))
+      ;; Insert lines to separate cell from next.
+      (insert (if (or no-fill (equal contents ""))
+		  "\n\n" "\n"))
+      (if (kview:get-attr kview 'blank-lines)
+	  nil
+	;; Make blank lines invisible.
+	(kproperty:put (1- (point)) (min (point) (point-max))
+		       '(invisible t)))
+      (kfile:narrow-to-kcells)
+      (setq new-point (point))
+      (goto-char old-point)
+      ;; Delete leading spaces used to get fill right in first cell
+      ;; line.  Replace it with label.
+      (delete-char thru-label)
+      (insert (format
+	       (format "%%%ds" (- thru-label (length label-separator)))
+	       label))
+      (setq old-point (point))
+      (insert label-separator)
+      (goto-char old-point)
+      ;; Add cell's attributes to the text property list at point.
+      (kproperty:set 'kcell cell)
+      (goto-char new-point))))
+
+(defun kcell-view:end (&optional pos)
+  "Return end position of cell from optional POS or point.
+Includes blank lines following cell contents."
+  (or pos (setq pos (point)))
+  (save-excursion
+    (or (re-search-forward "[\n\r][\n\r]" nil t)
+	(point-max))))
+
+(defun kcell-view:end-contents (&optional pos)
+  "Return end position of cell contents from optional POS or point.
+Excludes blank lines following cell contents."
+  (save-excursion
+    (if pos (goto-char pos))
+    (goto-char (kcell-view:end))
+    (skip-chars-backward "\n\r")
+    (point)))
+
+(defun kcell-view:expand (&optional pos label-sep-len)
+  "Expand cell at optional POS or point within the current view."
+  (save-excursion
+    (goto-char (kcell-view:start pos label-sep-len))
+    (subst-char-in-region (point) (kcell-view:end-contents) ?\r ?\n t)))
+
+(defun kcell-view:forward (&optional visible-p label-sep-len)
+  "Move to start of the following cell at the same level as the current cell.
+With optional VISIBLE-P, consider only visible cells.
+Return t unless no such cell."
+  (or label-sep-len (setq label-sep-len
+			  (kview:label-separator-length kview)))
+  (let ((opoint (point))
+	(found) (done)
+	(curr-indent 0)
+	(start-indent (kcell-view:indent nil label-sep-len)))
+    (while (and (not (or found done))
+		(kcell-view:next visible-p label-sep-len))
+      (setq curr-indent (kcell-view:indent nil label-sep-len))
+      (cond ((= curr-indent start-indent)
+	     (goto-char (kcell-view:start nil label-sep-len))
+	     (setq found t))
+	    ((< curr-indent start-indent)
+	     ;; Went past end of this tree without a match.
+	     (setq done t)
+	     (goto-char opoint))
+	    ;; else go to following node
+	    ))
+    ;; If didn't find a match, return to original point.
+    (or found (goto-char opoint))
+    found))
+
+(defun kcell-view:get-attr (attribute &optional pos)
+  "Return ATTRIBUTE's value for current cell or cell at optional POS."
+  (save-excursion
+    (if pos (goto-char pos))
+    (kcell:get-attr (kcell-view:cell) attribute)))
+
+(defun kcell-view:idstamp (&optional pos)
+  "Return idstamp string of cell at optional POS or point."
+  (save-excursion
+    (if pos (goto-char pos))
+    (format "0%d" (kcell:idstamp (kcell-view:cell)))))
+
+(defun kcell-view:indent (&optional pos label-sep-len)
+  "Return indentation of cell at optional POS or point.
+Optional LABEL-SEP-LEN is the view-specific length of the separator between a
+cell's label and the start of its contents."
+  (+ (save-excursion
+       (kcell-view:to-label-end pos)
+       (current-column))
+     (or label-sep-len (kview:label-separator-length kview))))
+
+(defun kcell-view:label (&optional pos)
+  "Return displayed label string of cell at optional POS or point.
+If labels are off, return cell's idstamp as a string."
+  (save-excursion
+    (if pos (goto-char pos))
+    (let ((label-type (kview:label-type kview)))
+      (if (eq label-type 'no)
+	  (kcell-view:idstamp)
+	(kcell-view:to-label-end)
+	(buffer-substring (point) (progn (skip-chars-backward "^ \t\n\r")
+					 (point)))))))
+
+(defun kcell-view:level (&optional pos label-sep-len indent)
+  "Return cell level relative to top cell of the outline for current cell or one at optional POS.
+0 = top cell level, 1 = 1st level in outline.
+Optional LABEL-SEP-LEN is length of spaces between a cell label and its the
+start of its body in the current view.  Optional INDENT is the indentation in
+characters of the cell whose level is desired."
+  (or label-sep-len (setq label-sep-len (kview:label-separator-length kview)))
+  (save-excursion
+    (if pos (goto-char pos))
+    (/ (- (or indent (kcell-view:indent nil label-sep-len)) label-sep-len)
+       (kview:level-indent kview))))
+
+(defun kcell-view:line (&optional pos)
+  "Return contents of cell line at point or optional POS as a string."
+  (save-excursion
+    (if pos (goto-char pos))
+    (if (kview:valid-position-p)
+	(buffer-substring
+	 (kotl-mode:beginning-of-line)
+	 (kotl-mode:end-of-line))
+      (error "(kcell-view:line): Invalid position, '%d'" (point)))))
+
+(defun kcell-view:next (&optional visible-p label-sep-len)
+  "Move to start of next cell within current view.
+With optional VISIBLE-P, consider only visible cells.
+Return t unless no next cell."
+  (let ((opoint (point))
+	pos)
+    ;;
+    ;; If a subtree is collapsed, be sure we end up at the start of a visible
+    ;; cell rather than within an invisible one.
+    (if visible-p
+	(progn (goto-char (kcell-view:end-contents)) (end-of-line)))
+    (setq pos (kproperty:next-single-change (point) 'kcell))
+    (if (or (null pos)
+	    (if (goto-char pos) (kotl-mode:eobp)))
+	(progn (goto-char opoint)
+	       nil)
+      (goto-char (kcell-view:start nil label-sep-len))
+      (not (eq opoint (point))))))
+
+(defun kcell-view:operate (function &optional start end)
+  "Invoke FUNCTION with view restricted to current cell contents.
+Optional START and END are start and endpoints of cell to use."
+  (save-restriction
+    (narrow-to-region (or start (kcell-view:start))
+		      (or end (kcell-view:end-contents)))
+    (funcall function)))
+
+(defun kcell-view:parent (&optional visible-p label-sep-len)
+  "Move to start of current cell's parent within current view.
+If parent is top cell, move to first cell within view and return 0.
+Otherwise, return t unless optional VISIBLE-P is non-nil and the parent cell
+is not part of the current view."
+  (or label-sep-len (setq label-sep-len (kview:label-separator-length kview)))
+  (let ((opoint (point))
+	(parent-level (1- (kcell-view:level nil label-sep-len))))
+    (if (= parent-level 0) ;; top cell
+	(progn (goto-char (point-min))
+	       (goto-char (kcell-view:start nil label-sep-len))
+	       0)
+      ;; Skip from point back past any siblings
+      (while (kcell-view:backward visible-p label-sep-len))
+      ;; Move back to parent.
+      (if (kcell-view:previous visible-p label-sep-len)
+	  t
+	;; Move back to previous point and return nil.
+	(goto-char opoint)
+	nil))))
+
+(defun kcell-view:previous (&optional visible-p label-sep-len)
+  "Move to start of previous cell within current view.
+With optional VISIBLE-P, consider only visible cells.
+Return t unless no previous cell."
+  (let ((opoint (point))
+	(pos (point)))
+    (goto-char (kcell-view:start nil label-sep-len))
+    ;;
+    ;; If a subtree is collapsed, be sure we end up at the start of a visible
+    ;; cell rather than within an invisible one.
+    (if visible-p
+	(beginning-of-line)
+      (if (setq pos (kproperty:previous-single-change (point) 'kcell))
+	  (goto-char pos)))
+    (if (and pos (not (kotl-mode:bobp))
+	     (setq pos (kproperty:previous-single-change (point) 'kcell)))
+	(progn (goto-char pos)
+	       (skip-chars-backward "\n\r")
+	       (if visible-p (beginning-of-line))
+	       (goto-char (kcell-view:start nil label-sep-len))
+	       (not (eq opoint (point))))
+      ;; No previous cell exists
+      (goto-char opoint)
+      nil)))
+
+(defun kcell-view:plist (&optional pos)
+  "Return attributes associated with cell at optional POS or point."
+  (kcell:plist (kcell-view:cell pos)))
+
+(defun kcell-view:plist-point (&optional pos)
+  "Return buffer position of attributes associated with cell at optional POS or point."
+  (save-excursion (1+ (kcell-view:to-label-end pos))))
+
+(defun kcell-view:to-label-end (&optional pos)
+  "Move point after end of current cell's label and return point."
+  (if pos (goto-char pos))
+  (kview:end-of-actual-line)
+  (cond ((null kview)
+	 (error "(kcell-view:to-label-end): Invalid kview; try {M-x kotl-mode RET} to fix it."))
+	(klabel-type:changing-flag
+	 ;; When changing from one label type to another, e.g. alpha to
+	 ;; legal, we can't depend on the label being of the type given by
+	 ;; the kview, so use kcell properties to find label end.
+	 (if (kproperty:get (1- (point)) 'kcell)
+	     nil
+	   ;; If not at beginning of cell contents, move there.
+	   (goto-char (kproperty:previous-single-change (point) 'kcell)))
+	 ;; Then move to end of label via embedded kcell property.
+	 (goto-char (kproperty:previous-single-change (point) 'kcell)))
+	((funcall (kview:get-attr kview 'to-label-end))
+	 (point))
+	(t (error "(kcell-view:to-label-end): Can't find end of current cell's label"))))
+
+(defun kcell-view:reference (&optional pos relative-dir)
+  "Return a reference to the kcell at optional POS or point for use in a link.
+The reference is a string of the form, \"<kcell-file, cell-ref>\" where
+cell-ref is as described in the documentation for 'kcell:ref-to-id'.
+Kcell-file is made relative to optional RELATIVE-DIR before it is returned."
+  (format "<%s, %s=%s>" (hpath:relative-to buffer-file-name relative-dir)
+	  (kcell-view:label pos) (kcell-view:idstamp pos)))
+
+(defun kcell-view:remove-attr (attribute &optional pos)
+  "Remove ATTRIBUTE, if any, for current cell or cell at optional POS."
+  (interactive "*SAttribute to remove: ")
+  (save-excursion
+    (if pos (goto-char pos))
+    (let ((kcell (kcell:remove-attr (kcell-view:cell) attribute)))
+      (if (interactive-p)
+	  (message "Cell <%s> now has no %s attribute."
+		   (kcell-view:label) attribute))
+      kcell)))
+
+(defun kcell-view:set-attr (attribute value &optional pos)
+  "Set ATTRIBUTE's VALUE for current cell or cell at optional POS and return the cell."
+  (save-excursion
+    (if pos (goto-char pos))
+    ;; Returns kcell.
+    (kcell:set-attr (kcell-view:cell) attribute value)))
+
+(defun kcell-view:set-cell (kcell)
+  "Attach KCELL property to cell at point."
+  (save-excursion
+    (kcell-view:to-label-end)
+    (kproperty:set 'kcell kcell)))
+
+(defun kcell-view:sibling-p (&optional pos visible-p label-sep-len)
+  "Return t if cell at optional POS or point has a successor.
+With optional VISIBLE-P, consider only visible siblings."
+  (save-excursion
+    (if pos (goto-char pos))
+    (kcell-view:forward visible-p label-sep-len)))
+
+(defun kcell-view:start (&optional pos label-sep-len)
+  "Return start position of cell contents from optional POS or point."
+  (save-excursion
+    (+ (kcell-view:to-label-end pos)
+       (or label-sep-len (kview:label-separator-length kview)))))
+
+;;;
+;;; kview - one view per buffer, multiple views per kotl
+;;;
+
+(defun kview:add-cell (klabel level &optional contents prop-list no-fill)
+  "Create a new cell with full KLABEL and add it at point at LEVEL within outline.
+1 = first level.  Optional cell CONTENTS and PROP-LIST may also be given, as
+well as NO-FILL which skips filling of any CONTENTS.
+Return new cell.  This function does not renumber any other cells."
+  (let ((new-cell (kcell:create contents (kview:id-increment kview)
+				prop-list)))
+    (kcell-view:create kview new-cell level klabel no-fill)
+    new-cell))
+
+(defun kview:buffer (kview)
+  "Return kview's buffer or nil if argument is not a kview."
+  (if (kview:is-p kview)
+      (get-buffer (kview:get-attr kview 'view-buffer-name))))
+
+(defun kview:create (buffer-name
+			 &optional id-counter label-type level-indent
+			 label-separator label-min-width blank-lines
+			 levels-to-show lines-to-show)
+  "Return a new kview for BUFFER-NAME.
+Optional ID-COUNTER is the maximum permanent id previously given out in this
+outline.  Optional LABEL-TYPE, LEVEL-INDENT, LABEL-SEPARATOR, LABEL-MIN-WIDTH,
+BLANK-LINES, LEVELS-TO-SHOW, and LINES-TO-SHOW may also be given, otherwise default values are used.
+
+  See documentation of:
+ 'kview:default-label-type' for LABEL-TYPE,
+ 'kview:default-level-indent' for LEVEL-INDENT,
+ 'kview:default-label-separator' for LABEL-SEPARATOR,
+ 'kview:default-label-min-width' for LABEL-MIN-WIDTH,
+ 'kview:default-blank-lines' for BLANK-LINES,
+ 'kview:default-levels-to-show' for LEVELS-TO-SHOW,
+ 'kview:default-lines-to-show' for LINES-TO-SHOW."
+
+  (let ((buf (get-buffer buffer-name)))
+    (cond ((null buf)
+	   (error "(kview:create): No such buffer, '%s'." buffer-name))
+	  ((or (null id-counter) (= id-counter 0))
+	   (setq id-counter 0))
+	  ((not (integerp id-counter))
+	   (error "(kview:create): 2nd arg, '%s', must be an integer." id-counter)))
+    (set-buffer buf)
+    (if (and (boundp 'kview) (eq (kview:buffer kview) buf))
+	;; Don't recreate view if it exists.
+	nil
+      (make-local-variable 'kview)
+      (setq kview
+	    (list 'kview 'plist
+		  (list 'view-buffer-name buffer-name
+			'top-cell
+			(kcell:create-top buffer-file-name id-counter)
+			'label-type (or label-type kview:default-label-type)
+			'label-min-width (or label-min-width
+					     kview:default-label-min-width)
+			'label-separator (or label-separator
+					     kview:default-label-separator)
+			'label-separator-length
+			(length (or label-separator
+				    kview:default-label-separator))
+			'level-indent (or level-indent
+					  kview:default-level-indent)
+			'blank-lines
+			(or blank-lines kview:default-blank-lines)
+			'levels-to-show
+			(or levels-to-show kview:default-levels-to-show)
+			'lines-to-show
+			(or lines-to-show kview:default-lines-to-show)
+)))
+      (kview:set-functions (or label-type kview:default-label-type)))
+    kview))
+
+;;; Using this stimulates an GNU Emacs V19.19 bug in text-property handling,
+;;  visible when one deletes a sibling cell and then deletes the prior cell,
+;;  the following cell is left with a different idstamp and its label
+;;  displays as "0".  Using delete-char here would solve the problem but we
+;;  suggest you upgrade to a newer version of GNU Emacs in which the bug is
+;;  fixed.
+(defun kview:delete-region (start end)
+  "Delete cells between START and END points from current view."
+  (delete-region start end))
+
+(defun kview:end-of-actual-line ()
+  "Go to the end of the current line whether collapsed or not."
+  (if (re-search-forward "[\n\r]" nil 'move)
+      (backward-char 1)))
+
+(defun kview:fill-region (start end &optional kcell justify)
+  "Fill region between START and END within current view.
+With optional KCELL, assume START and END delimit that cell's contents.
+With optional JUSTIFY, justify region as well.
+Fill-prefix must be a string of spaces the length of this cell's indent, when
+this function is called."
+  (let ((opoint (set-marker (make-marker) (point)))
+	(label-sep-len (kview:label-separator-length kview))
+	(continue t)
+	prev-point)
+    (goto-char start)
+    (while continue
+      (if (kcell:get-attr (or kcell (kcell-view:cell)) 'no-fill)
+	  (setq continue (kcell-view:next nil label-sep-len))
+	(fill-paragraph justify t)
+	(setq prev-point (point))
+	(forward-paragraph)
+	(re-search-forward "[^ \t\n\r]" nil t))
+      (setq continue (and continue
+			  (/= (point) prev-point)
+			  (< (point) (min end (point-max))))))
+    ;; Return to original point.
+    (goto-char opoint)
+    (set-marker opoint nil)))
+
+(cond ((and hyperb:xemacs-p (or (>= emacs-minor-version 12)
+				(> emacs-major-version 19)))
+       (defun kview:goto-cell-id (id-string)
+	 "Move point to start of cell with idstamp ID-STRING and return t, else nil."
+	 (let ((cell-id (string-to-int id-string))
+	       label-end kcell)
+	   (setq label-end
+		 (map-extents
+		  (function (lambda (extent unused)
+			      (setq kcell (extent-property extent 'kcell))
+			      (if (= (kcell:idstamp kcell) cell-id)
+				  (extent-end-position extent))))
+		  nil nil nil nil nil 'kcell))
+	   (if (null label-end)
+	       nil
+	     (goto-char label-end)
+	     t))))
+      (hyperb:lemacs-p
+       (defun kview:goto-cell-id (id-string)
+	 "Move point to start of cell with idstamp ID-STRING and return t, else nil."
+	 (let ((cell-id (string-to-int id-string))
+	       label-end kcell)
+	   (setq label-end
+		 (map-extents
+		  (function (lambda (extent unused)
+			      (setq kcell (extent-property extent 'kcell))
+			      (and kcell (= (kcell:idstamp kcell) cell-id)
+				   (extent-end-position extent))))))
+	   (if (null label-end)
+	       nil
+	     (goto-char label-end)
+	     t))))
+      ;; Emacs 19
+      (t (defun kview:goto-cell-id (id-string)
+	   "Move point to start of cell with idstamp ID-STRING and return t, else nil."
+	   (let ((cell-id (string-to-int id-string))
+		 (opoint (point))
+		 pos kcell)
+	     (goto-char (point-min))
+	     (while (and (setq pos
+			       (kproperty:next-single-change (point) 'kcell))
+			 (goto-char pos)
+			 (or (null (setq kcell (kproperty:get pos 'kcell)))
+			     (/= (kcell:idstamp kcell) cell-id))))
+	     (if pos
+		 (progn
+		   (forward-char (kview:label-separator-length kview))
+		   t)
+	       (goto-char opoint)
+	       nil))))
+)
+
+(defun kview:id-increment (kview)
+  "Return next idstamp (an integer) for KVIEW."
+  (let* ((top-cell (kview:get-attr kview 'top-cell))
+	 (counter (1+ (kcell:get-attr top-cell 'id-counter))))
+    (kcell:set-attr top-cell 'id-counter counter)
+    counter))
+
+(defun kview:idstamp-to-label (permanent-id)
+  "Return relative label for cell with PERMANENT-ID within current kview."
+  (save-excursion
+    (if (kotl-mode:goto-cell permanent-id)
+	(kcell-view:label))))
+
+(defun kview:insert-contents (kcell contents no-fill fill-prefix)
+  "Insert KCELL's CONTENTS into view at point and fill resulting paragraphs, unless NO-FILL is non-nil.
+FILL-PREFIX is the indentation string for the current cell.
+If CONTENTS is nil, get contents from KCELL.  Return contents inserted (this
+value may differ from the value passed in.)"
+  (let ((start (point))
+	end)
+    (setq contents (or contents (kcell:contents kcell) ""))
+    (insert contents)
+    ;;
+    ;; Delete any extra newlines at end of cell contents.
+    (setq end (point))
+    (skip-chars-backward "\n\r")
+    (delete-region (point) end)
+    (setq end (point))
+    ;;
+    (save-restriction
+      (if no-fill
+	  ;; Insert proper indent in all but the first line which has
+	  ;; already been indented.
+	  (progn
+	    (narrow-to-region start end)
+	    (goto-char (point-min))
+	    (while (re-search-forward "[\n\r]" nil t)
+	      (insert fill-prefix))
+	    (goto-char (point-max)))
+	;;
+	;; Filling cell will insert proper indent on all lines.
+	(if (equal contents "")
+	    nil
+	  (goto-char start)
+	  (beginning-of-line)
+	  (narrow-to-region (point) end)
+	  ;; Add fill-prefix to all but paragraph separator lines, so
+	  ;; filling is done properly.
+	  (while (re-search-forward "[\n\r][^\n\r]" nil t)
+	    (forward-char -1) (insert fill-prefix))
+	  (kview:fill-region start end kcell)
+	  (goto-char (point-min))
+	  ;; Now add fill-prefix to paragraph separator lines.
+	  (while (re-search-forward "[\n\r][\n\r]" nil t)
+	    (forward-char -1) (insert fill-prefix))
+	  ;;
+	  (goto-char (point-max))))))
+  contents)
+
+(defun kview:is-p (object)
+  "Is OBJECT a kview?"
+  (if (listp object) (eq (car object) 'kview)))
+
+(defun kview:kotl (kview)
+  "Return kview's kotl object or nil if argument is not a kview."
+  (if (kview:is-p kview)
+      (kview:get-attr kview 'kotl)))
+
+(defun kview:label (klabel-function prev-label child-p)
+  "Return label string to display for current cell computed from KLABEL-FUNCTION, PREV-LABEL and CHILD-P."
+  (funcall klabel-function prev-label child-p))
+
+(defun kview:label-function (kview)
+  "Return function which will return display label for current cell in KVIEW.
+Function signature is: (func prev-label &optional child-p), where prev-label
+is the display label of the cell preceding the current one and child-p is
+non-nil if cell is to be the child of the preceding cell."
+  (kview:get-attr kview 'label-function))
+
+(defun kview:label-min-width (kview)
+  "Return kview's label-min-width setting or nil if argument is not a kview.
+See documentation for kview:default-label-min-width."
+  (if (kview:is-p kview)
+      (kview:get-attr kview 'label-min-width)))
+
+(defun kview:label-separator (kview)
+  "Return kview's label-separator setting or nil if argument is not a kview.
+See documentation for kview:default-label-separator."
+  (if (kview:is-p kview)
+      (kview:get-attr kview 'label-separator)))
+
+(defun kview:label-separator-length (kview)
+  "Return kview's label-separator length or nil if argument is not a kview.
+See documentation for kview:default-label-separator."
+  (kview:get-attr kview 'label-separator-length))
+
+(defun kview:label-type (kview)
+  "Return kview's label-type setting or nil if argument is not a kview.
+See documentation for kview:default-label-type."
+  (if (kview:is-p kview)
+      (kview:get-attr kview 'label-type)))
+
+(defun kview:level-indent (kview)
+  "Return kview's level-indent setting or nil if argument is not a kview.
+See documentation for kview:default-level-indent."
+  (if (kview:is-p kview)
+      (kview:get-attr kview 'level-indent)))
+
+(defun kview:map-branch (func kview &optional first-p visible-p)
+  "Applies FUNC to the sibling trees from point forward within KVIEW and returns results as a list.
+With optional FIRST-P non-nil, begins with first sibling in current branch.
+With optional VISIBLE-P, considers only those sibling cells that are visible
+in the view.
+
+FUNC should take one argument, the kview local variable of the current
+buffer or some other kview, and should operate upon the cell at point.
+
+`Cell-indent' contains the indentation value of the first cell mapped when
+FUNC is called so that it may test against this value.  `Label-sep-len'
+contains the label separator length.
+
+See also 'kview:map-siblings' and 'kview:map-tree'."
+    (save-excursion
+      (set-buffer (kview:buffer kview))
+      (let ((results)
+	    (label-sep-len (kview:label-separator-length kview)))
+	(if first-p
+	    ;; Move back to first predecessor at same level.
+	    (while (kcell-view:backward t label-sep-len)))
+	(let ((cell-indent (kcell-view:indent nil label-sep-len)))
+	  ;; Terminate when no further cells or when reach a cell at an equal
+	  ;; or higher level in the kotl than the first cell that we processed.
+	  (while (and (progn (setq results (cons (funcall func kview) results))
+			     (kcell-view:next visible-p label-sep-len))
+		      (> (kcell-view:indent nil label-sep-len) cell-indent))))
+	(nreverse results))))
+
+(defun kview:map-siblings (func kview &optional first-p visible-p)
+  "Applies FUNC to the sibling cells from point forward within KVIEW and returns results as a list.
+With optional FIRST-P non-nil, begins with first sibling in current branch.
+With optional VISIBLE-P, considers only those sibling cells that are visible
+in the view.
+
+FUNC should take one argument, the kview local variable of the current
+buffer or some other kview, and should operate upon the cell at point.
+
+`Cell-indent' contains the indentation value of the first cell mapped when
+FUNC is called so that it may test against this value.  `Label-sep-len'
+contains the label separator length.
+
+See also 'kview:map-branch' and 'kview:map-tree'."
+    (save-excursion
+      (set-buffer (kview:buffer kview))
+      (let ((results)
+	    (label-sep-len (kview:label-separator-length kview)))
+	(if first-p
+	    ;; Move back to first predecessor at same level.
+	    (while (kcell-view:backward t label-sep-len)))
+	(let ((cell-indent (kcell-view:indent nil label-sep-len)))
+	  ;; Terminate when no further cells at same level.
+	  (while (progn (setq results (cons (funcall func kview) results))
+			(kcell-view:forward visible-p label-sep-len))))
+	(nreverse results))))
+
+(defun kview:map-tree (func kview &optional top-p visible-p)
+  "Applies FUNC to the tree starting at point within KVIEW and returns results as a list.
+With optional TOP-P non-nil, maps over all of kview's cells.
+With optional VISIBLE-P, considers only those cells that are visible in the
+view.
+
+FUNC should take one argument, the kview local variable of the current
+buffer or some other kview, and should operate upon the cell at point.
+
+`Cell-indent' contains the indentation value of the first cell mapped when
+FUNC is called so that it may test against this value.  `Label-sep-len'
+contains the label separator length.
+
+See also 'kview:map-branch' and 'kview:map-siblings'."
+  (let ((results)
+	(label-sep-len (kview:label-separator-length kview)))
+    (save-excursion
+      (set-buffer (kview:buffer kview))
+      (if top-p
+	  (progn (goto-char (point-min))
+		 (kview:end-of-actual-line)
+		 ;; Terminate when no further cells to process.
+		 (while (progn 
+			  (setq results (cons (funcall func kview) results))
+			  (kcell-view:next visible-p label-sep-len))))
+	(let ((cell-indent (kcell-view:indent nil label-sep-len)))
+	  ;; Terminate when no further cells or when reach a cell at an equal
+	  ;; or higher level in the kotl than the first cell that we processed.
+	  (while (and (progn (setq results (cons (funcall func kview) results))
+			     (kcell-view:next visible-p label-sep-len))
+		      (> (kcell-view:indent nil label-sep-len)
+			 cell-indent))))))
+    (nreverse results)))
+
+(defun kview:move (from-start from-end to-start from-indent to-indent
+                   &optional copy-p fill-p)
+  "Move tree between FROM-START and FROM-END to TO-START, changing FROM-INDENT to TO-INDENT.
+Copy tree if optional COPY-P is non-nil.  Refill cells if optional
+FILL-P is non-nil.  Leave point at TO-START."
+  (let ((region (buffer-substring from-start from-end))
+	(new-start (set-marker (make-marker) to-start))
+	collapsed-cells	expr new-end space)
+    ;;
+    ;; Move or copy tree region to new location.
+    (or copy-p (delete-region from-start from-end))
+    (goto-char new-start)
+    (insert region)
+    (setq new-end (point))
+    ;;
+    ;; Change indentation of tree cells.
+    (if (/= from-indent to-indent)
+	(save-restriction
+	  (narrow-to-region new-start new-end)
+	  ;; Store list of which cells are presently collapsed.
+	  (setq 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 t))
+	  ;; Expand all cells.
+	  (subst-char-in-region new-start new-end ?\r ?\n t)
+	  ;;
+	  (goto-char (point-min))
+	  (if (< from-indent to-indent)
+	      ;; Add indent
+	      (progn
+		(setq expr (make-string (1+ (- to-indent from-indent)) ?\ ))
+		(while (re-search-forward "^ " nil t)
+		  (replace-match expr t t)
+		  (forward-line 1)))
+	    ;; Reduce indent in all but first cell lines.
+	    (setq expr (concat "^" (make-string
+				    (- from-indent to-indent) ?\ )))
+	    (while (re-search-forward expr nil t)
+	      (replace-match "" t t)
+	      (forward-line 1))
+	    ;; Reduce indent in first cell lines which may have an
+	    ;; autonumber or other cell delimiter.
+	    (setq space (- from-indent to-indent
+			   (kview:label-separator-length kview)
+			   1))
+	    (if (zerop space)
+		nil
+	      (setq expr (concat "^" (make-string
+				      (- from-indent to-indent
+					 (kview:label-separator-length kview)
+					 1)
+				      ?\ )))
+	      (kview:map-tree
+	       (function (lambda (view)
+			   (save-excursion
+			     (beginning-of-line)
+			     (if (looking-at expr)
+				 (replace-match "" t t)))))
+	       kview t)))
+	  ;;
+	  (if fill-p
+	      ;; Refill cells without no-fill attribute.
+	      (kview:map-tree (function (lambda (view)
+					  (kotl-mode:fill-cell nil t)))
+			      kview t))
+	  ;;
+	  ;; 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 t))))
+    ;;
+    (goto-char new-start)
+    ;;
+    ;; Delete temporary markers.
+    (set-marker new-start nil)))
+
+(defun kview:set-buffer-name (kview new-name)
+  "Set kview's buffer name to NEW-NAME."
+  (if (kview:is-p kview)
+      (save-excursion
+	(let ((buf (kview:buffer kview)))
+	  (if buf (set-buffer buf)))
+	(kview:set-attr kview 'view-buffer-name new-name))
+    (error "(kview:set-buffer-name): Invalid kview argument")))
+
+(defun kview:set-label-type (kview new-type)
+  "Change kview's label display type to NEW-TYPE, updating all displayed labels.
+See documentation for variable, kview:default-label-type, for
+valid values of NEW-TYPE."
+  (interactive (list kview
+		     (let ((completion-ignore-case)
+			   (label-type (kview:label-type kview))
+			   new-type-str)
+		       (if (string=
+			    ""
+			    (setq new-type-str
+				  (completing-read
+				   (format "View label type (current = %s): "
+					   label-type)
+				   '(("alpha") ("legal") ("id") ("no")
+				     ("partial-alpha") ("star"))
+				   nil t)))
+			   label-type
+			 (intern new-type-str)))))
+  (if (not (memq new-type '(alpha legal id no partial-alpha star)))
+      (error "(kview:set-label-type): Invalid label type, '%s'." new-type))
+  ;; Disable use of partial-alpha for now since it is broken.
+  (if (eq new-type 'partial-alpha)
+      (error "(kview:set-label-type): Partial-alpha labels don't work, choose another type"))
+  (let ((old-label-type (kview:label-type kview)))
+    (if (eq old-label-type new-type)
+	nil
+      (klabel-type:set-labels new-type)
+      (kview:set-attr kview 'label-type new-type)
+      (kview:set-functions new-type)
+      (kvspec:update t))))
+
+(defun kview:top-cell (kview)
+  "Return kview's invisible top cell with idstamp 0 or nil if argument is not a kview."
+  (if (kview:is-p kview)
+      (kview:get-attr kview 'top-cell)))
+
+(defun kview:valid-position-p (&optional pos)
+  "Return non-nil iff point or optional POS is at a position where editing may occur.
+The read-only positions between cells and within cell indentations are invalid."
+  (cond ((null pos)
+	 (>= (current-column) (kcell-view:indent)))
+	((not (integer-or-marker-p pos))
+	 (error "(kview:valid-position-p): Argument POS not an integer
+or marker, '%s'" pos))
+	((or (< pos (point-min)) (> pos (point-max)))
+	 (error "(kview:valid-position-p): Invalid POS argument, '%d'"
+		pos))
+	(t (save-excursion
+	     (goto-char pos)
+	     (>= (current-column) (kcell-view:indent))))))
+
+;;; ************************************************************************
+;;; Private functions
+;;; ************************************************************************
+
+(defun kview:get-attr (obj attribute)
+  "Return the value of OBJECT's ATTRIBUTE."
+  (car (cdr (memq attribute (car (cdr (memq 'plist obj)))))))
+
+(defun kview:set-attr (obj attribute value)
+  "Set OBJECT's ATTRIBUTE to VALUE and return VALUE."
+  (let* ((plist-ptr (cdr (memq 'plist obj)))
+	 (plist (car plist-ptr))
+	 (attr (memq attribute plist)))
+    (if attr
+	(setcar (cdr attr) value)
+      (setcar plist-ptr
+	      (nconc (list attribute value) plist)))
+    value))
+
+(defun kview:set-functions (label-type)
+  "Setup functions which handle labels of LABEL-TYPE for current view."
+  (kview:set-attr kview 'label-function (klabel-type:function label-type))
+  (kview:set-attr kview 'label-child (klabel-type:child label-type))
+  (kview:set-attr kview 'label-increment (klabel-type:increment label-type))
+  (kview:set-attr kview 'label-parent (klabel-type:parent label-type))
+  (kview:set-attr kview 'to-label-end (klabel-type:to-label-end label-type)))
+
+(defun kview:set-label-separator (label-separator &optional set-default-p)
+  "Set the LABEL-SEPARATOR (a string) between labels and cell contents for the current kview.
+With optional prefix arg SET-DEFAULT-P, the default separator value used for
+new outlines is also set to this new value."
+  (interactive
+   (progn (barf-if-buffer-read-only)
+	  (list (if (kview:is-p kview)
+		    (read-string
+		     (format
+		      "Change current%s label separator from \"%s\" to: "
+		      (if current-prefix-arg " and default" "")
+		      (kview:label-separator kview))))
+		current-prefix-arg)))
+
+  (barf-if-buffer-read-only)
+  (cond ((not (kview:is-p kview))
+	 (error "(kview:set-label-separator): This is not a koutline"))
+	((not (stringp label-separator))
+	 (error "(kview:set-label-separator): Invalid separator, \"%s\""
+		label-separator))
+	((< (length label-separator) 2)
+	 (error "(kview:set-label-separator): Separator must be two or more characters, \"%s\""
+		label-separator)))
+
+  (let* ((old-sep-len (kview:label-separator-length kview))
+	 (sep-len (length label-separator))
+	 (sep-len-increase (- sep-len old-sep-len))
+	 (indent)
+	 (reindent-function
+	  (cond ((zerop sep-len-increase)
+		 (function (lambda ())))
+		((> sep-len-increase 0)
+		 ;; Increase indent in each cell line.
+		 (function (lambda ()
+			     (goto-char (point-min))
+			     (setq indent (make-string
+					   sep-len-increase ?\ ))
+			     (while (re-search-forward "[^\n\r][\n\r] " nil t)
+			       (insert indent)))))
+		(t
+		 ;; Decrease indent in each cell line.
+		 (function (lambda ()
+			     (goto-char (point-min))
+			     (setq indent
+				   (concat "[^\n\r][\n\r]"
+					   (make-string
+					    (- sep-len-increase) ?\ )))
+			     (while (re-search-forward indent nil t)
+			       (delete-region
+				(+ (match-beginning 0) 2) (match-end 0))))))))
+	 pos)
+    (save-excursion
+      (goto-char (point-min))
+      (kproperty:replace-separator pos label-separator old-sep-len)
+      ;; Reindent all lines in cells except the first line which has already
+      ;; been done.
+      (funcall reindent-function))
+    (kview:set-attr kview 'label-separator label-separator)
+    (kview:set-attr kview 'label-separator-length sep-len)
+    (if set-default-p
+	(setq kview:default-label-separator label-separator))))
+
+(provide 'kview)