-
Notifications
You must be signed in to change notification settings - Fork 302
/
markup.go
188 lines (160 loc) · 5.95 KB
/
markup.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// Copyright (c) 2013-2024 by Michael Dvorkin and contributors. All Rights Reserved.
// Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file.
package mop
import (
"regexp"
"strings"
"github.com/nsf/termbox-go"
)
// Markup implements some minimalistic text formatting conventions that
// get translated to Termbox colors and attributes. To colorize a string
// wrap it in <color-name>...</> tags. Unlike HTML each tag sets a new
// color whereas the </> tag changes color back to default. For example:
//
// <green>Hello, <red>world!</>
//
// The color tags could be combined with the attributes: <b>...</b> for
// bold, <u>...</u> for underline, and <r>...</r> for reverse. Unlike
// colors the attributes require matching closing tag.
//
// The <right>...</right> tag is used to right align the enclosed string
// (ex. when displaying current time in the upper right corner).
type Markup struct {
Foreground termbox.Attribute // Foreground color.
Background termbox.Attribute // Background color (so far always termbox.ColorDefault).
RightAligned bool // True when the string is right aligned.
tags map[string]termbox.Attribute // Tags to Termbox translation hash.
regex *regexp.Regexp // Regex to identify the supported tag names.
}
// Creates markup to define tag to Termbox translation rules and store default
// colors and column alignments.
func NewMarkup(profile *Profile) *Markup {
markup := &Markup{}
markup.tags = make(map[string]termbox.Attribute)
markup.tags[`/`] = termbox.ColorDefault
markup.tags[`black`] = termbox.ColorBlack
markup.tags[`red`] = termbox.ColorRed
markup.tags[`green`] = termbox.ColorGreen
markup.tags[`yellow`] = termbox.ColorYellow
markup.tags[`blue`] = termbox.ColorBlue
markup.tags[`magenta`] = termbox.ColorMagenta
markup.tags[`cyan`] = termbox.ColorCyan
markup.tags[`white`] = termbox.ColorWhite
markup.tags[`darkgray`] = termbox.ColorDarkGray
markup.tags[`lightred`] = termbox.ColorLightRed
markup.tags[`lightgreen`] = termbox.ColorLightGreen
markup.tags[`lightyellow`] = termbox.ColorLightYellow
markup.tags[`lightblue`] = termbox.ColorLightBlue
markup.tags[`lightmagenta`] = termbox.ColorLightMagenta
markup.tags[`lightcyan`] = termbox.ColorLightCyan
markup.tags[`lightgray`] = termbox.ColorLightGray
markup.tags[`right`] = termbox.ColorDefault // Termbox can combine attributes and a single color using bitwise OR.
markup.tags[`b`] = termbox.AttrBold // Attribute = 1 << (iota + 4)
markup.tags[`u`] = termbox.AttrUnderline
markup.tags[`r`] = termbox.AttrReverse
// Semantic markups
markup.tags[`gain`] = markup.tags[profile.Colors.Gain]
markup.tags[`loss`] = markup.tags[profile.Colors.Loss]
markup.tags[`tag`] = markup.tags[profile.Colors.Tag]
markup.tags[`header`] = markup.tags[profile.Colors.Header]
markup.tags[`time`] = markup.tags[profile.Colors.Time]
markup.tags[`default`] = markup.tags[profile.Colors.Default]
markup.Foreground = markup.tags[profile.Colors.Default]
markup.Background = termbox.ColorDefault
markup.RightAligned = false
markup.regex = markup.supportedTags() // Once we have the hash we could build the regex.
return markup
}
// Tokenize works just like strings.Split() except the resulting array includes
// the delimiters. For example, the "<green>Hello, <red>world!</>" string when
// tokenized by tags produces the following:
//
// [0] "<green>"
// [1] "Hello, "
// [2] "<red>"
// [3] "world!"
// [4] "</>"
//
func (markup *Markup) Tokenize(str string) []string {
matches := markup.regex.FindAllStringIndex(str, -1)
strings := make([]string, 0, len(matches))
head, tail := 0, 0
for _, match := range matches {
tail = match[0]
if match[1] != 0 {
if head != 0 || tail != 0 {
// Append the text between tags.
strings = append(strings, str[head:tail])
}
// Append the tag itmarkup.
strings = append(strings, str[match[0]:match[1]])
}
head = match[1]
}
if head != len(str) && tail != len(str) {
strings = append(strings, str[head:])
}
return strings
}
// IsTag returns true when the given string looks like markup tag. When the
// tag name matches one of the markup-supported tags it gets translated to
// relevant Termbox attributes and colors.
func (markup *Markup) IsTag(str string) bool {
tag, open := probeForTag(str)
if tag == `` {
return false
}
return markup.process(tag, open)
}
//-----------------------------------------------------------------------------
func (markup *Markup) process(tag string, open bool) bool {
if attribute, ok := markup.tags[tag]; ok {
switch tag {
case `right`:
markup.RightAligned = open // On for <right>, off for </right>.
default:
if open {
if attribute >= termbox.AttrBold {
markup.Foreground |= attribute // Set the Termbox attribute.
} else {
markup.Foreground = attribute // Set the Termbox color.
}
} else {
if attribute >= termbox.AttrBold {
markup.Foreground &= ^attribute // Clear the Termbox attribute.
} else {
markup.Foreground = markup.tags[`default`]
}
}
}
}
return true
}
// supportedTags returns regular expression that matches all possible tags
// supported by the markup, i.e. </?black>|</?red>| ... |<?b>| ... |</?right>
func (markup *Markup) supportedTags() *regexp.Regexp {
arr := []string{}
for tag := range markup.tags {
arr = append(arr, `</?`+tag+`>`)
}
return regexp.MustCompile(strings.Join(arr, `|`))
}
//-----------------------------------------------------------------------------
func probeForTag(str string) (string, bool) {
if len(str) > 2 && str[0:1] == `<` && str[len(str)-1:] == `>` {
return extractTagName(str), str[1:2] != `/`
}
return ``, false
}
// Extract tag name from the given tag, i.e. `<hello>` => `hello`.
func extractTagName(str string) string {
if len(str) < 3 {
return ``
} else if str[1:2] != `/` {
return str[1 : len(str)-1]
} else if len(str) > 3 {
return str[2 : len(str)-1]
}
return `/`
}