183 lines
4.3 KiB
TypeScript
183 lines
4.3 KiB
TypeScript
import * as vscode from "vscode";
|
|
import { diff_match_patch, Diff } from "diff-match-patch";
|
|
import { LiveRunMove } from "./types";
|
|
import { addRunMoves } from "./client";
|
|
import path from "path";
|
|
|
|
export function startMonitoring(useLocalhost: boolean, runId: string) {
|
|
const dmp = new diff_match_patch();
|
|
|
|
let moveId = 0;
|
|
let moves: LiveRunMove[] = [];
|
|
|
|
let lastText = "";
|
|
let lastCursorOffset: number | undefined;
|
|
let lastEventTime = Date.now();
|
|
let idleTimeout: NodeJS.Timeout | undefined;
|
|
let isSending = false;
|
|
|
|
const IDLE_MS = 350;
|
|
|
|
if (vscode.window.activeTextEditor) {
|
|
const editor = vscode.window.activeTextEditor;
|
|
lastText = editor.document.getText();
|
|
lastCursorOffset = editor.document.offsetAt(editor.selection.active);
|
|
}
|
|
|
|
async function flush(file: string | null, language: string | null) {
|
|
if (!moves.length || isSending) return;
|
|
|
|
isSending = true;
|
|
const batch = moves;
|
|
moves = [];
|
|
|
|
try {
|
|
await addRunMoves(useLocalhost, runId, file, language, batch);
|
|
} finally {
|
|
isSending = false;
|
|
}
|
|
}
|
|
|
|
function getLatency(): number {
|
|
const now = Date.now();
|
|
const latency = capLatency(now - lastEventTime, IDLE_MS);
|
|
lastEventTime = now;
|
|
return latency;
|
|
}
|
|
|
|
// --- Existing text change interval ---
|
|
const readInterval = setInterval(() => {
|
|
const editor = vscode.window.activeTextEditor;
|
|
if (!editor) return;
|
|
|
|
const newText = editor.document.getText();
|
|
if (newText !== lastText) {
|
|
let latency = getLatency();
|
|
|
|
const diffs = dmp.diff_main(lastText, newText);
|
|
dmp.diff_cleanupEfficiency(diffs);
|
|
|
|
const changes = diffsToChanges(diffs);
|
|
|
|
lastText = newText;
|
|
|
|
for (const change of changes) {
|
|
moves.push({
|
|
moveId: moveId++,
|
|
latency,
|
|
cursor: editor.document.offsetAt(editor.selection.active),
|
|
changes: change,
|
|
});
|
|
}
|
|
|
|
if (idleTimeout) clearTimeout(idleTimeout);
|
|
idleTimeout = setTimeout(
|
|
() =>
|
|
flush(
|
|
path.basename(editor?.document.fileName || "") || null,
|
|
editor?.document.languageId || null,
|
|
),
|
|
IDLE_MS,
|
|
);
|
|
}
|
|
}, 500);
|
|
|
|
// --- New cursor tracking interval ---
|
|
const cursorInterval = setInterval(() => {
|
|
const editor = vscode.window.activeTextEditor;
|
|
if (!editor) return;
|
|
|
|
const cursorOffset = editor.document.offsetAt(editor.selection.active);
|
|
|
|
if (cursorOffset !== lastCursorOffset) {
|
|
const latency = getLatency();
|
|
|
|
moves.push({
|
|
moveId: moveId++,
|
|
latency,
|
|
cursor: cursorOffset,
|
|
});
|
|
|
|
lastCursorOffset = cursorOffset;
|
|
|
|
if (idleTimeout) clearTimeout(idleTimeout);
|
|
idleTimeout = setTimeout(
|
|
() =>
|
|
flush(
|
|
path.basename(editor.document.fileName || "") || null,
|
|
editor.document.languageId || null,
|
|
),
|
|
IDLE_MS,
|
|
);
|
|
}
|
|
}, 1000); // every second
|
|
|
|
vscode.window.onDidChangeActiveTextEditor((editor) => {
|
|
let latency = getLatency();
|
|
|
|
if (idleTimeout) clearTimeout(idleTimeout);
|
|
|
|
moves = [];
|
|
|
|
moves.push({
|
|
moveId: moveId++,
|
|
latency,
|
|
cursor: editor?.document.offsetAt(editor.selection.active) || 0,
|
|
});
|
|
|
|
flush(
|
|
path.basename(editor?.document.fileName || "") || null,
|
|
editor?.document.languageId || null,
|
|
);
|
|
});
|
|
|
|
return () => {
|
|
clearInterval(readInterval);
|
|
clearInterval(cursorInterval);
|
|
if (idleTimeout) clearTimeout(idleTimeout);
|
|
flush(
|
|
path.basename(vscode.window.activeTextEditor?.document.fileName || "") ||
|
|
null,
|
|
vscode.window.activeTextEditor?.document.languageId || null,
|
|
);
|
|
};
|
|
}
|
|
|
|
function diffsToChanges(diffs: Diff[]) {
|
|
const changes: {
|
|
from: number;
|
|
to: number;
|
|
insert: string;
|
|
}[] = [];
|
|
|
|
let offset = 0;
|
|
|
|
for (const [op, text] of diffs) {
|
|
if (op === 0) {
|
|
// equal
|
|
offset += text.length;
|
|
} else if (op === -1) {
|
|
// delete
|
|
changes.push({
|
|
from: offset,
|
|
to: offset + text.length,
|
|
insert: "",
|
|
});
|
|
} else if (op === 1) {
|
|
// insert
|
|
changes.push({
|
|
from: offset,
|
|
to: offset,
|
|
insert: text,
|
|
});
|
|
offset += text.length;
|
|
}
|
|
}
|
|
|
|
return changes;
|
|
}
|
|
|
|
function capLatency(value: number, max: number) {
|
|
return value > max ? 0 : value;
|
|
}
|