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