summaryrefslogtreecommitdiff
path: root/tools/astro-languageserver/src/plugins/css/CSSPlugin.ts
diff options
context:
space:
mode:
authorGravatar Matthew Phillips <matthew@matthewphillips.info> 2021-05-17 14:27:24 -0400
committerGravatar GitHub <noreply@github.com> 2021-05-17 14:27:24 -0400
commitc3c96bf498f9f989825ada7110be7bc680adac53 (patch)
tree7b5ad7597697c771d7bd917504706a721cbe026b /tools/astro-languageserver/src/plugins/css/CSSPlugin.ts
parent27a7986a384263fab81695e1f9b16eb2f12caa2c (diff)
downloadastro-c3c96bf498f9f989825ada7110be7bc680adac53.tar.gz
astro-c3c96bf498f9f989825ada7110be7bc680adac53.tar.zst
astro-c3c96bf498f9f989825ada7110be7bc680adac53.zip
Adds CSS completions to VSCode extension (#214)
* Start on css completion * Support for CSS completions
Diffstat (limited to 'tools/astro-languageserver/src/plugins/css/CSSPlugin.ts')
-rw-r--r--tools/astro-languageserver/src/plugins/css/CSSPlugin.ts153
1 files changed, 153 insertions, 0 deletions
diff --git a/tools/astro-languageserver/src/plugins/css/CSSPlugin.ts b/tools/astro-languageserver/src/plugins/css/CSSPlugin.ts
new file mode 100644
index 000000000..4c0dcb949
--- /dev/null
+++ b/tools/astro-languageserver/src/plugins/css/CSSPlugin.ts
@@ -0,0 +1,153 @@
+import type { CompletionsProvider } from '../interfaces';
+import type { Document, DocumentManager } from '../../core/documents';
+import type { ConfigManager } from '../../core/config';
+import { getEmmetCompletionParticipants, doComplete as doEmmetComplete } from 'vscode-emmet-helper';
+import { CompletionContext, CompletionList, CompletionTriggerKind, Position } from 'vscode-languageserver';
+import { isInsideFrontmatter } from '../../core/documents/utils';
+import { CSSDocument, CSSDocumentBase } from './CSSDocument';
+import { getLanguage, getLanguageService } from './service';
+import { StyleAttributeDocument } from './StyleAttributeDocument';
+import { mapCompletionItemToOriginal } from '../../core/documents';
+import { AttributeContext, getAttributeContextAtPosition } from '../../core/documents/parseHtml';
+import { getIdClassCompletion } from './features/getIdClassCompletion';
+
+export class CSSPlugin implements CompletionsProvider {
+ private docManager: DocumentManager;
+ private configManager: ConfigManager;
+ private documents = new WeakMap<Document, CSSDocument>();
+ private triggerCharacters = new Set(['.', ':', '-', '/']);
+
+ constructor(docManager: DocumentManager, configManager: ConfigManager) {
+ this.docManager = docManager;
+ this.configManager = configManager;
+
+ this.docManager.on('documentChange', (document) => {
+ this.documents.set(document, new CSSDocument(document));
+ });
+ }
+
+ getCompletions(
+ document: Document,
+ position: Position,
+ completionContext?: CompletionContext
+ ): CompletionList | null {
+ const triggerCharacter = completionContext?.triggerCharacter;
+ const triggerKind = completionContext?.triggerKind;
+ const isCustomTriggerCharacter = triggerKind === CompletionTriggerKind.TriggerCharacter;
+
+ if (
+ isCustomTriggerCharacter &&
+ triggerCharacter &&
+ !this.triggerCharacters.has(triggerCharacter)
+ ) {
+ return null;
+ }
+
+ if(this.isInsideFrontmatter(document, position)) {
+ return null;
+ }
+
+ const cssDocument = this.getCSSDoc(document);
+
+ if (cssDocument.isInGenerated(position)) {
+ return this.getCompletionsInternal(document, position, cssDocument);
+ }
+
+ const attributeContext = getAttributeContextAtPosition(document, position);
+ if (!attributeContext) {
+ return null;
+ }
+
+ if (this.inStyleAttributeWithoutInterpolation(attributeContext, document.getText())) {
+ const [start, end] = attributeContext.valueRange;
+ return this.getCompletionsInternal(
+ document,
+ position,
+ new StyleAttributeDocument(document, start, end)
+ );
+ } else {
+ return getIdClassCompletion(cssDocument, attributeContext);
+ }
+ }
+
+ private getCompletionsInternal(
+ document: Document,
+ position: Position,
+ cssDocument: CSSDocumentBase
+ ) {
+ if (isSASS(cssDocument)) {
+ // the css language service does not support sass, still we can use
+ // the emmet helper directly to at least get emmet completions
+ return doEmmetComplete(document, position, 'sass', this.configManager.getEmmetConfig());
+ }
+
+ const type = extractLanguage(cssDocument);
+
+ const lang = getLanguageService(type);
+ const emmetResults: CompletionList = {
+ isIncomplete: true,
+ items: []
+ };
+ if (false /* this.configManager.getConfig().css.completions.emmet */) {
+ lang.setCompletionParticipants([
+ getEmmetCompletionParticipants(
+ cssDocument,
+ cssDocument.getGeneratedPosition(position),
+ getLanguage(type),
+ this.configManager.getEmmetConfig(),
+ emmetResults
+ )
+ ]);
+ }
+ const results = lang.doComplete(
+ cssDocument,
+ cssDocument.getGeneratedPosition(position),
+ cssDocument.stylesheet
+ );
+ return CompletionList.create(
+ [...(results ? results.items : []), ...emmetResults.items].map((completionItem) =>
+ mapCompletionItemToOriginal(cssDocument, completionItem)
+ ),
+ // Emmet completions change on every keystroke, so they are never complete
+ emmetResults.items.length > 0
+ );
+ }
+
+ private inStyleAttributeWithoutInterpolation(
+ attrContext: AttributeContext,
+ text: string
+ ): attrContext is Required<AttributeContext> {
+ return (
+ attrContext.name === 'style' &&
+ !!attrContext.valueRange &&
+ !text.substring(attrContext.valueRange[0], attrContext.valueRange[1]).includes('{')
+ );
+ }
+
+ private getCSSDoc(document: Document) {
+ let cssDoc = this.documents.get(document);
+ if (!cssDoc || cssDoc.version < document.version) {
+ cssDoc = new CSSDocument(document);
+ this.documents.set(document, cssDoc);
+ }
+ return cssDoc;
+ }
+
+ private isInsideFrontmatter(document: Document, position: Position) {
+ return isInsideFrontmatter(document.getText(), document.offsetAt(position));
+ }
+}
+
+function isSASS(document: CSSDocumentBase) {
+ switch (extractLanguage(document)) {
+ case 'sass':
+ return true;
+ default:
+ return false;
+ }
+}
+
+function extractLanguage(document: CSSDocumentBase): string {
+ const lang = document.languageId;
+ return lang.replace(/^text\//, '');
+} \ No newline at end of file