| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | package readline | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"bufio" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"io" | 
					
						
							|  |  |  | 	"os" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type Prompt struct { | 
					
						
							|  |  |  | 	Prompt         string | 
					
						
							|  |  |  | 	AltPrompt      string | 
					
						
							|  |  |  | 	Placeholder    string | 
					
						
							|  |  |  | 	AltPlaceholder string | 
					
						
							|  |  |  | 	UseAlt         bool | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-06 08:19:37 +08:00
										 |  |  | func (p *Prompt) prompt() string { | 
					
						
							|  |  |  | 	if p.UseAlt { | 
					
						
							|  |  |  | 		return p.AltPrompt | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return p.Prompt | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (p *Prompt) placeholder() string { | 
					
						
							|  |  |  | 	if p.UseAlt { | 
					
						
							|  |  |  | 		return p.AltPlaceholder | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return p.Placeholder | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | type Terminal struct { | 
					
						
							|  |  |  | 	outchan chan rune | 
					
						
							| 
									
										
										
										
											2024-02-15 13:28:35 +08:00
										 |  |  | 	rawmode bool | 
					
						
							|  |  |  | 	termios any | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type Instance struct { | 
					
						
							|  |  |  | 	Prompt   *Prompt | 
					
						
							|  |  |  | 	Terminal *Terminal | 
					
						
							|  |  |  | 	History  *History | 
					
						
							| 
									
										
										
										
											2023-11-26 12:30:34 +08:00
										 |  |  | 	Pasting  bool | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func New(prompt Prompt) (*Instance, error) { | 
					
						
							|  |  |  | 	term, err := NewTerminal() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	history, err := NewHistory() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return &Instance{ | 
					
						
							|  |  |  | 		Prompt:   &prompt, | 
					
						
							|  |  |  | 		Terminal: term, | 
					
						
							|  |  |  | 		History:  history, | 
					
						
							|  |  |  | 	}, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (i *Instance) Readline() (string, error) { | 
					
						
							| 
									
										
										
										
											2024-02-15 13:28:35 +08:00
										 |  |  | 	if !i.Terminal.rawmode { | 
					
						
							| 
									
										
										
										
											2024-05-23 00:00:38 +08:00
										 |  |  | 		fd := os.Stdin.Fd() | 
					
						
							| 
									
										
										
										
											2024-02-15 13:28:35 +08:00
										 |  |  | 		termios, err := SetRawMode(fd) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return "", err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		i.Terminal.rawmode = true | 
					
						
							|  |  |  | 		i.Terminal.termios = termios | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-06 08:19:37 +08:00
										 |  |  | 	prompt := i.Prompt.prompt() | 
					
						
							|  |  |  | 	if i.Pasting { | 
					
						
							|  |  |  | 		// force alt prompt when pasting
 | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 		prompt = i.Prompt.AltPrompt | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-10-28 11:01:48 +08:00
										 |  |  | 	fmt.Print(prompt) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-15 13:28:35 +08:00
										 |  |  | 	defer func() { | 
					
						
							| 
									
										
										
										
											2024-05-23 00:00:38 +08:00
										 |  |  | 		fd := os.Stdin.Fd() | 
					
						
							| 
									
										
										
										
											2024-05-22 12:52:20 +08:00
										 |  |  | 		//nolint:errcheck
 | 
					
						
							| 
									
										
										
										
											2024-02-15 13:28:35 +08:00
										 |  |  | 		UnsetRawMode(fd, i.Terminal.termios) | 
					
						
							|  |  |  | 		i.Terminal.rawmode = false | 
					
						
							|  |  |  | 	}() | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	buf, _ := NewBuffer(i.Prompt) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var esc bool | 
					
						
							|  |  |  | 	var escex bool | 
					
						
							|  |  |  | 	var metaDel bool | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var currentLineBuf []rune | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for { | 
					
						
							| 
									
										
										
										
											2023-11-26 12:30:34 +08:00
										 |  |  | 		// don't show placeholder when pasting unless we're in multiline mode
 | 
					
						
							|  |  |  | 		showPlaceholder := !i.Pasting || i.Prompt.UseAlt | 
					
						
							|  |  |  | 		if buf.IsEmpty() && showPlaceholder { | 
					
						
							| 
									
										
										
										
											2024-01-06 08:19:37 +08:00
										 |  |  | 			ph := i.Prompt.placeholder() | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 			fmt.Printf(ColorGrey + ph + fmt.Sprintf(CursorLeftN, len(ph)) + ColorDefault) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-28 11:01:48 +08:00
										 |  |  | 		r, err := i.Terminal.Read() | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-28 11:01:48 +08:00
										 |  |  | 		if buf.IsEmpty() { | 
					
						
							|  |  |  | 			fmt.Print(ClearToEOL) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-28 12:26:23 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return "", io.EOF | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 		if escex { | 
					
						
							|  |  |  | 			escex = false | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			switch r { | 
					
						
							|  |  |  | 			case KeyUp: | 
					
						
							|  |  |  | 				if i.History.Pos > 0 { | 
					
						
							|  |  |  | 					if i.History.Pos == i.History.Size() { | 
					
						
							|  |  |  | 						currentLineBuf = []rune(buf.String()) | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					buf.Replace(i.History.Prev()) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			case KeyDown: | 
					
						
							|  |  |  | 				if i.History.Pos < i.History.Size() { | 
					
						
							|  |  |  | 					buf.Replace(i.History.Next()) | 
					
						
							|  |  |  | 					if i.History.Pos == i.History.Size() { | 
					
						
							|  |  |  | 						buf.Replace(currentLineBuf) | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			case KeyLeft: | 
					
						
							|  |  |  | 				buf.MoveLeft() | 
					
						
							|  |  |  | 			case KeyRight: | 
					
						
							|  |  |  | 				buf.MoveRight() | 
					
						
							|  |  |  | 			case CharBracketedPaste: | 
					
						
							| 
									
										
										
										
											2023-10-27 06:57:00 +08:00
										 |  |  | 				var code string | 
					
						
							| 
									
										
										
										
											2024-05-22 13:21:04 +08:00
										 |  |  | 				for range 3 { | 
					
						
							| 
									
										
										
										
											2023-10-28 11:01:48 +08:00
										 |  |  | 					r, err = i.Terminal.Read() | 
					
						
							|  |  |  | 					if err != nil { | 
					
						
							|  |  |  | 						return "", io.EOF | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-27 06:57:00 +08:00
										 |  |  | 					code += string(r) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				if code == CharBracketedPasteStart { | 
					
						
							| 
									
										
										
										
											2023-11-26 12:30:34 +08:00
										 |  |  | 					i.Pasting = true | 
					
						
							| 
									
										
										
										
											2023-10-27 06:57:00 +08:00
										 |  |  | 				} else if code == CharBracketedPasteEnd { | 
					
						
							| 
									
										
										
										
											2023-11-26 12:30:34 +08:00
										 |  |  | 					i.Pasting = false | 
					
						
							| 
									
										
										
										
											2023-10-27 06:57:00 +08:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 			case KeyDel: | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 				if buf.DisplaySize() > 0 { | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 					buf.Delete() | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				metaDel = true | 
					
						
							|  |  |  | 			case MetaStart: | 
					
						
							|  |  |  | 				buf.MoveToStart() | 
					
						
							|  |  |  | 			case MetaEnd: | 
					
						
							|  |  |  | 				buf.MoveToEnd() | 
					
						
							|  |  |  | 			default: | 
					
						
							|  |  |  | 				// skip any keys we don't know about
 | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} else if esc { | 
					
						
							|  |  |  | 			esc = false | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			switch r { | 
					
						
							|  |  |  | 			case 'b': | 
					
						
							|  |  |  | 				buf.MoveLeftWord() | 
					
						
							|  |  |  | 			case 'f': | 
					
						
							|  |  |  | 				buf.MoveRightWord() | 
					
						
							| 
									
										
										
										
											2023-11-22 04:26:47 +08:00
										 |  |  | 			case CharBackspace: | 
					
						
							|  |  |  | 				buf.DeleteWord() | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 			case CharEscapeEx: | 
					
						
							|  |  |  | 				escex = true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		switch r { | 
					
						
							| 
									
										
										
										
											2023-10-28 11:01:48 +08:00
										 |  |  | 		case CharNull: | 
					
						
							|  |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 		case CharEsc: | 
					
						
							|  |  |  | 			esc = true | 
					
						
							|  |  |  | 		case CharInterrupt: | 
					
						
							|  |  |  | 			return "", ErrInterrupt | 
					
						
							|  |  |  | 		case CharLineStart: | 
					
						
							|  |  |  | 			buf.MoveToStart() | 
					
						
							|  |  |  | 		case CharLineEnd: | 
					
						
							|  |  |  | 			buf.MoveToEnd() | 
					
						
							|  |  |  | 		case CharBackward: | 
					
						
							|  |  |  | 			buf.MoveLeft() | 
					
						
							|  |  |  | 		case CharForward: | 
					
						
							|  |  |  | 			buf.MoveRight() | 
					
						
							|  |  |  | 		case CharBackspace, CharCtrlH: | 
					
						
							|  |  |  | 			buf.Remove() | 
					
						
							|  |  |  | 		case CharTab: | 
					
						
							|  |  |  | 			// todo: convert back to real tabs
 | 
					
						
							| 
									
										
										
										
											2024-05-22 13:21:04 +08:00
										 |  |  | 			for range 8 { | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 				buf.Add(' ') | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case CharDelete: | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 			if buf.DisplaySize() > 0 { | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 				buf.Delete() | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				return "", io.EOF | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case CharKill: | 
					
						
							|  |  |  | 			buf.DeleteRemaining() | 
					
						
							|  |  |  | 		case CharCtrlU: | 
					
						
							|  |  |  | 			buf.DeleteBefore() | 
					
						
							|  |  |  | 		case CharCtrlL: | 
					
						
							|  |  |  | 			buf.ClearScreen() | 
					
						
							|  |  |  | 		case CharCtrlW: | 
					
						
							|  |  |  | 			buf.DeleteWord() | 
					
						
							| 
									
										
										
										
											2023-12-02 08:04:09 +08:00
										 |  |  | 		case CharCtrlZ: | 
					
						
							| 
									
										
										
										
											2024-05-23 00:00:38 +08:00
										 |  |  | 			fd := os.Stdin.Fd() | 
					
						
							| 
									
										
										
										
											2024-02-15 13:28:35 +08:00
										 |  |  | 			return handleCharCtrlZ(fd, i.Terminal.termios) | 
					
						
							| 
									
										
										
										
											2024-05-08 06:26:07 +08:00
										 |  |  | 		case CharEnter, CharCtrlJ: | 
					
						
							| 
									
										
										
										
											2023-10-27 06:57:00 +08:00
										 |  |  | 			output := buf.String() | 
					
						
							|  |  |  | 			if output != "" { | 
					
						
							|  |  |  | 				i.History.Add([]rune(output)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			buf.MoveToEnd() | 
					
						
							|  |  |  | 			fmt.Println() | 
					
						
							| 
									
										
										
										
											2023-11-26 12:30:34 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-27 06:57:00 +08:00
										 |  |  | 			return output, nil | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 		default: | 
					
						
							|  |  |  | 			if metaDel { | 
					
						
							|  |  |  | 				metaDel = false | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2024-05-08 06:26:07 +08:00
										 |  |  | 			if r >= CharSpace || r == CharEnter || r == CharCtrlJ { | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 				buf.Add(r) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (i *Instance) HistoryEnable() { | 
					
						
							|  |  |  | 	i.History.Enabled = true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (i *Instance) HistoryDisable() { | 
					
						
							|  |  |  | 	i.History.Enabled = false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func NewTerminal() (*Terminal, error) { | 
					
						
							| 
									
										
										
										
											2024-05-23 00:00:38 +08:00
										 |  |  | 	fd := os.Stdin.Fd() | 
					
						
							| 
									
										
										
										
											2024-02-15 13:28:35 +08:00
										 |  |  | 	termios, err := SetRawMode(fd) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	t := &Terminal{ | 
					
						
							|  |  |  | 		outchan: make(chan rune), | 
					
						
							| 
									
										
										
										
											2024-02-15 13:28:35 +08:00
										 |  |  | 		rawmode: true, | 
					
						
							|  |  |  | 		termios: termios, | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	go t.ioloop() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return t, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t *Terminal) ioloop() { | 
					
						
							|  |  |  | 	buf := bufio.NewReader(os.Stdin) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		r, _, err := buf.ReadRune() | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2023-10-28 11:26:04 +08:00
										 |  |  | 			close(t.outchan) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		t.outchan <- r | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-28 11:01:48 +08:00
										 |  |  | func (t *Terminal) Read() (rune, error) { | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	r, ok := <-t.outchan | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							| 
									
										
										
										
											2023-10-28 11:01:48 +08:00
										 |  |  | 		return 0, io.EOF | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-10-28 11:01:48 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return r, nil | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | } |