]> code.delx.au - gnu-emacs/blobdiff - lisp/textmodes/css-mode.el
Add completion of colors in CSS mode
[gnu-emacs] / lisp / textmodes / css-mode.el
index cbef3d4026a240f361055bb073f20c03c83cfa30..060af3321791eece94180bdab4030c503f156584 100644 (file)
 ;; - electric ; and }
 ;; - filling code with auto-fill-mode
 ;; - fix font-lock errors with multi-line selectors
+;; - support completion of user-defined classes names and IDs
 
 ;;; Code:
 
 (require 'seq)
+(require 'sgml-mode)
 (require 'smie)
 
 (defgroup css nil
   "Identifiers for pseudo-elements.")
 
 (defconst css-at-ids
-  '("charset" "font-face" "import" "media" "namespace" "page")
+  '("charset" "font-face" "import" "keyframes" "media" "namespace"
+    "page")
   "Identifiers that appear in the form @foo.")
 
+(defconst scss-at-ids
+  '("at-root" "content" "debug" "each" "else" "else if" "error" "extend"
+    "for" "function" "if" "import" "include" "mixin" "return" "warn"
+    "while")
+  "Additional identifiers that appear in the form @foo in SCSS.")
+
+(defvar css--at-ids css-at-ids
+  "List of at-rules for the current mode.")
+(make-variable-buffer-local 'css--at-ids)
+
 (defconst css-bang-ids
   '("important")
   "Identifiers that appear in the form !foo.")
@@ -443,15 +456,15 @@ further value candidates, since that list would be infinite.")
      "xx-small" "x-small" "small" "medium" "large" "x-large"
      "xx-large")
     (alphavalue number)
+    (angle "calc()")
     (attachment "scroll" "fixed" "local")
     (bg-image image "none")
     (bg-layer bg-image position repeat-style attachment box)
     (bg-size length percentage "auto" "cover" "contain")
     (box "border-box" "padding-box" "content-box")
     (color
-     "aqua" "black" "blue" "fuchsia" "gray" "green" "lime" "maroon"
-     "navy" "olive" "orange" "purple" "red" "silver" "teal" "white"
-     "yellow" "transparent")
+     "rgb()" "rgba()" "hsl()" "hsla()" named-color "transparent"
+     "currentColor")
     (common-lig-values "common-ligatures" "no-common-ligatures")
     (contextual-alt-values "contextual" "no-contextual")
     (counter "counter()" "counters()")
@@ -470,6 +483,7 @@ further value candidates, since that list would be infinite.")
     (final-bg-layer
      bg-image position repeat-style attachment box color)
     (font-variant-css21 "normal" "small-caps")
+    (frequency "calc()")
     (generic-family
      "serif" "sans-serif" "cursive" "fantasy" "monospace")
     (generic-voice "male" "female" "child")
@@ -480,7 +494,8 @@ further value candidates, since that list would be infinite.")
      "historical-ligatures" "no-historical-ligatures")
     (image uri image-list element-reference gradient)
     (image-list "image()")
