tideui

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: May 25, 2026 License: MIT Imports: 7 Imported by: 0

README

tideui

tideui is a reusable Bubble Tea/Lipgloss presentation toolkit based on the themeable terminal interface originally built for Tide and later refined in TideMail. It renders application-provided content inside themed pane shells, status bars, and overlays.

The package is intentionally view-oriented. Applications retain ownership of their Bubble Tea model, commands, key routing, persistence, and viewport state; the optional picker handles its own navigation once an application opens it.

Lineage

The three-pane layout, theme preview workflow, and themed modal language began in Tide, a terminal RSS reader. TideMail later adapted and refined that interface; tideui packages the reusable UI primitives for use in additional Bubble Tea applications.

tideui three-pane layout and theme picker

Install

go get github.com/allisonhere/tideui

Features

  • Nineteen built-in palettes with optional background, foreground, and accent overrides.
  • StackedRight layout derived from Tide's three-pane reader and a general ThreeColumn layout.
  • Compact and comfortable density modes plus VT52 ASCII presentation.
  • Themed pane headers, rows, status bars, overlays, and a Tide-derived theme picker.
  • Terminal background sequences exposed for application-controlled terminal updates.
  • Output constrained to the requested terminal dimensions, including very small windows.

Usage

import "github.com/allisonhere/tideui"

theme, _ := tideui.ThemeByName("catppuccin-mocha")
renderer := tideui.NewRenderer(theme, tideui.StyleOptions{Density: tideui.Compact})

view := renderer.Render(tideui.Layout{
    Width: 80, Height: 24, Mode: tideui.StackedRight,
    Panes: [3]tideui.Pane{
        {Title: "Projects", Content: "inbox\narchive", Focused: true},
        {Title: "Tasks", Content: "ship tideui"},
        {Title: "Detail", Content: "Application-owned content."},
    },
    Status: &tideui.StatusBar{Left: "ready", Right: "? help"},
})

Bubble Tea Integration

Store terminal dimensions from tea.WindowSizeMsg, keep your application state in your own model, and construct the renderer from the currently selected theme:

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    if size, ok := msg.(tea.WindowSizeMsg); ok {
        m.width, m.height = size.Width, size.Height
    }
    return m, nil
}

func (m model) View() string {
    renderer := tideui.NewRenderer(m.theme, tideui.StyleOptions{Density: m.density})
    return renderer.Render(tideui.Layout{
        Width: m.width, Height: m.height, Mode: tideui.ThreeColumn,
        Panes: m.panes(),
    })
}

Layouts

StackedRight renders pane 0 as a left sidebar, pane 1 above pane 2 on the right, and defaults to Tide's 28% sidebar and 40% upper-right height. Configure it with SidebarRatio and UpperRightRatio.

ThreeColumn renders the three panes from left to right. Use ColumnRatios to allocate relative widths:

layout.Mode = tideui.ThreeColumn
layout.ColumnRatios = [3]float64{2, 3, 5}

Rows And Content

Pane content is application-provided text. Use RenderRow for themed list rows and the exported Styles for custom detail content:

rows := []string{
    renderer.RenderRow(tideui.Row{Prefix: "* ", Text: "Selected", Suffix: "3", Selected: true}, 26),
    renderer.RenderRow(tideui.Row{Prefix: "  ", Text: "Archived", Muted: true}, 26),
}
detail := renderer.Styles.DetailTitle.Render("Selected") + "\n\n" +
    renderer.Styles.DetailBody.Render("Application-owned detail content.")

Focused panes use the theme accent. Set Pane.Accent only when an individual pane should intentionally use another accent color.

Themes

Choose from BuiltinThemes, resolve a saved name with ThemeByName, or adjust a built-in theme through ThemeOverrides:

renderer := tideui.NewRenderer(tideui.VT100, tideui.StyleOptions{
    Density: tideui.Compact,
    Overrides: tideui.ThemeOverrides{
        Background: "#080b08",
        Foreground: "#8cff8c",
        Accent:     "#33ff33",
    },
})

Built-in theme names:

catppuccin-mocha, catppuccin-latte, catppuccin-frappe, catppuccin-macchiato, nord, dracula, gruvbox-dark, gruvbox-light, tokyo-night, tokyo-night-day, rose-pine, rose-pine-moon, rose-pine-dawn, one-dark, magenta-geode, coral-sunset, lavender-fields-forever, vt100, and vt52.

