From d580682ccf947d54e3de6c60378cf9d54e4654d7 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Wed, 27 Apr 2022 16:59:05 -0400 Subject: [PATCH] feat: repo files tab --- ui/components/selector/selector.go | 15 +- ui/pages/repo/files.go | 273 +++++++++++++++++++++++++++++ ui/pages/repo/filesitem.go | 119 +++++++++++++ ui/pages/repo/log.go | 58 ++++-- ui/pages/repo/repo.go | 75 +++++--- ui/pages/selection/selection.go | 3 +- ui/ui.go | 12 +- 7 files changed, 503 insertions(+), 52 deletions(-) create mode 100644 ui/pages/repo/files.go create mode 100644 ui/pages/repo/filesitem.go diff --git a/ui/components/selector/selector.go b/ui/components/selector/selector.go index d6bdd564ce04618dae422a724902fcdffbbcf1aa..8704c5cfab6c05043129a45ef2bc34cc225f4548 100644 --- a/ui/components/selector/selector.go +++ b/ui/components/selector/selector.go @@ -21,6 +21,11 @@ type IdentifiableItem interface { ID() string } +// ItemDelegate is a wrapper around list.ItemDelegate. +type ItemDelegate interface { + list.ItemDelegate +} + // SelectMsg is a message that is sent when an item is selected. type SelectMsg struct{ IdentifiableItem } @@ -28,7 +33,7 @@ type SelectMsg struct{ IdentifiableItem } type ActiveMsg struct{ IdentifiableItem } // New creates a new selector. -func New(common common.Common, items []IdentifiableItem, delegate list.ItemDelegate) *Selector { +func New(common common.Common, items []IdentifiableItem, delegate ItemDelegate) *Selector { itms := make([]list.Item, len(items)) for i, item := range items { itms[i] = item @@ -109,8 +114,12 @@ func (s *Selector) SetSize(width, height int) { } // SetItems sets the items in the selector. -func (s *Selector) SetItems(items []list.Item) tea.Cmd { - return s.Model.SetItems(items) +func (s *Selector) SetItems(items []IdentifiableItem) tea.Cmd { + its := make([]list.Item, len(items)) + for i, item := range items { + its[i] = item + } + return s.Model.SetItems(its) } // Index returns the index of the selected item. diff --git a/ui/pages/repo/files.go b/ui/pages/repo/files.go new file mode 100644 index 0000000000000000000000000000000000000000..2c9d153206a96bf5988ddb12fde75271cff2f405 --- /dev/null +++ b/ui/pages/repo/files.go @@ -0,0 +1,273 @@ +package repo + +import ( + "errors" + "fmt" + "log" + "path/filepath" + + tea "github.com/charmbracelet/bubbletea" + ggit "github.com/charmbracelet/soft-serve/git" + "github.com/charmbracelet/soft-serve/ui/common" + "github.com/charmbracelet/soft-serve/ui/components/code" + "github.com/charmbracelet/soft-serve/ui/components/selector" + "github.com/charmbracelet/soft-serve/ui/git" +) + +type filesView int + +const ( + filesViewFiles filesView = iota + filesViewContent +) + +var ( + errNoFileSelected = errors.New("no file selected") + errBinaryFile = errors.New("binary file") + errFileTooLarge = errors.New("file is too large") + errInvalidFile = errors.New("invalid file") +) + +// FileItemsMsg is a message that contains a list of files. +type FileItemsMsg []selector.IdentifiableItem + +// FileContentMsg is a message that contains the content of a file. +type FileContentMsg struct { + content string + ext string +} + +// Files is the model for the files view. +type Files struct { + common common.Common + selector *selector.Selector + ref *ggit.Reference + activeView filesView + repo git.GitRepo + code *code.Code + path string + currentItem *FileItem + currentContent FileContentMsg + lastSelected []int +} + +// NewFiles creates a new files model. +func NewFiles(common common.Common) *Files { + f := &Files{ + common: common, + code: code.New(common, "", ""), + activeView: filesViewFiles, + lastSelected: make([]int, 0), + } + selector := selector.New(common, []selector.IdentifiableItem{}, FileItemDelegate{common.Styles}) + selector.SetShowFilter(false) + selector.SetShowHelp(false) + selector.SetShowPagination(false) + selector.SetShowStatusBar(false) + selector.SetShowTitle(false) + selector.SetFilteringEnabled(false) + selector.DisableQuitKeybindings() + selector.KeyMap.NextPage = common.KeyMap.NextPage + selector.KeyMap.PrevPage = common.KeyMap.PrevPage + f.selector = selector + return f +} + +// SetSize implements common.Component. +func (f *Files) SetSize(width, height int) { + f.common.SetSize(width, height) + f.selector.SetSize(width, height) + f.code.SetSize(width, height) +} + +// Init implements tea.Model. +func (f *Files) Init() tea.Cmd { + f.path = "" + f.currentItem = nil + f.activeView = filesViewFiles + f.lastSelected = make([]int, 0) + return f.updateFilesCmd +} + +// Update implements tea.Model. +func (f *Files) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + cmds := make([]tea.Cmd, 0) + switch msg := msg.(type) { + case RepoMsg: + f.selector.Select(0) + f.repo = git.GitRepo(msg) + cmds = append(cmds, f.Init()) + case RefMsg: + f.ref = msg + cmds = append(cmds, f.Init()) + case FileItemsMsg: + cmds = append(cmds, + f.selector.SetItems(msg), + updateStatusBarCmd, + ) + case FileContentMsg: + f.activeView = filesViewContent + f.currentContent = msg + f.code.SetContent(msg.content, msg.ext) + f.code.GotoTop() + cmds = append(cmds, updateStatusBarCmd) + case selector.SelectMsg: + switch sel := msg.IdentifiableItem.(type) { + case FileItem: + f.currentItem = &sel + f.path = filepath.Join(f.path, sel.entry.Name()) + log.Printf("selected index %d", f.selector.Index()) + if sel.entry.IsTree() { + cmds = append(cmds, f.selectTreeCmd) + } else { + cmds = append(cmds, f.selectFileCmd) + } + } + case tea.KeyMsg: + switch f.activeView { + case filesViewFiles: + switch msg.String() { + case "l", "right": + cmds = append(cmds, f.selector.SelectItem) + case "h", "left": + cmds = append(cmds, f.deselectItemCmd) + } + case filesViewContent: + switch msg.String() { + case "h", "left": + cmds = append(cmds, f.deselectItemCmd) + } + } + case tea.WindowSizeMsg: + if f.currentContent.content != "" { + m, cmd := f.code.Update(msg) + f.code = m.(*code.Code) + if cmd != nil { + cmds = append(cmds, cmd) + } + } + if f.repo != nil { + cmds = append(cmds, f.updateFilesCmd) + } + } + switch f.activeView { + case filesViewFiles: + m, cmd := f.selector.Update(msg) + f.selector = m.(*selector.Selector) + if cmd != nil { + cmds = append(cmds, cmd) + } + case filesViewContent: + m, cmd := f.code.Update(msg) + f.code = m.(*code.Code) + if cmd != nil { + cmds = append(cmds, cmd) + } + } + return f, tea.Batch(cmds...) +} + +// View implements tea.Model. +func (f *Files) View() string { + switch f.activeView { + case filesViewFiles: + return f.selector.View() + case filesViewContent: + return f.code.View() + default: + return "" + } +} + +// StatusBarValue returns the status bar value. +func (f *Files) StatusBarValue() string { + p := f.path + if p == "." { + return "" + } + return p +} + +// StatusBarInfo returns the status bar info. +func (f *Files) StatusBarInfo() string { + switch f.activeView { + case filesViewFiles: + return fmt.Sprintf("%d/%d", f.selector.Index()+1, len(f.selector.VisibleItems())) + case filesViewContent: + return fmt.Sprintf("%.f%%", f.code.ScrollPercent()*100) + default: + return "" + } +} + +func (f *Files) updateFilesCmd() tea.Msg { + files := make([]selector.IdentifiableItem, 0) + dirs := make([]selector.IdentifiableItem, 0) + t, err := f.repo.Tree(f.ref, f.path) + if err != nil { + return common.ErrorMsg(err) + } + ents, err := t.Entries() + if err != nil { + return common.ErrorMsg(err) + } + ents.Sort() + for _, e := range ents { + if e.IsTree() { + dirs = append(dirs, FileItem{e}) + } else { + files = append(files, FileItem{e}) + } + } + return FileItemsMsg(append(dirs, files...)) +} + +func (f *Files) selectTreeCmd() tea.Msg { + if f.currentItem != nil && f.currentItem.entry.IsTree() { + f.lastSelected = append(f.lastSelected, f.selector.Index()) + f.selector.Select(0) + return f.updateFilesCmd() + } + return common.ErrorMsg(errNoFileSelected) +} + +func (f *Files) selectFileCmd() tea.Msg { + i := f.currentItem + if i != nil && !i.entry.IsTree() { + fi := i.entry.File() + if i.Mode().IsDir() || f == nil { + return common.ErrorMsg(errInvalidFile) + } + bin, err := fi.IsBinary() + if err != nil { + f.path = filepath.Dir(f.path) + return common.ErrorMsg(err) + } + if bin { + f.path = filepath.Dir(f.path) + return common.ErrorMsg(errBinaryFile) + } + c, err := fi.Bytes() + if err != nil { + f.path = filepath.Dir(f.path) + return common.ErrorMsg(err) + } + f.lastSelected = append(f.lastSelected, f.selector.Index()) + return FileContentMsg{string(c), i.entry.Name()} + } + return common.ErrorMsg(errNoFileSelected) +} + +func (f *Files) deselectItemCmd() tea.Msg { + f.path = filepath.Dir(f.path) + f.activeView = filesViewFiles + msg := f.updateFilesCmd() + index := 0 + if len(f.lastSelected) > 0 { + index = f.lastSelected[len(f.lastSelected)-1] + f.lastSelected = f.lastSelected[:len(f.lastSelected)-1] + } + log.Printf("deselect %d", index) + f.selector.Select(index) + return msg +} diff --git a/ui/pages/repo/filesitem.go b/ui/pages/repo/filesitem.go new file mode 100644 index 0000000000000000000000000000000000000000..813ef0dbcf25c0cf319029a4ce401a0a9067e324 --- /dev/null +++ b/ui/pages/repo/filesitem.go @@ -0,0 +1,119 @@ +package repo + +import ( + "fmt" + "io" + "io/fs" + + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/soft-serve/git" + "github.com/charmbracelet/soft-serve/ui/styles" + "github.com/dustin/go-humanize" +) + +// FileItem is a list item for a file. +type FileItem struct { + entry *git.TreeEntry +} + +// ID returns the ID of the file item. +func (i FileItem) ID() string { + return i.entry.Name() +} + +// Title returns the title of the file item. +func (i FileItem) Title() string { + return i.entry.Name() +} + +// Description returns the description of the file item. +func (i FileItem) Description() string { + return "" +} + +// Mode returns the mode of the file item. +func (i FileItem) Mode() fs.FileMode { + return i.entry.Mode() +} + +// FilterValue implements list.Item. +func (i FileItem) FilterValue() string { return i.Title() } + +// FileItems is a list of file items. +type FileItems []FileItem + +// Len implements sort.Interface. +func (cl FileItems) Len() int { return len(cl) } + +// Swap implements sort.Interface. +func (cl FileItems) Swap(i, j int) { cl[i], cl[j] = cl[j], cl[i] } + +// Less implements sort.Interface. +func (cl FileItems) Less(i, j int) bool { + if cl[i].entry.IsTree() && cl[j].entry.IsTree() { + return cl[i].Title() < cl[j].Title() + } else if cl[i].entry.IsTree() { + return true + } else if cl[j].entry.IsTree() { + return false + } else { + return cl[i].Title() < cl[j].Title() + } +} + +// FileItemDelegate is the delegate for the file item list. +type FileItemDelegate struct { + style *styles.Styles +} + +// Height returns the height of the file item list. Implements list.ItemDelegate. +func (d FileItemDelegate) Height() int { return 1 } + +// Spacing returns the spacing of the file item list. Implements list.ItemDelegate. +func (d FileItemDelegate) Spacing() int { return 0 } + +// Update implements list.ItemDelegate. +func (d FileItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil } + +// Render implements list.ItemDelegate. +func (d FileItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { + s := d.style + i, ok := listItem.(FileItem) + if !ok { + return + } + + name := i.Title() + size := humanize.Bytes(uint64(i.entry.Size())) + if i.entry.IsTree() { + size = "" + name = s.TreeFileDir.Render(name) + } + var cs lipgloss.Style + mode := i.Mode() + if index == m.Index() { + cs = s.TreeItemActive + fmt.Fprint(w, s.TreeItemSelector.Render(">")) + } else { + cs = s.TreeItemInactive + fmt.Fprint(w, s.TreeItemSelector.Render(" ")) + } + leftMargin := s.TreeItemSelector.GetMarginLeft() + + s.TreeItemSelector.GetWidth() + + s.TreeFileMode.GetMarginLeft() + + s.TreeFileMode.GetWidth() + + cs.GetMarginLeft() + rightMargin := s.TreeFileSize.GetMarginLeft() + lipgloss.Width(size) + name = truncateString(name, m.Width()-leftMargin-rightMargin, "…") + sizeStyle := s.TreeFileSize.Copy(). + Width(m.Width() - + leftMargin - + s.TreeFileSize.GetMarginLeft() - + lipgloss.Width(name)). + Align(lipgloss.Right) + fmt.Fprint(w, s.TreeFileMode.Render(mode.String())+ + cs.Render(name)+ + sizeStyle.Render(size)) +} diff --git a/ui/pages/repo/log.go b/ui/pages/repo/log.go index 0be7c436102fc07b3f5f11bc85b28232d7d2c39d..f7c235c23cd3bf5e02fdb12651c7a751a7854382 100644 --- a/ui/pages/repo/log.go +++ b/ui/pages/repo/log.go @@ -6,7 +6,6 @@ import ( "time" "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/glamour" gansi "github.com/charmbracelet/glamour/ansi" @@ -20,18 +19,18 @@ import ( "github.com/muesli/termenv" ) -type view int +type logView int const ( - logView view = iota - commitView + logViewCommits logView = iota + logViewDiff ) // LogCountMsg is a message that contains the number of commits in a repo. type LogCountMsg int64 // LogItemsMsg is a message that contains a slice of LogItem. -type LogItemsMsg []list.Item +type LogItemsMsg []selector.IdentifiableItem // LogCommitMsg is a message that contains a git commit. type LogCommitMsg *ggit.Commit @@ -44,11 +43,12 @@ type Log struct { common common.Common selector *selector.Selector vp *viewport.Viewport - activeView view + activeView logView repo git.GitRepo ref *ggit.Reference count int64 nextPage int + activeCommit *ggit.Commit selectedCommit *ggit.Commit currentDiff *ggit.Diff } @@ -58,7 +58,7 @@ func NewLog(common common.Common) *Log { l := &Log{ common: common, vp: viewport.New(), - activeView: logView, + activeView: logViewCommits, } selector := selector.New(common, []selector.IdentifiableItem{}, LogItemDelegate{common.Styles}) selector.SetShowFilter(false) @@ -84,7 +84,7 @@ func (l *Log) SetSize(width, height int) { // ShortHelp implements key.KeyMap. func (l *Log) ShortHelp() []key.Binding { switch l.activeView { - case logView: + case logViewCommits: return []key.Binding{ key.NewBinding( key.WithKeys( @@ -97,7 +97,7 @@ func (l *Log) ShortHelp() []key.Binding { ), ), } - case commitView: + case logViewDiff: return []key.Binding{ l.common.KeyMap.UpDown, key.NewBinding( @@ -143,9 +143,10 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, l.selector.SetItems(msg)) l.selector.SetPage(l.nextPage) l.SetSize(l.common.Width, l.common.Height) + l.activeCommit = l.selector.SelectedItem().(LogItem).Commit case tea.KeyMsg, tea.MouseMsg: switch l.activeView { - case logView: + case logViewCommits: switch key := msg.(type) { case tea.KeyMsg: switch key.String() { @@ -164,15 +165,21 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, l.updateCommitsCmd) } cmds = append(cmds, cmd) - case commitView: + case logViewDiff: switch key := msg.(type) { case tea.KeyMsg: switch key.String() { case "h", "left": - l.activeView = logView + l.activeView = logViewCommits } } } + case selector.ActiveMsg: + switch sel := msg.IdentifiableItem.(type) { + case LogItem: + l.activeCommit = sel.Commit + } + cmds = append(cmds, updateStatusBarCmd) case selector.SelectMsg: switch sel := msg.IdentifiableItem.(type) { case LogItem: @@ -191,7 +198,7 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) { ), ) l.vp.GotoTop() - l.activeView = commitView + l.activeView = logViewDiff cmds = append(cmds, updateStatusBarCmd) case tea.WindowSizeMsg: if l.selectedCommit != nil && l.currentDiff != nil { @@ -208,7 +215,7 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } switch l.activeView { - case commitView: + case logViewDiff: vp, cmd := l.vp.Update(msg) l.vp = vp.(*viewport.Viewport) if cmd != nil { @@ -221,23 +228,36 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // View implements tea.Model. func (l *Log) View() string { switch l.activeView { - case logView: + case logViewCommits: return l.selector.View() - case commitView: + case logViewDiff: return l.vp.View() default: return "" } } +// StatusBarValue returns the status bar value. +func (l *Log) StatusBarValue() string { + c := l.activeCommit + if c == nil { + return "" + } + return fmt.Sprintf("%s by %s on %s", + c.ID.String()[:7], + c.Author.Name, + c.Author.When.Format("02 Jan 2006"), + ) +} + // StatusBarInfo returns the status bar info. func (l *Log) StatusBarInfo() string { switch l.activeView { - case logView: + case logViewCommits: // We're using l.nextPage instead of l.selector.Paginator.Page because // of the paginator hack above. return fmt.Sprintf("%d/%d", l.nextPage+1, l.selector.TotalPages()) - case commitView: + case logViewDiff: return fmt.Sprintf("%.f%%", l.vp.ScrollPercent()*100) default: return "" @@ -262,7 +282,7 @@ func (l *Log) updateCommitsCmd() tea.Msg { count = int64(msg) } } - items := make([]list.Item, count) + items := make([]selector.IdentifiableItem, count) page := l.nextPage limit := l.selector.PerPage() skip := page * limit diff --git a/ui/pages/repo/repo.go b/ui/pages/repo/repo.go index 17a0f5d153fad1e97a3c76b5e6ee7eff01dbcf97..355fcd0eb9894e7f2dfffe342233e99fdf5412da 100644 --- a/ui/pages/repo/repo.go +++ b/ui/pages/repo/repo.go @@ -45,6 +45,7 @@ type Repo struct { statusbar *statusbar.StatusBar readme *code.Code log *Log + files *Files ref *ggit.Reference } @@ -55,6 +56,7 @@ func New(common common.Common, rs git.GitRepoSource) *Repo { readme := code.New(common, "", "") readme.NoContentStyle = readme.NoContentStyle.SetString("No readme found.") log := NewLog(common) + files := NewFiles(common) r := &Repo{ common: common, rs: rs, @@ -62,6 +64,7 @@ func New(common common.Common, rs git.GitRepoSource) *Repo { statusbar: sb, readme: readme, log: log, + files: files, } return r } @@ -79,6 +82,7 @@ func (r *Repo) SetSize(width, height int) { r.statusbar.SetSize(width, height-hm) r.readme.SetSize(width, height-hm) r.log.SetSize(width, height-hm) + r.files.SetSize(width, height-hm) } // ShortHelp implements help.KeyMap. @@ -127,25 +131,16 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { r.tabs.Init(), r.updateReadmeCmd, r.updateRefCmd, + r.updateModels(msg), ) - // Pass msg to log. - l, cmd := r.log.Update(msg) - r.log = l.(*Log) - if cmd != nil { - cmds = append(cmds, cmd) - } case RefMsg: r.ref = msg cmds = append(cmds, r.updateStatusBarCmd, r.log.Init(), + r.files.Init(), + r.updateModels(msg), ) - // Pass msg to log. - l, cmd := r.log.Update(msg) - r.log = l.(*Log) - if cmd != nil { - cmds = append(cmds, cmd) - } case tabs.ActiveTabMsg: r.activeTab = tab(msg) if r.selectedRepo != nil { @@ -155,6 +150,12 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if r.selectedRepo != nil { cmds = append(cmds, r.updateStatusBarCmd) } + case FileItemsMsg: + f, cmd := r.files.Update(msg) + r.files = f.(*Files) + if cmd != nil { + cmds = append(cmds, cmd) + } case LogCountMsg, LogItemsMsg: l, cmd := r.log.Update(msg) r.log = l.(*Log) @@ -169,11 +170,7 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if cmd != nil { cmds = append(cmds, cmd) } - l, cmd := r.log.Update(msg) - r.log = l.(*Log) - if cmd != nil { - cmds = append(cmds, cmd) - } + cmds = append(cmds, r.updateModels(msg)) } t, cmd := r.tabs.Update(msg) r.tabs = t.(*tabs.Tabs) @@ -193,6 +190,11 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, cmd) } case filesTab: + f, cmd := r.files.Update(msg) + r.files = f.(*Files) + if cmd != nil { + cmds = append(cmds, cmd) + } case commitsTab: l, cmd := r.log.Update(msg) r.log = l.(*Log) @@ -219,18 +221,21 @@ func (r *Repo) View() string { r.common.Styles.Tabs.GetVerticalFrameSize() mainStyle := repoBodyStyle. Height(r.common.Height - hm) - main := mainStyle.Render("") + main := "" switch r.activeTab { case readmeTab: - main = mainStyle.Render(r.readme.View()) + main = r.readme.View() case filesTab: + main = r.files.View() case commitsTab: - main = mainStyle.Render(r.log.View()) + main = r.log.View() + case branchesTab: + case tagsTab: } view := lipgloss.JoinVertical(lipgloss.Top, r.headerView(), r.tabs.View(), - main, + mainStyle.Render(main), r.statusbar.View(), ) return s.Render(view) @@ -242,7 +247,11 @@ func (r *Repo) headerView() string { } name := r.common.Styles.RepoHeaderName.Render(r.selectedItem.Title()) // TODO move this into a style. - url := lipgloss.NewStyle().MarginLeft(2).Render(r.selectedItem.URL()) + url := lipgloss.NewStyle(). + MarginLeft(1). + Width(r.common.Width - lipgloss.Width(name) - 1). + Align(lipgloss.Right). + Render(r.selectedItem.URL()) desc := r.common.Styles.RepoHeaderDesc.Render(r.selectedItem.Description()) style := r.common.Styles.RepoHeader.Copy().Width(r.common.Width) return style.Render( @@ -268,16 +277,21 @@ func (r *Repo) setRepoCmd(repo string) tea.Cmd { } func (r *Repo) updateStatusBarCmd() tea.Msg { + value := "" info := "" switch r.activeTab { case readmeTab: info = fmt.Sprintf("%.f%%", r.readme.ScrollPercent()*100) case commitsTab: + value = r.log.StatusBarValue() info = r.log.StatusBarInfo() + case filesTab: + value = r.files.StatusBarValue() + info = r.files.StatusBarInfo() } return statusbar.StatusBarMsg{ Key: r.selectedRepo.Name(), - Value: "", + Value: value, Info: info, Branch: fmt.Sprintf(" %s", r.ref.Name().Short()), } @@ -299,6 +313,21 @@ func (r *Repo) updateRefCmd() tea.Msg { return RefMsg(head) } +func (r *Repo) updateModels(msg tea.Msg) tea.Cmd { + cmds := make([]tea.Cmd, 0) + l, cmd := r.log.Update(msg) + r.log = l.(*Log) + if cmd != nil { + cmds = append(cmds, cmd) + } + f, cmd := r.files.Update(msg) + r.files = f.(*Files) + if cmd != nil { + cmds = append(cmds, cmd) + } + return tea.Batch(cmds...) +} + func updateStatusBarCmd() tea.Msg { return UpdateStatusBarMsg{} } diff --git a/ui/pages/selection/selection.go b/ui/pages/selection/selection.go index 77476edc608835b5ba56c86df22a308631e4727b..170628f022178b2ec14f2cddd24dd00b28a259db 100644 --- a/ui/pages/selection/selection.go +++ b/ui/pages/selection/selection.go @@ -5,7 +5,6 @@ import ( "strings" "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" appCfg "github.com/charmbracelet/soft-serve/config" @@ -130,7 +129,7 @@ func (s *Selection) Init() tea.Cmd { pty, _, _ := session.Pty() environ = append(environ, fmt.Sprintf("TERM=%s", pty.Term)) } - items := make([]list.Item, 0) + items := make([]selector.IdentifiableItem, 0) cfg := s.s.Config() // TODO clean up this and move style to its own var. yank := func(text string) *yankable.Yankable { diff --git a/ui/ui.go b/ui/ui.go index 397996a6c724f27af2467f12737b5675ba140c4b..ea4e75562e8c1b2eef9e1feafe3640d282b03ab0 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -120,6 +120,7 @@ func (ui *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds := make([]tea.Cmd, 0) switch msg := msg.(type) { case tea.WindowSizeMsg: + ui.SetSize(msg.Width, msg.Height) h, cmd := ui.header.Update(msg) ui.header = h.(*header.Header) if cmd != nil { @@ -137,7 +138,6 @@ func (ui *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, cmd) } } - ui.SetSize(msg.Width, msg.Height) case tea.KeyMsg: switch { case key.Matches(msg, ui.common.KeyMap.Back) && ui.error != nil: @@ -161,10 +161,12 @@ func (ui *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } } - m, cmd := ui.pages[ui.activePage].Update(msg) - ui.pages[ui.activePage] = m.(common.Page) - if cmd != nil { - cmds = append(cmds, cmd) + if ui.state == loadedState { + m, cmd := ui.pages[ui.activePage].Update(msg) + ui.pages[ui.activePage] = m.(common.Page) + if cmd != nil { + cmds = append(cmds, cmd) + } } return ui, tea.Batch(cmds...) }