kjelsrud.dev/node_modules/@astrojs/language-server/dist/plugins/css/CSSPlugin.js
2023-07-19 21:31:30 +02:00

274 lines
13 KiB
JavaScript

"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("../../core/documents");
const parseHtml_1 = require("../../core/documents/parseHtml");
const CSSDocument_1 = require("./CSSDocument");
const getIdClassCompletions_1 = require("./features/getIdClassCompletions");
const language_service_1 = require("./language-service");
const StyleAttributeDocument_1 = require("./StyleAttributeDocument");
class CSSPlugin {
constructor(configManager) {
this.__name = 'css';
this.cssDocuments = new WeakMap();
this.triggerCharacters = new Set(['.', ':', '-', '/']);
this.configManager = configManager;
}
async doHover(document, position) {
if (!(await this.featureEnabled(document, 'hover'))) {
return null;
}
if ((0, documents_1.isInsideFrontmatter)(document.getText(), document.offsetAt(position))) {
return null;
}
const styleTag = this.getStyleTagForPosition(document, position);
// We technically can return results even for open tags, however, a lot of the info returned is not valid
// Since most editors will automatically close the tag before the user start working in them, this shouldn't be a problem
if (styleTag && !styleTag.closed) {
return null;
}
// If we don't have a style tag at this position, we might be in a style property instead, let's check
if (!styleTag) {
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.doHoverInternal(new StyleAttributeDocument_1.StyleAttributeDocument(document, start, end), position);
}
return null;
}
const cssDocument = this.getCSSDocumentForStyleTag(styleTag, document);
const cssLang = extractLanguage(cssDocument);
if (!isSupportedByLangService(cssLang)) {
return null;
}
return this.doHoverInternal(cssDocument, position);
}
doHoverInternal(cssDocument, position) {
const hoverInfo = (0, language_service_1.getLanguageService)(extractLanguage(cssDocument)).doHover(cssDocument, cssDocument.getGeneratedPosition(position), cssDocument.stylesheet);
return hoverInfo ? (0, documents_1.mapHoverToParent)(cssDocument, hoverInfo) : hoverInfo;
}
async getCompletions(document, position, completionContext) {
if (!(await this.featureEnabled(document, 'completions'))) {
return null;
}
if ((0, documents_1.isInsideFrontmatter)(document.getText(), document.offsetAt(position))) {
return null;
}
const triggerCharacter = completionContext?.triggerCharacter;
const triggerKind = completionContext?.triggerKind;
const isCustomTriggerCharacter = triggerKind === vscode_languageserver_1.CompletionTriggerKind.TriggerCharacter;
if (isCustomTriggerCharacter && triggerCharacter && !this.triggerCharacters.has(triggerCharacter)) {
return null;
}
const styleTag = this.getStyleTagForPosition(document, position);
if (styleTag && !styleTag.closed) {
return null;
}
if (!styleTag) {
const attributeContext = (0, parseHtml_1.getAttributeContextAtPosition)(document, position);
if (!attributeContext) {
return null;
}
if (this.inStyleAttributeWithoutInterpolation(attributeContext, document.getText())) {
const [start, end] = attributeContext.valueRange;
return await this.getCompletionsInternal(document, position, new StyleAttributeDocument_1.StyleAttributeDocument(document, start, end));
}
// If we're not in a style attribute, instead give completions for ids and classes used in the current document
else if ((attributeContext.name == 'id' || attributeContext.name == 'class') && attributeContext.inValue) {
const stylesheets = this.getStylesheetsForDocument(document);
return (0, getIdClassCompletions_1.getIdClassCompletion)(stylesheets, attributeContext);
}
return null;
}
const cssDocument = this.getCSSDocumentForStyleTag(styleTag, document);
return await this.getCompletionsInternal(document, position, cssDocument);
}
async getCompletionsInternal(document, position, cssDocument) {
const emmetConfig = await this.configManager.getEmmetConfig(document);
if (isSASS(cssDocument)) {
// The CSS language service does not support SASS (not to be confused with SCSS)
// however we can at least still at least provide Emmet completions in SASS blocks
return (0, emmet_helper_1.doComplete)(document, position, 'sass', emmetConfig) || null;
}
const cssLang = extractLanguage(cssDocument);
const langService = (0, language_service_1.getLanguageService)(cssLang);
let emmetResults = {
isIncomplete: false,
items: [],
};
const extensionConfig = await this.configManager.getConfig('astro', document.uri);
if (extensionConfig?.css?.completions?.emmet ?? true) {
langService.setCompletionParticipants([
{
onCssProperty: (context) => {
if (context?.propertyName) {
emmetResults =
(0, emmet_helper_1.doComplete)(cssDocument, cssDocument.getGeneratedPosition(position), (0, language_service_1.getLanguage)(cssLang), emmetConfig) || emmetResults;
}
},
onCssPropertyValue: (context) => {
if (context?.propertyValue) {
emmetResults =
(0, emmet_helper_1.doComplete)(cssDocument, cssDocument.getGeneratedPosition(position), (0, language_service_1.getLanguage)(cssLang), emmetConfig) || emmetResults;
}
},
},
]);
}
const results = langService.doComplete(cssDocument, cssDocument.getGeneratedPosition(position), cssDocument.stylesheet);
return vscode_languageserver_1.CompletionList.create([...(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);
}
async getDocumentColors(document) {
if (!(await this.featureEnabled(document, 'documentColors'))) {
return [];
}
const allColorInfo = this.getCSSDocumentsForDocument(document).flatMap((cssDoc) => {
const cssLang = extractLanguage(cssDoc);
const langService = (0, language_service_1.getLanguageService)(cssLang);
if (!isSupportedByLangService(cssLang)) {
return [];
}
return langService
.findDocumentColors(cssDoc, cssDoc.stylesheet)
.map((colorInfo) => (0, documents_1.mapObjWithRangeToOriginal)(cssDoc, colorInfo));
});
return allColorInfo;
}
async getColorPresentations(document, range, color) {
if (!(await this.featureEnabled(document, 'documentColors'))) {
return [];
}
const allColorPres = this.getCSSDocumentsForDocument(document).flatMap((cssDoc) => {
const cssLang = extractLanguage(cssDoc);
const langService = (0, language_service_1.getLanguageService)(cssLang);
if ((!cssDoc.isInGenerated(range.start) && !cssDoc.isInGenerated(range.end)) ||
!isSupportedByLangService(cssLang)) {
return [];
}
return langService
.getColorPresentations(cssDoc, cssDoc.stylesheet, color, (0, documents_1.mapRangeToGenerated)(cssDoc, range))
.map((colorPres) => (0, documents_1.mapColorPresentationToOriginal)(cssDoc, colorPres));
});
return allColorPres;
}
prepareRename(document, position) {
const styleTag = this.getStyleTagForPosition(document, position);
if (!styleTag) {
return null;
}
const cssDocument = this.getCSSDocumentForStyleTag(styleTag, document);
const cssLang = extractLanguage(cssDocument);
const langService = (0, language_service_1.getLanguageService)(cssLang);
const range = langService.prepareRename(cssDocument, cssDocument.getGeneratedPosition(position), cssDocument.stylesheet);
if (!range) {
return null;
}
return (0, documents_1.mapRangeToOriginal)(cssDocument, range);
}
rename(document, position, newName) {
const styleTag = this.getStyleTagForPosition(document, position);
if (!styleTag) {
return null;
}
const cssDocument = this.getCSSDocumentForStyleTag(styleTag, document);
const cssLang = extractLanguage(cssDocument);
const langService = (0, language_service_1.getLanguageService)(cssLang);
const renames = langService.doRename(cssDocument, cssDocument.getGeneratedPosition(position), newName, cssDocument.stylesheet);
if (renames?.changes?.[document.uri]) {
renames.changes[document.uri] = renames?.changes?.[document.uri].map((edit) => (0, documents_1.mapObjWithRangeToOriginal)(cssDocument, edit));
}
return renames;
}
getFoldingRanges(document) {
const allFoldingRanges = this.getCSSDocumentsForDocument(document).flatMap((cssDoc) => {
const cssLang = extractLanguage(cssDoc);
const langService = (0, language_service_1.getLanguageService)(cssLang);
return langService.getFoldingRanges(cssDoc).map((foldingRange) => (0, documents_1.mapFoldingRangeToParent)(cssDoc, foldingRange));
});
return allFoldingRanges;
}
async getDocumentSymbols(document) {
if (!(await this.featureEnabled(document, 'documentSymbols'))) {
return [];
}
const allDocumentSymbols = this.getCSSDocumentsForDocument(document).flatMap((cssDoc) => {
return (0, language_service_1.getLanguageService)(extractLanguage(cssDoc))
.findDocumentSymbols(cssDoc, cssDoc.stylesheet)
.map((symbol) => (0, documents_1.mapSymbolInformationToOriginal)(cssDoc, symbol));
});
return allDocumentSymbols;
}
inStyleAttributeWithoutInterpolation(attrContext, text) {
return (attrContext.name === 'style' &&
!!attrContext.valueRange &&
!text.substring(attrContext.valueRange[0], attrContext.valueRange[1]).includes('{'));
}
/**
* Get the associated CSS Document for a style tag
*/
getCSSDocumentForStyleTag(tag, document) {
let cssDoc = this.cssDocuments.get(tag);
if (!cssDoc || cssDoc.version < document.version) {
cssDoc = new CSSDocument_1.CSSDocument(document, tag);
this.cssDocuments.set(tag, cssDoc);
}
return cssDoc;
}
/**
* Get all the CSSDocuments in a document
*/
getCSSDocumentsForDocument(document) {
return document.styleTags.map((tag) => this.getCSSDocumentForStyleTag(tag, document));
}
/**
* Get all the stylesheets (Stylesheet type) in a document
*/
getStylesheetsForDocument(document) {
return this.getCSSDocumentsForDocument(document).map((cssDoc) => cssDoc.stylesheet);
}
/**
* Get style tag at position for a document
*/
getStyleTagForPosition(document, position) {
return document.styleTags.find((styleTag) => {
return (0, documents_1.isInTag)(position, styleTag);
});
}
async featureEnabled(document, feature) {
return ((await this.configManager.isEnabled(document, 'css')) &&
(await this.configManager.isEnabled(document, 'css', feature)));
}
}
exports.CSSPlugin = CSSPlugin;
/**
* Check is a CSSDocument's language is supported by the CSS language service
*/
function isSupportedByLangService(language) {
switch (language) {
case 'css':
case 'scss':
case 'less':
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\//, '');
}