Moved terminal stuff to a separate repository

This commit is contained in:
Sasha Koshka 2023-04-07 23:40:05 -04:00
parent 43a664009c
commit dc077a02ab
7 changed files with 0 additions and 817 deletions

View File

@ -1,38 +0,0 @@
package ansi
// C0 represents a list of C0 control codes.
// https://en.wikipedia.org/wiki/C0_and_C1_control_codes
type C0 byte; const (
C0_Null C0 = iota
C0_StartOfHeading
C0_StartOfText
C0_EndOfText
C0_EndOfTransmission
C0_Enquiry
C0_Acknowledge
C0_Bell
C0_Backspace
C0_CharacterTab
C0_LineFeed
C0_LineTab
C0_FormFeed
C0_CarriageReturn
C0_ShiftOut
C0_ShiftIn
C0_DataLinkEscape
C0_DeviceControlOne
C0_DeviceControlTwo
C0_DeviceControlThree
C0_DeviceControlFour
C0_NegativeAcknowledge
C0_SynchronousIdle
C0_EndOfTransmissionBlock
C0_Cancel
C0_EndOfMedium
C0_Substitute
C0_Escape
C0_FileSeparator
C0_GroupSeparator
C0_RecordSeparator
C0_UnitSeparator
)

View File

@ -1,48 +0,0 @@
package ansi
// C1 represents a list of C1 control codes.
// https://en.wikipedia.org/wiki/C0_and_C1_control_codes
type C1 byte; const (
C1_PaddingCharacter C1 = iota + 128
C1_HighOctetPreset
C1_BreakPermittedHere
C1_NoBreakHere
C1_Index
C1_NextLine
C1_StartOfSelectedArea
C1_EndOfSelectedArea
C1_CharacterTabSet
C1_CharacterTabWithJustification
C1_LineTabSet
C1_PartialLineForward
C1_PartialLineBackward
C1_ReverseLineFeed
C1_SingleShift2
C1_SingleShift3
C1_DeviceControlString
C1_PrivateUse1
C1_PrivateUse2
C1_SetTransmitState
C1_CancelCharacter
C1_MessageWaiting
C1_StartOfProtectedArea
C1_EndOfProtectedArea
C1_StartOfString
C1_SingleGraphicCharacterIntroducer
C1_SingleCharacterIntroducer
C1_ControlSequenceIntroducer
C1_StringTerminator
C1_OperatingSystemCommand
C1_PrivacyMessage
C1_ApplicationProgramCommand
)
// Is checks if a byte is equal to a C0 code.
func (code C0) Is (test byte) bool {
return test == byte(code)
}
// Is checks if a byte is equal to a C1 code.
func (code C1) Is (test byte) bool {
return byte(code) == test
}

View File

@ -1,90 +0,0 @@
package ansi
import "image/color"
var _ color.Color = Color(0)
// Color represents a 3, 4, or 8-Bit ansi color.
type Color byte; const (
// Dim/standard colors
ColorBlack Color = iota
ColorRed
ColorGreen
ColorYellow
ColorBlue
ColorMagenta
ColorCyan
ColorWhite
// Bright colors
ColorBrightBlack
ColorBrightRed
ColorBrightGreen
ColorBrightYellow
ColorBrightBlue
ColorBrightMagenta
ColorBrightCyan
ColorBrightWhite
// 216 cube colors (16 - 231)
// 24 grayscale colors (232 - 255)
)
// Is16 returns whether the color is a dim or bright color, and can be assigned
// to a theme palette.
func (c Color) Is16 () bool {
return c.IsDim() || c.IsBright()
}
// IsDim returns whether the color is dim.
func (c Color) IsDim () bool {
return c < 8
}
// IsBright returns whether the color is bright.
func (c Color) IsBright () bool {
return c >= 8 && c < 16
}
// IsCube returns whether the color is part of the 6x6x6 cube.
func (c Color) IsCube () bool {
return c >= 16 && c < 232
}
// IsGrayscale returns whether the color grayscale.
func (c Color) IsGrayscale () bool {
return c >= 232 && c <= 255
}
// RGB returns the 8 bit RGB values of the color as a color.RGBA value.
func (c Color) RGB () (out color.RGBA) {
switch {
case c.Is16():
// each bit is a color component
out.R = 0xFF * uint8((c & 0x1) >> 0)
out.G = 0xFF * uint8((c & 0x2) >> 1)
out.B = 0xFF * uint8((c & 0x4) >> 3)
// dim if color is in the dim range
if c & 0x8 > 0 { out.R >>= 1; out.G >>= 1; out.B >>= 1 }
case c.IsCube():
index := int(c - 16)
out.R = uint8((((index / 36) % 6) * 255) / 5)
out.G = uint8((((index / 6) % 6) * 255) / 5)
out.B = uint8((((index ) % 6) * 255) / 5)
case c.IsGrayscale():
out.R = uint8(((int(c) - 232) * 255) / 23)
out.G = out.R
out.B = out.R
}
out.A = 0xFF
return
}
// RGBA fulfills the color.Color interface.
func (c Color) RGBA () (r, g, b, a uint32) {
return c.RGB().RGBA()
}

