Skip to content

Commit 435adc3

Browse files
committed
feat: add newTerminal option for executing commands in fresh terminal each time
Button clicks always reused same terminal, preventing parallel execution or output comparison. Example: clicking npm test while running would rerun in same terminal, losing previous output. Added branch logic to create new terminal without Map storage when newTerminal: true. Existing behavior (reuse) preserved as default false for backward compatibility. fix #245
1 parent 93e8229 commit 435adc3

9 files changed

Lines changed: 249 additions & 28 deletions

File tree

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,11 @@
246246
"type": "boolean",
247247
"default": false,
248248
"description": "%config.buttons.insertOnly.description%"
249+
},
250+
"newTerminal": {
251+
"type": "boolean",
252+
"default": false,
253+
"description": "%config.buttons.newTerminal.description%"
249254
}
250255
},
251256
"required": [

package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"config.buttons.executeAll.description": "Execute all commands in group simultaneously",
2222
"config.buttons.group.description": "Sub-commands for grouped buttons (supports infinite nesting)",
2323
"config.buttons.insertOnly.description": "Insert command into terminal without executing (user must press Enter manually)",
24+
"config.buttons.newTerminal.description": "Always create a new terminal for each execution instead of reusing existing one",
2425
"config.buttons.name.description": "Button label (supports $(icon-name) syntax)",
2526
"config.buttons.shortcut.description": "Single key shortcut for quick access (e.g. 'q', '1', 'F1')",
2627
"config.buttons.terminalName.description": "Custom terminal name",

package.nls.ko.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"config.buttons.executeAll.description": "그룹의 모든 명령 동시 실행",
2222
"config.buttons.group.description": "그룹 버튼의 하위 명령 (무한 중첩 지원)",
2323
"config.buttons.insertOnly.description": "명령을 실행하지 않고 터미널에만 입력 (사용자가 직접 Enter 입력 필요)",
24+
"config.buttons.newTerminal.description": "기존 터미널을 재사용하지 않고 매번 새 터미널에서 실행",
2425
"config.buttons.name.description": "버튼 라벨 ($(icon-name) 구문 지원)",
2526
"config.buttons.shortcut.description": "빠른 접근을 위한 단일 키 단축키 (예: 'q', '1', 'F1')",
2627
"config.buttons.terminalName.description": "사용자 정의 터미널 이름",

src/internal/adapters.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ export type TerminalExecutor = (
1717
useVsCodeApi?: boolean,
1818
terminalName?: string,
1919
buttonName?: string,
20-
buttonRef?: object
20+
buttonRef?: object,
21+
newTerminal?: boolean
2122
) => void;
2223

2324
export type ConfigReader = {

src/internal/command-executor.spec.ts

Lines changed: 139 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,8 @@ describe("command-executor", () => {
557557
command: "echo test",
558558
id: "test-terminal-1",
559559
name: "Test Button",
560-
})
560+
}),
561+
false
561562
);
562563
});
563564

@@ -582,7 +583,8 @@ describe("command-executor", () => {
582583
id: "test-vscode-api",
583584
name: "Test Button",
584585
useVsCodeApi: true,
585-
})
586+
}),
587+
false
586588
);
587589
});
588590

@@ -607,7 +609,8 @@ describe("command-executor", () => {
607609
id: "test-terminal-name",
608610
name: "Test Button",
609611
terminalName: "Custom Terminal",
610-
})
612+
}),
613+
false
611614
);
612615
});
613616

@@ -634,7 +637,8 @@ describe("command-executor", () => {
634637
name: "Test Button",
635638
terminalName: "Custom Terminal",
636639
useVsCodeApi: true,
637-
})
640+
}),
641+
false
638642
);
639643
});
640644

@@ -663,6 +667,52 @@ describe("command-executor", () => {
663667

664668
expect(mockTerminalExecutor).not.toHaveBeenCalled();
665669
});
670+
671+
it("should call terminalExecutor with newTerminal true", () => {
672+
const mockTerminalExecutor = vi.fn();
673+
const button: ButtonConfig = {
674+
command: "echo test",
675+
id: "test-new-terminal",
676+
name: "Test Button",
677+
newTerminal: true,
678+
};
679+
680+
executeTerminalCommand(button, mockTerminalExecutor);
681+
682+
expect(mockTerminalExecutor).toHaveBeenCalledWith(
683+
"echo test",
684+
false,
685+
undefined,
686+
"Test Button",
687+
expect.objectContaining({
688+
command: "echo test",
689+
id: "test-new-terminal",
690+
name: "Test Button",
691+
newTerminal: true,
692+
}),
693+
true
694+
);
695+
});
696+
697+
it("should call terminalExecutor with newTerminal false when not specified", () => {
698+
const mockTerminalExecutor = vi.fn();
699+
const button: ButtonConfig = {
700+
command: "echo test",
701+
id: "test-default-terminal",
702+
name: "Test Button",
703+
};
704+
705+
executeTerminalCommand(button, mockTerminalExecutor);
706+
707+
expect(mockTerminalExecutor).toHaveBeenCalledWith(
708+
"echo test",
709+
false,
710+
undefined,
711+
"Test Button",
712+
expect.anything(),
713+
false
714+
);
715+
});
666716
});
667717

