@@ -17,11 +17,9 @@ const packageName = 'codecane'
1717 * Terminal escape sequences to reset terminal state after the child process exits.
1818 * When the binary is SIGKILL'd, it can't clean up its own terminal state.
1919 * The wrapper (this process) survives and must reset these modes.
20- *
21- * Keep in sync with TERMINAL_RESET_SEQUENCES in cli/src/utils/renderer-cleanup.ts
2220 */
23- const TERMINAL_RESET_SEQUENCES =
24- '\x1b[?1049l' + // Exit alternate screen buffer
21+ const EXIT_ALTERNATE_SCREEN_SEQUENCE = '\x1b[?1049l'
22+ const SAFE_TERMINAL_RESET_SEQUENCES =
2523 '\x1b[?1000l' + // Disable X10 mouse mode
2624 '\x1b[?1002l' + // Disable button event mouse mode
2725 '\x1b[?1003l' + // Disable any-event mouse mode (all motion)
@@ -30,7 +28,12 @@ const TERMINAL_RESET_SEQUENCES =
3028 '\x1b[?2004l' + // Disable bracketed paste mode
3129 '\x1b[?25h' // Show cursor
3230
33- function resetTerminal ( ) {
31+ const FULL_TERMINAL_RESET_SEQUENCES =
32+ EXIT_ALTERNATE_SCREEN_SEQUENCE + SAFE_TERMINAL_RESET_SEQUENCES
33+
34+ function resetTerminal ( options = { } ) {
35+ const { exitAlternateScreen = false } = options
36+
3437 try {
3538 if ( process . stdin . isTTY && process . stdin . setRawMode ) {
3639 process . stdin . setRawMode ( false )
@@ -40,13 +43,37 @@ function resetTerminal() {
4043 }
4144 try {
4245 if ( process . stdout . isTTY ) {
43- process . stdout . write ( TERMINAL_RESET_SEQUENCES )
46+ // Exiting the alternate screen is only safe after an interactive child.
47+ // Plain CLI paths like --help never enter it, and ?1049l can erase output.
48+ process . stdout . write (
49+ exitAlternateScreen
50+ ? FULL_TERMINAL_RESET_SEQUENCES
51+ : SAFE_TERMINAL_RESET_SEQUENCES ,
52+ )
4453 }
4554 } catch {
4655 // stdout may be closed
4756 }
4857}
4958
59+ function getUnsignedExitCode ( code ) {
60+ return code != null && code < 0 ? ( code >>> 0 ) : code
61+ }
62+
63+ function isWindowsNativeCrashCode ( code ) {
64+ const unsignedCode = getUnsignedExitCode ( code )
65+ return (
66+ process . platform === 'win32' &&
67+ ( unsignedCode === 0xC000001D ||
68+ unsignedCode === 0xC0000005 ||
69+ unsignedCode === 0xC0000409 )
70+ )
71+ }
72+
73+ function shouldExitAlternateScreen ( code , signal ) {
74+ return Boolean ( signal ) || isWindowsNativeCrashCode ( code )
75+ }
76+
5077function createConfig ( packageName ) {
5178 const homeDir = os . homedir ( )
5279 const configDir = path . join ( homeDir , '.config' , 'manicode' )
@@ -465,7 +492,7 @@ async function checkForUpdates(runningProcess, exitListener) {
465492 } , 5000 )
466493 } )
467494
468- resetTerminal ( )
495+ resetTerminal ( { exitAlternateScreen : true } )
469496 console . log ( `Update available: ${ currentVersion } → ${ latestVersion } ` )
470497
471498 await downloadBinary ( latestVersion )
@@ -476,7 +503,9 @@ async function checkForUpdates(runningProcess, exitListener) {
476503 } )
477504
478505 newChild . on ( 'exit' , ( code , signal ) => {
479- resetTerminal ( )
506+ resetTerminal ( {
507+ exitAlternateScreen : shouldExitAlternateScreen ( code , signal ) ,
508+ } )
480509 printCrashDiagnostics ( code , signal )
481510 process . exit ( signal ? 1 : ( code || 0 ) )
482511 } )
@@ -495,7 +524,7 @@ async function checkForUpdates(runningProcess, exitListener) {
495524
496525function printCrashDiagnostics ( code , signal ) {
497526 // Windows NTSTATUS codes (unsigned DWORD)
498- const unsignedCode = code != null && code < 0 ? ( code >>> 0 ) : code
527+ const unsignedCode = getUnsignedExitCode ( code )
499528 const isIllegalInstruction =
500529 signal === 'SIGILL' ||
501530 ( process . platform === 'win32' && unsignedCode === 0xC000001D )
@@ -557,7 +586,9 @@ async function main() {
557586 } )
558587
559588 const exitListener = ( code , signal ) => {
560- resetTerminal ( )
589+ resetTerminal ( {
590+ exitAlternateScreen : shouldExitAlternateScreen ( code , signal ) ,
591+ } )
561592 printCrashDiagnostics ( code , signal )
562593 process . exit ( signal ? 1 : ( code || 0 ) )
563594 }
0 commit comments