1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package avt

import (
	"avt-go/parser"
	"avt-go/terminal"
)

// Vt is the public virtual terminal API.
type Vt struct {
	parser   *parser.Parser
	terminal *terminal.Terminal
}

// Builder builds a Vt with optional configuration.
type Builder struct {
	cols            int
	rows            int
	scrollbackLimit *int
}

func NewBuilder() *Builder {
	return &Builder{cols: 80, rows: 24}
}

func (b *Builder) Size(cols, rows int) *Builder {
	b.cols = cols
	b.rows = rows
	return b
}

func (b *Builder) ScrollbackLimit(limit int) *Builder {
	b.scrollbackLimit = &limit
	return b
}

func (b *Builder) Build() *Vt {
	return &Vt{
		parser:   parser.New(),
		terminal: terminal.New(b.cols, b.rows, b.scrollbackLimit),
	}
}

// New creates a Vt with the given dimensions and unlimited scrollback.
func New(cols, rows int) *Vt {
	return NewBuilder().Size(cols, rows).Build()
}

// Changes holds what changed after a FeedStr or Resize call.
type Changes struct {
	Lines      []int
	Scrollback []Line
}

// FeedStr feeds a string into the terminal and returns what changed.
func (v *Vt) FeedStr(s string) Changes {
	for _, ch := range s {
		if fun := v.parser.Feed(ch); fun != nil {
			v.terminal.Execute(fun)
		}
	}

	lines := v.terminal.Changes()
	scrollback := v.terminal.GC()

	return Changes{Lines: lines, Scrollback: scrollback}
}

// Feed feeds a single rune into the terminal.
func (v *Vt) Feed(ch rune) {
	if fun := v.parser.Feed(ch); fun != nil {
		v.terminal.Execute(fun)
	}
}

// Size returns (cols, rows).
func (v *Vt) Size() (int, int) { return v.terminal.Size() }

// Resize resizes the terminal and returns what changed.
func (v *Vt) Resize(cols, rows int) Changes {
	v.terminal.Resize(cols, rows)

	lines := v.terminal.Changes()
	scrollback := v.terminal.GC()

	return Changes{Lines: lines, Scrollback: scrollback}
}

// View returns the visible lines (rows in viewport).
func (v *Vt) View() []*Line { return v.terminal.View() }

// Lines returns all lines including scrollback.
func (v *Vt) Lines() []*Line { return v.terminal.Lines() }

// Line returns the visual line at index n.
func (v *Vt) Line(n int) *Line { return v.terminal.Line(n) }

// Text returns the text content of the primary buffer.
func (v *Vt) Text() []string { return v.terminal.Text() }

// Cursor returns the current cursor state.
func (v *Vt) Cursor() terminal.Cursor { return v.terminal.Cursor() }

// CursorKeyAppMode returns whether cursor keys are in application mode.
func (v *Vt) CursorKeyAppMode() bool { return v.terminal.CursorKeysAppMode() }

// Snapshot returns a comparable snapshot of all terminal + parser state.
func (v *Vt) Snapshot() VtSnapshot {
	return VtSnapshot{
		Terminal: v.terminal.Snapshot(),
		Parser:   v.parser.Snapshot(),
	}
}

// Verify checks internal consistency invariants. Returns a list of violation
// strings; empty means the terminal is in a valid state.
func (v *Vt) Verify() []string {
	return v.terminal.Verify()
}

// VtSnapshot holds a comparable snapshot of Vt state.
type VtSnapshot struct {
	Terminal terminal.Snapshot
	Parser   parser.ParserSnapshot
}

// Dump serialises the terminal state to a string that can be replayed.
func (v *Vt) Dump() string {
	funs := v.terminal.Dump()
	seq := parser.DumpFunctions(funs)
	seq += v.parser.Dump()
	return seq
}