bubbleteandmd

package module
v0.5.1 Latest Latest
Warning

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

Go to latest
Published: Nov 12, 2026 License: MIT Imports: 34 Imported by: 0

README

bubbleteandmd

Streaming Markdown for terminal programs built on bubbletea. A Go port of the ideas in llx-markdown (now named flowdown; MIT; see NOTICE), for the terminal. First user: dun.

Status: layer 1 (streaming core: NewStream, Frame, Final) in v0.1.0. Layers 2-3 in progress.

Problem

An LLM reply arrives token by token. Rendering the whole document again per frame costs more as the reply grows, and half-written syntax (**bold, an open code fence, a link without its )) shows raw until it closes.

Design (three layers, each usable alone)

  1. Streaming core. A line scanner splits the source into blocks only where a full parse would split. Blocks before the last two are final: rendered once, cached by (source, width, style). The block before the last renders again only when its source changes. The last block (the tail) is healed while streaming only — the final text renders as written — and rendered again. Lists, blockquotes, footnotes and link definitions force a wider re-render where needed. Block rendering uses glamour.
  2. Directive blocks. llx-markdown's syntax, unchanged, so the same model output renders in both: > [!NOTE]-style callouts, > [!STEPS], > [!TABS] + #### panels, > [!STATS], > [!PROS]/> [!CONS], > [!DETAILS], > [!SOURCES], ```tree fences, title="…" on code fences, GFM task lists. Drawn with lipgloss. Mermaid and math show as code. ```diff / ```patch fences: added and removed lines on a green/red background tint, changed words of a removed/added pair stronger, hunk and file headers faint, one line per source line (cut, not wrapped); with title="x.go" the code inside is coloured by the file's language. notty: no colour, the +/- marker only. Sample: testdata/directives/diff.md.
  3. Interactive blocks. A bubbletea component that holds fold and tab state per block id across re-renders: fold [!DETAILS] and trees, switch tabs. While a block streams, tabs show stacked.

