header.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. package model
  2. import (
  3. "fmt"
  4. "strings"
  5. "charm.land/lipgloss/v2"
  6. "github.com/charmbracelet/crush/internal/config"
  7. "github.com/charmbracelet/crush/internal/csync"
  8. "github.com/charmbracelet/crush/internal/fsext"
  9. "github.com/charmbracelet/crush/internal/lsp"
  10. "github.com/charmbracelet/crush/internal/session"
  11. "github.com/charmbracelet/crush/internal/ui/common"
  12. "github.com/charmbracelet/crush/internal/ui/styles"
  13. "github.com/charmbracelet/x/ansi"
  14. )
  15. const (
  16. headerDiag = "╱"
  17. minHeaderDiags = 3
  18. leftPadding = 1
  19. rightPadding = 1
  20. )
  21. // renderCompactHeader renders the compact header for the given session.
  22. func renderCompactHeader(
  23. com *common.Common,
  24. session *session.Session,
  25. lspClients *csync.Map[string, *lsp.Client],
  26. detailsOpen bool,
  27. width int,
  28. ) string {
  29. if session == nil || session.ID == "" {
  30. return ""
  31. }
  32. t := com.Styles
  33. var b strings.Builder
  34. b.WriteString(t.Header.Charm.Render("Charm™"))
  35. b.WriteString(" ")
  36. b.WriteString(styles.ApplyBoldForegroundGrad(t, "CRUSH", t.Secondary, t.Primary))
  37. b.WriteString(" ")
  38. availDetailWidth := width - leftPadding - rightPadding - lipgloss.Width(b.String()) - minHeaderDiags
  39. details := renderHeaderDetails(com, session, lspClients, detailsOpen, availDetailWidth)
  40. remainingWidth := width -
  41. lipgloss.Width(b.String()) -
  42. lipgloss.Width(details) -
  43. leftPadding -
  44. rightPadding
  45. if remainingWidth > 0 {
  46. b.WriteString(t.Header.Diagonals.Render(
  47. strings.Repeat(headerDiag, max(minHeaderDiags, remainingWidth)),
  48. ))
  49. b.WriteString(" ")
  50. }
  51. b.WriteString(details)
  52. return t.Base.Padding(0, rightPadding, 0, leftPadding).Render(b.String())
  53. }
  54. // renderHeaderDetails renders the details section of the header.
  55. func renderHeaderDetails(
  56. com *common.Common,
  57. session *session.Session,
  58. lspClients *csync.Map[string, *lsp.Client],
  59. detailsOpen bool,
  60. availWidth int,
  61. ) string {
  62. t := com.Styles
  63. var parts []string
  64. errorCount := 0
  65. for l := range lspClients.Seq() {
  66. errorCount += l.GetDiagnosticCounts().Error
  67. }
  68. if errorCount > 0 {
  69. parts = append(parts, t.LSP.ErrorDiagnostic.Render(fmt.Sprintf("%s%d", styles.ErrorIcon, errorCount)))
  70. }
  71. agentCfg := config.Get().Agents[config.AgentCoder]
  72. model := config.Get().GetModelByType(agentCfg.Model)
  73. percentage := (float64(session.CompletionTokens+session.PromptTokens) / float64(model.ContextWindow)) * 100
  74. formattedPercentage := t.Header.Percentage.Render(fmt.Sprintf("%d%%", int(percentage)))
  75. parts = append(parts, formattedPercentage)
  76. const keystroke = "ctrl+d"
  77. if detailsOpen {
  78. parts = append(parts, t.Header.Keystroke.Render(keystroke)+t.Header.KeystrokeTip.Render(" close"))
  79. } else {
  80. parts = append(parts, t.Header.Keystroke.Render(keystroke)+t.Header.KeystrokeTip.Render(" open "))
  81. }
  82. dot := t.Header.Separator.Render(" • ")
  83. metadata := strings.Join(parts, dot)
  84. metadata = dot + metadata
  85. const dirTrimLimit = 4
  86. cfg := com.Config()
  87. cwd := fsext.DirTrim(fsext.PrettyPath(cfg.WorkingDir()), dirTrimLimit)
  88. cwd = ansi.Truncate(cwd, max(0, availWidth-lipgloss.Width(metadata)), "…")
  89. cwd = t.Header.WorkingDir.Render(cwd)
  90. return cwd + metadata
  91. }