Merge pull request #1 from c-bata/suggest-with-description

Add Suggestion struct with Description
This commit is contained in:
c-bata 2017-07-19 00:40:02 +09:00 committed by GitHub
commit 58b2a9c539
6 changed files with 178 additions and 73 deletions

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

@ -11,12 +11,12 @@ func executor(t string) string {
return r
}
func completer(t string) []string {
return []string{
"users",
"sites",
"articles",
"comments",
func completer(t string) []prompt.Completion {
return []prompt.Completion{
{Text: "users", Description: "user table"},
{Text: "sites", Description: "sites table"},
{Text: "articles", Description: "articles table"},
{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 {
return func(p *Prompt) error {
p.maxCompletions = x
@ -127,20 +155,24 @@ func NewPrompt(executor Executor, completer Completer, opts ...option) *Prompt {
pt := &Prompt{
in: &VT100Parser{fd: syscall.Stdin},
renderer: &Render{
prefix: "> ",
out: &VT100Writer{fd: syscall.Stdout},
prefixTextColor: Blue,
prefixBGColor: DefaultColor,
inputTextColor: DefaultColor,
inputBGColor: DefaultColor,
outputTextColor: DefaultColor,
outputBGColor: DefaultColor,
previewSuggestionTextColor: Green,
previewSuggestionBGColor: DefaultColor,
suggestionTextColor: White,
suggestionBGColor: Cyan,
selectedSuggestionTextColor: Black,
selectedSuggestionBGColor: Turquoise,
prefix: "> ",
out: &VT100Writer{fd: syscall.Stdout},
prefixTextColor: Blue,
prefixBGColor: DefaultColor,
inputTextColor: DefaultColor,
inputBGColor: DefaultColor,
outputTextColor: DefaultColor,
outputBGColor: DefaultColor,
previewSuggestionTextColor: Green,
previewSuggestionBGColor: DefaultColor,
suggestionTextColor: White,
suggestionBGColor: Cyan,
selectedSuggestionTextColor: Black,
selectedSuggestionBGColor: Turquoise,
descriptionTextColor: Black,
descriptionBGColor: Turquoise,
selectedDescriptionTextColor: White,
selectedDescriptionBGColor: Cyan,
},
buf: NewBuffer(),
executor: executor,

@ -8,7 +8,11 @@ import (
)
type Executor func(string) string
type Completer func(string) []string
type Completer func(string) []Completion
type Completion struct {
Text string
Description string
}
type Prompt struct {
in ConsoleParser
@ -43,7 +47,7 @@ func (p *Prompt) Run() {
if w != "" {
p.buf.DeleteBeforeCursor(len([]rune(w)))
}
p.buf.InsertText(c, false, true)
p.buf.InsertText(c.Text, false, true)
}
p.selected = -1
p.buf.InsertText(string(b), false, true)
@ -54,7 +58,7 @@ func (p *Prompt) Run() {
if w != "" {
p.buf.DeleteBeforeCursor(len([]rune(w)))
}
p.buf.InsertText(c, false, true)
p.buf.InsertText(c.Text, false, true)
}
p.renderer.BreakLine(p.buf)
res := p.executor(p.buf.Text())
@ -93,7 +97,7 @@ func (p *Prompt) Run() {
}
}
func (p *Prompt) updateSelectedCompletion(completions []string) {
func (p *Prompt) updateSelectedCompletion(completions []Completion) {
max := int(p.maxCompletions)
if len(completions) < max {
max = len(completions)

118
render.go

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

@ -8,36 +8,55 @@ import (
func TestFormatCompletion(t *testing.T) {
scenarioTable := []struct {
scenario string
completions []string
completions []Completion
prefix string
suffix string
expected []string
expected []Completion
maxWidth int
expectedWidth int
}{
{
scenario: "",
completions: []string{
"select",
"from",
"insert",
"where",
completions: []Completion{
{Text: "select"},
{Text: "from"},
{Text: "insert"},
{Text: "where"},
},
prefix: " ",
suffix: " ",
expected: []string{
" select ",
" from ",
" insert ",
" where ",
expected: []Completion{
{Text: " select "},
{Text: " from "},
{Text: " insert "},
{Text: " where "},
},
maxWidth: 20,
expectedWidth: 8,
},
{
scenario: "",
completions: []Completion{
{Text: "select", Description: "select description"},
{Text: "from", Description: "from description"},
{Text: "insert", Description: "insert description"},
{Text: "where", Description: "where description"},
},
prefix: " ",
suffix: " ",
expected: []Completion{
{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 {
ac, width := formatCompletions(s.completions, s.maxWidth, s.prefix, s.suffix)
ac, width := formatCompletions(s.completions, s.maxWidth)
if !reflect.DeepEqual(ac, s.expected) {
t.Errorf("Should be %#v, but got %#v", s.expected, ac)
}