go-prompt/render.go

245 lines
6.6 KiB
Go
Raw Normal View History

2017-07-14 01:51:19 +00:00
package prompt
2017-07-17 14:17:08 +00:00
import "strings"
2017-07-18 15:36:16 +00:00
const (
2017-08-03 06:53:38 +00:00
leftPrefix = " "
leftSuffix = " "
rightPrefix = " "
rightSuffix = " "
leftMargin = len(leftPrefix + leftSuffix)
rightMargin = len(rightPrefix + rightSuffix)
2017-07-18 15:36:16 +00:00
completionMargin = leftMargin + rightMargin
)
2017-07-14 01:51:19 +00:00
type Render struct {
2017-07-17 17:01:24 +00:00
out ConsoleWriter
prefix string
title string
row uint16
col uint16
2017-07-16 19:32:42 +00:00
// colors
2017-07-18 15:36:16 +00:00
prefixTextColor Color
prefixBGColor Color
inputTextColor Color
inputBGColor Color
outputTextColor Color
outputBGColor Color
previewSuggestionTextColor Color
previewSuggestionBGColor Color
suggestionTextColor Color
suggestionBGColor Color
selectedSuggestionTextColor Color
selectedSuggestionBGColor Color
descriptionTextColor Color
descriptionBGColor Color
selectedDescriptionTextColor Color
selectedDescriptionBGColor Color
2017-07-15 11:22:56 +00:00
}
func (r *Render) Setup() {
2017-07-16 17:18:11 +00:00
if r.title != "" {
r.out.SetTitle(r.title)
2017-07-17 16:14:03 +00:00
r.out.Flush()
2017-07-15 11:22:56 +00:00
}
}
2017-07-15 16:18:40 +00:00
func (r *Render) renderPrefix() {
2017-07-18 16:16:51 +00:00
r.out.SetColor(r.prefixTextColor, r.prefixBGColor, false)
2017-07-16 17:11:52 +00:00
r.out.WriteStr(r.prefix)
2017-07-18 16:16:51 +00:00
r.out.SetColor(DefaultColor, DefaultColor, false)
2017-07-15 16:18:40 +00:00
}
2017-07-15 11:22:56 +00:00
func (r *Render) TearDown() {
r.out.ClearTitle()
r.out.EraseDown()
r.out.Flush()
2017-07-15 08:37:54 +00:00
}
func (r *Render) prepareArea(lines int) {
2017-07-15 08:37:54 +00:00
for i := 0; i < lines; i++ {
r.out.ScrollDown()
}
for i := 0; i < lines; i++ {
r.out.ScrollUp()
}
return
2017-07-14 01:51:19 +00:00
}
2017-07-15 09:03:18 +00:00
func (r *Render) UpdateWinSize(ws *WinSize) {
2017-07-14 01:51:19 +00:00
r.row = ws.Row
r.col = ws.Col
return
}
2017-07-17 16:14:03 +00:00
func (r *Render) renderWindowTooSmall() {
r.out.CursorGoTo(0, 0)
r.out.EraseScreen()
2017-07-18 16:16:51 +00:00
r.out.SetColor(DarkRed, White, false)
2017-07-17 16:14:03 +00:00
r.out.WriteStr("Your console window is too small...")
r.out.Flush()
return
}
2017-07-18 15:38:08 +00:00
func (r *Render) renderCompletion(buf *Buffer, completions []Completion, max uint16, selected int) {
2017-07-17 15:52:55 +00:00
if max > r.row {
max = r.row
2017-07-17 12:54:39 +00:00
}
2017-07-18 15:38:08 +00:00
if l := len(completions); l == 0 {
2017-07-15 11:22:56 +00:00
return
2017-07-17 15:52:55 +00:00
} else if l > int(max) {
2017-07-18 15:38:08 +00:00
completions = completions[:max]
2017-07-15 11:22:56 +00:00
}
2017-07-15 16:04:18 +00:00
2017-07-17 14:17:08 +00:00
formatted, width := formatCompletions(
2017-07-18 15:38:08 +00:00
completions,
2017-07-17 17:01:24 +00:00
int(r.col)-len(r.prefix),
2017-07-17 14:17:08 +00:00
)
2017-07-15 09:51:33 +00:00
l := len(formatted)
2017-07-18 13:06:33 +00:00
r.prepareArea(l)
2017-07-15 08:37:54 +00:00
2017-07-16 17:11:52 +00:00
d := (len(r.prefix) + len(buf.Document().TextBeforeCursor())) % int(r.col)
if d == 0 { // the cursor is on right end.
r.out.CursorBackward(width)
2017-07-17 20:32:14 +00:00
} else if d+width > int(r.col) {
2017-07-17 15:35:10 +00:00
r.out.CursorBackward(d + width - int(r.col))
2017-07-15 16:04:18 +00:00
}
2017-07-18 16:16:51 +00:00
r.out.SetColor(White, Cyan, false)
2017-07-15 09:51:33 +00:00
for i := 0; i < l; i++ {
r.out.CursorDown(1)
2017-07-17 15:52:55 +00:00
if i == selected {
2017-07-18 16:16:51 +00:00
r.out.SetColor(r.selectedSuggestionTextColor, r.selectedSuggestionBGColor, true)
2017-07-15 13:44:10 +00:00
} else {
2017-07-18 16:16:51 +00:00
r.out.SetColor(r.suggestionTextColor, r.suggestionBGColor, false)
2017-07-15 13:44:10 +00:00
}
2017-07-18 15:36:16 +00:00
r.out.WriteStr(formatted[i].Text)
if i == selected {
2017-07-18 16:16:51 +00:00
r.out.SetColor(r.selectedDescriptionTextColor, r.selectedDescriptionBGColor, false)
2017-07-18 15:36:16 +00:00
} else {
2017-07-18 16:16:51 +00:00
r.out.SetColor(r.descriptionTextColor, r.descriptionBGColor, false)
2017-07-18 15:36:16 +00:00
}
r.out.WriteStr(formatted[i].Description)
2017-07-17 15:35:10 +00:00
r.out.CursorBackward(width)
2017-07-15 09:51:33 +00:00
}
if d == 0 { // the cursor is on right end.
// DON'T CURSOR DOWN HERE. Because the line doesn't erase properly.
r.out.CursorForward(width + 1)
} else if d+width > int(r.col) {
2017-07-17 15:35:10 +00:00
r.out.CursorForward(d + width - int(r.col))
2017-07-15 16:04:18 +00:00
}
2017-07-15 08:37:54 +00:00
2017-07-15 09:51:33 +00:00
r.out.CursorUp(l)
2017-07-18 16:16:51 +00:00
r.out.SetColor(DefaultColor, DefaultColor, false)
2017-07-15 09:51:33 +00:00
return
}
2017-07-15 08:37:54 +00:00
2017-07-18 15:38:08 +00:00
func (r *Render) Render(buffer *Buffer, completions []Completion, maxCompletions uint16, selected int) {
2017-07-17 16:14:03 +00:00
// Erasing
2017-07-17 14:37:40 +00:00
r.out.CursorBackward(int(r.col) + len(buffer.Text()) + len(r.prefix))
2017-07-15 11:22:56 +00:00
r.out.EraseDown()
2017-07-17 16:14:03 +00:00
// prepare area
line := buffer.Text()
2017-07-17 16:14:03 +00:00
h := ((len(r.prefix) + len(line)) / int(r.col)) + 1 + int(maxCompletions)
2017-07-18 15:36:16 +00:00
if h > int(r.row) || completionMargin > int(r.col) {
2017-07-17 16:14:03 +00:00
r.renderWindowTooSmall()
return
}
// Rendering
r.renderPrefix()
2017-07-18 16:16:51 +00:00
r.out.SetColor(r.inputTextColor, r.inputBGColor, false)
2017-07-15 11:22:56 +00:00
r.out.WriteStr(line)
2017-07-18 16:16:51 +00:00
r.out.SetColor(DefaultColor, DefaultColor, false)
2017-07-15 11:22:56 +00:00
r.out.CursorBackward(len(line) - buffer.CursorPosition)
2017-07-17 15:52:55 +00:00
r.renderCompletion(buffer, completions, maxCompletions, selected)
if selected != -1 {
c := completions[selected]
2017-07-15 14:27:49 +00:00
r.out.CursorBackward(len([]rune(buffer.Document().GetWordBeforeCursor())))
2017-07-18 16:16:51 +00:00
r.out.SetColor(r.previewSuggestionTextColor, r.previewSuggestionBGColor, false)
2017-07-18 00:53:59 +00:00
r.out.WriteStr(c.Text)
2017-07-18 16:16:51 +00:00
r.out.SetColor(DefaultColor, DefaultColor, false)
2017-07-15 14:27:49 +00:00
}
2017-07-15 11:22:56 +00:00
r.out.Flush()
}
2017-07-18 11:48:50 +00:00
func (r *Render) BreakLine(buffer *Buffer) {
// CR
2017-07-17 16:14:03 +00:00
r.out.CursorBackward(int(r.col) + len(buffer.Text()) + len(r.prefix))
2017-07-18 11:48:50 +00:00
// Erasing and Render
2017-07-17 16:14:03 +00:00
r.out.EraseDown()
r.renderPrefix()
2017-07-18 16:16:51 +00:00
r.out.SetColor(r.inputTextColor, r.inputBGColor, false)
2017-07-16 19:55:05 +00:00
r.out.WriteStr(buffer.Document().Text + "\n")
2017-07-18 16:16:51 +00:00
r.out.SetColor(DefaultColor, DefaultColor, false)
2017-07-18 13:06:33 +00:00
r.out.Flush()
2017-07-18 11:48:50 +00:00
}
2017-07-17 16:47:50 +00:00
2017-07-18 11:48:50 +00:00
func (r *Render) RenderResult(result string) {
2017-07-17 16:47:50 +00:00
if result != "" {
2017-07-18 16:16:51 +00:00
r.out.SetColor(r.outputTextColor, r.outputBGColor, false)
2017-07-18 15:44:30 +00:00
r.out.WriteRawStr(result)
2017-07-17 16:47:50 +00:00
}
2017-07-18 16:16:51 +00:00
r.out.SetColor(DefaultColor, DefaultColor, false)
2017-07-15 11:22:56 +00:00
}
2017-07-18 15:38:08 +00:00
func formatCompletions(completions []Completion, max int) (new []Completion, width int) {
num := len(completions)
new = make([]Completion, num)
2017-07-18 15:36:16 +00:00
leftWidth := 0
rightWidth := 0
2017-07-15 08:37:54 +00:00
2017-07-15 09:51:33 +00:00
for i := 0; i < num; i++ {
2017-07-18 15:38:08 +00:00
if leftWidth < len([]rune(completions[i].Text)) {
leftWidth = len([]rune(completions[i].Text))
2017-07-18 15:36:16 +00:00
}
2017-07-18 15:38:08 +00:00
if rightWidth < len([]rune(completions[i].Description)) {
rightWidth = len([]rune(completions[i].Description))
2017-07-15 09:51:33 +00:00
}
}
2017-07-15 08:37:54 +00:00
2017-07-18 15:36:16 +00:00
if diff := max - completionMargin - leftWidth - rightWidth; diff < 0 {
if rightWidth > diff {
2017-07-19 07:06:02 +00:00
rightWidth += diff
} else if rightWidth+rightMargin > -diff {
leftWidth += rightWidth + rightMargin + diff
2017-07-18 15:36:16 +00:00
rightWidth = 0
}
}
if rightWidth == 0 {
width = leftWidth + leftMargin
} else {
width = leftWidth + leftMargin + rightWidth + rightMargin
2017-07-16 14:11:16 +00:00
}
2017-07-15 09:51:33 +00:00
for i := 0; i < num; i++ {
2017-07-18 15:36:16 +00:00
var newText string
var newDescription string
2017-07-18 15:38:08 +00:00
if l := len(completions[i].Text); l > leftWidth {
newText = leftPrefix + completions[i].Text[:leftWidth-len("...")] + "..." + leftSuffix
2017-07-18 15:36:16 +00:00
} else if l < width {
2017-07-18 15:38:08 +00:00
spaces := strings.Repeat(" ", leftWidth-len([]rune(completions[i].Text)))
newText = leftPrefix + completions[i].Text + spaces + leftSuffix
2017-07-18 15:36:16 +00:00
} else {
2017-07-18 15:38:08 +00:00
newText = leftPrefix + completions[i].Text + leftSuffix
2017-07-18 15:36:16 +00:00
}
if rightWidth == 0 {
newDescription = ""
2017-07-18 15:38:08 +00:00
} else if l := len(completions[i].Description); l > rightWidth {
newDescription = rightPrefix + completions[i].Description[:rightWidth-len("...")] + "..." + rightSuffix
2017-07-17 17:01:24 +00:00
} else if l < width {
2017-07-18 15:38:08 +00:00
spaces := strings.Repeat(" ", rightWidth-len([]rune(completions[i].Description)))
newDescription = rightPrefix + completions[i].Description + spaces + rightSuffix
2017-07-16 14:11:16 +00:00
} else {
2017-07-18 15:38:08 +00:00
newDescription = rightPrefix + completions[i].Description + rightSuffix
2017-07-15 09:51:33 +00:00
}
2017-07-18 15:38:08 +00:00
new[i] = Completion{Text: newText, Description: newDescription}
2017-07-15 09:51:33 +00:00
}
2017-07-15 14:27:49 +00:00
return
2017-07-14 01:51:19 +00:00
}