1package repo23import (4 "fmt"56 "charm.land/bubbles/v2/key"7 "charm.land/bubbles/v2/spinner"8 tea "charm.land/bubbletea/v2"9 "charm.land/lipgloss/v2"10 gitm "github.com/aymanbagabas/git-module"11 "github.com/charmbracelet/soft-serve/git"12 "github.com/charmbracelet/soft-serve/pkg/proto"13 "github.com/charmbracelet/soft-serve/pkg/ui/common"14 "github.com/charmbracelet/soft-serve/pkg/ui/components/code"15 "github.com/charmbracelet/soft-serve/pkg/ui/components/selector"16)1718type stashState int1920const (21 stashStateLoading stashState = iota22 stashStateList23 stashStatePatch24)2526// StashListMsg is a message sent when the stash list is loaded.27type StashListMsg []*gitm.Stash2829// StashPatchMsg is a message sent when the stash patch is loaded.30type StashPatchMsg struct{ *git.Diff }3132// Stash is the stash component page.33type Stash struct {34 common common.Common35 code *code.Code36 ref RefMsg37 repo proto.Repository38 spinner spinner.Model39 list *selector.Selector40 state stashState41 currentPatch StashPatchMsg42}4344// NewStash creates a new stash model.45func NewStash(common common.Common) *Stash {46 code := code.New(common, "", "")47 s := spinner.New(spinner.WithSpinner(spinner.Dot),48 spinner.WithStyle(common.Styles.Spinner))49 selector := selector.New(common, []selector.IdentifiableItem{}, StashItemDelegate{&common})50 selector.SetShowFilter(false)51 selector.SetShowHelp(false)52 selector.SetShowPagination(false)53 selector.SetShowStatusBar(false)54 selector.SetShowTitle(false)55 selector.SetFilteringEnabled(false)56 selector.DisableQuitKeybindings()57 selector.KeyMap.NextPage = common.KeyMap.NextPage58 selector.KeyMap.PrevPage = common.KeyMap.PrevPage59 return &Stash{60 code: code,61 common: common,62 spinner: s,63 list: selector,64 }65}6667// Path implements common.TabComponent.68func (s *Stash) Path() string {69 return ""70}7172// TabName returns the name of the tab.73func (s *Stash) TabName() string {74 return "Stash"75}7677// SetSize implements common.Component.78func (s *Stash) SetSize(width, height int) {79 s.common.SetSize(width, height)80 s.code.SetSize(width, height)81 s.list.SetSize(width, height)82}8384// ShortHelp implements help.KeyMap.85func (s *Stash) ShortHelp() []key.Binding {86 return []key.Binding{87 s.common.KeyMap.Select,88 s.common.KeyMap.Back,89 s.common.KeyMap.UpDown,90 }91}9293// FullHelp implements help.KeyMap.94func (s *Stash) FullHelp() [][]key.Binding {95 b := [][]key.Binding{96 {97 s.common.KeyMap.Select,98 s.common.KeyMap.Back,99 s.common.KeyMap.Copy,100 },101 {102 s.code.KeyMap.Down,103 s.code.KeyMap.Up,104 s.common.KeyMap.GotoTop,105 s.common.KeyMap.GotoBottom,106 },107 }108 return b109}110111// StatusBarValue implements common.Component.112func (s *Stash) StatusBarValue() string {113 item, ok := s.list.SelectedItem().(StashItem)114 if !ok {115 return " "116 }117 idx := s.list.Index()118 return fmt.Sprintf("stash@{%d}: %s", idx, item.Title())119}120121// StatusBarInfo implements common.Component.122func (s *Stash) StatusBarInfo() string {123 switch s.state {124 case stashStateList:125 totalPages := s.list.TotalPages()126 if totalPages <= 1 {127 return "p. 1/1"128 }129 return fmt.Sprintf("p. %d/%d", s.list.Page()+1, totalPages)130 case stashStatePatch:131 return common.ScrollPercent(s.code.ScrollPosition())132 default:133 return ""134 }135}136137// SpinnerID implements common.Component.138func (s *Stash) SpinnerID() int {139 return s.spinner.ID()140}141142// Init initializes the model.143func (s *Stash) Init() tea.Cmd {144 s.state = stashStateLoading145 return tea.Batch(s.spinner.Tick, s.fetchStash)146}147148// Update updates the model.149func (s *Stash) Update(msg tea.Msg) (common.Model, tea.Cmd) {150 cmds := make([]tea.Cmd, 0)151 switch msg := msg.(type) {152 case RepoMsg:153 s.repo = msg154 case RefMsg:155 s.ref = msg156 s.list.Select(0)157 cmds = append(cmds, s.Init())158 case tea.WindowSizeMsg:159 s.SetSize(msg.Width, msg.Height)160 case spinner.TickMsg:161 if s.state == stashStateLoading && s.spinner.ID() == msg.ID {162 sp, cmd := s.spinner.Update(msg)163 s.spinner = sp164 if cmd != nil {165 cmds = append(cmds, cmd)166 }167 }168 case tea.KeyPressMsg:169 switch s.state {170 case stashStateList:171 switch {172 case key.Matches(msg, s.common.KeyMap.BackItem):173 cmds = append(cmds, goBackCmd)174 case key.Matches(msg, s.common.KeyMap.Copy):175 cmds = append(cmds, copyCmd(s.list.SelectedItem().(StashItem).Title(), "Stash message copied to clipboard"))176 }177 case stashStatePatch:178 switch {179 case key.Matches(msg, s.common.KeyMap.BackItem):180 cmds = append(cmds, goBackCmd)181 case key.Matches(msg, s.common.KeyMap.Copy):182 if s.currentPatch.Diff != nil {183 patch := s.currentPatch.Diff184 cmds = append(cmds, copyCmd(patch.Patch(), "Stash patch copied to clipboard"))185 }186 }187 }188 case StashListMsg:189 s.state = stashStateList190 items := make([]selector.IdentifiableItem, len(msg))191 for i, stash := range msg {192 items[i] = StashItem{stash}193 }194 cmds = append(cmds, s.list.SetItems(items))195 case StashPatchMsg:196 s.state = stashStatePatch197 s.currentPatch = msg198 if msg.Diff != nil {199 title := s.common.Styles.Stash.Title.Render(s.list.SelectedItem().(StashItem).Title())200 content := lipgloss.JoinVertical(lipgloss.Left,201 title,202 "",203 renderSummary(msg.Diff, s.common.Styles, s.common.Width),204 renderDiff(msg.Diff, s.common.Width),205 )206 cmds = append(cmds, s.code.SetContent(content, ".diff"))207 s.code.GotoTop()208 }209 case selector.SelectMsg:210 switch msg.IdentifiableItem.(type) {211 case StashItem:212 cmds = append(cmds, s.fetchStashPatch)213 }214 case GoBackMsg:215 if s.state == stashStateList {216 s.list.Select(0)217 }218 s.state = stashStateList219 }220 switch s.state {221 case stashStateList:222 l, cmd := s.list.Update(msg)223 s.list = l.(*selector.Selector)224 if cmd != nil {225 cmds = append(cmds, cmd)226 }227 case stashStatePatch:228 c, cmd := s.code.Update(msg)229 s.code = c.(*code.Code)230 if cmd != nil {231 cmds = append(cmds, cmd)232 }233 }234 return s, tea.Batch(cmds...)235}236237// View returns the view.238func (s *Stash) View() string {239 switch s.state {240 case stashStateLoading:241 return renderLoading(s.common, s.spinner)242 case stashStateList:243 return s.list.View()244 case stashStatePatch:245 return s.code.View()246 }247 return ""248}249250func (s *Stash) fetchStash() tea.Msg {251 if s.repo == nil {252 return StashListMsg(nil)253 }254255 r, err := s.repo.Open()256 if err != nil {257 return common.ErrorMsg(err)258 }259260 stash, err := r.StashList()261 if err != nil {262 return common.ErrorMsg(err)263 }264265 return StashListMsg(stash)266}267268func (s *Stash) fetchStashPatch() tea.Msg {269 if s.repo == nil {270 return StashPatchMsg{nil}271 }272273 r, err := s.repo.Open()274 if err != nil {275 return common.ErrorMsg(err)276 }277278 diff, err := r.StashDiff(s.list.Index())279 if err != nil {280 return common.ErrorMsg(err)281 }282283 return StashPatchMsg{diff}284}