package html
import (
"encoding/json"
"fmt"
"github.com/bnema/vev-vt/core"
)
// UpdateSchemaVersion identifies the browser update contract.
const UpdateSchemaVersion uint16 = 2
// CursorStyle is a DECSCUSR cursor shape value from 0 through 6.
type CursorStyle uint8
// Cursor is the absolute cursor state associated with an update. StyleSet
// distinguishes an unset style from an explicit DECSCUSR style zero.
type Cursor struct {
Row int `json:"row"`
Column int `json:"column"`
Visible bool `json:"visible"`
Style CursorStyle `json:"style"`
StyleSet bool `json:"styleSet"`
}
// ColorKind identifies how a terminal color is resolved.
type ColorKind uint8
const (
ColorDefault ColorKind = iota
ColorIndexed
ColorRGB
)
// RGB is a browser-facing RGB color.
type RGB struct {
R uint8 `json:"r"`
G uint8 `json:"g"`
B uint8 `json:"b"`
}
// Color is a validated terminal color token.
type Color struct {
Kind ColorKind
Index uint8
RGB RGB
}
func (color Color) MarshalJSON() ([]byte, error) {
switch color.Kind {
case ColorDefault:
return []byte(`{"kind":0}`), nil
case ColorIndexed:
return json.Marshal(struct {
Kind ColorKind `json:"kind"`
Index uint8 `json:"index"`
}{Kind: color.Kind, Index: color.Index})
case ColorRGB:
return json.Marshal(struct {
Kind ColorKind `json:"kind"`
RGB RGB `json:"rgb"`
}{Kind: color.Kind, RGB: color.RGB})
default:
return nil, fmt.Errorf("html: invalid color kind %d", color.Kind)
}
}
// Style is the canonical, browser-facing subset of core.Style.
type Style struct {
Bold bool `json:"bold,omitempty"`
Italic bool `json:"italic,omitempty"`
Inverse bool `json:"inverse,omitempty"`
Dim bool `json:"dim,omitempty"`
Blink bool `json:"blink,omitempty"`
Strikethrough bool `json:"strikethrough,omitempty"`
Underline bool `json:"underline,omitempty"`
UnderlineStyle core.UnderlineStyle `json:"underlineStyle,omitempty"`
Foreground Color `json:"foreground"`
Background Color `json:"background"`
UnderlineColor Color `json:"underlineColor"`
}
// CellUpdate occupies Width terminal columns beginning at Column. It is
// either a text run of Width printable ASCII characters (blank cells are
// spaces) sharing one style, or one non-ASCII character of width 1 or 2.
// Wide-cell continuation markers are represented by the preceding cell's
// width. Text values of one update may share backing storage, so retaining
// one keeps that update's text alive.
type CellUpdate struct {
Column int `json:"column"`
Width int `json:"width"`
Text string `json:"text"`
Style int `json:"style"`
}
// RowUpdate replaces one complete logical terminal row.
type RowUpdate struct {
Row int `json:"row"`
Cells []CellUpdate `json:"cells"`
}
// Update is an immutable-by-contract browser update. Snapshot updates contain
// every logical row; incremental updates contain complete changed rows.
type Update struct {
SchemaVersion uint16 `json:"schemaVersion"`
Width int `json:"width"`
Height int `json:"height"`
Snapshot bool `json:"snapshot"`
Rows []RowUpdate `json:"rows"`
Styles []Style `json:"styles"`
Cursor Cursor `json:"cursor"`
}
func cloneUpdate(src Update) Update {
dst := src
dst.Styles = make([]Style, len(src.Styles))
copy(dst.Styles, src.Styles)
dst.Rows = make([]RowUpdate, len(src.Rows))
for i := range src.Rows {
dst.Rows[i] = src.Rows[i]
dst.Rows[i].Cells = append([]CellUpdate(nil), src.Rows[i].Cells...)
}
return dst
}