From 2d016c44bd321dcd4cdfb10cb304979afa95227e Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Sun, 17 Aug 2025 15:01:38 +0100 Subject: [PATCH] Add local selection update --- .../views/cursors/update-selection.test.ts | 111 ++++++++++++++++++ .../src/views/cursors/update-selection.ts | 37 ++++++ 2 files changed, 148 insertions(+) create mode 100644 frontend/obsidian-plugin/src/views/cursors/update-selection.test.ts create mode 100644 frontend/obsidian-plugin/src/views/cursors/update-selection.ts diff --git a/frontend/obsidian-plugin/src/views/cursors/update-selection.test.ts b/frontend/obsidian-plugin/src/views/cursors/update-selection.test.ts new file mode 100644 index 00000000..991ff76b --- /dev/null +++ b/frontend/obsidian-plugin/src/views/cursors/update-selection.test.ts @@ -0,0 +1,111 @@ +import { updateSelection } from "./update-selection"; + +describe("Selection update", () => { + it("should handle span fully before - insert", () => { + const spans = [{ start: 3, end: 5 }]; + updateSelection({ + fromA: 0, + toA: 0, + fromB: 0, + toB: 2, + spans + }); + expect(spans).toEqual([{ start: 5, end: 7 }]); + }); + + it("should handle span fully before - delete", () => { + const spans = [{ start: 3, end: 5 }]; + updateSelection({ + fromA: 0, + toA: 2, + fromB: 0, + toB: 0, + spans + }); + expect(spans).toEqual([{ start: 1, end: 3 }]); + }); + + it("should handle span fully after - insert", () => { + const spans = [{ start: 3, end: 5 }]; + updateSelection({ + fromA: 6, + toA: 6, + fromB: 6, + toB: 10, + spans + }); + expect(spans).toEqual([{ start: 3, end: 5 }]); + }); + + it("should handle span fully after - delete", () => { + const spans = [{ start: 3, end: 5 }]; + updateSelection({ + fromA: 6, + toA: 10, + fromB: 6, + toB: 6, + spans + }); + expect(spans).toEqual([{ start: 3, end: 5 }]); + }); + + it("should handle span fully within - insert", () => { + const spans = [{ start: 3, end: 5 }]; + updateSelection({ + fromA: 4, + toA: 4, + fromB: 4, + toB: 6, + spans + }); + expect(spans).toEqual([{ start: 3, end: 7 }]); + }); + + it("should handle span fully within - delete", () => { + const spans = [{ start: 3, end: 5 }]; + updateSelection({ + fromA: 4, + toA: 5, + fromB: 4, + toB: 4, + spans + }); + expect(spans).toEqual([{ start: 3, end: 4 }]); + }); + + it("should handle span overlapping with start", () => { + const spans = [{ start: 3, end: 5 }]; + updateSelection({ + fromA: 2, + toA: 4, + fromB: 2, + toB: 2, + spans + }); + expect(spans).toEqual([{ start: 2, end: 4 }]); + }); + + it("should handle span overlapping with end", () => { + const spans = [{ start: 3, end: 5 }]; + updateSelection({ + fromA: 4, + toA: 6, + fromB: 4, + toB: 4, + spans + }); + expect(spans).toEqual([{ start: 3, end: 4 }]); + }); + + it("delete entire selection", () => { + const spans = [{ start: 3, end: 5 }]; + updateSelection({ + fromA: 0, + toA: 10, + fromB: 0, + toB: 0, + spans + }); + expect(spans).toEqual([{ start: 0, end: 0 }]); + }); +}); diff --git a/frontend/obsidian-plugin/src/views/cursors/update-selection.ts b/frontend/obsidian-plugin/src/views/cursors/update-selection.ts new file mode 100644 index 00000000..2551f863 --- /dev/null +++ b/frontend/obsidian-plugin/src/views/cursors/update-selection.ts @@ -0,0 +1,37 @@ +import type { CursorSpan } from "sync-client"; + +export const updateSelection = ({ + fromA, + toA, + toB, + spans +}: { + fromA: number; + toA: number; + fromB: number; + toB: number; + spans: CursorSpan[]; +}): void => { + spans.forEach((span) => { + if (fromA <= span.start) { + // The change covers the entirety of the selection + if (toA > span.end) { + span.start = toB; + span.end = toB; + return; + } + + let change = toB - toA; + if (change < 0) { + change = Math.max(change, fromA - span.start); + } + + span.start += change; + span.end += change; + } else if (toA <= span.end) { + span.end += toB - toA; + } else if (toB <= span.end) { + span.end = toB; + } + }); +};