...
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:
127 c.cursor_x = 0
128 case 2:
129 if c.cursor_x > 0 {
130 c.cursor_x--
131 }
132 case 3:
133 return "", nil
134 case 4:
135 if len(c.input) > 0 {
136 continue
137 }
138 return "", io.EOF
139 case 5:
140 c.cursor_x = len(c.input)
141 case 6:
142 if c.cursor_x < len(c.input) {
143 c.cursor_x++
144 }
145 case 8, 0x7F:
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:
173 break loop
174 case 11:
175 c.input = c.input[:c.cursor_x]
176 dirty = true
177 case 12:
178 dirty = true
179 case 13:
180 break loop
181 case 21:
182 c.input = c.input[c.cursor_x:]
183 c.cursor_x = 0
184 dirty = true
185 case 23:
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