diff --git a/ui/components/code/code.go b/ui/components/code/code.go index fa05614dc2968a7b810f2c7d3a22473b12836ccc..44ab06ffa3d1983661d7a1bc5cf39dc407535302 100644 --- a/ui/components/code/code.go +++ b/ui/components/code/code.go @@ -88,16 +88,7 @@ func (r *Code) Init() tea.Cmd { if err != nil { return common.ErrorCmd(err) } - if r.showLineNumber { - f = withLineNumber(f) - } - // FIXME: this is a hack to reset formatting at the end of every line. - c = wrap.String(f, w) - s := strings.Split(c, "\n") - for i, l := range s { - s[i] = l + "\x1b[0m" - } - r.Viewport.Model.SetContent(strings.Join(s, "\n")) + r.Viewport.Model.SetContent(f) return nil } @@ -170,9 +161,6 @@ func (r *Code) ScrollPercent() float64 { func (r *Code) glamourize(w int, md string) (string, error) { r.renderMutex.Lock() defer r.renderMutex.Unlock() - if w > 120 { - w = 120 - } tr, err := glamour.NewTermRenderer( glamour.WithStyles(r.styleConfig), glamour.WithWordWrap(w), @@ -215,7 +203,17 @@ func (r *Code) renderFile(path, content string, width int) (string, error) { if err != nil { return "", err } - return s.String(), nil + c := s.String() + if r.showLineNumber { + c = withLineNumber(c) + } + // FIXME: this is a hack to reset formatting at the end of every line. + c = wrap.String(c, width) + f := strings.Split(c, "\n") + for i, l := range f { + f[i] = l + "\x1b[0m" + } + return strings.Join(f, "\n"), nil } func withLineNumber(s string) string { diff --git a/ui/pages/repo/files.go b/ui/pages/repo/files.go index 2018ea3d38f1ee38e0181ca0092640a0cb00409f..183fbc8890c8e7d5b4890e7329308d8adf2d4ebe 100644 --- a/ui/pages/repo/files.go +++ b/ui/pages/repo/files.go @@ -5,6 +5,7 @@ import ( "fmt" "path/filepath" + "github.com/alecthomas/chroma/lexers" "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" ggit "github.com/charmbracelet/soft-serve/git" @@ -107,12 +108,20 @@ func (f *Files) ShortHelp() []key.Binding { case filesViewContent: copyKey := f.common.KeyMap.Copy copyKey.SetHelp("c", "copy content") - return []key.Binding{ + b := []key.Binding{ f.common.KeyMap.UpDown, f.common.KeyMap.BackItem, copyKey, - lineNo, } + lexer := lexers.Match(f.currentContent.ext) + lang := "" + if lexer != nil && lexer.Config() != nil { + lang = lexer.Config().Name + } + if lang != "markdown" { + b = append(b, lineNo) + } + return b default: return []key.Binding{} } @@ -157,13 +166,21 @@ func (f *Files) FullHelp() [][]key.Binding { k.HalfPageDown, k.HalfPageUp, }, - { - k.Down, - k.Up, - copyKey, - lineNo, - }, }...) + lc := []key.Binding{ + k.Down, + k.Up, + copyKey, + } + lexer := lexers.Match(f.currentContent.ext) + lang := "" + if lexer != nil && lexer.Config() != nil { + lang = lexer.Config().Name + } + if lang != "markdown" { + lc = append(lc, lineNo) + } + b = append(b, lc) } return b } diff --git a/ui/pages/repo/log.go b/ui/pages/repo/log.go index a20e08098ec3de59039ef1680625bf7ba21bb213..8eabe8d65baf782b6d9189e64b5d80cc7d833086 100644 --- a/ui/pages/repo/log.go +++ b/ui/pages/repo/log.go @@ -88,6 +88,7 @@ func (l *Log) ShortHelp() []key.Binding { copyKey := l.common.KeyMap.Copy copyKey.SetHelp("c", "copy hash") return []key.Binding{ + l.common.KeyMap.UpDown, l.common.KeyMap.SelectItem, copyKey, } diff --git a/ui/pages/selection/item.go b/ui/pages/selection/item.go index fa4d0abfccc65fc4ddc02137b3c5afd25c23eb0e..e7086fc2d82dddc25408f91712f78a8cd02b4cc9 100644 --- a/ui/pages/selection/item.go +++ b/ui/pages/selection/item.go @@ -61,7 +61,7 @@ func (d ItemDelegate) Height() int { } // Spacing returns the spacing between items. Implements list.ItemDelegate. -func (d ItemDelegate) Spacing() int { return 0 } +func (d ItemDelegate) Spacing() int { return 1 } // Update implements list.ItemDelegate. func (d ItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { @@ -97,10 +97,13 @@ func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list itemStyle := styles.MenuItem.Copy() if isSelected { - itemStyle = itemStyle.BorderForeground(styles.ActiveBorderColor) + itemStyle = itemStyle.Copy(). + BorderStyle(lipgloss.Border{ + Left: "┃", + }). + BorderForeground(styles.ActiveBorderColor) if d.activeBox != nil && *d.activeBox == readmeBox { - // TODO make this into its own color - itemStyle = itemStyle.BorderForeground(lipgloss.Color("15")) + itemStyle = itemStyle.BorderForeground(styles.InactiveBorderColor) } } @@ -108,14 +111,17 @@ func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list if i.repo.IsPrivate() { title += " 🔒" } + if isSelected { + title += " " + } updatedStr := fmt.Sprintf(" Updated %s", humanize.Time(i.lastUpdate)) - updated := styles.MenuLastUpdate. - Copy(). - Width(m.Width() - itemStyle.GetHorizontalFrameSize() - lipgloss.Width(title)). - Render(updatedStr) - titleStyle := lipgloss.NewStyle(). - Align(lipgloss.Left). - Width(m.Width() - itemStyle.GetHorizontalFrameSize() - lipgloss.Width(updated)) + updatedStyle := styles.MenuLastUpdate.Copy(). + Align(lipgloss.Right). + Width(m.Width() - itemStyle.GetHorizontalFrameSize() - lipgloss.Width(title)) + if isSelected { + updatedStyle = updatedStyle.Bold(true) + } + updated := updatedStyle.Render(updatedStr) if isFiltered && index < len(m.VisibleItems()) { // Get indices of matched characters @@ -125,19 +131,30 @@ func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list if isFiltered { unmatched := lipgloss.NewStyle().Inline(true) matched := unmatched.Copy().Underline(true) + if isSelected { + unmatched = unmatched.Bold(true) + matched = matched.Bold(true) + } title = lipgloss.StyleRunes(title, matchedRunes, matched, unmatched) } + titleStyle := lipgloss.NewStyle() + if isSelected { + titleStyle = titleStyle.Bold(true) + } title = titleStyle.Render(title) + desc := lipgloss.NewStyle(). + Faint(true). + Render(i.Description()) s.WriteString(lipgloss.JoinHorizontal(lipgloss.Bottom, title, updated)) s.WriteString("\n") - s.WriteString(i.Description()) - s.WriteString("\n\n") + s.WriteString(desc) + s.WriteString("\n") cmdStyle := styles.RepoCommand.Copy() cmd := cmdStyle.Render(i.Command()) if !i.copied.IsZero() && i.copied.Add(time.Second).After(time.Now()) { cmd = cmdStyle.Render("Copied!") } s.WriteString(cmd) - w.Write([]byte(itemStyle.Render(s.String()))) + fmt.Fprint(w, itemStyle.Render(s.String())) } diff --git a/ui/pages/selection/selection.go b/ui/pages/selection/selection.go index 76dfd04db9735bd47826d822e9251191116831b1..7f21a2f655a59f9f18c9653e144112b1bf424eba 100644 --- a/ui/pages/selection/selection.go +++ b/ui/pages/selection/selection.go @@ -1,6 +1,8 @@ package selection import ( + "strings" + "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" @@ -21,11 +23,12 @@ const ( // Selection is the model for the selection screen/page. type Selection struct { - s session.Session - common common.Common - readme *code.Code - selector *selector.Selector - activeBox box + s session.Session + common common.Common + readme *code.Code + readmeHeight int + selector *selector.Selector + activeBox box } // New creates a new selection model. @@ -49,20 +52,31 @@ func New(s session.Session, common common.Common) *Selection { return sel } +func (s *Selection) getReadmeHeight() int { + rh := s.readmeHeight + if rh > s.common.Height/3 { + rh = s.common.Height / 3 + } + return rh +} + +func (s *Selection) getMargins() (wm, hm int) { + wm = 0 + hm = s.common.Styles.SelectorBox.GetVerticalFrameSize() + + s.common.Styles.SelectorBox.GetHeight() + if rh := s.getReadmeHeight(); rh > 0 { + hm += s.common.Styles.ReadmeBox.GetVerticalFrameSize() + + rh + } + return +} + // SetSize implements common.Component. func (s *Selection) SetSize(width, height int) { s.common.SetSize(width, height) - sw := s.common.Styles.SelectorBox.GetWidth() - wm := sw + - s.common.Styles.SelectorBox.GetHorizontalFrameSize() + - s.common.Styles.ReadmeBox.GetHorizontalFrameSize() + - // +1 to get wrapping to work. - // This is needed because the readme box width has to be -1 from the - // readme style in order for wrapping to not break. - 1 - hm := s.common.Styles.ReadmeBox.GetVerticalFrameSize() - s.readme.SetSize(width-wm, height-hm) - s.selector.SetSize(sw, height) + wm, hm := s.getMargins() + s.readme.SetSize(width-wm, s.getReadmeHeight()) + s.selector.SetSize(width-wm, height-hm) } // ShortHelp implements help.KeyMap. @@ -135,6 +149,7 @@ func (s *Selection) FullHelp() [][]key.Binding { // Init implements tea.Model. func (s *Selection) Init() tea.Cmd { + var readmeCmd tea.Cmd items := make([]selector.IdentifiableItem, 0) cfg := s.s.Config() pk := s.s.PublicKey() @@ -153,6 +168,11 @@ func (s *Selection) Init() tea.Cmd { }) } for _, r := range cfg.Source.AllRepos() { + if r.Repo() == "config" { + rm, rp := r.Readme() + s.readmeHeight = strings.Count(rm, "\n") + readmeCmd = s.readme.SetContent(rm, rp) + } if r.IsPrivate() && cfg.AuthRepo(r.Repo(), pk) < wgit.AdminAccess { continue } @@ -189,6 +209,7 @@ func (s *Selection) Init() tea.Cmd { return tea.Batch( s.selector.Init(), s.selector.SetItems(items), + readmeCmd, ) } @@ -207,10 +228,6 @@ func (s *Selection) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if cmd != nil { cmds = append(cmds, cmd) } - case selector.ActiveMsg: - cmds = append(cmds, s.changeActive(msg)) - // reset readme position when active item change - s.readme.GotoTop() case tea.KeyMsg: switch { case key.Matches(msg, s.common.KeyMap.Section): @@ -238,30 +255,20 @@ func (s *Selection) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // View implements tea.Model. func (s *Selection) View() string { - wm := s.common.Styles.SelectorBox.GetWidth() + - s.common.Styles.SelectorBox.GetHorizontalFrameSize() + - s.common.Styles.ReadmeBox.GetHorizontalFrameSize() - hm := s.common.Styles.ReadmeBox.GetVerticalFrameSize() + rh := s.getReadmeHeight() rs := s.common.Styles.ReadmeBox.Copy(). - Width(s.common.Width - wm). - Height(s.common.Height - hm) + Width(s.common.Width). + Height(rh) if s.activeBox == readmeBox { rs.BorderForeground(s.common.Styles.ActiveBorderColor) } - readme := rs.Render(s.readme.View()) - return lipgloss.JoinHorizontal( - lipgloss.Top, - readme, - s.selector.View(), - ) -} - -func (s *Selection) changeActive(msg selector.ActiveMsg) tea.Cmd { - item, ok := msg.IdentifiableItem.(Item) - if !ok { - return nil + view := s.selector.View() + if rh > 0 { + readme := rs.Render(s.readme.View()) + view = lipgloss.JoinVertical(lipgloss.Top, + readme, + view, + ) } - r := item.repo - rm, rp := r.Readme() - return s.readme.SetContent(rm, rp) + return view } diff --git a/ui/styles/styles.go b/ui/styles/styles.go index 0ec74fc76e70554f1b224d96ec94e5e8da16816c..20a583b92779ed5ff0fe8ca53375888cd384aad1 100644 --- a/ui/styles/styles.go +++ b/ui/styles/styles.go @@ -126,10 +126,11 @@ func DefaultStyles() *Styles { SetString(">") s.MenuItem = lipgloss.NewStyle(). - Padding(1, 2). - Height(4). - Border(lipgloss.RoundedBorder()). - BorderForeground(lipgloss.Color("241")) + PaddingLeft(1). + Border(lipgloss.Border{ + Left: " ", + }, false, false, false, true). + Height(3) s.MenuLastUpdate = lipgloss.NewStyle(). Foreground(lipgloss.Color("241")). @@ -253,7 +254,7 @@ func DefaultStyles() *Styles { s.LogItemActive = s.LogItemInactive.Copy(). Border(lipgloss.Border{ - Left: "│", + Left: "┃", }, false, false, false, true). BorderForeground(lipgloss.Color("#B083EA"))