Added URL to header.
[elpher.git] / elpher.el
index c22f7b8..9c0138b 100644 (file)
--- a/elpher.el
+++ b/elpher.el
@@ -1,10 +1,10 @@
-;;; elpher.el --- A friendly gopher client.
+;;; elpher.el --- A friendly gopher client.  -*- lexical-binding:t -*-
 
 ;; Copyright (C) 2019 Tim Vaughan
 
 ;; Author: Tim Vaughan <tgvaughan@gmail.com>
 ;; Created: 11 April 2019
 
 ;; Copyright (C) 2019 Tim Vaughan
 
 ;; Author: Tim Vaughan <tgvaughan@gmail.com>
 ;; Created: 11 April 2019
-;; Version: 2.0.0
+;; Version: 2.2.0
 ;; Keywords: comm gopher
 ;; Homepage: https://github.com/tgvaughan/elpher
 ;; Package-Requires: ((emacs "26"))
 ;; Keywords: comm gopher
 ;; Homepage: https://github.com/tgvaughan/elpher
 ;; Package-Requires: ((emacs "26"))
@@ -36,7 +36,8 @@
 ;; - pleasant and configurable colouring of Gopher directories,
 ;; - direct visualisation of image files,
 ;; - a simple bookmark management system,
 ;; - pleasant and configurable colouring of Gopher directories,
 ;; - direct visualisation of image files,
 ;; - a simple bookmark management system,
-;; - connections using TLS encryption.
+;; - connections using TLS encryption,
+;; - basic support for the fledgling Gemini protocol.
 
 ;; To launch Elpher, simply use 'M-x elpher'.  This will open a start
 ;; page containing information on key bindings and suggested starting
 
 ;; To launch Elpher, simply use 'M-x elpher'.  This will open a start
 ;; page containing information on key bindings and suggested starting
 (require 'pp)
 (require 'shr)
 (require 'url-util)
 (require 'pp)
 (require 'shr)
 (require 'url-util)
+(require 'subr-x)
 
 
 ;;; Global constants
 ;;
 
 
 
 ;;; Global constants
 ;;
 
-(defconst elpher-version "2.0.0"
+(defconst elpher-version "2.2.0"
   "Current version of elpher.")
 
 (defconst elpher-margin-width 6
   "Current version of elpher.")
 
 (defconst elpher-margin-width 6
@@ -75,7 +77,7 @@
     ((gopher ?4) elpher-get-gopher-node elpher-render-download "bin" elpher-binary)
     ((gopher ?5) elpher-get-gopher-node elpher-render-download "bin" elpher-binary)
     ((gopher ?7) elpher-get-gopher-query-node elpher-render-index "?" elpher-search)
     ((gopher ?4) elpher-get-gopher-node elpher-render-download "bin" elpher-binary)
     ((gopher ?5) elpher-get-gopher-node elpher-render-download "bin" elpher-binary)
     ((gopher ?7) elpher-get-gopher-query-node elpher-render-index "?" elpher-search)
-    ((gopher ?9) elpher-get-gopher-node elpher-render-node-download "bin" elpher-binary)
+    ((gopher ?9) elpher-get-gopher-node elpher-render-download "bin" elpher-binary)
     ((gopher ?g) elpher-get-gopher-node elpher-render-image "img" elpher-image)
     ((gopher ?p) elpher-get-gopher-node elpher-render-image "img" elpher-image)
     ((gopher ?I) elpher-get-gopher-node elpher-render-image "img" elpher-image)
     ((gopher ?g) elpher-get-gopher-node elpher-render-image "img" elpher-image)
     ((gopher ?p) elpher-get-gopher-node elpher-render-image "img" elpher-image)
     ((gopher ?I) elpher-get-gopher-node elpher-render-image "img" elpher-image)
@@ -88,7 +90,7 @@
     (other-url elpher-get-other-url-node nil "url" elpher-other-url)
     ((special bookmarks) elpher-get-bookmarks-node nil)
     ((special start) elpher-get-start-node nil))
     (other-url elpher-get-other-url-node nil "url" elpher-other-url)
     ((special bookmarks) elpher-get-bookmarks-node nil)
     ((special start) elpher-get-start-node nil))
-  "Association list from types to getters, margin codes and index faces.")
+  "Association list from types to getters, renderers, margin codes and index faces.")
 
 
 ;;; Customization group
 
 
 ;;; Customization group
   "Face used for html type directory records.")
 
 (defface elpher-gemini
   "Face used for html type directory records.")
 
 (defface elpher-gemini
-  '((t :inherit font-lock-function-name-face))
+  '((t :inherit font-lock-regexp-grouping-backslash))
   "Face used for html type directory records.")
 
 (defface elpher-other-url
   "Face used for html type directory records.")
 
 (defface elpher-other-url
 Otherwise, use the system browser via the BROWSE-URL function."
   :type '(boolean))
 
 Otherwise, use the system browser via the BROWSE-URL function."
   :type '(boolean))
 
-(defcustom elpher-buttonify-urls-in-directories nil
+(defcustom elpher-buttonify-urls-in-directories t
   "If non-nil, turns URLs matched in directories into clickable buttons."
   :type '(boolean))
 
   "If non-nil, turns URLs matched in directories into clickable buttons."
   :type '(boolean))
 
@@ -189,37 +191,49 @@ allows switching from an encrypted channel back to plain text without user input
   (let ((data (match-data))) ; Prevent parsing clobbering match data
     (unwind-protect
         (let ((url (url-generic-parse-url url-string)))
   (let ((data (match-data))) ; Prevent parsing clobbering match data
     (unwind-protect
         (let ((url (url-generic-parse-url url-string)))
-          (setf (url-fullness url) t)
-          (setf (url-filename url)
-                (url-unhex-string (url-filename url)))
-          (unless (url-type url)
-            (setf (url-type url) "gopher"))
-          (let ((is-gopher (or (equal "gopher" (url-type url))
-                               (equal "gophers" (url-type url))))
-                (is-gemini (equal "gemini" (url-type url))))
-            (when is-gopher
+          (unless (and (not (url-fullness url)) (url-type url))
+            (setf (url-fullness url) t)
+            (setf (url-filename url)
+                  (url-unhex-string (url-filename url)))
+            (unless (url-type url)
+              (setf (url-type url) "gopher"))
+            (when (or (equal "gopher" (url-type url))
+                      (equal "gophers" (url-type url)))
               ;; Gopher defaults
               (unless (url-host url)
                 (setf (url-host url) (url-filename url))
                 (setf (url-filename url) ""))
               (when (or (equal (url-filename url) "")
                         (equal (url-filename url) "/"))
               ;; Gopher defaults
               (unless (url-host url)
                 (setf (url-host url) (url-filename url))
                 (setf (url-filename url) ""))
               (when (or (equal (url-filename url) "")
                         (equal (url-filename url) "/"))
-                (setf (url-filename url) "/1"))))
+                (setf (url-filename url) "/1")))
+            (when (equal "gemini" (url-type url))
+              ;; Gemini defaults
+              (if (equal (url-filename url) "")
+                  (setf (url-filename url) "/"))))
           url)
       (set-match-data data))))
 
 (defun elpher-make-gopher-address (type selector host port &optional tls)
           url)
       (set-match-data data))))
 
 (defun elpher-make-gopher-address (type selector host port &optional tls)
-  "Create an ADDRESS object corresponding to the given gopher directory record
-attributes: TYPE, SELECTOR, HOST and PORT."
-  (if (and (equal type ?h)
-           (string-prefix-p "URL:" selector))
-      (elpher-address-from-url (elt (split-string selector "URL:") 1))
+  "Create an ADDRESS object using gopher directory record attributes.
+The basic attributes include: TYPE, SELECTOR, HOST and PORT.
+If the optional attribute TLS is non-nil, the address will be marked as
+requiring gopher-over-TLS."
+  (cond
+   ((and (equal type ?h)
+         (string-prefix-p "URL:" selector))
+    (elpher-address-from-url (elt (split-string selector "URL:") 1)))
+   ((equal type ?8)
+    (elpher-address-from-url
+     (concat "telnet"
+             "://" host
+             ":" (number-to-string port))))
+   (t
     (elpher-address-from-url
      (concat "gopher" (if tls "s" "")
              "://" host
              ":" (number-to-string port)
              "/" (string type)
     (elpher-address-from-url
      (concat "gopher" (if tls "s" "")
              "://" host
              ":" (number-to-string port)
              "/" (string type)
-             selector))))
+             selector)))))
 
 (defun elpher-make-special-address (type)
   "Create an ADDRESS object corresponding to the given special page symbol TYPE."
 
 (defun elpher-make-special-address (type)
   "Create an ADDRESS object corresponding to the given special page symbol TYPE."
@@ -232,7 +246,9 @@ attributes: TYPE, SELECTOR, HOST and PORT."
     nil))
 
 (defun elpher-address-type (address)
     nil))
 
 (defun elpher-address-type (address)
-  "Retrieve selector type from ADDRESS object."
+  "Retrieve type of ADDRESS object.
+This is used to determine how to retrieve and render the document the
+address refers to, via the table `elpher-type-map'."
   (if (symbolp address)
       (list 'special address)
     (let ((protocol (url-type address)))
   (if (symbolp address)
       (list 'special address)
     (let ((protocol (url-type address)))
@@ -244,14 +260,19 @@ attributes: TYPE, SELECTOR, HOST and PORT."
                      (string-to-char (substring (url-filename address) 1)))))
             ((equal protocol "gemini")
              'gemini)
                      (string-to-char (substring (url-filename address) 1)))))
             ((equal protocol "gemini")
              'gemini)
+            ((equal protocol "telnet")
+             'telnet)
             (t 'other-url)))))
 
 (defun elpher-address-protocol (address)
             (t 'other-url)))))
 
 (defun elpher-address-protocol (address)
+  "Retrieve the transport protocol for ADDRESS.  This is nil for special addresses."
   (if (symbolp address)
       nil
     (url-type address)))
 
 (defun elpher-address-filename (address)
   (if (symbolp address)
       nil
     (url-type address)))
 
 (defun elpher-address-filename (address)
+  "Retrieve the filename component of ADDRESS.
+For gopher addresses this is a combination of the selector type and selector."
   (if (symbolp address)
       nil
     (url-filename address)))
   (if (symbolp address)
       nil
     (url-filename address)))
@@ -262,7 +283,15 @@ attributes: TYPE, SELECTOR, HOST and PORT."
 
 (defun elpher-address-port (address)
   "Retrieve port from ADDRESS object."
 
 (defun elpher-address-port (address)
   "Retrieve port from ADDRESS object."
-  (url-port address))
+  (if (symbolp address)
+      nil)
+  (if (> (url-port address) 0)
+      (url-port address)
+    (or (and (or (equal (url-type address) "gopher")
+                 (equal (url-type address) "gophers"))
+             70)
+        (and (equal (url-type address) "gemini")
+             1965))))
 
 (defun elpher-address-special-p (address)
   "Return non-nil if ADDRESS object is special (e.g. start page, bookmarks page)."
 
 (defun elpher-address-special-p (address)
   "Return non-nil if ADDRESS object is special (e.g. start page, bookmarks page)."
@@ -361,9 +390,9 @@ unless PRESERVE-PARENT is non-nil."
         (`(gopher ,type-char)
          (error "Unsupported gopher selector type '%c' for '%s'"
                 type-char (elpher-address-to-url address)))
         (`(gopher ,type-char)
          (error "Unsupported gopher selector type '%c' for '%s'"
                 type-char (elpher-address-to-url address)))
-        (else
+        (other
          (error "Unsupported address type '%S' for '%s'"
          (error "Unsupported address type '%S' for '%s'"
-                type (elpher-address-to-url address)))))))
+                other (elpher-address-to-url address)))))))
 
 (defun elpher-visit-parent-node ()
   "Visit the parent of the current node."
 
 (defun elpher-visit-parent-node ()
   "Visit the parent of the current node."
@@ -395,7 +424,12 @@ unless PRESERVE-PARENT is non-nil."
 (defun elpher-update-header ()
   "If `elpher-use-header' is true, display current node info in window header."
   (if elpher-use-header
 (defun elpher-update-header ()
   "If `elpher-use-header' is true, display current node info in window header."
   (if elpher-use-header
-      (setq header-line-format (elpher-node-display-string elpher-current-node))))
+      (let* ((display-string (elpher-node-display-string elpher-current-node))
+             (address (elpher-node-address elpher-current-node))
+             (url-string (if (elpher-address-special-p address)
+                             ""
+                           (concat "  -  " (elpher-address-to-url address) ""))))
+        (setq header-line-format (list display-string url-string)))))
 
 (defmacro elpher-with-clean-buffer (&rest args)
   "Evaluate ARGS with a clean *elpher* buffer as current."
 
 (defmacro elpher-with-clean-buffer (&rest args)
   "Evaluate ARGS with a clean *elpher* buffer as current."
@@ -428,22 +462,20 @@ away CRs and any terminating period."
                                            (replace-regexp-in-string "\r" "" string))))
 
 
                                            (replace-regexp-in-string "\r" "" string))))
 
 
