UNPKG

svelte-language-server

Version:
403 lines 17.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CSSPlugin = void 0; const emmet_helper_1 = require("@vscode/emmet-helper"); const vscode_languageserver_1 = require("vscode-languageserver"); const documents_1 = require("../../lib/documents"); const CSSDocument_1 = require("./CSSDocument"); const service_1 = require("./service"); const global_vars_1 = require("./global-vars"); const getIdClassCompletion_1 = require("./features/getIdClassCompletion"); const parseHtml_1 = require("../../lib/documents/parseHtml"); const StyleAttributeDocument_1 = require("./StyleAttributeDocument"); const documentContext_1 = require("../documentContext"); const vscode_languageserver_types_1 = require("vscode-languageserver-types"); const indentFolding_1 = require("../../lib/foldingRange/indentFolding"); const wordHighlight_1 = require("../../lib/documentHighlight/wordHighlight"); const utils_1 = require("../../utils"); // https://github.com/microsoft/vscode/blob/c6f507deeb99925e713271b1048f21dbaab4bd54/extensions/css/language-configuration.json#L34 const wordPattern = /(#?-?\d*\.\d\w*%?)|(::?[\w-]*(?=[^,{;]*[,{]))|(([@#.!])?[\w-?]+%?|[@#!.])/g; class CSSPlugin { constructor(docManager, configManager, workspaceFolders, cssLanguageServices) { this.__name = 'css'; this.cssDocuments = new WeakMap(); this.triggerCharacters = ['.', ':', '-', '/']; this.cssLanguageServices = cssLanguageServices; this.workspaceFolders = workspaceFolders; this.configManager = configManager; this.updateConfigs(); const workspacePaths = workspaceFolders .map((folder) => (0, utils_1.urlToPath)(folder.uri)) .filter(utils_1.isNotNullOrUndefined); this.globalVars = new global_vars_1.GlobalVars(workspacePaths); this.globalVars.watchFiles(this.configManager.get('css.globals')); this.configManager.onChange((config) => { this.globalVars.watchFiles(config.get('css.globals')); this.updateConfigs(); }); docManager.on('documentChange', (document) => this.cssDocuments.set(document, new CSSDocument_1.CSSDocument(document, this.cssLanguageServices))); docManager.on('documentClose', (document) => this.cssDocuments.delete(document)); } getSelectionRange(document, position) { if (!this.featureEnabled('selectionRange') || !(0, documents_1.isInTag)(position, document.styleInfo)) { return null; } const cssDocument = this.getCSSDoc(document); const [range] = this.getLanguageService(extractLanguage(cssDocument)).getSelectionRanges(cssDocument, [cssDocument.getGeneratedPosition(position)], cssDocument.stylesheet); if (!range) { return null; } return (0, documents_1.mapSelectionRangeToParent)(cssDocument, range); } getDiagnostics(document) { if (!this.featureEnabled('diagnostics')) { return []; } const cssDocument = this.getCSSDoc(document); const kind = extractLanguage(cssDocument); if (shouldExcludeValidation(kind)) { return []; } return this.getLanguageService(kind) .doValidation(cssDocument, cssDocument.stylesheet) .map((diagnostic) => ({ ...diagnostic, source: (0, service_1.getLanguage)(kind) })) .map((diagnostic) => (0, documents_1.mapObjWithRangeToOriginal)(cssDocument, diagnostic)); } doHover(document, position) { if (!this.featureEnabled('hover')) { return null; } const cssDocument = this.getCSSDoc(document); if (shouldExcludeHover(cssDocument)) { return null; } if (cssDocument.isInGenerated(position)) { return this.doHoverInternal(cssDocument, position); } const attributeContext = (0, parseHtml_1.getAttributeContextAtPosition)(document, position); if (attributeContext && this.inStyleAttributeWithoutInterpolation(attributeContext, document.getText())) { const [start, end] = attributeContext.valueRange; return this.doHoverInternal(new StyleAttributeDocument_1.StyleAttributeDocument(document, start, end, this.cssLanguageServices), position); } return null; } doHoverInternal(cssDocument, position) { const hoverInfo = this.getLanguageService(extractLanguage(cssDocument)).doHover(cssDocument, cssDocument.getGeneratedPosition(position), cssDocument.stylesheet); return hoverInfo ? (0, documents_1.mapHoverToParent)(cssDocument, hoverInfo) : hoverInfo; } async getCompletions(document, position, completionContext) { const triggerCharacter = completionContext?.triggerCharacter; const triggerKind = completionContext?.triggerKind; const isCustomTriggerCharacter = triggerKind === vscode_languageserver_1.CompletionTriggerKind.TriggerCharacter; if (isCustomTriggerCharacter && triggerCharacter && !this.triggerCharacters.includes(triggerCharacter)) { return null; } if (!this.featureEnabled('completions')) { return null; } const cssDocument = this.getCSSDoc(document); if (cssDocument.isInGenerated(position)) { return this.getCompletionsInternal(document, position, cssDocument); } const attributeContext = (0, parseHtml_1.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_1.StyleAttributeDocument(document, start, end, this.cssLanguageServices)); } else { return (0, getIdClassCompletion_1.getIdClassCompletion)(cssDocument, attributeContext); } } inStyleAttributeWithoutInterpolation(attrContext, text) { return (attrContext.name === 'style' && !!attrContext.valueRange && !text.substring(attrContext.valueRange[0], attrContext.valueRange[1]).includes('{')); } async getCompletionsInternal(document, position, cssDocument) { 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 ((0, emmet_helper_1.doComplete)(document, position, 'sass', this.configManager.getEmmetConfig()) || null); } const type = extractLanguage(cssDocument); if (shouldExcludeCompletion(type)) { return null; } const lang = this.getLanguageService(type); let emmetResults = { isIncomplete: false, items: [] }; if (this.configManager.getConfig().css.completions.emmet && this.configManager.getEmmetConfig().showExpandedAbbreviation !== 'never') { lang.setCompletionParticipants([ { onCssProperty: (context) => { if (context?.propertyName) { emmetResults = (0, emmet_helper_1.doComplete)(cssDocument, cssDocument.getGeneratedPosition(position), (0, service_1.getLanguage)(type), this.configManager.getEmmetConfig()) || emmetResults; } }, onCssPropertyValue: (context) => { if (context?.propertyValue) { emmetResults = (0, emmet_helper_1.doComplete)(cssDocument, cssDocument.getGeneratedPosition(position), (0, service_1.getLanguage)(type), this.configManager.getEmmetConfig()) || emmetResults; } } } ]); } const results = await lang.doComplete2(cssDocument, cssDocument.getGeneratedPosition(position), cssDocument.stylesheet, (0, documentContext_1.getDocumentContext)(cssDocument.uri, this.workspaceFolders)); return vscode_languageserver_1.CompletionList.create(this.appendGlobalVars([...(results ? results.items : []), ...emmetResults.items].map((completionItem) => (0, documents_1.mapCompletionItemToOriginal)(cssDocument, completionItem))), // Emmet completions change on every keystroke, so they are never complete emmetResults.items.length > 0); } appendGlobalVars(items) { // Finding one value with that item kind means we are in a value completion scenario const value = items.find((item) => item.kind === vscode_languageserver_1.CompletionItemKind.Value); if (!value) { return items; } const additionalItems = this.globalVars .getGlobalVars() .map((globalVar) => ({ label: `var(${globalVar.name})`, sortText: '-', detail: `${globalVar.filename}\n\n${globalVar.name}: ${globalVar.value}`, kind: vscode_languageserver_1.CompletionItemKind.Value })); return [...items, ...additionalItems]; } getDocumentColors(document) { if (!this.featureEnabled('documentColors')) { return []; } const cssDocument = this.getCSSDoc(document); if (shouldExcludeColor(cssDocument)) { return []; } return this.getLanguageService(extractLanguage(cssDocument)) .findDocumentColors(cssDocument, cssDocument.stylesheet) .map((colorInfo) => (0, documents_1.mapObjWithRangeToOriginal)(cssDocument, colorInfo)); } getColorPresentations(document, range, color) { if (!this.featureEnabled('colorPresentations')) { return []; } const cssDocument = this.getCSSDoc(document); if ((!cssDocument.isInGenerated(range.start) && !cssDocument.isInGenerated(range.end)) || shouldExcludeColor(cssDocument)) { return []; } return this.getLanguageService(extractLanguage(cssDocument)) .getColorPresentations(cssDocument, cssDocument.stylesheet, color, (0, documents_1.mapRangeToGenerated)(cssDocument, range)) .map((colorPres) => (0, documents_1.mapColorPresentationToOriginal)(cssDocument, colorPres)); } getDocumentSymbols(document) { if (!this.featureEnabled('documentColors')) { return []; } const cssDocument = this.getCSSDoc(document); if (shouldExcludeDocumentSymbols(cssDocument)) { return []; } return this.getLanguageService(extractLanguage(cssDocument)) .findDocumentSymbols(cssDocument, cssDocument.stylesheet) .map((symbol) => { if (!symbol.containerName) { return { ...symbol, // TODO: this could contain other things, e.g. style.myclass containerName: 'style' }; } return symbol; }) .map((symbol) => (0, documents_1.mapSymbolInformationToOriginal)(cssDocument, symbol)); } getFoldingRanges(document) { if (!document.styleInfo) { return []; } const cssDocument = this.getCSSDoc(document); if (shouldUseIndentBasedFolding(cssDocument.languageId)) { return this.nonSyntacticFolding(document, document.styleInfo); } return this.getLanguageService(extractLanguage(cssDocument)) .getFoldingRanges(cssDocument) .map((range) => { const originalRange = (0, documents_1.mapRangeToOriginal)(cssDocument, { start: { line: range.startLine, character: range.startCharacter ?? 0 }, end: { line: range.endLine, character: range.endCharacter ?? 0 } }); return { startLine: originalRange.start.line, endLine: originalRange.end.line, kind: range.kind }; }); } nonSyntacticFolding(document, styleInfo) { const ranges = (0, indentFolding_1.indentBasedFoldingRangeForTag)(document, styleInfo); const startRegion = /^\s*(\/\/|\/\*\*?)\s*#?region\b/; const endRegion = /^\s*(\/\/|\/\*\*?)\s*#?endregion\b/; const lines = document .getText() .split(/\r?\n/) .slice(styleInfo.startPos.line, styleInfo.endPos.line); let start = -1; for (let index = 0; index < lines.length; index++) { const line = lines[index]; if (startRegion.test(line)) { start = index; } else if (endRegion.test(line)) { if (start >= 0) { ranges.push({ startLine: start + styleInfo.startPos.line, endLine: index + styleInfo.startPos.line, kind: vscode_languageserver_types_1.FoldingRangeKind.Region }); } start = -1; } } return ranges.sort((a, b) => a.startLine - b.startLine); } findDocumentHighlight(document, position) { const cssDocument = this.getCSSDoc(document); if (cssDocument.isInGenerated(position)) { if (shouldExcludeDocumentHighlights(cssDocument)) { return (0, wordHighlight_1.wordHighlightForTag)(document, position, document.styleInfo, wordPattern); } return this.findDocumentHighlightInternal(cssDocument, position); } const attributeContext = (0, parseHtml_1.getAttributeContextAtPosition)(document, position); if (attributeContext && this.inStyleAttributeWithoutInterpolation(attributeContext, document.getText())) { const [start, end] = attributeContext.valueRange; return this.findDocumentHighlightInternal(new StyleAttributeDocument_1.StyleAttributeDocument(document, start, end, this.cssLanguageServices), position); } return null; } findDocumentHighlightInternal(cssDocument, position) { const kind = extractLanguage(cssDocument); const result = (0, service_1.getLanguageService)(this.cssLanguageServices, kind) .findDocumentHighlights(cssDocument, cssDocument.getGeneratedPosition(position), cssDocument.stylesheet) .map((highlight) => (0, documents_1.mapObjWithRangeToOriginal)(cssDocument, highlight)); return result; } getCSSDoc(document) { let cssDoc = this.cssDocuments.get(document); if (!cssDoc || cssDoc.version < document.version) { cssDoc = new CSSDocument_1.CSSDocument(document, this.cssLanguageServices); this.cssDocuments.set(document, cssDoc); } return cssDoc; } updateConfigs() { this.getLanguageService('css')?.configure(this.configManager.getCssConfig()); this.getLanguageService('scss')?.configure(this.configManager.getScssConfig()); this.getLanguageService('less')?.configure(this.configManager.getLessConfig()); } featureEnabled(feature) { return (this.configManager.enabled('css.enable') && this.configManager.enabled(`css.${feature}.enable`)); } getLanguageService(kind) { return (0, service_1.getLanguageService)(this.cssLanguageServices, kind); } } exports.CSSPlugin = CSSPlugin; function shouldExcludeValidation(kind) { switch (kind) { case 'postcss': case 'sass': case 'stylus': case 'styl': return true; default: return false; } } function shouldExcludeCompletion(kind) { switch (kind) { case 'stylus': case 'styl': return true; default: return false; } } function shouldExcludeDocumentSymbols(document) { switch (extractLanguage(document)) { case 'sass': case 'stylus': case 'styl': return true; default: return false; } } function shouldExcludeHover(document) { switch (extractLanguage(document)) { case 'sass': case 'stylus': case 'styl': return true; default: return false; } } function shouldExcludeColor(document) { switch (extractLanguage(document)) { case 'sass': case 'stylus': case 'styl': return true; default: return false; } } function shouldUseIndentBasedFolding(kind) { switch (kind) { case 'postcss': case 'sass': case 'stylus': case 'styl': return true; default: return false; } } function shouldExcludeDocumentHighlights(document) { switch (extractLanguage(document)) { case 'postcss': case 'sass': case 'stylus': case 'styl': return true; default: return false; } } function isSASS(document) { switch (extractLanguage(document)) { case 'sass': return true; default: return false; } } function extractLanguage(document) { const lang = document.languageId; return lang.replace(/^text\//, ''); } //# sourceMappingURL=CSSPlugin.js.map