New Upstream Release - elpher

Ready changes

Summary

Merged new upstream version: 3.5.0 (was: 3.4.2).

Resulting package

Built on 2023-05-22T09:53 (took 4m22s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases elpa-elpher

Lintian Result

Diff

diff --git a/ISSUES.org b/ISSUES.org
index 9923820..363c25f 100644
--- a/ISSUES.org
+++ b/ISSUES.org
@@ -1,10 +1,10 @@
 #+TITLE: Issues and Dev Notes
 #+TODO: OPEN(o!) | CLOSED(c!) INVALID(i@)
 #+STARTUP: logdrawer
-   
+
 * Open Bugs
 
-** OPEN Sanitize certificate names
+** OPEN Sanitize certificate names :gemini:
    :LOGBOOK:
    - State "OPEN"       from              [2020-06-22 Mon 10:32]
    :END:
@@ -24,6 +24,41 @@ user interaction that may appear during the initial connection setup.
 E.g., asking for approval of uknown TLS certificates.
 
 * Closed Bugs
+
+** CLOSED Downloads failing
+:LOGBOOK:
+- State "CLOSED"     from "OPEN"       [2022-08-09 Tue 10:38]
+:END:
+
+Downloads fail when focus is shifted away from
+the elpher buffer before the download has completed.
+
+** CLOSED Relative Gemini links processed improperly
+:LOGBOOK:
+- State "CLOSED"     from "OPEN"       [2021-08-04 Wed 15:54]
+- State "OPEN"       from              [2021-08-04 Wed 13:53]
+:END:
+
+Skyjake's gemlog at gemini://skyjake.fi/gemlog/ demonstrate's the
+issue.  The link back to the root selector in the footer of that page
+is a relative link to the parent directory, i.e. "..".  For some
+reason elpher combines this with the current URL and produces
+"gemini://skyjake.fi" as the destination of the link.  Such URLs
+(i.e. without a filename) are allowed as input, but are assumed
+to not appear internally.
+
+To see why the internal distinction is important, consider a page
+where the current URL is gemini://example.com/a_page.  The current
+directory in this case is "/", meaning a relative link to
+"another_page" results in a destination link of
+"gemini://example.com/another_page.  On the other hand, if the current
+URL is gemini://example.com/a_page/, the same relative link is
+interpreted as refering to gemini://example.com/a_page/another_page.
+
+The fix will be to ensure gemini://skyjake.fi/gemlog/.. collapses to
+gemini://skyjake.fi/ rather than gemini://skyjake.fi.
+
+
   
 ** CLOSED Org mode faces are not present in recent emacs versions
 Even 26.1 doesn't seem to have these.  This means that, for many
@@ -90,6 +125,20 @@ pop the stack, meaning that subsequent "u" commands would succeed.
 The fix is just to zero out the history list in the `elpher` function just as
 `elpher-current-page` is cleared.
 
+** CLOSED Improve client certificate scope :gemini:
+:LOGBOOK:
+- State "CLOSED"     from "OPEN"       [2023-05-05 Fri 10:09]
+- State "OPEN"       from              [2022-10-12 Wed 09:33]
+:END:
+
+Once activated, elpher continues to use a client certificate
+for any connections to the host on which it was activated.
+However, it's now common to restrict certificates also to paths
+_below_ the path where the certificate was activated.
+
+I.e. gemini://example.com/~userA/ certificates are not applied
+automatically to gemini://example.com/~userB/.
+
 * Open Enhancements
 
 ** OPEN Allow multiple elpher buffers [33%]
@@ -104,19 +153,6 @@ this can happen:
 - [ ] make history stack variables buffer-local
 - [ ] have elpher-with-clean-buffer select appropriate buffer 
 
-** OPEN Replace support for user-specified starting pages
-This used to be available, but was removed during a refactor.
-   
-** OPEN Make installing existing certificates easier
-   :LOGBOOK:
-   - State "OPEN"       from "CLOSED"     [2020-06-22 Mon 10:34]
-   :END:
-
-It's naive to think that people don't have client certificates created
-outside of elpher. Thus we need some easy way to "install" these
-certificates, either by copying them or by referencing them in some
-way.
-
 * Closed Enhancements
   
 ** CLOSED Turn on lexical scoping
@@ -277,3 +313,22 @@ call is just incredibly slow for some bizarre reason.  Happily,
 (url-portspec) is functionally equivalent and is orders of magnitude
 faster.  With this replacement, loading the above page takes ~2s
 and there aren't any other hotspots.
+
+
+** CLOSED Replace support for user-specified starting pages
+:LOGBOOK:
+- State "CLOSED"     from "OPEN"       [2021-08-09 Mon 17:46]
+:END:
+This used to be available, but was removed during a refactor.
+
+
+** CLOSED Make installing existing certificates easier
+   :LOGBOOK:
+   - State "CLOSED"     from "OPEN"       [2023-05-05 Fri 10:10]
+   - State "OPEN"       from "CLOSED"     [2020-06-22 Mon 10:34]
+   :END:
+
+It's naive to think that people don't have client certificates created
+outside of elpher. Thus we need some easy way to "install" these
+certificates, either by copying them or by referencing them in some
+way.
diff --git a/README b/README
index 1ec85a2..6be2207 100644
--- a/README
+++ b/README
@@ -26,11 +26,17 @@ Any suggestions for improvements are welcome!
 Installation
 ------------
 
-Elpher is available from  MELPA (https://melpa.org).  If you have
-never installed packages from this repository before, you'll need
-to follow the instructions at https://melpa.org/#/getting-started.
+Elpher is available on the non-GNU ELPA package archive.  If you are
+using Emacs 28 or later, this archive should be available on your system
+by default.  For Emacs 27, you'll need to follow the instructions at
+https://elpa.nongnu.org to make the archive accessible.
 
-To install Elpher, enter the following:
+Alternatively, Elpher is available from MELPA (https://melpa.org).  If
+you have never installed packages from this repository before, you'll
+need to follow the instructions at https://melpa.org/#/getting-started.
+
+Once one of these package archives is installed, enter the following to
+install Elpher:
 
     M-x package-install RET elpher RET
 
@@ -85,30 +91,29 @@ Info directory which can be displayed using "C-h i".
 Contributors
 ------------
 
-Elpher was originally written by Tim Vaughan. Recent maintenance has
-been done by and with the help of Alex Schroeder. In addition, the
-following people (in alphabetical order) have generously provided
-assistance and/or patches:
-
-* Alexis
-* Christopher Brannon
-* Zhiwei Chen
-* condy0919
-* Étienne Deparis
-* Roy Koushik
-* Simon Nicolussi
-* Noodles!
-* Jens Östlund
-* Abhiseck Paira
-* F. Jason Park
-* Omar Polo
-* Koushk Roy
-* Michel Alexandre Salim
-* Alex Schroeder
-* Daniel Semyonov
-* Simon South
-* Bradley Thornton
-* Vee
+Elpher was originally written and is currently maintained by Tim Vaughan
+<plugd@thelambdalab.xyz>.  Significant improvements and
+maintenance have also been contributed by and with the help of Alex
+Schroeder <alex@gnu.org>.  In addition, the following people have
+all generously provided assistance and/or patches over the years:
+
+* Jens Östlund <jostlund@gmail.com>
+* F. Jason Park <jp@neverwas.me>
+* Christopher Brannon <chris@the-brannons.com>
+* Omar Polo <op@omarpolo.com>
+* Noodles! <nnoodle@chiru.no>
+* Abhiseck Paira <abhiseckpaira@disroot.org>
+* Zhiwei Chen <chenzhiwei03@kuaishou.com>
+* condy0919 <condy0919@gmail.com>
+* Alexis <flexibeast@gmail.com>
+* Étienne Deparis <etienne@depar.is>
+* Simon Nicolussi <sinic@sinic.name>
+* Michel Alexandre Salim <michel@michel-slm.name>
+* Koushk Roy <kroy@twilio.com>
+* Vee <vee@vnsf.xyz>
+* Simon South <simon@simonsouth.net>
+* Daniel Semyonov <daniel@dsemy.com>
+* Bradley Thornton <bradley@northtech.us>
 
 License
 -------
diff --git a/RELEASE b/RELEASE
index ade220d..1a08de8 100644
--- a/RELEASE
+++ b/RELEASE
@@ -7,9 +7,15 @@ When preparing a new release, set the version number:
 2. in elpher.el:     metadata at the top
 3. in elpher.el:     definition of elpher-version
 4. in elpher-pkg.el: second argument to 'define-package'
+
+For anything besides a patch release, a note describing
+the changes should be added to the documentation.  In
+the instance that the documentation itself is significantly
+changed, also update the documentation version:
+
 5. in elpher.texi:   'settitle' declaration at the top
 
-Make sure the documentation is up to date and builds:
+After any documentation updates, make sure it builds:
 
     make elpher.info elpher.html elpher.pdf
 
diff --git a/config.mk b/config.mk
index 0896d05..11af4ef 100644
--- a/config.mk
+++ b/config.mk
@@ -1,5 +1,5 @@
 PKG     = elpher
-VERSION = 3.2.2
+VERSION = 3.5.0
 
 INSTALLINFO = install-info
 MAKEINFO    = makeinfo
diff --git a/debian/changelog b/debian/changelog
index 5bbe2a3..832152f 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,8 +1,15 @@
-elpher (3.2.2-1) UNRELEASED; urgency=medium
+elpher (3.5.0-1) UNRELEASED; urgency=medium
 
+  [ Dhavan Vaidya ]
   * New upstream version 3.2.2
 
- -- Dhavan Vaidya <quark@codingquark.com>  Tue, 14 Sep 2021 17:15:56 +0530
+  [ Debian Janitor ]
+  * New upstream release.
+  * New upstream release.
+  * New upstream release.
+  * New upstream release.
+
+ -- Dhavan Vaidya <quark@codingquark.com>  Mon, 22 May 2023 09:50:03 -0000
 
 elpher (2.10.2-2) unstable; urgency=medium
 
diff --git a/elpher-pkg.el b/elpher-pkg.el
index 4d60307..5cdbef2 100644
--- a/elpher-pkg.el
+++ b/elpher-pkg.el
@@ -1,4 +1,4 @@
-(define-package "elpher" "3.2.2" "A friendly gopher and gemini client"
+(define-package "elpher" "3.5.0" "A friendly gopher and gemini client"
   '((emacs "27.1"))
   :keywords ("convenience")
   :authors (("Tim Vaughan" . "plugd@thelambdalab.xyz"))
diff --git a/elpher.el b/elpher.el
index 2e3c5ec..608f09d 100644
--- a/elpher.el
+++ b/elpher.el
@@ -1,27 +1,12 @@
 ;;; elpher.el --- A friendly gopher and gemini client  -*- lexical-binding: t -*-
 
-;; Copyright (C) 2021 Jens Östlund <jostlund@gmail.com>
-;; Copyright (C) 2021 F. Jason Park <jp@neverwas.me>
-;; Copyright (C) 2021 Christopher Brannon <chris@the-brannons.com>
-;; Copyright (C) 2021 Omar Polo <op@omarpolo.com>
-;; Copyright (C) 2021 Noodles! <nnoodle@chiru.no>
-;; Copyright (C) 2021 Abhiseck Paira <abhiseckpaira@disroot.org>
-;; Copyright (C) 2020-2021 Alex Schroeder <alex@gnu.org>
-;; Copyright (C) 2020 Zhiwei Chen <chenzhiwei03@kuaishou.com>
-;; Copyright (C) 2020 condy0919 <condy0919@gmail.com>
-;; Copyright (C) 2020 Alexis <flexibeast@gmail.com>
-;; Copyright (C) 2020 Étienne Deparis <etienne@depar.is>
-;; Copyright (C) 2020 Simon Nicolussi <sinic@sinic.name>
-;; Copyright (C) 2020 Michel Alexandre Salim <michel@michel-slm.name>
-;; Copyright (C) 2020 Koushk Roy <kroy@twilio.com>
-;; Copyright (C) 2020 Vee <vee@vnsf.xyz>
-;; Copyright (C) 2020 Simon South <simon@simonsouth.net>
-;; Copyright (C) 2019-2021 Tim Vaughan <plugd@thelambdalab.xyz>
+;; Copyright (C) 2019-2023 Tim Vaughan <plugd@thelambdalab.xyz>
+;; Copyright (C) 2020-2022 Elpher contributors (See info manual for full list)
 
 ;; Author: Tim Vaughan <plugd@thelambdalab.xyz>
 ;; Created: 11 April 2019
-;; Version: 3.2.2
-;; Keywords: comm gopher
+;; Version: 3.5.0
+;; Keywords: comm gopher gemini
 ;; Homepage: https://thelambdalab.xyz/elpher
 ;; Package-Requires: ((emacs "27.1"))
 
@@ -81,11 +66,12 @@
 (require 'gnutls)
 (require 'socks)
 (require 'bookmark)
+(require 'rx)
 
 ;;; Global constants
 ;;
 
-(defconst elpher-version "3.2.2"
+(defconst elpher-version "3.5.0"
   "Current version of elpher.")
 
 (defconst elpher-margin-width 6
@@ -127,11 +113,12 @@
   (declare-function org-link-store-props "ol")
   (declare-function org-link-set-parameters "ol")
   (defvar ansi-color-context)
+  (defvar xterm-color--current-fg)
+  (defvar xterm-color--current-bg)
   (defvar bookmark-make-record-function)
   (defvar mu4e~view-beginning-of-url-regexp)
   (defvar eww-use-browse-url)
-  (defvar thing-at-point-uri-schemes)
-  (defvar xterm-color-preserve-properties))
+  (defvar thing-at-point-uri-schemes))
 
 
 ;;; Customization group
@@ -165,8 +152,8 @@ plain text without user input."
 
 (defcustom elpher-filter-ansi-from-text nil
   "If non-nil, filter ANSI escape sequences from text.
-The default behaviour is to use the ansi-color package to interpret these
-sequences."
+The default behaviour is to use the ansi-color package (or xterm-color if it is
+available) to interpret these sequences."
   :type '(boolean))
 
 (defcustom elpher-certificate-directory
@@ -236,6 +223,32 @@ Emacs bookmark menu being accessible via \\[elpher-show-bookmarks] from
 the start page."
   :type '(string))
 
+(defcustom elpher-gemini-hide-preformatted nil
+  "Cause elpher to hide preformatted gemini text by default.
+When this option is enabled, preformatted text in text/gemini documents
+is replaced with a button which can be used to toggle its display.
+
+This is intended to improve accessibility, as preformatted text often
+includes art which can be difficult for screen readers to interpret
+meaningfully."
+  :type '(boolean))
+
+(defcustom elpher-gemini-preformatted-toggle-bullet "‣ "
+  "Margin symbol used to distinguish the preformatted text toggle."
+  :type '(string))
+
+(defcustom elpher-gemini-preformatted-toggle-label "[Toggle Preformatted Text]"
+  "Label of button used to toggle formatted text."
+  :type '(string))
+
+(defcustom elpher-certificate-map nil
+  "Register client certificates to be used for gemini URLs.
+This variable contains an alist representing a mapping between gemini
+URLs and the names of client certificates which will be automatically
+activated for those URLs.  Beware that the certificates will also be
+active for all subdirectories of the given URLs."
+  :type '(alist :key-type string :value-type string))
+
 ;; Face customizations
 
 (defgroup elpher-faces nil
@@ -306,14 +319,18 @@ the start page."
   '((t :inherit bold :height 1.2))
   "Face used for gemini heading level 3.")
 
-(defface elpher-gemini-preformatted
-  '((t :inherit fixed-pitch))
-  "Face used for pre-formatted gemini text blocks.")
-
 (defface elpher-gemini-quoted
   '((t :inherit font-lock-doc-face))
   "Face used for gemini quoted texts.")
 
+(defface elpher-gemini-preformatted
+  '((t :inherit default))
+  "Face used for gemini preformatted text.")
+
+(defface elpher-gemini-preformatted-toggle
+  '((t :inherit button))
+  "Face used for buttons used to toggle display of preformatted text.")
+
 ;;; Model
 ;;
 
@@ -324,15 +341,16 @@ the start page."
 ;; dynamically for and by elpher.  All others represent pages which
 ;; rely on content retrieved over the network.
 
-(defun elpher-address-from-url (url-string)
-  "Create a ADDRESS object corresponding to the given URL-STRING."
+(defun elpher-address-from-url (url-string &optional default-scheme)
+  "Create a ADDRESS object corresponding to the given URL-STRING.
+If DEFAULT-SCHEME is non-nil, this sets the scheme of the URL when one
+is not explicitly given."
   (let ((data (match-data))) ; Prevent parsing clobbering match data
     (unwind-protect
         (let ((url (url-generic-parse-url url-string)))
           (unless (and (not (url-fullness url)) (url-type url))
-            (setf (url-fullness url) t)
             (unless (url-type url)
-              (setf (url-type url) elpher-default-url-type))
+              (setf (url-type url) default-scheme))
             (unless (url-host url)
               (let ((p (split-string (url-filename url) "/" nil nil)))
                 (setf (url-host url) (car p))
@@ -340,6 +358,9 @@ the start page."
                       (if (cdr p)
                           (concat "/" (mapconcat #'identity (cdr p) "/"))
                         ""))))
+            (when (not (string-empty-p (url-host url)))
+              (setf (url-fullness url) t)
+              (setf (url-host url) (puny-encode-domain (url-host url))))
             (when (or (equal "gopher" (url-type url))
                       (equal "gophers" (url-type url)))
               ;; Gopher defaults
@@ -417,12 +438,12 @@ address refers to, via the table `elpher-type-map'."
     (_ 'other-url)))
 
 (defun elpher-address-about-p (address)
-  "Return non-nil if ADDRESS is an  about address."
+  "Return non-nil if ADDRESS is an about address."
   (pcase (elpher-address-type address) (`(about ,_) t)))
 
 (defun elpher-address-gopher-p (address)
-  "Return non-nill if ADDRESS object is a gopher address."
-  (eq 'gopher (elpher-address-type address)))
+  "Return non-nil if ADDRESS object is a gopher address."
+  (pcase (elpher-address-type address) (`(gopher ,_) t)))
 
 (defun elpher-address-protocol (address)
   "Retrieve the transport protocol for ADDRESS."
@@ -435,7 +456,21 @@ For gopher addresses this is a combination of the selector type and selector."
 
 (defun elpher-address-host (address)
   "Retrieve host from ADDRESS object."
-  (url-host address))
+  (pcase (url-host address)
+    ;; The following strips out square brackets which sometimes enclose IPv6
+    ;; addresses.  Doing this here rather than at the parsing stage may seem
+    ;; weird, but this lets us way we avoid having to muck with both URL parsing
+    ;; and reconstruction.  It's also more efficient, as this method is not
+    ;; called during page rendering.
+    ((rx (: "[" (let ipv6 (* (not "]"))) "]"))
+     ipv6)
+    ;; The following is a work-around for a parsing bug that causes
+    ;; URLs with empty (but not absent, see RFC 1738) usernames to have
+    ;; @ prepended to the hostname.
+    ((rx (: "@" (let rest (+ anything))))
+     rest)
+    (addr
+     addr)))
 
 (defun elpher-address-user (address)
   "Retrieve user from ADDRESS object."
@@ -443,8 +478,10 @@ For gopher addresses this is a combination of the selector type and selector."
 
 (defun elpher-address-port (address)
   "Retrieve port from ADDRESS object.
-If no address is defined, returns 0.  (This is for compatibility with the URL library.)"
-  (url-port address))
+If no address is defined, returns 0.  (This is for compatibility with
+the URL library.)"
+  (let ((port (url-portspec address))) ; (url-port) is too slow!
+    (if port port 0)))
 
 (defun elpher-gopher-address-selector (address)
   "Retrieve gopher selector from ADDRESS object."
@@ -498,12 +535,34 @@ If no address is defined, returns 0.  (This is for compatibility with the URL li
   "Set the address corresponding to PAGE to NEW-ADDRESS."
   (setcar (cdr page) new-address))
 
-(defun elpher-page-from-url (url)
+(defun elpher-page-from-url (url &optional default-scheme)
   "Create a page with address and display string defined by URL.
 The URL is unhexed prior to its use as a display string to improve
-readability."
-  (elpher-make-page (elpher-decode (url-unhex-string url))
-                    (elpher-address-from-url url)))
+readability.
+
+If DEFAULT-SCHEME is non-nil, this scheme is applied to the URL
+in the instance that URL itself doesn't specify one."
+  (let ((address (elpher-address-from-url url default-scheme)))
+    (elpher-make-page (elpher-address-to-iri address) address)))
+
+(defun elpher-address-to-iri (address)
+  "Return an IRI for ADDRESS.
+Decode percent-escapes and handle punycode in the domain name.
+Drop the password, if any."
+  (let ((data (match-data)) ; Prevent parsing clobbering match data
+        (host (url-host address))
+        (pass (url-password address)))
+    (unwind-protect
+        (let* ((host (url-host address))
+               (pass (url-password address)))
+          (when host
+            (setf (url-host address) (puny-decode-domain host)))
+          (when pass				 ; RFC 3986 says we should not render
+            (setf (url-password address) nil)) ; the password as clear text
+          (elpher-decode (url-unhex-string (url-recreate-url address))))
+      (setf (url-host address) host)
+      (setf (url-password address) pass)
+      (set-match-data data))))
 
 (defvar elpher-current-page nil
   "The current page for this Elpher buffer.")
@@ -520,7 +579,7 @@ This variable is used by `elpher-show-visited-pages'.")
 (defun elpher-visit-page (page &optional renderer no-history)
   "Visit PAGE using its own renderer or RENDERER, if non-nil.
 Additionally, push PAGE onto the history stack and the list of
-previously-visited pages,unless NO-HISTORY is non-nil."
+previously-visited pages, unless NO-HISTORY is non-nil."
   (elpher-save-pos)
   (elpher-process-cleanup)
   (unless no-history
@@ -573,6 +632,19 @@ previously-visited pages,unless NO-HISTORY is non-nil."
         (goto-char pos)
       (goto-char (point-min)))))
 
+(defun elpher-get-default-url-scheme ()
+  "Suggest default URL scheme for visiting addresses based on the current page."
+  (if elpher-current-page
+      (let* ((address (elpher-page-address elpher-current-page))
+             (current-type (elpher-address-type address)))
+        (pcase current-type
+          ((or (and 'file (guard (not elpher-history)))
+               `(about ,_))
+           elpher-default-url-type)
+          (_
+           (url-type address))))
+      elpher-default-url-type))
+
 
 ;;; Buffer preparation
 ;;
@@ -603,7 +675,9 @@ previously-visited pages,unless NO-HISTORY is non-nil."
        ;; avoid resetting buffer-local variables
        (elpher-mode))
      (let ((inhibit-read-only t)
-           (ansi-color-context nil)) ;; clean ansi interpreter state
+           (ansi-color-context nil)) ;; clean ansi interpreter state (also next 2 lines)
+       (setq-local xterm-color--current-fg nil)
+       (setq-local xterm-color--current-bg nil)
        (setq-local network-security-level
                    (default-value 'network-security-level))
        (erase-buffer)
@@ -668,7 +742,8 @@ away CRs and any terminating period."
                           'face 'button)))
     (buffer-string)))
 
-;;; ANSI colors or XTerm colors (application and filtering)
+
+;; ANSI colors or XTerm colors (application and filtering)
 
 (or (require 'xterm-color nil t)
     (require 'ansi-color))
@@ -687,17 +762,25 @@ away CRs and any terminating period."
     #'ansi-color-apply)
   "A function to apply ANSI escape sequences.")
 
-;;; Processing text for display
+(defun elpher-text-has-ansi-escapes-p (string)
+  "Return non-nil if STRING includes an ANSI escape code."
+  (save-match-data
+    (string-match "\x1b\\[" string)))
+
+
+;; Processing text for display
 
 (defun elpher-process-text-for-display (string)
   "Perform any desired processing of STRING prior to display as text.
 Currently includes buttonifying URLs and processing ANSI escape codes."
-  (elpher-buttonify-urls (if elpher-filter-ansi-from-text
-                             (elpher-color-filter-apply string)
-                           (elpher-color-apply string))))
+  (elpher-buttonify-urls (if (elpher-text-has-ansi-escapes-p string)
+                             (if elpher-filter-ansi-from-text
+                                 (elpher-color-filter-apply string)
+                               (elpher-color-apply string))
+                           string)))
 
 
-;;; Network error reporting
+;;; General network communication
 ;;
 
 (defun elpher-network-error (address error)
@@ -711,9 +794,6 @@ ERROR can be either an error object or a string."
            "Press 'u' to return to the previous page.")))
 
 
-;;; General network communication
-;;
-
 (defvar elpher-network-timer nil
   "Timer used for network connections.")
 
@@ -798,7 +878,8 @@ the host operating system and the local network capabilities.)"
                                                                  nil force-ipv4))
                                       (t
                                        (elpher-network-error address "Connection time-out."))))))
-               (proc (if socks (socks-open-network-stream "elpher-process" nil host service)
+               (proc (if socks
+                         (socks-open-network-stream "elpher-process" nil host service)
                        (make-network-process :name "elpher-process"
                                              :host host
                                              :family (and (or force-ipv4
@@ -812,6 +893,7 @@ the host operating system and the local network capabilities.)"
                                                   (cons 'gnutls-x509pki
                                                         (apply #'gnutls-boot-parameters
                                                                gnutls-params)))))))
+          (process-put proc 'elpher-buffer (current-buffer))
           (setq elpher-network-timer timer)
           (set-process-coding-system proc 'binary 'binary)
           (set-process-query-on-exit-flag proc nil)
@@ -855,17 +937,19 @@ the host operating system and the local network capabilities.)"
                                                                   response-processor
                                                                   use-tls t))
                                        (response-string-parts
-                                        (elpher-with-clean-buffer
-                                         (insert "Data received.  Rendering..."))
-                                        (funcall response-processor
-                                                 (apply #'concat (reverse response-string-parts)))
-                                        (elpher-restore-pos))
+                                        (with-current-buffer (process-get proc 'elpher-buffer)
+                                          (elpher-with-clean-buffer
+                                           (insert "Data received.  Rendering..."))
+                                          (funcall response-processor
+                                                   (apply #'concat (reverse response-string-parts)))
+                                          (elpher-restore-pos)))
                                        (t
                                         (error "No response from server")))
                                     (error
                                      (elpher-network-error address the-error)))))
           (when socks
-            (if use-tls (apply #'gnutls-negotiate :process proc gnutls-params))
+            (if use-tls
+                (apply #'gnutls-negotiate :process proc gnutls-params))
             (funcall (process-sentinel proc) proc "open\n")))
       (error
        (elpher-process-cleanup)
@@ -875,7 +959,8 @@ the host operating system and the local network capabilities.)"
 ;;; Client-side TLS Certificate Management
 ;;
 
-(defun elpher-generate-certificate (common-name key-file cert-file &optional temporary)
+(defun elpher-generate-certificate (common-name key-file cert-file url-prefix
+                                                &optional temporary)
   "Generate a key and a self-signed client TLS certificate using openssl.
 
 The Common Name field of the certificate is set to COMMON-NAME.  The
@@ -889,7 +974,8 @@ when the certificate is no longer needed for the current session.
 Otherwise, the certificate will be given a 100 year expiration period
 and the files will not be deleted.
 
-The function returns a list containing the current host name, the
+The function returns a list containing the URL-PREFIX of addresses
+for which the certificate should be used in this session, the
 temporary flag, and the key and cert file names in the form required
 by `gnutls-boot-parameters`."
   (let ((exp-key-file (expand-file-name key-file))
@@ -903,56 +989,70 @@ by `gnutls-boot-parameters`."
                         "-subj" (concat "/CN=" common-name)
                         "-keyout" exp-key-file
                         "-out" exp-cert-file)
-          (list (elpher-address-host (elpher-page-address elpher-current-page))
-                temporary exp-key-file exp-cert-file))
+          (list url-prefix temporary exp-key-file exp-cert-file))
       (error
        (message "Check that openssl is installed, or customize `elpher-openssl-command`.")
        (error "Program 'openssl', required for certificate generation, not found")))))
 
-(defun elpher-generate-throwaway-certificate ()
+(defun elpher-generate-throwaway-certificate (url-prefix)
   "Generate and return details of a throwaway certificate.
 The key and certificate files will be deleted when they are no
-longer needed for this session."
+longer needed for this session.
+
+The certificate will be marked as applying to all addresses with URLs
+starting with URL-PREFIX."
   (let* ((file-base (make-temp-name "elpher"))
          (key-file (concat temporary-file-directory file-base ".key"))
          (cert-file (concat temporary-file-directory file-base ".crt")))
-    (elpher-generate-certificate file-base key-file cert-file t)))
+    (elpher-generate-certificate file-base key-file cert-file url-prefix t)))
 
-(defun elpher-generate-persistent-certificate (file-base common-name)
+(defun elpher-generate-persistent-certificate (file-base common-name url-prefix)
   "Generate and return details of a persistent certificate.
 The argument FILE-BASE is used as the base for the key and certificate
 files, while COMMON-NAME specifies the common name field of the
 certificate.
 
-The key and certificate files are written to in `elpher-certificate-directory'."
+The key and certificate files are written to in `elpher-certificate-directory'.
+
+In this session, the certificate will remain active for all addresses
+having URLs starting with URL-PREFIX."
   (let* ((key-file (concat elpher-certificate-directory file-base ".key"))
          (cert-file (concat elpher-certificate-directory file-base ".crt")))
-    (elpher-generate-certificate common-name key-file cert-file)))
+    (elpher-generate-certificate common-name key-file cert-file url-prefix)))
 
-(defun elpher-get-existing-certificate (file-base)
+(defun elpher-get-existing-certificate (file-base url-prefix)
   "Return a certificate object corresponding to an existing certificate.
 It is assumed that the key files FILE-BASE.key and FILE-BASE.crt exist in
-the directory `elpher-certificate-directory'."
+the directory `elpher-certificate-directory'.
+
+In this session, the certificate will remain active for all addresses
+having URLs starting with URL-PREFIX."
   (let* ((key-file (concat elpher-certificate-directory file-base ".key"))
          (cert-file (concat elpher-certificate-directory file-base ".crt")))
-    (list (elpher-address-host (elpher-page-address elpher-current-page))
+    (list url-prefix
           nil
           (expand-file-name key-file)
           (expand-file-name cert-file))))
 
-(defun elpher-install-and-use-existing-certificate (key-file-src cert-file-src file-base)
+(defun elpher-install-certificate (key-file-src cert-file-src file-base url-prefix)
   "Install a key+certificate file pair in `elpher-certificate-directory'.
 The strings KEY-FILE-SRC and CERT-FILE-SRC are the existing key and
 certificate files to install.  The argument FILE-BASE is used as the
-base for the installed key and certificate files."
+base for the installed key and certificate files.
+
+In this session, the certificate will remain active for all addresses
+having URLs starting with URL-PREFIX."
   (let* ((key-file (concat elpher-certificate-directory file-base ".key"))
          (cert-file (concat elpher-certificate-directory file-base ".crt")))
     (if (or (file-exists-p key-file)
             (file-exists-p cert-file))
         (error "A certificate with base name %s is already installed" file-base))
+    (unless (and (file-exists-p key-file-src)
+                 (file-exists-p cert-file-src))
+      (error "Either of the key or certificate files do not exist"))
     (copy-file key-file-src key-file)
     (copy-file cert-file-src cert-file)
-    (list (elpher-address-host (elpher-page-address elpher-current-page))
+    (list url-prefix
           nil
           (expand-file-name key-file)
           (expand-file-name cert-file))))
@@ -978,7 +1078,7 @@ are also deleted."
       (when (cadr elpher-client-certificate)
         (delete-file (elt elpher-client-certificate 2))
         (delete-file (elt elpher-client-certificate 3)))
-      (setq elpher-client-certificate nil)
+      (setq-local elpher-client-certificate nil)
       (if (called-interactively-p 'any)
           (message "Client certificate forgotten.")))))
 
@@ -986,14 +1086,14 @@ are also deleted."
   "Retrieve the `gnutls-boot-parameters'-compatable keylist.
 
 This is obtained from the client certificate described by
-`elpher-current-certificate', if one is available and the host for
-that certificate matches the host in ADDRESS.
+`elpher-current-certificate', if one is available and the
+URL prefix for that certificate matches ADDRESS.
 
-If `elpher-current-certificate' is non-nil, and its host name doesn't
+If `elpher-current-certificate' is non-nil, and its URL prefix doesn't
 match that of ADDRESS, the certificate is forgotten."
   (if elpher-client-certificate
-      (if (string= (car elpher-client-certificate)
-                   (elpher-address-host address))
+      (if (string-prefix-p (car elpher-client-certificate)
+                           (elpher-address-to-url address))
           (list (cddr elpher-client-certificate))
         (elpher-forget-current-certificate)
         (message "Disabling client certificate for new host")
@@ -1029,10 +1129,12 @@ once they are retrieved from the gopher server."
         (error
          (elpher-network-error address the-error))))))
 
-;; Index rendering
+
+;;; Gopher index rendering
+;;
 
 (defun elpher-insert-margin (&optional type-name)
-  "Insert index margin, optionally containing the TYPE-NAME, into the current buffer."
+  "Insert index margin, optionally containing the TYPE-NAME, into current buffer."
   (if type-name
       (progn
         (insert (format (concat "%" (number-to-string (- elpher-margin-width 1)) "s")
@@ -1087,7 +1189,7 @@ If ADDRESS is not supplied or nil the record is rendered as an
     (insert "\n")))
 
 (defun elpher-click-link (button)
-  "Function called when the gopher link BUTTON is activated (via mouse or keypress)."
+  "Function called when the gopher link BUTTON is activated."
   (let ((page (button-get button 'elpher-page)))
     (elpher-visit-page page)))
 
@@ -1113,7 +1215,9 @@ If ADDRESS is not supplied or nil the record is rendered as an
      (elpher-cache-content (elpher-page-address elpher-current-page)
                            (buffer-string)))))
 
-;; Text rendering
+
+;;; Gopher text rendering
+;;
 
 (defun elpher-render-text (data &optional _mime-type-string)
   "Render DATA as text.  MIME-TYPE-STRING is unused."
@@ -1125,7 +1229,9 @@ If ADDRESS is not supplied or nil the record is rendered as an
       (elpher-page-address elpher-current-page)
       (buffer-string)))))
 
-;; Image retrieval
+
+;;; Image retrieval
+;;
 
 (defun elpher-render-image (data &optional _mime-type-string)
   "Display DATA as image.  MIME-TYPE-STRING is unused."
@@ -1134,17 +1240,21 @@ If ADDRESS is not supplied or nil the record is rendered as an
     (if (display-images-p)
         (let* ((image (create-image
                        data
-                       nil t))
-               (window (get-buffer-window elpher-buffer-name)))
-          (when window
-            (setf (image-property image :max-width) (window-body-width window t))
-            (setf (image-property image :max-height) (window-body-height window t)))
-          (elpher-with-clean-buffer
-           (insert-image image)
-           (elpher-restore-pos)))
+                       nil t)))
+          (if (not image)
+              (error "Unsupported image format")
+            (let ((window (get-buffer-window elpher-buffer-name)))
+              (when window
+                (setf (image-property image :max-width) (window-body-width window t))
+                (setf (image-property image :max-height) (window-body-height window t))))
+            (elpher-with-clean-buffer
+             (insert-image image)
+             (elpher-restore-pos))))
       (elpher-render-download data))))
 
-;; Search retrieval and rendering
+
+;;; Gopher search retrieval and rendering
+;;
 
 (defun elpher-get-gopher-query-page (renderer)
   "Getter for gopher addresses requiring input.
@@ -1173,7 +1283,9 @@ The response is rendered using the rendering function RENDERER."
         (if aborted
             (elpher-visit-previous-page))))))
 
-;; Raw server response rendering
+
+;;; Raw server response rendering
+;;
 
 (defun elpher-render-raw (data &optional mime-type-string)
   "Display raw DATA in buffer.  MIME-TYPE-STRING is also displayed if provided."
@@ -1186,7 +1298,9 @@ The response is rendered using the rendering function RENDERER."
      (goto-char (point-min)))
     (message "Displaying raw server response.  Reload or redraw to return to standard view.")))
 
-;; File save "rendering"
+
+;;; File save "rendering"
+;;
 
 (defun elpher-render-download (data &optional _mime-type-string)
   "Save DATA to file.  MIME-TYPE-STRING is unused."
@@ -1208,7 +1322,9 @@ The response is rendered using the rendering function RENDERER."
             (insert data)))
         (message (format "Saved to file %s." filename))))))
 
-;; HTML rendering
+
+;;; HTML rendering
+;;
 
 (defun elpher-render-html (data &optional _mime-type-string)
   "Render DATA as HTML using shr.  MIME-TYPE-STRING is unused."
@@ -1220,7 +1336,9 @@ The response is rendered using the rendering function RENDERER."
                   (libxml-parse-html-region (point-min) (point-max)))))
        (shr-insert-document dom)))))
 
-;; Gemini page retrieval
+
+;;; Gemini page retrieval
+;;
 
 (defvar elpher-gemini-redirect-chain)
 
@@ -1258,14 +1376,17 @@ that the response was malformed."
          (elpher-with-clean-buffer
           (insert "Gemini server is requesting input."))
          (let* ((query-string
-                 (if (eq (elt response-code 1) ?1)
-                     (read-passwd (concat response-meta ": "))
-                   (read-string (concat response-meta ": "))))
+                 (with-local-quit
+                   (if (eq (elt response-code 1) ?1)
+                       (read-passwd (concat response-meta ": "))
+                     (read-string (concat response-meta ": ")))))
                 (query-address (seq-copy (elpher-page-address elpher-current-page)))
                 (old-fname (url-filename query-address)))
-           (setf (url-filename query-address)
-                 (concat old-fname "?" (url-build-query-string `((,query-string)))))
-           (elpher-get-gemini-response query-address renderer)))
+           (if (not query-string)
+               (elpher-visit-previous-page)
+             (setf (url-filename query-address)
+                   (concat old-fname "?" (url-build-query-string `((,query-string)))))
+             (elpher-get-gemini-response query-address renderer))))
         (?2 ; Normal response
          (funcall renderer response-body response-meta))
         (?3 ; Redirect
@@ -1294,28 +1415,55 @@ that the response was malformed."
             (insert "Gemini server is requesting a valid TLS certificate:\n\n"))
           (auto-fill-mode 1)
           (elpher-gemini-insert-text response-meta))
-         (let ((chosen-certificate (elpher-choose-client-certificate)))
+         (let ((chosen-certificate
+                (with-local-quit
+                  (elpher-acquire-client-certificate
+                   (elpher-address-to-url (elpher-page-address elpher-current-page))))))
            (unless chosen-certificate
              (error "Gemini server requires a client certificate and none was provided"))
-           (setq elpher-client-certificate chosen-certificate))
+           (setq-local elpher-client-certificate chosen-certificate))
          (elpher-with-clean-buffer)
          (elpher-get-gemini-response (elpher-page-address elpher-current-page) renderer))
         (_other
          (error "Gemini server response unknown: %s %s"
                 response-code response-meta))))))
 
