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.
|
// Put stdin in raw mode if it is a terminal.
|
||||||
|
var oldState *term.State
|
||||||
if term.IsTerminal(int(os.Stdin.Fd())) {
|
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 {
|
if err != nil {
|
||||||
log.Printf("make raw: %v", err)
|
log.Printf("make raw: %v", err)
|
||||||
return 1
|
return 1
|
||||||
@@ -469,19 +470,111 @@ func run(name string, args []string) int {
|
|||||||
defer term.Restore(int(os.Stdin.Fd()), oldState)
|
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 {
|
if err := filterLoop(os.Stdout, ptmx, engine.ring); err != nil {
|
||||||
log.Printf("filter: %v", err)
|
log.Printf("filter: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.Wait(); err != nil {
|
return <-exitCodeCh
|
||||||
var exitErr *exec.ExitError
|
|
||||||
if errors.As(err, &exitErr) {
|
|
||||||
return exitErr.ExitCode()
|
|
||||||
}
|
|
||||||
log.Printf("wait: %v", err)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user