From 1dccbaf6b1615fbb3a5a377180ae2f85db97fb58 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 23 Aug 2021 11:48:48 +0200 Subject: [PATCH] Add iterLines to Text interface --- src/text.ts | 46 ++++++++++++++++++++++++++++++++++++++++++++++ test/test-text.ts | 15 +++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/src/text.ts b/src/text.ts index 68e7c3f..9b5c202 100644 --- a/src/text.ts +++ b/src/text.ts @@ -104,6 +104,23 @@ export abstract class Text implements Iterable { /// iterator will run in reverse. iterRange(from: number, to: number = this.length): TextIterator { return new PartialTextCursor(this, from, to) } + /// Return a cursor that iterates over the given range of lines, + /// _without_ returning the line breaks between, and yielding empty + /// strings for empty lines. + /// + /// When `from` and `to` are given, they should be 1-based line numbers. + iterLines(from?: number, to?: number) { + let inner + if (from == null) { + inner = this.iter() + } else { + if (to == null) to = this.lines + 1 + let start = this.line(from).from + inner = this.iterRange(start, Math.max(start, to == this.lines + 1 ? this.length : to <= 1 ? 0 : this.line(to - 1).to)) + } + return new LineCursor(inner) + } + /// @internal abstract decompose(from: number, to: number, target: Text[], open: Open): void @@ -466,6 +483,35 @@ class PartialTextCursor implements TextIterator { get lineBreak() { return this.cursor.lineBreak && this.value != "" } } +class LineCursor implements TextIterator { + afterBreak = true + value = "" + done = false + + constructor(readonly inner: TextIterator) {} + + next(skip = 0) { + let {done, lineBreak, value} = this.inner.next(skip) + if (done) { + this.done = true + this.value = "" + } else if (lineBreak) { + if (this.afterBreak) { + this.value = "" + } else { + this.afterBreak = true + this.next() + } + } else { + this.value = value + this.afterBreak = false + } + return this + } + + get lineBreak() { return false } +} + /// This type describes a line in the document. It is created /// on-demand when lines are [queried](#text.Text.lineAt). export class Line { diff --git a/test/test-text.ts b/test/test-text.ts index 2aab300..90b9a51 100644 --- a/test/test-text.ts +++ b/test/test-text.ts @@ -165,6 +165,21 @@ describe("Text", () => { ist(doc0.iterRange(doc0.length - 2, doc0.length - 1).next().value, "9") }) + it("can iterate over lines", () => { + let doc = Text.of(["ab", "cde", "", "", "f", "", "g"]) + function get(from?: number, to?: number) { + let result = [] + for (let i = doc.iterLines(from, to); !i.next().done;) result.push(i.value) + return result.join("\n") + } + ist(get(), "ab\ncde\n\n\nf\n\ng") + ist(get(1, doc.lines + 1), "ab\ncde\n\n\nf\n\ng") + ist(get(2, 3), "cde") + ist(get(1, 1), "") + ist(get(2, 1), "") + ist(get(3), "\n\nf\n\ng") + }) + it("can convert to JSON", () => { for (let i = 0; i < 200; i++) lines.push("line " + i) let text = Text.of(lines)