mirror of https://github.com/chzyer/readline.git
add remote mode
This commit is contained in:
parent
14e9df7f3c
commit
b57eccfd02
10
complete.go
10
complete.go
|
@ -22,6 +22,7 @@ type opCompleter struct {
|
||||||
w io.Writer
|
w io.Writer
|
||||||
op *Operation
|
op *Operation
|
||||||
ac AutoCompleter
|
ac AutoCompleter
|
||||||
|
width int
|
||||||
|
|
||||||
inCompleteMode bool
|
inCompleteMode bool
|
||||||
inSelectMode bool
|
inSelectMode bool
|
||||||
|
@ -32,11 +33,12 @@ type opCompleter struct {
|
||||||
candidateColNum int
|
candidateColNum int
|
||||||
}
|
}
|
||||||
|
|
||||||
func newOpCompleter(w io.Writer, op *Operation) *opCompleter {
|
func newOpCompleter(w io.Writer, op *Operation, width int) *opCompleter {
|
||||||
return &opCompleter{
|
return &opCompleter{
|
||||||
w: w,
|
w: w,
|
||||||
op: op,
|
op: op,
|
||||||
ac: op.cfg.AutoComplete,
|
ac: op.cfg.AutoComplete,
|
||||||
|
width: width,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,6 +173,10 @@ func (o *opCompleter) getMatrixSize() int {
|
||||||
return line * o.candidateColNum
|
return line * o.candidateColNum
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *opCompleter) OnWidthChange(newWidth int) {
|
||||||
|
o.width = newWidth
|
||||||
|
}
|
||||||
|
|
||||||
func (o *opCompleter) CompleteRefresh() {
|
func (o *opCompleter) CompleteRefresh() {
|
||||||
if !o.inCompleteMode {
|
if !o.inCompleteMode {
|
||||||
return
|
return
|
||||||
|
@ -183,7 +189,7 @@ func (o *opCompleter) CompleteRefresh() {
|
||||||
colWidth = w
|
colWidth = w
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
colNum := o.op.cfg.FuncGetWidth() / (colWidth + o.candidateOff + 2)
|
colNum := o.width / (colWidth + o.candidateOff + 2)
|
||||||
o.candidateColNum = colNum
|
o.candidateColNum = colNum
|
||||||
buf := bytes.NewBuffer(nil)
|
buf := bytes.NewBuffer(nil)
|
||||||
buf.Write(bytes.Repeat([]byte("\n"), lineCnt))
|
buf.Write(bytes.Repeat([]byte("\n"), lineCnt))
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/chzyer/readline"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := readline.DialRemote("tcp", ":12344"); err != nil {
|
||||||
|
println(err.Error())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/chzyer/readline"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cfg := &readline.Config{
|
||||||
|
Prompt: "readline-remote: ",
|
||||||
|
}
|
||||||
|
handleFunc := func(rl *readline.Instance) {
|
||||||
|
for {
|
||||||
|
line, err := rl.Readline()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fmt.Fprintln(rl.Stdout(), "receive:"+line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := readline.ListenRemote("tcp", ":12344", cfg, handleFunc)
|
||||||
|
if err != nil {
|
||||||
|
println(err.Error())
|
||||||
|
}
|
||||||
|
}
|
16
operation.go
16
operation.go
|
@ -53,17 +53,24 @@ func (w *wrapWriter) Write(b []byte) (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOperation(t *Terminal, cfg *Config) *Operation {
|
func NewOperation(t *Terminal, cfg *Config) *Operation {
|
||||||
|
width := cfg.FuncGetWidth()
|
||||||
op := &Operation{
|
op := &Operation{
|
||||||
t: t,
|
t: t,
|
||||||
buf: NewRuneBuffer(t, cfg.Prompt, cfg),
|
buf: NewRuneBuffer(t, cfg.Prompt, cfg, width),
|
||||||
outchan: make(chan []rune),
|
outchan: make(chan []rune),
|
||||||
errchan: make(chan error),
|
errchan: make(chan error),
|
||||||
}
|
}
|
||||||
op.w = op.buf.w
|
op.w = op.buf.w
|
||||||
op.SetConfig(cfg)
|
op.SetConfig(cfg)
|
||||||
op.opVim = newVimMode(op)
|
op.opVim = newVimMode(op)
|
||||||
op.opCompleter = newOpCompleter(op.buf.w, op)
|
op.opCompleter = newOpCompleter(op.buf.w, op, width)
|
||||||
op.opPassword = newOpPassword(op)
|
op.opPassword = newOpPassword(op)
|
||||||
|
op.cfg.FuncOnWidthChanged(func() {
|
||||||
|
newWidth := cfg.FuncGetWidth()
|
||||||
|
op.opCompleter.OnWidthChange(newWidth)
|
||||||
|
op.opSearch.OnWidthChange(newWidth)
|
||||||
|
op.buf.OnWidthChange(newWidth)
|
||||||
|
})
|
||||||
go op.ioloop()
|
go op.ioloop()
|
||||||
return op
|
return op
|
||||||
}
|
}
|
||||||
|
@ -381,11 +388,12 @@ func (op *Operation) SetConfig(cfg *Config) (*Config, error) {
|
||||||
op.SetPrompt(cfg.Prompt)
|
op.SetPrompt(cfg.Prompt)
|
||||||
op.SetMaskRune(cfg.MaskRune)
|
op.SetMaskRune(cfg.MaskRune)
|
||||||
op.buf.SetConfig(cfg)
|
op.buf.SetConfig(cfg)
|
||||||
|
width := op.cfg.FuncGetWidth()
|
||||||
|
|
||||||
if cfg.opHistory == nil {
|
if cfg.opHistory == nil {
|
||||||
op.SetHistoryPath(cfg.HistoryFile)
|
op.SetHistoryPath(cfg.HistoryFile)
|
||||||
cfg.opHistory = op.history
|
cfg.opHistory = op.history
|
||||||
cfg.opSearch = newOpSearch(op.buf.w, op.buf, op.history, cfg)
|
cfg.opSearch = newOpSearch(op.buf.w, op.buf, op.history, cfg, width)
|
||||||
}
|
}
|
||||||
op.history = cfg.opHistory
|
op.history = cfg.opHistory
|
||||||
|
|
||||||
|
@ -394,7 +402,7 @@ func (op *Operation) SetConfig(cfg *Config) (*Config, error) {
|
||||||
op.history.Init()
|
op.history.Init()
|
||||||
|
|
||||||
if op.cfg.AutoComplete != nil {
|
if op.cfg.AutoComplete != nil {
|
||||||
op.opCompleter = newOpCompleter(op.buf.w, op)
|
op.opCompleter = newOpCompleter(op.buf.w, op, width)
|
||||||
}
|
}
|
||||||
|
|
||||||
op.opSearch = cfg.opSearch
|
op.opSearch = cfg.opSearch
|
||||||
|
|
29
readline.go
29
readline.go
|
@ -45,8 +45,10 @@ type Config struct {
|
||||||
UniqueEditLine bool
|
UniqueEditLine bool
|
||||||
|
|
||||||
// force use interactive even stdout is not a tty
|
// force use interactive even stdout is not a tty
|
||||||
StdinFd int
|
FuncIsTerminal func() bool
|
||||||
StdoutFd int
|
FuncMakeRaw func() error
|
||||||
|
FuncExitRaw func() error
|
||||||
|
FuncOnWidthChanged func(func())
|
||||||
ForceUseInteractive bool
|
ForceUseInteractive bool
|
||||||
|
|
||||||
// private fields
|
// private fields
|
||||||
|
@ -59,7 +61,7 @@ func (c *Config) useInteractive() bool {
|
||||||
if c.ForceUseInteractive {
|
if c.ForceUseInteractive {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return IsTerminal(c.StdoutFd) && IsTerminal(c.StdinFd)
|
return c.FuncIsTerminal()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) Init() error {
|
func (c *Config) Init() error {
|
||||||
|
@ -76,12 +78,6 @@ func (c *Config) Init() error {
|
||||||
if c.Stderr == nil {
|
if c.Stderr == nil {
|
||||||
c.Stderr = Stderr
|
c.Stderr = Stderr
|
||||||
}
|
}
|
||||||
if c.StdinFd == 0 {
|
|
||||||
c.StdinFd = StdinFd
|
|
||||||
}
|
|
||||||
if c.StdoutFd == 0 {
|
|
||||||
c.StdoutFd = StdoutFd
|
|
||||||
}
|
|
||||||
if c.HistoryLimit == 0 {
|
if c.HistoryLimit == 0 {
|
||||||
c.HistoryLimit = 500
|
c.HistoryLimit = 500
|
||||||
}
|
}
|
||||||
|
@ -98,7 +94,20 @@ func (c *Config) Init() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.FuncGetWidth == nil {
|
if c.FuncGetWidth == nil {
|
||||||
c.FuncGetWidth = genGetWidthFunc(c.StdoutFd)
|
c.FuncGetWidth = GetScreenWidth
|
||||||
|
}
|
||||||
|
if c.FuncIsTerminal == nil {
|
||||||
|
c.FuncIsTerminal = DefaultIsTerminal
|
||||||
|
}
|
||||||
|
rm := new(RawMode)
|
||||||
|
if c.FuncMakeRaw == nil {
|
||||||
|
c.FuncMakeRaw = rm.Enter
|
||||||
|
}
|
||||||
|
if c.FuncExitRaw == nil {
|
||||||
|
c.FuncExitRaw = rm.Exit
|
||||||
|
}
|
||||||
|
if c.FuncOnWidthChanged == nil {
|
||||||
|
c.FuncOnWidthChanged = DefaultOnWidthChanged
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
426
remote.go
426
remote.go
|
@ -1,49 +1,409 @@
|
||||||
package readline
|
package readline
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Conn struct {
|
type MsgType int16
|
||||||
Conn net.Conn
|
|
||||||
Terminal *Terminal
|
const (
|
||||||
runChan chan error
|
T_DATA = MsgType(iota)
|
||||||
|
T_WIDTH
|
||||||
|
T_WIDTH_REPORT
|
||||||
|
T_ISTTY_REPORT
|
||||||
|
T_RAW
|
||||||
|
T_ERAW // exit raw
|
||||||
|
)
|
||||||
|
|
||||||
|
type RemoteSvr struct {
|
||||||
|
closed int32
|
||||||
|
width int32
|
||||||
|
reciveChan chan struct{}
|
||||||
|
writeChan chan *writeCtx
|
||||||
|
conn net.Conn
|
||||||
|
isTerminal bool
|
||||||
|
funcWidthChan func()
|
||||||
|
|
||||||
|
dataBufM sync.Mutex
|
||||||
|
dataBuf bytes.Buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConn(conn net.Conn, t *Terminal) (*Conn, error) {
|
type writeReply struct {
|
||||||
return &Conn{
|
n int
|
||||||
Conn: conn,
|
err error
|
||||||
Terminal: t,
|
|
||||||
runChan: make(chan error),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) Run() error {
|
type writeCtx struct {
|
||||||
c.Terminal.EnterRawMode()
|
msg *Message
|
||||||
go func() {
|
reply chan *writeReply
|
||||||
_, err := io.Copy(c.Conn, os.Stdin)
|
}
|
||||||
c.runChan <- err
|
|
||||||
}()
|
func newWriteCtx(msg *Message) *writeCtx {
|
||||||
go func() {
|
return &writeCtx{
|
||||||
_, err := io.Copy(os.Stdout, c.Conn)
|
msg: msg,
|
||||||
c.runChan <- err
|
reply: make(chan *writeReply),
|
||||||
}()
|
}
|
||||||
err := <-c.runChan
|
}
|
||||||
c.Terminal.ExitRawMode()
|
|
||||||
|
func NewRemoteSvr(conn net.Conn) (*RemoteSvr, error) {
|
||||||
|
rs := &RemoteSvr{
|
||||||
|
width: -1,
|
||||||
|
conn: conn,
|
||||||
|
writeChan: make(chan *writeCtx),
|
||||||
|
reciveChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
buf := bufio.NewReader(rs.conn)
|
||||||
|
|
||||||
|
if err := rs.init(buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go rs.readLoop(buf)
|
||||||
|
go rs.writeLoop()
|
||||||
|
return rs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemoteSvr) init(buf *bufio.Reader) error {
|
||||||
|
m, err := ReadMessage(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// receive isTerminal
|
||||||
|
if m.Type != T_ISTTY_REPORT {
|
||||||
|
return fmt.Errorf("unexpected init message")
|
||||||
|
}
|
||||||
|
r.GotIsTerminal(m.Data)
|
||||||
|
|
||||||
|
// receive width
|
||||||
|
m, err = ReadMessage(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if m.Type != T_WIDTH_REPORT {
|
||||||
|
return fmt.Errorf("unexpected init message")
|
||||||
|
}
|
||||||
|
r.GotReportWidth(m.Data)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemoteSvr) HandleConfig(cfg *Config) {
|
||||||
|
cfg.Stderr = r
|
||||||
|
cfg.Stdout = r
|
||||||
|
cfg.Stdin = r
|
||||||
|
cfg.FuncExitRaw = r.ExitRawMode
|
||||||
|
cfg.FuncIsTerminal = r.IsTerminal
|
||||||
|
cfg.FuncMakeRaw = r.EnterRawMode
|
||||||
|
cfg.FuncExitRaw = r.ExitRawMode
|
||||||
|
cfg.FuncGetWidth = r.GetWidth
|
||||||
|
cfg.FuncOnWidthChanged = func(f func()) {
|
||||||
|
r.funcWidthChan = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemoteSvr) IsTerminal() bool {
|
||||||
|
return r.isTerminal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemoteSvr) Read(b []byte) (int, error) {
|
||||||
|
r.dataBufM.Lock()
|
||||||
|
n, err := r.dataBuf.Read(b)
|
||||||
|
r.dataBufM.Unlock()
|
||||||
|
if n == 0 && err == io.EOF {
|
||||||
|
<-r.reciveChan
|
||||||
|
r.dataBufM.Lock()
|
||||||
|
n, err = r.dataBuf.Read(b)
|
||||||
|
r.dataBufM.Unlock()
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemoteSvr) writeMsg(m *Message) error {
|
||||||
|
ctx := newWriteCtx(m)
|
||||||
|
r.writeChan <- ctx
|
||||||
|
reply := <-ctx.reply
|
||||||
|
return reply.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemoteSvr) Write(b []byte) (int, error) {
|
||||||
|
ctx := newWriteCtx(NewMessage(T_DATA, b))
|
||||||
|
r.writeChan <- ctx
|
||||||
|
reply := <-ctx.reply
|
||||||
|
return reply.n, reply.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemoteSvr) EnterRawMode() error {
|
||||||
|
return r.writeMsg(NewMessage(T_RAW, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemoteSvr) ExitRawMode() error {
|
||||||
|
return r.writeMsg(NewMessage(T_ERAW, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemoteSvr) writeLoop() {
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
ctx, ok := <-r.writeChan
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
n, err := ctx.msg.WriteTo(r.conn)
|
||||||
|
ctx.reply <- &writeReply{n, err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemoteSvr) Close() {
|
||||||
|
if atomic.CompareAndSwapInt32(&r.closed, 0, 1) {
|
||||||
|
close(r.writeChan)
|
||||||
|
r.conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemoteSvr) readLoop(buf *bufio.Reader) {
|
||||||
|
defer r.Close()
|
||||||
|
for {
|
||||||
|
m, err := ReadMessage(buf)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch m.Type {
|
||||||
|
case T_DATA:
|
||||||
|
r.dataBufM.Lock()
|
||||||
|
r.dataBuf.Write(m.Data)
|
||||||
|
r.dataBufM.Unlock()
|
||||||
|
select {
|
||||||
|
case r.reciveChan <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
case T_WIDTH_REPORT:
|
||||||
|
r.GotReportWidth(m.Data)
|
||||||
|
case T_ISTTY_REPORT:
|
||||||
|
r.GotIsTerminal(m.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemoteSvr) GotIsTerminal(data []byte) {
|
||||||
|
if binary.BigEndian.Uint16(data) == 0 {
|
||||||
|
r.isTerminal = false
|
||||||
|
} else {
|
||||||
|
r.isTerminal = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemoteSvr) GotReportWidth(data []byte) {
|
||||||
|
atomic.StoreInt32(&r.width, int32(binary.BigEndian.Uint16(data)))
|
||||||
|
if r.funcWidthChan != nil {
|
||||||
|
r.funcWidthChan()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemoteSvr) GetWidth() int {
|
||||||
|
return int(atomic.LoadInt32(&r.width))
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
Type MsgType
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadMessage(r io.Reader) (*Message, error) {
|
||||||
|
m := new(Message)
|
||||||
|
var length int32
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &length); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &m.Type); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m.Data = make([]byte, int(length)-2)
|
||||||
|
if _, err := io.ReadFull(r, m.Data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMessage(t MsgType, data []byte) *Message {
|
||||||
|
return &Message{t, data}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) WriteTo(w io.Writer) (int, error) {
|
||||||
|
buf := bytes.NewBuffer(make([]byte, 0, len(m.Data)+2+4))
|
||||||
|
binary.Write(buf, binary.BigEndian, int32(len(m.Data)+2))
|
||||||
|
binary.Write(buf, binary.BigEndian, m.Type)
|
||||||
|
buf.Write(m.Data)
|
||||||
|
n, err := buf.WriteTo(w)
|
||||||
|
return int(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type RemoteCli struct {
|
||||||
|
conn net.Conn
|
||||||
|
raw RawMode
|
||||||
|
receiveChan chan struct{}
|
||||||
|
|
||||||
|
data bytes.Buffer
|
||||||
|
dataM sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRemoteCli(conn net.Conn) (*RemoteCli, error) {
|
||||||
|
r := &RemoteCli{
|
||||||
|
conn: conn,
|
||||||
|
receiveChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
if err := r.init(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemoteCli) init() error {
|
||||||
|
if err := r.reportIsTerminal(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.reportWidth(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// register sig for width changed
|
||||||
|
DefaultOnWidthChanged(func() {
|
||||||
|
r.reportWidth()
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemoteCli) writeMsg(m *Message) error {
|
||||||
|
r.dataM.Lock()
|
||||||
|
_, err := m.WriteTo(r.conn)
|
||||||
|
r.dataM.Unlock()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func Dial(network string, address string) (*Conn, error) {
|
func (r *RemoteCli) Write(b []byte) (int, error) {
|
||||||
conn, err := net.Dial(network, address)
|
m := NewMessage(T_DATA, b)
|
||||||
if err != nil {
|
r.dataM.Lock()
|
||||||
return nil, err
|
n, err := m.WriteTo(r.conn)
|
||||||
}
|
r.dataM.Unlock()
|
||||||
var cfg Config
|
return n, err
|
||||||
t, err := NewTerminal(&cfg)
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
func (r *RemoteCli) reportWidth() error {
|
||||||
}
|
screenWidth := GetScreenWidth()
|
||||||
return NewConn(conn, t)
|
data := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(data, uint16(screenWidth))
|
||||||
|
msg := NewMessage(T_WIDTH_REPORT, data)
|
||||||
|
|
||||||
|
if err := r.writeMsg(msg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemoteCli) reportIsTerminal() error {
|
||||||
|
isTerminal := DefaultIsTerminal()
|
||||||
|
data := make([]byte, 2)
|
||||||
|
if isTerminal {
|
||||||
|
binary.BigEndian.PutUint16(data, 1)
|
||||||
|
} else {
|
||||||
|
binary.BigEndian.PutUint16(data, 0)
|
||||||
|
}
|
||||||
|
msg := NewMessage(T_ISTTY_REPORT, data)
|
||||||
|
if err := r.writeMsg(msg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemoteCli) readLoop() {
|
||||||
|
buf := bufio.NewReader(r.conn)
|
||||||
|
for {
|
||||||
|
msg, err := ReadMessage(buf)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch msg.Type {
|
||||||
|
case T_ERAW:
|
||||||
|
r.raw.Exit()
|
||||||
|
case T_RAW:
|
||||||
|
r.raw.Enter()
|
||||||
|
case T_DATA:
|
||||||
|
os.Stdout.Write(msg.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemoteCli) Serve() error {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
n, _ := io.Copy(r, os.Stdin)
|
||||||
|
if n == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
r.readLoop()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListenRemote(n, addr string, cfg *Config, h func(*Instance)) error {
|
||||||
|
ln, err := net.Listen(n, addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
conn, err := ln.Accept()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
defer conn.Close()
|
||||||
|
rl, err := HandleConn(*cfg, conn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h(rl)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleConn(cfg Config, conn net.Conn) (*Instance, error) {
|
||||||
|
r, err := NewRemoteSvr(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.HandleConfig(&cfg)
|
||||||
|
|
||||||
|
rl, err := NewEx(&cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DialRemote(n, addr string) error {
|
||||||
|
conn, err := net.Dial(n, addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
cli, err := NewRemoteCli(conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return cli.Serve()
|
||||||
}
|
}
|
||||||
|
|
68
runebuf.go
68
runebuf.go
|
@ -1,6 +1,7 @@
|
||||||
package readline
|
package readline
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -24,9 +25,25 @@ type RuneBuffer struct {
|
||||||
interactive bool
|
interactive bool
|
||||||
cfg *Config
|
cfg *Config
|
||||||
|
|
||||||
|
width int
|
||||||
|
|
||||||
bck *runeBufferBck
|
bck *runeBufferBck
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RuneBuffer) OnWidthChange(newWidth int) {
|
||||||
|
oldWidth := r.width
|
||||||
|
if newWidth < oldWidth {
|
||||||
|
sp := SplitByMultiLine(
|
||||||
|
r.PromptLen(), oldWidth, newWidth, r.buf[:r.idx])
|
||||||
|
idxLine := len(sp) - 1
|
||||||
|
r.clean(idxLine)
|
||||||
|
} else {
|
||||||
|
r.Clean()
|
||||||
|
}
|
||||||
|
r.width = newWidth
|
||||||
|
r.print()
|
||||||
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) Backup() {
|
func (r *RuneBuffer) Backup() {
|
||||||
r.bck = &runeBufferBck{r.buf, r.idx}
|
r.bck = &runeBufferBck{r.buf, r.idx}
|
||||||
}
|
}
|
||||||
|
@ -41,11 +58,12 @@ func (r *RuneBuffer) Restore() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRuneBuffer(w io.Writer, prompt string, cfg *Config) *RuneBuffer {
|
func NewRuneBuffer(w io.Writer, prompt string, cfg *Config, width int) *RuneBuffer {
|
||||||
rb := &RuneBuffer{
|
rb := &RuneBuffer{
|
||||||
w: w,
|
w: w,
|
||||||
interactive: cfg.useInteractive(),
|
interactive: cfg.useInteractive(),
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
|
width: width,
|
||||||
}
|
}
|
||||||
rb.SetPrompt(prompt)
|
rb.SetPrompt(prompt)
|
||||||
return rb
|
return rb
|
||||||
|
@ -293,8 +311,11 @@ func (r *RuneBuffer) MoveToLineEnd() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) LineCount() int {
|
func (r *RuneBuffer) LineCount(width int) int {
|
||||||
return LineCount(r.cfg.FuncGetWidth(),
|
if width == -1 {
|
||||||
|
width = r.width
|
||||||
|
}
|
||||||
|
return LineCount(width,
|
||||||
runes.WidthAll(r.buf)+r.PromptLen())
|
runes.WidthAll(r.buf)+r.PromptLen())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,14 +348,13 @@ func (r *RuneBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) IdxLine() int {
|
func (r *RuneBuffer) IdxLine(width int) int {
|
||||||
sw := r.cfg.FuncGetWidth()
|
sp := SplitByLine(r.PromptLen(), width, r.buf[:r.idx])
|
||||||
sp := SplitByLine(r.PromptLen(), sw, r.buf[:r.idx])
|
|
||||||
return len(sp) - 1
|
return len(sp) - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) CursorLineCount() int {
|
func (r *RuneBuffer) CursorLineCount() int {
|
||||||
return r.LineCount() - r.IdxLine()
|
return r.LineCount(r.width) - r.IdxLine(r.width)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) Refresh(f func()) {
|
func (r *RuneBuffer) Refresh(f func()) {
|
||||||
|
@ -348,6 +368,10 @@ func (r *RuneBuffer) Refresh(f func()) {
|
||||||
if f != nil {
|
if f != nil {
|
||||||
f()
|
f()
|
||||||
}
|
}
|
||||||
|
r.print()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RuneBuffer) print() {
|
||||||
r.w.Write(r.output())
|
r.w.Write(r.output())
|
||||||
r.cleanInScreen = false
|
r.cleanInScreen = false
|
||||||
}
|
}
|
||||||
|
@ -367,8 +391,7 @@ func (r *RuneBuffer) output() []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
sw := r.cfg.FuncGetWidth()
|
sp := SplitByLine(r.PromptLen(), r.width, r.buf)
|
||||||
sp := SplitByLine(r.PromptLen(), sw, r.buf)
|
|
||||||
written := 0
|
written := 0
|
||||||
idxInLine := 0
|
idxInLine := 0
|
||||||
for idx, s := range sp {
|
for idx, s := range sp {
|
||||||
|
@ -383,7 +406,7 @@ func (r *RuneBuffer) output() []byte {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(r.buf) > r.idx {
|
if len(r.buf) > r.idx {
|
||||||
targetLine := r.IdxLine()
|
targetLine := r.IdxLine(r.width)
|
||||||
currentLine := len(sp) - 1
|
currentLine := len(sp) - 1
|
||||||
// assert currentLine >= targetLine
|
// assert currentLine >= targetLine
|
||||||
if targetLine == 0 {
|
if targetLine == 0 {
|
||||||
|
@ -452,27 +475,30 @@ func (r *RuneBuffer) SetPrompt(prompt string) {
|
||||||
r.prompt = []rune(prompt)
|
r.prompt = []rune(prompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) cleanOutput() []byte {
|
func (r *RuneBuffer) cleanOutput(w io.Writer, idxLine int) {
|
||||||
buf := bytes.NewBuffer(nil)
|
buf := bufio.NewWriter(w)
|
||||||
buf.Write([]byte("\033[J")) // just like ^k :)
|
buf.Write([]byte("\033[J")) // just like ^k :)
|
||||||
|
|
||||||
idxLine := r.IdxLine()
|
|
||||||
|
|
||||||
if idxLine == 0 {
|
if idxLine == 0 {
|
||||||
buf.WriteString("\033[2K\r")
|
io.WriteString(buf, "\033[2K\r")
|
||||||
return buf.Bytes()
|
} else {
|
||||||
}
|
|
||||||
for i := 0; i < idxLine; i++ {
|
for i := 0; i < idxLine; i++ {
|
||||||
buf.WriteString("\033[2K\r\033[A")
|
io.WriteString(buf, "\033[2K\r\033[A")
|
||||||
}
|
}
|
||||||
buf.WriteString("\033[2K\r")
|
io.WriteString(buf, "\033[2K\r")
|
||||||
return buf.Bytes()
|
}
|
||||||
|
buf.Flush()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) Clean() {
|
func (r *RuneBuffer) Clean() {
|
||||||
|
r.clean(r.IdxLine(r.width))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RuneBuffer) clean(idxLine int) {
|
||||||
if r.cleanInScreen || !r.interactive {
|
if r.cleanInScreen || !r.interactive {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r.cleanInScreen = true
|
r.cleanInScreen = true
|
||||||
r.w.Write(r.cleanOutput())
|
r.cleanOutput(r.w, idxLine)
|
||||||
}
|
}
|
||||||
|
|
10
search.go
10
search.go
|
@ -29,17 +29,23 @@ type opSearch struct {
|
||||||
cfg *Config
|
cfg *Config
|
||||||
markStart int
|
markStart int
|
||||||
markEnd int
|
markEnd int
|
||||||
|
width int
|
||||||
}
|
}
|
||||||
|
|
||||||
func newOpSearch(w io.Writer, buf *RuneBuffer, history *opHistory, cfg *Config) *opSearch {
|
func newOpSearch(w io.Writer, buf *RuneBuffer, history *opHistory, cfg *Config, width int) *opSearch {
|
||||||
return &opSearch{
|
return &opSearch{
|
||||||
w: w,
|
w: w,
|
||||||
buf: buf,
|
buf: buf,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
history: history,
|
history: history,
|
||||||
|
width: width,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *opSearch) OnWidthChange(newWidth int) {
|
||||||
|
o.width = newWidth
|
||||||
|
}
|
||||||
|
|
||||||
func (o *opSearch) IsSearchMode() bool {
|
func (o *opSearch) IsSearchMode() bool {
|
||||||
return o.inMode
|
return o.inMode
|
||||||
}
|
}
|
||||||
|
@ -125,7 +131,7 @@ func (o *opSearch) SearchRefresh(x int) {
|
||||||
}
|
}
|
||||||
x = o.buf.CurrentWidth(x)
|
x = o.buf.CurrentWidth(x)
|
||||||
x += o.buf.PromptLen()
|
x += o.buf.PromptLen()
|
||||||
x = x % o.cfg.FuncGetWidth()
|
x = x % o.width
|
||||||
|
|
||||||
if o.markStart > 0 {
|
if o.markStart > 0 {
|
||||||
o.buf.SetStyle(o.markStart, o.markEnd, "4")
|
o.buf.SetStyle(o.markStart, o.markEnd, "4")
|
||||||
|
|
15
terminal.go
15
terminal.go
|
@ -5,13 +5,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Terminal struct {
|
type Terminal struct {
|
||||||
cfg *Config
|
cfg *Config
|
||||||
state *terminal.State
|
|
||||||
outchan chan rune
|
outchan chan rune
|
||||||
closed int32
|
closed int32
|
||||||
stopChan chan struct{}
|
stopChan chan struct{}
|
||||||
|
@ -36,19 +33,11 @@ func NewTerminal(cfg *Config) (*Terminal, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) EnterRawMode() (err error) {
|
func (t *Terminal) EnterRawMode() (err error) {
|
||||||
t.state, err = MakeRaw(int(t.cfg.StdinFd))
|
return t.cfg.FuncMakeRaw()
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) ExitRawMode() (err error) {
|
func (t *Terminal) ExitRawMode() (err error) {
|
||||||
if t.state == nil {
|
return t.cfg.FuncExitRaw()
|
||||||
return
|
|
||||||
}
|
|
||||||
err = Restore(int(t.cfg.StdinFd), t.state)
|
|
||||||
if err == nil {
|
|
||||||
t.state = nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) Write(b []byte) (int, error) {
|
func (t *Terminal) Write(b []byte) (int, error) {
|
||||||
|
|
44
utils.go
44
utils.go
|
@ -4,7 +4,6 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/chzyer/readline/runes"
|
"github.com/chzyer/readline/runes"
|
||||||
|
|
||||||
|
@ -12,8 +11,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
StdinFd = int(uintptr(syscall.Stdin))
|
|
||||||
StdoutFd = int(uintptr(syscall.Stdout))
|
|
||||||
isWindows = false
|
isWindows = false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -89,6 +86,29 @@ func escapeKey(r rune) rune {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SplitByMultiLine(start, oldWidth, newWidth int, rs []rune) []string {
|
||||||
|
var ret []string
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
currentWidth := start
|
||||||
|
for _, r := range rs {
|
||||||
|
w := runes.Width(r)
|
||||||
|
currentWidth += w
|
||||||
|
buf.WriteRune(r)
|
||||||
|
if currentWidth == newWidth {
|
||||||
|
ret = append(ret, buf.String())
|
||||||
|
buf.Reset()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if currentWidth >= oldWidth {
|
||||||
|
ret = append(ret, buf.String())
|
||||||
|
buf.Reset()
|
||||||
|
currentWidth = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret = append(ret, buf.String())
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
func SplitByLine(start, screenWidth int, rs []rune) []string {
|
func SplitByLine(start, screenWidth int, rs []rune) []string {
|
||||||
var ret []string
|
var ret []string
|
||||||
buf := bytes.NewBuffer(nil)
|
buf := bytes.NewBuffer(nil)
|
||||||
|
@ -137,8 +157,18 @@ func GetInt(s []string, def int) int {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func genGetWidthFunc(fd int) func() int {
|
type RawMode struct {
|
||||||
return func() int {
|
state *terminal.State
|
||||||
return getWidth(fd)
|
}
|
||||||
}
|
|
||||||
|
func (r *RawMode) Enter() (err error) {
|
||||||
|
r.state, err = MakeRaw(GetStdin())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RawMode) Exit() error {
|
||||||
|
if r.state == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return Restore(GetStdin(), r.state)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,15 @@
|
||||||
package readline
|
package readline
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSplitByMultiLine(t *testing.T) {
|
||||||
|
rs := []rune("hello!bye!!!!")
|
||||||
|
expected := []string{"hell", "o!", "bye!", "!!", "!"}
|
||||||
|
ret := SplitByMultiLine(0, 6, 4, rs)
|
||||||
|
if !reflect.DeepEqual(ret, expected) {
|
||||||
|
t.Fatal(ret, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
package readline
|
package readline
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
@ -28,3 +31,40 @@ func getWidth(stdoutFd int) int {
|
||||||
}
|
}
|
||||||
return int(ws.Col)
|
return int(ws.Col)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetScreenWidth() int {
|
||||||
|
return getWidth(syscall.Stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultIsTerminal() bool {
|
||||||
|
return IsTerminal(syscall.Stdin) && IsTerminal(syscall.Stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetStdin() int {
|
||||||
|
return syscall.Stdin
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var (
|
||||||
|
widthChange sync.Once
|
||||||
|
widthChangeCallback func()
|
||||||
|
)
|
||||||
|
|
||||||
|
func DefaultOnWidthChanged(f func()) {
|
||||||
|
widthChangeCallback = f
|
||||||
|
widthChange.Do(func() {
|
||||||
|
ch := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(ch, syscall.SIGWINCH)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
_, ok := <-ch
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
widthChangeCallback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -2,15 +2,29 @@
|
||||||
|
|
||||||
package readline
|
package readline
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
func GetStdin() int {
|
||||||
|
return int(syscall.Stdin)
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
isWindows = true
|
isWindows = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// get width of the terminal
|
// get width of the terminal
|
||||||
func getWidth(fd int) int {
|
func GetScreenWidth() int {
|
||||||
info, _ := GetConsoleScreenBufferInfo()
|
info, _ := GetConsoleScreenBufferInfo()
|
||||||
if info == nil {
|
if info == nil {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
return int(info.dwSize.x)
|
return int(info.dwSize.x)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DefaultIsTerminal() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultOnWidthChanged(func()) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue