lots of unicode work

This commit is contained in:
Liam Galvin 2018-07-01 21:57:25 +01:00
parent b0c60e7336
commit 1a20091388
11 changed files with 452 additions and 248 deletions

View File

@ -1,6 +1,8 @@
Raft is a terminal emulator utilising OpenGL. Raft is a terminal emulator utilising OpenGL v4.1.
This project is purely a learning exercise right now. The project is purely a learning exercise right now.
Ensure you have your latest graphics card drivers installed before use.
## Build Dependencies ## Build Dependencies
@ -9,11 +11,40 @@ This project is purely a learning exercise right now.
- On Ubuntu/Debian-like Linux distributions, you need `libgl1-mesa-dev xorg-dev`. - On Ubuntu/Debian-like Linux distributions, you need `libgl1-mesa-dev xorg-dev`.
- On CentOS/Fedora-like Linux distributions, you need `libX11-devel libXcursor-devel libXrandr-devel libXinerama-devel mesa-libGL-devel libXi-devel`. - On CentOS/Fedora-like Linux distributions, you need `libX11-devel libXcursor-devel libXrandr-devel libXinerama-devel mesa-libGL-devel libXi-devel`.
## Planned Features
| Feature | Done | Notes |
|-------------------|------|-------|
| Pty allocation | ✔ | Linux only so far
| OpenGL rendering | ✔ |
| Resizing/content reordering | ✔ |
| ANSI escape codes | 50% |
| UTF-8 input | 90% | No copy + paste as yet
| UTF-8 output | ✘ |
| Copy/paste | ✘ |
| Customisable colour schemes | ✔ | Complete, but the config file has no entry for this yet
| Config file | 5% |
| Scrolling | 50% | Infinite buffer implemented, need GUI scrollbar & render updates
| Sweet render effects | ✘ |
||||
## Platform Support ## Platform Support
| Platform | Supported | Platform | Supported
|---------|------------ |----------|------------
| Linux | Yes | Linux | ✔
| MacOSX | Not yet... | MacOSX | ✘ (nearly)
| Windows | Not yet... | Windows | ✘
## Configuration
Raft looks for a config file in the following places: `~/.raft.yml`, `~/.raft/config.yml`, `~/.config/raft/config.yml` (earlier in the list prioritised).
Example config:
```
debug: False
```
The following options are available:
| Name | Type | Description
| debug | bool | Enables debug logging

View File

@ -1,5 +1,22 @@
package config package config
import (
"gitlab.com/liamg/raft/terminal"
yaml "gopkg.in/yaml.v2"
)
type Config struct { type Config struct {
DebugMode bool DebugMode bool `yaml:"debug"`
ColourScheme terminal.ColourScheme
}
var DefaultConfig = Config{
DebugMode: false,
ColourScheme: terminal.DefaultColourScheme,
}
func Parse(data []byte) (*Config, error) {
c := DefaultConfig
err := yaml.Unmarshal(data, &c)
return &c, err
} }

View File