+(defun elpher-acquire-client-certificate (url-prefix)
+  "Select a pre-defined client certificate or prompt for one.
+In this case, \"pre-defined\" means a certificate provided by
+the `elpher-certificate-map' variable.
+
+For this session, the certificate will remain active for all addresses
+having URLs begining with URL-PREFIX."
+  (let ((entry (assoc url-prefix
+                      elpher-certificate-map
+                      #'string-prefix-p)))
+    (if entry
+        (let ((cert-url-prefix (car entry))
+              (cert-name (cadr entry)))
+          (message "Using certificate \"%s\" specified in elpher-certificate-map with prefix \"%s\""
+                   cert-name cert-url-prefix)
+          (elpher-get-existing-certificate cert-name cert-url-prefix))
+      (elpher-prompt-for-client-certificate url-prefix))))
+
 (defun elpher--read-answer-polyfill (question answers)
   "Polyfill for `read-answer' in Emacs 26.1.
 QUESTION is a string containing a question, and ANSWERS
-is a list of possible answers."
-    (completing-read question (mapcar 'identity answers)))
+is a list of possible answers, or an alist whose keys
+are the possible answers."
+    (completing-read question answers))
 
 (if (fboundp 'read-answer)
     (defalias 'elpher-read-answer 'read-answer)
   (defalias 'elpher-read-answer 'elpher--read-answer-polyfill))
 
-(defun elpher-choose-client-certificate ()
-  "Prompt for a client certificate to use to establish a TLS connection."
+
+
+(defun elpher-prompt-for-client-certificate (url-prefix)
+  "Prompt for a client certificate to use to establish a TLS connection.
+
+In this session, the chosen certificate will remain active for all
+addresses with URLs matching URL-PREFIX."
   (let* ((read-answer-short t))
     (pcase (read-answer "What do you want to do? "
                         '(("throwaway" ?t
@@ -1325,7 +1473,7 @@ is a list of possible answers."
                           ("abort" ?a
                            "stop immediately")))
       ("throwaway"
-       (setq elpher-client-certificate (elpher-generate-throwaway-certificate)))
+       (setq-local elpher-client-certificate (elpher-generate-throwaway-certificate url-prefix)))
       ("persistent"
        (let* ((existing-certificates (elpher-list-existing-certificates))
               (file-base (completing-read
@@ -1334,8 +1482,8 @@ is a list of possible answers."
          (if (string-empty-p (string-trim file-base))
              nil
            (if (member file-base existing-certificates)
-               (setq elpher-client-certificate
-                     (elpher-get-existing-certificate file-base))
+               (setq-local elpher-client-certificate
+                     (elpher-get-existing-certificate file-base url-prefix))
              (pcase (read-answer "Generate new certificate or install externally-generated one? "
                                  '(("new" ?n
                                     "generate new certificate")
@@ -1348,15 +1496,16 @@ is a list of possible answers."
                                                 file-base)))
                   (message "New key and self-signed certificate written to %s"
                            elpher-certificate-directory)
-                  (elpher-generate-persistent-certificate file-base common-name)))
+                  (elpher-generate-persistent-certificate file-base
+                                                          common-name
+                                                          url-prefix)))
                ("install"
                 (let* ((cert-file (read-file-name "Certificate file: " nil nil t))
                        (key-file (read-file-name "Key file: " nil nil t)))
                   (message "Key and certificate installed in %s for future use"
                            elpher-certificate-directory)
-                  (elpher-install-and-use-existing-certificate key-file
-                                                               cert-file
-                                                               file-base)))
+                  (elpher-install-certificate key-file cert-file file-base
+                                              url-prefix)))
                ("abort" nil))))))
       ("abort" nil))))
 