View File

@ -1,17 +0,0 @@
package ansi
// CSI represents a list of CSI sequences that have no parameters.
// FIXME: some of these do indeed have parameters
type CSI int; const (
CSI_DeviceStatusReport CSI = iota
CSI_SaveCursorPosition
CSI_RestoreCursorPosition
CSI_ShowCursor
CSI_HideCursor
CSI_EnableReportingFocus
CSI_DisableReportingFocus
CSI_EnableAlternativeBuffer
CSI_DisableAlternativeBuffer
CSI_EnableBracketedPasteMode
CSI_DisableBracketedPasteMode
)

View File

@ -1,360 +0,0 @@
package ansi
import "strconv"
import "strings"
import "image/color"
// Useful: https://en.wikipedia.org/wiki/ANSI_escape_code
// Decoder is a state machine capable of decoding text contianing various escape
// codes and sequences. It satisfies io.Writer. It has no constructor and its
// zero value can be used safely.
type Decoder struct {
// OnText is called when a segment of text is processed.
OnText func (string)
// OnC0 is called when a C0 control code is processed that isn't a
// whitespace character.
OnC0 func (C0)
// OnC1 is called when a C1 escape sequence is processed.
OnC1 func (C1)
// OnDCS is called when a device control string is processed.
OnDCS func (string)
// OnCSI is called when a non-SGR CSI escape sequence with no parameters
// is processed.
OnCSI func (CSI)
// OnSGR is called when a CSI SGR escape sequence with no parameters is
// processed.
OnSGR func (SGR)
// OnPM is called when a privacy message is processed.
OnPM func (string)
// OnAPC is called when an application program command is processed.
OnAPC func (string)
// Non-SGR CSI sequences with parameters:
OnCursorUp func (distance int)
OnCursorDown func (distance int)
OnCursorForward func (distance int)
OnCursorBack func (distance int)
OnCursorNextLine func (distance int)
OnCursorPreviousLine func (distance int)
OnCursorHorizontalAbsolute func (column int)
OnCursorPosition func (column, row int)
OnEraseInDisplay func (mode int)
OnEraseInLine func (mode int)
OnScrollUp func (distance int)
OnScrollDown func (distance int)
OnHorizontalVerticalPosition func (column, row int)
// SGR CSI sequences with parameters:
OnForegroundColor func (Color)
OnForegroundColorTrue func (color.RGBA)
OnBackgroundColor func (Color)
OnBackgroundColorTrue func (color.RGBA)
OnUnderlineColor func (Color)
OnUnderlineColorTrue func (color.RGBA)
// OSC sequences from XTerm:
OnWindowTitle func (title string)
OnIconName func (name string)
OnIconFile func (path string)
OnXProperty func (property, value string)
OnSelectionPut func (selection, text string)
OnSelectionGet func (selection string)
OnQueryAllowed func ()
OnQueryDisallowed func ()
// OSC sequences from iTerm2:
OnCursorShape func (shape int)
OnHyperlink func (params map[string] string, text, link string)
OnBackgroundImage func (path string)
state decodeState
expectingST bool
gathered []byte
}
type decodeState int; const (
decodeStateText decodeState = iota
decodeStateAwaitC1
decodeStateGatherDCS
decodeStateGatherSOS
decodeStateGatherCSI
decodeStateGatherOSC
decodeStateGatherPM
decodeStateGatherAPC
)
func (decoder *Decoder) Write (buffer []byte) (wrote int, err error) {
wrote = len(buffer)
for len(buffer) > 0 {
switch decoder.state {
case decodeStateText:
if C0_Escape.Is(buffer[0]) {
// begin C1 control code
decoder.state = decodeStateAwaitC1
buffer = buffer[1:]
} else if buffer[0] < ' ' {
// process C0 control code
if decoder.OnC0 != nil {
decoder.OnC0(C0(buffer[0]))
}
buffer = buffer[1:]
} else {
// process as much plain text as we can
buffer = decoder.processString(buffer)
}
case decodeStateAwaitC1:
if buffer[0] < 128 {
// false alarm, this is just a C0 escape
if decoder.OnC0 != nil {
decoder.OnC0(C0_Escape)
}
break
}
switch C1(buffer[0]) {
case C1_DeviceControlString:
decoder.state = decodeStateGatherDCS
case C1_StartOfString:
decoder.state = decodeStateGatherSOS
case C1_ControlSequenceIntroducer:
decoder.state = decodeStateGatherCSI
case C1_OperatingSystemCommand:
decoder.state = decodeStateGatherOSC
case C1_PrivacyMessage:
decoder.state = decodeStateGatherPM
case C1_ApplicationProgramCommand:
decoder.state = decodeStateGatherAPC
default:
// process C1 control code
if decoder.OnC1 != nil {
decoder.OnC1(C1(buffer[0]))
}
}
buffer = buffer[1:]
case
decodeStateGatherDCS,
decodeStateGatherSOS,
decodeStateGatherOSC,
decodeStateGatherPM,
decodeStateGatherAPC:
if decoder.expectingST && C1_StringTerminator.Is(buffer[0]) {
// remove the trailing ESC
decoder.gathered = decoder.gathered [
:len(decoder.gathered) - 1]
if decoder.state == decodeStateGatherOSC {
// we understand some OSC codes so we
// handle them differently
decoder.processOSC()
} else {
// generic handler for uncommon stuff
decoder.processGeneric()
}
decoder.state = decodeStateText
}
if C0_Escape.Is(buffer[0]) {
decoder.expectingST = true
}
case decodeStateGatherCSI:
decoder.gather(buffer[0])
if buffer[0] < 0x30 || buffer[0] > 0x3F {
decoder.processCSI()
decoder.state = decodeStateText
}
buffer = buffer[1:]
}
}
return
}
func (decoder *Decoder) gather (character byte) {
decoder.gathered = append(decoder.gathered, character)
}
func (decoder *Decoder) processString (buffer []byte) []byte {
for index, char := range buffer {
if C0_Escape.Is(char) {
if decoder.OnText != nil {
decoder.OnText(string(buffer[:index]))
}
return buffer[:index]
}
}
return buffer
}
func (decoder *Decoder) processGeneric () {
parameter := string(decoder.gathered)
switch decoder.state {
case decodeStateGatherDCS:
if decoder.OnDCS != nil { decoder.OnDCS(parameter) }
case decodeStateGatherSOS:
if decoder.OnText != nil { decoder.OnText(parameter) }
case decodeStateGatherPM:
if decoder.OnPM != nil { decoder.OnPM(parameter) }
case decodeStateGatherAPC:
if decoder.OnAPC != nil { decoder.OnAPC(parameter) }
}
}
func (decoder *Decoder) processOSC () {
// TODO: analyze OSC
}
func (decoder *Decoder) processCSI () {
if len(decoder.gathered) < 2 { return }
parameters := ParameterInts(decoder.gathered)
var p0, p1, p2, p3 int
if len(parameters) > 0 { p0 = parameters[0] }
if len(parameters) > 1 { p1 = parameters[1] }
if len(parameters) > 2 { p2 = parameters[2] }
if len(parameters) > 3 { p3 = parameters[3] }
switch Last(decoder.gathered) {
case 'A': if decoder.OnCursorUp != nil { decoder.OnCursorUp(clampOne(p0)) }
case 'B': if decoder.OnCursorDown != nil { decoder.OnCursorDown(clampOne(p0)) }
case 'C': if decoder.OnCursorForward != nil { decoder.OnCursorForward(clampOne(p0)) }
case 'D': if decoder.OnCursorBack != nil { decoder.OnCursorBack(clampOne(p0)) }
case 'E': if decoder.OnCursorNextLine != nil { decoder.OnCursorNextLine(clampOne(p0)) }
case 'F': if decoder.OnCursorPreviousLine != nil { decoder.OnCursorPreviousLine(clampOne(p0)) }
case 'G': if decoder.OnCursorHorizontalAbsolute != nil { decoder.OnCursorHorizontalAbsolute(clampOne(p0)) }
case 'H', 'f': if decoder.OnCursorPosition != nil { decoder.OnCursorPosition(clampOne(p0), clampOne(p1)) }
case 'J': if decoder.OnEraseInDisplay != nil { decoder.OnEraseInDisplay(p0) }
case 'K': if decoder.OnEraseInLine != nil { decoder.OnEraseInLine(p0) }
case 'S': if decoder.OnScrollUp != nil { decoder.OnScrollUp(clampOne(p0)) }
case 'T': if decoder.OnScrollDown != nil { decoder.OnScrollDown(clampOne(p0)) }
case 'm':
p0 := SGR(p0)
switch {
case
p0 >= SGR_ForegroundColorBlack &&
p0 <= SGR_ForegroundColorWhite &&
decoder.OnForegroundColor != nil :
decoder.OnForegroundColor(Color(p0 - SGR_ForegroundColorBlack))
case
p0 >= SGR_ForegroundColorBrightBlack &&
p0 <= SGR_ForegroundColorBrightWhite &&
decoder.OnForegroundColor != nil :
decoder.OnForegroundColor(Color(p0 - SGR_ForegroundColorBrightBlack + 8))
case p0 == SGR_ForegroundColor:
switch p1 {
case 2:
if decoder.OnForegroundColor == nil { break }
decoder.OnForegroundColor(Color(p1))
case 5:
if decoder.OnForegroundColorTrue == nil { break }
decoder.OnForegroundColorTrue (color.RGBA {
R: uint8(p1),
G: uint8(p2),
B: uint8(p3),
A: 0xFF,
})
}
case
p0 >= SGR_BackgroundColorBlack &&
p0 <= SGR_BackgroundColorWhite &&
decoder.OnBackgroundColor != nil :
decoder.OnBackgroundColor(Color(p0 - SGR_BackgroundColorBlack))
case
p0 >= SGR_BackgroundColorBrightBlack &&
p0 <= SGR_BackgroundColorBrightWhite &&
decoder.OnBackgroundColor != nil :
decoder.OnBackgroundColor(Color(p0 - SGR_BackgroundColorBrightBlack + 8))
case p0 == SGR_BackgroundColor:
switch p1 {
case 2:
if decoder.OnBackgroundColor == nil { break }
decoder.OnBackgroundColor(Color(p1))
case 5:
if decoder.OnBackgroundColorTrue == nil { break }
decoder.OnBackgroundColorTrue (color.RGBA {
R: uint8(p1),
G: uint8(p2),
B: uint8(p3),
A: 0xFF,
})
}
case p0 == SGR_UnderlineColor:
switch p1 {
case 2:
if decoder.OnUnderlineColor == nil { break }
decoder.OnUnderlineColor(Color(p1))
case 5:
if decoder.OnUnderlineColorTrue == nil { break }
decoder.OnUnderlineColorTrue (color.RGBA {
R: uint8(p1),
G: uint8(p2),
B: uint8(p3),
A: 0xFF,
})
}
default: if decoder.OnSGR != nil { decoder.OnSGR(SGR(p0)) }
}
// TODO
case 'n':
case 's':
case 'u':
case 'h':
case 'l':
}
}
func clampOne (number int) int {
if number < 1 {
return 1
} else {
return number
}
}
// Last returns the last item of a slice.
func Last[T any] (source []T) T {
return source[len(source) - 1]
}
// ParameterStrings separates a byte slice by semicolons into a list of strings.
func ParameterStrings (source []byte) (parameters []string) {
parameters = strings.Split(string(source), ";")
for index := range parameters {
parameters[index] = strings.TrimSpace(parameters[index])
}
return
}
// ParameterInts is like ParameterStrings, but returns integers instead of
// strings. If a parameter is empty or cannot be converted into an integer, that
// parameter will be zero.
func ParameterInts (source []byte) (parameters []int) {
stringParameters := ParameterStrings(source)
parameters = make([]int, len(stringParameters))
for index, parameter := range stringParameters {
parameters[index], _ = strconv.Atoi(parameter)
}
return
}