-    (length number)
+    (integer "calc()")
+    (length "calc()" number)
     (line-height "normal" number length percentage)
     (line-style
      "none" "hidden" "dotted" "dashed" "solid" "double" "groove"
@@ -488,6 +503,37 @@ further value candidates, since that list would be infinite.")
     (line-width length "thin" "medium" "thick")
     (linear-gradient "linear-gradient()")
     (margin-width "auto" length percentage)
+    (named-color
+     "aliceblue" "antiquewhite" "aqua" "aquamarine" "azure" "beige"
+     "bisque" "black" "blanchedalmond" "blue" "blueviolet" "brown"
+     "burlywood" "cadetblue" "chartreuse" "chocolate" "coral"
+     "cornflowerblue" "cornsilk" "crimson" "cyan" "darkblue"
+     "darkcyan" "darkgoldenrod" "darkgray" "darkgreen" "darkkhaki"
+     "darkmagenta" "darkolivegreen" "darkorange" "darkorchid"
+     "darkred" "darksalmon" "darkseagreen" "darkslateblue"
+     "darkslategray" "darkturquoise" "darkviolet" "deeppink"
+     "deepskyblue" "dimgray" "dodgerblue" "firebrick" "floralwhite"
+     "forestgreen" "fuchsia" "gainsboro" "ghostwhite" "gold"
+     "goldenrod" "gray" "green" "greenyellow" "honeydew" "hotpink"
+     "indianred" "indigo" "ivory" "khaki" "lavender" "lavenderblush"
+     "lawngreen" "lemonchiffon" "lightblue" "lightcoral" "lightcyan"
+     "lightgoldenrodyellow" "lightgray" "lightgreen" "lightpink"
+     "lightsalmon" "lightseagreen" "lightskyblue" "lightslategray"
+     "lightsteelblue" "lightyellow" "lime" "limegreen" "linen"
+     "magenta" "maroon" "mediumaquamarine" "mediumblue" "mediumorchid"
+     "mediumpurple" "mediumseagreen" "mediumslateblue"
+     "mediumspringgreen" "mediumturquoise" "mediumvioletred"
+     "midnightblue" "mintcream" "mistyrose" "moccasin" "navajowhite"
+     "navy" "oldlace" "olive" "olivedrab" "orange" "orangered"
+     "orchid" "palegoldenrod" "palegreen" "paleturquoise"
+     "palevioletred" "papayawhip" "peachpuff" "peru" "pink" "plum"
+     "powderblue" "purple" "rebeccapurple" "red" "rosybrown"
+     "royalblue" "saddlebrown" "salmon" "sandybrown" "seagreen"
+     "seashell" "sienna" "silver" "skyblue" "slateblue" "slategray"
+     "snow" "springgreen" "steelblue" "tan" "teal" "thistle" "tomato"
+     "turquoise" "violet" "wheat" "white" "whitesmoke" "yellow"
+     "yellowgreen")
+    (number "calc()")
     (numeric-figure-values "lining-nums" "oldstyle-nums")
     (numeric-fraction-values "diagonal-fractions" "stacked-fractions")
     (numeric-spacing-values "proportional-nums" "tabular-nums")
@@ -518,6 +564,7 @@ further value candidates, since that list would be infinite.")
      "step-end" "steps()" "cubic-bezier()")
     (specific-voice identifier)
     (target-name string)
