Vectrum
Sign In
90 lines · 3.1 KB · Go
package tui

import (
	"strings"

	"github.com/charmbracelet/lipgloss"
)

// mascot is Codey: a gopher, because this is Go, wearing the two big eyes the
// half-block grid can just about afford.
//
// It is drawn with half-blocks from the box-drawing range, so it needs no
// patched font and degrades to something recognisable in a plain xterm. Every
// row is eight cells wide — the wizard assembles it a row at a time and the
// splash lays it out in a column, so a ragged row would show up as a wobble in
// both.
var mascot = []string{
	"▄██████▄",
	"█  ██  █",
	"███▀▀███",
	" ▀█▀▀█▀ ",
}

const mascotGap = "   "

// splash is the welcome block. It goes into the scrollback rather than into
// fixed chrome, so it scrolls away once there is real work to look at instead
// of permanently taxing four rows of a small terminal.
//
// On a narrow terminal the mascot is dropped rather than sliced: half a mascot
// reads as a rendering bug, where the three text lines alone read as a choice.
func splash(o Options, width int) string {
	name := styHeading.Render("codey")
	if o.Version != "" {
		name += " " + styDim.Render(o.Version)
	}
	lines := []string{
		name,
		styDim.Render(prettyModel(o.Client.Model()) + " · " + endpointLabel(o.Client)),
		styDim.Render(shortenHome(o.Root)),
	}

	// A fourth line, for the four things there is no other way to find out.
	// The mascot is four rows tall and the text was three, so this costs no
	// height at all on a terminal wide enough to show it — and on one that is
	// not, a half-sentence of hints would be worse than none, so it is dropped
	// whole rather than truncated.
	if width >= artWidth()+minSplashHints {
		lines = append(lines, styFaint.Render(splashHints))
	}

	// The mascot is dropped only when the terminal cannot hold it beside a
	// readable amount of text. It is measured against a minimum rather than
	// against the longest line, or a deeply nested working directory would
	// silently cost you the whole welcome block.
	art := mascot
	if width < artWidth()+minSplashText {
		art = nil
	}

	var b strings.Builder
	for i := 0; i < maxInt(len(art), len(lines)); i++ {
		var line string
		if i < len(art) {
			line = styCoral.Render(art[i]) + mascotGap
		}
		if i < len(lines) {
			line += lines[i]
		}
		b.WriteString(truncateStyled(line, maxInt(1, width)) + "\n")
	}
	return b.String()
}

// minSplashText is how much room the text beside the mascot needs before the
// mascot stops being worth its columns.
const minSplashText = 24

// splashHints is the one line a first run needs: how to ask, where the
// commands are, how to get a shell, and how to get back what you typed last
// time. Everything else is in /help, which this line is mostly there to point
// at.
const splashHints = "/help · ↑ earlier prompts · ! shell · shift+tab approvals"

// minSplashHints is how much room the hints need beside the mascot. Measured
// from the string rather than guessed at, so editing the line cannot quietly
// start truncating it.
var minSplashHints = lipgloss.Width(splashHints)

func artWidth() int { return lipgloss.Width(mascot[1]) + lipgloss.Width(mascotGap) }