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
}
}
Ayman Bagabas created
config/config.go | 2 +
config/git.go | 33 ++++++++++++----
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(-)
@@ -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
}
}
@@ -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
@@ -1,4 +1,4 @@
-package ui
+package server
import (
"github.com/charmbracelet/soft-serve/config"
@@ -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()
@@ -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)
+}
@@ -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 {
@@ -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())))
@@ -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)
-}
@@ -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
}
@@ -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
+ }
+}