返回 DeepSeek-Reasonix
chat_tui_statusline.go
根目录 / internal / cli / chat_tui_statusline.go
1 package cli
2
3 import (
4 "context"
5 "encoding/json"
6 "os"
7 "strings"
8 "time"
9
10 tea "charm.land/bubbletea/v2"
11
12 "reasonix/internal/hook"
13 )
14
15 // statuslineMsg carries the latest custom status-line output (one line, ""
16 // when none/failed).
17 type statuslineMsg struct{ out string }
18
19 // runStatusline runs the user's custom status-line command off the event loop,
20 // feeding it a small JSON context on stdin and returning its first stdout line.
21 // A no-op (nil) when no command is configured. Tight timeout so a slow script
22 // can't stall the UI; failures collapse to an empty line rather than an error.
23 func (m chatTUI) runStatusline() tea.Cmd {
24 cmd := m.statuslineCmd
25 if cmd == "" {
26 return nil
27 }
28 used, window := m.ctrl.ContextSnapshot()
29 cwd, _ := os.Getwd()
30 payload, _ := json.Marshal(map[string]any{
31 "model": m.label,
32 "contextUsed": used,
33 "contextWindow": window,
34 "cwd": cwd,
35 })
36 return func() tea.Msg { return statuslineMsg{out: runStatuslineCmd(cmd, string(payload))} }
37 }
38
39 const statuslineCommandTimeout = 2 * time.Second
40
41 // runStatuslineCmd runs a status-line command with the JSON context on stdin and
42 // returns its first stdout line (status lines are a single row). A tight timeout
43 // keeps a slow script from stalling the UI; any failure collapses to "".
44 func runStatuslineCmd(cmd, stdinPayload string) string {
45 return runStatuslineCmdWithTimeout(cmd, stdinPayload, statuslineCommandTimeout)
46 }
47
48 func runStatuslineCmdWithTimeout(cmd, stdinPayload string, timeout time.Duration) string {
49 return runStatuslineCmdWith(hook.DefaultSpawner, cmd, stdinPayload, timeout)
50 }
51
52 // runStatuslineCmdWith spawns from the workspace, which the user does not
53 // author, so a bare command name resolves through PATH alone.
54 func runStatuslineCmdWith(spawn hook.Spawner, cmd, stdinPayload string, timeout time.Duration) string {
55 res := spawn(context.Background(), hook.SpawnInput{
56 Command: cmd,
57 Stdin: stdinPayload + "\n",
58 Env: map[string]string{hook.NoCwdCommandSearchEnv: "1"},
59 Timeout: timeout,
60 })
61 out := strings.TrimSpace(res.Stdout)
62 if i := strings.IndexByte(out, '\n'); i >= 0 {
63 out = strings.TrimSpace(out[:i])
64 }
65 return out
66 }
67
67 lines GO