I made a terminal pager

The author details building a reusable Go component for terminal text navigation and using it to create 'lore', a custom terminal pager for daily use.
TL;DR: I build terminal applications (TUIs) like kl for k8s logs and wander for Nomad. Core functionality of TUIs includes interacting with large blocks of text, like application manifests and logs. I created a reusable viewport component in Go for text navigation in my projects. Terminal pagers are programs that allow you to interactively navigate multi-page text. I used my viewport component to make lore, which I’m now daily driving as my terminal pager.
In this post, I detail the features I wanted to support in my viewport as well as some learnings and design decisions on the way to making them a reality.
Introduction to Terminal Paging
Along with running commands, the terminal is often a place for viewing and navigating text. Terminals have a grid-like nature with a monospace font. Their size is defined in rows and columns, with text filling this grid accordingly.
Aside: styling text in terminals
You can style text in terminals with ANSI escape codes. \x1b[31m: begin red foreground text styling; red text: content to be styled; \x1b[0m: reset styling. This type of styling is how we get colors and patterns in the terminal.
Developers often scan through high volumes of text: verbose output from scripts, git diffs, application logs, man pages, and database query results. When text output is long, programs use a pager for interactive navigation. Programs check the PAGER environment variable and use that program to display text.
How programs use $PAGER
Programs like git and man check if PAGER is set. They’ll usually pipe content directly into the PAGER, skipping it if stdout isn’t a TTY. If stdout is a TTY, they’ll spawn the PAGER as a child process and connect the pipes.
Most systems use 'less' as a fallback. Other options include bat, most, and delta. You can even set PAGER=cat to dump everything directly.
Terminal Applications/TUIs
TUIs are like desktop apps but run in the terminal. They use the 'alt screen' to take over the full terminal. In a TUI, the smallest unit is the terminal grid cell rather than the pixel. This constraint pushes applications to be keyboard-driven and efficient.
In my tool 'kl', the selection tree and logs view are essentially mini terminal pagers. I extracted this into a 'viewport' component.
The Viewport Component
The viewport is a resizable, scrollable box for text. It supports percentage indicators, text wrapping, horizontal panning, search, and ANSI escape codes. It's written in Go using the Bubble Tea framework.
Implementation modules:
- item: wraps a string and handles row occupancy.
- viewport: displays items and navigation.
- filterableviewport: adds search functionality.
Unicode handling
Handling Unicode is tricky because not all characters take up one cell. For example, the sparkles emoji (✨) takes up two cells. Correctly calculating cell width is essential for proper text wrapping and navigation.
Source: Hacker News