View File

@ -1,97 +0,0 @@
package ansi
// SGR represents a list of Select Graphic Rendition parameters.
type SGR int; const (
SGR_Normal SGR = iota
SGR_Bold
SGR_Dim
SGR_Italic
SGR_Underline
SGR_SlowBlink
SGR_RapidBlink
SGR_Reverse
SGR_Conceal
SGR_Strike
SGR_FontPrimary
SGR_Font1
SGR_Font2
SGR_Font3
SGR_Font4
SGR_Font5
SGR_Font6
SGR_Font7
SGR_Font8
SGR_Font9
SGR_FontFraktur
SGR_DoubleUnderline
SGR_NormalIntensity
SGR_NeitherItalicNorBlackletter
SGR_NotUnderlined
SGR_NotBlinking
SGR_PorportionalSpacing
SGR_NotReversed
SGR_NotCrossedOut
SGR_ForegroundColorBlack
SGR_ForegroundColorRed
SGR_ForegroundColorGreen
SGR_ForegroundColorYellow
SGR_ForegroundColorBlue
SGR_ForegroundColorMagenta
SGR_ForegroundColorCyan
SGR_ForegroundColorWhite
SGR_ForegroundColor
SGR_ForegroundColorDefault
SGR_BackgroundColorBlack
SGR_BackgroundColorRed
SGR_BackgroundColorGreen
SGR_BackgroundColorYellow
SGR_BackgroundColorBlue
SGR_BackgroundColorMagenta
SGR_BackgroundColorCyan
SGR_BackgroundColorWhite
SGR_BackgroundColor
SGR_BackgroundColorDefault
SGR_DisablePorportionalSpacing
SGR_Framed
SGR_Encircled
SGR_Overlined
SGR_NeitherFramedNorEncircled
SGR_NotOverlined
SGR_UnderlineColor
SGR_UnderlineColorDefault
SGR_IdeogramUnderline
SGR_IdeogramDoubleUnderline
SGR_IdeogramOverline
SGR_IdeogramDoubleOverline
SGR_IdeogramStressMarking
SGR_NoIdeogramAttributes
SGR_Superscript
SGR_Subscript
SGR_NeitherSuperscriptNorSubscript
SGR_ForegroundColorBrightBlack
SGR_ForegroundColorBrightRed
SGR_ForegroundColorBrightGreen
SGR_ForegroundColorBrightYellow
SGR_ForegroundColorBrightBlue
SGR_ForegroundColorBrightMagenta
SGR_ForegroundColorBrightCyan
SGR_ForegroundColorBrightWhite
SGR_BackgroundColorBrightBlack
SGR_BackgroundColorBrightRed
SGR_BackgroundColorBrightGreen
SGR_BackgroundColorBrightYellow
SGR_BackgroundColorBrightBlue
SGR_BackgroundColorBrightMagenta
SGR_BackgroundColorBrightCyan
SGR_BackgroundColorBrightWhite
)

