func (o *opCompleter) CompleteRefresh() { if !o.inCompleteMode { return } lineCnt := o.op.buf.CursorLineCount() colWidth := 0 for _, c := range o.candidate { w := runes.WidthAll(c) if w > colWidth { colWidth = w } } colNum := getWidth(o.op.cfg.StdoutFd) / (colWidth + o.candidateOff + 2) o.candidateColNum = colNum buf := bytes.NewBuffer(nil) buf.Write(bytes.Repeat([]byte("\n"), lineCnt)) same := o.op.buf.RuneSlice(-o.candidateOff) colIdx := 0 lines := 1 buf.WriteString("\033[J") for idx, c := range o.candidate { inSelect := idx == o.candidateChoise && o.IsInCompleteSelectMode() if inSelect { buf.WriteString("\033[30;47m") } buf.WriteString(string(same)) buf.WriteString(string(c)) buf.Write(bytes.Repeat([]byte(" "), colWidth-len(c))) if inSelect { buf.WriteString("\033[0m") } buf.WriteString(" ") colIdx++ if colIdx == colNum { buf.WriteString("\n") lines++ colIdx = 0 } } // move back fmt.Fprintf(buf, "\033[%dA\r", lineCnt-1+lines) fmt.Fprintf(buf, "\033[%dC", o.op.buf.idx+o.op.buf.PromptLen()) o.w.Write(buf.Bytes()) }
func (r *RuneBuffer) IdxLine() int { totalWidth := runes.WidthAll(r.buf[:r.idx]) + r.PromptLen() w := getWidth(r.cfg.StdoutFd) if w <= 0 { return -1 } line := totalWidth / w // if cursor is in last colmun and not any character behind it // the cursor will in the first line, otherwise will in the second line // this situation only occurs in golang's Stdout // TODO: figure out why if totalWidth%w == 0 && len(r.buf) == r.idx && !isWindows { line-- } return line }
func (r *RuneBuffer) PromptLen() int { return runes.WidthAll(runes.ColorFilter(r.prompt)) }
func (r *RuneBuffer) CurrentWidth(x int) int { return runes.WidthAll(r.buf[:x]) }
func (r *RuneBuffer) calWidth(m int) int { if m > 0 { return runes.WidthAll(r.buf[r.idx : r.idx+m]) } return runes.WidthAll(r.buf[r.idx+m : r.idx]) }
func (r *RuneBuffer) LineCount() int { return LineCount(r.cfg.StdoutFd, runes.WidthAll(r.buf)+r.PromptLen()) }