diff options
-rwxr-xr-x | vellum | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/vellum b/vellum new file mode 100755 index 0000000..b436b6c --- /dev/null +++ b/vellum | |||
@@ -0,0 +1,170 @@ | |||
1 | #!/bin/sh | ||
2 | |||
3 | # https://unix.stackexchange.com/a/464963 | ||
4 | readc() { # arg: <variable-name> | ||
5 | if [ -t 0 ]; then | ||
6 | # if stdin is a tty device, put it out of icanon, set min and | ||
7 | # time to sane value, but don't otherwise touch other input or | ||
8 | # or local settings (echo, isig, icrnl...). Take a backup of the | ||
9 | # previous settings beforehand. | ||
10 | saved_tty_settings=$(stty -g) | ||
11 | stty -icanon min 1 time 0 | ||
12 | fi | ||
13 | eval "$1=" | ||
14 | while | ||
15 | # read one byte, using a work around for the fact that command | ||
16 | # substitution strips the last character. | ||
17 | c=$(dd bs=1 count=1 2> /dev/null; echo .) | ||
18 | c=${c%.} | ||
19 | |||
20 | # break out of the loop on empty input (eof) or if a full character | ||
21 | # has been accumulated in the output variable (using "wc -m" to count | ||
22 | # the number of characters). | ||
23 | [ -n "$c" ] && | ||
24 | eval "$1=\${$1}"'$c | ||
25 | [ "$(($(printf %s "${'"$1"'}" | wc -m)))" -eq 0 ]'; do | ||
26 | continue | ||
27 | done | ||
28 | if [ -t 0 ]; then | ||
29 | # restore settings saved earlier if stdin is a tty device. | ||
30 | stty "$saved_tty_settings" | ||
31 | fi | ||
32 | } | ||
33 | |||
34 | setup_terminal() { | ||
35 | # Setup the terminal for the TUI. | ||
36 | # '\e[?1049h': Use alternative screen buffer. | ||
37 | # '\e[?7l': Disable line wrapping. | ||
38 | # '\e[?25l': Hide the cursor. | ||
39 | # '\e[2J': Clear the screen. | ||
40 | # '\e[1;Nr': Limit scrolling to scrolling area. | ||
41 | # Also sets cursor to (0,0). | ||
42 | printf '\e[?1049h\e[?7l\e[2J\e[1;%sr' "$((LINES - 1))" | ||
43 | |||
44 | # Hide echoing of user input | ||
45 | stty -echo | ||
46 | } | ||
47 | |||
48 | reset_terminal() { | ||
49 | # Reset the terminal to a useable state (undo all changes). | ||
50 | # '\e[?7h': Re-enable line wrapping. | ||
51 | # '\e[?25h': Unhide the cursor. | ||
52 | # '\e[2J': Clear the terminal. | ||
53 | # '\e[;r': Set the scroll region to its default value. | ||
54 | # Also sets cursor to (0,0). | ||
55 | # '\e[?1049l: Restore main screen buffer. | ||
56 | printf '\e[?7h\e[?25h\e[2J\e[;r\e[?1049l' | ||
57 | |||
58 | # Show user input. | ||
59 | stty echo | ||
60 | } | ||
61 | |||
62 | printlines() { # printlines file min max | ||
63 | if test -n "$DEBUG"; then | ||
64 | echo --- >&2 | ||
65 | else | ||
66 | clear | ||
67 | fi | ||
68 | sed -n "$2,$3p" "$1" | ||
69 | notify_flush | ||
70 | } | ||
71 | |||
72 | notify() { # notify STRING | ||
73 | notification="$notification$1" | ||
74 | } | ||
75 | |||
76 | notify_flush() { # notify_flush | ||
77 | winch | ||
78 | printf '\e[s\e[%s;%sH' 0 "$c" | ||
79 | printf '%s' "$notification" | ||
80 | printf '\e[u' | ||
81 | notification= | ||
82 | } | ||
83 | |||
84 | scroll() { # scroll nlines -- MUTATES GLOBAL STATE | ||
85 | case "${1:-0}" in | ||
86 | \+*|\-*) n=$((n+$1)) ;; | ||
87 | 0) ;; | ||
88 | *) n=$1 ;; | ||
89 | esac | ||
90 | |||
91 | if test $n -gt $((max - l)) | ||
92 | then | ||
93 | n=$((max - l)) | ||
94 | notify '_' | ||
95 | fi | ||
96 | |||
97 | if test $n -lt $min | ||
98 | then | ||
99 | notify '^' | ||
100 | n=$min | ||
101 | fi | ||
102 | |||
103 | m=$((n+l)) | ||
104 | if $m -ge $max | ||
105 | then | ||
106 | m=$max | ||
107 | fi | ||
108 | # echo "min:$min max:$max n:$n m:$m l:$l" >&2 | ||
109 | } | ||
110 | |||
111 | ctty() { # ctty | ||
112 | ## Get the controlling TTY interface. | ||
113 | tty <&2 || # only works if not redirecting stderr | ||
114 | echo /proc/self/fd/1 # probably only works on linux | ||
115 | } | ||
116 | |||
117 | winch() { | ||
118 | stty size > "$wf" | ||
119 | read -r l c < "$wf" | ||
120 | # echo l:$l c:$c >&2 | ||
121 | l=$((l-2)) # last line | ||
122 | } | ||
123 | |||
124 | cleanup() { | ||
125 | rm "$bf" "$wf" | ||
126 | reset_terminal | ||
127 | exit | ||
128 | } | ||
129 | |||
130 | vellum() { # vellum [-n NUM] < INPUT | ||
131 | # Set up buffer | ||
132 | bf="$(mktemp)" # buffer | ||
133 | cat "${@:--}" > "$bf" | ||
134 | # Redirect input to the controlling terminal | ||
135 | ## XXX: This assumes that you haven't redirected stderr. | ||
136 | exec < "$(ctty)" | ||
137 | max=$(wc -l < "$bf") | ||
138 | min=1 # min = minimum accessible line num. | ||
139 | n=$min # n = num. first line to show | ||
140 | |||
141 | # Set up terminal | ||
142 | setup_terminal | ||
143 | wf="$(mktemp)" # winch file | ||
144 | winch; trap winch WINCH | ||
145 | m=$((n+l)) # m = num. last line to show | ||
146 | |||
147 | trap cleanup EXIT INT QUIT | ||
148 | |||
149 | # Main loop | ||
150 | while :; do | ||
151 | scroll | ||
152 | printlines "$bf" $n $m | ||
153 | |||
154 | readc key >/dev/null 2>&1 | ||
155 | case "$key" in | ||
156 | ' ') scroll +$l ;; | ||
157 | '') scroll -$l ;; | ||
158 | j) scroll +1 ;; | ||
159 | k) scroll -1 ;; | ||
160 | g) scroll $min ;; | ||
161 | G) scroll $max ;; | ||
162 | q) break ;; | ||
163 | esac | ||
164 | done | ||
165 | break | ||
166 | echo | ||
167 | } | ||
168 | |||
169 | test -n "$DEBUG" && { set -x; exec 2> /tmp/vellum.log; } | ||
170 | test -z "$SOURCE" && vellum "$@" | ||