├── .github └── workflows │ └── dispatch.yml ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json └── src ├── README.md ├── complete.ts └── css.ts /.github/workflows/dispatch.yml: -------------------------------------------------------------------------------- 1 | name: Trigger CI 2 | on: push 3 | 4 | jobs: 5 | build: 6 | name: Dispatch to main repo 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Emit repository_dispatch 10 | uses: mvasigh/dispatch-action@main 11 | with: 12 | # You should create a personal access token and store it in your repository 13 | token: ${{ secrets.DISPATCH_AUTH }} 14 | repo: dev 15 | owner: codemirror 16 | event_type: push 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | package-lock.json 3 | /dist 4 | /test/*.js 5 | /test/*.d.ts 6 | /test/*.d.ts.map 7 | .tern-* 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /src 2 | /test 3 | /node_modules 4 | .tern-* 5 | rollup.config.js 6 | tsconfig.json 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 6.3.1 (2024-11-26) 2 | 3 | ### Bug fixes 4 | 5 | When completing a property name, insert a colon and space after the name. 6 | 7 | ## 6.3.0 (2024-09-07) 8 | 9 | ### New features 10 | 11 | CSS autocompletion now completes `@`-keywords. 12 | 13 | ## 6.2.1 (2023-08-04) 14 | 15 | ### Bug fixes 16 | 17 | Allow keyframe blocks to be code-folded. 18 | 19 | ## 6.2.0 (2023-04-26) 20 | 21 | ### Bug fixes 22 | 23 | Explicitly list @lezer/common as a package dependency. 24 | 25 | ### New features 26 | 27 | Export `defineCSSCompletionSource`, which allows one to define a CSS-style completion source for dialects with their own variable syntax. 28 | 29 | ## 6.1.1 (2023-03-08) 30 | 31 | ### Bug fixes 32 | 33 | Provide better completions when completing directly in a `Styles` top node. 34 | 35 | ## 6.1.0 (2023-03-06) 36 | 37 | ### New features 38 | 39 | CSS completion can now complete variable names. 40 | 41 | ## 6.0.2 (2023-01-28) 42 | 43 | ### Bug fixes 44 | 45 | Fetch the available CSS property names in a way that works on Chrome. 46 | 47 | ## 6.0.1 (2022-10-24) 48 | 49 | ### Bug fixes 50 | 51 | CSS completion now supports a number of additional recent and semi-standardized pseudo-class names. 52 | 53 | ## 6.0.0 (2022-06-08) 54 | 55 | ### Breaking changes 56 | 57 | Update dependencies to 6.0.0 58 | 59 | ## 0.20.0 (2022-04-20) 60 | 61 | ### Breaking changes 62 | 63 | Update dependencies to 0.20.0 64 | 65 | ## 0.19.3 (2021-09-24) 66 | 67 | ### Bug fixes 68 | 69 | Use more appropriate highlighting tags for attribute names, tag names, and CSS variables. 70 | 71 | ## 0.19.2 (2021-09-22) 72 | 73 | ### New features 74 | 75 | The package now exports a completion source function, rather than a prebuilt completion extension. 76 | 77 | ## 0.19.1 (2021-08-11) 78 | 79 | ### Bug fixes 80 | 81 | Fix incorrect versions for @lezer dependencies. 82 | 83 | ## 0.19.0 (2021-08-11) 84 | 85 | ### Breaking changes 86 | 87 | Update dependencies to 0.19.0 88 | 89 | ## 0.18.0 (2021-03-03) 90 | 91 | ### Breaking changes 92 | 93 | Update dependencies to 0.18. 94 | 95 | ## 0.17.1 (2021-01-06) 96 | 97 | ### New features 98 | 99 | The package now also exports a CommonJS module. 100 | 101 | ## 0.17.0 (2020-12-29) 102 | 103 | ### Breaking changes 104 | 105 | First numbered release. 106 | 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2018-2021 by Marijn Haverbeke and others 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # @codemirror/lang-css [![NPM version](https://img.shields.io/npm/v/@codemirror/lang-css.svg)](https://www.npmjs.org/package/@codemirror/lang-css) 4 | 5 | [ [**WEBSITE**](https://codemirror.net/) | [**ISSUES**](https://github.com/codemirror/dev/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/lang-css/blob/main/CHANGELOG.md) ] 6 | 7 | This package implements CSS language support for the 8 | [CodeMirror](https://codemirror.net/) code editor. 9 | 10 | The [project page](https://codemirror.net/) has more information, a 11 | number of [examples](https://codemirror.net/examples/) and the 12 | [documentation](https://codemirror.net/docs/). 13 | 14 | This code is released under an 15 | [MIT license](https://github.com/codemirror/lang-css/tree/main/LICENSE). 16 | 17 | We aim to be an inclusive, welcoming community. To make that explicit, 18 | we have a [code of 19 | conduct](http://contributor-covenant.org/version/1/1/0/) that applies 20 | to communication around the project. 21 | 22 | ## Usage 23 | 24 | ```javascript 25 | import {EditorView, basicSetup} from "codemirror" 26 | import {css} from "@codemirror/lang-css" 27 | 28 | const view = new EditorView({ 29 | parent: document.body, 30 | doc: `p { background-color: purple }`, 31 | extensions: [basicSetup, css()] 32 | }) 33 | ``` 34 | 35 | ## API Reference 36 | 37 |
38 |
39 | css() → LanguageSupport
40 | 41 |

