diff options
| author | Stefan Monnier | 2002-03-28 16:06:38 +0000 |
|---|---|---|
| committer | Stefan Monnier | 2002-03-28 16:06:38 +0000 |
| commit | 1c1d2eb667186e204e220c623b8ac7ebcea65a7b (patch) | |
| tree | cb7c57034fb499cca8f7bd89134b94fa125374f0 | |
| parent | 3f270c8a1b726f8c0348e4268648cb38704f130f (diff) | |
| download | emacs-1c1d2eb667186e204e220c623b8ac7ebcea65a7b.tar.gz emacs-1c1d2eb667186e204e220c623b8ac7ebcea65a7b.zip | |
(sgml-make-syntax-table): New fun.
(sgml-mode-syntax-table): Use it.
(sgml-tag-syntax-table, sgml-tag-name-re): New const.
(sgml-tags-invisible): Use it.
(sgml-lexical-context): New fun.
(sgml-maybe-end-tag, sgml-beginning-of-tag): Use it.
(sgml-quote): Accept \n as entity reference terminator.
(sgml-calculate-indent, sgml-indent-line): New funs.
| -rw-r--r-- | lisp/textmodes/sgml-mode.el | 190 |
1 files changed, 150 insertions, 40 deletions
diff --git a/lisp/textmodes/sgml-mode.el b/lisp/textmodes/sgml-mode.el index ad5df701171..415d69eb800 100644 --- a/lisp/textmodes/sgml-mode.el +++ b/lisp/textmodes/sgml-mode.el | |||
| @@ -132,19 +132,31 @@ This takes effect when first loading the `sgml-mode' library.") | |||
| 132 | "Keymap for SGML mode. See also `sgml-specials'.") | 132 | "Keymap for SGML mode. See also `sgml-specials'.") |
| 133 | 133 | ||
| 134 | 134 | ||
| 135 | (defvar sgml-mode-syntax-table | 135 | (defun sgml-make-syntax-table (specials) |
| 136 | (let ((table (copy-syntax-table text-mode-syntax-table))) | 136 | (let ((table (make-syntax-table text-mode-syntax-table))) |
| 137 | (modify-syntax-entry ?< "(>" table) | 137 | (modify-syntax-entry ?< "(>" table) |
| 138 | (modify-syntax-entry ?> ")<" table) | 138 | (modify-syntax-entry ?> ")<" table) |
| 139 | (if (memq ?- sgml-specials) | 139 | (modify-syntax-entry ?: "_" table) |
| 140 | (modify-syntax-entry ?_ "_" table) | ||
| 141 | (modify-syntax-entry ?. "_" table) | ||
| 142 | (if (memq ?- specials) | ||
| 140 | (modify-syntax-entry ?- "_ 1234" table)) | 143 | (modify-syntax-entry ?- "_ 1234" table)) |
| 141 | (if (memq ?\" sgml-specials) | 144 | (if (memq ?\" specials) |
| 142 | (modify-syntax-entry ?\" "\"\"" table)) | 145 | (modify-syntax-entry ?\" "\"\"" table)) |
| 143 | (if (memq ?' sgml-specials) | 146 | (if (memq ?' specials) |
| 144 | (modify-syntax-entry ?\' "\"'" table)) | 147 | (modify-syntax-entry ?\' "\"'" table)) |
| 145 | table) | 148 | table)) |
| 149 | |||
| 150 | (defvar sgml-mode-syntax-table (sgml-make-syntax-table sgml-specials) | ||
| 146 | "Syntax table used in SGML mode. See also `sgml-specials'.") | 151 | "Syntax table used in SGML mode. See also `sgml-specials'.") |
| 147 | 152 | ||
| 153 | (defconst sgml-tag-syntax-table | ||
| 154 | (let ((table (sgml-make-syntax-table '(?- ?\" ?\')))) | ||
| 155 | (dolist (char '(?\( ?\) ?\{ ?\} ?\[ ?\] ?$ ?% ?& ?* ?+ ?/)) | ||
| 156 | (modify-syntax-entry char "." table)) | ||
| 157 | table) | ||
| 158 | "Syntax table used to parse SGML tags.") | ||
| 159 | |||
| 148 | 160 | ||
| 149 | (defcustom sgml-name-8bit-mode nil | 161 | (defcustom sgml-name-8bit-mode nil |
| 150 | "*When non-nil, insert non-ASCII characters as named entities." | 162 | "*When non-nil, insert non-ASCII characters as named entities." |
| @@ -225,6 +237,7 @@ separated by a space." | |||
| 225 | :type '(choice (const nil) integer) | 237 | :type '(choice (const nil) integer) |
| 226 | :group 'sgml) | 238 | :group 'sgml) |
| 227 | 239 | ||
| 240 | (defconst sgml-tag-name-re "<\\([!/?]?[[:alpha:]][-_.:[:alnum:]]*\\)") | ||
| 228 | (defconst sgml-start-tag-regex | 241 | (defconst sgml-start-tag-regex |
| 229 | "<[[:alpha:]]\\([-_.:[:alnum:]= \n\t]\\|\"[^\"]*\"\\|'[^']*'\\)*" | 242 | "<[[:alpha:]]\\([-_.:[:alnum:]= \n\t]\\|\"[^\"]*\"\\|'[^']*'\\)*" |
| 230 | "Regular expression that matches a non-empty start tag. | 243 | "Regular expression that matches a non-empty start tag. |
| @@ -235,7 +248,7 @@ Any terminating `>' or `/' is not matched.") | |||
| 235 | (defconst sgml-font-lock-keywords-1 | 248 | (defconst sgml-font-lock-keywords-1 |
| 236 | '(("<\\([!?][[:alpha:]][-_.:[:alnum:]]*\\)" 1 font-lock-keyword-face) | 249 | '(("<\\([!?][[:alpha:]][-_.:[:alnum:]]*\\)" 1 font-lock-keyword-face) |
| 237 | ("<\\(/?[[:alpha:]][-_.:[:alnum:]]*\\)" 1 font-lock-function-name-face) | 250 | ("<\\(/?[[:alpha:]][-_.:[:alnum:]]*\\)" 1 font-lock-function-name-face) |
| 238 | ;; FIXME: this doesn't cover the variable using a default value. | 251 | ;; FIXME: this doesn't cover the variables using a default value. |
| 239 | ("\\([[:alpha:]][-_.:[:alnum:]]*\\)=[\"']" 1 font-lock-variable-name-face) | 252 | ("\\([[:alpha:]][-_.:[:alnum:]]*\\)=[\"']" 1 font-lock-variable-name-face) |
| 240 | ("[&%][[:alpha:]][-_.:[:alnum:]]*;?" . font-lock-variable-name-face))) | 253 | ("[&%][[:alpha:]][-_.:[:alnum:]]*;?" . font-lock-variable-name-face))) |
| 241 | 254 | ||
| @@ -634,20 +647,12 @@ With prefix argument, only self insert." | |||
| 634 | "No description available"))) | 647 | "No description available"))) |
| 635 | 648 | ||
| 636 | 649 | ||
| 637 | (defun sgml-maybe-end-tag () | 650 | (defun sgml-maybe-end-tag (&optional arg) |
| 638 | "Name self unless in position to end a tag." | 651 | "Name self unless in position to end a tag or a prefix ARG is given." |
| 639 | (interactive) | 652 | (interactive "P") |
| 640 | (or (condition-case nil | 653 | (if (or arg (eq (car (sgml-lexical-context)) 'tag)) |
| 641 | (save-excursion (up-list -1)) | 654 | (self-insert-command (prefix-numeric-value arg)) |
| 642 | (error | 655 | (sgml-name-self))) |
| 643 | (sgml-name-self) | ||
| 644 | t)) | ||
| 645 | (condition-case nil | ||
| 646 | (progn | ||
| 647 | (save-excursion (up-list 1)) | ||
| 648 | (sgml-name-self)) | ||
| 649 | (error (self-insert-command 1))))) | ||
| 650 | |||
| 651 | 656 | ||
| 652 | (defun sgml-skip-tag-backward (arg) | 657 | (defun sgml-skip-tag-backward (arg) |
| 653 | "Skip to beginning of tag or matching opening tag if present. | 658 | "Skip to beginning of tag or matching opening tag if present. |
| @@ -769,8 +774,7 @@ With prefix argument ARG, repeat this ARG times." | |||
| 769 | (if arg | 774 | (if arg |
| 770 | (>= (prefix-numeric-value arg) 0) | 775 | (>= (prefix-numeric-value arg) 0) |
| 771 | (not sgml-tags-invisible))) | 776 | (not sgml-tags-invisible))) |
| 772 | (while (re-search-forward "<\\([!/?]?[[:alpha:]][-_.:[:alnum:]]*\\)" | 777 | (while (re-search-forward sgml-tag-name-re nil t) |
| 773 | nil t) | ||
| 774 | (setq string | 778 | (setq string |
| 775 | (cdr (assq (intern-soft (downcase (match-string 1))) | 779 | (cdr (assq (intern-soft (downcase (match-string 1))) |
| 776 | sgml-display-text))) | 780 | sgml-display-text))) |
| @@ -829,24 +833,49 @@ and move to the line in the SGML document that caused it." | |||
| 829 | (compile-internal command "No more errors")) | 833 | (compile-internal command "No more errors")) |
| 830 | 834 | ||
| 831 | 835 | ||
| 836 | (defun sgml-lexical-context (&optional limit) | ||
| 837 | "Return the lexical context at point as (TYPE . START). | ||
| 838 | START is the location of the start of the lexical element. | ||
| 839 | TYPE is one of `string', `comment', `tag', `cdata', .... | ||
| 840 | Return nil if we are inside text (i.e. outside of any kind of tag). | ||
| 841 | |||
| 842 | If non-nil LIMIT is a nearby position before point outside of any tag." | ||
| 843 | ;; As usual, it's difficult to get a reliable answer without parsing the | ||
| 844 | ;; whole buffer. We'll assume that a tag at indentation is outside of | ||
| 845 | ;; any string or tag or comment or ... | ||
| 846 | (save-excursion | ||
| 847 | (let ((pos (point)) | ||
| 848 | (state nil)) | ||
| 849 | ;; Hopefully this regexp will match something that's not inside | ||
| 850 | ;; a tag and also hopefully the match is nearby. | ||
| 851 | (when (or (and limit (goto-char limit)) | ||
| 852 | (re-search-backward "^[ \t]*<" nil t)) | ||
| 853 | (with-syntax-table sgml-tag-syntax-table | ||
| 854 | (while (< (point) pos) | ||
| 855 | ;; When entering this loop we're inside text. | ||
| 856 | (skip-chars-forward "^<" pos) | ||
| 857 | ;; We skipped text and reached a tag. Parse it. | ||
| 858 | ;; FIXME: this does not handle CDATA and funny stuff yet. | ||
| 859 | (setq state (parse-partial-sexp (point) pos 0))) | ||
| 860 | (cond | ||
| 861 | ((nth 3 state) (cons 'string (nth 8 state))) | ||
| 862 | ((nth 4 state) (cons 'comment (nth 8 state))) | ||
| 863 | ((and state (> (nth 0 state) 0)) (cons 'tag (nth 1 state))) | ||
| 864 | (t nil))))))) | ||
| 865 | |||
| 832 | (defun sgml-beginning-of-tag (&optional top-level) | 866 | (defun sgml-beginning-of-tag (&optional top-level) |
| 833 | "Skip to beginning of tag and return its name. | 867 | "Skip to beginning of tag and return its name. |
| 834 | If this can't be done, return t." | 868 | If this can't be done, return nil." |
| 835 | (or (if top-level | 869 | (let ((context (sgml-lexical-context))) |
| 836 | (condition-case nil | 870 | (if (eq (car context) 'tag) |
| 837 | (up-list -1) | 871 | (progn |
| 838 | (error t)) | 872 | (goto-char (cdr context)) |
| 839 | (>= (point) | 873 | (when (looking-at sgml-tag-name-re) |
| 840 | (if (search-backward "<" nil t) | 874 | (match-string-no-properties 1))) |
| 841 | (save-excursion | 875 | (if top-level nil |
| 842 | (forward-list) | 876 | (when context |
| 843 | (point)) | 877 | (goto-char (cdr context)) |
| 844 | 0))) | 878 | (sgml-beginning-of-tag t)))))) |
| 845 | (if (looking-at "<[!/?]?[[:alpha:]][-_.:[:alnum:]]*") | ||
| 846 | (buffer-substring-no-properties | ||
| 847 | (1+ (point)) | ||
| 848 | (match-end 0)) | ||
| 849 | t))) | ||
| 850 | 879 | ||
| 851 | (defun sgml-value (alist) | 880 | (defun sgml-value (alist) |
| 852 | "Interactively insert value taken from attributerule ALIST. | 881 | "Interactively insert value taken from attributerule ALIST. |
| @@ -875,7 +904,7 @@ With prefix argument, unquote the region." | |||
| 875 | (goto-char end) | 904 | (goto-char end) |
| 876 | (setq end start)) | 905 | (setq end start)) |
| 877 | (if unquotep | 906 | (if unquotep |
| 878 | (while (re-search-forward "&\\(amp\\|\\(l\\|\\(g\\)\\)t\\);" end t) | 907 | (while (re-search-forward "&\\(amp\\|\\(l\\|\\(g\\)\\)t\\)[;\n]" end t) |
| 879 | (replace-match (if (match-end 3) ">" (if (match-end 2) "<" "&")))) | 908 | (replace-match (if (match-end 3) ">" (if (match-end 2) "<" "&")))) |
| 880 | (while (re-search-forward "[&<>]" end t) | 909 | (while (re-search-forward "[&<>]" end t) |
| 881 | (replace-match (cdr (assq (char-before) '((?& . "&") | 910 | (replace-match (cdr (assq (char-before) '((?& . "&") |
| @@ -883,6 +912,87 @@ With prefix argument, unquote the region." | |||
| 883 | (?> . ">")))))))) | 912 | (?> . ">")))))))) |
| 884 | 913 | ||
| 885 | 914 | ||
| 915 | (defun sgml-calculate-indent () | ||
| 916 | "Calculate the column to which this line should be indented." | ||
| 917 | (let ((lcon (sgml-lexical-context))) | ||
| 918 | ;; Indent comment-start markers inside <!-- just like comment-end markers. | ||
| 919 | (if (and (eq (car lcon) 'tag) | ||
| 920 | (looking-at "--") | ||
| 921 | (save-excursion (goto-char (cdr lcon)) (looking-at "<!--"))) | ||
| 922 | (setq lcon (cons 'comment (+ (cdr lcon) 2)))) | ||
| 923 | |||
| 924 | (case (car lcon) | ||
| 925 | (string | ||
| 926 | ;; Go back to previous non-empty line. | ||
| 927 | (while (and (> (point) (cdr lcon)) | ||
| 928 | (zerop (forward-line -1)) | ||
| 929 | (looking-at "[ \t]*$"))) | ||
| 930 | (if (> (point) (cdr lcon)) | ||
| 931 | ;; Previous line is inside the string. | ||
| 932 | (current-indentation) | ||
| 933 | (goto-char (cdr lcon)) | ||
| 934 | (1+ (current-column)))) | ||
| 935 | |||
| 936 | (comment | ||
| 937 | (let ((mark (looking-at "--"))) | ||
| 938 | ;; Go back to previous non-empty line. | ||
| 939 | (while (and (> (point) (cdr lcon)) | ||
| 940 | (zerop (forward-line -1)) | ||
| 941 | (or (looking-at "[ \t]*$") | ||
| 942 | (if mark (not (looking-at "[ \t]*--")))))) | ||
| 943 | (if (> (point) (cdr lcon)) | ||
| 944 | ;; Previous line is inside the comment. | ||
| 945 | (skip-chars-forward " \t") | ||
| 946 | (goto-char (cdr lcon))) | ||
| 947 | (when (and (not mark) (looking-at "--")) | ||
| 948 | (forward-char 2) (skip-chars-forward " \t")) | ||
| 949 | (current-column))) | ||
| 950 | |||
| 951 | (tag | ||
| 952 | (goto-char (1+ (cdr lcon))) | ||
| 953 | (skip-chars-forward "^ \t\n") ;Skip tag name. | ||
| 954 | (skip-chars-forward " \t") | ||
| 955 | (if (not (eolp)) | ||
| 956 | (current-column) | ||
| 957 | ;; This is the first attribute: indent. | ||
| 958 | (goto-char (1+ (cdr lcon))) | ||
| 959 | (+ (current-column) sgml-basic-offset))) | ||
| 960 | |||
| 961 | (t | ||
| 962 | (while (looking-at "</") | ||
| 963 | (forward-sexp 1) | ||
| 964 | (skip-chars-forward " \t")) | ||
| 965 | (let ((context (xml-lite-get-context))) | ||
| 966 | (cond | ||
| 967 | ((null context) 0) ; no context | ||
| 968 | ;; Align closing tag with the opening one. | ||
| 969 | ;; ((and (eq (length context) 1) (looking-at "</")) | ||
| 970 | ;; (goto-char (xml-lite-tag-start (car context))) | ||
| 971 | ;; (current-column)) | ||
| 972 | (t | ||
| 973 | (let ((here (point))) | ||
| 974 | (goto-char (xml-lite-tag-end (car context))) | ||
| 975 | (skip-chars-forward " \t\n") | ||
| 976 | (if (< (point) here) | ||
| 977 | (current-column) | ||
| 978 | (goto-char (xml-lite-tag-start (car context))) | ||
| 979 | (+ (current-column) sgml-basic-offset)))))))))) | ||
| 980 | |||
| 981 | (defun sgml-indent-line () | ||
| 982 | "Indent the current line as SGML." | ||
| 983 | (interactive) | ||
| 984 | (let* ((savep (point)) | ||
| 985 | (indent-col | ||
| 986 | (save-excursion | ||
| 987 | (beginning-of-line) | ||
| 988 | (skip-chars-forward " \t") | ||
| 989 | (if (>= (point) savep) (setq savep nil)) | ||
| 990 | ;; calculate basic indent | ||
| 991 | (sgml-calculate-indent)))) | ||
| 992 | (if savep | ||
| 993 | (save-excursion (indent-line-to indent-col)) | ||
| 994 | (indent-line-to indent-col)))) | ||
| 995 | |||
| 886 | ;;; HTML mode | 996 | ;;; HTML mode |
| 887 | 997 | ||
| 888 | (defcustom html-mode-hook nil | 998 | (defcustom html-mode-hook nil |