+    (time "calc()")
     (transform-list
      "matrix()" "translate()" "translateX()" "translateY()" "scale()"
      "scaleX()" "scaleY()" "rotate()" "skew()" "skewX()" "skewY()"
@@ -535,9 +582,8 @@ a class of values, and that symbols in the CDRs always refer to
 other entries in this list, not to properties.
 
 The following classes have been left out above because they
-cannot be completed sensibly: `angle', `element-reference',
-`frequency', `id', `identifier', `integer', `number',
-`percentage', `string', and `time'.")
+cannot be completed sensibly: `element-reference', `id',
+`identifier', `percentage', and `string'.")
 
 (defcustom css-electric-keys '(?\} ?\;) ;; '()
   "Self inserting keys which should trigger re-indentation."
@@ -759,7 +805,7 @@ cannot be completed sensibly: `angle', `element-reference',
     (let ((pos (point)))
       (skip-chars-backward "-[:alnum:]")
       (when (eq (char-before) ?\@)
-        (list (point) pos css-at-ids)))))
+        (list (point) pos css--at-ids)))))
 
 (defvar css--property-value-cache
   (make-hash-table :test 'equal :size (length css-property-alist))
@@ -769,25 +815,29 @@ cannot be completed sensibly: `angle', `element-reference',
   "Return a list of value completion candidates for VALUE-CLASS.
 Completion candidates are looked up in `css-value-class-alist' by
 the symbol VALUE-CLASS."
-  (seq-mapcat
-   (lambda (value)
-     (if (stringp value)
-         (list value)
-       (css--value-class-lookup value)))
-   (cdr (assq value-class css-value-class-alist))))
+  (seq-uniq
+   (seq-mapcat
+    (lambda (value)
+      (if (stringp value)
+          (list value)
+        (css--value-class-lookup value)))
+    (cdr (assq value-class css-value-class-alist)))))
 
 (defun css--property-values (property)
   "Return a list of value completion candidates for PROPERTY.
 Completion candidates are looked up in `css-property-alist' by
 the string PROPERTY."
   (or (gethash property css--property-value-cache)
-      (seq-mapcat
-       (lambda (value)
-         (if (stringp value)
-             (list value)
-           (or (css--value-class-lookup value)
-               (css--property-values (symbol-name value)))))
-       (cdr (assoc property css-property-alist)))))
+      (let ((values
+             (seq-uniq
+              (seq-mapcat
+               (lambda (value)
+                 (if (stringp value)
+                     (list value)
+                   (or (css--value-class-lookup value)
+                       (css--property-values (symbol-name value)))))
+               (cdr (assoc property css-property-alist))))))
+        (puthash property values css--property-value-cache))))
 
 (defun css--complete-property-value ()
   "Complete property value at point."
@@ -805,15 +855,40 @@ the string PROPERTY."
           (list (point) end
                 (cons "inherit" (css--property-values property))))))))
 
+(defvar css--html-tags (mapcar #'car html-tag-alist)
+  "List of HTML tags.
+Used to provide completion of HTML tags in selectors.")
+
+(defvar css--nested-selectors-allowed nil
+  "Non-nil if nested selectors are allowed in the current mode.")
+(make-variable-buffer-local 'css--nested-selectors-allowed)
+
+;; TODO: Currently only supports completion of HTML tags.  By looking
+;; at open HTML mode buffers we should be able to provide completion
+;; of user-defined classes and IDs too.
+(defun css--complete-selector ()
+  "Complete part of a CSS selector at point."
+  (when (or (= (nth 0 (syntax-ppss)) 0) css--nested-selectors-allowed)
+    (save-excursion
+      (let ((end (point)))
+        (skip-chars-backward "-[:alnum:]")
+        (list (point) end css--html-tags)))))
+
 (defun css-completion-at-point ()
   "Complete current symbol at point.
 Currently supports completion of CSS properties, property values,
 pseudo-elements, pseudo-classes, at-rules, and bang-rules."
-  (or (css--complete-property)
-      (css--complete-bang-rule)
+  (or (css--complete-bang-rule)
       (css--complete-property-value)
       (css--complete-pseudo-element-or-class)
-      (css--complete-at-rule)))
+      (css--complete-at-rule)
+      (seq-let (prop-beg prop-end prop-table) (css--complete-property)
+        (seq-let (sel-beg sel-end sel-table) (css--complete-selector)
+          (when (or prop-table sel-table)
+            `(,@(if prop-table
+                    (list prop-beg prop-end)
+                  (list sel-beg sel-end))
+              ,(completion-table-merge prop-table sel-table)))))))
 
 ;;;###autoload
 (define-derived-mode css-mode prog-mode "CSS"
@@ -969,7 +1044,9 @@ pseudo-elements, pseudo-classes, at-rules, and bang-rules."
   (setq-local comment-continue " *")
   (setq-local comment-start-skip "/[*/]+[ \t]*")
   (setq-local comment-end-skip "[ \t]*\\(?:\n\\|\\*+/\\)")
+  (setq-local css--at-ids (append css-at-ids scss-at-ids))
   (setq-local css--bang-ids (append css-bang-ids scss-bang-ids))
+  (setq-local css--nested-selectors-allowed t)
   (setq-local font-lock-defaults
               (list (scss-font-lock-keywords) nil t)))