Skip to content

Commit

Permalink
fix: race lipgloss condition
Browse files Browse the repository at this point in the history
  • Loading branch information
aymanbagabas committed Jul 27, 2023
1 parent 125ce85 commit eb4372b
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 42 deletions.
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ require (
github.com/charmbracelet/bubbles v0.16.1
github.com/charmbracelet/bubbletea v0.24.2
github.com/charmbracelet/glamour v0.6.0
github.com/charmbracelet/lipgloss v0.7.1
github.com/charmbracelet/lipgloss v0.7.2-0.20230727003539-83909ad9a917
github.com/charmbracelet/wish v1.1.1
github.com/dustin/go-humanize v1.0.1
github.com/go-git/go-git/v5 v5.7.0
github.com/go-git/go-git/v5 v5.8.0
github.com/matryer/is v1.4.1
github.com/muesli/reflow v0.3.0
github.com/muesli/termenv v0.15.2
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM2
github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc=
github.com/charmbracelet/keygen v0.4.3 h1:ywOZRwkDlpmkawl0BgLTxaYWDSqp6Y4nfVVmgyyO1Mg=
github.com/charmbracelet/keygen v0.4.3/go.mod h1:4e4FT3HSdLU/u83RfJWvzJIaVb8aX4MxtDlfXwpDJaI=
github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
github.com/charmbracelet/lipgloss v0.7.2-0.20230727003539-83909ad9a917 h1:3HaVpdNIJYO3FPKpKoMAMX3MIozOg/O6qDNri/qNW9w=
github.com/charmbracelet/lipgloss v0.7.2-0.20230727003539-83909ad9a917/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
github.com/charmbracelet/log v0.2.3-0.20230725142510-280c4e3f1ef2 h1:0O3FNIElGsbl/nnUpeUVHqET7ZETJz6cUQocn/CKhoU=
github.com/charmbracelet/log v0.2.3-0.20230725142510-280c4e3f1ef2/go.mod h1:ZApwwzDbbETVTIRTk7724yQRJAXIktt98yGVMMaa3y8=
github.com/charmbracelet/ssh v0.0.0-20230720143903-5bdd92839155 h1:vJqYhlL0doAWQPz+EX/hK5x/ZYguoua773oRz77zYKo=
Expand All @@ -53,8 +53,8 @@ github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 h1:mtDjlmloH7ytdbl
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-git/v5 v5.7.0 h1:t9AudWVLmqzlo+4bqdf7GY+46SUuRsx59SboFxkq2aE=
github.com/go-git/go-git/v5 v5.7.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8=
github.com/go-git/go-git/v5 v5.8.0 h1:Rc543s6Tyq+YcyPwZRvU4jzZGM8rB/wWu94TnTIYALQ=
github.com/go-git/go-git/v5 v5.8.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8=
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
Expand Down
5 changes: 3 additions & 2 deletions server/ssh/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"time"

tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/soft-serve/server/access"
"github.com/charmbracelet/soft-serve/server/backend"
"github.com/charmbracelet/soft-serve/server/config"
Expand Down Expand Up @@ -55,8 +56,8 @@ func SessionHandler(s ssh.Session) *tea.Program {
}

envs := &sessionEnv{s}
output := termenv.NewOutput(s, termenv.WithColorCache(true), termenv.WithEnvironment(envs))
c := common.NewCommon(ctx, output, pty.Window.Width, pty.Window.Height)
re := lipgloss.NewRenderer(s, termenv.WithColorCache(true), termenv.WithEnvironment(envs))
c := common.NewCommon(ctx, re, pty.Window.Width, pty.Window.Height)
c.SetValue(common.ConfigKey, cfg)
m := ui.New(c, initialRepo)
p := tea.NewProgram(m,
Expand Down
6 changes: 4 additions & 2 deletions server/ui/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package common
import (
"context"

"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/log"
"github.com/charmbracelet/soft-serve/git"
"github.com/charmbracelet/soft-serve/server/backend"
Expand Down Expand Up @@ -31,20 +32,21 @@ type Common struct {
Styles *styles.Styles
KeyMap *keymap.KeyMap
Zone *zone.Manager
Renderer *lipgloss.Renderer
Output *termenv.Output
Logger *log.Logger
}

// NewCommon returns a new Common struct.
func NewCommon(ctx context.Context, out *termenv.Output, width, height int) Common {
func NewCommon(ctx context.Context, re *lipgloss.Renderer, width, height int) Common {
if ctx == nil {
ctx = context.TODO()
}
return Common{
ctx: ctx,
Width: width,
Height: height,
Output: out,
Output: re.Output(),
Styles: styles.DefaultStyles(),
KeyMap: keymap.DefaultKeyMap(),
Zone: zone.New(),
Expand Down
149 changes: 117 additions & 32 deletions server/ui/components/selector/selector.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package selector

import (
"sync"

"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
Expand All @@ -9,10 +11,13 @@ import (

// Selector is a list of items that can be selected.
type Selector struct {
list.Model
KeyMap list.KeyMap

model list.Model
common common.Common
active int
filterState list.FilterState
mtx sync.RWMutex
}

// IdentifiableItem is an item that can be identified by a string. Implements
Expand Down Expand Up @@ -42,95 +47,171 @@ func New(common common.Common, items []IdentifiableItem, delegate ItemDelegate)
l := list.New(itms, delegate, common.Width, common.Height)
l.Styles.NoItems = common.Styles.NoItems
s := &Selector{
Model: l,
model: l,
common: common,
KeyMap: list.DefaultKeyMap(),
}
s.SetSize(common.Width, common.Height)
return s
}

// FilterState returns the filter state.
func (s *Selector) FilterState() list.FilterState {
s.mtx.RLock()
defer s.mtx.RUnlock()
return s.model.FilterState()
}

// SelectedItem returns the selected item.
func (s *Selector) SelectedItem() list.Item {
s.mtx.RLock()
defer s.mtx.RUnlock()
return s.model.SelectedItem()
}

// Items returns the items.
func (s *Selector) Items() []list.Item {
s.mtx.RLock()
defer s.mtx.RUnlock()
return s.model.Items()
}

// VisibleItems returns the visible items.
func (s *Selector) VisibleItems() []list.Item {
s.mtx.RLock()
defer s.mtx.RUnlock()
return s.model.VisibleItems()
}

// PerPage returns the number of items per page.
func (s *Selector) PerPage() int {
return s.Model.Paginator.PerPage
s.mtx.RLock()
defer s.mtx.RUnlock()
return s.model.Paginator.PerPage
}

// SetPage sets the current page.
func (s *Selector) SetPage(page int) {
s.Model.Paginator.Page = page
s.mtx.Lock()
defer s.mtx.Unlock()
s.model.Paginator.Page = page
}

// Page returns the current page.
func (s *Selector) Page() int {
return s.Model.Paginator.Page
s.mtx.RLock()
defer s.mtx.RUnlock()
return s.model.Paginator.Page
}

// TotalPages returns the total number of pages.
func (s *Selector) TotalPages() int {
return s.Model.Paginator.TotalPages
s.mtx.RLock()
defer s.mtx.RUnlock()
return s.model.Paginator.TotalPages
}

// Select selects the item at the given index.
func (s *Selector) Select(index int) {
s.Model.Select(index)
s.mtx.Lock()
defer s.mtx.Unlock()
s.model.Select(index)
}

// SetShowTitle sets the show title flag.
func (s *Selector) SetShowTitle(show bool) {
s.Model.SetShowTitle(show)
s.mtx.Lock()
defer s.mtx.Unlock()
s.model.SetShowTitle(show)
}

// SetShowHelp sets the show help flag.
func (s *Selector) SetShowHelp(show bool) {
s.Model.SetShowHelp(show)
s.mtx.Lock()
defer s.mtx.Unlock()
s.model.SetShowHelp(show)
}

// SetShowStatusBar sets the show status bar flag.
func (s *Selector) SetShowStatusBar(show bool) {
s.Model.SetShowStatusBar(show)
s.mtx.Lock()
defer s.mtx.Unlock()
s.model.SetShowStatusBar(show)
}

// DisableQuitKeybindings disables the quit keybindings.
func (s *Selector) DisableQuitKeybindings() {
s.Model.DisableQuitKeybindings()
s.mtx.Lock()
defer s.mtx.Unlock()
s.model.DisableQuitKeybindings()
}

// SetShowFilter sets the show filter flag.
func (s *Selector) SetShowFilter(show bool) {
s.Model.SetShowFilter(show)
s.mtx.Lock()
defer s.mtx.Unlock()
s.model.SetShowFilter(show)
}

// SetShowPagination sets the show pagination flag.
func (s *Selector) SetShowPagination(show bool) {
s.Model.SetShowPagination(show)
s.mtx.Lock()
defer s.mtx.Unlock()
s.model.SetShowPagination(show)
}

// SetFilteringEnabled sets the filtering enabled flag.
func (s *Selector) SetFilteringEnabled(enabled bool) {
s.Model.SetFilteringEnabled(enabled)
s.mtx.Lock()
defer s.mtx.Unlock()
s.model.SetFilteringEnabled(enabled)
}

// SetSize implements common.Component.
func (s *Selector) SetSize(width, height int) {
s.common.SetSize(width, height)
s.Model.SetSize(width, height)
s.mtx.Lock()
s.model.SetSize(width, height)
s.mtx.Unlock()
}

// SetItems sets the items in the selector.
func (s *Selector) SetItems(items []IdentifiableItem) tea.Cmd {
s.mtx.Lock()
defer s.mtx.Unlock()
its := make([]list.Item, len(items))
for i, item := range items {
its[i] = item
}
return s.Model.SetItems(its)
return s.model.SetItems(its)
}

// Index returns the index of the selected item.
func (s *Selector) Index() int {
return s.Model.Index()
s.mtx.RLock()
defer s.mtx.RUnlock()
return s.model.Index()
}

// CursorUp moves the cursor up.
func (s *Selector) CursorUp() {
s.mtx.Lock()
defer s.mtx.Unlock()
s.model.CursorUp()
}

// CursorDown moves the cursor down.
func (s *Selector) CursorDown() {
s.mtx.Lock()
defer s.mtx.Unlock()
s.model.CursorDown()
}

// Init implements tea.Model.
func (s *Selector) Init() tea.Cmd {
s.mtx.Lock()
s.model.KeyMap = s.KeyMap
s.mtx.Unlock()
return s.activeCmd
}

Expand All @@ -141,26 +222,26 @@ func (s *Selector) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.MouseMsg:
switch msg.Type {
case tea.MouseWheelUp:
s.Model.CursorUp()
s.CursorUp()
case tea.MouseWheelDown:
s.Model.CursorDown()
s.CursorDown()
case tea.MouseLeft:
curIdx := s.Model.Index()
for i, item := range s.Model.Items() {
curIdx := s.Index()
for i, item := range s.Items() {
item, _ := item.(IdentifiableItem)
// Check each item to see if it's in bounds.
if item != nil && s.common.Zone.Get(item.ID()).InBounds(msg) {
if i == curIdx {
cmds = append(cmds, s.selectCmd)
} else {
s.Model.Select(i)
s.Select(i)
}
break
}
}
}
case tea.KeyMsg:
filterState := s.Model.FilterState()
filterState := s.FilterState()
switch {
case key.Matches(msg, s.common.KeyMap.Help):
if filterState == list.Filtering {
Expand All @@ -174,28 +255,32 @@ func (s *Selector) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case list.FilterMatchesMsg:
cmds = append(cmds, s.activeFilterCmd)
}
m, cmd := s.Model.Update(msg)
s.Model = m
s.mtx.Lock()
m, cmd := s.model.Update(msg)
s.model = m
if cmd != nil {
cmds = append(cmds, cmd)
}
s.mtx.Unlock()
// Track filter state and update active item when filter state changes.
filterState := s.Model.FilterState()
filterState := s.FilterState()
if s.filterState != filterState {
cmds = append(cmds, s.activeFilterCmd)
}
s.filterState = filterState
// Send ActiveMsg when index change.
if s.active != s.Model.Index() {
if s.active != s.Index() {
cmds = append(cmds, s.activeCmd)
}
s.active = s.Model.Index()
s.active = s.Index()
return s, tea.Batch(cmds...)
}

// View implements tea.Model.
func (s *Selector) View() string {
return s.Model.View()
s.mtx.RLock()
defer s.mtx.RUnlock()
return s.model.View()
}

// SelectItem is a command that selects the currently active item.
Expand All @@ -204,7 +289,7 @@ func (s *Selector) SelectItem() tea.Msg {
}

func (s *Selector) selectCmd() tea.Msg {
item := s.Model.SelectedItem()
item := s.SelectedItem()
i, ok := item.(IdentifiableItem)
if !ok {
return SelectMsg{}
Expand All @@ -213,7 +298,7 @@ func (s *Selector) selectCmd() tea.Msg {
}

func (s *Selector) activeCmd() tea.Msg {
item := s.Model.SelectedItem()
item := s.SelectedItem()
i, ok := item.(IdentifiableItem)
if !ok {
return ActiveMsg{}
Expand All @@ -225,7 +310,7 @@ func (s *Selector) activeFilterCmd() tea.Msg {
// Here we use VisibleItems because when list.FilterMatchesMsg is sent,
// VisibleItems is the only way to get the list of filtered items. The list
// bubble should export something like list.FilterMatchesMsg.Items().
items := s.Model.VisibleItems()
items := s.VisibleItems()
if len(items) == 0 {
return nil
}
Expand Down

0 comments on commit eb4372b

Please sign in to comment.