1package readline 2 3import ( 4 "bufio" 5 "bytes" 6 "io" 7 "strconv" 8 "strings" 9 "sync" 10) 11 12type runeBufferBck struct { 13 buf []rune 14 idx int 15} 16 17type RuneBuffer struct { 18 buf []rune 19 idx int 20 prompt []rune 21 w io.Writer 22 23 hadClean bool 24 interactive bool 25 cfg *Config 26 27 width int 28 29 bck *runeBufferBck 30 31 offset string 32 33 lastKill []rune 34 35 sync.Mutex 36} 37 38func (r* RuneBuffer) pushKill(text []rune) { 39 r.lastKill = append([]rune{}, text...) 40} 41 42func (r *RuneBuffer) OnWidthChange(newWidth int) { 43 r.Lock() 44 r.width = newWidth 45 r.Unlock() 46} 47 48func (r *RuneBuffer) Backup() { 49 r.Lock() 50 r.bck = &runeBufferBck{r.buf, r.idx} 51 r.Unlock() 52} 53 54func (r *RuneBuffer) Restore() { 55 r.Refresh(func() { 56 if r.bck == nil { 57 return 58 } 59 r.buf = r.bck.buf 60 r.idx = r.bck.idx 61 }) 62} 63 64func NewRuneBuffer(w io.Writer, prompt string, cfg *Config, width int) *RuneBuffer { 65 rb := &RuneBuffer{ 66 w: w, 67 interactive: cfg.useInteractive(), 68 cfg: cfg, 69 width: width, 70 } 71 rb.SetPrompt(prompt) 72 return rb 73} 74 75func (r *RuneBuffer) SetConfig(cfg *Config) { 76 r.Lock() 77 r.cfg = cfg 78 r.interactive = cfg.useInteractive() 79 r.Unlock() 80} 81 82func (r *RuneBuffer) SetMask(m rune) { 83 r.Lock() 84 r.cfg.MaskRune = m 85 r.Unlock() 86} 87 88func (r *RuneBuffer) CurrentWidth(x int) int { 89 r.Lock() 90 defer r.Unlock() 91 return runes.WidthAll(r.buf[:x]) 92} 93 94func (r *RuneBuffer) PromptLen() int { 95 r.Lock() 96 width := r.promptLen() 97 r.Unlock() 98 return width 99} 100 101func (r *RuneBuffer) promptLen() int { 102 return runes.WidthAll(runes.ColorFilter(r.prompt)) 103} 104 105func (r *RuneBuffer) RuneSlice(i int) []rune { 106 r.Lock() 107 defer r.Unlock() 108 109 if i > 0 { 110 rs := make([]rune, i) 111 copy(rs, r.buf[r.idx:r.idx+i]) 112 return rs 113 } 114 rs := make([]rune, -i) 115 copy(rs, r.buf[r.idx+i:r.idx]) 116 return rs 117} 118 119func (r *RuneBuffer) Runes() []rune { 120 r.Lock() 121 newr := make([]rune, len(r.buf)) 122 copy(newr, r.buf) 123 r.Unlock() 124 return newr 125} 126 127func (r *RuneBuffer) Pos() int { 128 r.Lock() 129 defer r.Unlock() 130 return r.idx 131} 132 133func (r *RuneBuffer) Len() int { 134 r.Lock() 135 defer r.Unlock() 136 return len(r.buf) 137} 138 139func (r *RuneBuffer) MoveToLineStart() { 140 r.Refresh(func() { 141 if r.idx == 0 { 142 return 143 } 144 r.idx = 0 145 }) 146} 147 148func (r *RuneBuffer) MoveBackward() { 149 r.Refresh(func() { 150 if r.idx == 0 { 151 return 152 } 153 r.idx-- 154 }) 155} 156 157func (r *RuneBuffer) WriteString(s string) { 158 r.WriteRunes([]rune(s)) 159} 160 161func (r *RuneBuffer) WriteRune(s rune) { 162 r.WriteRunes([]rune{s}) 163} 164 165func (r *RuneBuffer) WriteRunes(s []rune) { 166 r.Refresh(func() { 167 tail := append(s, r.buf[r.idx:]...) 168 r.buf = append(r.buf[:r.idx], tail...) 169 r.idx += len(s) 170 }) 171} 172 173func (r *RuneBuffer) MoveForward() { 174 r.Refresh(func() { 175 if r.idx == len(r.buf) { 176 return 177 } 178 r.idx++ 179 }) 180} 181 182func (r *RuneBuffer) IsCursorInEnd() bool { 183 r.Lock() 184 defer r.Unlock() 185 return r.idx == len(r.buf) 186} 187 188func (r *RuneBuffer) Replace(ch rune) { 189 r.Refresh(func() { 190 r.buf[r.idx] = ch 191 }) 192} 193 194func (r *RuneBuffer) Erase() { 195 r.Refresh(func() { 196 r.idx = 0 197 r.pushKill(r.buf[:]) 198 r.buf = r.buf[:0] 199 }) 200} 201 202func (r *RuneBuffer) Delete() (success bool) { 203 r.Refresh(func() { 204 if r.idx == len(r.buf) { 205 return 206 } 207 r.pushKill(r.buf[r.idx : r.idx+1]) 208 r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...) 209 success = true 210 }) 211 return 212} 213 214func (r *RuneBuffer) DeleteWord() { 215 if r.idx == len(r.buf) { 216 return 217 } 218 init := r.idx 219 for init < len(r.buf) && IsWordBreak(r.buf[init]) { 220 init++ 221 } 222 for i := init + 1; i < len(r.buf); i++ { 223 if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { 224 r.pushKill(r.buf[r.idx:i-1]) 225 r.Refresh(func() { 226 r.buf = append(r.buf[:r.idx], r.buf[i-1:]...) 227 }) 228 return 229 } 230 } 231 r.Kill() 232} 233 234func (r *RuneBuffer) MoveToPrevWord() (success bool) { 235 r.Refresh(func() { 236 if r.idx == 0 { 237 return 238 } 239 240 for i := r.idx - 1; i > 0; i-- { 241 if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { 242 r.idx = i 243 success = true 244 return 245 } 246 } 247 r.idx = 0 248 success = true 249 }) 250 return 251} 252 253func (r *RuneBuffer) KillFront() { 254 r.Refresh(func() { 255 if r.idx == 0 { 256 return 257 } 258 259 length := len(r.buf) - r.idx 260 r.pushKill(r.buf[:r.idx]) 261 copy(r.buf[:length], r.buf[r.idx:]) 262 r.idx = 0 263 r.buf = r.buf[:length] 264 }) 265} 266 267func (r *RuneBuffer) Kill() { 268 r.Refresh(func() { 269 r.pushKill(r.buf[r.idx:]) 270 r.buf = r.buf[:r.idx] 271 }) 272} 273 274func (r *RuneBuffer) Transpose() { 275 r.Refresh(func() { 276 if len(r.buf) == 1 { 277 r.idx++ 278 } 279 280 if len(r.buf) < 2 { 281 return 282 } 283 284 if r.idx == 0 { 285 r.idx = 1 286 } else if r.idx >= len(r.buf) { 287 r.idx = len(r.buf) - 1 288 } 289 r.buf[r.idx], r.buf[r.idx-1] = r.buf[r.idx-1], r.buf[r.idx] 290 r.idx++ 291 }) 292} 293 294func (r *RuneBuffer) MoveToNextWord() { 295 r.Refresh(func() { 296 for i := r.idx + 1; i < len(r.buf); i++ { 297 if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { 298 r.idx = i 299 return 300 } 301 } 302 303 r.idx = len(r.buf) 304 }) 305} 306 307func (r *RuneBuffer) MoveToEndWord() { 308 r.Refresh(func() { 309 // already at the end, so do nothing 310 if r.idx == len(r.buf) { 311 return 312 } 313 // if we are at the end of a word already, go to next 314 if !IsWordBreak(r.buf[r.idx]) && IsWordBreak(r.buf[r.idx+1]) { 315 r.idx++ 316 } 317 318 // keep going until at the end of a word 319 for i := r.idx + 1; i < len(r.buf); i++ { 320 if IsWordBreak(r.buf[i]) && !IsWordBreak(r.buf[i-1]) { 321 r.idx = i - 1 322 return 323 } 324 } 325 r.idx = len(r.buf) 326 }) 327} 328 329func (r *RuneBuffer) BackEscapeWord() { 330 r.Refresh(func() { 331 if r.idx == 0 { 332 return 333 } 334 for i := r.idx - 1; i > 0; i-- { 335 if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { 336 r.pushKill(r.buf[i:r.idx]) 337 r.buf = append(r.buf[:i], r.buf[r.idx:]...) 338 r.idx = i 339 return 340 } 341 } 342 343 r.buf = r.buf[:0] 344 r.idx = 0 345 }) 346} 347 348func (r *RuneBuffer) Yank() { 349 if len(r.lastKill) == 0 { 350 return 351 } 352 r.Refresh(func() { 353 buf := make([]rune, 0, len(r.buf) + len(r.lastKill)) 354 buf = append(buf, r.buf[:r.idx]...) 355 buf = append(buf, r.lastKill...) 356 buf = append(buf, r.buf[r.idx:]...) 357 r.buf = buf 358 r.idx += len(r.lastKill) 359 }) 360} 361 362func (r *RuneBuffer) Backspace() { 363 r.Refresh(func() { 364 if r.idx == 0 { 365 return 366 } 367 368 r.idx-- 369 r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...) 370 }) 371} 372 373func (r *RuneBuffer) MoveToLineEnd() { 374 r.Refresh(func() { 375 if r.idx == len(r.buf) { 376 return 377 } 378 379 r.idx = len(r.buf) 380 }) 381} 382 383func (r *RuneBuffer) LineCount(width int) int { 384 if width == -1 { 385 width = r.width 386 } 387 return LineCount(width, 388 runes.WidthAll(r.buf)+r.PromptLen()) 389} 390 391func (r *RuneBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) { 392 r.Refresh(func() { 393 if reverse { 394 for i := r.idx - 1; i >= 0; i-- { 395 if r.buf[i] == ch { 396 r.idx = i 397 if prevChar { 398 r.idx++ 399 } 400 success = true 401 return 402 } 403 } 404 return 405 } 406 for i := r.idx + 1; i < len(r.buf); i++ { 407 if r.buf[i] == ch { 408 r.idx = i 409 if prevChar { 410 r.idx-- 411 } 412 success = true 413 return 414 } 415 } 416 }) 417 return 418} 419 420func (r *RuneBuffer) isInLineEdge() bool { 421 if isWindows { 422 return false 423 } 424 sp := r.getSplitByLine(r.buf) 425 return len(sp[len(sp)-1]) == 0 426} 427 428func (r *RuneBuffer) getSplitByLine(rs []rune) []string { 429 return SplitByLine(r.promptLen(), r.width, rs) 430} 431 432func (r *RuneBuffer) IdxLine(width int) int { 433 r.Lock() 434 defer r.Unlock() 435 return r.idxLine(width) 436} 437 438func (r *RuneBuffer) idxLine(width int) int { 439 if width == 0 { 440 return 0 441 } 442 sp := r.getSplitByLine(r.buf[:r.idx]) 443 return len(sp) - 1 444} 445 446func (r *RuneBuffer) CursorLineCount() int { 447 return r.LineCount(r.width) - r.IdxLine(r.width) 448} 449 450func (r *RuneBuffer) Refresh(f func()) { 451 r.Lock() 452 defer r.Unlock() 453 454 if !r.interactive { 455 if f != nil { 456 f() 457 } 458 return 459 } 460 461 r.clean() 462 if f != nil { 463 f() 464 } 465 r.print() 466} 467 468func (r *RuneBuffer) SetOffset(offset string) { 469 r.Lock() 470 r.offset = offset 471 r.Unlock() 472} 473 474func (r *RuneBuffer) print() { 475 r.w.Write(r.output()) 476 r.hadClean = false 477} 478 479func (r *RuneBuffer) output() []byte { 480 buf := bytes.NewBuffer(nil) 481 buf.WriteString(string(r.prompt)) 482 if r.cfg.EnableMask && len(r.buf) > 0 { 483 buf.Write([]byte(strings.Repeat(string(r.cfg.MaskRune), len(r.buf)-1))) 484 if r.buf[len(r.buf)-1] == '\n' { 485 buf.Write([]byte{'\n'}) 486 } else { 487 buf.Write([]byte(string(r.cfg.MaskRune))) 488 } 489 if len(r.buf) > r.idx { 490 buf.Write(r.getBackspaceSequence()) 491 } 492 493 } else { 494 for _, e := range r.cfg.Painter.Paint(r.buf, r.idx) { 495 if e == '\t' { 496 buf.WriteString(strings.Repeat(" ", TabWidth)) 497 } else { 498 buf.WriteRune(e) 499 } 500 } 501 if r.isInLineEdge() { 502 buf.Write([]byte(" \b")) 503 } 504 } 505 // cursor position 506 if len(r.buf) > r.idx { 507 buf.Write(r.getBackspaceSequence()) 508 } 509 return buf.Bytes() 510} 511 512func (r *RuneBuffer) getBackspaceSequence() []byte { 513 var sep = map[int]bool{} 514 515 var i int 516 for { 517 if i >= runes.WidthAll(r.buf) { 518 break 519 } 520 521 if i == 0 { 522 i -= r.promptLen() 523 } 524 i += r.width 525 526 sep[i] = true 527 } 528 var buf []byte 529 for i := len(r.buf); i > r.idx; i-- { 530 // move input to the left of one 531 buf = append(buf, '\b') 532 if sep[i] { 533 // up one line, go to the start of the line and move cursor right to the end (r.width) 534 buf = append(buf, "\033[A\r"+"\033["+strconv.Itoa(r.width)+"C"...) 535 } 536 } 537 538 return buf 539 540} 541 542func (r *RuneBuffer) Reset() []rune { 543 ret := runes.Copy(r.buf) 544 r.buf = r.buf[:0] 545 r.idx = 0 546 return ret 547} 548 549func (r *RuneBuffer) calWidth(m int) int { 550 if m > 0 { 551 return runes.WidthAll(r.buf[r.idx : r.idx+m]) 552 } 553 return runes.WidthAll(r.buf[r.idx+m : r.idx]) 554} 555 556func (r *RuneBuffer) SetStyle(start, end int, style string) { 557 if end < start { 558 panic("end < start") 559 } 560 561 // goto start 562 move := start - r.idx 563 if move > 0 { 564 r.w.Write([]byte(string(r.buf[r.idx : r.idx+move]))) 565 } else { 566 r.w.Write(bytes.Repeat([]byte("\b"), r.calWidth(move))) 567 } 568 r.w.Write([]byte("\033[" + style + "m")) 569 r.w.Write([]byte(string(r.buf[start:end]))) 570 r.w.Write([]byte("\033[0m")) 571 // TODO: move back 572} 573 574func (r *RuneBuffer) SetWithIdx(idx int, buf []rune) { 575 r.Refresh(func() { 576 r.buf = buf 577 r.idx = idx 578 }) 579} 580 581func (r *RuneBuffer) Set(buf []rune) { 582 r.SetWithIdx(len(buf), buf) 583} 584 585func (r *RuneBuffer) SetPrompt(prompt string) { 586 r.Lock() 587 r.prompt = []rune(prompt) 588 r.Unlock() 589} 590 591func (r *RuneBuffer) cleanOutput(w io.Writer, idxLine int) { 592 buf := bufio.NewWriter(w) 593 594 if r.width == 0 { 595 buf.WriteString(strings.Repeat("\r\b", len(r.buf)+r.promptLen())) 596 buf.Write([]byte("\033[J")) 597 } else { 598 buf.Write([]byte("\033[J")) // just like ^k :) 599 if idxLine == 0 { 600 buf.WriteString("\033[2K") 601 buf.WriteString("\r") 602 } else { 603 for i := 0; i < idxLine; i++ { 604 io.WriteString(buf, "\033[2K\r\033[A") 605 } 606 io.WriteString(buf, "\033[2K\r") 607 } 608 } 609 buf.Flush() 610 return 611} 612 613func (r *RuneBuffer) Clean() { 614 r.Lock() 615 r.clean() 616 r.Unlock() 617} 618 619func (r *RuneBuffer) clean() { 620 r.cleanWithIdxLine(r.idxLine(r.width)) 621} 622 623func (r *RuneBuffer) cleanWithIdxLine(idxLine int) { 624 if r.hadClean || !r.interactive { 625 return 626 } 627 r.hadClean = true 628 r.cleanOutput(r.w, idxLine) 629} 630