go-prompt/prompt.go

170 lines
3.6 KiB
Go
Raw Normal View History

2017-07-15 08:37:54 +00:00
package prompt
import (
"os"
"os/signal"
2017-07-15 13:23:47 +00:00
"syscall"
2017-07-15 09:03:18 +00:00
"time"
2017-07-15 08:37:54 +00:00
)
type Executor func(string) string
2017-07-18 15:38:08 +00:00
type Completer func(string) []Completion
type Completion struct {
2017-07-18 00:53:59 +00:00
Text string
Description string
}
2017-07-15 08:37:54 +00:00
type Prompt struct {
2017-07-17 15:52:55 +00:00
in ConsoleParser
buf *Buffer
renderer *Render
executor Executor
completer Completer
maxCompletions uint16
2017-07-17 17:01:24 +00:00
selected int // -1 means nothing one is selected.
2017-07-15 08:37:54 +00:00
}
func (p *Prompt) Run() {
p.setUp()
defer p.tearDown()
p.renderer.Render(p.buf, p.completer(p.buf.Text()), p.maxCompletions, p.selected)
2017-07-15 08:37:54 +00:00
bufCh := make(chan []byte, 128)
go readBuffer(bufCh)
exitCh := make(chan bool, 16)
winSizeCh := make(chan *WinSize, 128)
2017-07-15 09:03:18 +00:00
go handleSignals(p.in, exitCh, winSizeCh)
2017-07-15 08:37:54 +00:00
for {
2017-07-15 09:03:18 +00:00
select {
2017-07-15 13:23:47 +00:00
case b := <-bufCh:
2017-07-15 09:03:18 +00:00
ac := p.in.GetASCIICode(b)
if ac == nil {
2017-07-17 15:52:55 +00:00
if p.selected != -1 {
c := p.completer(p.buf.Text())[p.selected]
2017-07-15 14:27:49 +00:00
w := p.buf.Document().GetWordBeforeCursor()
if w != "" {
p.buf.DeleteBeforeCursor(len([]rune(w)))
}
2017-07-18 00:53:59 +00:00
p.buf.InsertText(c.Text, false, true)
2017-07-15 14:27:49 +00:00
}
2017-07-17 15:52:55 +00:00
p.selected = -1
2017-07-15 09:03:18 +00:00
p.buf.InsertText(string(b), false, true)
} else if ac.Key == ControlJ || ac.Key == Enter {
2017-07-17 15:52:55 +00:00
if p.selected != -1 {
c := p.completer(p.buf.Text())[p.selected]
2017-07-15 14:27:49 +00:00
w := p.buf.Document().GetWordBeforeCursor()
if w != "" {
p.buf.DeleteBeforeCursor(len([]rune(w)))
}
2017-07-18 00:53:59 +00:00
p.buf.InsertText(c.Text, false, true)
2017-07-15 14:27:49 +00:00
}
2017-07-18 11:48:50 +00:00
p.renderer.BreakLine(p.buf)
2017-07-18 13:06:33 +00:00
res := p.executor(p.buf.Text())
p.renderer.RenderResult(res)
2017-07-15 09:03:18 +00:00
p.buf = NewBuffer()
2017-07-17 15:52:55 +00:00
p.selected = -1
2017-07-17 16:47:50 +00:00
} else if ac.Key == ControlC {
2017-07-18 11:48:50 +00:00
p.renderer.BreakLine(p.buf)
2017-07-17 16:47:50 +00:00
p.buf = NewBuffer()
p.selected = -1
} else if ac.Key == ControlD {
2017-07-15 09:03:18 +00:00
return
2017-07-15 13:55:11 +00:00
} else if ac.Key == BackTab || ac.Key == Up {
2017-07-17 15:52:55 +00:00
p.selected -= 1
2017-07-15 13:55:11 +00:00
} else if ac.Key == Tab || ac.Key == ControlI || ac.Key == Down {
2017-07-17 15:52:55 +00:00
p.selected += 1
2017-07-15 09:03:18 +00:00
} else {
2017-07-15 11:22:56 +00:00
InputHandler(ac, p.buf)
2017-07-17 15:52:55 +00:00
p.selected = -1
2017-07-15 09:03:18 +00:00
}
completions := p.completer(p.buf.Text())
2017-07-17 15:52:55 +00:00
p.updateSelectedCompletion(completions)
p.renderer.Render(p.buf, completions, p.maxCompletions, p.selected)
2017-07-15 13:23:47 +00:00
case w := <-winSizeCh:
2017-07-15 09:03:18 +00:00
p.renderer.UpdateWinSize(w)
completions := p.completer(p.buf.Text())
2017-07-17 15:52:55 +00:00
p.renderer.Render(p.buf, completions, p.maxCompletions, p.selected)
2017-07-15 13:23:47 +00:00
case e := <-exitCh:
2017-07-15 09:03:18 +00:00
if e {
return
}
default:
time.Sleep(10 * time.Millisecond)
2017-07-15 08:37:54 +00:00
}
}
}
2017-07-18 15:38:08 +00:00
func (p *Prompt) updateSelectedCompletion(completions []Completion) {
2017-07-17 15:52:55 +00:00
max := int(p.maxCompletions)
if len(completions) < max {
max = len(completions)
}
if p.selected >= max {
p.selected = -1
} else if p.selected < -1 {
p.selected = max - 1
}
}
2017-07-15 08:37:54 +00:00
func (p *Prompt) setUp() {
p.in.Setup()
2017-07-15 11:22:56 +00:00
p.renderer.Setup()
2017-07-15 16:04:18 +00:00
p.renderer.UpdateWinSize(p.in.GetWinSize())
2017-07-17 16:14:03 +00:00
p.selected = -1 // -1 means nothing one is selected.
2017-07-15 08:37:54 +00:00
}
func (p *Prompt) tearDown() {
p.in.TearDown()
2017-07-15 11:22:56 +00:00
p.renderer.TearDown()
2017-07-15 08:37:54 +00:00
}
func readBuffer(bufCh chan []byte) {
buf := make([]byte, 1024)
for {
if n, err := syscall.Read(syscall.Stdin, buf); err == nil {
bufCh <- buf[:n]
}
}
}
2017-07-16 08:20:58 +00:00
func handleSignals(in ConsoleParser, exitCh chan bool, winSizeCh chan *WinSize) {
2017-07-15 08:37:54 +00:00
sigCh := make(chan os.Signal, 1)
signal.Notify(
sigCh,
syscall.SIGHUP,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT,
syscall.SIGWINCH,
)
for {
s := <-sigCh
switch s {
// kill -SIGHUP XXXX
case syscall.SIGHUP:
exitCh <- true
// kill -SIGINT XXXX or Ctrl+c
case syscall.SIGINT:
exitCh <- true
// kill -SIGTERM XXXX
case syscall.SIGTERM:
exitCh <- true
// kill -SIGQUIT XXXX
case syscall.SIGQUIT:
exitCh <- true
case syscall.SIGWINCH:
winSizeCh <- in.GetWinSize()
default:
}
}
}