Skip to content

Commit 40e9eb4

Browse files
Fix multiline selection handling for number functions
Fixes #74 - Ensures sequence, increment, and decrement commands properly handle multiline selections as single units rather than processing line-by-line. This allows consistent max-length padding across entire multiline selections. Key changes: - Added numberFunctionNames handling to process multiline selections correctly - Updated sequence function to use replaceAll for global replacement - Added comprehensive tests for multiline selection scenarios - Fixed code formatting and trailing commas 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 871962c commit 40e9eb4

File tree

4 files changed

+94
-17
lines changed

4 files changed

+94
-17
lines changed

content.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,8 @@ prefix007 suffix008 middle009
6565
test01 value100 prefix007 99
6666
02 100 4
6767
010 100 123
68+
a001 b1
69+
c1 d01 e1
70+
f1 g1
71+
multi1 line2
72+
test3 data4

src/commands/index.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,12 @@ export const stringFunction = async (
8080
): Promise<{ replacedSelections: string[] } | undefined> => {
8181
const startTime = Date.now();
8282
const editor = vscode.window.activeTextEditor;
83-
83+
8484
if (!editor) {
8585
telemetryService.logError({
8686
errorType: "no_active_editor",
8787
source: `stringFunction.${executionSource}`,
88-
message: "No active text editor found"
88+
message: "No active text editor found",
8989
});
9090
return;
9191
}
@@ -107,7 +107,7 @@ export const stringFunction = async (
107107
executionSource,
108108
success: false,
109109
errorType: "user_cancelled_input",
110-
selectionCount: editor.selections.length
110+
selectionCount: editor.selections.length,
111111
});
112112
return;
113113
}
@@ -119,7 +119,7 @@ export const stringFunction = async (
119119
executionSource,
120120
success: false,
121121
errorType: "invalid_number_input",
122-
selectionCount: editor.selections.length
122+
selectionCount: editor.selections.length,
123123
});
124124
return;
125125
}
@@ -128,7 +128,9 @@ export const stringFunction = async (
128128
stringFunc = (str: string) =>
129129
(commandNameFunctionMap[commandName] as Function)(str, multiselectData);
130130
} else {
131-
stringFunc = commandNameFunctionMap[commandName] as (str: string) => string;
131+
stringFunc = commandNameFunctionMap[commandName] as (
132+
str: string
133+
) => string;
132134
}
133135

134136
if (
@@ -145,6 +147,13 @@ export const stringFunction = async (
145147
);
146148
selectionMap = result.selectionMap;
147149
replacedSelections = result.replacedSelections;
150+
} else if (numberFunctionNames.includes(commandName)) {
151+
for (const [index, selection] of editor.selections.entries()) {
152+
const text = editor.document.getText(selection);
153+
const replaced = stringFunc(text);
154+
replacedSelections.push(replaced);
155+
selectionMap[index] = { selection, replaced };
156+
}
148157
} else {
149158
for (const [index, selection] of editor.selections.entries()) {
150159
const text = editor.document.getText(selection);
@@ -167,7 +176,10 @@ export const stringFunction = async (
167176
commandName === "duplicateAndIncrement" ||
168177
commandName === "duplicateAndDecrement"
169178
) {
170-
editor.selections = updateSelectionsAfterDuplicate(editor, selectionMap);
179+
editor.selections = updateSelectionsAfterDuplicate(
180+
editor,
181+
selectionMap
182+
);
171183
}
172184

173185
context.globalState.update("lastAction", commandName);
@@ -180,7 +192,7 @@ export const stringFunction = async (
180192
executionSource,
181193
success: true,
182194
executionTimeMs: executionTime,
183-
selectionCount: editor.selections.length
195+
selectionCount: editor.selections.length,
184196
});
185197

186198
return await Promise.resolve({ replacedSelections });
@@ -193,13 +205,13 @@ export const stringFunction = async (
193205
success: false,
194206
errorType: "execution_error",
195207
executionTimeMs: executionTime,
196-
selectionCount: editor.selections.length
208+
selectionCount: editor.selections.length,
197209
});
198210

199211
telemetryService.logError({
200212
errorType: "command_execution_error",
201213
source: `stringFunction.${commandName}`,
202-
message: error.message || String(error)
214+
message: error.message || String(error),
203215
});
204216

205217
throw error; // Re-throw to maintain existing error handling