668718
describe("executeCommandsRecursively", () => {
@@ -697,23 +747,26 @@ describe("command-executor", () => {
697747
false,
698748
undefined,
699749
"Command 1[0]",
700-
expect.anything()
750+
expect.anything(),
751+
false
701752
);
702753
expect(mockTerminalExecutor).toHaveBeenNthCalledWith(
703754
2,
704755
"echo test2",
705756
true,
706757
undefined,
707758
"Command 2[1]",
708-
expect.anything()
759+
expect.anything(),
760+
false
709761
);
710762
expect(mockTerminalExecutor).toHaveBeenNthCalledWith(
711763
3,
712764
"echo test3",
713765
false,
714766
"Custom Terminal",
715767
"Command 3[2]",
716-
expect.anything()
768+
expect.anything(),
769+
false
717770
);
718771
});
719772

@@ -749,15 +802,17 @@ describe("command-executor", () => {
749802
false,
750803
undefined,
751804
"Group Command[0]>Child 1[0]",
752-
expect.anything()
805+
expect.anything(),
806+
false
753807
);
754808
expect(mockTerminalExecutor).toHaveBeenNthCalledWith(
755809
2,
756810
"echo child2",
757811
true,
758812
undefined,
759813
"Group Command[0]>Child 2[1]",
760-
expect.anything()
814+
expect.anything(),
815+
false
761816
);
762817
});
763818

@@ -821,15 +876,17 @@ describe("command-executor", () => {
821876
false,
822877
undefined,
823878
"Level 1 Group[0]>Level 2 Group[0]>Level 3 Command[0]",
824-
expect.anything()
879+
expect.anything(),
880+
false
825881
);
826882
expect(mockTerminalExecutor).toHaveBeenNthCalledWith(
827883
2,
828884
"echo level2",
829885
false,
830886
undefined,
831887
"Level 1 Group[0]>Level 2 Command[1]",
832-
expect.anything()
888+
expect.anything(),
889+
false
833890
);
834891
});
835892

@@ -856,7 +913,8 @@ describe("command-executor", () => {
856913
false,
857914
undefined,
858915
"Valid Command[0]",
859-
expect.anything()
916+
expect.anything(),
917+
false
860918
);
861919
});
862920

@@ -883,7 +941,8 @@ describe("command-executor", () => {
883941
false,
884942
undefined,
885943
"Valid Command[0]",
886-
expect.anything()
944+
expect.anything(),
945+
false
887946
);
888947
});
889948

@@ -944,15 +1003,17 @@ describe("command-executor", () => {
9441003
false,
9451004
undefined,
9461005
"Regular Command[0]",
947-
expect.anything()
1006+
expect.anything(),
1007+
false
9481008
);
9491009
expect(mockTerminalExecutor).toHaveBeenNthCalledWith(
9501010
2,
9511011
"echo child",
9521012
false,
9531013
undefined,
9541014
"Group with executeAll[1]>Child Command[0]",
955-
expect.anything()
1015+
expect.anything(),
1016+
false
9561017
);
9571018
});
9581019

@@ -1011,23 +1072,26 @@ describe("command-executor", () => {
10111072
false,
10121073
undefined,
10131074
"Root Group[0]>Branch 1[0]>Leaf 1[0]",
1014-
expect.anything()
1075+
expect.anything(),
1076+
false
10151077
);
10161078
expect(mockTerminalExecutor).toHaveBeenNthCalledWith(
10171079
2,
10181080
"echo leaf2",
10191081
false,
10201082
undefined,
10211083
"Root Group[0]>Branch 1[0]>Leaf 2[1]",
1022-
expect.anything()
1084+
expect.anything(),
1085+
false
10231086
);
10241087
expect(mockTerminalExecutor).toHaveBeenNthCalledWith(
10251088
3,
10261089
"echo direct",
10271090
false,
10281091
undefined,
10291092
"Root Group[0]>Direct Command[2]",
1030-
expect.anything()
1093+
expect.anything(),
1094+
false
10311095
);
10321096
});
10331097

