add remote mode

This commit is contained in:
chzyer 2016-03-13 18:32:48 +08:00
parent 14e9df7f3c
commit b57eccfd02
13 changed files with 637 additions and 100 deletions

View File

@ -22,6 +22,7 @@ type opCompleter struct {
w io.Writer
op *Operation
ac AutoCompleter
width int
inCompleteMode bool
inSelectMode bool
@ -32,11 +33,12 @@ type opCompleter struct {
candidateColNum int
}
func newOpCompleter(w io.Writer, op *Operation) *opCompleter {
func newOpCompleter(w io.Writer, op *Operation, width int) *opCompleter {
return &opCompleter{
w: w,
op: op,
ac: op.cfg.AutoComplete,
width: width,
}
}
@ -171,6 +173,10 @@ func (o *opCompleter) getMatrixSize() int {
return line * o.candidateColNum
}
func (o *opCompleter) OnWidthChange(newWidth int) {
o.width = newWidth
}
func (o *opCompleter) CompleteRefresh() {
if !o.inCompleteMode {
return
@ -183,7 +189,7 @@ func (o *opCompleter) CompleteRefresh() {
colWidth = w
}
}
colNum := o.op.cfg.FuncGetWidth() / (colWidth + o.candidateOff + 2)
colNum := o.width / (colWidth + o.candidateOff + 2)
o.candidateColNum = colNum
buf := bytes.NewBuffer(nil)
buf.Write(bytes.Repeat([]byte("\n"), lineCnt))

View File

@ -0,0 +1,9 @@
package main
import "github.com/chzyer/readline"
func main() {
if err := readline.DialRemote("tcp", ":12344"); err != nil {
println(err.Error())
}
}

View File

@ -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())
}
}

View File

