-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinit.go
More file actions
207 lines (174 loc) · 6.14 KB
/
init.go
File metadata and controls
207 lines (174 loc) · 6.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package devtui
import (
"bufio"
"os"
"sync"
"time"
"github.com/tinywasm/fmt"
tinytime "github.com/tinywasm/time"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/tinywasm/unixid"
)
// channelMsg es un tipo especial para mensajes del canal
type channelMsg tabContent
// Print representa un mensaje de actualización
type tickMsg time.Time
// DevTUI mantiene el estado de la aplicación
type DevTUI struct {
*TuiConfig
*tuiStyle
apiKey string // stored from TuiConfig.APIKey
id *unixid.UnixID
ready bool
viewport viewport.Model
focused bool // is the app focused
TabSections []*tabSection // represent sections in the tui
activeTab int // current tab index
editModeActivated bool // global flag to edit config
shortcutRegistry *ShortcutRegistry // NEW: Global shortcut key registry
currentTime string
tabContentsChan chan tabContent
tea *tea.Program
testMode bool // private: only used in tests to enable synchronous behavior
// MCP integration: separate logger for MCP tool execution
mcpLogger func(message ...any) // injected by mcpserve via SetLog()
cursorVisible bool // for blinking effect
}
// cursorTickMsg is used for blinking the cursor
type cursorTickMsg time.Time
// cursorTick creates a command that sends a message every 500ms
func (h *DevTUI) cursorTick() tea.Cmd {
return tea.Every(500*time.Millisecond, func(t time.Time) tea.Msg {
return cursorTickMsg(t)
})
}
type TuiConfig struct {
AppName string // app name eg: "MyApp"
AppVersion string // app version eg: "v1.0.0"
ExitChan chan bool // global chan to close app eg: make(chan bool)
Debug bool // NEW: Enable debug mode for unfiltered logs
TestMode bool // Set to true for synchronous execution in tests
/*// *ColorPalette style for the TUI
// if nil it will use default style:
type ColorPalette struct {
Foreground string // eg: #F4F4F4
Background string // eg: #000000
Primary string // eg: #FF6600
Secondary string // eg: #666666
}*/
Color *ColorPalette
Logger func(messages ...any) // function to write log error
ClientMode bool // true if it should listen to SSE
ClientURL string // e.g. http://localhost:3030/logs
APIKey string // Bearer token for secured daemon; set by app, empty = open/local
}
// NewTUI creates a new DevTUI instance and initializes it.
//
// Usage Example:
//
// config := &TuiConfig{
// AppName: "MyApp",
// ExitChan: make(chan bool),
// Color: nil, // or your *ColorPalette
// Logger: func(err any) { os.Stdout.WriteString(fmt.Sprintf("%v\n", err)) },
// }
// tui := NewTUI(config)
func NewTUI(c *TuiConfig) *DevTUI {
if c.AppName == "" {
c.AppName = "DevTUI"
}
// Initialize the unique ID generator first
id, err := unixid.NewUnixID()
if err != nil {
if c.Logger != nil {
c.Logger("Critical: Error initializing unixid:", err, "- timestamp generation will use fallback")
}
// id will remain nil, but createTabContent method will handle this gracefully now
}
tui := &DevTUI{
TuiConfig: c,
apiKey: c.APIKey,
focused: true, // assume the app is focused
TabSections: []*tabSection{},
activeTab: 0, // Will be adjusted in Start() method
tabContentsChan: make(chan tabContent, 1000),
currentTime: tinytime.FormatTime(tinytime.Now()),
tuiStyle: newTuiStyle(c.Color),
id: id, // Set the ID here
shortcutRegistry: newShortcutRegistry(), // NEW: Initialize shortcut registry
testMode: c.TestMode,
}
// FIXED: Removed manual content sending to prevent duplication
// HandlerDisplay automatically shows Content() when field is selected
// No need for manual sendMessageWithHandler() call
tui.tea = tea.NewProgram(tui,
tea.WithAltScreen(), // use the full size of the terminal in its "alternate screen buffer"
// Mouse support disabled to enable terminal text selection
)
return tui
}
// Init initializes the terminal UI application.
func (h *DevTUI) Init() tea.Cmd {
// Start SSE client here (sections must be registered before replay messages arrive)
if h.ClientMode && h.ClientURL != "" {
go h.startSSEClient(h.ClientURL)
}
return tea.Batch(
h.listenToMessages(),
h.tickEverySecond(),
h.cursorTick(), // Start blinking
)
}
// Start initializes and runs the terminal UI application.
//
// It accepts optional variadic arguments of any type. If a *sync.WaitGroup
// is provided among these arguments, Start will call its Done() method
// before returning.
//
// The method runs the UI using the internal tea engine, and handles any
// errors that may occur during execution. If an error occurs, it will be
// displayed on the console and the application will wait for user input
// before exiting.
//
// Parameters:
// - args ...any: Optional arguments. Can include a *sync.WaitGroup for synchronization.
func (h *DevTUI) Start(args ...any) {
// Check if a WaitGroup was passed
for _, arg := range args {
if wg, ok := arg.(*sync.WaitGroup); ok {
defer wg.Done()
break
}
}
// Add SHORTCUTS tab last, after all user tabs are registered
// Only add if it doesn't already exist (idempotency)
shortcutsExists := false
for _, tab := range h.TabSections {
if tab.Title == "SHORTCUTS" {
shortcutsExists = true
break
}
}
if !shortcutsExists {
createShortcutsTab(h)
}
// NEW: Trigger initial content display for interactive handlers
h.checkAndTriggerInteractiveContent()
h.notifyTabActive(h.activeTab)
if _, err := h.tea.Run(); err != nil {
os.Stdout.WriteString(fmt.Sprintf("Error running DevTUI: %v\n", err))
os.Stdout.WriteString("\nPress any key to exit...\n")
bufio.NewReader(os.Stdin).ReadBytes('\n')
}
}
// SetTestMode enables or disables test mode for synchronous behavior in tests.
// This should only be used in test files to make tests deterministic.
func (h *DevTUI) SetTestMode(enabled bool) {
h.testMode = enabled
}
// isTestMode returns true if the TUI is running in test mode (synchronous execution).
// This is an internal method used by field handlers to determine execution mode.
func (h *DevTUI) isTestMode() bool {
return h.testMode
}