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 }