X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/5f5c9e79243614837b422a197ad360ef1110b969..d3857d85ab7e18b5dafdb6395e811bb566f8184b:/lisp/textmodes/sgml-mode.el
diff --git a/lisp/textmodes/sgml-mode.el b/lisp/textmodes/sgml-mode.el
index c1d7d33090..584056bf30 100644
--- a/lisp/textmodes/sgml-mode.el
+++ b/lisp/textmodes/sgml-mode.el
@@ -1,11 +1,11 @@
;;; sgml-mode.el --- SGML- and HTML-editing modes
-;; Copyright (C) 1992, 1995, 1996, 1998 Free Software Foundation, Inc.
+;; Copyright (C) 1992,95,96,98,2001,2002, 2003 Free Software Foundation, Inc.
;; Author: James Clark
+;; Maintainer: FSF
;; Adapted-By: ESR, Daniel Pfeiffer ,
;; F.Potorti@cnuce.cnr.it
-;; Maintainer: ???
;; Keywords: wp, hypermedia, comm, languages
;; This file is part of GNU Emacs.
@@ -35,12 +35,18 @@
(eval-when-compile
(require 'skeleton)
- (require 'outline))
+ (require 'outline)
+ (require 'cl))
(defgroup sgml nil
"SGML editing mode"
:group 'languages)
+(defcustom sgml-basic-offset 2
+ "*Specifies the basic indentation level for `sgml-indent-line'."
+ :type 'integer
+ :group 'sgml)
+
(defcustom sgml-transformation 'identity
"*Default value for `skeleton-transformation' (which see) in SGML mode."
:type 'function
@@ -60,7 +66,7 @@
;; kludgy kind of tradeoff.
(defvar sgml-specials '(?\")
"List of characters that have a special meaning for SGML mode.
-This list is used when first loading the sgml-mode library.
+This list is used when first loading the `sgml-mode' library.
The supported characters and potential disadvantages are:
?\\\" Makes \" in text start a string.
@@ -75,12 +81,11 @@ Including ?- has the problem of affecting dashes that have nothing to do
with comments, so we normally turn it off.")
(defvar sgml-quick-keys nil
- "Use <, >, &, SPC and `sgml-specials' keys \"electrically\" when non-nil.
-This takes effect when first loading the sgml-mode library.")
-
+ "Use <, >, &, /, SPC and `sgml-specials' keys \"electrically\" when non-nil.
+This takes effect when first loading the `sgml-mode' library.")
(defvar sgml-mode-map
- (let ((map (list 'keymap (make-vector 256 nil)))
+ (let ((map (make-keymap)) ;`sparse' doesn't allow binding to charsets.
(menu-map (make-sparse-keymap "SGML")))
(define-key map "\C-c\C-i" 'sgml-tags-invisible)
(define-key map "/" 'sgml-slash)
@@ -94,18 +99,18 @@ This takes effect when first loading the sgml-mode library.")
(define-key map "\C-c\C-d" 'sgml-delete-tag)
(define-key map "\C-c\^?" 'sgml-delete-tag)
(define-key map "\C-c?" 'sgml-tag-help)
+ (define-key map "\C-c/" 'sgml-close-tag)
(define-key map "\C-c8" 'sgml-name-8bit-mode)
(define-key map "\C-c\C-v" 'sgml-validate)
- (if sgml-quick-keys
- (progn
- (define-key map "&" 'sgml-name-char)
- (define-key map "<" 'sgml-tag)
- (define-key map " " 'sgml-auto-attributes)
- (define-key map ">" 'sgml-maybe-end-tag)
- (if (memq ?\" sgml-specials)
- (define-key map "\"" 'sgml-name-self))
- (if (memq ?' sgml-specials)
- (define-key map "'" 'sgml-name-self))))
+ (when sgml-quick-keys
+ (define-key map "&" 'sgml-name-char)
+ (define-key map "<" 'sgml-tag)
+ (define-key map " " 'sgml-auto-attributes)
+ (define-key map ">" 'sgml-maybe-end-tag)
+ (when (memq ?\" sgml-specials)
+ (define-key map "\"" 'sgml-name-self))
+ (when (memq ?' sgml-specials)
+ (define-key map "'" 'sgml-name-self)))
(let ((c 127)
(map (nth 1 map)))
(while (< (setq c (1+ c)) 256)
@@ -130,23 +135,33 @@ This takes effect when first loading the sgml-mode library.")
map)
"Keymap for SGML mode. See also `sgml-specials'.")
-
-(defvar sgml-mode-syntax-table
- (let ((table (copy-syntax-table text-mode-syntax-table)))
+(defun sgml-make-syntax-table (specials)
+ (let ((table (make-syntax-table text-mode-syntax-table)))
(modify-syntax-entry ?< "(>" table)
(modify-syntax-entry ?> ")<" table)
- (if (memq ?- sgml-specials)
+ (modify-syntax-entry ?: "_" table)
+ (modify-syntax-entry ?_ "_" table)
+ (modify-syntax-entry ?. "_" table)
+ (if (memq ?- specials)
(modify-syntax-entry ?- "_ 1234" table))
- (if (memq ?\" sgml-specials)
+ (if (memq ?\" specials)
(modify-syntax-entry ?\" "\"\"" table))
- (if (memq ?' sgml-specials)
+ (if (memq ?' specials)
(modify-syntax-entry ?\' "\"'" table))
- table)
+ table))
+
+(defvar sgml-mode-syntax-table (sgml-make-syntax-table sgml-specials)
"Syntax table used in SGML mode. See also `sgml-specials'.")
+(defconst sgml-tag-syntax-table
+ (let ((table (sgml-make-syntax-table '(?- ?\" ?\'))))
+ (dolist (char '(?\( ?\) ?\{ ?\} ?\[ ?\] ?$ ?% ?& ?* ?+ ?/))
+ (modify-syntax-entry char "." table))
+ table)
+ "Syntax table used to parse SGML tags.")
(defcustom sgml-name-8bit-mode nil
- "*When non-nil, insert 8 bit characters with their names."
+ "*When non-nil, insert non-ASCII characters as named entities."
:type 'boolean
:group 'sgml)
@@ -185,6 +200,19 @@ This takes effect when first loading the sgml-mode library.")
"oslash" "ugrave" "uacute" "ucirc" "uuml" "yacute" "thorn" "yuml"]
"Vector of symbolic character names without `&' and `;'.")
+(put 'sgml-table 'char-table-extra-slots 0)
+
+(defvar sgml-char-names-table
+ (let ((table (make-char-table 'sgml-table))
+ (i 32)
+ elt)
+ (while (< i 128)
+ (setq elt (aref sgml-char-names i))
+ (if elt (aset table (make-char 'latin-iso8859-1 i) elt))
+ (setq i (1+ i)))
+ table)
+ "A table for mapping non-ASCII characters into SGML entity names.
+Currently, only Latin-1 characters are supported.")
;; nsgmls is a free SGML parser in the SP suite available from
;; ftp.jclark.com and otherwise packaged for GNU systems.
@@ -202,34 +230,64 @@ separated by a space."
(defvar sgml-saved-validate-command nil
"The command last used to validate in this buffer.")
-
-;;; I doubt that null end tags are used much for large elements,
-;;; so use a small distance here.
+;; I doubt that null end tags are used much for large elements,
+;; so use a small distance here.
(defcustom sgml-slash-distance 1000
"*If non-nil, is the maximum distance to search for matching `/'."
:type '(choice (const nil) integer)
:group 'sgml)
-(defconst sgml-start-tag-regex
- "<[A-Za-z]\\([-.A-Za-z0-9= \n\t]\\|\"[^\"]*\"\\|'[^']*'\\)*"
+(defconst sgml-namespace-re "[_[:alpha:]][-_.[:alnum:]]*")
+(defconst sgml-name-re "[_:[:alpha:]][-_.:[:alnum:]]*")
+(defconst sgml-tag-name-re (concat "<\\([!/?]?" sgml-name-re "\\)"))
+(defconst sgml-attrs-re "\\(?:[^\"'/><]\\|\"[^\"]*\"\\|'[^']*'\\)*")
+(defconst sgml-start-tag-regex (concat "<" sgml-name-re sgml-attrs-re)
"Regular expression that matches a non-empty start tag.
Any terminating `>' or `/' is not matched.")
+(defface sgml-namespace-face
+ '((t (:inherit font-lock-builtin-face)))
+ "`sgml-mode' face used to highlight the namespace part of identifiers.")
+(defvar sgml-namespace-face 'sgml-namespace-face)
;; internal
(defconst sgml-font-lock-keywords-1
- '(("<\\([!?][a-z][-.a-z0-9]*\\)" 1 font-lock-keyword-face)
- ("<\\(/?[a-z][-.a-z0-9]*\\)" 1 font-lock-function-name-face)
- ("[&%][a-z][-.a-z0-9]*;?" . font-lock-variable-name-face)
- ("" . font-lock-comment-face)))
-
-(defconst sgml-font-lock-keywords-2 ())
+ `((,(concat "<\\([!?]" sgml-name-re "\\)") 1 font-lock-keyword-face)
+ ;; We could use the simpler "\\(" sgml-namespace-re ":\\)?" instead,
+ ;; but it would cause a bit more backtracking in the re-matcher.
+ (,(concat "?\\(" sgml-namespace-re "\\)\\(?::\\(" sgml-name-re "\\)\\)?")
+ (1 (if (match-end 2) sgml-namespace-face font-lock-function-name-face))
+ (2 font-lock-function-name-face nil t))
+ ;; FIXME: this doesn't cover the variables using a default value.
+ (,(concat "\\(" sgml-namespace-re "\\)\\(?::\\("
+ sgml-name-re "\\)\\)?=[\"']")
+ (1 (if (match-end 2) sgml-namespace-face font-lock-variable-name-face))
+ (2 font-lock-variable-name-face nil t))
+ (,(concat "[&%]" sgml-name-re ";?") . font-lock-variable-name-face)))
+
+(defconst sgml-font-lock-keywords-2
+ (append
+ sgml-font-lock-keywords-1
+ '((eval
+ . (cons (concat "<"
+ (regexp-opt (mapcar 'car sgml-tag-face-alist) t)
+ "\\([ \t][^>]*\\)?>\\([^<]+\\)\\1>")
+ '(3 (cdr (assoc (downcase (match-string 1))
+ sgml-tag-face-alist)) prepend))))))
;; for font-lock, but must be defvar'ed after
;; sgml-font-lock-keywords-1 and sgml-font-lock-keywords-2 above
(defvar sgml-font-lock-keywords sgml-font-lock-keywords-1
"*Rules for highlighting SGML code. See also `sgml-tag-face-alist'.")
+(defvar sgml-font-lock-syntactic-keywords
+ ;; Use the `b' style of comments to avoid interference with the -- ... --
+ ;; comments recognized when `sgml-specials' includes ?-.
+ ;; FIXME: beware of blabla !!
+ '(("\\(<\\)!--" (1 "< b"))
+ ("--[ \t\n]*\\(>\\)" (1 "> b")))
+ "Syntactic keywords for `sgml-mode'.")
+
;; internal
(defvar sgml-face-tag-alist ()
"Alist of face and tag name for facemenu.")
@@ -239,14 +297,12 @@ Any terminating `>' or `/' is not matched.")
When `font-lock-maximum-decoration' is 1 this is always used for fontifying.
When more these are fontified together with `sgml-font-lock-keywords'.")
-
(defvar sgml-display-text ()
"Tag names as lowercase symbols, and display string when invisible.")
;; internal
(defvar sgml-tags-invisible nil)
-
(defcustom sgml-tag-alist
'(("![" ("ignore" t) ("include" t))
("!attlist")
@@ -259,8 +315,8 @@ This alist is made up as
((\"tag\" . TAGRULE)
...)
-TAGRULE is a list of optionally `t' (no endtag) or `\\n' (separate endtag by
-newlines) or a skeleton with `nil', `t' or `\\n' in place of the interactor
+TAGRULE is a list of optionally t (no endtag) or `\\n' (separate endtag by
+newlines) or a skeleton with nil, t or `\\n' in place of the interactor
followed by an ATTRIBUTERULE (for an always present attribute) or an
attribute alist.
@@ -269,7 +325,7 @@ The attribute alist is made up as
((\"attribute\" . ATTRIBUTERULE)
...)
-ATTRIBUTERULE is a list of optionally `t' (no value when no input) followed by
+ATTRIBUTERULE is a list of optionally t (no value when no input) followed by
an optional alist of possible values."
:type '(repeat (cons (string :tag "Tag Name")
(repeat :tag "Tag Rule" sexp)))
@@ -287,77 +343,45 @@ an optional alist of possible values."
(string :tag "Description")))
:group 'sgml)
-(defvar v2) ; free for skeleton
+(defcustom sgml-xml-mode nil
+ "*When non-nil, tag insertion functions will be XML-compliant.
+If this variable is customized, the custom value is used always.
+Otherwise, it is set to be buffer-local when the file has
+ a DOCTYPE or an XML declaration."
+ :type 'boolean
+ :version "21.4"
+ :group 'sgml)
-(defun sgml-mode-common (sgml-tag-face-alist sgml-display-text)
- "Common code for setting up `sgml-mode' and derived modes.
-SGML-TAG-FACE-ALIST is used for calculating `sgml-font-lock-keywords-2'.
-SGML-DISPLAY-TEXT sets up alternate text for when tags are invisible (see
-varables of same name)."
- (setq local-abbrev-table text-mode-abbrev-table)
- (set-syntax-table sgml-mode-syntax-table)
- (make-local-variable 'indent-line-function)
- (make-local-variable 'paragraph-start)
- (make-local-variable 'paragraph-separate)
- (make-local-variable 'adaptive-fill-regexp)
- (make-local-variable 'sgml-saved-validate-command)
- (make-local-variable 'comment-start)
- (make-local-variable 'comment-end)
- (make-local-variable 'comment-indent-function)
- (make-local-variable 'comment-indent-function)
- (make-local-variable 'sgml-tags-invisible)
- (make-local-variable 'skeleton-transformation)
- (make-local-variable 'skeleton-further-elements)
- (make-local-variable 'skeleton-end-hook)
- (make-local-variable 'font-lock-defaults)
- (make-local-variable 'sgml-font-lock-keywords-1)
- (make-local-variable 'sgml-font-lock-keywords-2)
- (make-local-variable 'facemenu-add-face-function)
- (make-local-variable 'facemenu-end-add-face)
- ;;(make-local-variable 'facemenu-remove-face-function)
- (and sgml-tag-face-alist
- (not (assq 1 sgml-tag-face-alist))
- (nconc sgml-tag-face-alist
- `((1 (,(concat "<\\("
- (mapconcat 'car sgml-tag-face-alist "\\|")
- "\\)\\([ \t].+\\)?>\\(.+\\)\\1>")
- 3 (cdr (assoc (downcase (match-string 1))
- ',sgml-tag-face-alist)))))))
- (setq indent-line-function 'indent-relative-maybe
- ;; A start or end tag by itself on a line separates a paragraph.
- ;; This is desirable because SGML discards a newline that appears
- ;; immediately after a start tag or immediately before an end tag.
- paragraph-separate "[ \t]*$\\|\
-\[ \t]*?\\([A-Za-z]\\([-.A-Za-z0-9= \t\n]\\|\"[^\"]*\"\\|'[^']*'\\)*\\)?>$"
- paragraph-start "[ \t]*$\\|\
-\[ \t]*?\\([A-Za-z]\\([-.A-Za-z0-9= \t\n]\\|\"[^\"]*\"\\|'[^']*'\\)*\\)?>"
- adaptive-fill-regexp "[ \t]*"
- comment-start ""
- comment-indent-function 'sgml-comment-indent
- skeleton-transformation sgml-transformation
- skeleton-further-elements '((completion-ignore-case t))
- skeleton-end-hook (lambda ()
- (or (eolp)
- (not (or (eq v2 '\n)
- (eq (car-safe v2) '\n)))
- (newline-and-indent)))
- sgml-font-lock-keywords-2 (append
- sgml-font-lock-keywords-1
- (cdr (assq 1 sgml-tag-face-alist)))
- font-lock-defaults '((sgml-font-lock-keywords
- sgml-font-lock-keywords-1
- sgml-font-lock-keywords-2)
- nil
- t)
- facemenu-add-face-function 'sgml-mode-facemenu-add-face-function)
- ;; This will allow existing comments within declarations to be
- ;; recognized.
- (set (make-local-variable 'comment-start-skip) "\\(?:\\)?")
- (dolist (pair sgml-display-text)
- (put (car pair) 'before-string (cdr pair))))
+(defvar sgml-empty-tags nil
+ "List of tags whose !ELEMENT definition says EMPTY.")
+
+(defvar sgml-unclosed-tags nil
+ "List of tags whose !ELEMENT definition says the end-tag is optional.")
+
+(defun sgml-xml-guess ()
+ "Guess whether the current buffer is XML."
+ (save-excursion
+ (goto-char (point-min))
+ (when (or (string= "xml" (file-name-extension (or buffer-file-name "")))
+ (looking-at "\\s-*<\\?xml")
+ (when (re-search-forward
+ (eval-when-compile
+ (mapconcat 'identity
+ '(""))
(concat "<" face ">"))
- (error "Face not configured for %s mode." mode-name)))
-
+ (error "Face not configured for %s mode" mode-name)))
;;;###autoload
-(defun sgml-mode ()
+(define-derived-mode sgml-mode text-mode "SGML"
"Major mode for editing SGML documents.
-Makes > match <. Makes / blink matching /.
-Keys <, &, SPC within <>, \" and ' can be electric depending on
+Makes > match <.
+Keys <, &, SPC within <>, \", / and ' can be electric depending on
`sgml-quick-keys'.
An argument of N to a tag-inserting command means to wrap it around
@@ -387,26 +410,77 @@ Use \\[sgml-validate] to validate your document with an SGML parser.
Do \\[describe-variable] sgml- SPC to see available variables.
Do \\[describe-key] on the following bindings to discover what they do.
\\{sgml-mode-map}"
- (interactive)
- (kill-all-local-variables)
- (setq mode-name "SGML"
- major-mode 'sgml-mode)
- (sgml-mode-common sgml-tag-face-alist sgml-display-text)
- ;; Set imenu-generic-expression here, rather than in sgml-mode-common,
- ;; because this definition probably is not useful in HTML mode.
- (make-local-variable 'imenu-generic-expression)
- (setq imenu-generic-expression
- ""))
+ (set (make-local-variable 'paragraph-separate)
+ (concat paragraph-start "$"))
+ (set (make-local-variable 'adaptive-fill-regexp) "[ \t]*")
+ (set (make-local-variable 'indent-line-function) 'sgml-indent-line)
+ (set (make-local-variable 'comment-start) "")
+ (set (make-local-variable 'comment-indent-function) 'sgml-comment-indent)
+ (set (make-local-variable 'comment-line-break-function)
+ 'sgml-comment-indent-new-line)
+ (set (make-local-variable 'skeleton-further-elements)
+ '((completion-ignore-case t)))
+ (set (make-local-variable 'skeleton-end-hook)
+ (lambda ()
+ (or (eolp)
+ (not (or (eq v2 '\n) (eq (car-safe v2) '\n)))
+ (newline-and-indent))))
+ (set (make-local-variable 'font-lock-defaults)
+ '((sgml-font-lock-keywords
+ sgml-font-lock-keywords-1
+ sgml-font-lock-keywords-2)
+ nil t nil nil
+ (font-lock-syntactic-keywords
+ . sgml-font-lock-syntactic-keywords)))
+ (set (make-local-variable 'facemenu-add-face-function)
+ 'sgml-mode-facemenu-add-face-function)
+ (sgml-xml-guess)
+ (if sgml-xml-mode
+ (setq mode-name "XML")
+ (set (make-local-variable 'skeleton-transformation) sgml-transformation))
+ ;; This will allow existing comments within declarations to be
+ ;; recognized.
+ (set (make-local-variable 'comment-start-skip) "\\(?:\\)?")
+ ;; This definition probably is not useful in derived modes.
+ (set (make-local-variable 'imenu-generic-expression)
+ (concat " (length sgml-tag-last) 0)
+ (format "Tag (default %s): " sgml-tag-last)
+ "Tag: ")
+ sgml-tag-alist nil nil nil 'sgml-tag-history sgml-tag-last)))
?< str |
(("") -1 '(undo-boundary) (identity "<")) | ; see comment above
`(("") '(setq v2 (sgml-attributes ,str t)) ?>
- (if (string= "![" ,str)
- (prog1 '(("") " [ " _ " ]]")
- (backward-char))
- (if (or (eq v2 t)
- (string-match "^[/!?]" ,str))
- ()
- (if (symbolp v2)
- ;; We go use `identity' to prevent skeleton from passing
- ;; `str' through skeleton-transformation a second time.
- '(("") v2 _ v2 "" (identity ',str) ?>)
- (if (eq (car v2) t)
- (cons '("") (cdr v2))
- (append '(("") (car v2))
- (cdr v2)
- '(resume: (car v2) _ "" (identity ',str) ?>))))))))
+ (cond
+ ((string= "![" ,str)
+ (backward-char)
+ '(("") " [ " _ " ]]"))
+ ((and (eq v2 t) sgml-xml-mode (member ,str sgml-empty-tags))
+ '(("") -1 "/>"))
+ ((or (and (eq v2 t) (not sgml-xml-mode)) (string-match "^[/!?]" ,str))
+ nil)
+ ((symbolp v2)
+ ;; Make sure we don't fall into an infinite loop.
+ ;; For xhtml's `tr' tag, we should maybe use \n instead.
+ (if (eq v2 t) (setq v2 nil))
+ ;; We use `identity' to prevent skeleton from passing
+ ;; `str' through skeleton-transformation a second time.
+ '(("") v2 _ v2 "" (identity ',str) ?>))
+ ((eq (car v2) t)
+ (cons '("") (cdr v2)))
+ (t
+ (append '(("") (car v2))
+ (cdr v2)
+ '(resume: (car v2) _ "" (identity ',str) ?>))))))
(autoload 'skeleton-read "skeleton")
@@ -578,7 +678,6 @@ With prefix argument, only self insert."
(or (> (point) point)
(self-insert-command 1)))))
-
(defun sgml-tag-help (&optional tag)
"Display description of tag TAG. If TAG is omitted, use the tag at point."
(interactive)
@@ -596,70 +695,72 @@ With prefix argument, only self insert."
(cdr (assoc (downcase (substring tag 1)) sgml-tag-help)))
"No description available")))
-
-(defun sgml-maybe-end-tag ()
- "Name self unless in position to end a tag."
- (interactive)
- (or (condition-case nil
- (save-excursion (up-list -1))
- (error
- (sgml-name-self)
- t))
- (condition-case nil
- (progn
- (save-excursion (up-list 1))
- (sgml-name-self))
- (error (self-insert-command 1)))))
-
+(defun sgml-maybe-end-tag (&optional arg)
+ "Name self unless in position to end a tag or a prefix ARG is given."
+ (interactive "P")
+ (if (or arg (eq (car (sgml-lexical-context)) 'tag))
+ (self-insert-command (prefix-numeric-value arg))
+ (sgml-name-self)))
(defun sgml-skip-tag-backward (arg)
"Skip to beginning of tag or matching opening tag if present.
With prefix argument ARG, repeat this ARG times."
(interactive "p")
+ ;; FIXME: use sgml-get-context or something similar.
(while (>= arg 1)
(search-backward "<" nil t)
(if (looking-at "\\([^ \n\t>]+\\)")
;; end tag, skip any nested pairs
(let ((case-fold-search t)
- (re (concat "?" (regexp-quote (match-string 1)))))
+ (re (concat "?" (regexp-quote (match-string 1))
+ ;; Ignore empty tags like .
+ "\\([^>]*[^/>]\\)?>")))
(while (and (re-search-backward re nil t)
(eq (char-after (1+ (point))) ?/))
(forward-char 1)
(sgml-skip-tag-backward 1))))
(setq arg (1- arg))))
-(defun sgml-skip-tag-forward (arg &optional return)
+(defun sgml-skip-tag-forward (arg)
"Skip to end of tag or matching closing tag if present.
With prefix argument ARG, repeat this ARG times.
Return t iff after a closing tag."
(interactive "p")
- (setq return t)
- (while (>= arg 1)
- (skip-chars-forward "^<>")
- (if (eq (following-char) ?>)
- (up-list -1))
- (if (looking-at "<\\([^/ \n\t>]+\\)")
- ;; start tag, skip any nested same pairs _and_ closing tag
- (let ((case-fold-search t)
- (re (concat "?" (regexp-quote (match-string 1))))
- point close)
- (forward-list 1)
- (setq point (point))
- (while (and (re-search-forward re nil t)
- (not (setq close
- (eq (char-after (1+ (match-beginning 0))) ?/)))
- (not (up-list -1))
- (sgml-skip-tag-forward 1))
- (setq close nil))
- (if close
- (up-list 1)
- (goto-char point)
- (setq return)))
- (forward-list 1))
- (setq arg (1- arg)))
- return)
+ ;; FIXME: Use sgml-get-context or something similar.
+ ;; It currently might jump to an unrelated
if the
+ ;; we're skipping has no matching
.
+ (let ((return t))
+ (with-syntax-table sgml-tag-syntax-table
+ (while (>= arg 1)
+ (skip-chars-forward "^<>")
+ (if (eq (following-char) ?>)
+ (up-list -1))
+ (if (looking-at "<\\([^/ \n\t>]+\\)\\([^>]*[^/>]\\)?>")
+ ;; start tag, skip any nested same pairs _and_ closing tag
+ (let ((case-fold-search t)
+ (re (concat "?" (regexp-quote (match-string 1))
+ ;; Ignore empty tags like .
+ "\\([^>]*[^/>]\\)?>"))
+ point close)
+ (forward-list 1)
+ (setq point (point))
+ ;; FIXME: This re-search-forward will mistakenly match
+ ;; tag-like text inside attributes.
+ (while (and (re-search-forward re nil t)
+ (not (setq close
+ (eq (char-after (1+ (match-beginning 0))) ?/)))
+ (goto-char (match-beginning 0))
+ (sgml-skip-tag-forward 1))
+ (setq close nil))
+ (unless close
+ (goto-char point)
+ (setq return nil)))
+ (forward-list 1))
+ (setq arg (1- arg)))
+ return)))
(defun sgml-delete-tag (arg)
+ ;; FIXME: Should be called sgml-kill-tag or should not touch the kill-ring.
"Delete tag on or after cursor, and matching closing or opening tag.
With prefix argument ARG, repeat this ARG times."
(interactive "p")
@@ -693,20 +794,23 @@ With prefix argument ARG, repeat this ARG times."
(goto-char close)
(kill-sexp 1))
(setq open (point))
- (sgml-skip-tag-forward 1)
- (backward-list)
- (forward-char)
- (if (eq (aref (sgml-beginning-of-tag) 0) ?/)
- (kill-sexp 1)))
+ (when (sgml-skip-tag-forward 1)
+ (kill-sexp -1)))
+ ;; Delete any resulting empty line. If we didn't kill-sexp,
+ ;; this *should* do nothing, because we're right after the tag.
+ (if (progn (forward-line 0) (looking-at "\\(?:[ \t]*$\\)\n?"))
+ (delete-region (match-beginning 0) (match-end 0)))
(goto-char open)
- (kill-sexp 1)))
+ (kill-sexp 1)
+ (if (progn (forward-line 0) (looking-at "\\(?:[ \t]*$\\)\n?"))
+ (delete-region (match-beginning 0) (match-end 0)))))
(setq arg (1- arg))))
+
;; Put read-only last to enable setting this even when read-only enabled.
(or (get 'sgml-tag 'invisible)
(setplist 'sgml-tag
(append '(invisible t
- intangible t
point-entered sgml-point-entered
rear-nonsticky t
read-only t)
@@ -717,34 +821,41 @@ With prefix argument ARG, repeat this ARG times."
(interactive "P")
(let ((modified (buffer-modified-p))
(inhibit-read-only t)
+ (inhibit-modification-hooks t)
+ ;; Avoid spurious the `file-locked' checks.
+ (buffer-file-name nil)
;; This is needed in case font lock gets called,
;; since it moves point and might call sgml-point-entered.
+ ;; How could it get called? -stef
(inhibit-point-motion-hooks t)
- symbol)
- (save-excursion
- (goto-char (point-min))
- (if (setq sgml-tags-invisible
- (if arg
- (>= (prefix-numeric-value arg) 0)
- (not sgml-tags-invisible)))
- (while (re-search-forward "<\\([!/?A-Za-z][-A-Za-z0-9]*\\)"
- nil t)
- (setq symbol (intern-soft (downcase (match-string 1))))
- (goto-char (match-beginning 0))
- (and (get symbol 'before-string)
- (not (overlays-at (point)))
- (overlay-put (make-overlay (point)
- (match-beginning 1))
- 'category symbol))
- (put-text-property (point)
- (progn (forward-list) (point))
- 'category 'sgml-tag))
- (let ((pos (point)))
- (while (< (setq pos (next-overlay-change pos)) (point-max))
- (delete-overlay (car (overlays-at pos)))))
- (remove-text-properties (point-min) (point-max)
- '(category sgml-tag intangible t))))
- (set-buffer-modified-p modified)
+ string)
+ (unwind-protect
+ (save-excursion
+ (goto-char (point-min))
+ (if (set (make-local-variable 'sgml-tags-invisible)
+ (if arg
+ (>= (prefix-numeric-value arg) 0)
+ (not sgml-tags-invisible)))
+ (while (re-search-forward sgml-tag-name-re nil t)
+ (setq string
+ (cdr (assq (intern-soft (downcase (match-string 1)))
+ sgml-display-text)))
+ (goto-char (match-beginning 0))
+ (and (stringp string)
+ (not (overlays-at (point)))
+ (let ((ol (make-overlay (point) (match-beginning 1))))
+ (overlay-put ol 'before-string string)
+ (overlay-put ol 'sgml-tag t)))
+ (put-text-property (point)
+ (progn (forward-list) (point))
+ 'category 'sgml-tag))
+ (let ((pos (point-min)))
+ (while (< (setq pos (next-overlay-change pos)) (point-max))
+ (dolist (ol (overlays-at pos))
+ (if (overlay-get ol 'sgml-tag)
+ (delete-overlay ol)))))
+ (remove-text-properties (point-min) (point-max) '(category nil))))
+ (restore-buffer-modified-p modified))
(run-hooks 'sgml-tags-invisible-hook)
(message "")))
@@ -753,7 +864,8 @@ With prefix argument ARG, repeat this ARG times."
(let ((inhibit-point-motion-hooks t))
(save-excursion
(message "Invisible tag: %s"
- (buffer-substring
+ ;; Strip properties, otherwise, the text is invisible.
+ (buffer-substring-no-properties
(point)
(if (or (and (> x y)
(not (eq (following-char) ?<)))
@@ -761,6 +873,7 @@ With prefix argument ARG, repeat this ARG times."
(eq (preceding-char) ?>)))
(backward-list)
(forward-list)))))))
+
(autoload 'compile-internal "compile")
@@ -782,48 +895,486 @@ and move to the line in the SGML document that caused it."
(save-some-buffers (not compilation-ask-about-save) nil)
(compile-internal command "No more errors"))
+(defsubst sgml-at-indentation-p ()
+ "Return true if point is at the first non-whitespace character on the line."
+ (save-excursion
+ (skip-chars-backward " \t")
+ (bolp)))
+
+(defun sgml-lexical-context (&optional limit)
+ "Return the lexical context at point as (TYPE . START).
+START is the location of the start of the lexical element.
+TYPE is one of `string', `comment', `tag', `cdata', or `text'.
+
+Optional argument LIMIT is the position to start parsing from.
+If nil, start from a preceding tag at indentation."
+ (save-excursion
+ (let ((pos (point))
+ text-start state)
+ (if limit
+ (goto-char limit)
+ ;; Skip tags backwards until we find one at indentation
+ (while (and (ignore-errors (sgml-parse-tag-backward))
+ (not (sgml-at-indentation-p)))))
+ (with-syntax-table sgml-tag-syntax-table
+ (while (< (point) pos)
+ ;; When entering this loop we're inside text.
+ (setq text-start (point))
+ (skip-chars-forward "^<" pos)
+ (setq state
+ (cond
+ ((= (point) pos)
+ ;; We got to the end without seeing a tag.
+ nil)
+ ((looking-at "" pos 'move)
+ (list 0 nil nil 'cdata nil nil nil nil cdata-start))))
+ (t
+ ;; We've reached a tag. Parse it.
+ ;; FIXME: Handle net-enabling start-tags
+ (parse-partial-sexp (point) pos 0))))))
+ (cond
+ ((eq (nth 3 state) 'cdata) (cons 'cdata (nth 8 state)))
+ ((nth 3 state) (cons 'string (nth 8 state)))
+ ((nth 4 state) (cons 'comment (nth 8 state)))
+ ((and state (> (nth 0 state) 0)) (cons 'tag (nth 1 state)))
+ (t (cons 'text text-start))))))
(defun sgml-beginning-of-tag (&optional top-level)
"Skip to beginning of tag and return its name.
-If this can't be done, return t."
- (or (if top-level
- (condition-case nil
- (up-list -1)
- (error t))
- (>= (point)
- (if (search-backward "<" nil t)
- (save-excursion
- (forward-list)
- (point))
- 0)))
- (if (looking-at "<[!?/]?[[A-Za-z][A-Za-z0-9]*")
- (buffer-substring-no-properties
- (1+ (point))
- (match-end 0))
- t)))
+If this can't be done, return nil."
+ (let ((context (sgml-lexical-context)))
+ (if (eq (car context) 'tag)
+ (progn
+ (goto-char (cdr context))
+ (when (looking-at sgml-tag-name-re)
+ (match-string-no-properties 1)))
+ (if top-level nil
+ (when (not (eq (car context) 'text))
+ (goto-char (cdr context))
+ (sgml-beginning-of-tag t))))))
(defun sgml-value (alist)
- "Interactively insert value taken from attributerule ALIST.
-See `sgml-tag-alist' for info about attributerules.."
+ "Interactively insert value taken from attribute-rule ALIST.
+See `sgml-tag-alist' for info about attribute rules."
(setq alist (cdr alist))
(if (stringp (car alist))
(insert "=\"" (car alist) ?\")
- (if (eq (car alist) t)
- (if (cdr alist)
- (progn
- (insert "=\"")
- (setq alist (skeleton-read '(completing-read
- "Value: " (cdr alist))))
- (if (string< "" alist)
- (insert alist ?\")
- (delete-backward-char 2))))
+ (if (and (eq (car alist) t) (not sgml-xml-mode))
+ (when (cdr alist)
+ (insert "=\"")
+ (setq alist (skeleton-read '(completing-read "Value: " (cdr alist))))
+ (if (string< "" alist)
+ (insert alist ?\")
+ (delete-backward-char 2)))
(insert "=\"")
- (if alist
- (insert (skeleton-read '(completing-read "Value: " alist))))
+ (when alist
+ (insert (skeleton-read '(completing-read "Value: " alist))))
(insert ?\"))))
-(provide 'sgml-mode)
+(defun sgml-quote (start end &optional unquotep)
+ "Quote SGML text in region START ... END.
+Only &, < and > are quoted, the rest is left untouched.
+With prefix argument UNQUOTEP, unquote the region."
+ (interactive "r\nP")
+ (save-restriction
+ (narrow-to-region start end)
+ (goto-char (point-min))
+ (if unquotep
+ ;; FIXME: We should unquote other named character references as well.
+ (while (re-search-forward
+ "\\(&\\(amp\\|\\(l\\|\\(g\\)\\)t\\)\\)[][<>&;\n\t \"%!'(),/=?]"
+ nil t)
+ (replace-match (if (match-end 4) ">" (if (match-end 3) "<" "&")) t t
+ nil (if (eq (char-before (match-end 0)) ?\;) 0 1)))
+ (while (re-search-forward "[&<>]" nil t)
+ (replace-match (cdr (assq (char-before) '((?& . "&")
+ (?< . "<")
+ (?> . ">"))))
+ t t)))))
+
+(defun sgml-pretty-print (beg end)
+ "Simple-minded pretty printer for SGML.
+Re-indents the code and inserts newlines between BEG and END.
+You might want to turn on `auto-fill-mode' to get better results."
+ ;; TODO:
+ ;; - insert newline between some start-tag and text.
+ ;; - don't insert newline in front of some end-tags.
+ (interactive "r")
+ (save-excursion
+ (if (< beg end)
+ (goto-char beg)
+ (goto-char end)
+ (setq end beg)
+ (setq beg (point)))
+ ;; Don't use narrowing because it screws up auto-indent.
+ (setq end (copy-marker end t))
+ (with-syntax-table sgml-tag-syntax-table
+ (while (re-search-forward "<" end t)
+ (goto-char (match-beginning 0))
+ (unless (or ;;(looking-at "")
+ (progn (skip-chars-backward " \t") (bolp)))
+ (reindent-then-newline-and-indent))
+ (forward-sexp 1)))
+ ;; (indent-region beg end)
+ ))
+
+
+;; Parsing
+
+(defstruct (sgml-tag
+ (:constructor sgml-make-tag (type start end name)))
+ type start end name)
+
+(defsubst sgml-parse-tag-name ()
+ "Skip past a tag-name, and return the name."
+ (buffer-substring-no-properties
+ (point) (progn (skip-syntax-forward "w_") (point))))
+
+(defsubst sgml-looking-back-at (str)
+ "Return t if the test before point matches STR."
+ (let ((start (- (point) (length str))))
+ (and (>= start (point-min))
+ (equal str (buffer-substring-no-properties start (point))))))
+
+(defun sgml-parse-tag-backward (&optional limit)
+ "Parse an SGML tag backward, and return information about the tag.
+Assume that parsing starts from within a textual context.
+Leave point at the beginning of the tag."
+ (let (tag-type tag-start tag-end name)
+ (or (re-search-backward "[<>]" limit 'move)
+ (error "No tag found"))
+ (when (eq (char-after) ?<)
+ ;; Oops!! Looks like we were not in a textual context after all!.
+ ;; Let's try to recover.
+ (with-syntax-table sgml-tag-syntax-table
+ (forward-sexp)
+ (forward-char -1)))
+ (setq tag-end (1+ (point)))
+ (cond
+ ((sgml-looking-back-at "--") ; comment
+ (setq tag-type 'comment
+ tag-start (search-backward ""))
+ (cdata (insert "]]>"))
+ (pi (insert " ?>"))
+ (jsp (insert " %>"))
+ (tag (insert " />"))
+ (text
+ (let ((context (save-excursion (sgml-get-context))))
+ (if context
+ (progn
+ (insert "" (sgml-tag-name (car (last context))) ">")
+ (indent-according-to-mode)))))
+ (otherwise
+ (error "Nothing to close"))))
+
+(defun sgml-empty-tag-p (tag-name)
+ "Return non-nil if TAG-NAME is an implicitly empty tag."
+ (and (not sgml-xml-mode)
+ (member-ignore-case tag-name sgml-empty-tags)))
+
+(defun sgml-unclosed-tag-p (tag-name)
+ "Return non-nil if TAG-NAME is a tag for which an end-tag is optional."
+ (and (not sgml-xml-mode)
+ (member-ignore-case tag-name sgml-unclosed-tags)))
+
+(defun sgml-calculate-indent (&optional lcon)
+ "Calculate the column to which this line should be indented.
+LCON is the lexical context, if any."
+ (unless lcon (setq lcon (sgml-lexical-context)))
+
+ ;; Indent comment-start markers inside