vscode-gem-languageservice
Version:
Language service for Gem
480 lines (465 loc) • 19.7 kB
JavaScript
;
// src/index.ts
var import_node4 = require("vscode-languageserver/node");
var import_vscode_languageserver_textdocument = require("vscode-languageserver-textdocument");
var import_timer = require("duoyun-ui/lib/timer");
// src/color.ts
var import_color = require("duoyun-ui/lib/color");
var import_node = require("vscode-languageserver/node");
// src/constants.ts
var COLOR_REG = /(?<start>'|")?(?<content>#([0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{3,4}))($1|\s*;|\s*\))/g;
var CSS_REG = /(?<start>\/\*\s*css\s*\*\/\s*`|(?<!`)(?:css|less|scss)\s*`)(?<content>.*?)(`(?=;|,?\s*\)))/gs;
var STYLE_REG = /(?<start>\/\*\s*style\s*\*\/\s*`|(?<!`)styled?\s*`)(?<content>.*?)(`(?=,|\s*}\s*\)))/gs;
var HTML_REG = /(?<start>\/\*\s*html\s*\*\/\s*`|(?<!`)(?:html|raw)\s*`)(?<content>[^`]*)(`)/g;
// src/color.ts
var ColorProvider = class {
provideDocumentColors(document) {
COLOR_REG.exec("null");
const documentText = document.getText();
const colors = [];
let match;
while ((match = COLOR_REG.exec(documentText)) !== null) {
const hex = match.groups.content;
const [red, green, blue, alpha] = (0, import_color.parseHexColor)(hex);
const offset = match.index + (match.groups.start?.length || 0);
const range = import_node.Range.create(document.positionAt(offset), document.positionAt(offset + hex.length));
const color = import_node.Color.create(red / 255, green / 255, blue / 255, alpha);
colors.push({ range, color });
}
return colors;
}
provideColorPresentations({ red, green, blue, alpha }) {
return [{ label: (0, import_color.rgbToHexColor)([red * 255, green * 255, blue * 255, alpha]) }];
}
};
// src/diagnostic.ts
var import_vscode_css_languageservice = require("vscode-css-languageservice");
var import_node3 = require("vscode-languageserver/node");
// src/util.ts
var import_vscode_html_languageservice = require("vscode-html-languageservice");
var import_node2 = require("vscode-languageserver/node");
function removeSlot(text) {
const v = text.replace(/\$\{[^${]*?\}/g, (str) => str.replaceAll(/[^\n]/g, "x"));
if (v === text) return v;
return removeSlot(v);
}
function removeHTMLSlot(text, position) {
const left = text.slice(0, position);
const right = text.slice(position);
const left1 = removeSlot(left);
const left2 = left1.replace(/(.*(?=html`))/s, (str) => str.replaceAll(/[^\n]/g, " "));
return left2 + removeSlot(right);
}
function translateCompletionList(result, position) {
const getRange = (item) => {
if (item.textEdit && "range" in item.textEdit) {
const { start, end } = item.textEdit.range;
return import_node2.Range.create(
import_node2.Position.create(position.line, start.character),
import_node2.Position.create(position.line, end.character)
);
}
};
return {
...result,
items: result?.items.map((item) => ({
...item,
textEdit: item.textEdit && {
...item.textEdit,
range: getRange(item)
}
}))
};
}
function matchOffset(regex, docText, offset) {
regex.exec("null");
let match;
while ((match = regex.exec(docText)) !== null) {
const [fullStr, startStr] = match;
const start = match.index + startStr.length;
const end = match.index + fullStr.length;
if (offset > start && offset < end) {
return match;
}
}
return null;
}
function createVirtualDocument(languageId, content) {
return import_vscode_html_languageservice.TextDocument.create(`embedded://document.${languageId}`, languageId, 1, content);
}
// src/diagnostic.ts
var cssLanguageService = (0, import_vscode_css_languageservice.getCSSLanguageService)();
function getDiagnostics(document, _relatedInformation) {
const diagnostics = [];
const text = document.getText();
const matchFragments = (regexp, appendBefore, appendAfter) => {
regexp.exec("null");
let match;
while (match = regexp.exec(text)) {
const matchContent = match.groups.content;
const offset = match.index + match.groups.start.length;
const virtualDocument = createVirtualDocument("css", `${appendBefore}${removeSlot(matchContent)}${appendAfter}`);
const vCss = cssLanguageService.parseStylesheet(virtualDocument);
const oDiagnostics = cssLanguageService.doValidation(virtualDocument, vCss);
for (const { message, range } of oDiagnostics) {
const { start, end } = range;
const startOffset = virtualDocument.offsetAt(start) - appendBefore.length + offset;
const endOffset = virtualDocument.offsetAt(end) - appendBefore.length + offset;
diagnostics.push({
range: import_node3.Range.create(document.positionAt(startOffset), document.positionAt(endOffset)),
message
});
}
}
};
matchFragments(CSS_REG, "", "");
matchFragments(STYLE_REG, ":host { ", " }");
return diagnostics;
}
// src/hover.ts
var import_vscode_html_languageservice2 = require("vscode-html-languageservice");
var import_vscode_css_languageservice2 = require("vscode-css-languageservice");
var HTMLHoverProvider = class {
#htmlLanguageService = (0, import_vscode_html_languageservice2.getLanguageService)();
provideHover(document, position) {
const currentOffset = document.offsetAt(position);
const documentText = removeHTMLSlot(document.getText(), currentOffset);
const match = matchOffset(HTML_REG, documentText, currentOffset);
if (!match) return null;
const matchContent = match.groups.content;
const matchStartOffset = match.index + match.groups.start.length;
const virtualOffset = currentOffset - matchStartOffset;
const virtualDocument = createVirtualDocument("html", matchContent);
const html = this.#htmlLanguageService.parseHTMLDocument(virtualDocument);
return this.#htmlLanguageService.doHover(virtualDocument, virtualDocument.positionAt(virtualOffset), html, {
documentation: true,
references: true
});
}
};
var CSSHoverProvider = class {
#cssLanguageService = (0, import_vscode_css_languageservice2.getCSSLanguageService)();
provideHover(document, position) {
const currentOffset = document.offsetAt(position);
const documentText = document.getText();
const match = matchOffset(CSS_REG, documentText, currentOffset);
if (!match) return null;
const matchContent = match.groups.content;
const matchStartOffset = match.index + match.groups.start.length;
const virtualOffset = currentOffset - matchStartOffset;
const virtualDocument = createVirtualDocument("css", removeSlot(matchContent));
const stylesheet = this.#cssLanguageService.parseStylesheet(virtualDocument);
return this.#cssLanguageService.doHover(virtualDocument, virtualDocument.positionAt(virtualOffset), stylesheet, {
documentation: true,
references: true
});
}
};
var StyleHoverProvider = class {
#cssLanguageService = (0, import_vscode_css_languageservice2.getCSSLanguageService)();
provideHover(document, position) {
const currentOffset = document.offsetAt(position);
const documentText = document.getText();
const match = matchOffset(STYLE_REG, documentText, currentOffset);
if (!match) return null;
const matchContent = match.groups.content;
const matchStartOffset = match.index + match.groups.start.length;
const virtualOffset = currentOffset - matchStartOffset + 8;
const virtualDocument = createVirtualDocument("css", `:host { ${removeSlot(matchContent)} }`);
const stylesheet = this.#cssLanguageService.parseStylesheet(virtualDocument);
return this.#cssLanguageService.doHover(virtualDocument, virtualDocument.positionAt(virtualOffset), stylesheet, {
documentation: true,
references: true
});
}
};
// src/css.ts
var import_vscode_html_languageservice3 = require("vscode-html-languageservice");
var import_vscode_css_languageservice3 = require("vscode-css-languageservice");
// src/cache.ts
var CompletionsCache = class {
#cachedCompletionsFile;
#cachedCompletionsPosition;
#cachedCompletionsContent;
#completions;
#equalPositions(left, right) {
return !!right && left.line === right.line && left.character === right.character;
}
getCached(doc, position) {
if (this.#completions && doc.uri === this.#cachedCompletionsFile && this.#equalPositions(position, this.#cachedCompletionsPosition) && doc.getText() === this.#cachedCompletionsContent) {
return this.#completions;
}
return void 0;
}
updateCached(context, position, completions) {
this.#cachedCompletionsFile = context.uri;
this.#cachedCompletionsPosition = position;
this.#cachedCompletionsContent = context.getText();
this.#completions = completions;
return completions;
}
};
// src/css.ts
function getRegionAtOffset(regions, offset) {
for (const region of regions) {
if (region.start <= offset) {
if (offset <= region.end) {
return region;
}
} else {
break;
}
}
return null;
}
function getLanguageRegions(service, data) {
const scanner = service.createScanner(data);
const regions = [];
let tokenType;
while ((tokenType = scanner.scan()) !== import_vscode_html_languageservice3.TokenType.EOS) {
switch (tokenType) {
case import_vscode_html_languageservice3.TokenType.Styles:
regions.push({
languageId: "css",
start: scanner.getTokenOffset(),
end: scanner.getTokenEnd(),
length: scanner.getTokenLength(),
content: scanner.getTokenText()
});
break;
default:
break;
}
}
return regions;
}
var HTMLStyleCompletionItemProvider = class {
#cssLanguageService = (0, import_vscode_css_languageservice3.getCSSLanguageService)();
#htmlLanguageService = (0, import_vscode_html_languageservice3.getLanguageService)();
#cache = new CompletionsCache();
provideCompletionItems(document, position) {
const cached = this.#cache.getCached(document, position);
if (cached) return cached;
const currentOffset = document.offsetAt(position);
const documentText = document.getText();
const match = matchOffset(HTML_REG, documentText, currentOffset);
if (!match) return;
const matchContent = match.groups.content;
const matchStartOffset = match.index + match.groups.start.length;
const regions = getLanguageRegions(this.#htmlLanguageService, matchContent);
if (regions.length <= 0) return;
const region = getRegionAtOffset(regions, currentOffset - matchStartOffset);
if (!region) return;
const virtualOffset = currentOffset - (matchStartOffset + region.start);
const virtualDocument = createVirtualDocument("css", removeSlot(region.content));
const stylesheet = this.#cssLanguageService.parseStylesheet(virtualDocument);
const completions = this.#cssLanguageService.doComplete(
virtualDocument,
virtualDocument.positionAt(virtualOffset),
stylesheet
);
return this.#cache.updateCached(document, position, translateCompletionList(completions, position));
}
};
var CSSCompletionItemProvider = class {
#cssLanguageService = (0, import_vscode_css_languageservice3.getCSSLanguageService)();
#cache = new CompletionsCache();
provideCompletionItems(document, position) {
const cached = this.#cache.getCached(document, position);
if (cached) return cached;
const currentOffset = document.offsetAt(position);
const documentText = document.getText();
const match = matchOffset(CSS_REG, documentText, currentOffset);
if (!match) return;
const matchContent = match.groups.content;
const matchStartOffset = match.index + match.groups.start.length;
const virtualOffset = currentOffset - matchStartOffset;
const virtualDocument = createVirtualDocument("css", removeSlot(matchContent));
const vCss = this.#cssLanguageService.parseStylesheet(virtualDocument);
const completions = this.#cssLanguageService.doComplete(
virtualDocument,
virtualDocument.positionAt(virtualOffset),
vCss
);
return this.#cache.updateCached(document, position, translateCompletionList(completions, position));
}
};
// src/html.ts
var import_vscode_html_languageservice4 = require("vscode-html-languageservice");
var import_emmet_helper = require("@vscode/emmet-helper");
var HTMLCompletionItemProvider = class {
#htmlLanguageService = (0, import_vscode_html_languageservice4.getLanguageService)();
#cache = new CompletionsCache();
#connection;
#emmetConfig;
constructor(connection2) {
this.#connection = connection2;
}
async #getEmmetConfig() {
if (!this.#emmetConfig) {
const emmetConfig = await this.#connection.workspace.getConfiguration("emmet");
this.#emmetConfig = {
showExpandedAbbreviation: emmetConfig.showExpandedAbbreviation,
showAbbreviationSuggestions: emmetConfig.showAbbreviationSuggestions,
syntaxProfiles: emmetConfig.syntaxProfiles,
variables: emmetConfig.variables
};
}
return this.#emmetConfig;
}
async provideCompletionItems(document, position) {
const emmetConfig = await this.#getEmmetConfig();
const cached = this.#cache.getCached(document, position);
if (cached) return cached;
const currentOffset = document.offsetAt(position);
const documentText = removeHTMLSlot(document.getText(), currentOffset);
const match = matchOffset(HTML_REG, documentText, currentOffset);
if (!match) return;
const matchContent = match.groups.content;
const matchStartOffset = match.index + match.groups.start.length;
const virtualOffset = currentOffset - matchStartOffset;
const virtualDocument = createVirtualDocument("html", matchContent);
const vHtml = this.#htmlLanguageService.parseHTMLDocument(virtualDocument);
let emmetResults = { isIncomplete: true, items: [] };
this.#htmlLanguageService.setCompletionParticipants([
{
onHtmlContent: async () => {
const pos = virtualDocument.positionAt(virtualOffset);
const result = (0, import_emmet_helper.doComplete)(virtualDocument, pos, "html", emmetConfig);
if (result) {
emmetResults = {
...result,
items: result.items.map((item) => ({
...item,
command: {
title: "Emmet Expand Abbreviation",
command: "editor.emmet.action.expandAbbreviation"
}
}))
};
}
}
}
]);
const completions = this.#htmlLanguageService.doComplete(
virtualDocument,
virtualDocument.positionAt(virtualOffset),
vHtml
);
if (emmetResults.items.length) {
completions.items.push(...emmetResults.items);
}
return this.#cache.updateCached(document, position, translateCompletionList(completions, position));
}
};
// src/style.ts
var import_vscode_css_languageservice4 = require("vscode-css-languageservice");
var StyleCompletionItemProvider = class {
#cssLanguageService = (0, import_vscode_css_languageservice4.getCSSLanguageService)();
#cache = new CompletionsCache();
provideCompletionItems(document, position) {
const cached = this.#cache.getCached(document, position);
if (cached) return cached;
const currentOffset = document.offsetAt(position);
const documentText = document.getText();
const match = matchOffset(STYLE_REG, documentText, currentOffset);
if (!match) return;
const matchContent = match.groups.content;
const matchStartOffset = match.index + match.groups.start.length;
const virtualOffset = currentOffset - matchStartOffset + 8;
const virtualDocument = createVirtualDocument("css", `:host { ${removeSlot(matchContent)} }`);
const vCss = this.#cssLanguageService.parseStylesheet(virtualDocument);
const completions = this.#cssLanguageService.doComplete(
virtualDocument,
virtualDocument.positionAt(virtualOffset),
vCss
);
return this.#cache.updateCached(document, position, translateCompletionList(completions, position));
}
};
// src/index.ts
var connection = (0, import_node4.createConnection)(import_node4.ProposedFeatures.all);
var documents = new import_node4.TextDocuments(import_vscode_languageserver_textdocument.TextDocument);
var hasConfigurationCapability = false;
var hasWorkspaceFolderCapability = false;
var hasDiagnosticRelatedInformationCapability = false;
connection.onInitialize(({ capabilities }) => {
hasConfigurationCapability = !!capabilities.workspace?.configuration;
hasWorkspaceFolderCapability = !!capabilities.workspace?.workspaceFolders;
hasDiagnosticRelatedInformationCapability = !!capabilities.textDocument?.publishDiagnostics?.relatedInformation;
return {
capabilities: {
completionProvider: {
resolveProvider: true,
triggerCharacters: ["!", ".", "}", ":", "*", "$", "]", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "<"]
},
hoverProvider: true,
colorProvider: true,
textDocumentSync: import_node4.TextDocumentSyncKind.Incremental,
workspace: !hasWorkspaceFolderCapability ? void 0 : { workspaceFolders: { supported: true } }
}
};
});
connection.onInitialized(() => {
if (hasConfigurationCapability) {
connection.client.register(import_node4.DidChangeConfigurationNotification.type, void 0);
}
if (hasWorkspaceFolderCapability) {
connection.workspace.onDidChangeWorkspaceFolders((_event) => {
connection.console.log("Workspace folder change event received.");
});
}
});
var defaultSettings = { maxNumberOfProblems: 1e3 };
var globalSettings = defaultSettings;
var documentSettings = /* @__PURE__ */ new Map();
connection.onDidChangeConfiguration((change) => {
if (hasConfigurationCapability) {
documentSettings.clear();
} else {
globalSettings = change.settings.languageServerGem || defaultSettings;
}
documents.all().forEach(validateTextDocument);
});
documents.onDidClose((e) => documentSettings.delete(e.document.uri));
documents.onDidChangeContent((change) => validateTextDocument(change.document));
var validateTextDocument = (0, import_timer.debounce)((textDocument) => {
connection.sendDiagnostics({
uri: textDocument.uri,
diagnostics: getDiagnostics(textDocument, hasDiagnosticRelatedInformationCapability)
});
});
connection.onDidChangeWatchedFiles((_change) => {
connection.console.log("We received a file change event");
});
var completionItemProviders = [
new CSSCompletionItemProvider(),
new HTMLStyleCompletionItemProvider(),
new HTMLCompletionItemProvider(connection),
new StyleCompletionItemProvider()
];
connection.onCompletion(async ({ textDocument, position }) => {
for (const provider of completionItemProviders) {
const result = await provider.provideCompletionItems(documents.get(textDocument.uri), position);
if (result) return { isIncomplete: true, items: result.items };
}
});
connection.onCompletionResolve((item) => item);
var colorProvider = new ColorProvider();
connection.onColorPresentation(({ color }) => {
return colorProvider.provideColorPresentations(color);
});
connection.onDocumentColor(({ textDocument }) => {
return colorProvider.provideDocumentColors(documents.get(textDocument.uri));
});
var hoverProviders = [new CSSHoverProvider(), new StyleHoverProvider(), new HTMLHoverProvider()];
connection.onHover(({ textDocument, position }) => {
for (const provider of hoverProviders) {
const result = provider.provideHover(documents.get(textDocument.uri), position);
if (result) return result;
}
});
documents.listen(connection);
connection.listen();
//# sourceMappingURL=index.js.map