From d88dba23027521e466b6246f9065c3e4e4feaf05 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sun, 8 Feb 2026 16:55:32 +0100 Subject: [PATCH] Simple live moves --- src/client.ts | 16 ++++++++++++- src/move.ts | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/run.ts | 8 ++++++- src/types.ts | 1 + 4 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 src/move.ts diff --git a/src/client.ts b/src/client.ts index 3f9b1b5..219a67c 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,4 +1,5 @@ import axios from "axios"; +import { LiveRunMove } from "./types"; var cookies: string | undefined; @@ -40,4 +41,17 @@ export async function submitRun(runId: string) { ); } -export async function addRunMoves() {} +export async function addRunMoves(runId: string, moves: LiveRunMove[]) { + await axios.post( + "http://localhost:3000/api/run/move", + { + runId, + moves, + }, + { + headers: { + Cookie: cookies, + }, + }, + ); +} diff --git a/src/move.ts b/src/move.ts new file mode 100644 index 0000000..2341b71 --- /dev/null +++ b/src/move.ts @@ -0,0 +1,65 @@ +import * as vscode from "vscode"; +import { LiveRunMove } from "./types"; +import { addRunMoves } from "./client"; +import { randomInt } from "crypto"; + +let lastText = ""; +let lastCursor = 0; +let moveId = 0; + +export function startMonitoring(runId: string) { + lastText = ""; + + return setInterval(() => { + const editor = vscode.window.activeTextEditor; + if (!editor) return; + + const currentText = editor.document.getText(); + const cursorOffset = editor.document.offsetAt(editor.selection.active); + + let change: { from: number; to: number; insert: string } | undefined; + + if (currentText !== lastText) { + let start = 0; + while ( + start < lastText.length && + start < currentText.length && + lastText[start] === currentText[start] + ) { + start++; + } + + let endOld = lastText.length - 1; + let endNew = currentText.length - 1; + while ( + endOld >= start && + endNew >= start && + lastText[endOld] === currentText[endNew] + ) { + endOld--; + endNew--; + } + + change = { + from: start, + to: endOld + 1, // ⚡ correct range in old text + insert: currentText.slice(start, endNew + 1), + }; + + lastText = currentText; + } + + const move: LiveRunMove = { + latency: 300, + cursor: cursorOffset, + changes: change, + moveId, + }; + + if (move.changes || move.cursor !== lastCursor) { + addRunMoves(runId, [move]); + } + + lastCursor = move.cursor; + }, 1000); +} diff --git a/src/run.ts b/src/run.ts index 9946190..67eae81 100644 --- a/src/run.ts +++ b/src/run.ts @@ -1,8 +1,10 @@ import * as vscode from "vscode"; import { addRun, submitRun } from "./client"; +import { startMonitoring } from "./move"; export var currentRun: { interval: NodeJS.Timeout; + monitorInterval: NodeJS.Timeout; problemId: string; start: Date; id: string; @@ -57,6 +59,7 @@ export async function startRun( .toString() .padStart(2, "0")}`; }, 50), + monitorInterval: startMonitoring(runId), }; await vscode.commands.executeCommand("setContext", "devrun.runActive", true); @@ -64,8 +67,11 @@ export async function startRun( export async function stopRun() { if (currentRun) { - clearTimeout(currentRun.interval); + clearInterval(currentRun.interval); + clearInterval(currentRun.monitorInterval); + submitRun(currentRun.id); + currentRun = null; } diff --git a/src/types.ts b/src/types.ts index 3ba77b9..d2f1bbc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,4 +6,5 @@ export interface LiveRunMove { to: number; insert: string; }; + moveId: number; }