summaryrefslogtreecommitdiff
path: root/lisp/textmodes/sgml-mode.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/textmodes/sgml-mode.el')
-rw-r--r--lisp/textmodes/sgml-mode.el225
1 files changed, 149 insertions, 76 deletions
diff --git a/lisp/textmodes/sgml-mode.el b/lisp/textmodes/sgml-mode.el
index 128e58810e5..8d3000e5d8b 100644
--- a/lisp/textmodes/sgml-mode.el
+++ b/lisp/textmodes/sgml-mode.el
@@ -46,8 +46,7 @@
(defcustom sgml-basic-offset 2
"Specifies the basic indentation level for `sgml-indent-line'."
- :type 'integer
- :group 'sgml)
+ :type 'integer)
(defcustom sgml-attribute-offset 0
"Specifies a delta for attribute indentation in `sgml-indent-line'.
@@ -65,16 +64,16 @@ When 2, attribute indentation looks like this:
</element>"
:version "25.1"
:type 'integer
- :safe 'integerp
- :group 'sgml)
+ :safe 'integerp)
(defcustom sgml-xml-mode nil
"When non-nil, tag insertion functions will be XML-compliant.
It is set to be buffer-local when the file has
a DOCTYPE or an XML declaration."
:type 'boolean
- :version "22.1"
- :group 'sgml)
+ :version "22.1")
+
+(defvaralias 'sgml-transformation 'sgml-transformation-function)
(defcustom sgml-transformation-function 'identity
"Default value for `skeleton-transformation-function' in SGML mode."
@@ -87,17 +86,14 @@ a DOCTYPE or an XML declaration."
(and (derived-mode-p 'sgml-mode)
(not sgml-xml-mode)
(setq skeleton-transformation-function val))))
- (buffer-list)))
- :group 'sgml)
+ (buffer-list))))
(put 'sgml-transformation-function 'variable-interactive
"aTransformation function: ")
-(defvaralias 'sgml-transformation 'sgml-transformation-function)
(defcustom sgml-mode-hook nil
"Hook run by command `sgml-mode'.
`text-mode-hook' is run first."
- :group 'sgml
:type 'hook)
;; The official handling of "--" is complicated in SGML, and
@@ -206,8 +202,7 @@ This takes effect when first loading the `sgml-mode' library.")
(defcustom sgml-name-8bit-mode nil
"When non-nil, insert non-ASCII characters as named entities."
- :type 'boolean
- :group 'sgml)
+ :type 'boolean)
(defvar sgml-char-names
[nil nil nil nil nil nil nil nil
@@ -277,8 +272,7 @@ Currently, only Latin-1 characters are supported.")
The file name of current buffer file name will be appended to this,
separated by a space."
:type 'string
- :version "21.1"
- :group 'sgml)
+ :version "21.1")
(defvar sgml-saved-validate-command nil
"The command last used to validate in this buffer.")
@@ -287,8 +281,7 @@ separated by a space."
;; 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)
+ :type '(choice (const nil) integer))
(defconst sgml-namespace-re "[_[:alpha:]][-_.[:alnum:]]*")
(defconst sgml-name-re "[_:[:alpha:]][-_.:[:alnum:]]*")
@@ -300,8 +293,7 @@ Any terminating `>' or `/' is not matched.")
(defface sgml-namespace
'((t (:inherit font-lock-builtin-face)))
- "`sgml-mode' face used to highlight the namespace part of identifiers."
- :group 'sgml)
+ "`sgml-mode' face used to highlight the namespace part of identifiers.")
(defvar sgml-namespace-face 'sgml-namespace)
;; internal
@@ -337,6 +329,30 @@ Any terminating `>' or `/' is not matched.")
(defvar sgml-font-lock-keywords sgml-font-lock-keywords-1
"Rules for highlighting SGML code. See also `sgml-tag-face-alist'.")
+(defun sgml-font-lock-syntactic-face (state)
+ "`font-lock-syntactic-face-function' for `sgml-mode'."
+ ;; Don't use string face outside of tags.
+ (cond ((and (nth 9 state) (nth 3 state)) font-lock-string-face)
+ ((nth 4 state) font-lock-comment-face)))
+
+(defvar-local sgml--syntax-propertize-ppss nil)
+
+(defun sgml--syntax-propertize-ppss (pos)
+ "Return PPSS at POS, fixing the syntax of any lone `>' along the way."
+ (cl-assert (>= pos (car sgml--syntax-propertize-ppss)))
+ (let ((ppss (parse-partial-sexp (car sgml--syntax-propertize-ppss) pos -1
+ nil (cdr sgml--syntax-propertize-ppss))))
+ (while (eq -1 (car ppss))
+ (put-text-property (1- (point)) (point)
+ 'syntax-table (string-to-syntax "."))
+ ;; Hack attack: rather than recompute the ppss from
+ ;; (car sgml--syntax-propertize-ppss), we manually "fix it".
+ (setcar ppss 0)
+ (setq ppss (parse-partial-sexp (point) pos -1 nil ppss)))
+ (setcdr sgml--syntax-propertize-ppss ppss)
+ (setcar sgml--syntax-propertize-ppss pos)
+ ppss))
+
(eval-and-compile
(defconst sgml-syntax-propertize-rules
(syntax-propertize-precompile-rules
@@ -347,20 +363,50 @@ Any terminating `>' or `/' is not matched.")
("--[ \t\n]*\\(>\\)" (1 "> b"))
("\\(<\\)[?!]" (1 (prog1 "|>"
(sgml-syntax-propertize-inside end))))
- ;; Quotes outside of tags should not introduce strings.
- ;; Be careful to call `syntax-ppss' on a position before the one we're
- ;; going to change, so as not to need to flush the data we just computed.
- ("[\"']" (0 (if (prog1 (zerop (car (syntax-ppss (match-beginning 0))))
- (goto-char (match-end 0)))
- (string-to-syntax ".")))))))
+ ;; Quotes outside of tags should not introduce strings which end up
+ ;; hiding tags. We used to test every quote and mark it as "."
+ ;; if it's outside of tags, but there are too many quotes and
+ ;; the resulting number of calls to syntax-ppss made it too slow
+ ;; (bug#33887), so we're now careful to leave alone any pair
+ ;; of quotes that doesn't hold a < or > char, which is the vast majority:
+ ;; either they're both within a tag (or a comment), in which case it's
+ ;; indeed correct to leave them as is, or they're both outside of tags, in
+ ;; which case they arguably should have punctuation syntax, but it is
+ ;; harmless to let them have string syntax because they won't "hide" any
+ ;; tag or comment from us (and we use the
+ ;; font-lock-syntactic-face-function to make sure those spurious "strings
+ ;; within text" aren't highlighted as strings).
+ ("\\([\"']\\)[^\"'<>]*"
+ (1 (if (eq (char-after) (char-after (match-beginning 0)))
+ ;; Fast-track case.
+ (forward-char 1)
+ ;; Point has moved to the end of the text we matched after the
+ ;; quote, but this risks overlooking a match to one of the other
+ ;; regexp in the rules. We could just (goto-char (match-end 1))
+ ;; to solve this, but that would be too easy, so instead we
+ ;; only move back enough to avoid skipping comment ender, which
+ ;; happens to be the only one that we could have overlooked.
+ (when (eq (char-after) ?>)
+ (skip-chars-backward "-"))
+ ;; Be careful to call `syntax-ppss' on a position before the one
+ ;; we're going to change, so as not to need to flush the data we
+ ;; just computed.
+ (if (zerop (save-excursion
+ (car (sgml--syntax-propertize-ppss
+ (match-beginning 0)))))
+ (string-to-syntax ".")))))
+ )))
(defun sgml-syntax-propertize (start end)
"Syntactic keywords for `sgml-mode'."
- (goto-char start)
+ (setq sgml--syntax-propertize-ppss (cons start (syntax-ppss start)))
+ (cl-assert (>= (cadr sgml--syntax-propertize-ppss) 0))
(sgml-syntax-propertize-inside end)
(funcall
(syntax-propertize-rules sgml-syntax-propertize-rules)
- start end))
+ start end)
+ ;; Catch any '>' after the last quote.
+ (sgml--syntax-propertize-ppss end))
(defun sgml-syntax-propertize-inside (end)
(let ((ppss (syntax-ppss)))
@@ -416,8 +462,7 @@ The attribute alist is made up as
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)))
- :group 'sgml)
+ (repeat :tag "Tag Rule" sexp))))
(put 'sgml-tag-alist 'risky-local-variable t)
(defcustom sgml-tag-help
@@ -429,8 +474,7 @@ an optional alist of possible values."
("!entity" . "Entity (macro) declaration"))
"Alist of tag name and short description."
:type '(repeat (cons (string :tag "Tag Name")
- (string :tag "Description")))
- :group 'sgml)
+ (string :tag "Description"))))
(defvar sgml-empty-tags nil
"List of tags whose !ELEMENT definition says EMPTY.")
@@ -456,7 +500,7 @@ an optional alist of possible values."
nil t)
(string-match "X\\(HT\\)?ML" (match-string 3))))))
-(defvar v2) ; free for skeleton
+(with-no-warnings (defvar v2)) ; free for skeleton
(defun sgml-comment-indent-new-line (&optional soft)
(let ((comment-start "-- ")
@@ -548,7 +592,7 @@ Do \\[describe-key] on the following bindings to discover what they do.
;; This is desirable because SGML discards a newline that appears
;; immediately after a start tag or immediately before an end tag.
(setq-local paragraph-start (concat "[ \t]*$\\|\
-[ \t]*</?\\(" sgml-name-re sgml-attrs-re "\\)?>"))
+\[ \t]*</?\\(" sgml-name-re sgml-attrs-re "\\)?>"))
(setq-local paragraph-separate (concat paragraph-start "$"))
(setq-local adaptive-fill-regexp "[ \t]*")
(add-hook 'fill-nobreak-predicate 'sgml-fill-nobreak nil t)
@@ -566,7 +610,9 @@ Do \\[describe-key] on the following bindings to discover what they do.
(setq font-lock-defaults '((sgml-font-lock-keywords
sgml-font-lock-keywords-1
sgml-font-lock-keywords-2)
- nil t))
+ nil t nil
+ (font-lock-syntactic-face-function
+ . sgml-font-lock-syntactic-face)))
(setq-local syntax-propertize-function #'sgml-syntax-propertize)
(setq-local facemenu-add-face-function 'sgml-mode-facemenu-add-face-function)
(setq-local sgml-xml-mode (sgml-xml-guess))
@@ -614,7 +660,7 @@ Behaves electrically if `sgml-quick-keys' is non-nil."
(delete-char -1)
(sgml-close-tag))
(t
- (sgml-slash-matching arg))))
+ (insert-char ?/ arg))))
(defun sgml-slash-matching (arg)
"Insert `/' and display any previous matching `/'.
@@ -770,8 +816,16 @@ If QUIET, do not print a message when there are no attributes for TAG."
(symbolp (car (car alist))))
(setq car (car alist)
alist (cdr alist)))
- (or quiet
- (message "No attributes configured."))
+ (unless (or alist quiet)
+ (message "No attributes configured."))
+ (when alist
+ ;; Add class and id attributes if a) the element has any
+ ;; other attributes configured, and b) they're not already
+ ;; present.
+ (unless (assoc-string "class" alist)
+ (setq alist (cons '("class") alist)))
+ (unless (assoc-string "id" alist)
+ (setq alist (cons '("id") alist))))
(if (stringp (car alist))
(progn
(insert (if (eq (preceding-char) ?\s) "" ?\s)
@@ -891,7 +945,7 @@ Return non-nil if we skipped over matched tags."
(condition-case err
(save-excursion
(goto-char end)
- (skip-chars-backward "[:alnum:]-_.:")
+ (skip-chars-backward "-[:alnum:]_.:")
(if (and ;; (<= (point) beg) ; This poses problems for downcase-word.
(or (eq (char-before) ?<)
(and (eq (char-before) ?/)
@@ -899,7 +953,7 @@ Return non-nil if we skipped over matched tags."
(null (get-char-property (point) 'text-clones)))
(let* ((endp (eq (char-before) ?/))
(cl-start (point))
- (cl-end (progn (skip-chars-forward "[:alnum:]-_.:") (point)))
+ (cl-end (progn (skip-chars-forward "-[:alnum:]_.:") (point)))
(match
(if endp
(when (sgml-skip-tag-backward 1) (forward-char 1) t)
@@ -916,7 +970,8 @@ Return non-nil if we skipped over matched tags."
(equal (buffer-substring cl-start cl-end)
(buffer-substring (point)
(save-excursion
- (skip-chars-forward "[:alnum:]-_.:")
+ (skip-chars-forward
+ "-[:alnum:]_.:")
(point))))
(or (not endp) (eq (char-after cl-end) ?>)))
(when clones
@@ -936,9 +991,6 @@ Return non-nil if we skipped over matched tags."
(define-minor-mode sgml-electric-tag-pair-mode
"Toggle SGML Electric Tag Pair mode.
-With a prefix argument ARG, enable the mode if ARG is positive,
-and disable it otherwise. If called from Lisp, enable the mode
-if ARG is omitted or nil.
SGML Electric Tag Pair mode is a buffer-local minor mode for use
with `sgml-mode' and related major modes. When enabled, editing
@@ -1237,8 +1289,11 @@ See `sgml-tag-alist' for info about attribute rules."
(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."
+Only &, <, >, ' and \" characters are quoted, the rest is left
+untouched. This is sufficient to use quoted text as SGML argument.
+
+With prefix argument UNQUOTEP, unquote the region. All numeric entities,
+\"amp\", \"lt\", \"gt\" and \"quot\" named entities are unquoted."
(interactive "r\nP")
(save-restriction
(narrow-to-region start end)
@@ -1246,14 +1301,23 @@ With prefix argument UNQUOTEP, unquote the region."
(if unquotep
;; FIXME: We should unquote other named character references as well.
(while (re-search-forward
- "\\(&\\(amp\\|\\(l\\|\\(g\\)\\)t\\)\\)[][<>&;\n\t \"%!'(),/=?]"
+ "\\(&\\(amp\\|quot\\|lt\\|gt\\|#\\([0-9]+\\|[xX][0-9a-fA-F]+\\)\\)\\)\\([][<>&;\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
+ (string
+ (or (cdr (assq (char-after (match-beginning 2))
+ '((?a . ?&) (?q . ?\") (?l . ?<) (?g . ?>))))
+ (let ((num (match-string 3)))
+ (if (or (eq ?x (aref num 0)) (eq ?X (aref num 0)))
+ (string-to-number (substring num 1) 16)
+ (string-to-number num 10)))))
+ t t nil (if (eq (char-before (match-end 0)) ?\;) 0 1)))
+ (while (re-search-forward "[&<>\"']" nil t)
(replace-match (cdr (assq (char-before) '((?& . "&amp;")
(?< . "&lt;")
- (?> . "&gt;"))))
+ (?> . "&gt;")
+ (?\" . "&#34;")
+ (?' . "&#39;"))))
t t)))))
(defun sgml-pretty-print (beg end)
@@ -1510,12 +1574,12 @@ Depending on context, inserts a matching close-tag, or closes
the current start-tag or the current comment or the current cdata, ..."
(interactive)
(pcase (car (sgml-lexical-context))
- (`comment (insert " -->"))
- (`cdata (insert "]]>"))
- (`pi (insert " ?>"))
- (`jsp (insert " %>"))
- (`tag (insert " />"))
- (`text
+ ('comment (insert " -->"))
+ ('cdata (insert "]]>"))
+ ('pi (insert " ?>"))
+ ('jsp (insert " %>"))
+ ('tag (insert " />"))
+ ('text
(let ((context (save-excursion (sgml-get-context))))
(if context
(progn
@@ -1548,7 +1612,7 @@ LCON is the lexical context, if any."
(pcase (car lcon)
- (`string
+ ('string
;; Go back to previous non-empty line.
(while (and (> (point) (cdr lcon))
(zerop (forward-line -1))
@@ -1559,7 +1623,7 @@ LCON is the lexical context, if any."
(goto-char (cdr lcon))
(1+ (current-column))))
- (`comment
+ ('comment
(let ((mark (looking-at "--")))
;; Go back to previous non-empty line.
(while (and (> (point) (cdr lcon))
@@ -1578,11 +1642,11 @@ LCON is the lexical context, if any."
(current-column)))
;; We don't know how to indent it. Let's be honest about it.
- (`cdata nil)
+ ('cdata nil)
;; We don't know how to indent it. Let's be honest about it.
- (`pi nil)
+ ('pi nil)
- (`tag
+ ('tag
(goto-char (+ (cdr lcon) sgml-attribute-offset))
(skip-chars-forward "^ \t\n") ;Skip tag name.
(skip-chars-forward " \t")
@@ -1592,7 +1656,7 @@ LCON is the lexical context, if any."
(goto-char (+ (cdr lcon) sgml-attribute-offset))
(+ (current-column) sgml-basic-offset)))
- (`text
+ ('text
(while (looking-at "</")
(sgml-forward-sexp 1)
(skip-chars-forward " \t"))
@@ -1708,7 +1772,6 @@ Currently just returns (EMPTY-TAGS UNCLOSED-TAGS)."
(defcustom html-mode-hook nil
"Hook run by command `html-mode'.
`text-mode-hook' and `sgml-mode-hook' are run first."
- :group 'sgml
:type 'hook
:options '(html-autoview-mode))
@@ -1740,6 +1803,7 @@ This takes effect when first loading the library.")
(define-key map "\C-c\C-ci" 'html-image)
(when html-quick-keys
(define-key map "\C-c-" 'html-horizontal-rule)
+ (define-key map "\C-cd" 'html-div)
(define-key map "\C-co" 'html-ordered-list)
(define-key map "\C-cu" 'html-unordered-list)
(define-key map "\C-cr" 'html-radio-buttons)
@@ -1747,7 +1811,8 @@ This takes effect when first loading the library.")
(define-key map "\C-cl" 'html-list-item)
(define-key map "\C-ch" 'html-href-anchor)
(define-key map "\C-cn" 'html-name-anchor)
- (define-key map "\C-ci" 'html-image))
+ (define-key map "\C-ci" 'html-image)
+ (define-key map "\C-cs" 'html-span))
(define-key map "\C-c\C-s" 'html-autoview-mode)
(define-key map "\C-c\C-v" 'browse-url-of-buffer)
(define-key map [menu-bar html] (cons "HTML" menu-map))
@@ -1948,7 +2013,7 @@ This takes effect when first loading the library.")
("dd" ,(not sgml-xml-mode))
("del" nil ("cite") ("datetime"))
("dfn")
- ("div")
+ ("div" \n ("id") ("class"))
("dl" (nil \n
( "Term: "
"<dt>" str (if sgml-xml-mode "</dt>")
@@ -2228,6 +2293,9 @@ buffer's tick counter (as produced by `buffer-modified-tick'),
and the CDR is the list of class names found in the buffer.")
(make-variable-buffer-local 'html--buffer-ids-cache)
+(declare-function libxml-parse-html-region "xml.c"
+ (start end &optional base-url discard-comments))
+
(defun html-current-buffer-classes ()
"Return a list of class names used in the current buffer.
The result is cached in `html--buffer-classes-cache'."
@@ -2359,18 +2427,14 @@ The third `match-string' will be the used in the menu.")
(define-minor-mode html-autoview-mode
"Toggle viewing of HTML files on save (HTML Autoview mode).
-With a prefix argument ARG, enable HTML Autoview mode if ARG is
-positive, and disable it otherwise. If called from Lisp, enable
-the mode if ARG is omitted or nil.
HTML Autoview mode is a buffer-local minor mode for use with
`html-mode'. If enabled, saving the file automatically runs
`browse-url-of-buffer' to view it."
nil nil nil
- :group 'sgml
(if html-autoview-mode
- (add-hook 'after-save-hook 'browse-url-of-buffer nil t)
- (remove-hook 'after-save-hook 'browse-url-of-buffer t)))
+ (add-hook 'after-save-hook #'browse-url-of-buffer nil t)
+ (remove-hook 'after-save-hook #'browse-url-of-buffer t)))
(define-skeleton html-href-anchor
@@ -2435,16 +2499,16 @@ HTML Autoview mode is a buffer-local minor mode for use with
(define-skeleton html-ordered-list
"HTML ordered list tags."
nil
- "<ol>" \n
+ \n "<ol>" \n
"<li>" _ (if sgml-xml-mode "</li>") \n
- "</ol>")
+ "</ol>" > \n)
(define-skeleton html-unordered-list
"HTML unordered list tags."
nil
- "<ul>" \n
+ \n "<ul>" \n
"<li>" _ (if sgml-xml-mode "</li>") \n
- "</ul>")
+ "</ul>" > \n)
(define-skeleton html-list-item
"HTML list item tag."
@@ -2455,8 +2519,17 @@ HTML Autoview mode is a buffer-local minor mode for use with
(define-skeleton html-paragraph
"HTML paragraph tag."
nil
- (if (bolp) nil ?\n)
- "<p>" _ (if sgml-xml-mode "</p>"))
+ \n "<p>" _ (if sgml-xml-mode "</p>"))
+
+(define-skeleton html-div
+ "HTML div tag."
+ nil
+ "<div>" > \n _ \n "</div>" >)
+
+(define-skeleton html-span
+ "HTML span tag."
+ nil
+ "<span>" > _ "</span>")
(define-skeleton html-checkboxes
"Group of connected checkbox inputs."