diff --git a/erasing.go b/erasing.go new file mode 100644 index 0000000..e58de5d --- /dev/null +++ b/erasing.go @@ -0,0 +1,20 @@ +package main + +import "github.com/c-bata/go-prompt-toolkit/prompt" + +func main() { + l := 20 + out := prompt.NewVT100Writer() + for i := 0; i < l; i++ { + out.CursorGoTo(i, 0) + out.WriteStr("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX") + } + + + out.CursorGoTo(5, 10) + out.EraseLine() + + out.CursorGoTo(l, 0) + out.Flush() + return +} diff --git a/main.go b/main.go index 5c211c7..de75876 100644 --- a/main.go +++ b/main.go @@ -3,18 +3,18 @@ package main import ( "fmt" "syscall" + "os" + "os/signal" "github.com/c-bata/go-prompt-toolkit/prompt" ) -func enterAlternateScreen(fd int) { - syscall.Write(fd, []byte{0x1b, 0x5b, 0x3f, 0x01, 0x00, 0x04, 0x09, 0x68, 0x1b, 0x5b, 0x48}) -} - func scroll(out *prompt.VT100Writer, lines int) { for i := 0; i < lines; i++ { out.ScrollDown() - defer out.ScrollUp() + } + for i := 0; i < lines; i++ { + out.ScrollUp() } return } @@ -23,8 +23,14 @@ func main() { in := prompt.NewVT100Parser() in.Setup() defer in.TearDown() - defer fmt.Println("\nexited!") + defer fmt.Println("\nGoodbye!") out := prompt.NewVT100Writer() + + renderer := prompt.Render{ + Prefix: ">>> ", + Out: out, + } + out.SetTitle("はろー") scroll(out, 7) out.Flush() @@ -32,68 +38,27 @@ func main() { bufCh := make(chan []byte, 128) go readBuffer(bufCh) + winSizeCh := make(chan *prompt.WinSize, 128) + go updateWindowSize(in, winSizeCh) + buffer := prompt.NewBuffer() for { b := <-bufCh - if ac := in.GetASCIICode(b); ac == nil { - out.EraseDown() - out.WriteRaw(b) + ac := in.GetASCIICode(b) + if ac == nil { buffer.InsertText(string(b), false, true) - } else if ac.Key == prompt.Enter || ac.Key == prompt.ControlJ { out.EraseDown() - out.WriteStr(buffer.Document().TextAfterCursor()) - - out.WriteStr("\n>>> Your input: '") - out.WriteStr(buffer.Text()) - out.WriteStr("' <<<\n") - buffer = prompt.NewBuffer() - } else if ac.Key == prompt.Left { - l := buffer.CursorLeft(1) - if l == 0 { - continue - } - out.EraseLine() - out.EraseDown() - after := buffer.Document().CurrentLine() - out.WriteStr(after) - out.CursorBackward(len(after) - buffer.CursorPosition) - } else if ac.Key == prompt.Right { - l := buffer.CursorRight(1) - if l == 0 { - continue - } - - out.CursorForward(l) out.WriteRaw(b) - out.EraseDown() after := buffer.Document().TextAfterCursor() out.WriteStr(after) - } else if ac.Key == prompt.Backspace { - deleted := buffer.DeleteBeforeCursor(1) - if deleted == "" { - continue - } - out.CursorBackward(1) - out.EraseDown() - - after := buffer.Document().TextAfterCursor() - out.WriteStr(after) - } else if ac.Key == prompt.Tab || ac.Key == prompt.ControlI { - } else if ac.Key == prompt.BackTab { - } else if ac.Key == prompt.Right { - buffer.CursorRight(1) - } else if ac.Key == prompt.ControlT { - enterAlternateScreen(syscall.Stdout) } else if ac.Key == prompt.ControlC { out.EraseDown() out.ClearTitle() out.Flush() return - } else if ac.Key == prompt.Up || ac.Key == prompt.Down { } else { - out.WriteRaw(b) - //buffer.InsertText(ac.Key.String(), false, true) + prompt.InputHandler(ac, buffer, out) } // Display completions @@ -132,6 +97,8 @@ func main() { out.SetColor("default", "default") } + completions := []string{"select", "insert", "update", "where"} + renderer.Render(buffer, completions) scroll(out, 4) out.Flush() } @@ -146,3 +113,36 @@ func readBuffer(bufCh chan []byte) { } } } + +func updateWindowSize(in *prompt.VT100Parser, winSizeCh chan *prompt.WinSize) { + 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: + + // kill -SIGINT XXXX or Ctrl+c + case syscall.SIGINT: + + // kill -SIGTERM XXXX + case syscall.SIGTERM: + + // kill -SIGQUIT XXXX + case syscall.SIGQUIT: + + case syscall.SIGWINCH: + winSizeCh <- in.GetWinSize() + default: + } + } +} diff --git a/prompt/completions.go b/prompt/completions.go new file mode 100644 index 0000000..0c4f39f --- /dev/null +++ b/prompt/completions.go @@ -0,0 +1,24 @@ +package prompt + +type Completion struct { + // The new string that will be inserted into document. + text string + // Position relative to the cursor position where the new text will start. + startPosition int +} + +func (c *Completion) NewCompletionFromPosition(position int) *Completion { + if position < c.startPosition { + panic("position argument must be smaller than start position.") + } + + return &Completion{ + text: c.text[position - c.startPosition:], + } +} + +func NewCompletion(text string) *Completion { + return &Completion{ + text: text, + } +} diff --git a/prompt/input.go b/prompt/input.go new file mode 100644 index 0000000..1b3b354 --- /dev/null +++ b/prompt/input.go @@ -0,0 +1,6 @@ +package prompt + +type WinSize struct { + Row uint16 + Col uint16 +} diff --git a/prompt/key_binding.go b/prompt/key_binding.go new file mode 100644 index 0000000..20da693 --- /dev/null +++ b/prompt/key_binding.go @@ -0,0 +1,63 @@ +package prompt + +var InputHandler = defaultHandler + +func defaultHandler(ac *ASCIICode, buffer *Buffer, out *VT100Writer) { + switch ac.Key { + case ControlJ: // this is equivalent with Enter Key. + fallthrough + case Enter: + out.EraseDown() + out.WriteStr(buffer.Document().TextAfterCursor()) + + out.WriteStr("\n>>> Your input: '") + out.WriteStr(buffer.Text()) + out.WriteStr("' <<<\n") + buffer = NewBuffer() + case Left: + l := buffer.CursorLeft(1) + if l == 0 { + return + } + out.EraseLine() + out.EraseDown() + after := buffer.Document().CurrentLine() + out.WriteStr(after) + out.CursorBackward(len(after) - buffer.CursorPosition) + case Right: + l := buffer.CursorRight(1) + if l == 0 { + return + } + + out.CursorForward(l) + out.WriteRaw(ac.ASCIICode) + out.EraseDown() + after := buffer.Document().TextAfterCursor() + out.WriteStr(after) + case Backspace: + deleted := buffer.DeleteBeforeCursor(1) + if deleted == "" { + return + } + out.CursorBackward(1) + out.EraseDown() + + after := buffer.Document().TextAfterCursor() + out.WriteStr(after) + case ControlI: // this is equivalent with TabKey. + fallthrough + case Tab: + break + case ControlT: + break + return + case Up: + break + case Down: + break + default: + break + } + return +} diff --git a/prompt/render.go b/prompt/render.go new file mode 100644 index 0000000..5fa1f03 --- /dev/null +++ b/prompt/render.go @@ -0,0 +1,21 @@ +package prompt + +type Render struct { + Prefix string + Out *VT100Writer + row uint16 + col uint16 // sigwinchで送られてくる列数を常に見ながら、prefixのlengthとbufferのcursor positionを比べて、completionの表示位置をずらす +} + +func (r *Render) UpdateWinSize(ws WinSize) { + r.row = ws.Row + r.col = ws.Col + return +} + +func (r *Render) renderCompletion(words []string) { + return +} + +func (r *Render) Render(buffer *Buffer, completions []string) { +} diff --git a/prompt/vt100_input.go b/prompt/vt100_input.go index 334a853..7dc1eaa 100644 --- a/prompt/vt100_input.go +++ b/prompt/vt100_input.go @@ -53,7 +53,7 @@ func (t *VT100Parser) GetASCIICode(b []byte) *ASCIICode { } // GetWinSize returns winsize struct which is the response of ioctl(2). -func (t *VT100Parser) GetWinSize() (row, col uint16) { +func (t *VT100Parser) GetWinSize() *WinSize { ws := &ioctlWinsize{} retCode, _, errno := syscall.Syscall( syscall.SYS_IOCTL, @@ -64,7 +64,10 @@ func (t *VT100Parser) GetWinSize() (row, col uint16) { if int(retCode) == -1 { panic(errno) } - return ws.Row, ws.Col + return &WinSize{ + Row: ws.Row, + Col: ws.Col, + } } var asciiSequences []*ASCIICode = []*ASCIICode{