func (nc *navColumn) FullWidth(h int) int { if nc == nil { return 0 } if nc.err != nil { return util.Wcswidth(nc.err.Error()) } maxw := 0 for _, s := range nc.candidates { maxw = max(maxw, util.Wcswidth(s.text)) } if maxw >= navigationListingMinWidthForPadding { maxw += 2 } if len(nc.candidates) > h { maxw++ } return maxw }
func moveDotUp(ed *Editor) { sol := util.FindLastSOL(ed.line[:ed.dot]) if sol == 0 { ed.flash() return } prevEOL := sol - 1 prevSOL := util.FindLastSOL(ed.line[:prevEOL]) width := util.Wcswidth(ed.line[sol:ed.dot]) ed.dot = prevSOL + len(util.TrimWcwidth(ed.line[prevSOL:prevEOL], width)) }
func moveDotDown(ed *Editor) { eol := util.FindFirstEOL(ed.line[ed.dot:]) + ed.dot if eol == len(ed.line) { ed.flash() return } nextSOL := eol + 1 nextEOL := util.FindFirstEOL(ed.line[nextSOL:]) + nextSOL sol := util.FindLastSOL(ed.line[:ed.dot]) width := util.Wcswidth(ed.line[sol:ed.dot]) ed.dot = nextSOL + len(util.TrimWcwidth(ed.line[nextSOL:nextEOL], width)) }
// maxWidth finds the maximum wcwidth of display texts of candidates [lo, hi). // hi may be larger than the number of candidates, in which case it is truncated // to the number of candidates. func (comp *completion) maxWidth(lo, hi int) int { if hi > len(comp.candidates) { hi = len(comp.candidates) } width := 0 for i := lo; i < hi; i++ { w := util.Wcswidth(comp.candidates[i].display.text) if width < w { width = w } } return width }
func wcswidth(ec *EvalCtx, s String) { out := ec.ports[1].Chan out <- String(strconv.Itoa(util.Wcswidth(string(s)))) }
// refresh redraws the line editor. The dot is passed as an index into text; // the corresponding position will be calculated. func (w *writer) refresh(es *editorState, fullRefresh bool) error { height, width := sys.GetWinsize(int(w.file.Fd())) mode := es.mode.Mode() var bufNoti, bufLine, bufMode, bufTips, bufListing, buf *buffer // butNoti if len(es.notifications) > 0 { bufNoti = newBuffer(width) bufNoti.writes(strings.Join(es.notifications, "\n"), "") es.notifications = nil } // bufLine b := newBuffer(width) bufLine = b b.newlineWhenFull = true b.writeStyleds(es.prompt) if b.line() == 0 && b.col*2 < b.width { b.indent = b.col } // i keeps track of number of bytes written. i := 0 // nowAt is called at every rune boundary. nowAt := func(i int) { if mode == modeCompletion && i == es.completion.begin { c := es.completion.selectedCandidate() b.writes(c.text, styleForCompleted) } if i == es.dot { b.dot = b.cursor() } } nowAt(0) tokens: for _, token := range es.tokens { for _, r := range token.Text { if mode == modeCompletion && es.completion.begin <= i && i <= es.completion.end { // Do nothing. This part is replaced by the completion candidate. } else { b.write(r, joinStyle(styleForType[token.Type], token.MoreStyle)) } i += utf8.RuneLen(r) nowAt(i) if mode == modeHistory && i == len(es.hist.prefix) { break tokens } } } if mode == modeHistory { // Put the rest of current history, position the cursor at the // end of the line, and finish writing h := es.hist b.writes(h.line[len(h.prefix):], styleForCompletedHistory) b.dot = b.cursor() } // Write rprompt padding := b.width - b.col for _, s := range es.rprompt { padding -= util.Wcswidth(s.text) } if padding >= 1 { b.newlineWhenFull = false b.writePadding(padding, "") b.writeStyleds(es.rprompt) } // bufMode bufMode = es.mode.ModeLine(width) // bufTips // TODO tips is assumed to contain no newlines. if len(es.tips) > 0 { bufTips = newBuffer(width) bufTips.writes(strings.Join(es.tips, "\n"), styleForTip) } hListing := 0 // Trim lines and determine the maximum height for bufListing // TODO come up with a UI to tell the user that something is not shown. switch { case height >= lines(bufNoti, bufLine, bufMode, bufTips): hListing = height - lines(bufLine, bufMode, bufTips) case height >= lines(bufNoti, bufLine, bufTips): bufMode = nil case height >= lines(bufNoti, bufLine): bufMode = nil if bufTips != nil { bufTips.trimToLines(0, height-lines(bufNoti, bufLine)) } case height >= lines(bufLine): bufTips, bufMode = nil, nil if bufNoti != nil { n := len(bufNoti.cells) bufNoti.trimToLines(n-(height-lines(bufLine)), n) } case height >= 1: bufNoti, bufTips, bufMode = nil, nil, nil dotLine := bufLine.dot.line bufLine.trimToLines(dotLine+1-height, dotLine+1) default: // Broken terminal. Still try to render one line of bufLine. bufNoti, bufTips, bufMode = nil, nil, nil dotLine := bufLine.dot.line bufLine.trimToLines(dotLine, dotLine+1) } // bufListing. if hListing > 0 { if lister, ok := es.mode.(Lister); ok { bufListing = lister.List(width, hListing) } // XXX When in completion mode, we re-render the mode line, since the // scrollbar in the mode line depends on completion.lastShown which is // only known after the listing has been rendered. Since rendering the // scrollbar never adds additional lines to bufMode, we may do this // without recalculating the layout. if mode == modeCompletion { bufMode = es.mode.ModeLine(width) } } if logWriterDetail { Logger.Printf("bufLine %d, bufMode %d, bufTips %d, bufListing %d", lines(bufLine), lines(bufMode), lines(bufTips), lines(bufListing)) } // Combine buffers (reusing bufLine) buf = bufLine buf.extend(bufMode, mode == modeLocation || mode == modeHistoryListing || (mode == modeCompletion && es.completion.filtering) || (mode == modeNavigation && es.navigation.filtering)) buf.extend(bufTips, false) buf.extend(bufListing, false) return w.commitBuffer(bufNoti, buf, fullRefresh) }