265 lines
13 KiB
Plaintext
265 lines
13 KiB
Plaintext
|
"use strict";
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.register = void 0;
|
||
|
const language_core_1 = require("@volar/language-core");
|
||
|
const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
|
||
|
const common_1 = require("../utils/common");
|
||
|
const cancellation_1 = require("../utils/cancellation");
|
||
|
const documents_1 = require("../documents");
|
||
|
function register(context) {
|
||
|
let fakeVersion = 0;
|
||
|
return async (uri, options, range, onTypeParams, token = cancellation_1.NoneCancellationToken) => {
|
||
|
let document = context.getTextDocument(uri);
|
||
|
if (!document)
|
||
|
return;
|
||
|
range ??= {
|
||
|
start: document.positionAt(0),
|
||
|
end: document.positionAt(document.getText().length),
|
||
|
};
|
||
|
const source = context.documents.getSourceByUri(document.uri);
|
||
|
if (!source) {
|
||
|
return onTypeParams
|
||
|
? (await tryFormat(document, onTypeParams.position, onTypeParams.ch))?.edits
|
||
|
: (await tryFormat(document, range, undefined))?.edits;
|
||
|
}
|
||
|
const initialIndentLanguageId = await context.env.getConfiguration?.('volar.format.initialIndent') ?? { html: true };
|
||
|
let tempSourceSnapshot = source.snapshot;
|
||
|
const tempVirtualFile = source.language.createVirtualFile(source.fileName, source.snapshot, source.languageId);
|
||
|
const originalDocument = document;
|
||
|
let level = 0;
|
||
|
while (true) {
|
||
|
const embeddedFiles = getEmbeddedFilesByLevel(tempVirtualFile, level++);
|
||
|
if (embeddedFiles.length === 0)
|
||
|
break;
|
||
|
// if (level===2) continue;
|
||
|
let edits = [];
|
||
|
const toPatchIndent = [];
|
||
|
for (const file of embeddedFiles) {
|
||
|
if (!file.capabilities.documentFormatting)
|
||
|
continue;
|
||
|
const isCodeBlock = file.mappings.length === 1 && file.mappings[0].generatedRange[0] === 0 && file.mappings[0].generatedRange[1] === file.snapshot.getLength();
|
||
|
if (onTypeParams && !isCodeBlock)
|
||
|
continue;
|
||
|
const docMap = createDocMap(file, source.fileName, tempSourceSnapshot);
|
||
|
if (!docMap)
|
||
|
continue;
|
||
|
let embeddedCodeResult;
|
||
|
if (onTypeParams) {
|
||
|
const embeddedPosition = docMap.toGeneratedPosition(onTypeParams.position);
|
||
|
if (embeddedPosition) {
|
||
|
embeddedCodeResult = await tryFormat(docMap.virtualFileDocument, embeddedPosition, onTypeParams.ch);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
embeddedCodeResult = await tryFormat(docMap.virtualFileDocument, {
|
||
|
start: docMap.virtualFileDocument.positionAt(0),
|
||
|
end: docMap.virtualFileDocument.positionAt(docMap.virtualFileDocument.getText().length),
|
||
|
});
|
||
|
}
|
||
|
if (!embeddedCodeResult)
|
||
|
continue;
|
||
|
toPatchIndent.push({
|
||
|
virtualFileName: file.fileName,
|
||
|
isCodeBlock,
|
||
|
service: embeddedCodeResult.service,
|
||
|
});
|
||
|
for (const textEdit of embeddedCodeResult.edits) {
|
||
|
const range = docMap.toSourceRange(textEdit.range);
|
||
|
if (range) {
|
||
|
edits.push({
|
||
|
newText: textEdit.newText,
|
||
|
range,
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
edits = edits.filter(edit => (0, common_1.isInsideRange)(range, edit.range));
|
||
|
if (edits.length > 0) {
|
||
|
const newText = vscode_languageserver_textdocument_1.TextDocument.applyEdits(document, edits);
|
||
|
document = vscode_languageserver_textdocument_1.TextDocument.create(document.uri, document.languageId, document.version + 1, newText);
|
||
|
tempSourceSnapshot = (0, common_1.stringToSnapshot)(newText);
|
||
|
source.language.updateVirtualFile(tempVirtualFile, tempSourceSnapshot);
|
||
|
}
|
||
|
if (level > 1) {
|
||
|
const baseIndent = options.insertSpaces ? ' '.repeat(options.tabSize) : '\t';
|
||
|
const editLines = new Set();
|
||
|
if (onTypeParams) {
|
||
|
for (const edit of edits) {
|
||
|
for (let line = edit.range.start.line; line <= edit.range.end.line; line++) {
|
||
|
editLines.add(line);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
for (const item of toPatchIndent) {
|
||
|
let virtualFile;
|
||
|
(0, language_core_1.forEachEmbeddedFile)(tempVirtualFile, file => {
|
||
|
if (file.fileName === item.virtualFileName) {
|
||
|
virtualFile = file;
|
||
|
}
|
||
|
});
|
||
|
const docMap = createDocMap(virtualFile, source.fileName, tempSourceSnapshot);
|
||
|
if (!docMap)
|
||
|
continue;
|
||
|
const indentSensitiveLines = new Set();
|
||
|
for (const service of item.service.provideFormattingIndentSensitiveLines ? [item.service] : Object.values(context.services)) {
|
||
|
if (token.isCancellationRequested)
|
||
|
break;
|
||
|
if (service.provideFormattingIndentSensitiveLines) {
|
||
|
const lines = await service.provideFormattingIndentSensitiveLines(docMap.virtualFileDocument, token);
|
||
|
if (lines) {
|
||
|
for (const line of lines) {
|
||
|
const sourceLine = docMap.toSourcePosition({ line: line, character: 0 })?.line;
|
||
|
if (sourceLine !== undefined) {
|
||
|
indentSensitiveLines.add(sourceLine);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
let indentEdits = patchIndents(document, item.isCodeBlock, docMap.map, initialIndentLanguageId[docMap.virtualFileDocument.languageId] ? baseIndent : '');
|
||
|
indentEdits = indentEdits.filter(edit => {
|
||
|
for (let line = edit.range.start.line; line <= edit.range.end.line; line++) {
|
||
|
if (indentSensitiveLines.has(line) && !edit.newText.includes('\n')) {
|
||
|
return false;
|
||
|
}
|
||
|
if (onTypeParams && !editLines.has(line)) {
|
||
|
return false;
|
||
|
}
|
||
|
if (!(0, common_1.isInsideRange)(range, edit.range)) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
});
|
||
|
if (indentEdits.length > 0) {
|
||
|
const newText = vscode_languageserver_textdocument_1.TextDocument.applyEdits(document, indentEdits);
|
||
|
document = vscode_languageserver_textdocument_1.TextDocument.create(document.uri, document.languageId, document.version + 1, newText);
|
||
|
tempSourceSnapshot = (0, common_1.stringToSnapshot)(newText);
|
||
|
source.language.updateVirtualFile(tempVirtualFile, tempSourceSnapshot);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (document.getText() === originalDocument.getText())
|
||
|
return;
|
||
|
const editRange = {
|
||
|
start: originalDocument.positionAt(0),
|
||
|
end: originalDocument.positionAt(originalDocument.getText().length),
|
||
|
};
|
||
|
const textEdit = {
|
||
|
range: editRange,
|
||
|
newText: document.getText(),
|
||
|
};
|
||
|
return [textEdit];
|
||
|
function getEmbeddedFilesByLevel(rootFile, level) {
|
||
|
const embeddedFilesByLevel = [[rootFile]];
|
||
|
while (true) {
|
||
|
if (embeddedFilesByLevel.length > level)
|
||
|
return embeddedFilesByLevel[level];
|
||
|
let nextLevel = [];
|
||
|
for (const file of embeddedFilesByLevel[embeddedFilesByLevel.length - 1]) {
|
||
|
nextLevel = nextLevel.concat(file.embeddedFiles);
|
||
|
}
|
||
|
embeddedFilesByLevel.push(nextLevel);
|
||
|
}
|
||
|
}
|
||
|
async function tryFormat(document, range, ch) {
|
||
|
let formatRange = range;
|
||
|
for (const service of Object.values(context.services)) {
|
||
|
if (token.isCancellationRequested)
|
||
|
break;
|
||
|
let edits;
|
||
|
try {
|
||
|
if (ch !== undefined && 'line' in formatRange && 'character' in formatRange) {
|
||
|
if (service.autoFormatTriggerCharacters?.includes(ch)) {
|
||
|
edits = await service.provideOnTypeFormattingEdits?.(document, formatRange, ch, options, token);
|
||
|
}
|
||
|
}
|
||
|
else if (ch === undefined && 'start' in formatRange && 'end' in formatRange) {
|
||
|
edits = await service.provideDocumentFormattingEdits?.(document, formatRange, options, token);
|
||
|
}
|
||
|
}
|
||
|
catch (err) {
|
||
|
console.warn(err);
|
||
|
}
|
||
|
if (!edits)
|
||
|
continue;
|
||
|
return {
|
||
|
service,
|
||
|
edits,
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
function createDocMap(file, _sourceFileName, _sourceSnapshot) {
|
||
|
const maps = (0, language_core_1.updateVirtualFileMaps)(file, (sourceFileName) => {
|
||
|
if (!sourceFileName) {
|
||
|
return [_sourceFileName, _sourceSnapshot];
|
||
|
}
|
||
|
});
|
||
|
if (maps.has(_sourceFileName) && maps.get(_sourceFileName)[0] === _sourceSnapshot) {
|
||
|
const [_, map] = maps.get(_sourceFileName);
|
||
|
const version = fakeVersion++;
|
||
|
return new documents_1.SourceMapWithDocuments(vscode_languageserver_textdocument_1.TextDocument.create(context.env.fileNameToUri(_sourceFileName), context.host.getLanguageId?.(_sourceFileName) ?? (0, common_1.resolveCommonLanguageId)(context.env.fileNameToUri(_sourceFileName)), version, _sourceSnapshot.getText(0, _sourceSnapshot.getLength())), vscode_languageserver_textdocument_1.TextDocument.create(context.env.fileNameToUri(file.fileName), context.host.getLanguageId?.(file.fileName) ?? (0, common_1.resolveCommonLanguageId)(context.env.fileNameToUri(file.fileName)), version, file.snapshot.getText(0, file.snapshot.getLength())), map);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
exports.register = register;
|
||
|
function patchIndents(document, isCodeBlock, map, initialIndent) {
|
||
|
const indentTextEdits = [];
|
||
|
if (!isCodeBlock) {
|
||
|
initialIndent = '';
|
||
|
}
|
||
|
for (let i = 0; i < map.mappings.length; i++) {
|
||
|
const mapping = map.mappings[i];
|
||
|
const firstLineIndent = getBaseIndent(mapping.sourceRange[0]);
|
||
|
const text = document.getText().substring(mapping.sourceRange[0], mapping.sourceRange[1]);
|
||
|
const lines = text.split('\n');
|
||
|
const baseIndent = firstLineIndent + initialIndent;
|
||
|
let lineOffset = lines[0].length + 1;
|
||
|
let insertedFinalNewLine = false;
|
||
|
if (!text.trim())
|
||
|
continue;
|
||
|
if (isCodeBlock && text.trimStart().length === text.length) {
|
||
|
indentTextEdits.push({
|
||
|
newText: '\n' + baseIndent,
|
||
|
range: {
|
||
|
start: document.positionAt(mapping.sourceRange[0]),
|
||
|
end: document.positionAt(mapping.sourceRange[0]),
|
||
|
},
|
||
|
});
|
||
|
}
|
||
|
if (isCodeBlock && text.trimEnd().length === text.length) {
|
||
|
indentTextEdits.push({
|
||
|
newText: '\n',
|
||
|
range: {
|
||
|
start: document.positionAt(mapping.sourceRange[1]),
|
||
|
end: document.positionAt(mapping.sourceRange[1]),
|
||
|
},
|
||
|
});
|
||
|
insertedFinalNewLine = true;
|
||
|
}
|
||
|
if (baseIndent && lines.length > 1) {
|
||
|
for (let i = 1; i < lines.length; i++) {
|
||
|
if (lines[i].trim() || i === lines.length - 1) {
|
||
|
const isLastLine = i === lines.length - 1 && !insertedFinalNewLine;
|
||
|
indentTextEdits.push({
|
||
|
newText: isLastLine ? firstLineIndent : baseIndent,
|
||
|
range: {
|
||
|
start: document.positionAt(mapping.sourceRange[0] + lineOffset),
|
||
|
end: document.positionAt(mapping.sourceRange[0] + lineOffset),
|
||
|
},
|
||
|
});
|
||
|
}
|
||
|
lineOffset += lines[i].length + 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return indentTextEdits;
|
||
|
function getBaseIndent(pos) {
|
||
|
const startPos = document.positionAt(pos);
|
||
|
const startLineText = document.getText({ start: { line: startPos.line, character: 0 }, end: startPos });
|
||
|
return startLineText.substring(0, startLineText.length - startLineText.trimStart().length);
|
||
|
}
|
||
|
}
|
||
|
//# sourceMappingURL=format.js.map
|