Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

horizontal scrolls and other improvements #791

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@ The above example is running from a single shell script ([source](./examples/dem

## Tutorial

Gum provides highly configurable, ready-to-use utilities to help you write
useful shell scripts and dotfiles aliases with just a few lines of code.
Let's build a simple script to help you write
[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary)
for your dotfiles.
Gum provides highly configurable, ready-to-use utilities to help you write useful shell scripts and dotfiles aliases with just a few lines of code.
Let's build a simple script to help you write [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) for your dotfiles.

Ask for the commit type with gum choose:

Expand Down
73 changes: 42 additions & 31 deletions filter/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/x/ansi"
"github.com/sahilm/fuzzy"
)

Expand All @@ -31,6 +30,18 @@ func defaultKeymap() keymap {
Up: key.NewBinding(
key.WithKeys("up", "ctrl+k", "ctrl+p"),
),
Left: key.NewBinding(
key.WithKeys("left"),
),
Right: key.NewBinding(
key.WithKeys("right"),
),
NLeft: key.NewBinding(
key.WithKeys("h"),
),
NRight: key.NewBinding(
key.WithKeys("l"),
),
NDown: key.NewBinding(
key.WithKeys("j"),
),
Expand Down Expand Up @@ -93,6 +104,10 @@ type keymap struct {
Up,
NDown,
NUp,
Right,
Left,
NRight,
NLeft,
Home,
End,
ToggleAndNext,
Expand All @@ -111,8 +126,8 @@ func (k keymap) FullHelp() [][]key.Binding { return nil }
func (k keymap) ShortHelp() []key.Binding {
return []key.Binding{
key.NewBinding(
key.WithKeys("up", "down"),
key.WithHelp("↓↑", "navigate"),
key.WithKeys("left", "down", "up", "rigth"),
key.WithHelp("←↓↑→", "navigate"),
),
k.FocusInSearch,
k.FocusOutSearch,
Expand Down Expand Up @@ -187,22 +202,11 @@ func (m model) View() string {
// The line's text style is set depending on whether or not the cursor
// points to this line.
if i == m.cursor {
s.WriteString(m.indicatorStyle.Render(m.indicator))
lineTextStyle = m.cursorTextStyle
} else {
s.WriteString(strings.Repeat(" ", lipgloss.Width(m.indicator)))
lineTextStyle = m.textStyle
}

// If there are multiple selections mark them, otherwise leave an empty space
if _, ok := m.selected[match.Str]; ok {
s.WriteString(m.selectedPrefixStyle.Render(m.selectedPrefix))
} else if m.limit > 1 {
s.WriteString(m.unselectedPrefixStyle.Render(m.unselectedPrefix))
} else {
s.WriteString(" ")
}

styledOption := m.choices[match.Str]
if len(match.MatchedIndexes) == 0 {
// No matches, just render the text.
Expand All @@ -211,36 +215,23 @@ func (m model) View() string {
continue
}

var buf strings.Builder
lastIdx := 0

// Use ansi.Truncate and ansi.TruncateLeft and ansi.StringWidth to
// style match.MatchedIndexes without losing the original option style:
ranges := []lipgloss.Range{}
for _, rng := range matchedRanges(match.MatchedIndexes) {
// fmt.Print("here ", lastIdx, rng, " - ", match.Str[rng[0]:rng[1]+1], "\r\n")
// Add the text before this match
if rng[0] > lastIdx {
buf.WriteString(ansi.Cut(styledOption, lastIdx, rng[0]))
}

// Add the matched character with highlight
buf.WriteString(m.matchStyle.Render(ansi.Cut(match.Str, rng[0], rng[1]+1)))
lastIdx = rng[1] + 1
ranges = append(ranges, lipgloss.NewRange(rng[0], rng[1]+1, m.matchStyle))
}

// Add any remaining text after the last match
buf.WriteString(ansi.TruncateLeft(styledOption, lastIdx, ""))

// Flush text buffer.
s.WriteString(lineTextStyle.Render(buf.String()))
s.WriteString(lipgloss.StyleRanges(styledOption, ranges...))

// We have finished displaying the match with all of it's matched
// characters highlighted and the rest filled in.
// Move on to the next match.
s.WriteRune('\n')
}

m.viewport.SetContent(s.String())
m.viewport.SetContent(strings.TrimSpace(s.String()))

help := ""
if m.showHelp {
Expand Down Expand Up @@ -276,10 +267,26 @@ func (m model) helpView() string {
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.viewport.LeftGutterFunc = func(gc viewport.GutterContext) string {
selectGutter := ""
if m.limit > 1 {
selectGutter = m.unselectedPrefixStyle.Render(m.unselectedPrefix)
}
if gc.Index < len(m.matches)-1 {
if _, ok := m.selected[m.matches[gc.Index].Str]; ok {
selectGutter = m.selectedPrefixStyle.Render(m.selectedPrefix)
}
}
if gc.Index == m.cursor {
return m.indicatorStyle.Render(m.indicator) + selectGutter
}
return strings.Repeat(" ", lipgloss.Width(m.indicator)) + selectGutter
}
var cmd, icmd tea.Cmd
m.textinput, icmd = m.textinput.Update(msg)
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.textinput.Width = msg.Width
if m.height == 0 || m.height > msg.Height {
m.viewport.Height = msg.Height - lipgloss.Height(m.textinput.View())
}
Expand Down Expand Up @@ -316,6 +323,10 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.CursorDown()
case key.Matches(msg, km.Up, km.NUp):
m.CursorUp()
case key.Matches(msg, km.Right, km.NRight):
m.viewport.MoveRight(6)
case key.Matches(msg, km.Left, km.NLeft):
m.viewport.MoveLeft(6)
case key.Matches(msg, km.Home):
m.cursor = 0
m.viewport.GotoTop()
Expand Down
2 changes: 1 addition & 1 deletion filter/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
type Options struct {
Options []string `arg:"" optional:"" help:"Options to filter."`

Indicator string `help:"Character for selection" default:"•" env:"GUM_FILTER_INDICATOR"`
Indicator string `help:"Character for selection" default:"• " env:"GUM_FILTER_INDICATOR"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this espace addition made on purpose?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

this is still very wip though

IndicatorStyle style.Styles `embed:"" prefix:"indicator." set:"defaultForeground=212" envprefix:"GUM_FILTER_INDICATOR_"`
Limit int `help:"Maximum number of options to pick" default:"1" group:"Selection"`
NoLimit bool `help:"Pick unlimited number of options (ignores limit)" group:"Selection"`
Expand Down
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ require (
github.com/charmbracelet/x/ansi v0.6.1-0.20250107110353-48b574af22a5
github.com/charmbracelet/x/editor v0.1.0
github.com/charmbracelet/x/term v0.2.1
github.com/muesli/reflow v0.3.0
github.com/muesli/roff v0.1.0
github.com/muesli/termenv v0.15.3-0.20241211131612-0d230cb6eb15
github.com/sahilm/fuzzy v0.1.1
Expand All @@ -39,6 +38,7 @@ require (
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/mango v0.2.0 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/yuin/goldmark v1.7.4 // indirect
github.com/yuin/goldmark-emoji v1.0.4 // indirect
Expand All @@ -48,3 +48,7 @@ require (
golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.22.0 // indirect
)

replace github.com/charmbracelet/bubbles => ../bubbles

replace github.com/charmbracelet/lipgloss => ../lipgloss
8 changes: 2 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,18 @@ github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWp
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
github.com/charmbracelet/bubbletea v1.2.5-0.20241207142916-e0515bc22ad1 h1:osd3dk14DEriOrqJBWzeDE9eN2Yd00BkKzFAiLXxkS8=
github.com/charmbracelet/bubbletea v1.2.5-0.20241207142916-e0515bc22ad1/go.mod h1:Hbk5+oE4a7cDyjfdPi4sHZ42aGTMYcmHnVDhsRswn7A=
github.com/charmbracelet/glamour v0.8.0 h1:tPrjL3aRcQbn++7t18wOpgLyl8wrOHUEDS7IZ68QtZs=
github.com/charmbracelet/glamour v0.8.0/go.mod h1:ViRgmKkf3u5S7uakt2czJ272WSg2ZenlYEZXT2x7Bjw=
github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
github.com/charmbracelet/x/ansi v0.6.1-0.20250107110353-48b574af22a5 h1:TSjbA80sXnABV/Vxhnb67Ho7p8bEYqz6NIdhLAx+1yg=
github.com/charmbracelet/x/ansi v0.6.1-0.20250107110353-48b574af22a5/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q=
github.com/charmbracelet/x/editor v0.1.0 h1:p69/dpvlwRTs9uYiPeAWruwsHqTFzHhTvQOd/WVSX98=
github.com/charmbracelet/x/editor v0.1.0/go.mod h1:oivrEbcP/AYt/Hpvk5pwDXXrQ933gQS6UzL6fxqAGSA=
github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b h1:MnAMdlwSltxJyULnrYbkZpp4k58Co7Tah3ciKhSNo0Q=
github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
Expand Down
32 changes: 17 additions & 15 deletions pager/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package pager

import (
"fmt"
"regexp"

"github.com/charmbracelet/bubbles/help"
"github.com/charmbracelet/bubbles/viewport"
Expand All @@ -18,30 +17,33 @@ func (o Options) Run() error {
vp.Style = o.Style.ToLipgloss()

if o.Content == "" {
stdin, err := stdin.Read()
stdin, err := stdin.Read(stdin.StripANSI(true))
if err != nil {
return fmt.Errorf("unable to read stdin")
}
if stdin != "" {
// Sanitize the input from stdin by removing backspace sequences.
backspace := regexp.MustCompile(".\x08")
o.Content = backspace.ReplaceAllString(stdin, "")
o.Content = stdin
} else {
return fmt.Errorf("provide some content to display")
}
}

if o.ShowLineNumbers {
vp.LeftGutterFunc = viewport.LineNumberGutter(o.LineNumberStyle.ToLipgloss())
}

vp.SoftWrap = o.SoftWrap
vp.FillHeight = o.ShowLineNumbers
vp.SetContent(o.Content)
vp.HighlightStyle = o.MatchStyle.ToLipgloss()
vp.SelectedHighlightStyle = o.MatchHighlightStyle.ToLipgloss()

m := model{
viewport: vp,
help: help.New(),
content: o.Content,
origContent: o.Content,
showLineNumbers: o.ShowLineNumbers,
lineNumberStyle: o.LineNumberStyle.ToLipgloss(),
softWrap: o.SoftWrap,
matchStyle: o.MatchStyle.ToLipgloss(),
matchHighlightStyle: o.MatchHighlightStyle.ToLipgloss(),
keymap: defaultKeymap(),
viewport: vp,
help: help.New(),
showLineNumbers: o.ShowLineNumbers,
lineNumberStyle: o.LineNumberStyle.ToLipgloss(),
keymap: defaultKeymap(),
}

ctx, cancel := timeout.Context(o.Timeout)
Expand Down
2 changes: 1 addition & 1 deletion pager/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type Options struct {
//nolint:staticcheck
Style style.Styles `embed:"" help:"Style the pager" set:"defaultBorder=rounded" set:"defaultPadding=0 1" set:"defaultBorderForeground=212" envprefix:"GUM_PAGER_"`
Content string `arg:"" optional:"" help:"Display content to scroll"`
ShowLineNumbers bool `help:"Show line numbers" default:"true"`
ShowLineNumbers bool `help:"Show line numbers" default:"true" negatable:""`
LineNumberStyle style.Styles `embed:"" prefix:"line-number." help:"Style the line numbers" set:"defaultForeground=237" envprefix:"GUM_PAGER_LINE_NUMBER_"`
SoftWrap bool `help:"Soft wrap lines" default:"true" negatable:""`
MatchStyle style.Styles `embed:"" prefix:"match." help:"Style the matched text" set:"defaultForeground=212" set:"defaultBold=true" envprefix:"GUM_PAGER_MATCH_"` //nolint:staticcheck
Expand Down
Loading
Loading