| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | package readline | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2023-10-31 04:18:12 +08:00
										 |  |  | 	"os" | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 	"github.com/emirpasic/gods/v2/lists/arraylist" | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 	"github.com/mattn/go-runewidth" | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	"golang.org/x/term" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type Buffer struct { | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 	DisplayPos int | 
					
						
							|  |  |  | 	Pos        int | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 	Buf        *arraylist.List[rune] | 
					
						
							| 
									
										
										
										
											2024-08-02 05:52:15 +08:00
										 |  |  | 	// LineHasSpace is an arraylist of bools to keep track of whether a line has a space at the end
 | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 	LineHasSpace *arraylist.List[bool] | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 	Prompt       *Prompt | 
					
						
							|  |  |  | 	LineWidth    int | 
					
						
							|  |  |  | 	Width        int | 
					
						
							|  |  |  | 	Height       int | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func NewBuffer(prompt *Prompt) (*Buffer, error) { | 
					
						
							| 
									
										
										
										
											2023-10-31 04:18:12 +08:00
										 |  |  | 	fd := int(os.Stdout.Fd()) | 
					
						
							| 
									
										
										
										
											2024-03-08 03:28:41 +08:00
										 |  |  | 	width, height := 80, 24 | 
					
						
							|  |  |  | 	if termWidth, termHeight, err := term.GetSize(fd); err == nil { | 
					
						
							|  |  |  | 		width, height = termWidth, termHeight | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-06 08:19:37 +08:00
										 |  |  | 	lwidth := width - len(prompt.prompt()) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	b := &Buffer{ | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 		DisplayPos:   0, | 
					
						
							|  |  |  | 		Pos:          0, | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 		Buf:          arraylist.New[rune](), | 
					
						
							|  |  |  | 		LineHasSpace: arraylist.New[bool](), | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 		Prompt:       prompt, | 
					
						
							|  |  |  | 		Width:        width, | 
					
						
							|  |  |  | 		Height:       height, | 
					
						
							|  |  |  | 		LineWidth:    lwidth, | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return b, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | func (b *Buffer) GetLineSpacing(line int) bool { | 
					
						
							|  |  |  | 	hasSpace, _ := b.LineHasSpace.Get(line) | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 	return hasSpace | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | func (b *Buffer) MoveLeft() { | 
					
						
							|  |  |  | 	if b.Pos > 0 { | 
					
						
							| 
									
										
										
										
											2024-08-02 05:52:15 +08:00
										 |  |  | 		// asserts that we retrieve a rune
 | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 		if r, ok := b.Buf.Get(b.Pos - 1); ok { | 
					
						
							|  |  |  | 			rLength := runewidth.RuneWidth(r) | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 			if b.DisplayPos%b.LineWidth == 0 { | 
					
						
							|  |  |  | 				fmt.Print(CursorUp + CursorBOL + CursorRightN(b.Width)) | 
					
						
							|  |  |  | 				if rLength == 2 { | 
					
						
							|  |  |  | 					fmt.Print(CursorLeft) | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 				line := b.DisplayPos/b.LineWidth - 1 | 
					
						
							|  |  |  | 				hasSpace := b.GetLineSpacing(line) | 
					
						
							|  |  |  | 				if hasSpace { | 
					
						
							|  |  |  | 					b.DisplayPos -= 1 | 
					
						
							|  |  |  | 					fmt.Print(CursorLeft) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				fmt.Print(CursorLeftN(rLength)) | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			b.Pos -= 1 | 
					
						
							|  |  |  | 			b.DisplayPos -= rLength | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (b *Buffer) MoveLeftWord() { | 
					
						
							|  |  |  | 	if b.Pos > 0 { | 
					
						
							|  |  |  | 		var foundNonspace bool | 
					
						
							|  |  |  | 		for { | 
					
						
							|  |  |  | 			v, _ := b.Buf.Get(b.Pos - 1) | 
					
						
							|  |  |  | 			if v == ' ' { | 
					
						
							|  |  |  | 				if foundNonspace { | 
					
						
							|  |  |  | 					break | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				foundNonspace = true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			b.MoveLeft() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if b.Pos == 0 { | 
					
						
							|  |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (b *Buffer) MoveRight() { | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 	if b.Pos < b.Buf.Size() { | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 		if r, ok := b.Buf.Get(b.Pos); ok { | 
					
						
							|  |  |  | 			rLength := runewidth.RuneWidth(r) | 
					
						
							|  |  |  | 			b.Pos += 1 | 
					
						
							|  |  |  | 			hasSpace := b.GetLineSpacing(b.DisplayPos / b.LineWidth) | 
					
						
							|  |  |  | 			b.DisplayPos += rLength | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if b.DisplayPos%b.LineWidth == 0 { | 
					
						
							|  |  |  | 				fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt()))) | 
					
						
							|  |  |  | 			} else if (b.DisplayPos-rLength)%b.LineWidth == b.LineWidth-1 && hasSpace { | 
					
						
							|  |  |  | 				fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt())+rLength)) | 
					
						
							|  |  |  | 				b.DisplayPos += 1 | 
					
						
							|  |  |  | 			} else if b.LineHasSpace.Size() > 0 && b.DisplayPos%b.LineWidth == b.LineWidth-1 && hasSpace { | 
					
						
							|  |  |  | 				fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt()))) | 
					
						
							|  |  |  | 				b.DisplayPos += 1 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				fmt.Print(CursorRightN(rLength)) | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (b *Buffer) MoveRightWord() { | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 	if b.Pos < b.Buf.Size() { | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 		for { | 
					
						
							|  |  |  | 			b.MoveRight() | 
					
						
							|  |  |  | 			v, _ := b.Buf.Get(b.Pos) | 
					
						
							|  |  |  | 			if v == ' ' { | 
					
						
							|  |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 			if b.Pos == b.Buf.Size() { | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (b *Buffer) MoveToStart() { | 
					
						
							|  |  |  | 	if b.Pos > 0 { | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 		currLine := b.DisplayPos / b.LineWidth | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 		if currLine > 0 { | 
					
						
							| 
									
										
										
										
											2024-05-22 13:21:04 +08:00
										 |  |  | 			for range currLine { | 
					
						
							| 
									
										
										
										
											2023-10-28 11:01:48 +08:00
										 |  |  | 				fmt.Print(CursorUp) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-08-14 04:40:37 +08:00
										 |  |  | 		fmt.Print(CursorBOL + CursorRightN(len(b.Prompt.prompt()))) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 		b.Pos = 0 | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 		b.DisplayPos = 0 | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (b *Buffer) MoveToEnd() { | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 	if b.Pos < b.Buf.Size() { | 
					
						
							|  |  |  | 		currLine := b.DisplayPos / b.LineWidth | 
					
						
							|  |  |  | 		totalLines := b.DisplaySize() / b.LineWidth | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 		if currLine < totalLines { | 
					
						
							| 
									
										
										
										
											2024-05-22 13:21:04 +08:00
										 |  |  | 			for range totalLines - currLine { | 
					
						
							| 
									
										
										
										
											2023-10-28 11:01:48 +08:00
										 |  |  | 				fmt.Print(CursorDown) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 			remainder := b.DisplaySize() % b.LineWidth | 
					
						
							| 
									
										
										
										
											2024-08-14 04:40:37 +08:00
										 |  |  | 			fmt.Print(CursorBOL + CursorRightN(len(b.Prompt.prompt())+remainder)) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2024-08-14 04:40:37 +08:00
										 |  |  | 			fmt.Print(CursorRightN(b.DisplaySize() - b.DisplayPos)) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 		b.Pos = b.Buf.Size() | 
					
						
							|  |  |  | 		b.DisplayPos = b.DisplaySize() | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | func (b *Buffer) DisplaySize() int { | 
					
						
							|  |  |  | 	sum := 0 | 
					
						
							| 
									
										
										
										
											2024-05-30 09:22:03 +08:00
										 |  |  | 	for i := range b.Buf.Size() { | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 		if r, ok := b.Buf.Get(i); ok { | 
					
						
							|  |  |  | 			sum += runewidth.RuneWidth(r) | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return sum | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (b *Buffer) Add(r rune) { | 
					
						
							|  |  |  | 	if b.Pos == b.Buf.Size() { | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 		b.AddChar(r, false) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		b.AddChar(r, true) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (b *Buffer) AddChar(r rune, insert bool) { | 
					
						
							|  |  |  | 	rLength := runewidth.RuneWidth(r) | 
					
						
							|  |  |  | 	b.DisplayPos += rLength | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if b.Pos > 0 { | 
					
						
							|  |  |  | 		if b.DisplayPos%b.LineWidth == 0 { | 
					
						
							|  |  |  | 			fmt.Printf("%c", r) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 			fmt.Printf("\n%s", b.Prompt.AltPrompt) | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			if insert { | 
					
						
							|  |  |  | 				b.LineHasSpace.Set(b.DisplayPos/b.LineWidth-1, false) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				b.LineHasSpace.Add(false) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// this case occurs when a double-width rune crosses the line boundary
 | 
					
						
							|  |  |  | 		} else if b.DisplayPos%b.LineWidth < (b.DisplayPos-rLength)%b.LineWidth { | 
					
						
							|  |  |  | 			if insert { | 
					
						
							|  |  |  | 				fmt.Print(ClearToEOL) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			fmt.Printf("\n%s", b.Prompt.AltPrompt) | 
					
						
							|  |  |  | 			b.DisplayPos += 1 | 
					
						
							|  |  |  | 			fmt.Printf("%c", r) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if insert { | 
					
						
							|  |  |  | 				b.LineHasSpace.Set(b.DisplayPos/b.LineWidth-1, true) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				b.LineHasSpace.Add(true) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			fmt.Printf("%c", r) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		fmt.Printf("%c", r) | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if insert { | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 		b.Buf.Insert(b.Pos, r) | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 	} else { | 
					
						
							|  |  |  | 		b.Buf.Add(r) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	b.Pos += 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if insert { | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 		b.drawRemaining() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | func (b *Buffer) countRemainingLineWidth(place int) int { | 
					
						
							|  |  |  | 	var sum int | 
					
						
							|  |  |  | 	counter := -1 | 
					
						
							|  |  |  | 	var prevLen int | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for place <= b.LineWidth { | 
					
						
							|  |  |  | 		counter += 1 | 
					
						
							|  |  |  | 		sum += prevLen | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 		if r, ok := b.Buf.Get(b.Pos + counter); ok { | 
					
						
							|  |  |  | 			place += runewidth.RuneWidth(r) | 
					
						
							|  |  |  | 			prevLen = len(string(r)) | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 		} else { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return sum | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | func (b *Buffer) drawRemaining() { | 
					
						
							|  |  |  | 	var place int | 
					
						
							|  |  |  | 	remainingText := b.StringN(b.Pos) | 
					
						
							|  |  |  | 	if b.Pos > 0 { | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 		place = b.DisplayPos % b.LineWidth | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-10-28 11:01:48 +08:00
										 |  |  | 	fmt.Print(CursorHide) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// render the rest of the current line
 | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 	currLineLength := b.countRemainingLineWidth(place) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	currLine := remainingText[:min(currLineLength, len(remainingText))] | 
					
						
							|  |  |  | 	currLineSpace := runewidth.StringWidth(currLine) | 
					
						
							|  |  |  | 	remLength := runewidth.StringWidth(remainingText) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	if len(currLine) > 0 { | 
					
						
							| 
									
										
										
										
											2024-08-14 04:40:37 +08:00
										 |  |  | 		fmt.Print(ClearToEOL + currLine + CursorLeftN(currLineSpace)) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2023-10-28 11:01:48 +08:00
										 |  |  | 		fmt.Print(ClearToEOL) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 	if currLineSpace != b.LineWidth-place && currLineSpace != remLength { | 
					
						
							|  |  |  | 		b.LineHasSpace.Set(b.DisplayPos/b.LineWidth, true) | 
					
						
							|  |  |  | 	} else if currLineSpace != b.LineWidth-place { | 
					
						
							|  |  |  | 		b.LineHasSpace.Remove(b.DisplayPos / b.LineWidth) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		b.LineHasSpace.Set(b.DisplayPos/b.LineWidth, false) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (b.DisplayPos+currLineSpace)%b.LineWidth == 0 && currLine == remainingText { | 
					
						
							| 
									
										
										
										
											2024-08-14 04:40:37 +08:00
										 |  |  | 		fmt.Print(CursorRightN(currLineSpace)) | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 		fmt.Printf("\n%s", b.Prompt.AltPrompt) | 
					
						
							| 
									
										
										
										
											2024-08-14 04:40:37 +08:00
										 |  |  | 		fmt.Print(CursorUp + CursorBOL + CursorRightN(b.Width-currLineSpace)) | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	// render the other lines
 | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 	if remLength > currLineSpace { | 
					
						
							|  |  |  | 		remaining := (remainingText[len(currLine):]) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 		var totalLines int | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 		var displayLength int | 
					
						
							|  |  |  | 		var lineLength int = currLineSpace | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for _, c := range remaining { | 
					
						
							|  |  |  | 			if displayLength == 0 || (displayLength+runewidth.RuneWidth(c))%b.LineWidth < displayLength%b.LineWidth { | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 				fmt.Printf("\n%s", b.Prompt.AltPrompt) | 
					
						
							|  |  |  | 				totalLines += 1 | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				if displayLength != 0 { | 
					
						
							|  |  |  | 					if lineLength == b.LineWidth { | 
					
						
							|  |  |  | 						b.LineHasSpace.Set(b.DisplayPos/b.LineWidth+totalLines-1, false) | 
					
						
							|  |  |  | 					} else { | 
					
						
							|  |  |  | 						b.LineHasSpace.Set(b.DisplayPos/b.LineWidth+totalLines-1, true) | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				lineLength = 0 | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			displayLength += runewidth.RuneWidth(c) | 
					
						
							|  |  |  | 			lineLength += runewidth.RuneWidth(c) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 			fmt.Printf("%c", c) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-08-14 04:40:37 +08:00
										 |  |  | 		fmt.Print(ClearToEOL + CursorUpN(totalLines) + CursorBOL + CursorRightN(b.Width-currLineSpace)) | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		hasSpace := b.GetLineSpacing(b.DisplayPos / b.LineWidth) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if hasSpace && b.DisplayPos%b.LineWidth != b.LineWidth-1 { | 
					
						
							|  |  |  | 			fmt.Print(CursorLeft) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-28 11:01:48 +08:00
										 |  |  | 	fmt.Print(CursorShow) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (b *Buffer) Remove() { | 
					
						
							|  |  |  | 	if b.Buf.Size() > 0 && b.Pos > 0 { | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 		if r, ok := b.Buf.Get(b.Pos - 1); ok { | 
					
						
							|  |  |  | 			rLength := runewidth.RuneWidth(r) | 
					
						
							|  |  |  | 			hasSpace := b.GetLineSpacing(b.DisplayPos/b.LineWidth - 1) | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 			if b.DisplayPos%b.LineWidth == 0 { | 
					
						
							|  |  |  | 				// if the user backspaces over the word boundary, do this magic to clear the line
 | 
					
						
							|  |  |  | 				// and move to the end of the previous line
 | 
					
						
							|  |  |  | 				fmt.Print(CursorBOL + ClearToEOL + CursorUp + CursorBOL + CursorRightN(b.Width)) | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 				if b.DisplaySize()%b.LineWidth < (b.DisplaySize()-rLength)%b.LineWidth { | 
					
						
							|  |  |  | 					b.LineHasSpace.Remove(b.DisplayPos/b.LineWidth - 1) | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 				if hasSpace { | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 					b.DisplayPos -= 1 | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 					fmt.Print(CursorLeft) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if rLength == 2 { | 
					
						
							|  |  |  | 					fmt.Print(CursorLeft + "  " + CursorLeftN(2)) | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 				} else { | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 					fmt.Print(" " + CursorLeft) | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 			} else if (b.DisplayPos-rLength)%b.LineWidth == 0 && hasSpace { | 
					
						
							|  |  |  | 				fmt.Print(CursorBOL + ClearToEOL + CursorUp + CursorBOL + CursorRightN(b.Width)) | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 				if b.Pos == b.Buf.Size() { | 
					
						
							|  |  |  | 					b.LineHasSpace.Remove(b.DisplayPos/b.LineWidth - 1) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				b.DisplayPos -= 1 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				fmt.Print(CursorLeftN(rLength)) | 
					
						
							|  |  |  | 				for range rLength { | 
					
						
							|  |  |  | 					fmt.Print(" ") | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 				fmt.Print(CursorLeftN(rLength)) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 			var eraseExtraLine bool | 
					
						
							|  |  |  | 			if (b.DisplaySize()-1)%b.LineWidth == 0 || (rLength == 2 && ((b.DisplaySize()-2)%b.LineWidth == 0)) || b.DisplaySize()%b.LineWidth == 0 { | 
					
						
							|  |  |  | 				eraseExtraLine = true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			b.Pos -= 1 | 
					
						
							|  |  |  | 			b.DisplayPos -= rLength | 
					
						
							|  |  |  | 			b.Buf.Remove(b.Pos) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if b.Pos < b.Buf.Size() { | 
					
						
							|  |  |  | 				b.drawRemaining() | 
					
						
							|  |  |  | 				// this erases a line which is left over when backspacing in the middle of a line and there
 | 
					
						
							|  |  |  | 				// are trailing characters which go over the line width boundary
 | 
					
						
							|  |  |  | 				if eraseExtraLine { | 
					
						
							|  |  |  | 					remainingLines := (b.DisplaySize() - b.DisplayPos) / b.LineWidth | 
					
						
							|  |  |  | 					fmt.Print(CursorDownN(remainingLines+1) + CursorBOL + ClearToEOL) | 
					
						
							|  |  |  | 					place := b.DisplayPos % b.LineWidth | 
					
						
							|  |  |  | 					fmt.Print(CursorUpN(remainingLines+1) + CursorRightN(place+len(b.Prompt.prompt()))) | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (b *Buffer) Delete() { | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 	if b.Buf.Size() > 0 && b.Pos < b.Buf.Size() { | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 		b.Buf.Remove(b.Pos) | 
					
						
							|  |  |  | 		b.drawRemaining() | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 		if b.DisplaySize()%b.LineWidth == 0 { | 
					
						
							|  |  |  | 			if b.DisplayPos != b.DisplaySize() { | 
					
						
							|  |  |  | 				remainingLines := (b.DisplaySize() - b.DisplayPos) / b.LineWidth | 
					
						
							| 
									
										
										
										
											2024-08-14 04:40:37 +08:00
										 |  |  | 				fmt.Print(CursorDownN(remainingLines) + CursorBOL + ClearToEOL) | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 				place := b.DisplayPos % b.LineWidth | 
					
						
							| 
									
										
										
										
											2024-08-14 04:40:37 +08:00
										 |  |  | 				fmt.Print(CursorUpN(remainingLines) + CursorRightN(place+len(b.Prompt.prompt()))) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (b *Buffer) DeleteBefore() { | 
					
						
							|  |  |  | 	if b.Pos > 0 { | 
					
						
							|  |  |  | 		for cnt := b.Pos - 1; cnt >= 0; cnt-- { | 
					
						
							|  |  |  | 			b.Remove() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (b *Buffer) DeleteRemaining() { | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 	if b.DisplaySize() > 0 && b.Pos < b.DisplaySize() { | 
					
						
							|  |  |  | 		charsToDel := b.Buf.Size() - b.Pos | 
					
						
							| 
									
										
										
										
											2024-05-22 13:21:04 +08:00
										 |  |  | 		for range charsToDel { | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 			b.Delete() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (b *Buffer) DeleteWord() { | 
					
						
							|  |  |  | 	if b.Buf.Size() > 0 && b.Pos > 0 { | 
					
						
							|  |  |  | 		var foundNonspace bool | 
					
						
							|  |  |  | 		for { | 
					
						
							|  |  |  | 			v, _ := b.Buf.Get(b.Pos - 1) | 
					
						
							|  |  |  | 			if v == ' ' { | 
					
						
							|  |  |  | 				if !foundNonspace { | 
					
						
							|  |  |  | 					b.Remove() | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					break | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				foundNonspace = true | 
					
						
							|  |  |  | 				b.Remove() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if b.Pos == 0 { | 
					
						
							|  |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (b *Buffer) ClearScreen() { | 
					
						
							| 
									
										
										
										
											2024-08-14 04:40:37 +08:00
										 |  |  | 	fmt.Print(ClearScreen + CursorReset + b.Prompt.prompt()) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	if b.IsEmpty() { | 
					
						
							| 
									
										
										
										
											2024-01-06 08:19:37 +08:00
										 |  |  | 		ph := b.Prompt.placeholder() | 
					
						
							| 
									
										
										
										
											2024-08-14 04:40:37 +08:00
										 |  |  | 		fmt.Print(ColorGrey + ph + CursorLeftN(len(ph)) + ColorDefault) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 		currPos := b.DisplayPos | 
					
						
							|  |  |  | 		currIndex := b.Pos | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 		b.Pos = 0 | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 		b.DisplayPos = 0 | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 		b.drawRemaining() | 
					
						
							| 
									
										
										
										
											2024-08-14 04:40:37 +08:00
										 |  |  | 		fmt.Print(CursorReset + CursorRightN(len(b.Prompt.prompt()))) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 		if currPos > 0 { | 
					
						
							|  |  |  | 			targetLine := currPos / b.LineWidth | 
					
						
							|  |  |  | 			if targetLine > 0 { | 
					
						
							| 
									
										
										
										
											2024-05-22 13:21:04 +08:00
										 |  |  | 				for range targetLine { | 
					
						
							| 
									
										
										
										
											2023-10-28 11:01:48 +08:00
										 |  |  | 					fmt.Print(CursorDown) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			remainder := currPos % b.LineWidth | 
					
						
							|  |  |  | 			if remainder > 0 { | 
					
						
							| 
									
										
										
										
											2024-08-14 04:40:37 +08:00
										 |  |  | 				fmt.Print(CursorRightN(remainder)) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 			if currPos%b.LineWidth == 0 { | 
					
						
							| 
									
										
										
										
											2024-08-14 04:40:37 +08:00
										 |  |  | 				fmt.Print(CursorBOL + b.Prompt.AltPrompt) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 		b.Pos = currIndex | 
					
						
							|  |  |  | 		b.DisplayPos = currPos | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (b *Buffer) IsEmpty() bool { | 
					
						
							|  |  |  | 	return b.Buf.Empty() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (b *Buffer) Replace(r []rune) { | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 	b.DisplayPos = 0 | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	b.Pos = 0 | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 	lineNums := b.DisplaySize() / b.LineWidth | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	b.Buf.Clear() | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-14 04:40:37 +08:00
										 |  |  | 	fmt.Print(CursorBOL + ClearToEOL) | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-30 09:22:03 +08:00
										 |  |  | 	for range lineNums { | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 		fmt.Print(CursorUp + CursorBOL + ClearToEOL) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-14 04:40:37 +08:00
										 |  |  | 	fmt.Print(CursorBOL + b.Prompt.prompt()) | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	for _, c := range r { | 
					
						
							|  |  |  | 		b.Add(c) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (b *Buffer) String() string { | 
					
						
							|  |  |  | 	return b.StringN(0) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (b *Buffer) StringN(n int) string { | 
					
						
							|  |  |  | 	return b.StringNM(n, 0) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (b *Buffer) StringNM(n, m int) string { | 
					
						
							|  |  |  | 	var s string | 
					
						
							|  |  |  | 	if m == 0 { | 
					
						
							| 
									
										
										
										
											2024-05-29 03:04:03 +08:00
										 |  |  | 		m = b.Buf.Size() | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	for cnt := n; cnt < m; cnt++ { | 
					
						
							|  |  |  | 		c, _ := b.Buf.Get(cnt) | 
					
						
							| 
									
										
										
										
											2024-12-21 16:02:50 +08:00
										 |  |  | 		s += string(c) | 
					
						
							| 
									
										
										
										
											2023-10-26 07:41:18 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return s | 
					
						
							|  |  |  | } |