ishell icon indicating copy to clipboard operation
ishell copied to clipboard

Wrong output from shell.Println when receiving from a goroutine

Open synw opened this issue 8 years ago • 11 comments

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
>>>  

synw avatar Feb 17 '17 13:02 synw

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 ?

abiosoft avatar Feb 18 '17 20:02 abiosoft

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
>>>

synw avatar Feb 19 '17 08:02 synw

Ideally I would like to have a shell print channel to where I can throw output in from goroutines

synw avatar Feb 19 '17 18:02 synw

You think a call to Println should not print on the same line as the prompt ?

abiosoft avatar Feb 19 '17 21:02 abiosoft

Yes: if it can do the job it would be fine. It should preserve the text being typed:

>>> typing someth
Feedback
>>> typing someth

synw avatar Feb 20 '17 08:02 synw

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.

abiosoft avatar Feb 20 '17 13:02 abiosoft

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.

synw avatar Apr 17 '17 16:04 synw

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.

danopia avatar Apr 22 '17 21:04 danopia

Gonna bring this up the priority list. No ETA but working on the fix.

abiosoft avatar Apr 23 '17 06:04 abiosoft

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?

matthijskooijman avatar Jan 29 '18 12:01 matthijskooijman

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()
}

b3r1ch avatar Jun 23 '23 23:06 b3r1ch