mirror of https://github.com/liamg/aminal.git
fixed resizing and got basic input working
This commit is contained in:
parent
392a6239ef
commit
49e502cc0b
|
@ -0,0 +1,58 @@
|
|||
package gui
|
||||
|
||||
import (
|
||||
v41 "github.com/4ydx/gltext/v4.1"
|
||||
"github.com/go-gl/mathgl/mgl32"
|
||||
)
|
||||
|
||||
type Cell struct {
|
||||
text *v41.Text
|
||||
}
|
||||
|
||||
func NewCell(font *v41.Font, x float32, y float32, w float32, h float32) Cell {
|
||||
cell := Cell{
|
||||
text: v41.NewText(font, 1.0, 1.1),
|
||||
}
|
||||
|
||||
cell.text.SetPosition(mgl32.Vec2{x, y})
|
||||
|
||||
return cell
|
||||
|
||||
}
|
||||
|
||||
func (cell *Cell) Draw() {
|
||||
|
||||
if cell.text != nil {
|
||||
cell.text.Draw()
|
||||
}
|
||||
}
|
||||
|
||||
func (cell *Cell) Show() {
|
||||
if cell.text != nil {
|
||||
cell.text.Show()
|
||||
}
|
||||
}
|
||||
|
||||
func (cell *Cell) Hide() {
|
||||
if cell.text != nil {
|
||||
cell.text.Hide()
|
||||
}
|
||||
}
|
||||
|
||||
func (cell *Cell) SetRune(r rune) {
|
||||
if cell.text != nil {
|
||||
cell.text.SetString(string(r))
|
||||
}
|
||||
}
|
||||
|
||||
func (cell *Cell) SetColour(r float32, g float32, b float32) {
|
||||
if cell.text != nil {
|
||||
cell.text.SetColor(mgl32.Vec3{r, g, b})
|
||||
}
|
||||
}
|
||||
|
||||
func (cell *Cell) Release() {
|
||||
if cell.text != nil {
|
||||
cell.text.Release()
|
||||
}
|
||||
}
|
159
gui/gui.go
159
gui/gui.go
|
@ -5,7 +5,6 @@ import (
|
|||
"math"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/4ydx/gltext"
|
||||
|
@ -29,10 +28,10 @@ type GUI struct {
|
|||
height int
|
||||
charWidth float32
|
||||
charHeight float32
|
||||
texts [][]*v41.Text
|
||||
textLock sync.Mutex
|
||||
cells [][]Cell
|
||||
cols int
|
||||
rows int
|
||||
capslock bool
|
||||
}
|
||||
|
||||
func New(config config.Config, terminal *terminal.Terminal, logger *zap.SugaredLogger) *GUI {
|
||||
|
@ -44,17 +43,46 @@ func New(config config.Config, terminal *terminal.Terminal, logger *zap.SugaredL
|
|||
width: 600,
|
||||
height: 300,
|
||||
terminal: terminal,
|
||||
texts: [][]*v41.Text{},
|
||||
cells: [][]Cell{},
|
||||
}
|
||||
}
|
||||
|
||||
// inspired by https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-1-hello-opengl
|
||||
|
||||
func (gui *GUI) SetSize(w int, h int) {
|
||||
gui.window.SetSize(w, h)
|
||||
gui.resize(gui.window, w, h)
|
||||
func (gui *GUI) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
|
||||
|
||||
caps := gui.capslock
|
||||
|
||||
if mods&glfw.ModShift > 0 {
|
||||
caps = !caps
|
||||
}
|
||||
|
||||
if action == glfw.Repeat || action == glfw.Press {
|
||||
|
||||
switch key {
|
||||
case glfw.KeyCapsLock:
|
||||
gui.capslock = !gui.capslock
|
||||
case glfw.KeyEnter:
|
||||
gui.terminal.Write([]byte{0x0a})
|
||||
default:
|
||||
if key >= 0x41 && key <= 0x5a { // A-Z, normalise to lower
|
||||
key += 0x20
|
||||
}
|
||||
if key >= 0x61 && key <= 0x7a { // a-z
|
||||
if caps {
|
||||
key -= 0x20
|
||||
}
|
||||
}
|
||||
gui.terminal.Write([]byte{byte(key)})
|
||||
}
|
||||
|
||||
//gui.logger.Debugf("Key pressed: 0x%X %q", key, string([]byte{byte(key)}))
|
||||
//gui.terminal.Write([]byte{byte(scancode)})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// can only be called on OS thread
|
||||
func (gui *GUI) resize(w *glfw.Window, width int, height int) {
|
||||
|
||||
if width == gui.width && height == gui.height {
|
||||
|
@ -90,10 +118,10 @@ func (gui *GUI) getTermSize() (int, int) {
|
|||
return gui.cols, gui.rows
|
||||
}
|
||||
|
||||
// checks if the terminals cells have been updated, and updates the text objects if needed
|
||||
// checks if the terminals cells have been updated, and updates the text objects if needed - only call on OS thread
|
||||
func (gui *GUI) updateTexts() {
|
||||
gui.textLock.Lock()
|
||||
defer gui.textLock.Unlock()
|
||||
|
||||
// runtime.LockOSThread() ?
|
||||
|
||||
//gui.logger.Debugf("Updating texts...")
|
||||
|
||||
|
@ -102,69 +130,56 @@ func (gui *GUI) updateTexts() {
|
|||
for row := 0; row < rows; row++ {
|
||||
for col := 0; col < cols; col++ {
|
||||
|
||||
r, err := gui.terminal.GetRuneAtPos(terminal.Position{Row: row, Col: col})
|
||||
if err != nil {
|
||||
gui.logger.Errorf("Failed to read rune: %s", err)
|
||||
c, err := gui.terminal.GetCellAtPos(terminal.Position{Line: row, Col: col})
|
||||
|
||||
if err != nil || c == nil {
|
||||
gui.cells[row][col].Hide()
|
||||
continue
|
||||
}
|
||||
if r > 0 {
|
||||
gui.texts[row][col].SetString(string(r))
|
||||
gui.texts[row][col].SetColor(mgl32.Vec3{1, 1, 1})
|
||||
// @todo set colour
|
||||
gui.texts[row][col].Show()
|
||||
} else {
|
||||
//gui.texts[row][col].Hide()
|
||||
gui.texts[row][col].SetString("?")
|
||||
gui.texts[row][col].SetColor(mgl32.Vec3{0.1, 0.1, 0.15})
|
||||
|
||||
if c.IsHidden() {
|
||||
|
||||
gui.cells[row][col].Hide()
|
||||
|
||||
// debug
|
||||
//gui.texts[row][col].SetColor(c.GetColourVec())
|
||||
//gui.texts[row][col].SetString("?")
|
||||
//gui.texts[row][col].Show()
|
||||
// end debug
|
||||
continue
|
||||
}
|
||||
|
||||
gui.cells[row][col].SetColour(c.GetColour())
|
||||
gui.cells[row][col].SetRune(c.GetRune())
|
||||
gui.cells[row][col].Show()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// builds text objects
|
||||
// builds text objects - only call on OS thread
|
||||
func (gui *GUI) createTexts() {
|
||||
gui.textLock.Lock()
|
||||
scaleMin, scaleMax := float32(1.0), float32(1.1)
|
||||
|
||||
cols, rows := gui.getTermSize()
|
||||
|
||||
texts := [][]*v41.Text{}
|
||||
cells := [][]Cell{}
|
||||
for row := 0; row < rows; row++ {
|
||||
|
||||
if len(texts) <= row {
|
||||
texts = append(texts, []*v41.Text{})
|
||||
if len(cells) <= row {
|
||||
cells = append(cells, []Cell{})
|
||||
}
|
||||
for col := 0; col < cols; col++ {
|
||||
if len(texts[row]) <= col {
|
||||
text := v41.NewText(gui.font, scaleMin, scaleMax)
|
||||
text.Hide()
|
||||
|
||||
if row < len(gui.texts) {
|
||||
if col < len(gui.texts[row]) {
|
||||
text.SetString(gui.texts[row][col].String)
|
||||
gui.texts[row][col].Release()
|
||||
if gui.texts[row][col].String != "" {
|
||||
text.Show()
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(cells[row]) <= col {
|
||||
|
||||
x := ((float32(col) * gui.charWidth) - (float32(gui.width) / 2)) + (gui.charWidth / 2)
|
||||
y := -(((float32(row) * gui.charHeight) - (float32(gui.height) / 2)) + (gui.charHeight / 2))
|
||||
|
||||
if col == 0 && row == 0 {
|
||||
gui.logger.Debugf("0,0 is at %f,%f", x, y)
|
||||
} else if col == cols-1 && row == rows-1 {
|
||||
gui.logger.Debugf("%d,%d is at %f,%f", col, row, x, y)
|
||||
}
|
||||
|
||||
text.SetPosition(mgl32.Vec2{x, y})
|
||||
texts[row] = append(texts[row], text)
|
||||
cells[row] = append(cells[row], NewCell(gui.font, x, y, gui.charWidth, gui.charHeight))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gui.texts = texts
|
||||
gui.textLock.Unlock()
|
||||
gui.cells = cells
|
||||
|
||||
gui.updateTexts()
|
||||
}
|
||||
|
@ -195,13 +210,19 @@ func (gui *GUI) Render() error {
|
|||
}
|
||||
|
||||
gui.window.SetFramebufferSizeCallback(gui.resize)
|
||||
gui.window.SetKeyCallback(gui.key)
|
||||
w, h := gui.window.GetSize()
|
||||
gui.resize(gui.window, w, h)
|
||||
|
||||
gl.Viewport(0, 0, int32(gui.width), int32(gui.height))
|
||||
|
||||
gui.logger.Debugf("Starting pty read handling...")
|
||||
gui.terminal.OnUpdate(gui.updateTexts)
|
||||
|
||||
updateChan := make(chan bool, 1024)
|
||||
|
||||
gui.terminal.OnUpdate(func() {
|
||||
updateChan <- true
|
||||
})
|
||||
go gui.terminal.Read()
|
||||
|
||||
scaleMin, scaleMax := float32(1.0), float32(1.1)
|
||||
|
@ -218,40 +239,57 @@ func (gui *GUI) Render() error {
|
|||
// stop smoothing fonts
|
||||
gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
||||
|
||||
updateRequired := false
|
||||
|
||||
gui.logger.Debugf("Starting render...")
|
||||
|
||||
gl.ClearColor(0.1, 0.1, 0.1, 1.0)
|
||||
|
||||
for !gui.window.ShouldClose() {
|
||||
|
||||
updateRequired = false
|
||||
|
||||
CheckUpdate:
|
||||
for {
|
||||
select {
|
||||
case <-updateChan:
|
||||
updateRequired = true
|
||||
case <-ticker.C:
|
||||
text.SetString(fmt.Sprintf("%d fps | %d, %d | %s", frames, gui.terminal.GetPosition().Col, gui.terminal.GetPosition().Row, gui.texts[0][0].String))
|
||||
text.SetString(fmt.Sprintf("%d fps | %dx%d", frames, gui.cols, gui.rows))
|
||||
frames = 0
|
||||
updateRequired = true
|
||||
default:
|
||||
break CheckUpdate
|
||||
}
|
||||
}
|
||||
|
||||
gl.UseProgram(program)
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
||||
|
||||
if updateRequired {
|
||||
|
||||
gui.updateTexts()
|
||||
|
||||
// Render the string.
|
||||
gui.window.SetTitle(gui.terminal.GetTitle())
|
||||
|
||||
gui.textLock.Lock()
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
||||
|
||||
cols, rows := gui.getTermSize()
|
||||
|
||||
for row := 0; row < rows; row++ {
|
||||
for col := 0; col < cols; col++ {
|
||||
gui.texts[row][col].SetColor(mgl32.Vec3{1, 1, 1})
|
||||
//gui.texts[row][col].SetString("?")
|
||||
gui.texts[row][col].Draw()
|
||||
gui.cells[row][col].Draw()
|
||||
}
|
||||
}
|
||||
gui.textLock.Unlock()
|
||||
|
||||
text.Draw()
|
||||
}
|
||||
|
||||
frames++
|
||||
|
||||
glfw.PollEvents()
|
||||
gui.window.SwapBuffers()
|
||||
|
||||
frames++
|
||||
}
|
||||
|
||||
gui.logger.Debugf("Stopping render...")
|
||||
|
@ -320,5 +358,6 @@ func (gui *GUI) createProgram() (uint32, error) {
|
|||
|
||||
prog := gl.CreateProgram()
|
||||
gl.LinkProgram(prog)
|
||||
|
||||
return prog, nil
|
||||
}
|
||||
|
|
2
main.go
2
main.go
|
@ -41,7 +41,7 @@ func main() {
|
|||
terminal := terminal.New(pty, sugaredLogger)
|
||||
|
||||
go func() {
|
||||
time.Sleep(time.Second * 5)
|
||||
time.Sleep(time.Second * 1)
|
||||
terminal.Write([]byte("tput cols && tput lines\n"))
|
||||
terminal.Write([]byte("ls -la\n"))
|
||||
}()
|
||||
|
|
|
@ -1,5 +1,40 @@
|
|||
package terminal
|
||||
|
||||
import "github.com/go-gl/mathgl/mgl32"
|
||||
|
||||
type Cell struct {
|
||||
r rune
|
||||
wrapper bool
|
||||
isWrapped bool
|
||||
}
|
||||
|
||||
func (cell *Cell) GetRune() rune {
|
||||
return cell.r
|
||||
}
|
||||
|
||||
func (cell *Cell) IsHidden() bool {
|
||||
return cell.r == 0
|
||||
}
|
||||
|
||||
func (cell *Cell) GetColour() (r float32, g float32, b float32) {
|
||||
|
||||
if cell.wrapper {
|
||||
return 0, 1, 0
|
||||
}
|
||||
|
||||
if cell.isWrapped {
|
||||
return 1, 1, 0
|
||||
}
|
||||
|
||||
if cell.IsHidden() {
|
||||
return 0, 0, 1
|
||||
}
|
||||
|
||||
return 1, 1, 1
|
||||
|
||||
}
|
||||
|
||||
func (cell *Cell) GetColourVec() mgl32.Vec3 {
|
||||
r, g, b := cell.GetColour()
|
||||
return mgl32.Vec3{r, g, b}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package terminal
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
@ -11,13 +12,64 @@ import (
|
|||
)
|
||||
|
||||
type Terminal struct {
|
||||
cells [][]Cell // y, x
|
||||
lines []Line // lines, where 0 is earliest, n is latest
|
||||
position Position // line and col
|
||||
lock sync.Mutex
|
||||
pty *os.File
|
||||
logger *zap.SugaredLogger
|
||||
title string
|
||||
position Position
|
||||
onUpdate []func()
|
||||
size Winsize
|
||||
}
|
||||
|
||||
type Line struct {
|
||||
Cells []Cell
|
||||
wrapped bool
|
||||
}
|
||||
|
||||
func NewLine() Line {
|
||||
return Line{
|
||||
Cells: []Cell{},
|
||||
}
|
||||
}
|
||||
|
||||
func (line *Line) String() string {
|
||||
s := ""
|
||||
for _, c := range line.Cells {
|
||||
s += string(c.r)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (line *Line) CutCellsAfter(n int) []Cell {
|
||||
cut := line.Cells[n:]
|
||||
line.Cells = line.Cells[:n]
|
||||
return cut
|
||||
}
|
||||
|
||||
func (line *Line) CutCellsFromBeginning(n int) []Cell {
|
||||
if n > len(line.Cells) {
|
||||
n = len(line.Cells)
|
||||
}
|
||||
cut := line.Cells[:n]
|
||||
line.Cells = line.Cells[n:]
|
||||
return cut
|
||||
}
|
||||
|
||||
func (line *Line) CutCellsFromEnd(n int) []Cell {
|
||||
cut := line.Cells[len(line.Cells)-n:]
|
||||
line.Cells = line.Cells[:len(line.Cells)-n]
|
||||
return cut
|
||||
}
|
||||
|
||||
func (line *Line) GetRenderedLength() int {
|
||||
l := 0
|
||||
for x, c := range line.Cells {
|
||||
if c.r > 0 {
|
||||
l = x
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
type Winsize struct {
|
||||
|
@ -28,13 +80,13 @@ type Winsize struct {
|
|||
}
|
||||
|
||||
type Position struct {
|
||||
Line int
|
||||
Col int
|
||||
Row int
|
||||
}
|
||||
|
||||
func New(pty *os.File, logger *zap.SugaredLogger) *Terminal {
|
||||
return &Terminal{
|
||||
cells: [][]Cell{},
|
||||
lines: []Line{},
|
||||
pty: pty,
|
||||
logger: logger,
|
||||
onUpdate: []func(){},
|
||||
|
@ -51,6 +103,25 @@ func (terminal *Terminal) triggerOnUpdate() {
|
|||
}
|
||||
}
|
||||
|
||||
func (terminal *Terminal) getPosition() Position {
|
||||
return terminal.position
|
||||
}
|
||||
|
||||
func (terminal *Terminal) incrementPosition() {
|
||||
position := terminal.getPosition()
|
||||
if position.Col+1 >= int(terminal.size.Width) {
|
||||
position.Line++
|
||||
position.Col = 0
|
||||
} else {
|
||||
position.Col++
|
||||
}
|
||||
terminal.SetPosition(position)
|
||||
}
|
||||
|
||||
func (terminal *Terminal) SetPosition(position Position) {
|
||||
terminal.position = position
|
||||
}
|
||||
|
||||
func (terminal *Terminal) GetTitle() string {
|
||||
return terminal.title
|
||||
}
|
||||
|
@ -61,13 +132,29 @@ func (terminal *Terminal) Write(data []byte) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (terminal *Terminal) ClearToEndOfLine() error {
|
||||
w, _ := terminal.GetSize()
|
||||
for i := terminal.position.Col; i < w; i++ {
|
||||
// @todo handle errors?
|
||||
terminal.setRuneAtPos(Position{Row: terminal.position.Row, Col: i}, 0)
|
||||
func (terminal *Terminal) ClearToEndOfLine() {
|
||||
|
||||
position := terminal.getPosition()
|
||||
|
||||
if position.Line < len(terminal.lines) {
|
||||
if position.Col < len(terminal.lines[position.Line].Cells) {
|
||||
terminal.lines[position.Line].Cells = terminal.lines[position.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)
|
||||
func (terminal *Terminal) getBufferedLine(line int) *Line {
|
||||
|
||||
if len(terminal.lines) >= int(terminal.size.Height) {
|
||||
line = len(terminal.lines) - int(terminal.size.Height) + line
|
||||
}
|
||||
|
||||
if line >= len(terminal.lines) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &terminal.lines[line]
|
||||
}
|
||||
|
||||
// Read needs to be run on a goroutine, as it continually reads output to set on the terminal
|
||||
|
@ -104,6 +191,30 @@ func (terminal *Terminal) Read() error {
|
|||
}
|
||||
|
||||
switch final {
|
||||
case byte('A'):
|
||||
distance := 1
|
||||
if len(params) > 0 {
|
||||
var err error
|
||||
distance, err = strconv.Atoi(string([]byte{params[0]}))
|
||||
if err != nil {
|
||||
distance = 1
|
||||
}
|
||||
}
|
||||
if terminal.position.Line-distance >= 0 {
|
||||
terminal.position.Line -= distance
|
||||
}
|
||||
case byte('B'):
|
||||
distance := 1
|
||||
if len(params) > 0 {
|
||||
var err error
|
||||
distance, err = strconv.Atoi(string([]byte{params[0]}))
|
||||
if err != nil {
|
||||
distance = 1
|
||||
}
|
||||
}
|
||||
|
||||
terminal.position.Line += distance
|
||||
|
||||
case 0x4b: // K - EOL - Erase to end of line
|
||||
if len(params) == 0 || params[0] == byte('0') {
|
||||
terminal.ClearToEndOfLine()
|
||||
|
@ -151,9 +262,7 @@ func (terminal *Terminal) Read() error {
|
|||
default:
|
||||
// render character at current location
|
||||
// fmt.Printf("%s\n", string([]byte{b}))
|
||||
if err := terminal.writeRune([]rune(string([]byte{b}))[0]); err != nil {
|
||||
terminal.logger.Errorf("Failed to write rune %s", string([]byte{b}))
|
||||
}
|
||||
terminal.writeRune([]rune(string([]byte{b}))[0])
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -178,102 +287,166 @@ func (terminal *Terminal) Read() error {
|
|||
}
|
||||
}
|
||||
|
||||
func (terminal *Terminal) writeRune(r rune) error {
|
||||
func (terminal *Terminal) writeRune(r rune) {
|
||||
terminal.setRuneAtPos(terminal.position, r)
|
||||
terminal.incrementPosition()
|
||||
|
||||
err := terminal.setRuneAtPos(terminal.position, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w, h := terminal.GetSize()
|
||||
if terminal.position.Col < w-1 {
|
||||
terminal.position.Col++
|
||||
} else {
|
||||
terminal.position.Col = 0
|
||||
if terminal.position.Row <= h-1 {
|
||||
terminal.position.Row++
|
||||
} else {
|
||||
panic(fmt.Errorf("Not implemented - need to shuffle all rows up one"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (terminal *Terminal) newLine() {
|
||||
_, h := terminal.GetSize()
|
||||
terminal.position.Col = 0
|
||||
if terminal.position.Row <= h-1 {
|
||||
terminal.position.Row++
|
||||
} else {
|
||||
panic(fmt.Errorf("Not implemented - need to shuffle all rows up one"))
|
||||
if terminal.position.Line >= len(terminal.lines) {
|
||||
terminal.lines = append(terminal.lines, NewLine())
|
||||
}
|
||||
|
||||
terminal.position.Col = 0
|
||||
terminal.position.Line++
|
||||
}
|
||||
|
||||
func (terminal *Terminal) Clear() {
|
||||
for y := range terminal.cells {
|
||||
for x := range terminal.cells[y] {
|
||||
terminal.cells[y][x].r = 0
|
||||
// @todo actually should just add a bunch of newlines?
|
||||
for i := 0; i < int(terminal.size.Height); i++ {
|
||||
terminal.newLine()
|
||||
}
|
||||
}
|
||||
terminal.position = Position{0, 0}
|
||||
terminal.SetPosition(Position{Line: 0, Col: 0})
|
||||
}
|
||||
|
||||
func (terminal *Terminal) GetPosition() Position {
|
||||
return terminal.position
|
||||
}
|
||||
func (terminal *Terminal) GetCellAtPos(pos Position) (*Cell, error) {
|
||||
|
||||
func (terminal *Terminal) GetRuneAtPos(pos Position) (rune, error) {
|
||||
if len(terminal.cells) <= pos.Row {
|
||||
return 0, fmt.Errorf("Row %d does not exist", pos.Row)
|
||||
if int(terminal.size.Height) <= pos.Line {
|
||||
terminal.logger.Errorf("Line %d does not exist", pos.Line)
|
||||
return nil, fmt.Errorf("Line %d does not exist", pos.Line)
|
||||
}
|
||||
|
||||
if len(terminal.cells) < 1 || len(terminal.cells[0]) <= pos.Col {
|
||||
return 0, fmt.Errorf("Col %d does not exist", pos.Col)
|
||||
if int(terminal.size.Width) <= pos.Col {
|
||||
terminal.logger.Errorf("Col %d does not exist", pos.Col)
|
||||
return nil, fmt.Errorf("Col %d does not exist", pos.Col)
|
||||
}
|
||||
|
||||
return terminal.cells[pos.Row][pos.Col].r, nil
|
||||
line := terminal.getBufferedLine(pos.Line)
|
||||
if line == nil {
|
||||
return nil, fmt.Errorf("Line missing")
|
||||
}
|
||||
for pos.Col >= len(line.Cells) {
|
||||
line.Cells = append(line.Cells, Cell{})
|
||||
}
|
||||
return &line.Cells[pos.Col], nil
|
||||
}
|
||||
|
||||
func (terminal *Terminal) setRuneAtPos(pos Position, r rune) error {
|
||||
|
||||
if len(terminal.cells) <= pos.Row {
|
||||
return fmt.Errorf("Row %d does not exist", pos.Row)
|
||||
if int(terminal.size.Height) <= pos.Line {
|
||||
terminal.logger.Errorf("Line %d does not exist", pos.Line)
|
||||
return fmt.Errorf("Line %d does not exist", pos.Line)
|
||||
}
|
||||
|
||||
if len(terminal.cells) < 1 || len(terminal.cells[0]) <= pos.Col {
|
||||
if int(terminal.size.Width) <= pos.Col {
|
||||
terminal.logger.Errorf("Col %d does not exist", pos.Col)
|
||||
return fmt.Errorf("Col %d does not exist", pos.Col)
|
||||
}
|
||||
|
||||
if pos.Row == 0 && pos.Col == 0 {
|
||||
fmt.Printf("\n\nSetting %d %d to %q\n\n\n", pos.Row, pos.Col, string(r))
|
||||
if pos.Line == 0 && pos.Col == 0 {
|
||||
fmt.Printf("\n\nSetting %d %d to %q\n\n\n", pos.Line, pos.Col, string(r))
|
||||
}
|
||||
|
||||
terminal.cells[pos.Row][pos.Col].r = r
|
||||
line := terminal.getBufferedLine(pos.Line)
|
||||
if line == nil {
|
||||
for pos.Line >= len(terminal.lines) {
|
||||
terminal.lines = append(terminal.lines, NewLine())
|
||||
}
|
||||
line = terminal.getBufferedLine(pos.Line)
|
||||
if line == nil {
|
||||
panic(fmt.Errorf("Impossible?"))
|
||||
}
|
||||
}
|
||||
|
||||
for pos.Col >= len(line.Cells) {
|
||||
line.Cells = append(line.Cells, Cell{})
|
||||
}
|
||||
|
||||
line.Cells[pos.Col].r = r
|
||||
return nil
|
||||
}
|
||||
|
||||
func (terminal *Terminal) GetSize() (int, int) {
|
||||
terminal.lock.Lock()
|
||||
defer terminal.lock.Unlock()
|
||||
if len(terminal.cells) == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
return len(terminal.cells[0]), len(terminal.cells)
|
||||
return int(terminal.size.Width), int(terminal.size.Height)
|
||||
}
|
||||
|
||||
func (terminal *Terminal) SetSize(cols int, rows int) error {
|
||||
func (terminal *Terminal) SetSize(newCols int, newLines int) error {
|
||||
terminal.lock.Lock()
|
||||
defer terminal.lock.Unlock()
|
||||
cells := make([][]Cell, rows)
|
||||
for i := range cells {
|
||||
cells[i] = make([]Cell, cols)
|
||||
|
||||
oldCols := int(terminal.size.Width)
|
||||
oldLines := int(terminal.size.Height)
|
||||
|
||||
if oldLines > 0 && oldCols > 0 { // only bother resizing content if there is some
|
||||
if newCols < oldCols { // if the width decreased, we need to do some line trimming
|
||||
|
||||
for l := range terminal.lines {
|
||||
if terminal.lines[l].GetRenderedLength() > newCols {
|
||||
cells := terminal.lines[l].CutCellsAfter(newCols)
|
||||
line := Line{
|
||||
Cells: cells,
|
||||
wrapped: true,
|
||||
}
|
||||
terminal.cells = cells
|
||||
terminal.lines = append(terminal.lines[:l+1], append([]Line{line}, terminal.lines[l+1:]...)...)
|
||||
if terminal.getPosition().Line > l {
|
||||
terminal.position.Line++
|
||||
} else if terminal.getPosition().Line == l {
|
||||
if terminal.getPosition().Col >= newCols {
|
||||
terminal.position.Line++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if newCols > oldCols { // if width increased, we need to potentially unwrap some lines
|
||||
for l := 0; l < len(terminal.lines); l++ {
|
||||
if terminal.lines[l].GetRenderedLength() < newCols { // there is space here to unwrap a line if needed
|
||||
if l+1 < len(terminal.lines) {
|
||||
if terminal.lines[l+1].wrapped {
|
||||
wrapSize := newCols - terminal.lines[l].GetRenderedLength()
|
||||
cells := terminal.lines[l+1].CutCellsFromBeginning(wrapSize)
|
||||
terminal.lines[l].Cells = append(terminal.lines[l].Cells, cells...)
|
||||
if terminal.lines[l+1].GetRenderedLength() == 0 {
|
||||
// remove line
|
||||
terminal.lines = append(terminal.lines[:l+1], terminal.lines[l+2:]...)
|
||||
if terminal.getPosition().Line >= l+1 {
|
||||
terminal.position.Line--
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
terminal.size.Width = uint16(newCols)
|
||||
terminal.size.Height = uint16(newLines)
|
||||
|
||||
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(terminal.pty.Fd()),
|
||||
uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(&Winsize{Width: uint16(cols), Height: uint16(rows)})))
|
||||
uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(&terminal.size)))
|
||||
if err != 0 {
|
||||
return fmt.Errorf("Failed to set terminal size vai ioctl: Error no %d", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
------------------ ->
|
||||
ssssssssssssssssss
|
||||
ssssPPPPPPPPPPPPPP
|
||||
xxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxx
|
||||
--------------------------
|
||||
ssssssssssssssssss
|
||||
SsssPPPPPPPPPPPPPP
|
||||
xxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxx
|
||||
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue