nxml-outln.el 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041
  1. ;;; nxml-outln.el --- outline support for nXML mode
  2. ;; Copyright (C) 2004, 2007-2012 Free Software Foundation, Inc.
  3. ;; Author: James Clark
  4. ;; Keywords: XML
  5. ;; This file is part of GNU Emacs.
  6. ;; GNU Emacs is free software: you can redistribute it and/or modify
  7. ;; it under the terms of the GNU General Public License as published by
  8. ;; the Free Software Foundation, either version 3 of the License, or
  9. ;; (at your option) any later version.
  10. ;; GNU Emacs is distributed in the hope that it will be useful,
  11. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ;; GNU General Public License for more details.
  14. ;; You should have received a copy of the GNU General Public License
  15. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  16. ;;; Commentary:
  17. ;; A section can be in one of three states
  18. ;; 1. display normally; this displays each child section
  19. ;; according to its state; anything not part of child sections is also
  20. ;; displayed normally
  21. ;; 2. display just the title specially; child sections are not displayed
  22. ;; regardless of their state; anything not part of child sections is
  23. ;; not displayed
  24. ;; 3. display the title specially and display child sections
  25. ;; according to their state; anything not part of the child section is
  26. ;; not displayed
  27. ;; The state of a section is determined by the value of the
  28. ;; nxml-outline-state text property of the < character that starts
  29. ;; the section.
  30. ;; For state 1 the value is nil or absent.
  31. ;; For state 2 it is the symbol hide-children.
  32. ;; For state 3 it is t.
  33. ;; The special display is achieved by using overlays. The overlays
  34. ;; are computed from the nxml-outline-state property by
  35. ;; `nxml-refresh-outline'. There overlays all have a category property
  36. ;; with an nxml-outline-display property with value t.
  37. ;;
  38. ;; For a section to be recognized as such, the following conditions must
  39. ;; be satisfied:
  40. ;; - its start-tag must occur at the start of a line (possibly indented)
  41. ;; - its local name must match `nxml-section-element-name-regexp'
  42. ;; - it must have a heading element; a heading element is an
  43. ;; element whose name matches `nxml-heading-element-name-regexp',
  44. ;; and that occurs as, or as a descendant of, the first child element
  45. ;; of the section
  46. ;;
  47. ;; XXX What happens if an nxml-outline-state property is attached to a
  48. ;; character that doesn't start a section element?
  49. ;;
  50. ;; An outlined section (an section with a non-nil nxml-outline-state
  51. ;; property) can be displayed in either single-line or multi-line
  52. ;; form. Single-line form is used when the outline state is hide-children
  53. ;; or there are no child sections; multi-line form is used otherwise.
  54. ;; There are two flavors of single-line form: with children and without.
  55. ;; The with-children flavor is used when there are child sections.
  56. ;; Single line with children looks like
  57. ;; <+section>A section title...</>
  58. ;; Single line without children looks like
  59. ;; <-section>A section title...</>
  60. ;; Multi line looks likes
  61. ;; <-section>A section title...
  62. ;; [child sections displayed here]
  63. ;; </-section>
  64. ;; The indent of an outlined section is computed relative to the
  65. ;; outermost containing outlined element. The indent of the
  66. ;; outermost containing element comes from the non-outlined
  67. ;; indent of the section start-tag.
  68. ;;; Code:
  69. (require 'xmltok)
  70. (require 'nxml-util)
  71. (require 'nxml-rap)
  72. (defcustom nxml-section-element-name-regexp
  73. "article\\|\\(sub\\)*section\\|chapter\\|div\\|appendix\\|part\\|preface\\|reference\\|simplesect\\|bibliography\\|bibliodiv\\|glossary\\|glossdiv"
  74. "Regular expression matching the name of elements used as sections.
  75. An XML element is treated as a section if:
  76. - its local name (that is, the name without the prefix) matches
  77. this regexp;
  78. - either its first child element or a descendant of that first child
  79. element has a local name matching the variable
  80. `nxml-heading-element-name-regexp'; and
  81. - its start-tag occurs at the beginning of a line (possibly indented)."
  82. :group 'nxml
  83. :type 'regexp)
  84. (defcustom nxml-heading-element-name-regexp "title\\|head"
  85. "Regular expression matching the name of elements used as headings.
  86. An XML element is only recognized as a heading if it occurs as or
  87. within the first child of an element that is recognized as a section.
  88. See the variable `nxml-section-element-name-regexp' for more details."
  89. :group 'nxml
  90. :type 'regexp)
  91. (defcustom nxml-outline-child-indent 2
  92. "Indentation in an outline for child element relative to parent element."
  93. :group 'nxml
  94. :type 'integer)
  95. (defface nxml-heading
  96. '((t (:weight bold)))
  97. "Face used for the contents of abbreviated heading elements."
  98. :group 'nxml-faces)
  99. (defface nxml-outline-indicator
  100. '((t (:inherit default)))
  101. "Face used for `+' or `-' before element names in outlines."
  102. :group 'nxml-faces)
  103. (defface nxml-outline-active-indicator
  104. '((t (:box t :inherit nxml-outline-indicator)))
  105. "Face used for clickable `+' or `-' before element names in outlines."
  106. :group 'nxml-faces)
  107. (defface nxml-outline-ellipsis
  108. '((t (:bold t :inherit default)))
  109. "Face used for `...' in outlines."
  110. :group 'nxml-faces)
  111. (defvar nxml-heading-scan-distance 1000
  112. "Maximum distance from section to scan for heading.")
  113. (defvar nxml-outline-prefix-map
  114. (let ((map (make-sparse-keymap)))
  115. (define-key map "\C-a" 'nxml-show-all)
  116. (define-key map "\C-t" 'nxml-hide-all-text-content)
  117. (define-key map "\C-r" 'nxml-refresh-outline)
  118. (define-key map "\C-c" 'nxml-hide-direct-text-content)
  119. (define-key map "\C-e" 'nxml-show-direct-text-content)
  120. (define-key map "\C-d" 'nxml-hide-subheadings)
  121. (define-key map "\C-s" 'nxml-show)
  122. (define-key map "\C-k" 'nxml-show-subheadings)
  123. (define-key map "\C-l" 'nxml-hide-text-content)
  124. (define-key map "\C-i" 'nxml-show-direct-subheadings)
  125. (define-key map "\C-o" 'nxml-hide-other)
  126. map))
  127. ;;; Commands for changing visibility
  128. (defun nxml-show-all ()
  129. "Show all elements in the buffer normally."
  130. (interactive)
  131. (nxml-with-unmodifying-text-property-changes
  132. (remove-text-properties (point-min)
  133. (point-max)
  134. '(nxml-outline-state nil)))
  135. (nxml-outline-set-overlay nil (point-min) (point-max)))
  136. (defun nxml-hide-all-text-content ()
  137. "Hide all text content in the buffer.
  138. Anything that is in a section but is not a heading will be hidden.
  139. The visibility of headings at any level will not be changed. See the
  140. variable `nxml-section-element-name-regexp' for more details on how to
  141. customize which elements are recognized as sections and headings."
  142. (interactive)
  143. (nxml-transform-buffer-outline '((nil . t))))
  144. (defun nxml-show-direct-text-content ()
  145. "Show the text content that is directly part of the section containing point.
  146. Each subsection will be shown according to its individual state, which
  147. will not be changed. The section containing point is the innermost
  148. section that contains the character following point. See the variable
  149. `nxml-section-element-name-regexp' for more details on how to
  150. customize which elements are recognized as sections and headings."
  151. (interactive)
  152. (nxml-outline-pre-adjust-point)
  153. (nxml-set-outline-state (nxml-section-start-position) nil)
  154. (nxml-refresh-outline)
  155. (nxml-outline-adjust-point))
  156. (defun nxml-show-direct-subheadings ()
  157. "Show the immediate subheadings of the section containing point.
  158. The section containing point is the innermost section that contains
  159. the character following point. See the variable
  160. `nxml-section-element-name-regexp' for more details on how to
  161. customize which elements are recognized as sections and headings."
  162. (interactive)
  163. (let ((pos (nxml-section-start-position)))
  164. (when (eq (nxml-get-outline-state pos) 'hide-children)
  165. (nxml-set-outline-state pos t)))
  166. (nxml-refresh-outline)
  167. (nxml-outline-adjust-point))
  168. (defun nxml-hide-direct-text-content ()
  169. "Hide the text content that is directly part of the section containing point.
  170. The heading of the section will remain visible. The state of
  171. subsections will not be changed. The section containing point is the
  172. innermost section that contains the character following point. See the
  173. variable `nxml-section-element-name-regexp' for more details on how to
  174. customize which elements are recognized as sections and headings."
  175. (interactive)
  176. (let ((pos (nxml-section-start-position)))
  177. (when (null (nxml-get-outline-state pos))
  178. (nxml-set-outline-state pos t)))
  179. (nxml-refresh-outline)
  180. (nxml-outline-adjust-point))
  181. (defun nxml-hide-subheadings ()
  182. "Hide the subheadings that are part of the section containing point.
  183. The text content will also be hidden, leaving only the heading of the
  184. section itself visible. The state of the subsections will also be
  185. changed to hide their headings, so that \\[nxml-show-direct-text-content]
  186. would show only the heading of the subsections. The section containing
  187. point is the innermost section that contains the character following
  188. point. See the variable `nxml-section-element-name-regexp' for more
  189. details on how to customize which elements are recognized as sections
  190. and headings."
  191. (interactive)
  192. (nxml-transform-subtree-outline '((nil . hide-children)
  193. (t . hide-children))))
  194. (defun nxml-show ()
  195. "Show the section containing point normally, without hiding anything.
  196. This includes everything in the section at any level. The section
  197. containing point is the innermost section that contains the character
  198. following point. See the variable `nxml-section-element-name-regexp'
  199. for more details on how to customize which elements are recognized as
  200. sections and headings."
  201. (interactive)
  202. (nxml-transform-subtree-outline '((hide-children . nil)
  203. (t . nil))))
  204. (defun nxml-hide-text-content ()
  205. "Hide text content at all levels in the section containing point.
  206. The section containing point is the innermost section that contains
  207. the character following point. See the variable
  208. `nxml-section-element-name-regexp' for more details on how to
  209. customize which elements are recognized as sections and headings."
  210. (interactive)
  211. (nxml-transform-subtree-outline '((nil . t))))
  212. (defun nxml-show-subheadings ()
  213. "Show the subheadings at all levels of the section containing point.
  214. The visibility of the text content at all levels in the section is not
  215. changed. The section containing point is the innermost section that
  216. contains the character following point. See the variable
  217. `nxml-section-element-name-regexp' for more details on how to
  218. customize which elements are recognized as sections and headings."
  219. (interactive)
  220. (nxml-transform-subtree-outline '((hide-children . t))))
  221. (defun nxml-hide-other ()
  222. "Hide text content other than that directly in the section containing point.
  223. Hide headings other than those of ancestors of that section and their
  224. immediate subheadings. The section containing point is the innermost
  225. section that contains the character following point. See the variable
  226. `nxml-section-element-name-regexp' for more details on how to
  227. customize which elements are recognized as sections and headings."
  228. (interactive)
  229. (let ((nxml-outline-state-transform-exceptions nil))
  230. (save-excursion
  231. (while (and (condition-case err
  232. (nxml-back-to-section-start)
  233. (nxml-outline-error (nxml-report-outline-error
  234. "Couldn't find containing section: %s"
  235. err)))
  236. (progn
  237. (when (and nxml-outline-state-transform-exceptions
  238. (null (nxml-get-outline-state (point))))
  239. (nxml-set-outline-state (point) t))
  240. (setq nxml-outline-state-transform-exceptions
  241. (cons (point)
  242. nxml-outline-state-transform-exceptions))
  243. (< nxml-prolog-end (point))))
  244. (goto-char (1- (point)))))
  245. (nxml-transform-buffer-outline '((nil . hide-children)
  246. (t . hide-children)))))
  247. ;; These variables are dynamically bound. They are use to pass information to
  248. ;; nxml-section-tag-transform-outline-state.
  249. (defvar nxml-outline-state-transform-exceptions nil)
  250. (defvar nxml-target-section-pos nil)
  251. (defvar nxml-depth-in-target-section nil)
  252. (defvar nxml-outline-state-transform-alist nil)
  253. (defun nxml-transform-buffer-outline (alist)
  254. (let ((nxml-target-section-pos nil)
  255. (nxml-depth-in-target-section 0)
  256. (nxml-outline-state-transform-alist alist)
  257. (nxml-outline-display-section-tag-function
  258. 'nxml-section-tag-transform-outline-state))
  259. (nxml-refresh-outline))
  260. (nxml-outline-adjust-point))
  261. (defun nxml-transform-subtree-outline (alist)
  262. (let ((nxml-target-section-pos (nxml-section-start-position))
  263. (nxml-depth-in-target-section nil)
  264. (nxml-outline-state-transform-alist alist)
  265. (nxml-outline-display-section-tag-function
  266. 'nxml-section-tag-transform-outline-state))
  267. (nxml-refresh-outline))
  268. (nxml-outline-adjust-point))
  269. (defun nxml-outline-pre-adjust-point ()
  270. (cond ((and (< (point-min) (point))
  271. (get-char-property (1- (point)) 'invisible)
  272. (not (get-char-property (point) 'invisible))
  273. (let ((str (or (get-char-property (point) 'before-string)
  274. (get-char-property (point) 'display))))
  275. (and (stringp str)
  276. (>= (length str) 3)
  277. (string= (substring str 0 3) "..."))))
  278. ;; The ellipsis is a display property on a visible character
  279. ;; following an invisible region. The position of the event
  280. ;; will be the position before that character. We want to
  281. ;; move point to the other side of the invisible region, i.e.
  282. ;; following the last visible character before that invisible
  283. ;; region.
  284. (goto-char (previous-single-char-property-change (1- (point))
  285. 'invisible)))
  286. ((and (< (point) (point-max))
  287. (get-char-property (point) 'display)
  288. (get-char-property (1+ (point)) 'invisible))
  289. (goto-char (next-single-char-property-change (1+ (point))
  290. 'invisible)))
  291. ((and (< (point) (point-max))
  292. (get-char-property (point) 'invisible))
  293. (goto-char (next-single-char-property-change (point)
  294. 'invisible)))))
  295. (defun nxml-outline-adjust-point ()
  296. "Adjust point after showing or hiding elements."
  297. (when (and (get-char-property (point) 'invisible)
  298. (< (point-min) (point))
  299. (get-char-property (1- (point)) 'invisible))
  300. (goto-char (previous-single-char-property-change (point)
  301. 'invisible
  302. nil
  303. nxml-prolog-end))))
  304. (defun nxml-transform-outline-state (section-start-pos)
  305. (let* ((old-state
  306. (nxml-get-outline-state section-start-pos))
  307. (change (assq old-state
  308. nxml-outline-state-transform-alist)))
  309. (when change
  310. (nxml-set-outline-state section-start-pos
  311. (cdr change)))))
  312. (defun nxml-section-tag-transform-outline-state (startp
  313. section-start-pos
  314. &optional
  315. heading-start-pos)
  316. (if (not startp)
  317. (setq nxml-depth-in-target-section
  318. (and nxml-depth-in-target-section
  319. (> nxml-depth-in-target-section 0)
  320. (1- nxml-depth-in-target-section)))
  321. (cond (nxml-depth-in-target-section
  322. (setq nxml-depth-in-target-section
  323. (1+ nxml-depth-in-target-section)))
  324. ((= section-start-pos nxml-target-section-pos)
  325. (setq nxml-depth-in-target-section 0)))
  326. (when (and nxml-depth-in-target-section
  327. (not (member section-start-pos
  328. nxml-outline-state-transform-exceptions)))
  329. (nxml-transform-outline-state section-start-pos))))
  330. (defun nxml-get-outline-state (pos)
  331. (get-text-property pos 'nxml-outline-state))
  332. (defun nxml-set-outline-state (pos state)
  333. (nxml-with-unmodifying-text-property-changes
  334. (if state
  335. (put-text-property pos (1+ pos) 'nxml-outline-state state)
  336. (remove-text-properties pos (1+ pos) '(nxml-outline-state nil)))))
  337. ;;; Mouse interface
  338. (defun nxml-mouse-show-direct-text-content (event)
  339. "Do the same as \\[nxml-show-direct-text-content] from a mouse click."
  340. (interactive "e")
  341. (and (nxml-mouse-set-point event)
  342. (nxml-show-direct-text-content)))
  343. (defun nxml-mouse-hide-direct-text-content (event)
  344. "Do the same as \\[nxml-hide-direct-text-content] from a mouse click."
  345. (interactive "e")
  346. (and (nxml-mouse-set-point event)
  347. (nxml-hide-direct-text-content)))
  348. (defun nxml-mouse-hide-subheadings (event)
  349. "Do the same as \\[nxml-hide-subheadings] from a mouse click."
  350. (interactive "e")
  351. (and (nxml-mouse-set-point event)
  352. (nxml-hide-subheadings)))
  353. (defun nxml-mouse-show-direct-subheadings (event)
  354. "Do the same as \\[nxml-show-direct-subheadings] from a mouse click."
  355. (interactive "e")
  356. (and (nxml-mouse-set-point event)
  357. (nxml-show-direct-subheadings)))
  358. (defun nxml-mouse-set-point (event)
  359. (mouse-set-point event)
  360. (and nxml-prolog-end t))
  361. ;; Display
  362. (defsubst nxml-token-start-tag-p ()
  363. (or (eq xmltok-type 'start-tag)
  364. (eq xmltok-type 'partial-start-tag)))
  365. (defsubst nxml-token-end-tag-p ()
  366. (or (eq xmltok-type 'end-tag)
  367. (eq xmltok-type 'partial-end-tag)))
  368. (defun nxml-refresh-outline ()
  369. "Refresh the outline to correspond to the current XML element structure."
  370. (interactive)
  371. (save-excursion
  372. (goto-char (point-min))
  373. (kill-local-variable 'line-move-ignore-invisible)
  374. (make-local-variable 'line-move-ignore-invisible)
  375. (condition-case err
  376. (nxml-outline-display-rest nil nil nil)
  377. (nxml-outline-error
  378. (nxml-report-outline-error "Cannot display outline: %s" err)))))
  379. (defvar nxml-outline-display-section-tag-function nil)
  380. (defun nxml-outline-display-rest (outline-state start-tag-indent tag-qnames)
  381. "Display up to and including the end of the current element.
  382. OUTLINE-STATE can be nil, t, hide-children. START-TAG-INDENT is the
  383. indent of the start-tag of the current element, or nil if no
  384. containing element has a non-nil OUTLINE-STATE. TAG-QNAMES is a list
  385. of the qnames of the open elements. Point is after the title content.
  386. Leave point after the closing end-tag. Return t if we had a
  387. non-transparent child section."
  388. (let ((last-pos (point))
  389. (transparent-depth 0)
  390. ;; don't want ellipsis before root element
  391. (had-children (not tag-qnames)))
  392. (while
  393. (cond ((not (nxml-section-tag-forward))
  394. (if (null tag-qnames)
  395. nil
  396. (nxml-outline-error "missing end-tag %s"
  397. (car tag-qnames))))
  398. ;; section end-tag
  399. ((nxml-token-end-tag-p)
  400. (when nxml-outline-display-section-tag-function
  401. (funcall nxml-outline-display-section-tag-function
  402. nil
  403. xmltok-start))
  404. (let ((qname (xmltok-end-tag-qname)))
  405. (unless tag-qnames
  406. (nxml-outline-error "extra end-tag %s" qname))
  407. (unless (string= (car tag-qnames) qname)
  408. (nxml-outline-error "mismatched end-tag; expected %s, got %s"
  409. (car tag-qnames)
  410. qname)))
  411. (cond ((> transparent-depth 0)
  412. (setq transparent-depth (1- transparent-depth))
  413. (setq tag-qnames (cdr tag-qnames))
  414. t)
  415. ((not outline-state)
  416. (nxml-outline-set-overlay nil last-pos (point))
  417. nil)
  418. ((or (not had-children)
  419. (eq outline-state 'hide-children))
  420. (nxml-outline-display-single-line-end-tag last-pos)
  421. nil)
  422. (t
  423. (nxml-outline-display-multi-line-end-tag last-pos
  424. start-tag-indent)
  425. nil)))
  426. ;; section start-tag
  427. (t
  428. (let* ((qname (xmltok-start-tag-qname))
  429. (section-start-pos xmltok-start)
  430. (heading-start-pos
  431. (and (or nxml-outline-display-section-tag-function
  432. (not (eq outline-state 'had-children))
  433. (not had-children))
  434. (nxml-token-starts-line-p)
  435. (nxml-heading-start-position))))
  436. (when nxml-outline-display-section-tag-function
  437. (funcall nxml-outline-display-section-tag-function
  438. t
  439. section-start-pos
  440. heading-start-pos))
  441. (setq tag-qnames (cons qname tag-qnames))
  442. (if (or (not heading-start-pos)
  443. (and (eq outline-state 'hide-children)
  444. (setq had-children t)))
  445. (setq transparent-depth (1+ transparent-depth))
  446. (nxml-display-section last-pos
  447. section-start-pos
  448. heading-start-pos
  449. start-tag-indent
  450. outline-state
  451. had-children
  452. tag-qnames)
  453. (setq had-children t)
  454. (setq tag-qnames (cdr tag-qnames))
  455. (setq last-pos (point))))
  456. t)))
  457. had-children))
  458. (defconst nxml-highlighted-less-than
  459. (propertize "<" 'face 'nxml-tag-delimiter))
  460. (defconst nxml-highlighted-greater-than
  461. (propertize ">" 'face 'nxml-tag-delimiter))
  462. (defconst nxml-highlighted-colon
  463. (propertize ":" 'face 'nxml-element-colon))
  464. (defconst nxml-highlighted-slash
  465. (propertize "/" 'face 'nxml-tag-slash))
  466. (defconst nxml-highlighted-ellipsis
  467. (propertize "..." 'face 'nxml-outline-ellipsis))
  468. (defconst nxml-highlighted-empty-end-tag
  469. (concat nxml-highlighted-ellipsis
  470. nxml-highlighted-less-than
  471. nxml-highlighted-slash
  472. nxml-highlighted-greater-than))
  473. (defconst nxml-highlighted-inactive-minus
  474. (propertize "-" 'face 'nxml-outline-indicator))
  475. (defconst nxml-highlighted-active-minus
  476. (propertize "-" 'face 'nxml-outline-active-indicator))
  477. (defconst nxml-highlighted-active-plus
  478. (propertize "+" 'face 'nxml-outline-active-indicator))
  479. (defun nxml-display-section (last-pos
  480. section-start-pos
  481. heading-start-pos
  482. parent-indent
  483. parent-outline-state
  484. had-children
  485. tag-qnames)
  486. (let* ((section-start-pos-bol
  487. (save-excursion
  488. (goto-char section-start-pos)
  489. (skip-chars-backward " \t")
  490. (point)))
  491. (outline-state (nxml-get-outline-state section-start-pos))
  492. (newline-before-section-start-category
  493. (cond ((and (not had-children) parent-outline-state)
  494. 'nxml-outline-display-ellipsis)
  495. (outline-state 'nxml-outline-display-show)
  496. (t nil))))
  497. (nxml-outline-set-overlay (and parent-outline-state
  498. 'nxml-outline-display-hide)
  499. last-pos
  500. (1- section-start-pos-bol)
  501. nil
  502. t)
  503. (if outline-state
  504. (let* ((indent (if parent-indent
  505. (+ parent-indent nxml-outline-child-indent)
  506. (save-excursion
  507. (goto-char section-start-pos)
  508. (current-column))))
  509. start-tag-overlay)
  510. (nxml-outline-set-overlay newline-before-section-start-category
  511. (1- section-start-pos-bol)
  512. section-start-pos-bol
  513. t)
  514. (nxml-outline-set-overlay 'nxml-outline-display-hide
  515. section-start-pos-bol
  516. section-start-pos)
  517. (setq start-tag-overlay
  518. (nxml-outline-set-overlay 'nxml-outline-display-show
  519. section-start-pos
  520. (1+ section-start-pos)
  521. t))
  522. ;; line motion commands don't work right if start-tag-overlay
  523. ;; covers multiple lines
  524. (nxml-outline-set-overlay 'nxml-outline-display-hide
  525. (1+ section-start-pos)
  526. heading-start-pos)
  527. (goto-char heading-start-pos)
  528. (nxml-end-of-heading)
  529. (nxml-outline-set-overlay 'nxml-outline-display-heading
  530. heading-start-pos
  531. (point))
  532. (let* ((had-children
  533. (nxml-outline-display-rest outline-state
  534. indent
  535. tag-qnames)))
  536. (overlay-put start-tag-overlay
  537. 'display
  538. (concat
  539. ;; indent
  540. (make-string indent ?\ )
  541. ;; <
  542. nxml-highlighted-less-than
  543. ;; + or - indicator
  544. (cond ((not had-children)
  545. nxml-highlighted-inactive-minus)
  546. ((eq outline-state 'hide-children)
  547. (overlay-put start-tag-overlay
  548. 'category
  549. 'nxml-outline-display-hiding-tag)
  550. nxml-highlighted-active-plus)
  551. (t
  552. (overlay-put start-tag-overlay
  553. 'category
  554. 'nxml-outline-display-showing-tag)
  555. nxml-highlighted-active-minus))
  556. ;; qname
  557. (nxml-highlighted-qname (car tag-qnames))
  558. ;; >
  559. nxml-highlighted-greater-than))))
  560. ;; outline-state nil
  561. (goto-char heading-start-pos)
  562. (nxml-end-of-heading)
  563. (nxml-outline-set-overlay newline-before-section-start-category
  564. (1- section-start-pos-bol)
  565. (point)
  566. t)
  567. (nxml-outline-display-rest outline-state
  568. (and parent-indent
  569. (+ parent-indent
  570. nxml-outline-child-indent))
  571. tag-qnames))))
  572. (defun nxml-highlighted-qname (qname)
  573. (let ((colon (string-match ":" qname)))
  574. (if colon
  575. (concat (propertize (substring qname 0 colon)
  576. 'face
  577. 'nxml-element-prefix)
  578. nxml-highlighted-colon
  579. (propertize (substring qname (1+ colon))
  580. 'face
  581. 'nxml-element-local-name))
  582. (propertize qname
  583. 'face
  584. 'nxml-element-local-name))))
  585. (defun nxml-outline-display-single-line-end-tag (last-pos)
  586. (nxml-outline-set-overlay 'nxml-outline-display-hide
  587. last-pos
  588. xmltok-start
  589. nil
  590. t)
  591. (overlay-put (nxml-outline-set-overlay 'nxml-outline-display-show
  592. xmltok-start
  593. (point)
  594. t)
  595. 'display
  596. nxml-highlighted-empty-end-tag))
  597. (defun nxml-outline-display-multi-line-end-tag (last-pos start-tag-indent)
  598. (let ((indentp (save-excursion
  599. (goto-char last-pos)
  600. (skip-chars-forward " \t")
  601. (and (eq (char-after) ?\n)
  602. (progn
  603. (goto-char (1+ (point)))
  604. (nxml-outline-set-overlay nil last-pos (point))
  605. (setq last-pos (point))
  606. (goto-char xmltok-start)
  607. (beginning-of-line)
  608. t))))
  609. end-tag-overlay)
  610. (nxml-outline-set-overlay 'nxml-outline-display-hide
  611. last-pos
  612. xmltok-start
  613. nil
  614. t)
  615. (setq end-tag-overlay
  616. (nxml-outline-set-overlay 'nxml-outline-display-showing-tag
  617. xmltok-start
  618. (point)
  619. t))
  620. (overlay-put end-tag-overlay
  621. 'display
  622. (concat (if indentp
  623. (make-string start-tag-indent ?\ )
  624. "")
  625. nxml-highlighted-less-than
  626. nxml-highlighted-slash
  627. nxml-highlighted-active-minus
  628. (nxml-highlighted-qname (xmltok-end-tag-qname))
  629. nxml-highlighted-greater-than))))
  630. (defvar nxml-outline-show-map
  631. (let ((map (make-sparse-keymap)))
  632. (define-key map "\C-m" 'nxml-show-direct-text-content)
  633. (define-key map [mouse-2] 'nxml-mouse-show-direct-text-content)
  634. map))
  635. (defvar nxml-outline-show-help "mouse-2: show")
  636. (put 'nxml-outline-display-show 'nxml-outline-display t)
  637. (put 'nxml-outline-display-show 'evaporate t)
  638. (put 'nxml-outline-display-show 'keymap nxml-outline-show-map)
  639. (put 'nxml-outline-display-show 'help-echo nxml-outline-show-help)
  640. (put 'nxml-outline-display-hide 'nxml-outline-display t)
  641. (put 'nxml-outline-display-hide 'evaporate t)
  642. (put 'nxml-outline-display-hide 'invisible t)
  643. (put 'nxml-outline-display-hide 'keymap nxml-outline-show-map)
  644. (put 'nxml-outline-display-hide 'help-echo nxml-outline-show-help)
  645. (put 'nxml-outline-display-ellipsis 'nxml-outline-display t)
  646. (put 'nxml-outline-display-ellipsis 'evaporate t)
  647. (put 'nxml-outline-display-ellipsis 'keymap nxml-outline-show-map)
  648. (put 'nxml-outline-display-ellipsis 'help-echo nxml-outline-show-help)
  649. (put 'nxml-outline-display-ellipsis 'before-string nxml-highlighted-ellipsis)
  650. (put 'nxml-outline-display-heading 'keymap nxml-outline-show-map)
  651. (put 'nxml-outline-display-heading 'help-echo nxml-outline-show-help)
  652. (put 'nxml-outline-display-heading 'nxml-outline-display t)
  653. (put 'nxml-outline-display-heading 'evaporate t)
  654. (put 'nxml-outline-display-heading 'face 'nxml-heading)
  655. (defvar nxml-outline-hiding-tag-map
  656. (let ((map (make-sparse-keymap)))
  657. (define-key map [mouse-1] 'nxml-mouse-show-direct-subheadings)
  658. (define-key map [mouse-2] 'nxml-mouse-show-direct-text-content)
  659. (define-key map "\C-m" 'nxml-show-direct-text-content)
  660. map))
  661. (defvar nxml-outline-hiding-tag-help
  662. "mouse-1: show subheadings, mouse-2: show text content")
  663. (put 'nxml-outline-display-hiding-tag 'nxml-outline-display t)
  664. (put 'nxml-outline-display-hiding-tag 'evaporate t)
  665. (put 'nxml-outline-display-hiding-tag 'keymap nxml-outline-hiding-tag-map)
  666. (put 'nxml-outline-display-hiding-tag 'help-echo nxml-outline-hiding-tag-help)
  667. (defvar nxml-outline-showing-tag-map
  668. (let ((map (make-sparse-keymap)))
  669. (define-key map [mouse-1] 'nxml-mouse-hide-subheadings)
  670. (define-key map [mouse-2] 'nxml-mouse-show-direct-text-content)
  671. (define-key map "\C-m" 'nxml-show-direct-text-content)
  672. map))
  673. (defvar nxml-outline-showing-tag-help
  674. "mouse-1: hide subheadings, mouse-2: show text content")
  675. (put 'nxml-outline-display-showing-tag 'nxml-outline-display t)
  676. (put 'nxml-outline-display-showing-tag 'evaporate t)
  677. (put 'nxml-outline-display-showing-tag 'keymap nxml-outline-showing-tag-map)
  678. (put 'nxml-outline-display-showing-tag
  679. 'help-echo
  680. nxml-outline-showing-tag-help)
  681. (defun nxml-outline-set-overlay (category
  682. start
  683. end
  684. &optional
  685. front-advance
  686. rear-advance)
  687. "Replace any `nxml-outline-display' overlays between START and END.
  688. Overlays are removed if they overlay the region between START and END,
  689. and have a non-nil `nxml-outline-display' property (typically via their
  690. category). If CATEGORY is non-nil, they will be replaced with a new
  691. overlay with that category from START to END. If CATEGORY is nil,
  692. no new overlay will be created."
  693. (when (< start end)
  694. (let ((overlays (overlays-in start end))
  695. overlay)
  696. (while overlays
  697. (setq overlay (car overlays))
  698. (setq overlays (cdr overlays))
  699. (when (overlay-get overlay 'nxml-outline-display)
  700. (delete-overlay overlay))))
  701. (and category
  702. (let ((overlay (make-overlay start
  703. end
  704. nil
  705. front-advance
  706. rear-advance)))
  707. (overlay-put overlay 'category category)
  708. (setq line-move-ignore-invisible t)
  709. overlay))))
  710. (defun nxml-end-of-heading ()
  711. "Move from the start of the content of the heading to the end.
  712. Do not move past the end of the line."
  713. (let ((pos (condition-case err
  714. (and (nxml-scan-element-forward (point) t)
  715. xmltok-start)
  716. (nxml-scan-error nil))))
  717. (end-of-line)
  718. (skip-chars-backward " \t")
  719. (cond ((not pos)
  720. (setq pos (nxml-token-before))
  721. (when (eq xmltok-type 'end-tag)
  722. (goto-char pos)))
  723. ((< pos (point))
  724. (goto-char pos)))
  725. (skip-chars-backward " \t")
  726. (point)))
  727. ;;; Navigating section structure
  728. (defun nxml-token-starts-line-p ()
  729. (save-excursion
  730. (goto-char xmltok-start)
  731. (skip-chars-backward " \t")
  732. (bolp)))
  733. (defvar nxml-cached-section-tag-regexp nil)
  734. (defvar nxml-cached-section-element-name-regexp nil)
  735. (defsubst nxml-make-section-tag-regexp ()
  736. (if (eq nxml-cached-section-element-name-regexp
  737. nxml-section-element-name-regexp)
  738. nxml-cached-section-tag-regexp
  739. (nxml-make-section-tag-regexp-1)))
  740. (defun nxml-make-section-tag-regexp-1 ()
  741. (setq nxml-cached-section-element-name-regexp nil)
  742. (setq nxml-cached-section-tag-regexp
  743. (concat "</?\\("
  744. "\\(" xmltok-ncname-regexp ":\\)?"
  745. nxml-section-element-name-regexp
  746. "\\)[ \t\r\n>]"))
  747. (setq nxml-cached-section-element-name-regexp
  748. nxml-section-element-name-regexp)
  749. nxml-cached-section-tag-regexp)
  750. (defun nxml-section-tag-forward ()
  751. "Move forward past the first tag that is a section start- or end-tag.
  752. Return `xmltok-type' for tag.
  753. If no tag found, return nil and move to the end of the buffer."
  754. (let ((case-fold-search nil)
  755. (tag-regexp (nxml-make-section-tag-regexp))
  756. match-end)
  757. (when (< (point) nxml-prolog-end)
  758. (goto-char nxml-prolog-end))
  759. (while (cond ((not (re-search-forward tag-regexp nil 'move))
  760. (setq xmltok-type nil)
  761. nil)
  762. ((progn
  763. (goto-char (match-beginning 0))
  764. (setq match-end (match-end 0))
  765. (nxml-ensure-scan-up-to-date)
  766. (let ((end (nxml-inside-end (point))))
  767. (when end
  768. (goto-char end)
  769. t))))
  770. ((progn
  771. (xmltok-forward)
  772. (and (memq xmltok-type '(start-tag
  773. partial-start-tag
  774. end-tag
  775. partial-end-tag))
  776. ;; just in case wildcard matched non-name chars
  777. (= xmltok-name-end (1- match-end))))
  778. nil)
  779. (t))))
  780. xmltok-type)
  781. (defun nxml-section-tag-backward ()
  782. "Move backward to the end of a tag that is a section start- or end-tag.
  783. The position of the end of the tag must be <= point.
  784. Point is at the end of the tag. `xmltok-start' is the start."
  785. (let ((case-fold-search nil)
  786. (start (point))
  787. (tag-regexp (nxml-make-section-tag-regexp))
  788. match-end)
  789. (if (< (point) nxml-prolog-end)
  790. (progn
  791. (goto-char (point-min))
  792. nil)
  793. (while (cond ((not (re-search-backward tag-regexp
  794. nxml-prolog-end
  795. 'move))
  796. (setq xmltok-type nil)
  797. (goto-char (point-min))
  798. nil)
  799. ((progn
  800. (goto-char (match-beginning 0))
  801. (setq match-end (match-end 0))
  802. (nxml-ensure-scan-up-to-date)
  803. (let ((pos (nxml-inside-start (point))))
  804. (when pos
  805. (goto-char (1- pos))
  806. t))))
  807. ((progn
  808. (xmltok-forward)
  809. (and (<= (point) start)
  810. (memq xmltok-type '(start-tag
  811. partial-start-tag
  812. end-tag
  813. partial-end-tag))
  814. ;; just in case wildcard matched non-name chars
  815. (= xmltok-name-end (1- match-end))))
  816. nil)
  817. (t (goto-char xmltok-start)
  818. t)))
  819. xmltok-type)))
  820. (defun nxml-section-start-position ()
  821. "Return the position of the start of the section containing point.
  822. Signal an error on failure."
  823. (condition-case err
  824. (save-excursion (if (nxml-back-to-section-start)
  825. (point)
  826. (error "Not in section")))
  827. (nxml-outline-error
  828. (nxml-report-outline-error "Couldn't determine containing section: %s"
  829. err))))
  830. (defun nxml-back-to-section-start (&optional invisible-ok)
  831. "Try to move back to the start of the section containing point.
  832. The start of the section must be <= point.
  833. Only visible sections are included unless INVISIBLE-OK is non-nil.
  834. If found, return t. Otherwise move to `point-min' and return nil.
  835. If unbalanced section tags are found, signal an `nxml-outline-error'."
  836. (when (or (nxml-after-section-start-tag)
  837. (nxml-section-tag-backward))
  838. (let (open-tags found)
  839. (while (let (section-start-pos)
  840. (setq section-start-pos xmltok-start)
  841. (if (nxml-token-end-tag-p)
  842. (setq open-tags (cons (xmltok-end-tag-qname)
  843. open-tags))
  844. (if (not open-tags)
  845. (when (and (nxml-token-starts-line-p)
  846. (or invisible-ok
  847. (not (get-char-property section-start-pos
  848. 'invisible)))
  849. (nxml-heading-start-position))
  850. (setq found t))
  851. (let ((qname (xmltok-start-tag-qname)))
  852. (unless (string= (car open-tags) qname)
  853. (nxml-outline-error "mismatched end-tag"))
  854. (setq open-tags (cdr open-tags)))))
  855. (goto-char section-start-pos)
  856. (and (not found)
  857. (nxml-section-tag-backward))))
  858. found)))
  859. (defun nxml-after-section-start-tag ()
  860. "If the character after point is in a section start-tag, move after it.
  861. Return the token type. Otherwise return nil.
  862. Set up variables like `xmltok-forward'."
  863. (let ((pos (nxml-token-after))
  864. (case-fold-search nil))
  865. (when (and (memq xmltok-type '(start-tag partial-start-tag))
  866. (save-excursion
  867. (goto-char xmltok-start)
  868. (looking-at (nxml-make-section-tag-regexp))))
  869. (goto-char pos)
  870. xmltok-type)))
  871. (defun nxml-heading-start-position ()
  872. "Return the position of the start of the content of a heading element.
  873. Adjust the position to be after initial leading whitespace.
  874. Return nil if no heading element is found. Requires point to be
  875. immediately after the section's start-tag."
  876. (let ((depth 0)
  877. (heading-regexp (concat "\\`\\("
  878. nxml-heading-element-name-regexp
  879. "\\)\\'"))
  880. (section-regexp (concat "\\`\\("
  881. nxml-section-element-name-regexp
  882. "\\)\\'"))
  883. (start (point))
  884. found)
  885. (save-excursion
  886. (while (and (xmltok-forward)
  887. (cond ((memq xmltok-type '(end-tag partial-end-tag))
  888. (and (not (string-match section-regexp
  889. (xmltok-end-tag-local-name)))
  890. (> depth 0)
  891. (setq depth (1- depth))))
  892. ;; XXX Not sure whether this is a good idea
  893. ;;((eq xmltok-type 'empty-element)
  894. ;; nil)
  895. ((not (memq xmltok-type
  896. '(start-tag partial-start-tag)))
  897. t)
  898. ((string-match section-regexp
  899. (xmltok-start-tag-local-name))
  900. nil)
  901. ((string-match heading-regexp
  902. (xmltok-start-tag-local-name))
  903. (skip-chars-forward " \t\r\n")
  904. (setq found (point))
  905. nil)
  906. (t
  907. (setq depth (1+ depth))
  908. t))
  909. (<= (- (point) start) nxml-heading-scan-distance))))
  910. found))
  911. ;;; Error handling
  912. (defun nxml-report-outline-error (msg err)
  913. (error msg (apply 'format (cdr err))))
  914. (defun nxml-outline-error (&rest args)
  915. (signal 'nxml-outline-error args))
  916. (put 'nxml-outline-error
  917. 'error-conditions
  918. '(error nxml-error nxml-outline-error))
  919. (put 'nxml-outline-error
  920. 'error-message
  921. "Cannot create outline of buffer that is not well-formed")
  922. ;;; Debugging
  923. (defun nxml-debug-overlays ()
  924. (interactive)
  925. (let ((overlays (nreverse (overlays-in (point-min) (point-max))))
  926. overlay)
  927. (while overlays
  928. (setq overlay (car overlays))
  929. (setq overlays (cdr overlays))
  930. (when (overlay-get overlay 'nxml-outline-display)
  931. (message "overlay %s: %s...%s (%s)"
  932. (overlay-get overlay 'category)
  933. (overlay-start overlay)
  934. (overlay-end overlay)
  935. (overlay-get overlay 'display))))))
  936. (provide 'nxml-outln)
  937. ;;; nxml-outln.el ends here