diff --git a/go.mod b/go.mod index 893753b..65fb532 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/mattn/go-runewidth v0.0.2 github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d + github.com/stretchr/testify v1.3.0 golang.org/x/arch v0.0.0-20181203225421-5a4828bb7045 // indirect golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b // indirect golang.org/x/sys v0.0.0-20190124100055-b90733256f2e // indirect diff --git a/go.sum b/go.sum index 5fee113..5f62130 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/cjbassi/drawille-go v0.0.0-20190126131713-27dc511fe6fd h1:XtfPmj9tQRilnrEmI1HjQhxXWRhEM+m8CACtaMJE/kM= github.com/cjbassi/drawille-go v0.0.0-20190126131713-27dc511fe6fd/go.mod h1:vjcQJUZJYD3MeVGhtZXSMnCHfUNZxsyYzJt90eCYxK4= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/pprof v0.0.0-20190109223431-e84dfd68c163 h1:beB+Da4k9B1zmgag78k3k1Bx4L/fdWr5FwNa0f8RxmY= github.com/google/pprof v0.0.0-20190109223431-e84dfd68c163/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6 h1:UDMh68UUwekSh5iP2OMhRRZJiiBccgV7axzUG8vi56c= @@ -10,6 +12,11 @@ github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzC github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840= github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/arch v0.0.0-20181203225421-5a4828bb7045 h1:Pn8fQdvx+z1avAi7fdM2kRYWQNxGlavNDSyzrQg2SsU= golang.org/x/arch v0.0.0-20181203225421-5a4828bb7045/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8= golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b h1:Elez2XeF2p9uyVj0yEUDqQ56NFcDtcBNkYP7yv8YbUE= diff --git a/style.go b/style.go index 3f8bb27..46a1914 100644 --- a/style.go +++ b/style.go @@ -1,5 +1,7 @@ package termui +import "strings" + // Color is an integer from -1 to 255 // -1 = ColorClear // 0-255 = Xterm colors @@ -63,3 +65,22 @@ func NewStyle(fg Color, args ...interface{}) Style { modifier, } } + +//String returns a string representation of a Style +func (self Style) String() string { + styles := make([]string, 0) + + if color, ok := textColorMap[self.Fg]; ok && self.Fg != StyleClear.Fg { + styles = append(styles, tokenFg + tokenValueSeparator + color) + } + + if color, ok := textColorMap[self.Bg]; ok && self.Bg != StyleClear.Bg { + styles = append(styles, tokenBg + tokenValueSeparator + color) + } + + if mod, ok := textModifierMap[self.Modifier]; ok && self.Modifier != StyleClear.Modifier { + styles = append(styles, tokenModifier + tokenValueSeparator + mod) + } + + return strings.Join(styles, tokenItemSeparator) +} diff --git a/style_parser.go b/style_parser.go index ce537da..f9e6793 100644 --- a/style_parser.go +++ b/style_parser.go @@ -44,12 +44,30 @@ var StyleParserColorMap = map[string]Color{ "magenta": ColorMagenta, } +var textColorMap = map[Color]string{ + ColorRed: "red", + ColorBlue: "blue", + ColorBlack: "black", + ColorCyan: "cyan", + ColorYellow: "yellow", + ColorWhite: "white", + ColorClear: "clear", + ColorGreen: "green", + ColorMagenta: "magenta", +} + var modifierMap = map[string]Modifier{ "bold": ModifierBold, "underline": ModifierUnderline, "reverse": ModifierReverse, } +var textModifierMap = map[Modifier]string{ + ModifierBold: "bold", + ModifierUnderline: "underline", + ModifierReverse: "reverse", +} + // readStyle translates an []rune like `fg:red,mod:bold,bg:white` to a style func readStyle(runes []rune, defaultStyle Style) Style { style := defaultStyle diff --git a/utils.go b/utils.go index fc2ea55..31a7e0d 100644 --- a/utils.go +++ b/utils.go @@ -8,6 +8,7 @@ import ( "fmt" "math" "reflect" + "strings" rw "github.com/mattn/go-runewidth" wordwrap "github.com/mitchellh/go-wordwrap" @@ -187,6 +188,40 @@ func RunesToStyledCells(runes []rune, style Style) []Cell { return cells } +//CellsToText converts []Cell to a string without any formatting tags +func CellsToText(cells []Cell) string { + runes := make([]rune, len(cells)) + for i, cell := range cells { + runes[i] = cell.Rune + } + return string(runes) +} + +//CellsToStyledText converts []Cell to a string preserving the formatting tags +func CellsToStyledText(cells []Cell, defaultStyle Style) string { + sb := strings.Builder{} + runes := make([]rune, len(cells)) + currentStyle := cells[0].Style + var j int + + for _, cell := range cells { + if currentStyle != cell.Style { + writeText(&sb, runes[:j], currentStyle, defaultStyle) + + currentStyle = cell.Style + j=0 + } + + runes[j] = cell.Rune + j++ + } + + //Write the last characters left in runes slice + writeText(&sb, runes[:j], currentStyle, defaultStyle) + + return sb.String() +} + func CellsToString(cells []Cell) string { runes := make([]rune, len(cells)) for i, cell := range cells { @@ -195,6 +230,19 @@ func CellsToString(cells []Cell) string { return string(runes) } +func writeText(sb *strings.Builder,runes []rune, currentStyle Style, defaultStyle Style) { + if currentStyle != defaultStyle { + sb.WriteByte(tokenBeginStyledText) + sb.WriteString(string(runes)) + sb.WriteByte(tokenEndStyledText) + sb.WriteByte(tokenBeginStyle) + sb.WriteString(currentStyle.String()) + sb.WriteByte(tokenEndStyle) + } else { + sb.WriteString(string(runes)) + } +} + func TrimCells(cells []Cell, w int) []Cell { s := CellsToString(cells) s = TrimString(s, w) @@ -221,6 +269,22 @@ func SplitCells(cells []Cell, r rune) [][]Cell { return splitCells } +//JoinCells converts [][]cell to a []cell using r as line breaker +func JoinCells(cells [][]Cell, r rune) []Cell { + joinCells := make([]Cell, 0) + lb := Cell{Rune: r} + length := len(cells) + + for i, cell := range cells { + if i < length - 1 { + cell = append(cell, lb) + } + joinCells = append(joinCells, cell...) + } + + return joinCells +} + type CellWithX struct { X int Cell Cell diff --git a/widgets/textbox.go b/widgets/textbox.go index 3d50f9a..35cee54 100644 --- a/widgets/textbox.go +++ b/widgets/textbox.go @@ -116,6 +116,20 @@ func (self *TextBox) SetText(input string) { self.InsertText(input) } +//GetText gets the text in string format along all its formatting tags +func (self *TextBox) Text() string { + cells := JoinCells(self.text, '\n') + + return CellsToStyledText(cells, self.TextStyle) +} + +//GetText gets the text in string format without any formatting tags +func (self *TextBox) RawText() string { + cells := JoinCells(self.text, '\n') + + return CellsToText(cells) +} + func (self *TextBox) MoveCursorLeft() { self.MoveCursor(self.cursorPoint.X-1, self.cursorPoint.Y) } diff --git a/widgets/textbox_test.go b/widgets/textbox_test.go new file mode 100644 index 0000000..2255193 --- /dev/null +++ b/widgets/textbox_test.go @@ -0,0 +1,48 @@ +package widgets + +import ( + "testing" + "github.com/stretchr/testify/assert" +) + +//TestGetRawText test simple string +func TestGetRawText(t *testing.T) { + text := "My Sample RawText" + tb := NewTextBox() + tb.SetText(text) + + assert.Equal(t, text, tb.RawText()) +} + +//TestGetRawTextWithLBs test line breaks in the text +func TestGetRawTextWithLBs(t *testing.T) { + text := `My Sample RawText + with + line + breaks` + + tb := NewTextBox() + tb.SetText(text) + + assert.Equal(t, text, tb.RawText()) +} + +//TestGetStyledText test styled text +func TestGetStyledText(t *testing.T) { + text := "[red text](fg:red,mod:bold) more text [blue text](fg:blue,mod:bold) a bit more" + + tb := NewTextBox() + tb.SetText(text) + + assert.Equal(t, text, tb.Text()) +} + +//TestGetStyledText2 test styled text ending in a styled string +func TestGetStyledText2(t *testing.T) { + text := "[red text](fg:red,mod:bold) more text [blue text](fg:blue,mod:bold) a [bit more](fg:black)" + + tb := NewTextBox() + tb.SetText(text) + + assert.Equal(t, text, tb.Text()) +} \ No newline at end of file