github.com/elves/elvish@v0.15.0/pkg/cli/term/buffer_builder.go (about)

     1  package term
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/elves/elvish/pkg/ui"
     7  	"github.com/elves/elvish/pkg/wcwidth"
     8  )
     9  
    10  // BufferBuilder supports building of Buffer.
    11  type BufferBuilder struct {
    12  	Width, Col, Indent int
    13  	// EagerWrap controls whether to wrap line as soon as the cursor reaches the
    14  	// right edge of the terminal. This is not often desirable as it creates
    15  	// unneessary line breaks, but is is useful when echoing the user input.
    16  	// will otherwise
    17  	EagerWrap bool
    18  	// Lines the content of the buffer.
    19  	Lines [][]Cell
    20  	// Dot is what the user perceives as the cursor.
    21  	Dot Pos
    22  }
    23  
    24  // NewBufferBuilder makes a new BufferBuilder, initially with one empty line.
    25  func NewBufferBuilder(width int) *BufferBuilder {
    26  	return &BufferBuilder{Width: width, Lines: [][]Cell{make([]Cell, 0, width)}}
    27  }
    28  
    29  func (bb *BufferBuilder) Cursor() Pos {
    30  	return Pos{len(bb.Lines) - 1, bb.Col}
    31  }
    32  
    33  // Buffer returns a Buffer built by the BufferBuilder.
    34  func (bb *BufferBuilder) Buffer() *Buffer {
    35  	return &Buffer{bb.Width, bb.Lines, bb.Dot}
    36  }
    37  
    38  func (bb *BufferBuilder) SetIndent(indent int) *BufferBuilder {
    39  	bb.Indent = indent
    40  	return bb
    41  }
    42  
    43  func (bb *BufferBuilder) SetEagerWrap(v bool) *BufferBuilder {
    44  	bb.EagerWrap = v
    45  	return bb
    46  }
    47  
    48  func (bb *BufferBuilder) setDot(dot Pos) *BufferBuilder {
    49  	bb.Dot = dot
    50  	return bb
    51  }
    52  
    53  func (bb *BufferBuilder) SetDotHere() *BufferBuilder {
    54  	return bb.setDot(bb.Cursor())
    55  }
    56  
    57  func (bb *BufferBuilder) appendLine() {
    58  	bb.Lines = append(bb.Lines, make([]Cell, 0, bb.Width))
    59  	bb.Col = 0
    60  }
    61  
    62  func (bb *BufferBuilder) appendCell(c Cell) {
    63  	n := len(bb.Lines)
    64  	bb.Lines[n-1] = append(bb.Lines[n-1], c)
    65  	bb.Col += wcwidth.Of(c.Text)
    66  }
    67  
    68  // Newline starts a newline.
    69  func (bb *BufferBuilder) Newline() *BufferBuilder {
    70  	bb.appendLine()
    71  
    72  	if bb.Indent > 0 {
    73  		for i := 0; i < bb.Indent; i++ {
    74  			bb.appendCell(Cell{Text: " "})
    75  		}
    76  	}
    77  
    78  	return bb
    79  }
    80  
    81  // WriteRuneSGR writes a single rune to a buffer with an SGR style, wrapping the
    82  // line when needed. If the rune is a control character, it will be written
    83  // using the caret notation (like ^X) and gets the additional style of
    84  // styleForControlChar.
    85  func (bb *BufferBuilder) WriteRuneSGR(r rune, style string) *BufferBuilder {
    86  	if r == '\n' {
    87  		bb.Newline()
    88  		return bb
    89  	}
    90  	c := Cell{string(r), style}
    91  	if r < 0x20 || r == 0x7f {
    92  		// Always show control characters in reverse video.
    93  		if style != "" {
    94  			style = style + ";7"
    95  		} else {
    96  			style = "7"
    97  		}
    98  		c = Cell{"^" + string(r^0x40), style}
    99  	}
   100  
   101  	if bb.Col+wcwidth.Of(c.Text) > bb.Width {
   102  		bb.Newline()
   103  		bb.appendCell(c)
   104  	} else {
   105  		bb.appendCell(c)
   106  		if bb.Col == bb.Width && bb.EagerWrap {
   107  			bb.Newline()
   108  		}
   109  	}
   110  	return bb
   111  }
   112  
   113  // Write is equivalent to calling WriteStyled with ui.T(text, style...).
   114  func (bb *BufferBuilder) Write(text string, ts ...ui.Styling) *BufferBuilder {
   115  	return bb.WriteStyled(ui.T(text, ts...))
   116  }
   117  
   118  // WriteSpaces writes w spaces with the given styles.
   119  func (bb *BufferBuilder) WriteSpaces(w int, ts ...ui.Styling) *BufferBuilder {
   120  	return bb.Write(strings.Repeat(" ", w), ts...)
   121  }
   122  
   123  // DotHere is a special argument to MarkLines to mark the position of the dot.
   124  var DotHere = struct{ x struct{} }{}
   125  
   126  // MarkLines is like calling WriteStyled with ui.MarkLines(args...), but accepts
   127  // an additional special parameter DotHere to mark the position of the dot.
   128  func (bb *BufferBuilder) MarkLines(args ...interface{}) *BufferBuilder {
   129  	for i, arg := range args {
   130  		if arg == DotHere {
   131  			return bb.WriteStyled(ui.MarkLines(args[:i]...)).
   132  				SetDotHere().WriteStyled(ui.MarkLines(args[i+1:]...))
   133  		}
   134  	}
   135  	return bb.WriteStyled(ui.MarkLines(args...))
   136  }
   137  
   138  // WriteStringSGR writes a string to a buffer with a SGR style.
   139  func (bb *BufferBuilder) WriteStringSGR(text, style string) *BufferBuilder {
   140  	for _, r := range text {
   141  		bb.WriteRuneSGR(r, style)
   142  	}
   143  	return bb
   144  }
   145  
   146  // WriteStyled writes a styled text.
   147  func (bb *BufferBuilder) WriteStyled(t ui.Text) *BufferBuilder {
   148  	for _, seg := range t {
   149  		bb.WriteStringSGR(seg.Text, seg.Style.SGR())
   150  	}
   151  	return bb
   152  }
   153  
   154  func makeSpacing(n int) []Cell {
   155  	s := make([]Cell, n)
   156  	for i := 0; i < n; i++ {
   157  		s[i].Text = " "
   158  	}
   159  	return s
   160  }