Я разрабатываю расширение VS Code, которое выполняет потоковую проверку выделенного диапазона текста. Расширение получает новые строки, предназначенные для замены выделенного текста, из соединения WebSocket, генерирует строки различий («те же», «старые», «новые») и применяет их к активному редактору в режиме реального времени.Проблема:
Функция editor.edit, которую я использую для вставки новых строк, блокирует основную поток в течение неожиданно длительного времени (в некоторых случаях до 40 секунд) во время процесс потоковой передачи. Это происходит даже несмотря на то, что editor.edit должен быть асинхронным. Проблема кажется более выраженной ближе к концу операции сравнения при вставке «новых» строк. Я просмотрел API vscode в editor.edit, чтобы увидеть, не сделал ли я плохую реализацию или что-то в этом роде, но никакого прогресса в этом нет.
Структура кода:
Мой код структурирован следующим образом:
- WebSocketClient получает строки сравнения с сервера .
- setMessageCallback клиента помещает новые строки в очередь строк.
- обрабатывает строки в очереди, вызывая handleStreamData для каждой строки.
Код: Выделить всё
processLinesQueue
- использует функцию-генератор showInlineDiffForSelectedRangeV2 для получения объектов DiffLine на основе пользовательской функции matchLine (которая использует расстояние Левенштейна для нечеткого сопоставления).
Код: Выделить всё
handleStreamData
< ли>использует editor.edit с editBuilder.insert для вставки новых строк в документ.Код: Выделить всё
insertLineAboveIndex
- применяет разницу Майерса после завершения потоковой передачи для обеспечения точности.
Код: Выделить всё
reapplyWithMeyersDiff
Код: Выделить всё
// ... (Other imports) ...
import { diffLines, Change } from 'diff';
// ... (Other type definitions) ...
type DiffLine = {
type: string;
line: string;
};
export type MatchLineResult = {
matchIndex: number;
isPerfectMatch: boolean;
newDiffedLine: string;
};
export class inlineChatProvider {
// ... (Properties) ...
private async processLinesQueue() {
if (this.processingQueue || this.linesQueue.length === 0) {
return;
}
this.processingQueue = true;
try {
while (this.linesQueue.length > 0) {
if (this.stopQueueProcessing) {
this.processingQueue = false;
return; // Exit the loop immediately
}
const newLine = this.linesQueue.shift()!;
console.time("handleStreamData");
await this.handleStreamData(newLine);
console.timeEnd("handleStreamData");
}
} finally {
this.processingQueue = false;
}
}
private async handleStreamData(newLine: string) {
const editor = vscode.window.activeTextEditor;
if (!editor || !this.oldRange) {
return;
}
if (this.stopQueueProcessing) {
this.processingQueue = false;
return; // Exit immediately if stopped
}
for await (const diffLine of this.showInlineDiffForSelectedRangeV2(newLine, this.showCodelens)) {
console.log("Processing diffLine:", diffLine);
if (this.stopQueueProcessing) {
return; // Exit if processing is stopped
}
switch (diffLine.type) {
case "same":
await this.insertDeletionBuffer();
this.incrementCurrentLineIndex();
break;
case "old":
this.deletionBuffer.push(diffLine.line);
await this.deleteLinesAt(this.currentLineIndex);
this.deletedLinesOffset++;
break;
case "new":
console.time("insertLineAboveIndex");
await this.insertLineAboveIndex(this.currentLineIndex, diffLine.line);
console.timeEnd("insertLineAboveIndex");
this.incrementCurrentLineIndex();
this.insertedInCurrentBlock++;
this.addedLinesOffset++;
// Expand oldRange if necessary
if (this.currentLineIndex >= this.oldRange!.end.line) {
this.oldRange = new vscode.Selection(
this.oldRange!.start.line,
this.oldRange!.start.character,
this.currentLineIndex,
0
);
}
break;
}
}
}
private async insertTextAboveLine(index: number, text: string) {
console.time(`the start:insertLineAboveIndex${index}`);
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
console.time(`editor await itself:insertLineAboveIndex${index}`);
await editor.edit(
(editBuilder) => {
const lineCount = editor.document.lineCount;
if (index >= lineCount) {
// Append to end of file
editBuilder.insert(
new vscode.Position(
lineCount,
editor.document.lineAt(lineCount - 1).text.length,
),
`\n${text}`,
);
} else {
console.time(`insertLineAboveIndex${index}`);
editBuilder.insert(new vscode.Position(index, 0), `${text}\n`);
console.timeEnd(`insertLineAboveIndex${index}`);
}
},
{
undoStopAfter: false,
undoStopBefore: false,
},
);
console.timeEnd(`editor await itself:insertLineAboveIndex${index}`);
console.timeEnd(`the start:insertLineAboveIndex${index}`);
}
private async deleteLinesAt(index: number) {
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
const startLine = new vscode.Position(index, 0);
await editor.edit(
(editBuilder) => {
editBuilder.delete(
new vscode.Range(startLine, startLine.translate(1)),
);
},
{
undoStopAfter: false,
undoStopBefore: false,
},
);
}
private async insertLineAboveIndex(index: number, line: string) {
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
console.time(`let's see edit speed:${index}`);
await this.insertTextAboveLine(index, line);
console.timeEnd(`let's see edit speed:${index}`);
const addedRange = new vscode.Range(index, 0, index, line.length);
editor.setDecorations(this.addedDecoratorType, [addedRange]);
}
private incrementCurrentLineIndex() {
this.currentLineIndex++;
}
async reapplyWithMeyersDiff() {
// ... (Implementation using diffLines and editor.edit) ...
}
// ... (Other methods) ...
private async *showInlineDiffForSelectedRangeV2(
newLine: string,
loadCodeLens: boolean = false
): AsyncGenerator {
let consecutiveNewLines = 0;
let iterationCount = 0;
const yieldInterval = 100;
while (
this.oldLinesCopy.length > 0 &&
!loadCodeLens &&
consecutiveNewLines < 3
) {
const startTime = performance.now();
const { matchIndex, isPerfectMatch, newDiffedLine } = matchLine(
newLine,
this.oldLinesCopy,
this.seenIndentationMistake
);
const endTime = performance.now();
const elapsedTime = endTime - startTime;
console.log(`matchLine took ${elapsedTime.toFixed(2)} milliseconds`);
if (!this.seenIndentationMistake && newLine !== newDiffedLine) {
this.seenIndentationMistake = true;
}
let type: string;
const isNewLine = matchIndex === -1;
if (isNewLine) {
type = "new";
consecutiveNewLines++;
} else {
consecutiveNewLines = 0;
// Insert all deleted lines before match
for (let i = 0; i < matchIndex; i++) {
yield { type: 'old', line: this.oldLinesCopy.shift()! };
}
type = isPerfectMatch ? "same" : "old";
}
switch (type) {
case "new":
yield { type, line: newDiffedLine };
break;
case "same":
yield { type, line: this.oldLinesCopy.shift()! };
break;
case "old":
const oldLine = this.oldLinesCopy.shift()!;
if (isPerfectMatch) {
yield { type: "same", line: newDiffedLine };
} else {
yield { type: "old", line: oldLine };
}
break;
default:
console.error(
`Error streaming diff, unrecognized diff type: ${type}`
);
}
iterationCount++;
if (iterationCount % yieldInterval === 0) {
// Yield to the event loop without producing a value
await new Promise(resolve => setTimeout(resolve, 0));
}
}
// Yield any remaining lines as "new"
if (!loadCodeLens) {
yield { type: "new", line: newLine };
while (this.oldLinesCopy.length > 0) {
yield { type: "old", line: this.oldLinesCopy.shift()! };
}
}
}
// ... (initSocketConnection, startEdit, etc.) ...
}
export function matchLine(
newLine: string,
oldCodeCopy: string[],
permissiveAboutIndentation = false
): MatchLineResult {
if (newLine.trim() === "" && oldCodeCopy[0]?.trim() === "") {
return {
matchIndex: 0,
isPerfectMatch: true,
newDiffedLine: newLine.trim(),
};
}
const isEndBracket = END_BRACKETS.includes(newLine.trim());
for (let i = 0; i < oldCodeCopy.length; i++) {
if (i > 4 && isEndBracket) {
return { matchIndex: -1, isPerfectMatch: false, newDiffedLine: newLine };
}
if (linesMatchPerfectly(newLine, oldCodeCopy[i])) {
return { matchIndex: i, isPerfectMatch: true, newDiffedLine: newLine };
}
// Use cached distance if available
const distanceKey = `${newLine}_${oldCodeCopy[i]}`;
if (linesMatch(newLine, oldCodeCopy[i], i)) {
// More permissive indentation handling
const newTrimmed = newLine.trim();
const oldTrimmed = oldCodeCopy[i].trim();
if (
newTrimmed === oldTrimmed ||
(permissiveAboutIndentation &&
newTrimmed.length > 0 &&
(newLine.length - newTrimmed.length) - (oldCodeCopy[i].length - oldTrimmed.length) setTimeout(resolve, 0)) для передачи управления циклу событий.
[*]Пакетное редактирование в editor.edit< /code>.
[*]Использование setInterval для периодической обработки строк вместо цикла while.
[*]Я пробовал использовать рабочие потоки, которые работают, но создают проблему diffLines не следуют правильному порядку
[/list]
[b]Что я ищу:[/b]
[list]
[*]Понимание того, почему файл editor.edit может блокироваться, несмотря на его асинхронный характер.
[*]Предложения по дальнейшей отладке или методам профилирования.
[*]Рекомендации по альтернативным подходам или оптимизации, позволяющие избежать замедления сохраняя при этом функциональность потокового сравнения (если это возможно).
[*]Любая соответствующая информация об известных ограничениях или соображениях производительности, связанных с editor.edit в API VS Code.
[/list]
Подробнее здесь: [url]https://stackoverflow.com/questions/79358248/vs-code-editor-edit-blocking-for-extended-period-during-streaming-diff-in-exte[/url]