svelte-language-server
Version:
A language server for Svelte
247 lines • 10.9 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.FoldingRangeProviderImpl = void 0;
const typescript_1 = __importDefault(require("typescript"));
const vscode_languageserver_1 = require("vscode-languageserver");
const documents_1 = require("../../../lib/documents");
const utils_1 = require("../../../utils");
const utils_2 = require("../utils");
const utils_3 = require("./utils");
const indentFolding_1 = require("../../../lib/foldingRange/indentFolding");
const svelte_ast_utils_1 = require("../svelte-ast-utils");
class FoldingRangeProviderImpl {
constructor(lsAndTsDocResolver, configManager) {
this.lsAndTsDocResolver = lsAndTsDocResolver;
this.configManager = configManager;
this.foldEndPairCharacters = ['}', ']', ')', '`', '>'];
}
async getFoldingRanges(document) {
// don't use ls.getProgram unless it's necessary
// this feature is pure syntactic and doesn't need type information
const { lang, tsDoc } = await this.lsAndTsDocResolver.getLsForSyntheticOperations(document);
const foldingRanges = tsDoc.parserError && !document.moduleScriptInfo && !document.scriptInfo
? []
: lang.getOutliningSpans(tsDoc.filePath);
const lineFoldingOnly = !!this.configManager.getClientCapabilities()?.textDocument?.foldingRange
?.lineFoldingOnly;
const result = foldingRanges
.filter((span) => !(0, utils_3.isTextSpanInGeneratedCode)(tsDoc.getFullText(), span.textSpan))
.map((span) => ({
originalRange: this.mapToOriginalRange(tsDoc, span.textSpan, document),
span
}))
.map(({ originalRange, span }) => this.convertOutliningSpan(span, document, originalRange, lineFoldingOnly))
.filter(utils_1.isNotNullOrUndefined)
.concat(this.collectSvelteBlockFolding(document, tsDoc, lineFoldingOnly))
.concat(this.getSvelteTagFoldingIfParserError(document, tsDoc))
.filter((r) => (lineFoldingOnly ? r.startLine < r.endLine : r.startLine <= r.endLine));
return result;
}
mapToOriginalRange(tsDoc, textSpan, document) {
const range = (0, documents_1.mapRangeToOriginal)(tsDoc, (0, utils_2.convertRange)(tsDoc, textSpan));
const startOffset = document.offsetAt(range.start);
if (range.start.line < 0 || range.end.line < 0 || range.start.line > range.end.line) {
return;
}
if ((0, documents_1.isInTag)(range.start, document.scriptInfo) ||
(0, documents_1.isInTag)(range.start, document.moduleScriptInfo)) {
return range;
}
const endOffset = document.offsetAt(range.end);
const originalText = document.getText().slice(startOffset, endOffset);
if (originalText.length === 0) {
return;
}
const generatedText = tsDoc.getText(textSpan.start, textSpan.start + textSpan.length);
const oneToOne = originalText.trim() === generatedText.trim();
if (oneToOne) {
return range;
}
}
/**
* Doing this here with the svelte2tsx's svelte ast is slightly
* less prone to error and faster than
* using the svelte ast in the svelte plugins.
*/
collectSvelteBlockFolding(document, tsDoc, lineFoldingOnly) {
if (tsDoc.parserError) {
return [];
}
const ranges = [];
const provider = this;
const enter = function (node, parent, key) {
if (key === 'attributes') {
this.skip();
}
// use sub-block for await block
if (!node.type.endsWith('Block') || node.type === 'AwaitBlock') {
return;
}
if (node.type === 'IfBlock') {
provider.getIfBlockFolding(node, document, ranges);
return;
}
if ((0, svelte_ast_utils_1.isElseBlockWithElseIf)(node)) {
return;
}
if ((node.type === 'CatchBlock' || node.type === 'ThenBlock') && (0, svelte_ast_utils_1.isAwaitBlock)(parent)) {
const expressionEnd = (node.type === 'CatchBlock' ? parent.error?.end : parent.value?.end) ??
document.getText().indexOf('}', node.start);
const beforeBlockStartTagEnd = document.getText().indexOf('}', expressionEnd);
if (beforeBlockStartTagEnd == -1) {
return;
}
ranges.push(provider.createFoldingRange(document, beforeBlockStartTagEnd + 1, node.end));
return;
}
if ((0, svelte_ast_utils_1.isEachBlock)(node)) {
const start = document.getText().indexOf('}', (node.key ?? node.expression).end);
const elseStart = node.else
? (0, svelte_ast_utils_1.findElseBlockTagStart)(document.getText(), node.else)
: -1;
ranges.push(provider.createFoldingRange(document, start, elseStart === -1 ? node.end : elseStart));
return;
}
if ('expression' in node && node.expression && typeof node.expression === 'object') {
const start = provider.getStartForNodeWithExpression(node, document);
const end = node.end;
ranges.push(provider.createFoldingRange(document, start, end));
return;
}
if (node.start != null && node.end != null) {
const start = node.start;
const end = node.end;
ranges.push(provider.createFoldingRange(document, start, end));
}
};
tsDoc.walkSvelteAst({
enter
});
if (lineFoldingOnly) {
return ranges.map((r) => ({
startLine: r.startLine,
endLine: this.previousLineOfEndLine(r.startLine, r.endLine)
}));
}
return ranges;
}
getIfBlockFolding(node, document, ranges) {
const typed = node;
const documentText = document.getText();
const start = this.getStartForNodeWithExpression(typed, document);
const end = (0, svelte_ast_utils_1.hasElseBlock)(typed)
? (0, svelte_ast_utils_1.findElseBlockTagStart)(documentText, typed.else)
: (0, svelte_ast_utils_1.findIfBlockEndTagStart)(documentText, typed);
ranges.push(this.createFoldingRange(document, start, end));
}
getStartForNodeWithExpression(node, document) {
return document.getText().indexOf('}', node.expression.end) + 1;
}
createFoldingRange(document, start, end) {
const range = (0, documents_1.toRange)(document, start, end);
return {
startLine: range.start.line,
startCharacter: range.start.character,
endLine: range.end.line,
endCharacter: range.end.character
};
}
convertOutliningSpan(span, document, originalRange, lineFoldingOnly) {
if (!originalRange) {
return null;
}
const end = lineFoldingOnly
? this.adjustFoldingEndToNotHideEnd(originalRange, document)
: originalRange.end;
const result = {
startLine: originalRange.start.line,
endLine: end.line,
kind: this.getFoldingRangeKind(span),
startCharacter: lineFoldingOnly ? undefined : originalRange.start.character,
endCharacter: lineFoldingOnly ? undefined : end.character
};
return result;
}
getFoldingRangeKind(span) {
switch (span.kind) {
case typescript_1.default.OutliningSpanKind.Comment:
return vscode_languageserver_1.FoldingRangeKind.Comment;
case typescript_1.default.OutliningSpanKind.Region:
return vscode_languageserver_1.FoldingRangeKind.Region;
case typescript_1.default.OutliningSpanKind.Imports:
return vscode_languageserver_1.FoldingRangeKind.Imports;
case typescript_1.default.OutliningSpanKind.Code:
default:
return undefined;
}
}
adjustFoldingEndToNotHideEnd(range, document) {
// don't fold end bracket, brace...
if (range.end.character > 0) {
const text = document.getText();
const offsetBeforeEnd = document.offsetAt({
line: range.end.line,
character: range.end.character - 1
});
const foldEndCharacter = text[offsetBeforeEnd];
if (this.foldEndPairCharacters.includes(foldEndCharacter)) {
return { line: this.previousLineOfEndLine(range.start.line, range.end.line) };
}
}
return range.end;
}
getSvelteTagFoldingIfParserError(document, tsDoc) {
if (!tsDoc.parserError) {
return [];
}
const htmlTemplateRanges = this.getHtmlTemplateRangesForChecking(document);
return (0, indentFolding_1.indentBasedFoldingRange)({
document,
skipFold: (_, lineContent) => {
return !/{\s*(#|\/|:)/.test(lineContent);
},
ranges: htmlTemplateRanges
});
}
getHtmlTemplateRangesForChecking(document) {
const ranges = [];
const excludeTags = [
document.templateInfo,
document.moduleScriptInfo,
document.scriptInfo,
document.styleInfo
]
.filter(utils_1.isNotNullOrUndefined)
.map((info) => ({
startLine: document.positionAt(info.container.start).line,
endLine: document.positionAt(info.container.end).line
}))
.sort((a, b) => a.startLine - b.startLine);
if (excludeTags.length === 0) {
return [{ startLine: 0, endLine: document.lineCount - 1 }];
}
if (excludeTags[0].startLine > 0) {
ranges.push({
startLine: 0,
endLine: excludeTags[0].startLine - 1
});
}
for (let index = 0; index < excludeTags.length; index++) {
const element = excludeTags[index];
const next = excludeTags[index + 1];
ranges.push({
startLine: element.endLine + 1,
endLine: next ? next.startLine - 1 : document.lineCount - 1
});
}
return ranges;
}
previousLineOfEndLine(startLine, endLine) {
return Math.max(endLine - 1, startLine);
}
}
exports.FoldingRangeProviderImpl = FoldingRangeProviderImpl;
//# sourceMappingURL=FoldingRangeProvider.js.map