viewport

package
v0.23.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 22, 2026 License: Apache-2.0, MIT Imports: 11 Imported by: 0

README

Viewport

An advanced terminal viewport component for Bubble Tea terminal UI (TUI) applications.

This is a fork of github.com/robinovitch61/viewport integrated into the zeta project.

Overview

The viewport module provides a feature-rich terminal viewport component for building interactive TUI applications. It offers advanced text display capabilities including wrapping, scrolling, selection, and filtering.

Features

Core Viewport
  • Text wrapping - Toggleable text wrapping with horizontal panning for unwrapped lines
  • ANSI & Unicode support - Full support for ANSI escape codes and Unicode characters
  • Item selection - Individual item selection with customizable styling
  • Sticky scrolling - Auto-follow new content with sticky top/bottom scrolling
  • Sticky header - Configurable sticky header that remains visible while scrolling
  • Highlight ranges - Highlight specific text ranges with custom styles
  • Content saving - Save viewport content to file
  • Efficient concatenation - Efficient item concatenation via MultiItem (e.g., prefixing line numbers)
Filterable Viewport

The filterableviewport package extends the core viewport with:

  • Multiple filter modes - Exact, regex, case-insensitive (built-in); custom modes supported
  • Match highlighting - Highlighted matches with focused/unfocused styles
  • Match navigation - Next/previous match navigation
  • Matches-only view - Hide non-matching items
  • Match limiting - Configurable match limit for large content
  • Search history - Browse previous searches (up/down arrow while editing)

Installation

This module is part of the zeta project and is located at github.com/antgroup/hugescm/modules/viewport.

Usage

Basic Viewport

Implement the Object interface on your type:

import (
    "github.com/antgroup/hugescm/modules/viewport"
    "github.com/antgroup/hugescm/modules/viewport/item"
)

type myObject struct {
    item item.Item
}

func (o myObject) GetItem() item.Item {
    return o.item
}

Create a viewport and set content:

vp := viewport.New[myObject](
    width, height,
    viewport.WithSelectionEnabled[myObject](true),
    viewport.WithWrapText[myObject](true),
)

objects := []myObject{
    {item: item.NewItem("first line")},
    {item: item.NewItem("second line")},
}

vp.SetObjects(objects)

Wire it into your Bubble Tea model's Update and View:

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    var cmd tea.Cmd
    m.viewport, cmd = m.viewport.Update(msg)
    return m, cmd
}

func (m model) View() string {
    return m.viewport.View()
}
Filterable Viewport

Wrap an existing viewport to add filtering:

import "github.com/antgroup/hugescm/modules/viewport/filterableviewport"

fvp := filterableviewport.New[myObject](
    vp,
    filterableviewport.WithPrefixText[myObject]("Filter:"),
    filterableviewport.WithEmptyText[myObject]("No Current Filter"),
    filterableviewport.WithMatchingItemsOnly[myObject](false),
    filterableviewport.WithCanToggleMatchingItemsOnly[myObject](true),
)

fvp.SetObjects(objects)
Custom Filter Modes

Define custom filter logic with a FilterMode:

import (
    "strings"

    "charm.land/bubbles/v2/key"
    "github.com/antgroup/hugescm/modules/viewport/filterableviewport"
    "github.com/antgroup/hugescm/modules/viewport/item"
)

const FilterPrefix filterableviewport.FilterModeName = "prefix"

prefixMode := filterableviewport.FilterMode{
    Name:  FilterPrefix,
    Key:   key.NewBinding(key.WithKeys("p"), key.WithHelp("p", "prefix filter")),
    Label: "[prefix]",
    GetMatchFunc: func(filterText string) (filterableviewport.MatchFunc, error) {
        return func(content string) []item.ByteRange {
            if strings.HasPrefix(content, filterText) {
                return []item.ByteRange{{Start: 0, End: len(filterText)}}
            }
            return nil
        }, nil
    },
}

Default Key Bindings

Viewport Navigation
Key Action
j / down / enter Scroll down
k / up Scroll up
f / pgdown / ctrl+f / space Page down
b / pgup / ctrl+b Page up
d / ctrl+d Half page down
u / ctrl+u Half page up
g / ctrl+g / home Jump to top
G / end Jump to bottom
left / right Horizontal pan

Note: The viewport does not handle quit keys (q, esc, ctrl+c) - this is intentional as viewport is a generic scrolling component and the quit logic should be handled by the parent application.

Filterable Viewport
Key Action
/ Start exact filter
r Start regex filter
i Start case-insensitive filter
enter Apply filter
esc Cancel/clear filter
n Next match
N (shift+n) Previous match
o Toggle matches-only view
up / down Browse search history (while editing)

License

MIT License - See LICENSE file for details.

Original work Copyright (c) 2026 Leo Robinovitch

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CompareFn

type CompareFn[T any] func(a, b T) bool

