diff --git a/src/editor-launch.js b/src/editor-launch.js index 81fa6d5..d298295 100644 --- a/src/editor-launch.js +++ b/src/editor-launch.js @@ -290,9 +290,16 @@ const REFUSAL_REASONS = { // editor that has since been uninstalled, is refused rather than resolved // through an environment that is not there. // -// The spawn options are the ones main.js holds everywhere else it starts a -// child: no shell, hidden on Windows, detached with no stdio so the editor -// outlives the app and cannot block on a pipe nobody reads. +// The spawn options: never a shell, and detached with no stdio so the editor +// outlives the app and cannot block on a pipe nobody reads. `detached` is +// unconditional here, unlike the runners in main.js which set it only off +// Windows — they are killed as a process group, and this child is released +// rather than ever signalled. +// +// No `windowsHide`. That flag fills STARTUPINFO with "start hidden", and a GUI +// application that honors it — VS Code does — launches with its window +// invisible while the spawn still reports success (#181). It is for children +// the app runs to collect their output; here the window is the point. async function openSiteInEditor(sitePath, editorPath, { sites, platform, @@ -321,8 +328,7 @@ async function openSiteInEditor(sitePath, editorPath, { child = spawn(command, args, { detached: true, stdio: 'ignore', - shell: false, - windowsHide: true + shell: false }); } catch (e) { // A synchronous throw is the argument-shape failure only. The one that diff --git a/test/editor-launch.test.cjs b/test/editor-launch.test.cjs index adee77a..6a370b2 100644 --- a/test/editor-launch.test.cjs +++ b/test/editor-launch.test.cjs @@ -390,7 +390,7 @@ test('a refusal is logged on one bounded line', async () => { assert.ok(description.length <= 121); }); -test('the child is detached, shell-free and hidden, and its handle released', async () => { +test('the child is detached and shell-free, and its handle released', async () => { const { calls, options } = launchDeps(); await openSiteInEditor(SITE, EDITOR, options); @@ -398,12 +398,34 @@ test('the child is detached, shell-free and hidden, and its handle released', as assert.deepEqual(calls[0].options, { detached: true, stdio: 'ignore', - shell: false, - windowsHide: true + shell: false }); assert.equal(calls[0].unrefed, true); }); +// `windowsHide` fills the new process's STARTUPINFO with "start hidden", and a +// GUI application that honors it — VS Code does — launches with its window +// invisible while the spawn still reports success, so the app said ok and the +// contributor saw nothing (#181). The flag exists to suppress console windows +// the app's own tooling creates; the editor's window is the point of the click. +test('the editor is not asked to start hidden on Windows', async () => { + const winEditor = 'C:\\Users\\dev\\AppData\\Local\\Programs\\Microsoft VS Code\\Code.exe'; + const winSite = 'C:\\Users\\dev\\Desktop\\wp'; + const fs = fakeFs({ [winEditor]: 'exe' }); + const { calls, spawn } = recordingSpawn(); + + const result = await openSiteInEditor(winSite, winEditor, { + sites: [winSite], + platform: 'win32', + statPath: fs.statPath, + spawn + }); + + assert.deepEqual(result, { ok: true }); + assert.ok(!('windowsHide' in calls[0].options), + 'a GUI editor must not inherit a hidden first window'); +}); + test('a spawn that throws is reported, not raised at the window', async () => { const { options } = launchDeps({ spawn: () => { throw new TypeError('args must be an array'); }