Mercurial > emacs
changeset 44330:96c839b7b4c2
(sgml-at-indentation-p, sgml-tag)
(sgml-parse-tag-name, sgml-looking-back-at, sgml-parse-tag-backward)
(sgml-inside-tag-p, sgml-get-context, sgml-show-context)
(sgml-insert-end-tag): New funs taken from xml-lite.el.
(sgml-calculate-indent): Use them.
(sgml-slash-matching): Rename from sgml-slash.
(sgml-slash): Copied from xml-lite and changed to use
sgml-slash-matching and sgml-quick-keys.
author | Stefan Monnier <monnier@iro.umontreal.ca> |
---|---|
date | Mon, 01 Apr 2002 23:32:15 +0000 |
parents | f532fa5aeca1 |
children | 5a8965629f02 |
files | lisp/textmodes/sgml-mode.el |
diffstat | 1 files changed, 206 insertions(+), 6 deletions(-) [+] |
line wrap: on
line diff
--- a/lisp/textmodes/sgml-mode.el Mon Apr 01 23:10:26 2002 +0000 +++ b/lisp/textmodes/sgml-mode.el Mon Apr 01 23:32:15 2002 +0000 @@ -80,7 +80,7 @@ with comments, so we normally turn it off.") (defvar sgml-quick-keys nil - "Use <, >, &, SPC and `sgml-specials' keys \"electrically\" when non-nil. + "Use <, >, &, /, SPC and `sgml-specials' keys \"electrically\" when non-nil. This takes effect when first loading the `sgml-mode' library.") @@ -384,7 +384,7 @@ (define-derived-mode sgml-mode text-mode "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 @@ -450,6 +450,22 @@ (defun sgml-slash (arg) + "Insert ARG slash characters. +Behaves electrically if `sgml-quick-keys' is non-nil." + (interactive "p") + (cond + ((not (and (eq (char-before) ?<) (= arg 1))) + (sgml-slash-matching arg)) + ((eq sgml-quick-keys 'indent) + (insert-char ?/ 1) + (indent-according-to-mode)) + ((eq sgml-quick-keys 'close) + (delete-backward-char 1) + (sgml-insert-end-tag)) + (t + (sgml-slash-matching arg)))) + +(defun sgml-slash-matching (arg) "Insert `/' and display any previous matching `/'. Two `/'s are treated as matching if the first `/' ends a net-enabling start tag, and the second `/' is the corresponding null end tag." @@ -925,6 +941,190 @@ (?> . ">")))))))) +(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))) + + +;; 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 (s) + (let ((limit (max (- (point) (length s)) (point-min)))) + (equal s (buffer-substring-no-properties limit (point))))) + +(defun sgml-parse-tag-backward () + "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) + (search-backward ">") + (setq tag-end (1+ (point))) + (cond + ((sgml-looking-back-at "--") ; comment + (setq tag-type 'comment + tag-start (search-backward "<!--" nil t))) + ((sgml-looking-back-at "]]") ; cdata + (setq tag-type 'cdata + tag-start (search-backward "<![CDATA[" nil t))) + (t + (setq tag-start + (with-syntax-table sgml-tag-syntax-table + (goto-char tag-end) + (backward-sexp) + (point))) + (goto-char (1+ tag-start)) + (case (char-after) + (?! ; declaration + (setq tag-type 'decl)) + (?? ; processing-instruction + (setq tag-type 'pi)) + (?/ ; close-tag + (forward-char 1) + (setq tag-type 'close + name (sgml-parse-tag-name))) + ((?% ?#) ; JSP tags etc + (setq tag-type 'unknown)) + (t ; open or empty tag + (setq tag-type 'open + name (sgml-parse-tag-name)) + (if (or (eq ?/ (char-before (- tag-end 1))) + (sgml-empty-tag-p name)) + (setq tag-type 'empty)))))) + (goto-char tag-start) + (sgml-make-tag tag-type tag-start tag-end name))) + +(defsubst sgml-inside-tag-p (tag-info &optional point) + "Return true if TAG-INFO contains the POINT." + (let ((end (sgml-tag-end tag-info)) + (point (or point (point)))) + (or (null end) + (> end point)))) + +(defun sgml-get-context (&optional full) + "Determine the context of the current position. +If FULL is `empty', return even if the context is empty (i.e. +we just skipped over some element and got to a beginning of line). +If FULL is non-nil, parse back to the beginning of the buffer, otherwise +parse until we find a start-tag as the first thing on a line. + +The context is a list of tag-info structures. The last one is the tag +immediately enclosing the current position." + (let ((here (point)) + (ignore nil) + (context nil) + tag-info) + ;; CONTEXT keeps track of the tag-stack + ;; IGNORE keeps track of the nesting level of point relative to the + ;; first (outermost) tag on the context. This is the list of + ;; enclosing start-tags we'll have to ignore. + (skip-chars-backward " \t\n") ; Make sure we're not at indentation. + (while + (and (or ignore + (not (if full (eq full 'empty) context)) + (not (sgml-at-indentation-p)) + (and context + (/= (point) (sgml-tag-start (car context))) + (sgml-unclosed-tag-p (sgml-tag-name (car context))))) + (setq tag-info (ignore-errors (sgml-parse-tag-backward)))) + + ;; This tag may enclose things we thought were tags. If so, + ;; discard them. + (while (and context + (> (sgml-tag-end tag-info) + (sgml-tag-end (car context)))) + (setq context (cdr context))) + + (cond + + ;; inside a tag ... + ((sgml-inside-tag-p tag-info here) + (push tag-info context)) + + ;; start-tag + ((eq (sgml-tag-type tag-info) 'open) + (cond + ((null ignore) + (if (and context + (sgml-unclosed-tag-p (sgml-tag-name tag-info)) + (eq t (compare-strings + (sgml-tag-name tag-info) nil nil + (sgml-tag-name (car context)) nil nil t))) + ;; There was an implicit end-tag. + nil + (push tag-info context))) + ((eq t (compare-strings (sgml-tag-name tag-info) nil nil + (car ignore) nil nil t)) + (setq ignore (cdr ignore))) + (t + ;; The open and close tags don't match. + (if (not sgml-xml-mode) + ;; Assume the open tag is simply not closed. + (unless (sgml-unclosed-tag-p (sgml-tag-name tag-info)) + (message "Unclosed tag <%s>" (sgml-tag-name tag-info))) + (message "Unmatched tags <%s> and </%s>" + (sgml-tag-name tag-info) (pop ignore)))))) + + ;; end-tag + ((eq (sgml-tag-type tag-info) 'close) + (if (sgml-empty-tag-p (sgml-tag-name tag-info)) + (message "Spurious </%s>: empty tag" (sgml-tag-name tag-info)) + (push (sgml-tag-name tag-info) ignore))) + )) + + ;; return context + context)) + +(defun sgml-show-context (&optional full) + "Display the current context. +If FULL is non-nil, parse back to the beginning of the buffer." + (interactive "P") + (with-output-to-temp-buffer "*XML Context*" + (pp (save-excursion (sgml-get-context full))))) + + +;; Editing shortcuts + +(defun sgml-insert-end-tag () + "Insert an end-tag for the current element." + (interactive) + (let* ((context (save-excursion (sgml-get-context))) + (tag-info (car (last context))) + (type (and tag-info (sgml-tag-type tag-info)))) + + (cond + + ((null context) + (error "Nothing to close")) + + ;; inside a tag + ((sgml-inside-tag-p tag-info) + (insert (cond + ((eq type 'empty) " />") + ((eq type 'comment) " -->") + ((eq type 'cdata) "]]>") + ((eq type 'jsp) "%>") + ((eq type 'pi) "?>") + (t ">")))) + + ;; inside an element + ((eq type 'open) + (insert "</" (sgml-tag-name tag-info) ">") + (indent-according-to-mode)) + + (t + (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) @@ -1003,19 +1203,19 @@ (> (point) (cdr lcon))) nil (goto-char here) - (nreverse (xml-lite-get-context (if unclosed nil 'empty))))) + (nreverse (sgml-get-context (if unclosed nil 'empty))))) (there (point))) ;; Ignore previous unclosed start-tag in context. (while (and context unclosed (eq t (compare-strings - (xml-lite-tag-name (car context)) nil nil + (sgml-tag-name (car context)) nil nil unclosed nil nil t))) (setq context (cdr context))) ;; Indent to reflect nesting. (if (and context - (goto-char (xml-lite-tag-end (car context))) + (goto-char (sgml-tag-end (car context))) (skip-chars-forward " \t\n") - (< (point) here) (xml-lite-at-indentation-p)) + (< (point) here) (sgml-at-indentation-p)) (current-column) (goto-char there) (+ (current-column)