From c9ad06a4417c09cd915a1b9807ead6cf58967eed Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sun, 8 Feb 2026 18:32:51 +0100 Subject: [PATCH] fixed issues --- package-lock.json | 14 ++++++ package.json | 2 + src/move.ts | 106 ++++++++++++++++++++++++++++++++-------------- 3 files changed, 90 insertions(+), 32 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6df8e4b..126fedc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,11 @@ "version": "0.0.1", "dependencies": { "@types/cors": "^2.8.19", + "@types/diff-match-patch": "^1.0.36", "@types/express": "^5.0.6", "axios": "^1.13.5", "cors": "^2.8.6", + "diff-match-patch": "^1.0.5", "express": "^5.2.1" }, "devDependencies": { @@ -302,6 +304,12 @@ "@types/node": "*" } }, + "node_modules/@types/diff-match-patch": { + "version": "1.0.36", + "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", + "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==", + "license": "MIT" + }, "node_modules/@types/express": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", @@ -1244,6 +1252,12 @@ "node": ">=0.3.1" } }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", + "license": "Apache-2.0" + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", diff --git a/package.json b/package.json index 2ea622a..939169a 100644 --- a/package.json +++ b/package.json @@ -84,9 +84,11 @@ }, "dependencies": { "@types/cors": "^2.8.19", + "@types/diff-match-patch": "^1.0.36", "@types/express": "^5.0.6", "axios": "^1.13.5", "cors": "^2.8.6", + "diff-match-patch": "^1.0.5", "express": "^5.2.1" } } diff --git a/src/move.ts b/src/move.ts index 7e11248..cd0ca43 100644 --- a/src/move.ts +++ b/src/move.ts @@ -1,70 +1,112 @@ import * as vscode from "vscode"; +import { diff_match_patch, Diff } from "diff-match-patch"; import { LiveRunMove } from "./types"; import { addRunMoves } from "./client"; export function startMonitoring(runId: string) { + const dmp = new diff_match_patch(); + let moveId = 0; let moves: LiveRunMove[] = []; - let lastLatency = Date.now(); + + let lastText = ""; + let lastEventTime = Date.now(); let idleTimeout: NodeJS.Timeout | undefined; let isSending = false; - const SEND_IDLE_MS = 350; + const IDLE_MS = 350; - const flushMoves = async () => { + const editor = vscode.window.activeTextEditor; + if (editor) { + lastText = editor.document.getText(); + } + + async function flush() { if (!moves.length || isSending) return; isSending = true; - const toSend = moves; + const batch = moves; moves = []; try { - await addRunMoves(runId, toSend); + await addRunMoves(runId, batch); } finally { isSending = false; } - }; + } - const docSub = vscode.workspace.onDidChangeTextDocument((e) => { + const readInterval = setInterval(() => { const editor = vscode.window.activeTextEditor; - if (!editor || e.document !== editor.document) return; + if (!editor) return; - const edit = e.contentChanges[0]; - if (!edit) return; + const newText = editor.document.getText(); + if (newText === lastText) return; const now = Date.now(); - const latency = capToZeroAbove(now - lastLatency, SEND_IDLE_MS); - lastLatency = now; + const latency = capLatency(now - lastEventTime, IDLE_MS); + lastEventTime = now; - const cursorOffset = editor.document.offsetAt(editor.selection.active); + const diffs = dmp.diff_main(lastText, newText); + dmp.diff_cleanupEfficiency(diffs); - moves.push({ - latency, - cursor: cursorOffset, - changes: { - from: edit.rangeOffset, - to: edit.rangeOffset + edit.rangeLength, - insert: edit.text, - }, - moveId: moveId++, - }); + const changes = diffsToChanges(diffs); - if (idleTimeout) { - clearTimeout(idleTimeout); + lastText = newText; + + for (const change of changes) { + moves.push({ + moveId: moveId++, + latency, + cursor: editor.document.offsetAt(editor.selection.active), + changes: change, + }); } - idleTimeout = setTimeout(() => { - flushMoves(); - }, SEND_IDLE_MS); - }); + if (idleTimeout) clearTimeout(idleTimeout); + idleTimeout = setTimeout(flush, IDLE_MS); + }, 500); return () => { - docSub.dispose(); + clearInterval(readInterval); if (idleTimeout) clearTimeout(idleTimeout); - flushMoves(); + flush(); }; } -function capToZeroAbove(value: number, max: number) { +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; }