@@ -1376,6 +1525,9 @@ is a list of possible answers."
       (error
        (elpher-network-error address the-error)))))
 
+;;; Gemini page rendering
+;;
+
 (defun elpher-render-gemini (body &optional mime-type-string)
   "Render gemini response BODY with rendering MIME-TYPE-STRING."
   (if (not body)
@@ -1421,25 +1573,27 @@ Returns nil in the event that the contents of the line following the
 
 (defun elpher-gemini-get-link-display-string (link-line)
   "Extract the display string portion of LINK-LINE, a gemini map file link line.
-Returns the url portion in the event that the display-string portion is empty."
+Return nil if this portion is not provided."
   (let* ((rest (string-trim (elt (split-string link-line "=>") 1)))
          (idx (string-match "[ \t]" rest)))
-    (string-trim (if idx
-                     (substring rest (+ idx 1))
-                   rest))))
+    (and idx
+         (elpher-color-filter-apply (string-trim (substring rest (+ idx 1)))))))
 
 (defun elpher-collapse-dot-sequences (filename)
-  "Collapse dot sequences in FILENAME.
-For instance, the filename /a/b/../c/./d will reduce to /a/c/d"
-  (let* ((path (split-string filename "/"))
+  "Collapse dot sequences in the (absolute) FILENAME.
+For instance, the filename \"/a/b/../c/./d\" will reduce to \"/a/c/d\""
+  (let* ((path (split-string filename "/" t))
+         (is-directory (string-match-p (rx (: (or "." ".." "/") line-end)) filename))
          (path-reversed-normalized
           (seq-reduce (lambda (a b)
-                        (cond ((and a (equal b "..") (cdr a)))
-                              ((and (not a) (equal b "..")) a) ;leading .. are dropped
+                        (cond ((equal b "..") (cdr a))
                               ((equal b ".") a)
                               (t (cons b a))))
-                      path nil)))
-    (string-join (reverse path-reversed-normalized) "/")))
+                      path nil))
+         (path-normalized (reverse path-reversed-normalized)))
+    (if path-normalized
+        (concat "/" (string-join path-normalized "/") (and is-directory "/"))
+      "/")))
 
 (defun elpher-address-from-gemini-url (url)
   "Extract address from URL with defaults as per gemini map files.
@@ -1449,16 +1603,18 @@ treatment that a separate function is warranted."
   (let ((address (url-generic-parse-url url))
         (current-address (elpher-page-address elpher-current-page)))
     (unless (and (url-type address) (not (url-fullness address))) ;avoid mangling mailto: urls
-      (setf (url-fullness address) t)
       (if (url-host address) ;if there is an explicit host, filenames are absolute
           (if (string-empty-p (url-filename address))
               (setf (url-filename address) "/")) ;ensure empty filename is marked as absolute
         (setf (url-host address) (url-host current-address))
+        (setf (url-fullness address) (url-host address)) ; set fullness to t if host is set
         (setf (url-portspec address) (url-portspec current-address)) ; (url-port) too slow!
         (unless (string-prefix-p "/" (url-filename address)) ;deal with relative links
           (setf (url-filename address)
                 (concat (file-name-directory (url-filename current-address))
                         (url-filename address)))))
+      (when (url-host address)
+        (setf (url-host address) (puny-encode-domain (url-host address))))
       (unless (url-type address)
         (setf (url-type address) (url-type current-address)))
       (when (equal (url-type address) "gemini")
@@ -1468,28 +1624,27 @@ treatment that a separate function is warranted."
 
 (defun elpher-gemini-insert-link (link-line)
   "Insert link described by LINK-LINE into a text/gemini document."
-  (let* ((url (elpher-gemini-get-link-url link-line))
-         (display-string (elpher-gemini-get-link-display-string link-line))
-         (address (elpher-address-from-gemini-url url))
-         (type (if address (elpher-address-type address) nil))
-         (type-map-entry (cdr (assoc type elpher-type-map))))
-    (when display-string
-      (insert elpher-gemini-link-string)
-      (if type-map-entry
+  (let ((url (elpher-gemini-get-link-url link-line)))
+    (when url
+      (let* ((given-display-string (elpher-gemini-get-link-display-string link-line))
+             (address (elpher-address-from-gemini-url url))
+             (type (if address (elpher-address-type address) nil))
+             (type-map-entry (cdr (assoc type elpher-type-map)))
+             (fill-prefix (make-string (+ 1 (length elpher-gemini-link-string)) ?\s)))
+        (when type-map-entry
+          (insert elpher-gemini-link-string)
           (let* ((face (elt type-map-entry 3))
-                 (filtered-display-string (elpher-color-filter-apply display-string))
-                 (page (elpher-make-page filtered-display-string address)))
-            (insert-text-button filtered-display-string
+                 (display-string (or given-display-string
+                                     (elpher-address-to-iri address)))
+                 (page (elpher-make-page display-string
+                                         address)))
+            (insert-text-button display-string
                                 'face face
                                 'elpher-page page
                                 'action #'elpher-click-link
                                 'follow-link t
                                 'help-echo #'elpher--page-button-help))
-        (insert (propertize display-string 'face 'elpher-unknown)))
-      (insert "\n"))))
-
-(defvar elpher--gemini-page-headings nil
-  "List of headings on the page.")
+          (newline))))))
 
 (defun elpher-gemini-insert-header (header-line)
   "Insert header described by HEADER-LINE into a text/gemini document.
@@ -1507,11 +1662,12 @@ by HEADER-LINE."
                             (/ (* fill-column
                                   (font-get (font-spec :name (face-font 'default)) :size))
                                (font-get (font-spec :name (face-font face)) :size)) fill-column)))
-      (setq elpher--gemini-page-headings (cons (cons header (point))
-                                               elpher--gemini-page-headings))
       (unless (display-graphic-p)
         (insert (make-string level ?#) " "))
-      (insert (propertize header 'face face 'rear-nonsticky t))
+      (insert (propertize header
+                          'face face
+                          'gemini-heading t
+                          'rear-nonsticky t))
       (newline))))
 
 (defun elpher-gemini-insert-text (text-line)
@@ -1520,7 +1676,6 @@ This function uses Emacs' auto-fill to wrap text sensibly to a maximum
 width defined by `elpher-gemini-max-fill-width'."
   (string-match
    (rx (: line-start
-          (* (any " \t"))
           (optional
            (group (or (: "*" (+ (any " \t")))
                       (: ">" (* (any " \t"))))))))
@@ -1538,36 +1693,77 @@ width defined by `elpher-gemini-max-fill-width'."
                      (propertize text-line 'face 'elpher-gemini-quoted))
                     (t text-line))
             text-line))
-         (adaptive-fill-mode t)
-	 ;; fill-prefix is important for adaptive-fill-mode: without
-	 ;; it, multi-line list items are not indented correct
-         (fill-prefix (if (match-string 1 text-line)
+         (fill-prefix (if line-prefix
                           (make-string (length (match-string 0 text-line)) ?\s)
-                        nil)))
+                        "")))
     (insert (elpher-process-text-for-display processed-text-line))
     (newline)))
 
