Changed something
This commit is contained in:
115
main.go
115
main.go
@@ -460,8 +460,9 @@ func run(name string, args []string) int {
|
||||
}()
|
||||
|
||||
// Put stdin in raw mode if it is a terminal.
|
||||
var oldState *term.State
|
||||
if term.IsTerminal(int(os.Stdin.Fd())) {
|
||||
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
|
||||
oldState, err = term.MakeRaw(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
log.Printf("make raw: %v", err)
|
||||
return 1
|
||||
@@ -469,19 +470,111 @@ func run(name string, args []string) int {
|
||||
defer term.Restore(int(os.Stdin.Fd()), oldState)
|
||||
}
|
||||
|
||||
go io.Copy(ptmx, os.Stdin)
|
||||
childPid := c.Process.Pid
|
||||
termState := oldState // updated on each MakeRaw; only touched by suspend goroutine
|
||||
|
||||
var suspendMu sync.Mutex // ensures only one suspend/resume cycle runs at a time
|
||||
|
||||
// Copy stdin → PTY, intercepting Ctrl+Z (0x1A) for job control.
|
||||
go func() {
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
n, err := os.Stdin.Read(buf)
|
||||
if n > 0 {
|
||||
i := 0
|
||||
for j := 0; j < n; j++ {
|
||||
if buf[j] == 0x1A { // Ctrl+Z
|
||||
if j > i {
|
||||
ptmx.Write(buf[i:j])
|
||||
}
|
||||
// Run the full suspend/resume cycle in one goroutine so
|
||||
// there is no cross-goroutine state to synchronize.
|
||||
go func() {
|
||||
if !suspendMu.TryLock() {
|
||||
return // already suspending
|
||||
}
|
||||
defer suspendMu.Unlock()
|
||||
|
||||
// Stop the child immediately without forwarding 0x1A.
|
||||
// Forwarding 0x1A causes copilot to write partial teardown
|
||||
// sequences that glitch the display before we can stop it.
|
||||
// We send to both the direct PID and the process group so
|
||||
// that subprocesses are also stopped regardless of whether
|
||||
// pty.Start created a new process group.
|
||||
// SIGSTOP is synchronous — the child is stopped before
|
||||
// Kill() returns, so no sleep is needed.
|
||||
syscall.Kill(childPid, syscall.SIGSTOP)
|
||||
syscall.Kill(-childPid, syscall.SIGSTOP)
|
||||
|
||||
// Exit alternate screen and show cursor in case copilot had
|
||||
// them active, so the shell prompt appears on a clean screen.
|
||||
os.Stdout.Write([]byte("\x1b[?1049l\x1b[?25h\r\n"))
|
||||
// Hand the terminal back to the shell.
|
||||
if termState != nil {
|
||||
term.Restore(int(os.Stdin.Fd()), termState)
|
||||
}
|
||||
// Stop ourselves; the shell takes over.
|
||||
syscall.Kill(os.Getpid(), syscall.SIGSTOP)
|
||||
|
||||
// ── Execution resumes here after "fg" ──────────
|
||||
// Re-apply raw mode.
|
||||
if termState != nil {
|
||||
if s, err := term.MakeRaw(int(os.Stdin.Fd())); err == nil {
|
||||
termState = s
|
||||
}
|
||||
}
|
||||
// Continue the child (direct PID + process group).
|
||||
syscall.Kill(childPid, syscall.SIGCONT)
|
||||
syscall.Kill(-childPid, syscall.SIGCONT)
|
||||
// Sync terminal size and ask copilot to redraw its UI.
|
||||
pty.InheritSize(os.Stdin, ptmx)
|
||||
syscall.Kill(childPid, syscall.SIGWINCH)
|
||||
}()
|
||||
i = j + 1
|
||||
}
|
||||
}
|
||||
if i < n {
|
||||
ptmx.Write(buf[i:n])
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// exitCodeCh receives the child's exit code when it terminates.
|
||||
exitCodeCh := make(chan int, 1)
|
||||
|
||||
// waitChild monitors the child for exit/signal; stop events are handled by
|
||||
// the suspend goroutine above so we just loop past them here.
|
||||
go func() {
|
||||
for {
|
||||
var ws syscall.WaitStatus
|
||||
_, err := syscall.Wait4(childPid, &ws, syscall.WUNTRACED, nil)
|
||||
if err != nil {
|
||||
if err == syscall.EINTR {
|
||||
continue
|
||||
}
|
||||
exitCodeCh <- 1
|
||||
return
|
||||
}
|
||||
|
||||
if ws.Exited() {
|
||||
exitCodeCh <- ws.ExitStatus()
|
||||
return
|
||||
}
|
||||
if ws.Signaled() {
|
||||
exitCodeCh <- 128 + int(ws.Signal())
|
||||
return
|
||||
}
|
||||
// ws.Stopped() — suspend goroutine handles this; loop for next event.
|
||||
}
|
||||
}()
|
||||
|
||||
if err := filterLoop(os.Stdout, ptmx, engine.ring); err != nil {
|
||||
log.Printf("filter: %v", err)
|
||||
}
|
||||
|
||||
if err := c.Wait(); err != nil {
|
||||
var exitErr *exec.ExitError
|
||||
if errors.As(err, &exitErr) {
|
||||
return exitErr.ExitCode()
|
||||
}
|
||||
log.Printf("wait: %v", err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
return <-exitCodeCh
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user