Theme Pickers

ThemePicker provides Tide-derived picker state and modal rendering: j/k and arrow keys preview themes, enter confirms, and esc reverts. Your application still decides when to open it, persists confirmed selections, and emits terminal background sequences.

type model struct {
    width, height int
    theme         tideui.Theme
    picker        tideui.ThemePicker
}

func newModel(savedName string) model {
    theme, _ := tideui.ThemeByName(savedName)
    return model{
        theme:  theme,
        picker: tideui.NewThemePicker(tideui.ThemePickerOptions{InitialTheme: theme.Name}),
    }
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    if key, ok := msg.(tea.KeyMsg); ok {
        if key.String() == "t" && !m.picker.Opened() {
            m.picker.Open(m.theme.Name)
        } else if m.picker.Opened() {
            action := m.picker.Update(key)
            m.theme = m.picker.PreviewTheme() // rebuild View immediately for live preview
            if action == tideui.ThemePickerConfirm {
                saveThemeName(m.picker.ConfirmedTheme().Name)
            }
        }
    }
    return m, nil
}

func (m model) View() string {
    renderer := tideui.NewRenderer(m.theme, tideui.StyleOptions{})
    layout := tideui.Layout{Width: m.width, Height: m.height, Panes: m.panes()}
    if m.picker.Opened() {
        modal := m.picker.Modal(renderer, 40, m.height)
        layout.Modal = &modal
    }
    return renderer.Render(layout)
}

Status Bars And Overlays

Provide a StatusBar and optional Overlay in the layout; Width on an overlay is the full modal width including its border:

layout.Status = &tideui.StatusBar{Left: "ready", Right: "? help"}
layout.Modal = &tideui.Overlay{
    Visible: showHelp,
    Title:   "HELP",
    Content: "j/k move\nenter select",
    Footer:  "esc close",
    Width:   36,
}

Terminal Background

TerminalBackgroundSequences returns OSC sequences for terminals that support changing their default background color. It does not write to stdout or the terminal; the application decides whether and where to emit the strings.

API Boundaries

In v1, tideui renders presentation primitives. The consuming application owns:

  • Bubble Tea Update behavior and commands.
  • Application keyboard navigation and focus state outside the theme picker.
  • Viewport scrolling and content formatting.
  • Persisted theme configuration after picker confirmation.
  • Terminal control sequence output.

Requirements

The module currently targets Go 1.26 or newer and uses Bubble Tea and Lipgloss.

Development

go test ./...
go vet ./...

Run the demo with go run ./cmd/demo. Use tab to move focus, l to change layout, t to open the theme picker, d to switch density, o to toggle the generic overlay, and q to quit.

Documentation

Overview

Package tideui renders themeable pane shells, status bars, overlays, theme pickers, and reusable row styles for Bubble Tea applications.

Applications own their domain state, persistence, and viewports, then pass rendered content into a Layout.

Index

Constants

View Source
const (
	// ThemeNameVT52 identifies the ASCII-style amber terminal theme.
	ThemeNameVT52 = "vt52"
	// ThemeNameVT100 identifies the green phosphor terminal theme.
	ThemeNameVT100 = "vt100"
)

Variables

BuiltinThemes contains the palettes supplied by the library.

View Source
var CatppuccinFrappe = Theme{
	Name: "catppuccin-frappe", Bg: "#303446", Fg: "#c6d0f5", Border: "#626880",
	BorderFocus: "#8caaee", Selected: "#8caaee", Unread: "#a6d189", Dimmed: "#51576d",
	StatusBar: "#292c3c", StatusFg: "#c6d0f5", Error: "#e78284", Overlay: "#292c3c",
	OverlayBorder: "#8caaee",
}

CatppuccinFrappe is the Catppuccin Frappe dark palette.

View Source
var CatppuccinLatte = Theme{
	Name: "catppuccin-latte", Bg: "#eff1f5", Fg: "#4c4f69", Border: "#9ca0b0",
	BorderFocus: "#1e66f5", Selected: "#1e66f5", Unread: "#40a02b", Dimmed: "#8c8fa1",
	StatusBar: "#e6e9ef", StatusFg: "#4c4f69", Error: "#d20f39", Overlay: "#e6e9ef",
	OverlayBorder: "#1e66f5",
}

