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