diff --git a/buffer/buffer.go b/buffer/buffer.go index fe0f127..4130386 100644 --- a/buffer/buffer.go +++ b/buffer/buffer.go @@ -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() + } +} diff --git a/buffer/buffer_test.go b/buffer/buffer_test.go index 415b698..00c998b 100644 --- a/buffer/buffer_test.go +++ b/buffer/buffer_test.go @@ -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) { diff --git a/buffer/cell.go b/buffer/cell.go index 98690e3..a0fa159 100644 --- a/buffer/cell.go +++ b/buffer/cell.go @@ -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 +} diff --git a/buffer/line.go b/buffer/line.go index e86e059..3099b3b 100644 --- a/buffer/line.go +++ b/buffer/line.go @@ -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 diff --git a/buffer/terminal_state.go b/buffer/terminal_state.go index f30ceb3..a8948ce 100644 --- a/buffer/terminal_state.go +++ b/buffer/terminal_state.go @@ -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 diff --git a/gui/gui.go b/gui/gui.go index 686db93..ace0b0e 100644 --- a/gui/gui.go +++ b/gui/gui.go @@ -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) } } diff --git a/gui/renderer.go b/gui/renderer.go index 0f9ab5e..6b81b7f 100644 --- a/gui/renderer.go +++ b/gui/renderer.go @@ -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() diff --git a/terminal/modes.go b/terminal/modes.go index 5ba6c94..a851ddf 100644 --- a/terminal/modes.go +++ b/terminal/modes.go @@ -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) diff --git a/terminal/sgr.go b/terminal/sgr.go index 87c40db..cc19451 100644 --- a/terminal/sgr.go +++ b/terminal/sgr.go @@ -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": diff --git a/terminal/terminal.go b/terminal/terminal.go index 025ec02..67ab419 100644 --- a/terminal/terminal.go +++ b/terminal/terminal.go @@ -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) +}