CatppuccinLatte is the Catppuccin Latte light palette.

View Source
var CatppuccinMacchiato = Theme{
	Name: "catppuccin-macchiato", Bg: "#24273a", Fg: "#cad3f5", Border: "#5b6078",
	BorderFocus: "#8aadf4", Selected: "#8aadf4", Unread: "#a6da95", Dimmed: "#494d64",
	StatusBar: "#1e2030", StatusFg: "#cad3f5", Error: "#ed8796", Overlay: "#1e2030",
	OverlayBorder: "#8aadf4",
}

CatppuccinMacchiato is the Catppuccin Macchiato dark palette.

View Source
var CatppuccinMocha = Theme{
	Name: "catppuccin-mocha", Bg: "#1e1e2e", Fg: "#cdd6f4", Border: "#6c7086",
	BorderFocus: "#89b4fa", Selected: "#89b4fa", Unread: "#a6e3a1", Dimmed: "#585b70",
	StatusBar: "#313244", StatusFg: "#cdd6f4", Error: "#f38ba8", Overlay: "#313244",
	OverlayBorder: "#89b4fa",
}

CatppuccinMocha is the Catppuccin Mocha dark palette.

View Source
var CoralSunset = Theme{
	Name: "coral-sunset", Bg: "#444154", Fg: "#fec9c1", Border: "#fc8b79", BorderFocus: "#ff7062",
	Selected: "#ff7062", Unread: "#fec9c1", Dimmed: "#7a637f", StatusBar: "#7a637f",
	StatusFg: "#fec9c1", Error: "#ff7062", Overlay: "#7a637f", OverlayBorder: "#ff7062",
}

CoralSunset is a muted purple and coral palette.

View Source
var Dracula = Theme{
	Name: "dracula", Bg: "#282a36", Fg: "#f8f8f2", Border: "#6272a4", BorderFocus: "#bd93f9",
	Selected: "#bd93f9", Unread: "#50fa7b", Dimmed: "#6272a4", StatusBar: "#21222c",
	StatusFg: "#f8f8f2", Error: "#ff5555", Overlay: "#21222c", OverlayBorder: "#bd93f9",
}

Dracula is the Dracula palette.

View Source
var GruvboxDark = Theme{
	Name: "gruvbox-dark", Bg: "#282828", Fg: "#ebdbb2", Border: "#504945", BorderFocus: "#83a598",
	Selected: "#83a598", Unread: "#b8bb26", Dimmed: "#504945", StatusBar: "#1d2021",
	StatusFg: "#ebdbb2", Error: "#fb4934", Overlay: "#32302f", OverlayBorder: "#83a598",
}

GruvboxDark is the dark Gruvbox palette.

View Source
var GruvboxLight = Theme{
	Name: "gruvbox-light", Bg: "#fbf1c7", Fg: "#3c3836", Border: "#bdae93", BorderFocus: "#076678",
	Selected: "#076678", Unread: "#79740e", Dimmed: "#bdae93", StatusBar: "#f2e5bc",
	StatusFg: "#3c3836", Error: "#cc241d", Overlay: "#f2e5bc", OverlayBorder: "#076678",
}

GruvboxLight is the light Gruvbox palette.

View Source
var LavenderFieldsForever = Theme{
	Name: "lavender-fields-forever", Bg: "#382d72", Fg: "#e5ccf4", Border: "#b7c2c6",
	BorderFocus: "#a080e1", Selected: "#a080e1", Unread: "#e5ccf4", Dimmed: "#5c509c",
	StatusBar: "#5c509c", StatusFg: "#e5ccf4", Error: "#ff7062", Overlay: "#5c509c",
	OverlayBorder: "#a080e1",
}

LavenderFieldsForever is a deep indigo and lavender palette.

View Source
var MagentaGeode = Theme{
	Name: "magenta-geode", Bg: "#47003c", Fg: "#f3b0dc", Border: "#aa4d84", BorderFocus: "#c83fa9",
	Selected: "#c83fa9", Unread: "#f3b0dc", Dimmed: "#77176e", StatusBar: "#77176e",
	StatusFg: "#f3b0dc", Error: "#ff7062", Overlay: "#77176e", OverlayBorder: "#c83fa9",
}

