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
@ -141,6 +169,10 @@ func NewPrompt(executor Executor, completer Completer, opts ...option) *Prompt {
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)

@ -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
@ -21,6 +31,10 @@ type Render struct {
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
} }
}
for i := 0; i < num; i++ { if rightWidth == 0 {
if l := len(suggestions[i].Text); l > width { width = leftWidth + leftMargin
new[i] = prefix + suggestions[i].Text[:width-len("...")] + "..." + suffix
} else if l < width {
spaces := strings.Repeat(" ", width-len([]rune(suggestions[i].Text)))
new[i] = prefix + suggestions[i].Text + spaces + suffix
} else { } else {
new[i] = prefix + suggestions[i].Text + suffix width = leftWidth + leftMargin + rightWidth + rightMargin
} }
for i := 0; i < num; i++ {
var newText string
var newDescription string
if l := len(suggestions[i].Text); l > leftWidth {
newText = leftPrefix + suggestions[i].Text[:leftWidth-len("...")] + "..." + leftSuffix
} else if l < width {
spaces := strings.Repeat(" ", leftWidth-len([]rune(suggestions[i].Text)))
newText = leftPrefix + suggestions[i].Text + spaces + leftSuffix
} else {
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)
} }