func (c *Canvas) ScrollBuffer(dir ScrollDir, reset bool, linesOpt gwutil.IntOption) int {
	prev := c.Offset
	if reset {
		c.Offset = c.Canvas.BoxRows() - c.BoxRows()
	} else {
		var lines int
		if linesOpt.IsNone() {
			lines = c.BoxRows() / 2
		} else {
			lines = linesOpt.Val()
		}
		if dir == ScrollDown {
			lines = -lines
		}
		maxScroll := c.Canvas.BoxRows() - c.BoxRows()
		c.Offset -= lines
		if c.Offset < 0 {
			c.Offset = 0
		} else if c.Offset > maxScroll {
			c.Offset = maxScroll
		}
	}
	c.SetTermCursor(gwutil.NoneInt(), gwutil.NoneInt())
	return c.Offset - prev
}
ScrollBuffer will return the number of lines actually scrolled.
package terminal
import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"strconv"
	"strings"
	"unicode/utf8"
	"github.com/gcla/gowid"
	"github.com/gcla/gowid/gwutil"
	"github.com/gdamore/tcell"
	"github.com/mattn/go-runewidth"
	log "github.com/sirupsen/logrus"
	"golang.org/x/text/encoding/charmap"
)
const (
	CharsetDefault = iota
	CharsetUTF8    = iota
)
const (
	EscByte byte = 27
)
type LEDSState int
const (
	LEDSClear      LEDSState = 0
	LEDSScrollLock LEDSState = 1
	LEDSNumLock    LEDSState = 2
	LEDSCapsLock   LEDSState = 3
)
const (
	DecSpecialChars    = "▮◆▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£·"
	AltDecSpecialChars = "_`abcdefghijklmnopqrstuvwxyz{|}~"
)
type ScrollDir bool
const (
	ScrollDown ScrollDir = false
	ScrollUp   ScrollDir = true
)
type IMouseSupport interface {
	MouseEnabled() bool
	MouseIsSgr() bool
	MouseReportButton() bool
	MouseReportAny() bool
}
type Modes struct {
	DisplayCtrl        bool
	Insert             bool
	LfNl               bool
	KeysAutoWrap       bool
	ReverseVideo       bool
	ConstrainScrolling bool
	DontAutoWrap       bool
	InvisibleCursor    bool
	Charset            int
	VT200Mouse         bool  
	ReportButton       bool  
	ReportAny          bool  
	SgrModeMouse       bool  
}
func (t Modes) MouseEnabled() bool {
	return t.VT200Mouse
}
func (t Modes) MouseIsSgr() bool {
	return t.SgrModeMouse
}
func (t Modes) MouseReportButton() bool {
	return t.ReportButton
}
func (t Modes) MouseReportAny() bool {
	return t.ReportAny
}
type CSIFunction func(canvas *Canvas, args []int, qmark bool)
type ICSICommand interface {
	MinArgs() int
	FallbackArg() int
	IsAlias() bool
	Alias() byte
	Call(canvas *Canvas, args []int, qmark bool)
}
type RegularCSICommand struct {
	minArgs     int
	fallbackArg int
	fn          CSIFunction
}
func (c RegularCSICommand) MinArgs() int     { return c.minArgs }
func (c RegularCSICommand) FallbackArg() int { return c.fallbackArg }
func (c RegularCSICommand) IsAlias() bool    { return false }
func (c RegularCSICommand) Alias() byte      { panic(errors.New("Do not call")) }
func (c RegularCSICommand) Call(canvas *Canvas, args []int, qmark bool) {
	c.fn(canvas, args, qmark)
}
type AliasCSICommand struct {
	alias byte
}
func (c AliasCSICommand) MinArgs() int     { panic(errors.New("Do not call")) }
func (c AliasCSICommand) FallbackArg() int { panic(errors.New("Do not call")) }
func (c AliasCSICommand) IsAlias() bool    { return true }
func (c AliasCSICommand) Alias() byte      { return c.alias }
func (c AliasCSICommand) Call(canvas *Canvas, args []int, qmark bool) {
	panic(errors.New("Do not call"))
}
type CSIMap map[byte]ICSICommand
var csiMap = CSIMap{
	'@': RegularCSICommand{1, 1, func(canvas *Canvas, args []int, qmark bool) {
		canvas.InsertChars(gwutil.NoneInt(), gwutil.NoneInt(), args[0], gwutil.NoneRune())
	}},
	'A': RegularCSICommand{1, 1, func(canvas *Canvas, args []int, qmark bool) {
		canvas.MoveCursor(0, -int(args[0]), true, false, false)
	}},
	'B': RegularCSICommand{1, 1, func(canvas *Canvas, args []int, qmark bool) {
		canvas.MoveCursor(0, int(args[0]), true, false, false)
	}},
	'C': RegularCSICommand{1, 1, func(canvas *Canvas, args []int, qmark bool) {
		canvas.MoveCursor(int(args[0]), 0, true, false, false)
	}},
	'D': RegularCSICommand{1, 1, func(canvas *Canvas, args []int, qmark bool) {
		canvas.MoveCursor(-int(args[0]), 0, true, false, false)
	}},
	'E': RegularCSICommand{1, 1, func(canvas *Canvas, args []int, qmark bool) {
		canvas.MoveCursor(0, int(args[0]), false, false, true)
	}},
	'F': RegularCSICommand{1, 1, func(canvas *Canvas, args []int, qmark bool) {
		canvas.MoveCursor(0, -int(args[0]), false, false, true)
	}},
	'G': RegularCSICommand{1, 1, func(canvas *Canvas, args []int, qmark bool) {
		canvas.MoveCursor(int(args[0])-1, 0, false, false, true)
	}},
	'H': RegularCSICommand{2, 1, func(canvas *Canvas, args []int, qmark bool) {
		canvas.MoveCursor(int(args[1])-1, int(args[0])-1, false, false, false)
	}},
	'J': RegularCSICommand{1, 0, func(canvas *Canvas, args []int, qmark bool) {
		canvas.CSIEraseDisplay(args[0])
	}},
	'K': RegularCSICommand{1, 0, func(canvas *Canvas, args []int, qmark bool) {
		canvas.CSIEraseLine(args[0])
	}},
	'L': RegularCSICommand{1, 1, func(canvas *Canvas, args []int, qmark bool) {
		canvas.InsertLines(true, args[0])
	}},
	'M': RegularCSICommand{1, 1, func(canvas *Canvas, args []int, qmark bool) {
		canvas.RemoveLines(true, args[0])
	}},
	'P': RegularCSICommand{1, 1, func(canvas *Canvas, args []int, qmark bool) {
		canvas.RemoveChars(gwutil.NoneInt(), gwutil.NoneInt(), args[0])
	}},
	'X': RegularCSICommand{1, 1, func(canvas *Canvas, args []int, qmark bool) {
		myx, myy := canvas.TermCursor()
		canvas.Erase(myx, myy, myx+args[0]-1, myy)
	}},
	'a': AliasCSICommand{alias: 'C'},
	'c': RegularCSICommand{0, 0, func(canvas *Canvas, args []int, qmark bool) {
		canvas.CSIGetDeviceAttributes(qmark)
	}},
	'd': RegularCSICommand{1, 1, func(canvas *Canvas, args []int, qmark bool) {
		canvas.MoveCursor(0, int(args[0])-1, false, true, false)
	}},
	'e': AliasCSICommand{alias: 'B'},
	'f': AliasCSICommand{alias: 'H'},
	'g': RegularCSICommand{1, 0, func(canvas *Canvas, args []int, qmark bool) {
		canvas.CSIClearTabstop(args[0])
	}},
	'h': RegularCSICommand{1, 0, func(canvas *Canvas, args []int, qmark bool) {
		canvas.CSISetModes(args, qmark, false)
	}},
	'l': RegularCSICommand{1, 0, func(canvas *Canvas, args []int, qmark bool) {
		canvas.CSISetModes(args, qmark, true)
	}},
	'm': RegularCSICommand{1, 0, func(canvas *Canvas, args []int, qmark bool) {
		canvas.CSISetAttr(args)
	}},
	'n': RegularCSICommand{1, 0, func(canvas *Canvas, args []int, qmark bool) {
		canvas.CSIStatusReport(args[0])
	}},
	'q': RegularCSICommand{1, 0, func(canvas *Canvas, args []int, qmark bool) {
		canvas.CSISetKeyboardLEDs(args[0])
	}},
	'r': RegularCSICommand{2, 0, func(canvas *Canvas, args []int, qmark bool) {
		canvas.CSISetScroll(args[0], args[1])
	}},
	's': RegularCSICommand{0, 0, func(canvas *Canvas, args []int, qmark bool) {
		canvas.SaveCursor(false)
	}},
	'u': RegularCSICommand{0, 0, func(canvas *Canvas, args []int, qmark bool) {
		canvas.RestoreCursor(false)
	}},
	'`': AliasCSICommand{alias: 'G'},
}
var charsetMapping = map[string]rune{
	"default": 0,
	"vt100":   '0',
	"ibmpc":   'U',
	"user":    0,
}
type Charset struct {
	SgrMapping bool
	Active     int
	Current    rune
	Mapping    []string
}
func NewTerminalCharset() *Charset {
	res := &Charset{}
	res.Mapping = []string{"default", "vt100"}
	res.Activate(0)
	return res
}
func (t *Charset) Activate(g int) {
	t.Active = g
	if val, ok := charsetMapping[t.Mapping[g]]; ok {
		t.Current = val
	} else {
		t.Current = 0
	}
}
func (t *Charset) Define(g int, charset string) {
	t.Mapping[g] = charset
	t.Activate(t.Active)
}
func (t *Charset) SetSgrIbmpc() {
	t.SgrMapping = true
}
func (t *Charset) ResetSgrIbmpc() {
	t.SgrMapping = false
	t.Activate(t.Active)
}
func (t *Charset) ApplyMapping(r rune) rune {
	if t.SgrMapping || t.Mapping[t.Active] == "ibmpc" {
		decPos := strings.IndexRune(DecSpecialChars, charmap.CodePage437.DecodeByte(byte(r)))
		if decPos >= 0 {
			t.Current = '0'
			return rune(AltDecSpecialChars[decPos])
		} else {
			t.Current = 'U'
			return r
		}
	} else {
		return r
	}
}
type ViewPortCanvas struct {
	*gowid.Canvas
	Offset int
	Height int
}
func NewViewPort(c *gowid.Canvas, offset, height int) *ViewPortCanvas {
	res := &ViewPortCanvas{
		Canvas: c,
		Offset: offset,
		Height: height,
	}
	return res
}
func (c *ViewPortCanvas) Duplicate() gowid.ICanvas {
	res := &ViewPortCanvas{
		Canvas: c.Canvas.Duplicate().(*gowid.Canvas),
		Offset: c.Offset,
		Height: c.Height,
	}
	return res
}
func (c *ViewPortCanvas) MergeUnder(c2 gowid.IMergeCanvas, leftOffset, topOffset int, bottomGetsCursor bool) {
	c.Canvas.MergeUnder(c2, leftOffset, topOffset+c.Offset, bottomGetsCursor)
}
func (v *ViewPortCanvas) BoxRows() int {
	return v.Height
}
func (v *ViewPortCanvas) Line(y int, cp gowid.LineCopy) gowid.LineResult {
	return v.Canvas.Line(y+v.Offset, cp)
}
func (v *ViewPortCanvas) SetLineAt(row int, line []gowid.Cell) {
	v.Canvas.SetLineAt(row+v.Offset, line)
}
func (v *ViewPortCanvas) CellAt(col, row int) gowid.Cell {
	return v.Canvas.CellAt(col, row+v.Offset)
}
func (v *ViewPortCanvas) SetCellAt(col, row int, c gowid.Cell) {
	v.Canvas.SetCellAt(col, row+v.Offset, c)
}
func (c *ViewPortCanvas) String() string {
	return gowid.CanvasToString(c)
}
type parseState int
const (
	defaultState parseState = iota
	csiState
	oscState
	nonCsiState
	ignoreState
)
func (p parseState) String() string {
	switch p {
	case defaultState:
		return "default"
	case csiState:
		return "csi"
	case oscState:
		return "osc"
	case nonCsiState:
		return "noncsi"
	case ignoreState:
		return "ignore"
	default:
		panic(fmt.Errorf("Invalid parse state: %d", int(p)))
	}
}
type Canvas struct {
	*ViewPortCanvas
	alternate                          *ViewPortCanvas
	alternateActive                    bool
	parsestate                         parseState
	scrollback                         int
	withinEscape                       bool
	savedx, savedy                     gwutil.IntOption
	savedstyles                        map[string]bool
	savedfg, savedbg                   gwutil.IntOption
	scrollRegionStart, scrollRegionEnd int
	terminal                           ITerminal
	charset                            *Charset
	tcx, tcy                           int
	styles                             map[string]bool
	tabstops                           []int
	isRottenCursor                     bool
	escbuf                             []byte
	fg, bg                             gwutil.IntOption
	utf8Buffer                         []byte
	gowid.ICallbacks
}
func NewCanvasOfSize(cols, rows int, scrollback int, widget ITerminal) *Canvas {
	res := &Canvas{
		ViewPortCanvas: NewViewPort(gowid.NewCanvasOfSize(cols, rows), 0, rows),
		alternate:      NewViewPort(gowid.NewCanvasOfSize(cols, rows), 0, rows),
		scrollback:     scrollback,
		terminal:       widget,
		utf8Buffer:     make([]byte, 0, 4),
		ICallbacks:     gowid.NewCallbacks(),
	}
	res.Reset()
	var _ io.Writer = res
	return res
}
func (c *Canvas) Write(p []byte) (n int, err error) {
	for _, b := range p {
		c.ProcessByte(b)
	}
	return len(p), nil
}
func (c *Canvas) Duplicate() gowid.ICanvas {
	res := &Canvas{}
	*res = *c
	res.ViewPortCanvas = c.ViewPortCanvas.Duplicate().(*ViewPortCanvas)
	res.savedstyles = make(map[string]bool)
	for k, v := range c.savedstyles {
		res.savedstyles[k] = v
	}
	res.styles = make(map[string]bool)
	for k, v := range c.styles {
		res.styles[k] = v
	}
	res.tabstops = make([]int, len(c.tabstops))
	for i, v := range res.tabstops {
		res.tabstops[i] = v
	}
	res.escbuf = make([]byte, len(c.escbuf))
	for i, v := range res.escbuf {
		res.escbuf[i] = v
	}
	res.utf8Buffer = make([]byte, len(c.utf8Buffer))
	for i, v := range res.utf8Buffer {
		res.utf8Buffer[i] = v
	}
	return res
}
func (c *Canvas) Reset() {
	c.alternateActive = false
	c.escbuf = make([]byte, 0)
	c.charset = NewTerminalCharset()
	c.parsestate = defaultState
	c.withinEscape = false
	c.savedx = gwutil.NoneInt()
	c.savedy = gwutil.NoneInt()
	c.savedfg = gwutil.NoneInt()
	c.savedbg = gwutil.NoneInt()
	c.savedstyles = make(map[string]bool)
	c.fg = gwutil.NoneInt()
	c.bg = gwutil.NoneInt()
	c.styles = make(map[string]bool)
	*c.terminal.Modes() = Modes{}
	c.ResetScroll()
	c.InitTabstops(false)
	c.Clear(gwutil.SomeInt(0), gwutil.SomeInt(0))
}
func (c *Canvas) IsScrollRegionSet() bool {
	return !((c.scrollRegionStart == 0) && (c.scrollRegionEnd == c.BoxRows()-1))
}
func (c *Canvas) ResetScroll() {
	c.scrollRegionStart = 0
	c.scrollRegionEnd = c.BoxRows() - 1
}
func (c *Canvas) CarriageReturn() {
	c.SetTermCursor(gwutil.SomeInt(0), gwutil.SomeInt(c.tcy))
}
func (c *Canvas) Tab(tabstop int) {
	x, y := c.TermCursor()
	for x < c.BoxColumns()-1 {
		x += 1
		if c.IsTabstop(x) {
			break
		}
	}
	c.isRottenCursor = false
	c.SetTermCursor(gwutil.SomeInt(x), gwutil.SomeInt(y))
}
func (c *Canvas) InitTabstops(extend bool) {
	tablen, mod := c.BoxColumns()/8, c.BoxColumns()
	if mod > 0 {
		tablen += 1
	}
	if extend {
		for len(c.tabstops) < tablen {
			c.tabstops = append(c.tabstops, 1)
		}
	} else {
		c.tabstops = []int{}
		for i := 0; i < tablen; i++ {
			c.tabstops = append(c.tabstops, 1)
		}
	}
}
func (c *Canvas) SetTabstop(x2 gwutil.IntOption, remove bool, clear bool) {
	if clear {
		for tab := 0; tab < len(c.tabstops); tab++ {
			c.tabstops[tab] = 0
		}
	} else {
		var x int
		if x2.IsNone() {
			x, _ = c.TermCursor()
		} else {
			x = x2.Val()
		}
		div, mod := x/8, x%8
		if remove {
			c.tabstops[div] &= ^(1 << uint(mod))
		} else {
			c.tabstops[div] |= 1 << uint(mod)
		}
	}
}
func (c *Canvas) IsTabstop(x int) bool {
	div, mod := x/8, x%8
	return (c.tabstops[div] & (1 << uint(mod))) > 0
}
func (c *Canvas) TermCursor() (x, y int) {
	x, y = c.tcx, c.tcy
	return
}
func (c *Canvas) SetTermCursor(x2, y2 gwutil.IntOption) {
	tx, ty := c.TermCursor()
	var x, y int
	if x2.IsNone() {
		x = tx
	} else {
		x = x2.Val()
	}
	if y2.IsNone() {
		y = ty
	} else {
		y = y2.Val()
	}
	c.tcx, c.tcy = c.ConstrainCoords(x, y, false)
	if !c.terminal.Modes().InvisibleCursor {
		c.SetCursorCoords(c.tcx, c.tcy)
	} else {
		c.SetCursorCoords(-1, -1)
	}
}
func (c *Canvas) ConstrainCoords(x, y int, ignoreScrolling bool) (int, int) {
	if x >= c.BoxColumns() {
		x = c.BoxColumns() - 1
	} else if x < 0 {
		x = 0
	}
	if c.terminal.Modes().ConstrainScrolling && !ignoreScrolling {
		if y > c.scrollRegionEnd {
			y = c.scrollRegionEnd
		} else if y < c.scrollRegionStart {
			y = c.scrollRegionStart
		}
	} else {
		if y >= c.BoxRows() {
			y = c.BoxRows() - 1
		} else if y < 0 {
			y = 0
		}
	}
	return x, y
}