zsh-users
 help / color / mirror / code / Atom feed
* 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 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).