package readline import ( "fmt" "strings" "golang.org/x/text/width" ) // updateHelpers is a key part of the whole refresh process: // it should coordinate reprinting the input line, any Infos and completions // and manage to get back to the current (computed) cursor coordinates func (rl *Instance) updateHelpers() { print(seqHideCursor) // Load all Infos & completions before anything. // Thus overwrites anything having been dirtily added/forced/modified, like rl.SetInfoText() rl.getInfoText() rl.getHintText() if rl.modeTabCompletion && !rl.completionOpen { rl.getTabCompletion() } else { if rl.completionOpen { rl.completionOpen = false } } // We clear everything rl.clearHelpers() // We are at the prompt line (with the latter // not printed yet), then reprint everything rl.renderHelpers() print(seqUnhideCursor) } const tabWidth = 4 func getWidth(x []rune) int { var w int for _, j := range x { k := width.LookupRune(j).Kind() if j == '\t' { w += tabWidth } else if k == width.EastAsianWide || k == width.EastAsianFullwidth { w += 2 } else { w++ } } return w } // Update reference should be called only once in a "loop" (not Readline(), but key control loop) func (rl *Instance) updateReferences() { // We always need to work with clean data, // since we will have incrementers all around rl.posX = 0 rl.fullX = 0 rl.posY = 0 rl.fullY = 0 var curLine []rune if len(rl.currentComp) > 0 { curLine = rl.lineComp } else { curLine = rl.line } fullLine := getWidth(curLine) cPosLine := getWidth(curLine[:rl.pos]) // We need the X offset of the whole line toEndLine := rl.promptLen + fullLine fullOffset := toEndLine / GetTermWidth() rl.fullY = fullOffset + strings.Count(string(curLine), "\n") fullRest := toEndLine % GetTermWidth() rl.fullX = fullRest if fullRest == 0 && fullOffset > 0 { print("\n") } // Use rl.pos value to get the offset to go TO/FROM the CURRENT POSITION lineToCursorPos := rl.promptLen + cPosLine offsetToCursor := lineToCursorPos / GetTermWidth() cPosRest := lineToCursorPos % GetTermWidth() // If we are at the end of line if fullLine == rl.pos { rl.posY = fullOffset if fullRest == 0 { rl.posX = 0 } else if fullRest > 0 { rl.posX = fullRest } } else if rl.pos < fullLine { // If we are somewhere in the middle of the line rl.posY = offsetToCursor if cPosRest == 0 { } else if cPosRest > 0 { rl.posX = cPosRest } } } func (rl *Instance) resetHelpers() { rl.modeAutoFind = false // Now reset all below-input helpers rl.resetInfoText() rl.resetTabCompletion() } // clearHelpers - Clears everything: prompt, input, Infos & comps, // and comes back at the prompt. func (rl *Instance) clearHelpers() { // Now go down to the last line of input moveCursorDown(rl.fullY - rl.posY) moveCursorBackwards(rl.posX) moveCursorForwards(rl.fullX) // Clear everything below //print(seqClearScreenBelow) // Go back to current cursor position moveCursorBackwards(GetTermWidth()) moveCursorUp(rl.fullY - rl.posY) moveCursorForwards(rl.posX) } // renderHelpers - pritns all components (prompt, line, Infos & comps) // and replaces the cursor to its current position. This function never // computes or refreshes any value, except from inside the echo function. func (rl *Instance) renderHelpers() { // when the instance is in this state we want it to be "below" the user's // input for it to be aligned properly if !rl.compConfirmWait { rl.writeHintText() } rl.echo() if rl.modeTabCompletion { // in tab complete mode we want it to update // when something has been selected // (dynamic!!) rl.getHintText() rl.writeHintText() } else if !rl.compConfirmWait { // for the same reason above of wanting it below user input, do nothing here } else { rl.writeHintText() } rl.echoRightPrompt() // Go at beginning of first line after input remainder moveCursorDown(rl.fullY - rl.posY) moveCursorBackwards(GetTermWidth()) // Print Infos, check for any confirmation Info current. // (do not overwrite the confirmation question Info) if !rl.compConfirmWait { if len(rl.infoText) > 0 { print("\n") } rl.writeInfoText() moveCursorBackwards(GetTermWidth()) // Print completions and go back to beginning of this line print("\n") rl.writeTabCompletion() moveCursorBackwards(GetTermWidth()) moveCursorUp(rl.tcUsedY) } // If we are still waiting for the user to confirm too long completions // Immediately refresh the Infos if rl.compConfirmWait { print("\n") rl.writeInfoText() rl.getInfoText() moveCursorBackwards(GetTermWidth()) } // Anyway, compensate for Info printout if len(rl.infoText) > 0 { moveCursorUp(rl.infoY) } else if !rl.compConfirmWait { moveCursorUp(1) } else if rl.compConfirmWait { moveCursorUp(1) } // Go back to current cursor position moveCursorUp(rl.fullY - rl.posY) moveCursorForwards(rl.posX) } func (rl *Instance) bufprintF(format string, a ...any) { fmt.Fprintf(rl.bufferedOut, format, a...) } func (rl *Instance) bufprint(text string) { fmt.Fprint(rl.bufferedOut, text) } func (rl *Instance) bufflush() { rl.bufferedOut.Flush() }