@ -53,17 +53,24 @@ func (w *wrapWriter) Write(b []byte) (int, error) {
}
func NewOperation(t *Terminal, cfg *Config) *Operation {
width := cfg.FuncGetWidth()
op := &Operation{
t: t,
buf: NewRuneBuffer(t, cfg.Prompt, cfg),
buf: NewRuneBuffer(t, cfg.Prompt, cfg, width),
outchan: make(chan []rune),
errchan: make(chan error),
}
op.w = op.buf.w
op.SetConfig(cfg)
op.opVim = newVimMode(op)
op.opCompleter = newOpCompleter(op.buf.w, op)
op.opCompleter = newOpCompleter(op.buf.w, op, width)
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()
return op
}
@ -381,11 +388,12 @@ func (op *Operation) SetConfig(cfg *Config) (*Config, error) {
op.SetPrompt(cfg.Prompt)
op.SetMaskRune(cfg.MaskRune)
op.buf.SetConfig(cfg)
width := op.cfg.FuncGetWidth()
if cfg.opHistory == nil {
op.SetHistoryPath(cfg.HistoryFile)
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
@ -394,7 +402,7 @@ func (op *Operation) SetConfig(cfg *Config) (*Config, error) {
op.history.Init()
if op.cfg.AutoComplete != nil {
op.opCompleter = newOpCompleter(op.buf.w, op)
op.opCompleter = newOpCompleter(op.buf.w, op, width)
}
op.opSearch = cfg.opSearch

View File

@ -45,8 +45,10 @@ type Config struct {
UniqueEditLine bool
// force use interactive even stdout is not a tty
StdinFd int
StdoutFd int
FuncIsTerminal func() bool
FuncMakeRaw func() error
FuncExitRaw func() error
FuncOnWidthChanged func(func())
ForceUseInteractive bool
// private fields
@ -59,7 +61,7 @@ func (c *Config) useInteractive() bool {
if c.ForceUseInteractive {
return true
}
return IsTerminal(c.StdoutFd) && IsTerminal(c.StdinFd)
return c.FuncIsTerminal()
}
func (c *Config) Init() error {
@ -76,12 +78,6 @@ func (c *Config) Init() error {
if c.Stderr == nil {
c.Stderr = Stderr
}
if c.StdinFd == 0 {
c.StdinFd = StdinFd
}
if c.StdoutFd == 0 {
c.StdoutFd = StdoutFd
}
if c.HistoryLimit == 0 {
c.HistoryLimit = 500
}
@ -98,7 +94,20 @@ func (c *Config) Init() error {
}
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

426
remote.go
View File

@ -1,49 +1,409 @@
package readline
import (
"bufio"
"bytes"
"encoding/binary"
"fmt"
"io"
"net"
"os"
"sync"
"sync/atomic"
)
type Conn struct {
Conn net.Conn
Terminal *Terminal
runChan chan error
type MsgType int16
const (
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) {
return &Conn{
Conn: conn,
Terminal: t,
runChan: make(chan error),
}, nil
type writeReply struct {
n int
err error
}
func (c *Conn) Run() error {
c.Terminal.EnterRawMode()
go func() {
_, err := io.Copy(c.Conn, os.Stdin)
c.runChan <- err
}()
go func() {
_, err := io.Copy(os.Stdout, c.Conn)
c.runChan <- err
}()
err := <-c.runChan
c.Terminal.ExitRawMode()
type writeCtx struct {
msg *Message
reply chan *writeReply
}
func newWriteCtx(msg *Message) *writeCtx {
return &writeCtx{
msg: msg,
reply: make(chan *writeReply),
}
}
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
}
func Dial(network string, address string) (*Conn, error) {
conn, err := net.Dial(network, address)
if err != nil {
return nil, err
}
var cfg Config
t, err := NewTerminal(&cfg)
if err != nil {
return nil, err
}
return NewConn(conn, t)
func (r *RemoteCli) Write(b []byte) (int, error) {
m := NewMessage(T_DATA, b)
r.dataM.Lock()
n, err := m.WriteTo(r.conn)
r.dataM.Unlock()
return n, err
}
func (r *RemoteCli) reportWidth() error {
screenWidth := GetScreenWidth()
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()
}

View File

@ -1,6 +1,7 @@
package readline
import (
"bufio"
"bytes"
"fmt"
"io"
@ -24,9 +25,25 @@ type RuneBuffer struct {
interactive bool
cfg *Config
width int
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() {
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{
w: w,
interactive: cfg.useInteractive(),
cfg: cfg,
width: width,
}
rb.SetPrompt(prompt)
return rb
@ -293,8 +311,11 @@ func (r *RuneBuffer) MoveToLineEnd() {
})
}
func (r *RuneBuffer) LineCount() int {
return LineCount(r.cfg.FuncGetWidth(),
func (r *RuneBuffer) LineCount(width int) int {
if width == -1 {
width = r.width
}
return LineCount(width,
runes.WidthAll(r.buf)+r.PromptLen())
}
@ -327,14 +348,13 @@ func (r *RuneBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) {
return
}
func (r *RuneBuffer) IdxLine() int {
sw := r.cfg.FuncGetWidth()
sp := SplitByLine(r.PromptLen(), sw, r.buf[:r.idx])
func (r *RuneBuffer) IdxLine(width int) int {
sp := SplitByLine(r.PromptLen(), width, r.buf[:r.idx])
return len(sp) - 1
}
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()) {
@ -348,6 +368,10 @@ func (r *RuneBuffer) Refresh(f func()) {
if f != nil {
f()
}
r.print()
}
func (r *RuneBuffer) print() {
r.w.Write(r.output())
r.cleanInScreen = false
}
@ -367,8 +391,7 @@ func (r *RuneBuffer) output() []byte {
}
} else {
sw := r.cfg.FuncGetWidth()
sp := SplitByLine(r.PromptLen(), sw, r.buf)
sp := SplitByLine(r.PromptLen(), r.width, r.buf)
written := 0
idxInLine := 0
for idx, s := range sp {
@ -383,7 +406,7 @@ func (r *RuneBuffer) output() []byte {
}
}
if len(r.buf) > r.idx {
targetLine := r.IdxLine()
targetLine := r.IdxLine(r.width)
currentLine := len(sp) - 1
// assert currentLine >= targetLine
if targetLine == 0 {
@ -452,27 +475,30 @@ func (r *RuneBuffer) SetPrompt(prompt string) {
r.prompt = []rune(prompt)
}
func (r *RuneBuffer) cleanOutput() []byte {
buf := bytes.NewBuffer(nil)
func (r *RuneBuffer) cleanOutput(w io.Writer, idxLine int) {
buf := bufio.NewWriter(w)
buf.Write([]byte("\033[J")) // just like ^k :)
idxLine := r.IdxLine()
if idxLine == 0 {
buf.WriteString("\033[2K\r")
return buf.Bytes()
}
io.WriteString(buf, "\033[2K\r")
} else {
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")
return buf.Bytes()
io.WriteString(buf, "\033[2K\r")
}
buf.Flush()
return
}
func (r *RuneBuffer) Clean() {
r.clean(r.IdxLine(r.width))
}
func (r *RuneBuffer) clean(idxLine int) {
if r.cleanInScreen || !r.interactive {
return
}
r.cleanInScreen = true
r.w.Write(r.cleanOutput())
r.cleanOutput(r.w, idxLine)
}

View File

@ -29,17 +29,23 @@ type opSearch struct {
cfg *Config
markStart 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{
w: w,
buf: buf,
cfg: cfg,
history: history,
width: width,
}
}
func (o *opSearch) OnWidthChange(newWidth int) {
o.width = newWidth
}
func (o *opSearch) IsSearchMode() bool {
return o.inMode
}
@ -125,7 +131,7 @@ func (o *opSearch) SearchRefresh(x int) {
}
x = o.buf.CurrentWidth(x)
x += o.buf.PromptLen()
x = x % o.cfg.FuncGetWidth()
x = x % o.width
if o.markStart > 0 {
o.buf.SetStyle(o.markStart, o.markEnd, "4")

View File

@ -5,13 +5,10 @@ import (
"fmt"
"sync"
"sync/atomic"
"golang.org/x/crypto/ssh/terminal"
)
type Terminal struct {
cfg *Config
state *terminal.State
outchan chan rune
closed int32
stopChan chan struct{}
@ -36,19 +33,11 @@ func NewTerminal(cfg *Config) (*Terminal, error) {
}
func (t *Terminal) EnterRawMode() (err error) {
t.state, err = MakeRaw(int(t.cfg.StdinFd))
return err
return t.cfg.FuncMakeRaw()
}
func (t *Terminal) ExitRawMode() (err error) {
if t.state == nil {
return
}
err = Restore(int(t.cfg.StdinFd), t.state)
if err == nil {
t.state = nil
}
return err
return t.cfg.FuncExitRaw()
}
func (t *Terminal) Write(b []byte) (int, error) {

View File

@ -4,7 +4,6 @@ import (
"bufio"
"bytes"
"strconv"
"syscall"
"github.com/chzyer/readline/runes"
@ -12,8 +11,6 @@ import (
)
var (
StdinFd = int(uintptr(syscall.Stdin))
StdoutFd = int(uintptr(syscall.Stdout))
isWindows = false
)
@ -89,6 +86,29 @@ func escapeKey(r rune) rune {
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 {
var ret []string
buf := bytes.NewBuffer(nil)
@ -137,8 +157,18 @@ func GetInt(s []string, def int) int {
return c
}
func genGetWidthFunc(fd int) func() int {
return func() int {
return getWidth(fd)
}
type RawMode struct {
state *terminal.State
}
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)
}

View File

@ -1 +1,15 @@
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)
}
}

View File

@ -3,6 +3,9 @@
package readline
import (
"os"
"os/signal"
"sync"
"syscall"
"unsafe"
)
@ -28,3 +31,40 @@ func getWidth(stdoutFd int) int {
}
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()
}
}()
})
}

View File

@ -2,15 +2,29 @@
package readline
import "syscall"
func GetStdin() int {
return int(syscall.Stdin)
}
func init() {
isWindows = true
}
// get width of the terminal
func getWidth(fd int) int {
func GetScreenWidth() int {
info, _ := GetConsoleScreenBufferInfo()
if info == nil {
return -1
}
return int(info.dwSize.x)
}
func DefaultIsTerminal() bool {
return true
}
func DefaultOnWidthChanged(func()) {
}