* Detect typed input without swallowing characters
@ 2022-02-10 21:21 Marc Cornellà
2022-02-11 0:04 ` Philippe Troin
0 siblings, 1 reply; 5+ messages in thread
From: Marc Cornellà @ 2022-02-10 21:21 UTC (permalink / raw)
To: zsh-users
Hi,
in Oh My Zsh we want to detect whether the user has typed characters
before showing the prompt to auto-update, so that if they have, to
skip it entirely and get them to the prompt as fast as possible.
The current solution is to detect whether there's input with `read -t
-k 1`, but this solution has the downside of swallowing one character.
I've been looking at sysread and zselect to poll stdin (fd 0), but I
believe they only tell whether stdin is ready for reading, not whether
it holds any data. These are the commands I'm using by the way:
sysread -t 0 -i 0
zselect -t 0 -r 0
Is there any way to do this or should I give up entirely?
Thanks!
Marc
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: Detect typed input without swallowing characters
2022-02-10 21:21 Detect typed input without swallowing characters Marc Cornellà
@ 2022-02-11 0:04 ` Philippe Troin
2022-02-11 9:08 ` Marc Cornellà
0 siblings, 1 reply; 5+ messages in thread
From: Philippe Troin @ 2022-02-11 0:04 UTC (permalink / raw)
To: Marc Cornellà, zsh-users
On Thu, 2022-02-10 at 22:21 +0100, Marc Cornellà wrote:
> in Oh My Zsh we want to detect whether the user has typed characters
> before showing the prompt to auto-update, so that if they have, to
> skip it entirely and get them to the prompt as fast as possible.
>
> The current solution is to detect whether there's input with `read -t
> -k 1`, but this solution has the downside of swallowing one
> character.
>
> I've been looking at sysread and zselect to poll stdin (fd 0), but I
> believe they only tell whether stdin is ready for reading, not
> whether
> it holds any data. These are the commands I'm using by the way:
>
> sysread -t 0 -i 0
> zselect -t 0 -r 0
>
> Is there any way to do this or should I give up entirely?
You can use zselect, but you have to disable canonical mode with stty,
otherwise characters are not counted unless you press enter (the
terminal is set to read a whole line at once).
This function should work:
type_ahead() {
emulate -L zsh
local termios=$(stty --save)
stty -icanon
zselect -t 0 -r 0
local ret=$?
stty $termios
return ret
}
There may be subtleties involved with the STTY variable as well as
ttyctl, check out the zsh documentation. I only did minimal testing:
sleep 5; if type_ahead; then echo TYPED AHEAD; fi
works as it should.
You can also get the number of bytes in the read buffer with the
FIONREAD ioctl() (man ioctl_tty for details).
This long python 1-liner does what you want:
python -c 'import array, fcntl, termios; nbuffered=array.array("i", [0]); old=termios.tcgetattr(0); new=old[:]; new[3] &= ~termios.ICANON; termios.tcsetattr(0, termios.TCSANOW, new); fcntl.ioctl(0, termios.FIONREAD, nbuffered); termios.tcsetattr(0, termios.TCSANOW, old); print(nbuffered[0])'
Same script in a more readable form:
#!/usr/bin/env python3
import array, fcntl, termios
nbuffered=array.array("i", [0])
old=termios.tcgetattr(0)
new=old[:] # Array copy
new[3] &= ~termios.ICANON # Disable canonical mode
try: # try/finally cannot appear in the 1-liner above
termios.tcsetattr(0, termios.TCSANOW, new)
fcntl.ioctl(0, termios.FIONREAD, nbuffered)
finally:
termios.tcsetattr(0, termios.TCSANOW, old)
print(nbuffered[0])
I doubt this would be useful for your use case, as firing up the whole
python interpreter to perform FIONREAD is likely to be slower than the
optimization gain you're trying to measure.
Phil.
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: Detect typed input without swallowing characters
2022-02-11 0:04 ` Philippe Troin
@ 2022-02-11 9:08 ` Marc Cornellà
2022-02-11 16:15 ` Philippe Troin
2022-02-11 19:08 ` Bart Schaefer
0 siblings, 2 replies; 5+ messages in thread
From: Marc Cornellà @ 2022-02-11 9:08 UTC (permalink / raw)
To: Philippe Troin; +Cc: zsh-users
On Fri, 11 Feb 2022 at 01:04, Philippe Troin <phil@fifi.org> wrote:
>
> You can use zselect, but you have to disable canonical mode with stty,
> otherwise characters are not counted unless you press enter (the
> terminal is set to read a whole line at once).
>
> This function should work:
>
> type_ahead() {
> emulate -L zsh
> local termios=$(stty --save)
> stty -icanon
> zselect -t 0 -r 0
> local ret=$?
> stty $termios
> return ret
> }
>
It does work, thanks! I tried my best at using STTY to avoid having to
reset it afterwards, but it didn't have the desired effect.
See https://github.com/ohmyzsh/ohmyzsh/commit/dbd92a62ce1fc25a6819ae6d0a29dc8b8ec9a7dd
Thanks!
Marc
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: Detect typed input without swallowing characters
2022-02-11 9:08 ` Marc Cornellà
@ 2022-02-11 16:15 ` Philippe Troin
2022-02-11 19:08 ` Bart Schaefer
1 sibling, 0 replies; 5+ messages in thread
From: Philippe Troin @ 2022-02-11 16:15 UTC (permalink / raw)
To: Marc Cornellà; +Cc: zsh-users
On Fri, 2022-02-11 at 10:08 +0100, Marc Cornellà wrote:
> On Fri, 11 Feb 2022 at 01:04, Philippe Troin <phil@fifi.org> wrote:
> >
> > You can use zselect, but you have to disable canonical mode with stty,
> > otherwise characters are not counted unless you press enter (the
> > terminal is set to read a whole line at once).
> >
> > This function should work:
> >
> > type_ahead() {
> > emulate -L zsh
> > local termios=$(stty --save)
> > stty -icanon
> > zselect -t 0 -r 0
> > local ret=$?
> > stty $termios
> > return ret
> > }
> >
>
> It does work, thanks! I tried my best at using STTY to avoid having to
> reset it afterwards, but it didn't have the desired effect.
> See https://github.com/ohmyzsh/ohmyzsh/commit/dbd92a62ce1fc25a6819ae6d0a29dc8b8ec9a7dd
This is the implementation you've checked in (thank you for the
credit):
function has_typed_input() {
emulate -L zsh
zmodload zsh/zselect
{
local termios=$(stty --save)
stty -icanon
zselect -t 0 -r 0
return $?
} always {
stty $termios
}
}
Kudos on using an always block to ensure that the termios are reset on
exiting the function, but if for some reason you're not connected to a
terminal, or if 'stty --save' fails, you still end up called stty many
times (my original code had the same problem):
% has_typed_input < /dev/null
stty: 'standard input': Inappropriate ioctl for device
stty: 'standard input': Inappropriate ioctl for device
stty: 'standard input': Inappropriate ioctl for device
%
I'd suggest moving the termios assignment outside of the always block
and exiting early if it fails:
function has_typed_input() {
emulate -L zsh
zmodload zsh/zselect
local termios
termios=$(stty --save) || return $?
{
stty -icanon
zselect -t 0 -r 0
return $?
} always {
stty $termios
}
}
% has_typed_input < /dev/null
stty: 'standard input': Inappropriate ioctl for device
%
You may also want to remove the error message with 2> /dev/null:
termios=$(stty --save 2> /dev/null) || return $?
Regarding your remark about STTY not working as expected: I suspect
assigning to STTY does not work for builtins. If it worked, the
function could simply be replaced by:
STTY=-icanon zselect -t 0 -r 0
Phil.
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: Detect typed input without swallowing characters
2022-02-11 9:08 ` Marc Cornellà
2022-02-11 16:15 ` Philippe Troin
@ 2022-02-11 19:08 ` Bart Schaefer
1 sibling, 0 replies; 5+ messages in thread
From: Bart Schaefer @ 2022-02-11 19:08 UTC (permalink / raw)
To: Marc Cornellà; +Cc: Philippe Troin, Zsh Users
On Fri, Feb 11, 2022 at 1:09 AM Marc Cornellà <hello@mcornella.com> wrote:
>
> It does work, thanks! I tried my best at using STTY to avoid having to
> reset it afterwards, but it didn't have the desired effect.
STTY= only works for external commands, not builtins / shell functions.
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2022-02-11 19:09 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-02-10 21:21 Detect typed input without swallowing characters Marc Cornellà
2022-02-11 0:04 ` Philippe Troin
2022-02-11 9:08 ` Marc Cornellà
2022-02-11 16:15 ` Philippe Troin
2022-02-11 19:08 ` Bart Schaefer
Code repositories for project(s) associated with this public inbox
https://git.vuxu.org/mirror/zsh/
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).