2 ;;;; A major mode for editing CSS.
4 ;;; Adds font locking, some rather primitive indentation handling and
7 (defvar cssm-version
"0.11"
8 "The current version number of css-mode.")
9 ;;; copyright (c) 1998 Lars Marius Garshol, larsga@ifi.uio.no
10 ;;; $Id: css-mode.el,v 1.9 2000/01/05 21:21:56 larsga Exp $
12 ;;; css-mode is free software; you can redistribute it and/or modify
13 ;;; it under the terms of the GNU General Public License as published
14 ;;; by the Free Software Foundation; either version 2, or (at your
15 ;;; option) any later version.
17 ;;; css-mode is distributed in the hope that it will be useful, but
18 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 ;;; General Public License for more details.
22 ;;; You should have received a copy of the GNU General Public License
23 ;;; along with GNU Emacs; see the file COPYING. If not, write to the
24 ;;; Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
26 ; Send me an email if you want new features (or if you add them yourself).
27 ; I will do my best to preserve the API to functions not explicitly marked
28 ; as internal and variables shown as customizable. I make no promises about
31 ; Bug reports are very welcome. New versions of the package will appear at
32 ; http://www.stud.ifi.uio.no/~larsga/download/css-mode.html
33 ; You can register at the same address if you want to be notified when a
34 ; new version appears.
36 ; Thanks to Philippe Le Hegaret, Kjetil Kjernsmo, Alf-Ivar Holm and
37 ; Alfred Correira for much useful feedback. Alf-Ivar Holm also contributed
40 ; To install, put this in your .emacs:
42 ; (autoload 'css-mode "css-mode")
43 ; (setq auto-mode-alist
44 ; (cons '("\\.css\\'" . css-mode) auto-mode-alist))
48 ; - must not color URL file name extensions as class selectors (*.css)
49 ; - color [] and url() constructs correctly, even if quoted strings present
50 ; - disregard anything inside strings
52 ;; Possible later additions:
54 ; - forward/backward style/@media rule commands
55 ; - more complete syntax table
65 ; Customizable variables:
67 (defvar cssm-indent-level
2 "The indentation level inside @media rules.")
68 (defvar cssm-mirror-mode t
69 "Whether brackets, quotes etc should be mirrored automatically on
71 (defvar cssm-newline-before-closing-bracket nil
72 "In mirror-mode, controls whether a newline should be inserted before the
73 closing bracket or not.")
74 (defvar cssm-indent-function
#'cssm-c-style-indenter
75 "Which function to use when deciding which column to indent to. To get
76 C-style indentation, use cssm-c-style-indenter.")
78 ; The rest of the code:
80 (defvar cssm-properties
81 '("font-family" "font-style" "font-variant" "font-weight"
82 "font-size" "font" "background-color" "background-image"
83 "background-repeat" "background-attachment" "background-position"
84 "color" "background" "word-spacing" "letter-spacing"
85 "border-top-width" "border-right-width" "border-left-width"
86 "border-bottom-width" "border-width" "list-style-type"
87 "list-style-image" "list-style-position" "text-decoration"
88 "vertical-align" "text-transform" "text-align" "text-indent"
89 "line-height" "margin-top" "margin-right" "margin-bottom"
90 "margin-left" "margin" "padding-top" "padding-right" "padding-bottom"
91 "padding-left" "padding" "border-top" "border-right" "border-bottom"
92 "border-left" "border" "width" "height" "float" "clear" "display"
93 "list-style" "white-space" "border-style" "border-color"
97 "azimuth" "border-bottom-color" "border-bottom-style"
98 "border-collapse" "border-left-color" "border-left-style"
99 "border-right-color" "border-right-style" "border-top-color"
100 "border-top-style" "caption-side" "cell-spacing" "clip" "column-span"
101 "content" "cue" "cue-after" "cue-before" "cursor" "direction"
102 "elevation" "font-size-adjust" "left" "marks" "max-height" "max-width"
103 "min-height" "min-width" "orphans" "overflow" "page-break-after"
104 "page-break-before" "pause" "pause-after" "pause-before" "pitch"
105 "pitch-range" "play-during" "position" "richness" "right" "row-span"
106 "size" "speak" "speak-date" "speak-header" "speak-punctuation"
107 "speak-time" "speech-rate" "stress" "table-layout" "text-shadow" "top"
108 "visibility" "voice-family" "volume" "widows" "z-index")
109 "A list of all CSS properties.")
111 (defvar cssm-properties-alist
112 (mapcar (lambda(prop)
113 (cons (concat prop
":") nil
)) cssm-properties
)
114 "An association list of the CSS properties for completion use.")
116 (defvar cssm-keywords
117 (append '("!\\s-*important"
121 "@media" "@import" "@page" "@font-face")
122 (mapcar (lambda(property)
123 (concat property
"\\s-*:"))
125 "A list of all CSS keywords.")
128 '("link" "visited" "active" "first-line" "first-letter"
131 "first-child" "before" "after" "hover")
132 "A list of all CSS pseudo-classes.")
135 (defun cssm-list-2-regexp(altlist)
136 "Takes a list and returns the regexp \\(elem1\\|elem2\\|...\\)"
137 (let ((regexp "\\("))
138 (mapcar (lambda(elem)
139 (setq regexp
(concat regexp elem
"\\|")))
141 (concat (substring regexp
0 -
2) ; cutting the last "\\|"
145 (defvar cssm-font-lock-keywords
147 (cons (cssm-list-2-regexp cssm-keywords
) font-lock-keyword-face
)
148 (cons "\\.[a-zA-Z][-a-zA-Z0-9.]+" font-lock-variable-name-face
)
149 (cons (concat ":" (cssm-list-2-regexp cssm-pseudos
))
150 font-lock-variable-name-face
)
151 (cons "#[a-fA-F0-9][a-fA-F0-9][a-fA-F0-9]\\([a-fA-F0-9][a-fA-F0-9][a-fA-F0-9]\\)?"
152 font-lock-reference-face
)
153 (cons "\\[.*\\]" font-lock-variable-name-face
)
154 (cons "#[-a-zA-Z0-9]*" font-lock-function-name-face
)
155 (cons "rgb(\\s-*[0-9]+\\(\\.[0-9]+\\s-*%\\s-*\\)?\\s-*,\\s-*[0-9]+\\(\\.[0-9]+\\s-*%\\s-*\\)?\\s-*,\\s-*[0-9]+\\(\\.[0-9]+\\s-*%\\s-*\\)?\\s-*)"
156 font-lock-reference-face
)
158 "Rules for highlighting CSS style sheets.")
160 (defvar cssm-mode-map
()
161 "Keymap used in CSS mode.")
162 (when (not cssm-mode-map
)
163 (setq cssm-mode-map
(make-sparse-keymap))
164 (define-key cssm-mode-map
(read-kbd-macro "C-c C-c") 'cssm-insert-comment
)
165 (define-key cssm-mode-map
(read-kbd-macro "C-c C-u") 'cssm-insert-url
)
166 (define-key cssm-mode-map
(read-kbd-macro "}") 'cssm-insert-right-brace-and-indent
)
167 (define-key cssm-mode-map
(read-kbd-macro "M-TAB") 'cssm-complete-property
))
169 ;;; Cross-version compatibility layer
171 (when (not (or (apropos-macrop 'kbd
)
174 "Convert KEYS to the internal Emacs key representation.
175 KEYS should be a string constant in the format used for
176 saving keyboard macros (see `insert-kbd-macro')."
177 (read-kbd-macro keys
)))
179 ;;; Auto-indentation support
182 (defun cssm-insert-right-brace-and-indent()
188 (defun cssm-inside-atmedia-rule()
189 "Decides if point is currently inside an @media rule."
190 (let ((orig-pos (point))
191 (atmedia (re-search-backward "@media" 0 t
))
192 (balance 1) ; used to keep the {} balance, 1 because we start on a {
194 ; Going to the accompanying {
195 (re-search-forward "{" (point-max) t
)
197 nil
; no @media before this point => not inside
198 (while (and (< (point) orig-pos
)
200 (if (null (re-search-forward "[{}]" (point-max) 0))
201 (goto-char (point-max)) ; break
203 (if (string= (match-string 0) "{")
210 (defun cssm-rule-is-atmedia()
211 "Decides if point is currently on the { of an @media or ordinary style rule."
212 (let ((result (re-search-backward "[@}{]" 0 t
)))
215 (string= (match-string 0) "@"))))
218 (defun cssm-find-column(first-char)
219 "Find which column to indent to."
221 ; Find out where to indent to by looking at previous lines
222 ; spinning backwards over comments
224 (while (and (setq pos
(re-search-backward (cssm-list-2-regexp
225 '("/\\*" "\\*/" "{" "}"))
227 (string= (match-string 0) "*/"))
228 (search-backward "/*" (point-min) t
))
230 ; did the last search find anything?
233 (let ((construct (match-string 0))
234 (column (current-column)))
235 (apply cssm-indent-function
237 ((string= construct
"{")
239 ((cssm-rule-is-atmedia)
241 ((cssm-inside-atmedia-rule)
242 'inside-rule-and-atmedia
)
245 ((string= construct
"/*")
247 ((string= construct
"}")
248 (if (cssm-inside-atmedia-rule)
255 (apply cssm-indent-function
260 (defun cssm-indent-line()
261 "Indents the current line."
264 (let* ((beg-of-line (point))
265 (pos (re-search-forward "[]@#a-zA-Z0-9;,.\"{}/*\n:[]" (point-max) t
))
266 (first (match-string 0))
267 (start (match-beginning 0)))
269 (goto-char beg-of-line
)
271 (let ((indent-column (cssm-find-column first
)))
272 (goto-char beg-of-line
)
274 ; Remove all leading whitespace on this line (
275 (if (not (or (null pos
)
276 (= beg-of-line start
)))
277 (kill-region beg-of-line start
))
279 (goto-char beg-of-line
)
282 (while (< 0 indent-column
)
284 (setq indent-column
(- indent-column
1))))))
286 ;;; Indent-style functions
288 (defun cssm-old-style-indenter(position column first-char-on-line
)
290 ((eq position
'inside-atmedia
)
291 (if (string= "}" first-char-on-line
)
295 ((eq position
'inside-rule
)
298 ((eq position
'inside-rule-and-atmedia
)
301 ((eq position
'inside-comment
)
304 ((eq position
'outside
)
307 (defun cssm-c-style-indenter(position column first-char-on-line
)
309 ((or (eq position
'inside-atmedia
)
310 (eq position
'inside-rule
))
311 (if (string= "}" first-char-on-line
)
315 ((eq position
'inside-rule-and-atmedia
)
316 (if (string= "}" first-char-on-line
)
318 (* 2 cssm-indent-level
)))
320 ((eq position
'inside-comment
)
323 ((eq position
'outside
)
328 (define-skeleton cssm-insert-curlies
329 "Inserts a pair of matching curly parenthesises." nil
330 "{ " _
(if cssm-newline-before-closing-bracket
"\n" " ")
333 (define-skeleton cssm-insert-quotes
334 "Inserts a pair of matching quotes." nil
337 (define-skeleton cssm-insert-parenthesises
338 "Inserts a pair of matching parenthesises." nil
341 (define-skeleton cssm-insert-comment
342 "Inserts a full comment." nil
345 (define-skeleton cssm-insert-url
349 (define-skeleton cssm-insert-brackets
350 "Inserts a pair of matching brackets." nil
353 (defun cssm-enter-mirror-mode()
354 "Turns on mirror mode, where quotes, brackets etc are mirrored automatically
357 (define-key cssm-mode-map
(read-kbd-macro "{") 'cssm-insert-curlies
)
358 (define-key cssm-mode-map
(read-kbd-macro "\"") 'cssm-insert-quotes
)
359 (define-key cssm-mode-map
(read-kbd-macro "(") 'cssm-insert-parenthesises
)
360 (define-key cssm-mode-map
(read-kbd-macro "[") 'cssm-insert-brackets
))
362 (defun cssm-leave-mirror-mode()
363 "Turns off mirror mode."
365 (define-key cssm-mode-map
(read-kbd-macro "{") 'self-insert-command
)
366 (define-key cssm-mode-map
(read-kbd-macro "\"") 'self-insert-command
)
367 (define-key cssm-mode-map
(read-kbd-macro "(") 'self-insert-command
)
368 (define-key cssm-mode-map
(read-kbd-macro "[") 'self-insert-command
))
370 ;;; Property completion
372 (defun cssm-property-at-point()
373 "If point is at the end of a property name: returns it."
375 (start (+ (re-search-backward "[^-A-Za-z]") 1)))
377 (buffer-substring start end
)))
380 (defun cssm-maximum-common(alt1 alt2
)
381 "Returns the maximum common starting substring of alt1 and alt2."
382 (let* ((maxlen (min (length alt1
) (length alt2
)))
383 (alt1 (substring alt1
0 maxlen
))
384 (alt2 (substring alt2
0 maxlen
)))
385 (while (not (string= (substring alt1
0 maxlen
)
386 (substring alt2
0 maxlen
)))
387 (setq maxlen
(- maxlen
1)))
388 (substring alt1
0 maxlen
)))
391 (defun cssm-common-beginning(alts)
392 "Returns the maximum common starting substring of all alts elements."
393 (let ((common (car alts
)))
394 (dolist (alt (cdr alts
) common
)
395 (setq common
(cssm-maximum-common alt common
)))))
397 (defun cssm-complete-property-frame(completions)
398 ; This code stolen from message.el. Kudos to larsi.
399 (let ((cur (current-buffer)))
400 (pop-to-buffer "*Completions*")
401 (buffer-disable-undo (current-buffer))
402 (let ((buffer-read-only nil
))
404 (let ((standard-output (current-buffer)))
405 (display-completion-list (sort completions
'string
<)))
406 (goto-char (point-min))
407 (pop-to-buffer cur
))))
409 (defun cssm-complete-property()
410 "Completes the CSS property being typed at point."
412 (let* ((prop (cssm-property-at-point))
413 (alts (all-completions prop cssm-properties-alist
))
414 (proplen (length prop
)))
415 (if (= (length alts
) 1)
416 (insert (substring (car alts
) proplen
))
417 (let ((beg (cssm-common-beginning alts
)))
418 (if (not (string= beg prop
))
419 (insert (substring beg proplen
))
421 (completing-read "Property: " cssm-properties-alist nil
426 "Major mode for editing CSS style sheets.
431 (kill-all-local-variables)
433 ; Setting up indentation handling
434 (make-local-variable 'indent-line-function
)
435 (setq indent-line-function
'cssm-indent-line
)
437 ; Setting up font-locking
438 (make-local-variable 'font-lock-defaults
)
439 (setq font-lock-defaults
'(cssm-font-lock-keywords nil t nil nil
))
441 ; Setting up typing shortcuts
442 (make-local-variable 'skeleton-end-hook
)
443 (setq skeleton-end-hook nil
)
445 (when cssm-mirror-mode
446 (cssm-enter-mirror-mode))
448 (use-local-map cssm-mode-map
)
450 ; Setting up syntax recognition
451 (make-local-variable 'comment-start
)
452 (make-local-variable 'comment-end
)
453 (make-local-variable 'comment-start-skip
)
455 (setq comment-start
"/* "
457 comment-start-skip
"/\\*[ \n\t]+")
459 ; Setting up syntax table
460 (modify-syntax-entry ?
* ". 23")
461 (modify-syntax-entry ?
/ ". 14")
463 ; Final stuff, then we're done
464 (setq mode-name
"CSS"
465 major-mode
'css-mode
)
466 (run-hooks 'css-mode-hook
))
470 ;; CSS-mode ends here