Added URL to header.
[elpher.git] / elpher.el
index 3735345..9c0138b 100644 (file)
--- a/elpher.el
+++ b/elpher.el
@@ -4,7 +4,7 @@
 
 ;; 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"))
@@ -36,7 +36,8 @@
 ;; - 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
 (require 'pp)
 (require 'shr)
 (require 'url-util)
+(require 'subr-x)
 
 
 ;;; Global constants
 ;;
 
-(defconst elpher-version "2.0.0"
+(defconst elpher-version "2.2.0"
   "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 ?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)
@@ -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))
-  "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
   "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
 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))
 
@@ -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)))
-          (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) "/"))
-                (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)
-  "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)
-             selector))))
+             selector)))))
 
 (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)
-  "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)))
@@ -244,14 +260,19 @@ attributes: TYPE, SELECTOR, HOST and PORT."
                      (string-to-char (substring (url-filename address) 1)))))
             ((equal protocol "gemini")
              'gemini)
+            ((equal protocol "telnet")
+             'telnet)
             (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)
+  "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)))
@@ -262,7 +283,15 @@ attributes: TYPE, SELECTOR, HOST and PORT."
 
 (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)."
@@ -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)))
-        (else
+        (other
          (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."
@@ -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
-      (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."
@@ -432,6 +466,7 @@ away CRs and any terminating period."
 ;;
 
 (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"
@@ -466,20 +501,18 @@ up to the calling function."
       (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)
-                                       (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
-                            (lambda (proc string)
+                            (lambda (_proc string)
                               (setq elpher-selector-string
                                     (concat elpher-selector-string string))))
         (set-process-sentinel proc after)
@@ -492,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
-           (message "Disengaging TLS mode.")
+           (message "Disengaging TLS gopher mode.")
            (setq elpher-use-tls nil)
            (elpher-get-selector address after))
        (elpher-process-cleanup)
@@ -505,6 +538,9 @@ up to the calling function."
                   "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))
@@ -514,7 +550,7 @@ up to the calling function."
       (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)))))))
@@ -527,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"))
-      (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."
@@ -577,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)
-         (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
@@ -593,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)))
 
-(defun elpher-render-index (data &optional mime-type-string)
-  "Render DATA as an index, MIME-TYPE-STRING is unused"
+(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
@@ -606,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(),]*\\)?"
-  "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."
@@ -621,11 +658,12 @@ If ADDRESS is not supplied or nil the record is rendered as an
                             '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)))
 
-(defun elpher-render-text (data &optional mime-type-string)
-  "Render DATA as text, MIME-TYPE-STRING is unused."
+(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
@@ -636,8 +674,8 @@ If ADDRESS is not supplied or nil the record is rendered as an
 
 ;; Image retrieval
 
-(defun elpher-render-image (data &optional mime-type-string)
-  "Display DATA as image, MIME-TYPE-STRING is unused."
+(defun elpher-render-image (data &optional _mime-type-string)
+  "Display DATA as image MIME-TYPE-STRING is unused."
   (if (not data)
       nil
     (if (display-images-p)
@@ -648,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-save-to-file data))))
+      (elpher-render-download data))))
 
 ;; 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))
@@ -674,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
-                                 (lambda (proc event)
+                                 (lambda (_proc event)
                                    (unless (string-prefix-p "deleted" event)
                                      (funcall renderer elpher-selector-string)
                                      (elpher-restore-pos)))))
@@ -683,8 +723,8 @@ If ADDRESS is not supplied or nil the record is rendered as an
  
 ;; Raw server response rendering
 
