diff --git a/kernel/driver/tty/tty.go b/kernel/driver/tty/tty.go index 2f74a7c..f46ce2f 100644 --- a/kernel/driver/tty/tty.go +++ b/kernel/driver/tty/tty.go @@ -5,6 +5,7 @@ import "io" // Tty is implemented by objects that can register themselves as ttys. type Tty interface { io.Writer + io.ByteWriter // Position returns the current cursor position (x, y). Position() (uint16, uint16) diff --git a/kernel/driver/tty/vt.go b/kernel/driver/tty/vt.go index 8ecd2a5..d831bc1 100644 --- a/kernel/driver/tty/vt.go +++ b/kernel/driver/tty/vt.go @@ -5,6 +5,7 @@ import "github.com/achilleasa/gopher-os/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 @@ -59,26 +60,47 @@ func (t *Vt) SetPosition(x, y uint16) { // Write implements io.Writer. func (t *Vt) Write(data []byte) (int, error) { - 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() - } - } + t.WriteByte(b) } return len(data), nil } +// Write 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) diff --git a/kernel/driver/tty/vt_test.go b/kernel/driver/tty/vt_test.go index 19233b0..844a81f 100644 --- a/kernel/driver/tty/vt_test.go +++ b/kernel/driver/tty/vt_test.go @@ -44,7 +44,12 @@ func TestWrite(t *testing.T) { vt.Clear() vt.SetPosition(0, 1) - vt.Write([]byte("12\n3\n4\r56")) + 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) @@ -56,9 +61,22 @@ func TestWrite(t *testing.T) { }{ {0, 0, '1'}, {1, 0, '2'}, - {0, 1, '3'}, + // 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, '!'}, } diff --git a/kernel/kfmt/early/early_fmt.go b/kernel/kfmt/early/early_fmt.go index 12fc2c9..d3b9bfe 100644 --- a/kernel/kfmt/early/early_fmt.go +++ b/kernel/kfmt/early/early_fmt.go @@ -7,7 +7,7 @@ var ( errWrongArgType = []byte("%!(WRONGTYPE)") errNoVerb = []byte("%!(NOVERB)") errExtraArg = []byte("%!(EXTRA)") - padding = []byte{' '} + padding = byte(' ') trueValue = []byte("true") falseValue = []byte("false") ) @@ -62,7 +62,9 @@ func Printf(format string, args ...interface{}) { } if blockStart < blockEnd { - hal.ActiveTerminal.Write([]byte(format[blockStart:blockEnd])) + for i := blockStart; i < blockEnd; i++ { + hal.ActiveTerminal.WriteByte(format[i]) + } } // Scan til we hit the format character @@ -109,7 +111,9 @@ func Printf(format string, args ...interface{}) { } if blockStart != blockEnd { - hal.ActiveTerminal.Write([]byte(format[blockStart:blockEnd])) + for i := blockStart; i < blockEnd; i++ { + hal.ActiveTerminal.WriteByte(format[i]) + } } // Check for unused args @@ -139,23 +143,25 @@ func fmtBool(v interface{}) { // padding specified by padLen. This function uses hal.ActiveTerminal for its // output. func fmtString(v interface{}, padLen int) { - var sval []byte - switch castedVal := v.(type) { case string: - sval = []byte(castedVal) + fmtRepeat(padding, padLen-len(castedVal)) + for i := 0; i < len(castedVal); i++ { + hal.ActiveTerminal.WriteByte(castedVal[i]) + } case []byte: - sval = castedVal + fmtRepeat(padding, padLen-len(castedVal)) + hal.ActiveTerminal.Write(castedVal) default: hal.ActiveTerminal.Write(errWrongArgType) - return } +} - for pad := padLen - len(sval); pad > 0; pad-- { - hal.ActiveTerminal.Write(padding) +// fmtRepeat writes count bytes with value ch to the hal.ActiveTerminal. +func fmtRepeat(ch byte, count int) { + for i := 0; i < count; i++ { + hal.ActiveTerminal.WriteByte(ch) } - - hal.ActiveTerminal.Write(sval) } // fmtInt prints out a formatted version of v in the requested base, applying the