~luxferre/Bopher-NG

50da581db6c2058d325a817e17e0aa83cb6c0784 — Luxferre 1 year, 8 months ago 73b71d5
Implemented reflow for plaintexts in the client itself
2 files changed, 40 insertions(+), 3 deletions(-)

M README.md
M bopher-ng.sh
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