-(defun elpher-render-raw (data &optional mime-type-string)
-  "Display raw DATA in buffer, MIME-TYPE-STRING is unused."
+(defun elpher-render-raw (data &optional _mime-type-string)
+  "Display raw DATA in buffer MIME-TYPE-STRING is unused."
   (if (not data)
       nil
     (elpher-with-clean-buffer
@@ -694,8 +734,8 @@ If ADDRESS is not supplied or nil the record is rendered as an
 
 ;; File save "rendering"
 
-(defun elpher-render-download (data &optional mime-type-string)
-  "Save DATA to file, MIME-TYPE-STRING is unused."
+(defun elpher-render-download (data &optional _mime-type-string)
+  "Save DATA to file MIME-TYPE-STRING is unused."
   (if (not data)
       nil
     (let* ((address (elpher-node-address elpher-current-node))
@@ -714,13 +754,13 @@ If ADDRESS is not supplied or nil the record is rendered as an
 
 ;; HTML rendering
 
-(defun elpher-render-html (data &optional mime-type-string)
-  "Render DATA as HTML using shr, MIME-TYPE-STRING is unused."
+(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
-                  (insert string)
+                  (insert data)
                   (libxml-parse-html-region (point-min) (point-max)))))
        (shr-insert-document dom)))))
 
@@ -738,13 +778,11 @@ The response is stored in the variable ‘elpher-gemini-response’."
            (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
-                          (lambda (proc string)
+                          (lambda (_proc string)
                             (setq elpher-gemini-response
                                   (concat elpher-gemini-response string))))
       (set-process-sentinel proc after)
@@ -753,8 +791,8 @@ The response is stored in the variable ‘elpher-gemini-response’."
 
 
 (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
       (let* ((response-header (car (split-string elpher-gemini-response "\r\n")))
              (response-body (substring elpher-gemini-response
@@ -771,19 +809,19 @@ pass the result to RENDERER."
                   (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)
+                                         (lambda (_proc event)
                                            (unless (string-prefix-p "deleted" event)
                                              (funcall #'elpher-process-gemini-response
                                                       renderer)
                                              (elpher-restore-pos))))))
           (?2 ; Normal response
-           (message response-header)
+           ;; (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)
+                                         (lambda (_proc event)
                                            (unless (string-prefix-p "deleted" event)
                                              (funcall #'elpher-process-gemini-response
                                                       renderer)
@@ -794,7 +832,7 @@ pass the result to RENDERER."
            (error "Gemini server reports PERMANENT FAILURE for this request"))
           (?6 ; Client certificate required
            (error "Gemini server requires client certificate (unsupported at this time)"))
-          (other
+          (_other
            (error "Gemini server responded with unknown response code %S"
                   response-code))))
     (error
@@ -812,7 +850,7 @@ pass the result to RENDERER."
           (elpher-with-clean-buffer
            (insert "LOADING GEMINI... (use 'u' to cancel)"))
           (elpher-get-gemini-response address
-                                      (lambda (proc event)
+                                      (lambda (_proc event)
                                         (unless (string-prefix-p "deleted" event)
                                           (funcall #'elpher-process-gemini-response
                                                    renderer)
@@ -822,14 +860,14 @@ pass the result to RENDERER."
 
 
 (defun elpher-render-gemini (body &optional mime-type-string)
-  "Render gemini response BODY with rendering hints in META."
+  "Render gemini response BODY with rendering MIME-TYPE-STRING."
   (if (not body)
       t
     (let* ((mime-type-string* (if (or (not mime-type-string)
                                       (string-empty-p 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 "=")))
@@ -841,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 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" "")
-         (elpher-render-gemini-text/gemini body parameters))
+         (elpher-render-gemini-map body parameters))
         ((pred (string-prefix-p "text/"))
-         (elpher-render-gemini-text/plain body parameters))
+         (elpher-render-gemini-plain-text body parameters))
         ((pred (string-prefix-p "image/"))
          (elpher-render-image body))
-        (other
+        (_other
          (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)
+  "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
@@ -871,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)
-      (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)
-                (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))
 
-(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)
@@ -897,9 +936,10 @@ pass the result to RENDERER."
     (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
-   (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))))
@@ -939,7 +979,7 @@ pass the result to RENDERER."
   "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"
@@ -954,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"
+           " - 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"
@@ -961,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"
-           " - 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"
-           " - 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"
@@ -978,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
-                         'action (lambda (button)
+                         'action (lambda (_)
                                    (interactive)
                                    (info "(elpher)"))
                          'follow-link t
@@ -996,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)
-    (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)))
@@ -1021,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
-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."
@@ -1036,11 +1076,10 @@ bookmark list, while ADDRESS is the address of the entry."
   "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."
-  (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"
@@ -1050,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."
-  (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.)))
@@ -1064,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)
-        (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)
@@ -1132,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)
-          (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)
-        (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."
@@ -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
-      (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))
 
@@ -1327,14 +1376,14 @@ If ADDRESS is already bookmarked, update the label only."
   (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)
-  (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
-        (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
@@ -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 [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)
@@ -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 "S") 'elpher-set-coding-system)
-    (when (fboundp 'evil-mode)
+    (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
+        [mouse-3] 'elpher-back
         (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 "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"
-  "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