src/commands/sequence.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,30 @@ export const sequence: CommandFunction = (
1414
}
1515
return 0;
1616
}));
17-
18-
return str.replace(/-?\d+/g, (n) => {
17+
18+
return str.replaceAll(/-?\d+/g, (n) => {
1919
const isFirst = typeof multiselectData.offset !== "number";
2020
multiselectData.offset = isFirst
2121
? Number(n)
2222
: (multiselectData.offset || 0) + 1;
23-
23+
2424
const sequenceValue = multiselectData.offset;
25-
25+
2626
// Use max length for consistent padding if any number in the string has leading zeros
2727
if (maxLength > 0) {
2828
const isNegative = sequenceValue < 0;
2929
const sequenceStr = String(sequenceValue);
30-
30+
3131
if (isNegative) {
3232
// For negative numbers, pad after the minus sign
3333
const absStr = sequenceStr.substring(1);
34-
return '-' + absStr.padStart(maxLength - 1, '0');
34+
return "-" + absStr.padStart(maxLength - 1, "0");
3535
} else {
3636
// For positive numbers, pad the entire string
37-
return sequenceStr.padStart(maxLength, '0');
37+
return sequenceStr.padStart(maxLength, "0");
3838
}
3939
}
40-
40+
4141
return String(sequenceValue);
4242
});
4343
};

src/test/extension.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,26 @@ suite("Extension Test Suite", () => {
422422
);
423423
});
424424

425+
test("increment handles multiline selections as single unit", async () => {
426+
const [output] = await getTextForSelectionsByCommand(
427+
"string-manipulation.increment",
428+
[
429+
{
430+
start: { line: 70, character: 0 },
431+
end: { line: 72, character: 10 },
432+
},
433+
]
434+
);
435+
436+
// Test that multiline selections are processed as a single unit
437+
// Input: "multi1 line2\ntest3 data4"
438+
// Expected: all numbers incremented by 1
439+
assert.strictEqual(
440+
output /* multi1 line2\ntest3 data4 */,
441+
"multi2 line3test4 data5"
442+
);
443+
});
444+
425445
test("decrement decreases all numbers in the selection by 1", async () => {
426446
const [output1, output2, output3] = await getTextForSelectionsByCommand(
427447
"string-manipulation.decrement",
@@ -455,6 +475,26 @@ suite("Extension Test Suite", () => {
455475
);
456476
});
457477

478+
test("decrement handles multiline selections as single unit", async () => {
479+
const [output] = await getTextForSelectionsByCommand(
480+
"string-manipulation.decrement",
481+
[
482+
{
483+
start: { line: 70, character: 0 },
484+
end: { line: 72, character: 10 },
485+
},
486+
]
487+
);
488+
489+
// Test that multiline selections are processed as a single unit
490+
// Input: "multi1 line2\ntest3 data4"
491+
// Expected: all numbers decremented by 1
492+
assert.strictEqual(
493+
output /* multi1 line2\ntest3 data4 */,
494+
"multi0 line1test2 data3"
495+
);
496+
});
497+
458498
test("incrementFloat increases all floats in the selection by 1", async () => {
459499
const [output] = await getTextForSelectionsByCommand(
460500
"string-manipulation.incrementFloat",
@@ -892,6 +932,26 @@ suite("Extension Test Suite", () => {
892932
);
893933
});
894934

935+
test("sequence handles multiline selections with consistent max-length padding", async () => {
936+
const [output] = await getTextForSelectionsByCommand(
937+
"string-manipulation.sequence",
938+
[
939+
{
940+
start: { line: 67, character: 0 },
941+
end: { line: 69, character: 6 },
942+
},
943+
]
944+
);
945+
946+
// Test the example from GitHub issue #74
947+
// Input: "a001 b1\nc1 d01 e1\nf1 g1"
948+
// Expected: all numbers should be padded to 3 digits (max length from "001")
949+
assert.strictEqual(
950+
output /* a001 b1\nc1 d01 e1\nf1 g1 */,
951+
"a001 b002c003 d004 e005f006 g007"
952+
);
953+
});
954+
895955
test("utf8ToChar converts Unicode escapes to characters", async () => {
896956
const [output] = await getTextForSelectionsByCommand(
897957
"string-manipulation.utf8ToChar",

0 commit comments

Comments
 (0)