226 lines
11 KiB
JavaScript
226 lines
11 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.register = register;
|
|
const language_core_1 = require("@volar/language-core");
|
|
const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
|
|
const vscode_uri_1 = require("vscode-uri");
|
|
const cancellation_1 = require("../utils/cancellation");
|
|
const common_1 = require("../utils/common");
|
|
const featureWorkers_1 = require("../utils/featureWorkers");
|
|
function register(context) {
|
|
return async (uri, options, range, onTypeParams, token = cancellation_1.NoneCancellationToken) => {
|
|
const sourceScript = context.language.scripts.get(uri);
|
|
if (!sourceScript) {
|
|
return;
|
|
}
|
|
let document = context.documents.get(uri, sourceScript.languageId, sourceScript.snapshot);
|
|
range ??= {
|
|
start: document.positionAt(0),
|
|
end: document.positionAt(document.getText().length),
|
|
};
|
|
if (!sourceScript.generated) {
|
|
return onTypeParams
|
|
? (await tryFormat(document, document, sourceScript, undefined, 0, onTypeParams.position, onTypeParams.ch))?.edits
|
|
: (await tryFormat(document, document, sourceScript, undefined, 0, range, undefined))?.edits;
|
|
}
|
|
const embeddedRanges = new Map(); // TODO: Formatting of upper-level virtual code may cause offset of lower-level selection range
|
|
const startOffset = document.offsetAt(range.start);
|
|
const endOffset = document.offsetAt(range.end);
|
|
for (const code of (0, language_core_1.forEachEmbeddedCode)(sourceScript.generated.root)) {
|
|
const map = context.language.maps.get(code, sourceScript);
|
|
if (map) {
|
|
const embeddedRange = (0, common_1.findOverlapCodeRange)(startOffset, endOffset, map, language_core_1.isFormattingEnabled);
|
|
if (embeddedRange) {
|
|
if (embeddedRange.start === map.mappings[0].generatedOffsets[0]) {
|
|
embeddedRange.start = 0;
|
|
}
|
|
const lastMapping = map.mappings[map.mappings.length - 1];
|
|
if (embeddedRange.end === lastMapping.generatedOffsets[lastMapping.generatedOffsets.length - 1] + (lastMapping.generatedLengths ?? lastMapping.lengths)[lastMapping.lengths.length - 1]) {
|
|
embeddedRange.end = code.snapshot.getLength();
|
|
}
|
|
embeddedRanges.set(code.id, embeddedRange);
|
|
}
|
|
}
|
|
}
|
|
try {
|
|
const originalDocument = document;
|
|
let tempSourceSnapshot = sourceScript.snapshot;
|
|
let tempVirtualFile = context.language.scripts.set(vscode_uri_1.URI.parse(sourceScript.id.toString() + '.tmp'), sourceScript.snapshot, sourceScript.languageId, [sourceScript.generated.languagePlugin])?.generated?.root;
|
|
if (!tempVirtualFile) {
|
|
return;
|
|
}
|
|
let currentCodes = [];
|
|
for (let depth = 0; (currentCodes = getNestedEmbeddedFiles(context, sourceScript.id, tempVirtualFile, depth)).length > 0; depth++) {
|
|
let edits = [];
|
|
for (const code of currentCodes) {
|
|
if (!code.mappings.some(mapping => (0, language_core_1.isFormattingEnabled)(mapping.data))) {
|
|
continue;
|
|
}
|
|
const currentRange = embeddedRanges.get(code.id);
|
|
if (!currentRange) {
|
|
continue;
|
|
}
|
|
const isChildRange = [...(0, language_core_1.forEachEmbeddedCode)(code)].some(child => {
|
|
if (child === code) {
|
|
return false;
|
|
}
|
|
const childRange = embeddedRanges.get(child.id);
|
|
return childRange && childRange.end - childRange.start >= currentRange.end - currentRange.start;
|
|
});
|
|
if (isChildRange) {
|
|
continue;
|
|
}
|
|
const docs = [
|
|
context.documents.get(uri, sourceScript.languageId, tempSourceSnapshot),
|
|
context.documents.get(context.encodeEmbeddedDocumentUri(uri, code.id), code.languageId, code.snapshot),
|
|
context.language.mapperFactory(code.mappings),
|
|
];
|
|
let embeddedResult;
|
|
if (onTypeParams) {
|
|
for (const embeddedPosition of (0, featureWorkers_1.getGeneratedPositions)(docs, onTypeParams.position)) {
|
|
embeddedResult = await tryFormat(docs[0], docs[1], sourceScript, code, depth, embeddedPosition, onTypeParams.ch);
|
|
break;
|
|
}
|
|
}
|
|
else if (currentRange) {
|
|
embeddedResult = await tryFormat(docs[0], docs[1], sourceScript, code, depth, {
|
|
start: docs[1].positionAt(currentRange.start),
|
|
end: docs[1].positionAt(currentRange.end),
|
|
});
|
|
}
|
|
if (!embeddedResult) {
|
|
continue;
|
|
}
|
|
for (const textEdit of embeddedResult.edits) {
|
|
const range = (0, featureWorkers_1.getSourceRange)(docs, textEdit.range);
|
|
if (range) {
|
|
edits.push({
|
|
newText: textEdit.newText,
|
|
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);
|
|
tempVirtualFile = context.language.scripts.set(vscode_uri_1.URI.parse(sourceScript.id.toString() + '.tmp'), tempSourceSnapshot, sourceScript.languageId, [sourceScript.generated.languagePlugin])?.generated?.root;
|
|
if (!tempVirtualFile) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
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];
|
|
}
|
|
finally {
|
|
context.language.scripts.delete(vscode_uri_1.URI.parse(sourceScript.id.toString() + '.tmp'));
|
|
}
|
|
async function tryFormat(sourceDocument, document, sourceScript, virtualCode, embeddedLevel, rangeOrPosition, ch) {
|
|
if (context.disabledEmbeddedDocumentUris.get(vscode_uri_1.URI.parse(document.uri))) {
|
|
return;
|
|
}
|
|
let codeOptions;
|
|
rangeOrPosition ??= {
|
|
start: document.positionAt(0),
|
|
end: document.positionAt(document.getText().length),
|
|
};
|
|
if (virtualCode) {
|
|
codeOptions = {
|
|
level: embeddedLevel,
|
|
initialIndentLevel: 0,
|
|
};
|
|
if (virtualCode.mappings.length) {
|
|
const firstMapping = virtualCode.mappings[0];
|
|
const startOffset = firstMapping.sourceOffsets[0];
|
|
const startPosition = sourceDocument.positionAt(startOffset);
|
|
codeOptions.initialIndentLevel = computeInitialIndent(sourceDocument.getText(), sourceDocument.offsetAt({ line: startPosition.line, character: 0 }), options);
|
|
}
|
|
for (const plugin of context.plugins) {
|
|
if (context.disabledServicePlugins.has(plugin[1])) {
|
|
continue;
|
|
}
|
|
codeOptions = await plugin[1].resolveEmbeddedCodeFormattingOptions?.(sourceScript, virtualCode, codeOptions, token) ?? codeOptions;
|
|
}
|
|
}
|
|
for (const plugin of context.plugins) {
|
|
if (context.disabledServicePlugins.has(plugin[1])) {
|
|
continue;
|
|
}
|
|
if (token.isCancellationRequested) {
|
|
break;
|
|
}
|
|
let edits;
|
|
try {
|
|
if (ch !== undefined && rangeOrPosition && 'line' in rangeOrPosition && 'character' in rangeOrPosition) {
|
|
if (plugin[0].capabilities.documentOnTypeFormattingProvider?.triggerCharacters?.includes(ch)) {
|
|
edits = await plugin[1].provideOnTypeFormattingEdits?.(document, rangeOrPosition, ch, options, codeOptions, token);
|
|
}
|
|
}
|
|
else if (ch === undefined && rangeOrPosition && 'start' in rangeOrPosition && 'end' in rangeOrPosition) {
|
|
edits = await plugin[1].provideDocumentFormattingEdits?.(document, rangeOrPosition, options, codeOptions, token);
|
|
}
|
|
}
|
|
catch (err) {
|
|
console.warn(err);
|
|
}
|
|
if (!edits) {
|
|
continue;
|
|
}
|
|
return {
|
|
plugin,
|
|
edits,
|
|
};
|
|
}
|
|
}
|
|
};
|
|
}
|
|
function getNestedEmbeddedFiles(context, uri, rootCode, depth) {
|
|
const nestedCodesByLevel = [[rootCode]];
|
|
while (true) {
|
|
if (nestedCodesByLevel.length > depth) {
|
|
return nestedCodesByLevel[depth];
|
|
}
|
|
const nestedCodes = [];
|
|
for (const code of nestedCodesByLevel[nestedCodesByLevel.length - 1]) {
|
|
if (code.embeddedCodes) {
|
|
for (const embedded of code.embeddedCodes) {
|
|
if (!context.disabledEmbeddedDocumentUris.get(context.encodeEmbeddedDocumentUri(uri, embedded.id))) {
|
|
nestedCodes.push(embedded);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
nestedCodesByLevel.push(nestedCodes);
|
|
}
|
|
}
|
|
function computeInitialIndent(content, i, options) {
|
|
let nChars = 0;
|
|
const tabSize = options.tabSize || 4;
|
|
while (i < content.length) {
|
|
const ch = content.charAt(i);
|
|
if (ch === ' ') {
|
|
nChars++;
|
|
}
|
|
else if (ch === '\t') {
|
|
nChars += tabSize;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
return Math.floor(nChars / tabSize);
|
|
}
|
|
//# sourceMappingURL=provideDocumentFormattingEdits.js.map
|