on main help sign in
chore: try to make the number of items in the sidebar dynamic
9416a8fKujtim Hoxha
internal/tui/components/chat/sidebar/sidebar.go+137−2
@@ -35,6 +35,14 @@ type FileHistory struct {
35
35
36const LogoHeightBreakpoint = 40
36const LogoHeightBreakpoint = 40
37
37
38+// Default maximum number of items to show in each section
39+const (
40+ DefaultMaxFilesShown = 10
41+ DefaultMaxLSPsShown = 8
42+ DefaultMaxMCPsShown = 8
43+ MinItemsPerSection = 2 // Minimum items to show per section
44+)
45+
38type SessionFile struct {
46type SessionFile struct {
39 History FileHistory
47 History FileHistory
40 FilePath string
48 FilePath string
@@ -266,6 +274,79 @@ func (m *sidebarCmp) getMaxWidth() int {
266 return min(m.width-2, 58) // -2 for padding
274 return min(m.width-2, 58) // -2 for padding
267}
275}
268
276
277+// calculateAvailableHeight estimates how much height is available for dynamic content
278+func (m *sidebarCmp) calculateAvailableHeight() int {
279+ usedHeight := 0
280+
281+ if !m.compactMode {
282+ if m.height > LogoHeightBreakpoint {
283+ usedHeight += 7 // Approximate logo height
284+ } else {
285+ usedHeight += 2 // Smaller logo height
286+ }
287+ usedHeight += 1 // Empty line after logo
288+ }
289+
290+ if m.session.ID != "" {
291+ usedHeight += 1 // Title line
292+ usedHeight += 1 // Empty line after title
293+ }
294+
295+ if !m.compactMode {
296+ usedHeight += 1 // CWD line
297+ usedHeight += 1 // Empty line after CWD
298+ }
299+
300+ usedHeight += 2 // Model info
301+
302+ usedHeight += 6 // 3 sections × 2 lines each (header + empty line)
303+
304+ // Base padding
305+ usedHeight += 2 // Top and bottom padding
306+
307+ return max(0, m.height-usedHeight)
308+}
309+
310+// getDynamicLimits calculates how many items to show in each section based on available height
311+func (m *sidebarCmp) getDynamicLimits() (maxFiles, maxLSPs, maxMCPs int) {
312+ availableHeight := m.calculateAvailableHeight()
313+
314+ // If we have very little space, use minimum values
315+ if availableHeight < 10 {
316+ return MinItemsPerSection, MinItemsPerSection, MinItemsPerSection
317+ }
318+
319+ // Distribute available height among the three sections
320+ // Give priority to files, then LSPs, then MCPs
321+ totalSections := 3
322+ heightPerSection := availableHeight / totalSections
323+
324+ // Calculate limits for each section, ensuring minimums
325+ maxFiles = max(MinItemsPerSection, min(DefaultMaxFilesShown, heightPerSection))
326+ maxLSPs = max(MinItemsPerSection, min(DefaultMaxLSPsShown, heightPerSection))
327+ maxMCPs = max(MinItemsPerSection, min(DefaultMaxMCPsShown, heightPerSection))
328+
329+ // If we have extra space, give it to files first
330+ remainingHeight := availableHeight - (maxFiles + maxLSPs + maxMCPs)
331+ if remainingHeight > 0 {
332+ extraForFiles := min(remainingHeight, DefaultMaxFilesShown-maxFiles)
333+ maxFiles += extraForFiles
334+ remainingHeight -= extraForFiles
335+
336+ if remainingHeight > 0 {
337+ extraForLSPs := min(remainingHeight, DefaultMaxLSPsShown-maxLSPs)
338+ maxLSPs += extraForLSPs
339+ remainingHeight -= extraForLSPs
340+
341+ if remainingHeight > 0 {
342+ maxMCPs += min(remainingHeight, DefaultMaxMCPsShown-maxMCPs)
343+ }
344+ }
345+ }
346+
347+ return maxFiles, maxLSPs, maxMCPs
348+}
349+
269func (m *sidebarCmp) filesBlock() string {
350func (m *sidebarCmp) filesBlock() string {
270 t := styles.CurrentTheme()
351 t := styles.CurrentTheme()
271
352
@@ -294,10 +375,19 @@ func (m *sidebarCmp) filesBlock() string {
294 return files[i].History.latestVersion.CreatedAt > files[j].History.latestVersion.CreatedAt
375 return files[i].History.latestVersion.CreatedAt > files[j].History.latestVersion.CreatedAt
295 })
376 })
296
377
378+ // Limit the number of files shown
379+ maxFiles, _, _ := m.getDynamicLimits()
380+ maxFiles = min(len(files), maxFiles)
381+ filesShown := 0
382+
297 for _, file := range files {
383 for _, file := range files {
298 if file.Additions == 0 && file.Deletions == 0 {
384 if file.Additions == 0 && file.Deletions == 0 {
299 continue // skip files with no changes
385 continue // skip files with no changes
300 }
386 }
387+ if filesShown >= maxFiles {
388+ break
389+ }
390+
301 var statusParts []string
391 var statusParts []string
302 if file.Additions > 0 {
392 if file.Additions > 0 {
303 statusParts = append(statusParts, t.S().Base.Foreground(t.Success).Render(fmt.Sprintf("+%d", file.Additions)))
393 statusParts = append(statusParts, t.S().Base.Foreground(t.Success).Render(fmt.Sprintf("+%d", file.Additions)))
@@ -323,6 +413,21 @@ func (m *sidebarCmp) filesBlock() string {
323 m.getMaxWidth(),
413 m.getMaxWidth(),
324 ),
414 ),
325 )
415 )
416+ filesShown++
417+ }
418+
419+ // Add indicator if there are more files
420+ totalFilesWithChanges := 0
421+ for _, file := range files {
422+ if file.Additions > 0 || file.Deletions > 0 {
423+ totalFilesWithChanges++
424+ }
425+ }
426+ if totalFilesWithChanges > maxFiles {
427+ remaining := totalFilesWithChanges - maxFiles
428+ fileList = append(fileList,
429+ t.S().Base.Foreground(t.FgMuted).Render(fmt.Sprintf("… and %d more", remaining)),
430+ )
326 }
431 }
327
432
328 return lipgloss.JoinVertical(
433 return lipgloss.JoinVertical(
@@ -350,7 +455,14 @@ func (m *sidebarCmp) lspBlock() string {
350 )
455 )
351 }
456 }
352
457
458+ // Limit the number of LSPs shown
459+ _, maxLSPs, _ := m.getDynamicLimits()
460+ maxLSPs = min(len(lsp), maxLSPs)
353− for _, l := range lsp {
461+ for i, l := range lsp {
462+ if i >= maxLSPs {
463+ break
464+ }
465+
354 iconColor := t.Success
466 iconColor := t.Success
355 if l.LSP.Disabled {
467 if l.LSP.Disabled {
356 iconColor = t.FgMuted
468 iconColor = t.FgMuted
@@ -398,6 +510,14 @@ func (m *sidebarCmp) lspBlock() string {
398 )
510 )
399 }
511 }
400
512
513+ // Add indicator if there are more LSPs
514+ if len(lsp) > maxLSPs {
515+ remaining := len(lsp) - maxLSPs
516+ lspList = append(lspList,
517+ t.S().Base.Foreground(t.FgMuted).Render(fmt.Sprintf("… and %d more", remaining)),
518+ )
519+ }
520+
401 return lipgloss.JoinVertical(
521 return lipgloss.JoinVertical(
402 lipgloss.Left,
522 lipgloss.Left,
403 lspList...,
523 lspList...,
@@ -423,7 +543,14 @@ func (m *sidebarCmp) mcpBlock() string {
423 )
543 )
424 }
544 }
425
545
546+ // Limit the number of MCPs shown
547+ _, _, maxMCPs := m.getDynamicLimits()
548+ maxMCPs = min(len(mcps), maxMCPs)
426− for _, l := range mcps {
549+ for i, l := range mcps {
550+ if i >= maxMCPs {
551+ break
552+ }
553+
427 iconColor := t.Success
554 iconColor := t.Success
428 if l.MCP.Disabled {
555 if l.MCP.Disabled {
429 iconColor = t.FgMuted
556 iconColor = t.FgMuted
@@ -440,6 +567,14 @@ func (m *sidebarCmp) mcpBlock() string {
440 )
567 )
441 }
568 }
442
569
570+ // Add indicator if there are more MCPs
571+ if len(mcps) > maxMCPs {
572+ remaining := len(mcps) - maxMCPs
573+ mcpList = append(mcpList,
574+ t.S().Base.Foreground(t.FgMuted).Render(fmt.Sprintf("… and %d more", remaining)),
575+ )
576+ }
577+
443 return lipgloss.JoinVertical(
578 return lipgloss.JoinVertical(
444 lipgloss.Left,
579 lipgloss.Left,
445 mcpList...,
580 mcpList...,