diff --git a/viewport/keymap.go b/viewport/keymap.go index 9289706..060bb87 100644 --- a/viewport/keymap.go +++ b/viewport/keymap.go @@ -15,6 +15,8 @@ type KeyMap struct { HalfPageDown key.Binding Down key.Binding Up key.Binding + Left key.Binding + Right key.Binding } // DefaultKeyMap returns a set of pager-like default keybindings. @@ -44,5 +46,13 @@ func DefaultKeyMap() KeyMap { key.WithKeys("down", "j"), key.WithHelp("↓/j", "down"), ), + Left: key.NewBinding( + key.WithKeys("left", "h"), + key.WithHelp("←/h", "move left"), + ), + Right: key.NewBinding( + key.WithKeys("right", "l"), + key.WithHelp("→/l", "move right"), + ), } } diff --git a/viewport/viewport.go b/viewport/viewport.go index f220d1e..99b137e 100644 --- a/viewport/viewport.go +++ b/viewport/viewport.go @@ -54,6 +54,10 @@ type Model struct { // Deprecated: high performance rendering is now deprecated in Bubble Tea. HighPerformanceRendering bool + // horizontal step represents the step of indent we add with one move left or right. + horizontalStep int + + indent int initialized bool lines []string } @@ -63,6 +67,7 @@ func (m *Model) setInitialValues() { m.MouseWheelEnabled = true m.MouseWheelDelta = 3 m.initialized = true + m.horizontalStep = 5 } // Init exists to satisfy the tea.Model interface for composability purposes. @@ -123,6 +128,16 @@ func (m Model) visibleLines() (lines []string) { bottom := clamp(m.YOffset+m.Height, top, len(m.lines)) lines = m.lines[top:bottom] } + + if m.indent > 0 { + cutLines := make([]string, len(lines)) + for i := range lines { + cutLines[i] = getStringWithIndent(lines[i], m.indent) + } + + return cutLines + } + return lines } @@ -290,6 +305,36 @@ func ViewUp(m Model, lines []string) tea.Cmd { return tea.ScrollUp(lines, top, bottom) } +// SetHorizontalStep is a setter for `horizontalStep`. +// Must be set before `MoveLeft` or `MoveRight` is used. +// If 0 or negative, left/right movement doesn't work. +func (m *Model) SetHorizontalStep(n int) { + if n < 1 { + n = 0 + } + + m.horizontalStep = n +} + +// MoveLeft moves all lines to set runes left. +// If current indent is 0, it doesn't work. +func (m *Model) MoveLeft() { + m.indent -= m.horizontalStep + if m.indent < 0 { + m.indent = 0 + } +} + +// MoveRight moves all lines to set runes right. +func (m *Model) MoveRight() { + m.indent += m.horizontalStep +} + +// Resets lines indent to zero. +func (m *Model) ResetIndent() { + m.indent = 0 +} + // Update handles standard message-based viewport updates. func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { var cmd tea.Cmd @@ -344,6 +389,12 @@ func (m Model) updateAsModel(msg tea.Msg) (Model, tea.Cmd) { if m.HighPerformanceRendering { cmd = ViewUp(m, lines) } + + case key.Matches(msg, m.KeyMap.Left): + m.MoveLeft() + + case key.Matches(msg, m.KeyMap.Right): + m.MoveRight() } case tea.MouseMsg: @@ -418,3 +469,16 @@ func max(a, b int) int { } return b } + +func getStringWithIndent(s string, indent int) string { + if indent == 0 { + return s + } + + r := []rune(s) + if indent > len(r) { + return "" + } + + return string(r[indent:]) +}