-;;; Index rendering
-;;
-
 ;;; Network error reporting
 ;;
 
 (defun elpher-network-error (address error)
 ;;; Network error reporting
 ;;
 
 (defun elpher-network-error (address error)
+  "Display ERROR message following unsuccessful negotiation with ADDRESS."
   (elpher-with-clean-buffer
    (insert (propertize "\n---- ERROR -----\n\n" 'face 'error)
            "When attempting to retrieve " (elpher-address-to-url address) ":\n"
   (elpher-with-clean-buffer
    (insert (propertize "\n---- ERROR -----\n\n" 'face 'error)
            "When attempting to retrieve " (elpher-address-to-url address) ":\n"
-           (error-message-string the-error) ".\n"
+           (error-message-string error) ".\n"
            (propertize "\n----------------\n\n" 'face 'error)
            "Press 'u' to return to the previous page.")))
 
 
            (propertize "\n----------------\n\n" 'face 'error)
            "Press 'u' to return to the previous page.")))
 
 
-;;; Gopher selector retrieval (all kinds)
+;;; Gopher selector retrieval
 ;;
 
 (defun elpher-process-cleanup ()
 ;;
 
 (defun elpher-process-cleanup ()
@@ -469,20 +501,18 @@ up to the calling function."
       (if (gnutls-available-p)
           (when (not elpher-use-tls)
             (setq elpher-use-tls t)
       (if (gnutls-available-p)
           (when (not elpher-use-tls)
             (setq elpher-use-tls t)
-            (message "Engaging TLS mode."))
-        (error "Cannot retrieve TLS selector: GnuTLS not available")))
+            (message "Engaging TLS gopher mode."))
+        (error "Cannot retrieve TLS gopher selector: GnuTLS not available")))
   (condition-case the-error
       (let* ((kill-buffer-query-functions nil)
              (proc (open-network-stream "elpher-process"
                                        nil
                                        (elpher-address-host address)
   (condition-case the-error
       (let* ((kill-buffer-query-functions nil)
              (proc (open-network-stream "elpher-process"
                                        nil
                                        (elpher-address-host address)
-                                       (if (> (elpher-address-port address) 0)
-                                           (elpher-address-port address)
-                                         70)
+                                       (elpher-address-port address)
                                        :type (if elpher-use-tls 'tls 'plain))))
         (set-process-coding-system proc 'binary)
         (set-process-filter proc
                                        :type (if elpher-use-tls 'tls 'plain))))
         (set-process-coding-system proc 'binary)
         (set-process-filter proc
-                            (lambda (proc string)
+                            (lambda (_proc string)
                               (setq elpher-selector-string
                                     (concat elpher-selector-string string))))
         (set-process-sentinel proc after)
                               (setq elpher-selector-string
                                     (concat elpher-selector-string string))))
         (set-process-sentinel proc after)
@@ -495,7 +525,7 @@ up to the calling function."
               (or elpher-auto-disengage-TLS
                   (yes-or-no-p "Could not establish encrypted connection.  Disable TLS mode? ")))
          (progn
               (or elpher-auto-disengage-TLS
                   (yes-or-no-p "Could not establish encrypted connection.  Disable TLS mode? ")))
          (progn
-           (message "Disengaging TLS mode.")
+           (message "Disengaging TLS gopher mode.")
            (setq elpher-use-tls nil)
            (elpher-get-selector address after))
        (elpher-process-cleanup)
            (setq elpher-use-tls nil)
            (elpher-get-selector address after))
        (elpher-process-cleanup)
@@ -508,17 +538,19 @@ up to the calling function."
                   "Press 'u' to return to the previous page.")))))))
 
 (defun elpher-get-gopher-node (renderer)
                   "Press 'u' to return to the previous page.")))))))
 
 (defun elpher-get-gopher-node (renderer)
+  "Getter function for gopher nodes.
+The RENDERER procedure is used to display the contents of the node
+once they are retrieved from the gopher server."
   (let* ((address (elpher-node-address elpher-current-node))
          (content (elpher-get-cached-content address)))
     (if (and content (funcall renderer nil))
   (let* ((address (elpher-node-address elpher-current-node))
          (content (elpher-get-cached-content address)))
     (if (and content (funcall renderer nil))
-        (progn
-          (elpher-with-clean-buffer)
-          (insert content)
-          (elpher-restore-pos))
+        (elpher-with-clean-buffer
+         (insert content)
+         (elpher-restore-pos))
       (elpher-with-clean-buffer
        (insert "LOADING... (use 'u' to cancel)"))
       (elpher-get-selector address
       (elpher-with-clean-buffer
        (insert "LOADING... (use 'u' to cancel)"))
       (elpher-get-selector address
-                           (lambda (proc event)
+                           (lambda (_proc event)
                              (unless (string-prefix-p "deleted" event)
                                (funcall renderer elpher-selector-string)
                                (elpher-restore-pos)))))))
                              (unless (string-prefix-p "deleted" event)
                                (funcall renderer elpher-selector-string)
                                (elpher-restore-pos)))))))
