1// +build windows linux darwin openbsd freebsd netbsd 2 3package liner 4 5import ( 6 "bufio" 7 "container/ring" 8 "errors" 9 "fmt" 10 "io" 11 "os" 12 "strings" 13 "unicode" 14 "unicode/utf8" 15) 16 17type action int 18 19const ( 20 left action = iota 21 right 22 up 23 down 24 home 25 end 26 insert 27 del 28 pageUp 29 pageDown 30 f1 31 f2 32 f3 33 f4 34 f5 35 f6 36 f7 37 f8 38 f9 39 f10 40 f11 41 f12 42 altB 43 altD 44 altF 45 altY 46 shiftTab 47 wordLeft 48 wordRight 49 winch 50 unknown 51) 52 53const ( 54 ctrlA = 1 55 ctrlB = 2 56 ctrlC = 3 57 ctrlD = 4 58 ctrlE = 5 59 ctrlF = 6 60 ctrlG = 7 61 ctrlH = 8 62 tab = 9 63 lf = 10 64 ctrlK = 11 65 ctrlL = 12 66 cr = 13 67 ctrlN = 14 68 ctrlO = 15 69 ctrlP = 16 70 ctrlQ = 17 71 ctrlR = 18 72 ctrlS = 19 73 ctrlT = 20 74 ctrlU = 21 75 ctrlV = 22 76 ctrlW = 23 77 ctrlX = 24 78 ctrlY = 25 79 ctrlZ = 26 80 esc = 27 81 bs = 127 82) 83 84const ( 85 beep = "\a" 86) 87 88type tabDirection int 89 90const ( 91 tabForward tabDirection = iota 92 tabReverse 93) 94 95func (s *State) refresh(prompt []rune, buf []rune, pos int) error { 96 if s.columns == 0 { 97 return ErrInternal 98 } 99 100 s.needRefresh = false 101 if s.multiLineMode { 102 return s.refreshMultiLine(prompt, buf, pos) 103 } 104 return s.refreshSingleLine(prompt, buf, pos) 105} 106 107func (s *State) refreshSingleLine(prompt []rune, buf []rune, pos int) error { 108 s.cursorPos(0) 109 _, err := fmt.Print(string(prompt)) 110 if err != nil { 111 return err 112 } 113 114 pLen := countGlyphs(prompt) 115 bLen := countGlyphs(buf) 116 pos = countGlyphs(buf[:pos]) 117 if pLen+bLen < s.columns { 118 _, err = fmt.Print(string(buf)) 119 s.eraseLine() 120 s.cursorPos(pLen + pos) 121 } else { 122 // Find space available 123 space := s.columns - pLen 124 space-- // space for cursor 125 start := pos - space/2 126 end := start + space 127 if end > bLen { 128 end = bLen 129 start = end - space 130 } 131 if start < 0 { 132 start = 0 133 end = space 134 } 135 pos -= start 136 137 // Leave space for markers 138 if start > 0 { 139 start++ 140 } 141 if end < bLen { 142 end-- 143 } 144 startRune := len(getPrefixGlyphs(buf, start)) 145 line := getPrefixGlyphs(buf[startRune:], end-start) 146 147 // Output 148 if start > 0 { 149 fmt.Print("{") 150 } 151 fmt.Print(string(line)) 152 if end < bLen { 153 fmt.Print("}") 154 } 155 156 // Set cursor position 157 s.eraseLine() 158 s.cursorPos(pLen + pos) 159 } 160 return err 161} 162 163func (s *State) refreshMultiLine(prompt []rune, buf []rune, pos int) error { 164 promptColumns := countMultiLineGlyphs(prompt, s.columns, 0) 165 totalColumns := countMultiLineGlyphs(buf, s.columns, promptColumns) 166 totalRows := (totalColumns + s.columns - 1) / s.columns 167 maxRows := s.maxRows 168 if totalRows > s.maxRows { 169 s.maxRows = totalRows 170 } 171 cursorRows := s.cursorRows 172 if cursorRows == 0 { 173 cursorRows = 1 174 } 175 176 /* First step: clear all the lines used before. To do so start by 177 * going to the last row. */ 178 if maxRows-cursorRows > 0 { 179 s.moveDown(maxRows - cursorRows) 180 } 181 182 /* Now for every row clear it, go up. */ 183 for i := 0; i < maxRows-1; i++ { 184 s.cursorPos(0) 185 s.eraseLine() 186 s.moveUp(1) 187 } 188 189 /* Clean the top line. */ 190 s.cursorPos(0) 191 s.eraseLine() 192 193 /* Write the prompt and the current buffer content */ 194 if _, err := fmt.Print(string(prompt)); err != nil { 195 return err 196 } 197 if _, err := fmt.Print(string(buf)); err != nil { 198 return err 199 } 200 201 /* If we are at the very end of the screen with our prompt, we need to 202 * emit a newline and move the prompt to the first column. */ 203 cursorColumns := countMultiLineGlyphs(buf[:pos], s.columns, promptColumns) 204 if cursorColumns == totalColumns && totalColumns%s.columns == 0 { 205 s.emitNewLine() 206 s.cursorPos(0) 207 totalRows++ 208 if totalRows > s.maxRows { 209 s.maxRows = totalRows 210 } 211 } 212 213 /* Move cursor to right position. */ 214 cursorRows = (cursorColumns + s.columns) / s.columns 215 if s.cursorRows > 0 && totalRows-cursorRows > 0 { 216 s.moveUp(totalRows - cursorRows) 217 } 218 /* Set column. */ 219 s.cursorPos(cursorColumns % s.columns) 220 221 s.cursorRows = cursorRows 222 return nil 223} 224 225func (s *State) resetMultiLine(prompt []rune, buf []rune, pos int) { 226 columns := countMultiLineGlyphs(prompt, s.columns, 0) 227 columns = countMultiLineGlyphs(buf[:pos], s.columns, columns) 228 columns += 2 // ^C 229 cursorRows := (columns + s.columns) / s.columns 230 if s.maxRows-cursorRows > 0 { 231 for i := 0; i < s.maxRows-cursorRows; i++ { 232 fmt.Println() // always moves the cursor down or scrolls the window up as needed 233 } 234 } 235 s.maxRows = 1 236 s.cursorRows = 0 237} 238 239func longestCommonPrefix(strs []string) string { 240 if len(strs) == 0 { 241 return "" 242 } 243 longest := strs[0] 244 245 for _, str := range strs[1:] { 246 for !strings.HasPrefix(str, longest) { 247 longest = longest[:len(longest)-1] 248 } 249 } 250 // Remove trailing partial runes 251 longest = strings.TrimRight(longest, "\uFFFD") 252 return longest 253} 254 255func (s *State) circularTabs(items []string) func(tabDirection) (string, error) { 256 item := -1 257 return func(direction tabDirection) (string, error) { 258 if direction == tabForward { 259 if item < len(items)-1 { 260 item++ 261 } else { 262 item = 0 263 } 264 } else if direction == tabReverse { 265 if item > 0 { 266 item-- 267 } else { 268 item = len(items) - 1 269 } 270 } 271 return items[item], nil 272 } 273} 274 275func calculateColumns(screenWidth int, items []string) (numColumns, numRows, maxWidth int) { 276 for _, item := range items { 277 if len(item) >= screenWidth { 278 return 1, len(items), screenWidth - 1 279 } 280 if len(item) >= maxWidth { 281 maxWidth = len(item) + 1 282 } 283 } 284 285 numColumns = screenWidth / maxWidth 286 numRows = len(items) / numColumns 287 if len(items)%numColumns > 0 { 288 numRows++ 289 } 290 291 if len(items) <= numColumns { 292 maxWidth = 0 293 } 294 295 return 296} 297 298func (s *State) printedTabs(items []string) func(tabDirection) (string, error) { 299 numTabs := 1 300 prefix := longestCommonPrefix(items) 301 return func(direction tabDirection) (string, error) { 302 if len(items) == 1 { 303 return items[0], nil 304 } 305 306 if numTabs == 2 { 307 if len(items) > 100 { 308 fmt.Printf("\nDisplay all %d possibilities? (y or n) ", len(items)) 309 prompt: 310 for { 311 next, err := s.readNext() 312 if err != nil { 313 return prefix, err 314 } 315 316 if key, ok := next.(rune); ok { 317 switch key { 318 case 'n', 'N': 319 return prefix, nil 320 case 'y', 'Y': 321 break prompt 322 case ctrlC, ctrlD, cr, lf: 323 s.restartPrompt() 324 } 325 } 326 } 327 } 328 fmt.Println("") 329 330 numColumns, numRows, maxWidth := calculateColumns(s.columns, items) 331 332 for i := 0; i < numRows; i++ { 333 for j := 0; j < numColumns*numRows; j += numRows { 334 if i+j < len(items) { 335 if maxWidth > 0 { 336 fmt.Printf("%-*.[1]*s", maxWidth, items[i+j]) 337 } else { 338 fmt.Printf("%v ", items[i+j]) 339 } 340 } 341 } 342 fmt.Println("") 343 } 344 } else { 345 numTabs++ 346 } 347 return prefix, nil 348 } 349} 350 351func (s *State) tabComplete(p []rune, line []rune, pos int) ([]rune, int, interface{}, error) { 352 if s.completer == nil { 353 return line, pos, rune(esc), nil 354 } 355 head, list, tail := s.completer(string(line), pos) 356 if len(list) <= 0 { 357 return line, pos, rune(esc), nil 358 } 359 hl := utf8.RuneCountInString(head) 360 if len(list) == 1 { 361 err := s.refresh(p, []rune(head+list[0]+tail), hl+utf8.RuneCountInString(list[0])) 362 return []rune(head + list[0] + tail), hl + utf8.RuneCountInString(list[0]), rune(esc), err 363 } 364 365 direction := tabForward 366 tabPrinter := s.circularTabs(list) 367 if s.tabStyle == TabPrints { 368 tabPrinter = s.printedTabs(list) 369 } 370 371 for { 372 pick, err := tabPrinter(direction) 373 if err != nil { 374 return line, pos, rune(esc), err 375 } 376 err = s.refresh(p, []rune(head+pick+tail), hl+utf8.RuneCountInString(pick)) 377 if err != nil { 378 return line, pos, rune(esc), err 379 } 380 381 next, err := s.readNext() 382 if err != nil { 383 return line, pos, rune(esc), err 384 } 385 if key, ok := next.(rune); ok { 386 if key == tab { 387 direction = tabForward 388 continue 389 } 390 if key == esc { 391 return line, pos, rune(esc), nil 392 } 393 } 394 if a, ok := next.(action); ok && a == shiftTab { 395 direction = tabReverse 396 continue 397 } 398 return []rune(head + pick + tail), hl + utf8.RuneCountInString(pick), next, nil 399 } 400} 401 402// reverse intelligent search, implements a bash-like history search. 403func (s *State) reverseISearch(origLine []rune, origPos int) ([]rune, int, interface{}, error) { 404 p := "(reverse-i-search)`': " 405 err := s.refresh([]rune(p), origLine, origPos) 406 if err != nil { 407 return origLine, origPos, rune(esc), err 408 } 409 410 line := []rune{} 411 pos := 0 412 foundLine := string(origLine) 413 foundPos := origPos 414 415 getLine := func() ([]rune, []rune, int) { 416 search := string(line) 417 prompt := "(reverse-i-search)`%s': " 418 return []rune(fmt.Sprintf(prompt, search)), []rune(foundLine), foundPos 419 } 420 421 history, positions := s.getHistoryByPattern(string(line)) 422 historyPos := len(history) - 1 423 424 for { 425 next, err := s.readNext() 426 if err != nil { 427 return []rune(foundLine), foundPos, rune(esc), err 428 } 429 430 switch v := next.(type) { 431 case rune: 432 switch v { 433 case ctrlR: // Search backwards 434 if historyPos > 0 && historyPos < len(history) { 435 historyPos-- 436 foundLine = history[historyPos] 437 foundPos = positions[historyPos] 438 } else { 439 fmt.Print(beep) 440 } 441 case ctrlS: // Search forward 442 if historyPos < len(history)-1 && historyPos >= 0 { 443 historyPos++ 444 foundLine = history[historyPos] 445 foundPos = positions[historyPos] 446 } else { 447 fmt.Print(beep) 448 } 449 case ctrlH, bs: // Backspace 450 if pos <= 0 { 451 fmt.Print(beep) 452 } else { 453 n := len(getSuffixGlyphs(line[:pos], 1)) 454 line = append(line[:pos-n], line[pos:]...) 455 pos -= n 456 457 // For each char deleted, display the last matching line of history 458 history, positions := s.getHistoryByPattern(string(line)) 459 historyPos = len(history) - 1 460 if len(history) > 0 { 461 foundLine = history[historyPos] 462 foundPos = positions[historyPos] 463 } else { 464 foundLine = "" 465 foundPos = 0 466 } 467 } 468 case ctrlG: // Cancel 469 return origLine, origPos, rune(esc), err 470 471 case tab, cr, lf, ctrlA, ctrlB, ctrlD, ctrlE, ctrlF, ctrlK, 472 ctrlL, ctrlN, ctrlO, ctrlP, ctrlQ, ctrlT, ctrlU, ctrlV, ctrlW, ctrlX, ctrlY, ctrlZ: 473 fallthrough 474 case 0, ctrlC, esc, 28, 29, 30, 31: 475 return []rune(foundLine), foundPos, next, err 476 default: 477 line = append(line[:pos], append([]rune{v}, line[pos:]...)...) 478 pos++ 479 480 // For each keystroke typed, display the last matching line of history 481 history, positions = s.getHistoryByPattern(string(line)) 482 historyPos = len(history) - 1 483 if len(history) > 0 { 484 foundLine = history[historyPos] 485 foundPos = positions[historyPos] 486 } else { 487 foundLine = "" 488 foundPos = 0 489 } 490 } 491 case action: 492 return []rune(foundLine), foundPos, next, err 493 } 494 err = s.refresh(getLine()) 495 if err != nil { 496 return []rune(foundLine), foundPos, rune(esc), err 497 } 498 } 499} 500 501// addToKillRing adds some text to the kill ring. If mode is 0 it adds it to a 502// new node in the end of the kill ring, and move the current pointer to the new 503// node. If mode is 1 or 2 it appends or prepends the text to the current entry 504// of the killRing. 505func (s *State) addToKillRing(text []rune, mode int) { 506 // Don't use the same underlying array as text 507 killLine := make([]rune, len(text)) 508 copy(killLine, text) 509 510 // Point killRing to a newNode, procedure depends on the killring state and 511 // append mode. 512 if mode == 0 { // Add new node to killRing 513 if s.killRing == nil { // if killring is empty, create a new one 514 s.killRing = ring.New(1) 515 } else if s.killRing.Len() >= KillRingMax { // if killring is "full" 516 s.killRing = s.killRing.Next() 517 } else { // Normal case 518 s.killRing.Link(ring.New(1)) 519 s.killRing = s.killRing.Next() 520 } 521 } else { 522 if s.killRing == nil { // if killring is empty, create a new one 523 s.killRing = ring.New(1) 524 s.killRing.Value = []rune{} 525 } 526 if mode == 1 { // Append to last entry 527 killLine = append(s.killRing.Value.([]rune), killLine...) 528 } else if mode == 2 { // Prepend to last entry 529 killLine = append(killLine, s.killRing.Value.([]rune)...) 530 } 531 } 532 533 // Save text in the current killring node 534 s.killRing.Value = killLine 535} 536 537func (s *State) yank(p []rune, text []rune, pos int) ([]rune, int, interface{}, error) { 538 if s.killRing == nil { 539 return text, pos, rune(esc), nil 540 } 541 542 lineStart := text[:pos] 543 lineEnd := text[pos:] 544 var line []rune 545 546 for { 547 value := s.killRing.Value.([]rune) 548 line = make([]rune, 0) 549 line = append(line, lineStart...) 550 line = append(line, value...) 551 line = append(line, lineEnd...) 552 553 pos = len(lineStart) + len(value) 554 err := s.refresh(p, line, pos) 555 if err != nil { 556 return line, pos, 0, err 557 } 558 559 next, err := s.readNext() 560 if err != nil { 561 return line, pos, next, err 562 } 563 564 switch v := next.(type) { 565 case rune: 566 return line, pos, next, nil 567 case action: 568 switch v { 569 case altY: 570 s.killRing = s.killRing.Prev() 571 default: 572 return line, pos, next, nil 573 } 574 } 575 } 576} 577 578// Prompt displays p and returns a line of user input, not including a trailing 579// newline character. An io.EOF error is returned if the user signals end-of-file 580// by pressing Ctrl-D. Prompt allows line editing if the terminal supports it. 581func (s *State) Prompt(prompt string) (string, error) { 582 return s.PromptWithSuggestion(prompt, "", 0) 583} 584 585// PromptWithSuggestion displays prompt and an editable text with cursor at 586// given position. The cursor will be set to the end of the line if given position 587// is negative or greater than length of text. Returns a line of user input, not 588// including a trailing newline character. An io.EOF error is returned if the user 589// signals end-of-file by pressing Ctrl-D. 590func (s *State) PromptWithSuggestion(prompt string, text string, pos int) (string, error) { 591 for _, r := range prompt { 592 if unicode.Is(unicode.C, r) { 593 return "", ErrInvalidPrompt 594 } 595 } 596 if s.inputRedirected || !s.terminalSupported { 597 return s.promptUnsupported(prompt) 598 } 599 p := []rune(prompt) 600 const minWorkingSpace = 10 601 if s.columns < countGlyphs(p)+minWorkingSpace { 602 return s.tooNarrow(prompt) 603 } 604 if s.outputRedirected { 605 return "", ErrNotTerminalOutput 606 } 607 608 s.historyMutex.RLock() 609 defer s.historyMutex.RUnlock() 610 611 fmt.Print(prompt) 612 var line = []rune(text) 613 historyEnd := "" 614 var historyPrefix []string 615 historyPos := 0 616 historyStale := true 617 historyAction := false // used to mark history related actions 618 killAction := 0 // used to mark kill related actions 619 620 defer s.stopPrompt() 621 622 if pos < 0 || len(text) < pos { 623 pos = len(text) 624 } 625 if len(line) > 0 { 626 err := s.refresh(p, line, pos) 627 if err != nil { 628 return "", err 629 } 630 } 631 632restart: 633 s.startPrompt() 634 s.getColumns() 635 636mainLoop: 637 for { 638 next, err := s.readNext() 639 haveNext: 640 if err != nil { 641 if s.shouldRestart != nil && s.shouldRestart(err) { 642 goto restart 643 } 644 return "", err 645 } 646 647 historyAction = false 648 switch v := next.(type) { 649 case rune: 650 switch v { 651 case cr, lf: 652 if s.needRefresh { 653 err := s.refresh(p, line, pos) 654 if err != nil { 655 return "", err 656 } 657 } 658 if s.multiLineMode { 659 s.resetMultiLine(p, line, pos) 660 } 661 fmt.Println() 662 break mainLoop 663 case ctrlA: // Start of line 664 pos = 0 665 s.needRefresh = true 666 case ctrlE: // End of line 667 pos = len(line) 668 s.needRefresh = true 669 case ctrlB: // left 670 if pos > 0 { 671 pos -= len(getSuffixGlyphs(line[:pos], 1)) 672 s.needRefresh = true 673 } else { 674 fmt.Print(beep) 675 } 676 case ctrlF: // right 677 if pos < len(line) { 678 pos += len(getPrefixGlyphs(line[pos:], 1)) 679 s.needRefresh = true 680 } else { 681 fmt.Print(beep) 682 } 683 case ctrlD: // del 684 if pos == 0 && len(line) == 0 { 685 // exit 686 return "", io.EOF 687 } 688 689 // ctrlD is a potential EOF, so the rune reader shuts down. 690 // Therefore, if it isn't actually an EOF, we must re-startPrompt. 691 s.restartPrompt() 692 693 if pos >= len(line) { 694 fmt.Print(beep) 695 } else { 696 n := len(getPrefixGlyphs(line[pos:], 1)) 697 line = append(line[:pos], line[pos+n:]...) 698 s.needRefresh = true 699 } 700 case ctrlK: // delete remainder of line 701 if pos >= len(line) { 702 fmt.Print(beep) 703 } else { 704 if killAction > 0 { 705 s.addToKillRing(line[pos:], 1) // Add in apend mode 706 } else { 707 s.addToKillRing(line[pos:], 0) // Add in normal mode 708 } 709 710 killAction = 2 // Mark that there was a kill action 711 line = line[:pos] 712 s.needRefresh = true 713 } 714 case ctrlP: // up 715 historyAction = true 716 if historyStale { 717 historyPrefix = s.getHistoryByPrefix(string(line)) 718 historyPos = len(historyPrefix) 719 historyStale = false 720 } 721 if historyPos > 0 { 722 if historyPos == len(historyPrefix) { 723 historyEnd = string(line) 724 } 725 historyPos-- 726 line = []rune(historyPrefix[historyPos]) 727 pos = len(line) 728 s.needRefresh = true 729 } else { 730 fmt.Print(beep) 731 } 732 case ctrlN: // down 733 historyAction = true 734 if historyStale { 735 historyPrefix = s.getHistoryByPrefix(string(line)) 736 historyPos = len(historyPrefix) 737 historyStale = false 738 } 739 if historyPos < len(historyPrefix) { 740 historyPos++ 741 if historyPos == len(historyPrefix) { 742 line = []rune(historyEnd) 743 } else { 744 line = []rune(historyPrefix[historyPos]) 745 } 746 pos = len(line) 747 s.needRefresh = true 748 } else { 749 fmt.Print(beep) 750 } 751 case ctrlT: // transpose prev glyph with glyph under cursor 752 if len(line) < 2 || pos < 1 { 753 fmt.Print(beep) 754 } else { 755 if pos == len(line) { 756 pos -= len(getSuffixGlyphs(line, 1)) 757 } 758 prev := getSuffixGlyphs(line[:pos], 1) 759 next := getPrefixGlyphs(line[pos:], 1) 760 scratch := make([]rune, len(prev)) 761 copy(scratch, prev) 762 copy(line[pos-len(prev):], next) 763 copy(line[pos-len(prev)+len(next):], scratch) 764 pos += len(next) 765 s.needRefresh = true 766 } 767 case ctrlL: // clear screen 768 s.eraseScreen() 769 s.needRefresh = true 770 case ctrlC: // reset 771 fmt.Println("^C") 772 if s.multiLineMode { 773 s.resetMultiLine(p, line, pos) 774 } 775 if s.ctrlCAborts { 776 return "", ErrPromptAborted 777 } 778 line = line[:0] 779 pos = 0 780 fmt.Print(prompt) 781 s.restartPrompt() 782 case ctrlH, bs: // Backspace 783 if pos <= 0 { 784 fmt.Print(beep) 785 } else { 786 n := len(getSuffixGlyphs(line[:pos], 1)) 787 line = append(line[:pos-n], line[pos:]...) 788 pos -= n 789 s.needRefresh = true 790 } 791 case ctrlU: // Erase line before cursor 792 if killAction > 0 { 793 s.addToKillRing(line[:pos], 2) // Add in prepend mode 794 } else { 795 s.addToKillRing(line[:pos], 0) // Add in normal mode 796 } 797 798 killAction = 2 // Mark that there was some killing 799 line = line[pos:] 800 pos = 0 801 s.needRefresh = true 802 case ctrlW: // Erase word 803 if pos == 0 { 804 fmt.Print(beep) 805 break 806 } 807 // Remove whitespace to the left 808 var buf []rune // Store the deleted chars in a buffer 809 for { 810 if pos == 0 || !unicode.IsSpace(line[pos-1]) { 811 break 812 } 813 buf = append(buf, line[pos-1]) 814 line = append(line[:pos-1], line[pos:]...) 815 pos-- 816 } 817 // Remove non-whitespace to the left 818 for { 819 if pos == 0 || unicode.IsSpace(line[pos-1]) { 820 break 821 } 822 buf = append(buf, line[pos-1]) 823 line = append(line[:pos-1], line[pos:]...) 824 pos-- 825 } 826 // Invert the buffer and save the result on the killRing 827 var newBuf []rune 828 for i := len(buf) - 1; i >= 0; i-- { 829 newBuf = append(newBuf, buf[i]) 830 } 831 if killAction > 0 { 832 s.addToKillRing(newBuf, 2) // Add in prepend mode 833 } else { 834 s.addToKillRing(newBuf, 0) // Add in normal mode 835 } 836 killAction = 2 // Mark that there was some killing 837 838 s.needRefresh = true 839 case ctrlY: // Paste from Yank buffer 840 line, pos, next, err = s.yank(p, line, pos) 841 goto haveNext 842 case ctrlR: // Reverse Search 843 line, pos, next, err = s.reverseISearch(line, pos) 844 s.needRefresh = true 845 goto haveNext 846 case tab: // Tab completion 847 line, pos, next, err = s.tabComplete(p, line, pos) 848 goto haveNext 849 // Catch keys that do nothing, but you don't want them to beep 850 case esc: 851 // DO NOTHING 852 // Unused keys 853 case ctrlG, ctrlO, ctrlQ, ctrlS, ctrlV, ctrlX, ctrlZ: 854 fallthrough 855 // Catch unhandled control codes (anything <= 31) 856 case 0, 28, 29, 30, 31: 857 fmt.Print(beep) 858 default: 859 if pos == len(line) && !s.multiLineMode && 860 len(p)+len(line) < s.columns*4 && // Avoid countGlyphs on large lines 861 countGlyphs(p)+countGlyphs(line) < s.columns-1 { 862 line = append(line, v) 863 fmt.Printf("%c", v) 864 pos++ 865 } else { 866 line = append(line[:pos], append([]rune{v}, line[pos:]...)...) 867 pos++ 868 s.needRefresh = true 869 } 870 } 871 case action: 872 switch v { 873 case del: 874 if pos >= len(line) { 875 fmt.Print(beep) 876 } else { 877 n := len(getPrefixGlyphs(line[pos:], 1)) 878 line = append(line[:pos], line[pos+n:]...) 879 } 880 case left: 881 if pos > 0 { 882 pos -= len(getSuffixGlyphs(line[:pos], 1)) 883 } else { 884 fmt.Print(beep) 885 } 886 case wordLeft, altB: 887 if pos > 0 { 888 var spaceHere, spaceLeft, leftKnown bool 889 for { 890 pos-- 891 if pos == 0 { 892 break 893 } 894 if leftKnown { 895 spaceHere = spaceLeft 896 } else { 897 spaceHere = unicode.IsSpace(line[pos]) 898 } 899 spaceLeft, leftKnown = unicode.IsSpace(line[pos-1]), true 900 if !spaceHere && spaceLeft { 901 break 902 } 903 } 904 } else { 905 fmt.Print(beep) 906 } 907 case right: 908 if pos < len(line) { 909 pos += len(getPrefixGlyphs(line[pos:], 1)) 910 } else { 911 fmt.Print(beep) 912 } 913 case wordRight, altF: 914 if pos < len(line) { 915 var spaceHere, spaceLeft, hereKnown bool 916 for { 917 pos++ 918 if pos == len(line) { 919 break 920 } 921 if hereKnown { 922 spaceLeft = spaceHere 923 } else { 924 spaceLeft = unicode.IsSpace(line[pos-1]) 925 } 926 spaceHere, hereKnown = unicode.IsSpace(line[pos]), true 927 if spaceHere && !spaceLeft { 928 break 929 } 930 } 931 } else { 932 fmt.Print(beep) 933 } 934 case up: 935 historyAction = true 936 if historyStale { 937 historyPrefix = s.getHistoryByPrefix(string(line)) 938 historyPos = len(historyPrefix) 939 historyStale = false 940 } 941 if historyPos > 0 { 942 if historyPos == len(historyPrefix) { 943 historyEnd = string(line) 944 } 945 historyPos-- 946 line = []rune(historyPrefix[historyPos]) 947 pos = len(line) 948 } else { 949 fmt.Print(beep) 950 } 951 case down: 952 historyAction = true 953 if historyStale { 954 historyPrefix = s.getHistoryByPrefix(string(line)) 955 historyPos = len(historyPrefix) 956 historyStale = false 957 } 958 if historyPos < len(historyPrefix) { 959 historyPos++ 960 if historyPos == len(historyPrefix) { 961 line = []rune(historyEnd) 962 } else { 963 line = []rune(historyPrefix[historyPos]) 964 } 965 pos = len(line) 966 } else { 967 fmt.Print(beep) 968 } 969 case home: // Start of line 970 pos = 0 971 case end: // End of line 972 pos = len(line) 973 case altD: // Delete next word 974 if pos == len(line) { 975 fmt.Print(beep) 976 break 977 } 978 // Remove whitespace to the right 979 var buf []rune // Store the deleted chars in a buffer 980 for { 981 if pos == len(line) || !unicode.IsSpace(line[pos]) { 982 break 983 } 984 buf = append(buf, line[pos]) 985 line = append(line[:pos], line[pos+1:]...) 986 } 987 // Remove non-whitespace to the right 988 for { 989 if pos == len(line) || unicode.IsSpace(line[pos]) { 990 break 991 } 992 buf = append(buf, line[pos]) 993 line = append(line[:pos], line[pos+1:]...) 994 } 995 // Save the result on the killRing 996 if killAction > 0 { 997 s.addToKillRing(buf, 2) // Add in prepend mode 998 } else { 999 s.addToKillRing(buf, 0) // Add in normal mode 1000 } 1001 killAction = 2 // Mark that there was some killing 1002 case winch: // Window change 1003 if s.multiLineMode { 1004 if s.maxRows-s.cursorRows > 0 { 1005 s.moveDown(s.maxRows - s.cursorRows) 1006 } 1007 for i := 0; i < s.maxRows-1; i++ { 1008 s.cursorPos(0) 1009 s.eraseLine() 1010 s.moveUp(1) 1011 } 1012 s.maxRows = 1 1013 s.cursorRows = 1 1014 } 1015 } 1016 s.needRefresh = true 1017 } 1018 if s.needRefresh && !s.inputWaiting() { 1019 err := s.refresh(p, line, pos) 1020 if err != nil { 1021 return "", err 1022 } 1023 } 1024 if !historyAction { 1025 historyStale = true 1026 } 1027 if killAction > 0 { 1028 killAction-- 1029 } 1030 } 1031 return string(line), nil 1032} 1033 1034// PasswordPrompt displays p, and then waits for user input. The input typed by 1035// the user is not displayed in the terminal. 1036func (s *State) PasswordPrompt(prompt string) (string, error) { 1037 for _, r := range prompt { 1038 if unicode.Is(unicode.C, r) { 1039 return "", ErrInvalidPrompt 1040 } 1041 } 1042 if !s.terminalSupported || s.columns == 0 { 1043 return "", errors.New("liner: function not supported in this terminal") 1044 } 1045 if s.inputRedirected { 1046 return s.promptUnsupported(prompt) 1047 } 1048 if s.outputRedirected { 1049 return "", ErrNotTerminalOutput 1050 } 1051 1052 p := []rune(prompt) 1053 const minWorkingSpace = 1 1054 if s.columns < countGlyphs(p)+minWorkingSpace { 1055 return s.tooNarrow(prompt) 1056 } 1057 1058 defer s.stopPrompt() 1059 1060restart: 1061 s.startPrompt() 1062 s.getColumns() 1063 1064 fmt.Print(prompt) 1065 var line []rune 1066 pos := 0 1067 1068mainLoop: 1069 for { 1070 next, err := s.readNext() 1071 if err != nil { 1072 if s.shouldRestart != nil && s.shouldRestart(err) { 1073 goto restart 1074 } 1075 return "", err 1076 } 1077 1078 switch v := next.(type) { 1079 case rune: 1080 switch v { 1081 case cr, lf: 1082 if s.needRefresh { 1083 err := s.refresh(p, line, pos) 1084 if err != nil { 1085 return "", err 1086 } 1087 } 1088 if s.multiLineMode { 1089 s.resetMultiLine(p, line, pos) 1090 } 1091 fmt.Println() 1092 break mainLoop 1093 case ctrlD: // del 1094 if pos == 0 && len(line) == 0 { 1095 // exit 1096 return "", io.EOF 1097 } 1098 1099 // ctrlD is a potential EOF, so the rune reader shuts down. 1100 // Therefore, if it isn't actually an EOF, we must re-startPrompt. 1101 s.restartPrompt() 1102 case ctrlL: // clear screen 1103 s.eraseScreen() 1104 err := s.refresh(p, []rune{}, 0) 1105 if err != nil { 1106 return "", err 1107 } 1108 case ctrlH, bs: // Backspace 1109 if pos <= 0 { 1110 fmt.Print(beep) 1111 } else { 1112 n := len(getSuffixGlyphs(line[:pos], 1)) 1113 line = append(line[:pos-n], line[pos:]...) 1114 pos -= n 1115 } 1116 case ctrlC: 1117 fmt.Println("^C") 1118 if s.multiLineMode { 1119 s.resetMultiLine(p, line, pos) 1120 } 1121 if s.ctrlCAborts { 1122 return "", ErrPromptAborted 1123 } 1124 line = line[:0] 1125 pos = 0 1126 fmt.Print(prompt) 1127 s.restartPrompt() 1128 // Unused keys 1129 case esc, tab, ctrlA, ctrlB, ctrlE, ctrlF, ctrlG, ctrlK, ctrlN, ctrlO, ctrlP, ctrlQ, ctrlR, ctrlS, 1130 ctrlT, ctrlU, ctrlV, ctrlW, ctrlX, ctrlY, ctrlZ: 1131 fallthrough 1132 // Catch unhandled control codes (anything <= 31) 1133 case 0, 28, 29, 30, 31: 1134 fmt.Print(beep) 1135 default: 1136 line = append(line[:pos], append([]rune{v}, line[pos:]...)...) 1137 pos++ 1138 } 1139 } 1140 } 1141 return string(line), nil 1142} 1143 1144func (s *State) tooNarrow(prompt string) (string, error) { 1145 // Docker and OpenWRT and etc sometimes return 0 column width 1146 // Reset mode temporarily. Restore baked mode in case the terminal 1147 // is wide enough for the next Prompt attempt. 1148 m, merr := TerminalMode() 1149 s.origMode.ApplyMode() 1150 if merr == nil { 1151 defer m.ApplyMode() 1152 } 1153 if s.r == nil { 1154 // Windows does not always set s.r 1155 s.r = bufio.NewReader(os.Stdin) 1156 defer func() { s.r = nil }() 1157 } 1158 return s.promptUnsupported(prompt) 1159} 1160