ishell
ishell copied to clipboard
Wrong output from shell.Println when receiving from a goroutine
I am trying to print some output from a channel to the shell. When receiving from a goroutine it prints without a newline and does not give the prompt back. Example:
package main
import (
"time"
"github.com/abiosoft/ishell"
)
func feed(c chan string) {
for i := 0; i<3; i++ {
c <- "Feedback"
time.Sleep(time.Second)
}
}
func main(){
shell := ishell.New()
c := make(chan string)
// listen
go func() {
for msg := range(c) {
shell.Println(msg)
}
}()
// this does output correctly
//feed(c)
// this does not
go feed(c)
shell.Start()
}
go feed(c)
outputs:
>>> Feedback
Feedback
Feedback
To get the prompt back you have to type a letter
feed(c)
outputs correctly:
Feedback
Feedback
Feedback
>>>
Ideally, shell must have started before prompt shows up. And that's why feed(c)
outputs before the prompt.
Just to be clear, what is the expected behaviour ?
I want to permanently listen to a channel that will print some info in the shell from time to time.
package main
import (
"time"
"github.com/abiosoft/ishell"
)
func feed(c chan string) {
time.Sleep(2*time.Second)
for i := 0; i<3; i++ {
c <- "Feedback"
time.Sleep(time.Second)
}
}
func main(){
shell := ishell.New()
c := make(chan string)
shell.AddCmd(&ishell.Cmd{
Name: "hi",
Help: "hello",
Func: func(ctx *ishell.Context) {
ctx.Println("Hello")
go feed(c)
},
})
// listen
go func() {
for msg := range(c) {
shell.Println(msg)
}
}()
shell.Start()
}
This outputs:
>>> hi
Hello
>>> Feedback
Feedback
Feedback
The desired behavior is:
>>> hi
Hello
>>>
Feedback
Feedback
Feedback
>>>
Ideally I would like to have a shell print channel to where I can throw output in from goroutines
You think a call to Println
should not print on the same line as the prompt ?
Yes: if it can do the job it would be fine. It should preserve the text being typed:
>>> typing someth
Feedback
>>> typing someth
I would not totally agree with always printing on the next line as the password prompt in the example shows a use case. https://github.com/abiosoft/ishell/blob/master/example/main.go#L118.
I will try to find a middle ground and will also try to make it safe to have calls to Print
from multiple goroutines.
Hi, any progress on this? Do you have plans for resolving this?
It could benefit to my user case: a client that permanently receives some messages and print them to the screen. The fact that printing them with ctx.Println
messes up with the prompt make it uncomfortable to use when lots of messages are coming in.
I tried putting together a chat-style app and had real problems trying to print an incoming message without interrupting the prompt. Any in-progress input got printed multiple times in the terminal and it wasn't lining up. It seems there should be some way to print output that is independent of whatever input is happening.
Gonna bring this up the priority list. No ETA but working on the fix.
I'm also running into this issue for printing background log lines, which now mess up the prompt.
I can see that repurposing the current Print/Prinln functions might not what is needed (though they are a bit weird now: AFAICS Print replaces the prompt content, but appends to the prompt on screen, while Println empties the prompt but not reshows the prompt).
Ideally, when printing a log line or similar, you would:
- Erase the current prompt
- Print the log line, with a newline
- Print the prompt again, including anything typed already
I guess this would warrant a new function, something like shell.PrintlnBeforePrompt()
? This is probably fairly easy to do as above for full lines.
Implementing something like shell.PrintBeforePrompt()
(so without a newline) would be more tricky, since that should (I guess) append to the existing line before the prompt, which probably requires keeping track of the current cursor position (or erasing the newline before the prompt, but I'm not sure if terminals can actually do this). In any case, just printing full lines, with a newline, are probably sufficient for most usecases?
You can fix this problem like this:
package main
import (
"time"
"github.com/abiosoft/readline"
"github.com/abiosoft/ishell"
)
func feed(c chan string) {
for i := 0; i<3; i++ {
c <- "Feedback"
time.Sleep(time.Second)
}
}
func main(){
rl, _ := readline.NewEx(&readline.Config{
Prompt: "\033[31m»\033[0m ",
InterruptPrompt: "^C",
EOFPrompt: "exit",
HistorySearchFold: true,
})
shell = ishell.NewWithReadline(rl)
c := make(chan string)
// listen
go func() {
for msg := range(c) {
rl.Write([]byte(msg))
}
}()
// this does output correctly
//feed(c)
this does not
go feed(c)
shell. Start()
}