232 lines
5.5 KiB
Bash
Executable File
232 lines
5.5 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Lesh: a pure bash (I think) pager
|
|
# Lesh is moreish
|
|
# Intended to behave like less, more or less
|
|
|
|
# Currently lesh can:
|
|
# read a file or pipe and display text
|
|
# tell you if you didn't give it anything to read
|
|
# scroll down/up with j/k or the down and up arrows
|
|
# page up and page down with page up and page down
|
|
# jump to the start/end of the file with g/G
|
|
# quit with q
|
|
#
|
|
# It doesn't mess up your terminal when terminated
|
|
# It does when killed (printf '\e[;r' to fix)
|
|
|
|
|
|
# allow terminal size to be read, then run useless command to populate variables
|
|
shopt -s checkwinsize; (:;:)
|
|
|
|
# exit with error if file not provided
|
|
# and there's nothing being piped
|
|
if [[ ! -f "$1" ]] && [[ -t 0 ]]; then
|
|
printf '%s\n' "nothing to read" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# if reading from a pipe, duplicate /dev/stdin
|
|
# then redirect the terminal back in order to read keypresses
|
|
[[ ! -t 0 ]] && exec 3<&0 && exec </dev/tty
|
|
|
|
# use the size of the terminal to determine the length of the scroll region
|
|
# and the width to fold text to
|
|
get_terminal_size() {
|
|
scroll_size=$((LINES - 1))
|
|
width=$COLUMNS
|
|
}
|
|
|
|
read_file() {
|
|
mapfile -t file < "${file_name:-/proc/self/fd/3}"
|
|
}
|
|
|
|
# fold lines from file to $width, in a separate array (text)
|
|
fold_text() {
|
|
# without setting IFS, "${file[*]}" is all one line
|
|
IFS=$'\n'
|
|
while read -r -n "$width" file_line; do
|
|
text+=("$file_line")
|
|
done <<< "${file[*]}"
|
|
text_length="${#text[@]}"
|
|
last_line=$((text_length-scroll_size))
|
|
}
|
|
|
|
setup_terminal() {
|
|
# save cursor position, then switch to alternate screen
|
|
printf '\e7\e[?1049h'
|
|
}
|
|
|
|
# move cursor to start/end of scroll region, or the prompt at the bottom
|
|
cursor() {
|
|
case "$1" in
|
|
start)
|
|
printf '\e[H'
|
|
;;
|
|
end)
|
|
printf '\e[%s;H' "$scroll_size"
|
|
;;
|
|
prompt)
|
|
printf '\e[%s;H' "$LINES"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# print a specified line of the folded text
|
|
# if that line is outside the contents of $text, print ~
|
|
print_line() {
|
|
if [ "$1" -lt 0 ] || [ "$1" -gt "$text_length" ]; then
|
|
printf '~'
|
|
else
|
|
printf '%s' "${text[$1]}"
|
|
fi
|
|
}
|
|
|
|
# fill the scroll region with lines of $text
|
|
# starting at $line
|
|
draw_lines() {
|
|
cursor prompt
|
|
printf '\e[1J'
|
|
cursor start
|
|
print_line "$line"
|
|
for ((i=line+1; i<line+scroll_size; i++)); do
|
|
printf '\n'
|
|
print_line "$i"
|
|
done
|
|
}
|
|
|
|
# not used for anything yet, but looks like less
|
|
# show if end of file reached
|
|
draw_prompt() {
|
|
cursor prompt
|
|
# clear line
|
|
printf '\e[2K'
|
|
if [ "$line" -ge "$last_line" ]; then
|
|
printf '\e[7m%s\e[0m' "(END)"
|
|
else
|
|
printf '%s' ":"
|
|
fi
|
|
}
|
|
|
|
draw_screen() {
|
|
# set scroll region, which moves cursor to start
|
|
printf '\e[1;%sr' "$scroll_size"
|
|
draw_lines
|
|
draw_prompt
|
|
}
|
|
|
|
# print file name as given in argument
|
|
# intended to be erased when any key is pressed
|
|
print_message() {
|
|
printf '\r\e[7m%s' "$file_name"
|
|
[ "$line" -ge "$last_line" ] && printf " (END)"
|
|
printf '\e[m'
|
|
}
|
|
|
|
# scroll the displayed text down, and print the next line up
|
|
scroll_up() {
|
|
cursor start
|
|
line=$((line-1))
|
|
printf '\EM'
|
|
print_line "$line"
|
|
}
|
|
|
|
# scroll the displayed text up, and print the next line down
|
|
scroll_down() {
|
|
cursor end
|
|
line=$((line+1))
|
|
printf '\n'
|
|
print_line "$((line-1+scroll_size))"
|
|
}
|
|
|
|
quit() {
|
|
# reset scroll region, switch back to main screen, restore cursor
|
|
printf '\e[;r\e[?1049l\e8'
|
|
exit
|
|
}
|
|
|
|
# infinite loop waiting for single character input
|
|
# or escape sequences from (e.g) arrow keys
|
|
key_loop() {
|
|
while read -rs -n 1; do
|
|
# when escape recieved, read any characters sent immediately after
|
|
# if there are none, then the escape key was pressed, but is ignored
|
|
if [ "$REPLY" = $'\e' ]; then
|
|
read -rs -t 0.0001
|
|
fi
|
|
case $REPLY in
|
|
k|"[A")
|
|
# if at start of the text, do nothing
|
|
[ ! "$line" = 0 ] && scroll_up
|
|
draw_prompt
|
|
;;
|
|
j|"[B")
|
|
# if at the end of text, do nothing
|
|
[ ! "$line" -ge "$last_line" ] && scroll_down
|
|
draw_prompt
|
|
;;
|
|
"[5~")
|
|
# page up
|
|
if [ ! "$line" = 0 ]; then
|
|
line=$((line-scroll_size))
|
|
[ "$line" -lt 0 ] && line=0
|
|
draw_lines
|
|
fi
|
|
draw_prompt
|
|
;;
|
|
"[6~")
|
|
# page down
|
|
if [ ! "$line" -ge "$last_line" ]; then
|
|
line=$((line+scroll_size))
|
|
[ "$line" -gt "$last_line" ] && line="$last_line"
|
|
draw_lines
|
|
fi
|
|
draw_prompt
|
|
;;
|
|
g)
|
|
if [ ! "$line" = 0 ]; then
|
|
line=0
|
|
draw_lines
|
|
fi
|
|
draw_prompt
|
|
;;
|
|
G)
|
|
if [ ! "$line" = "$last_line" ]; then
|
|
line="$last_line"
|
|
draw_lines
|
|
fi
|
|
draw_prompt
|
|
;;
|
|
q)
|
|
break
|
|
;;
|
|
*)
|
|
draw_prompt
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
main() {
|
|
read_file
|
|
fold_text
|
|
|
|
setup_terminal
|
|
draw_screen
|
|
[ "$file_name" ] && print_message
|
|
|
|
key_loop
|
|
|
|
quit
|
|
}
|
|
|
|
trap quit EXIT INT HUP
|
|
|
|
# set or initialise variables
|
|
declare -a file text
|
|
file_name="$1"
|
|
line=0
|
|
get_terminal_size
|
|
|
|
main
|