summaryrefslogtreecommitdiff
path: root/lisp/nxml/nxml-outln.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/nxml/nxml-outln.el')
-rw-r--r--lisp/nxml/nxml-outln.el1043
1 files changed, 1043 insertions, 0 deletions
diff --git a/lisp/nxml/nxml-outln.el b/lisp/nxml/nxml-outln.el
new file mode 100644
index 00000000000..96d8cebf5dc
--- /dev/null
+++ b/lisp/nxml/nxml-outln.el
@@ -0,0 +1,1043 @@
+;;; nxml-outln.el --- outline support for nXML mode
+
+;; Copyright (C) 2004, 2007 Free Software Foundation, Inc.
+
+;; Author: James Clark
+;; Keywords: XML
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
+
+;;; Commentary:
+
+;; A section can be in one of three states
+;; 1. display normally; this displays each child section
+;; according to its state; anything not part of child sections is also
+;; displayed normally
+;; 2. display just the title specially; child sections are not displayed
+;; regardless of their state; anything not part of child sections is
+;; not displayed
+;; 3. display the title specially and display child sections
+;; according to their state; anything not part of the child section is
+;; not displayed
+;; The state of a section is determined by the value of the
+;; nxml-outline-state text property of the < character that starts
+;; the section.
+;; For state 1 the value is nil or absent.
+;; For state 2 it is the symbol hide-children.
+;; For state 3 it is t.
+;; The special display is achieved by using overlays. The overlays
+;; are computed from the nxml-outline-state property by
+;; `nxml-refresh-outline'. There overlays all have a category property
+;; with an nxml-outline-display property with value t.
+;;
+;; For a section to be recognized as such, the following conditions must
+;; be satisfied:
+;; - its start-tag must occur at the start of a line (possibly indented)
+;; - its local name must match `nxml-section-element-name-regexp'
+;; - it must have a heading element; a heading element is an
+;; element whose name matches `nxml-heading-element-name-regexp',
+;; and that occurs as, or as a descendant of, the first child element
+;; of the section
+;;
+;; XXX What happens if an nxml-outline-state property is attached to a
+;; character that doesn't start a section element?
+;;
+;; An outlined section (an section with a non-nil nxml-outline-state
+;; property) can be displayed in either single-line or multi-line
+;; form. Single-line form is used when the outline state is hide-children
+;; or there are no child sections; multi-line form is used otherwise.
+;; There are two flavors of single-line form: with children and without.
+;; The with-childen flavor is used when there are child sections.
+;; Single line with children looks like
+;; <+section>A section title...</>
+;; Single line without children looks like
+;; <-section>A section title...</>
+;; Multi line looks likes
+;; <-section>A section title...
+;; [child sections displayed here]
+;; </-section>
+;; The indent of an outlined section is computed relative to the
+;; outermost containing outlined element. The indent of the
+;; outermost containing element comes from the non-outlined
+;; indent of the section start-tag.
+
+;;; Code:
+
+(require 'xmltok)
+(require 'nxml-util)
+(require 'nxml-rap)
+
+(defcustom nxml-section-element-name-regexp
+ "article\\|\\(sub\\)*section\\|chapter\\|div\\|appendix\\|part\\|preface\\|reference\\|simplesect\\|bibliography\\|bibliodiv\\|glossary\\|glossdiv"
+ "*Regular expression matching the name of elements used as sections.
+An XML element is treated as a section if:
+
+- its local name (that is, the name without the prefix) matches
+this regexp;
+
+- either its first child element or a descendant of that first child
+element has a local name matching the variable
+`nxml-heading-element-name-regexp'; and
+
+- its start-tag occurs at the beginning of a line (possibly indented)."
+ :group 'nxml
+ :type 'regexp)
+
+(defcustom nxml-heading-element-name-regexp "title\\|head"
+ "*Regular expression matching the name of elements used as headings.
+An XML element is only recognized as a heading if it occurs as or
+within the first child of an element that is recognized as a section.
+See the variable `nxml-section-element-name-regexp' for more details."
+ :group 'nxml
+ :type 'regexp)
+
+(defcustom nxml-outline-child-indent 2
+ "*Indentation in an outline for child element relative to parent element."
+ :group 'nxml
+ :type 'integer)
+
+(defface nxml-heading
+ '((t (:weight bold)))
+ "Face used for the contents of abbreviated heading elements."
+ :group 'nxml-faces)
+
+(defface nxml-outline-indicator
+ '((t (:inherit default)))
+ "Face used for `+' or `-' before element names in outlines."
+ :group 'nxml-faces)
+
+(defface nxml-outline-active-indicator
+ '((t (:box t :inherit nxml-outline-indicator)))
+ "Face used for clickable `+' or `-' before element names in outlines."
+ :group 'nxml-faces)
+
+(defface nxml-outline-ellipsis
+ '((t (:bold t :inherit default)))
+ "Face used for `...' in outlines."
+ :group 'nxml-faces)
+
+(defvar nxml-heading-scan-distance 1000
+ "Maximum distance from section to scan for heading.")
+
+(defvar nxml-outline-prefix-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map "\C-a" 'nxml-show-all)
+ (define-key map "\C-t" 'nxml-hide-all-text-content)
+ (define-key map "\C-r" 'nxml-refresh-outline)
+ (define-key map "\C-c" 'nxml-hide-direct-text-content)
+ (define-key map "\C-e" 'nxml-show-direct-text-content)
+ (define-key map "\C-d" 'nxml-hide-subheadings)
+ (define-key map "\C-s" 'nxml-show)
+ (define-key map "\C-k" 'nxml-show-subheadings)
+ (define-key map "\C-l" 'nxml-hide-text-content)
+ (define-key map "\C-i" 'nxml-show-direct-subheadings)
+ (define-key map "\C-o" 'nxml-hide-other)
+ map))
+
+;;; Commands for changing visibility
+
+(defun nxml-show-all ()
+ "Show all elements in the buffer normally."
+ (interactive)
+ (nxml-with-unmodifying-text-property-changes
+ (remove-text-properties (point-min)
+ (point-max)
+ '(nxml-outline-state nil)))
+ (nxml-outline-set-overlay nil (point-min) (point-max)))
+
+(defun nxml-hide-all-text-content ()
+ "Hide all text content in the buffer.
+Anything that is in a section but is not a heading will be hidden.
+The visibility of headings at any level will not be changed. See the
+variable `nxml-section-element-name-regexp' for more details on how to
+customize which elements are recognized as sections and headings."
+ (interactive)
+ (nxml-transform-buffer-outline '((nil . t))))
+
+(defun nxml-show-direct-text-content ()
+ "Show the text content that is directly part of the section containing point.
+Each subsection will be shown according to its individual state, which
+will not be changed. The section containing point is the innermost
+section that contains the character following point. See the variable
+`nxml-section-element-name-regexp' for more details on how to
+customize which elements are recognized as sections and headings."
+ (interactive)
+ (nxml-outline-pre-adjust-point)
+ (nxml-set-outline-state (nxml-section-start-position) nil)
+ (nxml-refresh-outline)
+ (nxml-outline-adjust-point))
+
+(defun nxml-show-direct-subheadings ()
+ "Show the immediate subheadings of the section containing point.
+The section containing point is the innermost section that contains
+the character following point. See the variable
+`nxml-section-element-name-regexp' for more details on how to
+customize which elements are recognized as sections and headings."
+ (interactive)
+ (let ((pos (nxml-section-start-position)))
+ (when (eq (nxml-get-outline-state pos) 'hide-children)
+ (nxml-set-outline-state pos t)))
+ (nxml-refresh-outline)
+ (nxml-outline-adjust-point))
+
+(defun nxml-hide-direct-text-content ()
+ "Hide the text content that is directly part of the section containing point.
+The heading of the section will remain visible. The state of
+subsections will not be changed. The section containing point is the
+innermost section that contains the character following point. See the
+variable `nxml-section-element-name-regexp' for more details on how to
+customize which elements are recognized as sections and headings."
+ (interactive)
+ (let ((pos (nxml-section-start-position)))
+ (when (null (nxml-get-outline-state pos))
+ (nxml-set-outline-state pos t)))
+ (nxml-refresh-outline)
+ (nxml-outline-adjust-point))
+
+(defun nxml-hide-subheadings ()
+ "Hide the subheadings that are part of the section containing point.
+The text content will also be hidden, leaving only the heading of the
+section itself visible. The state of the subsections will also be
+changed to hide their headings, so that \\[nxml-show-direct-text-content]
+would show only the heading of the subsections. The section containing
+point is the innermost section that contains the character following
+point. See the variable `nxml-section-element-name-regexp' for more
+details on how to customize which elements are recognized as sections
+and headings."
+ (interactive)
+ (nxml-transform-subtree-outline '((nil . hide-children)
+ (t . hide-children))))
+
+(defun nxml-show ()
+ "Show the section containing point normally, without hiding anything.
+This includes everything in the section at any level. The section
+containing point is the innermost section that contains the character
+following point. See the variable `nxml-section-element-name-regexp'
+for more details on how to customize which elements are recognized as
+sections and headings."
+ (interactive)
+ (nxml-transform-subtree-outline '((hide-children . nil)
+ (t . nil))))
+
+(defun nxml-hide-text-content ()
+ "Hide text content at all levels in the section containing point.
+The section containing point is the innermost section that contains
+the character following point. See the variable
+`nxml-section-element-name-regexp' for more details on how to
+customize which elements are recognized as sections and headings."
+ (interactive)
+ (nxml-transform-subtree-outline '((nil . t))))
+
+(defun nxml-show-subheadings ()
+ "Show the subheadings at all levels of the section containing point.
+The visibility of the text content at all levels in the section is not
+changed. The section containing point is the innermost section that
+contains the character following point. See the variable
+`nxml-section-element-name-regexp' for more details on how to
+customize which elements are recognized as sections and headings."
+ (interactive)
+ (nxml-transform-subtree-outline '((hide-children . t))))
+
+(defun nxml-hide-other ()
+ "Hide text content other than that directly in the section containing point.
+Hide headings other than those of ancestors of that section and their
+immediate subheadings. The section containing point is the innermost
+section that contains the character following point. See the variable
+`nxml-section-element-name-regexp' for more details on how to
+customize which elements are recognized as sections and headings."
+ (interactive)
+ (let ((nxml-outline-state-transform-exceptions nil))
+ (save-excursion
+ (while (and (condition-case err
+ (nxml-back-to-section-start)
+ (nxml-outline-error (nxml-report-outline-error
+ "Couldn't find containing section: %s"
+ err)))
+ (progn
+ (when (and nxml-outline-state-transform-exceptions
+ (null (nxml-get-outline-state (point))))
+ (nxml-set-outline-state (point) t))
+ (setq nxml-outline-state-transform-exceptions
+ (cons (point)
+ nxml-outline-state-transform-exceptions))
+ (< nxml-prolog-end (point))))
+ (goto-char (1- (point)))))
+ (nxml-transform-buffer-outline '((nil . hide-children)
+ (t . hide-children)))))
+
+;; These variables are dynamically bound. They are use to pass information to
+;; nxml-section-tag-transform-outline-state.
+
+(defvar nxml-outline-state-transform-exceptions nil)
+(defvar nxml-target-section-pos nil)
+(defvar nxml-depth-in-target-section nil)
+(defvar nxml-outline-state-transform-alist nil)
+
+(defun nxml-transform-buffer-outline (alist)
+ (let ((nxml-target-section-pos nil)
+ (nxml-depth-in-target-section 0)
+ (nxml-outline-state-transform-alist alist)
+ (nxml-outline-display-section-tag-function
+ 'nxml-section-tag-transform-outline-state))
+ (nxml-refresh-outline))
+ (nxml-outline-adjust-point))
+
+(defun nxml-transform-subtree-outline (alist)
+ (let ((nxml-target-section-pos (nxml-section-start-position))
+ (nxml-depth-in-target-section nil)
+ (nxml-outline-state-transform-alist alist)
+ (nxml-outline-display-section-tag-function
+ 'nxml-section-tag-transform-outline-state))
+ (nxml-refresh-outline))
+ (nxml-outline-adjust-point))
+
+(defun nxml-outline-pre-adjust-point ()
+ (cond ((and (< (point-min) (point))
+ (get-char-property (1- (point)) 'invisible)
+ (not (get-char-property (point) 'invisible))
+ (let ((str (or (get-char-property (point) 'before-string)
+ (get-char-property (point) 'display))))
+ (and (stringp str)
+ (>= (length str) 3)
+ (string= (substring str 0 3) "..."))))
+ ;; The ellipsis is a display property on a visible character
+ ;; following an invisible region. The position of the event
+ ;; will be the position before that character. We want to
+ ;; move point to the other side of the invisible region, i.e.
+ ;; following the last visible character before that invisible
+ ;; region.
+ (goto-char (previous-single-char-property-change (1- (point))
+ 'invisible)))
+ ((and (< (point) (point-max))
+ (get-char-property (point) 'display)
+ (get-char-property (1+ (point)) 'invisible))
+ (goto-char (next-single-char-property-change (1+ (point))
+ 'invisible)))
+ ((and (< (point) (point-max))
+ (get-char-property (point) 'invisible))
+ (goto-char (next-single-char-property-change (point)
+ 'invisible)))))
+
+(defun nxml-outline-adjust-point ()
+ "Adjust point after showing or hiding elements."
+ (when (and (get-char-property (point) 'invisible)
+ (< (point-min) (point))
+ (get-char-property (1- (point)) 'invisible))
+ (goto-char (previous-single-char-property-change (point)
+ 'invisible
+ nil
+ nxml-prolog-end))))
+
+(defun nxml-transform-outline-state (section-start-pos)
+ (let* ((old-state
+ (nxml-get-outline-state section-start-pos))
+ (change (assq old-state
+ nxml-outline-state-transform-alist)))
+ (when change
+ (nxml-set-outline-state section-start-pos
+ (cdr change)))))
+
+(defun nxml-section-tag-transform-outline-state (startp
+ section-start-pos
+ &optional
+ heading-start-pos)
+ (if (not startp)
+ (setq nxml-depth-in-target-section
+ (and nxml-depth-in-target-section
+ (> nxml-depth-in-target-section 0)
+ (1- nxml-depth-in-target-section)))
+ (cond (nxml-depth-in-target-section
+ (setq nxml-depth-in-target-section
+ (1+ nxml-depth-in-target-section)))
+ ((= section-start-pos nxml-target-section-pos)
+ (setq nxml-depth-in-target-section 0)))
+ (when (and nxml-depth-in-target-section
+ (not (member section-start-pos
+ nxml-outline-state-transform-exceptions)))
+ (nxml-transform-outline-state section-start-pos))))
+
+(defun nxml-get-outline-state (pos)
+ (get-text-property pos 'nxml-outline-state))
+
+(defun nxml-set-outline-state (pos state)
+ (nxml-with-unmodifying-text-property-changes
+ (if state
+ (put-text-property pos (1+ pos) 'nxml-outline-state state)
+ (remove-text-properties pos (1+ pos) '(nxml-outline-state nil)))))
+
+;;; Mouse interface
+
+(defun nxml-mouse-show-direct-text-content (event)
+ "Do the same as \\[nxml-show-direct-text-content] from a mouse click."
+ (interactive "e")
+ (and (nxml-mouse-set-point event)
+ (nxml-show-direct-text-content)))
+
+(defun nxml-mouse-hide-direct-text-content (event)
+ "Do the same as \\[nxml-hide-direct-text-content] from a mouse click."
+ (interactive "e")
+ (and (nxml-mouse-set-point event)
+ (nxml-hide-direct-text-content)))
+
+(defun nxml-mouse-hide-subheadings (event)
+ "Do the same as \\[nxml-hide-subheadings] from a mouse click."
+ (interactive "e")
+ (and (nxml-mouse-set-point event)
+ (nxml-hide-subheadings)))
+
+(defun nxml-mouse-show-direct-subheadings (event)
+ "Do the same as \\[nxml-show-direct-subheadings] from a mouse click."
+ (interactive "e")
+ (and (nxml-mouse-set-point event)
+ (nxml-show-direct-subheadings)))
+
+(defun nxml-mouse-set-point (event)
+ (mouse-set-point event)
+ (and nxml-prolog-end t))
+
+;; Display
+
+(defsubst nxml-token-start-tag-p ()
+ (or (eq xmltok-type 'start-tag)
+ (eq xmltok-type 'partial-start-tag)))
+
+(defsubst nxml-token-end-tag-p ()
+ (or (eq xmltok-type 'end-tag)
+ (eq xmltok-type 'partial-end-tag)))
+
+(defun nxml-refresh-outline ()
+ "Refresh the outline to correspond to the current XML element structure."
+ (interactive)
+ (save-excursion
+ (goto-char (point-min))
+ (kill-local-variable 'line-move-ignore-invisible)
+ (make-local-variable 'line-move-ignore-invisible)
+ (condition-case err
+ (nxml-outline-display-rest nil nil nil)
+ (nxml-outline-error
+ (nxml-report-outline-error "Cannot display outline: %s" err)))))
+
+(defvar nxml-outline-display-section-tag-function nil)
+
+(defun nxml-outline-display-rest (outline-state start-tag-indent tag-qnames)
+ "Display up to and including the end of the current element.
+OUTLINE-STATE can be nil, t, hide-children. START-TAG-INDENT is the
+indent of the start-tag of the current element, or nil if no
+containing element has a non-nil OUTLINE-STATE. TAG-QNAMES is a list
+of the qnames of the open elements. Point is after the title content.
+Leave point after the closing end-tag Return t if we had a
+non-transparent child section."
+ (let ((last-pos (point))
+ (transparent-depth 0)
+ ;; don't want ellipsis before root element
+ (had-children (not tag-qnames)))
+ (while
+ (cond ((not (nxml-section-tag-forward))
+ (if (null tag-qnames)
+ nil
+ (nxml-outline-error "missing end-tag %s"
+ (car tag-qnames))))
+ ;; section end-tag
+ ((nxml-token-end-tag-p)
+ (when nxml-outline-display-section-tag-function
+ (funcall nxml-outline-display-section-tag-function
+ nil
+ xmltok-start))
+ (let ((qname (xmltok-end-tag-qname)))
+ (unless tag-qnames
+ (nxml-outline-error "extra end-tag %s" qname))
+ (unless (string= (car tag-qnames) qname)
+ (nxml-outline-error "mismatched end-tag; expected %s, got %s"
+ (car tag-qnames)
+ qname)))
+ (cond ((> transparent-depth 0)
+ (setq transparent-depth (1- transparent-depth))
+ (setq tag-qnames (cdr tag-qnames))
+ t)
+ ((not outline-state)
+ (nxml-outline-set-overlay nil last-pos (point))
+ nil)
+ ((or (not had-children)
+ (eq outline-state 'hide-children))
+ (nxml-outline-display-single-line-end-tag last-pos)
+ nil)
+ (t
+ (nxml-outline-display-multi-line-end-tag last-pos
+ start-tag-indent)
+ nil)))
+ ;; section start-tag
+ (t
+ (let* ((qname (xmltok-start-tag-qname))
+ (section-start-pos xmltok-start)
+ (heading-start-pos
+ (and (or nxml-outline-display-section-tag-function
+ (not (eq outline-state 'had-children))
+ (not had-children))
+ (nxml-token-starts-line-p)
+ (nxml-heading-start-position))))
+ (when nxml-outline-display-section-tag-function
+ (funcall nxml-outline-display-section-tag-function
+ t
+ section-start-pos
+ heading-start-pos))
+ (setq tag-qnames (cons qname tag-qnames))
+ (if (or (not heading-start-pos)
+ (and (eq outline-state 'hide-children)
+ (setq had-children t)))
+ (setq transparent-depth (1+ transparent-depth))
+ (nxml-display-section last-pos
+ section-start-pos
+ heading-start-pos
+ start-tag-indent
+ outline-state
+ had-children
+ tag-qnames)
+ (setq had-children t)
+ (setq tag-qnames (cdr tag-qnames))
+ (setq last-pos (point))))
+ t)))
+ had-children))
+
+(defconst nxml-highlighted-less-than
+ (propertize "<" 'face 'nxml-tag-delimiter))
+
+(defconst nxml-highlighted-greater-than
+ (propertize ">" 'face 'nxml-tag-delimiter))
+
+(defconst nxml-highlighted-colon
+ (propertize ":" 'face 'nxml-element-colon))
+
+(defconst nxml-highlighted-slash
+ (propertize "/" 'face 'nxml-tag-slash))
+
+(defconst nxml-highlighted-ellipsis
+ (propertize "..." 'face 'nxml-outline-ellipsis))
+
+(defconst nxml-highlighted-empty-end-tag
+ (concat nxml-highlighted-ellipsis
+ nxml-highlighted-less-than
+ nxml-highlighted-slash
+ nxml-highlighted-greater-than))
+
+(defconst nxml-highlighted-inactive-minus
+ (propertize "-" 'face 'nxml-outline-indicator))
+
+(defconst nxml-highlighted-active-minus
+ (propertize "-" 'face 'nxml-outline-active-indicator))
+
+(defconst nxml-highlighted-active-plus
+ (propertize "+" 'face 'nxml-outline-active-indicator))
+
+(defun nxml-display-section (last-pos
+ section-start-pos
+ heading-start-pos
+ parent-indent
+ parent-outline-state
+ had-children
+ tag-qnames)
+ (let* ((section-start-pos-bol
+ (save-excursion
+ (goto-char section-start-pos)
+ (skip-chars-backward " \t")
+ (point)))
+ (outline-state (nxml-get-outline-state section-start-pos))
+ (newline-before-section-start-category
+ (cond ((and (not had-children) parent-outline-state)
+ 'nxml-outline-display-ellipsis)
+ (outline-state 'nxml-outline-display-show)
+ (t nil))))
+ (nxml-outline-set-overlay (and parent-outline-state
+ 'nxml-outline-display-hide)
+ last-pos
+ (1- section-start-pos-bol)
+ nil
+ t)
+ (if outline-state
+ (let* ((indent (if parent-indent
+ (+ parent-indent nxml-outline-child-indent)
+ (save-excursion
+ (goto-char section-start-pos)
+ (current-column))))
+ start-tag-overlay)
+ (nxml-outline-set-overlay newline-before-section-start-category
+ (1- section-start-pos-bol)
+ section-start-pos-bol
+ t)
+ (nxml-outline-set-overlay 'nxml-outline-display-hide
+ section-start-pos-bol
+ section-start-pos)
+ (setq start-tag-overlay
+ (nxml-outline-set-overlay 'nxml-outline-display-show
+ section-start-pos
+ (1+ section-start-pos)
+ t))
+ ;; line motion commands don't work right if start-tag-overlay
+ ;; covers multiple lines
+ (nxml-outline-set-overlay 'nxml-outline-display-hide
+ (1+ section-start-pos)
+ heading-start-pos)
+ (goto-char heading-start-pos)
+ (nxml-end-of-heading)
+ (nxml-outline-set-overlay 'nxml-outline-display-heading
+ heading-start-pos
+ (point))
+ (let* ((had-children
+ (nxml-outline-display-rest outline-state
+ indent
+ tag-qnames)))
+ (overlay-put start-tag-overlay
+ 'display
+ (concat
+ ;; indent
+ (make-string indent ?\ )
+ ;; <
+ nxml-highlighted-less-than
+ ;; + or - indicator
+ (cond ((not had-children)
+ nxml-highlighted-inactive-minus)
+ ((eq outline-state 'hide-children)
+ (overlay-put start-tag-overlay
+ 'category
+ 'nxml-outline-display-hiding-tag)
+ nxml-highlighted-active-plus)
+ (t
+ (overlay-put start-tag-overlay
+ 'category
+ 'nxml-outline-display-showing-tag)
+ nxml-highlighted-active-minus))
+ ;; qname
+ (nxml-highlighted-qname (car tag-qnames))
+ ;; >
+ nxml-highlighted-greater-than))))
+ ;; outline-state nil
+ (goto-char heading-start-pos)
+ (nxml-end-of-heading)
+ (nxml-outline-set-overlay newline-before-section-start-category
+ (1- section-start-pos-bol)
+ (point)
+ t)
+ (nxml-outline-display-rest outline-state
+ (and parent-indent
+ (+ parent-indent
+ nxml-outline-child-indent))
+ tag-qnames))))
+
+(defun nxml-highlighted-qname (qname)
+ (let ((colon (string-match ":" qname)))
+ (if colon
+ (concat (propertize (substring qname 0 colon)
+ 'face
+ 'nxml-element-prefix)
+ nxml-highlighted-colon
+ (propertize (substring qname (1+ colon))
+ 'face
+ 'nxml-element-local-name))
+ (propertize qname
+ 'face
+ 'nxml-element-local-name))))
+
+(defun nxml-outline-display-single-line-end-tag (last-pos)
+ (nxml-outline-set-overlay 'nxml-outline-display-hide
+ last-pos
+ xmltok-start
+ nil
+ t)
+ (overlay-put (nxml-outline-set-overlay 'nxml-outline-display-show
+ xmltok-start
+ (point)
+ t)
+ 'display
+ nxml-highlighted-empty-end-tag))
+
+(defun nxml-outline-display-multi-line-end-tag (last-pos start-tag-indent)
+ (let ((indentp (save-excursion
+ (goto-char last-pos)
+ (skip-chars-forward " \t")
+ (and (eq (char-after) ?\n)
+ (progn
+ (goto-char (1+ (point)))
+ (nxml-outline-set-overlay nil last-pos (point))
+ (setq last-pos (point))
+ (goto-char xmltok-start)
+ (beginning-of-line)
+ t))))
+ end-tag-overlay)
+ (nxml-outline-set-overlay 'nxml-outline-display-hide
+ last-pos
+ xmltok-start
+ nil
+ t)
+ (setq end-tag-overlay
+ (nxml-outline-set-overlay 'nxml-outline-display-showing-tag
+ xmltok-start
+ (point)
+ t))
+ (overlay-put end-tag-overlay
+ 'display
+ (concat (if indentp
+ (make-string start-tag-indent ?\ )
+ "")
+ nxml-highlighted-less-than
+ nxml-highlighted-slash
+ nxml-highlighted-active-minus
+ (nxml-highlighted-qname (xmltok-end-tag-qname))
+ nxml-highlighted-greater-than))))
+
+(defvar nxml-outline-show-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map "\C-m" 'nxml-show-direct-text-content)
+ (define-key map [mouse-2] 'nxml-mouse-show-direct-text-content)
+ map))
+
+(defvar nxml-outline-show-help "mouse-2: show")
+
+(put 'nxml-outline-display-show 'nxml-outline-display t)
+(put 'nxml-outline-display-show 'evaporate t)
+(put 'nxml-outline-display-show 'keymap nxml-outline-show-map)
+(put 'nxml-outline-display-show 'help-echo nxml-outline-show-help)
+
+(put 'nxml-outline-display-hide 'nxml-outline-display t)
+(put 'nxml-outline-display-hide 'evaporate t)
+(put 'nxml-outline-display-hide 'invisible t)
+(put 'nxml-outline-display-hide 'keymap nxml-outline-show-map)
+(put 'nxml-outline-display-hide 'help-echo nxml-outline-show-help)
+
+(put 'nxml-outline-display-ellipsis 'nxml-outline-display t)
+(put 'nxml-outline-display-ellipsis 'evaporate t)
+(put 'nxml-outline-display-ellipsis 'keymap nxml-outline-show-map)
+(put 'nxml-outline-display-ellipsis 'help-echo nxml-outline-show-help)
+(put 'nxml-outline-display-ellipsis 'before-string nxml-highlighted-ellipsis)
+
+(put 'nxml-outline-display-heading 'keymap nxml-outline-show-map)
+(put 'nxml-outline-display-heading 'help-echo nxml-outline-show-help)
+(put 'nxml-outline-display-heading 'nxml-outline-display t)
+(put 'nxml-outline-display-heading 'evaporate t)
+(put 'nxml-outline-display-heading 'face 'nxml-heading)
+
+(defvar nxml-outline-hiding-tag-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map [mouse-1] 'nxml-mouse-show-direct-subheadings)
+ (define-key map [mouse-2] 'nxml-mouse-show-direct-text-content)
+ (define-key map "\C-m" 'nxml-show-direct-text-content)
+ map))
+
+(defvar nxml-outline-hiding-tag-help
+ "mouse-1: show subheadings, mouse-2: show text content")
+
+(put 'nxml-outline-display-hiding-tag 'nxml-outline-display t)
+(put 'nxml-outline-display-hiding-tag 'evaporate t)
+(put 'nxml-outline-display-hiding-tag 'keymap nxml-outline-hiding-tag-map)
+(put 'nxml-outline-display-hiding-tag 'help-echo nxml-outline-hiding-tag-help)
+
+(defvar nxml-outline-showing-tag-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map [mouse-1] 'nxml-mouse-hide-subheadings)
+ (define-key map [mouse-2] 'nxml-mouse-show-direct-text-content)
+ (define-key map "\C-m" 'nxml-show-direct-text-content)
+ map))
+
+(defvar nxml-outline-showing-tag-help
+ "mouse-1: hide subheadings, mouse-2: show text content")
+
+(put 'nxml-outline-display-showing-tag 'nxml-outline-display t)
+(put 'nxml-outline-display-showing-tag 'evaporate t)
+(put 'nxml-outline-display-showing-tag 'keymap nxml-outline-showing-tag-map)
+(put 'nxml-outline-display-showing-tag
+ 'help-echo
+ nxml-outline-showing-tag-help)
+
+(defun nxml-outline-set-overlay (category
+ start
+ end
+ &optional
+ front-advance
+ rear-advance)
+ "Replace any nxml-outline-display overlays between START and END.
+Overlays are removed if they overlay the region between START and END,
+and have a non-nil nxml-outline-display property (typically via their
+category). If CATEGORY is non-nil, they will be replaced with a new overlay
+with that category from START to END. If CATEGORY is nil, no new
+overlay will be created."
+ (when (< start end)
+ (let ((overlays (overlays-in start end))
+ overlay)
+ (while overlays
+ (setq overlay (car overlays))
+ (setq overlays (cdr overlays))
+ (when (overlay-get overlay 'nxml-outline-display)
+ (delete-overlay overlay))))
+ (and category
+ (let ((overlay (make-overlay start
+ end
+ nil
+ front-advance
+ rear-advance)))
+ (overlay-put overlay 'category category)
+ (setq line-move-ignore-invisible t)
+ overlay))))
+
+(defun nxml-end-of-heading ()
+ "Move from the start of the content of the heading to the end.
+Do not move past the end of the line."
+ (let ((pos (condition-case err
+ (and (nxml-scan-element-forward (point) t)
+ xmltok-start)
+ nil)))
+ (end-of-line)
+ (skip-chars-backward " \t")
+ (cond ((not pos)
+ (setq pos (nxml-token-before))
+ (when (eq xmltok-type 'end-tag)
+ (goto-char pos)))
+ ((< pos (point))
+ (goto-char pos)))
+ (skip-chars-backward " \t")
+ (point)))
+
+;;; Navigating section structure
+
+(defun nxml-token-starts-line-p ()
+ (save-excursion
+ (goto-char xmltok-start)
+ (skip-chars-backward " \t")
+ (bolp)))
+
+(defvar nxml-cached-section-tag-regexp nil)
+(defvar nxml-cached-section-element-name-regexp nil)
+
+(defsubst nxml-make-section-tag-regexp ()
+ (if (eq nxml-cached-section-element-name-regexp
+ nxml-section-element-name-regexp)
+ nxml-cached-section-tag-regexp
+ (nxml-make-section-tag-regexp-1)))
+
+(defun nxml-make-section-tag-regexp-1 ()
+ (setq nxml-cached-section-element-name-regexp nil)
+ (setq nxml-cached-section-tag-regexp
+ (concat "</?\\("
+ "\\(" xmltok-ncname-regexp ":\\)?"
+ nxml-section-element-name-regexp
+ "\\)[ \t\r\n>]"))
+ (setq nxml-cached-section-element-name-regexp
+ nxml-section-element-name-regexp)
+ nxml-cached-section-tag-regexp)
+
+(defun nxml-section-tag-forward ()
+ "Move forward past the first tag that is a section start- or end-tag.
+Return xmltok-type for tag.
+If no tag found, return nil and move to the end of the buffer."
+ (let ((case-fold-search nil)
+ (tag-regexp (nxml-make-section-tag-regexp))
+ match-end)
+ (when (< (point) nxml-prolog-end)
+ (goto-char nxml-prolog-end))
+ (while (cond ((not (re-search-forward tag-regexp nil 'move))
+ (setq xmltok-type nil)
+ nil)
+ ((progn
+ (goto-char (match-beginning 0))
+ (setq match-end (match-end 0))
+ (nxml-ensure-scan-up-to-date)
+ (let ((end (nxml-inside-end (point))))
+ (when end
+ (goto-char end)
+ t))))
+ ((progn
+ (xmltok-forward)
+ (and (memq xmltok-type '(start-tag
+ partial-start-tag
+ end-tag
+ partial-end-tag))
+ ;; just in case wildcard matched non-name chars
+ (= xmltok-name-end (1- match-end))))
+ nil)
+ (t))))
+ xmltok-type)
+
+(defun nxml-section-tag-backward ()
+ "Move backward to the end of a tag that is a section start- or end-tag.
+The position of the end of the tag must be <= point
+Point is at the end of the tag. `xmltok-start' is the start."
+ (let ((case-fold-search nil)
+ (start (point))
+ (tag-regexp (nxml-make-section-tag-regexp))
+ match-end)
+ (if (< (point) nxml-prolog-end)
+ (progn
+ (goto-char (point-min))
+ nil)
+ (while (cond ((not (re-search-backward tag-regexp
+ nxml-prolog-end
+ 'move))
+ (setq xmltok-type nil)
+ (goto-char (point-min))
+ nil)
+ ((progn
+ (goto-char (match-beginning 0))
+ (setq match-end (match-end 0))
+ (nxml-ensure-scan-up-to-date)
+ (let ((pos (nxml-inside-start (point))))
+ (when pos
+ (goto-char (1- pos))
+ t))))
+ ((progn
+ (xmltok-forward)
+ (and (<= (point) start)
+ (memq xmltok-type '(start-tag
+ partial-start-tag
+ end-tag
+ partial-end-tag))
+ ;; just in case wildcard matched non-name chars
+ (= xmltok-name-end (1- match-end))))
+ nil)
+ (t (goto-char xmltok-start)
+ t)))
+ xmltok-type)))
+
+(defun nxml-section-start-position ()
+ "Return the position of the start of the section containing point.
+Signal an error on failure."
+ (condition-case err
+ (save-excursion (if (nxml-back-to-section-start)
+ (point)
+ (error "Not in section")))
+ (nxml-outline-error
+ (nxml-report-outline-error "Couldn't determine containing section: %s"
+ err))))
+
+(defun nxml-back-to-section-start (&optional invisible-ok)
+ "Try to move back to the start of the section containing point.
+The start of the section must be <= point.
+Only visible sections are included unless INVISIBLE-OK is non-nil.
+If found, return t. Otherwise move to point-min and return nil.
+If unbalanced section tags are found, signal an `nxml-outline-error'."
+ (when (or (nxml-after-section-start-tag)
+ (nxml-section-tag-backward))
+ (let (open-tags found)
+ (while (let (section-start-pos)
+ (setq section-start-pos xmltok-start)
+ (if (nxml-token-end-tag-p)
+ (setq open-tags (cons (xmltok-end-tag-qname)
+ open-tags))
+ (if (not open-tags)
+ (when (and (nxml-token-starts-line-p)
+ (or invisible-ok
+ (not (get-char-property section-start-pos
+ 'invisible)))
+ (nxml-heading-start-position))
+ (setq found t))
+ (let ((qname (xmltok-start-tag-qname)))
+ (unless (string= (car open-tags) qname)
+ (nxml-outline-error "mismatched end-tag"))
+ (setq open-tags (cdr open-tags)))))
+ (goto-char section-start-pos)
+ (and (not found)
+ (nxml-section-tag-backward))))
+ found)))
+
+(defun nxml-after-section-start-tag ()
+ "If the character after point is in a section start-tag, move after it.
+Return the token type. Otherwise return nil.
+Set up variables like `xmltok-forward'."
+ (let ((pos (nxml-token-after))
+ (case-fold-search nil))
+ (when (and (memq xmltok-type '(start-tag partial-start-tag))
+ (save-excursion
+ (goto-char xmltok-start)
+ (looking-at (nxml-make-section-tag-regexp))))
+ (goto-char pos)
+ xmltok-type)))
+
+(defun nxml-heading-start-position ()
+ "Return the position of the start of the content of a heading element.
+Adjust the position to be after initial leading whitespace.
+Return nil if no heading element is found. Requires point to be
+immediately after the section's start-tag."
+ (let ((depth 0)
+ (heading-regexp (concat "\\`\\("
+ nxml-heading-element-name-regexp
+ "\\)\\'"))
+
+ (section-regexp (concat "\\`\\("
+ nxml-section-element-name-regexp
+ "\\)\\'"))
+ (start (point))
+ found)
+ (save-excursion
+ (while (and (xmltok-forward)
+ (cond ((memq xmltok-type '(end-tag partial-end-tag))
+ (and (not (string-match section-regexp
+ (xmltok-end-tag-local-name)))
+ (> depth 0)
+ (setq depth (1- depth))))
+ ;; XXX Not sure whether this is a good idea
+ ;;((eq xmltok-type 'empty-element)
+ ;; nil)
+ ((not (memq xmltok-type
+ '(start-tag partial-start-tag)))
+ t)
+ ((string-match section-regexp
+ (xmltok-start-tag-local-name))
+ nil)
+ ((string-match heading-regexp
+ (xmltok-start-tag-local-name))
+ (skip-chars-forward " \t\r\n")
+ (setq found (point))
+ nil)
+ (t
+ (setq depth (1+ depth))
+ t))
+ (<= (- (point) start) nxml-heading-scan-distance))))
+ found))
+
+;;; Error handling
+
+(defun nxml-report-outline-error (msg err)
+ (error msg (apply 'format (cdr err))))
+
+(defun nxml-outline-error (&rest args)
+ (signal 'nxml-outline-error args))
+
+(put 'nxml-outline-error
+ 'error-conditions
+ '(error nxml-error nxml-outline-error))
+
+(put 'nxml-outline-error
+ 'error-message
+ "Cannot create outline of buffer that is not well-formed")
+
+;;; Debugging
+
+(defun nxml-debug-overlays ()
+ (interactive)
+ (let ((overlays (nreverse (overlays-in (point-min) (point-max))))
+ overlay)
+ (while overlays
+ (setq overlay (car overlays))
+ (setq overlays (cdr overlays))
+ (when (overlay-get overlay 'nxml-outline-display)
+ (message "overlay %s: %s...%s (%s)"
+ (overlay-get overlay 'category)
+ (overlay-start overlay)
+ (overlay-end overlay)
+ (overlay-get overlay 'display))))))
+
+(provide 'nxml-outln)
+
+;; arch-tag: 1f1b7454-e573-4cd7-a505-d9dc64eef828
+;;; nxml-outln.el ends here