@@ -1,676 +1,635 @@
8 "github.com/charmbracelet/bubbles/v2/key"
4 "github.com/charmbracelet/bubbles/v2/key"
9 tea "github.com/charmbracelet/bubbletea/v2"
5 tea "github.com/charmbracelet/bubbletea/v2"
10 "github.com/charmbracelet/lipgloss/v2"
6 "github.com/charmbracelet/lipgloss/v2"
11 "github.com/opencode-ai/opencode/internal/app"
7 "github.com/opencode-ai/opencode/internal/app"
12− "github.com/opencode-ai/opencode/internal/config"
13− "github.com/opencode-ai/opencode/internal/llm/agent"
14 "github.com/opencode-ai/opencode/internal/logging"
8 "github.com/opencode-ai/opencode/internal/logging"
15− "github.com/opencode-ai/opencode/internal/permission"
16 "github.com/opencode-ai/opencode/internal/pubsub"
9 "github.com/opencode-ai/opencode/internal/pubsub"
17− "github.com/opencode-ai/opencode/internal/session"
18− "github.com/opencode-ai/opencode/internal/tui/components/chat"
19 "github.com/opencode-ai/opencode/internal/tui/components/core"
10 "github.com/opencode-ai/opencode/internal/tui/components/core"
20− "github.com/opencode-ai/opencode/internal/tui/components/dialog"
11+ "github.com/opencode-ai/opencode/internal/tui/components/dialogs"
12+ "github.com/opencode-ai/opencode/internal/tui/components/dialogs/commands"
13+ "github.com/opencode-ai/opencode/internal/tui/components/dialogs/quit"
21 "github.com/opencode-ai/opencode/internal/tui/layout"
14 "github.com/opencode-ai/opencode/internal/tui/layout"
22 "github.com/opencode-ai/opencode/internal/tui/page"
15 "github.com/opencode-ai/opencode/internal/tui/page"
23 "github.com/opencode-ai/opencode/internal/tui/theme"
16 "github.com/opencode-ai/opencode/internal/tui/theme"
24 "github.com/opencode-ai/opencode/internal/tui/util"
17 "github.com/opencode-ai/opencode/internal/tui/util"
31− SwitchSession key.Binding
33− Filepicker key.Binding
35− SwitchTheme key.Binding
38−type startCompactSessionMsg struct{}
20+// type startCompactSessionMsg struct{}
45− Logs: key.NewBinding(
46− key.WithKeys("ctrl+l"),
47− key.WithHelp("ctrl+l", "logs"),
50− Quit: key.NewBinding(
51− key.WithKeys("ctrl+c"),
52− key.WithHelp("ctrl+c", "quit"),
54− Help: key.NewBinding(
55− key.WithKeys("ctrl+_"),
56− key.WithHelp("ctrl+?", "toggle help"),
59− SwitchSession: key.NewBinding(
60− key.WithKeys("ctrl+s"),
61− key.WithHelp("ctrl+s", "switch session"),
64− Commands: key.NewBinding(
65− key.WithKeys("ctrl+k"),
66− key.WithHelp("ctrl+k", "commands"),
68− Filepicker: key.NewBinding(
69− key.WithKeys("ctrl+f"),
70− key.WithHelp("ctrl+f", "select files to upload"),
72− Models: key.NewBinding(
73− key.WithKeys("ctrl+o"),
74− key.WithHelp("ctrl+o", "model selection"),
77− SwitchTheme: key.NewBinding(
78− key.WithKeys("ctrl+t"),
79− key.WithHelp("ctrl+t", "switch theme"),
83−var helpEsc = key.NewBinding(
85− key.WithHelp("?", "toggle help"),
88−var returnKey = key.NewBinding(
90− key.WithHelp("esc", "close"),
93−var logsKeyReturnKey = key.NewBinding(
94− key.WithKeys("esc", "backspace", quitKey),
95− key.WithHelp("esc/q", "go back"),
98type appModel struct {
22type appModel struct {
99− width, height int
23+ width, height int
100− currentPage page.PageID
26+ currentPage page.PageID
101− previousPage page.PageID
27+ previousPage page.PageID
102− pages map[page.PageID]util.Model
28+ pages map[page.PageID]util.Model
103− loadedPages map[page.PageID]bool
29+ loadedPages map[page.PageID]bool
104− status core.StatusCmp
31+ status core.StatusCmp
105− app *app.App
33+ app *app.App
106− selectedSession session.Session
35+ // selectedSession session.Session
108− showPermissions bool
37+ // showPermissions bool
109− permissions dialog.PermissionDialogCmp
38+ // permissions dialog.PermissionDialogCmp
111− showHelp bool
40+ // showHelp bool
112− help dialog.HelpCmp
41+ // help dialog.HelpCmp
115− quit dialog.QuitDialog
117− showSessionDialog bool
43+ // showSessionDialog bool
118− sessionDialog dialog.SessionDialog
44+ // sessionDialog dialog.SessionDialog
120− showCommandDialog bool
46+ // showCommandDialog bool
121− commandDialog dialog.CommandDialog
47+ // commandDialog dialog.CommandDialog
122− commands []dialog.Command
48+ // commands []dialog.Command
124− showModelDialog bool
50+ // showModelDialog bool
125− modelDialog dialog.ModelDialog
51+ // modelDialog dialog.ModelDialog
127− showInitDialog bool
53+ // showInitDialog bool
128− initDialog dialog.InitDialogCmp
54+ // initDialog dialog.InitDialogCmp
130− showFilepicker bool
56+ // showFilepicker bool
131− filepicker dialog.FilepickerCmp
57+ // filepicker dialog.FilepickerCmp
133− showThemeDialog bool
59+ // showThemeDialog bool
134− themeDialog dialog.ThemeDialog
60+ // themeDialog dialog.ThemeDialog
136− showMultiArgumentsDialog bool
62+ // showMultiArgumentsDialog bool
137− multiArgumentsDialog dialog.MultiArgumentsDialogCmp
63+ // multiArgumentsDialog dialog.MultiArgumentsDialogCmp
139− isCompacting bool
65+ // isCompacting bool
140− compactingMessage string
66+ // compactingMessage string
69+ dialog dialogs.DialogCmp
143func (a appModel) Init() tea.Cmd {
72func (a appModel) Init() tea.Cmd {
144 var cmds []tea.Cmd
73 var cmds []tea.Cmd
145 cmd := a.pages[a.currentPage].Init()
74 cmd := a.pages[a.currentPage].Init()
146− t := theme.CurrentTheme()
147− cmds = append(cmds, tea.SetBackgroundColor(t.Background()))
148− a.loadedPages[a.currentPage] = true
149 cmds = append(cmds, cmd)
75 cmds = append(cmds, cmd)
76+ a.loadedPages[a.currentPage] = true
150 cmd = a.status.Init()
78 cmd = a.status.Init()
151 cmds = append(cmds, cmd)
79 cmds = append(cmds, cmd)
153− cmds = append(cmds, cmd)
154− cmd = a.help.Init()
80+ // cmd = a.help.Init()
155− cmds = append(cmds, cmd)
81+ // cmds = append(cmds, cmd)
156− cmd = a.sessionDialog.Init()
82+ // cmd = a.sessionDialog.Init()
157− cmds = append(cmds, cmd)
83+ // cmds = append(cmds, cmd)
158− cmd = a.commandDialog.Init()
84+ // cmd = a.commandDialog.Init()
159− cmds = append(cmds, cmd)
85+ // cmds = append(cmds, cmd)
160− cmd = a.modelDialog.Init()
86+ // cmd = a.modelDialog.Init()
161− cmds = append(cmds, cmd)
87+ // cmds = append(cmds, cmd)
162− cmd = a.initDialog.Init()
88+ // cmd = a.initDialog.Init()
163− cmds = append(cmds, cmd)
89+ // cmds = append(cmds, cmd)
164− cmd = a.filepicker.Init()
90+ // cmd = a.filepicker.Init()
165− cmds = append(cmds, cmd)
91+ // cmds = append(cmds, cmd)
166− cmd = a.themeDialog.Init()
92+ // cmd = a.themeDialog.Init()
167− cmds = append(cmds, cmd)
93+ // cmds = append(cmds, cmd)
169 // Check if we should show the init dialog
95 // Check if we should show the init dialog
170− cmds = append(cmds, func() tea.Msg {
96+ // cmds = append(cmds, func() tea.Msg {
171− shouldShow, err := config.ShouldShowInitDialog()
97+ // shouldShow, err := config.ShouldShowInitDialog()
172− if err != nil {
98+ // if err != nil {
173− return util.InfoMsg{
99+ // return util.InfoMsg{
174− Type: util.InfoTypeError,
100+ // Type: util.InfoTypeError,
175− Msg: "Failed to check init status: " + err.Error(),
101+ // Msg: "Failed to check init status: " + err.Error(),
178− return dialog.ShowInitDialogMsg{Show: shouldShow}
104+ // return dialog.ShowInitDialogMsg{Show: shouldShow}
107+ t := theme.CurrentTheme()
108+ cmds = append(cmds, tea.SetBackgroundColor(t.Background()))
181 return tea.Batch(cmds...)
109 return tea.Batch(cmds...)
184−func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
112+func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
185 var cmds []tea.Cmd
113 var cmds []tea.Cmd
186 var cmd tea.Cmd
114 var cmd tea.Cmd
187 switch msg := msg.(type) {
116 switch msg := msg.(type) {
188 case tea.WindowSizeMsg:
117 case tea.WindowSizeMsg:
118+ return a, a.handleWindowResize(msg)
119+ // TODO: remove when refactor is done
189− msg.Height -= 1 // Make space for the status bar
120+ // msg.Height -= 1 // Make space for the status bar
190− a.width, a.height = msg.Width, msg.Height
121+ // a.width, a.height = msg.Width, msg.Height
192− s, _ := a.status.Update(msg)
123+ // s, _ := a.status.Update(msg)
193− a.status = s.(core.StatusCmp)
124+ // a.status = s.(core.StatusCmp)
194− updated, cmd := a.pages[a.currentPage].Update(msg)
125+ // updated, cmd := a.pages[a.currentPage].Update(msg)
195− a.pages[a.currentPage] = updated.(util.Model)
126+ // a.pages[a.currentPage] = updated.(util.Model)
196− cmds = append(cmds, cmd)
127+ // cmds = append(cmds, cmd)
198− prm, permCmd := a.permissions.Update(msg)
129+ // prm, permCmd := a.permissions.Update(msg)
199− a.permissions = prm.(dialog.PermissionDialogCmp)
130+ // a.permissions = prm.(dialog.PermissionDialogCmp)
200− cmds = append(cmds, permCmd)
131+ // cmds = append(cmds, permCmd)
202− help, helpCmd := a.help.Update(msg)
133+ // help, helpCmd := a.help.Update(msg)
203− a.help = help.(dialog.HelpCmp)
134+ // a.help = help.(dialog.HelpCmp)
204− cmds = append(cmds, helpCmd)
135+ // cmds = append(cmds, helpCmd)
206− session, sessionCmd := a.sessionDialog.Update(msg)
137+ // session, sessionCmd := a.sessionDialog.Update(msg)
207− a.sessionDialog = session.(dialog.SessionDialog)
138+ // a.sessionDialog = session.(dialog.SessionDialog)
208− cmds = append(cmds, sessionCmd)
139+ // cmds = append(cmds, sessionCmd)
210− command, commandCmd := a.commandDialog.Update(msg)
141+ // command, commandCmd := a.commandDialog.Update(msg)
211− a.commandDialog = command.(dialog.CommandDialog)
142+ // a.commandDialog = command.(dialog.CommandDialog)
212− cmds = append(cmds, commandCmd)
143+ // cmds = append(cmds, commandCmd)
214− filepicker, filepickerCmd := a.filepicker.Update(msg)
145+ // filepicker, filepickerCmd := a.filepicker.Update(msg)
215− a.filepicker = filepicker.(dialog.FilepickerCmp)
146+ // a.filepicker = filepicker.(dialog.FilepickerCmp)
216− cmds = append(cmds, filepickerCmd)
147+ // cmds = append(cmds, filepickerCmd)
218− a.initDialog.SetSize(msg.Width, msg.Height)
149+ // a.initDialog.SetSize(msg.Width, msg.Height)
220− if a.showMultiArgumentsDialog {
151+ // if a.showMultiArgumentsDialog {
221− a.multiArgumentsDialog.SetSize(msg.Width, msg.Height)
152+ // a.multiArgumentsDialog.SetSize(msg.Width, msg.Height)
222− args, argsCmd := a.multiArgumentsDialog.Update(msg)
153+ // args, argsCmd := a.multiArgumentsDialog.Update(msg)
223− a.multiArgumentsDialog = args.(dialog.MultiArgumentsDialogCmp)
154+ // a.multiArgumentsDialog = args.(dialog.MultiArgumentsDialogCmp)
224− cmds = append(cmds, argsCmd, a.multiArgumentsDialog.Init())
155+ // cmds = append(cmds, argsCmd, a.multiArgumentsDialog.Init())
158+ // dialog, cmd := a.dialog.Update(msg)
159+ // a.dialog = dialog.(dialogs.DialogCmp)
160+ // cmds = append(cmds, cmd)
162+ // return a, tea.Batch(cmds...)
165+ case dialogs.OpenDialogMsg, dialogs.CloseDialogMsg:
166+ u, dialogCmd := a.dialog.Update(msg)
167+ a.dialog = u.(dialogs.DialogCmp)
170+ // Page change messages
171+ case page.PageChangeMsg:
172+ return a, a.moveToPage(msg.ID)
227− return a, tea.Batch(cmds...)
228− // Status
174+ // Status Messages
229− case util.InfoMsg:
175+ case util.InfoMsg, util.ClearStatusMsg:
230 s, cmd := a.status.Update(msg)
176 s, cmd := a.status.Update(msg)
231 a.status = s.(core.StatusCmp)
177 a.status = s.(core.StatusCmp)
232 cmds = append(cmds, cmd)
178 cmds = append(cmds, cmd)
233 return a, tea.Batch(cmds...)
179 return a, tea.Batch(cmds...)
234 case pubsub.Event[logging.LogMessage]:
181 case pubsub.Event[logging.LogMessage]:
235− if msg.Payload.Persist {
182+ // Send to the status component
236− switch msg.Payload.Level {
238− s, cmd := a.status.Update(util.InfoMsg{
239− Type: util.InfoTypeError,
240− Msg: msg.Payload.Message,
241− TTL: msg.Payload.PersistTime,
243− a.status = s.(core.StatusCmp)
244− cmds = append(cmds, cmd)
246− s, cmd := a.status.Update(util.InfoMsg{
247− Type: util.InfoTypeInfo,
248− Msg: msg.Payload.Message,
249− TTL: msg.Payload.PersistTime,
251− a.status = s.(core.StatusCmp)
252− cmds = append(cmds, cmd)
255− s, cmd := a.status.Update(util.InfoMsg{
256− Type: util.InfoTypeWarn,
257− Msg: msg.Payload.Message,
258− TTL: msg.Payload.PersistTime,
261− a.status = s.(core.StatusCmp)
262− cmds = append(cmds, cmd)
264− s, cmd := a.status.Update(util.InfoMsg{
265− Type: util.InfoTypeInfo,
266− Msg: msg.Payload.Message,
267− TTL: msg.Payload.PersistTime,
269− a.status = s.(core.StatusCmp)
270− cmds = append(cmds, cmd)
273− case util.ClearStatusMsg:
274− s, _ := a.status.Update(msg)
183+ s, cmd := a.status.Update(msg)
275 a.status = s.(core.StatusCmp)
184 a.status = s.(core.StatusCmp)
185+ cmds = append(cmds, cmd)
277− // Permission
187+ // If the current page is logs, update the logs view
278− case pubsub.Event[permission.PermissionRequest]:
188+ if a.currentPage == page.LogsPage {
279− a.showPermissions = true
189+ updated, cmd := a.pages[a.currentPage].Update(msg)
280− return a, a.permissions.SetPermissions(msg.Payload)
190+ a.pages[a.currentPage] = updated.(util.Model)
281− case dialog.PermissionResponseMsg:
191+ cmds = append(cmds, cmd)
284− case dialog.PermissionAllow:
285− a.app.Permissions.Grant(msg.Permission)
286− case dialog.PermissionAllowForSession:
287− a.app.Permissions.GrantPersistant(msg.Permission)
288− case dialog.PermissionDeny:
289− a.app.Permissions.Deny(msg.Permission)
291− a.showPermissions = false
292− return a, cmd
193+ return a, tea.Batch(cmds...)
294− case page.PageChangeMsg:
295− return a, a.moveToPage(msg.ID)
297− case dialog.CloseQuitMsg:
301− case dialog.CloseSessionDialogMsg:
302− a.showSessionDialog = false
305− case dialog.CloseCommandDialogMsg:
306− a.showCommandDialog = false
309− case startCompactSessionMsg:
310− // Start compacting the current session
311− a.isCompacting = true
312− a.compactingMessage = "Starting summarization..."
196+ // case pubsub.Event[permission.PermissionRequest]:
197+ // a.showPermissions = true
198+ // return a, a.permissions.SetPermissions(msg.Payload)
199+ // case dialog.PermissionResponseMsg:
201+ // switch msg.Action {
202+ // case dialog.PermissionAllow:
203+ // a.app.Permissions.Grant(msg.Permission)
204+ // case dialog.PermissionAllowForSession:
205+ // a.app.Permissions.GrantPersistant(msg.Permission)
206+ // case dialog.PermissionDeny:
207+ // a.app.Permissions.Deny(msg.Permission)
209+ // a.showPermissions = false
213+ // case dialog.ThemeChangedMsg:
214+ // updated, cmd := a.pages[a.currentPage].Update(msg)
215+ // a.pages[a.currentPage] = updated.(util.Model)
216+ // a.showThemeDialog = false
217+ // return a, tea.Batch(cmd, util.ReportInfo("Theme changed to: "+msg.ThemeName))
219+ // case dialog.CloseSessionDialogMsg:
220+ // a.showSessionDialog = false
223+ // case dialog.CloseCommandDialogMsg:
224+ // a.showCommandDialog = false
227+ // case startCompactSessionMsg:
228+ // // Start compacting the current session
229+ // a.isCompacting = true
230+ // a.compactingMessage = "Starting summarization..."
314− if a.selectedSession.ID == "" {
232+ // if a.selectedSession.ID == "" {
315− a.isCompacting = false
233+ // a.isCompacting = false
316− return a, util.ReportWarn("No active session to summarize")
234+ // return a, util.ReportWarn("No active session to summarize")
237+ // // Start the summarization process
238+ // return a, func() tea.Msg {
239+ // ctx := context.Background()
240+ // a.app.CoderAgent.Summarize(ctx, a.selectedSession.ID)
244+ // case pubsub.Event[agent.AgentEvent]:
245+ // payload := msg.Payload
246+ // if payload.Error != nil {
247+ // a.isCompacting = false
248+ // return a, util.ReportError(payload.Error)
251+ // a.compactingMessage = payload.Progress
253+ // if payload.Done && payload.Type == agent.AgentEventTypeSummarize {
254+ // a.isCompacting = false
255+ // return a, util.ReportInfo("Session summarization complete")
256+ // } else if payload.Done && payload.Type == agent.AgentEventTypeResponse && a.selectedSession.ID != "" {
257+ // model := a.app.CoderAgent.Model()
258+ // contextWindow := model.ContextWindow
259+ // tokens := a.selectedSession.CompletionTokens + a.selectedSession.PromptTokens
260+ // if (tokens >= int64(float64(contextWindow)*0.95)) && config.Get().AutoCompact {
261+ // return a, util.CmdHandler(startCompactSessionMsg{})
264+ // // Continue listening for events
267+ // case dialog.CloseThemeDialogMsg:
268+ // a.showThemeDialog = false
271+ // case dialog.CloseModelDialogMsg:
272+ // a.showModelDialog = false
275+ // case dialog.ModelSelectedMsg:
276+ // a.showModelDialog = false
278+ // model, err := a.app.CoderAgent.Update(config.AgentCoder, msg.Model.ID)
280+ // return a, util.ReportError(err)
283+ // return a, util.ReportInfo(fmt.Sprintf("Model changed to %s", model.Name))
285+ // case dialog.ShowInitDialogMsg:
286+ // a.showInitDialog = msg.Show
289+ // case dialog.CloseInitDialogMsg:
290+ // a.showInitDialog = false
291+ // if msg.Initialize {
292+ // // Run the initialization command
293+ // for _, cmd := range a.commands {
294+ // if cmd.ID == "init" {
295+ // // Mark the project as initialized
296+ // if err := config.MarkProjectInitialized(); err != nil {
297+ // return a, util.ReportError(err)
299+ // return a, cmd.Handler(cmd)
303+ // // Mark the project as initialized without running the command
304+ // if err := config.MarkProjectInitialized(); err != nil {
305+ // return a, util.ReportError(err)
310+ // case chat.SessionSelectedMsg:
311+ // a.selectedSession = msg
312+ // a.sessionDialog.SetSelectedSession(msg.ID)
314+ // case pubsub.Event[session.Session]:
315+ // if msg.Type == pubsub.UpdatedEvent && msg.Payload.ID == a.selectedSession.ID {
316+ // a.selectedSession = msg.Payload
318+ // case dialog.SessionSelectedMsg:
319+ // a.showSessionDialog = false
320+ // if a.currentPage == page.ChatPage {
321+ // return a, util.CmdHandler(chat.SessionSelectedMsg(msg.Session))
325+ // case dialog.CommandSelectedMsg:
326+ // a.showCommandDialog = false
327+ // // Execute the command handler if available
328+ // if msg.Command.Handler != nil {
329+ // return a, msg.Command.Handler(msg.Command)
331+ // return a, util.ReportInfo("Command selected: " + msg.Command.Title)
333+ // case dialog.ShowMultiArgumentsDialogMsg:
334+ // // Show multi-arguments dialog
335+ // a.multiArgumentsDialog = dialog.NewMultiArgumentsDialogCmp(msg.CommandID, msg.Content, msg.ArgNames)
336+ // a.showMultiArgumentsDialog = true
337+ // return a, a.multiArgumentsDialog.Init()
339+ // case dialog.CloseMultiArgumentsDialogMsg:
340+ // // Close multi-arguments dialog
341+ // a.showMultiArgumentsDialog = false
343+ // // If submitted, replace all named arguments and run the command
345+ // content := msg.Content
347+ // // Replace each named argument with its value
348+ // for name, value := range msg.Args {
349+ // placeholder := "$" + name
350+ // content = strings.ReplaceAll(content, placeholder, value)
353+ // // Execute the command with arguments
354+ // return a, util.CmdHandler(dialog.CommandRunCustomMsg{
355+ // Content: content,
361+ case tea.KeyPressMsg:
362+ return a, a.handleKeyPressMsg(msg)
363+ // if a.dialog.HasDialogs() {
364+ // u, dialogCmd := a.dialog.Update(msg)
365+ // a.dialog = u.(dialogs.DialogCmp)
366+ // return a, dialogCmd
368+ // // If multi-arguments dialog is open, let it handle the key press first
369+ // if a.showMultiArgumentsDialog {
370+ // args, cmd := a.multiArgumentsDialog.Update(msg)
371+ // a.multiArgumentsDialog = args.(dialog.MultiArgumentsDialogCmp)
376+ // case key.Matches(msg, keys.Quit):
377+ // // TODO: fix this after testing
378+ // // a.showQuit = !a.showQuit
379+ // // if a.showHelp {
380+ // // a.showHelp = false
382+ // // if a.showSessionDialog {
383+ // // a.showSessionDialog = false
385+ // // if a.showCommandDialog {
386+ // // a.showCommandDialog = false
388+ // // if a.showFilepicker {
389+ // // a.showFilepicker = false
390+ // // a.filepicker.ToggleFilepicker(a.showFilepicker)
392+ // // if a.showModelDialog {
393+ // // a.showModelDialog = false
395+ // // if a.showMultiArgumentsDialog {
396+ // // a.showMultiArgumentsDialog = false
398+ // return a, util.CmdHandler(dialogs.OpenDialogMsg{
399+ // Model: quit.NewQuitDialog(),
401+ // case key.Matches(msg, keys.SwitchSession):
402+ // if a.currentPage == page.ChatPage && !a.showPermissions && !a.showCommandDialog {
403+ // // Load sessions and show the dialog
404+ // sessions, err := a.app.Sessions.List(context.Background())
406+ // return a, util.ReportError(err)
408+ // if len(sessions) == 0 {
409+ // return a, util.ReportWarn("No sessions available")
411+ // a.sessionDialog.SetSessions(sessions)
412+ // a.showSessionDialog = true
416+ // case key.Matches(msg, keys.Commands):
417+ // if a.currentPage == page.ChatPage && !a.showPermissions && !a.showSessionDialog && !a.showThemeDialog && !a.showFilepicker {
418+ // // Show commands dialog
419+ // if len(a.commands) == 0 {
420+ // return a, util.ReportWarn("No commands available")
422+ // a.commandDialog.SetCommands(a.commands)
423+ // a.showCommandDialog = true
426+ // return a, util.CmdHandler(dialogs.OpenDialogMsg{
427+ // Model: commands.NewCommandDialog(),
429+ // case key.Matches(msg, keys.Models):
430+ // if a.showModelDialog {
431+ // a.showModelDialog = false
434+ // if a.currentPage == page.ChatPage && !a.showPermissions && !a.showSessionDialog && !a.showCommandDialog {
435+ // a.showModelDialog = true
439+ // case key.Matches(msg, keys.SwitchTheme):
440+ // if !a.showPermissions && !a.showSessionDialog && !a.showCommandDialog {
441+ // // Show theme switcher dialog
442+ // a.showThemeDialog = true
443+ // // Theme list is dynamically loaded by the dialog component
444+ // return a, a.themeDialog.Init()
447+ // case key.Matches(msg, returnKey) || key.Matches(msg):
448+ // if msg.String() == quitKey {
449+ // if a.currentPage == page.LogsPage {
450+ // return a, a.moveToPage(page.ChatPage)
452+ // } else if !a.filepicker.IsCWDFocused() {
454+ // a.showHelp = !a.showHelp
457+ // if a.showInitDialog {
458+ // a.showInitDialog = false
459+ // // Mark the project as initialized without running the command
460+ // if err := config.MarkProjectInitialized(); err != nil {
461+ // return a, util.ReportError(err)
465+ // if a.showFilepicker {
466+ // a.showFilepicker = false
467+ // a.filepicker.ToggleFilepicker(a.showFilepicker)
470+ // if a.currentPage == page.LogsPage {
471+ // return a, a.moveToPage(page.ChatPage)
474+ // case key.Matches(msg, keys.Logs):
475+ // return a, a.moveToPage(page.LogsPage)
476+ // case key.Matches(msg, keys.Help):
477+ // a.showHelp = !a.showHelp
479+ // case key.Matches(msg, helpEsc):
480+ // if a.app.CoderAgent.IsBusy() {
481+ // a.showHelp = !a.showHelp
484+ // case key.Matches(msg, keys.Filepicker):
485+ // a.showFilepicker = !a.showFilepicker
486+ // a.filepicker.ToggleFilepicker(a.showFilepicker)
490+ // u, dialogCmd := a.dialog.Update(msg)
491+ // a.dialog = u.(dialogs.DialogCmp)
492+ // cmds = append(cmds, dialogCmd)
493+ // f, filepickerCmd := a.filepicker.Update(msg)
494+ // a.filepicker = f.(dialog.FilepickerCmp)
495+ // cmds = append(cmds, filepickerCmd)
498+ // if a.showFilepicker {
499+ // f, filepickerCmd := a.filepicker.Update(msg)
500+ // a.filepicker = f.(dialog.FilepickerCmp)
501+ // cmds = append(cmds, filepickerCmd)
502+ // // Only block key messages send all other messages down
503+ // if _, ok := msg.(tea.KeyPressMsg); ok {
504+ // return a, tea.Batch(cmds...)
508+ // if a.showPermissions {
509+ // d, permissionsCmd := a.permissions.Update(msg)
510+ // a.permissions = d.(dialog.PermissionDialogCmp)
511+ // cmds = append(cmds, permissionsCmd)
512+ // // Only block key messages send all other messages down
513+ // if _, ok := msg.(tea.KeyPressMsg); ok {
514+ // return a, tea.Batch(cmds...)
518+ // if a.showSessionDialog {
519+ // d, sessionCmd := a.sessionDialog.Update(msg)
520+ // a.sessionDialog = d.(dialog.SessionDialog)
521+ // cmds = append(cmds, sessionCmd)
522+ // // Only block key messages send all other messages down
523+ // if _, ok := msg.(tea.KeyPressMsg); ok {
524+ // return a, tea.Batch(cmds...)
528+ // if a.showCommandDialog {
529+ // d, commandCmd := a.commandDialog.Update(msg)
530+ // a.commandDialog = d.(dialog.CommandDialog)
531+ // cmds = append(cmds, commandCmd)
532+ // // Only block key messages send all other messages down
533+ // if _, ok := msg.(tea.KeyPressMsg); ok {
534+ // return a, tea.Batch(cmds...)
538+ // if a.showModelDialog {
539+ // d, modelCmd := a.modelDialog.Update(msg)
540+ // a.modelDialog = d.(dialog.ModelDialog)
541+ // cmds = append(cmds, modelCmd)
542+ // // Only block key messages send all other messages down
543+ // if _, ok := msg.(tea.KeyPressMsg); ok {
544+ // return a, tea.Batch(cmds...)
548+ // if a.showInitDialog {
549+ // d, initCmd := a.initDialog.Update(msg)
550+ // a.initDialog = d.(dialog.InitDialogCmp)
551+ // cmds = append(cmds, initCmd)
552+ // // Only block key messages send all other messages down
553+ // if _, ok := msg.(tea.KeyPressMsg); ok {
554+ // return a, tea.Batch(cmds...)
558+ // if a.showThemeDialog {
559+ // d, themeCmd := a.themeDialog.Update(msg)
560+ // a.themeDialog = d.(dialog.ThemeDialog)
561+ // cmds = append(cmds, themeCmd)
562+ // // Only block key messages send all other messages down
563+ // if _, ok := msg.(tea.KeyPressMsg); ok {
564+ // return a, tea.Batch(cmds...)
568+ s, _ := a.status.Update(msg)
569+ a.status = s.(core.StatusCmp)
570+ updated, cmd := a.pages[a.currentPage].Update(msg)
571+ a.pages[a.currentPage] = updated.(util.Model)
572+ cmds = append(cmds, cmd)
573+ return a, tea.Batch(cmds...)
319− // Start the summarization process
576+func (a *appModel) handleWindowResize(msg tea.WindowSizeMsg) tea.Cmd {
320− return a, func() tea.Msg {
577+ var cmds []tea.Cmd
321− ctx := context.Background()
578+ msg.Height -= 1 // Make space for the status bar
322− a.app.CoderAgent.Summarize(ctx, a.selectedSession.ID)
579+ a.width, a.height = msg.Width, msg.Height
326− case pubsub.Event[agent.AgentEvent]:
581+ // Update status bar
327− payload := msg.Payload
582+ s, cmd := a.status.Update(msg)
328− if payload.Error != nil {
583+ a.status = s.(core.StatusCmp)
329− a.isCompacting = false
584+ cmds = append(cmds, cmd)
330− return a, util.ReportError(payload.Error)
333− a.compactingMessage = payload.Progress
586+ // Update the current page
335− if payload.Done && payload.Type == agent.AgentEventTypeSummarize {
336− a.isCompacting = false
337− return a, util.ReportInfo("Session summarization complete")
338− } else if payload.Done && payload.Type == agent.AgentEventTypeResponse && a.selectedSession.ID != "" {
339− model := a.app.CoderAgent.Model()
340− contextWindow := model.ContextWindow
341− tokens := a.selectedSession.CompletionTokens + a.selectedSession.PromptTokens
342− if (tokens >= int64(float64(contextWindow)*0.95)) && config.Get().AutoCompact {
343− return a, util.CmdHandler(startCompactSessionMsg{})
346− // Continue listening for events
349− case dialog.CloseThemeDialogMsg:
350− a.showThemeDialog = false
353− case dialog.ThemeChangedMsg:
354− updated, cmd := a.pages[a.currentPage].Update(msg)
587+ updated, cmd := a.pages[a.currentPage].Update(msg)
355− a.pages[a.currentPage] = updated.(util.Model)
588+ a.pages[a.currentPage] = updated.(util.Model)
356− a.showThemeDialog = false
589+ cmds = append(cmds, cmd)
357− t := theme.CurrentTheme()
358− return a, tea.Batch(cmd, util.ReportInfo("Theme changed to: "+msg.ThemeName), tea.SetBackgroundColor(t.Background()))
360− case dialog.CloseModelDialogMsg:
361− a.showModelDialog = false
364− case dialog.ModelSelectedMsg:
365− a.showModelDialog = false
367− model, err := a.app.CoderAgent.Update(config.AgentCoder, msg.Model.ID)
369− return a, util.ReportError(err)
372− return a, util.ReportInfo(fmt.Sprintf("Model changed to %s", model.Name))
591+ // Update the dialogs
373−
592+ dialog, cmd := a.dialog.Update(msg)
374− case dialog.ShowInitDialogMsg:
593+ a.dialog = dialog.(dialogs.DialogCmp)
375− a.showInitDialog = msg.Show
594+ cmds = append(cmds, cmd)
378− case dialog.CloseInitDialogMsg:
379− a.showInitDialog = false
381− // Run the initialization command
382− for _, cmd := range a.commands {
383− if cmd.ID == "init" {
384− // Mark the project as initialized
385− if err := config.MarkProjectInitialized(); err != nil {
386− return a, util.ReportError(err)
388− return a, cmd.Handler(cmd)
392− // Mark the project as initialized without running the command
393− if err := config.MarkProjectInitialized(); err != nil {
394− return a, util.ReportError(err)
399− case chat.SessionSelectedMsg:
596+ return tea.Batch(cmds...)
400− a.selectedSession = msg
597+}
401− a.sessionDialog.SetSelectedSession(msg.ID)
403− case pubsub.Event[session.Session]:
599+func (a *appModel) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
404− if msg.Type == pubsub.UpdatedEvent && msg.Payload.ID == a.selectedSession.ID {
600+ switch {
405− a.selectedSession = msg.Payload
601+ // dialogs
406− }
602+ case key.Matches(msg, a.keyMap.Quit):
407− case dialog.SessionSelectedMsg:
408− a.showSessionDialog = false
409− if a.currentPage == page.ChatPage {
410− return a, util.CmdHandler(chat.SessionSelectedMsg(msg.Session))
603+ return util.CmdHandler(dialogs.OpenDialogMsg{
604+ Model: quit.NewQuitDialog(),
414− case dialog.CommandSelectedMsg:
607+ case key.Matches(msg, a.keyMap.Commands):
415− a.showCommandDialog = false
416− // Execute the command handler if available
417− if msg.Command.Handler != nil {
418− return a, msg.Command.Handler(msg.Command)
420− return a, util.ReportInfo("Command selected: " + msg.Command.Title)
422− case dialog.ShowMultiArgumentsDialogMsg:
423− // Show multi-arguments dialog
424− a.multiArgumentsDialog = dialog.NewMultiArgumentsDialogCmp(msg.CommandID, msg.Content, msg.ArgNames)
425− a.showMultiArgumentsDialog = true
426− return a, a.multiArgumentsDialog.Init()
428− case dialog.CloseMultiArgumentsDialogMsg:
429− // Close multi-arguments dialog
430− a.showMultiArgumentsDialog = false
432− // If submitted, replace all named arguments and run the command
434− content := msg.Content
436− // Replace each named argument with its value
437− for name, value := range msg.Args {
438− placeholder := "$" + name
439− content = strings.ReplaceAll(content, placeholder, value)
442− // Execute the command with arguments
443− return a, util.CmdHandler(dialog.CommandRunCustomMsg{
608+ return util.CmdHandler(dialogs.OpenDialogMsg{
444− Content: content,
609+ Model: commands.NewCommandDialog(),
450− case tea.KeyPressMsg:
612+ // Page navigation
451− // If multi-arguments dialog is open, let it handle the key press first
613+ case key.Matches(msg, a.keyMap.Logs):
452− if a.showMultiArgumentsDialog {
614+ return a.moveToPage(page.LogsPage)
453− args, cmd := a.multiArgumentsDialog.Update(msg)
454− a.multiArgumentsDialog = args.(dialog.MultiArgumentsDialogCmp)
459− case key.Matches(msg, keys.Quit):
460− a.showQuit = !a.showQuit
464− if a.showSessionDialog {
465− a.showSessionDialog = false
467− if a.showCommandDialog {
468− a.showCommandDialog = false
470− if a.showFilepicker {
471− a.showFilepicker = false
472− a.filepicker.ToggleFilepicker(a.showFilepicker)
474− if a.showModelDialog {
475− a.showModelDialog = false
477− if a.showMultiArgumentsDialog {
478− a.showMultiArgumentsDialog = false
481− case key.Matches(msg, keys.SwitchSession):
482− if a.currentPage == page.ChatPage && !a.showQuit && !a.showPermissions && !a.showCommandDialog {
483− // Load sessions and show the dialog
484− sessions, err := a.app.Sessions.List(context.Background())
486− return a, util.ReportError(err)
488− if len(sessions) == 0 {
489− return a, util.ReportWarn("No sessions available")
491− a.sessionDialog.SetSessions(sessions)
492− a.showSessionDialog = true
496− case key.Matches(msg, keys.Commands):
497− if a.currentPage == page.ChatPage && !a.showQuit && !a.showPermissions && !a.showSessionDialog && !a.showThemeDialog && !a.showFilepicker {
498− // Show commands dialog
499− if len(a.commands) == 0 {
500− return a, util.ReportWarn("No commands available")
502− a.commandDialog.SetCommands(a.commands)
503− a.showCommandDialog = true
507− case key.Matches(msg, keys.Models):
508− if a.showModelDialog {
509− a.showModelDialog = false
512− if a.currentPage == page.ChatPage && !a.showQuit && !a.showPermissions && !a.showSessionDialog && !a.showCommandDialog {
513− a.showModelDialog = true
517− case key.Matches(msg, keys.SwitchTheme):
518− if !a.showQuit && !a.showPermissions && !a.showSessionDialog && !a.showCommandDialog {
519− // Show theme switcher dialog
520− a.showThemeDialog = true
521− // Theme list is dynamically loaded by the dialog component
522− return a, a.themeDialog.Init()
525− case key.Matches(msg, returnKey) || key.Matches(msg):
526− if msg.String() == quitKey {
527− if a.currentPage == page.LogsPage {
528− return a, a.moveToPage(page.ChatPage)
530− } else if !a.filepicker.IsCWDFocused() {
532− a.showQuit = !a.showQuit
536− a.showHelp = !a.showHelp
539− if a.showInitDialog {
540− a.showInitDialog = false
541− // Mark the project as initialized without running the command
542− if err := config.MarkProjectInitialized(); err != nil {
543− return a, util.ReportError(err)
547− if a.showFilepicker {
548− a.showFilepicker = false
549− a.filepicker.ToggleFilepicker(a.showFilepicker)
552− if a.currentPage == page.LogsPage {
553− return a, a.moveToPage(page.ChatPage)
556− case key.Matches(msg, keys.Logs):
557− return a, a.moveToPage(page.LogsPage)
558− case key.Matches(msg, keys.Help):
562− a.showHelp = !a.showHelp
564− case key.Matches(msg, helpEsc):
565− if a.app.CoderAgent.IsBusy() {
569− a.showHelp = !a.showHelp
572− case key.Matches(msg, keys.Filepicker):
573− a.showFilepicker = !a.showFilepicker
574− a.filepicker.ToggleFilepicker(a.showFilepicker)
578− f, filepickerCmd := a.filepicker.Update(msg)
617+ if a.dialog.HasDialogs() {
579− a.filepicker = f.(dialog.FilepickerCmp)
618+ u, dialogCmd := a.dialog.Update(msg)
580− cmds = append(cmds, filepickerCmd)
619+ a.dialog = u.(dialogs.DialogCmp)
581− }
620+ return dialogCmd
583− if a.showFilepicker {
622+ updated, cmd := a.pages[a.currentPage].Update(msg)
584− f, filepickerCmd := a.filepicker.Update(msg)
623+ a.pages[a.currentPage] = updated.(util.Model)
585− a.filepicker = f.(dialog.FilepickerCmp)
586− cmds = append(cmds, filepickerCmd)
587− // Only block key messages send all other messages down
588− if _, ok := msg.(tea.KeyPressMsg); ok {
589− return a, tea.Batch(cmds...)
594− q, quitCmd := a.quit.Update(msg)
595− a.quit = q.(dialog.QuitDialog)
596− cmds = append(cmds, quitCmd)
597− // Only block key messages send all other messages down
598− if _, ok := msg.(tea.KeyPressMsg); ok {
599− return a, tea.Batch(cmds...)
624+ return cmd
602− if a.showPermissions {
603− d, permissionsCmd := a.permissions.Update(msg)
604− a.permissions = d.(dialog.PermissionDialogCmp)
605− cmds = append(cmds, permissionsCmd)
606− // Only block key messages send all other messages down
607− if _, ok := msg.(tea.KeyPressMsg); ok {
608− return a, tea.Batch(cmds...)
612− if a.showSessionDialog {
613− d, sessionCmd := a.sessionDialog.Update(msg)
614− a.sessionDialog = d.(dialog.SessionDialog)
615− cmds = append(cmds, sessionCmd)
616− // Only block key messages send all other messages down
617− if _, ok := msg.(tea.KeyPressMsg); ok {
618− return a, tea.Batch(cmds...)
622− if a.showCommandDialog {
623− d, commandCmd := a.commandDialog.Update(msg)
624− a.commandDialog = d.(dialog.CommandDialog)
625− cmds = append(cmds, commandCmd)
626− // Only block key messages send all other messages down
627− if _, ok := msg.(tea.KeyPressMsg); ok {
628− return a, tea.Batch(cmds...)
632− if a.showModelDialog {
633− d, modelCmd := a.modelDialog.Update(msg)
634− a.modelDialog = d.(dialog.ModelDialog)
635− cmds = append(cmds, modelCmd)
636− // Only block key messages send all other messages down
637− if _, ok := msg.(tea.KeyPressMsg); ok {
638− return a, tea.Batch(cmds...)
642− if a.showInitDialog {
643− d, initCmd := a.initDialog.Update(msg)
644− a.initDialog = d.(dialog.InitDialogCmp)
645− cmds = append(cmds, initCmd)
646− // Only block key messages send all other messages down
647− if _, ok := msg.(tea.KeyPressMsg); ok {
648− return a, tea.Batch(cmds...)
652− if a.showThemeDialog {
653− d, themeCmd := a.themeDialog.Update(msg)
654− a.themeDialog = d.(dialog.ThemeDialog)
655− cmds = append(cmds, themeCmd)
656− // Only block key messages send all other messages down
657− if _, ok := msg.(tea.KeyPressMsg); ok {
658− return a, tea.Batch(cmds...)
662− s, _ := a.status.Update(msg)
663− a.status = s.(core.StatusCmp)
664− updated, cmd := a.pages[a.currentPage].Update(msg)
665− a.pages[a.currentPage] = updated.(util.Model)
666− cmds = append(cmds, cmd)
667− return a, tea.Batch(cmds...)
670// RegisterCommand adds a command to the command dialog
629// RegisterCommand adds a command to the command dialog
671−func (a *appModel) RegisterCommand(cmd dialog.Command) {
630+// func (a *appModel) RegisterCommand(cmd dialog.Command) {
672− a.commands = append(a.commands, cmd)
631+// a.commands = append(a.commands, cmd)
675func (a *appModel) moveToPage(pageID page.PageID) tea.Cmd {
634func (a *appModel) moveToPage(pageID page.PageID) tea.Cmd {
676 if a.app.CoderAgent.IsBusy() {
635 if a.app.CoderAgent.IsBusy() {
@@ -694,265 +653,276 @@ func (a *appModel) moveToPage(pageID page.PageID) tea.Cmd {
694 return tea.Batch(cmds...)
653 return tea.Batch(cmds...)
697−func (a appModel) View() string {
656+func (a *appModel) View() tea.View {
657+ pageView := a.pages[a.currentPage].View()
698 components := []string{
658 components := []string{
699− a.pages[a.currentPage].View(),
659+ pageView.String(),
701− components = append(components, a.status.View())
661+ components = append(components, a.status.View().String())
703 appView := lipgloss.JoinVertical(lipgloss.Top, components...)
663 appView := lipgloss.JoinVertical(lipgloss.Top, components...)
705− if a.showPermissions {
665+ // if a.showPermissions {
706− overlay := a.permissions.View()
666+ // overlay := a.permissions.View().String()
707− row := lipgloss.Height(appView) / 2
667+ // row := lipgloss.Height(appView) / 2
708− row -= lipgloss.Height(overlay) / 2
668+ // row -= lipgloss.Height(overlay) / 2
709− col := lipgloss.Width(appView) / 2
669+ // col := lipgloss.Width(appView) / 2
710− col -= lipgloss.Width(overlay) / 2
670+ // col -= lipgloss.Width(overlay) / 2
711− appView = layout.PlaceOverlay(
671+ // appView = layout.PlaceOverlay(
714− overlay,
674+ // overlay,
715− appView,
675+ // appView,
720− if a.showFilepicker {
680+ // if a.showFilepicker {
721− overlay := a.filepicker.View()
681+ // overlay := a.filepicker.View().String()
722− row := lipgloss.Height(appView) / 2
682+ // row := lipgloss.Height(appView) / 2
723− row -= lipgloss.Height(overlay) / 2
683+ // row -= lipgloss.Height(overlay) / 2
724− col := lipgloss.Width(appView) / 2
684+ // col := lipgloss.Width(appView) / 2
725− col -= lipgloss.Width(overlay) / 2
685+ // col -= lipgloss.Width(overlay) / 2
726− appView = layout.PlaceOverlay(
686+ // appView = layout.PlaceOverlay(
729− overlay,
689+ // overlay,
730− appView,
690+ // appView,
735− // Show compacting status overlay
695+ // // Show compacting status overlay
736− if a.isCompacting {
696+ // if a.isCompacting {
737− t := theme.CurrentTheme()
697+ // t := theme.CurrentTheme()
738− style := lipgloss.NewStyle().
698+ // style := lipgloss.NewStyle().
739− Border(lipgloss.RoundedBorder()).
699+ // Border(lipgloss.RoundedBorder()).
740− BorderForeground(t.BorderFocused()).
700+ // BorderForeground(t.BorderFocused()).
741− BorderBackground(t.Background()).
701+ // BorderBackground(t.Background()).
742− Padding(1, 2).
702+ // Padding(1, 2).
743− Background(t.Background()).
703+ // Background(t.Background()).
744− Foreground(t.Text())
704+ // Foreground(t.Text())
746− overlay := style.Render("Summarizing\n" + a.compactingMessage)
706+ // overlay := style.Render("Summarizing\n" + a.compactingMessage)
747− row := lipgloss.Height(appView) / 2
707+ // row := lipgloss.Height(appView) / 2
748− row -= lipgloss.Height(overlay) / 2
708+ // row -= lipgloss.Height(overlay) / 2
749− col := lipgloss.Width(appView) / 2
709+ // col := lipgloss.Width(appView) / 2
750− col -= lipgloss.Width(overlay) / 2
710+ // col -= lipgloss.Width(overlay) / 2
751− appView = layout.PlaceOverlay(
711+ // appView = layout.PlaceOverlay(
754− overlay,
714+ // overlay,
755− appView,
715+ // appView,
760− if a.showHelp {
720+ // if a.showHelp {
761− bindings := layout.KeyMapToSlice(keys)
721+ // bindings := layout.KeyMapToSlice(a.keymap)
762− if p, ok := a.pages[a.currentPage].(layout.Bindings); ok {
722+ // if p, ok := a.pages[a.currentPage].(layout.Bindings); ok {
763− bindings = append(bindings, p.BindingKeys()...)
723+ // bindings = append(bindings, p.BindingKeys()...)
765− if a.showPermissions {
725+ // if a.showPermissions {
766− bindings = append(bindings, a.permissions.BindingKeys()...)
726+ // bindings = append(bindings, a.permissions.BindingKeys()...)
768− if a.currentPage == page.LogsPage {
728+ // if a.currentPage == page.LogsPage {
769− bindings = append(bindings, logsKeyReturnKey)
729+ // // bindings = append(bindings, logsKeyReturnKey)
771− if !a.app.CoderAgent.IsBusy() {
731+ // if !a.app.CoderAgent.IsBusy() {
772− bindings = append(bindings, helpEsc)
732+ // // bindings = append(bindings, helpEsc)
774− a.help.SetBindings(bindings)
735+ // a.help.SetBindings(bindings)
776− overlay := a.help.View()
737+ // overlay := a.help.View().String()
777− row := lipgloss.Height(appView) / 2
778− row -= lipgloss.Height(overlay) / 2
779− col := lipgloss.Width(appView) / 2
780− col -= lipgloss.Width(overlay) / 2
781− appView = layout.PlaceOverlay(
791− overlay := a.quit.View()
792− row := lipgloss.Height(appView) / 2
738+ // row := lipgloss.Height(appView) / 2
793− row -= lipgloss.Height(overlay) / 2
739+ // row -= lipgloss.Height(overlay) / 2
794− col := lipgloss.Width(appView) / 2
740+ // col := lipgloss.Width(appView) / 2
795− col -= lipgloss.Width(overlay) / 2
741+ // col -= lipgloss.Width(overlay) / 2
796− appView = layout.PlaceOverlay(
742+ // appView = layout.PlaceOverlay(
799− overlay,
745+ // overlay,
800− appView,
746+ // appView,
805− if a.showSessionDialog {
751+ // if a.showSessionDialog {
806− overlay := a.sessionDialog.View()
752+ // overlay := a.sessionDialog.View().String()
807− row := lipgloss.Height(appView) / 2
753+ // row := lipgloss.Height(appView) / 2
808− row -= lipgloss.Height(overlay) / 2
754+ // row -= lipgloss.Height(overlay) / 2
809− col := lipgloss.Width(appView) / 2
755+ // col := lipgloss.Width(appView) / 2
810− col -= lipgloss.Width(overlay) / 2
756+ // col -= lipgloss.Width(overlay) / 2
811− appView = layout.PlaceOverlay(
757+ // appView = layout.PlaceOverlay(
814− overlay,
760+ // overlay,
815− appView,
761+ // appView,
820− if a.showModelDialog {
766+ // if a.showModelDialog {
821− overlay := a.modelDialog.View()
767+ // overlay := a.modelDialog.View().String()
822− row := lipgloss.Height(appView) / 2
768+ // row := lipgloss.Height(appView) / 2
823− row -= lipgloss.Height(overlay) / 2
769+ // row -= lipgloss.Height(overlay) / 2
824− col := lipgloss.Width(appView) / 2
770+ // col := lipgloss.Width(appView) / 2
825− col -= lipgloss.Width(overlay) / 2
771+ // col -= lipgloss.Width(overlay) / 2
826− appView = layout.PlaceOverlay(
772+ // appView = layout.PlaceOverlay(
829− overlay,
775+ // overlay,
830− appView,
776+ // appView,
835− if a.showCommandDialog {
781+ // if a.showCommandDialog {
836− overlay := a.commandDialog.View()
782+ // overlay := a.commandDialog.View().String()
837− row := lipgloss.Height(appView) / 2
783+ // row := lipgloss.Height(appView) / 2
838− row -= lipgloss.Height(overlay) / 2
784+ // row -= lipgloss.Height(overlay) / 2
839− col := lipgloss.Width(appView) / 2
785+ // col := lipgloss.Width(appView) / 2
840− col -= lipgloss.Width(overlay) / 2
786+ // col -= lipgloss.Width(overlay) / 2
841− appView = layout.PlaceOverlay(
787+ // appView = layout.PlaceOverlay(
844− overlay,
790+ // overlay,
845− appView,
791+ // appView,
850− if a.showInitDialog {
796+ // if a.showInitDialog {
851− overlay := a.initDialog.View()
797+ // overlay := a.initDialog.View()
852− appView = layout.PlaceOverlay(
798+ // appView = layout.PlaceOverlay(
853− a.width/2-lipgloss.Width(overlay)/2,
799+ // a.width/2-lipgloss.Width(overlay)/2,
854− a.height/2-lipgloss.Height(overlay)/2,
800+ // a.height/2-lipgloss.Height(overlay)/2,
855− overlay,
801+ // overlay,
856− appView,
802+ // appView,
861− if a.showThemeDialog {
807+ // if a.showThemeDialog {
862− overlay := a.themeDialog.View()
808+ // overlay := a.themeDialog.View().String()
809+ // row := lipgloss.Height(appView) / 2
810+ // row -= lipgloss.Height(overlay) / 2
811+ // col := lipgloss.Width(appView) / 2
812+ // col -= lipgloss.Width(overlay) / 2
813+ // appView = layout.PlaceOverlay(
822+ // if a.showMultiArgumentsDialog {
823+ // overlay := a.multiArgumentsDialog.View()
863− row := lipgloss.Height(appView) / 2
824+ // row := lipgloss.Height(appView) / 2
864− row -= lipgloss.Height(overlay) / 2
825+ // row -= lipgloss.Height(overlay) / 2
865− col := lipgloss.Width(appView) / 2
826+ // col := lipgloss.Width(appView) / 2
866− col -= lipgloss.Width(overlay) / 2
827+ // col -= lipgloss.Width(overlay) / 2
867− appView = layout.PlaceOverlay(
828+ // appView = layout.PlaceOverlay(
870− overlay,
831+ // overlay,
836+ t := theme.CurrentTheme()
837+ if a.dialog.HasDialogs() {
871− appView,
840+ lipgloss.NewLayer(appView),
842+ a.dialog.GetLayers()...,
874− }
844+ canvas := lipgloss.NewCanvas(
876− if a.showMultiArgumentsDialog {
877− overlay := a.multiArgumentsDialog.View()
878− row := lipgloss.Height(appView) / 2
879− row -= lipgloss.Height(overlay) / 2
880− col := lipgloss.Width(appView) / 2
881− col -= lipgloss.Width(overlay) / 2
882− appView = layout.PlaceOverlay(
847+ view := tea.NewView(canvas.Render())
848+ activeView := a.dialog.ActiveView()
849+ view.SetBackgroundColor(t.Background())
850+ view.SetCursor(activeView.Cursor())
891− return appView
854+ view := tea.NewView(appView)
855+ view.SetCursor(pageView.Cursor())
856+ view.SetBackgroundColor(t.Background())
894func New(app *app.App) tea.Model {
860func New(app *app.App) tea.Model {
895 startPage := page.ChatPage
861 startPage := page.ChatPage
896 model := &appModel{
862 model := &appModel{
897− currentPage: startPage,
863+ currentPage: startPage,
865+ status: core.NewStatusCmp(app.LSPClients),
898− loadedPages: make(map[page.PageID]bool),
866+ loadedPages: make(map[page.PageID]bool),
899− status: core.NewStatusCmp(app.LSPClients),
867+ keyMap: DefaultKeyMap(),
900− help: dialog.NewHelpCmp(),
869+ // help: dialog.NewHelpCmp(),
901− quit: dialog.NewQuitCmp(),
902− sessionDialog: dialog.NewSessionDialogCmp(),
870+ // sessionDialog: dialog.NewSessionDialogCmp(),
903− commandDialog: dialog.NewCommandDialogCmp(),
871+ // commandDialog: dialog.NewCommandDialogCmp(),
904− modelDialog: dialog.NewModelDialogCmp(),
872+ // modelDialog: dialog.NewModelDialogCmp(),
905− permissions: dialog.NewPermissionDialogCmp(),
873+ // permissions: dialog.NewPermissionDialogCmp(),
906− initDialog: dialog.NewInitDialogCmp(),
874+ // initDialog: dialog.NewInitDialogCmp(),
907− themeDialog: dialog.NewThemeDialogCmp(),
875+ // themeDialog: dialog.NewThemeDialogCmp(),
909− commands: []dialog.Command{},
876+ // commands: []dialog.Command{},
910 pages: map[page.PageID]util.Model{
877 pages: map[page.PageID]util.Model{
911 page.ChatPage: page.NewChatPage(app),
878 page.ChatPage: page.NewChatPage(app),
912 page.LogsPage: page.NewLogsPage(),
879 page.LogsPage: page.NewLogsPage(),
914− filepicker: dialog.NewFilepickerCmp(app),
881+ // filepicker: dialog.NewFilepickerCmp(app),
917− model.RegisterCommand(dialog.Command{
883+ // New dialog
918− ID: "init",
884+ dialog: dialogs.NewDialogCmp(),
919− Title: "Initialize Project",
920− Description: "Create/Update the OpenCode.md memory file",
921− Handler: func(cmd dialog.Command) tea.Cmd {
922− prompt := `Please analyze this codebase and create a OpenCode.md file containing:
923−1. Build/lint/test commands - especially for running a single test
924−2. Code style guidelines including imports, formatting, types, naming conventions, error handling, etc.
926−The file you create will be given to agentic coding agents (such as yourself) that operate in this repository. Make it about 20 lines long.
927−If there's already a opencode.md, improve it.
928−If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (in .github/copilot-instructions.md), make sure to include them.`
930− util.CmdHandler(chat.SendMsg{
937− model.RegisterCommand(dialog.Command{
939− Title: "Compact Session",
940− Description: "Summarize the current session and create a new one with the summary",
941− Handler: func(cmd dialog.Command) tea.Cmd {
942− return func() tea.Msg {
943− return startCompactSessionMsg{}
947− // Load custom commands
948− customCommands, err := dialog.LoadCustomCommands()
950− logging.Warn("Failed to load custom commands", "error", err)
952− for _, cmd := range customCommands {
953− model.RegisterCommand(cmd)
887+ // model.RegisterCommand(dialog.Command{
889+ // Title: "Initialize Project",
890+ // Description: "Create/Update the OpenCode.md memory file",
891+ // Handler: func(cmd dialog.Command) tea.Cmd {
892+ // prompt := `Please analyze this codebase and create a OpenCode.md file containing:
893+ // 1. Build/lint/test commands - especially for running a single test
894+ // 2. Code style guidelines including imports, formatting, types, naming conventions, error handling, etc.
896+ // The file you create will be given to agentic coding agents (such as yourself) that operate in this repository. Make it about 20 lines long.
897+ // If there's already a opencode.md, improve it.
898+ // If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (in .github/copilot-instructions.md), make sure to include them.`
899+ // return tea.Batch(
900+ // util.CmdHandler(chat.SendMsg{
907+ // model.RegisterCommand(dialog.Command{
909+ // Title: "Compact Session",
910+ // Description: "Summarize the current session and create a new one with the summary",
911+ // Handler: func(cmd dialog.Command) tea.Cmd {
912+ // return func() tea.Msg {
913+ // return startCompactSessionMsg{}
917+ // // Load custom commands
918+ // customCommands, err := dialog.LoadCustomCommands()
920+ // logging.Warn("Failed to load custom commands", "error", err)
922+ // for _, cmd := range customCommands {
923+ // model.RegisterCommand(cmd)
957 return model
927 return model