@ -14,14 +14,16 @@ type Cell struct {
colourAttr uint32 colourAttr uint32
points []float32 points []float32
colour [3]float32 colour [3]float32
hidden bool
} }
func (gui *GUI) NewCell(font *v41.Font, x float32, y float32, w float32, h float32, colourAttr uint32) Cell { func (gui *GUI) NewCell(font *v41.Font, x float32, y float32, w float32, h float32, colourAttr uint32, bgColour [3]float32) Cell {
cell := Cell{ cell := Cell{
text: v41.NewText(font, 1.0, 1.1), text: v41.NewText(font, 1.0, 1.1),
colourAttr: colourAttr, colourAttr: colourAttr,
} }
cell.colour = bgColour
cell.text.SetPosition(mgl32.Vec2{x, y}) cell.text.SetPosition(mgl32.Vec2{x, y})
x = (x - (w / 2)) / (float32(gui.width) / 2) x = (x - (w / 2)) / (float32(gui.width) / 2)
@ -97,12 +99,17 @@ func (cell *Cell) makeVao() {
} }
func (cell *Cell) DrawBg() { func (cell *Cell) DrawBg() {
if cell.hidden {
return
}
gl.BindVertexArray(cell.vao) gl.BindVertexArray(cell.vao)
gl.DrawArrays(gl.TRIANGLES, 0, 6) gl.DrawArrays(gl.TRIANGLES, 0, 6)
} }
func (cell *Cell) DrawText() { func (cell *Cell) DrawText() {
if cell.hidden {
return
}
if cell.text != nil { if cell.text != nil {
cell.text.Draw() cell.text.Draw()
} }
@ -110,21 +117,22 @@ func (cell *Cell) DrawText() {
} }
func (cell *Cell) Show() { func (cell *Cell) Show() {
if cell.text != nil { cell.hidden = false
cell.text.Show()
}
} }
func (cell *Cell) Hide() { func (cell *Cell) Hide() {
if cell.text != nil { cell.hidden = true
cell.text.Hide()
}
} }
func (cell *Cell) SetRune(r rune) { func (cell *Cell) SetRune(r rune) {
if cell.text != nil { if cell.text != nil {
if r == '%' {
cell.text.SetString("%%")
} else {
cell.text.SetString(string(r)) cell.text.SetString(string(r))
} }
}
} }
func (cell *Cell) Release() { func (cell *Cell) Release() {

View File

@ -64,6 +64,8 @@ func (gui *GUI) resize(w *glfw.Window, width int, height int) {
gui.font.ResizeWindow(float32(width), float32(height)) gui.font.ResizeWindow(float32(width), float32(height))
} }
gl.Viewport(0, 0, int32(gui.width), int32(gui.height))
scaleMin, scaleMax := float32(1.0), float32(1.1) scaleMin, scaleMax := float32(1.0), float32(1.1)
text := v41.NewText(gui.font, scaleMin, scaleMax) text := v41.NewText(gui.font, scaleMin, scaleMax)
text.SetString("A") text.SetString("A")
@ -98,12 +100,7 @@ func (gui *GUI) updateTexts() {
c, err := gui.terminal.GetCellAtPos(terminal.Position{Line: row, Col: col}) c, err := gui.terminal.GetCellAtPos(terminal.Position{Line: row, Col: col})
if err != nil || c == nil { if err != nil || c == nil || c.IsHidden() {
gui.cells[row][col].Hide()
continue
}
if c.IsHidden() {
gui.cells[row][col].Hide() gui.cells[row][col].Hide()
continue continue
} }
@ -113,6 +110,13 @@ func (gui *GUI) updateTexts() {
gui.cells[row][col].SetRune(c.GetRune()) gui.cells[row][col].SetRune(c.GetRune())
gui.cells[row][col].Show() gui.cells[row][col].Show()
if gui.terminal.GetPosition().Col == col && gui.terminal.GetPosition().Line == row {
gui.cells[row][col].SetBgColour(
gui.config.ColourScheme.Cursor[0],
gui.config.ColourScheme.Cursor[1],
gui.config.ColourScheme.Cursor[2],
)
}
} }
} }
} }
@ -134,7 +138,7 @@ func (gui *GUI) createTexts() {
x := ((float32(col) * gui.charWidth) - (float32(gui.width) / 2)) + (gui.charWidth / 2) x := ((float32(col) * gui.charWidth) - (float32(gui.width) / 2)) + (gui.charWidth / 2)
y := -(((float32(row) * gui.charHeight) - (float32(gui.height) / 2)) + (gui.charHeight / 2)) y := -(((float32(row) * gui.charHeight) - (float32(gui.height) / 2)) + (gui.charHeight / 2))
cells[row] = append(cells[row], gui.NewCell(gui.font, x, y, gui.charWidth, gui.charHeight, gui.colourAttr)) cells[row] = append(cells[row], gui.NewCell(gui.font, x, y, gui.charWidth, gui.charHeight, gui.colourAttr, gui.config.ColourScheme.DefaultBg))
} }
} }
} }
@ -171,8 +175,8 @@ func (gui *GUI) Render() error {
gl.BindFragDataLocation(program, 0, gl.Str("outColour\x00")) gl.BindFragDataLocation(program, 0, gl.Str("outColour\x00"))
gui.logger.Debugf("Loading font...") gui.logger.Debugf("Loading font...")
//gui.font, err = gui.loadFont("/usr/share/fonts/nerd-fonts-complete/ttf/Roboto Mono Nerd Font Complete.ttf", 12) if err := gui.loadFont("/usr/share/fonts/nerd-fonts-complete/ttf/Roboto Mono Nerd Font Complete.ttf", 12); err != nil {
if err := gui.loadFont("./fonts/CamingoCode-Regular.ttf", 12); err != nil { //if err := gui.loadFont("./fonts/CamingoCode-Regular.ttf", 12); err != nil {
return fmt.Errorf("Failed to load font: %s", err) return fmt.Errorf("Failed to load font: %s", err)
} }
@ -202,51 +206,60 @@ func (gui *GUI) Render() error {
text.SetColor(mgl32.Vec3{1, 0, 0}) text.SetColor(mgl32.Vec3{1, 0, 0})
text.SetPosition(mgl32.Vec2{0, 0}) text.SetPosition(mgl32.Vec2{0, 0})
ticker := time.NewTicker(time.Second) ticker := time.NewTicker(time.Millisecond * 100)
defer ticker.Stop() defer ticker.Stop()
//gl.Disable(gl.MULTISAMPLE) //gl.Disable(gl.MULTISAMPLE)
// stop smoothing fonts // stop smoothing fonts
gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
updateRequired := false updateRequired := 0
gui.logger.Debugf("Starting render...") gui.logger.Debugf("Starting render...")
gl.UseProgram(program)
// todo set bg colour // todo set bg colour
//bgColour := gui.terminal.colourScheme.DefaultBgColor //bgColour :=
gl.ClearColor(0.1, 0.1, 0.1, 1.0) gl.ClearColor(
gui.config.ColourScheme.DefaultBg[0],
gui.config.ColourScheme.DefaultBg[1],
gui.config.ColourScheme.DefaultBg[2],
1.0,
)
for !gui.window.ShouldClose() { for !gui.window.ShouldClose() {
updateRequired = false if updateRequired > 0 {
updateRequired--
} else {
CheckUpdate: CheckUpdate:
for { for {
select { select {
case <-updateChan: case <-updateChan:
updateRequired = true updateRequired = 2
case <-ticker.C: case <-ticker.C:
text.SetString(fmt.Sprintf("%dx%d", gui.cols, gui.rows)) text.SetString(fmt.Sprintf("%dx%d@%d,%d", gui.cols, gui.rows, gui.terminal.GetPosition().Col, gui.terminal.GetPosition().Line))
updateRequired = true updateRequired = 2
default: default:
break CheckUpdate break CheckUpdate
} }
} }
}
gl.UseProgram(program) gl.UseProgram(program)
if updateRequired { if updateRequired > 0 {
gl.Viewport(0, 0, int32(gui.width), int32(gui.height))
gui.updateTexts() gui.updateTexts()
// Render the string. // Render the string.
gui.window.SetTitle(gui.terminal.GetTitle()) gui.window.SetTitle(gui.terminal.GetTitle())
//gl.ClearColor(0.5, 0.5, 0.5, 1.0)
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
cols, rows := gui.getTermSize() cols, rows := gui.getTermSize()
for row := 0; row < rows; row++ { for row := 0; row < rows; row++ {
@ -257,16 +270,16 @@ func (gui *GUI) Render() error {
for row := 0; row < rows; row++ { for row := 0; row < rows; row++ {
for col := 0; col < cols; col++ { for col := 0; col < cols; col++ {
gui.cells[row][col].DrawText() gui.cells[row][col].DrawText()
} }
} }
text.Draw() // debug to show co-ords
//text.Draw()
} }
glfw.PollEvents() glfw.PollEvents()
if updateRequired { if updateRequired > 0 {
gui.window.SwapBuffers() gui.window.SwapBuffers()
} }
} }
@ -286,7 +299,8 @@ func (gui *GUI) loadFont(path string, scale int32) error {
runeRanges := make(gltext.RuneRanges, 0) runeRanges := make(gltext.RuneRanges, 0)
runeRanges = append(runeRanges, gltext.RuneRange{Low: 32, High: 127}) runeRanges = append(runeRanges, gltext.RuneRange{Low: 32, High: 127})
/*runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x3000, High: 0x3030}) /*
runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x0, High: 0x3030})
runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x3040, High: 0x309f}) runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x3040, High: 0x309f})
runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x30a0, High: 0x30ff}) runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x30a0, High: 0x30ff})
runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x4e00, High: 0x9faf}) runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x4e00, High: 0x9faf})

View File

@ -14,12 +14,25 @@ func (gui *GUI) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Acti
switch true { switch true {
case mods&glfw.ModControl > 0: case mods&glfw.ModControl > 0:
if mods&glfw.ModAlt > 0 {
// ctrl + alt +
switch key {
case glfw.KeyV:
// todo handle both these errors
if buf, err := gui.window.GetClipboardString(); err == nil {
_ = gui.terminal.Write([]byte(buf))
}
}
} else {
// ctrl +
switch key { switch key {
case glfw.KeyC: // ctrl^c case glfw.KeyC: // ctrl^c
gui.logger.Debugf("Sending CTRL^C") gui.logger.Debugf("Sending CTRL^C")
gui.terminal.Write([]byte{0x3}) // send EOT gui.terminal.Write([]byte{0x3}) // send EOT
} }
} }
}
switch key { switch key {
case glfw.KeyEnter: case glfw.KeyEnter:

67
main.go
View File

@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"gitlab.com/liamg/raft/config" "gitlab.com/liamg/raft/config"
@ -11,10 +12,33 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
func main() { func loadConfig() config.Config {
// parse this home := os.Getenv("HOME")
conf := config.Config{DebugMode: true} if home == "" {
return config.DefaultConfig
}
places := []string{
fmt.Sprintf("%s/.raft.yml", home),
fmt.Sprintf("%s/.raft/config.yml", home),
fmt.Sprintf("%s/.config/raft/config.yml", home),
}
for _, place := range places {
if b, err := ioutil.ReadFile(place); err == nil {
if c, err := config.Parse(b); err == nil {
return *c
} else {
fmt.Printf("Invalid config at %s: %s\n", place, err)
}
}
}
return config.DefaultConfig
}
func getLogger(conf config.Config) (*zap.SugaredLogger, error) {
var logger *zap.Logger var logger *zap.Logger
var err error var err error
@ -24,30 +48,35 @@ func main() {
logger, err = zap.NewProduction() logger, err = zap.NewProduction()
} }
if err != nil { if err != nil {
fmt.Printf("Failed to create logger: %s", err) return nil, fmt.Errorf("Failed to create logger: %s", err)
}
return logger.Sugar(), nil
}
func main() {
// parse this
conf := loadConfig()
logger, err := getLogger(conf)
if err != nil {
fmt.Printf("Failed to create logger: %s\n", err)
os.Exit(1) os.Exit(1)
} }
sugaredLogger := logger.Sugar() defer logger.Sync()
defer sugaredLogger.Sync()
sugaredLogger.Infof("Allocationg pty...") logger.Infof("Allocating pty...")
pty, err := pty.NewPtyWithShell() pty, err := pty.NewPtyWithShell()
if err != nil { if err != nil {
sugaredLogger.Fatalf("Failed to allocate pty: %s", err) logger.Fatalf("Failed to allocate pty: %s", err)
} }
sugaredLogger.Infof("Creating terminal...") logger.Infof("Creating terminal...")
terminal := terminal.New(pty, sugaredLogger, terminal.DefaultColourScheme) terminal := terminal.New(pty, logger, conf.ColourScheme)
/*
go func() {
time.Sleep(time.Second * 1)
terminal.Write([]byte("tput cols && tput lines\n"))
terminal.Write([]byte("ls -la\n"))
}()
*/
g := gui.New(conf, terminal, sugaredLogger) g := gui.New(conf, terminal, logger)
if err := g.Render(); err != nil { if err := g.Render(); err != nil {
sugaredLogger.Fatalf("Render error: %s", err) logger.Fatalf("Render error: %s", err)
} }
} }

0
md Normal file
View File

1
shit Normal file
View File

@ -0,0 +1 @@

View File

@ -16,6 +16,15 @@ type CellAttributes struct {
Hidden bool Hidden bool
} }
func (terminal *Terminal) NewCell() Cell {
return Cell{
attr: CellAttributes{
FgColour: terminal.colourScheme.DefaultFg,
BgColour: terminal.colourScheme.DefaultBg,
},
}
}
func (cell *Cell) GetRune() rune { func (cell *Cell) GetRune() rune {
return cell.r return cell.r
} }

View File

@ -1,6 +1,7 @@
package terminal package terminal
type ColourScheme struct { type ColourScheme struct {
Cursor [3]float32
DefaultFg [3]float32 DefaultFg [3]float32
BlackFg [3]float32 BlackFg [3]float32
RedFg [3]float32 RedFg [3]float32
@ -38,6 +39,7 @@ type ColourScheme struct {
} }
var DefaultColourScheme = ColourScheme{ var DefaultColourScheme = ColourScheme{
Cursor: [3]float32{0.8, 0.8, 0.8},
//fg //fg
DefaultFg: [3]float32{1, 1, 1}, DefaultFg: [3]float32{1, 1, 1},
BlackFg: [3]float32{0, 0, 0}, BlackFg: [3]float32{0, 0, 0},

View File

@ -1,6 +1,7 @@
package terminal package terminal
import ( import (
"bufio"
"fmt" "fmt"
"os" "os"
"strconv" "strconv"
@ -126,6 +127,10 @@ func (terminal *Terminal) incrementPosition() {
position := terminal.getPosition() position := terminal.getPosition()
if position.Col+1 >= int(terminal.size.Width) { if position.Col+1 >= int(terminal.size.Width) {
position.Line++ position.Line++
_, h := terminal.GetSize()
if position.Line >= h {
position.Line--
}
position.Col = 0 position.Col = 0
} else { } else {
position.Col++ position.Col++
@ -137,6 +142,10 @@ func (terminal *Terminal) SetPosition(position Position) {
terminal.position = position terminal.position = position
} }
func (terminal *Terminal) GetPosition() Position {
return terminal.position
}
func (terminal *Terminal) GetTitle() string { func (terminal *Terminal) GetTitle() string {
return terminal.title return terminal.title
} }
@ -151,11 +160,13 @@ func (terminal *Terminal) ClearToEndOfLine() {
position := terminal.getPosition() position := terminal.getPosition()
if position.Line < len(terminal.lines) { line := terminal.getBufferedLine(position.Line)
if position.Col < len(terminal.lines[position.Line].Cells) { if line != nil {
terminal.lines[position.Line].Cells = terminal.lines[position.Line].Cells[:position.Col] if position.Col < len(line.Cells) {
line.Cells = line.Cells[:position.Col]
} }
} }
} }
// we have thousands of lines of output. if the terminal is X lines high, we just want to lookat the most recent X lines to render (unless scroll etc) // we have thousands of lines of output. if the terminal is X lines high, we just want to lookat the most recent X lines to render (unless scroll etc)
@ -172,12 +183,7 @@ func (terminal *Terminal) getBufferedLine(line int) *Line {
return &terminal.lines[line] return &terminal.lines[line]
} }
// Read needs to be run on a goroutine, as it continually reads output to set on the terminal func (terminal *Terminal) processInput(buffer chan rune) {
func (terminal *Terminal) Read() error {
buffer := make(chan byte, 0xffff)
go func() {
// https://en.wikipedia.org/wiki/ANSI_escape_code // https://en.wikipedia.org/wiki/ANSI_escape_code
@ -188,9 +194,9 @@ func (terminal *Terminal) Read() error {
b = <-buffer b = <-buffer
switch b { switch b {
case 0x5b: // CSI: Control Sequence Introducer ] case 0x5b: // CSI: Control Sequence Introducer ]
var final byte var final rune
params := []byte{} params := []rune{}
intermediate := []byte{} intermediate := []rune{}
CSI: CSI:
for { for {
b = <-buffer b = <-buffer
@ -206,11 +212,11 @@ func (terminal *Terminal) Read() error {
} }
switch final { switch final {
case byte('A'): case rune('A'):
distance := 1 distance := 1
if len(params) > 0 { if len(params) > 0 {
var err error var err error
distance, err = strconv.Atoi(string([]byte{params[0]})) distance, err = strconv.Atoi(string(params[0]))
if err != nil { if err != nil {
distance = 1 distance = 1
} }
@ -218,11 +224,11 @@ func (terminal *Terminal) Read() error {
if terminal.position.Line-distance >= 0 { if terminal.position.Line-distance >= 0 {
terminal.position.Line -= distance terminal.position.Line -= distance
} }
case byte('B'): case rune('B'):
distance := 1 distance := 1
if len(params) > 0 { if len(params) > 0 {
var err error var err error
distance, err = strconv.Atoi(string([]byte{params[0]})) distance, err = strconv.Atoi(string(params[0]))
if err != nil { if err != nil {
distance = 1 distance = 1
} }
@ -231,12 +237,12 @@ func (terminal *Terminal) Read() error {
terminal.position.Line += distance terminal.position.Line += distance
case 0x4b: // K - EOL - Erase to end of line case 0x4b: // K - EOL - Erase to end of line
if len(params) == 0 || params[0] == byte('0') { if len(params) == 0 || params[0] == rune('0') {
terminal.ClearToEndOfLine() terminal.ClearToEndOfLine()
} else { } else {
terminal.logger.Errorf("Unsupported EL") terminal.logger.Errorf("Unsupported EL")
} }
case byte('m'): case rune('m'):
// SGR: colour and shit // SGR: colour and shit
sgr := string(params) sgr := string(params)
sgrParams := strings.Split(sgr, ";") sgrParams := strings.Split(sgr, ";")
@ -270,21 +276,88 @@ func (terminal *Terminal) Read() error {
case "28": case "28":
terminal.cellAttr.Hidden = false terminal.cellAttr.Hidden = false
case "39": case "39":
terminal.cellAttr.FgColour = terminal.colourScheme.DefaultFg
case "30":
terminal.cellAttr.FgColour = terminal.colourScheme.BlackFg
case "31":
terminal.cellAttr.FgColour = terminal.colourScheme.RedFg
case "32":
terminal.cellAttr.FgColour = terminal.colourScheme.GreenFg
case "33":
terminal.cellAttr.FgColour = terminal.colourScheme.YellowFg
case "34":
terminal.cellAttr.FgColour = terminal.colourScheme.BlueFg
case "35":
terminal.cellAttr.FgColour = terminal.colourScheme.MagentaFg
case "36":
terminal.cellAttr.FgColour = terminal.colourScheme.CyanFg
case "37":
terminal.cellAttr.FgColour = terminal.colourScheme.LightGreyFg
case "90":
terminal.cellAttr.FgColour = terminal.colourScheme.DarkGreyFg
case "91":
terminal.cellAttr.FgColour = terminal.colourScheme.LightRedFg
case "92":
terminal.cellAttr.FgColour = terminal.colourScheme.LightGreenFg
case "93":
terminal.cellAttr.FgColour = terminal.colourScheme.LightYellowFg
case "94":
terminal.cellAttr.FgColour = terminal.colourScheme.LightBlueFg
case "95":
terminal.cellAttr.FgColour = terminal.colourScheme.LightMagentaFg
case "96":
terminal.cellAttr.FgColour = terminal.colourScheme.LightCyanFg
case "97":
terminal.cellAttr.FgColour = terminal.colourScheme.WhiteFg
case "49":
terminal.cellAttr.BgColour = terminal.colourScheme.DefaultBg
case "40":
terminal.cellAttr.BgColour = terminal.colourScheme.BlackBg
case "41":
terminal.cellAttr.BgColour = terminal.colourScheme.RedBg
case "42":
terminal.cellAttr.BgColour = terminal.colourScheme.GreenBg
case "43":
terminal.cellAttr.BgColour = terminal.colourScheme.YellowBg
case "44":
terminal.cellAttr.BgColour = terminal.colourScheme.BlueBg
case "45":
terminal.cellAttr.BgColour = terminal.colourScheme.MagentaBg
case "46":
terminal.cellAttr.BgColour = terminal.colourScheme.CyanBg
case "47":
terminal.cellAttr.BgColour = terminal.colourScheme.LightGreenBg
case "100":
terminal.cellAttr.BgColour = terminal.colourScheme.DarkGreyBg
case "101":
terminal.cellAttr.BgColour = terminal.colourScheme.LightRedBg
case "102":
terminal.cellAttr.BgColour = terminal.colourScheme.LightGreenBg
case "103":
terminal.cellAttr.BgColour = terminal.colourScheme.LightYellowBg
case "104":
terminal.cellAttr.BgColour = terminal.colourScheme.LightBlueBg
case "105":
terminal.cellAttr.BgColour = terminal.colourScheme.LightMagentaBg
case "106":
terminal.cellAttr.BgColour = terminal.colourScheme.LightCyanBg
case "107":
terminal.cellAttr.BgColour = terminal.colourScheme.WhiteBg
} }
} }
terminal.logger.Debugf("SGR params %#v intermediate %#v", params, intermediate)
default: default:
b = <-buffer b = <-buffer
terminal.logger.Debugf("Unknown CSI control sequence: 0x%02X (%s)", final, string([]byte{final})) terminal.logger.Errorf("Unknown CSI control sequence: 0x%02X (%s)", final, string(final))
} }
case 0x5d: // OSC: Operating System Command case 0x5d: // OSC: Operating System Command
b = <-buffer b = <-buffer
switch b { switch b {
case byte('0'): case rune('0'):
b = <-buffer b = <-buffer
if b == byte(';') { if b == rune(';') {
title := []byte{} title := []rune{}
for { for {
b = <-buffer b = <-buffer
if b == 0x07 { if b == 0x07 {
@ -295,24 +368,29 @@ func (terminal *Terminal) Read() error {
terminal.logger.Debugf("Terminal title set to: %s", string(title)) terminal.logger.Debugf("Terminal title set to: %s", string(title))
terminal.title = string(title) terminal.title = string(title)
} else { } else {
terminal.logger.Debugf("Invalid OSC 0 control sequence: 0x%02X", b) terminal.logger.Errorf("Invalid OSC 0 control sequence: 0x%02X", b)
} }
default: default:
terminal.logger.Debugf("Unknown OSC control sequence: 0x%02X", b) terminal.logger.Errorf("Unknown OSC control sequence: 0x%02X", b)
} }
case byte('c'): case rune('c'):
terminal.logger.Errorf("RIS not yet supported") terminal.logger.Errorf("RIS not yet supported")
case rune(')'), rune('('):
b = <-buffer
terminal.logger.Debugf("Ignoring character set control code )%s", string(b))
default: default:
terminal.logger.Debugf("Unknown control sequence: 0x%02X", b) terminal.logger.Errorf("Unknown control sequence: 0x%02X [%s]", b, string(b))
} }
} else { } else {
switch b { switch b {
case 0x0a: case 0x0a:
for terminal.position.Line+1 >= len(terminal.lines) {
terminal.lines = append(terminal.lines, NewLine())
}
terminal.position.Line++ terminal.position.Line++
_, h := terminal.GetSize()
if terminal.position.Line >= h {
terminal.position.Line--
}
terminal.lines = append(terminal.lines, NewLine())
case 0x0d: case 0x0d:
terminal.position.Col = 0 terminal.position.Col = 0
case 0x08: case 0x08:
@ -323,28 +401,28 @@ func (terminal *Terminal) Read() error {
default: default:
// render character at current location // render character at current location
// fmt.Printf("%s\n", string([]byte{b})) // fmt.Printf("%s\n", string([]byte{b}))
terminal.writeRune([]rune(string([]byte{b}))[0]) terminal.writeRune(b)
} }
} }
terminal.triggerOnUpdate() terminal.triggerOnUpdate()
} }
}() }
// Read needs to be run on a goroutine, as it continually reads output to set on the terminal
func (terminal *Terminal) Read() error {
buffer := make(chan rune, 0xffff)
reader := bufio.NewReader(terminal.pty)
go terminal.processInput(buffer)
for { for {
readBytes := make([]byte, 1024) r, size, err := reader.ReadRune()
n, err := terminal.pty.Read(readBytes)
if err != nil { if err != nil {
terminal.logger.Errorf("Failed to read from pty: %s", err)
return err return err
} } else if size > 0 {
if len(readBytes) > 0 { buffer <- r
readBytes = readBytes[:n]
fmt.Printf("Data in: %q\n", string(readBytes))
for _, x := range readBytes {
buffer <- x
}
} }
} }
} }
@ -380,7 +458,7 @@ func (terminal *Terminal) GetCellAtPos(pos Position) (*Cell, error) {
return nil, fmt.Errorf("Line missing") return nil, fmt.Errorf("Line missing")
} }
for pos.Col >= len(line.Cells) { for pos.Col >= len(line.Cells) {
line.Cells = append(line.Cells, Cell{}) line.Cells = append(line.Cells, terminal.NewCell())
} }
return &line.Cells[pos.Col], nil return &line.Cells[pos.Col], nil
} }
@ -396,15 +474,17 @@ func (terminal *Terminal) setRuneAtPos(pos Position, r rune) error {
fmt.Printf("\n\nSetting %d %d to %q\n\n\n", pos.Line, pos.Col, string(r)) fmt.Printf("\n\nSetting %d %d to %q\n\n\n", pos.Line, pos.Col, string(r))
} }
for terminal.position.Line >= len(terminal.lines) {
terminal.lines = append(terminal.lines, NewLine())
}
line := terminal.getBufferedLine(pos.Line) line := terminal.getBufferedLine(pos.Line)
if line == nil { if line == nil {
return fmt.Errorf("Impossible?") return fmt.Errorf("Impossible?")
} }
for pos.Col >= len(line.Cells) { for pos.Col >= len(line.Cells) {
line.Cells = append(line.Cells, Cell{}) line.Cells = append(line.Cells, terminal.NewCell())
} }
line.Cells[pos.Col].attr = terminal.cellAttr line.Cells[pos.Col].attr = terminal.cellAttr