add my own session manager
This commit is contained in:
@@ -0,0 +1 @@
|
||||
coverage.txt
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 ktr
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
# go-ansisgr
|
||||
|
||||
[](https://pkg.go.dev/github.com/ktr0731/go-ansisgr)
|
||||
[](https://github.com/ktr0731/go-ansisgr/actions)
|
||||
[](https://codecov.io/gh/ktr0731/go-ansisgr)
|
||||
|
||||
`go-ansisgr` provides a SGR (Select Graphic Rendition, a part of ANSI Escape Sequence) parser.
|
||||
|
||||
- 16 colors, 256 colors and RGB colors support
|
||||
- All attributes support
|
||||
|
||||
## Installation
|
||||
``` bash
|
||||
go get github.com/ktr0731/go-fuzzyfinder
|
||||
```
|
||||
|
||||
## Usage
|
||||
`ansisgr.NewIterator` is the only entry-point API. This function returns an iteratorw which consumes the passed string.
|
||||
|
||||
``` go
|
||||
in := "a\x1b[1;31mb"
|
||||
iter := ansisgr.NewIterator(in)
|
||||
|
||||
for {
|
||||
r, style, ok := iter.Next()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
// do something.
|
||||
}
|
||||
```
|
||||
|
||||
`r` is a rune, and `style` is the foreground/background color and attributes `r` has.
|
||||
|
||||
``` go
|
||||
if color, ok := style.Foreground(); ok {
|
||||
// Foreground color is specified.
|
||||
}
|
||||
if color, ok := style.Background(); ok {
|
||||
// Background color is specified.
|
||||
}
|
||||
```
|
||||
|
||||
The attribute method reports whether `r` has the attribute.
|
||||
|
||||
``` go
|
||||
style.Bold()
|
||||
style.Italic()
|
||||
```
|
||||
|
||||
## go-ansisgr with gdamore/tcell
|
||||
`go-ansisgr` is useful when you construct a rich terminal user interface by using `gdamore/tcell` or others. Although `gdamore/tcell` and other libraries provides SGR functionality from their API, they doesn't support "raw" strings which contain ANSI Escape Sequence. Therefore, `go-ansisgr` translates these strings and makes it easy to use the TUI library's functionality.
|
||||
For example, the following code displays colored string powered by `gdamore/tcell`.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
screen, err := tcell.NewScreen()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer screen.Fini()
|
||||
|
||||
if err := screen.Init(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
in := "\x1b[38;2;100;200;200mhello, \x1b[0;1;30;48;5;245mworld!"
|
||||
iter := ansisgr.NewIterator(in)
|
||||
for i := 0; ; i++ {
|
||||
r, rstyle, ok := iter.Next()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
style := tcell.StyleDefault
|
||||
if color, ok := rstyle.Foreground(); ok {
|
||||
switch color.Mode() {
|
||||
case ansisgr.Mode16:
|
||||
style = style.Foreground(tcell.PaletteColor(color.Value() - 30))
|
||||
case ansisgr.Mode256:
|
||||
style = style.Foreground(tcell.PaletteColor(color.Value()))
|
||||
case ansisgr.ModeRGB:
|
||||
r, g, b := color.RGB()
|
||||
style = style.Foreground(tcell.NewRGBColor(int32(r), int32(g), int32(b)))
|
||||
}
|
||||
}
|
||||
if color, valid := rstyle.Background(); valid {
|
||||
switch color.Mode() {
|
||||
case ansisgr.Mode16:
|
||||
style = style.Background(tcell.PaletteColor(color.Value() - 40))
|
||||
case ansisgr.Mode256:
|
||||
style = style.Background(tcell.PaletteColor(color.Value()))
|
||||
case ansisgr.ModeRGB:
|
||||
r, g, b := color.RGB()
|
||||
style = style.Background(tcell.NewRGBColor(int32(r), int32(g), int32(b)))
|
||||
}
|
||||
}
|
||||
|
||||
style = style.
|
||||
Bold(rstyle.Bold()).
|
||||
Dim(rstyle.Dim()).
|
||||
Italic(rstyle.Italic()).
|
||||
Underline(rstyle.Underline()).
|
||||
Blink(rstyle.Blink()).
|
||||
Reverse(rstyle.Reverse()).
|
||||
StrikeThrough(rstyle.Strikethrough())
|
||||
|
||||
screen.SetContent(i, 0, r, nil, style)
|
||||
}
|
||||
|
||||
screen.Show()
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
```
|
||||
+296
@@ -0,0 +1,296 @@
|
||||
// go-ansisgr provides an SGR (Select Graphic Rendition, a part of ANSI Escape Sequence) parser.
|
||||
package ansisgr
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
attrReset = 0
|
||||
attrBold = 1 << iota
|
||||
attrDim
|
||||
attrItalic
|
||||
attrUnderline
|
||||
attrBlink
|
||||
attrReverse
|
||||
attrInvisible
|
||||
attrStrikethrough
|
||||
)
|
||||
|
||||
const (
|
||||
colorValid = 1 << (24 + iota)
|
||||
colorIs16
|
||||
colorIs256
|
||||
colorIsRGB
|
||||
)
|
||||
|
||||
// Style represents a set of attributes, foreground color, and background color.
|
||||
type Style struct {
|
||||
attr int
|
||||
foreground int
|
||||
background int
|
||||
}
|
||||
|
||||
// Mode represents a color syntax.
|
||||
type Mode int
|
||||
|
||||
const (
|
||||
// ModeNone is used to when color is not specified.
|
||||
ModeNone Mode = iota
|
||||
// Mode16 represents 16 colors.
|
||||
Mode16
|
||||
// Mode256 represents 256 colors.
|
||||
Mode256
|
||||
// ModeRGB represents RGB colors (a.k.a. True Color).
|
||||
ModeRGB
|
||||
)
|
||||
|
||||
// Color represents color value specified by SGR.
|
||||
type Color struct{ v int }
|
||||
|
||||
// Mode returns the color's Mode.
|
||||
func (c Color) Mode() Mode {
|
||||
switch {
|
||||
case c.v&colorIs16 == colorIs16:
|
||||
return Mode16
|
||||
case c.v&colorIs256 == colorIs256:
|
||||
return Mode256
|
||||
case c.v&colorIsRGB == colorIsRGB:
|
||||
return ModeRGB
|
||||
}
|
||||
|
||||
return ModeNone
|
||||
}
|
||||
|
||||
// RGB returns red, green, and blue color values. It assumes that c.Mode() == ModeRGB.
|
||||
func (c Color) RGB() (int, int, int) {
|
||||
return 0xff0000 & c.v >> 16, 0x00ff00 & c.v >> 8, 0x0000ff & c.v
|
||||
}
|
||||
|
||||
// Value returns the color value represented by int.
|
||||
// For example, '\x1b[31m' is 31, '\x1b[38;5;116m' is 116, and '\x1b[38;2;10;20;30' is 660510 (0x0a141e).
|
||||
func (c Color) Value() int { return c.v & 0xffffff }
|
||||
|
||||
// Foreground returns the foreground color. the second return value indicates whether the color is valid or not.
|
||||
func (s *Style) Foreground() (Color, bool) {
|
||||
return Color{v: s.foreground}, s.foreground&colorValid == colorValid
|
||||
}
|
||||
|
||||
// Background returns the background color. the second return value indicates whether the color is valid or not.
|
||||
func (s *Style) Background() (Color, bool) {
|
||||
return Color{v: s.background}, s.background&colorValid == colorValid
|
||||
}
|
||||
|
||||
// Bold indicates whether bold is enabled.
|
||||
func (s *Style) Bold() bool { return s.attr&attrBold == attrBold }
|
||||
|
||||
// Dim indicates whether dim (faint) is enabled.
|
||||
func (s *Style) Dim() bool { return s.attr&attrDim == attrDim }
|
||||
|
||||
// Italic indicates whether italic is enabled.
|
||||
func (s *Style) Italic() bool { return s.attr&attrItalic == attrItalic }
|
||||
|
||||
// Underline indicates whether underline is enabled.
|
||||
func (s *Style) Underline() bool { return s.attr&attrUnderline == attrUnderline }
|
||||
|
||||
// Blink indicates whether blink is enabled.
|
||||
func (s *Style) Blink() bool { return s.attr&attrBlink == attrBlink }
|
||||
|
||||
// Reverse indicates whether reverse (hidden) is enabled.
|
||||
func (s *Style) Reverse() bool { return s.attr&attrReverse == attrReverse }
|
||||
|
||||
// Invisible indicates whether invisible is enabled.
|
||||
func (s *Style) Invisible() bool { return s.attr&attrInvisible == attrInvisible }
|
||||
|
||||
// Strikethrough indicates whether strikethrough is enabled.
|
||||
func (s *Style) Strikethrough() bool { return s.attr&attrStrikethrough == attrStrikethrough }
|
||||
|
||||
// Iterator is an iterator over a parsed string.
|
||||
type Iterator struct {
|
||||
runes []rune
|
||||
i int
|
||||
style Style
|
||||
}
|
||||
|
||||
// NewIterator returns a new Iterator.
|
||||
func NewIterator(s string) *Iterator {
|
||||
return &Iterator{i: -1, runes: []rune(s)}
|
||||
}
|
||||
|
||||
// Next returns a next rune and its style. the third return value indicates there is a next value.
|
||||
func (a *Iterator) Next() (rune, Style, bool) {
|
||||
for a.next() {
|
||||
r := a.runes[a.i]
|
||||
if r != 0x1b {
|
||||
return r, a.style, true
|
||||
}
|
||||
|
||||
if ok := a.next(); ok && a.runes[a.i] != '[' {
|
||||
continue
|
||||
}
|
||||
|
||||
args := a.consumeSequence()
|
||||
LOOP:
|
||||
for len(args) != 0 {
|
||||
if args[0] == 38 {
|
||||
offset, consumed := consumeAs256OrRGB(args)
|
||||
args = args[offset:]
|
||||
if consumed.valid {
|
||||
a.style.foreground = consumed.v
|
||||
continue
|
||||
}
|
||||
} else if args[0] == 48 {
|
||||
offset, consumed := consumeAs256OrRGB(args)
|
||||
args = args[offset:]
|
||||
if consumed.valid {
|
||||
a.style.background = consumed.v
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for _, arg := range args {
|
||||
switch arg {
|
||||
case 38, 48:
|
||||
if len(args) != 1 {
|
||||
continue LOOP
|
||||
}
|
||||
|
||||
// Ignore when the arg is the last element.
|
||||
case 0:
|
||||
a.style.attr = attrReset
|
||||
a.style.foreground = 0 | colorIs16
|
||||
a.style.background = 0 | colorIs16
|
||||
case 39:
|
||||
a.style.foreground = 0 | colorIs16
|
||||
case 49:
|
||||
a.style.background = 0 | colorIs16
|
||||
case 1:
|
||||
a.style.attr |= attrBold
|
||||
case 2:
|
||||
a.style.attr |= attrDim
|
||||
case 3:
|
||||
a.style.attr |= attrItalic
|
||||
case 4:
|
||||
a.style.attr |= attrUnderline
|
||||
case 5:
|
||||
a.style.attr |= attrBlink
|
||||
case 7:
|
||||
a.style.attr |= attrReverse
|
||||
case 8:
|
||||
a.style.attr |= attrInvisible
|
||||
case 9:
|
||||
a.style.attr |= attrStrikethrough
|
||||
case 22:
|
||||
if a.style.attr&attrBold == attrBold {
|
||||
a.style.attr ^= attrBold
|
||||
}
|
||||
if a.style.attr&attrDim == attrDim {
|
||||
a.style.attr ^= attrDim
|
||||
}
|
||||
case 23:
|
||||
a.style.attr ^= attrItalic
|
||||
case 24:
|
||||
a.style.attr ^= attrUnderline
|
||||
case 25:
|
||||
a.style.attr ^= attrBlink
|
||||
case 27:
|
||||
a.style.attr ^= attrReverse
|
||||
case 28:
|
||||
a.style.attr ^= attrInvisible
|
||||
case 29:
|
||||
a.style.attr ^= attrStrikethrough
|
||||
default:
|
||||
switch {
|
||||
case (arg >= 30 && arg <= 37):
|
||||
a.style.foreground = arg | colorIs16 | colorValid
|
||||
case arg >= 40 && arg <= 47:
|
||||
a.style.background = arg | colorIs16 | colorValid
|
||||
case arg >= 90 && arg <= 97:
|
||||
a.style.foreground = arg | colorIs16 | colorValid
|
||||
case arg >= 100 && arg <= 107:
|
||||
a.style.background = arg | colorIs16 | colorValid
|
||||
}
|
||||
}
|
||||
|
||||
args = args[1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, Style{}, false
|
||||
}
|
||||
|
||||
func (a *Iterator) next() bool {
|
||||
a.i++
|
||||
return a.i < len(a.runes)
|
||||
}
|
||||
|
||||
func (a *Iterator) consumeSequence() []int {
|
||||
var args []int
|
||||
var val int
|
||||
|
||||
for a.next() {
|
||||
switch a.runes[a.i] {
|
||||
case 'm':
|
||||
return append(args, val)
|
||||
case ';':
|
||||
args = append(args, val)
|
||||
val = 0
|
||||
default:
|
||||
if !unicode.IsDigit(a.runes[a.i]) {
|
||||
return nil // Broken sequence, ignore
|
||||
}
|
||||
|
||||
val = val*10 + int(a.runes[a.i]-'0')
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type consumedValue struct {
|
||||
v int
|
||||
valid bool
|
||||
}
|
||||
|
||||
func consumeAs256OrRGB(args []int) (offset int, v consumedValue) {
|
||||
if len(args) < 2 || (args[0] != 38 && args[0] != 48) {
|
||||
return 0, v
|
||||
}
|
||||
|
||||
switch args[1] {
|
||||
case 5: // 256
|
||||
if l := len(args); l < 3 {
|
||||
return l, v
|
||||
}
|
||||
|
||||
if args[2] > 255 {
|
||||
return 3, v
|
||||
}
|
||||
|
||||
return 3, consumedValue{
|
||||
v: args[2] | colorValid | colorIs256,
|
||||
valid: true,
|
||||
}
|
||||
case 2: // RGB
|
||||
if l := len(args); l < 5 {
|
||||
return l, v
|
||||
}
|
||||
|
||||
var val int
|
||||
for i := 0; i < 3; i++ {
|
||||
if args[i+2] > 255 {
|
||||
return i + 2 + 1, v
|
||||
}
|
||||
|
||||
val |= args[i+2] << (8 * (2 - i))
|
||||
}
|
||||
|
||||
return 5, consumedValue{
|
||||
v: val | colorValid | colorIsRGB,
|
||||
valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
return 2, v
|
||||
}
|
||||
Reference in New Issue
Block a user