mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
Refactor VT implementation
This commit refactors the old VT implementation to work with the revised TTY interface and adds support for: - scrollback - terminal state handling When a terminal becomes activated, it overwrites the attached console contents with the contents of its viewport.
This commit is contained in:
@@ -1,128 +0,0 @@
|
||||
package tty
|
||||
|
||||
import "gopheros/kernel/driver/video/console"
|
||||
|
||||
const (
|
||||
defaultFg = console.LightGrey
|
||||
defaultBg = console.Black
|
||||
tabWidth = 4
|
||||
)
|
||||
|
||||
// Vt implements a simple terminal that can process LF and CR characters. The
|
||||
// terminal uses a console device for its output.
|
||||
type Vt struct {
|
||||
// Go interfaces will not work before we can get memory allocation working.
|
||||
// Till then we need to use concrete types instead.
|
||||
cons *console.Ega
|
||||
|
||||
width uint16
|
||||
height uint16
|
||||
|
||||
curX uint16
|
||||
curY uint16
|
||||
curAttr console.Attr
|
||||
}
|
||||
|
||||
// AttachTo links the terminal with the specified console device and updates
|
||||
// the terminal's dimensions to match the ones reported by the attached device.
|
||||
func (t *Vt) AttachTo(cons *console.Ega) {
|
||||
t.cons = cons
|
||||
t.width, t.height = cons.Dimensions()
|
||||
t.curX = 0
|
||||
t.curY = 0
|
||||
|
||||
// Default to lightgrey on black text.
|
||||
t.curAttr = makeAttr(defaultFg, defaultBg)
|
||||
}
|
||||
|
||||
// Clear clears the terminal.
|
||||
func (t *Vt) Clear() {
|
||||
t.clear()
|
||||
}
|
||||
|
||||
// Position returns the current cursor position (x, y).
|
||||
func (t *Vt) Position() (uint16, uint16) {
|
||||
return t.curX, t.curY
|
||||
}
|
||||
|
||||
// SetPosition sets the current cursor position to (x,y).
|
||||
func (t *Vt) SetPosition(x, y uint16) {
|
||||
if x >= t.width {
|
||||
x = t.width - 1
|
||||
}
|
||||
|
||||
if y >= t.height {
|
||||
y = t.height - 1
|
||||
}
|
||||
|
||||
t.curX, t.curY = x, y
|
||||
}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (t *Vt) Write(data []byte) (int, error) {
|
||||
for _, b := range data {
|
||||
t.WriteByte(b)
|
||||
}
|
||||
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
// WriteByte implements io.ByteWriter.
|
||||
func (t *Vt) WriteByte(b byte) error {
|
||||
switch b {
|
||||
case '\r':
|
||||
t.cr()
|
||||
case '\n':
|
||||
t.cr()
|
||||
t.lf()
|
||||
case '\b':
|
||||
if t.curX > 0 {
|
||||
t.cons.Write(' ', t.curAttr, t.curX, t.curY)
|
||||
t.curX--
|
||||
}
|
||||
case '\t':
|
||||
for i := 0; i < tabWidth; i++ {
|
||||
t.cons.Write(' ', t.curAttr, t.curX, t.curY)
|
||||
t.curX++
|
||||
if t.curX == t.width {
|
||||
t.cr()
|
||||
t.lf()
|
||||
}
|
||||
}
|
||||
default:
|
||||
t.cons.Write(b, t.curAttr, t.curX, t.curY)
|
||||
t.curX++
|
||||
if t.curX == t.width {
|
||||
t.cr()
|
||||
t.lf()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// cls clears the terminal.
|
||||
func (t *Vt) clear() {
|
||||
t.cons.Clear(0, 0, t.width, t.height)
|
||||
}
|
||||
|
||||
// cr resets the x coordinate of the terminal cursor to 0.
|
||||
func (t *Vt) cr() {
|
||||
t.curX = 0
|
||||
}
|
||||
|
||||
// lf advances the y coordinate of the terminal cursor by one line scrolling
|
||||
// the terminal contents if the end of the last terminal line is reached.
|
||||
func (t *Vt) lf() {
|
||||
if t.curY+1 < t.height {
|
||||
t.curY++
|
||||
return
|
||||
}
|
||||
|
||||
t.cons.Scroll(console.Up, 1)
|
||||
t.cons.Clear(0, t.height-1, t.width, 1)
|
||||
}
|
||||
|
||||
func makeAttr(fg, bg console.Attr) console.Attr {
|
||||
return (bg << 4) | (fg & 0xF)
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package tty
|
||||
|
||||
import (
|
||||
"gopheros/kernel/driver/video/console"
|
||||
"testing"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func TestVtPosition(t *testing.T) {
|
||||
specs := []struct {
|
||||
inX, inY uint16
|
||||
expX, expY uint16
|
||||
}{
|
||||
{20, 20, 20, 20},
|
||||
{100, 20, 79, 20},
|
||||
{10, 200, 10, 24},
|
||||
{10, 200, 10, 24},
|
||||
{100, 100, 79, 24},
|
||||
}
|
||||
|
||||
fb := make([]uint16, 80*25)
|
||||
var cons console.Ega
|
||||
cons.Init(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
||||
|
||||
var vt Vt
|
||||
vt.AttachTo(&cons)
|
||||
|
||||
for specIndex, spec := range specs {
|
||||
vt.SetPosition(spec.inX, spec.inY)
|
||||
if x, y := vt.Position(); x != spec.expX || y != spec.expY {
|
||||
t.Errorf("[spec %d] expected setting position to (%d, %d) to update the position to (%d, %d); got (%d, %d)", specIndex, spec.inX, spec.inY, spec.expX, spec.expY, x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite(t *testing.T) {
|
||||
fb := make([]uint16, 80*25)
|
||||
var cons console.Ega
|
||||
cons.Init(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
||||
|
||||
var vt Vt
|
||||
vt.AttachTo(&cons)
|
||||
|
||||
vt.Clear()
|
||||
vt.SetPosition(0, 1)
|
||||
vt.Write([]byte("12\n\t3\n4\r567\b8"))
|
||||
|
||||
// Tab spanning rows
|
||||
vt.SetPosition(78, 4)
|
||||
vt.WriteByte('\t')
|
||||
vt.WriteByte('9')
|
||||
|
||||
// Trigger scroll
|
||||
vt.SetPosition(79, 24)
|
||||
vt.Write([]byte{'!'})
|
||||
|
||||
specs := []struct {
|
||||
x, y uint16
|
||||
expChar byte
|
||||
}{
|
||||
{0, 0, '1'},
|
||||
{1, 0, '2'},
|
||||
// tabs
|
||||
{0, 1, ' '},
|
||||
{1, 1, ' '},
|
||||
{2, 1, ' '},
|
||||
{3, 1, ' '},
|
||||
{4, 1, '3'},
|
||||
// tab spanning 2 rows
|
||||
{78, 3, ' '},
|
||||
{79, 3, ' '},
|
||||
{0, 4, ' '},
|
||||
{1, 4, ' '},
|
||||
{2, 4, '9'},
|
||||
//
|
||||
{0, 2, '5'},
|
||||
{1, 2, '6'},
|
||||
{2, 2, '8'}, // overwritten by BS
|
||||
{79, 23, '!'},
|
||||
}
|
||||
|
||||
for specIndex, spec := range specs {
|
||||
ch := (byte)(fb[(spec.y*vt.width)+spec.x] & 0xFF)
|
||||
if ch != spec.expChar {
|
||||
t.Errorf("[spec %d] expected char at (%d, %d) to be %c; got %c", specIndex, spec.x, spec.y, spec.expChar, ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user