Less –quit-if-one-screen without –no-init

less

I'm on a terminal that supports the alternate screen which is used by less, vim, etc. to restore the previous display after exiting. That's a nice feature, but it really breaks the --quit-if-one-screen switch of less since in that case less switches to the alternate screen, displays its data, figures out there is only one screen, and exits, taking the contents of the alternate screen with it.

The commonly suggested workaround is using the --no-init switch to avoid using the alternate screen altogether. However, this is somewhat ugly because I do want to use it in case less actually acts as a pager. Therefore I'm looking for a solution to use the alternate screen only if less doesn't terminate automatically.

I'll mostly use this as Git's pager, so a wrapper shell script that only runs less in case there is enough output would be fine, too. At least if there's no way to do it without one.

Best Answer

Since less 530 (released in December 2017), less --quit-if-one-screen does not switch to the alternate screen if it reads less than one screenful. So you won't have this problem if your version of less is recent enough.

In earlier versions, less has to decide whether to use the alternate screen when it starts. You can't defer that choice to when it terminates.

You could call less, let it use the alternate screen, and cat the content onto the primary screen if less terminates automatically. However I don't know of a way to detect automatic termination.

On the other hand, it isn't that difficult to call cat for short inputs and less for larger inputs, even preserving buffering so that you don't have to wait for the whole input to start seeing stuff in less (the buffer may be slightly larger — you won't see anything until you have at least one screenful of data — but not much more).

#!/bin/sh
n=3  # number of screen lines that should remain visible in addition to the content
lines=
newline='
'
case $LINES in
  ''|*[!0-9]*) exec less;;
esac
while [ $n -lt $LINES ] && IFS= read -r line; do
  lines="$lines$newline$line"
done
if [ $n -eq $LINES ]; then
  { printf %s "$lines"; exec cat; } | exec less
else
  printf %s "$lines"
fi

You might prefer to see the lines on the main screen as they come in, and switch to the alternate screen if the lines would cause scrolling.

#!/bin/sh
n=3  # number of screen lines that should remain visible in addition to the content
beginning=
newline='
'
# If we can't determine the terminal height, execute less directly
[ -n "$LINES" ] || LINES=$(tput lines) 2>/dev/null
case $LINES in
  ''|*[!0-9]*) exec less "$@";;
esac
# Read and display enough lines to fill most of the terminal
while [ $n -lt $LINES ] && IFS= read -r line; do
  beginning="$beginning$newline$line"
  printf '%s\n' -- "$line"
  n=$((n + 1))
done
# If the input is longer, run the pager
if [ $n -eq $LINES ]; then
  { printf %s "$beginning"; exec cat; } | exec less "$@"
fi
Related Question