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