@@ -1049,7 +1113,8 @@ describe("command-executor", () => {
10491113
false,
10501114
undefined,
10511115
"Test Command[0]",
1052-
expect.objectContaining({ insertOnly: true })
1116+
expect.objectContaining({ insertOnly: true }),
1117+
false
10531118
);
10541119
});
10551120

@@ -1086,15 +1151,64 @@ describe("command-executor", () => {
10861151
false,
10871152
undefined,
10881153
"Dev Tools[0]>Docker Shell[0]",
1089-
expect.objectContaining({ insertOnly: true })
1154+
expect.objectContaining({ insertOnly: true }),
1155+
false
10901156
);
10911157
expect(mockTerminalExecutor).toHaveBeenNthCalledWith(
10921158
2,
10931159
"git status",
10941160
false,
10951161
undefined,
10961162
"Dev Tools[0]>Git Status[1]",
1097-
expect.objectContaining({ insertOnly: false })
1163+
expect.objectContaining({ insertOnly: false }),
1164+
false
1165+
);
1166+
});
1167+
1168+
it("should pass newTerminal flag through buttonRef in nested groups", () => {
1169+
const mockTerminalExecutor = vi.fn();
1170+
const commands: ButtonConfig[] = [
1171+
{
1172+
executeAll: true,
1173+
group: [
1174+
{
1175+
command: "npm run build",
1176+
id: "build-cmd",
1177+
name: "Build",
1178+
newTerminal: true,
1179+
},
1180+
{
1181+
command: "npm run test",
1182+
id: "test-cmd",
1183+
name: "Test",
1184+
newTerminal: false,
1185+
},
1186+
],
1187+
id: "ci-group",
1188+
name: "CI Commands",
1189+
},
1190+
];
1191+
1192+
executeCommandsRecursively(commands, mockTerminalExecutor);
1193+
1194+
expect(mockTerminalExecutor).toHaveBeenCalledTimes(2);
1195+
expect(mockTerminalExecutor).toHaveBeenNthCalledWith(
1196+
1,
1197+
"npm run build",
1198+
false,
1199+
undefined,
1200+
"CI Commands[0]>Build[0]",
1201+
expect.objectContaining({ newTerminal: true }),
1202+
true
1203+
);
1204+
expect(mockTerminalExecutor).toHaveBeenNthCalledWith(
1205+
2,
1206+
"npm run test",
1207+
false,
1208+
undefined,
1209+
"CI Commands[0]>Test[1]",
1210+
expect.objectContaining({ newTerminal: false }),
1211+
false
10981212
);
10991213
});
11001214
});
@@ -1172,7 +1286,8 @@ describe("command-executor", () => {
11721286
false,
11731287
undefined,
11741288
"Test 1",
1175-
expect.objectContaining({ shortcut: "a" })
1289+
expect.objectContaining({ shortcut: "a" }),
1290+
false
11761291
);
11771292
});
11781293

@@ -1223,7 +1338,8 @@ describe("command-executor", () => {
12231338
false,
12241339
undefined,
12251340
"Test 2",
1226-
expect.objectContaining({ shortcut: "b" })
1341+
expect.objectContaining({ shortcut: "b" }),
1342+
false
12271343
);
12281344
});
12291345

src/internal/command-executor.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ export const executeTerminalCommand = (
140140
button.useVsCodeApi || false,
141141
button.terminalName,
142142
button.name,
143-
button
143+
button,
144+
button.newTerminal || false
144145
);
145146

146147
eventBus?.emit("button:executed", { button, success: true });
@@ -208,7 +209,14 @@ export const executeCommandsRecursively = (
208209
if (cmd.group && cmd.executeAll) {
209210
executeCommandsRecursively(cmd.group, terminalExecutor, buttonId, eventBus);
210211
} else if (cmd.command) {
211-
terminalExecutor(cmd.command, cmd.useVsCodeApi || false, cmd.terminalName, buttonId, cmd);
212+
terminalExecutor(
213+
cmd.command,
214+
cmd.useVsCodeApi || false,
215+
cmd.terminalName,
216+
buttonId,
217+
cmd,
218+
cmd.newTerminal || false
219+
);
212220
eventBus?.emit("button:executed", { button: cmd, success: true });
213221
}
214222
});

0 commit comments

Comments
 (0)