CompareFn is a function type for comparing two items of type T.

type Highlight

type Highlight struct {
	ItemIndex     int // index of the item
	ItemHighlight item.Highlight
}

Highlight represents a specific position and style to highlight

type KeyMap

type KeyMap struct {
	PageDown     key.Binding
	PageUp       key.Binding
	HalfPageUp   key.Binding
	HalfPageDown key.Binding
	Up           key.Binding
	Down         key.Binding
	Left         key.Binding
	Right        key.Binding
	Top          key.Binding
	Bottom       key.Binding
}

KeyMap contains viewport key bindings

func DefaultKeyMap

func DefaultKeyMap() KeyMap

DefaultKeyMap returns a set of default key bindings for the viewport

type Model

type Model[T Object] struct {
	// contains filtered or unexported fields
}

Model represents a viewport component

func New

func New[T Object](width, height int, opts ...Option[T]) (m *Model[T])

New creates a new viewport model with reasonable defaults

func (*Model[T]) EnsureItemInView

func (m *Model[T]) EnsureItemInView(itemIdx, startWidth, endWidth, verticalPad, horizontalPad int)

EnsureItemInView scrolls or pans the viewport so that the specified portion of an item is visible. If the desired item portion is above or below the current view, it scrolls vertically to bring it into view, leaving verticalPad number of lines of context if possible. If the desired item portion is to the left or right of the current view, it pans horizontally to bring it into view, leaving horizontalPad number of columns of context if possible. Afterwards, it's possible that the selection is out of view of the viewport.

func (*Model[T]) GetHeight

func (m *Model[T]) GetHeight() int

GetHeight returns the viewport height

func (*Model[T]) GetHighlights

func (m *Model[T]) GetHighlights() []Highlight

GetHighlights returns all highlights.

func (*Model[T]) GetPreFooterLine

func (m *Model[T]) GetPreFooterLine() string

GetPreFooterLine returns the current pre-footer line.

func (*Model[T]) GetSelectedItem

func (m *Model[T]) GetSelectedItem() *T

GetSelectedItem returns a pointer to the currently selected item

func (*Model[T]) GetSelectedItemIdx

func (m *Model[T]) GetSelectedItemIdx() int

GetSelectedItemIdx returns the currently selected item index

func (*Model[T]) GetSelectionEnabled

func (m *Model[T]) GetSelectionEnabled() bool

GetSelectionEnabled returns whether the viewport allows line selection

func (*Model[T]) GetTopItemIdxAndLineOffset

func (m *Model[T]) GetTopItemIdxAndLineOffset() (int, int)

GetTopItemIdxAndLineOffset returns the current top item index and line offset within that item

func (*Model[T]) GetWidth

func (m *Model[T]) GetWidth() int

GetWidth returns the viewport width

func (*Model[T]) GetWrapText

func (m *Model[T]) GetWrapText() bool

GetWrapText returns whether the viewport wraps text

func (*Model[T]) GetXOffsetWidth

func (m *Model[T]) GetXOffsetWidth() int

GetXOffsetWidth returns the horizontal offset, in terminal cell width, for panning when text wrapping is disabled

func (*Model[T]) IsCapturingInput

func (m *Model[T]) IsCapturingInput() bool

IsCapturingInput returns true when the viewport is in a mode that should capture all input (e.g., filename entry for saving). Callers should forward all messages to the viewport without processing them when this returns true.

func (*Model[T]) SetBottomSticky

func (m *Model[T]) SetBottomSticky(bottomSticky bool)

SetBottomSticky sets whether selection should stay at bottom when new Item added and selection is at the bottom

func (*Model[T]) SetFooterEnabled

func (m *Model[T]) SetFooterEnabled(footerEnabled bool)

SetFooterEnabled sets whether the viewport shows the footer when it overflows

func (*Model[T]) SetHeader

func (m *Model[T]) SetHeader(header []string)

SetHeader sets the header, an unselectable set of lines at the top of the viewport

func (*Model[T]) SetHeight

func (m *Model[T]) SetHeight(height int)

SetHeight sets the viewport's height, including header and footer

func (*Model[T]) SetHighlights

func (m *Model[T]) SetHighlights(highlights []Highlight)

SetHighlights sets specific positions to highlight with custom styles in the viewport.

func (*Model[T]) SetObjects

func (m *Model[T]) SetObjects(objects []T)

SetObjects sets the objects

func (*Model[T]) SetPostHeaderLine

func (m *Model[T]) SetPostHeaderLine(line string)

SetPostHeaderLine sets a line to render just below the header. Pass empty string to disable. The line will be truncated to viewport width.

func (*Model[T]) SetPreFooterLine

func (m *Model[T]) SetPreFooterLine(line string)

SetPreFooterLine sets a line to render just above the footer. Pass empty string to disable. The line will be truncated to viewport width.

