svelte-language-server
Version:
A language server for Svelte
211 lines • 9.81 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DocumentHighlightProviderImpl = void 0;
const typescript_1 = __importDefault(require("typescript"));
const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
const vscode_languageserver_types_1 = require("vscode-languageserver-types");
const documents_1 = require("../../../lib/documents");
const utils_1 = require("../../../utils");
const utils_2 = require("../utils");
const utils_3 = require("./utils");
const svelte_ast_utils_1 = require("../svelte-ast-utils");
class DocumentHighlightProviderImpl {
constructor(lsAndTsDocResolver) {
this.lsAndTsDocResolver = lsAndTsDocResolver;
}
async findDocumentHighlight(document, position) {
const { tsDoc } = await this.lsAndTsDocResolver.getLsForSyntheticOperations(document);
const svelteResult = await this.getSvelteDocumentHighlight(document, tsDoc, position);
if (svelteResult) {
return svelteResult;
}
const { lang } = await this.lsAndTsDocResolver.getLSAndTSDoc(document);
const offset = tsDoc.offsetAt(tsDoc.getGeneratedPosition(position));
const highlights = lang
.getDocumentHighlights(tsDoc.filePath, offset, [tsDoc.filePath])
?.filter((highlight) => highlight.fileName === tsDoc.filePath);
if (!highlights?.length) {
return null;
}
const result = (0, utils_1.flatten)(highlights.map((highlight) => highlight.highlightSpans))
.filter(this.notInGeneratedCode(tsDoc.getFullText()))
.map((highlight) => vscode_languageserver_protocol_1.DocumentHighlight.create((0, utils_2.convertToLocationRange)(tsDoc, highlight.textSpan), this.convertHighlightKind(highlight)))
.filter((highlight) => !(0, utils_1.isSamePosition)(highlight.range.start, highlight.range.end));
if (!result.length) {
return null;
}
return result;
}
convertHighlightKind(highlight) {
return highlight.kind === typescript_1.default.HighlightSpanKind.writtenReference
? vscode_languageserver_types_1.DocumentHighlightKind.Write
: vscode_languageserver_types_1.DocumentHighlightKind.Read;
}
async getSvelteDocumentHighlight(document, tsDoc, position) {
if ((0, documents_1.inStyleOrScript)(document, position)) {
return null;
}
const offset = document.offsetAt(position);
const offsetStart = Math.max(offset - 10, 0);
const charactersAroundOffset = document
.getText()
// use last 10 and next 10 characters, should cover 99% of all cases
.substr(offsetStart, 20);
if (!['#', '/', ':', '@', 'then', 'catch'].some((keyword) => charactersAroundOffset.includes(keyword))) {
return null;
}
const candidate = this.findCandidateSvelteTag(tsDoc, offset);
if (!candidate) {
return null;
}
if (candidate.type.endsWith('Tag')) {
return this.getTagHighlight(offset, document, candidate);
}
if (candidate.type.endsWith('Block')) {
return this.getBlockHighlight(offset, document, candidate);
}
return null;
}
findCandidateSvelteTag(tsDoc, offset) {
let candidate;
const subBlocks = ['ThenBlock', 'CatchBlock', 'PendingBlock', 'ElseBlock'];
tsDoc.walkSvelteAst({
enter(node, parent, key) {
if (node.type === 'Fragment') {
return;
}
const templateNode = node;
const isWithin = templateNode.start <= offset && templateNode.end >= offset;
const canSkip = !isWithin ||
key === 'expression' ||
key === 'context' ||
((parent.type === 'InlineComponent' || parent.type === 'Element') &&
key !== 'children');
if (canSkip) {
this.skip();
return;
}
if (node.type.endsWith('Tag')) {
candidate = templateNode;
return;
}
// don't use sub-blocks so we can highlight the whole block
if (node.type.endsWith('Block') && !subBlocks.includes(node.type)) {
if (
// else if
node.type === 'IfBlock' &&
parent.type === 'ElseBlock' &&
parent.start === node.start) {
return;
}
candidate = templateNode;
return;
}
}
});
return candidate;
}
getTagHighlight(offset, document, candidate) {
const name = candidate.type === 'RawMustacheTag'
? 'html'
: candidate.type.replace('Tag', '').toLocaleLowerCase();
const startTag = '@' + name;
const indexOfName = document.getText().indexOf(startTag, candidate.start);
if (indexOfName < 0 || indexOfName > offset || candidate.start + startTag.length < offset) {
return null;
}
return [
{
kind: vscode_languageserver_types_1.DocumentHighlightKind.Read,
range: vscode_languageserver_types_1.Range.create(document.positionAt(indexOfName), document.positionAt(indexOfName + startTag.length))
}
];
}
getBlockHighlight(offset, document, candidate) {
const name = candidate.type.replace('Block', '').toLowerCase();
const startTag = '#' + name;
const startTagStart = document.getText().indexOf(startTag, candidate.start);
if (startTagStart < 0) {
return null;
}
const ranges = [];
ranges.push([startTagStart, startTagStart + startTag.length]);
const content = document.getText();
const endTag = '/' + name;
const endTagStart = content.lastIndexOf(endTag, candidate.end);
if (endTagStart < startTagStart) {
return null; // can happen in loose parser mode for unclosed tags
}
ranges.push([endTagStart, endTagStart + endTag.length]);
if (candidate.type === 'EachBlock' && candidate.else) {
const elseStart = content.lastIndexOf(':else', candidate.else.start);
ranges.push([elseStart, elseStart + ':else'.length]);
}
ranges.push(...this.getElseHighlightsForIfBlock(candidate, content), ...this.getAwaitBlockHighlight(candidate, content));
if (!ranges.some(([start, end]) => offset >= start && offset <= end)) {
return null;
}
return ranges.map(([start, end]) => ({
range: vscode_languageserver_types_1.Range.create(document.positionAt(start), document.positionAt(end)),
kind: vscode_languageserver_types_1.DocumentHighlightKind.Read
}));
}
getElseHighlightsForIfBlock(candidate, content) {
if (candidate.type !== 'IfBlock' || !candidate.else) {
return [];
}
const ranges = new Map();
(0, svelte_ast_utils_1.walkSvelteAst)(candidate.else, {
enter(node) {
const templateNode = node;
if (templateNode.type === 'IfBlock' && templateNode.elseif) {
const elseIfStart = content.lastIndexOf(':else if', templateNode.expression.start);
if (elseIfStart > 0) {
ranges.set(elseIfStart, [elseIfStart, elseIfStart + ':else if'.length]);
}
}
if (templateNode.type === 'ElseBlock') {
const elseStart = content.lastIndexOf(':else', templateNode.start);
if (elseStart > 0 &&
content.slice(elseStart, elseStart + ':else if'.length) !== ':else if') {
ranges.set(elseStart, [elseStart, elseStart + ':else'.length]);
}
}
}
});
return Array.from(ranges.values());
}
getAwaitBlockHighlight(candidate, content) {
if (candidate.type !== 'AwaitBlock' || (candidate.then.skip && candidate.catch.skip)) {
return [];
}
const ranges = [];
if (candidate.value) {
const thenKeyword = candidate.pending.skip ? 'then' : ':then';
const thenStart = content.lastIndexOf(thenKeyword, candidate.value.start);
ranges.push([thenStart, thenStart + thenKeyword.length]);
}
// {#await promise catch error} or {:catch error}
if (candidate.error) {
const catchKeyword = candidate.pending.skip && candidate.then.skip ? 'catch' : ':catch';
const catchStart = content.lastIndexOf(catchKeyword, candidate.error.start);
ranges.push([catchStart, catchStart + catchKeyword.length]);
}
else if (!candidate.catch.skip) {
// {:catch}
const catchStart = content.indexOf(':catch', candidate.catch.start);
ranges.push([catchStart, catchStart + ':catch'.length]);
}
return ranges;
}
notInGeneratedCode(text) {
return (ref) => {
return !(0, utils_3.isInGeneratedCode)(text, ref.textSpan.start, ref.textSpan.start + ref.textSpan.length);
};
}
}
exports.DocumentHighlightProviderImpl = DocumentHighlightProviderImpl;
//# sourceMappingURL=DocumentHighlightProvider.js.map