Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
languageId: plaintext
command:
version: 7
spokenForm: decrement file
action:
name: decrement
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: document}
usePrePhraseSnapshot: false
initialState:
documentContents: |-
10_000
10_000.0
10_000.0_10
010_000.0
0_0.0_1
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}
marks: {}
finalState:
documentContents: |-
9_999
9_999.0
9_999.0_10
009_999.0
-0_0.0_9
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}
thatMark:
- type: UntypedTarget
contentRange:
start: {line: 0, character: 0}
end: {line: 0, character: 5}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 1, character: 0}
end: {line: 1, character: 7}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 2, character: 0}
end: {line: 2, character: 10}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 3, character: 0}
end: {line: 3, character: 9}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 4, character: 0}
end: {line: 4, character: 8}
isReversed: false
hasExplicitRange: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
languageId: plaintext
command:
version: 7
spokenForm: increment file
action:
name: increment
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: document}
usePrePhraseSnapshot: false
initialState:
documentContents: |-
10_000
10_000.0
10_000.0_10
010_000.0
0_0.0_1
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}
marks: {}
finalState:
documentContents: |-
10_001
10_001.0
10_001.0_10
010_001.0
0_0.1_1
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}
thatMark:
- type: UntypedTarget
contentRange:
start: {line: 0, character: 0}
end: {line: 0, character: 6}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 1, character: 0}
end: {line: 1, character: 8}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 2, character: 0}
end: {line: 2, character: 11}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 3, character: 0}
end: {line: 3, character: 9}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 4, character: 0}
end: {line: 4, character: 7}
isReversed: false
hasExplicitRange: true
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,14 @@ initialState:
active: {line: 0, character: 8}
marks: {}
finalState:
documentContents: "2_02_002"
documentContents: "1_01_002"
selections:
- anchor: {line: 0, character: 8}
active: {line: 0, character: 8}
thatMark:
- type: UntypedTarget
contentRange:
start: {line: 0, character: 0}
end: {line: 0, character: 1}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 0, character: 2}
end: {line: 0, character: 4}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 0, character: 5}
end: {line: 0, character: 8}
isReversed: false
hasExplicitRange: true
97 changes: 74 additions & 23 deletions packages/cursorless-engine/src/actions/incrementDecrement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { runForEachEditor } from "../util/targetUtils";
import type { Actions } from "./Actions";
import type { ActionReturnValue } from "./actions.types";

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

class IncrementDecrement {
constructor(
Expand Down Expand Up @@ -95,51 +95,102 @@ function updateNumber(isIncrement: boolean, text: string): string {
}

function updateInteger(isIncrement: boolean, text: string): string {
const original = parseInt(text);
const textWithoutUnderscores = text.replaceAll("_", "");
const original = parseInt(textWithoutUnderscores);
const diff = 1;
const value = original + (isIncrement ? diff : -diff);
return formatNumber(value, text);
return formatNumber(value, text, textWithoutUnderscores);
}

function updateFloat(isIncrement: boolean, text: string): string {
const original = parseFloat(text);
const textWithoutUnderscores = text.replaceAll("_", "");
const original = parseFloat(textWithoutUnderscores);
const isPercentage = Math.abs(original) <= 1.0;
const diff = isPercentage ? 0.1 : 1;
const updated = original + (isIncrement ? diff : -diff);
// Remove precision problems that would add a lot of extra digits
const value = parseFloat(updated.toPrecision(15)) / 1;
const decimalPlaces = text.split(".")[1]?.length;
return formatNumber(value, text, decimalPlaces);
const decimalPlaces = textWithoutUnderscores.split(".")[1]?.length;
return formatNumber(value, text, textWithoutUnderscores, decimalPlaces);
}

function formatNumber(
value: number,
text: string,
textWithoutUnderscores: string,
decimalPlaces?: number,
): string {
const result = (() => {
if (hasLeadingZeros(textWithoutUnderscores)) {
return formatNumberWithLeadingZeros(
value,
textWithoutUnderscores,
decimalPlaces,
);
}

return decimalPlaces != null
? value.toFixed(decimalPlaces)
: value.toString();
})();

return formatNumberWithUnderscores(text, textWithoutUnderscores, result);
}

function formatNumberWithLeadingZeros(
value: number,
text: string,
decimalPlaces: number | undefined,
): string {
const sign = value < 0 ? "-" : "";
const absValue = Math.abs(value);
const integerPartLength = getIntegerPartLength(text);
const integerPart = Math.floor(absValue)
.toString()
.padStart(integerPartLength, "0");

if (decimalPlaces != null) {
const fractionPart = (absValue - Math.floor(absValue))
.toFixed(decimalPlaces)
// Remove "0."
.slice(2);
return `${sign}${integerPart}.${fractionPart}`;
}

if (hasLeadingZeros(text)) {
const integerPartLength = getIntegerPartLength(text);
const integerPart = Math.floor(absValue)
.toString()
.padStart(integerPartLength, "0");

if (decimalPlaces != null) {
const fractionPart = (absValue - Math.floor(absValue))
.toFixed(decimalPlaces)
// Remove "0."
.slice(2);
return `${sign}${integerPart}.${fractionPart}`;
}
return `${sign}${integerPart}`;
}

// Reinsert underscores at original positions
function formatNumberWithUnderscores(
original: string,
originalWithoutUnderscores: string,
updated: string,
): string {
const underscoreMatches = Array.from(original.matchAll(/_/g));

if (underscoreMatches.length === 0) {
return updated;
}

return `${sign}${integerPart}`;
let resultWithUnderscores = updated;
const signOffset =
(updated[0] === "-" ? 1 : 0) - (original[0] === "-" ? 1 : 0);
const intOffset =
getIntegerPartLength(updated) -
getIntegerPartLength(originalWithoutUnderscores);
const offset = signOffset + intOffset;

for (const match of underscoreMatches) {
const index = match.index + offset;
if (index < resultWithUnderscores.length) {
resultWithUnderscores =
resultWithUnderscores.slice(0, index) +
"_" +
resultWithUnderscores.slice(index);
}
}

return decimalPlaces != null
? value.toFixed(decimalPlaces)
: value.toString();
return resultWithUnderscores;
}

function hasLeadingZeros(text: string): boolean {
Expand Down
Loading