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 }