UNPKG

vscode-gem-languageservice

Version:
480 lines (465 loc) 19.7 kB
#!/usr/bin/env node "use strict"; // 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