]> code.delx.au - gnu-emacs/blob - lisp/textmodes/css-mode.el
e30fb3e6d14128792bc54418a80994f64ceb3463
[gnu-emacs] / lisp / textmodes / css-mode.el
1 ;;; css-mode.el --- Major mode to edit CSS files -*- lexical-binding: t -*-
2
3 ;; Copyright (C) 2006-2016 Free Software Foundation, Inc.
4
5 ;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
6 ;; Maintainer: Simen Heggestøyl <simenheg@gmail.com>
7 ;; Keywords: hypermedia
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs is free software: you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation, either version 3 of the License, or
14 ;; (at your option) any later version.
15
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
23
24 ;;; Commentary:
25
26 ;; Yet another CSS mode.
27
28 ;;; Todo:
29
30 ;; - electric ; and }
31 ;; - filling code with auto-fill-mode
32 ;; - fix font-lock errors with multi-line selectors
33
34 ;;; Code:
35
36 (require 'seq)
37 (require 'smie)
38
39 (defgroup css nil
40 "Cascading Style Sheets (CSS) editing mode."
41 :group 'languages)
42
43 (defconst css-pseudo-class-ids
44 '("active" "checked" "disabled" "empty" "enabled" "first"
45 "first-child" "first-of-type" "focus" "hover" "indeterminate" "lang"
46 "last-child" "last-of-type" "left" "link" "not" "nth-child"
47 "nth-last-child" "nth-last-of-type" "nth-of-type" "only-child"
48 "only-of-type" "right" "root" "target" "visited")
49 "Identifiers for pseudo-classes.")
50
51 (defconst css-pseudo-element-ids
52 '("after" "before" "first-letter" "first-line")
53 "Identifiers for pseudo-elements.")
54
55 (defconst css-at-ids
56 '("charset" "font-face" "import" "keyframes" "media" "namespace"
57 "page")
58 "Identifiers that appear in the form @foo.")
59
60 (defconst scss-at-ids
61 '("at-root" "content" "debug" "each" "else" "else if" "error" "extend"
62 "for" "function" "if" "import" "include" "mixin" "return" "warn"
63 "while")
64 "Additional identifiers that appear in the form @foo in SCSS.")
65
66 (defvar css--at-ids css-at-ids
67 "List of at-rules for the current mode.")
68 (make-variable-buffer-local 'css--at-ids)
69
70 (defconst css-bang-ids
71 '("important")
72 "Identifiers that appear in the form !foo.")
73
74 (defconst scss-bang-ids
75 '("default" "global" "optional")
76 "Additional identifiers that appear in the form !foo in SCSS.")
77
78 (defvar css--bang-ids css-bang-ids
79 "List of bang-rules for the current mode.")
80 (make-variable-buffer-local 'css--bang-ids)
81
82 (defconst css-descriptor-ids
83 '("ascent" "baseline" "bbox" "cap-height" "centerline" "definition-src"
84 "descent" "font-family" "font-size" "font-stretch" "font-style"
85 "font-variant" "font-weight" "mathline" "panose-1" "slope" "src" "stemh"
86 "stemv" "topline" "unicode-range" "units-per-em" "widths" "x-height")
87 "Identifiers for font descriptors.")
88
89 (defconst css-media-ids
90 '("all" "aural" "bitmap" "continuous" "grid" "paged" "static" "tactile"
91 "visual")
92 "Identifiers for types of media.")
93
94 (defconst css-property-alist
95 ;; CSS 2.1 properties (http://www.w3.org/TR/CSS21/propidx.html).
96 ;;
97 ;; Properties duplicated by any of the CSS3 modules below have been
98 ;; removed.
99 '(("azimuth" angle "left-side" "far-left" "left" "center-left"
100 "center" "center-right" "right" "far-right" "right-side" "behind"
101 "leftwards" "rightwards")
102 ("border-collapse" "collapse" "separate")
103 ("border-spacing" length)
104 ("bottom" length percentage "auto")
105 ("caption-side" "top" "bottom")
106 ("clear" "none" "left" "right" "both")
107 ("clip" shape "auto")
108 ("content" "normal" "none" string uri counter "attr()"
109 "open-quote" "close-quote" "no-open-quote" "no-close-quote")
110 ("counter-increment" identifier integer "none")
111 ("counter-reset" identifier integer "none")
112 ("cue" cue-before cue-after)
113 ("cue-after" uri "none")
114 ("cue-before" uri "none")
115 ("direction" "ltr" "rtl")
116 ("display" "inline" "block" "list-item" "inline-block" "table"
117 "inline-table" "table-row-group" "table-header-group"
118 "table-footer-group" "table-row" "table-column-group"
119 "table-column" "table-cell" "table-caption" "none"
120 ;; CSS Flexible Box Layout Module Level 1
121 ;; (https://www.w3.org/TR/css3-flexbox/#valdef-display-flex)
122 "flex" "inline-flex")
123 ("elevation" angle "below" "level" "above" "higher" "lower")
124 ("empty-cells" "show" "hide")
125 ("float" "left" "right" "none")
126 ("height" length percentage "auto")
127 ("left" length percentage "auto")
128 ("line-height" "normal" number length percentage)
129 ("list-style" list-style-type list-style-position
130 list-style-image)
131 ("list-style-image" uri "none")
132 ("list-style-position" "inside" "outside")
133 ("list-style-type" "disc" "circle" "square" "decimal"
134 "decimal-leading-zero" "lower-roman" "upper-roman" "lower-greek"
135 "lower-latin" "upper-latin" "armenian" "georgian" "lower-alpha"
136 "upper-alpha" "none")
137 ("margin" margin-width)
138 ("margin-bottom" margin-width)
139 ("margin-left" margin-width)
140 ("margin-right" margin-width)
141 ("margin-top" margin-width)
142 ("max-height" length percentage "none")
143 ("max-width" length percentage "none")
144 ("min-height" length percentage)
145 ("min-width" length percentage)
146 ("padding" padding-width)
147 ("padding-bottom" padding-width)
148 ("padding-left" padding-width)
149 ("padding-right" padding-width)
150 ("padding-top" padding-width)
151 ("page-break-after" "auto" "always" "avoid" "left" "right")
152 ("page-break-before" "auto" "always" "avoid" "left" "right")
153 ("page-break-inside" "avoid" "auto")
154 ("pause" time percentage)
155 ("pause-after" time percentage)
156 ("pause-before" time percentage)
157 ("pitch" frequency "x-low" "low" "medium" "high" "x-high")
158 ("pitch-range" number)
159 ("play-during" uri "mix" "repeat" "auto" "none")
160 ("position" "static" "relative" "absolute" "fixed")
161 ("quotes" string "none")
162 ("richness" number)
163 ("right" length percentage "auto")
164 ("speak" "normal" "none" "spell-out")
165 ("speak-header" "once" "always")
166 ("speak-numeral" "digits" "continuous")
167 ("speak-punctuation" "code" "none")
168 ("speech-rate" number "x-slow" "slow" "medium" "fast" "x-fast"
169 "faster" "slower")
170 ("stress" number)
171 ("table-layout" "auto" "fixed")
172 ("top" length percentage "auto")
173 ("unicode-bidi" "normal" "embed" "bidi-override")
174 ("vertical-align" "baseline" "sub" "super" "top" "text-top"
175 "middle" "bottom" "text-bottom" percentage length)
176 ("visibility" "visible" "hidden" "collapse")
177 ("voice-family" specific-voice generic-voice specific-voice
178 generic-voice)
179 ("volume" number percentage "silent" "x-soft" "soft" "medium"
180 "loud" "x-loud")
181 ("width" length percentage "auto")
182 ("z-index" "auto" integer)
183
184 ;; CSS Animations
185 ;; (http://www.w3.org/TR/css3-animations/#property-index)
186 ("animation" single-animation-name time single-timing-function
187 single-animation-iteration-count single-animation-direction
188 single-animation-fill-mode single-animation-play-state)
189 ("animation-delay" time)
190 ("animation-direction" single-animation-direction)
191 ("animation-duration" time)
192 ("animation-fill-mode" single-animation-fill-mode)
193 ("animation-iteration-count" single-animation-iteration-count)
194 ("animation-name" single-animation-name)
195 ("animation-play-state" single-animation-play-state)
196 ("animation-timing-function" single-timing-function)
197
198 ;; CSS Backgrounds and Borders Module Level 3
199 ;; (http://www.w3.org/TR/css3-background/#property-index)
200 ("background" bg-layer final-bg-layer)
201 ("background-attachment" attachment)
202 ("background-clip" box)
203 ("background-color" color)
204 ("background-image" bg-image)
205 ("background-origin" box)
206 ("background-position" position)
207 ("background-repeat" repeat-style)
208 ("background-size" bg-size)
209 ("border" line-width line-style color)
210 ("border-bottom" line-width line-style color)
211 ("border-bottom-color" color)
212 ("border-bottom-left-radius" length percentage)
213 ("border-bottom-right-radius" length percentage)
214 ("border-bottom-style" line-style)
215 ("border-bottom-width" line-width)
216 ("border-color" color)
217 ("border-image" border-image-source border-image-slice
218 border-image-width border-image-outset border-image-repeat)
219 ("border-image-outset" length number)
220 ("border-image-repeat" "stretch" "repeat" "round" "space")
221 ("border-image-slice" number percentage "fill")
222 ("border-image-source" "none" image)
223 ("border-image-width" length percentage number "auto")
224 ("border-left" line-width line-style color)
225 ("border-left-color" color)
226 ("border-left-style" line-style)
227 ("border-left-width" line-width)
228 ("border-radius" length percentage)
229 ("border-right" line-width line-style color)
230 ("border-right-color" color)
231 ("border-right-style" line-style)
232 ("border-right-width" line-width)
233 ("border-style" line-style)
234 ("border-top" line-width line-style color)
235 ("border-top-color" color)
236 ("border-top-left-radius" length percentage)
237 ("border-top-right-radius" length percentage)
238 ("border-top-style" line-style)
239 ("border-top-width" line-width)
240 ("border-width" line-width)
241 ("box-shadow" "none" shadow)
242
243 ;; CSS Basic User Interface Module Level 3 (CSS3 UI)
244 ;; (http://www.w3.org/TR/css3-ui/#property-index)
245 ("box-sizing" "content-box" "border-box")
246 ("caret-color" "auto" color)
247 ("cursor" uri x y "auto" "default" "none" "context-menu" "help"
248 "pointer" "progress" "wait" "cell" "crosshair" "text"
249 "vertical-text" "alias" "copy" "move" "no-drop" "not-allowed"
250 "grab" "grabbing" "e-resize" "n-resize" "ne-resize" "nw-resize"
251 "s-resize" "se-resize" "sw-resize" "w-resize" "ew-resize"
252 "ns-resize" "nesw-resize" "nwse-resize" "col-resize" "row-resize"
253 "all-scroll" "zoom-in" "zoom-out")
254 ("nav-down" "auto" id "current" "root" target-name)
255 ("nav-left" "auto" id "current" "root" target-name)
256 ("nav-right" "auto" id "current" "root" target-name)
257 ("nav-up" "auto" id "current" "root" target-name)
258 ("outline" outline-color outline-style outline-width)
259 ("outline-color" color "invert")
260 ("outline-offset" length)
261 ("outline-style" "auto" border-style)
262 ("outline-width" border-width)
263 ("resize" "none" "both" "horizontal" "vertical")
264 ("text-overflow" "clip" "ellipsis" string)
265
266 ;; CSS Color Module Level 3
267 ;; (http://www.w3.org/TR/css3-color/#property)
268 ("color" color)
269 ("opacity" alphavalue)
270
271 ;; CSS Flexible Box Layout Module Level 1
272 ;; (http://www.w3.org/TR/css-flexbox-1/#property-index)
273 ("align-content" "flex-start" "flex-end" "center" "space-between"
274 "space-around" "stretch")
275 ("align-items" "flex-start" "flex-end" "center" "baseline"
276 "stretch")
277 ("align-self" "auto" "flex-start" "flex-end" "center" "baseline"
278 "stretch")
279 ("flex" "none" flex-grow flex-shrink flex-basis)
280 ("flex-basis" "auto" "content" width)
281 ("flex-direction" "row" "row-reverse" "column" "column-reverse")
282 ("flex-flow" flex-direction flex-wrap)
283 ("flex-grow" number)
284 ("flex-shrink" number)
285 ("flex-wrap" "nowrap" "wrap" "wrap-reverse")
286 ("justify-content" "flex-start" "flex-end" "center"
287 "space-between" "space-around")
288 ("order" integer)
289
290 ;; CSS Fonts Module Level 3
291 ;; (http://www.w3.org/TR/css3-fonts/#property-index)
292 ("font" font-style font-variant-css21 font-weight font-stretch
293 font-size line-height font-family "caption" "icon" "menu"
294 "message-box" "small-caption" "status-bar")
295 ("font-family" family-name generic-family)
296 ("font-feature-settings" "normal" feature-tag-value)
297 ("font-kerning" "auto" "normal" "none")
298 ("font-language-override" "normal" string)
299 ("font-size" absolute-size relative-size length percentage)
300 ("font-size-adjust" "none" number)
301 ("font-stretch" "normal" "ultra-condensed" "extra-condensed"
302 "condensed" "semi-condensed" "semi-expanded" "expanded"
303 "extra-expanded" "ultra-expanded")
304 ("font-style" "normal" "italic" "oblique")
305 ("font-synthesis" "none" "weight" "style")
306 ("font-variant" "normal" "none" common-lig-values
307 discretionary-lig-values historical-lig-values
308 contextual-alt-values "stylistic()" "historical-forms"
309 "styleset()" "character-variant()" "swash()" "ornaments()"
310 "annotation()" "small-caps" "all-small-caps" "petite-caps"
311 "all-petite-caps" "unicase" "titling-caps" numeric-figure-values
312 numeric-spacing-values numeric-fraction-values "ordinal"
313 "slashed-zero" east-asian-variant-values east-asian-width-values
314 "ruby")
315 ("font-variant-alternates" "normal" "stylistic()"
316 "historical-forms" "styleset()" "character-variant()" "swash()"
317 "ornaments()" "annotation()")
318 ("font-variant-caps" "normal" "small-caps" "all-small-caps"
319 "petite-caps" "all-petite-caps" "unicase" "titling-caps")
320 ("font-variant-east-asian" "normal" east-asian-variant-values
321 east-asian-width-values "ruby")
322 ("font-variant-ligatures" "normal" "none" common-lig-values
323 discretionary-lig-values historical-lig-values
324 contextual-alt-values)
325 ("font-variant-numeric" "normal" numeric-figure-values
326 numeric-spacing-values numeric-fraction-values "ordinal"
327 "slashed-zero")
328 ("font-variant-position" "normal" "sub" "super")
329 ("font-weight" "normal" "bold" "bolder" "lighter" "100" "200"
330 "300" "400" "500" "600" "700" "800" "900")
331
332 ;; CSS Fragmentation Module Level 3
333 ;; (https://www.w3.org/TR/css-break-3/#property-index)
334 ("box-decoration-break" "slice" "clone")
335 ("break-after" "auto" "avoid" "avoid-page" "page" "left" "right"
336 "recto" "verso" "avoid-column" "column" "avoid-region" "region")
337 ("break-before" "auto" "avoid" "avoid-page" "page" "left" "right"
338 "recto" "verso" "avoid-column" "column" "avoid-region" "region")
339 ("break-inside" "auto" "avoid" "avoid-page" "avoid-column"
340 "avoid-region")
341 ("orphans" integer)
342 ("widows" integer)
343
344 ;; CSS Multi-column Layout Module
345 ;; (https://www.w3.org/TR/css3-multicol/#property-index)
346 ;; "break-after", "break-before", and "break-inside" are left out
347 ;; below, because they're already included in CSS Fragmentation
348 ;; Module Level 3.
349 ("column-count" integer "auto")
350 ("column-fill" "auto" "balance")
351 ("column-gap" length "normal")
352 ("column-rule" column-rule-width column-rule-style
353 column-rule-color "transparent")
354 ("column-rule-color" color)
355 ("column-rule-style" border-style)
356 ("column-rule-width" border-width)
357 ("column-span" "none" "all")
358 ("column-width" length "auto")
359 ("columns" column-width column-count)
360
361 ;; CSS Overflow Module Level 3
362 ;; (http://www.w3.org/TR/css-overflow-3/#property-index)
363 ("max-lines" "none" integer)
364 ("overflow" "visible" "hidden" "scroll" "auto" "paged-x" "paged-y"
365 "paged-x-controls" "paged-y-controls" "fragments")
366 ("overflow-x" "visible" "hidden" "scroll" "auto" "paged-x"
367 "paged-y" "paged-x-controls" "paged-y-controls" "fragments")
368 ("overflow-y" "visible" "hidden" "scroll" "auto" "paged-x"
369 "paged-y" "paged-x-controls" "paged-y-controls" "fragments")
370
371 ;; CSS Text Decoration Module Level 3
372 ;; (http://dev.w3.org/csswg/css-text-decor-3/#property-index)
373 ("text-decoration" text-decoration-line text-decoration-style
374 text-decoration-color)
375 ("text-decoration-color" color)
376 ("text-decoration-line" "none" "underline" "overline"
377 "line-through" "blink")
378 ("text-decoration-skip" "none" "objects" "spaces" "ink" "edges"
379 "box-decoration")
380 ("text-decoration-style" "solid" "double" "dotted" "dashed"
381 "wavy")
382 ("text-emphasis" text-emphasis-style text-emphasis-color)
383 ("text-emphasis-color" color)
384 ("text-emphasis-position" "over" "under" "right" "left")
385 ("text-emphasis-style" "none" "filled" "open" "dot" "circle"
386 "double-circle" "triangle" "sesame" string)
387 ("text-shadow" "none" length color)
388 ("text-underline-position" "auto" "under" "left" "right")
389
390 ;; CSS Text Module Level 3
391 ;; (http://www.w3.org/TR/css3-text/#property-index)
392 ("hanging-punctuation" "none" "first" "force-end" "allow-end"
393 "last")
394 ("hyphens" "none" "manual" "auto")
395 ("letter-spacing" "normal" length)
396 ("line-break" "auto" "loose" "normal" "strict")
397 ("overflow-wrap" "normal" "break-word")
398 ("tab-size" integer length)
399 ("text-align" "start" "end" "left" "right" "center" "justify"
400 "match-parent")
401 ("text-align-last" "auto" "start" "end" "left" "right" "center"
402 "justify")
403 ("text-indent" length percentage)
404 ("text-justify" "auto" "none" "inter-word" "distribute")
405 ("text-transform" "none" "capitalize" "uppercase" "lowercase"
406 "full-width")
407 ("white-space" "normal" "pre" "nowrap" "pre-wrap" "pre-line")
408 ("word-break" "normal" "keep-all" "break-all")
409 ("word-spacing" "normal" length percentage)
410 ("word-wrap" "normal" "break-word")
411
412 ;; CSS Transforms Module Level 1
413 ;; (http://www.w3.org/TR/css3-2d-transforms/#property-index)
414 ("backface-visibility" "visible" "hidden")
415 ("perspective" "none" length)
416 ("perspective-origin" "left" "center" "right" "top" "bottom"
417 percentage length)
418 ("transform" "none" transform-list)
419 ("transform-origin" "left" "center" "right" "top" "bottom"
420 percentage length)
421 ("transform-style" "flat" "preserve-3d")
422
423 ;; CSS Transitions
424 ;; (http://www.w3.org/TR/css3-transitions/#property-index)
425 ("transition" single-transition)
426 ("transition-delay" time)
427 ("transition-duration" time)
428 ("transition-property" "none" single-transition-property "all")
429 ("transition-timing-function" single-transition-timing-function)
430
431 ;; Filter Effects Module Level 1
432 ;; (http://www.w3.org/TR/filter-effects/#property-index)
433 ("color-interpolation-filters" "auto" "sRGB" "linearRGB")
434 ("filter" "none" filter-function-list)
435 ("flood-color" color)
436 ("flood-opacity" number percentage)
437 ("lighting-color" color))
438 "Identifiers for properties and their possible values.
439 The CAR of each entry is the name of a property, while the CDR is
440 a list of possible values for that property. String values in
441 the CDRs represent literal values, while symbols represent one of
442 the value classes found in `css-value-class-alist'. If a symbol
443 is not found in `css-value-class-alist', it's interpreted as a
444 reference back to one of the properties in this list. Some
445 symbols, such as `number' or `identifier', don't produce any
446 further value candidates, since that list would be infinite.")
447
448 (defconst css-property-ids
449 (mapcar #'car css-property-alist)
450 "Identifiers for properties.")
451
452 (defconst css-value-class-alist
453 '((absolute-size
454 "xx-small" "x-small" "small" "medium" "large" "x-large"
455 "xx-large")
456 (alphavalue number)
457 (angle "calc()")
458 (attachment "scroll" "fixed" "local")
459 (bg-image image "none")
460 (bg-layer bg-image position repeat-style attachment box)
461 (bg-size length percentage "auto" "cover" "contain")
462 (box "border-box" "padding-box" "content-box")
463 (color
464 "aqua" "black" "blue" "fuchsia" "gray" "green" "lime" "maroon"
465 "navy" "olive" "orange" "purple" "red" "silver" "teal" "white"
466 "yellow" "transparent")
467 (common-lig-values "common-ligatures" "no-common-ligatures")
468 (contextual-alt-values "contextual" "no-contextual")
469 (counter "counter()" "counters()")
470 (discretionary-lig-values
471 "discretionary-ligatures" "no-discretionary-ligatures")
472 (east-asian-variant-values
473 "jis78" "jis83" "jis90" "jis04" "simplified" "traditional")
474 (east-asian-width-values "full-width" "proportional-width")
475 (family-name "Courier" "Helvetica" "Times")
476 (feature-tag-value string integer "on" "off")
477 (filter-function
478 "blur()" "brightness()" "contrast()" "drop-shadow()"
479 "grayscale()" "hue-rotate()" "invert()" "opacity()" "sepia()"
480 "saturate()")
481 (filter-function-list filter-function uri)
482 (final-bg-layer
483 bg-image position repeat-style attachment box color)
484 (font-variant-css21 "normal" "small-caps")
485 (frequency "calc()")
486 (generic-family
487 "serif" "sans-serif" "cursive" "fantasy" "monospace")
488 (generic-voice "male" "female" "child")
489 (gradient
490 linear-gradient radial-gradient repeating-linear-gradient
491 repeating-radial-gradient)
492 (historical-lig-values
493 "historical-ligatures" "no-historical-ligatures")
494 (image uri image-list element-reference gradient)
495 (image-list "image()")
496 (integer "calc()")
497 (length "calc()" number)
498 (line-height "normal" number length percentage)
499 (line-style
500 "none" "hidden" "dotted" "dashed" "solid" "double" "groove"
501 "ridge" "inset" "outset")
502 (line-width length "thin" "medium" "thick")
503 (linear-gradient "linear-gradient()")
504 (margin-width "auto" length percentage)
505 (number "calc()")
506 (numeric-figure-values "lining-nums" "oldstyle-nums")
507 (numeric-fraction-values "diagonal-fractions" "stacked-fractions")
508 (numeric-spacing-values "proportional-nums" "tabular-nums")
509 (padding-width length percentage)
510 (position
511 "left" "center" "right" "top" "bottom" percentage length)
512 (radial-gradient "radial-gradient()")
513 (relative-size "larger" "smaller")
514 (repeat-style
515 "repeat-x" "repeat-y" "repeat" "space" "round" "no-repeat")
516 (repeating-linear-gradient "repeating-linear-gradient()")
517 (repeating-radial-gradient "repeating-radial-gradient()")
518 (shadow "inset" length color)
519 (shape "rect()")
520 (single-animation-direction
521 "normal" "reverse" "alternate" "alternate-reverse")
522 (single-animation-fill-mode "none" "forwards" "backwards" "both")
523 (single-animation-iteration-count "infinite" number)
524 (single-animation-name "none" identifier)
525 (single-animation-play-state "running" "paused")
526 (single-timing-function single-transition-timing-function)
527 (single-transition
528 "none" single-transition-property time
529 single-transition-timing-function)
530 (single-transition-property "all" identifier)
531 (single-transition-timing-function
532 "ease" "linear" "ease-in" "ease-out" "ease-in-out" "step-start"
533 "step-end" "steps()" "cubic-bezier()")
534 (specific-voice identifier)
535 (target-name string)
536 (time "calc()")
537 (transform-list
538 "matrix()" "translate()" "translateX()" "translateY()" "scale()"
539 "scaleX()" "scaleY()" "rotate()" "skew()" "skewX()" "skewY()"
540 "matrix3d()" "translate3d()" "translateZ()" "scale3d()"
541 "scaleZ()" "rotate3d()" "rotateX()" "rotateY()" "rotateZ()"
542 "perspective()")
543 (uri "url()")
544 (width length percentage "auto")
545 (x number)
546 (y number))
547 "Property value classes and their values.
548 The format is similar to that of `css-property-alist', except
549 that the CARs aren't actual CSS properties, but rather a name for
550 a class of values, and that symbols in the CDRs always refer to
551 other entries in this list, not to properties.
552
553 The following classes have been left out above because they
554 cannot be completed sensibly: `element-reference', `id',
555 `identifier', `percentage', and `string'.")
556
557 (defcustom css-electric-keys '(?\} ?\;) ;; '()
558 "Self inserting keys which should trigger re-indentation."
559 :version "22.2"
560 :type '(repeat character)
561 :options '((?\} ?\;))
562 :group 'css)
563
564 (defvar css-mode-syntax-table
565 (let ((st (make-syntax-table)))
566 ;; C-style comments.
567 (modify-syntax-entry ?/ ". 14" st)
568 (modify-syntax-entry ?* ". 23b" st)
569 ;; Strings.
570 (modify-syntax-entry ?\" "\"" st)
571 (modify-syntax-entry ?\' "\"" st)
572 ;; Blocks.
573 (modify-syntax-entry ?\{ "(}" st)
574 (modify-syntax-entry ?\} "){" st)
575 ;; Args in url(...) thingies and other "function calls".
576 (modify-syntax-entry ?\( "()" st)
577 (modify-syntax-entry ?\) ")(" st)
578 ;; To match attributes in selectors.
579 (modify-syntax-entry ?\[ "(]" st)
580 (modify-syntax-entry ?\] ")[" st)
581 ;; Special chars that sometimes come at the beginning of words.
582 (modify-syntax-entry ?@ "'" st)
583 ;; (modify-syntax-entry ?: "'" st)
584 (modify-syntax-entry ?# "'" st)
585 ;; Distinction between words and symbols.
586 (modify-syntax-entry ?- "_" st)
587 st))
588
589 (eval-and-compile
590 (defconst css--uri-re
591 (concat
592 "url\\((\\)[[:space:]]*\\(?:\\\\.\\|[^()[:space:]\n'\"]\\)+"
593 "[[:space:]]*\\()\\)")))
594
595 (defconst css-syntax-propertize-function
596 (syntax-propertize-rules
597 (css--uri-re (1 "|") (2 "|"))))
598
599 (defconst css-escapes-re
600 "\\\\\\(?:[^\000-\037\177]\\|[0-9a-fA-F]+[ \n\t\r\f]?\\)")
601 (defconst css-nmchar-re (concat "\\(?:[-[:alnum:]]\\|" css-escapes-re "\\)"))
602 (defconst css-nmstart-re (concat "\\(?:[[:alpha:]]\\|" css-escapes-re "\\)"))
603 (defconst css-ident-re ;; (concat css-nmstart-re css-nmchar-re "*")
604 ;; Apparently, "at rules" names can start with a dash, e.g. @-moz-keyframes.
605 (concat css-nmchar-re "+"))
606 (defconst css-proprietary-nmstart-re ;; Vendor-specific properties.
607 (concat "[-_]" (regexp-opt '("ms" "moz" "o" "khtml" "webkit")) "-"))
608 (defconst css-name-re (concat css-nmchar-re "+"))
609
610 (defconst scss--hash-re "#\\(?:{[$-_[:alnum:]]+}\\|[[:alnum:]]+\\)")
611
612 (defface css-selector '((t :inherit font-lock-function-name-face))
613 "Face to use for selectors."
614 :group 'css)
615 (defface css-property '((t :inherit font-lock-variable-name-face))
616 "Face to use for properties."
617 :group 'css)
618 (defface css-proprietary-property '((t :inherit (css-property italic)))
619 "Face to use for vendor-specific properties.")
620
621 (defun css--font-lock-keywords (&optional sassy)
622 `((,(concat "!\\s-*" (regexp-opt css--bang-ids))
623 (0 font-lock-builtin-face))
624 ;; Atrules keywords. IDs not in css-at-ids are valid (ignored).
625 ;; In fact the regexp should probably be
626 ;; (,(concat "\\(@" css-ident-re "\\)\\([ \t\n][^;{]*\\)[;{]")
627 ;; (1 font-lock-builtin-face))
628 ;; Since "An at-rule consists of everything up to and including the next
629 ;; semicolon (;) or the next block, whichever comes first."
630 (,(concat "@" css-ident-re) (0 font-lock-builtin-face))
631 ;; Variables.
632 (,(concat "--" css-ident-re) (0 font-lock-variable-name-face))
633 ;; Selectors.
634 ;; FIXME: attribute selectors don't work well because they may contain
635 ;; strings which have already been highlighted as f-l-string-face and
636 ;; thus prevent this highlighting from being applied (actually now that
637 ;; I use `keep' this should work better). But really the part of the
638 ;; selector between [...] should simply not be highlighted.
639 (,(concat
640 "^[ \t]*\\("
641 (if (not sassy)
642 ;; We don't allow / as first char, so as not to
643 ;; take a comment as the beginning of a selector.
644 "[^@/:{}() \t\n][^:{}()]+"
645 ;; Same as for non-sassy except we do want to allow { and }
646 ;; chars in selectors in the case of #{$foo}
647 ;; variable interpolation!
648 (concat "\\(?:" scss--hash-re
649 "\\|[^@/:{}() \t\n#]\\)"
650 "[^:{}()#]*\\(?:" scss--hash-re "[^:{}()#]*\\)*"))
651 ;; Even though pseudo-elements should be prefixed by ::, a
652 ;; single colon is accepted for backward compatibility.
653 "\\(?:\\(:" (regexp-opt (append css-pseudo-class-ids
654 css-pseudo-element-ids) t)
655 "\\|\\::" (regexp-opt css-pseudo-element-ids t) "\\)"
656 "\\(?:([^)]+)\\)?"
657 (if (not sassy)
658 "[^:{}()\n]*"
659 (concat "[^:{}()\n#]*\\(?:" scss--hash-re "[^:{}()\n#]*\\)*"))
660 "\\)*"
661 "\\)\\(?:\n[ \t]*\\)*{")
662 (1 'css-selector keep))
663 ;; In the above rule, we allow the open-brace to be on some subsequent
664 ;; line. This will only work if we properly mark the intervening text
665 ;; as being part of a multiline element (and even then, this only
666 ;; ensures proper refontification, but not proper discovery).
667 ("^[ \t]*{" (0 (save-excursion
668 (goto-char (match-beginning 0))
669 (skip-chars-backward " \n\t")
670 (put-text-property (point) (match-end 0)
671 'font-lock-multiline t)
672 ;; No face.
673 nil)))
674 ;; Properties. Again, we don't limit ourselves to css-property-ids.
675 (,(concat "\\(?:[{;]\\|^\\)[ \t]*\\("
676 "\\(?:\\(" css-proprietary-nmstart-re "\\)\\|"
677 css-nmstart-re "\\)" css-nmchar-re "*"
678 "\\)\\s-*:")
679 (1 (if (match-end 2) 'css-proprietary-property 'css-property)))
680 ;; Make sure the parens in a url(...) expression receive the
681 ;; default face. This is done because the parens may sometimes
682 ;; receive generic string delimiter syntax (see
683 ;; `css-syntax-propertize-function').
684 (,css--uri-re
685 (1 'default t) (2 'default t))))
686
687 (defvar css-font-lock-keywords (css--font-lock-keywords))
688
689 (defvar css-font-lock-defaults
690 '(css-font-lock-keywords nil t))
691
692 (defcustom css-indent-offset 4
693 "Basic size of one indentation step."
694 :version "22.2"
695 :type 'integer
696 :safe 'integerp)
697
698 (defconst css-smie-grammar
699 (smie-prec2->grammar
700 (smie-precs->prec2 '((assoc ";") (assoc ",") (left ":")))))
701
702 (defun css-smie--forward-token ()
703 (cond
704 ((and (eq (char-before) ?\})
705 (scss-smie--not-interpolation-p)
706 ;; FIXME: If the next char is not whitespace, what should we do?
707 (or (memq (char-after) '(?\s ?\t ?\n))
708 (looking-at comment-start-skip)))
709 (if (memq (char-after) '(?\s ?\t ?\n))
710 (forward-char 1) (forward-comment 1))
711 ";")
712 ((progn (forward-comment (point-max))
713 (looking-at "[;,:]"))
714 (forward-char 1) (match-string 0))
715 (t (smie-default-forward-token))))
716
717 (defun css-smie--backward-token ()
718 (let ((pos (point)))
719 (forward-comment (- (point)))
720 (cond
721 ;; FIXME: If the next char is not whitespace, what should we do?
722 ((and (eq (char-before) ?\}) (scss-smie--not-interpolation-p)
723 (> pos (point))) ";")
724 ((memq (char-before) '(?\; ?\, ?\:))
725 (forward-char -1) (string (char-after)))
726 (t (smie-default-backward-token)))))
727
728 (defun css-smie-rules (kind token)
729 (pcase (cons kind token)
730 (`(:elem . basic) css-indent-offset)
731 (`(:elem . arg) 0)
732 (`(:list-intro . ,(or `";" `"")) t) ;"" stands for BOB (bug#15467).
733 (`(:before . "{")
734 (when (or (smie-rule-hanging-p) (smie-rule-bolp))
735 (smie-backward-sexp ";")
736 (smie-indent-virtual)))
737 (`(:before . ,(or "{" "("))
738 (if (smie-rule-hanging-p) (smie-rule-parent 0)))))
739
740 ;;; Completion
741
742 (defun css--complete-property ()
743 "Complete property at point."
744 (save-excursion
745 (let ((pos (point)))
746 (skip-chars-backward "-[:alnum:]")
747 (let ((start (point)))
748 (skip-chars-backward " \t\r\n")
749 (when (memq (char-before) '(?\{ ?\;))
750 (list start pos css-property-ids))))))
751
752 (defun css--complete-bang-rule ()
753 "Complete bang-rule at point."
754 (save-excursion
755 (let ((pos (point)))
756 (skip-chars-backward "-[:alnum:]")
757 (when (eq (char-before) ?\!)
758 (list (point) pos css--bang-ids)))))
759
760 (defun css--complete-pseudo-element-or-class ()
761 "Complete pseudo-element or pseudo-class at point."
762 (save-excursion
763 (let ((pos (point)))
764 (skip-chars-backward "-[:alnum:]")
765 (when (eq (char-before) ?\:)
766 (list (point) pos
767 (if (eq (char-before (- (point) 1)) ?\:)
768 css-pseudo-element-ids
769 css-pseudo-class-ids))))))
770
771 (defun css--complete-at-rule ()
772 "Complete at-rule (statement beginning with `@') at point."
773 (save-excursion
774 (let ((pos (point)))
775 (skip-chars-backward "-[:alnum:]")
776 (when (eq (char-before) ?\@)
777 (list (point) pos css--at-ids)))))
778
779 (defvar css--property-value-cache
780 (make-hash-table :test 'equal :size (length css-property-alist))
781 "Cache of previously completed property values.")
782
783 (defun css--value-class-lookup (value-class)
784 "Return a list of value completion candidates for VALUE-CLASS.
785 Completion candidates are looked up in `css-value-class-alist' by
786 the symbol VALUE-CLASS."
787 (seq-uniq
788 (seq-mapcat
789 (lambda (value)
790 (if (stringp value)
791 (list value)
792 (css--value-class-lookup value)))
793 (cdr (assq value-class css-value-class-alist)))))
794
795 (defun css--property-values (property)
796 "Return a list of value completion candidates for PROPERTY.
797 Completion candidates are looked up in `css-property-alist' by
798 the string PROPERTY."
799 (or (gethash property css--property-value-cache)
800 (let ((values
801 (seq-uniq
802 (seq-mapcat
803 (lambda (value)
804 (if (stringp value)
805 (list value)
806 (or (css--value-class-lookup value)
807 (css--property-values (symbol-name value)))))
808 (cdr (assoc property css-property-alist))))))
809 (puthash property values css--property-value-cache))))
810
811 (defun css--complete-property-value ()
812 "Complete property value at point."
813 (let ((property
814 (save-excursion
815 (re-search-backward ":[^/]" (line-beginning-position) t)
816 (let ((property-end (point)))
817 (skip-chars-backward "-[:alnum:]")
818 (let ((property (buffer-substring (point) property-end)))
819 (car (member property css-property-ids)))))))
820 (when property
821 (let ((end (point)))
822 (save-excursion
823 (skip-chars-backward "[:graph:]")
824 (list (point) end
825 (cons "inherit" (css--property-values property))))))))
826
827 (defun css-completion-at-point ()
828 "Complete current symbol at point.
829 Currently supports completion of CSS properties, property values,
830 pseudo-elements, pseudo-classes, at-rules, and bang-rules."
831 (or (css--complete-property)
832 (css--complete-bang-rule)
833 (css--complete-property-value)
834 (css--complete-pseudo-element-or-class)
835 (css--complete-at-rule)))
836
837 ;;;###autoload
838 (define-derived-mode css-mode prog-mode "CSS"
839 "Major mode to edit Cascading Style Sheets."
840 (setq-local font-lock-defaults css-font-lock-defaults)
841 (setq-local comment-start "/*")
842 (setq-local comment-start-skip "/\\*+[ \t]*")
843 (setq-local comment-end "*/")
844 (setq-local comment-end-skip "[ \t]*\\*+/")
845 (setq-local syntax-propertize-function
846 css-syntax-propertize-function)
847 (setq-local fill-paragraph-function #'css-fill-paragraph)
848 (setq-local adaptive-fill-function #'css-adaptive-fill)
849 (setq-local add-log-current-defun-function #'css-current-defun-name)
850 (smie-setup css-smie-grammar #'css-smie-rules
851 :forward-token #'css-smie--forward-token
852 :backward-token #'css-smie--backward-token)
853 (setq-local electric-indent-chars
854 (append css-electric-keys electric-indent-chars))
855 (add-hook 'completion-at-point-functions
856 #'css-completion-at-point nil 'local))
857
858 (defvar comment-continue)
859
860 (defun css-fill-paragraph (&optional justify)
861 (save-excursion
862 ;; Fill succeeding comment when invoked right before a multi-line
863 ;; comment.
864 (when (save-excursion
865 (beginning-of-line)
866 (comment-search-forward (point-at-eol) t))
867 (goto-char (match-end 0)))
868 (let ((ppss (syntax-ppss))
869 (eol (line-end-position)))
870 (cond
871 ((and (nth 4 ppss)
872 (save-excursion
873 (goto-char (nth 8 ppss))
874 (forward-comment 1)
875 (prog1 (not (bolp))
876 (setq eol (point)))))
877 ;; Filling inside a comment whose comment-end marker is not \n.
878 ;; This code is meant to be generic, so that it works not only for
879 ;; css-mode but for all modes.
880 (save-restriction
881 (narrow-to-region (nth 8 ppss) eol)
882 (comment-normalize-vars) ;Will define comment-continue.
883 (let ((fill-paragraph-function nil)
884 (paragraph-separate
885 (if (and comment-continue
886 (string-match "[^ \t]" comment-continue))
887 (concat "\\(?:[ \t]*\\(?:"
888 (regexp-quote comment-continue) "\\|"
889 comment-start-skip "\\|"
890 comment-end-skip "\\)\\)?"
891 "\\(?:" paragraph-separate "\\)")
892 paragraph-separate))
893 (paragraph-start
894 (if (and comment-continue
895 (string-match "[^ \t]" comment-continue))
896 (concat "\\(?:[ \t]*" (regexp-quote comment-continue)
897 "\\)?\\(?:" paragraph-start "\\)")
898 paragraph-start)))
899 (fill-paragraph justify)
900 ;; Don't try filling again.
901 t)))
902
903 ((and (null (nth 8 ppss))
904 (or (nth 1 ppss)
905 (and (ignore-errors
906 (down-list 1)
907 (when (<= (point) eol)
908 (setq ppss (syntax-ppss)))))))
909 (goto-char (nth 1 ppss))
910 (let ((end (save-excursion
911 (ignore-errors (forward-sexp 1) (copy-marker (point) t)))))
912 (when end
913 (while (re-search-forward "[{;}]" end t)
914 (cond
915 ;; This is a false positive inside a string or comment.
916 ((nth 8 (syntax-ppss)) nil)
917 ;; This is a false positive when encountering an
918 ;; interpolated variable (bug#19751).
919 ((eq (char-before (- (point) 1)) ?#) nil)
920 ((eq (char-before) ?\})
921 (save-excursion
922 (forward-char -1)
923 (skip-chars-backward " \t")
924 (when (and (not (bolp))
925 (scss-smie--not-interpolation-p))
926 (newline))))
927 (t
928 (while
929 (progn
930 (setq eol (line-end-position))
931 (and (forward-comment 1)
932 (> (point) eol)
933 ;; A multi-line comment should be on its own line.
934 (save-excursion (forward-comment -1)
935 (when (< (point) eol)
936 (newline)
937 t)))))
938 (if (< (point) eol) (newline)))))
939 (goto-char (nth 1 ppss))
940 (indent-region (line-beginning-position 2) end)
941 ;; Don't use the default filling code.
942 t)))))))
943
944 (defun css-adaptive-fill ()
945 (when (looking-at "[ \t]*/\\*[ \t]*")
946 (let ((str (match-string 0)))
947 (and (string-match "/\\*" str)
948 (replace-match " *" t t str)))))
949
950 (defun css-current-defun-name ()
951 "Return the name of the CSS section at point, or nil."
952 (save-excursion
953 (let ((max (max (point-min) (- (point) 1600)))) ; approx 20 lines back
954 (when (search-backward "{" max t)
955 (skip-chars-backward " \t\r\n")
956 (beginning-of-line)
957 (if (looking-at "^[ \t]*\\([^{\r\n]*[^ {\t\r\n]\\)")
958 (match-string-no-properties 1))))))
959
960 ;;; SCSS mode
961
962 (defvar scss-mode-syntax-table
963 (let ((st (make-syntax-table css-mode-syntax-table)))
964 (modify-syntax-entry ?/ ". 124" st)
965 (modify-syntax-entry ?\n ">" st)
966 ;; Variable names are prefixed by $.
967 (modify-syntax-entry ?$ "'" st)
968 st))
969
970 (defun scss-font-lock-keywords ()
971 (append `((,(concat "$" css-ident-re) (0 font-lock-variable-name-face)))
972 (css--font-lock-keywords 'sassy)
973 `((,(concat "@mixin[ \t]+\\(" css-ident-re "\\)[ \t]*(")
974 (1 font-lock-function-name-face)))))
975
976 (defun scss-smie--not-interpolation-p ()
977 (save-excursion
978 (forward-char -1)
979 (or (zerop (skip-chars-backward "-[:alnum:]"))
980 (not (looking-back "#{\\$" (- (point) 3))))))
981
982 ;;;###autoload (add-to-list 'auto-mode-alist '("\\.scss\\'" . scss-mode))
983 ;;;###autoload
984 (define-derived-mode scss-mode css-mode "SCSS"
985 "Major mode to edit \"Sassy CSS\" files."
986 (setq-local comment-start "// ")
987 (setq-local comment-end "")
988 (setq-local comment-continue " *")
989 (setq-local comment-start-skip "/[*/]+[ \t]*")
990 (setq-local comment-end-skip "[ \t]*\\(?:\n\\|\\*+/\\)")
991 (setq-local css--at-ids (append css-at-ids scss-at-ids))
992 (setq-local css--bang-ids (append css-bang-ids scss-bang-ids))
993 (setq-local font-lock-defaults
994 (list (scss-font-lock-keywords) nil t)))
995
996 (provide 'css-mode)
997 ;;; css-mode.el ends here