...

Source file src/github.com/mattn/go-tty/ttyutil/readline.go

Documentation: github.com/mattn/go-tty/ttyutil

     1  package ttyutil
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"os/signal"
     9  
    10  	"github.com/mattn/go-colorable"
    11  	"github.com/mattn/go-runewidth"
    12  	"github.com/mattn/go-tty"
    13  )
    14  
    15  type ctx struct {
    16  	w        io.Writer
    17  	input    []rune
    18  	last     []rune
    19  	prompt   string
    20  	cursor_x int
    21  	old_row  int
    22  	old_crow int
    23  	size     int
    24  }
    25  
    26  func (c *ctx) redraw(dirty bool, passwordChar rune) error {
    27  	var buf bytes.Buffer
    28  
    29  	buf.WriteString("\x1b[5>h")
    30  
    31  	buf.WriteString("\x1b[1G")
    32  	if dirty {
    33  		buf.WriteString("\x1b[0K")
    34  	}
    35  	for i := 0; i < c.old_row-c.old_crow; i++ {
    36  		buf.WriteString("\x1b[B")
    37  	}
    38  	for i := 0; i < c.old_row; i++ {
    39  		if dirty {
    40  			buf.WriteString("\x1b[2K")
    41  		}
    42  		buf.WriteString("\x1b[A")
    43  	}
    44  
    45  	var rs []rune
    46  	if passwordChar != 0 {
    47  		for i := 0; i < len(c.input); i++ {
    48  			rs = append(rs, passwordChar)
    49  		}
    50  	} else {
    51  		rs = c.input
    52  	}
    53  
    54  	ccol, crow, col, row := -1, 0, 0, 0
    55  	plen := len([]rune(c.prompt))
    56  	for i, r := range []rune(c.prompt + string(rs)) {
    57  		if i == plen+c.cursor_x {
    58  			ccol = col
    59  			crow = row
    60  		}
    61  		rw := runewidth.RuneWidth(r)
    62  		if col+rw > c.size {
    63  			col = 0
    64  			row++
    65  			if dirty {
    66  				buf.WriteString("\n\r\x1b[0K")
    67  			}
    68  		}
    69  		if dirty {
    70  			buf.WriteString(string(r))
    71  		}
    72  		col += rw
    73  	}
    74  	if dirty {
    75  		buf.WriteString("\x1b[1G")
    76  		for i := 0; i < row; i++ {
    77  			buf.WriteString("\x1b[A")
    78  		}
    79  	}
    80  	if ccol == -1 {
    81  		ccol = col
    82  		crow = row
    83  	}
    84  	for i := 0; i < crow; i++ {
    85  		buf.WriteString("\x1b[B")
    86  	}
    87  	buf.WriteString(fmt.Sprintf("\x1b[%dG", ccol+1))
    88  
    89  	buf.WriteString("\x1b[5>l")
    90  	io.Copy(c.w, &buf)
    91  
    92  	c.old_row = row
    93  	c.old_crow = crow
    94  
    95  	return nil
    96  }
    97  
    98  func ReadLine(tty *tty.TTY) (string, error) {
    99  	c := new(ctx)
   100  	c.w = colorable.NewColorableStdout()
   101  	quit := false
   102  	sc := make(chan os.Signal, 1)
   103  	signal.Notify(sc, os.Interrupt)
   104  	go func() {
   105  		<-sc
   106  		c.input = nil
   107  		quit = true
   108  	}()
   109  	c.size = 80
   110  
   111  	dirty := true
   112  loop:
   113  	for !quit {
   114  		err := c.redraw(dirty, 0)
   115  		if err != nil {
   116  			return "", err
   117  		}
   118  		dirty = false
   119  
   120  		r, err := tty.ReadRune()
   121  		if err != nil {
   122  			break
   123  		}
   124  		switch r {
   125  		case 0:
   126  		case 1: // CTRL-A
   127  			c.cursor_x = 0
   128  		case 2: // CTRL-B
   129  			if c.cursor_x > 0 {
   130  				c.cursor_x--
   131  			}
   132  		case 3: // BREAK
   133  			return "", nil
   134  		case 4: // CTRL-D
   135  			if len(c.input) > 0 {
   136  				continue
   137  			}
   138  			return "", io.EOF
   139  		case 5: // CTRL-E
   140  			c.cursor_x = len(c.input)
   141  		case 6: // CTRL-F
   142  			if c.cursor_x < len(c.input) {
   143  				c.cursor_x++
   144  			}
   145  		case 8, 0x7F: // BS
   146  			if c.cursor_x > 0 {
   147  				c.input = append(c.input[0:c.cursor_x-1], c.input[c.cursor_x:len(c.input)]...)
   148  				c.cursor_x--
   149  				dirty = true
   150  			}
   151  		case 27:
   152  			if !tty.Buffered() {
   153  				return "", io.EOF
   154  			}
   155  			r, err = tty.ReadRune()
   156  			if err == nil && r == 0x5b {
   157  				r, err = tty.ReadRune()
   158  				if err != nil {
   159  					panic(err)
   160  				}
   161  				switch r {
   162  				case 'C':
   163  					if c.cursor_x < len(c.input) {
   164  						c.cursor_x++
   165  					}
   166  				case 'D':
   167  					if c.cursor_x > 0 {
   168  						c.cursor_x--
   169  					}
   170  				}
   171  			}
   172  		case 10: // LF
   173  			break loop
   174  		case 11: // CTRL-K
   175  			c.input = c.input[:c.cursor_x]
   176  			dirty = true
   177  		case 12: // CTRL-L
   178  			dirty = true
   179  		case 13: // CR
   180  			break loop
   181  		case 21: // CTRL-U
   182  			c.input = c.input[c.cursor_x:]
   183  			c.cursor_x = 0
   184  			dirty = true
   185  		case 23: // CTRL-W
   186  			for i := len(c.input) - 1; i >= 0; i-- {
   187  				if i == 0 || c.input[i] == ' ' || c.input[i] == '\t' {
   188  					c.input = append(c.input[:i], c.input[c.cursor_x:]...)
   189  					c.cursor_x = i
   190  					dirty = true
   191  					break
   192  				}
   193  			}
   194  		default:
   195  			tmp := []rune{}
   196  			tmp = append(tmp, c.input[0:c.cursor_x]...)
   197  			tmp = append(tmp, r)
   198  			c.input = append(tmp, c.input[c.cursor_x:len(c.input)]...)
   199  			c.cursor_x++
   200  			dirty = true
   201  		}
   202  	}
   203  	os.Stdout.WriteString("\n")
   204  
   205  	if c.input == nil {
   206  		return "", io.EOF
   207  	}
   208  
   209  	return string(c.input), nil
   210  }
   211  

View as plain text