Moved terminal stuff to a separate repository
This commit is contained in:
		
							parent
							
								
									43a664009c
								
							
						
					
					
						commit
						dc077a02ab
					
				
							
								
								
									
										38
									
								
								ansi/c0.go
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								ansi/c0.go
									
									
									
									
									
								
							@ -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
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										48
									
								
								ansi/c1.go
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								ansi/c1.go
									
									
									
									
									
								
							@ -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
 | 
			
		||||
}
 | 
			
		||||
@ -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()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								ansi/csi.go
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								ansi/csi.go
									
									
									
									
									
								
							@ -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
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										360
									
								
								ansi/decoder.go
									
									
									
									
									
								
							
							
						
						
									
										360
									
								
								ansi/decoder.go
									
									
									
									
									
								
							@ -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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										97
									
								
								ansi/sgr.go
									
									
									
									
									
								
							
							
						
						
									
										97
									
								
								ansi/sgr.go
									
									
									
									
									
								
							@ -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
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										167
									
								
								elements/grid.go
									
									
									
									
									
								
							
							
						
						
									
										167
									
								
								elements/grid.go
									
									
									
									
									
								
							@ -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 {
 | 
			
		||||
	
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user