func (*Model[T]) SetProgressBarEnabled

func (m *Model[T]) SetProgressBarEnabled(enabled bool)

SetProgressBarEnabled sets whether the footer displays a Unicode progress bar in the footer

func (*Model[T]) SetSelectedItemIdx

func (m *Model[T]) SetSelectedItemIdx(selectedItemIdx int)

SetSelectedItemIdx sets the selected context index. Automatically puts selection in view as necessary

func (*Model[T]) SetSelectionComparator

func (m *Model[T]) SetSelectionComparator(compareFn CompareFn[T])

SetSelectionComparator sets the comparator function for maintaining the current selection when Item changes. If compareFn is non-nil, the viewport will try to maintain the current selection when Item changes.

func (*Model[T]) SetSelectionEnabled

func (m *Model[T]) SetSelectionEnabled(selectionEnabled bool)

SetSelectionEnabled sets whether the viewport allows line selection

func (*Model[T]) SetStyles

func (m *Model[T]) SetStyles(styles Styles)

SetStyles sets the styling for the viewport

func (*Model[T]) SetTopSticky

func (m *Model[T]) SetTopSticky(topSticky bool)

SetTopSticky sets whether selection should stay at top when new Item added and selection is at the top

func (*Model[T]) SetWidth

func (m *Model[T]) SetWidth(width int)

SetWidth sets the viewport's width

func (*Model[T]) SetWrapText

func (m *Model[T]) SetWrapText(wrapText bool)

SetWrapText sets whether the viewport wraps text

func (*Model[T]) SetXOffset

func (m *Model[T]) SetXOffset(widthOffset int)

SetXOffset sets the horizontal offset, in terminal cell width, for panning when text wrapping is disabled

func (*Model[T]) Update

func (m *Model[T]) Update(msg tea.Msg) (*Model[T], tea.Cmd)

Update processes messages and updates the model

func (*Model[T]) View

func (m *Model[T]) View() string

View renders the viewport

type Object

type Object interface {
	GetItem() item.Item
}

Object is implemented by types that can return an Item It exists to allow the viewport to return the selected object without (de)serializing it

type Option

type Option[T Object] func(*Model[T])

Option is a functional option for configuring the viewport

func WithFileSaving

func WithFileSaving[T Object](saveDir string, saveKey key.Binding) Option[T]

WithFileSaving configures automatic file saving when a hotkey is pressed. Files are saved to the specified directory with timestamp-based names.

func WithFooterEnabled

func WithFooterEnabled[T Object](enabled bool) Option[T]

WithFooterEnabled sets whether the viewport shows the footer

func WithKeyMap

func WithKeyMap[T Object](keyMap KeyMap) Option[T]

WithKeyMap sets the key mapping for the viewport

func WithProgressBarEnabled

func WithProgressBarEnabled[T Object](enabled bool) Option[T]

WithProgressBarEnabled sets whether the footer displays a Unicode progress bar

func WithSelectionEnabled

func WithSelectionEnabled[T Object](enabled bool) Option[T]

WithSelectionEnabled sets whether the viewport allows selection

func WithSelectionStyleOverridesItemStyle

func WithSelectionStyleOverridesItemStyle[T Object](overrides bool) Option[T]

WithSelectionStyleOverridesItemStyle controls whether the selection style replaces the item's existing ANSI styling. When true (default), the selected item is stripped of its original styling and the selection style is applied to all non-highlighted regions. When false, the item keeps its original styling and the selection style is applied only to unstyled regions.

func WithStickyBottom

func WithStickyBottom[T Object](stickyBottom bool) Option[T]

WithStickyBottom sets whether to automatically scroll to the bottom when content changes

func WithStickyTop

func WithStickyTop[T Object](stickyTop bool) Option[T]

WithStickyTop sets whether to automatically scroll to the top when content changes

func WithStyles

func WithStyles[T Object](styles Styles) Option[T]

WithStyles sets the styling for the viewport

func WithWrapText

func WithWrapText[T Object](wrap bool) Option[T]

WithWrapText sets whether the viewport wraps text

type Styles

type Styles struct {
	// SelectionPrefix is prepended to each visible line of the selected item.
	// Non-selected lines get equivalent-width blank padding to maintain alignment.
	// Only applied when selection is enabled and this string is non-empty.
	// This is the primary mechanism for selection visibility under NO_COLOR.
	SelectionPrefix string

	FooterStyle       lipgloss.Style
	SelectedItemStyle lipgloss.Style
}

Styles contains styling configuration for the viewport

func DefaultStyles

func DefaultStyles() Styles

DefaultStyles returns a set of default styles for the viewport. Uses only reverse video — no 256-color or true-color values.

Directories

Path Synopsis
fuzzy
Package fuzzy provides fuzzy string matching.
Package fuzzy provides fuzzy string matching.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL