* added Tab Stops support

* added support for Screen Mode (DECSCNM) -- reverse colors

* bug fix: cursor rendition in Origin Mode
This commit is contained in:
rrrooommmaaa 2019-01-30 18:42:55 +03:00 committed by Liam Galvin
parent 97fe7362ce
commit 23797d50f3
10 changed files with 150 additions and 71 deletions

View File

@ -361,7 +361,7 @@ func (buffer *Buffer) RestoreCursor() {
}
func (buffer *Buffer) CursorAttr() *CellAttributes {
return &buffer.terminalState.cursorAttr
return &buffer.terminalState.CursorAttr
}
func (buffer *Buffer) GetCell(viewCol uint16, viewRow uint16) *Cell {
@ -391,7 +391,12 @@ func (buffer *Buffer) CursorColumn() uint16 {
return buffer.terminalState.cursorX
}
// Line returns cursor line
// CursorLineAbsolute returns absolute cursor line coordinate (ignoring Origin Mode)
func (buffer *Buffer) CursorLineAbsolute() uint16 {
return buffer.terminalState.cursorY
}
// CursorLine returns cursor line (in Origin Mode it is relative to the top margin)
func (buffer *Buffer) CursorLine() uint16 {
if buffer.terminalState.OriginMode {
result := buffer.terminalState.cursorY - uint16(buffer.terminalState.topMargin)
@ -502,7 +507,7 @@ func (buffer *Buffer) InsertBlankCharacters(count int) {
index := int(buffer.RawLine())
for i := 0; i < count; i++ {
cells := buffer.lines[index].cells
buffer.lines[index].cells = append(cells[:buffer.terminalState.cursorX], append([]Cell{buffer.terminalState.defaultCell}, cells[buffer.terminalState.cursorX:]...)...)
buffer.lines[index].cells = append(cells[:buffer.terminalState.cursorX], append([]Cell{buffer.terminalState.DefaultCell(true)}, cells[buffer.terminalState.cursorX:]...)...)
}
}
@ -619,9 +624,9 @@ func (buffer *Buffer) Write(runes ...rune) {
}
for int(buffer.CursorColumn()) >= len(line.cells) {
line.cells = append(line.cells, buffer.terminalState.defaultCell)
line.cells = append(line.cells, buffer.terminalState.DefaultCell(int(buffer.CursorColumn()) == len(line.cells)))
}
line.cells[buffer.terminalState.cursorX].attr = buffer.terminalState.cursorAttr
line.cells[buffer.terminalState.cursorX].attr = buffer.terminalState.CursorAttr
line.cells[buffer.terminalState.cursorX].setRune(r)
buffer.incrementCursorPosition()
continue
@ -635,11 +640,11 @@ func (buffer *Buffer) Write(runes ...rune) {
newLine := buffer.getCurrentLine()
if len(newLine.cells) == 0 {
newLine.cells = append(newLine.cells, buffer.terminalState.defaultCell)
newLine.cells = append(newLine.cells, buffer.terminalState.DefaultCell(true))
}
cell := &newLine.cells[0]
cell.setRune(r)
cell.attr = buffer.terminalState.cursorAttr
cell.attr = buffer.terminalState.CursorAttr
} else {
// no more room on line and wrapping is disabled
@ -650,12 +655,12 @@ func (buffer *Buffer) Write(runes ...rune) {
} else {
for int(buffer.CursorColumn()) >= len(line.cells) {
line.cells = append(line.cells, buffer.terminalState.defaultCell)
line.cells = append(line.cells, buffer.terminalState.DefaultCell(int(buffer.CursorColumn()) == len(line.cells)))
}
cell := &line.cells[buffer.CursorColumn()]
cell.setRune(r)
cell.attr = buffer.terminalState.cursorAttr
cell.attr = buffer.terminalState.CursorAttr
}
buffer.incrementCursorPosition()
@ -848,7 +853,7 @@ func (buffer *Buffer) EraseLineToCursor() {
line := buffer.getCurrentLine()
for i := 0; i <= int(buffer.terminalState.cursorX); i++ {
if i < len(line.cells) {
line.cells[i].erase(buffer.terminalState.defaultCell.attr.BgColour)
line.cells[i].erase(buffer.terminalState.CursorAttr.BgColour)
}
}
}
@ -909,7 +914,7 @@ func (buffer *Buffer) EraseCharacters(n int) {
}
for i := int(buffer.terminalState.cursorX); i < max; i++ {
line.cells[i].erase(buffer.terminalState.defaultCell.attr.BgColour)
line.cells[i].erase(buffer.terminalState.CursorAttr.BgColour)
}
}
@ -939,7 +944,7 @@ func (buffer *Buffer) EraseDisplayToCursor() {
if i >= len(line.cells) {
break
}
line.cells[i].erase(buffer.terminalState.defaultCell.attr.BgColour)
line.cells[i].erase(buffer.terminalState.CursorAttr.BgColour)
}
for i := uint16(0); i < buffer.terminalState.cursorY; i++ {
rawLine := buffer.convertViewLineToRawLine(i)
@ -1086,3 +1091,11 @@ func (buffer *Buffer) Compare(path string) bool {
}
return bytes.Equal(f, bufferContent)
}
func (buffer *Buffer) ReverseVideo() {
defer buffer.emitDisplayChange()
for _, line := range buffer.lines {
line.ReverseVideo()
}
}

View File

@ -393,7 +393,7 @@ func TestGetCellWithBadCursor(t *testing.T) {
func TestCursorAttr(t *testing.T) {
b := NewBuffer(NewTerminalState(80, 2, CellAttributes{}, 1000))
assert.Equal(t, &b.terminalState.cursorAttr, b.CursorAttr())
assert.Equal(t, &b.terminalState.CursorAttr, b.CursorAttr())
}
func TestCursorPositionQuerying(t *testing.T) {

View File

@ -17,7 +17,7 @@ type CellAttributes struct {
Dim bool
Underline bool
Blink bool
Reverse bool
Inverse bool
Hidden bool
}
@ -40,20 +40,20 @@ func (cell *Cell) Rune() rune {
}
func (cell *Cell) Fg() [3]float32 {
if cell.Attr().Reverse {
if cell.Attr().Inverse {
return cell.attr.BgColour
}
return cell.attr.FgColour
}
func (cell *Cell) Bg() [3]float32 {
if cell.Attr().Reverse {
if cell.Attr().Inverse {
return cell.attr.FgColour
}
return cell.attr.BgColour
}
func (cell *Cell) erase(bgColour [3]float32) {
func (cell *Cell) erase(bgColour [3]float32) {
cell.setRune(0)
cell.attr.BgColour = bgColour
}
@ -69,3 +69,9 @@ func NewBackgroundCell(colour [3]float32) Cell {
},
}
}
func (cellAttr *CellAttributes) ReverseVideo() {
oldFgColour := cellAttr.FgColour
cellAttr.FgColour = cellAttr.BgColour
cellAttr.BgColour = oldFgColour
}

View File

@ -20,6 +20,12 @@ func (line *Line) Cells() []Cell {
return line.cells
}
func (line *Line) ReverseVideo() {
for i, _ := range line.cells {
line.cells[i].attr.ReverseVideo()
}
}
// Cleanse removes null bytes from the end of the row
func (line *Line) Cleanse() {
cut := 0

View File

@ -4,8 +4,7 @@ type TerminalState struct {
scrollLinesFromBottom uint
cursorX uint16
cursorY uint16
cursorAttr CellAttributes
defaultCell Cell
CursorAttr CellAttributes
viewHeight uint16
viewWidth uint16
topMargin uint // see DECSTBM docs - this is for scrollable regions
@ -13,6 +12,7 @@ type TerminalState struct {
ReplaceMode bool // overwrite character at cursor or insert new
OriginMode bool // see DECOM docs - whether cursor is positioned within the margins or not
LineFeedMode bool
ScreenMode bool // DECSCNM (black on white background)
AutoWrap bool
maxLines uint64
tabStops map[uint16]struct{}
@ -23,9 +23,8 @@ func NewTerminalState(viewCols uint16, viewLines uint16, attr CellAttributes, ma
b := &TerminalState{
cursorX: 0,
cursorY: 0,
cursorAttr: attr,
CursorAttr: attr,
AutoWrap: true,
defaultCell: Cell{attr: attr},
maxLines: maxLines,
viewWidth: viewCols,
viewHeight: viewLines,
@ -36,6 +35,19 @@ func NewTerminalState(viewCols uint16, viewLines uint16, attr CellAttributes, ma
return b
}
func (terminalState *TerminalState) DefaultCell(applyEffects bool) Cell {
attr := terminalState.CursorAttr
if !applyEffects {
attr.Blink = false
attr.Bold = false
attr.Dim = false
attr.Inverse = false
attr.Underline = false
attr.Dim = false
}
return Cell{attr: attr}
}
func (terminalState *TerminalState) SetVerticalMargins(top uint, bottom uint) {
terminalState.topMargin = top
terminalState.bottomMargin = bottom

View File

@ -48,6 +48,7 @@ type GUI struct {
resizeLock *sync.Mutex
handCursor *glfw.Cursor
arrowCursor *glfw.Cursor
defaultCell *buffer.Cell
prevLeftClickX uint16
prevLeftClickY uint16
@ -185,6 +186,22 @@ func (gui *GUI) resizeToTerminal(newCols uint, newRows uint) {
gui.window.SetSize(roundedWidth, roundedHeight) // will trigger resize()
}
func (gui *GUI) generateDefaultCell(reverse bool) {
color := gui.config.ColourScheme.Background
if reverse {
color = gui.config.ColourScheme.Foreground
}
cell := buffer.NewBackgroundCell(color)
gui.renderer.backgroundColour = color
gui.defaultCell = &cell
gl.ClearColor(
color[0],
color[1],
color[2],
1.0,
)
}
// can only be called on OS thread
func (gui *GUI) resize(w *glfw.Window, width int, height int) {
@ -232,7 +249,7 @@ func (gui *GUI) resize(w *glfw.Window, width int, height int) {
gui.logger.Debugf("Resize complete!")
gui.redraw(buffer.NewBackgroundCell(gui.config.ColourScheme.Background))
gui.redraw()
gui.window.SwapBuffers()
}
@ -279,6 +296,7 @@ func (gui *GUI) Render() error {
titleChan := make(chan bool, 1)
resizeChan := make(chan bool, 1)
reverseChan := make(chan bool, 1)
gui.renderer = NewOpenGLRenderer(gui.config, gui.fontMap, 0, 0, gui.width, gui.height, gui.colourAttr, program)
@ -297,6 +315,8 @@ func (gui *GUI) Render() error {
}
})
gui.generateDefaultCell(false)
{
w, h := gui.window.GetFramebufferSize()
gui.resize(gui.window, w, h)
@ -320,21 +340,13 @@ func (gui *GUI) Render() error {
gl.Disable(gl.DEPTH_TEST)
gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
gl.ClearColor(
gui.config.ColourScheme.Background[0],
gui.config.ColourScheme.Background[1],
gui.config.ColourScheme.Background[2],
1.0,
)
gui.terminal.AttachTitleChangeHandler(titleChan)
gui.terminal.AttachResizeHandler(resizeChan)
gui.terminal.AttachReverseHandler(reverseChan)
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
defaultCell := buffer.NewBackgroundCell(gui.config.ColourScheme.Background)
go func() {
for {
<-ticker.C
@ -359,20 +371,25 @@ func (gui *GUI) Render() error {
for !gui.window.ShouldClose() {
forceRedraw := false
select {
case <-titleChan:
gui.window.SetTitle(gui.terminal.GetTitle())
case <-resizeChan:
cols, rows := gui.terminal.GetSize()
gui.resizeToTerminal(uint(cols), uint(rows))
case reverse := <-reverseChan:
gui.generateDefaultCell(reverse)
forceRedraw = true
default:
// this is more efficient than glfw.PollEvents()
glfw.WaitEventsTimeout(0.02) // up to 50fps on no input, otherwise higher
}
if gui.terminal.CheckDirty() {
if gui.terminal.CheckDirty() || forceRedraw {
gui.redraw(defaultCell)
gui.redraw()
if gui.showDebugInfo {
gui.textbox(2, 2, fmt.Sprintf(`Cursor: %d,%d
@ -422,7 +439,7 @@ Buffer Size: %d lines
}
func (gui *GUI) redraw(defaultCell buffer.Cell) {
func (gui *GUI) redraw() {
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
lines := gui.terminal.GetVisibleLines()
lineCount := int(gui.terminal.ActiveBuffer().ViewHeight())
@ -446,18 +463,18 @@ func (gui *GUI) redraw(defaultCell buffer.Cell) {
colour = nil
}
cell := defaultCell
cell := gui.defaultCell
if colour != nil || cursor || x < len(cells) {
if x < len(cells) {
cell = cells[x]
cell = &cells[x]
if cell.Image() != nil {
gui.renderer.DrawCellImage(cell, uint(x), uint(y))
gui.renderer.DrawCellImage(*cell, uint(x), uint(y))
continue
}
}
gui.renderer.DrawCellBg(cell, uint(x), uint(y), cursor, colour, false)
gui.renderer.DrawCellBg(*cell, uint(x), uint(y), cursor, colour, false)
}
}

View File

@ -11,20 +11,21 @@ import (
)
type OpenGLRenderer struct {
areaWidth int
areaHeight int
areaX int
areaY int
cellWidth float32
cellHeight float32
termCols uint
termRows uint
cellPositions map[[2]uint][2]float32
config *config.Config
colourAttr uint32
program uint32
textureMap map[*image.RGBA]uint32
fontMap *FontMap
areaWidth int
areaHeight int
areaX int
areaY int
cellWidth float32
cellHeight float32
termCols uint
termRows uint
cellPositions map[[2]uint][2]float32
config *config.Config
colourAttr uint32
program uint32
textureMap map[*image.RGBA]uint32
fontMap *FontMap
backgroundColour [3]float32
}
type rectangle struct {
@ -185,7 +186,7 @@ func (r *OpenGLRenderer) GetRectangleSize(col uint, row uint) (float32, float32)
func (r *OpenGLRenderer) getRectangle(col uint, row uint) *rectangle {
x := float32(float32(col) * r.cellWidth)
y := float32(float32(row) * r.cellHeight) + r.cellHeight
y := float32(float32(row)*r.cellHeight) + r.cellHeight
return r.newRectangle(x, y, r.colourAttr)
}
@ -207,13 +208,17 @@ func (r *OpenGLRenderer) DrawCellBg(cell buffer.Cell, col uint, row uint, cursor
} else {
if cursor {
bg = r.config.ColourScheme.Cursor
if r.config.ColourScheme.Cursor != r.backgroundColour {
bg = r.config.ColourScheme.Cursor
} else {
bg = cell.Fg()
}
} else {
bg = cell.Bg()
}
}
if bg != r.config.ColourScheme.Background || force {
if bg != r.backgroundColour || force {
rect := r.getRectangle(col, row)
rect.setColour(bg)
rect.Draw()

View File

@ -47,18 +47,13 @@ func csiSetMode(modeStr string, enabled bool, terminal *Terminal) error {
terminal.SetSize(80, uint(lines))
}
terminal.Clear()
/*
case "?4":
// DECSCLM
// @todo smooth scrolling / jump scrolling
case "?5":
// DECSCNM
if enabled {
// @todo SCreeN Mode, black on white background
} else {
// @todo Normal screen (white on black background)
}
*/
/*
case "?4":
// DECSCLM
// @todo smooth scrolling / jump scrolling
*/
case "?5": // DECSCNM
terminal.SetScreenMode(enabled)
case "?6":
// DECOM
terminal.SetOriginMode(enabled)

View File

@ -35,7 +35,7 @@ func sgrSequenceHandler(params []string, terminal *Terminal) error {
case "5", "05":
terminal.ActiveBuffer().CursorAttr().Blink = true
case "7", "07":
terminal.ActiveBuffer().CursorAttr().Reverse = true
terminal.ActiveBuffer().CursorAttr().Inverse = true
case "8", "08":
terminal.ActiveBuffer().CursorAttr().Hidden = true
case "21":
@ -49,7 +49,7 @@ func sgrSequenceHandler(params []string, terminal *Terminal) error {
case "25":
terminal.ActiveBuffer().CursorAttr().Blink = false
case "27":
terminal.ActiveBuffer().CursorAttr().Reverse = false
terminal.ActiveBuffer().CursorAttr().Inverse = false
case "28":
terminal.ActiveBuffer().CursorAttr().Hidden = false
case "29":

View File

@ -41,6 +41,7 @@ type Terminal struct {
config *config.Config
titleHandlers []chan bool
resizeHandlers []chan bool
reverseHandlers []chan bool
modes Modes
mouseMode MouseMode
bracketedPasteMode bool
@ -196,6 +197,10 @@ func (terminal *Terminal) AttachResizeHandler(handler chan bool) {
terminal.resizeHandlers = append(terminal.resizeHandlers, handler)
}
func (terminal *Terminal) AttachReverseHandler(handler chan bool) {
terminal.reverseHandlers = append(terminal.reverseHandlers, handler)
}
func (terminal *Terminal) Modes() Modes {
return terminal.modes
}
@ -216,6 +221,14 @@ func (terminal *Terminal) emitResize() {
}
}
func (terminal *Terminal) emitReverse(reverse bool) {
for _, h := range terminal.reverseHandlers {
go func(c chan bool) {
c <- reverse
}(h)
}
}
func (terminal *Terminal) GetLogicalCursorX() uint16 {
if terminal.ActiveBuffer().CursorColumn() >= terminal.ActiveBuffer().Width() {
return 0
@ -226,10 +239,10 @@ func (terminal *Terminal) GetLogicalCursorX() uint16 {
func (terminal *Terminal) GetLogicalCursorY() uint16 {
if terminal.ActiveBuffer().CursorColumn() >= terminal.ActiveBuffer().Width() {
return terminal.ActiveBuffer().CursorLine() + 1
return terminal.ActiveBuffer().CursorLineAbsolute() + 1
}
return terminal.ActiveBuffer().CursorLine()
return terminal.ActiveBuffer().CursorLineAbsolute()
}
func (terminal *Terminal) GetTitle() string {
@ -349,3 +362,15 @@ func (terminal *Terminal) SetLineFeedMode() {
func (terminal *Terminal) ResetVerticalMargins() {
terminal.terminalState.ResetVerticalMargins()
}
func (terminal *Terminal) SetScreenMode(enabled bool) {
if terminal.terminalState.ScreenMode == enabled {
return
}
terminal.terminalState.ScreenMode = enabled
terminal.terminalState.CursorAttr.ReverseVideo()
for _, buffer := range terminal.buffers {
buffer.ReverseVideo()
}
terminal.emitReverse(enabled)
}