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  }