mirror of https://github.com/liamg/aminal.git
more efficient text rendering and streamlining of output handling
This commit is contained in:
parent
1a932fa2c3
commit
177e928b71
6
Makefile
6
Makefile
|
@ -22,12 +22,6 @@ install-tools:
|
|||
which dep || curl -L https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
which packr || go get -u github.com/gobuffalo/packr/packr
|
||||
|
||||
.PHONY: update-fonts
|
||||
update-fonts: install-tools
|
||||
curl -L https://github.com/ryanoasis/nerd-fonts/raw/master/patched-fonts/Hack/Regular/complete/Hack%20Regular%20Nerd%20Font%20Complete.ttf -o "${FONTPATH}/Hack Regular Nerd Font Complete.ttf"
|
||||
curl -L https://github.com/ryanoasis/nerd-fonts/raw/master/patched-fonts/Hack/Bold/complete/Hack%20Bold%20Nerd%20Font%20Complete.ttf -o "${FONTPATH}/Hack Bold Nerd Font Complete.ttf"
|
||||
packr -v
|
||||
|
||||
.PHONY: build-linux
|
||||
build-linux:
|
||||
mkdir -p bin/linux
|
||||
|
|
|
@ -610,20 +610,10 @@ func (buffer *Buffer) ReverseIndex() {
|
|||
func (buffer *Buffer) Write(runes ...rune) {
|
||||
|
||||
// scroll to bottom on input
|
||||
inc := true
|
||||
buffer.scrollLinesFromBottom = 0
|
||||
|
||||
for _, r := range runes {
|
||||
if r == 0x0a {
|
||||
buffer.NewLine()
|
||||
continue
|
||||
} else if r == 0x0d {
|
||||
buffer.CarriageReturn()
|
||||
continue
|
||||
} else if r == 0x9 {
|
||||
buffer.Tab()
|
||||
continue
|
||||
}
|
||||
|
||||
line := buffer.getCurrentLine()
|
||||
|
||||
if buffer.replaceMode {
|
||||
|
@ -634,7 +624,7 @@ func (buffer *Buffer) Write(runes ...rune) {
|
|||
}
|
||||
|
||||
for int(buffer.CursorColumn()) >= len(line.cells) {
|
||||
line.cells = append(line.cells, NewBackgroundCell(buffer.cursorAttr.BgColour))
|
||||
line.cells = append(line.cells, Cell{})
|
||||
}
|
||||
line.cells[buffer.cursorX].attr = buffer.cursorAttr
|
||||
line.cells[buffer.cursorX].setRune(r)
|
||||
|
@ -665,31 +655,23 @@ func (buffer *Buffer) Write(runes ...rune) {
|
|||
} else {
|
||||
|
||||
for int(buffer.CursorColumn()) >= len(line.cells) {
|
||||
line.cells = append(line.cells, NewBackgroundCell(buffer.cursorAttr.BgColour))
|
||||
line.cells = append(line.cells, Cell{})
|
||||
}
|
||||
|
||||
cell := &line.cells[buffer.CursorColumn()]
|
||||
cell.setRune(r)
|
||||
cell.attr = buffer.cursorAttr
|
||||
|
||||
}
|
||||
|
||||
if inc {
|
||||
buffer.incrementCursorPosition()
|
||||
}
|
||||
buffer.incrementCursorPosition()
|
||||
}
|
||||
}
|
||||
|
||||
func (buffer *Buffer) incrementCursorPosition() {
|
||||
|
||||
defer buffer.emitDisplayChange()
|
||||
|
||||
// we can increment one column past the end of the line.
|
||||
// this is effectively the beginning of the next line, except when we \r etc.
|
||||
if buffer.CursorColumn() < buffer.Width() { // if not at end of line
|
||||
|
||||
if buffer.CursorColumn() < buffer.Width() {
|
||||
buffer.cursorX++
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -708,7 +690,6 @@ func (buffer *Buffer) Backspace() {
|
|||
}
|
||||
|
||||
func (buffer *Buffer) CarriageReturn() {
|
||||
defer buffer.emitDisplayChange()
|
||||
|
||||
for {
|
||||
line := buffer.getCurrentLine()
|
||||
|
@ -726,7 +707,6 @@ func (buffer *Buffer) CarriageReturn() {
|
|||
}
|
||||
|
||||
func (buffer *Buffer) Tab() {
|
||||
defer buffer.emitDisplayChange()
|
||||
tabSize := 4
|
||||
shift := int(buffer.cursorX-1) % tabSize
|
||||
if shift == 0 {
|
||||
|
@ -738,7 +718,6 @@ func (buffer *Buffer) Tab() {
|
|||
}
|
||||
|
||||
func (buffer *Buffer) NewLine() {
|
||||
defer buffer.emitDisplayChange()
|
||||
|
||||
buffer.cursorX = 0
|
||||
buffer.Index()
|
||||
|
|
|
@ -11,10 +11,18 @@ import (
|
|||
|
||||
func TestOffsets(t *testing.T) {
|
||||
b := NewBuffer(10, 3, CellAttributes{})
|
||||
b.Write([]rune("hello\r\n")...)
|
||||
b.Write([]rune("hello\r\n")...)
|
||||
b.Write([]rune("hello\r\n")...)
|
||||
b.Write([]rune("hello\r\n")...)
|
||||
b.Write([]rune("hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello")...)
|
||||
assert.Equal(t, uint16(10), b.ViewWidth())
|
||||
assert.Equal(t, uint16(10), b.Width())
|
||||
|
@ -84,14 +92,14 @@ func TestWritingNewLineAsFirstRuneOnWrappedLine(t *testing.T) {
|
|||
b.Write('a', 'b', 'c')
|
||||
assert.Equal(t, uint16(3), b.cursorX)
|
||||
assert.Equal(t, uint16(0), b.cursorY)
|
||||
b.Write(0x0a)
|
||||
b.NewLine()
|
||||
assert.Equal(t, uint16(0), b.cursorX)
|
||||
assert.Equal(t, uint16(1), b.cursorY)
|
||||
|
||||
b.Write('d', 'e', 'f')
|
||||
assert.Equal(t, uint16(3), b.cursorX)
|
||||
assert.Equal(t, uint16(1), b.cursorY)
|
||||
b.Write(0x0a)
|
||||
b.NewLine()
|
||||
|
||||
assert.Equal(t, uint16(0), b.cursorX)
|
||||
assert.Equal(t, uint16(2), b.cursorY)
|
||||
|
@ -114,11 +122,11 @@ func TestWritingNewLineAsSecondRuneOnWrappedLine(t *testing.T) {
|
|||
*/
|
||||
|
||||
b.Write('a', 'b', 'c', 'd')
|
||||
b.Write(0x0a)
|
||||
b.NewLine()
|
||||
b.Write('e', 'f')
|
||||
b.Write(0x0a)
|
||||
b.Write(0x0a)
|
||||
b.Write(0x0a)
|
||||
b.NewLine()
|
||||
b.NewLine()
|
||||
b.NewLine()
|
||||
b.Write('z')
|
||||
|
||||
assert.Equal(t, "abc", b.lines[0].String())
|
||||
|
@ -170,19 +178,45 @@ func TestMovePosition(t *testing.T) {
|
|||
func TestVisibleLines(t *testing.T) {
|
||||
|
||||
b := NewBuffer(80, 10, CellAttributes{})
|
||||
b.Write([]rune("hello 1\r\n")...)
|
||||
b.Write([]rune("hello 2\r\n")...)
|
||||
b.Write([]rune("hello 3\r\n")...)
|
||||
b.Write([]rune("hello 4\r\n")...)
|
||||
b.Write([]rune("hello 5\r\n")...)
|
||||
b.Write([]rune("hello 6\r\n")...)
|
||||
b.Write([]rune("hello 7\r\n")...)
|
||||
b.Write([]rune("hello 8\r\n")...)
|
||||
b.Write([]rune("hello 9\r\n")...)
|
||||
b.Write([]rune("hello 10\r\n")...)
|
||||
b.Write([]rune("hello 11\r\n")...)
|
||||
b.Write([]rune("hello 12\r\n")...)
|
||||
b.Write([]rune("hello 13\r\n")...)
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 2")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 3")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 4")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 5")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 6")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 7")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 8")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 9")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 10")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 11")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 12")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 13")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 14")...)
|
||||
|
||||
lines := b.GetVisibleLines()
|
||||
|
@ -194,9 +228,13 @@ func TestVisibleLines(t *testing.T) {
|
|||
|
||||
func TestClearWithoutFullView(t *testing.T) {
|
||||
b := NewBuffer(80, 10, CellAttributes{})
|
||||
b.Write([]rune("hello 1\r\n")...)
|
||||
b.Write([]rune("hello 2\r\n")...)
|
||||
b.Write([]rune("hello 3")...)
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.Clear()
|
||||
lines := b.GetVisibleLines()
|
||||
for _, line := range lines {
|
||||
|
@ -206,14 +244,28 @@ func TestClearWithoutFullView(t *testing.T) {
|
|||
|
||||
func TestClearWithFullView(t *testing.T) {
|
||||
b := NewBuffer(80, 5, CellAttributes{})
|
||||
b.Write([]rune("hello 1\r\n")...)
|
||||
b.Write([]rune("hello 2\r\n")...)
|
||||
b.Write([]rune("hello 3\r\n")...)
|
||||
b.Write([]rune("hello 4\r\n")...)
|
||||
b.Write([]rune("hello 5\r\n")...)
|
||||
b.Write([]rune("hello 6\r\n")...)
|
||||
b.Write([]rune("hello 7\r\n")...)
|
||||
b.Write([]rune("hello 8\r\n")...)
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.Clear()
|
||||
lines := b.GetVisibleLines()
|
||||
for _, line := range lines {
|
||||
|
@ -241,7 +293,10 @@ func TestCarriageReturnOnFullLine(t *testing.T) {
|
|||
|
||||
func TestCarriageReturnOnFullLastLine(t *testing.T) {
|
||||
b := NewBuffer(20, 2, CellAttributes{})
|
||||
b.Write([]rune("\nabcdeabcdeabcdeabcde\rxxxxxxxxxxxxxxxxxxxx")...)
|
||||
b.NewLine()
|
||||
b.Write([]rune("abcdeabcdeabcdeabcde")...)
|
||||
b.CarriageReturn()
|
||||
b.Write([]rune("xxxxxxxxxxxxxxxxxxxx")...)
|
||||
lines := b.GetVisibleLines()
|
||||
assert.Equal(t, "", lines[0].String())
|
||||
assert.Equal(t, "xxxxxxxxxxxxxxxxxxxx", lines[1].String())
|
||||
|
@ -249,7 +304,10 @@ func TestCarriageReturnOnFullLastLine(t *testing.T) {
|
|||
|
||||
func TestCarriageReturnOnWrappedLine(t *testing.T) {
|
||||
b := NewBuffer(80, 6, CellAttributes{})
|
||||
b.Write([]rune("hello!\rsecret")...)
|
||||
b.Write([]rune("hello!")...)
|
||||
b.CarriageReturn()
|
||||
b.Write([]rune("secret")...)
|
||||
|
||||
lines := b.GetVisibleLines()
|
||||
assert.Equal(t, "secret", lines[0].String())
|
||||
}
|
||||
|
@ -257,14 +315,22 @@ func TestCarriageReturnOnWrappedLine(t *testing.T) {
|
|||
func TestCarriageReturnOnLineThatDoesntExist(t *testing.T) {
|
||||
b := NewBuffer(6, 10, CellAttributes{})
|
||||
b.cursorY = 3
|
||||
b.Write('\r')
|
||||
b.CarriageReturn()
|
||||
assert.Equal(t, uint16(0), b.cursorX)
|
||||
assert.Equal(t, uint16(3), b.cursorY)
|
||||
}
|
||||
|
||||
func TestGetCell(t *testing.T) {
|
||||
b := NewBuffer(80, 20, CellAttributes{})
|
||||
b.Write([]rune("Hello\r\nthere\r\nsomething...")...)
|
||||
b.Write([]rune("Hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
||||
b.Write([]rune("there")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
||||
b.Write([]rune("something...")...)
|
||||
cell := b.GetCell(8, 2)
|
||||
require.NotNil(t, cell)
|
||||
assert.Equal(t, 'g', cell.Rune())
|
||||
|
@ -272,7 +338,17 @@ func TestGetCell(t *testing.T) {
|
|||
|
||||
func TestGetCellWithHistory(t *testing.T) {
|
||||
b := NewBuffer(80, 2, CellAttributes{})
|
||||
b.Write([]rune("Hello\r\nthere\r\nsomething...")...)
|
||||
|
||||
b.Write([]rune("Hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
||||
b.Write([]rune("there")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
||||
b.Write([]rune("something...")...)
|
||||
|
||||
cell := b.GetCell(8, 1)
|
||||
require.NotNil(t, cell)
|
||||
assert.Equal(t, 'g', cell.Rune())
|
||||
|
@ -301,7 +377,35 @@ func TestCursorPositionQuerying(t *testing.T) {
|
|||
|
||||
func TestRawPositionQuerying(t *testing.T) {
|
||||
b := NewBuffer(80, 5, CellAttributes{})
|
||||
b.Write([]rune("a\r\na\r\na\r\na\r\na\r\na\r\na\r\na\r\na\r\na")...)
|
||||
b.Write([]rune("a")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("a")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("a")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("a")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("a")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("a")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("a")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("a")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("a")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("a")...)
|
||||
|
||||
b.cursorX = 3
|
||||
b.cursorY = 4
|
||||
assert.Equal(t, uint64(9), b.RawLine())
|
||||
|
@ -310,7 +414,10 @@ func TestRawPositionQuerying(t *testing.T) {
|
|||
// CSI 2 K
|
||||
func TestEraseLine(t *testing.T) {
|
||||
b := NewBuffer(80, 5, CellAttributes{})
|
||||
b.Write([]rune("hello, this is a test\r\nthis line should be deleted")...)
|
||||
b.Write([]rune("hello, this is a test")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("this line should be deleted")...)
|
||||
b.EraseLine()
|
||||
assert.Equal(t, "hello, this is a test", b.lines[0].String())
|
||||
assert.Equal(t, "", b.lines[1].String())
|
||||
|
@ -319,7 +426,11 @@ func TestEraseLine(t *testing.T) {
|
|||
// CSI 1 K
|
||||
func TestEraseLineToCursor(t *testing.T) {
|
||||
b := NewBuffer(80, 5, CellAttributes{})
|
||||
b.Write([]rune("hello, this is a test\r\ndeleted")...)
|
||||
b.Write([]rune("hello, this is a test")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("deleted")...)
|
||||
|
||||
b.MovePosition(-3, 0)
|
||||
b.EraseLineToCursor()
|
||||
assert.Equal(t, "hello, this is a test", b.lines[0].String())
|
||||
|
@ -329,7 +440,10 @@ func TestEraseLineToCursor(t *testing.T) {
|
|||
// CSI 0 K
|
||||
func TestEraseLineAfterCursor(t *testing.T) {
|
||||
b := NewBuffer(80, 5, CellAttributes{})
|
||||
b.Write([]rune("hello, this is a test\r\ndeleted")...)
|
||||
b.Write([]rune("hello, this is a test")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("deleted")...)
|
||||
b.MovePosition(-3, 0)
|
||||
b.EraseLineFromCursor()
|
||||
assert.Equal(t, "hello, this is a test", b.lines[0].String())
|
||||
|
@ -337,7 +451,13 @@ func TestEraseLineAfterCursor(t *testing.T) {
|
|||
}
|
||||
func TestEraseDisplay(t *testing.T) {
|
||||
b := NewBuffer(80, 5, CellAttributes{})
|
||||
b.Write([]rune("hello\r\nasdasd\r\nthing")...)
|
||||
b.Write([]rune("hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("asdasd")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("thing")...)
|
||||
b.MovePosition(2, 1)
|
||||
b.EraseDisplay()
|
||||
lines := b.GetVisibleLines()
|
||||
|
@ -347,7 +467,13 @@ func TestEraseDisplay(t *testing.T) {
|
|||
}
|
||||
func TestEraseDisplayToCursor(t *testing.T) {
|
||||
b := NewBuffer(80, 5, CellAttributes{})
|
||||
b.Write([]rune("hello\r\nasdasd\r\nthing")...)
|
||||
b.Write([]rune("hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("asdasd")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("thing")...)
|
||||
b.MovePosition(-2, 0)
|
||||
b.EraseDisplayToCursor()
|
||||
lines := b.GetVisibleLines()
|
||||
|
@ -359,7 +485,13 @@ func TestEraseDisplayToCursor(t *testing.T) {
|
|||
|
||||
func TestEraseDisplayFromCursor(t *testing.T) {
|
||||
b := NewBuffer(80, 5, CellAttributes{})
|
||||
b.Write([]rune("hello\r\nasdasd\r\nthings")...)
|
||||
b.Write([]rune("hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("asdasd")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("things")...)
|
||||
b.MovePosition(-3, -1)
|
||||
b.EraseDisplayFromCursor()
|
||||
lines := b.GetVisibleLines()
|
||||
|
@ -381,9 +513,12 @@ func TestHorizontalResizeView(t *testing.T) {
|
|||
b := NewBuffer(80, 10, CellAttributes{})
|
||||
|
||||
// 60 characters
|
||||
b.Write([]rune(
|
||||
`hellohellohellohellohellohellohellohellohellohellohellohello
|
||||
goodbyegoodbye`)...)
|
||||
b.Write([]rune(`hellohellohellohellohellohellohellohellohellohellohellohello`)...)
|
||||
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
||||
b.Write([]rune(`goodbyegoodbye`)...)
|
||||
|
||||
require.Equal(t, uint16(14), b.cursorX)
|
||||
require.Equal(t, uint16(1), b.cursorY)
|
||||
|
|
|
@ -40,10 +40,16 @@ func (cell *Cell) Rune() rune {
|
|||
}
|
||||
|
||||
func (cell *Cell) Fg() [3]float32 {
|
||||
if cell.Attr().Reverse {
|
||||
return cell.attr.BgColour
|
||||
}
|
||||
return cell.attr.FgColour
|
||||
}
|
||||
|
||||
func (cell *Cell) Bg() [3]float32 {
|
||||
if cell.Attr().Reverse {
|
||||
return cell.attr.FgColour
|
||||
}
|
||||
return cell.attr.BgColour
|
||||
}
|
||||
|
||||
|
|
|
@ -2,14 +2,15 @@ package glfont
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/draw"
|
||||
"io"
|
||||
|
||||
"github.com/go-gl/gl/all-core/gl"
|
||||
"github.com/golang/freetype"
|
||||
"github.com/golang/freetype/truetype"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/math/fixed"
|
||||
"image"
|
||||
"image/draw"
|
||||
"io"
|
||||
)
|
||||
|
||||
const DPI = 72
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -28,16 +28,16 @@ func (a *annotation) render(gui *GUI) {
|
|||
}
|
||||
cell := cells[x]
|
||||
|
||||
var colour *[3]float32
|
||||
var colour [3]float32 = cell.Fg()
|
||||
var alpha float32 = 0.6
|
||||
|
||||
if y == int(a.hint.StartY) {
|
||||
if x >= int(a.hint.StartX) && x <= int(a.hint.StartX+uint16(len(a.hint.Word))) {
|
||||
colour = &[3]float32{0.2, 1.0, 0.2}
|
||||
colour = [3]float32{0.2, 1.0, 0.2}
|
||||
alpha = 1.0
|
||||
}
|
||||
}
|
||||
gui.renderer.DrawCellText(cell, uint(x), uint(y), alpha, colour)
|
||||
gui.renderer.DrawCellText(string(cell.Rune()), uint(x), uint(y), alpha, colour, cell.Attr().Bold)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,73 +5,25 @@ import "github.com/liamg/aminal/glfont"
|
|||
type FontMap struct {
|
||||
defaultFont *glfont.Font
|
||||
defaultBoldFont *glfont.Font
|
||||
runeMap map[rune]*glfont.Font
|
||||
ranges map[runeRange]*glfont.Font
|
||||
}
|
||||
|
||||
type runeRange struct {
|
||||
start rune
|
||||
end rune // inclusive
|
||||
}
|
||||
|
||||
func NewFontMap(defaultFont *glfont.Font, defaultBoldFont *glfont.Font) *FontMap {
|
||||
return &FontMap{
|
||||
defaultFont: defaultFont,
|
||||
defaultBoldFont: defaultBoldFont,
|
||||
runeMap: map[rune]*glfont.Font{},
|
||||
ranges: map[runeRange]*glfont.Font{},
|
||||
}
|
||||
}
|
||||
|
||||
func (fm *FontMap) UpdateResolution(w int, h int) {
|
||||
fm.defaultFont.UpdateResolution(w, h)
|
||||
fm.defaultBoldFont.UpdateResolution(w, h)
|
||||
for _, f := range fm.ranges {
|
||||
f.UpdateResolution(w, h)
|
||||
}
|
||||
}
|
||||
|
||||
func (fm *FontMap) findOverride(r rune) *glfont.Font {
|
||||
|
||||
override, ok := fm.runeMap[r]
|
||||
if ok {
|
||||
return override
|
||||
}
|
||||
|
||||
for rr, f := range fm.ranges {
|
||||
if r >= rr.start && r <= rr.end {
|
||||
fm.runeMap[r] = f
|
||||
return f
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fm *FontMap) setOverrideRange(start rune, end rune, font *glfont.Font) {
|
||||
fm.ranges[runeRange{start: start, end: end}] = font
|
||||
}
|
||||
|
||||
func (fm *FontMap) GetFont(r rune) *glfont.Font {
|
||||
if r <= 0xff {
|
||||
return fm.defaultFont
|
||||
}
|
||||
|
||||
if f := fm.findOverride(r); f != nil {
|
||||
return f
|
||||
}
|
||||
func (fm *FontMap) DefaultFont() *glfont.Font {
|
||||
|
||||
return fm.defaultFont
|
||||
}
|
||||
|
||||
func (fm *FontMap) GetBoldFont(r rune) *glfont.Font {
|
||||
if r <= 0xff {
|
||||
return fm.defaultBoldFont
|
||||
}
|
||||
|
||||
if f := fm.findOverride(r); f != nil {
|
||||
return f
|
||||
}
|
||||
|
||||
func (fm *FontMap) BoldFont() *glfont.Font {
|
||||
return fm.defaultBoldFont
|
||||
}
|
||||
|
|
129
gui/gui.go
129
gui/gui.go
|
@ -205,6 +205,7 @@ func (gui *GUI) Render() error {
|
|||
}()
|
||||
|
||||
startTime := time.Now()
|
||||
showMessage := true
|
||||
|
||||
for !gui.window.ShouldClose() {
|
||||
|
||||
|
@ -223,58 +224,86 @@ func (gui *GUI) Render() error {
|
|||
lines := gui.terminal.GetVisibleLines()
|
||||
lineCount := int(gui.terminal.ActiveBuffer().ViewHeight())
|
||||
colCount := int(gui.terminal.ActiveBuffer().ViewWidth())
|
||||
cx := uint(gui.terminal.GetLogicalCursorX())
|
||||
cy := uint(gui.terminal.GetLogicalCursorY()) + uint(gui.terminal.GetScrollOffset())
|
||||
|
||||
var colour *config.Colour
|
||||
for y := 0; y < lineCount; y++ {
|
||||
for x := 0; x < colCount; x++ {
|
||||
if y < len(lines) {
|
||||
cells := lines[y].Cells()
|
||||
for x := 0; x < colCount; x++ {
|
||||
|
||||
cell := defaultCell
|
||||
|
||||
if y < len(lines) {
|
||||
cells := lines[y].Cells()
|
||||
if x < len(cells) {
|
||||
cell = cells[x]
|
||||
cursor := false
|
||||
if gui.terminal.Modes().ShowCursor {
|
||||
cursor = cx == uint(x) && cy == uint(y)
|
||||
}
|
||||
}
|
||||
|
||||
cursor := false
|
||||
if gui.terminal.Modes().ShowCursor {
|
||||
cx := uint(gui.terminal.GetLogicalCursorX())
|
||||
cy := uint(gui.terminal.GetLogicalCursorY())
|
||||
cy = cy + uint(gui.terminal.GetScrollOffset())
|
||||
cursor = cx == uint(x) && cy == uint(y)
|
||||
}
|
||||
if gui.terminal.ActiveBuffer().InSelection(uint16(x), uint16(y)) {
|
||||
colour = &gui.config.ColourScheme.Selection
|
||||
} else {
|
||||
colour = nil
|
||||
}
|
||||
|
||||
var colour *config.Colour
|
||||
cell := defaultCell
|
||||
if colour != nil || cursor || x < len(cells) {
|
||||
|
||||
if x < len(cells) {
|
||||
cell = cells[x]
|
||||
if cell.Image() != nil {
|
||||
gui.renderer.DrawCellImage(cell, uint(x), uint(y))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
gui.renderer.DrawCellBg(cell, uint(x), uint(y), cursor, colour, false)
|
||||
}
|
||||
|
||||
if gui.terminal.ActiveBuffer().InSelection(uint16(x), uint16(y)) {
|
||||
colour = &gui.config.ColourScheme.Selection
|
||||
}
|
||||
if cell.Image() != nil {
|
||||
gui.renderer.DrawCellImage(cell, uint(x), uint(y))
|
||||
} else {
|
||||
gui.renderer.DrawCellBg(cell, uint(x), uint(y), cursor, colour, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for y := 0; y < lineCount; y++ {
|
||||
for x := 0; x < colCount; x++ {
|
||||
|
||||
cell := defaultCell
|
||||
hasText := false
|
||||
if y < len(lines) {
|
||||
|
||||
if y < len(lines) {
|
||||
cells := lines[y].Cells()
|
||||
bufStr := ""
|
||||
bold := false
|
||||
dim := false
|
||||
col := 0
|
||||
colour := [3]float32{0, 0, 0}
|
||||
cells := lines[y].Cells()
|
||||
|
||||
for x := 0; x < colCount; x++ {
|
||||
if x < len(cells) {
|
||||
cell = cells[x]
|
||||
if cell.Rune() != 0 && cell.Rune() != 32 {
|
||||
hasText = true
|
||||
cell := cells[x]
|
||||
if bufStr != "" && (cell.Attr().Dim != dim || cell.Attr().Bold != bold || colour != cell.Fg()) {
|
||||
var alpha float32 = 1.0
|
||||
if dim {
|
||||
alpha = 0.5
|
||||
}
|
||||
gui.renderer.DrawCellText(bufStr, uint(col), uint(y), alpha, colour, bold)
|
||||
col = x
|
||||
bufStr = ""
|
||||
}
|
||||
dim = cell.Attr().Dim
|
||||
colour = cell.Fg()
|
||||
bold = cell.Attr().Bold
|
||||
r := cell.Rune()
|
||||
if r == 0 {
|
||||
r = ' '
|
||||
}
|
||||
bufStr += string(r)
|
||||
}
|
||||
}
|
||||
|
||||
if hasText {
|
||||
gui.renderer.DrawCellText(cell, uint(x), uint(y), 1.0, nil)
|
||||
if bufStr != "" {
|
||||
var alpha float32 = 1.0
|
||||
if dim {
|
||||
alpha = 0.5
|
||||
}
|
||||
gui.renderer.DrawCellText(bufStr, uint(col), uint(y), alpha, colour, bold)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
gui.renderOverlay()
|
||||
|
@ -295,22 +324,26 @@ Buffer Size: %d lines
|
|||
)
|
||||
}
|
||||
|
||||
if latestVersion != "" && time.Since(startTime) < time.Second*10 && gui.terminal.ActiveBuffer().RawLine() == 0 {
|
||||
time.AfterFunc(time.Second, gui.terminal.SetDirty)
|
||||
_, h := gui.terminal.GetSize()
|
||||
var msg string
|
||||
if version.Version == "" {
|
||||
msg = "You are using a development build of Aminal."
|
||||
if showMessage {
|
||||
if latestVersion != "" && time.Since(startTime) < time.Second*10 && gui.terminal.ActiveBuffer().RawLine() == 0 {
|
||||
time.AfterFunc(time.Second, gui.terminal.SetDirty)
|
||||
_, h := gui.terminal.GetSize()
|
||||
var msg string
|
||||
if version.Version == "" {
|
||||
msg = "You are using a development build of Aminal."
|
||||
} else {
|
||||
msg = fmt.Sprintf("Version %s of Aminal is now available.", strings.Replace(latestVersion, "v", "", -1))
|
||||
}
|
||||
gui.textbox(
|
||||
2,
|
||||
uint16(h-3),
|
||||
fmt.Sprintf("%s (%d)", msg, 10-int(time.Since(startTime).Seconds())),
|
||||
[3]float32{1, 1, 1},
|
||||
[3]float32{0, 0.5, 0},
|
||||
)
|
||||
} else {
|
||||
msg = fmt.Sprintf("Version %s of Aminal is now available.", strings.Replace(latestVersion, "v", "", -1))
|
||||
showMessage = false
|
||||
}
|
||||
gui.textbox(
|
||||
2,
|
||||
uint16(h-3),
|
||||
fmt.Sprintf("%s (%d)", msg, 10-int(time.Since(startTime).Seconds())),
|
||||
[3]float32{1, 1, 1},
|
||||
[3]float32{0, 0.5, 0},
|
||||
)
|
||||
}
|
||||
|
||||
gui.window.SwapBuffers()
|
||||
|
|
|
@ -161,7 +161,7 @@ func (r *OpenGLRenderer) SetArea(areaX int, areaY int, areaWidth int, areaHeight
|
|||
r.areaHeight = areaHeight
|
||||
r.areaX = areaX
|
||||
r.areaY = areaY
|
||||
f := r.fontMap.GetFont('X')
|
||||
f := r.fontMap.DefaultFont()
|
||||
_, r.cellHeight = f.MaxSize()
|
||||
r.cellWidth, _ = f.Size("X")
|
||||
//= f.LineHeight() // includes vertical padding
|
||||
|
@ -200,8 +200,6 @@ func (r *OpenGLRenderer) DrawCellBg(cell buffer.Cell, col uint, row uint, cursor
|
|||
|
||||
if cursor {
|
||||
bg = r.config.ColourScheme.Cursor
|
||||
} else if cell.Attr().Reverse {
|
||||
bg = cell.Fg()
|
||||
} else {
|
||||
bg = cell.Bg()
|
||||
}
|
||||
|
@ -215,32 +213,21 @@ func (r *OpenGLRenderer) DrawCellBg(cell buffer.Cell, col uint, row uint, cursor
|
|||
|
||||
}
|
||||
|
||||
func (r *OpenGLRenderer) DrawCellText(cell buffer.Cell, col uint, row uint, alpha float32, colour *[3]float32) {
|
||||
func (r *OpenGLRenderer) DrawCellText(text string, col uint, row uint, alpha float32, colour [3]float32, bold bool) {
|
||||
|
||||
var fg [3]float32
|
||||
|
||||
if colour != nil {
|
||||
fg = *colour
|
||||
} else if cell.Attr().Reverse {
|
||||
fg = cell.Bg()
|
||||
var f *glfont.Font
|
||||
if bold {
|
||||
f = r.fontMap.BoldFont()
|
||||
} else {
|
||||
fg = cell.Fg()
|
||||
f = r.fontMap.DefaultFont()
|
||||
}
|
||||
|
||||
f := r.fontMap.GetFont(cell.Rune())
|
||||
if cell.Attr().Bold {
|
||||
f = r.fontMap.GetBoldFont(cell.Rune())
|
||||
}
|
||||
|
||||
if cell.Attr().Dim {
|
||||
alpha = 0.5 * alpha
|
||||
}
|
||||
f.SetColor(fg[0], fg[1], fg[2], alpha)
|
||||
f.SetColor(colour[0], colour[1], colour[2], alpha)
|
||||
|
||||
x := float32(r.areaX) + float32(col)*r.cellWidth
|
||||
y := float32(r.areaY) + (float32(row+1) * r.cellHeight) + f.MinY()
|
||||
|
||||
f.Print(x, y, string(cell.Rune()))
|
||||
f.Print(x, y, text)
|
||||
}
|
||||
|
||||
func (r *OpenGLRenderer) DrawCellImage(cell buffer.Cell, col uint, row uint) {
|
||||
|
|
|
@ -89,7 +89,7 @@ DONE:
|
|||
|
||||
x := float32(col) * gui.renderer.cellWidth
|
||||
|
||||
f := gui.fontMap.GetFont('X')
|
||||
f := gui.fontMap.DefaultFont()
|
||||
f.SetColor(fg[0], fg[1], fg[2], 1)
|
||||
|
||||
for i, line := range lines {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package terminal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -27,21 +26,25 @@ var escapeSequenceMap = map[rune]escapeSequenceHandler{
|
|||
|
||||
func newLineSequenceHandler(pty chan rune, terminal *Terminal) error {
|
||||
terminal.ActiveBuffer().NewLine()
|
||||
terminal.isDirty = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func tabSequenceHandler(pty chan rune, terminal *Terminal) error {
|
||||
terminal.ActiveBuffer().Tab()
|
||||
terminal.isDirty = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func carriageReturnSequenceHandler(pty chan rune, terminal *Terminal) error {
|
||||
terminal.ActiveBuffer().CarriageReturn()
|
||||
terminal.isDirty = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func backspaceSequenceHandler(pty chan rune, terminal *Terminal) error {
|
||||
terminal.ActiveBuffer().Backspace()
|
||||
terminal.isDirty = true
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -65,47 +68,33 @@ func shiftInSequenceHandler(pty chan rune, terminal *Terminal) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (terminal *Terminal) processInput(ctx context.Context, pty chan rune) {
|
||||
func (terminal *Terminal) processInput(pty chan rune) {
|
||||
|
||||
// https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
|
||||
for {
|
||||
var b rune
|
||||
|
||||
select {
|
||||
case <-terminal.pauseChan:
|
||||
// @todo alert user when terminal is suspended
|
||||
terminal.logger.Debugf("Terminal suspended")
|
||||
<-terminal.resumeChan
|
||||
case <-ctx.Done():
|
||||
break
|
||||
default:
|
||||
}
|
||||
for {
|
||||
|
||||
if terminal.config.Slomo {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
|
||||
b := <-pty
|
||||
b = <-pty
|
||||
|
||||
terminal.logger.Debugf("0x%q", string(b))
|
||||
|
||||
handler, ok := escapeSequenceMap[b]
|
||||
|
||||
if ok {
|
||||
//terminal.logger.Debugf("Handling escape sequence: 0x%x", b)
|
||||
if err := handler(pty, terminal); err != nil {
|
||||
terminal.logger.Errorf("Error handling escape sequence: %s", err)
|
||||
}
|
||||
} else {
|
||||
//terminal.logger.Debugf("Received character 0x%X: %q", b, string(b))
|
||||
if b >= 0x20 {
|
||||
//terminal.logger.Debugf("%c", b)
|
||||
terminal.ActiveBuffer().Write(b)
|
||||
} else {
|
||||
terminal.logger.Error("Non-readable rune received: 0x%X", b)
|
||||
if b < 0x20 {
|
||||
if handler, ok := escapeSequenceMap[b]; ok {
|
||||
//terminal.logger.Debugf("Handling escape sequence: 0x%x", b)
|
||||
if err := handler(pty, terminal); err != nil {
|
||||
terminal.logger.Errorf("Error handling escape sequence: %s", err)
|
||||
}
|
||||
terminal.isDirty = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
//terminal.logger.Debugf("Received character 0x%X: %q", b, string(b))
|
||||
terminal.ActiveBuffer().Write(b)
|
||||
terminal.isDirty = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package terminal
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
@ -35,7 +34,7 @@ const (
|
|||
type Terminal struct {
|
||||
program uint32
|
||||
buffers []*buffer.Buffer
|
||||
activeBufferIndex uint8
|
||||
activeBuffer *buffer.Buffer
|
||||
lock sync.Mutex
|
||||
pty *os.File
|
||||
logger *zap.SugaredLogger
|
||||
|
@ -43,8 +42,6 @@ type Terminal struct {
|
|||
size Winsize
|
||||
config *config.Config
|
||||
titleHandlers []chan bool
|
||||
pauseChan chan bool
|
||||
resumeChan chan bool
|
||||
modes Modes
|
||||
mouseMode MouseMode
|
||||
bracketedPasteMode bool
|
||||
|
@ -87,13 +84,11 @@ func New(pty *os.File, logger *zap.SugaredLogger, config *config.Config) *Termin
|
|||
logger: logger,
|
||||
config: config,
|
||||
titleHandlers: []chan bool{},
|
||||
pauseChan: make(chan bool, 1),
|
||||
resumeChan: make(chan bool, 1),
|
||||
modes: Modes{
|
||||
ShowCursor: true,
|
||||
},
|
||||
}
|
||||
|
||||
t.activeBuffer = t.buffers[0]
|
||||
return t
|
||||
|
||||
}
|
||||
|
@ -129,32 +124,30 @@ func (terminal *Terminal) GetMouseMode() MouseMode {
|
|||
}
|
||||
|
||||
func (terminal *Terminal) UseMainBuffer() {
|
||||
terminal.activeBufferIndex = MainBuffer
|
||||
terminal.activeBuffer = terminal.buffers[MainBuffer]
|
||||
terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height))
|
||||
}
|
||||
|
||||
func (terminal *Terminal) UseAltBuffer() {
|
||||
terminal.activeBufferIndex = AltBuffer
|
||||
terminal.activeBuffer = terminal.buffers[AltBuffer]
|
||||
terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height))
|
||||
}
|
||||
|
||||
func (terminal *Terminal) UseInternalBuffer() {
|
||||
terminal.pauseChan <- true
|
||||
terminal.activeBufferIndex = InternalBuffer
|
||||
terminal.activeBuffer = terminal.buffers[InternalBuffer]
|
||||
terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height))
|
||||
}
|
||||
|
||||
func (terminal *Terminal) ExitInternalBuffer() {
|
||||
terminal.activeBufferIndex = terminal.lastBuffer
|
||||
terminal.resumeChan <- true
|
||||
terminal.activeBuffer = terminal.buffers[terminal.lastBuffer]
|
||||
}
|
||||
|
||||
func (terminal *Terminal) ActiveBuffer() *buffer.Buffer {
|
||||
return terminal.buffers[terminal.activeBufferIndex]
|
||||
return terminal.activeBuffer
|
||||
}
|
||||
|
||||
func (terminal *Terminal) UsingMainBuffer() bool {
|
||||
return terminal.activeBufferIndex == MainBuffer
|
||||
return terminal.activeBuffer == terminal.buffers[MainBuffer]
|
||||
}
|
||||
|
||||
func (terminal *Terminal) GetScrollOffset() uint {
|
||||
|
@ -255,20 +248,17 @@ func (terminal *Terminal) Read() error {
|
|||
buffer := make(chan rune, 0xffff)
|
||||
|
||||
reader := bufio.NewReader(terminal.pty)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
go terminal.processInput(ctx, buffer)
|
||||
go terminal.processInput(buffer)
|
||||
for {
|
||||
r, size, err := reader.ReadRune()
|
||||
r, _, err := reader.ReadRune()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return err
|
||||
} else if size > 0 {
|
||||
buffer <- r
|
||||
}
|
||||
buffer <- r
|
||||
}
|
||||
|
||||
//clean exit
|
||||
|
|
Loading…
Reference in New Issue