+(defun elpher-gemini-pref-expand-collapse (button)
+  "Function called when the preformatted text toggle BUTTON is activated."
+  (let ((id (button-get button 'pref-id)))
+    (if (invisible-p id)
+        (remove-from-invisibility-spec id)
+      (add-to-invisibility-spec id))
+    (redraw-display)))
+
+(defun elpher-gemini-insert-preformatted-toggler (alt-text)
+  "Insert a button for toggling the visibility of preformatted text.
+If non-nil, ALT-TEXT is displayed alongside the button."
+  (let* ((url-string (url-recreate-url (elpher-page-address elpher-current-page)))
+         (pref-id (intern (concat "pref-"
+                                  (number-to-string (point))
+                                  "-"
+                                  url-string))))
+    (insert elpher-gemini-preformatted-toggle-bullet)
+    (when alt-text
+      (insert (propertize (concat alt-text " ")
+                          'face 'elpher-gemin-preformatted)))
+    (insert-text-button elpher-gemini-preformatted-toggle-label
+                        'action #'elpher-gemini-pref-expand-collapse
+                        'pref-id pref-id
+                        'face 'elpher-gemini-preformatted-toggle)
+    (add-to-invisibility-spec pref-id)
+    (newline)
+    pref-id))
+
+(defun elpher-gemini-insert-preformatted-line (line &optional pref-id)
+  "Insert a LINE of preformatted text.
+PREF-ID is the value assigned to the \"invisible\" text attribute, which
+can be used to toggle the display of the preformatted text."
+  (insert (propertize (concat (elpher-process-text-for-display
+                               (propertize line 'face 'elpher-gemini-preformatted))
+                              "\n")
+                      'invisible pref-id
+                      'rear-nonsticky t)))
+
 (defun elpher-render-gemini-map (data _parameters)
   "Render DATA as a gemini map file, PARAMETERS is currently unused."
   (elpher-with-clean-buffer
-   (setq elpher--gemini-page-headings nil)
-   (let ((preformatted nil))
-     (auto-fill-mode 1)
+   (auto-fill-mode 1)
+   (setq-local buffer-invisibility-spec nil)
+   (let ((preformatted nil)
+         (adaptive-fill-mode nil)) ;Prevent automatic setting of fill-prefix
      (setq-local fill-column (min (window-width) elpher-gemini-max-fill-width))
      (dolist (line (split-string data "\n"))
-       (cond
-        ((string-prefix-p "```" line) (setq preformatted (not preformatted)))
-        (preformatted (insert (elpher-process-text-for-display
-                               (propertize line 'face 'elpher-gemini-preformatted))
-                              "\n"))
-        ((string-prefix-p "=>" line)
-         (elpher-gemini-insert-link line))
-        ((string-prefix-p "#" line) (elpher-gemini-insert-header line))
-        (t (elpher-gemini-insert-text line)))))
-   (setq elpher--gemini-page-headings (nreverse elpher--gemini-page-headings))
+       (pcase line
+         ((rx (: string-start "```" (opt (let alt-text (+ any)))))
+          (setq preformatted
+                (if preformatted
+                    nil
+                  (if elpher-gemini-hide-preformatted
+                      (elpher-gemini-insert-preformatted-toggler alt-text)
+                    t))))
+         ((guard  preformatted)
+          (elpher-gemini-insert-preformatted-line line preformatted))
+         ((pred (string-prefix-p "=>"))
+          (elpher-gemini-insert-link line))
+         ((pred (string-prefix-p "#"))
+          (elpher-gemini-insert-header line))
+         (_ (elpher-gemini-insert-text line))))
    (elpher-cache-content
     (elpher-page-address elpher-current-page)
-    (buffer-string))))
+    (buffer-string)))))
 
 (defun elpher-render-gemini-plain-text (data _parameters)
   "Render DATA as plain text file.  PARAMETERS is currently unused."
@@ -1577,8 +1773,23 @@ width defined by `elpher-gemini-max-fill-width'."
     (elpher-page-address elpher-current-page)
     (buffer-string))))
 
-
-;; Finger page connection
+(defun elpher-build-current-imenu-index ()
+  "Build imenu index for current elpher buffer."
+  (save-excursion
+    (goto-char (point-min))
+    (let ((match nil)
+          (headers nil))
+      (while (setq match (text-property-search-forward 'gemini-heading t t))
+        (push (cons
+               (buffer-substring-no-properties (prop-match-beginning match)
+                                               (prop-match-end match))
+               (prop-match-beginning match))
+              headers))
+      (reverse headers))))
+
+
+;;; Finger page connection
+;;
 
 (defun elpher-get-finger-page (renderer)
   "Opens a finger connection to the current page address.
@@ -1604,7 +1815,8 @@ The result is rendered using RENDERER."
          (elpher-network-error address the-error))))))
 
 
-;; Telnet page connection
+;;; Telnet page connection
+;;
 
 (defun elpher-get-telnet-page (renderer)
   "Opens a telnet connection to the current page address (RENDERER must be nil)."
@@ -1620,10 +1832,12 @@ The result is rendered using RENDERER."
       (telnet host))))
 
 
-;; Other URL page opening
+;;; Other URL page opening
+;;
 
 (defun elpher-get-other-url-page (renderer)
-  "Getter which attempts to open the URL specified by the current page (RENDERER must be nil)."
+  "Getter which attempts to open the URL specified by the current page.
+The RENDERER argument to this getter must be nil."
   (when renderer
     (elpher-visit-previous-page)
     (error "Command not supported for general URLs"))
@@ -1635,7 +1849,9 @@ The result is rendered using RENDERER."
         (browse-web url)
       (browse-url url))))
 
-;; File page
+
+;;; File page
+;;
 
 (defun elpher-get-file-page (renderer)
   "Getter which renders a local file using RENDERER.
@@ -1644,10 +1860,10 @@ Assumes UTF-8 encoding for all text files."
          (filename (elpher-address-filename address)))
     (unless (file-exists-p filename)
       (elpher-visit-previous-page)
-        (error "File not found"))
+      (error "File not found"))
     (unless (file-readable-p filename)
       (elpher-visit-previous-page)
-        (error "Could not read from file"))
+      (error "Could not read from file"))
     (let ((body (with-temp-buffer
        (let ((coding-system-for-read 'binary)
              (coding-system-for-write 'binary))
@@ -1664,12 +1880,15 @@ Assumes UTF-8 encoding for all text files."
             (elpher-render-text (decode-coding-string body 'utf-8)))
            ((or "jpg" "jpeg" "gif" "png" "bmp" "tif" "tiff")
             (elpher-render-image body))
+           ((or "gopher" "gophermap")
+            (elpher-render-index (elpher-decode body)))
            (_
             (elpher-render-download body))))
        (elpher-restore-pos))))
 
 
-;; Welcome page retrieval
+;;; Welcome page retrieval
+;;
 
 (defun elpher-get-welcome-page (renderer)
   "Getter which displays the welcome page (RENDERER must be nil)."
@@ -1716,15 +1935,14 @@ Assumes UTF-8 encoding for all text files."
                                (elpher-address-from-url "gemini://geminispace.info/search"))
    (insert "\n"
            "Your bookmarks are stored in your ")
-   (let ((help-string "RET,mouse-1: Open bookmark list"))
-     (insert-text-button "bookmark list"
-                         'face 'link
-                         'action #'elpher-click-link
-                         'follow-link t
-                         'help-echo #'elpher--page-button-help
-                         'elpher-page
-                         (elpher-make-page "Elpher Bookmarks"
-                                           (elpher-make-about-address 'bookmarks))))
+   (insert-text-button "bookmark list"
+                       'face 'link
+                       'action #'elpher-click-link
+                       'follow-link t
+                       'help-echo #'elpher--page-button-help
+                       'elpher-page
+                       (elpher-make-page "Elpher Bookmarks"
+                                         (elpher-make-about-address 'bookmarks)))
    (insert ".\n")
    (insert (propertize
             "(Bookmarks from legacy elpher-bookmarks files will be automatically imported.)\n"
@@ -1757,12 +1975,15 @@ Assumes UTF-8 encoding for all text files."
                        'help-echo help-string))
    (insert "\n")
    (insert (propertize
-            (concat "(These documents should be available if you have installed Elpher \n"
-                    " using MELPA. Otherwise you may have to install the manual yourself.)\n")
+            (concat "(These documents should be available if you have installed Elpher\n"
+                    " from MELPA or non-GNU ELPA. Otherwise you may have to install the\n"
+                    " manual yourself.)\n")
             'face 'shadow))
    (elpher-restore-pos)))
 
-;; History page retrieval
+
+;;; History page retrieval
+;;
 
 (defun elpher-show-history ()
   "Show the current contents of elpher's history stack.
@@ -1819,6 +2040,7 @@ This is rendered using `elpher-get-visited-pages-page' via `elpher-type-map'."
 
 
 ;;; Bookmarks
+;;
 
 ;; This code allows Elpher to use the standard Emacs bookmarks: `C-x r
 ;; m' to add a bookmark, `C-x r l' to list bookmarks (which is where
@@ -1863,10 +2085,11 @@ then making that buffer the current buffer.  It should not switch
 to the buffer."
   (let* ((url (cdr (assq 'location bookmark)))
          (cleaned-url (string-trim url))
-         (page (elpher-page-from-url cleaned-url)))
+         (page (elpher-page-from-url cleaned-url))
+         (buffer (get-buffer-create elpher-buffer-name)))
     (elpher-with-clean-buffer
      (elpher-visit-page page))
-    (set-buffer (get-buffer elpher-buffer-name))
+    (set-buffer buffer)
     nil))
 
 (defun elpher-bookmark-link ()
@@ -2020,6 +2243,11 @@ supports the old protocol elpher, where the link is self-contained."
    :export (lambda (link description format _plist)
              (elpher-org-export-link link description format "gopher"))
    :follow (lambda (link _arg) (elpher-org-follow-link link "gopher")))
+  (org-link-set-parameters
+   "gophers"
+   :export (lambda (link description format _plist)
+             (elpher-org-export-link link description format "gophers"))
+   :follow (lambda (link _arg) (elpher-org-follow-link link "gophers")))
   (org-link-set-parameters
    "finger"
    :export (lambda (link description format _plist)
@@ -2028,7 +2256,7 @@ supports the old protocol elpher, where the link is self-contained."
 
 (add-hook 'org-mode-hook #'elpher-org-mode-integration)
 
-;;; Browse URL
+;; Browse URL
 
 ;;;###autoload
 (defun elpher-browse-url-elpher (url &rest _args)
@@ -2041,36 +2269,39 @@ supports the old protocol elpher, where the link is self-contained."
 (if (boundp 'browse-url-default-handlers)
     (add-to-list
      'browse-url-default-handlers
-     '("^\\(gopher\\|finger\\|gemini\\)://" . elpher-browse-url-elpher))
+     '("^\\(gopher\\|gophers\\|finger\\|gemini\\)://" . elpher-browse-url-elpher))
   ;; Patch `browse-url-browser-function' for older ones. The value of
   ;; that variable is `browse-url-default-browser' by default, so
-  ;; that's the function that gets advised.
-  (advice-add browse-url-browser-function :before-while
-              (lambda (url &rest _args)
-		"Handle gemini, gopher, and finger schemes using Elpher."
-                (let ((scheme (downcase (car (split-string url ":" t)))))
-                  (if (member scheme '("gemini" "gopher" "finger"))
-                      ;; `elpher-go' always returns nil, which will stop the
-                      ;; advice chain here in a before-while
-                      (elpher-go url)
-                    ;; chain must continue, then return t.
-                    t)))))
+  ;; that's the function that gets advised. If the value is an alist,
+  ;; however, we don't know what to do. Better not interfere?
+  (when (and (symbolp browse-url-browser-function)
+             (fboundp browse-url-browser-function))
+    (advice-add browse-url-browser-function :before-while
+		(lambda (url &rest _args)
+		  "Handle gemini, gopher, and finger schemes using Elpher."
+                  (let ((scheme (downcase (car (split-string url ":" t)))))
+                    (if (member scheme '("gemini" "gopher" "gophers" "finger"))
+			;; `elpher-go' always returns nil, which will stop the
+			;; advice chain here in a before-while
+			(elpher-go url)
+                      ;; chain must continue, then return t.
+                      t))))))
 
 ;; Register "gemini://" as a URI scheme so `browse-url' does the right thing
 (with-eval-after-load 'thingatpt
   (add-to-list 'thing-at-point-uri-schemes "gemini://"))
 
-;;; Mu4e:
+;; Mu4e:
 
 ;; Make mu4e aware of the gemini world
 (setq mu4e~view-beginning-of-url-regexp
-      "\\(?:https?\\|gopher\\|finger\\|gemini\\)://\\|mailto:")
+      "\\(?:https?\\|gopher\\|gophers\\|finger\\|gemini\\)://\\|mailto:")
 
-;;; eww:
+;; eww:
 
 ;; Let elpher handle gemini, gopher links in eww buffer.
 (setq eww-use-browse-url
-      "\\`mailto:\\|\\(\\`gemini\\|\\`gopher\\|\\`finger\\)://")
+      "\\`mailto:\\|\\(\\`gemini\\|\\`gopher\\|\\`gophers\\|\\`finger\\)://")
 
 
 ;;; Interactive procedures
@@ -2095,24 +2326,33 @@ supports the old protocol elpher, where the link is self-contained."
 (defun elpher-go (host-or-url)
   "Go to a particular gopher site HOST-OR-URL.
 When run interactively HOST-OR-URL is read from the minibuffer."
-  (interactive "sGopher or Gemini URL: ")
+  (interactive (list
+                (read-string (format "Visit URL (default scheme %s): "
+                                     (elpher-get-default-url-scheme)))))
   (let ((trimmed-host-or-url (string-trim host-or-url)))
     (unless (string-empty-p trimmed-host-or-url)
-      (let ((page (elpher-page-from-url trimmed-host-or-url)))
-        (switch-to-buffer elpher-buffer-name)
+      (let ((page (elpher-page-from-url trimmed-host-or-url
+                                        (elpher-get-default-url-scheme))))
+        (unless (get-buffer-window elpher-buffer-name t)
+          (switch-to-buffer elpher-buffer-name))
         (elpher-with-clean-buffer
          (elpher-visit-page page))
         nil)))) ; non-nil value is displayed by eshell
 
 (defun elpher-go-current ()
-  "Go to a particular site read from the minibuffer, initialized with the current URL."
+  "Go to a particular URL which is read from the minibuffer.
+Unlike `elpher-go', the reader is initialized with the URL of the
+current page."
   (interactive)
   (let* ((address (elpher-page-address elpher-current-page))
-         (url (read-string "Gopher or Gemini URL: "
-                           (unless (elpher-address-about-p address)
-                             (elpher-address-to-url address)))))
-    (unless (string-empty-p (string-trim url))
-      (elpher-visit-page (elpher-page-from-url url)))))
+         (url (read-string (format "Visit URL (default scheme %s): "
+                                   (elpher-get-default-url-scheme))
+                           (elpher-address-to-url address))))
+    (let ((trimmed-url (string-trim url)))
+      (unless (string-empty-p trimmed-url)
+        (elpher-with-clean-buffer
+         (elpher-visit-page
+          (elpher-page-from-url trimmed-url (elpher-get-default-url-scheme))))))))
 
 (defun elpher-redraw ()
   "Redraw current page."
@@ -2175,9 +2415,7 @@ When run interactively HOST-OR-URL is read from the minibuffer."
   (if (elpher-address-about-p (elpher-page-address elpher-current-page))
       (error "Cannot download %s"
              (elpher-page-display-string elpher-current-page))
-    (elpher-visit-page (elpher-make-page
-                        (elpher-page-display-string elpher-current-page)
-                        (elpher-page-address elpher-current-page))
+    (elpher-visit-page elpher-current-page
                        #'elpher-render-download
                        t)))
 
@@ -2220,8 +2458,12 @@ When run interactively HOST-OR-URL is read from the minibuffer."
 
 (defun elpher-info-page (page)
   "Display URL of PAGE in minibuffer."
-  (let ((address (elpher-page-address page)))
-    (message "%s" (elpher-address-to-url address))))
+  (let* ((address (elpher-page-address page))
+         (url (elpher-address-to-url address))
+         (iri (elpher-address-to-iri address)))
+    (if (equal url iri)
+        (message "%s" url)
+      (message "%s (Raw: %s)" iri url))))
 
 (defun elpher-info-link ()
   "Display information on page corresponding to link at point."
@@ -2347,13 +2589,11 @@ When run interactively HOST-OR-URL is read from the minibuffer."
 This mode is automatically enabled by the interactive
 functions which initialize the client, namely
 `elpher', and `elpher-go'."
-  (setq-local elpher--gemini-page-headings nil)
   (setq-local elpher-current-page nil)
   (setq-local elpher-history nil)
   (setq-local elpher-buffer-name (buffer-name))
   (setq-local bookmark-make-record-function #'elpher-bookmark-make-record)
-  (setq-local imenu-create-index-function (lambda () elpher--gemini-page-headings))
-  (setq-local xterm-color-preserve-properties t))
+  (setq-local imenu-create-index-function #'elpher-build-current-imenu-index))
 
 (when (fboundp 'evil-set-initial-state)
   (evil-set-initial-state 'elpher-mode 'motion))
diff --git a/elpher.texi b/elpher.texi
index 5892db3..8b5c65d 100644
--- a/elpher.texi
+++ b/elpher.texi
@@ -1,7 +1,7 @@
 \input texinfo @c -*-texinfo-*-
 
 @setfilename elpher.info
-@settitle Elpher Manual v3.2.2
+@settitle Elpher Manual v3.5.0
 
 @dircategory Emacs
 @direntry
@@ -11,7 +11,7 @@
 @copying
 This manual documents Elpher, a gopher and gemini client for Emacs.
 
-Copyright @copyright{} 2019, 2020, 2021 Tim Vaughan@*
+Copyright @copyright{} 2019-2023  Tim Vaughan@*
 Copyright @copyright{} 2021 Daniel Semyonov@*
 Copyright @copyright{} 2021 Alex Schroeder
 
@@ -23,7 +23,7 @@ version 3, or (at your option) any later version.
 
 Elpher is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-FITNElpher FOR A PARTICULAR PURPOSE. See the GNU General Public License in
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License in
 the file COPYING in the same directory as this file for more details.
 @end quotation
 @end copying
@@ -57,20 +57,40 @@ the file COPYING in the same directory as this file for more details.
 * Finger support::              Support for the Finger protocol
 * Local files::                 Opening local files in elpher
 * About pages::                 Special pages and how to reference them
+* ANSI support::                Notes in Elpher's ANSI support
 * Customization::               How to customize various aspects of Elpher
 * Command Index::
 * News::                        Changes introduced by major releases
-* Acknowledgements::            Contributors to Elpher
+* Contributors::                Contributors to Elpher
 
 @detailmenu
  --- The Detailed Node Listing ---
 
+Installation
+
+* Installing from ELPA or MELPA::  Installing from a package repository
+* Installing by hand::          Installing directly from the source
+
 Navigation
 
 * Within-page navigation::      Moving about within a page
 * Between-page navigation::     Commands for moving between pages
 * History and Caching::         Explanation of how Elpher represents history
 
+Gemini support
+
+* Client Certificates for Gemini::  Accessing secure gemini pages
+* Hiding preformatted text in text/gemini documents::  An accessibility option
+
+News
+
+* v3.5.0::
+* v3.4.0::
+* v3.3.0::
+* v3.2.0::
+* v3.1.0::
+* v3.0.0::
+
 @end detailmenu
 @end menu
 
@@ -122,11 +142,25 @@ have some ideas.
 @node Installation, Quick Start, Introduction, Top
 @chapter Installation
 
-Elpher is available from the MELPA package repository.  If you have
+@menu
+* Installing from ELPA or MELPA::  Installing from a package repository
+* Installing by hand::          Installing directly from the source
+@end menu
+
+@node Installing from ELPA or MELPA, Installing by hand, Installation, Installation
+@section Installing from ELPA or MELPA
+
+Elpher is available on the non-GNU ELPA package archive.  If you are
+using Emacs 28 or later, this archive should be available on your system
+by default.  For Emacs 27, you'll need to follow the instructions at
+@url{https://elpa.nongnu.org} to make the archive accessible.
+
+Alternatively, Elpher is available from the MELPA package archive.  If you have
 never installed packages from this repository before, you'll need
 to follow the instructions at @url{https://melpa.org/#/getting-started}.
 
-@noindent To install Elpher, enter the following:
+Once one of these package archives is installed, enter the following to
+install Elpher:
 
 @example
 @kbd{M-x package-install @key{RET} elpher @key{RET}}
@@ -138,6 +172,9 @@ to follow the instructions at @url{https://melpa.org/#/getting-started}.
 @kbd{M-x package-delete @key{RET} elpher @key{RET}}.
 @end example
 
+@node Installing by hand,  , Installing from ELPA or MELPA, Installation
+@section Installing by hand
+
 It is also possible to install Elpher directly by downloading the file
 @file{elpher.el} from @url{gopher://thelambdalab.xyz/1/projects/elpher}
 (you'll need to download the ``source archive'' and extract it), adding
@@ -310,7 +347,7 @@ causes Elpher to prompt for a filename in which to save the content.
 
 @item
 Following links of type `h' with a selector having the `URL:' prefix, or
-unsuported URLs in text files, will result in Elpher using an external
+unsupported URLs in text files, will result in Elpher using an external
 programme to open the URL.  This will be either the default system browser
 or, if the @code{elpher-open-urls-with-eww} customization variable is non-nil,
 Emacs' own EWW browser. (See @pxref{Customization}.) 
@@ -563,6 +600,12 @@ I should emphasize however that, while it is definitely functional,
 Elpher's gemini support is still experimental, and various aspects will
 change as the protocol develops further.
 
+@menu
+* Client Certificates for Gemini::  Accessing secure gemini pages
+* Hiding preformatted text in text/gemini documents::  An accessibility option
+@end menu
+
+@node Client Certificates for Gemini, Hiding preformatted text in text/gemini documents, Gemini support, Gemini support
 @section Client Certificates for Gemini
 
 Gemini makes explicit use of the client certificate mechanism that TLS
@@ -613,13 +656,14 @@ Pressing the @key{n} key will cause Elpher to begin the process of
 creating a new persistent certificate, using some additional
 details for which you will be prompted.
 Alternatively, pressing the @key{i} key will cause Elpher to ask for the
-locations of edisting key and certificate files to add to
+locations of existing key and certificate files to add to
 @code{elpher-certificate-directory} under the chosen name.
 
-Once a certificate is selected, it will be used for all subsequent TLS
-transactions to the host for which the certificate was created.
-It is immediately ``forgotten'' when a TLS connection to another host
-is attempted, or the following command is issued:
+Once a certificate is selected, it will be used for all subsequent
+gemini requests involving URLs begining with the URL for for which the
+certificate was created.  It is immediately ``forgotten'' when a TLS
+connection to a non-matching URL is attempted, or the following command
+is issued:
 
 @table @asis
 @keycmd{@key{F},elpher-forget-certificate}
@@ -630,6 +674,30 @@ In either case, ``forgetting'' means that the details of the key and
 certificate file pair are erased from memory.  Furthermore, in the case
 of throw-away certificates, the corresponding files are deleted.
 
+Persistant client certificates can be added to the alist contained in the
+customization variable @code{elpher-certificate-map} so that they are
+automatically activated whenever a gemini page with the matching URL
+prefix is visited.
+
+@node Hiding preformatted text in text/gemini documents,  , Client Certificates for Gemini, Gemini support
+@section Hiding preformatted text in text/gemini documents
+
+Preformatted text is often used to display ``ASCII art'' or other
+similar text-based artwork.  While for many this is a fun way to
+introduce personality into their gemini documents, such text can
+pose difficulties for screen readers.
+
+Setting the @code{elpher-gemini-hide-preformatted} customization option
+to non-nil causes Elpher to hide all preformatted text blocks by
+default.  In place of the preformatted text, Elpher instead displays the
+``alt text'' (if any is available), along with a button which can be
+used to make specific blocks visible as required.
+
+Other related customization options are
+@code{elpher-gemini-preformatted-toggle-label}, which is the label used
+for the toggle button, and
+@code{elpher-gemini-preformatted-toggle-bullet}, which is the margin
+string used to distinguish the line replacing the preformatted text.
 
 @node Finger support, Local files, Gemini support, Top
 @chapter Finger support
@@ -683,6 +751,11 @@ particular their extension.  The current mappings are as follows:
 Plain text documents.  All local text files are assumed to be
 UTF-8-encoded.
 
+@item @samp{gophermap},@samp{gopher}
+
+Gophermap files, i.e. files containing a valid directory list as specified
+by RFC 1436.
+
 @item @samp{gemini},@samp{gmi}
 
 Gemini documents (i.e. documents of MIME type ``text/gemini'').  All
@@ -703,12 +776,8 @@ much that elpher can sensibly do with unknown binary files.)
 
 @end table
 
-Gophermap files (i.e. files containing literally the intended output of
-querying a directory selector according to RFC 1436) cannot yet rendered
-using @samp{file:}.
 
-
-@node About pages, Customization, Local files, Top
+@node About pages, ANSI support, Local files, Top
 @chapter About pages
 
 Like other browsers, elpher makes certain internally-generated pages
@@ -719,7 +788,33 @@ the ``about:'' type.
 This means that these pages can be bookmarked and, more usefully,
 added to a local file to be rendered as a user-defined start page.
 
-@node Customization, Command Index, About pages, Top
+To see the address of a special page, simply visit the page and
+press @key{I}.
+
+@node ANSI support, Customization, About pages, Top
+@chapter ANSI support
+
+Depending on which parts of the gopher/gemini universe you frequent,
+you may occasionally stumble on sites which use ANSI escape codes to
+either produce specific characters or to colour text.
+
+By default, elpher uses Emacs' built-in ANSI rendering library,
+@samp{ansi-color}, to process ANSI codes.  This robustly interprets
+the escape codes but only supports 8 colours.  Any colours unsupported
+by the library are simply stripped, leaving uncoloured text in the
+majority of cases.
+
+To drastically improve the number of colours produced, install the
+@samp{xterm-color} package from MELPA.  This package will be automatically
+used by elpher if it is available, and supports 256 colours.
+This should be sufficient to properly render many ANSI colour
+gopher holes and gemini capsules quite nicely.
+
+In case you prefer to completely strip out the ANSI escape codes entirely
+by customizing the @code{elpher-filter-ansi-from-text} variable.
+
+
+@node Customization, Command Index, ANSI support, Top
 @chapter Customization
 
 Various parts of Elpher can be customized via the 
@@ -757,11 +852,70 @@ See the customization group itself for details.
 
 @printindex fn
 
-@node News, Acknowledgements, Command Index, Top
+@node News, Contributors, Command Index, Top
 @chapter News
 
 This chapter documents the major changes introduced by Elpher releases.
 
+@menu
+* v3.5.0::
+* v3.4.0::
+* v3.3.0::
+* v3.2.0::
+* v3.1.0::
+* v3.0.0::
+@end menu
+
+@node v3.5.0, v3.4.0, News, News
+@section v3.5.0
+
+@subsection Automatic activation of client certificates in gemini
+
+This version introduces a new customization variable
+@code{elpher-certificate-map} which allows you to pre-specify
+a set of gemini URLs and the client certificates which should
+be used when accessing them.
+
+@xref{Client Certificates for Gemini} for more details.
+
+@node v3.4.0, v3.3.0, v3.5.0, News
+@section v3.4.0
+
+@subsection Toggling preformatted text visibility
+
+This version introduces the ability to hide preformatted text in
+text/gemini documents by default.  To enable this, set the customization
+variable @code{elpher-gemini-hide-preformatted} to non-nil.  This causes
+all preformated text blocks to be replaced with their ``alt-text'' (if
+any is available) and a button for toggling the visibility of the text
+block.
+
+This feature is intended to make it easier for people using screen
+readers to read text/gemini documents.
+
+@node v3.3.0, v3.2.0, v3.4.0, News
+@section v3.3.0
+
+This version includes lots of bug fixes, as well as a couple of new
+features.
+
+@subsection Local gophermaps
+
+Local gophermaps can now be displayed using @samp{file:}. To render
+correctly, they must have the suffix @samp{.gopher} or @samp{.gophermap}.
+
+@subsection IRI support
+
+Internationalized Resource Identifiers (IRI) are an extension of URLs
+(or, more accurately, URIs) to support a larger set of characters beyond
+those in the US-ASCII character set.  They map directly onto URIs for
+backwards compatibility, but look like gibberish if not properly
+decoded.  When displaying URLs, Elpher now automatically decodes any IRI
+characters and displays the decoded IRI.  (For security reasons, the
+@code{elpher-info-current} command (@kbd{I}) always displays both the
+decoded IRI and the URI when they differ.)
+
+@node v3.2.0, v3.1.0, v3.3.0, News
 @section v3.2.0
 
 This version introduces several minor changes which, together, make it
@@ -771,7 +925,7 @@ possible to set up alternative start pages configured to your liking.
 
 Special elpher pages such as the welcome page (previously ``start''
 page), the bookmarks page, the browsing history stack and list of
-visited pages are now addressible via @samp{about:} URLs.  For instance,
+visited pages are now addressable via @samp{about:} URLs.  For instance,
 the standard welcome page has the address @samp{about:welcome}.
 
 @subsection Local files
@@ -788,6 +942,7 @@ of the document to be loaded as elpher's ``start page''.  By default
 this is set to @samp{about:welcome}, but any elpher-accessible URL is
 valid. @pxref{Customization} for suggestions.
 
+@node v3.1.0, v3.0.0, v3.2.0, News
 @section v3.1.0
 
 @subsection Bookmarks system
@@ -810,6 +965,7 @@ use the customization variable @code{elpher-use-emacs-bookmark-menu}
 to have the @key{B} key open the Emacs bookmark menu directly, as in
 the previous release.
 
+@node v3.0.0,  , v3.1.0, News
 @section v3.0.0
 
 @subsection Bookmarks system
@@ -853,35 +1009,33 @@ mail system.
 Gemini document headers are now navigable via imenu.
 For details, @xref{Imenu, , , emacs, The Emacs Editor}.
 
-@node Acknowledgements,  , News, Top
-@chapter Acknowledgements
+@node Contributors,  , News, Top
+@chapter Contributors
 
-Elpher was originally written by Tim Vaughan. Recent maintenance has
-been done by and with the help of Alex Schroeder. In addition, the
-following people (in alphabetical order) have generously provided
-assistance and/or patches:
+Elpher was originally written and is currently maintained by Tim Vaughan
+@email{plugd@@thelambdalab.xyz}.  Significant improvements and
+maintenance have also been contributed by and with the help of Alex
+Schroeder @email{alex@@gnu.org}.  In addition, the following people have
+all generously provided assistance and/or patches:
 
 @itemize
-@item Alexis
-@item Christopher Brannon
-@item Zhiwei Chen
-@item condy0919
-@item Étienne Deparis
-@item Roy Koushik
-@item Simon Nicolussi
-@item Noodles!
-@item Jens Östlund
-@item Abhiseck Paira
-@item F. Jason Park
-@item Omar Polo
-@item Koushk Roy
-@item Michel Alexandre Salim
-@item Alex Schroeder
-@item Daniel Semyonov
-@item Simon South
-@item Bradley Thornton
-@item Vee
+@item Jens Östlund @email{jostlund@@gmail.com}
+@item F. Jason Park @email{jp@@neverwas.me}
+@item Christopher Brannon @email{chris@@the-brannons.com}
+@item Omar Polo @email{op@@omarpolo.com}
+@item Noodles! @email{nnoodle@@chiru.no}
+@item Abhiseck Paira @email{abhiseckpaira@@disroot.org}
+@item Zhiwei Chen @email{chenzhiwei03@@kuaishou.com}
+@item condy0919 @email{condy0919@@gmail.com}
+@item Alexis @email{flexibeast@@gmail.com}
+@item Étienne Deparis @email{etienne@@depar.is}
+@item Simon Nicolussi @email{sinic@@sinic.name}
+@item Michel Alexandre Salim @email{michel@@michel-slm.name}
+@item Koushk Roy @email{kroy@@twilio.com}
+@item Vee @email{vee@@vnsf.xyz}
+@item Simon South @email{simon@@simonsouth.net}
+@item Daniel Semyonov @email{daniel@@dsemy.com}
+@item Bradley Thornton @email{bradley@@northtech.us}
 @end itemize
 
-
 @bye

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/share/doc/elpa-elpher/README.gz
-rw-r--r--  root/root   /usr/share/emacs/site-lisp/elpa-src/elpher-3.5.0/elpher-autoloads.el
-rw-r--r--  root/root   /usr/share/emacs/site-lisp/elpa-src/elpher-3.5.0/elpher-pkg.el
-rw-r--r--  root/root   /usr/share/emacs/site-lisp/elpa-src/elpher-3.5.0/elpher.el

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/share/doc/elpa-elpher/README
-rw-r--r--  root/root   /usr/share/emacs/site-lisp/elpa-src/elpher-3.2.2/elpher-autoloads.el
-rw-r--r--  root/root   /usr/share/emacs/site-lisp/elpa-src/elpher-3.2.2/elpher-pkg.el
-rw-r--r--  root/root   /usr/share/emacs/site-lisp/elpa-src/elpher-3.2.2/elpher.el

No differences were encountered in the control files

More details

Full run details