Plus a short format prompt that teaches a model the directive forms (like llx-markdown's answerFormatPrompt).

Use

r, err := bubbleteandmd.NewRenderer(styles.DarkStyleConfig, width) // or a raw *glamour.TermRenderer
cache := bubbleteandmd.NewCache(512)                              // one per program
s := bubbleteandmd.NewStream(r, fmt.Sprintf("dark/%d", width), cache) // one per reply
out, err := s.Frame(reply) // each time the reply grows
out, err = s.Final(reply)  // once, when the reply ends
  • One Stream per reply. When the width or style changes, make a new Renderer and a new Stream with a key that names both.
  • Call Final when the stream ends and show its output. Frame hides a partial table row and other half-written syntax, so the last Frame of a reply that ends in one is not the reply.
  • Pass the Renderer raw. Process the source before Frame and the output after; a wrapper that trims glamour's newlines gives ErrTrimmed.
  • Tables (NewRenderer): each row of a top-level table is one line. A table wider than width renders at its own width, cut at width with … +N cols →. While a table streams, each whole row draws as it arrives; column widths can change from frame to frame, the height only grows. A lone header line shows table… until its delimiter row ends. Not single-line: tables in lists, quotes and form bodies, tables with an email address or emoji shortcode, NewRenderer given glamour options, a raw glamour Renderer (glamour wraps cells).
  • r.WideBlocks(pane) (before the first Render): a top-level code block or table wider than width is not wrapped; it renders at its own width and each line is cut at pane with … +N cols →. Prose still wraps at width. Put pane in the Stream key.
  • Interactive forms: d := bubbleteandmd.NewDoc(r, key, cache) in place of NewStream; d.Frame/d.Final as above. The layout stays as streamed until the user acts. d.Focus() gives the Doc the keys; d.HandleKey(msg, bubbleteandmd.DefaultKeyMap()) returns false for a key the host handles (↑/↓ at height 0, tab past the last form, esc). Render again after a true; d.Stops() and d.Focused() give the lines to scroll to. On a width change make a new Renderer and Doc and copy the state: *nd.State() = *old.State() (form keys are byte offsets, the same at every width).
  • Viewport: d.SetHeight(h) (h > 0) makes the Doc own the scroll. Frame/Final/d.View() return only lines [d.Offset(), d.Offset()+h); ↑/↓, pgup/pgdown, home/end scroll (consumed, also at an edge and with no focus; show d.View(), no render needed); a focus change scrolls the focused stop into view; a view at the bottom follows the reply as it grows, a view scrolled up stays put. The view opens at the top when the first output it shows is taller than h, else at the bottom. Height 0 (default): all lines, the scroll keys return false. Stop lines count in the whole output.
  • On an error, show the raw text (after Sanitize). A panic in chroma while it highlights a code block is an error too (Render and Stream return "" and the error).
  • Call Sanitize on model text you render outside Stream.
Lex timeout (opt-in)

chroma gives each lexer rule a 250 ms match timeout on a wall clock. On a loaded machine a match can time out on valid code, and one character then draws in the Error style (a red background). To stop this, call once at program start, before any rendering:

bubbleteandmd.DisableLexTimeout()

Trade-off: the setting is for the whole process. It also removes the guard against runaway matches of chroma and of every other user of regexp2 in the process. The library never calls it.

Demo

make demo   # builds ./bin/demo
./bin/demo

Or, with no clone (Go 1.26.7 or later; no patches, no cgo, no generated code):

go install github.com/iodesystems/bubbleteandmd/cmd/demo@latest   # or: make install
demo

One reply streams at token speed into two panes: on the left a Doc (NewRenderer + WideBlocks + SetHeight), on the right glamour rendering the whole reply on every token (no heal, no cache, no Sanitize). The default scenario, all, streams the showcase, a half-written-Markdown part, a part with terminal controls, then the corpus long-100k reply, at 150 tokens/s.

Per pane: frame ms (last, avg, max), frames per second, total render ms, share of one core busy rendering (last second), dropped stream steps, lag (tokens and seconds; longest token-to-screen time), height shrinks (the title flashes ▼ shrank on each), time to the final output, and a bar chart of the longest frame per part of the reply, on one scale for both panes: flat on the left, rising with the reply on the right. The naive pane shows control bytes as ␛, ␇, ⟨U+202E⟩ (they are not sent to the terminal); the left pane counts the bytes Sanitize removed. A summary screen opens when both panes have the final output.

Scenarios (--scenario, or c to go to the next): all, showcase, heal, unsafe, long (long-100k), code (code-50k), forms (forms-20k), thread (thread-30: 30 finished replies on the page; the naive pane renders the page per token).

Keys: q quit · space pause · p freeze (both panes stop on the same token; the header shows the end of the source) · n one token while stopped · s summary · c next scenario · r restart · +/- rate. Left pane only (glamour shows llx forms as quoted text): tab/shift+tab forms · enter toggle · ←/→ tabs · ↑/↓ pgup/pgdown scroll. Flags: --scenario NAME, --style dark|light|notty, --width N (per pane; default half the terminal), --rate N (tokens/s, default 150), --file path.md, --wide=false, --seed N.

Rules

  • Terminal controls in the source never reach the terminal: Stream calls Sanitize first; call it yourself for text you render outside Stream.
  • Healed text never reaches the stored source.
  • Joined blocks must equal glamour's whole-document spacing: the final frame must not move when the stream ends (checked by a test that compares streamed and whole renders at every chunk boundary).

Documentation

Overview

Package bubbleteandmd renders streaming LLM Markdown in a terminal, for bubbletea programs. Finished blocks are rendered once and cached; only the open tail is rendered again as tokens arrive, and that tail is healed (an open fence, `**`, a half-typed link) so raw syntax does not show mid-stream. It reads llx-markdown's directive syntax (`> [!STEPS]`, `> [!TABS]`, ```tree fences …). See README.md.

Index

Constants

View Source
const (
	MarkHeader = directive.MarkHeader
	MarkPanel  = directive.MarkPanel
	MarkRow    = directive.MarkRow
)

Variables

Actions is every Action, in the order a key is looked up and a help list shows them.

View Source
var ErrTrimmed = errors.New("bubbleteandmd: Renderer output ends a paragraph without a newline; pass a raw *glamour.TermRenderer")

ErrTrimmed is returned by Frame and Final when the Renderer ends a paragraph without its newline (a wrapper that trims glamour's output): the blocks cannot be joined as the whole document renders.

Functions

func DisableLexTimeout added in v0.4.1

func DisableLexTimeout()

DisableLexTimeout stops regexp2's match timeouts in the whole process, so chroma never draws valid code with an Error token because of a timeout.

Why: chroma (v2.27.0) gives every lexer rule a 250 ms regexp2 MatchTimeout on a wall clock and reads a timeout as no match, so the next character gets the Error style (a red background in most styles). A goroutine that is off the CPU for 250 ms in the middle of a match (a loaded machine, many goroutines) times out on code that is valid.

Trade-off: the setting is process-wide (regexp2 has no per-call setting). It removes chroma's guard, and the guard of every other regexp2 user in the process, against a runaway (catastrophic backtracking) match: such a match then runs until it ends.

Opt-in: the library never calls it. Call it once at program start, before any rendering or other regexp2 use (it writes a regexp2 setting without a lock).

func Sanitize added in v0.2.0

func Sanitize(src string) string

Sanitize removes from src what a terminal reads as a command, not text, so model output cannot write the clipboard (OSC 52), set the title, move the cursor, clear the screen or overwrite a line. glamour v2.0.1 passes these through unchanged, in prose, tables and code.

Removed:

  • C0 controls (U+0000–U+001F) except tab and newline; "\r\n" becomes "\n" and any other "\r" goes too. ESC goes, so the text of an escape sequence after it stays as plain characters.
  • DEL (U+007F) and C1 controls (U+0080–U+009F), among them CSI U+009B.
  • bytes that are not UTF-8 (a raw 0x9B byte is one).
  • an incomplete UTF-8 sequence at the end of src: it can complete in the next chunk, so it is held back, not shown as an error.
  • Unicode bidi embeddings, overrides and isolates (U+202A–U+202E, U+2066–U+2069): they reorder the text shown, so a line can read differently from its bytes (spoofing).

Everything else stays, ZWJ and emoji included.

Sanitize is prefix-stable: for any cut k, Sanitize(s[:k]) is a prefix of Sanitize(s), so a stream that grows keeps its rendered blocks. It is idempotent. Stream.Frame and Stream.Final call it; a caller that renders model text some other way must call it first.

Types

type Action added in v0.4.0

type Action string

Action is a named thing a key does to a Doc.

const (
	// ActionNext and ActionPrev move the focus to the next or previous
	// stop. Past the last (first) stop the Doc blurs and the action is not
	// consumed, so the host moves its own focus on.
	ActionNext Action = "focus.next"
	ActionPrev Action = "focus.prev"
	// ActionToggle folds or opens a details form or the tree folder at the
	// cursor, or switches tabs between stacked and one-tab view.
	ActionToggle Action = "toggle"
	// ActionLeft and ActionRight: tabs, the previous or next panel (from
	// stacked, the first press switches to one-tab view at the selected
	// panel); tree, the cursor to the previous or next drawn folder. Both
	// wrap.
	ActionLeft  Action = "move.left"
	ActionRight Action = "move.right"
	// ActionLeave blurs the Doc and is not consumed: the host still does
	// the key's job (esc).
	ActionLeave Action = "focus.leave"
	// The scroll actions move the view of a Doc with a height by a line, a
	// page (the height), or to the top or bottom. They act with or without
	// focus, and are consumed at an edge too (nothing moves). At height 0
	// they are not consumed: the host scrolls.
	ActionScrollUp   Action = "scroll.up"
	ActionScrollDown Action = "scroll.down"
	ActionPageUp     Action = "page.up"
	ActionPageDown   Action = "page.down"
	ActionTop        Action = "scroll.top"
	ActionBottom     Action = "scroll.bottom"
)

type Cache

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

Cache holds rendered stable blocks, keyed by (Stream key, block source). One Cache can serve many Streams; it is safe for concurrent use.

func NewCache

func NewCache(max int) *Cache

NewCache returns a cache of at most max rendered blocks.

type DetailsState added in v0.4.0

type DetailsState = directive.DetailsState

TabsState, DetailsState and TreeState are the state of one form.

type DirectiveRenderer added in v0.2.0

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

DirectiveRenderer renders Markdown as glamour does, and draws llx-markdown's directive forms (callouts, steps, tabs, stats, pros and cons, details, sources, quotes with a source, file trees, titled code fences) and `$$` formulas. It satisfies Renderer, so NewStream can stream it. A document with no form, no `$$` formula and no top-level table wider than the width renders exactly as glamour renders it; such a table is not wrapped: each row is one line, cut at the width (wide.go). Past the nesting caps (internal/nest: 16 blocks, 100 inline spans) it differs: the deeper part renders as text. With glamour options given, no caps.

A DirectiveRenderer is used by one goroutine at a time.

func NewRenderer added in v0.2.0

func NewRenderer(style ansi.StyleConfig, width int, opts ...glamour.TermRendererOption) (*DirectiveRenderer, error)

NewRenderer returns a renderer for style at word wrap width. opts go to glamour after the style, the word wrap and the chroma formatter; they must not change the style or the word wrap.

Code blocks use chroma formatter "bubbleteandmd-terminal256" (same bytes as "terminal256", style tables cached; chromafmt.go). The first NewRenderer in a process registers it in chroma's formatter registry, which has no lock: make it before other goroutines render with chroma.

func (*DirectiveRenderer) GlamourOptions added in v0.3.0

func (r *DirectiveRenderer) GlamourOptions() (ansi.Options, bool)

GlamourOptions returns the options of the glamour renderer that draws plain Markdown at the top level: the style, the word wrap and the chroma formatter. ok is false when NewRenderer was given glamour options: they cannot be read back. Stream uses it to render a growing table from cached rows (GlamourOptions).

func (*DirectiveRenderer) Render added in v0.2.0

func (r *DirectiveRenderer) Render(md string) (string, error)

Render renders md. An md that starts with join.Sentinel (as Stream sends every block after the first) renders its first block in not-first place.

func (*DirectiveRenderer) RenderState added in v0.4.0

func (r *DirectiveRenderer) RenderState(md string, offset int, st *State) (string, []Mark, error)

RenderState renders md as Render does, with each form drawn in its state in st (nil: as streamed), and returns the marks of the interactive parts, lines counted in out (StateRenderer). offset is the document offset of md's text after the sentinel: form keys are document keys (form offsets in the source as written, before `$$` formulas are rewritten). md should be sanitized already, else offsets move.

func (*DirectiveRenderer) WideBlocks added in v0.4.0

func (r *DirectiveRenderer) WideBlocks(pane int)

WideBlocks turns wide blocks on: top-level code blocks and tables wider than the word wrap width render at their own width and are cut at pane. A pane below the word wrap width is read as that width; 0 turns wide code blocks off (the default). Tables are wide blocks with or without it: with pane 0 a wide table is cut at the word wrap width, so each row is one line (Carl 2026-11-11, single-line table rows). Call it before the first Render. The Stream key must name pane too.

Not in wide blocks: code blocks and tables inside lists, quotes and form bodies; titled code fences and file trees (forms: cut at the width, as before); a table with a row the width model does not read (an email address, an emoji shortcode) or with glamour options given to NewRenderer.

type Doc added in v0.4.0

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

Doc is one reply with interactive forms: a Stream that draws in a State the Doc holds, the stops (forms that take keys) of its last output, and the actions that change the State. It is not a tea.Model: Do and HandleKey report whether the key was consumed, and the host handles a key that was not (Carl 2026-11-11).

The layout stays as streamed until an action changes it: Frame and Final draw the zero State, and the end of the stream changes no form. One exception: a table header that Frame held as a placeholder (no whole delimiter row yet, tablehold.go) is drawn by Final. Focus alone changes no line count or width (a same-width cue only). After an action that returns true, render again (Frame or Final with the same source) to show it; Stops and Focused are those of the last render. A scroll action needs no render: View shows it.

SetHeight > 0 makes the Doc a viewport that owns the scroll (viewport.go).

A Doc is not safe for concurrent use.

func NewDoc added in v0.4.0

func NewDoc(r Renderer, key string, c *Cache) *Doc

NewDoc returns a Doc that renders through NewStream(r, key, c). r should be a StateRenderer (*DirectiveRenderer); with any other Renderer the Doc has no stops.

func (*Doc) Blur added in v0.4.0

func (d *Doc) Blur()

Blur takes the focus away; Focus returns to the same stop.

func (*Doc) Do added in v0.4.0

func (d *Doc) Do(a Action) bool

Do applies an action to the focused stop and reports whether it was consumed. With no focus nothing is consumed (the host calls Focus to give the Doc the keys). An action that leaves the Doc (ActionNext past the last stop, ActionPrev before the first, ActionLeave) blurs it and returns false, so the host still acts on the key. A scroll action is consumed when the Doc has a height (viewport.go), with or without focus.

func (*Doc) Final added in v0.4.0

func (d *Doc) Final(src string) (string, error)

Final is Stream.Final in the Doc's State; it updates the stops and returns View.

func (*Doc) Focus added in v0.4.0

func (d *Doc) Focus() bool

Focus focuses the stop focused before the last Blur when it is still a stop, else the first. False when there is no stop.

func (*Doc) FocusLast added in v0.4.0

func (d *Doc) FocusLast() bool

FocusLast focuses the last stop (the host's focus arrives from below). False when there is no stop.

func (*Doc) Focused added in v0.4.0

func (d *Doc) Focused() (Stop, bool)

Focused returns the focused stop of the last output, if any.

func (*Doc) Frame added in v0.4.0

func (d *Doc) Frame(src string) (string, error)

Frame is Stream.Frame in the Doc's State; it updates the stops and returns View (with a height, only the view).

func (*Doc) HandleKey added in v0.4.0

func (d *Doc) HandleKey(msg tea.KeyPressMsg, km KeyMap) bool

HandleKey does the action msg is bound to in km and reports whether the key was consumed (Do). False: the host handles the key.

func (*Doc) Height added in v0.4.0

func (d *Doc) Height() int

Height is the height set by SetHeight (0: unconstrained).

func (*Doc) Lines added in v0.4.0

func (d *Doc) Lines() int

Lines is the number of lines in the last output (all of it, not the view).

func (*Doc) Offset added in v0.4.0

func (d *Doc) Offset() int

Offset is the first output line the view shows (0 when the height is 0). Stop lines are counted in the whole output: a stop shows when its lines are in [Offset, Offset+Height).

func (*Doc) Reset added in v0.4.0

func (d *Doc) Reset()

Reset clears all state: the layout as streamed, nothing focused. The height and the view's offset stay.

func (*Doc) SetHeight added in v0.4.0

func (d *Doc) SetHeight(h int)

SetHeight sets the number of lines the Doc shows (0 or less: all of them). A view at the bottom stays at the bottom. The view opens on the first output it shows: at the top when the output is taller than h, else at the bottom (it then follows a stream as it grows). That output is the current one when SetHeight comes after a render, else the next render's, so the order of SetHeight and Final does not matter. Width is the host's: for a new width make a new Renderer and Doc and copy the State (Doc.State).

func (*Doc) State added in v0.4.0

func (d *Doc) State() *State

State returns the Doc's State. The host may read it (State.Focused in notty, where focus has no colour); a change to it shows on the next render. On a width change the host makes a new Renderer and Doc and copies the State: `*nd.State() = *old.State()`. Form keys are byte offsets in the source, the same at every width, so the forms keep their folds, tabs and focus. The copy shares the State's maps; use old no more.

func (*Doc) Stops added in v0.4.0

func (d *Doc) Stops() []Stop

Stops returns the stops of the last output, in focus order, lines counted in the whole output. At height 0 the host scrolls with them.

func (*Doc) View added in v0.4.0

func (d *Doc) View() string

View returns the view of the last output: all of it at height 0, else lines [Offset, Offset+Height), a substring of the output (fewer lines when the output is shorter). A scroll action changes it with no new render.

type GlamourOptions added in v0.3.0

type GlamourOptions interface {
	GlamourOptions() (opts ansi.Options, ok bool)
}

GlamourOptions is an optional interface of a Renderer: the glamour options it renders a plain Markdown table with. ok is false when they are not known (for example, glamour options were given that cannot be read back). When ok, Stream renders a growing table in the tail from cached rows (internal/tablestream), with the same bytes as r.Render; without it, the tail renders whole on every call. *DirectiveRenderer implements it.

type KeyMap added in v0.4.0

type KeyMap map[Action][]string

KeyMap binds each Action to keys, named as tea.KeyPressMsg.String names them ("tab", "shift+tab", "enter", "space", "left"). An action with no keys is off. The scroll keys act only in a Doc with a height; at height 0 HandleKey returns false for them and the host scrolls.

func DefaultKeyMap added in v0.4.0

func DefaultKeyMap() KeyMap

DefaultKeyMap: tab and shift+tab move between stops, enter and space toggle, ←/→ change the tab or the tree folder, esc leaves; with a height, ↑/↓ scroll a line, pgup/pgdown a page, home/end to the top/bottom.

func (KeyMap) Action added in v0.4.0

func (k KeyMap) Action(key string) (Action, bool)

Action returns the action key is bound to; actions are looked up in the order of Actions, so a key bound twice is the earlier action's.

func (KeyMap) Label added in v0.4.0

func (k KeyMap) Label(a Action) string

Label is how a hint names an action's key: its first, or "" if it has none.

type Mark added in v0.4.0

type Mark = directive.Mark

Mark is where an interactive part of a form is drawn: the form's Key, what part (MarkKind), its first line and its height in lines.

type MarkKind added in v0.4.0

type MarkKind = directive.MarkKind

MarkKind is what a Mark points at.

type Renderer

type Renderer interface {
	Render(string) (string, error)
}

Renderer renders one whole Markdown document. *glamour.TermRenderer satisfies it. A Renderer is used by one goroutine at a time.

Pass a raw *glamour.TermRenderer (or a DirectiveRenderer), not a wrapper that changes its output: Stream renders each block alone and joins the pieces by the document prefix, suffix and line ends glamour writes. A wrapper that trims the newlines makes Frame and Final return ErrTrimmed. Do pre-processing on src before Frame; do post-processing on the output of Frame and Final.

type State added in v0.4.0

type State = directive.State

State is the per-form state of one document (folded details, selected tab, closed tree folders, the focused form), keyed by form key: the byte offset of a top-level form in the document, "<parent key>/<slot>@<offset>" for a form in a body. The zero State and a nil *State draw the layout as streamed (Carl 2026-11-11). Only the host changes it.

type StateRenderer added in v0.4.0

type StateRenderer interface {
	Renderer
	RenderState(md string, offset int, st *State) (string, []Mark, error)
}

StateRenderer is an optional interface of a Renderer: it draws each form in its State and tells where the interactive parts are. RenderState(md, offset, nil) returns what Render(md) returns. offset is the document offset of md's text (after join's sentinel, when md has it), so form keys are document keys; mark lines are counted in the output. *DirectiveRenderer implements it.

type Stop added in v0.4.0

type Stop struct {
	Key          string
	Kind         StopKind
	Line, Height int
}

Stop is one form that takes keys, as drawn in the last output: Key is its State key; Line and Height are the lines of its marks (details: the title lines; tabs: the header and the drawn panels; tree: the drawn rows), counted in that output.

type StopKind added in v0.4.0

type StopKind int

StopKind is the kind of form a Stop is.

const (
	StopDetails StopKind = iota + 1
	StopTabs
	StopTree
)

func (StopKind) String added in v0.4.0

func (k StopKind) String() string

type Stream

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

Stream renders one document that grows at its end. Blocks before the last two are rendered once and cached; the block before the last is rendered when its source changes (the splitter can still reopen it); the last block (the tail) is rendered on every call. Both first remove terminal controls (Sanitize). Frame heals the tail; Final does not. The result equals r.Render of the whole sanitized (and healed, for Frame) text, with one exception (Carl B1): while a code fence of more than codestream.ChunkLines lines grows in the tail, Frame renders it in chunks, and SGR bytes (colours at a chunk seam) can differ; lines and their text do not. Final is exact. A growing table tail renders from cached rows, exact, when the Renderer tells its options (GlamourOptions).

With a StateRenderer, every block renders in the State given to SetState (Final equals RenderState of the whole sanitized text), and Marks returns where the interactive parts of the last output are.

A Stream is not safe for concurrent use.

func NewStream

func NewStream(r Renderer, key string, c *Cache) *Stream

NewStream returns a Stream that renders with r and caches stable blocks in c under key. key must name everything that changes r's output (dun: width and style). c may be shared by Streams; nil means no cache.

func (*Stream) Final

func (s *Stream) Final(src string) (string, error)

Final renders src as written, with no healing; only Sanitize applies.

func (*Stream) Frame

func (s *Stream) Frame(src string) (string, error)

Frame renders src while it is still streaming: the tail is healed (heal.Close), so half-written syntax does not show raw.

func (*Stream) Marks added in v0.4.0

func (s *Stream) Marks() []Mark

Marks returns the marks of the last Frame or Final: where each interactive part is drawn, lines counted in that output. nil when the Renderer is not a StateRenderer, or the last call failed.

func (*Stream) SetState added in v0.4.0

func (s *Stream) SetState(st *State)

SetState sets the State the next Frame and Final draw forms in (nil: as streamed). It is read on every call, so a change to *st shows on the next call; blocks whose part of the State did not change still come from the cache. It has an effect only when the Renderer is a StateRenderer.

type TabsState added in v0.4.0

type TabsState = directive.TabsState

TabsState, DetailsState and TreeState are the state of one form.

type TreeState added in v0.4.0

type TreeState = directive.TreeState

TabsState, DetailsState and TreeState are the state of one form.

Directories

Path Synopsis
cmd
demo command
Command demo streams one Markdown reply into two panes side by side: bubbleteandmd on the left, glamour rendering the whole reply per token on the right, with render cost, lag, bounce and safety numbers per pane.
Command demo streams one Markdown reply into two panes side by side: bubbleteandmd on the left, glamour rendering the whole reply per token on the right, with render cost, lag, bounce and safety numbers per pane.
internal
blocks
Package blocks cuts a Markdown document into top-level blocks that can be rendered, and cached, one by one.
Package blocks cuts a Markdown document into top-level blocks that can be rendered, and cached, one by one.
codestream
Package codestream renders one growing fenced code block in chunks, so a frame does not re-render (and re-lex) the whole block.
Package codestream renders one growing fenced code block in chunks, so a frame does not re-render (and re-lex) the whole block.
corpus
Package corpus holds the streaming test corpora, the seeded reply generator and the chunking helpers ported from llx-markdown, for invariant tests.
Package corpus holds the streaming test corpora, the seeded reply generator and the chunking helpers ported from llx-markdown, for invariant tests.
diffview
Package diffview reads the lines of a ```diff fence: what each line is (added, removed, hunk header, file header, context), which removed and added lines pair up, and which words of a pair changed.
Package diffview reads the lines of a ```diff fence: what each line is (added, removed, hunk header, file header, context), which removed and added lines pair up, and which words of a pair changed.
directive
Package directive reads llx-markdown's directive forms out of one top-level block: callouts, steps, tabs, stats, pros and cons, folded sections, sources, quotes with a source, file trees and titled code fences.
Package directive reads llx-markdown's directive forms out of one top-level block: callouts, steps, tabs, stats, pros and cons, folded sections, sources, quotes with a source, file trees and titled code fences.
heal
Package heal stabilizes the tail of a markdown text that is still streaming.
Package heal stabilizes the tail of a markdown text that is still streaming.
join
Package join puts separately rendered glamour blocks together so that the result equals glamour's render of the whole document.
Package join puts separately rendered glamour blocks together so that the result equals glamour's render of the whole document.
lextimeout
Package lextimeout stops regexp2's match timeouts in the process.
Package lextimeout stops regexp2's match timeouts in the process.
lru
Package lru is a small bounded least-recently-used cache.
Package lru is a small bounded least-recently-used cache.
nest
Package nest caps how deep Markdown nests, as flowdown (llx-markdown v0.5.0, engine/blocks.ts and engine/inlines.ts MAX_NESTING) does: a line that would open a block deeper than Blocks is read as text of the block it is in; an emphasis, strikethrough, link or image deeper than Inlines is kept as its text.
Package nest caps how deep Markdown nests, as flowdown (llx-markdown v0.5.0, engine/blocks.ts and engine/inlines.ts MAX_NESTING) does: a line that would open a block deeper than Blocks is read as text of the block it is in; an emphasis, strikethrough, link or image deeper than Inlines is kept as its text.
tablestream
Package tablestream renders a growing GFM table block in less time than a whole render, with the same bytes.
Package tablestream renders a growing GFM table block in less time than a whole render, with the same bytes.
Package prompt holds the instructions that teach a model to write the directive forms the renderer draws.
Package prompt holds the instructions that teach a model to write the directive forms the renderer draws.

Jump to

Keyboard shortcuts

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