feat: added support for full page jumping
This commit is contained in:
parent
a9dd5c008f
commit
0767ee0982
@ -412,6 +412,271 @@ func TestHalfPageScrollRoundTrip(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests use terminal 80x30: viewportH=28, full-scroll=28, scrollOff=8, safe zone relY 8-19.
|
||||||
|
|
||||||
|
func TestFullPageScrollDown(t *testing.T) {
|
||||||
|
t.Run("ctrl+f scrolls viewport down by full page", func(t *testing.T) {
|
||||||
|
// cursor at line 15 (relY=15, in safe zone), scrollY starts at 0
|
||||||
|
// After ctrl+f: newScrollY=28, newCursorY=28+15=43
|
||||||
|
lines := generateLines(100)
|
||||||
|
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 15}, 80, 30)
|
||||||
|
sendKeys(tm, "ctrl+f")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveWindow().ScrollY != 28 {
|
||||||
|
t.Errorf("ScrollY() = %d, want 28", m.ActiveWindow().ScrollY)
|
||||||
|
}
|
||||||
|
if m.ActiveWindow().Cursor.Line != 43 {
|
||||||
|
t.Errorf("CursorY() = %d, want 43", m.ActiveWindow().Cursor.Line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ctrl+f preserves cursor relative position in viewport", func(t *testing.T) {
|
||||||
|
// relY=15 before and after
|
||||||
|
lines := generateLines(100)
|
||||||
|
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 15}, 80, 30)
|
||||||
|
sendKeys(tm, "ctrl+f")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
relY := m.ActiveWindow().Cursor.Line - m.ActiveWindow().ScrollY
|
||||||
|
if relY != 15 {
|
||||||
|
t.Errorf("relative position = %d, want 15", relY)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ctrl+f clamps cursor to scrollOff when cursor is near top", func(t *testing.T) {
|
||||||
|
// cursor at line 0 (relY=0 < scrollOff=8), clamp to scrollOff
|
||||||
|
// After ctrl+f: newScrollY=28, newCursorY=28+8=36
|
||||||
|
lines := generateLines(100)
|
||||||
|
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 0}, 80, 30)
|
||||||
|
sendKeys(tm, "ctrl+f")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveWindow().ScrollY != 28 {
|
||||||
|
t.Errorf("ScrollY() = %d, want 28", m.ActiveWindow().ScrollY)
|
||||||
|
}
|
||||||
|
if m.ActiveWindow().Cursor.Line != 36 {
|
||||||
|
t.Errorf("CursorY() = %d, want 36", m.ActiveWindow().Cursor.Line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ctrl+f at end of file does not scroll past max", func(t *testing.T) {
|
||||||
|
// 50 lines, viewport 28: maxScroll=22
|
||||||
|
// cursor at line 45: AdjustScroll puts cursor at scrollY=22 (clamped), relY=23
|
||||||
|
// After ctrl+f: newScrollY clamped to 22, relY=23>19 clamped to 19, newCursorY=41
|
||||||
|
lines := generateLines(50)
|
||||||
|
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 45}, 80, 30)
|
||||||
|
sendKeys(tm, "ctrl+f")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
maxScroll := 50 - 28
|
||||||
|
if m.ActiveWindow().ScrollY > maxScroll {
|
||||||
|
t.Errorf("ScrollY() = %d, want <= %d", m.ActiveWindow().ScrollY, maxScroll)
|
||||||
|
}
|
||||||
|
if m.ActiveWindow().Cursor.Line != 41 {
|
||||||
|
t.Errorf("CursorY() = %d, want 41", m.ActiveWindow().Cursor.Line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ctrl+f on file smaller than viewport does not crash", func(t *testing.T) {
|
||||||
|
// 20 lines < viewport 28: maxScroll=0, scrollY stays 0
|
||||||
|
// relY=0 < scrollOff, clamp to 8; newCursorY=0+8=8
|
||||||
|
lines := generateLines(20)
|
||||||
|
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 0}, 80, 30)
|
||||||
|
sendKeys(tm, "ctrl+f")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveWindow().ScrollY != 0 {
|
||||||
|
t.Errorf("ScrollY() = %d, want 0", m.ActiveWindow().ScrollY)
|
||||||
|
}
|
||||||
|
if m.ActiveWindow().Cursor.Line != 8 {
|
||||||
|
t.Errorf("CursorY() = %d, want 8", m.ActiveWindow().Cursor.Line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ctrl+f clamps cursor x to new line length", func(t *testing.T) {
|
||||||
|
// Lines 0-27: "hello world" (11 chars), lines 28-99: "hi" (2 chars)
|
||||||
|
// cursor at line 5, cursorX=10
|
||||||
|
// ctrl+f: relY=5<8 clamp to 8; newScrollY=28; newCursorY=36 (a "hi" line)
|
||||||
|
// ClampCursorX: 10 >= 2, so cursorX=2
|
||||||
|
lines := make([]string, 100)
|
||||||
|
for i := range 28 {
|
||||||
|
lines[i] = "hello world"
|
||||||
|
}
|
||||||
|
for i := 28; i < 100; i++ {
|
||||||
|
lines[i] = "hi"
|
||||||
|
}
|
||||||
|
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 10, Line: 5}, 80, 30)
|
||||||
|
sendKeys(tm, "ctrl+f")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveWindow().Cursor.Line != 36 {
|
||||||
|
t.Errorf("CursorY() = %d, want 36", m.ActiveWindow().Cursor.Line)
|
||||||
|
}
|
||||||
|
if m.ActiveWindow().Cursor.Col > m.ActiveBuffer().Lines[m.ActiveWindow().Cursor.Line].Len() {
|
||||||
|
t.Errorf("CursorX() = %d exceeds line length %d", m.ActiveWindow().Cursor.Col, m.ActiveBuffer().Lines[m.ActiveWindow().Cursor.Line].Len())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("multiple ctrl+f presses scroll incrementally", func(t *testing.T) {
|
||||||
|
// cursor at line 15, scrollY=0, relY=15
|
||||||
|
// ctrl+f #1: scrollY=28, cursorY=43, relY=15
|
||||||
|
// ctrl+f #2: scrollY=56, cursorY=71, relY=15
|
||||||
|
lines := generateLines(200)
|
||||||
|
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 15}, 80, 30)
|
||||||
|
sendKeys(tm, "ctrl+f", "ctrl+f")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveWindow().ScrollY != 56 {
|
||||||
|
t.Errorf("ScrollY() = %d, want 56", m.ActiveWindow().ScrollY)
|
||||||
|
}
|
||||||
|
if m.ActiveWindow().Cursor.Line != 71 {
|
||||||
|
t.Errorf("CursorY() = %d, want 71", m.ActiveWindow().Cursor.Line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFullPageScrollUp(t *testing.T) {
|
||||||
|
t.Run("ctrl+b scrolls viewport up by full page", func(t *testing.T) {
|
||||||
|
// cursor at line 50: AdjustScroll -> scrollY=31, relY=19
|
||||||
|
// After ctrl+b: newScrollY=3, newCursorY=3+19=22
|
||||||
|
lines := generateLines(100)
|
||||||
|
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 50}, 80, 30)
|
||||||
|
sendKeys(tm, "ctrl+b")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveWindow().ScrollY != 3 {
|
||||||
|
t.Errorf("ScrollY() = %d, want 3", m.ActiveWindow().ScrollY)
|
||||||
|
}
|
||||||
|
if m.ActiveWindow().Cursor.Line != 22 {
|
||||||
|
t.Errorf("CursorY() = %d, want 22", m.ActiveWindow().Cursor.Line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ctrl+b preserves cursor relative position in viewport", func(t *testing.T) {
|
||||||
|
// relY=19 before and after
|
||||||
|
lines := generateLines(100)
|
||||||
|
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 50}, 80, 30)
|
||||||
|
sendKeys(tm, "ctrl+b")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
relY := m.ActiveWindow().Cursor.Line - m.ActiveWindow().ScrollY
|
||||||
|
if relY != 19 {
|
||||||
|
t.Errorf("relative position = %d, want 19", relY)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ctrl+b at top of file does not make scrollY negative", func(t *testing.T) {
|
||||||
|
// cursor at line 20, scrollY=0, relY=20 (clamped to 19 by scrollOff)
|
||||||
|
// AdjustScroll: relY=20>19, clamp to 19, scrollY stays 0, cursorY=19
|
||||||
|
// ctrl+b: newScrollY=max(0,-28)=0, relY=19 preserved, cursorY=19
|
||||||
|
lines := generateLines(100)
|
||||||
|
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 20}, 80, 30)
|
||||||
|
sendKeys(tm, "ctrl+b")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveWindow().ScrollY < 0 {
|
||||||
|
t.Errorf("ScrollY() = %d, want >= 0", m.ActiveWindow().ScrollY)
|
||||||
|
}
|
||||||
|
if m.ActiveWindow().ScrollY != 0 {
|
||||||
|
t.Errorf("ScrollY() = %d, want 0", m.ActiveWindow().ScrollY)
|
||||||
|
}
|
||||||
|
// Cursor should be clamped to safe zone bottom (relY=19)
|
||||||
|
if m.ActiveWindow().Cursor.Line != 19 {
|
||||||
|
t.Errorf("CursorY() = %d, want 19", m.ActiveWindow().Cursor.Line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ctrl+b clamps cursor to scrollOff when cursor is near top of viewport", func(t *testing.T) {
|
||||||
|
// cursor at line 30, scrollY=0, relY=30>19 -> clamp to 19, cursorY=19
|
||||||
|
// ctrl+b: newScrollY=0; relY=19; newCursorY=19
|
||||||
|
lines := generateLines(100)
|
||||||
|
// First normalize the position
|
||||||
|
m := getFinalModel(t, newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 30}, 80, 30))
|
||||||
|
initialY := m.ActiveWindow().Cursor.Line
|
||||||
|
|
||||||
|
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: initialY}, 80, 30)
|
||||||
|
sendKeys(tm, "ctrl+b")
|
||||||
|
|
||||||
|
m2 := getFinalModel(t, tm)
|
||||||
|
if m2.ActiveWindow().ScrollY != 0 {
|
||||||
|
t.Errorf("ScrollY() = %d, want 0", m2.ActiveWindow().ScrollY)
|
||||||
|
}
|
||||||
|
// Cursor should remain in safe zone
|
||||||
|
relY := m2.ActiveWindow().Cursor.Line - m2.ActiveWindow().ScrollY
|
||||||
|
if relY < 8 || relY > 19 {
|
||||||
|
t.Errorf("relY = %d, want in range [8, 19]", relY)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("multiple ctrl+b presses scroll incrementally", func(t *testing.T) {
|
||||||
|
// cursor at line 80: AdjustScroll -> scrollY=61, relY=19
|
||||||
|
// ctrl+b #1: newScrollY=33, cursorY=52
|
||||||
|
// ctrl+b #2: newScrollY=5, cursorY=24
|
||||||
|
lines := generateLines(200)
|
||||||
|
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 80}, 80, 30)
|
||||||
|
sendKeys(tm, "ctrl+b", "ctrl+b")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveWindow().ScrollY != 5 {
|
||||||
|
t.Errorf("ScrollY() = %d, want 5", m.ActiveWindow().ScrollY)
|
||||||
|
}
|
||||||
|
if m.ActiveWindow().Cursor.Line != 24 {
|
||||||
|
t.Errorf("CursorY() = %d, want 24", m.ActiveWindow().Cursor.Line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFullPageScrollRoundTrip(t *testing.T) {
|
||||||
|
t.Run("ctrl+f then ctrl+b returns cursor to original position", func(t *testing.T) {
|
||||||
|
// cursor at line 15, scrollY=0, relY=15
|
||||||
|
// ctrl+f: scrollY=28, cursorY=43, relY=15
|
||||||
|
// ctrl+b: newScrollY=max(0,28-28)=0, cursorY=0+15=15
|
||||||
|
lines := generateLines(200)
|
||||||
|
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 15}, 80, 30)
|
||||||
|
sendKeys(tm, "ctrl+f", "ctrl+b")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveWindow().ScrollY != 0 {
|
||||||
|
t.Errorf("ScrollY() = %d, want 0", m.ActiveWindow().ScrollY)
|
||||||
|
}
|
||||||
|
if m.ActiveWindow().Cursor.Line != 15 {
|
||||||
|
t.Errorf("CursorY() = %d, want 15", m.ActiveWindow().Cursor.Line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ctrl+b then ctrl+f returns cursor to original position", func(t *testing.T) {
|
||||||
|
// cursor at line 50: AdjustScroll -> scrollY=31, relY=19
|
||||||
|
// ctrl+b: scrollY=3, cursorY=22, relY=19
|
||||||
|
// ctrl+f: scrollY=31, cursorY=50, relY=19
|
||||||
|
lines := generateLines(200)
|
||||||
|
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 50}, 80, 30)
|
||||||
|
sendKeys(tm, "ctrl+b", "ctrl+f")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveWindow().ScrollY != 31 {
|
||||||
|
t.Errorf("ScrollY() = %d, want 31", m.ActiveWindow().ScrollY)
|
||||||
|
}
|
||||||
|
if m.ActiveWindow().Cursor.Line != 50 {
|
||||||
|
t.Errorf("CursorY() = %d, want 50", m.ActiveWindow().Cursor.Line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("alternating ctrl+f and ctrl+b maintains scroll stability", func(t *testing.T) {
|
||||||
|
lines := generateLines(200)
|
||||||
|
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 15}, 80, 30)
|
||||||
|
sendKeys(tm, "ctrl+f", "ctrl+b", "ctrl+f", "ctrl+b")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveWindow().ScrollY != 0 {
|
||||||
|
t.Errorf("ScrollY() = %d, want 0 after 2 round trips", m.ActiveWindow().ScrollY)
|
||||||
|
}
|
||||||
|
if m.ActiveWindow().Cursor.Line != 15 {
|
||||||
|
t.Errorf("CursorY() = %d, want 15 after 2 round trips", m.ActiveWindow().Cursor.Line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestScrollWithCount(t *testing.T) {
|
func TestScrollWithCount(t *testing.T) {
|
||||||
t.Run("5j scrolls appropriately", func(t *testing.T) {
|
t.Run("5j scrolls appropriately", func(t *testing.T) {
|
||||||
lines := generateLines(50)
|
lines := generateLines(50)
|
||||||
|
|||||||
@ -41,8 +41,10 @@ func NewNormalKeymap() *Keymap {
|
|||||||
"B": motion.MoveBackwardWORD{Count: 1},
|
"B": motion.MoveBackwardWORD{Count: 1},
|
||||||
"ge": motion.MoveBackwardWordEnd{Count: 1},
|
"ge": motion.MoveBackwardWordEnd{Count: 1},
|
||||||
"gE": motion.MoveBackwardWORDEnd{Count: 1},
|
"gE": motion.MoveBackwardWORDEnd{Count: 1},
|
||||||
"ctrl+u": motion.ScrollUpHalfPage{},
|
"ctrl+u": motion.ScrollUpPage{Divisor: 2},
|
||||||
"ctrl+d": motion.ScrollDownHalfPage{},
|
"ctrl+d": motion.ScrollDownPage{Divisor: 2},
|
||||||
|
"ctrl+b": motion.ScrollUpPage{Divisor: 1},
|
||||||
|
"ctrl+f": motion.ScrollDownPage{Divisor: 1},
|
||||||
";": action.RepeatFind{Count: 1, Reverse: false},
|
";": action.RepeatFind{Count: 1, Reverse: false},
|
||||||
",": action.RepeatFind{Count: 1, Reverse: true},
|
",": action.RepeatFind{Count: 1, Reverse: true},
|
||||||
},
|
},
|
||||||
@ -130,6 +132,12 @@ func NewVisualKeymap() *Keymap {
|
|||||||
"B": motion.MoveBackwardWORD{Count: 1},
|
"B": motion.MoveBackwardWORD{Count: 1},
|
||||||
"ge": motion.MoveBackwardWordEnd{Count: 1},
|
"ge": motion.MoveBackwardWordEnd{Count: 1},
|
||||||
"gE": motion.MoveBackwardWORDEnd{Count: 1},
|
"gE": motion.MoveBackwardWORDEnd{Count: 1},
|
||||||
|
"ctrl+u": motion.ScrollUpPage{Divisor: 2},
|
||||||
|
"ctrl+d": motion.ScrollDownPage{Divisor: 2},
|
||||||
|
"ctrl+b": motion.ScrollUpPage{Divisor: 1},
|
||||||
|
"ctrl+f": motion.ScrollDownPage{Divisor: 1},
|
||||||
|
";": action.RepeatFind{Count: 1, Reverse: false},
|
||||||
|
",": action.RepeatFind{Count: 1, Reverse: true},
|
||||||
// TODO: O and o. These are fun ones! Should be simple too
|
// TODO: O and o. These are fun ones! Should be simple too
|
||||||
},
|
},
|
||||||
operators: map[string]action.Operator{
|
operators: map[string]action.Operator{
|
||||||
|
|||||||
@ -111,21 +111,23 @@ func (a MoveToColumn) WithCount(n int) action.Action {
|
|||||||
|
|
||||||
// TODO: Count for these, maybe?
|
// TODO: Count for these, maybe?
|
||||||
|
|
||||||
// ScrollDownHalfPage implements Motion (ctrl+d) - linewise
|
// ScrollDownPage implements Motion (ctrl+d) - linewise
|
||||||
type ScrollDownHalfPage struct{}
|
type ScrollDownPage struct {
|
||||||
|
Divisor int
|
||||||
|
}
|
||||||
|
|
||||||
// ScrollDownHalfPage.Execute: Scrolls down half a page while maintaining the
|
// ScrollDownHalfPage.Execute: Scrolls down half a page while maintaining the
|
||||||
// cursor's relative position in the viewport.
|
// cursor's relative position in the viewport.
|
||||||
func (a ScrollDownHalfPage) Execute(m action.Model) tea.Cmd {
|
func (a ScrollDownPage) Execute(m action.Model) tea.Cmd {
|
||||||
win := m.ActiveWindow()
|
win := m.ActiveWindow()
|
||||||
buf := m.ActiveBuffer()
|
buf := m.ActiveBuffer()
|
||||||
|
|
||||||
viewportHeight := win.Height - 2
|
viewportHeight := win.ViewportHeight()
|
||||||
if viewportHeight <= 0 {
|
if viewportHeight <= 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
scroll := viewportHeight / 2
|
scroll := viewportHeight / a.Divisor
|
||||||
scrollOff := win.Options.ScrollOff
|
scrollOff := win.Options.ScrollOff
|
||||||
|
|
||||||
// Current relative position in viewport
|
// Current relative position in viewport
|
||||||
@ -152,22 +154,24 @@ func (a ScrollDownHalfPage) Execute(m action.Model) tea.Cmd {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a ScrollDownHalfPage) Type() core.MotionType { return core.Linewise }
|
func (a ScrollDownPage) Type() core.MotionType { return core.Linewise }
|
||||||
|
|
||||||
// ScrollUpHalfPage implements Motion (ctrl+u) - linewise
|
// ScrollUpPage implements Motion (ctrl+u) - linewise
|
||||||
type ScrollUpHalfPage struct{}
|
type ScrollUpPage struct {
|
||||||
|
Divisor int
|
||||||
|
}
|
||||||
|
|
||||||
// ScrollUpHalfPage.Execute: Scrolls up half a page while maintaining the
|
// ScrollUpHalfPage.Execute: Scrolls up half a page while maintaining the
|
||||||
// cursor's relative position in the viewport.
|
// cursor's relative position in the viewport.
|
||||||
func (a ScrollUpHalfPage) Execute(m action.Model) tea.Cmd {
|
func (a ScrollUpPage) Execute(m action.Model) tea.Cmd {
|
||||||
win := m.ActiveWindow()
|
win := m.ActiveWindow()
|
||||||
buf := m.ActiveBuffer()
|
buf := m.ActiveBuffer()
|
||||||
|
|
||||||
viewportHeight := win.Height - 2
|
viewportHeight := win.ViewportHeight()
|
||||||
if viewportHeight <= 0 {
|
if viewportHeight <= 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
scroll := viewportHeight / 2
|
scroll := viewportHeight / a.Divisor
|
||||||
scrollOff := win.Options.ScrollOff
|
scrollOff := win.Options.ScrollOff
|
||||||
|
|
||||||
// Current relative position in viewport
|
// Current relative position in viewport
|
||||||
@ -193,4 +197,4 @@ func (a ScrollUpHalfPage) Execute(m action.Model) tea.Cmd {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a ScrollUpHalfPage) Type() core.MotionType { return core.Linewise }
|
func (a ScrollUpPage) Type() core.MotionType { return core.Linewise }
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user