package hhc_common import ( "fmt" "io" "strings" ) type RunesEditor struct { currentLine []rune history [][]rune insertPosition int historyPosition int cleanLineOffset int cleanLineCount int lastTermWidth int lastTermPeekOut []string } func NewRunesEditor() *RunesEditor { re := &RunesEditor{} re.ClearAll() return re } func (re *RunesEditor) ClearHistory() { re.history = make([][]rune, 0) re.historyPosition = 0 } func (re *RunesEditor) ClearCurrentLine(saveHistory bool) { if saveHistory { if len(re.currentLine) > 0 { l := len(re.history) if l > 0 { if !TestRuneSlice(re.history[l-1], re.currentLine) { re.history = append(re.history, re.currentLine) } } else { re.history = append(re.history, re.currentLine) } } } re.historyPosition = len(re.history) re.currentLine = []rune{} re.insertPosition = 0 } func (re *RunesEditor) ClearAll() { re.ClearCurrentLine(false) re.ClearHistory() } func (re *RunesEditor) fixInsertPos() { bl := len(re.currentLine) if re.insertPosition > bl { re.insertPosition = bl } if re.insertPosition < 0 { re.insertPosition = 0 } } func (re *RunesEditor) InsertString(s string) { rs := []rune(s) for _, v := range rs { re.InsertRune(v) } } func (re *RunesEditor) InsertRune(r rune) { re.fixInsertPos() p := re.insertPosition switch { case p == 0: nrs := make([]rune, 1, len(re.currentLine)+1) nrs[0] = r re.currentLine = append(nrs, re.currentLine...) break case p == len(re.currentLine): re.currentLine = append(re.currentLine, r) break default: left := re.currentLine[:p] right := re.currentLine[p:] nrs := make([]rune, 0, len(re.currentLine)+1) nrs = append(nrs, left...) nrs = append(nrs, r) re.currentLine = append(nrs, right...) break } re.insertPosition++ re.fixInsertPos() } func (re *RunesEditor) MoveInsertPosition(offset int) { re.insertPosition += offset re.fixInsertPos() } func (re *RunesEditor) SetInsertPosition(pos int) { re.insertPosition = pos re.fixInsertPos() } func (re *RunesEditor) Backspace() { re.fixInsertPos() l := len(re.currentLine) switch { case re.insertPosition <= 0: re.insertPosition = 0 return case l == 0: return case re.insertPosition == l: re.currentLine = re.currentLine[:l-1] break default: p := re.insertPosition left := re.currentLine[:p-1] right := re.currentLine[p:] nrs := make([]rune, 0, len(re.currentLine)-1) nrs = append(nrs, left...) re.currentLine = append(nrs, right...) break } re.insertPosition-- re.fixInsertPos() } func (re *RunesEditor) Delete() { re.fixInsertPos() l := len(re.currentLine) switch { case re.insertPosition == l: return default: p := re.insertPosition left := re.currentLine[:p] right := re.currentLine[p+1:] nrs := make([]rune, 0, len(re.currentLine)-1) nrs = append(nrs, left...) re.currentLine = append(nrs, right...) break } re.fixInsertPos() } func (re *RunesEditor) Enter() []rune { ret := re.currentLine re.ClearCurrentLine(true) return ret } func (re *RunesEditor) Peek() []rune { return re.currentLine } func (re *RunesEditor) fixHistoryPos() { l := len(re.history) if re.historyPosition < 0 { re.historyPosition = 0 } if re.historyPosition > l { re.historyPosition = l } } func (re *RunesEditor) LoadNextHistory() { l := len(re.history) if re.historyPosition == l { return } re.historyPosition++ re.fixHistoryPos() if re.historyPosition == l { re.ClearCurrentLine(false) return } re.currentLine = re.history[re.historyPosition] re.MoveInsertPositionToEnd() } func (re *RunesEditor) LoadPreviousHistory() { l := len(re.history) if l == 0 { return } re.historyPosition-- re.fixHistoryPos() if re.historyPosition == l { re.ClearCurrentLine(false) return } re.currentLine = re.history[re.historyPosition] re.MoveInsertPositionToEnd() } func (re *RunesEditor) MoveInsertPositionToEnd() { re.insertPosition = len(re.currentLine) } func (re *RunesEditor) MoveInsertPositionToHome() { re.insertPosition = 0 } func (re *RunesEditor) GetInsertPosition() int { return re.insertPosition } func (re *RunesEditor) CleanTerminalPeek(wr io.Writer) { switch { case re.cleanLineOffset > 0: _, _ = fmt.Fprintf(wr, "\033[%dA", re.cleanLineOffset) break case re.cleanLineOffset < 0: _, _ = fmt.Fprintf(wr, "\033[%dB", -re.cleanLineOffset) break } for i := 0; i < re.cleanLineCount; i++ { _, _ = fmt.Fprint(wr, "\r\033[K") if i != re.cleanLineCount-1 { _, _ = fmt.Fprint(wr, "\033[1A") } } } func (re *RunesEditor) backTerminalPeekCursorToHome(wr io.Writer) { switch { case re.cleanLineOffset > 0: _, _ = fmt.Fprintf(wr, "\033[%dA", re.cleanLineOffset) break case re.cleanLineOffset < 0: _, _ = fmt.Fprintf(wr, "\033[%dB", -re.cleanLineOffset) break } for i := 0; i < re.cleanLineCount; i++ { if i != re.cleanLineCount-1 { _, _ = fmt.Fprint(wr, "\033[1A") } else { _, _ = fmt.Fprint(wr, "\r") } } } func (re *RunesEditor) RefreshTerminal(wr io.Writer, prefix string, termWidth int, diffRefresh bool) { if diffRefresh { if termWidth != re.lastTermWidth { re.hardRefreshTerminal(wr, prefix, termWidth) return } // TODO: Support for diffRefresh // Now temporary using hardRefresh instead. re.hardRefreshTerminal(wr, prefix, termWidth) return } else { re.hardRefreshTerminal(wr, prefix, termWidth) } } func (re *RunesEditor) hardRefreshTerminal(wr io.Writer, prefix string, termWidth int) { re.CleanTerminalPeek(wr) re.PeekIntoTerminal(prefix, termWidth, wr) } func (re *RunesEditor) PeekIntoTerminal(prefix string, termWidth int, wr io.Writer) { re.lastTermWidth = termWidth re.fixInsertPos() rc := re.Peek() sc := string(rc) psb := strings.Builder{} psb.WriteString(prefix) psb.WriteString(sc) ws := psb.String() lpre := len([]rune(prefix)) curpos := lpre + re.insertPosition lines, cy, cx := PrintRunesIntoLinesWithCursor([]rune(ws), termWidth, curpos) re.lastTermPeekOut = lines re.cleanLineCount = len(lines) lastln := re.cleanLineCount - 1 lastline := lines[lastln] lll := len(lastline) for i, line := range lines { _, _ = fmt.Fprint(wr, line) if i != lastln { _, _ = fmt.Fprint(wr, "\r\n") } } bky := lastln - cy bkx := lll - cx + 1 re.cleanLineOffset = -bky //fmt.Println("BKY=", bky, "BKX=", bkx) switch { case bky > 0: _, _ = fmt.Fprintf(wr, "\033[%dA", bky) break case bky < 0: _, _ = fmt.Fprintf(wr, "\033[%dB", -bky) break } switch { case bkx > 0: _, _ = fmt.Fprintf(wr, "\033[%dD", bkx) break case bkx < 0: _, _ = fmt.Fprintf(wr, "\033[%dC", -bkx) break } }