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.el268
1 files changed, 187 insertions, 81 deletions
diff --git a/lisp/textmodes/sgml-mode.el b/lisp/textmodes/sgml-mode.el
index 33dfa277330..55a1e6d26db 100644
--- a/lisp/textmodes/sgml-mode.el
+++ b/lisp/textmodes/sgml-mode.el
@@ -1,10 +1,10 @@
-;;; sgml-mode.el --- SGML- and HTML-editing modes -*- coding: utf-8 -*-
+;;; sgml-mode.el --- SGML- and HTML-editing modes -*- lexical-binding:t -*-
-;; Copyright (C) 1992, 1995-1996, 1998, 2001-2013 Free Software
+;; Copyright (C) 1992, 1995-1996, 1998, 2001-2015 Free Software
;; Foundation, Inc.
;; Author: James Clark <jjc@jclark.com>
-;; Maintainer: FSF
+;; Maintainer: emacs-devel@gnu.org
;; Adapted-By: ESR, Daniel Pfeiffer <occitan@esperanto.org>,
;; F.Potorti@cnuce.cnr.it
;; Keywords: wp, hypermedia, comm, languages
@@ -46,6 +46,25 @@
:type 'integer
:group 'sgml)
+(defcustom sgml-attribute-offset 0
+ "Specifies a delta for attribute indentation in `sgml-indent-line'.
+
+When 0, attribute indentation looks like this:
+
+ <element
+ attribute=\"value\">
+ </element>
+
+When 2, attribute indentation looks like this:
+
+ <element
+ attribute=\"value\">
+ </element>"
+ :version "25.1"
+ :type 'integer
+ :safe 'integerp
+ :group 'sgml)
+
(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
@@ -87,10 +106,10 @@ This list is used when first loading the `sgml-mode' library.
The supported characters and potential disadvantages are:
?\\\" Makes \" in text start a string.
- ?' Makes ' in text start a string.
+ ?\\=' Makes \\=' in text start a string.
?- Makes -- in text start a comment.
-When only one of ?\\\" or ?' are included, \"'\" or '\"', as can be found in
+When only one of ?\\\" or ?\\=' are included, \"\\='\" or \\='\"\\=', as can be found in
DTDs, start a string. To partially avoid this problem this also makes these
self insert as named entities depending on `sgml-quick-keys'.
@@ -240,12 +259,21 @@ This takes effect when first loading the `sgml-mode' library.")
"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.
-;; Its error messages can be parsed by next-error.
-;; The -s option suppresses output.
-
-(defcustom sgml-validate-command "nsgmls -s" ; replaced old `sgmls'
+(defcustom sgml-validate-command
+ ;; prefer tidy because (o)nsgmls is often built without --enable-http
+ ;; which makes it next to useless
+ (cond ((executable-find "tidy")
+ ;; tidy is available from http://tidy.sourceforge.net/
+ "tidy --gnu-emacs yes -utf8 -e -q")
+ ((executable-find "nsgmls")
+ ;; nsgmls is a free SGML parser in the SP suite available from
+ ;; ftp.jclark.com, replaced old `sgmls'.
+ "nsgmls -s")
+ ((executable-find "onsgmls")
+ ;; onsgmls is the community version of `nsgmls'
+ ;; hosted on http://openjade.sourceforge.net/
+ "onsgmls -s")
+ (t "Install (o)nsgmls, tidy, or some other SGML validator, and set `sgml-validate-command'"))
"The command to validate an SGML document.
The file name of current buffer file name will be appended to this,
separated by a space."
@@ -414,7 +442,7 @@ an optional alist of possible values."
(comment-style 'plain))
(comment-indent-new-line soft)))
-(defun sgml-mode-facemenu-add-face-function (face end)
+(defun sgml-mode-facemenu-add-face-function (face _end)
(let ((tag-face (cdr (assq face sgml-face-tag-alist))))
(cond (tag-face
(setq tag-face (funcall skeleton-transformation-function tag-face))
@@ -447,18 +475,21 @@ This function is designed for use in `fill-nobreak-predicate'.
(skip-chars-backward "/?!")
(eq (char-before) ?<))))
+(defvar tildify-space-string)
+(defvar tildify-foreach-region-function)
+
;;;###autoload
(define-derived-mode sgml-mode text-mode '(sgml-xml-mode "XML" "SGML")
"Major mode for editing SGML documents.
Makes > match <.
-Keys <, &, SPC within <>, \", / and ' can be electric depending on
+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
the next N words. In Transient Mark mode, when the mark is active,
N defaults to -1, which means to wrap it around the current region.
-If you like upcased tags, put (setq sgml-transformation-function 'upcase)
+If you like upcased tags, put (setq sgml-transformation-function \\='upcase)
in your init file.
Use \\[sgml-validate] to validate your document with an SGML parser.
@@ -468,12 +499,33 @@ Do \\[describe-key] on the following bindings to discover what they do.
\\{sgml-mode-map}"
(make-local-variable 'sgml-saved-validate-command)
(make-local-variable 'facemenu-end-add-face)
+ ;; If encoding does not allow non-break space character, use reference.
+ ;; FIXME: Perhaps use &nbsp; if possible (e.g. when we know its HTML)?
+ (setq-local tildify-space-string
+ (if (equal (decode-coding-string
+ (encode-coding-string " " buffer-file-coding-system)
+ buffer-file-coding-system) " ")
+ " " "&#160;"))
+ ;; FIXME: Use the fact that we're parsing the document already
+ ;; rather than using regex-based filtering.
+ (setq-local tildify-foreach-region-function
+ (apply-partially
+ 'tildify-foreach-ignore-environments
+ `((,(eval-when-compile
+ (concat
+ "<\\("
+ (regexp-opt '("pre" "dfn" "code" "samp" "kbd" "var"
+ "PRE" "DFN" "CODE" "SAMP" "KBD" "VAR"))
+ "\\)\\>[^>]*>"))
+ . ("</" 1 ">"))
+ ("<! *--" . "-- *>")
+ ("<" . ">"))))
;;(make-local-variable 'facemenu-remove-face-function)
;; 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.
(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)
@@ -707,9 +759,10 @@ If QUIET, do not print a message when there are no attributes for TAG."
(insert ?\s)
(insert (funcall skeleton-transformation-function
(setq attribute
- (skeleton-read '(completing-read
- "Attribute: "
- alist)))))
+ (skeleton-read (lambda ()
+ (completing-read
+ "Attribute: "
+ alist))))))
(if (string= "" attribute)
(setq i 0)
(sgml-value (assoc (downcase attribute) alist))
@@ -792,7 +845,7 @@ Return non-nil if we skipped over matched tags."
(defvar sgml-electric-tag-pair-overlays nil)
(defvar sgml-electric-tag-pair-timer nil)
-(defun sgml-electric-tag-pair-before-change-function (beg end)
+(defun sgml-electric-tag-pair-before-change-function (_beg end)
(condition-case err
(save-excursion
(goto-char end)
@@ -960,7 +1013,7 @@ With prefix argument ARG, repeat this ARG times."
(or (get 'sgml-tag 'invisible)
(setplist 'sgml-tag
(append '(invisible t
- point-entered sgml-point-entered
+ cursor-sensor-functions (sgml-cursor-sensor)
rear-nonsticky t
read-only t)
(symbol-plist 'sgml-tag))))
@@ -968,63 +1021,59 @@ With prefix argument ARG, repeat this ARG times."
(defun sgml-tags-invisible (arg)
"Toggle visibility of existing tags."
(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)
+ (let ((inhibit-read-only t)
string)
- (unwind-protect
- (save-excursion
- (goto-char (point-min))
- (if (setq-local 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))
+ (with-silent-modifications
+ (save-excursion
+ (goto-char (point-min))
+ (if (setq-local 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)))))
+ (cursor-sensor-mode (if sgml-tags-invisible 1 -1))
(run-hooks 'sgml-tags-invisible-hook)
(message "")))
-(defun sgml-point-entered (x y)
- ;; Show preceding or following hidden tag, depending of cursor direction.
- (let ((inhibit-point-motion-hooks t))
- (save-excursion
- (condition-case nil
- (message "Invisible tag: %s"
- ;; Strip properties, otherwise, the text is invisible.
- (buffer-substring-no-properties
- (point)
- (if (or (and (> x y)
- (not (eq (following-char) ?<)))
- (and (< x y)
- (eq (preceding-char) ?>)))
- (backward-list)
- (forward-list))))
- (error nil)))))
-
+(defun sgml-cursor-sensor (window x dir)
+ ;; Show preceding or following hidden tag, depending of cursor direction (and
+ ;; `dir' is not the direction in this sense).
+ (when (eq dir 'entered)
+ (ignore-errors
+ (let* ((y (window-point window))
+ (otherend
+ (save-excursion
+ (goto-char y)
+ (cond
+ ((and (eq (char-before) ?>)
+ (or (not (eq (char-after) ?<))
+ (> x y)))
+ (backward-sexp))
+ ((eq (char-after y) ?<)
+ (forward-sexp)))
+ (point))))
+ (message "Invisible tag: %s"
+ ;; Strip properties, otherwise, the text is invisible.
+ (buffer-substring-no-properties
+ y otherend))))))
(defun sgml-validate (command)
@@ -1106,7 +1155,7 @@ If nil, start from a preceding tag at indentation."
((and state (> (nth 0 state) 0)) (cons 'tag (nth 1 state)))
(t (cons 'text text-start))))))
-(defun sgml-beginning-of-tag (&optional top-level)
+(defun sgml-beginning-of-tag (&optional only-immediate)
"Skip to beginning of tag and return its name.
If this can't be done, return nil."
(let ((context (sgml-lexical-context)))
@@ -1115,7 +1164,7 @@ If this can't be done, return nil."
(goto-char (cdr context))
(when (looking-at sgml-tag-name-re)
(match-string-no-properties 1)))
- (if top-level nil
+ (if only-immediate nil
(when (not (eq (car context) 'text))
(goto-char (cdr context))
(sgml-beginning-of-tag t))))))
@@ -1129,13 +1178,16 @@ See `sgml-tag-alist' for info about attribute rules."
(if (and (eq (car alist) t) (not sgml-xml-mode))
(when (cdr alist)
(insert "=\"")
- (setq alist (skeleton-read '(completing-read "Value: " (cdr alist))))
+ (setq alist (skeleton-read (lambda ()
+ (completing-read
+ "Value: " (cdr alist)))))
(if (string< "" alist)
(insert alist ?\")
(delete-char -2)))
(insert "=\"")
(if (cdr alist)
- (insert (skeleton-read '(completing-read "Value: " alist)))
+ (insert (skeleton-read (lambda ()
+ (completing-read "Value: " alist))))
(when (null alist)
(insert (skeleton-read '(read-string "Value: ")))))
(insert ?\"))))
@@ -1477,13 +1529,13 @@ LCON is the lexical context, if any."
(`pi nil)
(`tag
- (goto-char (1+ (cdr lcon)))
+ (goto-char (+ (cdr lcon) sgml-attribute-offset))
(skip-chars-forward "^ \t\n") ;Skip tag name.
(skip-chars-forward " \t")
(if (not (eolp))
(current-column)
;; This is the first attribute: indent.
- (goto-char (1+ (cdr lcon)))
+ (goto-char (+ (cdr lcon) sgml-attribute-offset))
(+ (current-column) sgml-basic-offset)))
(`text
@@ -1529,6 +1581,19 @@ LCON is the lexical context, if any."
(skip-chars-forward " \t\n")
(< (point) here) (sgml-at-indentation-p))
(current-column))
+ ;; ;; If the parsing failed, try to recover.
+ ;; ((and (null context) (bobp)
+ ;; (not (eq (char-after here) ?<)))
+ ;; (goto-char here)
+ ;; (if (and (looking-at "--[ \t\n]*>")
+ ;; (re-search-backward "<!--" nil t))
+ ;; ;; No wonder parsing failed: we're in a comment.
+ ;; (sgml-calculate-indent (prog2 (goto-char (match-end 0))
+ ;; (sgml-lexical-context)
+ ;; (goto-char here)))
+ ;; ;; We have no clue what's going on, let's be honest about it.
+ ;; nil))
+ ;; Otherwise, just follow the rules.
(t
(goto-char there)
(+ (current-column)
@@ -1767,6 +1832,8 @@ This takes effect when first loading the library.")
("array" (nil \n
("Item: " "<item>" str (if sgml-xml-mode "</item>") \n))
"align")
+ ("article" \n)
+ ("aside" \n)
("au")
("b")
("big")
@@ -1792,7 +1859,10 @@ This takes effect when first loading the library.")
"<dd>" (if sgml-xml-mode "</dd>") \n))
("em")
("fn" "id" "fn") ;; Footnotes were deprecated in HTML 3.2
+ ("footer" \n)
("head" \n)
+ ("header" \n)
+ ("hgroup" \n)
("html" (\n
"<head>\n"
"<title>" (setq str (read-input "Title: ")) "</title>\n"
@@ -1810,6 +1880,7 @@ This takes effect when first loading the library.")
("lang")
("li" ,(not sgml-xml-mode))
("math" \n)
+ ("nav" \n)
("nobr")
("option" t ("value") ("label") ("selected" t))
("over" t)
@@ -1819,6 +1890,7 @@ This takes effect when first loading the library.")
("rev")
("s")
("samp")
+ ("section" \n)
("small")
("span" nil
("class"
@@ -1849,6 +1921,8 @@ This takes effect when first loading the library.")
("acronym" . "Acronym")
("address" . "Formatted mail address")
("array" . "Math array")
+ ("article" . "An independent part of document or site")
+ ("aside" . "Secondary content related to surrounding content (e.g. page or article)")
("au" . "Author")
("b" . "Bold face")
("base" . "Base address for URLs")
@@ -1878,6 +1952,7 @@ This takes effect when first loading the library.")
("figt" . "Figure text")
("fn" . "Footnote") ;; No one supports special footnote rendering.
("font" . "Font size")
+ ("footer" . "Footer of a section")
("form" . "Form with input fields")
("group" . "Document grouping")
("h1" . "Most important section headline")
@@ -1887,6 +1962,8 @@ This takes effect when first loading the library.")
("h5" . "Unimportant section headline")
("h6" . "Least important section headline")
("head" . "Document header")
+ ("header" . "Header of a section")
+ ("hgroup" . "Group of headings - h1-h6 elements")
("hr" . "Horizontal rule")
("html" . "HTML Document")
("i" . "Italic face")
@@ -1899,8 +1976,9 @@ This takes effect when first loading the library.")
("li" . "List item")
("link" . "Link relationship")
("math" . "Math formula")
- ("menu" . "Menu list (obsolete)")
+ ("menu" . "List of commands")
("mh" . "Form mail header")
+ ("nav" . "Group of navigational links")
("nextid" . "Allocate new id")
("nobr" . "Text without line break")
("ol" . "Ordered list")
@@ -1914,6 +1992,7 @@ This takes effect when first loading the library.")
("rev" . "Reverse video")
("s" . "Strikeout")
("samp" . "Sample text")
+ ("section" . "Section of a document")
("select" . "Selection list")
("small" . "Font size")
("sp" . "Nobreak space")
@@ -1987,7 +2066,7 @@ Images in many formats can be inlined with <img src=\"URL\">.
If you mainly create your own documents, `sgml-specials' might be
interesting. But note that some HTML 2 browsers can't handle `&apos;'.
To work around that, do:
- (eval-after-load \"sgml-mode\" '(aset sgml-char-names ?' nil))
+ (eval-after-load \"sgml-mode\" \\='(aset sgml-char-names ?\\=' nil))
\\{html-mode-map}"
(setq-local sgml-display-text html-display-text)
@@ -2181,6 +2260,33 @@ HTML Autoview mode is a buffer-local minor mode for use with
"")))
\n))
+(define-skeleton html-navigational-links
+ "Group of navigational links."
+ nil
+ "<nav>" \n
+ "<ul>" \n
+ "<li><a href=\"" (skeleton-read "URL: " "#") "\">"
+ (skeleton-read "Title: ") "</a>"
+ (if sgml-xml-mode (if sgml-xml-mode "</li>")) \n
+ "</ul>" \n
+ "</nav>")
+
+(define-skeleton html-html5-template
+ "Initial HTML5 template"
+ nil
+ "<!DOCTYPE html>" \n
+ "<html lang=\"en\">" \n
+ "<head>" \n
+ "<meta charset=\"utf-8\">" \n
+ "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">" \n
+ "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">" \n
+ "<title>" (skeleton-read "Page Title: ") "</title>" \n
+ "</head>" \n
+ "<body>" \n
+ "<div id=\"app\"></div>" \n
+ "</body>" \n
+ "</html>")
+
(provide 'sgml-mode)
;;; sgml-mode.el ends here