turbo-editors/turbo-corepublic Fork 0
v1.0.2
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.

screen_edit.go · 195 lines · 5.7 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 15h ago1package terminal
2
3import "github.com/gdamore/tcell/v2"
4
5// EraseMode says how much of the display or the line an erase covers.
6type EraseMode int
7
8// The three erase modes the protocol defines.
9const (
10 // EraseToEnd clears from the cursor to the end, cursor included.
11 EraseToEnd EraseMode = iota
12 // EraseToStart clears from the start to the cursor, cursor included.
13 EraseToStart
14 // EraseAll clears the whole display or line.
15 EraseAll
16)
17
18// EraseDisplay clears part or all of the screen, as ED does. The cursor does
19// not move: a program that wants it elsewhere says so separately.
20func (s *Screen) EraseDisplay(mode EraseMode) {
21 switch mode {
22 case EraseToEnd:
23 s.eraseLineRange(s.cursor.Row, s.cursor.Col, s.width-1)
24 s.blankRows(s.cursor.Row+1, s.height-1)
25 case EraseToStart:
26 s.blankRows(0, s.cursor.Row-1)
27 s.eraseLineRange(s.cursor.Row, 0, s.cursor.Col)
28 case EraseAll:
29 s.blankRows(0, s.height-1)
30 }
31 s.wrapPending = false
32}
33
34// EraseLine clears part or all of the cursor's line, as EL does.
35func (s *Screen) EraseLine(mode EraseMode) {
36 switch mode {
37 case EraseToEnd:
38 s.eraseLineRange(s.cursor.Row, s.cursor.Col, s.width-1)
39 case EraseToStart:
40 s.eraseLineRange(s.cursor.Row, 0, s.cursor.Col)
41 case EraseAll:
42 s.eraseLineRange(s.cursor.Row, 0, s.width-1)
43 }
44 s.wrapPending = false
45}
46
47// EraseChars blanks n characters from the cursor rightwards without moving
48// anything along, as ECH does.
49func (s *Screen) EraseChars(n int) {
50 s.eraseLineRange(s.cursor.Row, s.cursor.Col, s.cursor.Col+max(n, 1)-1)
51 s.wrapPending = false
52}
53
54// blankRows clears whole rows, inclusive, ignoring any outside the screen.
55func (s *Screen) blankRows(from, to int) {
56 for row := max(from, 0); row <= min(to, s.height-1); row++ {
57 s.lines[row] = s.blankLine()
58 }
59}
60
61// eraseLineRange blanks the columns [from, to] of a row, inclusive.
62func (s *Screen) eraseLineRange(row, from, to int) {
63 if row < 0 || row >= s.height {
64 return
65 }
66 for col := max(from, 0); col <= min(to, s.width-1); col++ {
67 s.lines[row][col] = blank(s.style)
68 }
69}
70
71// InsertLines opens n blank lines at the cursor's row, pushing the rest of the
72// scrolling region down and off its bottom, as IL does.
73//
74// It does nothing when the cursor is outside the scrolling region, which is
75// what the protocol requires and what stops a program with a status line from
76// pushing that line about.
77func (s *Screen) InsertLines(n int) {
78 if !s.cursorInRegion() {
79 return
80 }
81 s.scrollRegionFrom(s.cursor.Row, max(n, 1), down)
82}
83
84// DeleteLines removes n lines at the cursor's row, pulling the rest of the
85// scrolling region up and blanking its bottom, as DL does.
86func (s *Screen) DeleteLines(n int) {
87 if !s.cursorInRegion() {
88 return
89 }
90 s.scrollRegionFrom(s.cursor.Row, max(n, 1), up)
91}
92
93// direction says which way a partial scroll moves the lines.
94type direction int
95
96const (
97 up direction = iota
98 down
99)
100
101// scrollRegionFrom scrolls the part of the scrolling region that starts at a
102// row, which is what insert-lines and delete-lines both do.
103func (s *Screen) scrollRegionFrom(from, n int, dir direction) {
104 rows := s.bottom - from + 1
105 n = min(n, rows)
106
107 if dir == up {
108 copy(s.lines[from:s.bottom+1-n], s.lines[from+n:s.bottom+1])
109 s.blankRows(s.bottom+1-n, s.bottom)
110 } else {
111 copy(s.lines[from+n:s.bottom+1], s.lines[from:s.bottom+1-n])
112 s.blankRows(from, from+n-1)
113 }
114 s.wrapPending = false
115}
116
117// cursorInRegion reports whether the cursor is inside the scrolling region.
118func (s *Screen) cursorInRegion() bool {
119 return s.cursor.Row >= s.top && s.cursor.Row <= s.bottom
120}
121
122// InsertChars opens n blank cells at the cursor, pushing the rest of the line
123// right and off its end, as ICH does.
124func (s *Screen) InsertChars(n int) {
125 line := s.lines[s.cursor.Row]
126 n = min(max(n, 1), s.width-s.cursor.Col)
127
128 copy(line[s.cursor.Col+n:], line[s.cursor.Col:s.width-n])
129 s.eraseLineRange(s.cursor.Row, s.cursor.Col, s.cursor.Col+n-1)
130 s.wrapPending = false
131}
132
133// DeleteChars removes n cells at the cursor, pulling the rest of the line left
134// and blanking its end, as DCH does.
135func (s *Screen) DeleteChars(n int) {
136 line := s.lines[s.cursor.Row]
137 n = min(max(n, 1), s.width-s.cursor.Col)
138
139 copy(line[s.cursor.Col:s.width-n], line[s.cursor.Col+n:])
140 s.eraseLineRange(s.cursor.Row, s.width-n, s.width-1)
141 s.wrapPending = false
142}
143
144// UseAlternate switches to the alternate screen and back.
145//
146// The alternate screen is a blank scratch surface with no scrollback: a
147// full-screen program asks for it so that whatever the terminal showed before
148// comes back untouched when the program exits.
149func (s *Screen) UseAlternate(alternate bool) {
150 if alternate == s.alternate {
151 return
152 }
153
154 if alternate {
155 s.primaryLines, s.primaryCursor = s.lines, s.cursor
156 s.primaryStyle, s.primaryHistory = s.style, s.scrollback
157
158 s.alternate = true
159 s.lines = s.blankLines(s.height)
160 s.scrollback = nil
161 s.MoveTo(0, 0)
162 s.resetScrollRegion()
163 return
164 }
165
166 s.alternate = false
167 s.lines, s.cursor = s.primaryLines, s.primaryCursor
168 s.style, s.scrollback = s.primaryStyle, s.primaryHistory
169 s.primaryLines, s.primaryHistory = nil, nil
170 s.resetScrollRegion()
171 s.fitCursor()
172}
173
174// Reset puts the screen back to how it started, as RIS does.
175func (s *Screen) Reset() {
176 s.alternate = false
177 s.primaryLines, s.primaryHistory = nil, nil
178
179 s.style = tcell.StyleDefault
180 s.savedStyle = tcell.StyleDefault
181 s.lines = s.blankLines(s.height)
182 s.scrollback = nil
183
184 s.cursor, s.savedCursor = Cursor{}, Cursor{}
185 s.cursorVisible, s.autoWrap, s.wrapPending = true, true, false
186 s.applicationCursor = false
187 s.resetScrollRegion()
188}
189
190// fitCursor pulls the cursor back onto the screen after a resize or a swap.
191func (s *Screen) fitCursor() {
192 s.cursor.Row = min(max(s.cursor.Row, 0), s.height-1)
193 s.cursor.Col = min(max(s.cursor.Col, 0), s.width-1)
194 s.wrapPending = false
195}