mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
Merge pull request #3 from achilleasa/implement-simple-terminal
Implement simple terminal
This commit is contained in:
commit
08142d90f6
18
kernel/driver/tty/tty.go
Normal file
18
kernel/driver/tty/tty.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package tty
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
// Tty is implemented by objects that can register themselves as ttys.
|
||||||
|
type Tty interface {
|
||||||
|
io.Writer
|
||||||
|
|
||||||
|
// Position returns the current cursor position (x, y).
|
||||||
|
Position() (uint16, uint16)
|
||||||
|
|
||||||
|
// SetPosition sets the current cursor position to (x,y). Console implementations
|
||||||
|
// must clip the provided cursor position if it exceeds the console dimensions.
|
||||||
|
SetPosition(x, y uint16)
|
||||||
|
|
||||||
|
// Clear clears the terminal.
|
||||||
|
Clear()
|
||||||
|
}
|
118
kernel/driver/tty/vt.go
Normal file
118
kernel/driver/tty/vt.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package tty
|
||||||
|
|
||||||
|
import "github.com/achilleasa/gopher-os/kernel/driver/video/console"
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultFg = console.LightGrey
|
||||||
|
defaultBg = console.Black
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.Vga
|
||||||
|
|
||||||
|
width uint16
|
||||||
|
height uint16
|
||||||
|
|
||||||
|
curX uint16
|
||||||
|
curY uint16
|
||||||
|
curAttr console.Attr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Vt) Init(cons *console.Vga) {
|
||||||
|
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.cons.Lock()
|
||||||
|
defer t.cons.Unlock()
|
||||||
|
|
||||||
|
t.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position returns the current cursor position (x, y).
|
||||||
|
func (t *Vt) Position() (uint16, uint16) {
|
||||||
|
t.cons.Lock()
|
||||||
|
defer t.cons.Unlock()
|
||||||
|
|
||||||
|
return t.curX, t.curY
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPosition sets the current cursor position to (x,y).
|
||||||
|
func (t *Vt) SetPosition(x, y uint16) {
|
||||||
|
t.cons.Lock()
|
||||||
|
defer t.cons.Unlock()
|
||||||
|
|
||||||
|
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) {
|
||||||
|
t.cons.Lock()
|
||||||
|
defer t.cons.Unlock()
|
||||||
|
|
||||||
|
attr := t.curAttr
|
||||||
|
for _, b := range data {
|
||||||
|
switch b {
|
||||||
|
case '\r':
|
||||||
|
t.cr()
|
||||||
|
case '\n':
|
||||||
|
t.cr()
|
||||||
|
t.lf()
|
||||||
|
default:
|
||||||
|
t.cons.Write(b, attr, t.curX, t.curY)
|
||||||
|
t.curX++
|
||||||
|
if t.curX == t.width {
|
||||||
|
t.lf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(data), 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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeAttr(fg, bg console.Attr) console.Attr {
|
||||||
|
return (bg << 4) | (fg & 0xF)
|
||||||
|
}
|
70
kernel/driver/tty/vt_test.go
Normal file
70
kernel/driver/tty/vt_test.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package tty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/achilleasa/gopher-os/kernel/driver/video/console"
|
||||||
|
)
|
||||||
|
|
||||||
|
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},
|
||||||
|
}
|
||||||
|
|
||||||
|
var cons console.Vga
|
||||||
|
cons.Init()
|
||||||
|
|
||||||
|
var vt Vt
|
||||||
|
vt.Init(&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)
|
||||||
|
cons := &console.Vga{}
|
||||||
|
cons.OverrideFb(fb)
|
||||||
|
cons.Init()
|
||||||
|
|
||||||
|
var vt Vt
|
||||||
|
vt.Init(cons)
|
||||||
|
|
||||||
|
vt.Clear()
|
||||||
|
vt.SetPosition(0, 1)
|
||||||
|
vt.Write([]byte("12\n3\n4\r56"))
|
||||||
|
|
||||||
|
// Trigger scroll
|
||||||
|
vt.SetPosition(79, 24)
|
||||||
|
vt.Write([]byte{'!'})
|
||||||
|
|
||||||
|
specs := []struct {
|
||||||
|
x, y uint16
|
||||||
|
expChar byte
|
||||||
|
}{
|
||||||
|
{0, 0, '1'},
|
||||||
|
{1, 0, '2'},
|
||||||
|
{0, 1, '3'},
|
||||||
|
{0, 2, '5'},
|
||||||
|
{1, 2, '6'},
|
||||||
|
{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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -42,6 +42,13 @@ func (cons *Vga) Init() {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OverrideFb overrides the console framebuffer slice with the supplied slice.
|
||||||
|
// This is a temporary function used by tests that will be removed once we can work
|
||||||
|
// with interfaces.
|
||||||
|
func (cons *Vga) OverrideFb(fb []uint16) {
|
||||||
|
cons.fb = fb
|
||||||
|
}
|
||||||
|
|
||||||
// Clear clears the specified rectangular region
|
// Clear clears the specified rectangular region
|
||||||
func (cons *Vga) Clear(x, y, width, height uint16) {
|
func (cons *Vga) Clear(x, y, width, height uint16) {
|
||||||
var (
|
var (
|
||||||
|
@ -202,3 +202,15 @@ func TestVgaWrite(t *testing.T) {
|
|||||||
t.Errorf("expected call to Write() to set fb[0] to %d; got %d", expVal, got)
|
t.Errorf("expected call to Write() to set fb[0] to %d; got %d", expVal, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVgaOverrideFb(t *testing.T) {
|
||||||
|
var cons = Vga{}
|
||||||
|
cons.Init()
|
||||||
|
|
||||||
|
fb := []uint16{}
|
||||||
|
cons.OverrideFb(fb)
|
||||||
|
|
||||||
|
if len(cons.fb) != len(fb) {
|
||||||
|
t.Fatalf("expected calling OverrideFb to change the framebuffer for the console")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user