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