fix: various improvements

Ayman Bagabas created

* don't add line numbers to markdown files
* fix selection active item styles
* fix selection readme styles
* add selection description styles

Change summary

ui/components/code/code.go      | 26 ++++-----
ui/pages/repo/files.go          | 33 +++++++++---
ui/pages/repo/log.go            |  1 
ui/pages/selection/item.go      | 45 ++++++++++++-----
ui/pages/selection/selection.go | 89 ++++++++++++++++++----------------
ui/styles/styles.go             | 11 ++-
6 files changed, 123 insertions(+), 82 deletions(-)

Detailed changes

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 {

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
 }

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,
 		}

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()))
 }

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
 }

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"))