From d3040965362bb80f50a2d476d2c9d19f5bcc27e9 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 29 Apr 2022 16:02:54 -0400 Subject: [PATCH] fix: initial repo --- config/config.go | 2 + config/git.go | 33 +++++++++++----- {ui => server}/git.go | 2 +- server/session.go | 5 +++ ui/git/git.go | 15 ++++++++ ui/pages/repo/repo.go | 40 ++++++------------- ui/pages/selection/item.go | 17 ++++----- ui/pages/selection/selection.go | 34 +++++++---------- ui/session/session.go | 2 + ui/ui.go | 68 ++++++++++++++++++--------------- 10 files changed, 119 insertions(+), 99 deletions(-) rename {ui => server}/git.go (97%) diff --git a/config/config.go b/config/config.go index 1e5f5621fe10cfda79fc36f8122ee53d973b9332..8351c775c0d01d2fef669f6a7eed1ade37973fd8 100644 --- a/config/config.go +++ b/config/config.go @@ -152,6 +152,8 @@ func (cfg *Config) Reload() error { for _, rr := range cfg.Repos { if name == rr.Repo { rp = rr.Readme + r.name = rr.Name + r.description = rr.Note break } } diff --git a/config/git.go b/config/git.go index 14412ba76deaa128f4da3b8035845beca4c26405..2b82f0a38b9fea54f7ee75a3f25fa0f092d4887a 100644 --- a/config/git.go +++ b/config/git.go @@ -17,13 +17,15 @@ var ErrMissingRepo = errors.New("missing repo") // Repo represents a Git repository. type Repo struct { - path string - repository *git.Repository - readme string - readmePath string - head *git.Reference - refs []*git.Reference - patchCache *lru.Cache + name string + description string + path string + repository *git.Repository + readme string + readmePath string + head *git.Reference + refs []*git.Reference + patchCache *lru.Cache } // open opens a Git repository. @@ -53,11 +55,24 @@ func (r *Repo) Path() string { return r.path } -// GetName returns the name of the repository. -func (r *Repo) Name() string { +// Repo returns the repository directory name. +func (r *Repo) Repo() string { return filepath.Base(r.path) } +// Name returns the name of the repository. +func (r *Repo) Name() string { + if r.name == "" { + return r.Repo() + } + return r.name +} + +// Description returns the description for a repository. +func (r *Repo) Description() string { + return r.description +} + // Readme returns the readme and its path for the repository. func (r *Repo) Readme() (readme string, path string) { return r.readme, r.readmePath diff --git a/ui/git.go b/server/git.go similarity index 97% rename from ui/git.go rename to server/git.go index e4dcf80a2453d0f946ae09ad5dbd7ba71eb53571..f08e1e58affc049ab3cd3cc6bc579a7a7e00177c 100644 --- a/ui/git.go +++ b/server/git.go @@ -1,4 +1,4 @@ -package ui +package server import ( "github.com/charmbracelet/soft-serve/config" diff --git a/server/session.go b/server/session.go index 9d829c2a0f6f996d297b4f29492759b5ac8732f1..15b08e7cb1443dc39f06b97234309c5a57a078d7 100644 --- a/server/session.go +++ b/server/session.go @@ -7,6 +7,7 @@ import ( appCfg "github.com/charmbracelet/soft-serve/config" "github.com/charmbracelet/soft-serve/ui" "github.com/charmbracelet/soft-serve/ui/common" + "github.com/charmbracelet/soft-serve/ui/git" "github.com/charmbracelet/soft-serve/ui/keymap" "github.com/charmbracelet/soft-serve/ui/styles" bm "github.com/charmbracelet/wish/bubbletea" @@ -36,6 +37,10 @@ func (s *Session) Session() ssh.Session { return s.session } +func (s *Session) Source() git.GitRepoSource { + return &source{s.Cfg.Source} +} + func SessionHandler(ac *appCfg.Config) bm.ProgramHandler { return func(s ssh.Session) *tea.Program { pty, _, active := s.Pty() diff --git a/ui/git/git.go b/ui/git/git.go index 578544fb1e31ee7cad99427bea349f8cfc21e23c..0c9dc473167777bbb189c7c8ae03a9914eb3000b 100644 --- a/ui/git/git.go +++ b/ui/git/git.go @@ -2,14 +2,19 @@ package git import ( "errors" + "fmt" "github.com/charmbracelet/soft-serve/git" ) +// ErrMissingRepo indicates that the requested repository could not be found. var ErrMissingRepo = errors.New("missing repo") +// GitRepo is an interface for Git repositories. type GitRepo interface { + Repo() string Name() string + Description() string Readme() (string, string) HEAD() (*git.Reference, error) CommitsByPage(*git.Reference, int, int) (git.Commits, error) @@ -19,7 +24,17 @@ type GitRepo interface { Tree(*git.Reference, string) (*git.Tree, error) } +// GitRepoSource is an interface for Git repository factory. type GitRepoSource interface { GetRepo(string) (GitRepo, error) AllRepos() []GitRepo } + +// RepoURL returns the URL of the repository. +func RepoURL(host string, port int, name string) string { + p := "" + if port != 22 { + p += fmt.Sprintf(":%d", port) + } + return fmt.Sprintf("git clone ssh://%s/%s", host+p, name) +} diff --git a/ui/pages/repo/repo.go b/ui/pages/repo/repo.go index a27499508d589a018b1434aba42a48026e3f188f..5728f7a687753850548fc88670b998c88d222bfc 100644 --- a/ui/pages/repo/repo.go +++ b/ui/pages/repo/repo.go @@ -10,11 +10,10 @@ import ( 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/components/statusbar" "github.com/charmbracelet/soft-serve/ui/components/tabs" "github.com/charmbracelet/soft-serve/ui/git" - "github.com/charmbracelet/soft-serve/ui/pages/selection" + "github.com/charmbracelet/soft-serve/ui/session" ) type tab int @@ -38,10 +37,10 @@ type RefMsg *ggit.Reference // Repo is a view for a git repository. type Repo struct { + s session.Session common common.Common rs git.GitRepoSource selectedRepo git.GitRepo - selectedItem selection.Item activeTab tab tabs *tabs.Tabs statusbar *statusbar.StatusBar @@ -50,7 +49,7 @@ type Repo struct { } // New returns a new Repo. -func New(c common.Common, rs git.GitRepoSource) *Repo { +func New(s session.Session, c common.Common) *Repo { sb := statusbar.New(c) // Tabs must match the order of tab constants above. tb := tabs.New(c, []string{"Readme", "Files", "Commits", "Branches", "Tags"}) @@ -69,8 +68,9 @@ func New(c common.Common, rs git.GitRepoSource) *Repo { tags, } r := &Repo{ + s: s, common: c, - rs: rs, + rs: s.Source(), tabs: tb, statusbar: sb, boxes: boxes, @@ -146,23 +146,16 @@ func (r *Repo) FullHelp() [][]key.Binding { // Init implements tea.View. func (r *Repo) Init() tea.Cmd { - cmds := make([]tea.Cmd, 0) - cmds = append(cmds, + return tea.Batch( r.tabs.Init(), r.statusbar.Init(), ) - return tea.Batch(cmds...) } // Update implements tea.Model. func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds := make([]tea.Cmd, 0) switch msg := msg.(type) { - case selector.SelectMsg: - switch msg.IdentifiableItem.(type) { - case selection.Item: - r.selectedItem = msg.IdentifiableItem.(selection.Item) - } case RepoMsg: r.activeTab = 0 r.selectedRepo = git.GitRepo(msg) @@ -281,14 +274,16 @@ func (r *Repo) headerView() string { if r.selectedRepo == nil { return "" } - name := r.common.Styles.RepoHeaderName.Render(r.selectedItem.Title()) + cfg := r.s.Config() + name := r.common.Styles.RepoHeaderName.Render(r.selectedRepo.Name()) + url := git.RepoURL(cfg.Host, cfg.Port, r.selectedRepo.Repo()) // TODO move this into a style. - url := lipgloss.NewStyle(). + 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()) + Render(url) + desc := r.common.Styles.RepoHeaderDesc.Render(r.selectedRepo.Description()) style := r.common.Styles.RepoHeader.Copy().Width(r.common.Width) return style.Render( lipgloss.JoinVertical(lipgloss.Top, @@ -301,17 +296,6 @@ func (r *Repo) headerView() string { ) } -func (r *Repo) setRepoCmd(repo string) tea.Cmd { - return func() tea.Msg { - for _, r := range r.rs.AllRepos() { - if r.Name() == repo { - return RepoMsg(r) - } - } - return common.ErrorMsg(git.ErrMissingRepo) - } -} - func (r *Repo) updateStatusBarCmd() tea.Msg { var info, value string switch r.activeTab { diff --git a/ui/pages/selection/item.go b/ui/pages/selection/item.go index db54f5f9eed026c1b0d9d1231bb456daf55040c2..343195340e73c2e22170e76f0c96125dc8842e48 100644 --- a/ui/pages/selection/item.go +++ b/ui/pages/selection/item.go @@ -10,32 +10,31 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/soft-serve/ui/components/yankable" + "github.com/charmbracelet/soft-serve/ui/git" "github.com/charmbracelet/soft-serve/ui/styles" "github.com/dustin/go-humanize" ) // Item represents a single item in the selector. type Item struct { - name string - repo string - desc string + repo git.GitRepo lastUpdate time.Time url *yankable.Yankable } // ID implements selector.IdentifiableItem. func (i Item) ID() string { - return i.repo + return i.repo.Repo() } // Title returns the item title. Implements list.DefaultItem. -func (i Item) Title() string { return i.name } +func (i Item) Title() string { return i.repo.Name() } // Description returns the item description. Implements list.DefaultItem. -func (i Item) Description() string { return i.desc } +func (i Item) Description() string { return i.repo.Description() } // FilterValue implements list.Item. -func (i Item) FilterValue() string { return i.name } +func (i Item) FilterValue() string { return i.Title() } // URL returns the item URL view. func (i Item) URL() string { @@ -119,7 +118,7 @@ func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list } } - title := i.name + title := i.Title() updatedStr := fmt.Sprintf(" Updated %s", humanize.Time(i.lastUpdate)) updated := d.styles.MenuLastUpdate. Copy(). @@ -143,7 +142,7 @@ func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list s.WriteString(lipgloss.JoinHorizontal(lipgloss.Bottom, title, updated)) s.WriteString("\n") - s.WriteString(i.desc) + s.WriteString(i.Description()) s.WriteString("\n\n") s.WriteString(i.url.View()) w.Write([]byte(itemStyle.Render(s.String()))) diff --git a/ui/pages/selection/selection.go b/ui/pages/selection/selection.go index 61ea96b2480bb6f64850c7da6b5906aa59c944c1..6d40c7de61b34ff87bd92545d492eab30957cef8 100644 --- a/ui/pages/selection/selection.go +++ b/ui/pages/selection/selection.go @@ -1,18 +1,17 @@ package selection import ( - "errors" "fmt" "strings" "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - appCfg "github.com/charmbracelet/soft-serve/config" "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/components/yankable" + "github.com/charmbracelet/soft-serve/ui/git" "github.com/charmbracelet/soft-serve/ui/session" ) @@ -165,11 +164,13 @@ func (s *Selection) Init() tea.Cmd { } // Put configured repos first for _, r := range cfg.Repos { + repo, err := cfg.Source.GetRepo(r.Repo) + if err != nil { + continue + } items = append(items, Item{ - name: r.Name, - repo: r.Repo, - desc: r.Note, - url: yank(repoUrl(cfg, r.Repo)), + repo: repo, + url: yank(git.RepoURL(cfg.Host, cfg.Port, r.Repo)), }) } for _, r := range cfg.Source.AllRepos() { @@ -185,7 +186,7 @@ func (s *Selection) Init() tea.Cmd { lastUpdate := lc[0].Committer.When for _, item := range items { item := item.(Item) - if item.repo == r.Name() { + if item.repo.Repo() == r.Repo() { exists = true item.lastUpdate = lastUpdate break @@ -193,11 +194,9 @@ func (s *Selection) Init() tea.Cmd { } if !exists { items = append(items, Item{ - name: r.Name(), - repo: r.Name(), - desc: "", + repo: r, lastUpdate: lastUpdate, - url: yank(repoUrl(cfg, r.Name())), + url: yank(git.RepoURL(cfg.Host, cfg.Port, r.Name())), }) } } @@ -230,8 +229,9 @@ func (s *Selection) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch { case key.Matches(msg, s.common.KeyMap.Section): s.activeBox = (s.activeBox + 1) % 2 - case msg.String() == "a": - cmds = append(cmds, common.ErrorCmd(errors.New("not implemented"))) + case key.Matches(msg, s.common.KeyMap.Back): + s.selector.Select(0) + cmds = append(cmds, s.selector.Init()) } } switch s.activeBox { @@ -280,11 +280,3 @@ func (s *Selection) changeActive(msg selector.ActiveMsg) tea.Cmd { rm, rp := r.Readme() return s.readme.SetContent(rm, rp) } - -func repoUrl(cfg *appCfg.Config, name string) string { - port := "" - if cfg.Port != 22 { - port += fmt.Sprintf(":%d", cfg.Port) - } - return fmt.Sprintf("git clone ssh://%s/%s", cfg.Host+port, name) -} diff --git a/ui/session/session.go b/ui/session/session.go index cb9bc29436f8b26b7db982ab33b11441729886fe..fb55cc4ce710d62b3bb48ae7df4f6294395e4727 100644 --- a/ui/session/session.go +++ b/ui/session/session.go @@ -3,6 +3,7 @@ package session import ( tea "github.com/charmbracelet/bubbletea" appCfg "github.com/charmbracelet/soft-serve/config" + "github.com/charmbracelet/soft-serve/ui/git" "github.com/gliderlabs/ssh" ) @@ -15,4 +16,5 @@ type Session interface { // PublicKey returns the public key of the user. PublicKey() ssh.PublicKey Session() ssh.Session + Source() git.GitRepoSource } diff --git a/ui/ui.go b/ui/ui.go index 2f408f95571f4253456a24c3e3ff11cafd60abce..ec207d7e584b9e0eb30ccd764c1451328af063f2 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -33,26 +33,28 @@ const ( // UI is the main UI model. type UI struct { - s session.Session - common common.Common - pages []common.Page - activePage page - state sessionState - header *header.Header - footer *footer.Footer - error error + s session.Session + initialRepo string + common common.Common + pages []common.Page + activePage page + state sessionState + header *header.Header + footer *footer.Footer + error error } // New returns a new UI model. func New(s session.Session, c common.Common, initialRepo string) *UI { h := header.New(c, s.Config().Name) ui := &UI{ - s: s, - common: c, - pages: make([]common.Page, 2), // selection & repo - activePage: selectionPage, - state: startState, - header: h, + s: s, + common: c, + pages: make([]common.Page, 2), // selection & repo + activePage: selectionPage, + state: startState, + header: h, + initialRepo: initialRepo, } ui.footer = footer.New(c, ui) ui.SetSize(c.Width, c.Height) @@ -114,28 +116,19 @@ func (ui *UI) SetSize(width, height int) { // Init implements tea.Model. func (ui *UI) Init() tea.Cmd { - cfg := ui.s.Config() ui.pages[selectionPage] = selection.New(ui.s, ui.common) - ui.pages[repoPage] = repo.New(ui.common, &source{cfg.Source}) + ui.pages[repoPage] = repo.New(ui.s, ui.common) ui.SetSize(ui.common.Width, ui.common.Height) - cmd := tea.Batch( + cmds := make([]tea.Cmd, 0) + cmds = append(cmds, ui.pages[selectionPage].Init(), ui.pages[repoPage].Init(), ) - var msg tea.Msg - if cmd != nil { - msg = cmd() - switch msg := msg.(type) { - case common.ErrorMsg: - ui.state = errorState - return common.ErrorCmd(msg) - } + if ui.initialRepo != "" { + cmds = append(cmds, ui.initialRepoCmd(ui.initialRepo)) } ui.state = loadedState - if msg != nil { - return func() tea.Msg { return msg } - } - return nil + return tea.Batch(cmds...) } // Update implements tea.Model. @@ -175,7 +168,6 @@ func (ui *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg.IdentifiableItem.(type) { case selection.Item: if ui.activePage == selectionPage { - ui.activePage = repoPage cmds = append(cmds, ui.setRepoCmd(msg.ID())) } } @@ -237,13 +229,27 @@ func (ui *UI) View() string { } func (ui *UI) setRepoCmd(rn string) tea.Cmd { - rs := ui.s.Config().Source + rs := ui.s.Source() return func() tea.Msg { for _, r := range rs.AllRepos() { if r.Name() == rn { + ui.activePage = repoPage return repo.RepoMsg(r) } } return common.ErrorMsg(git.ErrMissingRepo) } } + +func (ui *UI) initialRepoCmd(rn string) tea.Cmd { + rs := ui.s.Source() + return func() tea.Msg { + for _, r := range rs.AllRepos() { + if r.Name() == rn { + ui.activePage = repoPage + return repo.RepoMsg(r) + } + } + return nil + } +}