Refactor completions
This commit is contained in:
parent
b578417898
commit
e871f177ec
104
completion.go
104
completion.go
|
@ -1,35 +1,56 @@
|
|||
package prompt
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Suggest struct {
|
||||
Text string
|
||||
Description string
|
||||
}
|
||||
|
||||
type CompletionManager struct {
|
||||
selected int // -1 means nothing one is selected.
|
||||
tmp []*Suggest
|
||||
Max uint16
|
||||
selected int // -1 means nothing one is selected.
|
||||
tmp []Suggest
|
||||
Max uint16
|
||||
completer Completer
|
||||
}
|
||||
|
||||
func (c *CompletionManager) GetSelectedSuggestion() (s Suggest, ok bool) {
|
||||
if c.selected == -1 {
|
||||
return Suggest{}, false
|
||||
} else if c.selected < -1 {
|
||||
log.Printf("[ERROR] shoud be reached here, selected=%d", c.selected)
|
||||
return Suggest{}, false
|
||||
}
|
||||
return c.tmp[c.selected], true
|
||||
}
|
||||
|
||||
func (c *CompletionManager) GetSuggestions() []Suggest {
|
||||
return c.tmp
|
||||
}
|
||||
|
||||
func (c *CompletionManager) Reset() {
|
||||
c.selected = -1
|
||||
c.Update([]*Suggest{})
|
||||
c.Update("")
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CompletionManager) Update(new []*Suggest) {
|
||||
c.selected = -1
|
||||
c.tmp = new
|
||||
func (c *CompletionManager) Update(in string) {
|
||||
c.tmp = c.completer(in)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CompletionManager) Previous() {
|
||||
c.selected--
|
||||
c.update()
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CompletionManager) Next() {
|
||||
c.selected++
|
||||
c.update()
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -37,10 +58,10 @@ func (c *CompletionManager) Completing() bool {
|
|||
return c.selected != -1
|
||||
}
|
||||
|
||||
func (c *CompletionManager) update(completions []Suggest) {
|
||||
func (c *CompletionManager) update() {
|
||||
max := int(c.Max)
|
||||
if len(completions) < max {
|
||||
max = len(completions)
|
||||
if len(c.tmp) < max {
|
||||
max = len(c.tmp)
|
||||
}
|
||||
if c.selected >= max {
|
||||
c.Reset()
|
||||
|
@ -49,9 +70,66 @@ func (c *CompletionManager) update(completions []Suggest) {
|
|||
}
|
||||
}
|
||||
|
||||
func NewCompletionManager(max uint16) *CompletionManager {
|
||||
func formatCompletions(completions []Suggest, max int) (new []Suggest, width int) {
|
||||
num := len(completions)
|
||||
new = make([]Suggest, num)
|
||||
leftWidth := 0
|
||||
rightWidth := 0
|
||||
|
||||
for i := 0; i < num; 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 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++ {
|
||||
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(" ", leftWidth-len([]rune(completions[i].Text)))
|
||||
newText = leftPrefix + completions[i].Text + spaces + leftSuffix
|
||||
} else {
|
||||
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] = Suggest{Text: newText, Description: newDescription}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func NewCompletionManager(completer Completer, max uint16) *CompletionManager {
|
||||
return &CompletionManager{
|
||||
selected: -1,
|
||||
Max: max,
|
||||
selected: -1,
|
||||
Max: max,
|
||||
completer: completer,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -166,11 +166,10 @@ func New(executor Executor, completer Completer, opts ...option) *Prompt {
|
|||
selectedDescriptionTextColor: White,
|
||||
selectedDescriptionBGColor: Cyan,
|
||||
},
|
||||
buf: NewBuffer(),
|
||||
executor: executor,
|
||||
completer: completer,
|
||||
history: NewHistory(),
|
||||
completion: NewCompletionManager(6),
|
||||
buf: NewBuffer(),
|
||||
executor: executor,
|
||||
history: NewHistory(),
|
||||
completion: NewCompletionManager(completer, 6),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
|
|
31
prompt.go
31
prompt.go
|
@ -22,7 +22,6 @@ type Prompt struct {
|
|||
buf *Buffer
|
||||
renderer *Render
|
||||
executor Executor
|
||||
completer Completer
|
||||
history *History
|
||||
completion *CompletionManager
|
||||
}
|
||||
|
@ -45,7 +44,7 @@ func (p *Prompt) Run() {
|
|||
log.Println("[INFO] Logging is enabled.")
|
||||
}
|
||||
|
||||
p.renderer.Render(p.buf, p.completer(p.buf.Text()), p.completion.Max, p.completion.selected)
|
||||
p.renderer.Render(p.buf, p.completion)
|
||||
|
||||
bufCh := make(chan []byte, 128)
|
||||
stopReadBufCh := make(chan struct{})
|
||||
|
@ -69,22 +68,19 @@ func (p *Prompt) Run() {
|
|||
p.in.TearDown()
|
||||
p.executor(e.input)
|
||||
|
||||
completions := p.completer(p.buf.Text())
|
||||
p.completion.update(completions)
|
||||
p.renderer.Render(p.buf, completions, p.completion.Max, p.completion.selected)
|
||||
p.completion.Update(p.buf.Text())
|
||||
p.renderer.Render(p.buf, p.completion)
|
||||
|
||||
// Set raw mode
|
||||
p.in.Setup()
|
||||
go p.readBuffer(bufCh, stopReadBufCh)
|
||||
} else {
|
||||
completions := p.completer(p.buf.Text())
|
||||
p.completion.update(completions)
|
||||
p.renderer.Render(p.buf, completions, p.completion.Max, p.completion.selected)
|
||||
p.completion.Update(p.buf.Text())
|
||||
p.renderer.Render(p.buf, p.completion)
|
||||
}
|
||||
case w := <-winSizeCh:
|
||||
p.renderer.UpdateWinSize(w)
|
||||
completions := p.completer(p.buf.Text())
|
||||
p.renderer.Render(p.buf, completions, p.completion.Max, p.completion.selected)
|
||||
p.renderer.Render(p.buf, p.completion)
|
||||
case code := <-exitCh:
|
||||
p.tearDown()
|
||||
os.Exit(code)
|
||||
|
@ -99,13 +95,12 @@ func (p *Prompt) feed(b []byte) (shouldExit bool, exec *Exec) {
|
|||
|
||||
switch key {
|
||||
case ControlJ, Enter:
|
||||
if p.completion.Completing() {
|
||||
c := p.completer(p.buf.Text())[p.completion.selected]
|
||||
if s, ok := p.completion.GetSelectedSuggestion(); ok {
|
||||
w := p.buf.Document().GetWordBeforeCursor()
|
||||
if w != "" {
|
||||
p.buf.DeleteBeforeCursor(len([]rune(w)))
|
||||
}
|
||||
p.buf.InsertText(c.Text, false, true)
|
||||
p.buf.InsertText(s.Text, false, true)
|
||||
}
|
||||
p.renderer.BreakLine(p.buf)
|
||||
|
||||
|
@ -148,24 +143,22 @@ func (p *Prompt) feed(b []byte) (shouldExit bool, exec *Exec) {
|
|||
case Right:
|
||||
p.buf.CursorRight(1)
|
||||
case Backspace:
|
||||
if p.completion.Completing() {
|
||||
c := p.completer(p.buf.Text())[p.completion.selected]
|
||||
if s, ok := p.completion.GetSelectedSuggestion(); ok {
|
||||
w := p.buf.Document().GetWordBeforeCursor()
|
||||
if w != "" {
|
||||
p.buf.DeleteBeforeCursor(len([]rune(w)))
|
||||
}
|
||||
p.buf.InsertText(c.Text, false, true)
|
||||
p.buf.InsertText(s.Text, false, true)
|
||||
p.completion.Reset()
|
||||
}
|
||||
p.buf.DeleteBeforeCursor(1)
|
||||
case NotDefined:
|
||||
if p.completion.Completing() {
|
||||
c := p.completer(p.buf.Text())[p.completion.selected]
|
||||
if s, ok := p.completion.GetSelectedSuggestion(); ok {
|
||||
w := p.buf.Document().GetWordBeforeCursor()
|
||||
if w != "" {
|
||||
p.buf.DeleteBeforeCursor(len([]rune(w)))
|
||||
}
|
||||
p.buf.InsertText(c.Text, false, true)
|
||||
p.buf.InsertText(s.Text, false, true)
|
||||
}
|
||||
p.completion.Reset()
|
||||
p.buf.InsertText(string(b), false, true)
|
||||
|
|
86
render.go
86
render.go
|
@ -1,6 +1,8 @@
|
|||
package prompt
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
const (
|
||||
leftPrefix = " "
|
||||
|
@ -79,21 +81,24 @@ func (r *Render) renderWindowTooSmall() {
|
|||
return
|
||||
}
|
||||
|
||||
func (r *Render) renderCompletion(buf *Buffer, completions []Suggest, max uint16, selected int) {
|
||||
func (r *Render) renderCompletion(buf *Buffer, completions *CompletionManager) {
|
||||
max := completions.Max
|
||||
if max > r.row {
|
||||
max = r.row
|
||||
}
|
||||
|
||||
if l := len(completions); l == 0 {
|
||||
suggestions := completions.GetSuggestions()
|
||||
if l := len(completions.GetSuggestions()); l == 0 {
|
||||
return
|
||||
} else if l > int(max) {
|
||||
completions = completions[:max]
|
||||
suggestions = suggestions[:max]
|
||||
}
|
||||
|
||||
formatted, width := formatCompletions(
|
||||
completions,
|
||||
suggestions,
|
||||
int(r.col)-len(r.prefix),
|
||||
)
|
||||
log.Printf("[INFO] formatted: %#v\n", formatted)
|
||||
l := len(formatted)
|
||||
r.prepareArea(l)
|
||||
|
||||
|
@ -107,14 +112,14 @@ func (r *Render) renderCompletion(buf *Buffer, completions []Suggest, max uint16
|
|||
r.out.SetColor(White, Cyan, false)
|
||||
for i := 0; i < l; i++ {
|
||||
r.out.CursorDown(1)
|
||||
if i == selected {
|
||||
if i == completions.selected {
|
||||
r.out.SetColor(r.selectedSuggestionTextColor, r.selectedSuggestionBGColor, true)
|
||||
} else {
|
||||
r.out.SetColor(r.suggestionTextColor, r.suggestionBGColor, false)
|
||||
}
|
||||
r.out.WriteStr(formatted[i].Text)
|
||||
|
||||
if i == selected {
|
||||
if i == completions.selected {
|
||||
r.out.SetColor(r.selectedDescriptionTextColor, r.selectedDescriptionBGColor, false)
|
||||
} else {
|
||||
r.out.SetColor(r.descriptionTextColor, r.descriptionBGColor, false)
|
||||
|
@ -134,14 +139,14 @@ func (r *Render) renderCompletion(buf *Buffer, completions []Suggest, max uint16
|
|||
return
|
||||
}
|
||||
|
||||
func (r *Render) Render(buffer *Buffer, completions []Suggest, maxCompletions uint16, selected int) {
|
||||
func (r *Render) Render(buffer *Buffer, completion *CompletionManager) {
|
||||
// Erasing
|
||||
r.out.CursorBackward(int(r.col) + len(buffer.Text()) + len(r.prefix))
|
||||
r.out.EraseDown()
|
||||
|
||||
// prepare area
|
||||
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(completion.Max)
|
||||
if h > int(r.row) || completionMargin > int(r.col) {
|
||||
r.renderWindowTooSmall()
|
||||
return
|
||||
|
@ -154,12 +159,11 @@ func (r *Render) Render(buffer *Buffer, completions []Suggest, maxCompletions ui
|
|||
r.out.WriteStr(line)
|
||||
r.out.SetColor(DefaultColor, DefaultColor, false)
|
||||
r.out.CursorBackward(len(line) - buffer.CursorPosition)
|
||||
r.renderCompletion(buffer, completions, maxCompletions, selected)
|
||||
if selected != -1 {
|
||||
c := completions[selected]
|
||||
r.renderCompletion(buffer, completion)
|
||||
if suggest, ok := completion.GetSelectedSuggestion(); ok {
|
||||
r.out.CursorBackward(len([]rune(buffer.Document().GetWordBeforeCursor())))
|
||||
r.out.SetColor(r.previewSuggestionTextColor, r.previewSuggestionBGColor, false)
|
||||
r.out.WriteStr(c.Text)
|
||||
r.out.WriteStr(suggest.Text)
|
||||
r.out.SetColor(DefaultColor, DefaultColor, false)
|
||||
}
|
||||
r.out.Flush()
|
||||
|
@ -176,59 +180,3 @@ func (r *Render) BreakLine(buffer *Buffer) {
|
|||
r.out.SetColor(DefaultColor, DefaultColor, false)
|
||||
r.out.Flush()
|
||||
}
|
||||
|
||||
func formatCompletions(completions []Suggest, max int) (new []Suggest, width int) {
|
||||
num := len(completions)
|
||||
new = make([]Suggest, num)
|
||||
leftWidth := 0
|
||||
rightWidth := 0
|
||||
|
||||
for i := 0; i < num; 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 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++ {
|
||||
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(" ", leftWidth-len([]rune(completions[i].Text)))
|
||||
newText = leftPrefix + completions[i].Text + spaces + leftSuffix
|
||||
} else {
|
||||
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] = Suggest{Text: newText, Description: newDescription}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -41,12 +41,10 @@ func (t *VT100Parser) TearDown() error {
|
|||
|
||||
func (t *VT100Parser) setRawMode() error {
|
||||
x := t.origTermios.Lflag
|
||||
if x &^= syscall.ICANON; x != 0 && x == t.origTermios.Lflag {
|
||||
if x &^= syscall.ICANON; x != 0 && x == t.origTermios.Lflag {
|
||||
// fd is already raw mode
|
||||
log.Print("[INFO] already raw mode.")
|
||||
return nil
|
||||
}
|
||||
log.Print("[INFO] set raw mode.")
|
||||
var n syscall.Termios
|
||||
if err := termios.Tcgetattr(uintptr(t.fd), &t.origTermios); err != nil {
|
||||
return err
|
||||
|
@ -64,7 +62,6 @@ func (t *VT100Parser) resetRawMode() error {
|
|||
if t.origTermios.Lflag == 0 {
|
||||
return nil
|
||||
}
|
||||
log.Print("[INFO] Reset raw mode.")
|
||||
return termios.Tcsetattr(uintptr(t.fd), termios.TCSANOW, &t.origTermios)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue