From 88ecc256c05fe4b8ae48a7d0424423408b823bf2 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 5 Dec 2023 09:56:47 +0100 Subject: [PATCH] Make sure from/to differences are computed from clipped values FIX: Fix an issue where `Text.slice` and `Text.replace` could return objects with incorrect `length` when the given `from`/`to` values were out of range for the text. Closes https://github.com/codemirror/dev/issues/1303 --- src/text.ts | 11 +++++++++++ test/test-text.ts | 8 ++++++++ 2 files changed, 19 insertions(+) diff --git a/src/text.ts b/src/text.ts index 1c9013e..1184db2 100644 --- a/src/text.ts +++ b/src/text.ts @@ -52,6 +52,7 @@ export abstract class Text implements Iterable { /// Replace a range of the text with the given content. replace(from: number, to: number, text: Text): Text { + ;[from, to] = clip(this, from, to) let parts: Text[] = [] this.decompose(0, from, parts, Open.To) if (text.length) text.decompose(0, text.length, parts, Open.From | Open.To) @@ -66,6 +67,7 @@ export abstract class Text implements Iterable { /// Retrieve the text between the given points. slice(from: number, to: number = this.length): Text { + ;[from, to] = clip(this, from, to) let parts: Text[] = [] this.decompose(from, to, parts, 0 as Open) return TextNode.from(parts, to - from) @@ -198,6 +200,7 @@ class TextLeaf extends Text { replace(from: number, to: number, text: Text): Text { if (!(text instanceof TextLeaf)) return super.replace(from, to, text) + ;[from, to] = clip(this, from, to) let lines = appendText(this.text, appendText(text.text, sliceText(this.text, 0, from)), to) let newLen = this.length + text.length - (to - from) if (lines.length <= Tree.Branch) return new TextLeaf(lines, newLen) @@ -205,6 +208,7 @@ class TextLeaf extends Text { } sliceString(from: number, to = this.length, lineSep = "\n") { + ;[from, to] = clip(this, from, to) let result = "" for (let pos = 0, i = 0; pos <= to && i < this.text.length; i++) { let line = this.text[i], end = pos + line.length @@ -274,6 +278,7 @@ class TextNode extends Text { } replace(from: number, to: number, text: Text): Text { + ;[from, to] = clip(this, from, to) if (text.lines < this.lines) for (let i = 0, pos = 0; i < this.children.length; i++) { let child = this.children[i], end = pos + child.length // Fast path: if the change only affects one child and the @@ -296,6 +301,7 @@ class TextNode extends Text { } sliceString(from: number, to = this.length, lineSep = "\n") { + ;[from, to] = clip(this, from, to) let result = "" for (let i = 0, pos = 0; i < this.children.length && pos <= to; i++) { let child = this.children[i], end = pos + child.length @@ -569,3 +575,8 @@ export class Line { /// The length of the line (not including any line break after it). get length() { return this.to - this.from } } + +function clip(text: Text, from: number, to: number) { + from = Math.max(0, Math.min(text.length, from)) + return [from, Math.max(from, Math.min(text.length, to))] +} diff --git a/test/test-text.ts b/test/test-text.ts index bdadbdb..dc2cbba 100644 --- a/test/test-text.ts +++ b/test/test-text.ts @@ -225,4 +225,12 @@ describe("Text", () => { ist(doc0.slice(from, to).toString(), text0.slice(from, to)) } }) + + it("clips out-of-range boundaries", () => { + ist(doc0.slice(0, -10).length, 0) + ist(Text.empty.slice(0, 10).length, 0) + ist(Text.empty.slice(1000, 1100).length, 0) + ist(doc0.slice(5, 0).length, 0) + ist(doc0.slice(-5, 0).length, 0) + }) })