MagentaGeode is a saturated purple and pink palette.

View Source
var Nord = Theme{
	Name: "nord", Bg: "#2e3440", Fg: "#eceff4", Border: "#4c566a", BorderFocus: "#88c0d0",
	Selected: "#88c0d0", Unread: "#a3be8c", Dimmed: "#4c566a", StatusBar: "#3b4252",
	StatusFg: "#d8dee9", Error: "#bf616a", Overlay: "#3b4252", OverlayBorder: "#88c0d0",
}

Nord is the Nord palette.

View Source
var OneDark = Theme{
	Name: "one-dark", Bg: "#282c34", Fg: "#abb2bf", Border: "#3e4451", BorderFocus: "#61afef",
	Selected: "#61afef", Unread: "#98c379", Dimmed: "#3e4451", StatusBar: "#21252b",
	StatusFg: "#abb2bf", Error: "#e06c75", Overlay: "#21252b", OverlayBorder: "#61afef",
}

OneDark is the One Dark palette.

View Source
var RosePine = Theme{
	Name: "rose-pine", Bg: "#191724", Fg: "#e0def4", Border: "#403d52", BorderFocus: "#c4a7e7",
	Selected: "#c4a7e7", Unread: "#9ccfd8", Dimmed: "#403d52", StatusBar: "#1f1d2e",
	StatusFg: "#e0def4", Error: "#eb6f92", Overlay: "#1f1d2e", OverlayBorder: "#c4a7e7",
}

RosePine is the dark Rose Pine palette.

View Source
var RosePineDawn = Theme{
	Name: "rose-pine-dawn", Bg: "#faf4ed", Fg: "#575279", Border: "#d7d2be", BorderFocus: "#907aa9",
	Selected: "#907aa9", Unread: "#286983", Dimmed: "#d7d2be", StatusBar: "#fffaf3",
	StatusFg: "#575279", Error: "#b4637a", Overlay: "#fffaf3", OverlayBorder: "#907aa9",
}

RosePineDawn is the light Rose Pine Dawn palette.

View Source
var RosePineMoon = Theme{
	Name: "rose-pine-moon", Bg: "#232136", Fg: "#e0def4", Border: "#44415a", BorderFocus: "#c4a7e7",
	Selected: "#c4a7e7", Unread: "#9ccfd8", Dimmed: "#44415a", StatusBar: "#2a2837",
	StatusFg: "#e0def4", Error: "#eb6f92", Overlay: "#2a2837", OverlayBorder: "#c4a7e7",
}

RosePineMoon is the Rose Pine Moon palette.

View Source
var TokyoNight = Theme{
	Name: "tokyo-night", Bg: "#1a1b26", Fg: "#c0caf5", Border: "#414868", BorderFocus: "#7aa2f7",
	Selected: "#7aa2f7", Unread: "#9ece6a", Dimmed: "#414868", StatusBar: "#16161e",
	StatusFg: "#a9b1d6", Error: "#f7768e", Overlay: "#16161e", OverlayBorder: "#7aa2f7",
}

TokyoNight is the dark Tokyo Night palette.

View Source
var TokyoNightDay = Theme{
	Name: "tokyo-night-day", Bg: "#e1e2e7", Fg: "#3760bf", Border: "#a8aecb", BorderFocus: "#2e7de9",
	Selected: "#2e7de9", Unread: "#587539", Dimmed: "#a8aecb", StatusBar: "#d0d5e3",
	StatusFg: "#3760bf", Error: "#f52a65", Overlay: "#d0d5e3", OverlayBorder: "#2e7de9",
}

TokyoNightDay is the light Tokyo Night palette.

View Source
var VT100 = Theme{
	Name: ThemeNameVT100, Bg: "#000000", Fg: "#33ff33", Border: "#145214", BorderFocus: "#00ff00",
	Selected: "#00ff00", Unread: "#66ff66", Dimmed: "#3dcc3d", StatusBar: "#001a00",
	StatusFg: "#33ff33", Error: "#ff6b6b", Overlay: "#001200", OverlayBorder: "#00ff00",
}

VT100 is a green phosphor terminal palette.

