on main help sign in
feat: add details overlay
commit4d402fdab1b6c3168060aa5abb3d5a53a02243ce authorKujtim Hoxha <kujtimii.h@gmail.com> dateWed 18 Jun 2025, 13:58 (1 year ago) parentf19a809
4 files changed+87−25
internal/tui/components/chat/header/header.go+5 internal/tui/components/chat/sidebar/sidebar.go+17−8 internal/tui/page/chat/chat.go+60−17 internal/tui/page/chat/keys.go+5
internal/tui/components/chat/header/header.go+5
@@ -19,6 +19,7 @@ import (
1919type Header interface {
2020 util.Model
2121 SetSession(session session.Session)
22+ SetDetailsOpen(open bool)
2223}
2324
2425type header struct {
@@ -119,6 +120,10 @@ func (h *header) details() string {
119120 return strings.Join(parts, dot)
120121}
121122
123+func (h *header) SetDetailsOpen(open bool) {
124+ h.detailsOpen = open
125+}
126+
122127// SetSession implements Header.
123128func (h *header) SetSession(session session.Session) {
124129 h.session = session
internal/tui/components/chat/sidebar/sidebar.go+17−8
@@ -60,15 +60,17 @@ type sidebarCmp struct {
6060 logo string
6161 cwd string
6262 lspClients map[string]*lsp.Client
63+ compactMode bool
6364 history history.Service
6465 // Using a sync map here because we might receive file history events concurrently
6566 files sync.Map
6667}
6768
68−func NewSidebarCmp(history history.Service, lspClients map[string]*lsp.Client) Sidebar {
69+func NewSidebarCmp(history history.Service, lspClients map[string]*lsp.Client, compact bool) Sidebar {
6970 return &sidebarCmp{
70− lspClients: lspClients,
71− history: history,
71+ lspClients: lspClients,
72+ history: history,
73+ compactMode: compact,
7274 }
7375}
7476
@@ -109,17 +111,24 @@ func (m *sidebarCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
109111
110112func (m *sidebarCmp) View() tea.View {
111113 t := styles.CurrentTheme()
112− parts := []string{
113− m.logo,
114+ parts := []string{}
115+ if !m.compactMode {
116+ parts = append(parts, m.logo)
114117 }
115118
116− if m.session.ID != "" {
119+ if !m.compactMode && m.session.ID != "" {
117120 parts = append(parts, t.S().Muted.Render(m.session.Title), "")
121+ } else if m.session.ID != "" {
122+ parts = append(parts, t.S().Text.Render(m.session.Title), "")
118123 }
119124
125+ if !m.compactMode {
126+ parts = append(parts,
127+ m.cwd,
128+ "",
129+ )
130+ }
120131 parts = append(parts,
121− m.cwd,
122− "",
123132 m.currentModelBlock(),
124133 )
125134 if m.session.ID != "" {
internal/tui/page/chat/chat.go+60−17
@@ -18,7 +18,9 @@ import (
1818 "github.com/charmbracelet/crush/internal/tui/components/core/layout"
1919 "github.com/charmbracelet/crush/internal/tui/components/dialogs/commands"
2020 "github.com/charmbracelet/crush/internal/tui/page"
21+ "github.com/charmbracelet/crush/internal/tui/styles"
2122 "github.com/charmbracelet/crush/internal/tui/util"
23+ "github.com/charmbracelet/lipgloss/v2"
2224)
2325
2426var ChatPageID page.PageID = "chat"
@@ -51,7 +53,9 @@ type chatPage struct {
5153
5254 compactMode bool
5355 forceCompactMode bool // Force compact mode regardless of window size
56+ showDetails bool // Show details in the header
5457 header header.Header
58+ compactSidebar layout.Container
5559}
5660
5761func (p *chatPage) Init() tea.Cmd {
@@ -69,6 +73,9 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
6973 h, cmd := p.header.Update(msg)
7074 cmds = append(cmds, cmd)
7175 p.header = h.(header.Header)
76+ if p.compactMode && p.showDetails {
77+ cmds = append(cmds, p.compactSidebar.SetSize(msg.Width-4, 0))
78+ }
7279 // the mode is only relevant when there is a session
7380 if p.session.ID != "" {
7481 // Only auto-switch to compact mode if not forced
@@ -178,15 +185,34 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
178185 p.app.CoderAgent.Cancel(p.session.ID)
179186 return p, nil
180187 }
188+ case key.Matches(msg, p.keyMap.Details):
189+ if p.session.ID == "" || !p.compactMode {
190+ return p, nil // No session to show details for
191+ }
192+ p.showDetails = !p.showDetails
193+ p.header.SetDetailsOpen(p.showDetails)
194+ if p.showDetails {
195+ p.compactSidebar = sidebarCmp(p.app, true)
196+ c, cmd := p.compactSidebar.Update(chat.SessionSelectedMsg(p.session))
197+ p.compactSidebar = c.(layout.Container)
198+ return p, tea.Batch(
199+ cmd,
200+ p.compactSidebar.SetSize(p.wWidth-4, 0),
201+ )
202+ }
203+
204+ return p, nil
181205 }
182206 }
183207 u, cmd := p.layout.Update(msg)
184208 cmds = append(cmds, cmd)
185209 p.layout = u.(layout.SplitPaneLayout)
186210
187− h, cmd := p.header.Update(msg)
188− cmds = append(cmds, cmd)
189− p.header = h.(header.Header)
211+ if p.compactMode && p.showDetails {
212+ s, cmd := p.compactSidebar.Update(msg)
213+ p.compactSidebar = s.(layout.Container)
214+ cmds = append(cmds, cmd)
215+ }
190216 return p, tea.Batch(cmds...)
191217}
192218
@@ -199,7 +225,7 @@ func (p *chatPage) setMessages() tea.Cmd {
199225}
200226
201227func (p *chatPage) setSidebar() tea.Cmd {
202− sidebarContainer := sidebarCmp(p.app)
228+ sidebarContainer := sidebarCmp(p.app, false)
203229 sidebarContainer.Init()
204230 return p.layout.SetRightPanel(sidebarContainer)
205231}
@@ -267,16 +293,29 @@ func (p *chatPage) View() tea.View {
267293 return p.layout.View()
268294 }
269295 layoutView := p.layout.View()
270− chatView := tea.NewView(
271− strings.Join(
272− []string{
273− p.header.View().String(),
274− layoutView.String(),
275− }, "\n",
276− ),
296+ chatView := strings.Join(
297+ []string{
298+ p.header.View().String(),
299+ layoutView.String(),
300+ }, "\n",
301+ )
302+ layers := []*lipgloss.Layer{
303+ lipgloss.NewLayer(chatView).X(0).Y(0),
304+ }
305+ if p.showDetails {
306+ t := styles.CurrentTheme()
307+ style := t.S().Base.
308+ Border(lipgloss.RoundedBorder()).
309+ BorderForeground(t.BorderFocus)
310+ details := style.Render(p.compactSidebar.View().String())
311+ layers = append(layers, lipgloss.NewLayer(details).X(1).Y(1))
312+ }
313+ canvas := lipgloss.NewCanvas(
314+ layers...,
277315 )
278− chatView.SetCursor(layoutView.Cursor())
279− return chatView
316+ view := tea.NewView(canvas.Render())
317+ view.SetCursor(layoutView.Cursor())
318+ return view
280319}
281320
282321func (p *chatPage) Bindings() []key.Binding {
@@ -308,10 +347,14 @@ func (p *chatPage) Bindings() []key.Binding {
308347 return bindings
309348}
310349
311−func sidebarCmp(app *app.App) layout.Container {
350+func sidebarCmp(app *app.App, compact bool) layout.Container {
351+ padding := layout.WithPadding(1, 1, 1, 1)
352+ if compact {
353+ padding = layout.WithPadding(0, 1, 1, 1)
354+ }
312355 return layout.NewContainer(
313− sidebar.NewSidebarCmp(app.History, app.LSPClients),
314− layout.WithPadding(1, 1, 1, 1),
356+ sidebar.NewSidebarCmp(app.History, app.LSPClients, compact),
357+ padding,
315358 )
316359}
317360
@@ -322,7 +365,7 @@ func NewChatPage(app *app.App) ChatPage {
322365 return &chatPage{
323366 app: app,
324367 layout: layout.NewSplitPane(
325− layout.WithRightPanel(sidebarCmp(app)),
368+ layout.WithRightPanel(sidebarCmp(app, false)),
326369 layout.WithBottomPanel(editorContainer),
327370 layout.WithFixedBottomHeight(5),
328371 layout.WithFixedRightWidth(31),
internal/tui/page/chat/keys.go+5
@@ -9,6 +9,7 @@ type KeyMap struct {
99 AddAttachment key.Binding
1010 Cancel key.Binding
1111 Tab key.Binding
12+ Details key.Binding
1213}
1314
1415func DefaultKeyMap() KeyMap {
@@ -29,5 +30,9 @@ func DefaultKeyMap() KeyMap {
2930 key.WithKeys("tab"),
3031 key.WithHelp("tab", "change focus"),
3132 ),
33+ Details: key.NewBinding(
34+ key.WithKeys("ctrl+d"),
35+ key.WithHelp("ctrl+d", "toggle details"),
36+ ),
3237 }
3338}