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

     1  package cli
     2  
     3  import (
     4  	"github.com/elves/elvish/pkg/cli/term"
     5  	"github.com/elves/elvish/pkg/ui"
     6  	"github.com/elves/elvish/pkg/wcwidth"
     7  )
     8  
     9  // View model, calculated from State and used for rendering.
    10  type view struct {
    11  	prompt  ui.Text
    12  	rprompt ui.Text
    13  	code    ui.Text
    14  	dot     int
    15  	errors  []error
    16  }
    17  
    18  var stylingForPending = ui.Underlined
    19  
    20  func getView(w *codeArea) *view {
    21  	s := w.CopyState()
    22  	code, pFrom, pTo := patchPending(s.Buffer, s.Pending)
    23  	styledCode, errors := w.Highlighter(code.Content)
    24  	if pFrom < pTo {
    25  		// Apply stylingForPending to [pFrom, pTo)
    26  		parts := styledCode.Partition(pFrom, pTo)
    27  		pending := ui.StyleText(parts[1], stylingForPending)
    28  		styledCode = ui.Concat(parts[0], pending, parts[2])
    29  	}
    30  
    31  	var rprompt ui.Text
    32  	if !s.HideRPrompt {
    33  		rprompt = w.RPrompt()
    34  	}
    35  
    36  	return &view{w.Prompt(), rprompt, styledCode, code.Dot, errors}
    37  }
    38  
    39  func patchPending(c CodeBuffer, p PendingCode) (CodeBuffer, int, int) {
    40  	if p.From > p.To || p.From < 0 || p.To > len(c.Content) {
    41  		// Invalid Pending.
    42  		return c, 0, 0
    43  	}
    44  	if p.From == p.To && p.Content == "" {
    45  		return c, 0, 0
    46  	}
    47  	newContent := c.Content[:p.From] + p.Content + c.Content[p.To:]
    48  	newDot := 0
    49  	switch {
    50  	case c.Dot < p.From:
    51  		// Dot is before the replaced region. Keep it.
    52  		newDot = c.Dot
    53  	case c.Dot >= p.From && c.Dot < p.To:
    54  		// Dot is within the replaced region. Place the dot at the end.
    55  		newDot = p.From + len(p.Content)
    56  	case c.Dot >= p.To:
    57  		// Dot is after the replaced region. Maintain the relative position of
    58  		// the dot.
    59  		newDot = c.Dot - (p.To - p.From) + len(p.Content)
    60  	}
    61  	return CodeBuffer{Content: newContent, Dot: newDot}, p.From, p.From + len(p.Content)
    62  }
    63  
    64  func renderView(v *view, buf *term.BufferBuilder) {
    65  	buf.EagerWrap = true
    66  
    67  	buf.WriteStyled(v.prompt)
    68  	if len(buf.Lines) == 1 && buf.Col*2 < buf.Width {
    69  		buf.Indent = buf.Col
    70  	}
    71  
    72  	parts := v.code.Partition(v.dot)
    73  	buf.
    74  		WriteStyled(parts[0]).
    75  		SetDotHere().
    76  		WriteStyled(parts[1])
    77  
    78  	buf.EagerWrap = false
    79  	buf.Indent = 0
    80  
    81  	// Handle rprompts with newlines.
    82  	if rpromptWidth := styledWcswidth(v.rprompt); rpromptWidth > 0 {
    83  		padding := buf.Width - buf.Col - rpromptWidth
    84  		if padding >= 1 {
    85  			buf.WriteSpaces(padding)
    86  			buf.WriteStyled(v.rprompt)
    87  		}
    88  	}
    89  
    90  	if len(v.errors) > 0 {
    91  		for _, err := range v.errors {
    92  			buf.Newline()
    93  			buf.Write(err.Error())
    94  		}
    95  	}
    96  }
    97  
    98  func truncateToHeight(b *term.Buffer, maxHeight int) {
    99  	switch {
   100  	case len(b.Lines) <= maxHeight:
   101  		// We can show all line; do nothing.
   102  	case b.Dot.Line < maxHeight:
   103  		// We can show all lines before the cursor, and as many lines after the
   104  		// cursor as we can, adding up to maxHeight.
   105  		b.TrimToLines(0, maxHeight)
   106  	default:
   107  		// We can show maxHeight lines before and including the cursor line.
   108  		b.TrimToLines(b.Dot.Line-maxHeight+1, b.Dot.Line+1)
   109  	}
   110  }
   111  
   112  func styledWcswidth(t ui.Text) int {
   113  	w := 0
   114  	for _, seg := range t {
   115  		w += wcwidth.Of(seg.Text)
   116  	}
   117  	return w
   118  }