Keep neoirc-cli responding when a key or a typed line updates the screen (closes #223)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
The Enter handler of the input line and the Alt and digit key handler ran the typed line on the tview event loop. Every UI method that changes the screen waits for that loop, so the client stopped responding. Each line now runs on its own goroutine, which first waits for the line before it, so lines keep the order they were typed in. /window now runs off the event loop. It reads the buffer name through the new BufferName, which runs on the loop, and switches only when that buffer exists. One new test drives a running UI on a simulation screen with Alt and 1, then a typed line and Enter. Another holds the first line and checks that the second waits for it. Model: opus-5-5
This commit is contained in:
+53
-11
@@ -16,7 +16,10 @@ type Buffer struct {
|
||||
Unread int
|
||||
}
|
||||
|
||||
// UI manages the terminal interface.
|
||||
// UI manages the terminal interface. Most of its methods wait for
|
||||
// the tview event loop to run their update. Code that runs on that
|
||||
// loop, such as a key handler, must not call them: it would wait
|
||||
// for itself.
|
||||
type UI struct {
|
||||
app *tview.Application
|
||||
messages *tview.TextView
|
||||
@@ -28,6 +31,10 @@ type UI struct {
|
||||
currentBuffer int
|
||||
|
||||
onInput func(string)
|
||||
// inputDone is closed once the line last passed to onInput
|
||||
// has been handled. Only callOnInput, on the event loop, uses
|
||||
// it.
|
||||
inputDone chan struct{}
|
||||
}
|
||||
|
||||
// NewUI creates the tview-based IRC-like UI.
|
||||
@@ -176,9 +183,24 @@ func (ui *UI) SetStatus(
|
||||
})
|
||||
}
|
||||
|
||||
// BufferCount returns the number of buffers.
|
||||
func (ui *UI) BufferCount() int {
|
||||
return len(ui.buffers)
|
||||
// BufferName returns the name of the buffer at index bufIndex,
|
||||
// and whether there is such a buffer.
|
||||
func (ui *UI) BufferName(bufIndex int) (string, bool) {
|
||||
var (
|
||||
name string
|
||||
found bool
|
||||
)
|
||||
|
||||
ui.app.QueueUpdate(func() {
|
||||
if bufIndex < 0 || bufIndex >= len(ui.buffers) {
|
||||
return
|
||||
}
|
||||
|
||||
name = ui.buffers[bufIndex].Name
|
||||
found = true
|
||||
})
|
||||
|
||||
return name, found
|
||||
}
|
||||
|
||||
// BufferIndex returns the index of a named buffer.
|
||||
@@ -226,10 +248,7 @@ func (ui *UI) initInput() {
|
||||
}
|
||||
|
||||
ui.input.SetText("")
|
||||
|
||||
if ui.onInput != nil {
|
||||
ui.onInput(text)
|
||||
}
|
||||
ui.callOnInput(text)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -244,9 +263,7 @@ func (ui *UI) initKeyCapture() {
|
||||
// SwitchBuffer alone would leave the target as it was.
|
||||
r := event.Rune()
|
||||
if r >= '0' && r <= '9' {
|
||||
if ui.onInput != nil {
|
||||
ui.onInput("/window " + string(r))
|
||||
}
|
||||
ui.callOnInput("/window " + string(r))
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -256,6 +273,31 @@ func (ui *UI) initKeyCapture() {
|
||||
)
|
||||
}
|
||||
|
||||
// callOnInput passes text to the input callback on a new
|
||||
// goroutine. The key handlers call it on the event loop, and the
|
||||
// callback waits for that loop to update the screen, so they must
|
||||
// not wait for the callback. The goroutine first waits for the
|
||||
// line before it to be handled, which keeps lines in the order
|
||||
// they were typed.
|
||||
func (ui *UI) callOnInput(text string) {
|
||||
if ui.onInput == nil {
|
||||
return
|
||||
}
|
||||
|
||||
previous := ui.inputDone
|
||||
done := make(chan struct{})
|
||||
ui.inputDone = done
|
||||
|
||||
go func() {
|
||||
if previous != nil {
|
||||
<-previous
|
||||
}
|
||||
|
||||
ui.onInput(text)
|
||||
close(done)
|
||||
}()
|
||||
}
|
||||
|
||||
func (ui *UI) refreshStatusBar() {
|
||||
// Placeholder; full refresh needs nick/target context.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user