Detailed changes
@@ -9,7 +9,6 @@ import (
"charm.land/bubbles/v2/spinner"
"charm.land/bubbles/v2/textinput"
tea "charm.land/bubbletea/v2"
- "charm.land/lipgloss/v2"
"github.com/charmbracelet/catwalk/pkg/catwalk"
"github.com/charmbracelet/crush/internal/commands"
"github.com/charmbracelet/crush/internal/config"
@@ -17,7 +16,6 @@ import (
"github.com/charmbracelet/crush/internal/ui/list"
"github.com/charmbracelet/crush/internal/ui/styles"
uv "github.com/charmbracelet/ultraviolet"
- "github.com/charmbracelet/x/ansi"
)
// CommandsID is the identifier for the commands dialog.
@@ -247,6 +245,7 @@ func (c *Commands) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
// we need to reset the command items when width changes
c.setCommandItems(c.selected)
}
+
innerWidth := width - c.com.Styles.Dialog.View.GetHorizontalFrameSize()
heightOffset := t.Dialog.Title.GetVerticalFrameSize() + titleContentHeight +
t.Dialog.InputPrompt.GetVerticalFrameSize() + inputContentHeight +
@@ -257,18 +256,20 @@ func (c *Commands) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
c.list.SetSize(innerWidth, height-heightOffset)
c.help.SetWidth(innerWidth)
- radio := commandsRadioView(t, c.selected, len(c.customCommands) > 0, len(c.mcpPrompts) > 0)
- titleStyle := t.Dialog.Title
- dialogStyle := t.Dialog.View.Width(width)
- headerOffset := lipgloss.Width(radio) + titleStyle.GetHorizontalFrameSize() + dialogStyle.GetHorizontalFrameSize()
- helpView := ansi.Truncate(c.help.View(c), innerWidth, "")
- header := common.DialogTitle(t, "Commands", width-headerOffset) + radio
+ rc := NewRenderContext(t, width)
+ rc.Title = "Commands"
+ rc.TitleInfo = commandsRadioView(t, c.selected, len(c.customCommands) > 0, len(c.mcpPrompts) > 0)
+ inputView := t.Dialog.InputPrompt.Render(c.input.View())
+ rc.AddPart(inputView)
+ listView := t.Dialog.List.Height(c.list.Height()).Render(c.list.Render())
+ rc.AddPart(listView)
+ rc.Help = c.help.View(c)
if c.loading {
- helpView = t.Dialog.HelpView.Width(width).Render(c.spinner.View() + " Generating Prompt...")
+ rc.Help = c.spinner.View() + " Generating Prompt..."
}
- view := HeaderInputListHelpView(t, width, c.list.Height(), header,
- c.input.View(), c.list.Render(), helpView)
+
+ view := rc.Render()
cur := c.Cursor()
DrawCenterCursor(scr, area, view, cur)
@@ -4,8 +4,10 @@ import (
"strings"
tea "charm.land/bubbletea/v2"
+ "charm.land/lipgloss/v2"
"github.com/charmbracelet/crush/internal/ui/common"
"github.com/charmbracelet/crush/internal/ui/styles"
+ "github.com/charmbracelet/x/ansi"
)
// InputCursor adjusts the cursor position for an input field within a dialog.
@@ -43,9 +45,15 @@ type RenderContext struct {
// Width is the total width of the dialog including any margins, borders,
// and paddings.
Width int
+ // Gap is the gap between content parts. Zero means no gap.
+ Gap int
// Title is the title of the dialog. This will be styled using the default
// dialog title style and prepended to the content parts slice.
Title string
+ // TitleInfo is additional information to display next to the title. This
+ // part is displayed as is, any styling must be applied before setting this
+ // field.
+ TitleInfo string
// Parts are the rendered parts of the dialog.
Parts []string
// Help is the help view content. This will be appended to the content parts
@@ -76,55 +84,47 @@ func (rc *RenderContext) Render() string {
parts := []string{}
if len(rc.Title) > 0 {
+ var titleInfoWidth int
+ if len(rc.TitleInfo) > 0 {
+ titleInfoWidth = lipgloss.Width(rc.TitleInfo)
+ }
title := common.DialogTitle(rc.Styles, rc.Title,
max(0, rc.Width-dialogStyle.GetHorizontalFrameSize()-
- titleStyle.GetHorizontalFrameSize()))
- parts = append(parts, titleStyle.Render(title), "")
+ titleStyle.GetHorizontalFrameSize()-
+ titleInfoWidth))
+ if len(rc.TitleInfo) > 0 {
+ title += rc.TitleInfo
+ }
+ parts = append(parts, titleStyle.Render(title))
+ if rc.Gap > 0 {
+ parts = append(parts, make([]string, rc.Gap)...)
+ }
}
- for i, p := range rc.Parts {
- if len(p) > 0 {
- parts = append(parts, p)
- }
- if i < len(rc.Parts)-1 {
- parts = append(parts, "")
+ if rc.Gap <= 0 {
+ parts = append(parts, rc.Parts...)
+ } else {
+ for i, p := range rc.Parts {
+ if len(p) > 0 {
+ parts = append(parts, p)
+ }
+ if i < len(rc.Parts)-1 {
+ parts = append(parts, make([]string, rc.Gap)...)
+ }
}
}
if len(rc.Help) > 0 {
- parts = append(parts, "")
+ if rc.Gap > 0 {
+ parts = append(parts, make([]string, rc.Gap)...)
+ }
helpStyle := rc.Styles.Dialog.HelpView
helpStyle = helpStyle.Width(rc.Width - dialogStyle.GetHorizontalFrameSize())
- parts = append(parts, helpStyle.Render(rc.Help))
+ helpView := ansi.Truncate(helpStyle.Render(rc.Help), rc.Width, "")
+ parts = append(parts, helpView)
}
content := strings.Join(parts, "\n")
return dialogStyle.Render(content)
}
-
-// HeaderInputListHelpView generates a view for dialogs with a header, input,
-// list, and help sections.
-func HeaderInputListHelpView(t *styles.Styles, width, listHeight int, header, input, list, help string) string {
- rc := NewRenderContext(t, width)
-
- titleStyle := t.Dialog.Title
- inputStyle := t.Dialog.InputPrompt
- listStyle := t.Dialog.List.Height(listHeight)
- listContent := listStyle.Render(list)
-
- if len(header) > 0 {
- rc.AddPart(titleStyle.Render(header))
- }
- if len(input) > 0 {
- rc.AddPart(inputStyle.Render(input))
- }
- if len(list) > 0 {
- rc.AddPart(listContent)
- }
- if len(help) > 0 {
- rc.Help = help
- }
-
- return rc.Render()
-}
@@ -234,6 +234,7 @@ func (f *FilePicker) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
t := f.com.Styles
rc := NewRenderContext(t, width)
+ rc.Gap = 1
rc.Title = "Add Image"
rc.Help = f.help.View(f)
@@ -10,13 +10,11 @@ import (
"charm.land/bubbles/v2/key"
"charm.land/bubbles/v2/textinput"
tea "charm.land/bubbletea/v2"
- "charm.land/lipgloss/v2"
"github.com/charmbracelet/catwalk/pkg/catwalk"
"github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/ui/common"
"github.com/charmbracelet/crush/internal/uiutil"
uv "github.com/charmbracelet/ultraviolet"
- "github.com/charmbracelet/x/ansi"
)
// ModelType represents the type of model to select.
@@ -253,19 +251,16 @@ func (m *Models) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
m.list.SetSize(innerWidth, height-heightOffset)
m.help.SetWidth(innerWidth)
- titleStyle := t.Dialog.Title
- dialogStyle := t.Dialog.View
+ rc := NewRenderContext(t, width)
+ rc.Title = "Switch Model"
+ rc.TitleInfo = m.modelTypeRadioView()
+ inputView := t.Dialog.InputPrompt.Render(m.input.View())
+ rc.AddPart(inputView)
+ listView := t.Dialog.List.Height(m.list.Height()).Render(m.list.Render())
+ rc.AddPart(listView)
+ rc.Help = m.help.View(m)
- radios := m.modelTypeRadioView()
-
- headerOffset := lipgloss.Width(radios) + titleStyle.GetHorizontalFrameSize() +
- dialogStyle.GetHorizontalFrameSize()
-
- header := common.DialogTitle(t, "Switch Model", width-headerOffset) + radios
-
- helpView := ansi.Truncate(m.help.View(m), innerWidth, "")
- view := HeaderInputListHelpView(t, width, m.list.Height(), header,
- m.input.View(), m.list.Render(), helpView)
+ view := rc.Render()
cur := m.Cursor()
DrawCenterCursor(scr, area, view, cur)
@@ -10,7 +10,6 @@ import (
"github.com/charmbracelet/crush/internal/ui/common"
"github.com/charmbracelet/crush/internal/ui/list"
uv "github.com/charmbracelet/ultraviolet"
- "github.com/charmbracelet/x/ansi"
)
// SessionsID is the identifier for the session selector dialog.
@@ -154,15 +153,15 @@ func (s *Session) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
s.list.SetSize(innerWidth, height-heightOffset)
s.help.SetWidth(innerWidth)
- titleStyle := s.com.Styles.Dialog.Title
- dialogStyle := s.com.Styles.Dialog.View.Width(width)
- header := common.DialogTitle(s.com.Styles, "Switch Session",
- max(0, width-dialogStyle.GetHorizontalFrameSize()-
- titleStyle.GetHorizontalFrameSize()))
+ rc := NewRenderContext(t, width)
+ rc.Title = "Switch Session"
+ inputView := t.Dialog.InputPrompt.Render(s.input.View())
+ rc.AddPart(inputView)
+ listView := t.Dialog.List.Height(s.list.Height()).Render(s.list.Render())
+ rc.AddPart(listView)
+ rc.Help = s.help.View(s)
- helpView := ansi.Truncate(s.help.View(s), innerWidth, "")
- view := HeaderInputListHelpView(s.com.Styles, width, s.list.Height(), header,
- s.input.View(), s.list.Render(), helpView)
+ view := rc.Render()
cur := s.Cursor()
DrawCenterCursor(scr, area, view, cur)