Fix chat redraw and input history
This commit is contained in:
@@ -162,6 +162,7 @@ You can see the below keystrokes in your config file.
|
||||
Pressing tab inside the main window switches between the user/channel tree view and the message input box.
|
||||
When in the message input box:
|
||||
* left and right arrow keys move by character
|
||||
* up and down arrow keys move through previously submitted input
|
||||
* home/end moves to the beginning/end of the text respectively
|
||||
* enter submits the entered message
|
||||
|
||||
|
||||
+72
-1
@@ -17,6 +17,9 @@ type Textbox struct {
|
||||
active bool
|
||||
x0, y0, x1, y1 int
|
||||
pos int
|
||||
history []string
|
||||
historyIndex int
|
||||
historyDraft string
|
||||
}
|
||||
|
||||
func (t *Textbox) uiInitialize(ui *Ui) {
|
||||
@@ -100,14 +103,20 @@ func (t *Textbox) uiKeyEvent(key Key) {
|
||||
case KeyCtrlC:
|
||||
t.Text = ""
|
||||
t.pos = 0
|
||||
t.resetHistoryNavigation()
|
||||
redraw = true
|
||||
case KeyEnter:
|
||||
text := t.Text
|
||||
if t.Input != nil {
|
||||
t.Input(t.ui, t, t.Text)
|
||||
t.Input(t.ui, t, text)
|
||||
}
|
||||
t.addHistory(text)
|
||||
t.Text = ""
|
||||
t.pos = 0
|
||||
t.resetHistoryNavigation()
|
||||
redraw = true
|
||||
case KeyArrowUp, KeyAltArrowUp, KeyArrowDown, KeyAltArrowDown:
|
||||
redraw = t.handleHistoryKey(key)
|
||||
case KeySpace:
|
||||
t.uiCharacterEvent(' ')
|
||||
case KeyBackspace:
|
||||
@@ -136,3 +145,65 @@ func (t *Textbox) uiCharacterEvent(chr rune) {
|
||||
t.pos += len(s)
|
||||
t.uiDraw()
|
||||
}
|
||||
|
||||
func (t *Textbox) setText(text string) {
|
||||
t.Text = text
|
||||
t.pos = len(text)
|
||||
}
|
||||
|
||||
func (t *Textbox) addHistory(text string) {
|
||||
if strings.TrimSpace(text) == "" {
|
||||
return
|
||||
}
|
||||
if len(t.history) > 0 && t.history[len(t.history)-1] == text {
|
||||
t.historyIndex = len(t.history)
|
||||
return
|
||||
}
|
||||
t.history = append(t.history, text)
|
||||
t.historyIndex = len(t.history)
|
||||
}
|
||||
|
||||
func (t *Textbox) handleHistoryKey(key Key) bool {
|
||||
switch key {
|
||||
case KeyArrowUp, KeyAltArrowUp:
|
||||
return t.previousHistory()
|
||||
case KeyArrowDown, KeyAltArrowDown:
|
||||
return t.nextHistory()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Textbox) previousHistory() bool {
|
||||
if len(t.history) == 0 {
|
||||
return false
|
||||
}
|
||||
if t.historyIndex == len(t.history) {
|
||||
t.historyDraft = t.Text
|
||||
}
|
||||
if t.historyIndex > 0 {
|
||||
t.historyIndex--
|
||||
}
|
||||
t.setText(t.history[t.historyIndex])
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *Textbox) nextHistory() bool {
|
||||
if len(t.history) == 0 || t.historyIndex == len(t.history) {
|
||||
return false
|
||||
}
|
||||
if t.historyIndex < len(t.history)-1 {
|
||||
t.historyIndex++
|
||||
t.setText(t.history[t.historyIndex])
|
||||
return true
|
||||
}
|
||||
t.historyIndex = len(t.history)
|
||||
t.setText(t.historyDraft)
|
||||
t.historyDraft = ""
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *Textbox) resetHistoryNavigation() {
|
||||
t.historyIndex = len(t.history)
|
||||
t.historyDraft = ""
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package uiterm
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestTextboxHistoryNavigatesSubmittedText(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
textbox := Textbox{}
|
||||
textbox.addHistory("first")
|
||||
textbox.addHistory("second")
|
||||
|
||||
if !textbox.previousHistory() {
|
||||
t.Fatal("expected previous history item")
|
||||
}
|
||||
if textbox.Text != "second" {
|
||||
t.Fatalf("expected second history item, got %q", textbox.Text)
|
||||
}
|
||||
|
||||
if !textbox.previousHistory() {
|
||||
t.Fatal("expected older history item")
|
||||
}
|
||||
if textbox.Text != "first" {
|
||||
t.Fatalf("expected first history item, got %q", textbox.Text)
|
||||
}
|
||||
|
||||
if !textbox.nextHistory() {
|
||||
t.Fatal("expected newer history item")
|
||||
}
|
||||
if textbox.Text != "second" {
|
||||
t.Fatalf("expected second history item after next, got %q", textbox.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextboxHistoryNavigatesAltArrowKeys(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
textbox := Textbox{}
|
||||
textbox.addHistory("first")
|
||||
textbox.addHistory("second")
|
||||
|
||||
if redraw := textbox.handleHistoryKey(KeyAltArrowUp); !redraw {
|
||||
t.Fatal("expected alt-up to recall history")
|
||||
}
|
||||
if textbox.Text != "second" {
|
||||
t.Fatalf("expected second history item, got %q", textbox.Text)
|
||||
}
|
||||
|
||||
if redraw := textbox.handleHistoryKey(KeyAltArrowDown); !redraw {
|
||||
t.Fatal("expected alt-down to move forward through history")
|
||||
}
|
||||
if textbox.Text != "" {
|
||||
t.Fatalf("expected alt-down to restore empty draft, got %q", textbox.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextboxHistoryRestoresDraft(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
textbox := Textbox{Text: "draft"}
|
||||
textbox.addHistory("previous")
|
||||
textbox.setText("draft")
|
||||
|
||||
if !textbox.previousHistory() {
|
||||
t.Fatal("expected previous history item")
|
||||
}
|
||||
if textbox.Text != "previous" {
|
||||
t.Fatalf("expected previous history item, got %q", textbox.Text)
|
||||
}
|
||||
|
||||
if !textbox.nextHistory() {
|
||||
t.Fatal("expected history next to restore draft")
|
||||
}
|
||||
if textbox.Text != "draft" {
|
||||
t.Fatalf("expected draft to be restored, got %q", textbox.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextboxHistorySkipsBlankAndConsecutiveDuplicateEntries(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
textbox := Textbox{}
|
||||
textbox.addHistory("")
|
||||
textbox.addHistory(" ")
|
||||
textbox.addHistory("repeat")
|
||||
textbox.addHistory("repeat")
|
||||
|
||||
if len(textbox.history) != 1 {
|
||||
t.Fatalf("expected one history item, got %d", len(textbox.history))
|
||||
}
|
||||
if textbox.history[0] != "repeat" {
|
||||
t.Fatalf("expected repeat history item, got %q", textbox.history[0])
|
||||
}
|
||||
}
|
||||
+28
-5
@@ -77,6 +77,7 @@ func (t *Textview) updateParsedLines() {
|
||||
|
||||
if t.Lines == nil || width <= 0 {
|
||||
t.parsedLines = nil
|
||||
t.CurrentLine = 0
|
||||
return
|
||||
}
|
||||
|
||||
@@ -108,6 +109,7 @@ func (t *Textview) updateParsedLines() {
|
||||
}
|
||||
}
|
||||
t.parsedLines = parsed
|
||||
t.clampCurrentLine()
|
||||
}
|
||||
|
||||
func (t *Textview) AddLine(line string) {
|
||||
@@ -129,12 +131,9 @@ func (t *Textview) uiDraw() {
|
||||
|
||||
var reader *strings.Reader
|
||||
writeableLines := t.y1 - t.y0
|
||||
lineNum := 0
|
||||
if writeableLines < len(t.parsedLines) {
|
||||
lineNum = len(t.parsedLines) - writeableLines
|
||||
}
|
||||
lineNum := t.visibleStartLine(writeableLines)
|
||||
//Beep()
|
||||
for y := t.y0; y < writeableLines; y++ {
|
||||
for y := t.y0; y < t.y1; y++ {
|
||||
if lineNum < len(t.parsedLines) {
|
||||
reader = strings.NewReader(t.parsedLines[lineNum])
|
||||
} else {
|
||||
@@ -153,6 +152,30 @@ func (t *Textview) uiDraw() {
|
||||
} //each y
|
||||
} //func
|
||||
|
||||
func (t *Textview) visibleStartLine(writeableLines int) int {
|
||||
if writeableLines <= 0 || len(t.parsedLines) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
bottomStart := len(t.parsedLines) - writeableLines
|
||||
if bottomStart < 0 {
|
||||
bottomStart = 0
|
||||
}
|
||||
start := bottomStart - t.CurrentLine
|
||||
if start < 0 {
|
||||
return 0
|
||||
}
|
||||
return start
|
||||
}
|
||||
|
||||
func (t *Textview) clampCurrentLine() {
|
||||
if len(t.parsedLines) == 0 {
|
||||
t.CurrentLine = 0
|
||||
return
|
||||
}
|
||||
t.CurrentLine = bounded(t.CurrentLine, 0, len(t.parsedLines)-1)
|
||||
}
|
||||
|
||||
func (t *Textview) uiKeyEvent(key Key) {
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package uiterm
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestTextviewVisibleStartLineShowsBottomWhenNotScrolled(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
textview := Textview{
|
||||
parsedLines: []string{"one", "two", "three", "four"},
|
||||
}
|
||||
|
||||
if got := textview.visibleStartLine(3); got != 1 {
|
||||
t.Fatalf("expected visible lines to start at 1, got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextviewVisibleStartLineHonorsScrollOffset(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
textview := Textview{
|
||||
CurrentLine: 2,
|
||||
parsedLines: []string{"one", "two", "three", "four", "five"},
|
||||
}
|
||||
|
||||
if got := textview.visibleStartLine(3); got != 0 {
|
||||
t.Fatalf("expected scrolled view to start at 0, got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextviewUpdateParsedLinesClampsScrollOffset(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
textview := Textview{
|
||||
CurrentLine: 10,
|
||||
Lines: []string{"one", "two"},
|
||||
showTimestamps: true,
|
||||
x0: 0,
|
||||
x1: 20,
|
||||
}
|
||||
|
||||
textview.updateParsedLines()
|
||||
|
||||
if textview.CurrentLine != 1 {
|
||||
t.Fatalf("expected current line to clamp to 1, got %d", textview.CurrentLine)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user