| 1 | package persistentshell |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "fmt" |
| 6 | "strings" |
| 7 | ) |
| 8 | |
| 9 | type commandStage struct { |
| 10 | script string |
| 11 | ack string |
| 12 | } |
| 13 | |
| 14 | // Leave framing headroom below Darwin's 1024-byte canonical input limit. |
| 15 | const commandWordLimit = 512 |
| 16 | |
| 17 | // Keep each input below the canonical PTY limit and wait until it is consumed. |
| 18 | // Newlines alone do not fence Readline's terminal-mode transitions, which can |
| 19 | // discard queued input. Staging never runs user code; only the final eval does. |
| 20 | func commandStages(command, start, end string) []commandStage { |
| 21 | if len(ansiCQuote(command)) <= commandWordLimit { |
| 22 | return []commandStage{{script: posixCommandScript(command, start, end)}} |
| 23 | } |
| 24 | variable := "__rx_command_" + newMarkerID() |
| 25 | var stages []commandStage |
| 26 | for offset := 0; offset < len(command); offset += commandWordLimit / 4 { |
| 27 | assignment := variable + "=" |
| 28 | if offset > 0 { |
| 29 | assignment += `"$` + variable + `"` |
| 30 | } |
| 31 | ack := fmt.Sprintf("%s_INPUT_%d", start, offset) |
| 32 | script := assignment + ansiCQuote(command[offset:min(offset+commandWordLimit/4, len(command))]) + |
| 33 | "; printf '%s\\n' " + posixQuote(ack) + "\n" |
| 34 | stages = append(stages, commandStage{script: script, ack: ack}) |
| 35 | } |
| 36 | return append(stages, commandStage{script: commandWordScript(`"$`+variable+`"`, start, end, "; unset "+variable)}) |
| 37 | } |
| 38 | |
| 39 | func (s *session) writeCommand(ctx context.Context, command, start, end string) error { |
| 40 | for _, stage := range commandStages(command, start, end) { |
| 41 | if err := ctx.Err(); err != nil { |
| 42 | return err |
| 43 | } |
| 44 | if err := s.writeScript(stage.script); err != nil { |
| 45 | return err |
| 46 | } |
| 47 | if stage.ack == "" { |
| 48 | return nil |
| 49 | } |
| 50 | var pending string |
| 51 | if err := s.pump(ctx, func(text string) bool { |
| 52 | pending += text |
| 53 | if strings.Contains(pending, stage.ack+"\n") { |
| 54 | return true |
| 55 | } |
| 56 | if len(pending) > markerOverlap { |
| 57 | pending = pending[len(pending)-markerOverlap:] |
| 58 | } |
| 59 | return false |
| 60 | }); err != nil { |
| 61 | return err |
| 62 | } |
| 63 | } |
| 64 | return nil |
| 65 | } |
| 66 |