Documentation
¶
Index ¶
- func FormatClock(ts int32) string
- func FormatDayLabel(ts int32) string
- func FormatLastSeen(ts int32) string
- func FormatRelativeShort(ts int32) string
- func FormatRelativeTime(ts int32) string
- func FormatTimestamp(ts int32) string
- func FormatTimestampSmart(ts int32) string
- func Now() time.Time
- func PinClock(t time.Time) (restore func())
- func PinClockFunc(c clock) (restore func())
- func RenderInline(ft *telegram.FormattedText, roles theme.Roles, o textOpts) string
- func RenderText(ft *telegram.FormattedText, roles theme.Roles, width int) []string
- func SameDay(a, b int32) bool
- func SenderName(msg *telegram.Message, s *store.Store) string
- type BodyOptions
- type Link
- type MessageRenderer
- func (r *MessageRenderer) RenderBody(msg *telegram.Message, s *store.Store, opts BodyOptions) []string
- func (r *MessageRenderer) SetHyperlinks(on bool)
- func (r *MessageRenderer) SetImageProtocol(protocol media.Protocol, maxCols, maxRows int)
- func (r *MessageRenderer) SetInlineImages(policy string)
- func (r *MessageRenderer) SetRoles(roles theme.Roles)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FormatClock ¶
FormatClock is the thread grid's time column: bare wall-clock, always five cells, no date part. The grid puts the date in a day divider instead, so repeating it on every row would only cost body width.
func FormatDayLabel ¶
FormatDayLabel is the label of a thread grid day divider. It is upper case because a divider is a rule with a word set into it, not a sentence.
The year is dropped within the current year: the divider's job is to say which day the messages under it belong to, and "12 AUG" answers that for anything recent while "12 AUG 2024" is what you need once it does not.
func FormatLastSeen ¶
FormatLastSeen formats a user's last seen timestamp.
func FormatRelativeShort ¶
FormatRelativeShort is a relative time in the smallest space that still says something: "now", "4m", "4h", "yd", "2d", or a date once weeks have passed.
The rail's right-hand field is four cells and the chat list's is five. "4h ago" does not fit beside a name, and the "ago" is the half that carries no information — a relative time is already relative.
Hours give way to days at the DAY boundary, not at twenty-four hours. "23h" for something sent at nine last night is arithmetic; what the reader wants to know is whether it happened today, and a message from yesterday evening reading "14h" while one from this morning reads "6h" puts them on the same scale when the useful distinction is the night between them.
func FormatRelativeTime ¶
FormatRelativeTime returns a relative time string (e.g., "2m ago").
func FormatTimestamp ¶
FormatTimestamp formats a Unix timestamp for display.
func FormatTimestampSmart ¶
FormatTimestampSmart formats a Unix timestamp using a compact, date-aware form suitable for lists (e.g. the chat list) where a full timestamp on every row would be redundant: messages from today show only the time, messages from the last 6 calendar days show a short weekday plus time, and anything older falls back to an ISO date.
func Now ¶
Now is the instant the renderer measures against: the real clock, unless a test has pinned one.
func PinClock ¶
PinClock fixes the renderer's clock at t and returns the function that restores the previous one. Callers must restore it — a leaked pin makes every later test in the process render at somebody else's instant.
func PinClockFunc ¶
func PinClockFunc(c clock) (restore func())
PinClockFunc is PinClock with a moving clock, for the tests that need one instant per call rather than one for the whole test.
func RenderInline ¶
func RenderInline(ft *telegram.FormattedText, roles theme.Roles, o textOpts) string
RenderInline styles a whole formatted text with the semantic palette, and with OSC 8 hyperlinks when o.links says the terminal has them.
The hyperlinks were absent through phases 4 to 8 for a reason that turned out to be smaller than it looked: ansi.Wrap breaks a line between a link's opening and closing sequences and repairs neither, so the rest of that row — its padding and the panel rule beside it — becomes part of the link. The design record concluded that fixing it meant owning a grapheme-aware wrapper, because reopening a link means knowing which runes belong to it after wrapping. It does not: the URI is carried in the opening sequence, so cell.OpenLink can recover it from the wrapped line the same way OpenStyle recovers an SGR run. See docs/tui-2.0.md, divergence 14.
func RenderText ¶
RenderText lays formatted text out as body lines, exactly as a message body is drawn: blocks split out, entities styled, everything wrapped to width with each line closing its own styles.
It is exported for the composer's preview, which has to be drawn by the same code that draws received messages. A preview rendered by a second implementation is a preview that can disagree with what arrives, and being trusted is the entire purpose of it.
No hyperlinks and no revealed spoilers: this is what the text WILL be, and a clickable link in a draft is an invitation to click a thing that has not been sent yet.
Types ¶
type BodyOptions ¶
type BodyOptions struct {
// Width is the body column's width in display cells.
Width int
// RevealSpoilers un-hides spoiler spans. True only for the message
// under the cursor, and only after x.
RevealSpoilers bool
// ArmedLinkLo/ArmedLinkHi are the rune offsets of the link the reader
// has armed with gx, from [Link.Lo] and [Link.Hi]. Set only for the
// message under the cursor, the same as RevealSpoilers.
//
// Hi <= Lo means nothing is armed.
ArmedLinkLo, ArmedLinkHi int
}
BodyOptions is how a message is to be laid out for the thread grid.
type Link ¶ added in v0.0.20
type Link struct {
// Text is the visible run the entity covers.
Text string
// URI is the destination, resolved by [entityURI] — the same function
// the renderer uses for OSC 8, so a link cannot be drawn pointing one
// way and opened pointing another.
URI string
// InSpoiler marks a link that a spoiler covers. A hidden spoiler is
// drawn foreground-on-background so it occupies the right cells and
// reads as a deliberate block — which means a mark laid over it is
// invisible too. The caller decides what to do about that; this only
// reports it.
InSpoiler bool
// Lo and Hi are the rune offsets the entity covers in the message's own
// text. The renderer marks exactly this range when the link is armed;
// the block splitter preserves absolute offsets, so a link inside a
// quote is still findable by them.
Lo, Hi int
}
Link is one followable destination in a message, as the reader sees it.
Text is what is on screen and URI is where it actually goes, and the two are allowed to disagree — that is what a text_url entity IS. Keeping both is the point: a reader deciding whether to follow a link needs to see the destination, not the label somebody chose for it.
func MessageLinks ¶ added in v0.0.20
MessageLinks lists a message's links in reading order.
Reading order rather than entity order: Telegram does not promise its entities are sorted, and "the second link" has to mean the second one down the screen or cycling through them is a lottery. Ties break on the wider span first, matching splitBlocks, so nesting is at least deterministic.
Both a message's text and an attachment's caption are searched, because from the reader's side a link under a photo is a link.
func (Link) Openable ¶ added in v0.0.20
Openable reports whether this destination can be handed to the platform opener: the scheme is one a message plausibly means, and the URI survives the same encoding and length rules an OSC 8 hyperlink is held to.
Checked at the point of opening rather than when the list is built, so a link with a scheme this client refuses is still listed and still cycled past. A key that silently skips something the reader can plainly see underlined is a key that looks broken.
type MessageRenderer ¶
type MessageRenderer struct {
// contains filtered or unexported fields
}
MessageRenderer turns message content into terminal lines. It holds the image renderer and its cache; everything else it does is stateless.
func NewMessageRenderer ¶
func NewMessageRenderer() *MessageRenderer
func (*MessageRenderer) RenderBody ¶
func (r *MessageRenderer) RenderBody(msg *telegram.Message, s *store.Store, opts BodyOptions) []string
RenderBody lays a message's content out for the TUI 2.0 thread grid's body column: one string per terminal line, with no frame of its own. The grid owns everything to the left of the body column, so this returns content and nothing else.
The result is never empty — a message with nothing renderable still occupies one line, because a zero-line message would make the scroll index disagree with what is on screen.
func (*MessageRenderer) SetHyperlinks ¶
func (r *MessageRenderer) SetHyperlinks(on bool)
SetHyperlinks turns OSC 8 hyperlinks on the message body on or off. The caller resolves the policy and the terminal's capability together, once, so the renderer never has to.
func (*MessageRenderer) SetImageProtocol ¶
func (r *MessageRenderer) SetImageProtocol(protocol media.Protocol, maxCols, maxRows int)
SetImageProtocol replaces the image renderer used for photo bubbles. Zero cols/rows fall back to the historical 50x25 bubble size.
func (*MessageRenderer) SetInlineImages ¶
func (r *MessageRenderer) SetInlineImages(policy string)
SetInlineImages sets the resolved ui.inline_images policy: whether a photo is drawn as art or as a metadata card.
"never" is the setting that matters. A terminal whose image support was guessed wrong turns every photo into a screenful of garbage, and the user needs a way to say so that does not also turn off downloading.
func (*MessageRenderer) SetRoles ¶
func (r *MessageRenderer) SetRoles(roles theme.Roles)
SetRoles supplies the TUI 2.0 semantic palette used for entity styling, code frames, quotes and media cards.