go-prompt/render.go

235 lines
6.3 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
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-08-04 11:30:50 +00:00
func (r *Render) renderCompletion(buf *Buffer, completions []Suggest, 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-08-04 11:30:50 +00:00
func (r *Render) Render(buffer *Buffer, completions []Suggest, 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-08-04 11:30:50 +00:00
func formatCompletions(completions []Suggest, max int) (new []Suggest, width int) {
2017-07-18 15:38:08 +00:00
num := len(completions)
2017-08-04 11:30:50 +00:00
new = make([]Suggest, 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-08-04 11:30:50 +00:00
new[i] = Suggest{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
}