-
-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Playground autocomplete #77
Changes from 3 commits
4d72eb7
f5dbee6
65f116f
cc11eff
b58c1bb
b699ec7
b2937f2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,296 @@ | ||
import { | ||
CompletionContext, | ||
CompletionResult, | ||
CompletionSource, | ||
} from '@codemirror/autocomplete'; | ||
import {sassCompletionSource} from '@codemirror/lang-sass'; | ||
import {syntaxTree} from '@codemirror/language'; | ||
import {EditorState} from '@codemirror/state'; | ||
|
||
const atRuleKeywords = [ | ||
'use', | ||
'forward', | ||
'import', | ||
'mixin', | ||
'include', | ||
'function', | ||
'extend', | ||
'error', | ||
'warn', | ||
'debug', | ||
'at-root', | ||
'if', | ||
'else', | ||
'each', | ||
'for', | ||
'while', | ||
]; | ||
|
||
const atRuleOptions = Object.freeze( | ||
atRuleKeywords.map(keyword => ({ | ||
label: `@${keyword} `, | ||
type: 'keyword', | ||
})) | ||
); | ||
|
||
function atRuleCompletion(context: CompletionContext): CompletionResult | null { | ||
const atRule = context.matchBefore(/@\w*/); | ||
if (!atRule) return null; | ||
if (atRule.from === atRule.to && !context.explicit) return null; | ||
return { | ||
from: atRule.from, | ||
to: atRule.to, | ||
options: atRuleOptions, | ||
}; | ||
} | ||
|
||
type CompletionInfo = { | ||
name: string; | ||
description?: string; | ||
}; | ||
|
||
type ModuleDefinition = CompletionInfo & { | ||
functions?: CompletionInfo[]; | ||
variables?: CompletionInfo[]; | ||
}; | ||
|
||
const builtinModules: ModuleDefinition[] = [ | ||
{ | ||
name: 'color', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason we're not including the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm holding out hope there's a way to get this from Sass directly, as this is a long list, and will be changing once the CSS Color 4 changes are made in Sass. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense -- add a comment so we don't forget? |
||
description: | ||
'generates new colors based on existing ones, making it easy to build color themes', | ||
}, | ||
{ | ||
name: 'list', | ||
description: 'lets you access and modify values in lists', | ||
functions: [ | ||
{name: 'length'}, | ||
{name: 'nth'}, | ||
{name: 'set-nth'}, | ||
{name: 'join'}, | ||
{name: 'append'}, | ||
{name: 'zip'}, | ||
{name: 'index'}, | ||
{name: 'is-bracketed'}, | ||
{name: 'separator'}, | ||
{name: 'slash'}, | ||
], | ||
}, | ||
{ | ||
name: 'map', | ||
description: | ||
'makes it possible to look up the value associated with a key in a map, and much more', | ||
functions: [ | ||
{name: 'get'}, | ||
{name: 'set'}, | ||
{name: 'merge'}, | ||
{name: 'remove'}, | ||
{name: 'keys'}, | ||
{name: 'values'}, | ||
{name: 'has-key'}, | ||
{name: 'deep-merge'}, | ||
{name: 'deep-remove'}, | ||
], | ||
}, | ||
{ | ||
name: 'math', | ||
description: 'provides functions that operate on numbers', | ||
variables: [ | ||
{name: '$e', description: undefined}, | ||
jamesnw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{name: '$pi'}, | ||
{name: '$epsilon'}, | ||
{name: '$max-safe-integer'}, | ||
{name: '$min-safe-integer'}, | ||
{name: '$max-number'}, | ||
{name: '$min-number'}, | ||
], | ||
functions: [ | ||
{name: 'ceil', description: undefined}, | ||
{name: 'clamp'}, | ||
{name: 'floor'}, | ||
{name: 'max'}, | ||
{name: 'min'}, | ||
{name: 'round'}, | ||
{name: 'abs'}, | ||
{name: 'hypot'}, | ||
{name: 'log'}, | ||
{name: 'pow'}, | ||
{name: 'sqrt'}, | ||
{name: 'acos'}, | ||
{name: 'asin'}, | ||
{name: 'atan'}, | ||
{name: 'atan2'}, | ||
{name: 'cos'}, | ||
{name: 'sin'}, | ||
{name: 'tan'}, | ||
{name: 'compatible'}, | ||
{name: 'is-unitless'}, | ||
{name: 'unit'}, | ||
{name: 'div'}, | ||
{name: 'percentage'}, | ||
{name: 'random'}, | ||
], | ||
}, | ||
{ | ||
name: 'meta', | ||
description: 'exposes the details of Sass’s inner workings', | ||
functions: [ | ||
{name: 'apply'}, | ||
{name: 'load-css'}, | ||
{name: 'accepts-content'}, | ||
{name: 'calc-args'}, | ||
jamesnw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{name: 'call'}, | ||
{name: 'context-exists'}, | ||
{name: 'feature-exists'}, | ||
{name: 'function-exists'}, | ||
{name: 'mixin-exists'}, | ||
{name: 'variable-exists'}, | ||
{name: 'get-function'}, | ||
{name: 'get-mixin'}, | ||
{name: 'global-variable-exists'}, | ||
{name: 'keywords'}, | ||
{name: 'module-functions'}, | ||
{name: 'module-mixins'}, | ||
{name: 'module-variables'}, | ||
{name: 'type-of'}, | ||
jamesnw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
], | ||
}, | ||
{ | ||
name: 'selector', | ||
description: 'provides access to Sass’s powerful selector engine', | ||
functions: [ | ||
{name: 'isSuperselector'}, | ||
{name: 'simpleSelectors'}, | ||
jamesnw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{name: 'parse'}, | ||
{name: 'nest'}, | ||
{name: 'append'}, | ||
{name: 'extend'}, | ||
{name: 'replace'}, | ||
{name: 'unify'}, | ||
], | ||
}, | ||
{ | ||
name: 'string', | ||
description: 'makes it easy to combine, search, or split apart strings', | ||
functions: [ | ||
{name: 'unquote'}, | ||
{name: 'quote'}, | ||
{name: 'to-upper-case'}, | ||
{name: 'to-lower-case'}, | ||
{name: 'length'}, | ||
{name: 'insert'}, | ||
{name: 'index'}, | ||
{name: 'slice'}, | ||
{name: 'unique-id'}, | ||
{name: 'split'}, | ||
], | ||
}, | ||
]; | ||
|
||
const moduleNames = builtinModules.map(mod => mod.name); | ||
const moduleNameRegExp = new RegExp(`(${moduleNames.join('|')}).\\$?(\\w)*`); | ||
jamesnw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const moduleCompletions = Object.freeze( | ||
builtinModules.map(mod => ({ | ||
label: `"sass:${mod.name}"`, | ||
// don't add extra quote on the end, as it likely is already there | ||
apply: `"sass:${mod.name}`, | ||
info: mod.description, | ||
type: 'class', | ||
})) | ||
); | ||
|
||
function builtinModulesCompletion( | ||
context: CompletionContext | ||
): CompletionResult | null { | ||
const nodeBefore = syntaxTree(context.state).resolveInner(context.pos, -1); | ||
if (nodeBefore.parent?.type.name !== 'UseStatement') return null; | ||
|
||
const atRule = context.matchBefore(/"(sass:)?\w*/); | ||
|
||
if (!atRule) return null; | ||
if (atRule.from === atRule.to && !context.explicit) return null; | ||
return { | ||
from: atRule.from, | ||
to: atRule.to, | ||
options: moduleCompletions, | ||
}; | ||
} | ||
|
||
const moduleVariableCompletions = Object.freeze( | ||
builtinModules.reduce( | ||
(acc: {[k: string]: CompletionResult['options'] | []}, mod) => { | ||
acc[mod.name] = | ||
mod.variables?.map(variable => ({ | ||
label: `${mod.name}.${variable.name}`, | ||
info: variable?.description, | ||
type: 'variable', | ||
})) || []; | ||
return acc; | ||
}, | ||
{} | ||
) | ||
); | ||
|
||
const moduleFunctionsCompletions = Object.freeze( | ||
builtinModules.reduce( | ||
(acc: {[k: string]: CompletionResult['options'] | []}, mod) => { | ||
acc[mod.name] = | ||
mod.functions?.map(variable => ({ | ||
label: `${mod.name}.${variable.name}`, | ||
apply: `${mod.name}.${variable.name}(`, | ||
info: variable?.description, | ||
type: 'method', | ||
boost: 10, | ||
})) || []; | ||
return acc; | ||
}, | ||
{} | ||
) | ||
); | ||
|
||
function includedBuiltinModules(state: EditorState) { | ||
const text = state.doc.toString(); | ||
const modNames = builtinModules.map(mod => mod.name); | ||
return modNames.filter(name => text.includes(`sass:${name}`)); | ||
} | ||
|
||
function builtinModuleItemCompletion( | ||
context: CompletionContext | ||
): CompletionResult | null { | ||
const nodeBefore = syntaxTree(context.state).resolveInner(context.pos, -1); | ||
if ( | ||
![nodeBefore.type.name, nodeBefore.parent?.type.name].includes( | ||
'NamespacedValue' | ||
) | ||
) | ||
return null; | ||
const atRule = context.matchBefore(moduleNameRegExp); | ||
|
||
if (!atRule) return null; | ||
if (atRule.from === atRule.to && !context.explicit) return null; | ||
|
||
const includedModules = includedBuiltinModules(context.state); | ||
|
||
const includedModFunctions = includedModules.flatMap( | ||
mod => moduleFunctionsCompletions[mod] | ||
); | ||
const includedModVariables = includedModules.flatMap( | ||
mod => moduleVariableCompletions[mod] | ||
); | ||
|
||
return { | ||
from: atRule.from, | ||
to: atRule.to, | ||
options: [...includedModVariables, ...includedModFunctions], | ||
}; | ||
} | ||
|
||
const playgroundCompletions: CompletionSource[] = [ | ||
atRuleCompletion, | ||
builtinModulesCompletion, | ||
sassCompletionSource, | ||
builtinModuleItemCompletion, | ||
]; | ||
|
||
export default playgroundCompletions; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to add
@media
,@supports
, and@keyframes
here? And maybe also add autocomplete for other CSS at-rules?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think css at-rules would be good to consider supporting. Hopefully there's a clear enough division between that and auto-completing css properties/values, so this doesn't become a very slippery scope slope.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that probably should go upstream into @codemirror/lang-css- that shouldn't be too challenging, I don't think.
We are already getting auto-completion of css properties and values from that package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense -- add a comment or file a new issue so we don't lose track?