diff options
| author | ewy <ewy0@protonmail.com> | 2026-04-29 00:47:34 +0200 |
|---|---|---|
| committer | ewy <ewy0@protonmail.com> | 2026-04-29 00:47:34 +0200 |
| commit | 630d77e1962b43ee95e88a664f5e8b8993213060 (patch) | |
| tree | 8849c2a87443cd231786aed53b76dc7e4ea11aed /viewport | |
| parent | 8efcf029576ad82908b4ae80b2c92022dfb857d2 (diff) | |
big stuff
* send empty screen after tui confirmation
* add scroll view / viewport
* auto enable scroll view on short terminals
* add motd tips
* add subdirs as categories
* add inline toggle hotkey (i)
Diffstat (limited to 'viewport')
| -rw-r--r-- | viewport/scroll.go | 46 | ||||
| -rw-r--r-- | viewport/viewport.go | 67 | ||||
| -rw-r--r-- | viewport/viewport_test.go | 54 |
3 files changed, 167 insertions, 0 deletions
diff --git a/viewport/scroll.go b/viewport/scroll.go new file mode 100644 index 0000000..809fe17 --- /dev/null +++ b/viewport/scroll.go @@ -0,0 +1,46 @@ +package viewport + +import ( + "github.com/charmbracelet/lipgloss" + "pik/menu/style" + "strings" +) + +var ( + ScrollTop = StyleBarBackground.Render("╷") + ScrollSpace = StyleBarBackground.Render("│") + ScrollBarTopEnd = StyleBar.Render("╓") + ScrollBar = StyleBar.Render("║") + ScrollBarBottomEnd = StyleBar.Render("╙") + ScrollBottom = StyleBarBackground.Render("╵") +) + +var ( + StyleBar = style.New(func() lipgloss.Style { + return lipgloss.NewStyle() + }) + StyleBarBackground = style.New(func() lipgloss.Style { + return StyleBar.Get().Faint(true) + }) +) + +func WithScroll(input string, barBegin int, barEnd int) string { + lines := strings.Split(input, "\n") + for i, line := range lines { + selection := ScrollSpace + switch { + case i == barBegin: + selection = StyleBar.Render(ScrollBarTopEnd) + case i == 0: + selection = StyleBarBackground.Render(ScrollTop) + case i == barEnd: + selection = StyleBar.Render(ScrollBarBottomEnd) + case i > barBegin && i < barEnd: + selection = StyleBar.Render(ScrollBar) + case i == len(lines)-1: + selection = StyleBar.Render(ScrollBottom) + } + lines[i] = selection + " " + line + } + return strings.Join(lines, "\n") +} diff --git a/viewport/viewport.go b/viewport/viewport.go new file mode 100644 index 0000000..ff1ca05 --- /dev/null +++ b/viewport/viewport.go @@ -0,0 +1,67 @@ +package viewport + +import ( + "strings" +) + +const Caret = "⏵" + +func NeedsViewport(input string, height int) bool { + lines := strings.Split(input, "\n") + return len(lines)-1 > height +} + +func Process(input string, height int) string { + lines := strings.Split(input, "\n") + if len(lines) > height { + cropped, top, bottom := Crop(input, lines, height) + return WithScroll(cropped, int(top*float32(height)), int(bottom*float32(height))) + } + return input +} + +func Focus(lines []string, needle string) int { + for i, l := range lines { + if strings.Contains(l, needle) { + return i + } + } + return -1 +} + +func Crop(input string, lines []string, height int) (output string, scrollStart float32, scrollEnd float32) { + output = input + selectionIndex := Focus(lines, Caret) + size := len(lines) + if size <= height { + return output, 0, 1 + } + + linesAbove := height / 2 + linesBelow := height - linesAbove + if linesAbove*2 < selectionIndex { + linesBelow++ + } + + start := selectionIndex - linesAbove + end := selectionIndex + linesBelow + + if start < 0 { + end += -start + start = 0 + } + + if end >= size { + diff := size - 1 - end + start += diff + end += diff + } + + scrollStart = float32(start) / float32(size) + scrollEnd = float32(end)/float32(size) + float32(1)/float32(size) + if scrollEnd > 1 { + scrollEnd = 1 + } + + return strings.Join(lines[start:end], "\n"), scrollStart, scrollEnd +} diff --git a/viewport/viewport_test.go b/viewport/viewport_test.go new file mode 100644 index 0000000..9da4c3c --- /dev/null +++ b/viewport/viewport_test.go @@ -0,0 +1,54 @@ +//go:build test + +package viewport + +import ( + "github.com/stretchr/testify/assert" + "strings" + "testing" +) + +func TestCrop(t *testing.T) { + input := `0000 +AAAA +` + Caret + `BBB +CCC` + expected := `AAAA +` + Caret + `BBB` + result, _, _ := Crop(input, strings.Split(input, "\n"), 2) + assert.Equal(t, expected, result) +} + +func TestCrop_Under(t *testing.T) { + input := `0000 +AAAA +` + Caret + `BBB` + expected := `AAAA +` + Caret + `BBB` + result, _, _ := Crop(input, strings.Split(input, "\n"), 2) + assert.Equal(t, expected, result) +} + +func TestCrop_Unnecessary(t *testing.T) { + input := `AAAA +` + Caret + `BBB +CCC +DDDD` + expected := input + result, _, _ := Crop(input, strings.Split(input, "\n"), 8) + assert.Equal(t, expected, result) +} + +func TestNeedsViewport(t *testing.T) { + amount := 3 + input := strings.Repeat("\n", amount) + output := NeedsViewport(input, 4) + assert.Equal(t, false, output) +} + +func TestNeedsViewport_Negative(t *testing.T) { + amount := 8 + input := strings.Repeat("\n", amount) + output := NeedsViewport(input, 4) + assert.Equal(t, true, output) +} |
