From 19d7da8c2482de495f0555910b8a25b7d85ed856 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Fri, 19 Jul 2024 03:47:06 +0200 Subject: [PATCH 01/12] Replace firstNonWhitespaceCharacterIndex with rangeTrimmed --- .../inMemoryTextDocument/InMemoryTextLine.ts | 14 ++++++++------ .../test/InMemoryTextDocumentLineAt.test.ts | 9 +++------ packages/common/src/types/TextLine.ts | 16 +++++----------- .../cursorless-engine/src/actions/BreakLine.ts | 5 +---- .../cursorless-engine/src/actions/JoinLines.ts | 7 +------ .../scopeHandlers/LineScopeHandler.ts | 8 +------- .../processTargets/targets/DestinationImpl.ts | 16 ++++++++++------ .../src/ide/vscode/VscodeTextLineImpl.ts | 18 +++++++++++------- .../src/ide/vscode/textLine.vscode.test.ts | 6 +++--- 9 files changed, 43 insertions(+), 56 deletions(-) diff --git a/packages/common/src/ide/inMemoryTextDocument/InMemoryTextLine.ts b/packages/common/src/ide/inMemoryTextDocument/InMemoryTextLine.ts index 83414aed08..c0e3df2855 100644 --- a/packages/common/src/ide/inMemoryTextDocument/InMemoryTextLine.ts +++ b/packages/common/src/ide/inMemoryTextDocument/InMemoryTextLine.ts @@ -4,11 +4,10 @@ import type { TextLine } from "../../types/TextLine"; import { getLeadingWhitespace, getTrailingWhitespace } from "../../util/regex"; export class InMemoryTextLine implements TextLine { + readonly isEmptyOrWhitespace: boolean; readonly range: Range; readonly rangeIncludingLineBreak: Range; - readonly firstNonWhitespaceCharacterIndex: number; - readonly lastNonWhitespaceCharacterIndex: number; - readonly isEmptyOrWhitespace: boolean; + readonly rangeTrimmed: Range; readonly lengthIncludingEol: number; constructor( @@ -23,10 +22,13 @@ export class InMemoryTextLine implements TextLine { const end = new Position(lineNumber, text.length); const endIncludingLineBreak = eol != null ? new Position(lineNumber + 1, 0) : end; - this.firstNonWhitespaceCharacterIndex = getLeadingWhitespace(text).length; - this.lastNonWhitespaceCharacterIndex = - text.length - getTrailingWhitespace(text).length; this.range = new Range(start, end); this.rangeIncludingLineBreak = new Range(start, endIncludingLineBreak); + this.rangeTrimmed = this.isEmptyOrWhitespace + ? this.range + : new Range( + start.translate(undefined, getLeadingWhitespace(text).length), + end.translate(undefined, -getTrailingWhitespace(text).length), + ); } } diff --git a/packages/common/src/ide/inMemoryTextDocument/test/InMemoryTextDocumentLineAt.test.ts b/packages/common/src/ide/inMemoryTextDocument/test/InMemoryTextDocumentLineAt.test.ts index e31d668e72..fb8aeadc36 100644 --- a/packages/common/src/ide/inMemoryTextDocument/test/InMemoryTextDocumentLineAt.test.ts +++ b/packages/common/src/ide/inMemoryTextDocument/test/InMemoryTextDocumentLineAt.test.ts @@ -47,26 +47,23 @@ suite("InMemoryTextDocument.lineAt", () => { assert.equal(line0.lineNumber, 0); assert.equal(line0.text, "hello "); assert.equal(line0.isEmptyOrWhitespace, false); - assert.equal(line0.firstNonWhitespaceCharacterIndex, 0); - assert.equal(line0.lastNonWhitespaceCharacterIndex, 5); assert.equal(line0.range.toString(), "0:0-0:7"); assert.equal(line0.rangeIncludingLineBreak.toString(), "0:0-1:0"); + assert.equal(line0.rangeTrimmed.toString(), "0:0-0:5"); assert.equal(line1.lineNumber, 1); assert.equal(line1.text, " world"); assert.equal(line1.isEmptyOrWhitespace, false); - assert.equal(line1.firstNonWhitespaceCharacterIndex, 2); - assert.equal(line1.lastNonWhitespaceCharacterIndex, 7); assert.equal(line1.range.toString(), "1:0-1:7"); assert.equal(line1.rangeIncludingLineBreak.toString(), "1:0-2:0"); + assert.equal(line1.rangeTrimmed.toString(), "1:2-1:7"); assert.equal(line2.lineNumber, 2); assert.equal(line2.text, " "); assert.equal(line2.isEmptyOrWhitespace, true); - assert.equal(line2.firstNonWhitespaceCharacterIndex, 2); - assert.equal(line2.lastNonWhitespaceCharacterIndex, 0); assert.equal(line2.range.toString(), "2:0-2:2"); assert.equal(line2.rangeIncludingLineBreak.toString(), "2:0-2:2"); + assert.equal(line2.rangeTrimmed.toString(), "2:0-2:2"); assert.equal(lineUnderflow.lineNumber, 0); assert.equal(lineOverflow.lineNumber, 2); diff --git a/packages/common/src/types/TextLine.ts b/packages/common/src/types/TextLine.ts index abbaf0c496..635cbe8d1a 100644 --- a/packages/common/src/types/TextLine.ts +++ b/packages/common/src/types/TextLine.ts @@ -28,20 +28,14 @@ export interface TextLine { readonly rangeIncludingLineBreak: Range; /** - * The offset of the first character which is not a whitespace character as defined - * by `/\s/`. **Note** that if a line is all whitespace the length of the line is returned. + * The trimmed range which is not a whitespace character as defined by `/\s/`. + * **Note** that if a line is all whitespace the full a range of the line is + * used. */ - readonly firstNonWhitespaceCharacterIndex: number; + readonly rangeTrimmed: Range; /** - * The offset of the last character which is not a whitespace character as defined - * by `/\s/`. **Note** that if a line is all whitespace 0 is returned. - */ - readonly lastNonWhitespaceCharacterIndex: number; - - /** - * Whether this line is whitespace only, shorthand - * for {@link TextLine.firstNonWhitespaceCharacterIndex} === {@link TextLine.text TextLine.text.length}. + * Whether this line is whitespace only */ readonly isEmptyOrWhitespace: boolean; } diff --git a/packages/cursorless-engine/src/actions/BreakLine.ts b/packages/cursorless-engine/src/actions/BreakLine.ts index c8bd79126e..0d78b3ec40 100644 --- a/packages/cursorless-engine/src/actions/BreakLine.ts +++ b/packages/cursorless-engine/src/actions/BreakLine.ts @@ -51,10 +51,7 @@ function getEdits(editor: TextEditor, contentRanges: Range[]): Edit[] { for (const range of contentRanges) { const position = range.start; const line = document.lineAt(position); - const indentation = line.text.slice( - 0, - line.firstNonWhitespaceCharacterIndex, - ); + const indentation = line.text.slice(0, line.rangeTrimmed.start.character); const characterTrailingWhitespace = line.text .slice(0, position.character) .search(/\s+$/); diff --git a/packages/cursorless-engine/src/actions/JoinLines.ts b/packages/cursorless-engine/src/actions/JoinLines.ts index 7d8acb0c0e..f4933da2b0 100644 --- a/packages/cursorless-engine/src/actions/JoinLines.ts +++ b/packages/cursorless-engine/src/actions/JoinLines.ts @@ -52,12 +52,7 @@ function getEdits(editor: TextEditor, contentRanges: Range[]): Edit[] { ); for (const [line1, line2] of pairwise(lineIter)) { edits.push({ - range: new Range( - line1.range.end.line, - line1.lastNonWhitespaceCharacterIndex, - line2.range.start.line, - line2.firstNonWhitespaceCharacterIndex, - ), + range: new Range(line1.rangeTrimmed.end, line2.rangeTrimmed.start), text: line2.isEmptyOrWhitespace ? "" : " ", isReplace: true, }); diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/LineScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/LineScopeHandler.ts index e57b1094c5..d08c3e9241 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/LineScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/LineScopeHandler.ts @@ -66,11 +66,5 @@ export function createLineTarget( export function fitRangeToLineContent(editor: TextEditor, range: Range) { const startLine = editor.document.lineAt(range.start); const endLine = editor.document.lineAt(range.end); - - return new Range( - startLine.lineNumber, - startLine.firstNonWhitespaceCharacterIndex, - endLine.lineNumber, - endLine.lastNonWhitespaceCharacterIndex, - ); + return new Range(startLine.rangeTrimmed.start, endLine.rangeTrimmed.end); } diff --git a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts index b1dc7884cb..bdda953847 100644 --- a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts +++ b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts @@ -122,12 +122,12 @@ export class DestinationImpl implements Destination { if (this.isLineDelimiter) { const line = this.editor.document.lineAt(insertionPosition); - const nonWhitespaceCharacterIndex = this.isBefore - ? line.firstNonWhitespaceCharacterIndex - : line.lastNonWhitespaceCharacterIndex; + const trimmedPosition = this.isBefore + ? line.rangeTrimmed.start + : line.rangeTrimmed.end; // Use the full line with included indentation and trailing whitespaces - if (insertionPosition.character === nonWhitespaceCharacterIndex) { + if (insertionPosition.isEqual(trimmedPosition)) { return this.isBefore ? line.range.start : line.range.end; } } @@ -197,8 +197,12 @@ function getIndentationString(editor: TextEditor, range: Range) { continue; } - if (line.firstNonWhitespaceCharacterIndex < length) { - length = line.firstNonWhitespaceCharacterIndex; + const position = line.isEmptyOrWhitespace + ? line.range.end + : line.rangeTrimmed.start; + + if (position.character < length) { + length = position.character; indentationString = line.text.slice(0, length); } } diff --git a/packages/cursorless-vscode/src/ide/vscode/VscodeTextLineImpl.ts b/packages/cursorless-vscode/src/ide/vscode/VscodeTextLineImpl.ts index 2f19862870..4b4da063d6 100644 --- a/packages/cursorless-vscode/src/ide/vscode/VscodeTextLineImpl.ts +++ b/packages/cursorless-vscode/src/ide/vscode/VscodeTextLineImpl.ts @@ -1,4 +1,4 @@ -import { Range, TextLine } from "@cursorless/common"; +import { Position, Range, TextLine } from "@cursorless/common"; import { fromVscodeRange } from "@cursorless/vscode-common"; import * as vscode from "vscode"; @@ -21,12 +21,16 @@ export default class VscodeTextLineImpl implements TextLine { return fromVscodeRange(this.line.rangeIncludingLineBreak); } - get firstNonWhitespaceCharacterIndex(): number { - return this.line.firstNonWhitespaceCharacterIndex; - } - - get lastNonWhitespaceCharacterIndex(): number { - return this.line.text.trimEnd().length; + get rangeTrimmed(): Range { + return this.line.isEmptyOrWhitespace + ? this.range + : new Range( + new Position( + this.lineNumber, + this.line.firstNonWhitespaceCharacterIndex, + ), + new Position(this.lineNumber, this.line.text.trimEnd().length), + ); } get isEmptyOrWhitespace(): boolean { diff --git a/packages/cursorless-vscode/src/ide/vscode/textLine.vscode.test.ts b/packages/cursorless-vscode/src/ide/vscode/textLine.vscode.test.ts index cbbe591d64..aa56ff40bd 100644 --- a/packages/cursorless-vscode/src/ide/vscode/textLine.vscode.test.ts +++ b/packages/cursorless-vscode/src/ide/vscode/textLine.vscode.test.ts @@ -8,7 +8,7 @@ import VscodeTextLineImpl from "./VscodeTextLineImpl"; * `[text, firstNonWhitespaceCharacterIndex, lastNonWhitespaceCharacterIndex]` */ const whiteSpaceTests: [string, number, number][] = [ - [" ", 3, 0], + [" ", 0, 3], ["foo", 0, 3], [" foo ", 1, 4], ]; @@ -27,11 +27,11 @@ suite("TextLine", function () { const line = new VscodeTextLineImpl(editor.document.lineAt(0)); assert.equal( - line.firstNonWhitespaceCharacterIndex, + line.rangeTrimmed.start.character, firstNonWhitespaceCharacterIndex, ); assert.equal( - line.lastNonWhitespaceCharacterIndex, + line.rangeTrimmed.end.character, lastNonWhitespaceCharacterIndex, ); }); From 3e66031821037fa72b91e7c03ab927d038471b22 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Fri, 19 Jul 2024 03:51:56 +0200 Subject: [PATCH 02/12] Update comment --- .../src/processTargets/targets/DestinationImpl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts index bdda953847..da23d1e3e4 100644 --- a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts +++ b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts @@ -126,7 +126,7 @@ export class DestinationImpl implements Destination { ? line.rangeTrimmed.start : line.rangeTrimmed.end; - // Use the full line with included indentation and trailing whitespaces + // Use the full line width including indentation and trailing whitespaces if (insertionPosition.isEqual(trimmedPosition)) { return this.isBefore ? line.range.start : line.range.end; } From 77f8fbc9ce8c596e1b03a10d9da4ebbbb41f514c Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Fri, 19 Jul 2024 03:54:01 +0200 Subject: [PATCH 03/12] rename variable --- .../src/processTargets/targets/DestinationImpl.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts index da23d1e3e4..e2799efb4b 100644 --- a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts +++ b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts @@ -197,12 +197,12 @@ function getIndentationString(editor: TextEditor, range: Range) { continue; } - const position = line.isEmptyOrWhitespace - ? line.range.end + const trimmedPosition = line.isEmptyOrWhitespace + ? line.rangeTrimmed.end : line.rangeTrimmed.start; - if (position.character < length) { - length = position.character; + if (trimmedPosition.character < length) { + length = trimmedPosition.character; indentationString = line.text.slice(0, length); } } From 014a7949018d08928a49b528f966b5dc0a179c2a Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Fri, 19 Jul 2024 03:55:17 +0200 Subject: [PATCH 04/12] rename variables --- .../src/ide/vscode/textLine.vscode.test.ts | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/packages/cursorless-vscode/src/ide/vscode/textLine.vscode.test.ts b/packages/cursorless-vscode/src/ide/vscode/textLine.vscode.test.ts index aa56ff40bd..96464f0319 100644 --- a/packages/cursorless-vscode/src/ide/vscode/textLine.vscode.test.ts +++ b/packages/cursorless-vscode/src/ide/vscode/textLine.vscode.test.ts @@ -16,25 +16,13 @@ const whiteSpaceTests: [string, number, number][] = [ suite("TextLine", function () { this.timeout("100s"); this.retries(5); - whiteSpaceTests.forEach( - ([ - text, - firstNonWhitespaceCharacterIndex, - lastNonWhitespaceCharacterIndex, - ]) => { - test(`whitespace '${text}'`, async () => { - const editor = await openNewEditor(text); - const line = new VscodeTextLineImpl(editor.document.lineAt(0)); + whiteSpaceTests.forEach(([text, trimmedStart, trimmedEnd]) => { + test(`whitespace '${text}'`, async () => { + const editor = await openNewEditor(text); + const line = new VscodeTextLineImpl(editor.document.lineAt(0)); - assert.equal( - line.rangeTrimmed.start.character, - firstNonWhitespaceCharacterIndex, - ); - assert.equal( - line.rangeTrimmed.end.character, - lastNonWhitespaceCharacterIndex, - ); - }); - }, - ); + assert.equal(line.rangeTrimmed.start.character, trimmedStart); + assert.equal(line.rangeTrimmed.end.character, trimmedEnd); + }); + }); }); From 78b7345aab0c10a90a95ba22315715f85f8b5b46 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Sun, 28 Jul 2024 16:22:49 +0100 Subject: [PATCH 05/12] docstring --- packages/common/src/types/TextLine.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/common/src/types/TextLine.ts b/packages/common/src/types/TextLine.ts index 635cbe8d1a..e5ba3f5ad4 100644 --- a/packages/common/src/types/TextLine.ts +++ b/packages/common/src/types/TextLine.ts @@ -28,9 +28,9 @@ export interface TextLine { readonly rangeIncludingLineBreak: Range; /** - * The trimmed range which is not a whitespace character as defined by `/\s/`. - * **Note** that if a line is all whitespace the full a range of the line is - * used. + * The trimmed range, which excludes leading and trailing whitespace (`/\s/`). + * **Note** that if a line is all whitespace the full range of the line is + * returned. */ readonly rangeTrimmed: Range; From 2f7ecb5d610f6b058ec408856053772b0b8e74e4 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 1 Aug 2024 21:09:30 +0200 Subject: [PATCH 06/12] Make trimmed range optional --- .../common/src/ide/inMemoryTextDocument/InMemoryTextLine.ts | 4 ++-- packages/common/src/types/TextLine.ts | 5 ++--- .../cursorless-vscode/src/ide/vscode/VscodeTextLineImpl.ts | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/common/src/ide/inMemoryTextDocument/InMemoryTextLine.ts b/packages/common/src/ide/inMemoryTextDocument/InMemoryTextLine.ts index c0e3df2855..b250a53c63 100644 --- a/packages/common/src/ide/inMemoryTextDocument/InMemoryTextLine.ts +++ b/packages/common/src/ide/inMemoryTextDocument/InMemoryTextLine.ts @@ -7,7 +7,7 @@ export class InMemoryTextLine implements TextLine { readonly isEmptyOrWhitespace: boolean; readonly range: Range; readonly rangeIncludingLineBreak: Range; - readonly rangeTrimmed: Range; + readonly rangeTrimmed: Range | undefined; readonly lengthIncludingEol: number; constructor( @@ -25,7 +25,7 @@ export class InMemoryTextLine implements TextLine { this.range = new Range(start, end); this.rangeIncludingLineBreak = new Range(start, endIncludingLineBreak); this.rangeTrimmed = this.isEmptyOrWhitespace - ? this.range + ? undefined : new Range( start.translate(undefined, getLeadingWhitespace(text).length), end.translate(undefined, -getTrailingWhitespace(text).length), diff --git a/packages/common/src/types/TextLine.ts b/packages/common/src/types/TextLine.ts index 635cbe8d1a..b6420416f5 100644 --- a/packages/common/src/types/TextLine.ts +++ b/packages/common/src/types/TextLine.ts @@ -29,10 +29,9 @@ export interface TextLine { /** * The trimmed range which is not a whitespace character as defined by `/\s/`. - * **Note** that if a line is all whitespace the full a range of the line is - * used. + * **Note** that if a line is all whitespace this is undefined. */ - readonly rangeTrimmed: Range; + readonly rangeTrimmed: Range | undefined; /** * Whether this line is whitespace only diff --git a/packages/cursorless-vscode/src/ide/vscode/VscodeTextLineImpl.ts b/packages/cursorless-vscode/src/ide/vscode/VscodeTextLineImpl.ts index 4b4da063d6..c2648e5852 100644 --- a/packages/cursorless-vscode/src/ide/vscode/VscodeTextLineImpl.ts +++ b/packages/cursorless-vscode/src/ide/vscode/VscodeTextLineImpl.ts @@ -21,9 +21,9 @@ export default class VscodeTextLineImpl implements TextLine { return fromVscodeRange(this.line.rangeIncludingLineBreak); } - get rangeTrimmed(): Range { + get rangeTrimmed(): Range | undefined { return this.line.isEmptyOrWhitespace - ? this.range + ? undefined : new Range( new Position( this.lineNumber, From 7361e7ac23d11d419aab24e81aceba6089494950 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 1 Aug 2024 21:22:45 +0200 Subject: [PATCH 07/12] More updates --- .../test/InMemoryTextDocumentLineAt.test.ts | 6 +++--- packages/cursorless-engine/src/actions/BreakLine.ts | 5 ++++- packages/cursorless-engine/src/actions/JoinLines.ts | 5 ++++- .../modifiers/scopeHandlers/LineScopeHandler.ts | 5 ++++- .../src/processTargets/targets/DestinationImpl.ts | 8 +++----- .../src/ide/vscode/textLine.vscode.test.ts | 8 ++++---- 6 files changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/common/src/ide/inMemoryTextDocument/test/InMemoryTextDocumentLineAt.test.ts b/packages/common/src/ide/inMemoryTextDocument/test/InMemoryTextDocumentLineAt.test.ts index fb8aeadc36..7ee72d948e 100644 --- a/packages/common/src/ide/inMemoryTextDocument/test/InMemoryTextDocumentLineAt.test.ts +++ b/packages/common/src/ide/inMemoryTextDocument/test/InMemoryTextDocumentLineAt.test.ts @@ -49,21 +49,21 @@ suite("InMemoryTextDocument.lineAt", () => { assert.equal(line0.isEmptyOrWhitespace, false); assert.equal(line0.range.toString(), "0:0-0:7"); assert.equal(line0.rangeIncludingLineBreak.toString(), "0:0-1:0"); - assert.equal(line0.rangeTrimmed.toString(), "0:0-0:5"); + assert.equal(line0.rangeTrimmed?.toString(), "0:0-0:5"); assert.equal(line1.lineNumber, 1); assert.equal(line1.text, " world"); assert.equal(line1.isEmptyOrWhitespace, false); assert.equal(line1.range.toString(), "1:0-1:7"); assert.equal(line1.rangeIncludingLineBreak.toString(), "1:0-2:0"); - assert.equal(line1.rangeTrimmed.toString(), "1:2-1:7"); + assert.equal(line1.rangeTrimmed?.toString(), "1:2-1:7"); assert.equal(line2.lineNumber, 2); assert.equal(line2.text, " "); assert.equal(line2.isEmptyOrWhitespace, true); assert.equal(line2.range.toString(), "2:0-2:2"); assert.equal(line2.rangeIncludingLineBreak.toString(), "2:0-2:2"); - assert.equal(line2.rangeTrimmed.toString(), "2:0-2:2"); + assert.equal(line2.rangeTrimmed, undefined); assert.equal(lineUnderflow.lineNumber, 0); assert.equal(lineOverflow.lineNumber, 2); diff --git a/packages/cursorless-engine/src/actions/BreakLine.ts b/packages/cursorless-engine/src/actions/BreakLine.ts index 0d78b3ec40..189052bddc 100644 --- a/packages/cursorless-engine/src/actions/BreakLine.ts +++ b/packages/cursorless-engine/src/actions/BreakLine.ts @@ -51,7 +51,10 @@ function getEdits(editor: TextEditor, contentRanges: Range[]): Edit[] { for (const range of contentRanges) { const position = range.start; const line = document.lineAt(position); - const indentation = line.text.slice(0, line.rangeTrimmed.start.character); + const indentation = line.text.slice( + 0, + line.rangeTrimmed?.start?.character ?? line.range.start.character, + ); const characterTrailingWhitespace = line.text .slice(0, position.character) .search(/\s+$/); diff --git a/packages/cursorless-engine/src/actions/JoinLines.ts b/packages/cursorless-engine/src/actions/JoinLines.ts index f4933da2b0..a690cd4167 100644 --- a/packages/cursorless-engine/src/actions/JoinLines.ts +++ b/packages/cursorless-engine/src/actions/JoinLines.ts @@ -52,7 +52,10 @@ function getEdits(editor: TextEditor, contentRanges: Range[]): Edit[] { ); for (const [line1, line2] of pairwise(lineIter)) { edits.push({ - range: new Range(line1.rangeTrimmed.end, line2.rangeTrimmed.start), + range: new Range( + line1.rangeTrimmed?.end ?? line1.range.end, + line2.rangeTrimmed?.start ?? line2.range.start, + ), text: line2.isEmptyOrWhitespace ? "" : " ", isReplace: true, }); diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/LineScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/LineScopeHandler.ts index d08c3e9241..bcf3399556 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/LineScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/LineScopeHandler.ts @@ -66,5 +66,8 @@ export function createLineTarget( export function fitRangeToLineContent(editor: TextEditor, range: Range) { const startLine = editor.document.lineAt(range.start); const endLine = editor.document.lineAt(range.end); - return new Range(startLine.rangeTrimmed.start, endLine.rangeTrimmed.end); + return new Range( + startLine.rangeTrimmed?.start ?? startLine.range.start, + endLine.rangeTrimmed?.end ?? endLine.range.end, + ); } diff --git a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts index e2799efb4b..3a60db1de6 100644 --- a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts +++ b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts @@ -123,8 +123,8 @@ export class DestinationImpl implements Destination { if (this.isLineDelimiter) { const line = this.editor.document.lineAt(insertionPosition); const trimmedPosition = this.isBefore - ? line.rangeTrimmed.start - : line.rangeTrimmed.end; + ? line.rangeTrimmed?.start ?? line.range.start + : line.rangeTrimmed?.end ?? line.range.end; // Use the full line width including indentation and trailing whitespaces if (insertionPosition.isEqual(trimmedPosition)) { @@ -197,9 +197,7 @@ function getIndentationString(editor: TextEditor, range: Range) { continue; } - const trimmedPosition = line.isEmptyOrWhitespace - ? line.rangeTrimmed.end - : line.rangeTrimmed.start; + const trimmedPosition = line.rangeTrimmed?.start ?? line.range.end; if (trimmedPosition.character < length) { length = trimmedPosition.character; diff --git a/packages/cursorless-vscode/src/ide/vscode/textLine.vscode.test.ts b/packages/cursorless-vscode/src/ide/vscode/textLine.vscode.test.ts index 96464f0319..693bd9ccd7 100644 --- a/packages/cursorless-vscode/src/ide/vscode/textLine.vscode.test.ts +++ b/packages/cursorless-vscode/src/ide/vscode/textLine.vscode.test.ts @@ -7,8 +7,8 @@ import VscodeTextLineImpl from "./VscodeTextLineImpl"; * * `[text, firstNonWhitespaceCharacterIndex, lastNonWhitespaceCharacterIndex]` */ -const whiteSpaceTests: [string, number, number][] = [ - [" ", 0, 3], +const whiteSpaceTests: [string, number | undefined, number | undefined][] = [ + [" ", undefined, undefined], ["foo", 0, 3], [" foo ", 1, 4], ]; @@ -21,8 +21,8 @@ suite("TextLine", function () { const editor = await openNewEditor(text); const line = new VscodeTextLineImpl(editor.document.lineAt(0)); - assert.equal(line.rangeTrimmed.start.character, trimmedStart); - assert.equal(line.rangeTrimmed.end.character, trimmedEnd); + assert.equal(line.rangeTrimmed?.start?.character, trimmedStart); + assert.equal(line.rangeTrimmed?.end?.character, trimmedEnd); }); }); }); From f50a066793dd9f08e4b2aac9a1e90b0f610437f1 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 1 Aug 2024 21:32:06 +0200 Subject: [PATCH 08/12] Neovim --- .../test/InMemoryTextDocumentLineAt.test.ts | 16 +++++++++---- .../src/ide/neovim/NeovimTextLineImpl.ts | 23 +++++++------------ 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/common/src/ide/inMemoryTextDocument/test/InMemoryTextDocumentLineAt.test.ts b/packages/common/src/ide/inMemoryTextDocument/test/InMemoryTextDocumentLineAt.test.ts index 7ee72d948e..f77e1b9cee 100644 --- a/packages/common/src/ide/inMemoryTextDocument/test/InMemoryTextDocumentLineAt.test.ts +++ b/packages/common/src/ide/inMemoryTextDocument/test/InMemoryTextDocumentLineAt.test.ts @@ -34,13 +34,14 @@ suite("InMemoryTextDocument.lineAt", () => { } test("basic", () => { - const document = createTestDocument("hello \n world\n "); + const document = createTestDocument("hello \n world\n \n"); - assert.equal(document.lineCount, 3); + assert.equal(document.lineCount, 4); const line0 = document.lineAt(0); const line1 = document.lineAt(1); const line2 = document.lineAt(2); + const line3 = document.lineAt(3); const lineUnderflow = document.lineAt(-1); const lineOverflow = document.lineAt(100); @@ -62,10 +63,17 @@ suite("InMemoryTextDocument.lineAt", () => { assert.equal(line2.text, " "); assert.equal(line2.isEmptyOrWhitespace, true); assert.equal(line2.range.toString(), "2:0-2:2"); - assert.equal(line2.rangeIncludingLineBreak.toString(), "2:0-2:2"); + assert.equal(line2.rangeIncludingLineBreak.toString(), "2:0-3:0"); assert.equal(line2.rangeTrimmed, undefined); + assert.equal(line3.lineNumber, 3); + assert.equal(line3.text, ""); + assert.equal(line3.isEmptyOrWhitespace, true); + assert.equal(line3.range.toString(), "3:0-3:0"); + assert.equal(line3.rangeIncludingLineBreak.toString(), "3:0-3:0"); + assert.equal(line3.rangeTrimmed, undefined); + assert.equal(lineUnderflow.lineNumber, 0); - assert.equal(lineOverflow.lineNumber, 2); + assert.equal(lineOverflow.lineNumber, 3); }); }); diff --git a/packages/neovim-common/src/ide/neovim/NeovimTextLineImpl.ts b/packages/neovim-common/src/ide/neovim/NeovimTextLineImpl.ts index 1ffea9121a..a236196629 100644 --- a/packages/neovim-common/src/ide/neovim/NeovimTextLineImpl.ts +++ b/packages/neovim-common/src/ide/neovim/NeovimTextLineImpl.ts @@ -45,23 +45,16 @@ export default class NeovimTextLineImpl implements TextLine { return new Range(this._lineNumber, 0, this._lineNumber + 1, 0); } - get firstNonWhitespaceCharacterIndex(): number { - const index = /^(\s*)/.exec(this._text)![1].length; - // console.debug( - // `NeovimTextLineImpl.firstNonWhitespaceCharacterIndex=${index}`, - // ); - return index; - } - - get lastNonWhitespaceCharacterIndex(): number { - const index = this.text.trimEnd().length; - // console.debug( - // `NeovimTextLineImpl.lastNonWhitespaceCharacterIndex index=${index}`, - // ); - return index; + get rangeTrimmed(): Range | undefined { + const startIndex = /^(\s*)/.exec(this._text)![1].length; + if (startIndex === this._text.length) { + return undefined; + } + const endIndex = this.text.trimEnd().length; + return new Range(this._lineNumber, startIndex, this._lineNumber, endIndex); } get isEmptyOrWhitespace(): boolean { - return this.firstNonWhitespaceCharacterIndex === this._text.length; + return this.rangeTrimmed == null; } } From 4f0ed83179dd761074caabe5be01927ff9fe11ae Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 1 Aug 2024 21:36:21 +0200 Subject: [PATCH 09/12] Update comments --- .../src/processTargets/targets/DestinationImpl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts index 3a60db1de6..46e271ed2d 100644 --- a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts +++ b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts @@ -126,7 +126,7 @@ export class DestinationImpl implements Destination { ? line.rangeTrimmed?.start ?? line.range.start : line.rangeTrimmed?.end ?? line.range.end; - // Use the full line width including indentation and trailing whitespaces + // Use the full line with including indentation and trailing whitespaces if (insertionPosition.isEqual(trimmedPosition)) { return this.isBefore ? line.range.start : line.range.end; } From c34f4af0e73d8f9687f6a758cbec385dbf62b4c4 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Fri, 2 Aug 2024 09:47:35 +0200 Subject: [PATCH 10/12] Update comment --- packages/common/src/types/TextLine.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/common/src/types/TextLine.ts b/packages/common/src/types/TextLine.ts index 9ff4d7f8b3..056c2f1286 100644 --- a/packages/common/src/types/TextLine.ts +++ b/packages/common/src/types/TextLine.ts @@ -29,7 +29,7 @@ export interface TextLine { /** * The trimmed range, which excludes leading and trailing whitespace (`/\s/`). - * **Note** that if a line is all whitespace this is undefined. + * **Note** that if a line is all whitespace this is undefined so the trimmed range can never be empty. */ readonly rangeTrimmed: Range | undefined; From 42d05480fc7486129891639ab7ea21c4cfd132c0 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sat, 3 Aug 2024 08:57:22 +0200 Subject: [PATCH 11/12] Update packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts Co-authored-by: Phil Cohen --- .../src/processTargets/targets/DestinationImpl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts index 46e271ed2d..793aa9433e 100644 --- a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts +++ b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts @@ -126,7 +126,7 @@ export class DestinationImpl implements Destination { ? line.rangeTrimmed?.start ?? line.range.start : line.rangeTrimmed?.end ?? line.range.end; - // Use the full line with including indentation and trailing whitespaces + // Use the full line, including indentation and trailing whitespaces if (insertionPosition.isEqual(trimmedPosition)) { return this.isBefore ? line.range.start : line.range.end; } From 0caf11410e828dc9dbe6a8563d4a7c908ec054e5 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sat, 3 Aug 2024 08:57:52 +0200 Subject: [PATCH 12/12] Update packages/common/src/types/TextLine.ts Co-authored-by: Phil Cohen --- packages/common/src/types/TextLine.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/common/src/types/TextLine.ts b/packages/common/src/types/TextLine.ts index 056c2f1286..949b871914 100644 --- a/packages/common/src/types/TextLine.ts +++ b/packages/common/src/types/TextLine.ts @@ -29,7 +29,7 @@ export interface TextLine { /** * The trimmed range, which excludes leading and trailing whitespace (`/\s/`). - * **Note** that if a line is all whitespace this is undefined so the trimmed range can never be empty. + * **Note:** if a line is all whitespace, the `rangeTrimmed` is undefined. Thus, a trimmed range can never be empty. */ readonly rangeTrimmed: Range | undefined;