on main help sign in
feat: add details overlay
4d402fdKujtim Hoxha
internal/tui/components/chat/header/header.go+5
@@ -19,6 +19,7 @@ import (
19type Header interface {
19type Header interface {
20 util.Model
20 util.Model
21 SetSession(session session.Session)
21 SetSession(session session.Session)
22+ SetDetailsOpen(open bool)
22}
23}
23
24
24type header struct {
25type header struct {
@@ -119,6 +120,10 @@ func (h *header) details() string {
119 return strings.Join(parts, dot)
120 return strings.Join(parts, dot)
120}
121}
121
122
123+func (h *header) SetDetailsOpen(open bool) {
124+ h.detailsOpen = open
125+}
126+
122// SetSession implements Header.
127// SetSession implements Header.
123func (h *header) SetSession(session session.Session) {
128func (h *header) SetSession(session session.Session) {
124 h.session = session
129 h.session = session
internal/tui/components/chat/sidebar/sidebar.go+17−8
@@ -60,15 +60,17 @@ type sidebarCmp struct {
60 logo string
60 logo string
61 cwd string
61 cwd string
62 lspClients map[string]*lsp.Client
62 lspClients map[string]*lsp.Client
63+ compactMode bool
63 history history.Service
64 history history.Service
64 // Using a sync map here because we might receive file history events concurrently
65 // Using a sync map here because we might receive file history events concurrently
65 files sync.Map
66 files sync.Map
66}
67}
67
68
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 {
69 return &sidebarCmp{
70 return &sidebarCmp{
70− lspClients: lspClients,
71+ lspClients: lspClients,
71− history: history,
72+ history: history,
73+ compactMode: compact,
72 }
74 }
73}
75}
74
76
@@ -109,17 +111,24 @@ func (m *sidebarCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
109
111
110func (m *sidebarCmp) View() tea.View {
112func (m *sidebarCmp) View() tea.View {
111 t := styles.CurrentTheme()
113 t := styles.CurrentTheme()
112− parts := []string{
114+ parts := []string{}
113− m.logo,
115+ if !m.compactMode {
116+ parts = append(parts, m.logo)
114 }
117 }
115
118
116− if m.session.ID != "" {
119+ if !m.compactMode && m.session.ID != "" {
117 parts = append(parts, t.S().Muted.Render(m.session.Title), "")
120 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), "")
118 }
123 }
119
124
125+ if !m.compactMode {
126+ parts = append(parts,
127+ m.cwd,
128+ "",
129+ )
130+ }
120 parts = append(parts,
131 parts = append(parts,
121− m.cwd,
122− "",
123 m.currentModelBlock(),
132 m.currentModelBlock(),
124 )
133 )
125 if m.session.ID != "" {
134 if m.session.ID != "" {
internal/tui/page/chat/chat.go+60−17
@@ -18,7 +18,9 @@ import (
18 "github.com/charmbracelet/crush/internal/tui/components/core/layout"
18 "github.com/charmbracelet/crush/internal/tui/components/core/layout"
19 "github.com/charmbracelet/crush/internal/tui/components/dialogs/commands"
19 "github.com/charmbracelet/crush/internal/tui/components/dialogs/commands"
20 "github.com/charmbracelet/crush/internal/tui/page"
20 "github.com/charmbracelet/crush/internal/tui/page"
21+ "github.com/charmbracelet/crush/internal/tui/styles"
21 "github.com/charmbracelet/crush/internal/tui/util"
22 "github.com/charmbracelet/crush/internal/tui/util"
23+ "github.com/charmbracelet/lipgloss/v2"
22)
24)
23
25
24var ChatPageID page.PageID = "chat"
26var ChatPageID page.PageID = "chat"
@@ -51,7 +53,9 @@ type chatPage struct {
51
53
52 compactMode bool
54 compactMode bool
53 forceCompactMode bool // Force compact mode regardless of window size
55 forceCompactMode bool // Force compact mode regardless of window size
56+ showDetails bool // Show details in the header
54 header header.Header
57 header header.Header
58+ compactSidebar layout.Container
55}
59}
56
60
57func (p *chatPage) Init() tea.Cmd {
61func (p *chatPage) Init() tea.Cmd {
@@ -69,6 +73,9 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
69 h, cmd := p.header.Update(msg)
73 h, cmd := p.header.Update(msg)
70 cmds = append(cmds, cmd)
74 cmds = append(cmds, cmd)
71 p.header = h.(header.Header)
75 p.header = h.(header.Header)
76+ if p.compactMode && p.showDetails {
77+ cmds = append(cmds, p.compactSidebar.SetSize(msg.Width-4, 0))
78+ }
72 // the mode is only relevant when there is a session
79 // the mode is only relevant when there is a session
73 if p.session.ID != "" {
80 if p.session.ID != "" {
74 // Only auto-switch to compact mode if not forced
81 // Only auto-switch to compact mode if not forced
@@ -178,15 +185,34 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
178 p.app.CoderAgent.Cancel(p.session.ID)
185 p.app.CoderAgent.Cancel(p.session.ID)
179 return p, nil
186 return p, nil
180 }
187 }
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
181 }
205 }
182 }
206 }
183 u, cmd := p.layout.Update(msg)
207 u, cmd := p.layout.Update(msg)
184 cmds = append(cmds, cmd)
208 cmds = append(cmds, cmd)
185 p.layout = u.(layout.SplitPaneLayout)
209 p.layout = u.(layout.SplitPaneLayout)
186
210
211+ if p.compactMode && p.showDetails {
187− h, cmd := p.header.Update(msg)
212+ s, cmd := p.compactSidebar.Update(msg)
213+ p.compactSidebar = s.(layout.Container)
188− cmds = append(cmds, cmd)
214+ cmds = append(cmds, cmd)
189− p.header = h.(header.Header)
215+ }
190 return p, tea.Batch(cmds...)
216 return p, tea.Batch(cmds...)
191}
217}
192
218
@@ -199,7 +225,7 @@ func (p *chatPage) setMessages() tea.Cmd {
199}
225}
200
226
201func (p *chatPage) setSidebar() tea.Cmd {
227func (p *chatPage) setSidebar() tea.Cmd {
202− sidebarContainer := sidebarCmp(p.app)
228+ sidebarContainer := sidebarCmp(p.app, false)
203 sidebarContainer.Init()
229 sidebarContainer.Init()
204 return p.layout.SetRightPanel(sidebarContainer)
230 return p.layout.SetRightPanel(sidebarContainer)
205}
231}
@@ -267,16 +293,29 @@ func (p *chatPage) View() tea.View {
267 return p.layout.View()
293 return p.layout.View()
268 }
294 }
269 layoutView := p.layout.View()
295 layoutView := p.layout.View()
270− chatView := tea.NewView(
271− strings.Join(
296+ chatView := strings.Join(
272− []string{
297+ []string{
273− p.header.View().String(),
298+ p.header.View().String(),
274− layoutView.String(),
299+ layoutView.String(),
275− }, "\n",
300+ }, "\n",
276− ),
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...,
277 )
315 )
316+ view := tea.NewView(canvas.Render())
278− chatView.SetCursor(layoutView.Cursor())
317+ view.SetCursor(layoutView.Cursor())
279− return chatView
318+ return view
280}
319}
281
320
282func (p *chatPage) Bindings() []key.Binding {
321func (p *chatPage) Bindings() []key.Binding {
@@ -308,10 +347,14 @@ func (p *chatPage) Bindings() []key.Binding {
308 return bindings
347 return bindings
309}
348}
310
349
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+ }
312 return layout.NewContainer(
355 return layout.NewContainer(
313− sidebar.NewSidebarCmp(app.History, app.LSPClients),
356+ sidebar.NewSidebarCmp(app.History, app.LSPClients, compact),
314− layout.WithPadding(1, 1, 1, 1),
357+ padding,
315 )
358 )
316}
359}
317
360
@@ -322,7 +365,7 @@ func NewChatPage(app *app.App) ChatPage {
322 return &chatPage{
365 return &chatPage{
323 app: app,
366 app: app,
324 layout: layout.NewSplitPane(
367 layout: layout.NewSplitPane(
325− layout.WithRightPanel(sidebarCmp(app)),
368+ layout.WithRightPanel(sidebarCmp(app, false)),
326 layout.WithBottomPanel(editorContainer),
369 layout.WithBottomPanel(editorContainer),
327 layout.WithFixedBottomHeight(5),
370 layout.WithFixedBottomHeight(5),
328 layout.WithFixedRightWidth(31),
371 layout.WithFixedRightWidth(31),
internal/tui/page/chat/keys.go+5
@@ -9,6 +9,7 @@ type KeyMap struct {
9 AddAttachment key.Binding
9 AddAttachment key.Binding
10 Cancel key.Binding
10 Cancel key.Binding
11 Tab key.Binding
11 Tab key.Binding
12+ Details key.Binding
12}
13}
13
14
14func DefaultKeyMap() KeyMap {
15func DefaultKeyMap() KeyMap {
@@ -29,5 +30,9 @@ func DefaultKeyMap() KeyMap {
29 key.WithKeys("tab"),
30 key.WithKeys("tab"),
30 key.WithHelp("tab", "change focus"),
31 key.WithHelp("tab", "change focus"),
31 ),
32 ),
33+ Details: key.NewBinding(
34+ key.WithKeys("ctrl+d"),
35+ key.WithHelp("ctrl+d", "toggle details"),
36+ ),
32 }
37 }
33}
38}