Language support for CSS.

42 |
43 |
44 | cssLanguage: LRLanguage
45 | 46 |

A language provider based on the Lezer CSS 47 | parser, extended with 48 | highlighting and indentation information.

49 |
50 |
51 | cssCompletionSource: CompletionSource
52 | 53 |

CSS property, variable, and value keyword completion source.

54 |
55 |
56 | defineCSSCompletionSource(isVariable: fn(nodeSyntaxNodeRef) → boolean) → CompletionSource
57 | 58 |

Create a completion source for a CSS dialect, providing a 59 | predicate for determining what kind of syntax node can act as a 60 | completable variable. This is used by language modes like Sass and 61 | Less to reuse this package's completion logic.

62 |
63 |
64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@codemirror/lang-css", 3 | "version": "6.3.1", 4 | "description": "CSS language support for the CodeMirror code editor", 5 | "scripts": { 6 | "test": "cm-runtests", 7 | "prepare": "cm-buildhelper src/css.ts" 8 | }, 9 | "keywords": [ 10 | "editor", 11 | "code" 12 | ], 13 | "author": { 14 | "name": "Marijn Haverbeke", 15 | "email": "marijn@haverbeke.berlin", 16 | "url": "http://marijnhaverbeke.nl" 17 | }, 18 | "type": "module", 19 | "main": "dist/index.cjs", 20 | "exports": { 21 | "import": "./dist/index.js", 22 | "require": "./dist/index.cjs" 23 | }, 24 | "types": "dist/index.d.ts", 25 | "module": "dist/index.js", 26 | "sideEffects": false, 27 | "license": "MIT", 28 | "dependencies": { 29 | "@codemirror/autocomplete": "^6.0.0", 30 | "@codemirror/language": "^6.0.0", 31 | "@codemirror/state": "^6.0.0", 32 | "@lezer/common": "^1.0.2", 33 | "@lezer/css": "^1.1.7" 34 | }, 35 | "devDependencies": { 36 | "@codemirror/buildhelper": "^1.0.0" 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "https://github.com/codemirror/lang-css.git" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # @codemirror/lang-css [![NPM version](https://img.shields.io/npm/v/@codemirror/lang-css.svg)](https://www.npmjs.org/package/@codemirror/lang-css) 4 | 5 | [ [**WEBSITE**](https://codemirror.net/) | [**ISSUES**](https://github.com/codemirror/dev/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/lang-css/blob/main/CHANGELOG.md) ] 6 | 7 | This package implements CSS language support for the 8 | [CodeMirror](https://codemirror.net/) code editor. 9 | 10 | The [project page](https://codemirror.net/) has more information, a 11 | number of [examples](https://codemirror.net/examples/) and the 12 | [documentation](https://codemirror.net/docs/). 13 | 14 | This code is released under an 15 | [MIT license](https://github.com/codemirror/lang-css/tree/main/LICENSE). 16 | 17 | We aim to be an inclusive, welcoming community. To make that explicit, 18 | we have a [code of 19 | conduct](http://contributor-covenant.org/version/1/1/0/) that applies 20 | to communication around the project. 21 | 22 | ## Usage 23 | 24 | ```javascript 25 | import {EditorView, basicSetup} from "codemirror" 26 | import {css} from "@codemirror/lang-css" 27 | 28 | const view = new EditorView({ 29 | parent: document.body, 30 | doc: `p { background-color: purple }`, 31 | extensions: [basicSetup, css()] 32 | }) 33 | ``` 34 | 35 | ## API Reference 36 | 37 | @css 38 | 39 | @cssLanguage 40 | 41 | @cssCompletionSource 42 | 43 | @defineCSSCompletionSource 44 | -------------------------------------------------------------------------------- /src/complete.ts: -------------------------------------------------------------------------------- 1 | import {CompletionSource, Completion} from "@codemirror/autocomplete" 2 | import {syntaxTree} from "@codemirror/language" 3 | import {SyntaxNode, SyntaxNodeRef, NodeWeakMap, IterMode} from "@lezer/common" 4 | import {Text} from "@codemirror/state" 5 | 6 | let _properties: readonly Completion[] | null = null 7 | function properties() { 8 | if (!_properties && typeof document == "object" && document.body) { 9 | let {style} = document.body, names = [], seen = new Set 10 | for (let prop in style) if (prop != "cssText" && prop != "cssFloat") { 11 | if (typeof style[prop] == "string") { 12 | if (/[A-Z]/.test(prop)) prop = prop.replace(/[A-Z]/g, ch => "-" + ch.toLowerCase()) 13 | if (!seen.has(prop)) { 14 | names.push(prop) 15 | seen.add(prop) 16 | } 17 | } 18 | } 19 | _properties = names.sort().map(name => ({type: "property", label: name, apply: name + ": "})) 20 | } 21 | return _properties || [] 22 | } 23 | 24 | const pseudoClasses = [ 25 | "active", "after", "any-link", "autofill", "backdrop", "before", 26 | "checked", "cue", "default", "defined", "disabled", "empty", 27 | "enabled", "file-selector-button", "first", "first-child", 28 | "first-letter", "first-line", "first-of-type", "focus", 29 | "focus-visible", "focus-within", "fullscreen", "has", "host", 30 | "host-context", "hover", "in-range", "indeterminate", "invalid", 31 | "is", "lang", "last-child", "last-of-type", "left", "link", "marker", 32 | "modal", "not", "nth-child", "nth-last-child", "nth-last-of-type", 33 | "nth-of-type", "only-child", "only-of-type", "optional", "out-of-range", 34 | "part", "placeholder", "placeholder-shown", "read-only", "read-write", 35 | "required", "right", "root", "scope", "selection", "slotted", "target", 36 | "target-text", "valid", "visited", "where" 37 | ].map(name => ({type: "class", label: name})) 38 | 39 | const values = [ 40 | "above", "absolute", "activeborder", "additive", "activecaption", "after-white-space", 41 | "ahead", "alias", "all", "all-scroll", "alphabetic", "alternate", "always", 42 | "antialiased", "appworkspace", "asterisks", "attr", "auto", "auto-flow", "avoid", "avoid-column", 43 | "avoid-page", "avoid-region", "axis-pan", "background", "backwards", "baseline", "below", 44 | "bidi-override", "blink", "block", "block-axis", "bold", "bolder", "border", "border-box", 45 | "both", "bottom", "break", "break-all", "break-word", "bullets", "button", "button-bevel", 46 | "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "calc", "capitalize", 47 | "caps-lock-indicator", "caption", "captiontext", "caret", "cell", "center", "checkbox", "circle", 48 | "cjk-decimal", "clear", "clip", "close-quote", "col-resize", "collapse", "color", "color-burn", 49 | "color-dodge", "column", "column-reverse", "compact", "condensed", "contain", "content", 50 | "contents", "content-box", "context-menu", "continuous", "copy", "counter", "counters", "cover", 51 | "crop", "cross", "crosshair", "currentcolor", "cursive", "cyclic", "darken", "dashed", "decimal", 52 | "decimal-leading-zero", "default", "default-button", "dense", "destination-atop", "destination-in", 53 | "destination-out", "destination-over", "difference", "disc", "discard", "disclosure-closed", 54 | "disclosure-open", "document", "dot-dash", "dot-dot-dash", "dotted", "double", "down", "e-resize", 55 | "ease", "ease-in", "ease-in-out", "ease-out", "element", "ellipse", "ellipsis", "embed", "end", 56 | "ethiopic-abegede-gez", "ethiopic-halehame-aa-er", "ethiopic-halehame-gez", "ew-resize", "exclusion", 57 | "expanded", "extends", "extra-condensed", "extra-expanded", "fantasy", "fast", "fill", "fill-box", 58 | "fixed", "flat", "flex", "flex-end", "flex-start", "footnotes", "forwards", "from", 59 | "geometricPrecision", "graytext", "grid", "groove", "hand", "hard-light", "help", "hidden", "hide", 60 | "higher", "highlight", "highlighttext", "horizontal", "hsl", "hsla", "hue", "icon", "ignore", 61 | "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite", "infobackground", "infotext", 62 | "inherit", "initial", "inline", "inline-axis", "inline-block", "inline-flex", "inline-grid", 63 | "inline-table", "inset", "inside", "intrinsic", "invert", "italic", "justify", "keep-all", 64 | "landscape", "large", "larger", "left", "level", "lighter", "lighten", "line-through", "linear", 65 | "linear-gradient", "lines", "list-item", "listbox", "listitem", "local", "logical", "loud", "lower", 66 | "lower-hexadecimal", "lower-latin", "lower-norwegian", "lowercase", "ltr", "luminosity", "manipulation", 67 | "match", "matrix", "matrix3d", "medium", "menu", "menutext", "message-box", "middle", "min-intrinsic", 68 | "mix", "monospace", "move", "multiple", "multiple_mask_images", "multiply", "n-resize", "narrower", 69 | "ne-resize", "nesw-resize", "no-close-quote", "no-drop", "no-open-quote", "no-repeat", "none", 70 | "normal", "not-allowed", "nowrap", "ns-resize", "numbers", "numeric", "nw-resize", "nwse-resize", 71 | "oblique", "opacity", "open-quote", "optimizeLegibility", "optimizeSpeed", "outset", "outside", 72 | "outside-shape", "overlay", "overline", "padding", "padding-box", "painted", "page", "paused", 73 | "perspective", "pinch-zoom", "plus-darker", "plus-lighter", "pointer", "polygon", "portrait", 74 | "pre", "pre-line", "pre-wrap", "preserve-3d", "progress", "push-button", "radial-gradient", "radio", 75 | "read-only", "read-write", "read-write-plaintext-only", "rectangle", "region", "relative", "repeat", 76 | "repeating-linear-gradient", "repeating-radial-gradient", "repeat-x", "repeat-y", "reset", "reverse", 77 | "rgb", "rgba", "ridge", "right", "rotate", "rotate3d", "rotateX", "rotateY", "rotateZ", "round", 78 | "row", "row-resize", "row-reverse", "rtl", "run-in", "running", "s-resize", "sans-serif", "saturation", 79 | "scale", "scale3d", "scaleX", "scaleY", "scaleZ", "screen", "scroll", "scrollbar", "scroll-position", 80 | "se-resize", "self-start", "self-end", "semi-condensed", "semi-expanded", "separate", "serif", "show", 81 | "single", "skew", "skewX", "skewY", "skip-white-space", "slide", "slider-horizontal", 82 | "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow", "small", "small-caps", 83 | "small-caption", "smaller", "soft-light", "solid", "source-atop", "source-in", "source-out", 84 | "source-over", "space", "space-around", "space-between", "space-evenly", "spell-out", "square", "start", 85 | "static", "status-bar", "stretch", "stroke", "stroke-box", "sub", "subpixel-antialiased", "svg_masks", 86 | "super", "sw-resize", "symbolic", "symbols", "system-ui", "table", "table-caption", "table-cell", 87 | "table-column", "table-column-group", "table-footer-group", "table-header-group", "table-row", 88 | "table-row-group", "text", "text-bottom", "text-top", "textarea", "textfield", "thick", "thin", 89 | "threeddarkshadow", "threedface", "threedhighlight", "threedlightshadow", "threedshadow", "to", "top", 90 | "transform", "translate", "translate3d", "translateX", "translateY", "translateZ", "transparent", 91 | "ultra-condensed", "ultra-expanded", "underline", "unidirectional-pan", "unset", "up", "upper-latin", 92 | "uppercase", "url", "var", "vertical", "vertical-text", "view-box", "visible", "visibleFill", 93 | "visiblePainted", "visibleStroke", "visual", "w-resize", "wait", "wave", "wider", "window", "windowframe", 94 | "windowtext", "words", "wrap", "wrap-reverse", "x-large", "x-small", "xor", "xx-large", "xx-small" 95 | ].map(name => ({type: "keyword", label: name})).concat([ 96 | "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige", 97 | "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown", 98 | "burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue", 99 | "cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod", 100 | "darkgray", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen", 101 | "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen", 102 | "darkslateblue", "darkslategray", "darkturquoise", "darkviolet", 103 | "deeppink", "deepskyblue", "dimgray", "dodgerblue", "firebrick", 104 | "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite", 105 | "gold", "goldenrod", "gray", "grey", "green", "greenyellow", "honeydew", 106 | "hotpink", "indianred", "indigo", "ivory", "khaki", "lavender", 107 | "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral", 108 | "lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightpink", 109 | "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", 110 | "lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta", 111 | "maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple", 112 | "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise", 113 | "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin", 114 | "navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered", 115 | "orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred", 116 | "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue", 117 | "purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown", 118 | "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue", 119 | "slateblue", "slategray", "snow", "springgreen", "steelblue", "tan", 120 | "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white", 121 | "whitesmoke", "yellow", "yellowgreen" 122 | ].map(name => ({type: "constant", label: name}))) 123 | 124 | const tags = [ 125 | "a", "abbr", "address", "article", "aside", "b", "bdi", "bdo", "blockquote", "body", 126 | "br", "button", "canvas", "caption", "cite", "code", "col", "colgroup", "dd", "del", 127 | "details", "dfn", "dialog", "div", "dl", "dt", "em", "figcaption", "figure", "footer", 128 | "form", "header", "hgroup", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "html", "i", "iframe", 129 | "img", "input", "ins", "kbd", "label", "legend", "li", "main", "meter", "nav", "ol", "output", 130 | "p", "pre", "ruby", "section", "select", "small", "source", "span", "strong", "sub", "summary", 131 | "sup", "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "tr", "u", "ul" 132 | ].map(name => ({type: "type", label: name})) 133 | 134 | const atRules = [ 135 | "@charset","@color-profile","@container","@counter-style","@font-face","@font-feature-values", 136 | "@font-palette-values","@import","@keyframes","@layer","@media","@namespace","@page", 137 | "@position-try","@property","@scope","@starting-style","@supports","@view-transition" 138 | ].map(label => ({type: "keyword", label})) 139 | 140 | const identifier = /^(\w[\w-]*|-\w[\w-]*|)$/, variable = /^-(-[\w-]*)?$/ 141 | 142 | function isVarArg(node: SyntaxNode, doc: Text) { 143 | if (node.name == "(" || node.type.isError) node = node.parent || node 144 | if (node.name != "ArgList") return false 145 | let callee = node.parent?.firstChild 146 | if (callee?.name != "Callee") return false 147 | return doc.sliceString(callee.from, callee.to) == "var" 148 | } 149 | 150 | const VariablesByNode = new NodeWeakMap() 151 | const declSelector = ["Declaration"] 152 | 153 | function astTop(node: SyntaxNode) { 154 | for (let cur: SyntaxNode | null = node;;) { 155 | if (cur.type.isTop) return cur 156 | if (!(cur = cur.parent)) return node 157 | } 158 | } 159 | 160 | function variableNames(doc: Text, node: SyntaxNode, 161 | isVariable: (node: SyntaxNodeRef) => boolean): readonly Completion[] { 162 | if (node.to - node.from > 4096) { 163 | let known = VariablesByNode.get(node) 164 | if (known) return known 165 | let result = [], seen = new Set, cursor = node.cursor(IterMode.IncludeAnonymous) 166 | if (cursor.firstChild()) do { 167 | for (let option of variableNames(doc, cursor.node, isVariable)) if (!seen.has(option.label)) { 168 | seen.add(option.label) 169 | result.push(option) 170 | } 171 | } while (cursor.nextSibling()) 172 | VariablesByNode.set(node, result) 173 | return result 174 | } else { 175 | let result: Completion[] = [], seen = new Set 176 | node.cursor().iterate(node => { 177 | if (isVariable(node) && node.matchContext(declSelector) && node.node.nextSibling?.name == ":") { 178 | let name = doc.sliceString(node.from, node.to) 179 | if (!seen.has(name)) { 180 | seen.add(name) 181 | result.push({label: name, type: "variable"}) 182 | } 183 | } 184 | }) 185 | return result 186 | } 187 | } 188 | 189 | /// Create a completion source for a CSS dialect, providing a 190 | /// predicate for determining what kind of syntax node can act as a 191 | /// completable variable. This is used by language modes like Sass and 192 | /// Less to reuse this package's completion logic. 193 | export const defineCSSCompletionSource = (isVariable: (node: SyntaxNodeRef) => boolean): CompletionSource => context => { 194 | let {state, pos} = context, node = syntaxTree(state).resolveInner(pos, -1) 195 | let isDash = node.type.isError && node.from == node.to - 1 && state.doc.sliceString(node.from, node.to) == "-" 196 | if (node.name == "PropertyName" || 197 | (isDash || node.name == "TagName") && /^(Block|Styles)$/.test(node.resolve(node.to).name)) 198 | return {from: node.from, options: properties(), validFor: identifier} 199 | if (node.name == "ValueName") 200 | return {from: node.from, options: values, validFor: identifier} 201 | if (node.name == "PseudoClassName") 202 | return {from: node.from, options: pseudoClasses, validFor: identifier} 203 | if (isVariable(node) || (context.explicit || isDash) && isVarArg(node, state.doc)) 204 | return {from: isVariable(node) || isDash ? node.from : pos, 205 | options: variableNames(state.doc, astTop(node), isVariable), 206 | validFor: variable} 207 | if (node.name == "TagName") { 208 | for (let {parent} = node; parent; parent = parent.parent) 209 | if (parent.name == "Block") return {from: node.from, options: properties(), validFor: identifier} 210 | return {from: node.from, options: tags, validFor: identifier} 211 | } 212 | if (node.name == "AtKeyword") 213 | return {from: node.from, options: atRules, validFor: identifier} 214 | 215 | if (!context.explicit) return null 216 | 217 | let above = node.resolve(pos), before = above.childBefore(pos) 218 | if (before && before.name == ":" && above.name == "PseudoClassSelector") 219 | return {from: pos, options: pseudoClasses, validFor: identifier} 220 | if (before && before.name == ":" && above.name == "Declaration" || above.name == "ArgList") 221 | return {from: pos, options: values, validFor: identifier} 222 | if (above.name == "Block" || above.name == "Styles") 223 | return {from: pos, options: properties(), validFor: identifier} 224 | 225 | return null 226 | } 227 | 228 | /// CSS property, variable, and value keyword completion source. 229 | export const cssCompletionSource: CompletionSource = defineCSSCompletionSource(n => n.name == "VariableName" ) 230 | -------------------------------------------------------------------------------- /src/css.ts: -------------------------------------------------------------------------------- 1 | import {parser} from "@lezer/css" 2 | import {LRLanguage, continuedIndent, indentNodeProp, foldNodeProp, foldInside, LanguageSupport} from "@codemirror/language" 3 | import {cssCompletionSource} from "./complete" 4 | export {cssCompletionSource, defineCSSCompletionSource} from "./complete" 5 | 6 | /// A language provider based on the [Lezer CSS 7 | /// parser](https://github.com/lezer-parser/css), extended with 8 | /// highlighting and indentation information. 9 | export const cssLanguage = LRLanguage.define({ 10 | name: "css", 11 | parser: parser.configure({ 12 | props: [ 13 | indentNodeProp.add({ 14 | Declaration: continuedIndent() 15 | }), 16 | foldNodeProp.add({ 17 | "Block KeyframeList": foldInside 18 | }) 19 | ] 20 | }), 21 | languageData: { 22 | commentTokens: {block: {open: "/*", close: "*/"}}, 23 | indentOnInput: /^\s*\}$/, 24 | wordChars: "-" 25 | } 26 | }) 27 | 28 | /// Language support for CSS. 29 | export function css() { 30 | return new LanguageSupport(cssLanguage, cssLanguage.data.of({autocomplete: cssCompletionSource})) 31 | } 32 | --------------------------------------------------------------------------------