my_wins 1.5.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/conemu.md +40 -0
- package/lib/index.js +39 -13
- package/package.json +1 -1
- package/readme.md +2 -0
- package/test/run-test.js +16 -6
package/docs/conemu.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# ConEmu integration
|
|
2
|
+
|
|
3
|
+
`my_wins` can open console commands in `ConEmu` tabs instead of separate system `cmd.exe` windows.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
- Top-level `conemu` in `my_wins.json` points to `ConEmu.exe` or `ConEmu64.exe`.
|
|
8
|
+
- When `conemu` is configured, console wins use `ConEmu` by default.
|
|
9
|
+
- Per-win `conemu: true|false` overrides the default.
|
|
10
|
+
- New tabs are created through `ConEmu` shell/macros.
|
|
11
|
+
- After tab creation, `my_wins` explicitly renames the visible ConEmu tab caption to the win `title`.
|
|
12
|
+
- The internal console title may still change while commands run, but the ConEmu tab caption should stay stable.
|
|
13
|
+
|
|
14
|
+
## Example
|
|
15
|
+
|
|
16
|
+
```json5
|
|
17
|
+
{
|
|
18
|
+
conemu: "D:\\ProgsReady\\ConEmu\\ConEmu64.exe",
|
|
19
|
+
wins: {
|
|
20
|
+
api: {
|
|
21
|
+
title: "API",
|
|
22
|
+
cmd: "npm run dev"
|
|
23
|
+
},
|
|
24
|
+
worker: {
|
|
25
|
+
title: "Worker",
|
|
26
|
+
cmd: "npm run worker"
|
|
27
|
+
},
|
|
28
|
+
plain_cmd: {
|
|
29
|
+
conemu: false,
|
|
30
|
+
cmd: "echo this one uses a normal cmd window"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Current limitations
|
|
37
|
+
|
|
38
|
+
- `ConEmu` support is Windows-only.
|
|
39
|
+
- Background color for `ConEmu` tabs is applied after the tab is activated.
|
|
40
|
+
- Automated integration testing for `ConEmu` is skipped if `ConEmu` is not installed at the expected test path.
|
package/lib/index.js
CHANGED
|
@@ -61,6 +61,13 @@ function toPowerShellSingleQuotedString(value) {
|
|
|
61
61
|
return `'${String(value).replace(/'/g, "''")}'`;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
function buildConEmuCreateTabCommand(win) {
|
|
65
|
+
const titleCommand = `title ${win.matchTitle}`;
|
|
66
|
+
return {
|
|
67
|
+
titleCommand,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
64
71
|
function getConEmuInfo(settings) {
|
|
65
72
|
if (!settings.conemu) return null;
|
|
66
73
|
|
|
@@ -94,24 +101,25 @@ function shouldUseConEmuForWin(settings, win, conemuInfo) {
|
|
|
94
101
|
return true;
|
|
95
102
|
}
|
|
96
103
|
|
|
97
|
-
async function execConEmuMacro(conemu, macro) {
|
|
104
|
+
async function execConEmuMacro(conemu, macro, tabIndex) {
|
|
98
105
|
if (!conemu || !conemu.guiPid) throw new Error("ConEmu GUI PID is not initialized");
|
|
106
|
+
const guiMacroTarget = tabIndex ? `-GuiMacro:${conemu.guiPid}:T${tabIndex}` : `-GuiMacro:${conemu.guiPid}`;
|
|
99
107
|
const command = [
|
|
100
108
|
"&",
|
|
101
109
|
toPowerShellSingleQuotedString(conemu.macroPath),
|
|
102
|
-
toPowerShellSingleQuotedString(
|
|
110
|
+
toPowerShellSingleQuotedString(guiMacroTarget),
|
|
103
111
|
toPowerShellSingleQuotedString(macro),
|
|
104
112
|
].join(" ");
|
|
105
113
|
const result = await execFileAsync("powershell", ["-NoProfile", "-Command", command]);
|
|
106
114
|
return (result.stdout || "").trim();
|
|
107
115
|
}
|
|
108
116
|
|
|
109
|
-
async function execConEmuCommand(conemu, command, commandArgs) {
|
|
117
|
+
async function execConEmuCommand(conemu, command, commandArgs, tabIndex) {
|
|
110
118
|
const serializedArgs = (commandArgs || []).map((arg)=>{
|
|
111
119
|
if (typeof arg === "number") return String(arg);
|
|
112
120
|
return `"${String(arg).replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
113
121
|
});
|
|
114
|
-
return execConEmuMacro(conemu, `${command}(${serializedArgs.join(",")})
|
|
122
|
+
return execConEmuMacro(conemu, `${command}(${serializedArgs.join(",")})`, tabIndex);
|
|
115
123
|
}
|
|
116
124
|
|
|
117
125
|
async function findConEmuGuiPidByActiveTitle(title) {
|
|
@@ -178,7 +186,7 @@ async function waitForConEmuGuiPid(title, timeoutMs, beforePids) {
|
|
|
178
186
|
}
|
|
179
187
|
|
|
180
188
|
async function launchConEmuTab(conemu, win, isFirstTab) {
|
|
181
|
-
const titleCommand =
|
|
189
|
+
const { titleCommand } = buildConEmuCreateTabCommand(win);
|
|
182
190
|
if (isFirstTab) {
|
|
183
191
|
const beforePids = await listConEmuGuiPids();
|
|
184
192
|
const beforePid = beforePids.length ? beforePids[beforePids.length - 1] : null;
|
|
@@ -191,14 +199,18 @@ async function launchConEmuTab(conemu, win, isFirstTab) {
|
|
|
191
199
|
child.unref();
|
|
192
200
|
conemu.guiPid = await waitForConEmuGuiPid(null, 10000, beforePids);
|
|
193
201
|
const afterTabs = await listConEmuTabsByPid(conemu.guiPid, conemu.macroPath);
|
|
194
|
-
|
|
202
|
+
const tabIndex = afterTabs.length > beforeTabs.length ? afterTabs.length : afterTabs.length;
|
|
203
|
+
await renameConEmuTab(conemu, tabIndex, win.tabTitle);
|
|
204
|
+
return tabIndex;
|
|
195
205
|
}
|
|
196
206
|
|
|
197
207
|
const beforeTabs = await listConEmuTabsByPid(conemu.guiPid, conemu.macroPath);
|
|
198
208
|
await execConEmuCommand(conemu, "Shell", ["new_console", "cmd.exe", `/k ${titleCommand}`]);
|
|
199
209
|
await awaitDelay(300);
|
|
200
210
|
const afterTabs = await listConEmuTabsByPid(conemu.guiPid, conemu.macroPath);
|
|
201
|
-
|
|
211
|
+
const tabIndex = afterTabs.length > beforeTabs.length ? afterTabs.length : afterTabs.length;
|
|
212
|
+
await renameConEmuTab(conemu, tabIndex, win.tabTitle);
|
|
213
|
+
return tabIndex;
|
|
202
214
|
}
|
|
203
215
|
|
|
204
216
|
async function activateConEmuTab(conemu, tabIndex) {
|
|
@@ -206,22 +218,33 @@ async function activateConEmuTab(conemu, tabIndex) {
|
|
|
206
218
|
await awaitDelay(150);
|
|
207
219
|
}
|
|
208
220
|
|
|
221
|
+
async function renameConEmuTab(conemu, tabIndex, tabTitle) {
|
|
222
|
+
if (!tabTitle) return;
|
|
223
|
+
await execConEmuCommand(conemu, "Rename", [1, tabTitle], tabIndex);
|
|
224
|
+
await awaitDelay(150);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function stabilizeConEmuTabTitle(conemu, win) {
|
|
228
|
+
if (!conemu || !win || !win.conemuTabIndex || !win.tabTitle) return;
|
|
229
|
+
await awaitDelay(500);
|
|
230
|
+
await renameConEmuTab(conemu, win.conemuTabIndex, win.tabTitle);
|
|
231
|
+
await awaitDelay(1000);
|
|
232
|
+
await renameConEmuTab(conemu, win.conemuTabIndex, win.tabTitle);
|
|
233
|
+
}
|
|
234
|
+
|
|
209
235
|
async function setConEmuBackgroundColor(conemu, tabIndex, backgroundcolor) {
|
|
210
236
|
if (!backgroundcolor) return;
|
|
211
|
-
await
|
|
212
|
-
await execConEmuCommand(conemu, "SetOption", ["bgImage", backgroundcolor]);
|
|
237
|
+
await execConEmuCommand(conemu, "SetOption", ["bgImage", backgroundcolor], tabIndex);
|
|
213
238
|
await awaitDelay(150);
|
|
214
239
|
}
|
|
215
240
|
|
|
216
241
|
async function printInConEmuTab(conemu, tabIndex, cmd) {
|
|
217
|
-
await
|
|
218
|
-
await execConEmuCommand(conemu, "Print", [cmd]);
|
|
242
|
+
await execConEmuCommand(conemu, "Print", [cmd], tabIndex);
|
|
219
243
|
await awaitDelay(200);
|
|
220
244
|
}
|
|
221
245
|
|
|
222
246
|
async function pressEnterInConEmuTab(conemu, tabIndex) {
|
|
223
|
-
await
|
|
224
|
-
await execConEmuCommand(conemu, "Keys", ["Enter"]);
|
|
247
|
+
await execConEmuCommand(conemu, "Keys", ["Enter"], tabIndex);
|
|
225
248
|
await awaitDelay(200);
|
|
226
249
|
}
|
|
227
250
|
|
|
@@ -436,6 +459,9 @@ async function runOnce(baseSettings, filterNames) {
|
|
|
436
459
|
await awaitDelay(delay);
|
|
437
460
|
}
|
|
438
461
|
await w.pressEnter();
|
|
462
|
+
if (shouldUseConEmuForWin(settings, w, conemu)) {
|
|
463
|
+
await stabilizeConEmuTabTitle(conemu, w);
|
|
464
|
+
}
|
|
439
465
|
}
|
|
440
466
|
}
|
|
441
467
|
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -5,6 +5,7 @@ Automates starting your console windows for tsc, babel, tests, verdaccio, etc...
|
|
|
5
5
|
my_wins opens **cmd** promts for you, position them nicely and types-in your commands into them.
|
|
6
6
|
|
|
7
7
|
If you set `conemu`, my_wins will open tabs in that ConEmu instance instead of separate system `cmd` windows.
|
|
8
|
+
Project docs are gradually being collected in [`docs`](./docs).
|
|
8
9
|
|
|
9
10
|
## Why?
|
|
10
11
|
|
|
@@ -125,6 +126,7 @@ It's recommended to gitignore "my_wins_personal.json".
|
|
|
125
126
|
**x,y,height,width** *number* - is first cmd window's position and size
|
|
126
127
|
|
|
127
128
|
**conemu** *string* - optional full path to `ConEmu.exe` or `ConEmu64.exe`. When present, console commands use ConEmu by default and are sent through ConEmu GuiMacro instead of the window key sender.
|
|
129
|
+
ConEmu tabs keep their visible title from the win `title` field even if commands later change the internal console title.
|
|
128
130
|
|
|
129
131
|
**y** gets incremented by **height** for each next window
|
|
130
132
|
|
package/test/run-test.js
CHANGED
|
@@ -8,6 +8,7 @@ const tempDir = path.join(repoRoot, "temp");
|
|
|
8
8
|
const conemuTestFile = path.join(tempDir, "my_wins_conemu_test.txt");
|
|
9
9
|
const conemuPath = "D:\\ProgsReady\\ConEmu\\ConEmu64.exe";
|
|
10
10
|
const conemuTabTitle = "My Wins Visible Title";
|
|
11
|
+
const conemuRuntimeTitle = "Changed By Command";
|
|
11
12
|
const conemuBackgroundColor = "#123456";
|
|
12
13
|
|
|
13
14
|
function sleep(ms) {
|
|
@@ -60,7 +61,7 @@ async function run() {
|
|
|
60
61
|
test_conemu: {
|
|
61
62
|
title: conemuTabTitle,
|
|
62
63
|
backgroundcolor: conemuBackgroundColor,
|
|
63
|
-
cmd: `echo my_wins conemu test> ${conemuTestFile} && ping -n
|
|
64
|
+
cmd: `title ${conemuRuntimeTitle} && echo my_wins conemu test> ${conemuTestFile} && ping -n 12 127.0.0.1 > nul`,
|
|
64
65
|
},
|
|
65
66
|
},
|
|
66
67
|
},
|
|
@@ -89,14 +90,23 @@ async function run() {
|
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
const macroPath = path.join(path.dirname(conemuPath), "ConEmu", "ConEmuC64.exe");
|
|
92
|
-
const
|
|
93
|
+
const guiPidsRaw = execFileSync("powershell", [
|
|
93
94
|
"-NoProfile",
|
|
94
95
|
"-Command",
|
|
95
|
-
"(Get-Process ConEmu64,ConEmu -ErrorAction SilentlyContinue | Where-Object { $_.MainWindowHandle -ne 0 -and $_.MainWindowTitle -notlike 'About ConEmu*' } | Sort-Object Id -Descending | Select-Object -
|
|
96
|
+
"(Get-Process ConEmu64,ConEmu -ErrorAction SilentlyContinue | Where-Object { $_.MainWindowHandle -ne 0 -and $_.MainWindowTitle -notlike 'About ConEmu*' } | Sort-Object Id -Descending | Select-Object -ExpandProperty Id)",
|
|
96
97
|
], { encoding: "utf8" }).trim();
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
const guiPids = guiPidsRaw
|
|
99
|
+
.split(/\r?\n/)
|
|
100
|
+
.map((line) => line.trim())
|
|
101
|
+
.filter(Boolean);
|
|
102
|
+
const tabsByPid = guiPids.map((guiPid) => ({
|
|
103
|
+
guiPid,
|
|
104
|
+
tabs: execFileSync(macroPath, [`-GuiMacro:${guiPid}`, "Tab(12)"], { encoding: "utf8" }),
|
|
105
|
+
}));
|
|
106
|
+
const matchingWindow = tabsByPid.find((entry) => entry.tabs.includes(conemuTabTitle));
|
|
107
|
+
if (!matchingWindow) {
|
|
108
|
+
const allTabs = tabsByPid.map((entry) => `[${entry.guiPid}]\n${entry.tabs}`).join("\n---\n");
|
|
109
|
+
throw new Error(`Expected some ConEmu tabs list to include visible title '${conemuTabTitle}', got: ${allTabs}`);
|
|
100
110
|
}
|
|
101
111
|
}
|
|
102
112
|
|