Complete with description

This commit is contained in:
c-bata 2017-07-19 00:36:16 +09:00
parent c7333f7c05
commit 2e1d7d8eb0
6 changed files with 163 additions and 62 deletions

@ -71,6 +71,10 @@ func main() {
#### `OptionSelectedSuggestionTextColor(x Color)` #### `OptionSelectedSuggestionTextColor(x Color)`
#### `OptionSelectedSuggestionBGColor(x Color)` #### `OptionSelectedSuggestionBGColor(x Color)`
#### `OptionMaxCompletions(x uint16)` #### `OptionMaxCompletions(x uint16)`
#### `OptionDescriptionTextColor(x Color)`
#### `OptionDescriptionBGColor(x Color)`
#### `OptionSelectedDescriptionTextColor(x Color)`
#### `OptionSelectedDescriptionBGColor(x Color)`
## LICENSE ## LICENSE

@ -11,12 +11,12 @@ func executor(t string) string {
return r return r
} }
func completer(t string) []*prompt.Suggestion { func completer(t string) []prompt.Suggestion {
return []*prompt.Suggestion{ return []prompt.Suggestion{
{Text: "users"}, {Text: "users", Description: "user table"},
{Text: "sites"}, {Text: "sites", Description: "sites table"},
{Text: "articles"}, {Text: "articles", Description: "articles table"},
{Text: "comments"}, {Text: "comments", Description: "comments table"},
} }
} }