@@ -531,17 +563,18 @@ up to the calling function."
   ;; LF-only servers sadly exist, hence the following.
   (let ((str-processed (elpher-preprocess-text-response string)))
     (dolist (line (split-string str-processed "\n"))
   ;; LF-only servers sadly exist, hence the following.
   (let ((str-processed (elpher-preprocess-text-response string)))
     (dolist (line (split-string str-processed "\n"))
-      (unless (= (length line) 0)
-        (let* ((type (elt line 0))
-               (fields (split-string (substring line 1) "\t"))
-               (display-string (elt fields 0))
-               (selector (elt fields 1))
-               (host (elt fields 2))
-               (port (if (elt fields 3)
-                         (string-to-number (elt fields 3))
-                       nil))
-               (address (elpher-make-gopher-address type selector host port)))
-          (elpher-insert-index-record display-string address))))))
+      (ignore-errors
+        (unless (= (length line) 0)
+          (let* ((type (elt line 0))
+                 (fields (split-string (substring line 1) "\t"))
+                 (display-string (elt fields 0))
+                 (selector (elt fields 1))
+                 (host (elt fields 2))
+                 (port (if (elt fields 3)
+                           (string-to-number (elt fields 3))
+                         nil))
+                 (address (elpher-make-gopher-address type selector host port)))
+            (elpher-insert-index-record display-string address)))))))
 
 (defun elpher-insert-margin (&optional type-name)
   "Insert index margin, optionally containing the TYPE-NAME, into the current buffer."
 
 (defun elpher-insert-margin (&optional type-name)
   "Insert index margin, optionally containing the TYPE-NAME, into the current buffer."
@@ -568,8 +601,8 @@ If ADDRESS is not supplied or nil the record is rendered as an
   (let* ((type (if address (elpher-address-type address) nil))
          (type-map-entry (cdr (assoc type elpher-type-map))))
     (if type-map-entry
   (let* ((type (if address (elpher-address-type address) nil))
          (type-map-entry (cdr (assoc type elpher-type-map))))
     (if type-map-entry
-        (let* ((margin-code (elt type-map-entry 1))
-               (face (elt type-map-entry 2))
+        (let* ((margin-code (elt type-map-entry 2))
+               (face (elt type-map-entry 3))
                (node (elpher-make-node display-string address)))
           (elpher-insert-margin margin-code)
           (insert-text-button display-string
                (node (elpher-make-node display-string address)))
           (elpher-insert-margin margin-code)
           (insert-text-button display-string
@@ -581,11 +614,11 @@ If ADDRESS is not supplied or nil the record is rendered as an
       (pcase type
         ((or '(gopher ?i) 'nil) ;; Information
          (elpher-insert-margin)
       (pcase type
         ((or '(gopher ?i) 'nil) ;; Information
          (elpher-insert-margin)
-         (insert (propertize
-                  (if elpher-buttonify-urls-in-directories
-                      (elpher-buttonify-urls display-string)
-                    display-string)
-                  'face 'elpher-info)))
+         (let ((propertized-display-string
+                (propertize display-string 'face 'elpher-info)))
+           (insert (if elpher-buttonify-urls-in-directories
+                       (elpher-buttonify-urls propertized-display-string)
+                     propertized-display-string))))
         (`(gopher ,selector-type) ;; Unknown
          (elpher-insert-margin (concat (char-to-string selector-type) "?"))
          (insert (propertize display-string
         (`(gopher ,selector-type) ;; Unknown
          (elpher-insert-margin (concat (char-to-string selector-type) "?"))
          (insert (propertize display-string
@@ -597,8 +630,8 @@ If ADDRESS is not supplied or nil the record is rendered as an
   (let ((node (button-get button 'elpher-node)))
     (elpher-visit-node node)))
 
   (let ((node (button-get button 'elpher-node)))
     (elpher-visit-node node)))
 
-(defun elpher-render-index (data)
-  "Render DATA as an index."
+(defun elpher-render-index (data &optional _mime-type-string)
+  "Render DATA as an index.  MIME-TYPE-STRING is unused."
   (elpher-with-clean-buffer
    (if (not data)
        t
   (elpher-with-clean-buffer
    (if (not data)
        t
@@ -610,7 +643,7 @@ If ADDRESS is not supplied or nil the record is rendered as an
 
 (defconst elpher-url-regex
   "\\([a-zA-Z]+\\)://\\([a-zA-Z0-9.\-]+\\|\[[a-zA-Z0-9:]+\]\\)\\(?3::[0-9]+\\)?\\(?4:/[^<> \r\n\t(),]*\\)?"
 
 (defconst elpher-url-regex
   "\\([a-zA-Z]+\\)://\\([a-zA-Z0-9.\-]+\\|\[[a-zA-Z0-9:]+\]\\)\\(?3::[0-9]+\\)?\\(?4:/[^<> \r\n\t(),]*\\)?"
-  "Regexp used to locate and buttinofy URLs in text files loaded by elpher.")
+  "Regexp used to locate and buttniofy URLs in text files loaded by elpher.")
 
 (defun elpher-buttonify-urls (string)
   "Turn substrings which look like urls in STRING into clickable buttons."
 
 (defun elpher-buttonify-urls (string)
   "Turn substrings which look like urls in STRING into clickable buttons."
@@ -625,27 +658,26 @@ If ADDRESS is not supplied or nil the record is rendered as an
                             'elpher-node  node
                             'action #'elpher-click-link
                             'follow-link t
                             'elpher-node  node
                             'action #'elpher-click-link
                             'follow-link t
-                            'help-echo (elpher-node-button-help node))))
+                            'help-echo (elpher-node-button-help node)
+                            'face 'button)))
     (buffer-string)))
 
     (buffer-string)))
 
-(defun elpher-render-text (data)
-  "Render DATA as text."
+(defun elpher-render-text (data &optional _mime-type-string)
+  "Render DATA as text.  MIME-TYPE-STRING is unused."
   (elpher-with-clean-buffer
    (if (not data)
        t
   (elpher-with-clean-buffer
    (if (not data)
        t
-     (insert (elpher-buttonify-urls
-              (elpher-preprocess-text-response)
-              elpher-selector-string))
+     (insert (elpher-buttonify-urls (elpher-preprocess-text-response data)))
      (elpher-cache-content
       (elpher-node-address elpher-current-node)
       (buffer-string)))))
 
 ;; Image retrieval
 
      (elpher-cache-content
       (elpher-node-address elpher-current-node)
       (buffer-string)))))
 
 ;; Image retrieval
 
-(defun elpher-render-image (data)
-  "Display DATA as image."
+(defun elpher-render-image (data &optional _mime-type-string)
+  "Display DATA as image.  MIME-TYPE-STRING is unused."
   (if (not data)
   (if (not data)
-      f
+      nil
     (if (display-images-p)
         (progn
           (let ((image (create-image
     (if (display-images-p)
         (progn
           (let ((image (create-image
@@ -654,11 +686,13 @@ If ADDRESS is not supplied or nil the record is rendered as an
             (elpher-with-clean-buffer
              (insert-image image)
              (elpher-restore-pos))))
             (elpher-with-clean-buffer
              (insert-image image)
              (elpher-restore-pos))))
-      (elpher-save-to-file data))))
+      (elpher-render-download data))))
 
 ;; Search retrieval and rendering
 
 (defun elpher-get-gopher-query-node (renderer)
 
 ;; Search retrieval and rendering
 
 (defun elpher-get-gopher-query-node (renderer)
+  "Getter for gopher addresses requiring input.
+The response is rendered using the rendering function RENDERER."
    (let* ((address (elpher-node-address elpher-current-node))
           (content (elpher-get-cached-content address))
           (aborted t))
    (let* ((address (elpher-node-address elpher-current-node))
           (content (elpher-get-cached-content address))
           (aborted t))
@@ -680,7 +714,7 @@ If ADDRESS is not supplied or nil the record is rendered as an
             (elpher-with-clean-buffer
              (insert "LOADING RESULTS... (use 'u' to cancel)"))
             (elpher-get-selector search-address
             (elpher-with-clean-buffer
              (insert "LOADING RESULTS... (use 'u' to cancel)"))
             (elpher-get-selector search-address
-                                 (lambda (proc event)
+                                 (lambda (_proc event)
                                    (unless (string-prefix-p "deleted" event)
                                      (funcall renderer elpher-selector-string)
                                      (elpher-restore-pos)))))
                                    (unless (string-prefix-p "deleted" event)
                                      (funcall renderer elpher-selector-string)
                                      (elpher-restore-pos)))))
@@ -689,10 +723,10 @@ If ADDRESS is not supplied or nil the record is rendered as an
  
 ;; Raw server response rendering
 
  
 ;; Raw server response rendering
 
-(defun elpher-render-raw (data)
-  "Display raw DATA in buffer."
+(defun elpher-render-raw (data &optional _mime-type-string)
+  "Display raw DATA in buffer.  MIME-TYPE-STRING is unused."
   (if (not data)
   (if (not data)
-      f
+      nil
     (elpher-with-clean-buffer
      (insert data)
      (goto-char (point-min)))
     (elpher-with-clean-buffer
      (insert data)
      (goto-char (point-min)))
@@ -700,10 +734,10 @@ If ADDRESS is not supplied or nil the record is rendered as an
 
 ;; File save "rendering"
 
 
 ;; File save "rendering"
 
-(defun elpher-render-download (data)
-  "Save DATA to file."
+(defun elpher-render-download (data &optional _mime-type-string)
+  "Save DATA to file.  MIME-TYPE-STRING is unused."
   (if (not data)
   (if (not data)
-      f
+      nil
     (let* ((address (elpher-node-address elpher-current-node))
            (selector (elpher-gopher-address-selector address)))
       (elpher-visit-parent-node) ; Do first in case of non-local exits.
     (let* ((address (elpher-node-address elpher-current-node))
            (selector (elpher-gopher-address-selector address)))
       (elpher-visit-parent-node) ; Do first in case of non-local exits.
@@ -712,21 +746,21 @@ If ADDRESS is not supplied or nil the record is rendered as an
                                        nil nil nil
                                        (if (> (length filename-proposal) 0)
                                            filename-proposal
                                        nil nil nil
                                        (if (> (length filename-proposal) 0)
                                            filename-proposal
-                                         "gopher.file"))))
-        (with-temp-file filename
-          (insert elpher-selector-string)
-          (message (format "Saved to file %s."
-                           elpher-download-filename)))))))
+                                         "download.file"))))
+        (let ((coding-system-for-write 'binary))
+          (with-temp-file filename
+            (insert data)))
+        (message (format "Saved to file %s." filename))))))
 
 ;; HTML rendering
 
 
 ;; HTML rendering
 
-(defun elpher-render-html (data)
-  "Render DATA as HTML using shr."
+(defun elpher-render-html (data &optional _mime-type-string)
+  "Render DATA as HTML using shr.  MIME-TYPE-STRING is unused."
   (elpher-with-clean-buffer
    (if (not data)
        t
      (let ((dom (with-temp-buffer
   (elpher-with-clean-buffer
    (if (not data)
        t
      (let ((dom (with-temp-buffer
-                  (insert string)
+                  (insert data)
                   (libxml-parse-html-region (point-min) (point-max)))))
        (shr-insert-document dom)))))
 
                   (libxml-parse-html-region (point-min) (point-max)))))
        (shr-insert-document dom)))))
 
@@ -734,9 +768,8 @@ If ADDRESS is not supplied or nil the record is rendered as an
 
 (defvar elpher-gemini-response)
 
 
 (defvar elpher-gemini-response)
 
-
-(defun elpher-get-gemini-response (address renderer)
-  "Retrieve gemini ADDRESS, then execute RENDERER on the result.
+(defun elpher-get-gemini-response (address after)
+  "Retrieve gemini ADDRESS, then execute AFTER.
 The response is stored in the variable â€˜elpher-gemini-response’."
   (setq elpher-gemini-response "")
   (if (not (gnutls-available-p))
 The response is stored in the variable â€˜elpher-gemini-response’."
   (setq elpher-gemini-response "")
   (if (not (gnutls-available-p))
@@ -745,59 +778,63 @@ The response is stored in the variable â€˜elpher-gemini-response’."
            (proc (open-network-stream "elpher-process"
                                       nil
                                       (elpher-address-host address)
            (proc (open-network-stream "elpher-process"
                                       nil
                                       (elpher-address-host address)
-                                      (if (> (elpher-address-port address) 0)
-                                          (elpher-address-port address)
-                                        1965)
+                                      (elpher-address-port address)
                                       :type 'tls)))
       (set-process-coding-system proc 'binary)
       (set-process-filter proc
                                       :type 'tls)))
       (set-process-coding-system proc 'binary)
       (set-process-filter proc
-                          (lambda (proc string)
+                          (lambda (_proc string)
                             (setq elpher-gemini-response
                                   (concat elpher-gemini-response string))))
                             (setq elpher-gemini-response
                                   (concat elpher-gemini-response string))))
-      (set-process-sentinel proc
-                            (lambda (proc event)
-                              (unless (string-prefix-p "deleted" event)
-                                (elpher-process-gemini-response #'after))))
+      (set-process-sentinel proc after)
       (process-send-string proc
                            (concat (elpher-address-to-url address) "\r\n")))))
 
 
 (defun elpher-process-gemini-response (renderer)
       (process-send-string proc
                            (concat (elpher-address-to-url address) "\r\n")))))
 
 
 (defun elpher-process-gemini-response (renderer)
-  "Process the gemini response found in the variable elpher-gemini-response and
-pass the result to RENDERER."
+  "Process the gemini response and pass the result to RENDERER.
+The response is assumed to be in the variable `elpher-gemini-response'."
   (condition-case the-error
   (condition-case the-error
-      (unless (string-prefix-p "deleted" event)
-        (let* ((response-header (car (split-string elpher-gemini-response "\r\n")))
-               (response-body (substring elpher-gemini-response
-                                         (+ (string-match "\r\n" elpher-gemini-response) 2)))
-               (response-code (car (split-string response-header)))
-               (response-meta (string-trim
-                               (substring response-header
-                                          (string-match "[ \t]+" response-header)))))
-          (pcase (elt response-code 0)
-            (?1 ; Input required
-             (elpher-with-clean-buffer
-              (insert "Gemini server is requesting input."))
-             (let* ((query-string (read-string (concat response-meta ": ")))
-                    (url (elpher-address-to-url (elpher-node-address elpher-current-node)))
-                    (query-address (elpher-address-from-url (concat url "?" query-string))))
-               (elpher-get-gemini query-address #'renderer)))
-            (?2 ; Normal response
-             (message response-header)
-             (funcall #'renderer elpher-gemini-response))
-            (?3 ; Redirect
-             (message "Following redirect to %s" meta)
-             (let ((redirect-address (elpher-address-from-gemini-url meta)))
-               (elpher-get-gemini redirect-address #'renderer)))
-            (?4 ; Temporary failure
-             (error "Gemini server reports TEMPORARY FAILURE for this request"))
-            (?5 ; Permanent failure
-             (error "Gemini server reports PERMANENT FAILURE for this request"))
-            (?6 ; Client certificate required
-             (error "Gemini server requires client certificate (unsupported at this time)"))
-            (other
-             (error "Gemini server responded with unknown response code %S"
-                    response-code)))))
+      (let* ((response-header (car (split-string elpher-gemini-response "\r\n")))
+             (response-body (substring elpher-gemini-response
+                                       (+ (string-match "\r\n" elpher-gemini-response) 2)))
+             (response-code (car (split-string response-header)))
+             (response-meta (string-trim
+                             (substring response-header
+                                        (string-match "[ \t]+" response-header)))))
+        (pcase (elt response-code 0)
+          (?1 ; Input required
+           (elpher-with-clean-buffer
+            (insert "Gemini server is requesting input."))
+           (let* ((query-string (read-string (concat response-meta ": ")))
+                  (url (elpher-address-to-url (elpher-node-address elpher-current-node)))
+                  (query-address (elpher-address-from-url (concat url "?" query-string))))
+             (elpher-get-gemini-response query-address
+                                         (lambda (_proc event)
+                                           (unless (string-prefix-p "deleted" event)
+                                             (funcall #'elpher-process-gemini-response
+                                                      renderer)
+                                             (elpher-restore-pos))))))
+          (?2 ; Normal response
+           ;; (message response-header)
+           (funcall renderer response-body response-meta))
+          (?3 ; Redirect
+           (message "Following redirect to %s" response-meta)
+           (let ((redirect-address (elpher-address-from-gemini-url response-meta)))
+             (elpher-get-gemini-response redirect-address
+                                         (lambda (_proc event)
+                                           (unless (string-prefix-p "deleted" event)
+                                             (funcall #'elpher-process-gemini-response
+                                                      renderer)
+                                             (elpher-restore-pos))))))
+          (?4 ; Temporary failure
+           (error "Gemini server reports TEMPORARY FAILURE for this request"))
+          (?5 ; Permanent failure
+           (error "Gemini server reports PERMANENT FAILURE for this request"))
+          (?6 ; Client certificate required
+           (error "Gemini server requires client certificate (unsupported at this time)"))
+          (_other
+           (error "Gemini server responded with unknown response code %S"
+                  response-code))))
     (error
      (elpher-network-error (elpher-node-address elpher-current-node) the-error))))
 
     (error
      (elpher-network-error (elpher-node-address elpher-current-node) the-error))))
 
@@ -807,31 +844,30 @@ pass the result to RENDERER."
          (content (elpher-get-cached-content address)))
     (condition-case the-error
         (if (and content (funcall renderer nil))
          (content (elpher-get-cached-content address)))
     (condition-case the-error
         (if (and content (funcall renderer nil))
-            (progn
+            (elpher-with-clean-buffer
               (insert content)
               (elpher-restore-pos))
           (elpher-with-clean-buffer
            (insert "LOADING GEMINI... (use 'u' to cancel)"))
               (insert content)
               (elpher-restore-pos))
           (elpher-with-clean-buffer
            (insert "LOADING GEMINI... (use 'u' to cancel)"))
-          (elpher-get-gemini address
-                             (lambda (proc event)
-                               (unless (string-prefix-p "deleted" event)
-                                 (funcall renderer elpher-gemini-response)
-                                 (elpher-restore-pos)))))
+          (elpher-get-gemini-response address
+                                      (lambda (_proc event)
+                                        (unless (string-prefix-p "deleted" event)
+                                          (funcall #'elpher-process-gemini-response
+                                                   renderer)
+                                          (elpher-restore-pos)))))
       (error
        (elpher-network-error address the-error)))))
 
 
       (error
        (elpher-network-error address the-error)))))
 
 
-(defun elpher-render-gemini (data)
-  "Render gemini response DATA."
-  (if (not data)
+(defun elpher-render-gemini (body &optional mime-type-string)
+  "Render gemini response BODY with rendering MIME-TYPE-STRING."
+  (if (not body)
       t
       t
-    (let* ((response-header (car (split-string data "\r\n")))
-           (response-body (substring data (+ (string-match "\r\n" data) 2)))
-           (mime-type-string (string-trim (substring response-header 2)))
-           (mime-type-string* (if (string-empty-p mime-type-string)
+    (let* ((mime-type-string* (if (or (not mime-type-string)
+                                      (string-empty-p mime-type-string))
                                   "text/gemini; charset=utf-8"
                                 mime-type-string))
                                   "text/gemini; charset=utf-8"
                                 mime-type-string))
-           (mime-type-split (split-string mime-type-string* ";"))
+           (mime-type-split (split-string mime-type-string* ";" t))
            (mime-type (string-trim (car mime-type-split)))
            (parameters (mapcar (lambda (s)
                                  (let ((key-val (split-string s "=")))
            (mime-type (string-trim (car mime-type-split)))
            (parameters (mapcar (lambda (s)
                                  (let ((key-val (split-string s "=")))
@@ -843,25 +879,25 @@ pass the result to RENDERER."
           (setq parameters (cons (list "charset" "utf-8") parameters)))
       (when (string-prefix-p "text/" mime-type)
         (if (assoc "charset" parameters)
           (setq parameters (cons (list "charset" "utf-8") parameters)))
       (when (string-prefix-p "text/" mime-type)
         (if (assoc "charset" parameters)
-            (setq elpher-gemini-response
-                  (decode-coding-string elpher-gemini-response
-                                        (intern (cadr (assoc "charset" parameters))))))
-        (setq elpher-gemini-response
-              (replace-regexp-in-string "\r" "" elpher-gemini-response)))
+            (setq body (decode-coding-string body
+                                             (intern (cadr (assoc "charset" parameters))))))
+        (setq body (replace-regexp-in-string "\r" "" body)))
       (pcase mime-type
         ((or "text/gemini" "")
       (pcase mime-type
         ((or "text/gemini" "")
-         (elpher-render-gemini-text/gemini response-body parameters))
+         (elpher-render-gemini-map body parameters))
         ((pred (string-prefix-p "text/"))
         ((pred (string-prefix-p "text/"))
-         (elpher-render-gemini-text/plain response-body parameters))
+         (elpher-render-gemini-plain-text body parameters))
         ((pred (string-prefix-p "image/"))
         ((pred (string-prefix-p "image/"))
-         (elpher-render-image response-body))
-        (other
+         (elpher-render-image body))
+        (_other
          (error "Unsupported MIME type %S" mime-type))))))
 
 (defun elpher-gemini-get-link-url (line)
          (error "Unsupported MIME type %S" mime-type))))))
 
 (defun elpher-gemini-get-link-url (line)
+  "Extract the url portion of LINE, a gemini map file link line."
   (string-trim (elt (split-string (substring line 2)) 0)))
 
 (defun elpher-gemini-get-link-display-string (line)
   (string-trim (elt (split-string (substring line 2)) 0)))
 
 (defun elpher-gemini-get-link-display-string (line)
+  "Extract the display string portion of LINE, a gemini map file link line."
   (let* ((rest (string-trim (elt (split-string line "=>") 1)))
          (idx (string-match "[ \t]" rest)))
     (if idx
   (let* ((rest (string-trim (elt (split-string line "=>") 1)))
          (idx (string-match "[ \t]" rest)))
     (if idx
@@ -873,18 +909,19 @@ pass the result to RENDERER."
   (let ((address (url-generic-parse-url url)))
     (unless (and (url-type address) (not (url-fullness address))) ;avoid mangling mailto: urls
       (setf (url-fullness address) t)
   (let ((address (url-generic-parse-url url)))
     (unless (and (url-type address) (not (url-fullness address))) ;avoid mangling mailto: urls
       (setf (url-fullness address) t)
-      (unless (url-host address) ;if there is an explicit host, filenames are explicit
+      (unless (url-host address) ;if there is an explicit host, filenames are absolute
         (setf (url-host address) (url-host (elpher-node-address elpher-current-node)))
         (unless (string-prefix-p "/" (url-filename address)) ;deal with relative links
           (setf (url-filename address)
         (setf (url-host address) (url-host (elpher-node-address elpher-current-node)))
         (unless (string-prefix-p "/" (url-filename address)) ;deal with relative links
           (setf (url-filename address)
-                (concat (file-name-directory 
+                (concat (file-name-directory
                          (url-filename (elpher-node-address elpher-current-node)))
                         (url-filename address)))))
       (unless (url-type address)
         (setf (url-type address) "gemini")))
     address))
 
                          (url-filename (elpher-node-address elpher-current-node)))
                         (url-filename address)))))
       (unless (url-type address)
         (setf (url-type address) "gemini")))
     address))
 
-(defun elpher-render-gemini-text/gemini (data parameters)
+(defun elpher-render-gemini-map (data _parameters)
+  "Render DATA as a gemini map file, PARAMETERS is currently unused."
   (elpher-with-clean-buffer
    (dolist (line (split-string data "\n"))
      (if (string-prefix-p "=>" line)
   (elpher-with-clean-buffer
    (dolist (line (split-string data "\n"))
      (if (string-prefix-p "=>" line)
@@ -899,9 +936,10 @@ pass the result to RENDERER."
     (elpher-node-address elpher-current-node)
     (buffer-string))))
 
     (elpher-node-address elpher-current-node)
     (buffer-string))))
 
-(defun elpher-render-gemini-text/plain (data parameters)
+(defun elpher-render-gemini-plain-text (data _parameters)
+  "Render DATA as plain text file.  PARAMETERS is currently unused."
   (elpher-with-clean-buffer
   (elpher-with-clean-buffer
-   (insert (elpher-buttonify-urls (elpher-preprocess-text-response data)))
+   (insert (elpher-buttonify-urls data))
    (elpher-cache-content
     (elpher-node-address elpher-current-node)
     (buffer-string))))
    (elpher-cache-content
     (elpher-node-address elpher-current-node)
     (buffer-string))))
@@ -941,7 +979,7 @@ pass the result to RENDERER."
   "Getter which displays the start page (RENDERER must be nil)."
   (when renderer
     (elpher-visit-parent-node)
   "Getter which displays the start page (RENDERER must be nil)."
   (when renderer
     (elpher-visit-parent-node)
-    (error "Command not supported for start page."))
+    (error "Command not supported for start page"))
   (elpher-with-clean-buffer
    (insert "     --------------------------------------------\n"
            "                Elpher Gopher Client             \n"
   (elpher-with-clean-buffer
    (insert "     --------------------------------------------\n"
            "                Elpher Gopher Client             \n"
@@ -956,6 +994,7 @@ pass the result to RENDERER."
            " - u: return to previous page\n"
            " - o/O: visit different selector or the root menu of the current server\n"
            " - g: go to a particular gopher address\n"
            " - u: return to previous page\n"
            " - o/O: visit different selector or the root menu of the current server\n"
            " - g: go to a particular gopher address\n"
+           " - d/D: download item under cursor or current page\n"
            " - i/I: info on item under cursor or current page\n"
            " - c/C: copy URL representation of item under cursor or current page\n"
            " - a/A: bookmark the item under cursor or current page\n"
            " - i/I: info on item under cursor or current page\n"
            " - c/C: copy URL representation of item under cursor or current page\n"
            " - a/A: bookmark the item under cursor or current page\n"
@@ -963,10 +1002,9 @@ pass the result to RENDERER."
            " - B: visit the bookmarks page\n"
            " - r: redraw current page (using cached contents if available)\n"
            " - R: reload current page (regenerates cache)\n"
            " - B: visit the bookmarks page\n"
            " - r: redraw current page (using cached contents if available)\n"
            " - R: reload current page (regenerates cache)\n"
-           " - T: toggle TLS mode\n"
-           " - d/D: download item under cursor or current page\n"
+           " - S: set character coding system for gopher (default is to autodetect)\n"
+           " - T: toggle TLS gopher mode\n"
            " - .: display the raw server response for the current page\n"
            " - .: display the raw server response for the current page\n"
-           " - S: set an explicit character coding system (default is to autodetect)\n"
            "\n"
            "Start your exploration of gopher space:\n")
    (elpher-insert-index-record "Floodgap Systems Gopher Server"
            "\n"
            "Start your exploration of gopher space:\n")
    (elpher-insert-index-record "Floodgap Systems Gopher Server"
@@ -980,7 +1018,7 @@ pass the result to RENDERER."
    (let ((help-string "RET,mouse-1: Open Elpher info manual (if available)"))
      (insert-text-button "Elpher info manual"
                          'face 'link
    (let ((help-string "RET,mouse-1: Open Elpher info manual (if available)"))
      (insert-text-button "Elpher info manual"
                          'face 'link
-                         'action (lambda (button)
+                         'action (lambda (_)
                                    (interactive)
                                    (info "(elpher)"))
                          'follow-link t
                                    (interactive)
                                    (info "(elpher)"))
                          'follow-link t
@@ -998,7 +1036,7 @@ pass the result to RENDERER."
   "Getter to load and display the current bookmark list (RENDERER must be nil)."
   (when renderer
     (elpher-visit-parent-node)
   "Getter to load and display the current bookmark list (RENDERER must be nil)."
   (when renderer
     (elpher-visit-parent-node)
-    (error "Command not supported for bookmarks page."))
+    (error "Command not supported for bookmarks page"))
   (elpher-with-clean-buffer
    (insert "---- Bookmark list ----\n\n")
    (let ((bookmarks (elpher-load-bookmarks)))
   (elpher-with-clean-buffer
    (insert "---- Bookmark list ----\n\n")
    (let ((bookmarks (elpher-load-bookmarks)))
@@ -1023,8 +1061,8 @@ pass the result to RENDERER."
 (defun elpher-make-bookmark (display-string url)
   "Make an elpher bookmark.
 DISPLAY-STRING determines how the bookmark will appear in the
 (defun elpher-make-bookmark (display-string url)
   "Make an elpher bookmark.
 DISPLAY-STRING determines how the bookmark will appear in the
-bookmark list, while ADDRESS is the address of the entry."
-  (list display-string (elpher-address-to-url address)))
+bookmark list, while URL is the url of the entry."
+  (list display-string url))
   
 (defun elpher-bookmark-display-string (bookmark)
   "Get the display string of BOOKMARK."
   
 (defun elpher-bookmark-display-string (bookmark)
   "Get the display string of BOOKMARK."
@@ -1038,11 +1076,10 @@ bookmark list, while ADDRESS is the address of the entry."
   "Get the address for BOOKMARK."
   (elt bookmark 1))
 
   "Get the address for BOOKMARK."
   (elt bookmark 1))
 
-
 (defun elpher-save-bookmarks (bookmarks)
   "Record the bookmark list BOOKMARKS to the user's bookmark file.
 Beware that this completely replaces the existing contents of the file."
 (defun elpher-save-bookmarks (bookmarks)
   "Record the bookmark list BOOKMARKS to the user's bookmark file.
 Beware that this completely replaces the existing contents of the file."
-  (with-temp-file (locate-user-emacs-file "elpher2-bookmarks")
+  (with-temp-file (locate-user-emacs-file "elpher-bookmarks")
     (erase-buffer)
     (insert "; Elpher bookmarks file\n\n"
             "; Bookmarks are stored as a list of (label URL) items.\n"
     (erase-buffer)
     (insert "; Elpher bookmarks file\n\n"
             "; Bookmarks are stored as a list of (label URL) items.\n"
@@ -1052,11 +1089,21 @@ Beware that this completely replaces the existing contents of the file."
 
 (defun elpher-load-bookmarks ()
   "Get the list of bookmarks from the users's bookmark file."
 
 (defun elpher-load-bookmarks ()
   "Get the list of bookmarks from the users's bookmark file."
-  (with-temp-buffer
-    (ignore-errors
-      (insert-file-contents (locate-user-emacs-file "elpher2-bookmarks"))
-      (goto-char (point-min))
-      (read (current-buffer)))))
+  (let ((bookmarks
+         (with-temp-buffer
+           (ignore-errors
+             (insert-file-contents (locate-user-emacs-file "elpher-bookmarks"))
+             (goto-char (point-min))
+             (read (current-buffer))))))
+    (if (and bookmarks (listp (cadar bookmarks)))
+        (progn
+          (message "Reading old bookmark file. (Will be updated on write.)")
+          (mapcar (lambda (old-bm)
+                    (list (car old-bm)
+                          (elpher-address-to-url (apply #'elpher-make-gopher-address
+                                                        (cadr old-bm)))))
+                  bookmarks))
+      bookmarks)))
 
 (defun elpher-add-address-bookmark (address display-string)
   "Save a bookmark for ADDRESS with label DISPLAY-STRING.)))
 
 (defun elpher-add-address-bookmark (address display-string)
   "Save a bookmark for ADDRESS with label DISPLAY-STRING.)))
@@ -1066,7 +1113,7 @@ If ADDRESS is already bookmarked, update the label only."
     (let ((existing-bookmark (rassoc (list url) bookmarks)))
       (if existing-bookmark
           (elpher-set-bookmark-display-string existing-bookmark display-string)
     (let ((existing-bookmark (rassoc (list url) bookmarks)))
       (if existing-bookmark
           (elpher-set-bookmark-display-string existing-bookmark display-string)
-        (add-to-list 'bookmarks (elpher-make-bookmark display-string url))))
+        (push (elpher-make-bookmark display-string url) bookmarks)))
     (elpher-save-bookmarks bookmarks)))
 
 (defun elpher-remove-address-bookmark (address)
     (elpher-save-bookmarks bookmarks)))
 
 (defun elpher-remove-address-bookmark (address)
@@ -1110,7 +1157,7 @@ If ADDRESS is already bookmarked, update the label only."
   (interactive)
   (let ((address (elpher-node-address elpher-current-node)))
     (if (elpher-address-special-p address)
   (interactive)
   (let ((address (elpher-node-address elpher-current-node)))
     (if (elpher-address-special-p address)
-        (error "Command not valid for this page")
+        (error "Command invalid for this page")
       (let ((url (read-string "Gopher or Gemini URL: " (elpher-address-to-url address))))
         (elpher-visit-node (elpher-make-node url (elpher-address-from-url url)))))))
 
       (let ((url (read-string "Gopher or Gemini URL: " (elpher-address-to-url address))))
         (elpher-visit-node (elpher-make-node url (elpher-address-from-url url)))))))
 
@@ -1134,10 +1181,10 @@ If ADDRESS is already bookmarked, update the label only."
   (setq elpher-use-tls (not elpher-use-tls))
   (if elpher-use-tls
       (if (gnutls-available-p)
   (setq elpher-use-tls (not elpher-use-tls))
   (if elpher-use-tls
       (if (gnutls-available-p)
-          (message "TLS mode enabled.  (Will not affect current page until reload.)")
+          (message "TLS gopher mode enabled.  (Will not affect current page until reload.)")
         (setq elpher-use-tls nil)
         (setq elpher-use-tls nil)
-        (error "Cannot enable TLS mode: GnuTLS not available"))
-    (message "TLS mode disabled.  (Will not affect current page until reload.)")))
+        (error "Cannot enable TLS gopher mode: GnuTLS not available"))
+    (message "TLS gopher mode disabled.  (Will not affect current page until reload.)")))
 
 (defun elpher-view-raw ()
   "View raw server response for current page."
 
 (defun elpher-view-raw ()
   "View raw server response for current page."
@@ -1146,7 +1193,7 @@ If ADDRESS is already bookmarked, update the label only."
       (if (elpher-address-special-p (elpher-node-address elpher-current-node))
           (error "This page was not generated by a server")
         (elpher-visit-node elpher-current-node
       (if (elpher-address-special-p (elpher-node-address elpher-current-node))
           (error "This page was not generated by a server")
         (elpher-visit-node elpher-current-node
-                           #'elpher-get-node-raw))
+                           #'elpher-render-raw))
     (message "No current site.")))
 
 (defun elpher-back ()
     (message "No current site.")))
 
 (defun elpher-back ()
@@ -1163,21 +1210,23 @@ If ADDRESS is already bookmarked, update the label only."
     (if button
         (let ((node (button-get button 'elpher-node)))
           (if (elpher-address-special-p (elpher-node-address node))
     (if button
         (let ((node (button-get button 'elpher-node)))
           (if (elpher-address-special-p (elpher-node-address node))
-              (error "Cannot download this link")
+              (error "Cannot download %s"
+                     (elpher-node-display-string node))
             (elpher-visit-node (button-get button 'elpher-node)
             (elpher-visit-node (button-get button 'elpher-node)
-                               #'elpher-get-node-download)))
+                               #'elpher-render-download)))
       (error "No link selected"))))
 
 (defun elpher-download-current ()
   "Download the current page."
   (interactive)
   (if (elpher-address-special-p (elpher-node-address elpher-current-node))
       (error "No link selected"))))
 
 (defun elpher-download-current ()
   "Download the current page."
   (interactive)
   (if (elpher-address-special-p (elpher-node-address elpher-current-node))
-      (error "Cannot download this page")
+      (error "Cannot download %s"
+             (elpher-node-display-string elpher-current-node))
     (elpher-visit-node (elpher-make-node
                         (elpher-node-display-string elpher-current-node)
                         (elpher-node-address elpher-current-node)
                         elpher-current-node)
     (elpher-visit-node (elpher-make-node
                         (elpher-node-display-string elpher-current-node)
                         (elpher-node-address elpher-current-node)
                         elpher-current-node)
-                       #'elpher-get-node-download
+                       #'elpher-render-download
                        t)))
 
 (defun elpher-build-link-map ()
                        t)))
 
 (defun elpher-build-link-map ()
@@ -1185,7 +1234,7 @@ If ADDRESS is already bookmarked, update the label only."
   (let ((link-map nil)
         (b (next-button (point-min) t)))
     (while b
   (let ((link-map nil)
         (b (next-button (point-min) t)))
     (while b
-      (add-to-list 'link-map (cons (button-label b) b))
+      (push (cons (button-label b) b) link-map)
       (setq b (next-button (button-start b))))
     link-map))
 
       (setq b (next-button (button-start b))))
     link-map))
 
@@ -1217,7 +1266,7 @@ If ADDRESS is already bookmarked, update the label only."
             (elpher-visit-node
              (elpher-make-node (elpher-address-to-url address-copy)
                                address-copy))))
             (elpher-visit-node
              (elpher-make-node (elpher-address-to-url address-copy)
                                address-copy))))
-      (error "Command invalid for this page"))))
+      (error "Command invalid for %s" (elpher-node-display-string elpher-current-node)))))
 
 (defun elpher-bookmarks-current-p ()
   "Return non-nil if current node is a bookmarks page."
 
 (defun elpher-bookmarks-current-p ()
   "Return non-nil if current node is a bookmarks page."
@@ -1327,14 +1376,14 @@ If ADDRESS is already bookmarked, update the label only."
   (interactive)
   (elpher-copy-node-url elpher-current-node))
 
   (interactive)
   (elpher-copy-node-url elpher-current-node))
 
-(defun elpher-set-coding-system ()
-  "Specify an explicit character coding system."
+(defun elpher-set-gopher-coding-system ()
+  "Specify an explicit character coding system for gopher selectors."
   (interactive)
   (interactive)
-  (let ((system (read-coding-system "Set coding system to use (default is to autodetect): " nil)))
+  (let ((system (read-coding-system "Set coding system to use for gopher (default is to autodetect): " nil)))
     (setq elpher-user-coding-system system)
     (if system
     (setq elpher-user-coding-system system)
     (if system
-        (message "Coding system fixed to %s. (Reload to see effect)." system)
-      (message "Coding system set to autodetect. (Reload to see effect)."))))
+        (message "Gopher coding system fixed to %s. (Reload to see effect)." system)
+      (message "Gopher coding system set to autodetect. (Reload to see effect)."))))
 
 
 ;;; Mode and keymap
 
 
 ;;; Mode and keymap
@@ -1345,6 +1394,7 @@ If ADDRESS is already bookmarked, update the label only."
     (define-key map (kbd "TAB") 'elpher-next-link)
     (define-key map (kbd "<backtab>") 'elpher-prev-link)
     (define-key map (kbd "u") 'elpher-back)
     (define-key map (kbd "TAB") 'elpher-next-link)
     (define-key map (kbd "<backtab>") 'elpher-prev-link)
     (define-key map (kbd "u") 'elpher-back)
+    (define-key map [mouse-3] 'elpher-back)
     (define-key map (kbd "O") 'elpher-root-dir)
     (define-key map (kbd "g") 'elpher-go)
     (define-key map (kbd "o") 'elpher-go-current)
     (define-key map (kbd "O") 'elpher-root-dir)
     (define-key map (kbd "g") 'elpher-go)
     (define-key map (kbd "o") 'elpher-go-current)
@@ -1364,13 +1414,14 @@ If ADDRESS is already bookmarked, update the label only."
     (define-key map (kbd "x") 'elpher-unbookmark-link)
     (define-key map (kbd "X") 'elpher-unbookmark-current)
     (define-key map (kbd "B") 'elpher-bookmarks)
     (define-key map (kbd "x") 'elpher-unbookmark-link)
     (define-key map (kbd "X") 'elpher-unbookmark-current)
     (define-key map (kbd "B") 'elpher-bookmarks)
-    (define-key map (kbd "S") 'elpher-set-coding-system)
-    (when (fboundp 'evil-define-key)
-      (evil-define-key 'motion map
+    (define-key map (kbd "S") 'elpher-set-gopher-coding-system)
+    (when (fboundp 'evil-define-key*)
+      (evil-define-key* 'motion map
         (kbd "TAB") 'elpher-next-link
         (kbd "C-") 'elpher-follow-current-link
         (kbd "C-t") 'elpher-back
         (kbd "u") 'elpher-back
         (kbd "TAB") 'elpher-next-link
         (kbd "C-") 'elpher-follow-current-link
         (kbd "C-t") 'elpher-back
         (kbd "u") 'elpher-back
+        [mouse-3] 'elpher-back
         (kbd "g") 'elpher-go
         (kbd "o") 'elpher-go-current
         (kbd "r") 'elpher-redraw
         (kbd "g") 'elpher-go
         (kbd "o") 'elpher-go-current
         (kbd "r") 'elpher-redraw
@@ -1389,12 +1440,12 @@ If ADDRESS is already bookmarked, update the label only."
         (kbd "x") 'elpher-unbookmark-link
         (kbd "X") 'elpher-unbookmark-current
         (kbd "B") 'elpher-bookmarks
         (kbd "x") 'elpher-unbookmark-link
         (kbd "X") 'elpher-unbookmark-current
         (kbd "B") 'elpher-bookmarks
-        (kbd "S") 'elpher-set-coding-system))
+        (kbd "S") 'elpher-set-gopher-coding-system))
     map)
   "Keymap for gopher client.")
 
 (define-derived-mode elpher-mode special-mode "elpher"
     map)
   "Keymap for gopher client.")
 
 (define-derived-mode elpher-mode special-mode "elpher"
-  "Major mode for elpher, an elisp gopher client.)
+  "Major mode for elpher, an elisp gopher client.
 
 This mode is automatically enabled by the interactive
 functions which initialize the gopher client, namely
 
 This mode is automatically enabled by the interactive
 functions which initialize the gopher client, namely