more efficient text rendering and streamlining of output handling

This commit is contained in:
Liam Galvin 2018-12-01 22:09:20 +00:00
parent 1a932fa2c3
commit 177e928b71
13 changed files with 323 additions and 269 deletions

View File

@ -22,12 +22,6 @@ install-tools:
which dep || curl -L https://raw.githubusercontent.com/golang/dep/master/install.sh | sh which dep || curl -L https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
which packr || go get -u github.com/gobuffalo/packr/packr 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 .PHONY: build-linux
build-linux: build-linux:
mkdir -p bin/linux mkdir -p bin/linux

View File

@ -610,20 +610,10 @@ func (buffer *Buffer) ReverseIndex() {
func (buffer *Buffer) Write(runes ...rune) { func (buffer *Buffer) Write(runes ...rune) {
// scroll to bottom on input // scroll to bottom on input
inc := true
buffer.scrollLinesFromBottom = 0 buffer.scrollLinesFromBottom = 0
for _, r := range runes { 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() line := buffer.getCurrentLine()
if buffer.replaceMode { if buffer.replaceMode {
@ -634,7 +624,7 @@ func (buffer *Buffer) Write(runes ...rune) {
} }
for int(buffer.CursorColumn()) >= len(line.cells) { 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].attr = buffer.cursorAttr
line.cells[buffer.cursorX].setRune(r) line.cells[buffer.cursorX].setRune(r)
@ -665,31 +655,23 @@ func (buffer *Buffer) Write(runes ...rune) {
} else { } else {
for int(buffer.CursorColumn()) >= len(line.cells) { 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 := &line.cells[buffer.CursorColumn()]
cell.setRune(r) cell.setRune(r)
cell.attr = buffer.cursorAttr cell.attr = buffer.cursorAttr
} }
if inc {
buffer.incrementCursorPosition() buffer.incrementCursorPosition()
} }
}
} }
func (buffer *Buffer) incrementCursorPosition() { func (buffer *Buffer) incrementCursorPosition() {
defer buffer.emitDisplayChange()
// we can increment one column past the end of the line. // 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. // 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++ buffer.cursorX++
} }
} }
@ -708,7 +690,6 @@ func (buffer *Buffer) Backspace() {
} }
func (buffer *Buffer) CarriageReturn() { func (buffer *Buffer) CarriageReturn() {
defer buffer.emitDisplayChange()
for { for {
line := buffer.getCurrentLine() line := buffer.getCurrentLine()
@ -726,7 +707,6 @@ func (buffer *Buffer) CarriageReturn() {
} }
func (buffer *Buffer) Tab() { func (buffer *Buffer) Tab() {
defer buffer.emitDisplayChange()
tabSize := 4 tabSize := 4
shift := int(buffer.cursorX-1) % tabSize shift := int(buffer.cursorX-1) % tabSize
if shift == 0 { if shift == 0 {
@ -738,7 +718,6 @@ func (buffer *Buffer) Tab() {
} }
func (buffer *Buffer) NewLine() { func (buffer *Buffer) NewLine() {
defer buffer.emitDisplayChange()
buffer.cursorX = 0 buffer.cursorX = 0
buffer.Index() buffer.Index()

View File

@ -11,10 +11,18 @@ import (
func TestOffsets(t *testing.T) { func TestOffsets(t *testing.T) {
b := NewBuffer(10, 3, CellAttributes{}) b := NewBuffer(10, 3, CellAttributes{})
b.Write([]rune("hello\r\n")...) b.Write([]rune("hello")...)
b.Write([]rune("hello\r\n")...) b.CarriageReturn()
b.Write([]rune("hello\r\n")...) b.NewLine()
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.Write([]rune("hello")...)
assert.Equal(t, uint16(10), b.ViewWidth()) assert.Equal(t, uint16(10), b.ViewWidth())
assert.Equal(t, uint16(10), b.Width()) assert.Equal(t, uint16(10), b.Width())
@ -84,14 +92,14 @@ func TestWritingNewLineAsFirstRuneOnWrappedLine(t *testing.T) {
b.Write('a', 'b', 'c') b.Write('a', 'b', 'c')
assert.Equal(t, uint16(3), b.cursorX) assert.Equal(t, uint16(3), b.cursorX)
assert.Equal(t, uint16(0), b.cursorY) assert.Equal(t, uint16(0), b.cursorY)
b.Write(0x0a) b.NewLine()
assert.Equal(t, uint16(0), b.cursorX) assert.Equal(t, uint16(0), b.cursorX)
assert.Equal(t, uint16(1), b.cursorY) assert.Equal(t, uint16(1), b.cursorY)
b.Write('d', 'e', 'f') b.Write('d', 'e', 'f')
assert.Equal(t, uint16(3), b.cursorX) assert.Equal(t, uint16(3), b.cursorX)
assert.Equal(t, uint16(1), b.cursorY) assert.Equal(t, uint16(1), b.cursorY)
b.Write(0x0a) b.NewLine()
assert.Equal(t, uint16(0), b.cursorX) assert.Equal(t, uint16(0), b.cursorX)
assert.Equal(t, uint16(2), b.cursorY) assert.Equal(t, uint16(2), b.cursorY)
@ -114,11 +122,11 @@ func TestWritingNewLineAsSecondRuneOnWrappedLine(t *testing.T) {
*/ */
b.Write('a', 'b', 'c', 'd') b.Write('a', 'b', 'c', 'd')
b.Write(0x0a) b.NewLine()
b.Write('e', 'f') b.Write('e', 'f')
b.Write(0x0a) b.NewLine()
b.Write(0x0a) b.NewLine()
b.Write(0x0a) b.NewLine()
b.Write('z') b.Write('z')
assert.Equal(t, "abc", b.lines[0].String()) assert.Equal(t, "abc", b.lines[0].String())
@ -170,19 +178,45 @@ func TestMovePosition(t *testing.T) {
func TestVisibleLines(t *testing.T) { func TestVisibleLines(t *testing.T) {
b := NewBuffer(80, 10, CellAttributes{}) b := NewBuffer(80, 10, CellAttributes{})
b.Write([]rune("hello 1\r\n")...) b.Write([]rune("hello 1")...)
b.Write([]rune("hello 2\r\n")...) b.CarriageReturn()
b.Write([]rune("hello 3\r\n")...) b.NewLine()
b.Write([]rune("hello 4\r\n")...) b.Write([]rune("hello 2")...)
b.Write([]rune("hello 5\r\n")...) b.CarriageReturn()
b.Write([]rune("hello 6\r\n")...) b.NewLine()
b.Write([]rune("hello 7\r\n")...) b.Write([]rune("hello 3")...)
b.Write([]rune("hello 8\r\n")...) b.CarriageReturn()
b.Write([]rune("hello 9\r\n")...) b.NewLine()
b.Write([]rune("hello 10\r\n")...) b.Write([]rune("hello 4")...)
b.Write([]rune("hello 11\r\n")...) b.CarriageReturn()
b.Write([]rune("hello 12\r\n")...) b.NewLine()
b.Write([]rune("hello 13\r\n")...) 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")...) b.Write([]rune("hello 14")...)
lines := b.GetVisibleLines() lines := b.GetVisibleLines()
@ -194,9 +228,13 @@ func TestVisibleLines(t *testing.T) {
func TestClearWithoutFullView(t *testing.T) { func TestClearWithoutFullView(t *testing.T) {
b := NewBuffer(80, 10, CellAttributes{}) b := NewBuffer(80, 10, CellAttributes{})
b.Write([]rune("hello 1\r\n")...) b.Write([]rune("hello 1")...)
b.Write([]rune("hello 2\r\n")...) b.CarriageReturn()
b.Write([]rune("hello 3")...) b.NewLine()
b.Write([]rune("hello 1")...)
b.CarriageReturn()
b.NewLine()
b.Write([]rune("hello 1")...)
b.Clear() b.Clear()
lines := b.GetVisibleLines() lines := b.GetVisibleLines()
for _, line := range lines { for _, line := range lines {
@ -206,14 +244,28 @@ func TestClearWithoutFullView(t *testing.T) {
func TestClearWithFullView(t *testing.T) { func TestClearWithFullView(t *testing.T) {
b := NewBuffer(80, 5, CellAttributes{}) b := NewBuffer(80, 5, CellAttributes{})
b.Write([]rune("hello 1\r\n")...) b.Write([]rune("hello 1")...)
b.Write([]rune("hello 2\r\n")...) b.CarriageReturn()
b.Write([]rune("hello 3\r\n")...) b.NewLine()
b.Write([]rune("hello 4\r\n")...) b.Write([]rune("hello 1")...)
b.Write([]rune("hello 5\r\n")...) b.CarriageReturn()
b.Write([]rune("hello 6\r\n")...) b.NewLine()
b.Write([]rune("hello 7\r\n")...) b.Write([]rune("hello 1")...)
b.Write([]rune("hello 8\r\n")...) 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() b.Clear()
lines := b.GetVisibleLines() lines := b.GetVisibleLines()
for _, line := range lines { for _, line := range lines {
@ -241,7 +293,10 @@ func TestCarriageReturnOnFullLine(t *testing.T) {
func TestCarriageReturnOnFullLastLine(t *testing.T) { func TestCarriageReturnOnFullLastLine(t *testing.T) {
b := NewBuffer(20, 2, CellAttributes{}) 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() lines := b.GetVisibleLines()
assert.Equal(t, "", lines[0].String()) assert.Equal(t, "", lines[0].String())
assert.Equal(t, "xxxxxxxxxxxxxxxxxxxx", lines[1].String()) assert.Equal(t, "xxxxxxxxxxxxxxxxxxxx", lines[1].String())
@ -249,7 +304,10 @@ func TestCarriageReturnOnFullLastLine(t *testing.T) {
func TestCarriageReturnOnWrappedLine(t *testing.T) { func TestCarriageReturnOnWrappedLine(t *testing.T) {
b := NewBuffer(80, 6, CellAttributes{}) b := NewBuffer(80, 6, CellAttributes{})
b.Write([]rune("hello!\rsecret")...) b.Write([]rune("hello!")...)
b.CarriageReturn()
b.Write([]rune("secret")...)
lines := b.GetVisibleLines() lines := b.GetVisibleLines()
assert.Equal(t, "secret", lines[0].String()) assert.Equal(t, "secret", lines[0].String())
} }
@ -257,14 +315,22 @@ func TestCarriageReturnOnWrappedLine(t *testing.T) {
func TestCarriageReturnOnLineThatDoesntExist(t *testing.T) { func TestCarriageReturnOnLineThatDoesntExist(t *testing.T) {
b := NewBuffer(6, 10, CellAttributes{}) b := NewBuffer(6, 10, CellAttributes{})
b.cursorY = 3 b.cursorY = 3
b.Write('\r') b.CarriageReturn()
assert.Equal(t, uint16(0), b.cursorX) assert.Equal(t, uint16(0), b.cursorX)
assert.Equal(t, uint16(3), b.cursorY) assert.Equal(t, uint16(3), b.cursorY)
} }
func TestGetCell(t *testing.T) { func TestGetCell(t *testing.T) {
b := NewBuffer(80, 20, CellAttributes{}) 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) cell := b.GetCell(8, 2)
require.NotNil(t, cell) require.NotNil(t, cell)
assert.Equal(t, 'g', cell.Rune()) assert.Equal(t, 'g', cell.Rune())
@ -272,7 +338,17 @@ func TestGetCell(t *testing.T) {
func TestGetCellWithHistory(t *testing.T) { func TestGetCellWithHistory(t *testing.T) {
b := NewBuffer(80, 2, CellAttributes{}) 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) cell := b.GetCell(8, 1)
require.NotNil(t, cell) require.NotNil(t, cell)
assert.Equal(t, 'g', cell.Rune()) assert.Equal(t, 'g', cell.Rune())
@ -301,7 +377,35 @@ func TestCursorPositionQuerying(t *testing.T) {
func TestRawPositionQuerying(t *testing.T) { func TestRawPositionQuerying(t *testing.T) {
b := NewBuffer(80, 5, CellAttributes{}) 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.cursorX = 3
b.cursorY = 4 b.cursorY = 4
assert.Equal(t, uint64(9), b.RawLine()) assert.Equal(t, uint64(9), b.RawLine())
@ -310,7 +414,10 @@ func TestRawPositionQuerying(t *testing.T) {
// CSI 2 K // CSI 2 K
func TestEraseLine(t *testing.T) { func TestEraseLine(t *testing.T) {
b := NewBuffer(80, 5, CellAttributes{}) 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() b.EraseLine()
assert.Equal(t, "hello, this is a test", b.lines[0].String()) assert.Equal(t, "hello, this is a test", b.lines[0].String())
assert.Equal(t, "", b.lines[1].String()) assert.Equal(t, "", b.lines[1].String())
@ -319,7 +426,11 @@ func TestEraseLine(t *testing.T) {
// CSI 1 K // CSI 1 K
func TestEraseLineToCursor(t *testing.T) { func TestEraseLineToCursor(t *testing.T) {
b := NewBuffer(80, 5, CellAttributes{}) 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.MovePosition(-3, 0)
b.EraseLineToCursor() b.EraseLineToCursor()
assert.Equal(t, "hello, this is a test", b.lines[0].String()) assert.Equal(t, "hello, this is a test", b.lines[0].String())
@ -329,7 +440,10 @@ func TestEraseLineToCursor(t *testing.T) {
// CSI 0 K // CSI 0 K
func TestEraseLineAfterCursor(t *testing.T) { func TestEraseLineAfterCursor(t *testing.T) {
b := NewBuffer(80, 5, CellAttributes{}) 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.MovePosition(-3, 0)
b.EraseLineFromCursor() b.EraseLineFromCursor()
assert.Equal(t, "hello, this is a test", b.lines[0].String()) 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) { func TestEraseDisplay(t *testing.T) {
b := NewBuffer(80, 5, CellAttributes{}) 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.MovePosition(2, 1)
b.EraseDisplay() b.EraseDisplay()
lines := b.GetVisibleLines() lines := b.GetVisibleLines()
@ -347,7 +467,13 @@ func TestEraseDisplay(t *testing.T) {
} }
func TestEraseDisplayToCursor(t *testing.T) { func TestEraseDisplayToCursor(t *testing.T) {
b := NewBuffer(80, 5, CellAttributes{}) 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.MovePosition(-2, 0)
b.EraseDisplayToCursor() b.EraseDisplayToCursor()
lines := b.GetVisibleLines() lines := b.GetVisibleLines()
@ -359,7 +485,13 @@ func TestEraseDisplayToCursor(t *testing.T) {
func TestEraseDisplayFromCursor(t *testing.T) { func TestEraseDisplayFromCursor(t *testing.T) {
b := NewBuffer(80, 5, CellAttributes{}) 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.MovePosition(-3, -1)
b.EraseDisplayFromCursor() b.EraseDisplayFromCursor()
lines := b.GetVisibleLines() lines := b.GetVisibleLines()
@ -381,9 +513,12 @@ func TestHorizontalResizeView(t *testing.T) {
b := NewBuffer(80, 10, CellAttributes{}) b := NewBuffer(80, 10, CellAttributes{})
// 60 characters // 60 characters
b.Write([]rune( b.Write([]rune(`hellohellohellohellohellohellohellohellohellohellohellohello`)...)
`hellohellohellohellohellohellohellohellohellohellohellohello
goodbyegoodbye`)...) b.CarriageReturn()
b.NewLine()
b.Write([]rune(`goodbyegoodbye`)...)
require.Equal(t, uint16(14), b.cursorX) require.Equal(t, uint16(14), b.cursorX)
require.Equal(t, uint16(1), b.cursorY) require.Equal(t, uint16(1), b.cursorY)

View File

@ -40,10 +40,16 @@ func (cell *Cell) Rune() rune {
} }
func (cell *Cell) Fg() [3]float32 { func (cell *Cell) Fg() [3]float32 {
if cell.Attr().Reverse {
return cell.attr.BgColour
}
return cell.attr.FgColour return cell.attr.FgColour
} }
func (cell *Cell) Bg() [3]float32 { func (cell *Cell) Bg() [3]float32 {
if cell.Attr().Reverse {
return cell.attr.FgColour
}
return cell.attr.BgColour return cell.attr.BgColour
} }

View File

@ -2,14 +2,15 @@ package glfont
import ( import (
"fmt" "fmt"
"image"
"image/draw"
"io"
"github.com/go-gl/gl/all-core/gl" "github.com/go-gl/gl/all-core/gl"
"github.com/golang/freetype" "github.com/golang/freetype"
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
"golang.org/x/image/font" "golang.org/x/image/font"
"golang.org/x/image/math/fixed" "golang.org/x/image/math/fixed"
"image"
"image/draw"
"io"
) )
const DPI = 72 const DPI = 72

File diff suppressed because one or more lines are too long

View File

@ -28,16 +28,16 @@ func (a *annotation) render(gui *GUI) {
} }
cell := cells[x] cell := cells[x]
var colour *[3]float32 var colour [3]float32 = cell.Fg()
var alpha float32 = 0.6 var alpha float32 = 0.6
if y == int(a.hint.StartY) { if y == int(a.hint.StartY) {
if x >= int(a.hint.StartX) && x <= int(a.hint.StartX+uint16(len(a.hint.Word))) { 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 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)
} }
} }

View File

@ -5,73 +5,25 @@ import "github.com/liamg/aminal/glfont"
type FontMap struct { type FontMap struct {
defaultFont *glfont.Font defaultFont *glfont.Font
defaultBoldFont *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 { func NewFontMap(defaultFont *glfont.Font, defaultBoldFont *glfont.Font) *FontMap {
return &FontMap{ return &FontMap{
defaultFont: defaultFont, defaultFont: defaultFont,
defaultBoldFont: defaultBoldFont, defaultBoldFont: defaultBoldFont,
runeMap: map[rune]*glfont.Font{},
ranges: map[runeRange]*glfont.Font{},
} }
} }
func (fm *FontMap) UpdateResolution(w int, h int) { func (fm *FontMap) UpdateResolution(w int, h int) {
fm.defaultFont.UpdateResolution(w, h) fm.defaultFont.UpdateResolution(w, h)
fm.defaultBoldFont.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 { func (fm *FontMap) DefaultFont() *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
}
return fm.defaultFont return fm.defaultFont
} }
func (fm *FontMap) GetBoldFont(r rune) *glfont.Font { func (fm *FontMap) BoldFont() *glfont.Font {
if r <= 0xff {
return fm.defaultBoldFont
}
if f := fm.findOverride(r); f != nil {
return f
}
return fm.defaultBoldFont return fm.defaultBoldFont
} }

View File

@ -205,6 +205,7 @@ func (gui *GUI) Render() error {
}() }()
startTime := time.Now() startTime := time.Now()
showMessage := true
for !gui.window.ShouldClose() { for !gui.window.ShouldClose() {
@ -223,58 +224,86 @@ func (gui *GUI) Render() error {
lines := gui.terminal.GetVisibleLines() lines := gui.terminal.GetVisibleLines()
lineCount := int(gui.terminal.ActiveBuffer().ViewHeight()) lineCount := int(gui.terminal.ActiveBuffer().ViewHeight())
colCount := int(gui.terminal.ActiveBuffer().ViewWidth()) 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 y := 0; y < lineCount; y++ {
for x := 0; x < colCount; x++ {
cell := defaultCell
if y < len(lines) { if y < len(lines) {
cells := lines[y].Cells() cells := lines[y].Cells()
if x < len(cells) { for x := 0; x < colCount; x++ {
cell = cells[x]
}
}
cursor := false cursor := false
if gui.terminal.Modes().ShowCursor { 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) cursor = cx == uint(x) && cy == uint(y)
} }
var colour *config.Colour
if gui.terminal.ActiveBuffer().InSelection(uint16(x), uint16(y)) { if gui.terminal.ActiveBuffer().InSelection(uint16(x), uint16(y)) {
colour = &gui.config.ColourScheme.Selection colour = &gui.config.ColourScheme.Selection
}
if cell.Image() != nil {
gui.renderer.DrawCellImage(cell, uint(x), uint(y))
} else { } else {
gui.renderer.DrawCellBg(cell, uint(x), uint(y), cursor, colour, false) colour = nil
} }
}
}
for y := 0; y < lineCount; y++ {
for x := 0; x < colCount; x++ {
cell := defaultCell cell := defaultCell
hasText := false if colour != nil || cursor || x < len(cells) {
if y < len(lines) {
cells := lines[y].Cells()
if x < len(cells) { if x < len(cells) {
cell = cells[x] cell = cells[x]
if cell.Rune() != 0 && cell.Rune() != 32 { if cell.Image() != nil {
hasText = true gui.renderer.DrawCellImage(cell, uint(x), uint(y))
continue
}
}
gui.renderer.DrawCellBg(cell, uint(x), uint(y), cursor, colour, false)
}
} }
} }
} }
if hasText { for y := 0; y < lineCount; y++ {
gui.renderer.DrawCellText(cell, uint(x), uint(y), 1.0, nil)
if y < len(lines) {
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 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 bufStr != "" {
var alpha float32 = 1.0
if dim {
alpha = 0.5
}
gui.renderer.DrawCellText(bufStr, uint(col), uint(y), alpha, colour, bold)
}
}
} }
gui.renderOverlay() gui.renderOverlay()
@ -295,6 +324,7 @@ Buffer Size: %d lines
) )
} }
if showMessage {
if latestVersion != "" && time.Since(startTime) < time.Second*10 && gui.terminal.ActiveBuffer().RawLine() == 0 { if latestVersion != "" && time.Since(startTime) < time.Second*10 && gui.terminal.ActiveBuffer().RawLine() == 0 {
time.AfterFunc(time.Second, gui.terminal.SetDirty) time.AfterFunc(time.Second, gui.terminal.SetDirty)
_, h := gui.terminal.GetSize() _, h := gui.terminal.GetSize()
@ -311,6 +341,9 @@ Buffer Size: %d lines
[3]float32{1, 1, 1}, [3]float32{1, 1, 1},
[3]float32{0, 0.5, 0}, [3]float32{0, 0.5, 0},
) )
} else {
showMessage = false
}
} }
gui.window.SwapBuffers() gui.window.SwapBuffers()

View File

@ -161,7 +161,7 @@ func (r *OpenGLRenderer) SetArea(areaX int, areaY int, areaWidth int, areaHeight
r.areaHeight = areaHeight r.areaHeight = areaHeight
r.areaX = areaX r.areaX = areaX
r.areaY = areaY r.areaY = areaY
f := r.fontMap.GetFont('X') f := r.fontMap.DefaultFont()
_, r.cellHeight = f.MaxSize() _, r.cellHeight = f.MaxSize()
r.cellWidth, _ = f.Size("X") r.cellWidth, _ = f.Size("X")
//= f.LineHeight() // includes vertical padding //= f.LineHeight() // includes vertical padding
@ -200,8 +200,6 @@ func (r *OpenGLRenderer) DrawCellBg(cell buffer.Cell, col uint, row uint, cursor
if cursor { if cursor {
bg = r.config.ColourScheme.Cursor bg = r.config.ColourScheme.Cursor
} else if cell.Attr().Reverse {
bg = cell.Fg()
} else { } else {
bg = cell.Bg() 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 var f *glfont.Font
if bold {
if colour != nil { f = r.fontMap.BoldFont()
fg = *colour
} else if cell.Attr().Reverse {
fg = cell.Bg()
} else { } else {
fg = cell.Fg() f = r.fontMap.DefaultFont()
} }
f := r.fontMap.GetFont(cell.Rune()) f.SetColor(colour[0], colour[1], colour[2], alpha)
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)
x := float32(r.areaX) + float32(col)*r.cellWidth x := float32(r.areaX) + float32(col)*r.cellWidth
y := float32(r.areaY) + (float32(row+1) * r.cellHeight) + f.MinY() 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) { func (r *OpenGLRenderer) DrawCellImage(cell buffer.Cell, col uint, row uint) {

View File

@ -89,7 +89,7 @@ DONE:
x := float32(col) * gui.renderer.cellWidth x := float32(col) * gui.renderer.cellWidth
f := gui.fontMap.GetFont('X') f := gui.fontMap.DefaultFont()
f.SetColor(fg[0], fg[1], fg[2], 1) f.SetColor(fg[0], fg[1], fg[2], 1)
for i, line := range lines { for i, line := range lines {

View File

@ -1,7 +1,6 @@
package terminal package terminal
import ( import (
"context"
"time" "time"
) )
@ -27,21 +26,25 @@ var escapeSequenceMap = map[rune]escapeSequenceHandler{
func newLineSequenceHandler(pty chan rune, terminal *Terminal) error { func newLineSequenceHandler(pty chan rune, terminal *Terminal) error {
terminal.ActiveBuffer().NewLine() terminal.ActiveBuffer().NewLine()
terminal.isDirty = true
return nil return nil
} }
func tabSequenceHandler(pty chan rune, terminal *Terminal) error { func tabSequenceHandler(pty chan rune, terminal *Terminal) error {
terminal.ActiveBuffer().Tab() terminal.ActiveBuffer().Tab()
terminal.isDirty = true
return nil return nil
} }
func carriageReturnSequenceHandler(pty chan rune, terminal *Terminal) error { func carriageReturnSequenceHandler(pty chan rune, terminal *Terminal) error {
terminal.ActiveBuffer().CarriageReturn() terminal.ActiveBuffer().CarriageReturn()
terminal.isDirty = true
return nil return nil
} }
func backspaceSequenceHandler(pty chan rune, terminal *Terminal) error { func backspaceSequenceHandler(pty chan rune, terminal *Terminal) error {
terminal.ActiveBuffer().Backspace() terminal.ActiveBuffer().Backspace()
terminal.isDirty = true
return nil return nil
} }
@ -65,47 +68,33 @@ func shiftInSequenceHandler(pty chan rune, terminal *Terminal) error {
return nil 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 // https://en.wikipedia.org/wiki/ANSI_escape_code
for { var b rune
select { for {
case <-terminal.pauseChan:
// @todo alert user when terminal is suspended
terminal.logger.Debugf("Terminal suspended")
<-terminal.resumeChan
case <-ctx.Done():
break
default:
}
if terminal.config.Slomo { if terminal.config.Slomo {
time.Sleep(time.Millisecond * 100) time.Sleep(time.Millisecond * 100)
} }
b := <-pty b = <-pty
terminal.logger.Debugf("0x%q", string(b)) if b < 0x20 {
if handler, ok := escapeSequenceMap[b]; ok {
handler, ok := escapeSequenceMap[b]
if ok {
//terminal.logger.Debugf("Handling escape sequence: 0x%x", b) //terminal.logger.Debugf("Handling escape sequence: 0x%x", b)
if err := handler(pty, terminal); err != nil { if err := handler(pty, terminal); err != nil {
terminal.logger.Errorf("Error handling escape sequence: %s", err) terminal.logger.Errorf("Error handling escape sequence: %s", err)
} }
} else { terminal.isDirty = true
//terminal.logger.Debugf("Received character 0x%X: %q", b, string(b)) continue
if b >= 0x20 {
//terminal.logger.Debugf("%c", b)
terminal.ActiveBuffer().Write(b)
} else {
terminal.logger.Error("Non-readable rune received: 0x%X", b)
} }
} }
//terminal.logger.Debugf("Received character 0x%X: %q", b, string(b))
terminal.ActiveBuffer().Write(b)
terminal.isDirty = true terminal.isDirty = true
} }
} }

View File

@ -2,7 +2,6 @@ package terminal
import ( import (
"bufio" "bufio"
"context"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -35,7 +34,7 @@ const (
type Terminal struct { type Terminal struct {
program uint32 program uint32
buffers []*buffer.Buffer buffers []*buffer.Buffer
activeBufferIndex uint8 activeBuffer *buffer.Buffer
lock sync.Mutex lock sync.Mutex
pty *os.File pty *os.File
logger *zap.SugaredLogger logger *zap.SugaredLogger
@ -43,8 +42,6 @@ type Terminal struct {
size Winsize size Winsize
config *config.Config config *config.Config
titleHandlers []chan bool titleHandlers []chan bool
pauseChan chan bool
resumeChan chan bool
modes Modes modes Modes
mouseMode MouseMode mouseMode MouseMode
bracketedPasteMode bool bracketedPasteMode bool
@ -87,13 +84,11 @@ func New(pty *os.File, logger *zap.SugaredLogger, config *config.Config) *Termin
logger: logger, logger: logger,
config: config, config: config,
titleHandlers: []chan bool{}, titleHandlers: []chan bool{},
pauseChan: make(chan bool, 1),
resumeChan: make(chan bool, 1),
modes: Modes{ modes: Modes{
ShowCursor: true, ShowCursor: true,
}, },
} }
t.activeBuffer = t.buffers[0]
return t return t
} }
@ -129,32 +124,30 @@ func (terminal *Terminal) GetMouseMode() MouseMode {
} }
func (terminal *Terminal) UseMainBuffer() { func (terminal *Terminal) UseMainBuffer() {
terminal.activeBufferIndex = MainBuffer terminal.activeBuffer = terminal.buffers[MainBuffer]
terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height)) terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height))
} }
func (terminal *Terminal) UseAltBuffer() { func (terminal *Terminal) UseAltBuffer() {
terminal.activeBufferIndex = AltBuffer terminal.activeBuffer = terminal.buffers[AltBuffer]
terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height)) terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height))
} }
func (terminal *Terminal) UseInternalBuffer() { func (terminal *Terminal) UseInternalBuffer() {
terminal.pauseChan <- true terminal.activeBuffer = terminal.buffers[InternalBuffer]
terminal.activeBufferIndex = InternalBuffer
terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height)) terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height))
} }
func (terminal *Terminal) ExitInternalBuffer() { func (terminal *Terminal) ExitInternalBuffer() {
terminal.activeBufferIndex = terminal.lastBuffer terminal.activeBuffer = terminal.buffers[terminal.lastBuffer]
terminal.resumeChan <- true
} }
func (terminal *Terminal) ActiveBuffer() *buffer.Buffer { func (terminal *Terminal) ActiveBuffer() *buffer.Buffer {
return terminal.buffers[terminal.activeBufferIndex] return terminal.activeBuffer
} }
func (terminal *Terminal) UsingMainBuffer() bool { func (terminal *Terminal) UsingMainBuffer() bool {
return terminal.activeBufferIndex == MainBuffer return terminal.activeBuffer == terminal.buffers[MainBuffer]
} }
func (terminal *Terminal) GetScrollOffset() uint { func (terminal *Terminal) GetScrollOffset() uint {
@ -255,20 +248,17 @@ func (terminal *Terminal) Read() error {
buffer := make(chan rune, 0xffff) buffer := make(chan rune, 0xffff)
reader := bufio.NewReader(terminal.pty) reader := bufio.NewReader(terminal.pty)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go terminal.processInput(ctx, buffer) go terminal.processInput(buffer)
for { for {
r, size, err := reader.ReadRune() r, _, err := reader.ReadRune()
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
break break
} }
return err return err
} else if size > 0 {
buffer <- r
} }
buffer <- r
} }
//clean exit //clean exit