start spawns `snix start -c CONFIG` and returns a tea.Cmd that pumps the subprocess's output into the model's log viewport.
()
| 184 | // start spawns `snix start -c CONFIG` and returns a tea.Cmd that pumps the |
| 185 | // subprocess's output into the model's log viewport. |
| 186 | func (m runModel) start() tea.Cmd { |
| 187 | m.st.mu.Lock() |
| 188 | if m.st.running { |
| 189 | m.st.mu.Unlock() |
| 190 | return setStatus("engine already running") |
| 191 | } |
| 192 | ctx, cancel := context.WithCancel(context.Background()) |
| 193 | cmd := exec.CommandContext(ctx, m.app.opts.ExePath, "start", "-c", m.app.opts.ConfigPath) |
| 194 | stdout, _ := cmd.StdoutPipe() |
| 195 | stderr, _ := cmd.StderrPipe() |
| 196 | |
| 197 | if err := cmd.Start(); err != nil { |
| 198 | cancel() |
| 199 | m.st.mu.Unlock() |
| 200 | return setStatus("failed to start engine: %v", err) |
| 201 | } |
| 202 | m.st.cmd = cmd |
| 203 | m.st.cancel = cancel |
| 204 | m.st.running = true |
| 205 | m.st.started = time.Now() |
| 206 | ch := make(chan tea.Msg, 64) |
| 207 | m.st.msgCh = ch |
| 208 | m.st.mu.Unlock() |
| 209 | |
| 210 | // Owner goroutine: scans stdout+stderr into ch, waits for exit, emits |
| 211 | // runExitMsg, closes ch. Calling cmd.Wait() exactly once. |
| 212 | go func() { |
| 213 | var wg sync.WaitGroup |
| 214 | feed := func(r io.ReadCloser) { |
| 215 | defer wg.Done() |
| 216 | sc := bufio.NewScanner(r) |
| 217 | sc.Buffer(make([]byte, 64*1024), 1024*1024) |
| 218 | for sc.Scan() { |
| 219 | select { |
| 220 | case ch <- runLogMsg(sc.Text()): |
| 221 | case <-ctx.Done(): |
| 222 | return |
| 223 | } |
| 224 | } |
| 225 | } |
| 226 | wg.Add(2) |
| 227 | go feed(stdout) |
| 228 | go feed(stderr) |
| 229 | err := cmd.Wait() |
| 230 | wg.Wait() |
| 231 | select { |
| 232 | case ch <- runExitMsg{err: err}: |
| 233 | default: |
| 234 | } |
| 235 | close(ch) |
| 236 | }() |
| 237 | |
| 238 | return pumpCmd(ch) |
| 239 | } |
| 240 | |
| 241 | // pumpCmd blocks on ch and returns each message. When ch closes, emits a |
| 242 | // sentinel runExitMsg with a nil err so the model clears its state. |