EasyPG 1.07 Released
[packages] / xemacs-packages / text-modes / css-mode.el.upstream
1
2 ;;;; A major mode for editing CSS.
3
4 ;;; Adds font locking, some rather primitive indentation handling and
5 ;;; some typing help.
6 ;;;
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 $
11
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.
16 ;;;
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.
21 ;;;
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.
25
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
29 ; the rest.
30
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.
35
36 ; Thanks to Philippe Le Hegaret, Kjetil Kjernsmo, Alf-Ivar Holm and
37 ; Alfred Correira for much useful feedback. Alf-Ivar Holm also contributed
38 ; patches.
39
40 ; To install, put this in your .emacs:
41 ;
42 ; (autoload 'css-mode "css-mode")
43 ; (setq auto-mode-alist       
44 ;      (cons '("\\.css\\'" . css-mode) auto-mode-alist))
45
46 ;; Todo:
47
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
51
52 ;; Possible later additions:
53 ;
54 ; - forward/backward style/@media rule commands
55 ; - more complete syntax table
56
57 ;; Required modules
58
59 (require 'apropos)
60 (require 'font-lock)
61 (require 'cl)
62
63 ;;; The code itself
64
65 ; Customizable variables:
66
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
70   insertion.")
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-old-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.")
77   
78 ; The rest of the code:
79
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"
94
95     ; CSS level 2:
96
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.")
110
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.")
115
116 (defvar cssm-keywords 
117   (append '("!\\s-*important"
118     
119           ; CSS level 2:
120
121             "@media" "@import" "@page" "@font-face")
122           (mapcar (lambda(property)
123                     (concat property "\\s-*:"))
124                   cssm-properties))
125   "A list of all CSS keywords.")
126
127 (defvar cssm-pseudos
128   '("link" "visited" "active" "first-line" "first-letter"
129
130     ; CSS level 2
131     "first-child" "before" "after" "hover")
132   "A list of all CSS pseudo-classes.")
133
134 ; internal
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 "\\|")))
140             altlist)
141     (concat (substring regexp 0 -2) ; cutting the last "\\|"
142             "\\)")
143     ))
144
145 (defvar cssm-font-lock-keywords
146   (list
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)
157    )
158   "Rules for highlighting CSS style sheets.")
159
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))
168
169 ;;; Cross-version compatibility layer
170
171 (when (not (or (apropos-macrop 'kbd)
172              (fboundp 'kbd)))
173     (defmacro kbd (keys)
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)))
178
179 ;;; Auto-indentation support
180
181 ; internal
182 (defun cssm-insert-right-brace-and-indent()
183   (interactive)
184   (insert "}")
185   (cssm-indent-line))
186
187 ; internal
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 {
193         )
194      ; Going to the accompanying {
195     (re-search-forward "{" (point-max) t)
196     (if (null atmedia)
197         nil  ; no @media before this point => not inside
198       (while (and (< (point) orig-pos)
199                   (< 0 balance))
200         (if (null (re-search-forward "[{}]" (point-max) 0))
201             (goto-char (point-max)) ; break
202           (setq balance
203                 (if (string= (match-string 0) "{")
204                     (+ balance 1)
205                   (- balance 1)))))
206       (= balance 1))
207     ))
208
209 ; internal
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)))
213     (if (null result)
214         nil
215       (string= (match-string 0) "@"))))
216
217 ; internal
218 (defun cssm-find-column(first-char)
219   "Find which column to indent to." 
220
221   ; Find out where to indent to by looking at previous lines
222   ; spinning backwards over comments
223   (let (pos)
224     (while (and (setq pos (re-search-backward (cssm-list-2-regexp
225                                                '("/\\*" "\\*/" "{" "}"))
226                                               (point-min) t))
227                 (string= (match-string 0) "*/"))
228       (search-backward "/*" (point-min) t))
229
230     ; did the last search find anything?
231     (if pos
232         (save-excursion
233           (let ((construct      (match-string 0))
234                 (column         (current-column)))
235             (apply cssm-indent-function
236                    (list (cond
237                           ((string= construct "{")
238                            (cond
239                             ((cssm-rule-is-atmedia)
240                              'inside-atmedia)
241                             ((cssm-inside-atmedia-rule)
242                              'inside-rule-and-atmedia)
243                             (t
244                              'inside-rule)))
245                           ((string= construct "/*")
246                            'inside-comment)
247                           ((string= construct "}")
248                            (if (cssm-inside-atmedia-rule)
249                                'inside-atmedia
250                              'outside))
251                           (t 'outside))
252                          column
253                          first-char))))
254       
255       (apply cssm-indent-function
256              (list 'outside
257                    (current-column)
258                    first-char)))))
259
260 (defun cssm-indent-line()
261   "Indents the current line."
262   (interactive)
263   (beginning-of-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)))
268
269     (goto-char beg-of-line)
270
271     (let ((indent-column (cssm-find-column first)))
272       (goto-char beg-of-line)
273
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))
278
279       (goto-char beg-of-line)
280     
281       ; Indent
282       (while (< 0 indent-column)
283         (insert " ")
284         (setq indent-column (- indent-column 1))))))
285
286 ;;; Indent-style functions
287
288 (defun cssm-old-style-indenter(position column first-char-on-line)
289   (cond
290    ((eq position 'inside-atmedia)
291     (if (string= "}" first-char-on-line)
292         0
293       cssm-indent-level))
294    
295    ((eq position 'inside-rule)
296     (+ column 2))
297
298    ((eq position 'inside-rule-and-atmedia)
299     (+ column 2))
300
301    ((eq position 'inside-comment)
302     (+ column 3))
303
304    ((eq position 'outside)
305     0)))
306
307 (defun cssm-c-style-indenter(position column first-char-on-line)
308   (cond
309    ((or (eq position 'inside-atmedia)
310         (eq position 'inside-rule))
311     (if (string= "}" first-char-on-line)
312         0
313       cssm-indent-level))
314
315    ((eq position 'inside-rule-and-atmedia)
316     (if (string= "}" first-char-on-line)
317         cssm-indent-level
318       (* 2 cssm-indent-level)))
319
320    ((eq position 'inside-comment)
321     (+ column 3))
322
323    ((eq position 'outside)
324     0)))
325
326 ;;; Typing shortcuts
327
328 (define-skeleton cssm-insert-curlies
329   "Inserts a pair of matching curly parenthesises." nil
330   "{ " _ (if cssm-newline-before-closing-bracket "\n" " ")
331   "}")
332
333 (define-skeleton cssm-insert-quotes
334   "Inserts a pair of matching quotes." nil
335   "\"" _ "\"")
336
337 (define-skeleton cssm-insert-parenthesises
338   "Inserts a pair of matching parenthesises." nil
339   "(" _ ")")
340
341 (define-skeleton cssm-insert-comment
342   "Inserts a full comment." nil
343   "/* " _ " */")
344
345 (define-skeleton cssm-insert-url
346   "Inserts a URL." nil
347   "url(" _ ")")
348
349 (define-skeleton cssm-insert-brackets
350   "Inserts a pair of matching brackets." nil
351   "[" _ "]")
352
353 (defun cssm-enter-mirror-mode()
354   "Turns on mirror mode, where quotes, brackets etc are mirrored automatically
355   on insertion."
356   (interactive)
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))
361
362 (defun cssm-leave-mirror-mode()
363   "Turns off mirror mode."
364   (interactive)
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))
369
370 ;;; Property completion
371
372 (defun cssm-property-at-point()
373   "If point is at the end of a property name: returns it."
374   (let ((end (point))
375         (start (+ (re-search-backward "[^-A-Za-z]") 1)))
376     (goto-char end)
377     (buffer-substring start end)))
378
379 ; internal
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)))
389
390 ; internal
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)))))
396
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))
403       (erase-buffer)
404       (let ((standard-output (current-buffer)))
405         (display-completion-list (sort completions 'string<)))
406       (goto-char (point-min))
407       (pop-to-buffer cur))))
408
409 (defun cssm-complete-property()
410   "Completes the CSS property being typed at point."
411   (interactive)
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))
420           (insert (substring
421                    (completing-read "Property: " cssm-properties-alist nil
422                                     nil prop)
423                    proplen)))))))
424
425 (defun css-mode()
426   "Major mode for editing CSS style sheets.
427 \\{cssm-mode-map}"
428   (interactive)
429
430   ; Initializing
431   (kill-all-local-variables)
432
433   ; Setting up indentation handling
434   (make-local-variable 'indent-line-function)
435   (setq indent-line-function 'cssm-indent-line)
436   
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))
440
441   ; Setting up typing shortcuts
442   (make-local-variable 'skeleton-end-hook)
443   (setq skeleton-end-hook nil)
444   
445   (when cssm-mirror-mode
446     (cssm-enter-mirror-mode))
447   
448   (use-local-map cssm-mode-map)
449   
450   ; Setting up syntax recognition
451   (make-local-variable 'comment-start)
452   (make-local-variable 'comment-end)
453   (make-local-variable 'comment-start-skip)
454
455   (setq comment-start "/* "
456         comment-end " */"
457         comment-start-skip "/\\*[ \n\t]+")
458
459   ; Setting up syntax table
460   (modify-syntax-entry ?* ". 23")
461   (modify-syntax-entry ?/ ". 14")
462   
463   ; Final stuff, then we're done
464   (setq mode-name "CSS"
465         major-mode 'css-mode)
466   (run-hooks 'css-mode-hook))
467
468 (provide 'css-mode)
469
470 ;; CSS-mode ends here