feat: implementing scrolling cmd output window: tested
All checks were successful
Run Test Suite / test (push) Successful in 42s
All checks were successful
Run Test Suite / test (push) Successful in 42s
This commit is contained in:
parent
b618e3a382
commit
5ff473d0d9
@ -1,6 +1,8 @@
|
||||
package core
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const CommandOutputExitMessage = "Press ENTER to continue"
|
||||
|
||||
@ -37,3 +39,39 @@ func (c *CommandOutput) Height() int {
|
||||
func (c *CommandOutput) IsActive() bool {
|
||||
return len(c.Lines) > 0
|
||||
}
|
||||
|
||||
// maxOutputWindowHeight: Calculates the max height of the output window. This is simply
|
||||
// just 3/4 of the terminal height. This allows the title to always be shown, but also
|
||||
// allows the user to not totally lose mental context when viewing a large output.
|
||||
func maxOutputWindowHeight(termHeight int) int {
|
||||
return int(float64(termHeight) * 0.75)
|
||||
}
|
||||
|
||||
// CommandOutput.Viewport: Returns a list of the lines in the current viewport, depends on
|
||||
// the height of the terminal. This function should be in place of the Lines property.
|
||||
func (c *CommandOutput) Viewport(height int) []string {
|
||||
start := c.ScrollOffset
|
||||
end := maxOutputWindowHeight(height) + start
|
||||
|
||||
// Clamp end to available lines
|
||||
if end > len(c.Lines) {
|
||||
end = len(c.Lines)
|
||||
}
|
||||
|
||||
return c.Lines[start:end]
|
||||
}
|
||||
|
||||
// CommandOutput.ScrollDown: Manages the scrolling down logic and handles bounds checks.
|
||||
// This function depends on the height on the terminal.
|
||||
func (c *CommandOutput) ScrollDown(height int) {
|
||||
if (c.ScrollOffset + maxOutputWindowHeight(height)) < len(c.Lines) {
|
||||
c.ScrollOffset++
|
||||
}
|
||||
}
|
||||
|
||||
// CommandOutput.ScrollUp: Manages the scrolling up logic and handles bounds checks.
|
||||
func (c *CommandOutput) ScrollUp() {
|
||||
if c.ScrollOffset > 0 {
|
||||
c.ScrollOffset--
|
||||
}
|
||||
}
|
||||
|
||||
588
internal/core/command_test.go
Normal file
588
internal/core/command_test.go
Normal file
@ -0,0 +1,588 @@
|
||||
package core
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestCommandOutputHeight tests the Height() method for various configurations
|
||||
func TestCommandOutputHeight(t *testing.T) {
|
||||
t.Run("inline mode returns 1", func(t *testing.T) {
|
||||
co := &CommandOutput{
|
||||
Title: "Test Title",
|
||||
Lines: []string{"line1", "line2", "line3"},
|
||||
Inline: true,
|
||||
}
|
||||
|
||||
if got := co.Height(); got != 1 {
|
||||
t.Errorf("Height() = %d, want 1 for inline mode", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty output with no title", func(t *testing.T) {
|
||||
co := &CommandOutput{
|
||||
Lines: []string{},
|
||||
Inline: false,
|
||||
}
|
||||
|
||||
// 0 lines + 0 title + 2 padding = 2
|
||||
want := 2
|
||||
if got := co.Height(); got != want {
|
||||
t.Errorf("Height() = %d, want %d", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("one line with title", func(t *testing.T) {
|
||||
co := &CommandOutput{
|
||||
Title: "Test",
|
||||
Lines: []string{"line1"},
|
||||
Inline: false,
|
||||
}
|
||||
|
||||
// 1 line + 1 title + 2 padding = 4
|
||||
want := 4
|
||||
if got := co.Height(); got != want {
|
||||
t.Errorf("Height() = %d, want %d", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("one line without title", func(t *testing.T) {
|
||||
co := &CommandOutput{
|
||||
Lines: []string{"line1"},
|
||||
Inline: false,
|
||||
}
|
||||
|
||||
// 1 line + 0 title + 2 padding = 3
|
||||
want := 3
|
||||
if got := co.Height(); got != want {
|
||||
t.Errorf("Height() = %d, want %d", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("multiple lines with title", func(t *testing.T) {
|
||||
co := &CommandOutput{
|
||||
Title: "Output",
|
||||
Lines: []string{"line1", "line2", "line3", "line4", "line5"},
|
||||
Inline: false,
|
||||
}
|
||||
|
||||
// 5 lines + 1 title + 2 padding = 8
|
||||
want := 8
|
||||
if got := co.Height(); got != want {
|
||||
t.Errorf("Height() = %d, want %d", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("whitespace-only title counts as no title", func(t *testing.T) {
|
||||
co := &CommandOutput{
|
||||
Title: " ",
|
||||
Lines: []string{"line1"},
|
||||
Inline: false,
|
||||
}
|
||||
|
||||
// 1 line + 0 title (whitespace) + 2 padding = 3
|
||||
want := 3
|
||||
if got := co.Height(); got != want {
|
||||
t.Errorf("Height() = %d, want %d", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty string title counts as no title", func(t *testing.T) {
|
||||
co := &CommandOutput{
|
||||
Title: "",
|
||||
Lines: []string{"line1", "line2"},
|
||||
Inline: false,
|
||||
}
|
||||
|
||||
// 2 lines + 0 title + 2 padding = 4
|
||||
want := 4
|
||||
if got := co.Height(); got != want {
|
||||
t.Errorf("Height() = %d, want %d", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestCommandOutputIsActive tests the IsActive() method
|
||||
func TestCommandOutputIsActive(t *testing.T) {
|
||||
t.Run("active with lines", func(t *testing.T) {
|
||||
co := &CommandOutput{
|
||||
Lines: []string{"line1"},
|
||||
}
|
||||
|
||||
if !co.IsActive() {
|
||||
t.Error("IsActive() = false, want true when lines present")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("inactive with no lines", func(t *testing.T) {
|
||||
co := &CommandOutput{
|
||||
Lines: []string{},
|
||||
}
|
||||
|
||||
if co.IsActive() {
|
||||
t.Error("IsActive() = true, want false when no lines")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("inactive with nil lines", func(t *testing.T) {
|
||||
co := &CommandOutput{
|
||||
Lines: nil,
|
||||
}
|
||||
|
||||
if co.IsActive() {
|
||||
t.Error("IsActive() = true, want false when lines is nil")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("active with multiple lines", func(t *testing.T) {
|
||||
co := &CommandOutput{
|
||||
Lines: []string{"line1", "line2", "line3"},
|
||||
}
|
||||
|
||||
if !co.IsActive() {
|
||||
t.Error("IsActive() = false, want true when multiple lines present")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestMaxOutputWindowHeight tests the maxOutputWindowHeight helper function
|
||||
func TestMaxOutputWindowHeight(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
termHeight int
|
||||
want int
|
||||
}{
|
||||
{"100 height terminal", 100, 75},
|
||||
{"80 height terminal", 80, 60},
|
||||
{"40 height terminal", 40, 30},
|
||||
{"24 height terminal", 24, 18},
|
||||
{"20 height terminal", 20, 15},
|
||||
{"10 height terminal", 10, 7},
|
||||
{"1 height terminal", 1, 0},
|
||||
{"0 height terminal", 0, 0},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := maxOutputWindowHeight(tt.termHeight)
|
||||
if got != tt.want {
|
||||
t.Errorf("maxOutputWindowHeight(%d) = %d, want %d", tt.termHeight, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCommandOutputViewport tests the Viewport() method
|
||||
func TestCommandOutputViewport(t *testing.T) {
|
||||
t.Run("viewport at start with small content", func(t *testing.T) {
|
||||
co := &CommandOutput{
|
||||
Lines: []string{"line1", "line2", "line3"},
|
||||
ScrollOffset: 0,
|
||||
}
|
||||
|
||||
// Terminal height 100 → max window = 75, viewport shows all 3 lines
|
||||
viewport := co.Viewport(100)
|
||||
|
||||
if len(viewport) != 3 {
|
||||
t.Errorf("Viewport() returned %d lines, want 3", len(viewport))
|
||||
}
|
||||
|
||||
want := []string{"line1", "line2", "line3"}
|
||||
for i, line := range viewport {
|
||||
if line != want[i] {
|
||||
t.Errorf("Viewport()[%d] = %q, want %q", i, line, want[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("viewport at start with large content", func(t *testing.T) {
|
||||
lines := make([]string, 100)
|
||||
for i := range lines {
|
||||
lines[i] = string(rune('A' + (i % 26)))
|
||||
}
|
||||
|
||||
co := &CommandOutput{
|
||||
Lines: lines,
|
||||
ScrollOffset: 0,
|
||||
}
|
||||
|
||||
// Terminal height 100 → max window = 75
|
||||
viewport := co.Viewport(100)
|
||||
|
||||
if len(viewport) != 75 {
|
||||
t.Errorf("Viewport() returned %d lines, want 75", len(viewport))
|
||||
}
|
||||
|
||||
// Should start with first line
|
||||
if viewport[0] != "A" {
|
||||
t.Errorf("Viewport()[0] = %q, want 'A'", viewport[0])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("viewport scrolled down", func(t *testing.T) {
|
||||
co := &CommandOutput{
|
||||
Lines: []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J"},
|
||||
ScrollOffset: 3,
|
||||
}
|
||||
|
||||
// Terminal height 20 → max window = 15
|
||||
viewport := co.Viewport(20)
|
||||
|
||||
// Should start at offset 3 (line "D")
|
||||
if viewport[0] != "D" {
|
||||
t.Errorf("Viewport()[0] = %q, want 'D' (offset 3)", viewport[0])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("viewport with different terminal heights", func(t *testing.T) {
|
||||
lines := make([]string, 100)
|
||||
for i := range lines {
|
||||
lines[i] = string(rune('0' + (i % 10)))
|
||||
}
|
||||
|
||||
co := &CommandOutput{
|
||||
Lines: lines,
|
||||
ScrollOffset: 0,
|
||||
}
|
||||
|
||||
// Small terminal (40 lines) → max window = 30
|
||||
viewport := co.Viewport(40)
|
||||
if len(viewport) != 30 {
|
||||
t.Errorf("Viewport(40) returned %d lines, want 30", len(viewport))
|
||||
}
|
||||
|
||||
// Large terminal (200 lines) → max window = 150, but only 100 lines available
|
||||
viewport = co.Viewport(200)
|
||||
if len(viewport) != 100 {
|
||||
t.Errorf("Viewport(200) returned %d lines, want 100 (all available)", len(viewport))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("viewport at maximum scroll", func(t *testing.T) {
|
||||
co := &CommandOutput{
|
||||
Lines: []string{"A", "B", "C", "D", "E"},
|
||||
ScrollOffset: 2, // Showing lines from index 2 onwards
|
||||
}
|
||||
|
||||
// Terminal height 20 → max window = 15 (but only 3 lines available from offset 2)
|
||||
viewport := co.Viewport(20)
|
||||
|
||||
want := []string{"C", "D", "E"}
|
||||
if len(viewport) != len(want) {
|
||||
t.Errorf("Viewport() returned %d lines, want %d", len(viewport), len(want))
|
||||
}
|
||||
|
||||
for i, line := range viewport {
|
||||
if line != want[i] {
|
||||
t.Errorf("Viewport()[%d] = %q, want %q", i, line, want[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestCommandOutputScrollDown tests the ScrollDown() method
|
||||
func TestCommandOutputScrollDown(t *testing.T) {
|
||||
t.Run("scroll down with space available", func(t *testing.T) {
|
||||
lines := make([]string, 100)
|
||||
for i := range lines {
|
||||
lines[i] = string(rune('A' + (i % 26)))
|
||||
}
|
||||
|
||||
co := &CommandOutput{
|
||||
Lines: lines,
|
||||
ScrollOffset: 0,
|
||||
}
|
||||
|
||||
// Terminal height 100 → max window = 75
|
||||
// Can scroll since 100 lines > 75 viewport
|
||||
co.ScrollDown(100)
|
||||
|
||||
if co.ScrollOffset != 1 {
|
||||
t.Errorf("ScrollOffset = %d after ScrollDown, want 1", co.ScrollOffset)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("scroll down multiple times", func(t *testing.T) {
|
||||
lines := make([]string, 100)
|
||||
for i := range lines {
|
||||
lines[i] = string(rune('A' + (i % 26)))
|
||||
}
|
||||
|
||||
co := &CommandOutput{
|
||||
Lines: lines,
|
||||
ScrollOffset: 0,
|
||||
}
|
||||
|
||||
// Scroll down 5 times
|
||||
for i := 0; i < 5; i++ {
|
||||
co.ScrollDown(100)
|
||||
}
|
||||
|
||||
if co.ScrollOffset != 5 {
|
||||
t.Errorf("ScrollOffset = %d after 5 ScrollDown calls, want 5", co.ScrollOffset)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("cannot scroll past end of content", func(t *testing.T) {
|
||||
co := &CommandOutput{
|
||||
Lines: []string{"A", "B", "C", "D", "E"},
|
||||
ScrollOffset: 0,
|
||||
}
|
||||
|
||||
// Terminal height 20 → max window = 15
|
||||
// 5 lines fit entirely in viewport, should not scroll
|
||||
co.ScrollDown(20)
|
||||
|
||||
if co.ScrollOffset != 0 {
|
||||
t.Errorf("ScrollOffset = %d, want 0 (should not scroll when content fits)", co.ScrollOffset)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("scroll down to maximum", func(t *testing.T) {
|
||||
co := &CommandOutput{
|
||||
Lines: []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J"},
|
||||
ScrollOffset: 0,
|
||||
}
|
||||
|
||||
// Terminal height 16 → max window = 12
|
||||
// 10 lines total, can scroll down until offset + 12 >= 10
|
||||
// Max offset = 10 - 12 = -2, but we can't go negative
|
||||
// Actually: can scroll while (offset + 12) < 10
|
||||
// So max offset before stopping = 10 - 12 = can't scroll at all? Let me recalculate
|
||||
// offset=0: show lines 0-11 (but only 10 exist) → shows all 10
|
||||
// Can't scroll since viewport > content
|
||||
|
||||
// Let's use different numbers: 20 lines, window of 12
|
||||
lines := make([]string, 20)
|
||||
for i := range lines {
|
||||
lines[i] = string(rune('A' + i))
|
||||
}
|
||||
co.Lines = lines
|
||||
|
||||
// Max scroll: offset + 12 < 20 → offset < 8 → max offset = 7
|
||||
// But the function allows offset until (offset + 12) < 20
|
||||
// So when offset = 8, offset + 12 = 20, not < 20, stops
|
||||
for i := 0; i < 20; i++ {
|
||||
co.ScrollDown(16)
|
||||
}
|
||||
|
||||
// Maximum offset should be 8 (showing lines 8-19, which is 12 lines)
|
||||
want := 8
|
||||
if co.ScrollOffset != want {
|
||||
t.Errorf("ScrollOffset = %d after scrolling to max, want %d", co.ScrollOffset, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("scroll down at maximum has no effect", func(t *testing.T) {
|
||||
lines := make([]string, 20)
|
||||
for i := range lines {
|
||||
lines[i] = string(rune('A' + i))
|
||||
}
|
||||
|
||||
co := &CommandOutput{
|
||||
Lines: lines,
|
||||
ScrollOffset: 8, // Already at max for height 16 (window 12)
|
||||
}
|
||||
|
||||
co.ScrollDown(16)
|
||||
|
||||
if co.ScrollOffset != 8 {
|
||||
t.Errorf("ScrollOffset = %d, want 8 (should not scroll past end)", co.ScrollOffset)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("scroll with different terminal heights", func(t *testing.T) {
|
||||
lines := make([]string, 100)
|
||||
for i := range lines {
|
||||
lines[i] = string(rune('A' + (i % 26)))
|
||||
}
|
||||
|
||||
co := &CommandOutput{
|
||||
Lines: lines,
|
||||
ScrollOffset: 0,
|
||||
}
|
||||
|
||||
// Small terminal (height 40 → window 30)
|
||||
co.ScrollDown(40)
|
||||
if co.ScrollOffset != 1 {
|
||||
t.Errorf("ScrollOffset = %d for height 40, want 1", co.ScrollOffset)
|
||||
}
|
||||
|
||||
// Reset
|
||||
co.ScrollOffset = 0
|
||||
|
||||
// Large terminal (height 200 → window 150)
|
||||
co.ScrollDown(200)
|
||||
if co.ScrollOffset != 0 {
|
||||
t.Errorf("ScrollOffset = %d for height 200, want 0 (content fits entirely)", co.ScrollOffset)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestCommandOutputScrollUp tests the ScrollUp() method
|
||||
func TestCommandOutputScrollUp(t *testing.T) {
|
||||
t.Run("scroll up from offset", func(t *testing.T) {
|
||||
co := &CommandOutput{
|
||||
Lines: []string{"A", "B", "C", "D", "E"},
|
||||
ScrollOffset: 3,
|
||||
}
|
||||
|
||||
co.ScrollUp()
|
||||
|
||||
if co.ScrollOffset != 2 {
|
||||
t.Errorf("ScrollOffset = %d after ScrollUp, want 2", co.ScrollOffset)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("scroll up multiple times", func(t *testing.T) {
|
||||
co := &CommandOutput{
|
||||
Lines: []string{"A", "B", "C", "D", "E"},
|
||||
ScrollOffset: 5,
|
||||
}
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
co.ScrollUp()
|
||||
}
|
||||
|
||||
if co.ScrollOffset != 2 {
|
||||
t.Errorf("ScrollOffset = %d after 3 ScrollUp calls, want 2", co.ScrollOffset)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("cannot scroll up past zero", func(t *testing.T) {
|
||||
co := &CommandOutput{
|
||||
Lines: []string{"A", "B", "C", "D", "E"},
|
||||
ScrollOffset: 0,
|
||||
}
|
||||
|
||||
co.ScrollUp()
|
||||
|
||||
if co.ScrollOffset != 0 {
|
||||
t.Errorf("ScrollOffset = %d, want 0 (should not go negative)", co.ScrollOffset)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("scroll up to zero", func(t *testing.T) {
|
||||
co := &CommandOutput{
|
||||
Lines: []string{"A", "B", "C", "D", "E"},
|
||||
ScrollOffset: 3,
|
||||
}
|
||||
|
||||
// Scroll up 3 times to reach 0
|
||||
for i := 0; i < 3; i++ {
|
||||
co.ScrollUp()
|
||||
}
|
||||
|
||||
if co.ScrollOffset != 0 {
|
||||
t.Errorf("ScrollOffset = %d after scrolling to top, want 0", co.ScrollOffset)
|
||||
}
|
||||
|
||||
// Try scrolling up one more time
|
||||
co.ScrollUp()
|
||||
|
||||
if co.ScrollOffset != 0 {
|
||||
t.Errorf("ScrollOffset = %d after scrolling past top, want 0", co.ScrollOffset)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("scroll up from large offset", func(t *testing.T) {
|
||||
lines := make([]string, 100)
|
||||
for i := range lines {
|
||||
lines[i] = string(rune('A' + (i % 26)))
|
||||
}
|
||||
|
||||
co := &CommandOutput{
|
||||
Lines: lines,
|
||||
ScrollOffset: 50,
|
||||
}
|
||||
|
||||
co.ScrollUp()
|
||||
|
||||
if co.ScrollOffset != 49 {
|
||||
t.Errorf("ScrollOffset = %d after ScrollUp, want 49", co.ScrollOffset)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestCommandOutputScrollingIntegration tests combined scrolling behavior
|
||||
func TestCommandOutputScrollingIntegration(t *testing.T) {
|
||||
t.Run("scroll down and up", func(t *testing.T) {
|
||||
lines := make([]string, 50)
|
||||
for i := range lines {
|
||||
lines[i] = string(rune('A' + (i % 26)))
|
||||
}
|
||||
|
||||
co := &CommandOutput{
|
||||
Lines: lines,
|
||||
ScrollOffset: 0,
|
||||
}
|
||||
|
||||
termHeight := 40 // max window = 30
|
||||
|
||||
// Scroll down 5 times
|
||||
for i := 0; i < 5; i++ {
|
||||
co.ScrollDown(termHeight)
|
||||
}
|
||||
|
||||
if co.ScrollOffset != 5 {
|
||||
t.Errorf("ScrollOffset = %d after 5 down, want 5", co.ScrollOffset)
|
||||
}
|
||||
|
||||
// Scroll up 2 times
|
||||
for i := 0; i < 2; i++ {
|
||||
co.ScrollUp()
|
||||
}
|
||||
|
||||
if co.ScrollOffset != 3 {
|
||||
t.Errorf("ScrollOffset = %d after 2 up, want 3", co.ScrollOffset)
|
||||
}
|
||||
|
||||
// Scroll back to top
|
||||
for i := 0; i < 10; i++ {
|
||||
co.ScrollUp()
|
||||
}
|
||||
|
||||
if co.ScrollOffset != 0 {
|
||||
t.Errorf("ScrollOffset = %d after scrolling to top, want 0", co.ScrollOffset)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("scroll to bottom and back to top", func(t *testing.T) {
|
||||
lines := make([]string, 30)
|
||||
for i := range lines {
|
||||
lines[i] = string(rune('A' + i))
|
||||
}
|
||||
|
||||
co := &CommandOutput{
|
||||
Lines: lines,
|
||||
ScrollOffset: 0,
|
||||
}
|
||||
|
||||
termHeight := 28 // max window = 21
|
||||
|
||||
// Scroll to bottom (max offset = 30 - 21 = 9)
|
||||
for i := 0; i < 20; i++ {
|
||||
co.ScrollDown(termHeight)
|
||||
}
|
||||
|
||||
want := 9
|
||||
if co.ScrollOffset != want {
|
||||
t.Errorf("ScrollOffset = %d after scrolling to bottom, want %d", co.ScrollOffset, want)
|
||||
}
|
||||
|
||||
// Verify viewport shows last lines
|
||||
viewport := co.Viewport(termHeight)
|
||||
if len(viewport) != 21 {
|
||||
t.Errorf("Viewport length = %d, want 21", len(viewport))
|
||||
}
|
||||
if viewport[len(viewport)-1] != string(rune('A'+29)) {
|
||||
t.Errorf("Last viewport line = %q, want %q", viewport[len(viewport)-1], string(rune('A'+29)))
|
||||
}
|
||||
|
||||
// Scroll back to top
|
||||
for i := 0; i < 20; i++ {
|
||||
co.ScrollUp()
|
||||
}
|
||||
|
||||
if co.ScrollOffset != 0 {
|
||||
t.Errorf("ScrollOffset = %d after scrolling back to top, want 0", co.ScrollOffset)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -60,9 +60,14 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// TODO: Any vim action should exit also
|
||||
// Simple override for command output mode for now
|
||||
if m.Mode() == core.CommandOutputMode {
|
||||
if msg.Type == tea.KeyEnter {
|
||||
switch msg.String() {
|
||||
case "enter":
|
||||
m.SetMode(core.NormalMode)
|
||||
m.SetCommandOutput(&core.CommandOutput{})
|
||||
case "j":
|
||||
m.CommandOutput().ScrollDown(m.termHeight)
|
||||
case "k":
|
||||
m.CommandOutput().ScrollUp()
|
||||
}
|
||||
} else {
|
||||
cmd = m.input.Handle(m, msg.String())
|
||||
|
||||
@ -35,7 +35,7 @@ func (m Model) View() string {
|
||||
// TODO: This is not idea, but it works for now
|
||||
cmd := m.CommandOutput()
|
||||
if cmd != nil && cmd.IsActive() && !cmd.Inline && cmd.Height() > 0 {
|
||||
view = overlayCommandOutputWindow(view, cmd, styles, m.termWidth)
|
||||
view = overlayCommandOutputWindow(view, cmd, styles, m.termWidth, m.termHeight)
|
||||
}
|
||||
|
||||
return view
|
||||
@ -319,7 +319,7 @@ func posInsideSelection(w *core.Window, mode core.Mode, col, line int) bool {
|
||||
|
||||
// overlayCommandOutputWindow: Draw the overlay of the command output window. This will override
|
||||
// (overlay) the displayed content, so it should be used only when needed.
|
||||
func overlayCommandOutputWindow(view string, cmd *core.CommandOutput, styles style.Styles, termWidth int) string {
|
||||
func overlayCommandOutputWindow(view string, cmd *core.CommandOutput, styles style.Styles, termWidth int, termHeight int) string {
|
||||
// Safety check
|
||||
if cmd == nil {
|
||||
return view
|
||||
@ -336,7 +336,8 @@ func overlayCommandOutputWindow(view string, cmd *core.CommandOutput, styles sty
|
||||
title := styles.LineStyle.Render(cmd.Title)
|
||||
overlay = append(overlay, title)
|
||||
}
|
||||
for _, l := range cmd.Lines {
|
||||
viewLines := cmd.Viewport(termHeight)
|
||||
for _, l := range viewLines {
|
||||
content := styles.LineStyle.Render(strings.ReplaceAll(l, "\n", "\\n"))
|
||||
overlay = append(overlay, content)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user