github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/ui/mark_lines.go (about) 1 package ui 2 3 // RuneStylesheet maps runes to stylings. 4 type RuneStylesheet map[rune]Styling 5 6 // MarkLines provides a way to construct a styled text by separating the content 7 // and the styling. 8 // 9 // The arguments are groups of either 10 // 11 // * A single string, in which case it represents an unstyled line; 12 // 13 // * Three arguments that can be passed to MarkLine, in which case they are passed 14 // to MarkLine and the return value is used as a styled line. 15 // 16 // Lines represented by all the groups are joined together. 17 // 18 // This function is mainly useful for constructing multi-line Text's with 19 // alignment across those lines. An example: 20 // 21 // var stylesheet = map[rune]string{ 22 // '-': Reverse, 23 // 'x': Stylings(Blue, BgGreen), 24 // } 25 // var text = FromMarkedLines( 26 // "foo bar foobar", stylesheet, 27 // "--- xxx ------" 28 // "lorem ipsum dolar", 29 // ) 30 func MarkLines(args ...interface{}) Text { 31 var text Text 32 for i := 0; i < len(args); i++ { 33 line, ok := args[i].(string) 34 if !ok { 35 // TODO(xiaq): Surface the error. 36 continue 37 } 38 if i+2 < len(args) { 39 if stylesheet, ok := args[i+1].(RuneStylesheet); ok { 40 if style, ok := args[i+2].(string); ok { 41 text = Concat(text, MarkText(line, stylesheet, style)) 42 i += 2 43 continue 44 } 45 } 46 } 47 text = Concat(text, T(line)) 48 } 49 return text 50 } 51 52 // MarkText applies styles to all the runes in the line, using the runes in 53 // the style string. The stylesheet argument specifies which style each rune 54 // represents. 55 func MarkText(line string, stylesheet RuneStylesheet, style string) Text { 56 var text Text 57 styleRuns := toRuns(style) 58 for _, styleRun := range styleRuns { 59 i := bytesForFirstNRunes(line, styleRun.n) 60 text = Concat(text, T(line[:i], stylesheet[styleRun.r])) 61 line = line[i:] 62 } 63 if len(line) > 0 { 64 text = Concat(text, T(line)) 65 } 66 return text 67 } 68 69 type run struct { 70 r rune 71 n int 72 } 73 74 func toRuns(s string) []run { 75 var runs []run 76 current := run{} 77 for _, r := range s { 78 if r != current.r { 79 if current.n > 0 { 80 runs = append(runs, current) 81 } 82 current = run{r, 1} 83 } else { 84 current.n++ 85 } 86 } 87 if current.n > 0 { 88 runs = append(runs, current) 89 } 90 return runs 91 } 92 93 func bytesForFirstNRunes(s string, n int) int { 94 k := 0 95 for i := range s { 96 if k == n { 97 return i 98 } 99 k++ 100 } 101 return len(s) 102 }