turbo-editors/turbo-corepublic Fork 0
28d59854361aeda8541d853093e732126f3d7bff
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

🛟 Updated. 28d5985 · on 28d59854361aeda8541d853093e732126f3d7bff · k33g · 20h ago
screen_write.go · 155 lines · 4.2 KBGo Blame HistoryRaw
  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

// WriteRune puts one character at the cursor and moves on.
//
// The cursor stops in the last column rather than moving past it: only the
// next character written wraps to the following line. That is what stops a
// character in the bottom-right corner from scrolling the screen by itself,
// which is the difference between a terminal that redraws correctly and one
// that creeps upwards.
func (s *Screen) WriteRune(r rune) {
	if s.wrapPending && s.autoWrap {
		s.CarriageReturn()
		s.LineFeed()
	}
	s.wrapPending = false

	s.lines[s.cursor.Row][s.cursor.Col] = Cell{Rune: r, Style: s.style}

	if s.cursor.Col == s.width-1 {
		s.wrapPending = true
		return
	}
	s.cursor.Col++
}

// MoveTo puts the cursor at a position, clamped to the screen.
func (s *Screen) MoveTo(row, col int) {
	s.cursor.Row = min(max(row, 0), s.height-1)
	s.cursor.Col = min(max(col, 0), s.width-1)
	s.wrapPending = false
}

// MoveBy moves the cursor relative to where it is, clamped to the screen.
func (s *Screen) MoveBy(rows, cols int) {
	s.MoveTo(s.cursor.Row+rows, s.cursor.Col+cols)
}

// CarriageReturn takes the cursor to the first column of its line.
func (s *Screen) CarriageReturn() {
	s.cursor.Col = 0
	s.wrapPending = false
}

// LineFeed moves the cursor down a line, scrolling the region when it is
// already on the last row of it.
func (s *Screen) LineFeed() {
	s.wrapPending = false

	if s.cursor.Row == s.bottom {
		s.ScrollUp(1)
		return
	}
	if s.cursor.Row < s.height-1 {
		s.cursor.Row++
	}
}

// ReverseLineFeed moves the cursor up a line, scrolling the region down when
// it is already on the first row of it.
func (s *Screen) ReverseLineFeed() {
	s.wrapPending = false

	if s.cursor.Row == s.top {
		s.ScrollDown(1)
		return
	}
	if s.cursor.Row > 0 {
		s.cursor.Row--
	}
}

// Backspace moves the cursor one column left, stopping at the first column.
// It does not erase: a program that wants the character gone writes a space.
func (s *Screen) Backspace() {
	s.wrapPending = false
	if s.cursor.Col > 0 {
		s.cursor.Col--
	}
}

// TabWidth is how far apart the default tab stops are.
const TabWidth = 8

// Tab moves the cursor to the next tab stop, or to the last column when there
// is no further stop on the line.
func (s *Screen) Tab() {
	s.wrapPending = false
	next := (s.cursor.Col/TabWidth + 1) * TabWidth
	s.cursor.Col = min(next, s.width-1)
}

// SaveCursor remembers the cursor and the current style, as DECSC does.
func (s *Screen) SaveCursor() {
	s.savedCursor, s.savedStyle = s.cursor, s.style
}

// RestoreCursor puts back what SaveCursor remembered, as DECRC does.
func (s *Screen) RestoreCursor() {
	s.cursor, s.style = s.savedCursor, s.savedStyle
	s.cursor.Row = min(s.cursor.Row, s.height-1)
	s.cursor.Col = min(s.cursor.Col, s.width-1)
	s.wrapPending = false
}

// SetScrollRegion narrows the rows that scroll to the inclusive range
// [top, bottom], and puts the cursor at the top left as DECSTBM does.
//
// A region that is empty or the wrong way round is ignored, which is what a
// terminal does with a malformed request rather than corrupting its own state.
func (s *Screen) SetScrollRegion(top, bottom int) {
	top = min(max(top, 0), s.height-1)
	bottom = min(max(bottom, 0), s.height-1)
	if top >= bottom {
		return
	}

	s.top, s.bottom = top, bottom
	s.MoveTo(0, 0)
}

// ScrollUp moves the scrolling region up by n lines, blanking the lines that
// appear at the bottom. Lines leaving the top of a full-height region are kept
// in the scrollback.
func (s *Screen) ScrollUp(n int) {
	rows := s.bottom - s.top + 1
	n = min(max(n, 0), rows)
	if n == 0 {
		return
	}

	for i := range n {
		s.remember(s.lines[s.top+i])
	}

	copy(s.lines[s.top:s.bottom+1-n], s.lines[s.top+n:s.bottom+1])
	for row := s.bottom + 1 - n; row <= s.bottom; row++ {
		s.lines[row] = s.blankLine()
	}
}

// ScrollDown moves the scrolling region down by n lines, blanking the lines
// that appear at the top. Nothing is remembered: these lines are not history,
// they are being pushed back down.
func (s *Screen) ScrollDown(n int) {
	rows := s.bottom - s.top + 1
	n = min(max(n, 0), rows)
	if n == 0 {
		return
	}

	copy(s.lines[s.top+n:s.bottom+1], s.lines[s.top:s.bottom+1-n])
	for row := s.top; row < s.top+n; row++ {
		s.lines[row] = s.blankLine()
	}
}