Skip to content

Commit 1786571

Browse files
Support underscores in increment action (#3144)
1 parent fd7456a commit 1786571

4 files changed

Lines changed: 203 additions & 36 deletions

File tree

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
languageId: plaintext
2+
command:
3+
version: 7
4+
spokenForm: decrement file
5+
action:
6+
name: decrement
7+
target:
8+
type: primitive
9+
modifiers:
10+
- type: containingScope
11+
scopeType: {type: document}
12+
usePrePhraseSnapshot: false
13+
initialState:
14+
documentContents: |-
15+
10_000
16+
10_000.0
17+
10_000.0_10
18+
010_000.0
19+
0_0.0_1
20+
selections:
21+
- anchor: {line: 0, character: 0}
22+
active: {line: 0, character: 0}
23+
marks: {}
24+
finalState:
25+
documentContents: |-
26+
9_999
27+
9_999.0
28+
9_999.0_10
29+
009_999.0
30+
-0_0.0_9
31+
selections:
32+
- anchor: {line: 0, character: 0}
33+
active: {line: 0, character: 0}
34+
thatMark:
35+
- type: UntypedTarget
36+
contentRange:
37+
start: {line: 0, character: 0}
38+
end: {line: 0, character: 5}
39+
isReversed: false
40+
hasExplicitRange: true
41+
- type: UntypedTarget
42+
contentRange:
43+
start: {line: 1, character: 0}
44+
end: {line: 1, character: 7}
45+
isReversed: false
46+
hasExplicitRange: true
47+
- type: UntypedTarget
48+
contentRange:
49+
start: {line: 2, character: 0}
50+
end: {line: 2, character: 10}
51+
isReversed: false
52+
hasExplicitRange: true
53+
- type: UntypedTarget
54+
contentRange:
55+
start: {line: 3, character: 0}
56+
end: {line: 3, character: 9}
57+
isReversed: false
58+
hasExplicitRange: true
59+
- type: UntypedTarget
60+
contentRange:
61+
start: {line: 4, character: 0}
62+
end: {line: 4, character: 8}
63+
isReversed: false
64+
hasExplicitRange: true
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
languageId: plaintext
2+
command:
3+
version: 7
4+
spokenForm: increment file
5+
action:
6+
name: increment
7+
target:
8+
type: primitive
9+
modifiers:
10+
- type: containingScope
11+
scopeType: {type: document}
12+
usePrePhraseSnapshot: false
13+
initialState:
14+
documentContents: |-
15+
10_000
16+
10_000.0
17+
10_000.0_10
18+
010_000.0
19+
0_0.0_1
20+
selections:
21+
- anchor: {line: 0, character: 0}
22+
active: {line: 0, character: 0}
23+
marks: {}
24+
finalState:
25+
documentContents: |-
26+
10_001
27+
10_001.0
28+
10_001.0_10
29+
010_001.0
30+
0_0.1_1
31+
selections:
32+
- anchor: {line: 0, character: 0}
33+
active: {line: 0, character: 0}
34+
thatMark:
35+
- type: UntypedTarget
36+
contentRange:
37+
start: {line: 0, character: 0}
38+
end: {line: 0, character: 6}
39+
isReversed: false
40+
hasExplicitRange: true
41+
- type: UntypedTarget
42+
contentRange:
43+
start: {line: 1, character: 0}
44+
end: {line: 1, character: 8}
45+
isReversed: false
46+
hasExplicitRange: true
47+
- type: UntypedTarget
48+
contentRange:
49+
start: {line: 2, character: 0}
50+
end: {line: 2, character: 11}
51+
isReversed: false
52+
hasExplicitRange: true
53+
- type: UntypedTarget
54+
contentRange:
55+
start: {line: 3, character: 0}
56+
end: {line: 3, character: 9}
57+
isReversed: false
58+
hasExplicitRange: true
59+
- type: UntypedTarget
60+
contentRange:
61+
start: {line: 4, character: 0}
62+
end: {line: 4, character: 7}
63+
isReversed: false
64+
hasExplicitRange: true

data/fixtures/recorded/actions/incrementDecrement/incrementThis.yml

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,14 @@ initialState:
1515
active: {line: 0, character: 8}
1616
marks: {}
1717
finalState:
18-
documentContents: "2_02_002"
18+
documentContents: "1_01_002"
1919
selections:
2020
- anchor: {line: 0, character: 8}
2121
active: {line: 0, character: 8}
2222
thatMark:
2323
- type: UntypedTarget
2424
contentRange:
2525
start: {line: 0, character: 0}
26-
end: {line: 0, character: 1}
27-
isReversed: false
28-
hasExplicitRange: true
29-
- type: UntypedTarget
30-
contentRange:
31-
start: {line: 0, character: 2}
32-
end: {line: 0, character: 4}
33-
isReversed: false
34-
hasExplicitRange: true
35-
- type: UntypedTarget
36-
contentRange:
37-
start: {line: 0, character: 5}
3826
end: {line: 0, character: 8}
3927
isReversed: false
4028
hasExplicitRange: true

packages/cursorless-engine/src/actions/incrementDecrement.ts

Lines changed: 74 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { runForEachEditor } from "../util/targetUtils";
77
import type { Actions } from "./Actions";
88
import type { ActionReturnValue } from "./actions.types";
99

10-
const REGEX = /-?\d+(\.\d+)?/g;
10+
const REGEX = /-?[\d_]+(\.[\d_]+)?/g;
1111

1212
class IncrementDecrement {
1313
constructor(
@@ -95,51 +95,102 @@ function updateNumber(isIncrement: boolean, text: string): string {
9595
}
9696

9797
function updateInteger(isIncrement: boolean, text: string): string {
98-
const original = parseInt(text);
98+
const textWithoutUnderscores = text.replaceAll("_", "");
99+
const original = parseInt(textWithoutUnderscores);
99100
const diff = 1;
100101
const value = original + (isIncrement ? diff : -diff);
101-
return formatNumber(value, text);
102+
return formatNumber(value, text, textWithoutUnderscores);
102103
}
103104

104105
function updateFloat(isIncrement: boolean, text: string): string {
105-
const original = parseFloat(text);
106+
const textWithoutUnderscores = text.replaceAll("_", "");
107+
const original = parseFloat(textWithoutUnderscores);
106108
const isPercentage = Math.abs(original) <= 1.0;
107109
const diff = isPercentage ? 0.1 : 1;
108110
const updated = original + (isIncrement ? diff : -diff);
109111
// Remove precision problems that would add a lot of extra digits
110112
const value = parseFloat(updated.toPrecision(15)) / 1;
111-
const decimalPlaces = text.split(".")[1]?.length;
112-
return formatNumber(value, text, decimalPlaces);
113+
const decimalPlaces = textWithoutUnderscores.split(".")[1]?.length;
114+
return formatNumber(value, text, textWithoutUnderscores, decimalPlaces);
113115
}
114116

115117
function formatNumber(
116118
value: number,
117119
text: string,
120+
textWithoutUnderscores: string,
118121
decimalPlaces?: number,
122+
): string {
123+
const result = (() => {
124+
if (hasLeadingZeros(textWithoutUnderscores)) {
125+
return formatNumberWithLeadingZeros(
126+
value,
127+
textWithoutUnderscores,
128+
decimalPlaces,
129+
);
130+
}
131+
132+
return decimalPlaces != null
133+
? value.toFixed(decimalPlaces)
134+
: value.toString();
135+
})();
136+
137+
return formatNumberWithUnderscores(text, textWithoutUnderscores, result);
138+
}
139+
140+
function formatNumberWithLeadingZeros(
141+
value: number,
142+
text: string,
143+
decimalPlaces: number | undefined,
119144
): string {
120145
const sign = value < 0 ? "-" : "";
121146
const absValue = Math.abs(value);
147+
const integerPartLength = getIntegerPartLength(text);
148+
const integerPart = Math.floor(absValue)
149+
.toString()
150+
.padStart(integerPartLength, "0");
151+
152+
if (decimalPlaces != null) {
153+
const fractionPart = (absValue - Math.floor(absValue))
154+
.toFixed(decimalPlaces)
155+
// Remove "0."
156+
.slice(2);
157+
return `${sign}${integerPart}.${fractionPart}`;
158+
}
122159

123-
if (hasLeadingZeros(text)) {
124-
const integerPartLength = getIntegerPartLength(text);
125-
const integerPart = Math.floor(absValue)
126-
.toString()
127-
.padStart(integerPartLength, "0");
128-
129-
if (decimalPlaces != null) {
130-
const fractionPart = (absValue - Math.floor(absValue))
131-
.toFixed(decimalPlaces)
132-
// Remove "0."
133-
.slice(2);
134-
return `${sign}${integerPart}.${fractionPart}`;
135-
}
160+
return `${sign}${integerPart}`;
161+
}
162+
163+
// Reinsert underscores at original positions
164+
function formatNumberWithUnderscores(
165+
original: string,
166+
originalWithoutUnderscores: string,
167+
updated: string,
168+
): string {
169+
const underscoreMatches = Array.from(original.matchAll(/_/g));
170+
171+
if (underscoreMatches.length === 0) {
172+
return updated;
173+
}
136174

137-
return `${sign}${integerPart}`;
175+
let resultWithUnderscores = updated;
176+
const signOffset =
177+
(updated[0] === "-" ? 1 : 0) - (original[0] === "-" ? 1 : 0);
178+
const intOffset =
179+
getIntegerPartLength(updated) -
180+
getIntegerPartLength(originalWithoutUnderscores);
181+
const offset = signOffset + intOffset;
182+
183+
for (const match of underscoreMatches) {
184+
const index = match.index + offset;
185+
if (index < resultWithUnderscores.length) {
186+
resultWithUnderscores =
187+
resultWithUnderscores.slice(0, index) +
188+
"_" +
189+
resultWithUnderscores.slice(index);
190+
}
138191
}
139192

140-
return decimalPlaces != null
141-
? value.toFixed(decimalPlaces)
142-
: value.toString();
193+
return resultWithUnderscores;
143194
}
144195

145196
function hasLeadingZeros(text: string): boolean {

0 commit comments

Comments
 (0)