Skip to content

Commit 7a77402

Browse files
Properly calculate range when inserting new lines into CRLF documents (#1561)
Fixes #1542 ## Checklist - [x] I have added [tests](https://www.cursorless.org/docs/contributing/test-case-recorder/) - [ ] I have updated the [docs](https://github.com/cursorless-dev/cursorless/tree/main/docs) and [cheatsheet](https://github.com/cursorless-dev/cursorless/tree/main/cursorless-talon/src/cheatsheet) - [ ] I have not broken the cheatsheet
1 parent 4ac0276 commit 7a77402

File tree

9 files changed

+99
-3
lines changed

9 files changed

+99
-3
lines changed

packages/common/src/types/TextDocument.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { Position, Range, TextLine } from "..";
21
import type { URI } from "vscode-uri";
2+
import type { EndOfLine, Position, Range, TextLine } from "..";
33

44
export interface TextDocument {
55
/**
@@ -31,6 +31,12 @@ export interface TextDocument {
3131
*/
3232
readonly range: Range;
3333

34+
/**
35+
* The {@link EndOfLine end of line} sequence that is predominately
36+
* used in this document.
37+
*/
38+
readonly eol: EndOfLine;
39+
3440
/**
3541
* Returns a text line denoted by the line number. Note
3642
* that the returned object is *not* live and changes to the

packages/cursorless-engine/src/processTargets/targets/PositionTarget.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export default class PositionTarget extends BaseTarget<PositionTargetParameters>
125125
const startIndex = this.isBefore
126126
? baseStartOffset + this.indentationString.length
127127
: baseStartOffset +
128-
this.insertionDelimiter.length +
128+
this.getLengthOfInsertionDelimiter() +
129129
this.indentationString.length;
130130

131131
const endIndex = startIndex + text.length;
@@ -135,6 +135,19 @@ export default class PositionTarget extends BaseTarget<PositionTargetParameters>
135135
this.editor.document.positionAt(endIndex),
136136
);
137137
}
138+
139+
private getLengthOfInsertionDelimiter(): number {
140+
// Went inserting a new line with eol `CRLF` each `\n` will be converted to
141+
// `\r\n` and therefore the length is doubled.
142+
if (this.editor.document.eol === "CRLF") {
143+
// This function is only called when inserting after a range. Therefore we
144+
// only care about leading new lines in the insertion delimiter.
145+
const match = this.insertionDelimiter.match(/^\n+/);
146+
const nlCount = match?.[0].length ?? 0;
147+
return this.insertionDelimiter.length + nlCount;
148+
}
149+
return this.insertionDelimiter.length;
150+
}
138151
}
139152

140153
export function removalUnsupportedForPosition(position: string): Range {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
languageId: plaintext
2+
command:
3+
version: 5
4+
spokenForm: pour block
5+
action: {name: editNewLineAfter}
6+
targets:
7+
- type: primitive
8+
modifiers:
9+
- type: containingScope
10+
scopeType: {type: paragraph}
11+
usePrePhraseSnapshot: true
12+
initialState:
13+
documentContents: "\r\n foo"
14+
selections:
15+
- anchor: {line: 1, character: 4}
16+
active: {line: 1, character: 4}
17+
marks: {}
18+
finalState:
19+
documentContents: "\r\n foo\r\n\r\n "
20+
selections:
21+
- anchor: {line: 3, character: 4}
22+
active: {line: 3, character: 4}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
languageId: plaintext
2+
command:
3+
version: 5
4+
spokenForm: pour item
5+
action: {name: editNewLineAfter}
6+
targets:
7+
- type: primitive
8+
modifiers:
9+
- type: containingScope
10+
scopeType: {type: collectionItem}
11+
usePrePhraseSnapshot: true
12+
initialState:
13+
documentContents: "[\r\n 111,\r\n 222\r\n]"
14+
selections:
15+
- anchor: {line: 1, character: 4}
16+
active: {line: 1, character: 4}
17+
marks: {}
18+
finalState:
19+
documentContents: "[\r\n 111,\r\n ,\r\n 222\r\n]"
20+
selections:
21+
- anchor: {line: 2, character: 4}
22+
active: {line: 2, character: 4}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
languageId: python
2+
command:
3+
version: 5
4+
spokenForm: pour state
5+
action: {name: editNewLineAfter}
6+
targets:
7+
- type: primitive
8+
modifiers:
9+
- type: containingScope
10+
scopeType: {type: statement}
11+
usePrePhraseSnapshot: true
12+
initialState:
13+
documentContents: "def whatever():\r\n if True:\r\n pass"
14+
selections:
15+
- anchor: {line: 1, character: 4}
16+
active: {line: 1, character: 4}
17+
marks: {}
18+
finalState:
19+
documentContents: "def whatever():\r\n if True:\r\n pass\r\n "
20+
selections:
21+
- anchor: {line: 3, character: 4}
22+
active: {line: 3, character: 4}

packages/cursorless-vscode/src/ide/vscode/VscodeTextDocumentImpl.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
import { Position, Range, TextDocument, TextLine } from "@cursorless/common";
21
import {
2+
EndOfLine,
3+
Position,
4+
Range,
5+
TextDocument,
6+
TextLine,
7+
} from "@cursorless/common";
8+
import {
9+
fromVscodeEndOfLine,
310
fromVscodePosition,
411
toVscodePosition,
512
toVscodeRange,
@@ -30,6 +37,10 @@ export class VscodeTextDocumentImpl implements TextDocument {
3037
return new Range(0, 0, end.line, end.character);
3138
}
3239

40+
get eol(): EndOfLine {
41+
return fromVscodeEndOfLine(this.document.eol);
42+
}
43+
3344
constructor(private document: vscode.TextDocument) {}
3445

3546
public lineAt(lineOrPosition: number | Position): TextLine {

0 commit comments

Comments
 (0)