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