Complete with description
This commit is contained in:
parent
c7333f7c05
commit
2e1d7d8eb0
@ -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"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
60
option.go
60
option.go
@ -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
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)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user