M README.md => README.md +2 -1
@@ 6,7 6,7 @@
## What is it?
-Bopher-NG is an ambitious attempt to write a full-featured Gopher client/browser in under 300 SLOC of pure Bash code. It started off as a really crude and unoptimized prototype developed right in [this blog post](https://chronovir.us/2023/03/28/I-wrote-a-browser/) for educational purposes.
+Bopher-NG is an ambitious attempt to write a full-featured Gopher client/browser in under 350 SLOC of pure Bash code. It started off as a really crude and unoptimized prototype developed right in [this blog post](https://chronovir.us/2023/03/28/I-wrote-a-browser/) for educational purposes.
Improvements over the original Bopher from that post:
@@ 14,6 14,7 @@ Improvements over the original Bopher from that post:
- smoother rendering and scrolling
- better edge-case stability (e.g. on macOS where using file descriptor 3 actually crashes everything)
- better Gophermap processing according to the RFC1436
+- an actual text reflow when viewing plaintext documents with no hard wrapping
- multi-level navigation history (although you can only go back)
- status bar with currently opened resource name
- ability to accept `gopher://` URLs from the command line
M bopher-ng.sh => bopher-ng.sh +38 -2
@@ 8,6 8,7 @@
# - smoother rendering and scrolling
# - better edge-case stability
# - better Gophermap processing according to the RFC1436
+# - an actual text reflow when viewing plaintext documents with no hard wrapping
# - multi-level navigation history (although you can only go back)
# - status bar with currently opened resource name
# - ability to accept gopher:// URLs from the command line
@@ 121,6 122,38 @@ gmparse() { # args: line, curhost, curport
printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$action" "$desc" "$rhost" "$rport" "$sel" "$rtype" # output the final formatted line
}
+phlow_lite() { # a single-parameter line reflow algorithm
+ local line="$1"
+ local TARGET_WIDTH="$2"
+ local reflowfmt="%-${TARGET_WIDTH}s\n"
+ local llen="${#line}" # get effective line length
+ if (( 0 == TARGET_WIDTH || llen < TARGET_WIDTH )); then # no need to run the logic for smaller lines or if TARGET_WIDTH is 0
+ printf "$reflowfmt" "$line"
+ return
+ fi
+ local lastws=0 # variable to track last whitespace
+ local cpos=0 # variable to track current position within the page line
+ local pagepos=0 # variable to track the position of new line start
+ local outbuf='' # temporary output buffer
+ local c='' # temporary character buffer
+ for ((i=0;i<llen;i++,cpos++)); do # start iterating over characters
+ if (( cpos >= TARGET_WIDTH )); then # we already exceeded the page width
+ (( lastws == 0 )) && lastws=$TARGET_WIDTH # no whitespace encountered here
+ printf "$reflowfmt" "${outbuf:0:$lastws}" # truncate the buffer
+ outbuf=''
+ pagepos=$(( pagepos + lastws ))
+ cpos=0
+ lastws=0
+ i=$pagepos # update current iteration index from the last valid whitespace
+ else # save the whitespace position if found
+ c="${line:i:1}" # get the current character
+ [[ "$c" == $'\x20' ]] && lastws="$cpos"
+ outbuf="${outbuf}${c}" # save the character itself
+ fi
+ done
+ [[ ! -z "$outbuf" ]] && printf "$reflowfmt" "$outbuf" # output the last unprocessed chunk
+}
+
# convert AM line back to gopher:// URL
amtogopher() { # args: AM line
readarray -d $'\t' -t fields < <(printf '%s' "$1")
@@ 159,9 192,13 @@ amclick() { # args: AM line[, forcedl], output: AM line(s)
gmparse "$line" "$rhost" "$rport"
done
elif [[ 'P' == "$action" ]]; then # plain text content (can be delimited with both CRLF or LF)
+ read -r TERMROWS TERMCOLS < <(stty size) # get current terminal size info
readarray -t lines -d $'\n' < <(gophetch "$rhost" "$rport" "$puresel" "$input") # split on LF
for line in "${lines[@]}"; do # iterate over every fetched line
- printf 'E\t%s\n' "${line%%$'\r'}" # remove a trailing CR if it's there
+ readarray -t reflow_lines -d $'\n' < <(phlow_lite "${line%%$'\r'}" "$TERMCOLS") # remove a trailing CR if it's there
+ for rline in "${reflow_lines[@]}"; do
+ printf 'E\t%s\n' "$rline"
+ done
done
fi
}
@@ 309,7 346,6 @@ readmouseinput() { # correctly read the remaining mouse input in semi-long mode
printf '%d %d %d %c\n' "$battr" "$mx" "$my" "$c"
}
-
# entry point code starts here
printf '%s' "$ALTBUFON$CLS" # enter ALTBUF mode and clear the screen