UNPKG

typescript-lit-html-plugin

Version:

TypeScript language service plugin that adds IntelliSense for html tagged templates

342 lines 14.3 kB
"use strict"; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // // Original code forked from https://github.com/Quramy/ts-graphql-plugin Object.defineProperty(exports, "__esModule", { value: true }); const vscode = require("vscode-languageserver-types"); const embeddedSupport_1 = require("./embeddedSupport"); const emptyCompletionList = { isIncomplete: false, items: [], }; class CompletionsCache { getCached(context, position) { if (this._completions && context.fileName === this._cachedCompletionsFile && this._cachedCompletionsPosition && arePositionsEqual(position, this._cachedCompletionsPosition) && context.text === this._cachedCompletionsContent) { return this._completions; } return undefined; } updateCached(context, position, completions) { this._cachedCompletionsFile = context.fileName; this._cachedCompletionsPosition = position; this._cachedCompletionsContent = context.text; this._completions = completions; } } class HtmlTemplateLanguageService { constructor(typescript, configuration, virtualDocumentProvider, htmlLanguageService, styledLanguageService, _logger) { this.typescript = typescript; this.configuration = configuration; this.virtualDocumentProvider = virtualDocumentProvider; this.htmlLanguageService = htmlLanguageService; this.styledLanguageService = styledLanguageService; this._completionsCache = new CompletionsCache(); } getCompletionsAtPosition(context, position) { const entry = this.getCompletionItems(context, position); if (entry.type === 'styled') { return entry.value; } return translateCompletionItemsToCompletionInfo(this.typescript, context, entry.value); } getCompletionEntryDetails(context, position, name) { const entry = this.getCompletionItems(context, position); if (entry.type === 'styled') { return this.styledLanguageService.getCompletionEntryDetails(context, position, name); } const item = entry.value.items.find(x => x.label === name); if (!item) { return { name, kind: this.typescript.ScriptElementKind.unknown, kindModifiers: '', tags: [], displayParts: toDisplayParts(name), documentation: [], }; } return translateCompletionItemsToCompletionEntryDetails(this.typescript, item); } getQuickInfoAtPosition(context, position) { const document = this.virtualDocumentProvider.createVirtualDocument(context); const documentRegions = embeddedSupport_1.getDocumentRegions(this.htmlLanguageService, document); const languageId = documentRegions.getLanguageAtPosition(position); switch (languageId) { case 'html': const htmlDoc = this.htmlLanguageService.parseHTMLDocument(document); const hover = this.htmlLanguageService.doHover(document, position, htmlDoc); return hover ? this.translateHover(hover, position, context) : undefined; case 'css': return this.styledLanguageService.getQuickInfoAtPosition(context, position); } return undefined; } getFormattingEditsForRange(context, start, end, settings) { if (!this.configuration.format.enabled) { return []; } // Disable formatting for blocks that contain a style tag // // Formatting styled blocks gets complex since we want to preserve interpolations inside the output // but we can't format content with `{` property. // The best fix would be to add `style` to `contentUnformatted` but // https://github.com/Microsoft/vscode-html-languageservice/issues/29 is causing problems and I'm not sure how // to work around it well if (context.text.match(/<style/g)) { return []; } const document = this.virtualDocumentProvider.createVirtualDocument(context); // Make sure we don't get rid of leading newline const leading = context.text.match(/^\s*\n/); if (leading) { start += leading[0].length; } // or any trailing newlines const trailing = context.text.match(/\n\s*$/); if (trailing) { end -= trailing[0].length; } if (end <= start) { return []; } const range = this.toVsRange(context, start, end); const edits = this.htmlLanguageService.format(document, range, { tabSize: settings.tabSize, insertSpaces: !!settings.convertTabsToSpaces, wrapLineLength: 120, unformatted: '', contentUnformatted: 'pre,code,textarea', indentInnerHtml: false, preserveNewLines: true, maxPreserveNewLines: undefined, indentHandlebars: false, endWithNewline: false, extraLiners: 'head, body, /html', wrapAttributes: 'auto', }); return edits.map(vsedit => toTsTextChange(context, vsedit)); } getSignatureHelpItemsAtPosition(_context, _position) { // Html does not support sig help return undefined; } getOutliningSpans(context) { const document = this.virtualDocumentProvider.createVirtualDocument(context); const ranges = this.htmlLanguageService.getFoldingRanges(document); return ranges.map(range => this.translateOutliningSpan(context, range)); } getSemanticDiagnostics(context) { return this.styledLanguageService.getSemanticDiagnostics(context); } getSupportedCodeFixes() { return this.styledLanguageService.getSupportedCodeFixes(); } getCodeFixesAtPosition(context, start, end, errorCodes, format) { return this.styledLanguageService.getCodeFixesAtPosition(context, start, end, errorCodes, format); } getReferencesAtPosition(context, position) { const document = this.virtualDocumentProvider.createVirtualDocument(context); const htmlDoc = this.htmlLanguageService.parseHTMLDocument(document); const highlights = this.htmlLanguageService.findDocumentHighlights(document, position, htmlDoc); return highlights.map(highlight => ({ fileName: context.fileName, textSpan: toTsSpan(context, highlight.range), })); } getJsxClosingTagAtPosition(context, position) { const document = this.virtualDocumentProvider.createVirtualDocument(context); const htmlDocument = this.htmlLanguageService.parseHTMLDocument(document); const tagComplete = this.htmlLanguageService.doTagComplete(document, position, htmlDocument); if (!tagComplete) { return undefined; } // Html returns completions with snippet placeholders. Strip these out. return { newText: tagComplete.replace(/\$\d/g, ''), }; } toVsRange(context, start, end) { return { start: context.toPosition(start), end: context.toPosition(end), }; } getCompletionItems(context, position) { const cached = this._completionsCache.getCached(context, position); if (cached) { return cached; } const document = this.virtualDocumentProvider.createVirtualDocument(context); const documentRegions = embeddedSupport_1.getDocumentRegions(this.htmlLanguageService, document); const languageId = documentRegions.getLanguageAtPosition(position); switch (languageId) { case 'html': { const htmlDoc = this.htmlLanguageService.parseHTMLDocument(document); const htmlCompletions = { type: 'html', value: this.htmlLanguageService.doComplete(document, position, htmlDoc) || emptyCompletionList, }; this._completionsCache.updateCached(context, position, htmlCompletions); return htmlCompletions; } case 'css': { const styledCompletions = { type: 'styled', value: this.styledLanguageService.getCompletionsAtPosition(context, position), }; this._completionsCache.updateCached(context, position, styledCompletions); return styledCompletions; } } const completions = { type: 'html', value: emptyCompletionList, }; this._completionsCache.updateCached(context, position, completions); return completions; } translateHover(hover, position, context) { const header = []; const docs = []; const convertPart = (hoverContents) => { if (typeof hoverContents === 'string') { docs.push({ kind: 'unknown', text: hoverContents }); } else if (Array.isArray(hoverContents)) { hoverContents.forEach(convertPart); } else { header.push({ kind: 'unknown', text: hoverContents.value }); } }; convertPart(hover.contents); const start = context.toOffset(hover.range ? hover.range.start : position); return { kind: this.typescript.ScriptElementKind.string, kindModifiers: '', textSpan: { start, length: hover.range ? context.toOffset(hover.range.end) - start : 1, }, displayParts: header, documentation: docs, tags: [], }; } translateOutliningSpan(context, range) { const startOffset = context.toOffset({ line: range.startLine, character: range.startCharacter || 0 }); const endOffset = context.toOffset({ line: range.endLine, character: range.endCharacter || 0 }); const span = { start: startOffset, length: endOffset - startOffset, }; return { autoCollapse: false, kind: this.typescript.OutliningSpanKind.Code, bannerText: '', textSpan: span, hintSpan: span, }; } } exports.default = HtmlTemplateLanguageService; function translateCompletionItemsToCompletionInfo(typescript, context, items) { return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries: items.items.map(x => translateCompetionEntry(typescript, context, x)), }; } function translateCompletionItemsToCompletionEntryDetails(typescript, item) { return { name: item.label, kindModifiers: 'declare', kind: item.kind ? translateionCompletionItemKind(typescript, item.kind) : typescript.ScriptElementKind.unknown, displayParts: toDisplayParts(item.detail), documentation: toDisplayParts(item.documentation), tags: [], }; } function translateCompetionEntry(typescript, context, vsItem) { const kind = vsItem.kind ? translateionCompletionItemKind(typescript, vsItem.kind) : typescript.ScriptElementKind.unknown; const entry = { name: vsItem.label, kind, sortText: '0', }; if (vsItem.textEdit) { entry.insertText = vsItem.textEdit.newText; entry.replacementSpan = toTsSpan(context, vsItem.textEdit.range); } return entry; } function translateionCompletionItemKind(typescript, kind) { switch (kind) { case vscode.CompletionItemKind.Method: return typescript.ScriptElementKind.memberFunctionElement; case vscode.CompletionItemKind.Function: return typescript.ScriptElementKind.functionElement; case vscode.CompletionItemKind.Constructor: return typescript.ScriptElementKind.constructorImplementationElement; case vscode.CompletionItemKind.Field: case vscode.CompletionItemKind.Variable: return typescript.ScriptElementKind.variableElement; case vscode.CompletionItemKind.Class: return typescript.ScriptElementKind.classElement; case vscode.CompletionItemKind.Interface: return typescript.ScriptElementKind.interfaceElement; case vscode.CompletionItemKind.Module: return typescript.ScriptElementKind.moduleElement; case vscode.CompletionItemKind.Property: return typescript.ScriptElementKind.memberVariableElement; case vscode.CompletionItemKind.Unit: case vscode.CompletionItemKind.Value: return typescript.ScriptElementKind.constElement; case vscode.CompletionItemKind.Enum: return typescript.ScriptElementKind.enumElement; case vscode.CompletionItemKind.Keyword: return typescript.ScriptElementKind.keyword; case vscode.CompletionItemKind.Color: return typescript.ScriptElementKind.constElement; case vscode.CompletionItemKind.Reference: return typescript.ScriptElementKind.alias; case vscode.CompletionItemKind.File: return typescript.ScriptElementKind.moduleElement; case vscode.CompletionItemKind.Snippet: case vscode.CompletionItemKind.Text: default: return typescript.ScriptElementKind.unknown; } } function toDisplayParts(text) { if (!text) { return []; } return [{ kind: 'text', text: typeof text === 'string' ? text : text.value, }]; } function arePositionsEqual(left, right) { return left.line === right.line && left.character === right.character; } function toTsSpan(context, range) { const editStart = context.toOffset(range.start); const editEnd = context.toOffset(range.end); return { start: editStart, length: editEnd - editStart, }; } function toTsTextChange(context, vsedit) { return { span: toTsSpan(context, vsedit.range), newText: vsedit.newText, }; } //# sourceMappingURL=html-template-language-service.js.map