@ -116,6 +116,34 @@ func OptionSelectedSuggestionBGColor(x Color) option {
} }
} }
func OptionDescriptionTextColor(x Color) option {
return func(p *Prompt) error {
p.renderer.descriptionTextColor = x
return nil
}
}
func OptionDescriptionBGColor(x Color) option {
return func(p *Prompt) error {
p.renderer.descriptionBGColor = x
return nil
}
}
func OptionSelectedDescriptionTextColor(x Color) option {
return func(p *Prompt) error {
p.renderer.selectedDescriptionTextColor = x
return nil
}
}
func OptionSelectedDescriptionBGColor(x Color) option {
return func(p *Prompt) error {
p.renderer.selectedDescriptionBGColor = x
return nil
}
}
func OptionMaxCompletions(x uint16) option { func OptionMaxCompletions(x uint16) option {
return func(p *Prompt) error { return func(p *Prompt) error {
p.maxCompletions = x p.maxCompletions = x
@ -127,20 +155,24 @@ func NewPrompt(executor Executor, completer Completer, opts ...option) *Prompt {
pt := &Prompt{ pt := &Prompt{
in: &VT100Parser{fd: syscall.Stdin}, in: &VT100Parser{fd: syscall.Stdin},
renderer: &Render{ renderer: &Render{
prefix: "> ", prefix: "> ",
out: &VT100Writer{fd: syscall.Stdout}, out: &VT100Writer{fd: syscall.Stdout},
prefixTextColor: Blue, prefixTextColor: Blue,
prefixBGColor: DefaultColor, prefixBGColor: DefaultColor,
inputTextColor: DefaultColor, inputTextColor: DefaultColor,
inputBGColor: DefaultColor, inputBGColor: DefaultColor,
outputTextColor: DefaultColor, outputTextColor: DefaultColor,
outputBGColor: DefaultColor, outputBGColor: DefaultColor,
previewSuggestionTextColor: Green, previewSuggestionTextColor: Green,
previewSuggestionBGColor: DefaultColor, previewSuggestionBGColor: DefaultColor,
suggestionTextColor: White, suggestionTextColor: White,
suggestionBGColor: Cyan, suggestionBGColor: Cyan,
selectedSuggestionTextColor: Black, selectedSuggestionTextColor: Black,
selectedSuggestionBGColor: Turquoise, selectedSuggestionBGColor: Turquoise,
descriptionTextColor: Black,
descriptionBGColor: Turquoise,
selectedDescriptionTextColor: White,
selectedDescriptionBGColor: Cyan,
}, },
buf: NewBuffer(), buf: NewBuffer(),
executor: executor, executor: executor,

@ -8,7 +8,7 @@ import (
) )
type Executor func(string) string type Executor func(string) string
type Completer func(string) []*Suggestion type Completer func(string) []Suggestion
type Suggestion struct { type Suggestion struct {
Text string Text string
Description string Description string
@ -97,7 +97,7 @@ func (p *Prompt) Run() {
} }
} }
func (p *Prompt) updateSelectedCompletion(completions []*Suggestion) { func (p *Prompt) updateSelectedCompletion(completions []Suggestion) {
max := int(p.maxCompletions) max := int(p.maxCompletions)
if len(completions) < max { if len(completions) < max {
max = len(completions) max = len(completions)

108
render.go

@ -2,6 +2,16 @@ package prompt
import "strings" import "strings"
const (
leftPrefix = " "
leftSuffix = " "
rightPrefix = " "
rightSuffix = " "
leftMargin = len(leftPrefix + leftSuffix)
rightMargin = len(rightPrefix + rightSuffix)
completionMargin = leftMargin + rightMargin
)
type Render struct { type Render struct {
out ConsoleWriter out ConsoleWriter
prefix string prefix string
@ -9,18 +19,22 @@ type Render struct {
row uint16 row uint16
col uint16 col uint16
// colors // colors
prefixTextColor Color prefixTextColor Color
prefixBGColor Color prefixBGColor Color
inputTextColor Color inputTextColor Color
inputBGColor Color inputBGColor Color
outputTextColor Color outputTextColor Color
outputBGColor Color outputBGColor Color
previewSuggestionTextColor Color previewSuggestionTextColor Color
previewSuggestionBGColor Color previewSuggestionBGColor Color
suggestionTextColor Color suggestionTextColor Color
suggestionBGColor Color suggestionBGColor Color
selectedSuggestionTextColor Color selectedSuggestionTextColor Color
selectedSuggestionBGColor Color selectedSuggestionBGColor Color
descriptionTextColor Color
descriptionBGColor Color
selectedDescriptionTextColor Color
selectedDescriptionBGColor Color
} }
func (r *Render) Setup() { func (r *Render) Setup() {
@ -67,7 +81,7 @@ func (r *Render) renderWindowTooSmall() {
return return
} }
func (r *Render) renderCompletion(buf *Buffer, suggestions []*Suggestion, max uint16, selected int) { func (r *Render) renderCompletion(buf *Buffer, suggestions []Suggestion, max uint16, selected int) {
if max > r.row { if max > r.row {
max = r.row max = r.row
} }
@ -81,8 +95,6 @@ func (r *Render) renderCompletion(buf *Buffer, suggestions []*Suggestion, max ui
formatted, width := formatCompletions( formatted, width := formatCompletions(
suggestions, suggestions,
int(r.col)-len(r.prefix), int(r.col)-len(r.prefix),
" ",
" ",
) )
l := len(formatted) l := len(formatted)
r.prepareArea(l) r.prepareArea(l)
@ -102,7 +114,14 @@ func (r *Render) renderCompletion(buf *Buffer, suggestions []*Suggestion, max ui
} else { } else {
r.out.SetColor(r.suggestionTextColor, r.suggestionBGColor) r.out.SetColor(r.suggestionTextColor, r.suggestionBGColor)
} }
r.out.WriteStr(formatted[i]) r.out.WriteStr(formatted[i].Text)
if i == selected {
r.out.SetColor(r.selectedDescriptionTextColor, r.selectedDescriptionBGColor)
} else {
r.out.SetColor(r.descriptionTextColor, r.descriptionBGColor)
}
r.out.WriteStr(formatted[i].Description)
r.out.CursorBackward(width) r.out.CursorBackward(width)
} }
if d == 0 { // the cursor is on right end. if d == 0 { // the cursor is on right end.
@ -117,7 +136,7 @@ func (r *Render) renderCompletion(buf *Buffer, suggestions []*Suggestion, max ui
return return
} }
func (r *Render) Render(buffer *Buffer, completions []*Suggestion, maxCompletions uint16, selected int) { func (r *Render) Render(buffer *Buffer, completions []Suggestion, maxCompletions uint16, selected int) {
// Erasing // Erasing
r.out.CursorBackward(int(r.col) + len(buffer.Text()) + len(r.prefix)) r.out.CursorBackward(int(r.col) + len(buffer.Text()) + len(r.prefix))
r.out.EraseDown() r.out.EraseDown()
@ -125,7 +144,7 @@ func (r *Render) Render(buffer *Buffer, completions []*Suggestion, maxCompletion
// prepare area // prepare area
line := buffer.Text() line := buffer.Text()
h := ((len(r.prefix) + len(line)) / int(r.col)) + 1 + int(maxCompletions) h := ((len(r.prefix) + len(line)) / int(r.col)) + 1 + int(maxCompletions)
if h > int(r.row) { if h > int(r.row) || completionMargin > int(r.col) {
r.renderWindowTooSmall() r.renderWindowTooSmall()
return return
} }
@ -169,31 +188,58 @@ func (r *Render) RenderResult(result string) {
r.out.SetColor(DefaultColor, DefaultColor) r.out.SetColor(DefaultColor, DefaultColor)
} }
func formatCompletions(suggestions []*Suggestion, max int, prefix string, suffix string) (new []string, width int) { func formatCompletions(suggestions []Suggestion, max int) (new []Suggestion, width int) {
num := len(suggestions) num := len(suggestions)
new = make([]string, num) new = make([]Suggestion, num)
width = 0 leftWidth := 0
rightWidth := 0
for i := 0; i < num; i++ { for i := 0; i < num; i++ {
if width < len([]rune(suggestions[i].Text)) { if leftWidth < len([]rune(suggestions[i].Text)) {
width = len([]rune(suggestions[i].Text)) leftWidth = len([]rune(suggestions[i].Text))
}
if rightWidth < len([]rune(suggestions[i].Description)) {
rightWidth = len([]rune(suggestions[i].Description))
} }
} }
if len(prefix)+width+len(suffix) > max { if diff := max - completionMargin - leftWidth - rightWidth; diff < 0 {
width = max - len(prefix) - len(suffix) if rightWidth > diff {
rightWidth -= diff
} else if rightWidth+rightMargin > diff {
leftWidth += rightWidth + rightMargin - diff
rightWidth = 0
}
}
if rightWidth == 0 {
width = leftWidth + leftMargin
} else {
width = leftWidth + leftMargin + rightWidth + rightMargin
} }
for i := 0; i < num; i++ { for i := 0; i < num; i++ {
if l := len(suggestions[i].Text); l > width { var newText string
new[i] = prefix + suggestions[i].Text[:width-len("...")] + "..." + suffix var newDescription string
if l := len(suggestions[i].Text); l > leftWidth {
newText = leftPrefix + suggestions[i].Text[:leftWidth-len("...")] + "..." + leftSuffix
} else if l < width { } else if l < width {
spaces := strings.Repeat(" ", width-len([]rune(suggestions[i].Text))) spaces := strings.Repeat(" ", leftWidth-len([]rune(suggestions[i].Text)))
new[i] = prefix + suggestions[i].Text + spaces + suffix newText = leftPrefix + suggestions[i].Text + spaces + leftSuffix
} else { } else {
new[i] = prefix + suggestions[i].Text + suffix newText = leftPrefix + suggestions[i].Text + leftSuffix
} }
if rightWidth == 0 {
newDescription = ""
} else if l := len(suggestions[i].Description); l > rightWidth {
newDescription = rightPrefix + suggestions[i].Description[:rightWidth-len("...")] + "..." + rightSuffix
} else if l < width {
spaces := strings.Repeat(" ", rightWidth-len([]rune(suggestions[i].Description)))
newDescription = rightPrefix + suggestions[i].Description + spaces + rightSuffix
} else {
newDescription = rightPrefix + suggestions[i].Description + rightSuffix
}
new[i] = Suggestion{Text: newText, Description: newDescription}
} }
width += len(prefix) + len(suffix)
return return
} }

@ -8,16 +8,16 @@ import (
func TestFormatCompletion(t *testing.T) { func TestFormatCompletion(t *testing.T) {
scenarioTable := []struct { scenarioTable := []struct {
scenario string scenario string
completions []*Suggestion completions []Suggestion
prefix string prefix string
suffix string suffix string
expected []string expected []Suggestion
maxWidth int maxWidth int
expectedWidth int expectedWidth int
}{ }{
{ {
scenario: "", scenario: "",
completions: []*Suggestion{ completions: []Suggestion{
{Text: "select"}, {Text: "select"},
{Text: "from"}, {Text: "from"},
{Text: "insert"}, {Text: "insert"},
@ -25,19 +25,38 @@ func TestFormatCompletion(t *testing.T) {
}, },
prefix: " ", prefix: " ",
suffix: " ", suffix: " ",
expected: []string{ expected: []Suggestion{
" select ", {Text: " select "},
" from ", {Text: " from "},
" insert ", {Text: " insert "},
" where ", {Text: " where "},
}, },
maxWidth: 20, maxWidth: 20,
expectedWidth: 8, expectedWidth: 8,
}, },
{
scenario: "",
completions: []Suggestion{
{Text: "select", Description: "select description"},
{Text: "from", Description: "from description"},
{Text: "insert", Description: "insert description"},
{Text: "where", Description: "where description"},
},
prefix: " ",
suffix: " ",
expected: []Suggestion{
{Text: " select ", Description: " select description "},
{Text: " from ", Description: " from description "},
{Text: " insert ", Description: " insert description "},
{Text: " where ", Description: " where description "},
},
maxWidth: 40,
expectedWidth: 28,
},
} }
for _, s := range scenarioTable { for _, s := range scenarioTable {
ac, width := formatCompletions(s.completions, s.maxWidth, s.prefix, s.suffix) ac, width := formatCompletions(s.completions, s.maxWidth)
if !reflect.DeepEqual(ac, s.expected) { if !reflect.DeepEqual(ac, s.expected) {
t.Errorf("Should be %#v, but got %#v", s.expected, ac) t.Errorf("Should be %#v, but got %#v", s.expected, ac)
} }