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
|
package terminal
import (
"github.com/gdamore/tcell/v2"
"rickub.com/turbo-editors/turbo-core/theme"
"rickub.com/turbo-editors/turbo-core/ui"
)
// Draw paints the terminal's grid.
func (v *View) Draw(p *ui.Painter, th *theme.Theme) {
v.mu.Lock()
defer v.mu.Unlock()
area := p.Size()
base := th.Style(theme.KeyTerminalText)
p.Fill(area, ' ', base)
for row := range area.H {
v.drawLine(p, v.lineAt(row), row, area.W, base)
}
v.drawCursor(p, th)
}
// drawLine paints one row of the grid.
func (v *View) drawLine(p *ui.Painter, line Line, row, width int, base tcell.Style) {
for col, cell := range line {
if col >= width {
return
}
p.SetCell(col, row, cell.Rune, resolve(cell.Style, base))
}
}
// resolve fills a cell's unset colours in from the theme.
//
// A program that has said nothing about colour gets the theme's, which is what
// lets a terminal window look like the rest of the editor instead of like the
// terminal's own defaults.
func resolve(style, base tcell.Style) tcell.Style {
foreground, background, _ := style.Decompose()
baseForeground, baseBackground, _ := base.Decompose()
if foreground == tcell.ColorDefault {
style = style.Foreground(baseForeground)
}
if background == tcell.ColorDefault {
style = style.Background(baseBackground)
}
return style
}
// drawCursor marks where the shell's cursor is.
//
// It is drawn only on the live screen: a cursor shown while the user is
// reading back through the history would point at a line the shell is not
// writing to.
func (v *View) drawCursor(p *ui.Painter, th *theme.Theme) {
screen := v.parser.Screen()
if v.scrollOffset != 0 || !screen.CursorVisible() || !v.Focused() {
return
}
cursor := screen.Cursor()
character, _ := p.CellAt(cursor.Col, cursor.Row)
p.SetCell(cursor.Col, cursor.Row, character, th.Style(theme.KeyTerminalCursor))
p.ShowCursor(cursor.Col, cursor.Row)
}
// lineAt returns the line to show on a visible row, which comes from the
// history when the view has been scrolled back.
//
// It must be called with the lock held.
func (v *View) lineAt(row int) Line {
screen := v.parser.Screen()
index := screen.ScrollbackLen() - v.scrollOffset + row
if index < screen.ScrollbackLen() {
return screen.ScrollbackLine(index)
}
return screen.Line(index - screen.ScrollbackLen())
}
// ScrollOffset returns how many lines back into the history the view is
// looking. Zero is the live screen.
func (v *View) ScrollOffset() int {
v.mu.Lock()
defer v.mu.Unlock()
return v.scrollOffset
}
// ScrollBy moves the view back through the history, or forwards towards the
// live screen with a negative count. It stops at either end.
func (v *View) ScrollBy(lines int) {
v.mu.Lock()
defer v.mu.Unlock()
v.scrollOffset = min(max(v.scrollOffset+lines, 0), v.parser.Screen().ScrollbackLen())
}
// ScrollToBottom goes back to the live screen, which is where any key press
// puts the view.
func (v *View) ScrollToBottom() {
v.mu.Lock()
defer v.mu.Unlock()
v.scrollOffset = 0
}
|