View Source
var VT52 = Theme{
	Name: ThemeNameVT52, Bg: "#000000", Fg: "#ffcc66", Border: "#6b4e14", BorderFocus: "#ffb020",
	Selected: "#ffb020", Unread: "#ffe6a8", Dimmed: "#a67c2e", StatusBar: "#1a1206",
	StatusFg: "#ffcc66", Error: "#ff6666", Overlay: "#140e04", OverlayBorder: "#ffb020",
}

VT52 is an amber terminal palette with ASCII presentation.

Functions

func TerminalBackgroundSequences

func TerminalBackgroundSequences(theme Theme) (set string, reset string)

TerminalBackgroundSequences returns OSC strings; the consumer decides where to write them.

Types

type Density

type Density string

Density controls vertical spacing in rows and overlays.

const (
	// Compact removes optional spacer rows and modal padding.
	Compact Density = "compact"
	// Comfortable adds breathing room between rows and in modals.
	Comfortable Density = "comfortable"
)

type Layout

type Layout struct {
	Width  int
	Height int
	Mode   LayoutMode
	Panes  [3]Pane
	Status *StatusBar
	Modal  *Overlay

	// SidebarRatio controls pane 0 in StackedRight mode. Zero uses 0.28.
	SidebarRatio float64
	// UpperRightRatio controls pane 1 in StackedRight mode. Zero uses 0.40.
	UpperRightRatio float64
	// ColumnRatios controls widths in ThreeColumn mode. Zero values use equal columns.
	ColumnRatios [3]float64
}

Layout supplies terminal dimensions, three panes, and optional shell chrome.

type LayoutMode

type LayoutMode int

LayoutMode selects how the three panes are arranged in the rendered shell.

const (
	// StackedRight places pane 0 on the left and stacks panes 1 and 2 on the right.
	StackedRight LayoutMode = iota
	// ThreeColumn places all three panes side by side.
	ThreeColumn
)

type Overlay

type Overlay struct {
	Visible bool
	Title   string
	Content string
	Footer  string
	Width   int
}

Overlay describes a centered modal displayed over the rendered layout.

type Pane

type Pane struct {
	Title   string
	Hint    string
	Content string
	Focused bool
	Accent  lipgloss.Color
}

Pane supplies a header and rendered body for one region of a Layout.

type Renderer

type Renderer struct {
	Styles Styles
}

Renderer renders Layout and Row values using one resolved set of styles.

func NewRenderer

func NewRenderer(theme Theme, options StyleOptions) Renderer

NewRenderer creates a renderer for a theme and style options.

func (Renderer) Render

func (r Renderer) Render(layout Layout) string

Render produces a terminal-sized themed view for layout.

func (Renderer) RenderRow

func (r Renderer) RenderRow(row Row, width int) string

RenderRow formats one Row to the requested content width.

type Row

type Row struct {
	Prefix   string
	Text     string
	Suffix   string
	Selected bool
	Muted    bool
}

Row is a generic themed list row with optional left and right content.

type StatusBar

type StatusBar struct {
	Left  string
	Right string
}

StatusBar supplies optional left- and right-aligned footer text.

type StyleOptions

type StyleOptions struct {
	Density   Density
	Overrides ThemeOverrides
}

StyleOptions controls density and optional theme color replacements.

type Styles

type Styles struct {
	Theme   Theme
	PlainUI bool
	Density Density

	Pane               lipgloss.Style
	PaneHeaderActive   lipgloss.Style
	PaneHeaderInactive lipgloss.Style
	Item               lipgloss.Style
	ItemMuted          lipgloss.Style
	ItemSelected       lipgloss.Style
	Badge              lipgloss.Style
	DetailTitle        lipgloss.Style
	DetailMeta         lipgloss.Style
	DetailBody         lipgloss.Style
	DetailFocusLine    lipgloss.Style
	SearchMatch        lipgloss.Style

	StatusBar       lipgloss.Style
	StatusError     lipgloss.Style
	StatusHint      lipgloss.Style
	StatusBarJoiner lipgloss.Style
	StatusNotice    lipgloss.Style

	Overlay      lipgloss.Style
	OverlayTitle lipgloss.Style
	OverlayBody  lipgloss.Style
	OverlayHint  lipgloss.Style
	InputFocused lipgloss.Style
	InputIdle    lipgloss.Style
	InputLabel   lipgloss.Style
}

