Documentation
¶
Index ¶
- Variables
- func ActionDescription(action, target string, isGroup bool) string
- func BarColumn(fg color.Color, bg color.Color, content string) string
- func DockerActionFor(msg tea.KeyPressMsg) (string, bool)
- func EmptyCard(width, availHeight int, bg color.Color, title, body, key, hint string) string
- func FitBox(s lipgloss.Style, w, h int) lipgloss.Style
- func HandleSpinnerTick(spinnerModel spinner.Model, pendingAction *PendingAction, msg tea.Msg) (spinner.Model, tea.Cmd)
- func HealthColor(health string) color.Color
- func Hyperlink(text, url string) string
- func ListRowBg(isActive bool) color.Color
- func ModalHints(hints ...KeyHint) string
- func ModalListHeight(items, termHeight int) int
- func ModalSurface(bg color.Color, content string) string
- func ModalTitle(text string) string
- func NewSpinner() spinner.Model
- func PanelBg() color.Color
- func PanelBodyHeight(total int) int
- func PanelBodyWidth(total int) int
- func PanelBodyWithFooter(width, avail int, bg color.Color, content, footer string) string
- func PanelFrame(title string, titleRight string, width int, height int, body string) string
- func PanelRule(width int) string
- func PortLabel(p types.ServicePortConfig) string
- func PublishedPorts(ports []types.ServicePortConfig) []string
- func RenderKeyHints(hints []KeyHint, descColor color.Color) string
- func ShortImage(ref string, width int) string
- func Truncate(s string, w int) string
- type KeyHint
- type PendingAction
Constants ¶
This section is empty.
Variables ¶
var ListWrapperStyle = lipgloss.NewStyle().
Padding(1, 2, 2, 2)
ListWrapperStyle is the frame around the two body lists. Its padding is what separates the list content from the panel edges, and its frame size is subtracted from the panel box when the inner list is sized.
var WrapperStyle = lipgloss.NewStyle().
Padding(1, 2)
Functions ¶
func ActionDescription ¶
ActionDescription returns a full description of the pending action.
func BarColumn ¶
BarColumn renders the nav's ▌ indicator once per line of content, so the bar spans a multi-line row's full height instead of a sliver at its top. bg may be nil to leave the cell background unset.
func DockerActionFor ¶
func DockerActionFor(msg tea.KeyPressMsg) (string, bool)
DockerActionFor returns the `docker compose` action a keypress asks for. Both details panels read it, which is what makes "s starts a group" and "s starts a service" the same fact rather than two switch statements that happen to agree. Remove is absent on purpose: it is destructive, so it goes through a confirmation instead of straight to a command.
func EmptyCard ¶
EmptyCard renders a dim, centered, rounded-border card used for the empty / onboarding states. `key` is shown in the accent color inside brackets, `hint` is the trailing description in a dim color. `availHeight` is the vertical space in which the card should be centered. `bg` is the panel's background tier, which the space around the card sits on; the card itself is recessed below that tier so it reads as inset into the panel.
func FitBox ¶
FitBox constrains a style to an exact w x h box: Width/Height pad it out, Max* clip anything that would otherwise overflow (Width alone pads but never truncates, which is how a too-wide panel ends up wrapped by the terminal). Non-positive dimensions are left unset so a component still renders naturally before the first SetBodyLayoutMsg arrives.
func HandleSpinnerTick ¶
func HandleSpinnerTick(spinnerModel spinner.Model, pendingAction *PendingAction, msg tea.Msg) (spinner.Model, tea.Cmd)
HandleSpinnerTick updates the spinner and returns the next tick command. Returns nil if no spinner is active.
func HealthColor ¶
func Hyperlink ¶
Hyperlink wraps already-sized text in an OSC 8 terminal hyperlink pointing at url. text must already be truncated and padded to its column - OSC 8's escape sequence is not text, and handing a not-yet-truncated string to runewidth.Truncate (chrome.Truncate) will cut through the middle of it, corrupting the rest of the screen. Terminals without OSC 8 support simply do not render the sequence, so text is what they show.
func ListRowBg ¶
ListRowBg is the background a list row renders on. The active row is lifted to the surface tier; every other row sits flush on the panel's elevated tier. Rows need an explicit background (rather than inheriting the panel's) because each row is rendered and sealed on its own - see appstyles.FillBackground.
func ModalHints ¶
ModalHints renders a modal's own help line, in the footer bar's format but with the lighter description color the modal surface needs. Every modal carries one: the footer bar is hidden behind the modal while it is open, so the keys the modal takes over are advertised here or nowhere.
func ModalListHeight ¶
ModalListHeight is the height to build a modal's list with so the modal fits a terminal termHeight rows tall.
renderWithModal (src/model/View.go) centers a modal by clamping y to 0, so a modal taller than the terminal does not scroll or shrink - it loses its hint line and bottom border off the bottom of the screen. A list sized to len(items) is therefore a latent overflow on any project big enough, and the caller pairs this with SetShowPagination(height < len(items)) so the rows that no longer fit stay reachable and say so.
The floor of 3 is deliberate: below about 12 rows there is no honest answer, and a terminal that short cannot show the modal's own chrome either.
func ModalSurface ¶
ModalSurface wraps a modal's content in the shared modal chrome: an accent rounded border, padding, and a background sealed against `bg` so the modal reads as one opaque surface over the page it is composited onto. Modals in particular cannot afford an unpainted cell - the page shows through it.
BorderBackground is set explicitly because lipgloss leaves border cells on the default background otherwise, which outlines the modal in the terminal's color.
func ModalTitle ¶
ModalTitle renders a modal's heading. Every modal names itself, so a user who lands on one mid-flow can tell what it is about to do without having to infer it from the fields. It is the shared accent chip - appstyles.NormalTitle - stood off the body by its own margin, so a style or theme change to the chip lands on modals and panes alike.
The margin replaces the blank line each caller would otherwise have to add - it matches the blank row the hint line sits above: the heading and the footer are the modal's chrome, and both stand off the body.
func NewSpinner ¶
NewSpinner creates a spinner styled with the active theme's accent color.
func PanelBg ¶
PanelBg is the background tier a body panel renders on. Both body panels are always active now that focus is gone, so they share the elevated tier (what the previously "focused" panel used to get). Focus used to lift the whole panel rather than adding a border, so the panel's box stayed the same size either way - that lift is now the steady state.
func PanelBodyHeight ¶
func PanelBodyWidth ¶
PanelBodyWidth and PanelBodyHeight are the space a panel body gets inside a frame of the given total size: the frame's own padding taken off, plus the title row for the vertical axis. Callers size their content with these rather than with hardcoded offsets, so a change to WrapperStyle's padding doesn't silently push content out of the panel.
func PanelBodyWithFooter ¶
PanelBodyWithFooter lays out a details panel's body: `content` at the top, `footer` on the body's last rows, and blank rows between them. Both details panels build their body through here, which is what makes the pending-action spinner land on the same line of both rather than wherever each panel's content happens to end - see "The panel footer" in docs/DESIGN.md.
The content is clipped before the footer is attached, so a panel whose content outgrows its box loses its last rows rather than its footer. Doing it the other way round - joining first and clipping the result - takes the bottom off, which is exactly the row that has to survive.
An empty footer costs no rows. lipgloss.Height("") is 1, so passing "" would otherwise reserve a blank line at the foot of every panel that has nothing to pin there - which is most frames, now that the panels only use the footer for the spinner and the service panel's transient confirmations.
func PanelFrame ¶
PanelFrame renders the title chrome shared by DetailsPanel and GroupDetailsPanel, filling exactly the width x height box the panel was given. Both body panels are always active now that focus is gone, so they share the elevated background tier.
`titleRight` is an optional accessory pinned to the right end of the title row - the group status pill uses it. A blank row separates the title row from the body, so the panel's own label doesn't read as part of whatever the body starts with; PanelBodyHeight accounts for both rows.
Callers embed their action buttons at the bottom of `body`, which pins the action row to the bottom of the panel. The chip itself is appstyles.NormalTitle; the MarginLeft(2) here is the frame's own left gutter, matching the 2 columns the bubbles list TitleBar adds inside the list wrappers - see appstyles.NormalTitle.
func PanelRule ¶
PanelRule is the thin horizontal line both details panels separate their sections with. One helper rather than five copies of the same three-line style, so the panels cannot drift to different rules.
func PortLabel ¶
func PortLabel(p types.ServicePortConfig) string
PortLabel is one published port in full: the form the service details panel shows, where there is room for it.
14533->4533/tcp 127.0.0.1:14533->4533/tcp (bound to loopback: not reachable off-host) 4533/tcp (exposed, not published)
func PublishedPorts ¶
func PublishedPorts(ports []types.ServicePortConfig) []string
PublishedPorts returns the host-side ports a service publishes, in file order, deduplicated, for a column too narrow for the full mapping. A service that publishes nothing returns an empty slice.
[]{14533:4533/tcp} -> ["14533"]
[]{8080:8080/tcp, 6881:6881/tcp+udp} -> ["8080", "6881"]
[]{4533/tcp} -> [] (not published)
[]{53:53/udp} -> ["53/udp"]
func RenderKeyHints ¶
RenderKeyHints renders hints as "key desc · key desc": the key bold in the primary text color, the description in descColor. Modals render their own help lines through this so they read the same as the footer bar, passing a lighter descColor when they sit on a lighter surface than the bar does.
func ShortImage ¶
ShortImage renders an image reference to fit width columns, giving up whole parts of the reference in a fixed order rather than truncating the tail — the name is the part a reader needs and it is the part a plain truncation destroys first.
The ladder, widest first, each rung tried in full - the first that fits wins:
- as written, minus a ":latest" tag
- drop the registry host
- drop the namespace
- drop the tag (or digest)
- Truncate, as a backstop
func Truncate ¶
Truncate hard-truncates s to w display columns, appending an ellipsis when it is shortened. lipgloss Width wraps rather than truncates, so cells are pre-truncated to keep every row on a single line.
The ansi.StringWidth check is a fast path, not an alternate algorithm: a plain string that already fits returns unchanged either way, but a string carrying a zero-width escape sequence - an OSC 8 hyperlink built by chrome.Hyperlink, already sized to its column before being wrapped - does not, because runewidth.Truncate is not ANSI-aware and would cut through the middle of the sequence. See docs/plans/service-urls.md D5, which is why Hyperlink's own doc comment requires already-sized text rather than truncating after wrapping.
Types ¶
type KeyHint ¶
KeyHint represents a single keybinding for display in the bottom bar. Key is the literal key (e.g. "n", "space", "←/→"). Desc is a short verb describing what the key does.
type PendingAction ¶
PendingAction tracks a docker action that is currently running.