mirror of https://github.com/liamg/aminal.git
263 lines
6.0 KiB
Go
263 lines
6.0 KiB
Go
package buffer
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type Buffer struct {
|
|
lines []Line
|
|
cursorX uint16
|
|
cursorY uint16
|
|
viewHeight uint16
|
|
viewWidth uint16
|
|
cursorAttr CellAttributes
|
|
}
|
|
|
|
// NewBuffer creates a new terminal buffer
|
|
func NewBuffer(viewCols uint16, viewLines uint16) *Buffer {
|
|
b := &Buffer{
|
|
cursorX: 0,
|
|
cursorY: 0,
|
|
lines: []Line{},
|
|
}
|
|
b.ResizeView(viewCols, viewLines)
|
|
return b
|
|
}
|
|
|
|
func (buffer *Buffer) attachDisplayChangeHandler(handler func()) {
|
|
|
|
}
|
|
|
|
func (buffer *Buffer) attachLineChangeHandler(handler func(line uint16)) {
|
|
|
|
}
|
|
|
|
func (buffer *Buffer) attachCellChangeHandler(handler func(col uint16, line uint16)) {
|
|
|
|
}
|
|
|
|
// Column returns cursor column
|
|
func (buffer *Buffer) CursorColumn() uint16 {
|
|
return buffer.cursorX
|
|
}
|
|
|
|
// Line returns cursor line
|
|
func (buffer *Buffer) CursorLine() uint16 {
|
|
return buffer.cursorY
|
|
}
|
|
|
|
// translates the cursor line to the raw buffer line
|
|
func (buffer *Buffer) RawLine() uint64 {
|
|
rawHeight := buffer.Height()
|
|
if int(buffer.viewHeight) > rawHeight {
|
|
return uint64(buffer.cursorY)
|
|
}
|
|
return uint64(int(buffer.cursorY) + (rawHeight - int(buffer.viewHeight)))
|
|
}
|
|
|
|
// Width returns the width of the buffer in columns
|
|
func (buffer *Buffer) Width() uint16 {
|
|
return buffer.viewWidth
|
|
}
|
|
|
|
func (buffer *Buffer) ViewWidth() uint16 {
|
|
return buffer.viewWidth
|
|
}
|
|
|
|
func (buffer *Buffer) Height() int {
|
|
return len(buffer.lines)
|
|
}
|
|
|
|
func (buffer *Buffer) ViewHeight() uint16 {
|
|
return buffer.viewHeight
|
|
}
|
|
|
|
func (buffer *Buffer) ensureLinesExistToRawHeight() {
|
|
for int(buffer.RawLine()) >= len(buffer.lines) {
|
|
buffer.lines = append(buffer.lines, newLine())
|
|
}
|
|
}
|
|
|
|
// Write will write a rune to the terminal at the position of the cursor, and increment the cursor position
|
|
func (buffer *Buffer) Write(runes ...rune) {
|
|
for _, r := range runes {
|
|
buffer.ensureLinesExistToRawHeight()
|
|
if r == 0x0a {
|
|
buffer.NewLine()
|
|
continue
|
|
}
|
|
line := &buffer.lines[buffer.RawLine()]
|
|
for int(buffer.CursorColumn()) >= len(line.cells) {
|
|
line.cells = append(line.cells, newCell())
|
|
}
|
|
cell := &line.cells[buffer.CursorColumn()]
|
|
cell.setRune(r)
|
|
cell.attr = buffer.cursorAttr
|
|
buffer.incrementCursorPosition()
|
|
}
|
|
}
|
|
|
|
func (buffer *Buffer) incrementCursorPosition() {
|
|
|
|
if buffer.CursorColumn()+1 < buffer.Width() {
|
|
buffer.cursorX++
|
|
} else {
|
|
if buffer.cursorY == buffer.viewHeight-1 { // if we're on the last line, we can't move the cursor down, we have to move the buffer up, i.e. add a new line
|
|
line := newLine()
|
|
line.setWrapped(true)
|
|
buffer.lines = append(buffer.lines, line)
|
|
buffer.cursorX = 0
|
|
} else {
|
|
buffer.cursorX = 0
|
|
if buffer.Height() < int(buffer.ViewHeight()) {
|
|
line := newLine()
|
|
line.setWrapped(true)
|
|
buffer.lines = append(buffer.lines, line)
|
|
buffer.cursorY++
|
|
} else {
|
|
panic("no test for this yet - not sure if possible?")
|
|
line := &buffer.lines[buffer.RawLine()]
|
|
line.setWrapped(true)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (buffer *Buffer) NewLine() {
|
|
// if we're at the beginning of a line which wrapped from the previous one, and we need a new line, we can effectively not add a new line, and set the current one to non-wrapped
|
|
if buffer.cursorX == 0 {
|
|
line := &buffer.lines[buffer.RawLine()]
|
|
if line.wrapped {
|
|
line.setWrapped(false)
|
|
return
|
|
}
|
|
}
|
|
|
|
if buffer.cursorY == buffer.viewHeight-1 {
|
|
buffer.lines = append(buffer.lines, newLine())
|
|
buffer.cursorX = 0
|
|
} else {
|
|
buffer.cursorX = 0
|
|
buffer.cursorY++
|
|
}
|
|
}
|
|
|
|
func (buffer *Buffer) MovePosition(x int16, y int16) {
|
|
|
|
if int16(buffer.cursorX)+x < 0 {
|
|
x = -int16(buffer.cursorX)
|
|
}
|
|
|
|
if int16(buffer.cursorY)+y < 0 {
|
|
y = -int16(buffer.cursorY)
|
|
}
|
|
|
|
buffer.SetPosition(uint16(int16(buffer.cursorX)+x), uint16(int16(buffer.cursorY)+y))
|
|
}
|
|
|
|
func (buffer *Buffer) SetPosition(col uint16, line uint16) {
|
|
if col >= buffer.ViewWidth() {
|
|
col = buffer.ViewWidth() - 1
|
|
logrus.Errorf("Cannot set cursor position: column %d is outside of the current view width (%d columns)", col, buffer.ViewWidth())
|
|
}
|
|
if line >= buffer.ViewHeight() {
|
|
line = buffer.ViewHeight() - 1
|
|
logrus.Errorf("Cannot set cursor position: line %d is outside of the current view height (%d lines)", line, buffer.ViewHeight())
|
|
}
|
|
buffer.cursorX = col
|
|
buffer.cursorY = line
|
|
}
|
|
|
|
func (buffer *Buffer) GetVisibleLines() []Line {
|
|
lines := []Line{}
|
|
for i := buffer.Height() - int(buffer.ViewHeight()); i < buffer.Height(); i++ {
|
|
if i >= 0 && i < len(buffer.lines) {
|
|
lines = append(lines, buffer.lines[i])
|
|
}
|
|
}
|
|
return lines
|
|
}
|
|
|
|
// tested to here
|
|
|
|
func (buffer *Buffer) Clear() {
|
|
for i := 0; i < int(buffer.ViewHeight()); i++ {
|
|
buffer.lines = append(buffer.lines, newLine())
|
|
}
|
|
buffer.SetPosition(0, 0)
|
|
}
|
|
|
|
func (buffer *Buffer) getCurrentLine() (*Line, error) {
|
|
|
|
if int(buffer.RawLine()) < len(buffer.lines) {
|
|
return &buffer.lines[buffer.RawLine()], nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("Line %d does not exist", buffer.cursorY)
|
|
}
|
|
|
|
func (buffer *Buffer) EraseLine() {
|
|
line, err := buffer.getCurrentLine()
|
|
if err != nil {
|
|
return
|
|
}
|
|
line.cells = []Cell{}
|
|
}
|
|
|
|
func (buffer *Buffer) EraseLineToCursor() {
|
|
line, err := buffer.getCurrentLine()
|
|
if err != nil {
|
|
return
|
|
}
|
|
for i := 0; i <= int(buffer.cursorX); i++ {
|
|
if i < len(line.cells) {
|
|
line.cells[i].erase()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (buffer *Buffer) EraseLineAfterCursor() {
|
|
line, err := buffer.getCurrentLine()
|
|
if err != nil {
|
|
return
|
|
}
|
|
for i := int(buffer.cursorX + 1); i < len(line.cells); i++ {
|
|
line.cells[i].erase()
|
|
}
|
|
}
|
|
|
|
func (buffer *Buffer) EraseDisplayAfterCursor() {
|
|
line, err := buffer.getCurrentLine()
|
|
if err != nil {
|
|
return
|
|
}
|
|
line.cells = line.cells[:buffer.cursorX]
|
|
for i := int(buffer.RawLine() + 1); i < buffer.Height(); i++ {
|
|
if i < len(buffer.lines) {
|
|
buffer.lines[i].cells = []Cell{}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (buffer *Buffer) EraseDisplayToCursor() {
|
|
line, err := buffer.getCurrentLine()
|
|
if err != nil {
|
|
return
|
|
}
|
|
line.cells = line.cells[buffer.cursorX+1:]
|
|
for i := 0; i < int(buffer.RawLine()); i++ {
|
|
if i < len(buffer.lines) {
|
|
buffer.lines[i].cells = []Cell{}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (buffer *Buffer) ResizeView(width uint16, height uint16) {
|
|
buffer.viewWidth = width
|
|
buffer.viewHeight = height
|
|
|
|
// @todo wrap/unwrap
|
|
}
|