Styles exposes resolved Lipgloss styles for composing application content.

func BuildStyles

func BuildStyles(base Theme, options StyleOptions) Styles

BuildStyles resolves a theme and options into reusable Lipgloss styles.

func (Styles) ListItemLineStride

func (s Styles) ListItemLineStride() int

ListItemLineStride returns the terminal-line height expected per rendered row.

func (Styles) StatusBarSeparator

func (s Styles) StatusBarSeparator() string

StatusBarSeparator returns theme-appropriate separator text for footer segments.

type Theme

type Theme struct {
	Name          string
	Bg            lipgloss.Color
	Fg            lipgloss.Color
	Border        lipgloss.Color
	BorderFocus   lipgloss.Color
	Selected      lipgloss.Color
	Unread        lipgloss.Color
	Dimmed        lipgloss.Color
	StatusBar     lipgloss.Color
	StatusFg      lipgloss.Color
	Error         lipgloss.Color
	Overlay       lipgloss.Color
	OverlayBorder lipgloss.Color
}

Theme contains the semantic colors used by the shell and its components.

func ThemeByName

func ThemeByName(name string) (Theme, bool)

ThemeByName returns a built-in theme by name, falling back to Catppuccin Mocha.

func (Theme) UsesASCII

func (t Theme) UsesASCII() bool

UsesASCII reports whether this theme uses plain terminal presentation.

type ThemeOverrides

type ThemeOverrides struct {
	Background lipgloss.Color
	Foreground lipgloss.Color
	Accent     lipgloss.Color
}

ThemeOverrides replaces presentation colors without requiring a new theme.

func (ThemeOverrides) Apply

func (o ThemeOverrides) Apply(base Theme) Theme

Apply returns base with each non-empty override applied.

type ThemePicker

type ThemePicker struct {
	// contains filtered or unexported fields
}

ThemePicker manages theme navigation, preview, confirmation, and modal output.

Applications remain responsible for persistence and terminal background control sequences after inspecting the picker result and active theme.

func NewThemePicker

func NewThemePicker(options ThemePickerOptions) ThemePicker

NewThemePicker creates a picker with an initially confirmed selection.

func (ThemePicker) ConfirmedTheme

func (p ThemePicker) ConfirmedTheme() Theme

ConfirmedTheme returns the most recently confirmed theme.

func (ThemePicker) Modal

func (p ThemePicker) Modal(renderer Renderer, width, height int) Overlay

Modal renders the picker as an Overlay for assignment to Layout.Modal. Height limits the number of rendered themes while keeping the cursor visible.

func (*ThemePicker) Open

func (p *ThemePicker) Open(confirmedName string)

Open displays the picker and begins a preview session from confirmedName. An unknown name selects the first configured theme.

func (ThemePicker) Opened

func (p ThemePicker) Opened() bool

Opened reports whether the picker should currently be displayed.

func (ThemePicker) PreviewTheme

func (p ThemePicker) PreviewTheme() Theme

PreviewTheme returns the currently highlighted theme.

func (*ThemePicker) Update

func (p *ThemePicker) Update(msg tea.KeyMsg) ThemePickerAction

Update applies TideMail-compatible picker navigation and completion keys. After navigation, applications can rebuild their renderer from PreviewTheme.

type ThemePickerAction

type ThemePickerAction int

ThemePickerAction reports terminal picker actions after an input update.

const (
	// ThemePickerNone indicates navigation or ignored input.
	ThemePickerNone ThemePickerAction = iota
	// ThemePickerConfirm indicates that the previewed theme was confirmed.
	ThemePickerConfirm
	// ThemePickerCancel indicates that the preview was reverted.
	ThemePickerCancel
)

type ThemePickerOptions

type ThemePickerOptions struct {
	// Themes supplies selectable palettes. Empty uses BuiltinThemes.
	Themes []Theme
	// InitialTheme is the initially confirmed theme name.
	InitialTheme string
	// Title labels the modal. Empty uses "THEME".
	Title string
}

ThemePickerOptions configures a reusable theme selection modal.

Directories

Path Synopsis
cmd
demo command

Jump to

Keyboard shortcuts

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