View File

@ -1,167 +0,0 @@
package elements
import "image"
import "golang.org/x/image/font"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
import "git.tebibyte.media/sashakoshka/tomo/default/config"
// import "git.tebibyte.media/sashakoshka/tomo/textdraw"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
type gridCell struct {
rune
tomo.FontStyle
background tomo.Color
foreground tomo.Color
clean bool
}
func (cell *gridCell) initColor () {
cell.background = tomo.ColorBackground
cell.foreground = tomo.ColorForeground
}
type gridBuffer struct {
cells []gridCell
stride int
}
// Grid is an array of monospaced character cells. Each one has a foreground and
// background color. It satisfies io.Writer and can be fed text with ANSI escape
// codes.
type Grid struct {
*core.Core
*core.FocusableCore
core core.CoreControl
cells []gridCell
stride int
cellWidth int
cellHeight int
cursor image.Point
face font.Face
config config.Wrapped
theme theme.Wrapped
onResize func ()
}
func NewGrid () (element *Grid) {
element = &Grid { }
element.theme.Case = tomo.C("tomo", "grid")
element.Core, element.core = core.NewCore(element, element.drawAndPush)
element.updateFont()
element.updateMinimumSize()
return
}
func (element *Grid) OnResize (callback func ()) {
element.onResize = callback
}
func (element *Grid) Write (data []byte) (wrote int, err error) {
// TODO process ansi escape codes etx
}
func (element *Grid) HandleMouseDown (x, y int, button input.Button) {
}
func (element *Grid) HandleMouseUp (x, y int, button input.Button) {
}
func (element *Grid) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
// TODO we need to grab shift ctrl c for copying text
}
func (element *Grid) HandleKeyUp(key input.Key, modifiers input.Modifiers) { }
// SetTheme sets the element's theme.
func (element *Grid) SetTheme (new tomo.Theme) {
if new == element.theme.Theme { return }
element.theme.Theme = new
element.updateFont()
element.updateMinimumSize()
element.drawAndPush()
}
// SetConfig sets the element's configuration.
func (element *Grid) SetConfig (new tomo.Config) {
if new == element.config.Config { return }
element.config.Config = new
element.updateMinimumSize()
element.drawAndPush()
}
func (element *Grid) alloc () bool {
bounds := element.Bounds()
width := bounds.Dx() / element.cellWidth
height := bounds.Dy() / element.cellHeight
unchanged :=
width == element.stride &&
height == len(element.cells) / element.stride
if unchanged { return false }
oldCells := element.cells
oldWidth := element.stride
oldHeight := len(element.cells) / element.stride
heightLarger := height < oldHeight
element.stride = width
element.cells = make([]gridCell, width * height)
// TODO: attempt to wrap text?
if heightLarger {
for index := range element.cells[oldHeight * width:] {
element.cells[index].initColor()
}}
commonHeight := height
if heightLarger { commonHeight = oldHeight }
for index := range element.cells[:commonHeight * width] {
x := index % width
if x < oldWidth {
element.cells[index] = oldCells[x + index / oldWidth]
} else {
element.cells[index].initColor()
}
}
if element.onResize != nil { element.onResize() }
return true
}
func (element *Grid) updateFont () {
element.face = element.theme.FontFace (
tomo.FontStyleMonospace,
tomo.FontSizeNormal)
emSpace, _ := element.face.GlyphAdvance('M')
metrics := element.face.Metrics()
element.cellWidth = emSpace.Round()
element.cellHeight = metrics.Height.Round()
}
func (element *Grid) updateMinimumSize () {
element.core.SetMinimumSize(element.cellWidth, element.cellHeight)
}
func (element *Grid) state () tomo.State {
return tomo.State {
}
}
func (element *Grid) drawAndPush () {
if element.core.HasImage () {
element.core.DamageRegion(element.draw(true))
}
}
func (element *Grid) draw (force bool) image.Rectangle {
}