Detailed changes
@@ -112,7 +112,7 @@ func testEnv(t *testing.T) fakeEnv {
require.NoError(t, err)
q := db.New(conn)
- sessions := session.NewService(q)
+ sessions := session.NewService(q, conn)
messages := message.NewService(q)
permissions := permission.NewPermissionService(workingDir, true, []string{})
@@ -73,7 +73,7 @@ type App struct {
// New initializes a new application instance.
func New(ctx context.Context, conn *sql.DB, cfg *config.Config) (*App, error) {
q := db.New(conn)
- sessions := session.NewService(q)
+ sessions := session.NewService(q, conn)
messages := message.NewService(q)
files := history.NewService(q, conn)
skipPermissionsRequests := cfg.Permissions != nil && cfg.Permissions.SkipRequests
@@ -61,7 +61,8 @@ type Service interface {
type service struct {
*pubsub.Broker[Session]
- q db.Querier
+ db *sql.DB
+ q *db.Queries
}
func (s *service) Create(ctx context.Context, title string) (Session, error) {
@@ -107,14 +108,32 @@ func (s *service) CreateTitleSession(ctx context.Context, parentSessionID string
}
func (s *service) Delete(ctx context.Context, id string) error {
- session, err := s.Get(ctx, id)
+ tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
- return err
+ return fmt.Errorf("beginning transaction: %w", err)
}
- err = s.q.DeleteSession(ctx, session.ID)
+ defer tx.Rollback() //nolint:errcheck
+
+ qtx := s.q.WithTx(tx)
+
+ dbSession, err := qtx.GetSessionByID(ctx, id)
if err != nil {
return err
}
+ if err = qtx.DeleteSessionMessages(ctx, dbSession.ID); err != nil {
+ return fmt.Errorf("deleting session messages: %w", err)
+ }
+ if err = qtx.DeleteSessionFiles(ctx, dbSession.ID); err != nil {
+ return fmt.Errorf("deleting session files: %w", err)
+ }
+ if err = qtx.DeleteSession(ctx, dbSession.ID); err != nil {
+ return fmt.Errorf("deleting session: %w", err)
+ }
+ if err = tx.Commit(); err != nil {
+ return fmt.Errorf("committing transaction: %w", err)
+ }
+
+ session := s.fromDBItem(dbSession)
s.Publish(pubsub.DeletedEvent, session)
event.SessionDeleted()
return nil
@@ -223,11 +242,12 @@ func unmarshalTodos(data string) ([]Todo, error) {
return todos, nil
}
-func NewService(q db.Querier) Service {
+func NewService(q *db.Queries, conn *sql.DB) Service {
broker := pubsub.NewBroker[Session]()
return &service{
- broker,
- q,
+ Broker: broker,
+ db: conn,
+ q: q,
}
}
@@ -177,13 +177,13 @@ func Section(t *styles.Styles, text string, width int, info ...string) string {
// DialogTitle renders a dialog title with a decorative line filling the
// remaining width.
-func DialogTitle(t *styles.Styles, title string, width int) string {
+func DialogTitle(t *styles.Styles, title string, width int, fromColor, toColor color.Color) string {
char := "╱"
length := lipgloss.Width(title) + 1
remainingWidth := width - length
if remainingWidth > 0 {
lines := strings.Repeat(char, remainingWidth)
- lines = styles.ApplyForegroundGrad(t, lines, t.Primary, t.Secondary)
+ lines = styles.ApplyForegroundGrad(t, lines, fromColor, toColor)
title = title + " " + lines
}
return title
@@ -207,7 +207,7 @@ func (m *APIKeyInput) headerView() string {
return textStyle.Render(m.dialogTitle())
}
headerOffset := titleStyle.GetHorizontalFrameSize() + dialogStyle.GetHorizontalFrameSize()
- return common.DialogTitle(t, titleStyle.Render(m.dialogTitle()), m.width-headerOffset)
+ return common.DialogTitle(t, titleStyle.Render(m.dialogTitle()), m.width-headerOffset, m.com.Styles.Primary, m.com.Styles.Secondary)
}
func (m *APIKeyInput) dialogTitle() string {
@@ -316,7 +316,7 @@ func (a *Arguments) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
titleText = "Arguments"
}
- header := common.DialogTitle(s, titleText, width)
+ header := common.DialogTitle(s, titleText, width, s.Primary, s.Secondary)
// Add description if available.
var description string
@@ -387,7 +387,7 @@ func (c *Commands) setCommandItems(commandType CommandType) {
func (c *Commands) defaultCommands() []*CommandItem {
commands := []*CommandItem{
NewCommandItem(c.com.Styles, "new_session", "New Session", "ctrl+n", ActionNewSession{}),
- NewCommandItem(c.com.Styles, "switch_session", "Switch Session", "ctrl+s", ActionOpenDialog{SessionsID}),
+ NewCommandItem(c.com.Styles, "switch_session", "Sessions", "ctrl+s", ActionOpenDialog{SessionsID}),
NewCommandItem(c.com.Styles, "switch_model", "Switch Model", "ctrl+l", ActionOpenDialog{ModelsID}),
}
@@ -66,5 +66,11 @@ func (c *CommandItem) Shortcut() string {
// Render implements ListItem.
func (c *CommandItem) Render(width int) string {
- return renderItem(c.t, c.title, c.shortcut, c.focused, width, c.cache, &c.m)
+ styles := ListIemStyles{
+ ItemBlurred: c.t.Dialog.NormalItem,
+ ItemFocused: c.t.Dialog.SelectedItem,
+ InfoTextBlurred: c.t.Base,
+ InfoTextFocused: c.t.Subtle,
+ }
+ return renderItem(styles, c.title, c.shortcut, c.focused, width, c.cache, &c.m)
}
@@ -1,6 +1,7 @@
package dialog
import (
+ "image/color"
"strings"
tea "charm.land/bubbletea/v2"
@@ -42,6 +43,14 @@ func InputCursor(t *styles.Styles, cur *tea.Cursor) *tea.Cursor {
type RenderContext struct {
// Styles is the styles to use for rendering.
Styles *styles.Styles
+ // TitleStyle is the style of the dialog title by default it uses Styles.Dialog.Title
+ TitleStyle lipgloss.Style
+ // ViewStyle is the style of the dialog title by default it uses Styles.Dialog.View
+ ViewStyle lipgloss.Style
+ // TitleGradientFromColor is the color the title gradient starts by defaults its Style.Primary
+ TitleGradientFromColor color.Color
+ // TitleGradientToColor is the color the title gradient starts by defaults its Style.Secondary
+ TitleGradientToColor color.Color
// Width is the total width of the dialog including any margins, borders,
// and paddings.
Width int
@@ -68,9 +77,13 @@ type RenderContext struct {
// NewRenderContext creates a new RenderContext with the provided styles and width.
func NewRenderContext(t *styles.Styles, width int) *RenderContext {
return &RenderContext{
- Styles: t,
- Width: width,
- Parts: []string{},
+ Styles: t,
+ TitleStyle: t.Dialog.Title,
+ ViewStyle: t.Dialog.View,
+ TitleGradientFromColor: t.Primary,
+ TitleGradientToColor: t.Secondary,
+ Width: width,
+ Parts: []string{},
}
}
@@ -83,8 +96,8 @@ func (rc *RenderContext) AddPart(part string) {
// Render renders the dialog using the provided context.
func (rc *RenderContext) Render() string {
- titleStyle := rc.Styles.Dialog.Title
- dialogStyle := rc.Styles.Dialog.View.Width(rc.Width)
+ titleStyle := rc.TitleStyle
+ dialogStyle := rc.ViewStyle.Width(rc.Width)
var parts []string
@@ -96,7 +109,7 @@ func (rc *RenderContext) Render() string {
title := common.DialogTitle(rc.Styles, rc.Title,
max(0, rc.Width-dialogStyle.GetHorizontalFrameSize()-
titleStyle.GetHorizontalFrameSize()-
- titleInfoWidth))
+ titleInfoWidth), rc.TitleGradientFromColor, rc.TitleGradientToColor)
if len(rc.TitleInfo) > 0 {
title += rc.TitleInfo
}
@@ -106,7 +106,13 @@ func (m *ModelItem) Render(width int) string {
if m.showProvider {
providerInfo = string(m.prov.Name)
}
- return renderItem(m.t, m.model.Name, providerInfo, m.focused, width, m.cache, &m.m)
+ styles := ListIemStyles{
+ ItemBlurred: m.t.Dialog.NormalItem,
+ ItemFocused: m.t.Dialog.SelectedItem,
+ InfoTextBlurred: m.t.Base,
+ InfoTextFocused: m.t.Subtle,
+ }
+ return renderItem(styles, m.model.Name, providerInfo, m.focused, width, m.cache, &m.m)
}
// SetFocused implements ListItem.
@@ -227,7 +227,7 @@ func (m *OAuth) headerContent() string {
if m.isOnboarding {
return textStyle.Render(dialogTitle)
}
- return common.DialogTitle(t, titleStyle.Render(dialogTitle), m.width-headerOffset)
+ return common.DialogTitle(t, titleStyle.Render(dialogTitle), m.width-headerOffset, t.Primary, t.Secondary)
}
func (m *OAuth) innerDialogContent() string {
@@ -410,7 +410,7 @@ func (p *Permissions) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
func (p *Permissions) renderHeader(contentWidth int) string {
t := p.com.Styles
- title := common.DialogTitle(t, "Permission Required", contentWidth-t.Dialog.Title.GetHorizontalFrameSize())
+ title := common.DialogTitle(t, "Permission Required", contentWidth-t.Dialog.Title.GetHorizontalFrameSize(), t.Primary, t.Secondary)
title = t.Dialog.Title.Render(title)
// Tool info.
@@ -293,5 +293,11 @@ func (r *ReasoningItem) Render(width int) string {
if r.isCurrent {
info = "current"
}
- return renderItem(r.t, r.title, info, r.focused, width, r.cache, &r.m)
+ styles := ListIemStyles{
+ ItemBlurred: r.t.Dialog.NormalItem,
+ ItemFocused: r.t.Dialog.SelectedItem,
+ InfoTextBlurred: r.t.Base,
+ InfoTextFocused: r.t.Subtle,
+ }
+ return renderItem(styles, r.title, info, r.focused, width, r.cache, &r.m)
}
@@ -7,14 +7,25 @@ import (
"charm.land/bubbles/v2/key"
"charm.land/bubbles/v2/textinput"
tea "charm.land/bubbletea/v2"
+ "github.com/charmbracelet/crush/internal/session"
"github.com/charmbracelet/crush/internal/ui/common"
"github.com/charmbracelet/crush/internal/ui/list"
+ "github.com/charmbracelet/crush/internal/uiutil"
uv "github.com/charmbracelet/ultraviolet"
)
// SessionsID is the identifier for the session selector dialog.
const SessionsID = "session"
+type sessionsMode uint8
+
+// Possible modes a session item can be in
+const (
+ sessionsModeNormal sessionsMode = iota
+ sessionsModeDeleting
+ sessionsModeUpdating
+)
+
// Session is a session selector dialog.
type Session struct {
com *common.Common
@@ -22,13 +33,19 @@ type Session struct {
list *list.FilterableList
input textinput.Model
selectedSessionInx int
+ sessions []session.Session
+
+ sessionsMode sessionsMode
keyMap struct {
- Select key.Binding
- Next key.Binding
- Previous key.Binding
- UpDown key.Binding
- Close key.Binding
+ Select key.Binding
+ Next key.Binding
+ Previous key.Binding
+ UpDown key.Binding
+ Delete key.Binding
+ ConfirmDelete key.Binding
+ CancelDelete key.Binding
+ Close key.Binding
}
}
@@ -37,12 +54,14 @@ var _ Dialog = (*Session)(nil)
// NewSessions creates a new Session dialog.
func NewSessions(com *common.Common, selectedSessionID string) (*Session, error) {
s := new(Session)
+ s.sessionsMode = sessionsModeNormal
s.com = com
sessions, err := com.App.Sessions.List(context.TODO())
if err != nil {
return nil, err
}
+ s.sessions = sessions
for i, sess := range sessions {
if sess.ID == selectedSessionID {
s.selectedSessionInx = i
@@ -54,7 +73,7 @@ func NewSessions(com *common.Common, selectedSessionID string) (*Session, error)
help.Styles = com.Styles.DialogHelpStyles()
s.help = help
- s.list = list.NewFilterableList(sessionItems(com.Styles, sessions...)...)
+ s.list = list.NewFilterableList(sessionItems(com.Styles, sessionsModeNormal, sessions...)...)
s.list.Focus()
s.list.SetSelected(s.selectedSessionInx)
@@ -80,6 +99,18 @@ func NewSessions(com *common.Common, selectedSessionID string) (*Session, error)
key.WithKeys("up", "down"),
key.WithHelp("↑↓", "choose"),
)
+ s.keyMap.Delete = key.NewBinding(
+ key.WithKeys("ctrl+x"),
+ key.WithHelp("ctrl+x", "delete"),
+ )
+ s.keyMap.ConfirmDelete = key.NewBinding(
+ key.WithKeys("y"),
+ key.WithHelp("y", "delete"),
+ )
+ s.keyMap.CancelDelete = key.NewBinding(
+ key.WithKeys("n", "esc"),
+ key.WithHelp("n", "cancel"),
+ )
s.keyMap.Close = CloseKey
return s, nil
@@ -94,40 +125,57 @@ func (s *Session) ID() string {
func (s *Session) HandleMsg(msg tea.Msg) Action {
switch msg := msg.(type) {
case tea.KeyPressMsg:
- switch {
- case key.Matches(msg, s.keyMap.Close):
- return ActionClose{}
- case key.Matches(msg, s.keyMap.Previous):
- s.list.Focus()
- if s.list.IsSelectedFirst() {
- s.list.SelectLast()
- s.list.ScrollToBottom()
- break
+ switch s.sessionsMode {
+ case sessionsModeDeleting:
+ switch {
+ case key.Matches(msg, s.keyMap.ConfirmDelete):
+ return s.confirmDeleteSession()
+ case key.Matches(msg, s.keyMap.CancelDelete):
+ s.sessionsMode = sessionsModeNormal
+ s.list.SetItems(sessionItems(s.com.Styles, sessionsModeNormal, s.sessions...)...)
}
- s.list.SelectPrev()
- s.list.ScrollToSelected()
- case key.Matches(msg, s.keyMap.Next):
- s.list.Focus()
- if s.list.IsSelectedLast() {
- s.list.SelectFirst()
+ default:
+ switch {
+ case key.Matches(msg, s.keyMap.Close):
+ return ActionClose{}
+ case key.Matches(msg, s.keyMap.Delete):
+ if s.isCurrentSessionBusy() {
+ return ActionCmd{uiutil.ReportWarn("Agent is busy, please wait...")}
+ }
+ s.sessionsMode = sessionsModeDeleting
+ s.list.SetItems(sessionItems(s.com.Styles, sessionsModeDeleting, s.sessions...)...)
+ case key.Matches(msg, s.keyMap.Previous):
+ s.list.Focus()
+ if s.list.IsSelectedFirst() {
+ s.list.SelectLast()
+ s.list.ScrollToBottom()
+ break
+ }
+ s.list.SelectPrev()
+ s.list.ScrollToSelected()
+ case key.Matches(msg, s.keyMap.Next):
+ s.list.Focus()
+ if s.list.IsSelectedLast() {
+ s.list.SelectFirst()
+ s.list.ScrollToTop()
+ break
+ }
+ s.list.SelectNext()
+ s.list.ScrollToSelected()
+ case key.Matches(msg, s.keyMap.Select):
+ if item := s.list.SelectedItem(); item != nil {
+ sessionItem := item.(*SessionItem)
+ return ActionSelectSession{sessionItem.Session}
+ }
+ default:
+ var cmd tea.Cmd
+ s.input, cmd = s.input.Update(msg)
+ value := s.input.Value()
+ s.list.SetFilter(value)
s.list.ScrollToTop()
- break
+ s.list.SetSelected(0)
+ return ActionCmd{cmd}
}
- s.list.SelectNext()
- s.list.ScrollToSelected()
- case key.Matches(msg, s.keyMap.Select):
- if item := s.list.SelectedItem(); item != nil {
- sessionItem := item.(*SessionItem)
- return ActionSelectSession{sessionItem.Session}
- }
- default:
- var cmd tea.Cmd
- s.input, cmd = s.input.Update(msg)
- value := s.input.Value()
- s.list.SetFilter(value)
- s.list.ScrollToTop()
- s.list.SetSelected(0)
- return ActionCmd{cmd}
}
}
return nil
@@ -160,27 +208,101 @@ func (s *Session) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
s.list.ScrollToSelected()
}
+ var cur *tea.Cursor
rc := NewRenderContext(t, width)
- rc.Title = "Switch Session"
- inputView := t.Dialog.InputPrompt.Render(s.input.View())
- rc.AddPart(inputView)
+ rc.Title = "Sessions"
+ switch s.sessionsMode {
+ case sessionsModeDeleting:
+ rc.TitleStyle = t.Dialog.Sessions.DeletingTitle
+ rc.TitleGradientFromColor = t.Dialog.Sessions.DeletingTitleGradientFromColor
+ rc.TitleGradientToColor = t.Dialog.Sessions.DeletingTitleGradientToColor
+ rc.ViewStyle = t.Dialog.Sessions.DeletingView
+ rc.AddPart(t.Dialog.Sessions.DeletingMessage.Render("Delete this session?"))
+ default:
+ inputView := t.Dialog.InputPrompt.Render(s.input.View())
+ cur = s.Cursor()
+ rc.AddPart(inputView)
+ }
listView := t.Dialog.List.Height(s.list.Height()).Render(s.list.Render())
rc.AddPart(listView)
rc.Help = s.help.View(s)
view := rc.Render()
- cur := s.Cursor()
DrawCenterCursor(scr, area, view, cur)
return cur
}
+func (s *Session) selectedSessionItem() *SessionItem {
+ if item := s.list.SelectedItem(); item != nil {
+ return item.(*SessionItem)
+ }
+ return nil
+}
+
+func (s *Session) confirmDeleteSession() Action {
+ sessionItem := s.selectedSessionItem()
+ s.sessionsMode = sessionsModeNormal
+ if sessionItem == nil {
+ return nil
+ }
+
+ s.removeSession(sessionItem.ID())
+ return ActionCmd{s.deleteSessionCmd(sessionItem.ID())}
+}
+
+func (s *Session) removeSession(id string) {
+ var newSessions []session.Session
+ for _, sess := range s.sessions {
+ if sess.ID == id {
+ continue
+ }
+ newSessions = append(newSessions, sess)
+ }
+ s.sessions = newSessions
+ s.list.SetItems(sessionItems(s.com.Styles, sessionsModeNormal, s.sessions...)...)
+ s.list.SelectFirst()
+ s.list.ScrollToSelected()
+}
+
+func (s *Session) deleteSessionCmd(id string) tea.Cmd {
+ return func() tea.Msg {
+ err := s.com.App.Sessions.Delete(context.TODO(), id)
+ if err != nil {
+ return uiutil.NewErrorMsg(err)
+ }
+ return nil
+ }
+}
+
+func (s *Session) isCurrentSessionBusy() bool {
+ sessionItem := s.selectedSessionItem()
+ if sessionItem == nil {
+ return false
+ }
+
+ if s.com.App.AgentCoordinator == nil {
+ return false
+ }
+
+ return s.com.App.AgentCoordinator.IsSessionBusy(sessionItem.ID())
+}
+
// ShortHelp implements [help.KeyMap].
func (s *Session) ShortHelp() []key.Binding {
- return []key.Binding{
- s.keyMap.UpDown,
- s.keyMap.Select,
- s.keyMap.Close,
+ switch s.sessionsMode {
+ case sessionsModeDeleting:
+ return []key.Binding{
+ s.keyMap.ConfirmDelete,
+ s.keyMap.CancelDelete,
+ }
+ default:
+ return []key.Binding{
+ s.keyMap.UpDown,
+ s.keyMap.Select,
+ s.keyMap.Delete,
+ s.keyMap.Close,
+ }
}
}
@@ -191,8 +313,17 @@ func (s *Session) FullHelp() [][]key.Binding {
s.keyMap.Select,
s.keyMap.Next,
s.keyMap.Previous,
+ s.keyMap.Delete,
s.keyMap.Close,
}
+
+ switch s.sessionsMode {
+ case sessionsModeDeleting:
+ slice = []key.Binding{
+ s.keyMap.ConfirmDelete,
+ s.keyMap.CancelDelete,
+ }
+ }
for i := 0; i < len(slice); i += 4 {
end := min(i+4, len(slice))
m = append(m, slice[i:end])
@@ -28,10 +28,11 @@ type ListItem interface {
// SessionItem wraps a [session.Session] to implement the [ListItem] interface.
type SessionItem struct {
session.Session
- t *styles.Styles
- m fuzzy.Match
- cache map[int]string
- focused bool
+ t *styles.Styles
+ sessionsMode sessionsMode
+ m fuzzy.Match
+ cache map[int]string
+ focused bool
}
var _ ListItem = &SessionItem{}
@@ -55,10 +56,29 @@ func (s *SessionItem) SetMatch(m fuzzy.Match) {
// Render returns the string representation of the session item.
func (s *SessionItem) Render(width int) string {
info := humanize.Time(time.Unix(s.UpdatedAt, 0))
- return renderItem(s.t, s.Title, info, s.focused, width, s.cache, &s.m)
+ styles := ListIemStyles{
+ ItemBlurred: s.t.Dialog.NormalItem,
+ ItemFocused: s.t.Dialog.SelectedItem,
+ InfoTextBlurred: s.t.Subtle,
+ InfoTextFocused: s.t.Base,
+ }
+
+ switch s.sessionsMode {
+ case sessionsModeDeleting:
+ styles.ItemBlurred = s.t.Dialog.Sessions.DeletingItemBlurred
+ styles.ItemFocused = s.t.Dialog.Sessions.DeletingItemFocused
+ }
+ return renderItem(styles, s.Title, info, s.focused, width, s.cache, &s.m)
+}
+
+type ListIemStyles struct {
+ ItemBlurred lipgloss.Style
+ ItemFocused lipgloss.Style
+ InfoTextBlurred lipgloss.Style
+ InfoTextFocused lipgloss.Style
}
-func renderItem(t *styles.Styles, title string, info string, focused bool, width int, cache map[int]string, m *fuzzy.Match) string {
+func renderItem(t ListIemStyles, title string, info string, focused bool, width int, cache map[int]string, m *fuzzy.Match) string {
if cache == nil {
cache = make(map[int]string)
}
@@ -68,9 +88,9 @@ func renderItem(t *styles.Styles, title string, info string, focused bool, width
return cached
}
- style := t.Dialog.NormalItem
+ style := t.ItemBlurred
if focused {
- style = t.Dialog.SelectedItem
+ style = t.ItemFocused
}
var infoText string
@@ -79,9 +99,9 @@ func renderItem(t *styles.Styles, title string, info string, focused bool, width
if len(info) > 0 {
infoText = fmt.Sprintf(" %s ", info)
if focused {
- infoText = t.Base.Render(infoText)
+ infoText = t.InfoTextFocused.Render(infoText)
} else {
- infoText = t.Subtle.Render(infoText)
+ infoText = t.InfoTextBlurred.Render(infoText)
}
infoWidth = lipgloss.Width(infoText)
@@ -134,10 +154,10 @@ func (s *SessionItem) SetFocused(focused bool) {
// sessionItems takes a slice of [session.Session]s and convert them to a slice
// of [ListItem]s.
-func sessionItems(t *styles.Styles, sessions ...session.Session) []list.FilterableItem {
+func sessionItems(t *styles.Styles, mode sessionsMode, sessions ...session.Session) []list.FilterableItem {
items := make([]list.FilterableItem, len(sessions))
for i, s := range sessions {
- items[i] = &SessionItem{Session: s, t: t}
+ items[i] = &SessionItem{Session: s, t: t, sessionsMode: mode}
}
return items
}
@@ -412,6 +412,12 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.dialog.CloseFrontDialog()
case pubsub.Event[session.Session]:
+ if msg.Type == pubsub.DeletedEvent {
+ if m.session != nil && m.session.ID == msg.Payload.ID {
+ m.newSession()
+ }
+ break
+ }
if m.session != nil && msg.Payload.ID == m.session.ID {
prevHasInProgress := hasInProgressTodo(m.session.Todos)
m.session = &msg.Payload
@@ -338,6 +338,7 @@ type Styles struct {
FullDesc lipgloss.Style
FullSeparator lipgloss.Style
}
+
NormalItem lipgloss.Style
SelectedItem lipgloss.Style
InputPrompt lipgloss.Style
@@ -366,6 +367,16 @@ type Styles struct {
Commands struct{}
ImagePreview lipgloss.Style
+
+ Sessions struct {
+ DeletingView lipgloss.Style
+ DeletingItemFocused lipgloss.Style
+ DeletingItemBlurred lipgloss.Style
+ DeletingTitle lipgloss.Style
+ DeletingMessage lipgloss.Style
+ DeletingTitleGradientFromColor color.Color
+ DeletingTitleGradientToColor color.Color
+ }
}
// Status bar and help
@@ -1268,6 +1279,14 @@ func DefaultStyles() Styles {
s.Dialog.Arguments.InputRequiredMarkBlurred = base.Foreground(fgMuted).SetString("*")
s.Dialog.Arguments.InputRequiredMarkFocused = base.Foreground(primary).Bold(true).SetString("*")
+ s.Dialog.Sessions.DeletingTitle = s.Dialog.Title.Foreground(red)
+ s.Dialog.Sessions.DeletingView = s.Dialog.View.BorderForeground(red)
+ s.Dialog.Sessions.DeletingMessage = s.Base.Padding(1)
+ s.Dialog.Sessions.DeletingTitleGradientFromColor = red
+ s.Dialog.Sessions.DeletingTitleGradientToColor = s.Primary
+ s.Dialog.Sessions.DeletingItemBlurred = s.Dialog.NormalItem.Foreground(fgSubtle)
+ s.Dialog.Sessions.DeletingItemFocused = s.Dialog.SelectedItem.Background(red)
+
s.Status.Help = lipgloss.NewStyle().Padding(0, 1)
s.Status.SuccessIndicator = base.Foreground(bgSubtle).Background(green).Padding(0, 1).Bold(true).SetString("OKAY!")
s.Status.InfoIndicator = s.Status.SuccessIndicator