Closed slavikm closed 9 years ago
There is not currently a way to interrupt the user. Partly because it can be a confusing UX, but primarily because it can't possibly work when $TERM == "dumb"
.
For a less racy version of your hack, you probably want to use a chan string
instead of a string, and add the new chan to the select{} block at the top of readNext in input.go instead of injecting a fake rune into the input chan. (Making either version work on Windows left as an exercise for the reader).
See also the comments in https://github.com/peterh/liner/pull/16
Makes sense. Thanks.
I personally don't think this issue should be closed as a wontfix. A lot of apps (chats, interactive shells that run background tasks etc) need to print messages asynchronously. It's ok to buffer messages in the channel and print periodically at the app level. So maybe Prompt() could take a timeout argument somehow? So the app could handle some timeout error it would return and handle pending messages?
Anyway, the following workaround might work on some systems:
go func() {
for {
time.Sleep(time.Second)
fmt.Printf("\r")
fmt.Println("Hey", time.Now())
// Sending WINCH causes the prompt to be refreshed keeping the cursor position etc
syscall.Kill(syscall.Getpid(), syscall.SIGWINCH)
}
}()
Still hoping that a proper solution would appear one day.
I forked and added PrintAbovePrompt that does just that. Not 100% sure regarding my Windows implementation. It definitely works but handling Ctrl-C, Ctrl-D might be wrong. Check it at https://github.com/slavikm/liner
If Peter is interested I will issue a pull request.
@zserge You almost had me convinced, right up until you said "chats". If you're implementing IRC, you have multiple types of out-of-band data, and really want one of the ncurses wrappers (or reimplementations), not Liner. As to "interactive shells that run background tasks", I use bash
all the time, and it happily queues up background task messages until I'm done editing. I prefer it this way, because the text that I am referencing above the prompt does not move while I'm editing my line.
@slavikm That fork looks interesting. I did notice two bugs, though.
@peterh Ok, let me explain with a bit more details. 1) Chats - I didn't mean a full IRC client, or something that complex. However, a single-room chat like http://tools.suckless.org/sic/ is a good example. Single incoming channel of strings, single line editor to send data. 2) By interactive shells I didn't mean bash and friends. On my work I often write simple utilities to test hardware and communication protocols. For example, imagine a board that can either send messages when a button is pressed, or toggle LEDs when incoming command is received. I made simple apps in Go to print button press messages to stdout, and to send LED commands from stdin. It can be used in unix pipelines as well. But of course I would prefer my LED command input to not be interrupted when I press a button on the board.
So basically, I speak about two-directional communication over a single socket, or RS232, or RS485, or USB etc and a user-friendly interface to that that would support line editing.
@peterh thanks for the feedback.
I don't know what the right solution is (aside from going full ncurses).
Perhaps you could add a function s.DrainLineAbove (I'm sure you can come up with a better name) that reads from s.lineAbovePrompt using select with default, that can be called after s.Prompt returns and
This should handle both cases, although note that s.PrintAbovePrompt may block until s.DrainAboveLine is called, which might be awkward in some programs.
Alternatively, perhaps it's better to buffer a small number and export the s.LineAbovePrompt channel, so that senders can send in select with default if they don't want to block, and the main goroutine can drain the channel when s.Prompt returns.
@peterh I added a small buffer and made PrintAbovePrompt return an error if buffer is full. Also, I drain the buffer before returning from Prompt and PromptPassword. See https://github.com/slavikm/liner/commit/b41ae2c3c53b90de25d7b7467e7daf7acb0d358a.
I submitted #119, anyone here interested to review?
Is there a way to output something to the user while in prompt (from a separate go routine) - so that the output will be printed on a separate line and the prompt will be refreshed on a line below?
I made an ugly hack to make it work by creating a public function InjectString that saves the string to be injected on the State and pushed a marker rune on State.next. Then, I handle it in State.Prompt by doing:
But, it is really an ugly hack. I needed to change State.next to a regular channel so that I can write to it and not only read from it... There must be a better way but I could not find it in a few minutes of looking in the code.