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