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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package terminal

import "avt-go/types"

// Snapshot is a comparable dump of all terminal state used in equality checks.
type Snapshot struct {
	ActiveBufferType  BufferType
	Cursor            Cursor
	Pen               types.Pen
	Charsets          [2]types.Charset
	ActiveCharset     int
	Tabs              types.Tabs
	InsertMode        bool
	OriginMode        bool
	AutoWrapMode      bool
	NewLineMode       bool
	CursorKeysAppMode bool
	TopMargin         int
	BottomMargin      int
	SavedCtx          SavedCtxSnapshot
	AltSavedCtx       SavedCtxSnapshot
	// PrimaryView is the visible lines of the primary buffer (text + wrapped flag).
	PrimaryView []LineSnapshot
	// AltView is set only when alternate buffer is active.
	AltView []LineSnapshot
}

type SavedCtxSnapshot struct {
	CursorCol    int
	CursorRow    int
	Pen          types.Pen
	OriginMode   bool
	AutoWrapMode bool
}

type LineSnapshot struct {
	Text    string
	Wrapped bool
}

func (t *Terminal) Snapshot() Snapshot {
	var primaryCtx, altCtx *savedCtx
	if t.activeBufferType == BufferTypePrimary {
		primaryCtx = &t.savedCtx
		altCtx = &t.alternateSavedCtx
	} else {
		primaryCtx = &t.alternateSavedCtx
		altCtx = &t.savedCtx
	}

	snap := Snapshot{
		ActiveBufferType:  t.activeBufferType,
		Cursor:            t.cursor,
		Pen:               t.pen,
		Charsets:          t.charsets,
		ActiveCharset:     t.activeCharset,
		Tabs:              t.tabs,
		InsertMode:        t.insertMode,
		OriginMode:        t.originMode,
		AutoWrapMode:      t.autoWrapMode,
		NewLineMode:       t.newLineMode,
		CursorKeysAppMode: t.cursorKeysMode == cursorKeysModeApplication,
		TopMargin:         t.topMargin,
		BottomMargin:      t.bottomMargin,
		SavedCtx:          snapshotCtx(primaryCtx),
		AltSavedCtx:       snapshotCtx(altCtx),
	}

	for _, l := range t.primaryBuffer().View() {
		snap.PrimaryView = append(snap.PrimaryView, LineSnapshot{Text: l.Text(), Wrapped: l.Wrapped})
	}

	if t.activeBufferType == BufferTypeAlternate {
		for _, l := range t.alternateBuffer().View() {
			snap.AltView = append(snap.AltView, LineSnapshot{Text: l.Text(), Wrapped: l.Wrapped})
		}
	}

	return snap
}

// Verify checks internal consistency invariants (mirrors Rust's terminal.verify()).
func (t *Terminal) Verify() []string {
	var errs []string

	if t.cursor.Row >= t.rows {
		errs = append(errs, "cursor.Row >= rows")
	}
	if t.cursor.Col > t.cols {
		errs = append(errs, "cursor.Col > cols")
	}

	for i, l := range t.buffer.Lines() {
		if l.Len() != t.cols {
			errs = append(errs, "line "+itoa(i)+" len "+itoa(l.Len())+" != cols "+itoa(t.cols))
		}
	}

	lines := t.buffer.Lines()
	if len(lines) > 0 && lines[len(lines)-1].Wrapped {
		errs = append(errs, "last line is wrapped")
	}

	for _, l := range t.buffer.Lines() {
		for i := 0; i < t.cols; i++ {
			c := l.At(i)
			if c.Occupancy() == types.OccupancyWideTail {
				if i == 0 {
					errs = append(errs, "WideTail at col 0")
				} else if l.At(i-1).Occupancy() != types.OccupancyWideHead {
					errs = append(errs, "WideTail not preceded by WideHead at col "+itoa(i))
				}
			} else if c.Occupancy() == types.OccupancyWideHead {
				if i+1 >= t.cols || l.At(i+1).Occupancy() != types.OccupancyWideTail {
					errs = append(errs, "WideHead not followed by WideTail at col "+itoa(i))
				}
			}
		}
	}

	return errs
}

func snapshotCtx(c *savedCtx) SavedCtxSnapshot {
	return SavedCtxSnapshot{
		CursorCol:    c.cursorCol,
		CursorRow:    c.cursorRow,
		Pen:          c.pen,
		OriginMode:   c.originMode,
		AutoWrapMode: c.autoWrapMode,
	}
}

func itoa(n int) string {
	if n == 0 {
		return "0"
	}
	neg := false
	if n < 0 {
		neg = true
		n = -n
	}
	buf := [20]byte{}
	i := len(buf)
	for n > 0 {
		i--
		buf[i] = byte('0' + n%10)
		n /= 10
	}
	if neg {
		i--
		buf[i] = '-'
	}
	return string(buf[i:])
}