diff options
| author | Stefan Monnier | 2007-05-31 00:52:35 +0000 |
|---|---|---|
| committer | Stefan Monnier | 2007-05-31 00:52:35 +0000 |
| commit | d500a09264ee5f33f73d9af010cf9aca80549f13 (patch) | |
| tree | 8021edb5574966fc92702b30cc4af054b951f600 | |
| parent | 5faa03baf49e340f7df3aea960aa54bfaef7a5c5 (diff) | |
| download | emacs-d500a09264ee5f33f73d9af010cf9aca80549f13.tar.gz emacs-d500a09264ee5f33f73d9af010cf9aca80549f13.zip | |
css-mode.el: New file.
| -rw-r--r-- | etc/NEWS | 1 | ||||
| -rw-r--r-- | lisp/ChangeLog | 19 | ||||
| -rw-r--r-- | lisp/textmodes/css-mode.el | 469 |
3 files changed, 481 insertions, 8 deletions
| @@ -31,6 +31,7 @@ with a prefix argument or by typing C-u C-h C-n. | |||
| 31 | 31 | ||
| 32 | * New Modes and Packages in Emacs 23.1 | 32 | * New Modes and Packages in Emacs 23.1 |
| 33 | 33 | ||
| 34 | ** css-mode to edit Cascading Style Sheets. | ||
| 34 | 35 | ||
| 35 | * Changes in Specialized Modes and Packages in Emacs 23.1 | 36 | * Changes in Specialized Modes and Packages in Emacs 23.1 |
| 36 | 37 | ||
diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 9a79d4a0b55..07778c2f734 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog | |||
| @@ -1,3 +1,7 @@ | |||
| 1 | 2007-05-31 Stefan Monnier <monnier@iro.umontreal.ca> | ||
| 2 | |||
| 3 | * textmodes/css-mode.el: New file. | ||
| 4 | |||
| 1 | 2007-05-30 Michael Olson <mwolson@gnu.org> | 5 | 2007-05-30 Michael Olson <mwolson@gnu.org> |
| 2 | 6 | ||
| 3 | * emacs-lisp/tq.el (tq-queue-pop): Stifle error when a process has | 7 | * emacs-lisp/tq.el (tq-queue-pop): Stifle error when a process has |
| @@ -9,8 +13,8 @@ | |||
| 9 | 2007-05-29 Martin Rudalics <rudalics@gmx.at> | 13 | 2007-05-29 Martin Rudalics <rudalics@gmx.at> |
| 10 | 14 | ||
| 11 | * textmodes/table.el (table--point-entered-cell-function) | 15 | * textmodes/table.el (table--point-entered-cell-function) |
| 12 | (table--point-left-cell-function): Bind | 16 | (table--point-left-cell-function): |
| 13 | `inhibit-point-motion-hooks' to t. | 17 | Bind `inhibit-point-motion-hooks' to t. |
| 14 | 18 | ||
| 15 | 2007-05-29 Nikolaj Schumacher <n_schumacher@web.de> (tiny change) | 19 | 2007-05-29 Nikolaj Schumacher <n_schumacher@web.de> (tiny change) |
| 16 | 20 | ||
| @@ -35,18 +39,17 @@ | |||
| 35 | 39 | ||
| 36 | * net/tramp.el (top): Make `set-buffer-multibyte' an alias if it | 40 | * net/tramp.el (top): Make `set-buffer-multibyte' an alias if it |
| 37 | doesn't exist. | 41 | doesn't exist. |
| 38 | (with-parsed-tramp-file-name): Protect debug spec during | 42 | (with-parsed-tramp-file-name): Protect debug spec during compilation. |
| 39 | compilation. | ||
| 40 | (tramp-handle-insert-directory): Check (featurep 'ls-lisp). | 43 | (tramp-handle-insert-directory): Check (featurep 'ls-lisp). |
| 41 | (tramp-file-name-p, tramp-file-name-multi-method) | 44 | (tramp-file-name-p, tramp-file-name-multi-method) |
| 42 | (tramp-file-name-method, tramp-file-name-user) | 45 | (tramp-file-name-method, tramp-file-name-user) |
| 43 | (tramp-file-name-host, tramp-file-name-localname): New defuns, | 46 | (tramp-file-name-host, tramp-file-name-localname): New defuns, |
| 44 | replacing defstruct `tramp-file-name'. | 47 | replacing defstruct `tramp-file-name'. |
| 45 | (tramp-handle-file-remote-p, tramp-completion-dissect-file-name1) | 48 | (tramp-handle-file-remote-p, tramp-completion-dissect-file-name1) |
| 46 | (tramp-dissect-file-name, tramp-dissect-multi-file-name): Apply | 49 | (tramp-dissect-file-name, tramp-dissect-multi-file-name): |
| 47 | `vector' instead of `make-tramp-file-name'. | 50 | Apply `vector' instead of `make-tramp-file-name'. |
| 48 | (tramp-handle-make-auto-save-file-name): Apply | 51 | (tramp-handle-make-auto-save-file-name): |
| 49 | `tramp-temporary-file-directory' for compatibility reasons. | 52 | Apply `tramp-temporary-file-directory' for compatibility reasons. |
| 50 | (tramp-completion-mode): Use `natnump' instead of `wholenump' | 53 | (tramp-completion-mode): Use `natnump' instead of `wholenump' |
| 51 | because of XEmacs. | 54 | because of XEmacs. |
| 52 | (tramp-completion-mode): `last-input-event' is nil when XEmacs is | 55 | (tramp-completion-mode): `last-input-event' is nil when XEmacs is |
diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el new file mode 100644 index 00000000000..8bf9ac2ee96 --- /dev/null +++ b/lisp/textmodes/css-mode.el | |||
| @@ -0,0 +1,469 @@ | |||
| 1 | ;;; css-mode.el --- Major mode to edit CSS files | ||
| 2 | |||
| 3 | ;; Copyright (C) 2006, 2007 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Stefan Monnier <monnier@iro.umontreal.ca> | ||
| 6 | ;; Keywords: hypermedia | ||
| 7 | |||
| 8 | ;; This file is free software; you can redistribute it and/or modify | ||
| 9 | ;; it under the terms of the GNU General Public License as published by | ||
| 10 | ;; the Free Software Foundation; either version 2, or (at your option) | ||
| 11 | ;; any later version. | ||
| 12 | |||
| 13 | ;; This file is distributed in the hope that it will be useful, | ||
| 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 16 | ;; GNU General Public License for more details. | ||
| 17 | |||
| 18 | ;; You should have received a copy of the GNU General Public License | ||
| 19 | ;; along with GNU Emacs; see the file COPYING. If not, write to | ||
| 20 | ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||
| 21 | ;; Boston, MA 02110-1301, USA. | ||
| 22 | |||
| 23 | ;;; Commentary: | ||
| 24 | |||
| 25 | ;; Yet another CSS mode. | ||
| 26 | |||
| 27 | ;;; Todo: | ||
| 28 | |||
| 29 | ;; - electric ; and } | ||
| 30 | ;; - filling code with auto-fill-mode | ||
| 31 | ;; - completion | ||
| 32 | ;; - fix font-lock errors with multi-line selectors | ||
| 33 | |||
| 34 | ;;; Code: | ||
| 35 | |||
| 36 | (defun css-extract-keyword-list (res) | ||
| 37 | (with-temp-buffer | ||
| 38 | (url-insert-file-contents "http://www.w3.org/TR/REC-CSS2/css2.txt") | ||
| 39 | (goto-char (point-max)) | ||
| 40 | (search-backward "Appendix H. Index") | ||
| 41 | (forward-line) | ||
| 42 | (delete-region (point-min) (point)) | ||
| 43 | (let ((result nil) | ||
| 44 | keys) | ||
| 45 | (dolist (re res) | ||
| 46 | (goto-char (point-min)) | ||
| 47 | (setq keys nil) | ||
| 48 | (while (re-search-forward (cdr re) nil t) | ||
| 49 | (push (match-string 1) keys)) | ||
| 50 | (push (cons (car re) (sort keys 'string-lessp)) result)) | ||
| 51 | (nreverse result)))) | ||
| 52 | |||
| 53 | (defun css-extract-parse-val-grammar (string env) | ||
| 54 | (let ((start 0) | ||
| 55 | (elems ()) | ||
| 56 | name) | ||
| 57 | (while (string-match | ||
| 58 | (concat "\\(?:" | ||
| 59 | (concat "<a [^>]+><span [^>]+>\\(?:" | ||
| 60 | "<\\([^&]+\\)>\\|'\\([^']+\\)'" | ||
| 61 | "\\)</span></a>") | ||
| 62 | "\\|" "\\(\\[\\)" | ||
| 63 | "\\|" "\\(]\\)" | ||
| 64 | "\\|" "\\(||\\)" | ||
| 65 | "\\|" "\\(|\\)" | ||
| 66 | "\\|" "\\([*+?]\\)" | ||
| 67 | "\\|" "\\({[^}]+}\\)" | ||
| 68 | "\\|" "\\(\\w+\\(?:-\\w+\\)*\\)" | ||
| 69 | "\\)[ \t\n]*") | ||
| 70 | string start) | ||
| 71 | ;; (assert (eq start (match-beginning 0))) | ||
| 72 | (setq start (match-end 0)) | ||
| 73 | (cond | ||
| 74 | ;; Reference to a type of value. | ||
| 75 | ((setq name (match-string-no-properties 1 string)) | ||
| 76 | (push (intern name) elems)) | ||
| 77 | ;; Reference to another property's values. | ||
| 78 | ((setq name (match-string-no-properties 2 string)) | ||
| 79 | (setq elems (delete-dups (append (cdr (assoc name env)) elems)))) | ||
| 80 | ;; A literal | ||
| 81 | ((setq name (match-string-no-properties 9 string)) | ||
| 82 | (push name elems)) | ||
| 83 | ;; We just ignore the rest. I.e. we ignore the structure because | ||
| 84 | ;; it's too difficult to exploit anyway (it would allow us to only | ||
| 85 | ;; complete top/center/bottom after one of left/center/right and | ||
| 86 | ;; vice-versa). | ||
| 87 | (t nil))) | ||
| 88 | elems)) | ||
| 89 | |||
| 90 | |||
| 91 | (defun css-extract-props-and-vals () | ||
| 92 | (with-temp-buffer | ||
| 93 | (url-insert-file-contents "http://www.w3.org/TR/CSS21/propidx.html") | ||
| 94 | (goto-char (point-min)) | ||
| 95 | (let ((props ())) | ||
| 96 | (while (re-search-forward "#propdef-\\([^\"]+\\)\"><span class=\"propinst-\\1 xref\">'\\1'</span></a>" nil t) | ||
| 97 | (let ((prop (match-string-no-properties 1))) | ||
| 98 | (save-excursion | ||
| 99 | (goto-char (match-end 0)) | ||
| 100 | (search-forward "<td>") | ||
| 101 | (let ((vals-string (buffer-substring (point) | ||
| 102 | (progn | ||
| 103 | (re-search-forward "[ \t\n]+|[ \t\n]+<a href=\"cascade.html#value-def-inherit\" class=\"noxref\"><span class=\"value-inst-inherit\">inherit</span></a>") | ||
| 104 | (match-beginning 0))))) | ||
| 105 | ;; | ||
| 106 | (push (cons prop (css-extract-parse-val-grammar vals-string props)) | ||
| 107 | props))))) | ||
| 108 | props))) | ||
| 109 | |||
| 110 | ;; Extraction was done with: | ||
| 111 | ;; (css-extract-keyword-list | ||
| 112 | ;; '((pseudo . "^ +\\* :\\([^ \n,]+\\)") | ||
| 113 | ;; (at . "^ +\\* @\\([^ \n,]+\\)") | ||
| 114 | ;; (descriptor . "^ +\\* '\\([^ '\n]+\\)' (descriptor)") | ||
| 115 | ;; (media . "^ +\\* '\\([^ '\n]+\\)' media group") | ||
| 116 | ;; (property . "^ +\\* '\\([^ '\n]+\\)',"))) | ||
| 117 | |||
| 118 | (defconst css-pseudo-ids | ||
| 119 | '("active" "after" "before" "first" "first-child" "first-letter" "first-line" | ||
| 120 | "focus" "hover" "lang" "left" "link" "right" "visited") | ||
| 121 | "Identifiers for pseudo-elements and pseudo-classes.") | ||
| 122 | |||
| 123 | (defconst css-at-ids | ||
| 124 | '("charset" "font-face" "import" "media" "page") | ||
| 125 | "Identifiers that appear in the form @foo.") | ||
| 126 | |||
| 127 | (defconst css-descriptor-ids | ||
| 128 | '("ascent" "baseline" "bbox" "cap-height" "centerline" "definition-src" | ||
| 129 | "descent" "font-family" "font-size" "font-stretch" "font-style" | ||
| 130 | "font-variant" "font-weight" "mathline" "panose-1" "slope" "src" "stemh" | ||
| 131 | "stemv" "topline" "unicode-range" "units-per-em" "widths" "x-height") | ||
| 132 | "Identifiers for font descriptors.") | ||
| 133 | |||
| 134 | (defconst css-media-ids | ||
| 135 | '("all" "aural" "bitmap" "continuous" "grid" "paged" "static" "tactile" | ||
| 136 | "visual") | ||
| 137 | "Identifiers for types of media.") | ||
| 138 | |||
| 139 | (defconst css-property-ids | ||
| 140 | '("azimuth" "background" "background-attachment" "background-color" | ||
| 141 | "background-image" "background-position" "background-repeat" "block" | ||
| 142 | "border" "border-bottom" "border-bottom-color" "border-bottom-style" | ||
| 143 | "border-bottom-width" "border-collapse" "border-color" "border-left" | ||
| 144 | "border-left-color" "border-left-style" "border-left-width" "border-right" | ||
| 145 | "border-right-color" "border-right-style" "border-right-width" | ||
| 146 | "border-spacing" "border-style" "border-top" "border-top-color" | ||
| 147 | "border-top-style" "border-top-width" "border-width" "bottom" | ||
| 148 | "caption-side" "clear" "clip" "color" "compact" "content" | ||
| 149 | "counter-increment" "counter-reset" "cue" "cue-after" "cue-before" | ||
| 150 | "cursor" "dashed" "direction" "display" "dotted" "double" "elevation" | ||
| 151 | "empty-cells" "float" "font" "font-family" "font-size" "font-size-adjust" | ||
| 152 | "font-stretch" "font-style" "font-variant" "font-weight" "groove" "height" | ||
| 153 | "hidden" "inline" "inline-table" "inset" "left" "letter-spacing" | ||
| 154 | "line-height" "list-item" "list-style" "list-style-image" | ||
| 155 | "list-style-position" "list-style-type" "margin" "margin-bottom" | ||
| 156 | "margin-left" "margin-right" "margin-top" "marker-offset" "marks" | ||
| 157 | "max-height" "max-width" "min-height" "min-width" "orphans" "outline" | ||
| 158 | "outline-color" "outline-style" "outline-width" "outset" "overflow" | ||
| 159 | "padding" "padding-bottom" "padding-left" "padding-right" "padding-top" | ||
| 160 | "page" "page-break-after" "page-break-before" "page-break-inside" "pause" | ||
| 161 | "pause-after" "pause-before" "pitch" "pitch-range" "play-during" "position" | ||
| 162 | "quotes" "richness" "ridge" "right" "run-in" "size" "solid" "speak" | ||
| 163 | "speak-header" "speak-numeral" "speak-punctuation" "speech-rate" "stress" | ||
| 164 | "table" "table-caption" "table-cell" "table-column" "table-column-group" | ||
| 165 | "table-footer-group" "table-header-group" "table-layout" "table-row" | ||
| 166 | "table-row-group" "text-align" "text-decoration" "text-indent" | ||
| 167 | "text-shadow" "text-transform" "top" "unicode-bidi" "vertical-align" | ||
| 168 | "visibility" "voice-family" "volume" "white-space" "widows" "width" | ||
| 169 | "word-spacing" "z-index") | ||
| 170 | "Identifiers for properties.") | ||
| 171 | |||
| 172 | (defcustom css-electrick-keys '(?\} ?\;) ;; '() | ||
| 173 | "Self inserting keys which should trigger re-indentation." | ||
| 174 | :type '(repeat character) | ||
| 175 | :options '((?\} ?\;))) | ||
| 176 | |||
| 177 | (defvar css-mode-syntax-table | ||
| 178 | (let ((st (make-syntax-table))) | ||
| 179 | ;; C-style comments. | ||
| 180 | (modify-syntax-entry ?/ ". 14" st) | ||
| 181 | (modify-syntax-entry ?* ". 23" st) | ||
| 182 | ;; Strings. | ||
| 183 | (modify-syntax-entry ?\" "\"" st) | ||
| 184 | (modify-syntax-entry ?\' "\"" st) | ||
| 185 | ;; Blocks. | ||
| 186 | (modify-syntax-entry ?\{ "(}" st) | ||
| 187 | (modify-syntax-entry ?\} "){" st) | ||
| 188 | ;; Args in url(...) thingies and other "function calls". | ||
| 189 | (modify-syntax-entry ?\( "()" st) | ||
| 190 | (modify-syntax-entry ?\) ")(" st) | ||
| 191 | ;; To match attributes in selectors. | ||
| 192 | (modify-syntax-entry ?\[ "(]" st) | ||
| 193 | (modify-syntax-entry ?\] ")[" st) | ||
| 194 | ;; Special chars that sometimes come at the beginning of words. | ||
| 195 | (modify-syntax-entry ?@ "'" st) | ||
| 196 | ;; (modify-syntax-entry ?: "'" st) | ||
| 197 | (modify-syntax-entry ?# "'" st) | ||
| 198 | ;; Distinction between words and symbols. | ||
| 199 | (modify-syntax-entry ?- "_" st) | ||
| 200 | st)) | ||
| 201 | |||
| 202 | (defconst css-escapes-re | ||
| 203 | "\\\\\\(?:[^\000-\037\177]\\|[0-9a-fA-F]+[ \n\t\r\f]?\\)") | ||
| 204 | (defconst css-nmchar-re (concat "\\(?:[-[:alnum:]]\\|" css-escapes-re "\\)")) | ||
| 205 | (defconst css-nmstart-re (concat "\\(?:[[:alpha:]]\\|" css-escapes-re "\\)")) | ||
| 206 | (defconst css-ident-re (concat css-nmstart-re css-nmchar-re "*")) | ||
| 207 | (defconst css-name-re (concat css-nmchar-re "+")) | ||
| 208 | |||
| 209 | (defface css-selector '((t :inherit font-lock-function-name-face)) | ||
| 210 | "Face to use for selectors.") | ||
| 211 | (defface css-property '((t :inherit font-lock-variable-name-face)) | ||
| 212 | "Face to use for properties.") | ||
| 213 | |||
| 214 | (defvar css-font-lock-keywords | ||
| 215 | `(("!\\s-*important" . font-lock-builtin-face) | ||
| 216 | ;; Atrules keywords. IDs not in css-at-ids are valid (ignored). | ||
| 217 | ;; In fact the regexp should probably be | ||
| 218 | ;; (,(concat "\\(@" css-ident-re "\\)\\([ \t\n][^;{]*\\)[;{]") | ||
| 219 | ;; (1 font-lock-builtin-face)) | ||
| 220 | ;; Since "An at-rule consists of everything up to and including the next | ||
| 221 | ;; semicolon (;) or the next block, whichever comes first." | ||
| 222 | (,(concat "@" css-ident-re) . font-lock-builtin-face) | ||
| 223 | ;; Selectors. | ||
| 224 | ;; FIXME: attribute selectors don't work well because they may contain | ||
| 225 | ;; strings which have already been highlighted as f-l-string-face and | ||
| 226 | ;; thus prevent this highlighting from being applied (actually now that | ||
| 227 | ;; I use `append' this should work better). But really the part of hte | ||
| 228 | ;; selector between [...] should simply not be highlighted. | ||
| 229 | (,(concat "^\\([ \t]*[^@:{\n][^:{\n]+\\(?::" (regexp-opt css-pseudo-ids t) | ||
| 230 | "\\(?:([^)]+)\\)?[^:{\n]*\\)*\\)\\(?:\n[ \t]*\\)*{") | ||
| 231 | (1 'css-selector append)) | ||
| 232 | ;; In the above rule, we allow the open-brace to be on some subsequent | ||
| 233 | ;; line. This will only work if we properly mark the intervening text | ||
| 234 | ;; as being part of a multiline element (and even then, this only | ||
| 235 | ;; ensures proper refontification, but not proper discovery). | ||
| 236 | ("^[ \t]*{" (0 (save-excursion | ||
| 237 | (goto-char (match-beginning 0)) | ||
| 238 | (skip-chars-backward " \n\t") | ||
| 239 | (put-text-property (point) (match-end 0) | ||
| 240 | 'font-lock-multiline t) | ||
| 241 | ;; No face. | ||
| 242 | nil))) | ||
| 243 | ;; Properties. Again, we don't limit ourselves to css-property-ids. | ||
| 244 | (,(concat "\\(?:[{;]\\|^\\)[ \t]*\\(" css-ident-re "\\)\\s-*:") | ||
| 245 | (1 'css-property)))) | ||
| 246 | |||
| 247 | (defvar css-font-lock-defaults | ||
| 248 | '(css-font-lock-keywords nil t)) | ||
| 249 | |||
| 250 | (unless (fboundp 'prog-mode) (defalias 'prog-mode 'fundamental-mode)) | ||
| 251 | |||
| 252 | ;;;###autoload (add-to-list 'auto-mode-alist '("\\.css\\'" . css-mode)) | ||
| 253 | ;;;###autoload | ||
| 254 | (define-derived-mode css-mode prog-mode "CSS" | ||
| 255 | "Major mode to edit Cascading Style Sheets." | ||
| 256 | (set (make-local-variable 'font-lock-defaults) css-font-lock-defaults) | ||
| 257 | (set (make-local-variable 'comment-start) "/*") | ||
| 258 | (set (make-local-variable 'comment-start-skip) "/\\*+[ \t]*") | ||
| 259 | (set (make-local-variable 'comment-end) "*/") | ||
| 260 | (set (make-local-variable 'comment-end-skip) "[ \t]*\\*+/") | ||
| 261 | (set (make-local-variable 'forward-sexp-function) 'css-forward-sexp) | ||
| 262 | (set (make-local-variable 'parse-sexp-ignore-comments) t) | ||
| 263 | (set (make-local-variable 'indent-line-function) 'css-indent-line) | ||
| 264 | (set (make-local-variable 'fill-paragraph-function) | ||
| 265 | 'css-fill-paragraph) | ||
| 266 | (when css-electrick-keys | ||
| 267 | (let ((fc (make-char-table 'auto-fill-chars))) | ||
| 268 | (set-char-table-parent fc auto-fill-chars) | ||
| 269 | (dolist (c css-electrick-keys) | ||
| 270 | (aset fc c 'indent-according-to-mode)) | ||
| 271 | (set (make-local-variable 'auto-fill-chars) fc)))) | ||
| 272 | |||
| 273 | (defun css-fill-paragraph (&optional justify) | ||
| 274 | (save-excursion | ||
| 275 | (let ((ppss (syntax-ppss)) | ||
| 276 | (eol (line-end-position))) | ||
| 277 | (cond | ||
| 278 | ((and (nth 4 ppss) | ||
| 279 | (save-excursion | ||
| 280 | (goto-char (nth 8 ppss)) | ||
| 281 | (forward-comment 1) | ||
| 282 | (prog1 (not (bolp)) | ||
| 283 | (setq eol (point))))) | ||
| 284 | ;; Filling inside a comment whose comment-end marker is not \n. | ||
| 285 | ;; This code is meant to be generic, so that it works not only for | ||
| 286 | ;; css-mode but for all modes. | ||
| 287 | (save-restriction | ||
| 288 | (narrow-to-region (nth 8 ppss) eol) | ||
| 289 | (comment-normalize-vars) | ||
| 290 | (let ((fill-paragraph-function nil) | ||
| 291 | (paragraph-separate | ||
| 292 | (if (and comment-continue | ||
| 293 | (string-match "[^ \t]" comment-continue)) | ||
| 294 | (concat "\\(?:[ \t]*" (regexp-quote comment-continue) | ||
| 295 | "\\)?\\(?:" paragraph-separate "\\)") | ||
| 296 | paragraph-separate)) | ||
| 297 | (paragraph-start | ||
| 298 | (if (and comment-continue | ||
| 299 | (string-match "[^ \t]" comment-continue)) | ||
| 300 | (concat "\\(?:[ \t]*" (regexp-quote comment-continue) | ||
| 301 | "\\)?\\(?:" paragraph-start "\\)") | ||
| 302 | paragraph-start))) | ||
| 303 | (fill-paragraph justify) | ||
| 304 | ;; Don't try filling again. | ||
| 305 | t))) | ||
| 306 | |||
| 307 | ((and (null (nth 8 ppss)) | ||
| 308 | (or (nth 1 ppss) | ||
| 309 | (and (ignore-errors | ||
| 310 | (down-list 1) | ||
| 311 | (when (<= (point) eol) | ||
| 312 | (setq ppss (syntax-ppss))))))) | ||
| 313 | (goto-char (nth 1 ppss)) | ||
| 314 | (let ((end (save-excursion | ||
| 315 | (ignore-errors (forward-sexp 1) (copy-marker (point) t))))) | ||
| 316 | (when end | ||
| 317 | (while (re-search-forward "[{;}]" end t) | ||
| 318 | (cond | ||
| 319 | ;; This is a false positive inside a string or comment. | ||
| 320 | ((nth 8 (syntax-ppss)) nil) | ||
| 321 | ((eq (char-before) ?\}) | ||
| 322 | (save-excursion | ||
| 323 | (forward-char -1) | ||
| 324 | (skip-chars-backward " \t") | ||
| 325 | (unless (bolp) (newline)))) | ||
| 326 | (t | ||
| 327 | (while | ||
| 328 | (progn | ||
| 329 | (setq eol (line-end-position)) | ||
| 330 | (and (forward-comment 1) | ||
| 331 | (> (point) eol) | ||
| 332 | ;; A multi-line comment should be on its own line. | ||
| 333 | (save-excursion (forward-comment -1) | ||
| 334 | (when (< (point) eol) | ||
| 335 | (newline) | ||
| 336 | t))))) | ||
| 337 | (if (< (point) eol) (newline))))) | ||
| 338 | (goto-char (nth 1 ppss)) | ||
| 339 | (indent-region (line-beginning-position 2) end) | ||
| 340 | ;; Don't use the default filling code. | ||
| 341 | t))))))) | ||
| 342 | |||
| 343 | ;;; Navigation and indentation. | ||
| 344 | |||
| 345 | (defconst css-navigation-syntax-table | ||
| 346 | (let ((st (make-syntax-table css-mode-syntax-table))) | ||
| 347 | (map-char-table (lambda (c v) | ||
| 348 | ;; Turn punctuation (code = 1) into symbol (code = 1). | ||
| 349 | (if (eq (car-safe v) 1) | ||
| 350 | (aset st c (cons 3 (cdr v))))) | ||
| 351 | st) | ||
| 352 | st)) | ||
| 353 | |||
| 354 | (defun css-backward-sexp (n) | ||
| 355 | (let ((forward-sexp-function nil)) | ||
| 356 | (if (< n 0) (css-forward-sexp (- n)) | ||
| 357 | (while (> n 0) | ||
| 358 | (setq n (1- n)) | ||
| 359 | (forward-comment (- (point-max))) | ||
| 360 | (if (not (eq (char-before) ?\;)) | ||
| 361 | (backward-sexp 1) | ||
| 362 | (while (progn (backward-sexp 1) | ||
| 363 | (save-excursion | ||
| 364 | (forward-comment (- (point-max))) | ||
| 365 | ;; FIXME: We should also skip punctuation. | ||
| 366 | (not (memq (char-before) '(?\; ?\{))))))))))) | ||
| 367 | |||
| 368 | (defun css-forward-sexp (n) | ||
| 369 | (let ((forward-sexp-function nil)) | ||
| 370 | (if (< n 0) (css-backward-sexp (- n)) | ||
| 371 | (while (> n 0) | ||
| 372 | (setq n (1- n)) | ||
| 373 | (forward-comment (point-max)) | ||
| 374 | (if (not (eq (char-after) ?\;)) | ||
| 375 | (forward-sexp 1) | ||
| 376 | (while (progn (forward-sexp 1) | ||
| 377 | (save-excursion | ||
| 378 | (forward-comment (point-max)) | ||
| 379 | ;; FIXME: We should also skip punctuation. | ||
| 380 | (not (memq (char-after) '(?\; ?\}))))))))))) | ||
| 381 | |||
| 382 | (defun css-indent-calculate-virtual () | ||
| 383 | (if (or (save-excursion (skip-chars-backward " \t") (bolp)) | ||
| 384 | (if (looking-at "\\s(") | ||
| 385 | (save-excursion | ||
| 386 | (forward-char 1) (skip-chars-forward " \t") | ||
| 387 | (not (or (eolp) (looking-at comment-start-skip)))))) | ||
| 388 | (current-column) | ||
| 389 | (css-indent-calculate))) | ||
| 390 | |||
| 391 | (defcustom css-indent-offset 4 | ||
| 392 | "Basic size of one indentation step." | ||
| 393 | :type 'integer) | ||
| 394 | |||
| 395 | (defun css-indent-calculate () | ||
| 396 | (let ((ppss (syntax-ppss)) | ||
| 397 | pos) | ||
| 398 | (with-syntax-table css-navigation-syntax-table | ||
| 399 | (save-excursion | ||
| 400 | (cond | ||
| 401 | ;; Inside a string. | ||
| 402 | ((nth 3 ppss) 'noindent) | ||
| 403 | ;; Inside a comment. | ||
| 404 | ((nth 4 ppss) | ||
| 405 | (setq pos (point)) | ||
| 406 | (forward-line -1) | ||
| 407 | (skip-chars-forward " \t") | ||
| 408 | (if (>= (nth 8 ppss) (point)) | ||
| 409 | (progn | ||
| 410 | (goto-char (nth 8 ppss)) | ||
| 411 | (if (eq (char-after pos) ?*) | ||
| 412 | (forward-char 1) | ||
| 413 | (if (not (looking-at comment-start-skip)) | ||
| 414 | (error "Internal css-mode error") | ||
| 415 | (goto-char (match-end 0)))) | ||
| 416 | (current-column)) | ||
| 417 | (if (and (eq (char-after pos) ?*) (eq (char-after) ?*)) | ||
| 418 | (current-column) | ||
| 419 | ;; 'noindent | ||
| 420 | (current-column) | ||
| 421 | ))) | ||
| 422 | ;; In normal code. | ||
| 423 | (t | ||
| 424 | (or | ||
| 425 | (when (looking-at "\\s)") | ||
| 426 | (forward-char 1) | ||
| 427 | (backward-sexp 1) | ||
| 428 | (css-indent-calculate-virtual)) | ||
| 429 | (when (looking-at comment-start-skip) | ||
| 430 | (forward-comment (point-max)) | ||
| 431 | (css-indent-calculate)) | ||
| 432 | (when (save-excursion (forward-comment (- (point-max))) | ||
| 433 | (setq pos (point)) | ||
| 434 | (eq (char-syntax (preceding-char)) ?\()) | ||
| 435 | (goto-char (1- pos)) | ||
| 436 | (if (not (looking-at "\\s([ \t]*")) | ||
| 437 | (error "Internal css-mode error") | ||
| 438 | (if (or (memq (char-after (match-end 0)) '(?\n nil)) | ||
| 439 | (save-excursion (goto-char (match-end 0)) | ||
| 440 | (looking-at comment-start-skip))) | ||
| 441 | (+ (css-indent-calculate-virtual) css-indent-offset) | ||
| 442 | (progn (goto-char (match-end 0)) (current-column))))) | ||
| 443 | (progn | ||
| 444 | (css-backward-sexp 1) | ||
| 445 | (if (looking-at "\\s(") | ||
| 446 | (css-indent-calculate) | ||
| 447 | (css-indent-calculate-virtual)))))))))) | ||
| 448 | |||
| 449 | |||
| 450 | (defun css-indent-line () | ||
| 451 | "Indent current line according to CSS indentation rules." | ||
| 452 | (interactive) | ||
| 453 | (let* ((savep (point)) | ||
| 454 | (forward-sexp-function nil) | ||
| 455 | (indent (condition-case nil | ||
| 456 | (save-excursion | ||
| 457 | (forward-line 0) | ||
| 458 | (skip-chars-forward " \t") | ||
| 459 | (if (>= (point) savep) (setq savep nil)) | ||
| 460 | (css-indent-calculate)) | ||
| 461 | (error nil)))) | ||
| 462 | (if (not (numberp indent)) 'noindent | ||
| 463 | (if savep | ||
| 464 | (save-excursion (indent-line-to indent)) | ||
| 465 | (indent-line-to indent))))) | ||
| 466 | |||
| 467 | (provide 'css-mode) | ||
| 468 | ;; arch-tag: b4d8b8e2-b130-4e74-b3aa-cd8f1ab659d0 | ||
| 469 | ;;; css-mode.el ends here | ||