2022-03-13 17:48:49 +00:00
|
|
|
package readline
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
ansi "github.com/acarl005/stripansi"
|
|
|
|
"github.com/rivo/uniseg"
|
|
|
|
)
|
|
|
|
|
|
|
|
// SetPrompt will define the readline prompt string.
|
|
|
|
// It also calculates the runes in the string as well as any non-printable escape codes.
|
|
|
|
func (rl *Instance) SetPrompt(s string) {
|
|
|
|
rl.mainPrompt = s
|
2022-03-29 17:07:27 +00:00
|
|
|
rl.computePrompt()
|
2022-03-13 17:48:49 +00:00
|
|
|
}
|
|
|
|
|
2022-04-13 14:13:46 +00:00
|
|
|
// SetRightPrompt sets the right prompt.
|
|
|
|
func (rl *Instance) SetRightPrompt(s string) {
|
|
|
|
rl.rightPrompt = s + " "
|
|
|
|
rl.computePrompt()
|
|
|
|
}
|
|
|
|
|
2022-03-13 17:48:49 +00:00
|
|
|
// RefreshPromptLog - A simple function to print a string message (a log, or more broadly,
|
|
|
|
// an asynchronous event) without bothering the user, and by "pushing" the prompt below the message.
|
|
|
|
func (rl *Instance) RefreshPromptLog(log string) (err error) {
|
|
|
|
|
|
|
|
// We adjust cursor movement, depending on which mode we're currently in.
|
|
|
|
if !rl.modeTabCompletion {
|
|
|
|
rl.tcUsedY = 1
|
2022-03-26 21:34:09 +00:00
|
|
|
// Account for the info line
|
2022-03-13 17:48:49 +00:00
|
|
|
} else if rl.modeTabCompletion && rl.modeAutoFind {
|
|
|
|
rl.tcUsedY = 0
|
|
|
|
} else {
|
|
|
|
rl.tcUsedY = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prompt offset
|
|
|
|
if rl.Multiline {
|
|
|
|
rl.tcUsedY += 1
|
|
|
|
} else {
|
|
|
|
rl.tcUsedY += 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear the current prompt and everything below
|
|
|
|
print(seqClearLine)
|
|
|
|
if rl.stillOnRefresh {
|
|
|
|
moveCursorUp(1)
|
|
|
|
}
|
|
|
|
rl.stillOnRefresh = true
|
2022-03-26 21:34:09 +00:00
|
|
|
moveCursorUp(rl.infoY + rl.tcUsedY)
|
2022-03-13 17:48:49 +00:00
|
|
|
moveCursorBackwards(GetTermWidth())
|
|
|
|
print("\r\n" + seqClearScreenBelow)
|
|
|
|
|
|
|
|
// Print the log
|
|
|
|
fmt.Printf(log)
|
|
|
|
|
|
|
|
// Add a new line between the message and the prompt, so not overloading the UI
|
|
|
|
print("\n")
|
|
|
|
|
|
|
|
// Print the prompt
|
|
|
|
if rl.Multiline {
|
|
|
|
rl.tcUsedY += 3
|
|
|
|
fmt.Println(rl.mainPrompt)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
rl.tcUsedY += 2
|
|
|
|
fmt.Print(rl.mainPrompt)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Refresh the line
|
|
|
|
rl.updateHelpers()
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// RefreshPromptInPlace - Refreshes the prompt in the very same place he is.
|
|
|
|
func (rl *Instance) RefreshPromptInPlace(prompt string) (err error) {
|
|
|
|
// We adjust cursor movement, depending on which mode we're currently in.
|
|
|
|
// Prompt data intependent
|
|
|
|
if !rl.modeTabCompletion {
|
|
|
|
rl.tcUsedY = 1
|
2022-03-26 21:34:09 +00:00
|
|
|
// Account for the info line
|
2022-03-13 17:48:49 +00:00
|
|
|
} else if rl.modeTabCompletion && rl.modeAutoFind {
|
|
|
|
rl.tcUsedY = 0
|
|
|
|
} else {
|
|
|
|
rl.tcUsedY = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the prompt if a special has been passed.
|
|
|
|
if prompt != "" {
|
2022-03-29 17:07:27 +00:00
|
|
|
rl.SetPrompt(prompt)
|
2022-03-13 17:48:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if rl.Multiline {
|
|
|
|
rl.tcUsedY += 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear the input line and everything below
|
|
|
|
print(seqClearLine)
|
2022-03-26 21:34:09 +00:00
|
|
|
moveCursorUp(rl.infoY + rl.tcUsedY)
|
2022-03-13 17:48:49 +00:00
|
|
|
moveCursorBackwards(GetTermWidth())
|
|
|
|
print("\r\n" + seqClearScreenBelow)
|
|
|
|
|
|
|
|
// Add a new line if needed
|
|
|
|
if rl.Multiline {
|
|
|
|
fmt.Println(rl.mainPrompt)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
fmt.Print(rl.mainPrompt)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Refresh the line
|
|
|
|
rl.updateHelpers()
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// RefreshPromptCustom - Refresh the console prompt with custom values.
|
|
|
|
// @prompt => If not nil (""), will use this prompt instead of the currently set prompt.
|
|
|
|
// @offset => Used to set the number of lines to go upward, before reprinting. Set to 0 if not used.
|
|
|
|
// @clearLine => If true, will clean the current input line on the next refresh.
|
|
|
|
func (rl *Instance) RefreshPromptCustom(prompt string, offset int, clearLine bool) (err error) {
|
|
|
|
|
|
|
|
// We adjust cursor movement, depending on which mode we're currently in.
|
|
|
|
if !rl.modeTabCompletion {
|
|
|
|
rl.tcUsedY = 1
|
2022-03-26 21:34:09 +00:00
|
|
|
} else if rl.modeTabCompletion && rl.modeAutoFind { // Account for the info line
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.tcUsedY = 0
|
|
|
|
} else {
|
|
|
|
rl.tcUsedY = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add user-provided offset
|
|
|
|
rl.tcUsedY += offset
|
|
|
|
|
|
|
|
// Go back to prompt position, then up to the user provided offset.
|
|
|
|
moveCursorBackwards(GetTermWidth())
|
|
|
|
moveCursorUp(rl.posY)
|
|
|
|
moveCursorUp(offset)
|
|
|
|
|
|
|
|
// Then clear everything below our new position
|
|
|
|
print(seqClearScreenBelow)
|
|
|
|
|
|
|
|
// Update the prompt if a special has been passed.
|
|
|
|
if prompt != "" {
|
2022-03-29 17:07:27 +00:00
|
|
|
rl.SetPrompt(prompt)
|
2022-03-13 17:48:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Add a new line if needed
|
|
|
|
if rl.Multiline && prompt == "" {
|
|
|
|
} else if rl.Multiline {
|
|
|
|
fmt.Println(rl.mainPrompt)
|
|
|
|
} else {
|
|
|
|
fmt.Print(rl.mainPrompt)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Refresh the line
|
|
|
|
rl.updateHelpers()
|
|
|
|
|
|
|
|
// If input line was empty, check that we clear it from detritus
|
|
|
|
// The three lines are borrowed from clearLine(), we don't need more.
|
|
|
|
if clearLine {
|
|
|
|
rl.clearLine()
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// computePrompt - At any moment, returns an (1st or 2nd line) actualized prompt,
|
|
|
|
// considering all input mode parameters and prompt string values.
|
|
|
|
func (rl *Instance) computePrompt() (prompt []rune) {
|
|
|
|
if rl.Multiline {
|
|
|
|
if rl.MultilinePrompt != "" {
|
|
|
|
rl.realPrompt = []rune(rl.MultilinePrompt)
|
|
|
|
} else {
|
|
|
|
rl.realPrompt = []rune{} //rl.defaultPrompt
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !rl.Multiline {
|
|
|
|
if rl.mainPrompt != "" {
|
|
|
|
rl.realPrompt = []rune(rl.mainPrompt)
|
|
|
|
}
|
|
|
|
// We add the multiline prompt anyway, because it might be empty and thus have
|
|
|
|
// no effect on our user interface, or be specified and thus needed.
|
|
|
|
// if rl.MultilinePrompt != "" {
|
|
|
|
rl.realPrompt = append(rl.realPrompt, []rune(rl.MultilinePrompt)...)
|
|
|
|
// } else {
|
|
|
|
// rl.realPrompt = append(rl.realPrompt, rl.defaultPrompt...)
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
|
|
|
|
// Strip color escapes
|
|
|
|
rl.promptLen = getRealLength(string(rl.realPrompt))
|
2022-04-13 14:13:46 +00:00
|
|
|
rl.rightPromptLen = getRealLength(string(rl.rightPrompt))
|
2022-03-13 17:48:49 +00:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rl *Instance) colorizeVimPrompt(p []rune) (cp []rune) {
|
|
|
|
if rl.VimModeColorize {
|
|
|
|
return []rune(fmt.Sprintf("%s%s%s", BOLD, string(p), RESET))
|
|
|
|
}
|
|
|
|
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
|
|
|
// getRealLength - Some strings will have ANSI escape codes, which might be wrongly
|
|
|
|
// interpreted as legitimate parts of the strings. This will bother if some prompt
|
|
|
|
// components depend on other's length, so we always pass the string in this for
|
|
|
|
// getting its real-printed length.
|
|
|
|
func getRealLength(s string) (l int) {
|
|
|
|
stripped := ansi.Strip(s)
|
|
|
|
return uniseg.GraphemeClusterCount(stripped)
|
|
|
|
}
|
2022-04-13 14:13:46 +00:00
|
|
|
|
|
|
|
func (rl *Instance) echoRightPrompt() {
|
|
|
|
if rl.fullX < GetTermWidth() - rl.rightPromptLen - 1 {
|
|
|
|
moveCursorForwards(GetTermWidth())
|
|
|
|
moveCursorBackwards(rl.rightPromptLen)
|
|
|
|
print(rl.rightPrompt)
|
|
|
|
}
|
|
|
|
}
|