Change browse-url advice for older releases
[elpher.git] / elpher.el
index e16c52d..f94b33f 100644 (file)
--- a/elpher.el
+++ b/elpher.el
@@ -1,4 +1,4 @@
-;;; elpher.el --- A friendly gopher and gemini client  -*- lexical-binding:t -*-
+;;; elpher.el --- A friendly gopher and gemini client  -*- lexical-binding: t -*-
 
 ;; Copyright (C) 2021 Jens Östlund <jostlund@gmail.com>
 ;; Copyright (C) 2021 F. Jason Park <jp@neverwas.me>
 
 ;; Copyright (C) 2021 Jens Östlund <jostlund@gmail.com>
 ;; Copyright (C) 2021 F. Jason Park <jp@neverwas.me>
@@ -21,8 +21,8 @@
 ;; Created: 11 April 2019
 ;; Version: 2.11.0
 ;; Keywords: comm gopher
 ;; Created: 11 April 2019
 ;; Version: 2.11.0
 ;; Keywords: comm gopher
-;; Homepage: http://thelambdalab.xyz/elpher
-;; Package-Requires: ((emacs "26.2"))
+;; Homepage: https://alexschroeder.ch/cgit/elpher
+;; Package-Requires: ((emacs "27.1"))
 
 ;; This file is not part of GNU Emacs.
 
 
 ;; This file is not part of GNU Emacs.
 
@@ -63,7 +63,7 @@
 
 ;; Elpher is under active development.  Any suggestions for
 ;; improvements are welcome, and can be made on the official
 
 ;; Elpher is under active development.  Any suggestions for
 ;; improvements are welcome, and can be made on the official
-;; project page, gopher://thelambdalab.xyz/1/projects/elpher/.
+;; project page, https://alexschroeder.ch/cgit/elpher.
 
 ;;; Code:
 
 
 ;;; Code:
 
@@ -90,8 +90,8 @@
 (defalias 'elpher-color-filter-apply
   (if (fboundp 'xterm-color-filter)
       (lambda (s)
 (defalias 'elpher-color-filter-apply
   (if (fboundp 'xterm-color-filter)
       (lambda (s)
-       (let ((xterm-color-render nil))
-         (xterm-color-filter s)))
+        (let ((xterm-color-render nil))
+          (xterm-color-filter s)))
     'ansi-color-filter-apply)
   "A function to filter out ANSI escape sequences.")
 (defalias 'elpher-color-apply
     'ansi-color-filter-apply)
   "A function to filter out ANSI escape sequences.")
 (defalias 'elpher-color-apply
 
 (defcustom elpher-open-urls-with-eww nil
   "If non-nil, open URL selectors using eww.
 
 (defcustom elpher-open-urls-with-eww nil
   "If non-nil, open URL selectors using eww.
-Otherwise, use the system browser via the BROWSE-URL function."
+Otherwise, use the system browser via the `browse-url' function."
   :type '(boolean))
 
 (defcustom elpher-use-header t
   :type '(boolean))
 
 (defcustom elpher-use-header t
@@ -165,8 +165,9 @@ Otherwise, use the system browser via the BROWSE-URL function."
 
 (defcustom elpher-auto-disengage-TLS nil
   "If non-nil, automatically disengage TLS following an unsuccessful connection.
 
 (defcustom elpher-auto-disengage-TLS nil
   "If non-nil, automatically disengage TLS following an unsuccessful connection.
-While enabling this may seem convenient, it is also potentially dangerous as it
-allows switching from an encrypted channel back to plain text without user input."
+While enabling this may seem convenient, it is also potentially
+dangerous as it allows switching from an encrypted channel back to
+plain text without user input."
   :type '(boolean))
 
 (defcustom elpher-connection-timeout 5
   :type '(boolean))
 
 (defcustom elpher-connection-timeout 5
@@ -509,8 +510,8 @@ If no address is defined, returns 0.  (This is for compatibility with the URL li
   "Set the address corresponding to PAGE to NEW-ADDRESS."
   (setcar (cdr page) new-address))
 
   "Set the address corresponding to PAGE to NEW-ADDRESS."
   (setcar (cdr page) new-address))
 
-(defvar elpher-current-page nil)       ; buffer local
-(defvar elpher-history nil)            ; buffer local
+(defvar elpher-current-page nil) ; buffer local
+(defvar elpher-history nil)      ; buffer local
 
 (defun elpher-visit-page (page &optional renderer no-history)
   "Visit PAGE using its own renderer or RENDERER, if non-nil.
 
 (defun elpher-visit-page (page &optional renderer no-history)
   "Visit PAGE using its own renderer or RENDERER, if non-nil.
@@ -636,7 +637,7 @@ If LINE is non-nil, replace that line instead."
   "Preprocess text selector response contained in STRING.
 This involes decoding the character representation, and clearing
 away CRs and any terminating period."
   "Preprocess text selector response contained in STRING.
 This involes decoding the character representation, and clearing
 away CRs and any terminating period."
-  (elpher-decode (replace-regexp-in-string "\n\.\n$" "\n"
+  (elpher-decode (replace-regexp-in-string "\n\\.\n$" "\n"
                                            (replace-regexp-in-string "\r" "" string))))
 
 
                                            (replace-regexp-in-string "\r" "" string))))
 
 
@@ -757,10 +758,10 @@ the host operating system and the local network capabilities."
                                   (when (> new-hkbytes-received hkbytes-received)
                                     (setq hkbytes-received new-hkbytes-received)
                                     (elpher-buffer-message
                                   (when (> new-hkbytes-received hkbytes-received)
                                     (setq hkbytes-received new-hkbytes-received)
                                     (elpher-buffer-message
-                                        (concat "("
-                                                (number-to-string (/ hkbytes-received 10.0))
-                                                " MB read)")
-                                        1)))
+                                     (concat "("
+                                             (number-to-string (/ hkbytes-received 10.0))
+                                             " MB read)")
+                                     1)))
                                 (setq response-string-parts
                                       (cons string response-string-parts))))
           (set-process-sentinel proc
                                 (setq response-string-parts
                                       (cons string response-string-parts))))
           (set-process-sentinel proc
@@ -1053,7 +1054,7 @@ If ADDRESS is not supplied or nil the record is rendered as an
 ;; Text rendering
 
 (defconst elpher-url-regex
 ;; Text rendering
 
 (defconst elpher-url-regex
-  "\\([a-zA-Z]+\\)://\\([a-zA-Z0-9.\-]*[a-zA-Z0-9\-]\\|\[[a-zA-Z0-9:]+\]\\)\\(:[0-9]+\\)?\\(/\\([0-9a-zA-Z\-_~?/@|:.%#=&]*[0-9a-zA-Z\-_~?/@|#]\\)?\\)?"
+  "\\([a-zA-Z]+\\)://\\([a-zA-Z0-9.-]*[a-zA-Z0-9-]\\|\\[[a-zA-Z0-9:]+\\]\\)\\(:[0-9]+\\)?\\(/\\([0-9a-zA-Z_~?/@|:.%#=&-]*[0-9a-zA-Z_~?/@|#-]\\)?\\)?"
   "Regexp used to locate and buttinofy URLs in text files loaded by elpher.")
 
 (defun elpher-buttonify-urls (string)
   "Regexp used to locate and buttinofy URLs in text files loaded by elpher.")
 
 (defun elpher-buttonify-urls (string)
@@ -1064,17 +1065,17 @@ If ADDRESS is not supplied or nil the record is rendered as an
     (while (re-search-forward elpher-url-regex nil t)
       (let ((page (elpher-make-page (substring-no-properties (match-string 0))
                                     (elpher-address-from-url (match-string 0)))))
     (while (re-search-forward elpher-url-regex nil t)
       (let ((page (elpher-make-page (substring-no-properties (match-string 0))
                                     (elpher-address-from-url (match-string 0)))))
-          (make-text-button (match-beginning 0)
-                            (match-end 0)
-                            'elpher-page  page
-                            'action #'elpher-click-link
-                            'follow-link t
-                            'help-echo #'elpher--page-button-help
-                            'face 'button)))
+        (make-text-button (match-beginning 0)
+                          (match-end 0)
+                          'elpher-page  page
+                          'action #'elpher-click-link
+                          'follow-link t
+                          'help-echo #'elpher--page-button-help
+                          'face 'button)))
     (buffer-string)))
 
 (defconst elpher-ansi-regex "\x1b\\[[^m]*m"
     (buffer-string)))
 
 (defconst elpher-ansi-regex "\x1b\\[[^m]*m"
-  "Wildly incomplete regexp used to strip out some troublesome ANSI escape sequences.")
+  "Incomplete regexp used to strip out some troublesome ANSI escape sequences.")
 
 (defun elpher-process-text-for-display (string)
   "Perform any desired processing of STRING prior to display as text.
 
 (defun elpher-process-text-for-display (string)
   "Perform any desired processing of STRING prior to display as text.
@@ -1114,9 +1115,9 @@ Currently includes buttonifying URLs and processing ANSI escape codes."
 (defun elpher-get-gopher-query-page (renderer)
   "Getter for gopher addresses requiring input.
 The response is rendered using the rendering function RENDERER."
 (defun elpher-get-gopher-query-page (renderer)
   "Getter for gopher addresses requiring input.
 The response is rendered using the rendering function RENDERER."
-   (let* ((address (elpher-page-address elpher-current-page))
-          (content (elpher-get-cached-content address))
-          (aborted t))
+  (let* ((address (elpher-page-address elpher-current-page))
+         (content (elpher-get-cached-content address))
+         (aborted t))
     (if (and content (funcall renderer nil))
         (elpher-with-clean-buffer
          (insert content)
     (if (and content (funcall renderer nil))
         (elpher-with-clean-buffer
          (insert content)
@@ -1270,6 +1271,11 @@ that the response was malformed."
          (error "Gemini server response unknown: %s %s"
                 response-code response-meta))))))
 
          (error "Gemini server response unknown: %s %s"
                 response-code response-meta))))))
 
+(unless (fboundp 'read-answer)
+  (defun read-answer (question answers)
+    "Backfill for the new read-answer code."
+    (completing-read question (mapcar 'identity answers))))
+
 (defun elpher-choose-client-certificate ()
   "Prompt for a client certificate to use to establish a TLS connection."
   (let* ((read-answer-short t))
 (defun elpher-choose-client-certificate ()
   "Prompt for a client certificate to use to establish a TLS connection."
   (let* ((read-answer-short t))
@@ -1323,8 +1329,8 @@ that the response was malformed."
     (condition-case the-error
         (if (and content (funcall renderer nil))
             (elpher-with-clean-buffer
     (condition-case the-error
         (if (and content (funcall renderer nil))
             (elpher-with-clean-buffer
-              (insert content)
-              (elpher-restore-pos))
+             (insert content)
+             (elpher-restore-pos))
           (elpher-with-clean-buffer
            (insert "LOADING GEMINI... (use 'u' to cancel)\n"))
           (setq elpher-gemini-redirect-chain nil)
           (elpher-with-clean-buffer
            (insert "LOADING GEMINI... (use 'u' to cancel)\n"))
           (setq elpher-gemini-redirect-chain nil)
@@ -1457,10 +1463,10 @@ by HEADER-LINE."
                    (2 'elpher-gemini-heading2)
                    (3 'elpher-gemini-heading3)
                    (_ 'default)))
                    (2 'elpher-gemini-heading2)
                    (3 'elpher-gemini-heading3)
                    (_ 'default)))
-          (fill-column (if (display-graphic-p)
-                           (/ (* fill-column
-                                 (font-get (font-spec :name (face-font 'default)) :size))
-                              (font-get (font-spec :name (face-font face)) :size)) fill-column)))
+           (fill-column (if (display-graphic-p)
+                            (/ (* fill-column
+                                  (font-get (font-spec :name (face-font 'default)) :size))
+                               (font-get (font-spec :name (face-font face)) :size)) fill-column)))
       (setq elpher--gemini-page-headings (cons (cons header (point))
                                                elpher--gemini-page-headings))
       (unless (display-graphic-p)
       (setq elpher--gemini-page-headings (cons (cons header (point))
                                                elpher--gemini-page-headings))
       (unless (display-graphic-p)
@@ -1471,14 +1477,14 @@ by HEADER-LINE."
 (defun elpher-gemini-insert-text (text-line)
   "Insert a plain non-preformatted TEXT-LINE into a text/gemini document.
 This function uses Emacs' auto-fill to wrap text sensibly to a maximum
 (defun elpher-gemini-insert-text (text-line)
   "Insert a plain non-preformatted TEXT-LINE into a text/gemini document.
 This function uses Emacs' auto-fill to wrap text sensibly to a maximum
-width defined by elpher-gemini-max-fill-width."
-  (string-match "\\(^[ \t]*\\)\\(\*[ \t]+\\|>[ \t]*\\)?" text-line)
+width defined by `elpher-gemini-max-fill-width'."
+  (string-match "\\(^[ \t]*\\)\\(\\*[ \t]+\\|>[ \t]*\\)?" text-line)
   (let* ((line-prefix (match-string 2 text-line))
          (processed-text-line
           (if line-prefix
               (cond ((string-prefix-p "*" line-prefix)
                      (concat
   (let* ((line-prefix (match-string 2 text-line))
          (processed-text-line
           (if line-prefix
               (cond ((string-prefix-p "*" line-prefix)
                      (concat
-                      (replace-regexp-in-string "\*"
+                      (replace-regexp-in-string "\\*"
                                                 elpher-gemini-bullet-string
                                                 (match-string 0 text-line))
                       (substring text-line (match-end 0))))
                                                 elpher-gemini-bullet-string
                                                 (match-string 0 text-line))
                       (substring text-line (match-end 0))))
@@ -1486,7 +1492,7 @@ width defined by elpher-gemini-max-fill-width."
                      (propertize text-line 'face 'elpher-gemini-quoted))
                     (t text-line))
             text-line))
                      (propertize text-line 'face 'elpher-gemini-quoted))
                     (t text-line))
             text-line))
-         (adaptive-fill-mode nil))
+         (adaptive-fill-mode t))
     (insert (elpher-process-text-for-display processed-text-line))
     (newline)))
 
     (insert (elpher-process-text-for-display processed-text-line))
     (newline)))
 
@@ -1778,48 +1784,125 @@ If ADDRESS is already bookmarked, update the label only."
 ;;; Integrations
 ;;
 
 ;;; Integrations
 ;;
 
-(defun elpher-org-link-store ()
-  "Store link to an `elpher' page in org-mode."
+;;; Org
+
+;; Avoid byte compilation warnings.
+(eval-when-compile
+  (declare-function org-link-store-props "ol")
+  (declare-function org-link-set-parameters "ol"))
+
+(defun elpher-org-export-link (link description format protocol)
+  "Export a LINK with DESCRIPTION for the given PROTOCOL and FORMAT.
+
+FORMAT is an Org export backend.  DESCRIPTION may be nil.  PROTOCOL may be one
+of gemini, gopher or finger."
+  (let* ((url (if (equal protocol "elpher")
+                  (string-remove-prefix "elpher:" link)
+                (format "%s:%s" protocol link)))
+         (desc (or description url)))
+    (pcase format
+      (`gemini (format "=> %s %s" url desc))
+      (`html (format "<a href=\"%s\">%s</a>" url desc))
+      (`latex (format "\\href{%s}{%s}" url desc))
+      (_ (if (not description)
+             url
+           (format "%s (%s)" desc url))))))
+
+(defun elpher-org-store-link ()
+  "Store link to an `elpher' page in Org."
   (when (eq major-mode 'elpher-mode)
   (when (eq major-mode 'elpher-mode)
-    (let ((link (concat "elpher:" (elpher-info-current)))
-          (desc (car elpher-current-page)))
-      (org-link-store-props :type "elpher"
-                            :link link
-                            :description desc)
+    (let* ((url (elpher-info-current))
+           (desc (car elpher-current-page))
+           (protocol (cond
+                      ((string-prefix-p "gemini:" url) "gemini")
+                      ((string-prefix-p "gopher:" url) "gopher")
+                      ((string-prefix-p "finger:" url) "finger")
+                      (t "elpher"))))
+      (when (equal "elpher" protocol)
+        ;; Weird link. Or special inner link?
+        (setq url (concat "elpher:" url)))
+      (org-link-store-props :type protocol :link url :description desc)
       t)))
 
       t)))
 
-(defun elpher-org-link-follow (link _args)
-  "Follow an `elpher' link in an `org' buffer."
-  (require 'elpher)
-  (message (concat "Got link: " link))
-  (when (or
-         (string-match-p "^gemini://.+" link)
-         (string-match-p "^gopher://.+" link)
-         (string-match-p "^finger://.+" link))
-    (elpher-go (string-remove-prefix "elpher:" link))))
-
-(with-eval-after-load "org"
-  ;; Use `org-link-set-parameters' if defined (org-mode 9+)
-  (if (fboundp 'org-link-set-parameters)
-      (org-link-set-parameters "elpher"
-                               :store #'elpher-org-link-store
-                               :follow #'elpher-org-link-follow)
-    (org-add-link-type "mu4e" 'elpher-org-link-follow)
-    (add-hook 'org-store-link-functions 'elpher-org-link-store)))
-
-(defun browse-url-elpher (url &rest _args)
-  "Browse URL. This function is used by `browse-url'."
+(defun elpher-org-follow-link (link protocol)
+  "Visit a LINK for the given PROTOCOL.
+
+PROTOCOL may be one of gemini, gopher or finger.  This method also
+supports the old protocol elpher, where the link is self-contained."
+  (let ((url (if (equal protocol "elpher")
+                 (string-remove-prefix "elpher:" link)
+               (format "%s:%s" protocol link))))
+    (elpher-go url)))
+
+(with-eval-after-load 'org
+  (org-link-set-parameters
+   "elpher"
+   :store #'elpher-org-store-link
+   :export (lambda (link description format _plist)
+             (elpher-org-export-link link description format "elpher"))
+   :follow (lambda (link _arg) (elpher-org-follow-link link "elpher")))
+  (org-link-set-parameters
+   "gemini"
+   :export (lambda (link description format _plist)
+             (elpher-org-export-link link description format "gemini"))
+   :follow (lambda (link _arg) (elpher-org-follow-link link "gemini")))
+  (org-link-set-parameters
+   "gopher"
+   :export (lambda (link description format _plist)
+             (elpher-org-export-link link description format "gopher"))
+   :follow (lambda (link _arg) (elpher-org-follow-link link "gopher")))
+  (org-link-set-parameters
+   "finger"
+   :export (lambda (link description format _plist)
+             (elpher-org-export-link link description format "finger"))
+   :follow (lambda (link _arg) (elpher-org-follow-link link "finger"))))
+
+;;; Browse URL
+
+;; Avoid byte compilation warnings.
+(eval-when-compile
+  (defvar thing-at-point-uri-schemes))
+
+;;;###autoload
+(defun elpher-browse-url-elpher (url &rest _args)
+  "Browse URL using Elpher.  This function is used by `browse-url'."
   (interactive (browse-url-interactive-arg "Elpher URL: "))
   (elpher-go url))
 
   (interactive (browse-url-interactive-arg "Elpher URL: "))
   (elpher-go url))
 
-(with-eval-after-load "browse-url"
-  ;; Use elpher to open gopher, finger and gemini links
-  (when (boundp 'browse-url-default-handlers)
-    (add-to-list 'browse-url-default-handlers
-                '("^\\(gopher\\|finger\\|gemini\\)://" . browse-url-elpher)))
-  ;; Register "gemini://" as a URI scheme so `browse-url' does the right thing
+;; Use elpher to open gopher, finger and gemini links
+;; For recent version of `browse-url' package
+(if (boundp 'browse-url-default-handlers)
+    (add-to-list
+     'browse-url-default-handlers
+     '("^\\(gopher\\|finger\\|gemini\\)://" . elpher-browse-url-elpher))
+  ;; Patch `browse-url-browser-function' for older ones. The value of
+  ;; that variable is `browse-url-default-browser' by default, so
+  ;; that's the function that gets advised.
+  (advice-add browse-url-browser-function :before-while
+              (lambda (url &rest _args)
+               "Handle gemini, gopher, and finger schemes using Elpher."
+                (let ((scheme (downcase (car (split-string url ":" t)))))
+                  (if (member scheme '("gemini" "gopher" "finger"))
+                      ;; `elpher-go' always returns nil, which will stop the
+                      ;; advice chain here in a before-while
+                      (elpher-go url)
+                    ;; chain must continue, then return t.
+                    t)))))
+
+;; Register "gemini://" as a URI scheme so `browse-url' does the right thing
+(with-eval-after-load 'thingatpt
   (add-to-list 'thing-at-point-uri-schemes "gemini://"))
 
   (add-to-list 'thing-at-point-uri-schemes "gemini://"))
 
+;;; Mu4e:
+
+(eval-when-compile
+  (defvar mu4e~view-beginning-of-url-regexp))
+
+(with-eval-after-load 'mu4e-view
+  ;; Make mu4e aware of the gemini world
+  (setq mu4e~view-beginning-of-url-regexp
+        "\\(?:https?\\|gopher\\|finger\\|gemini\\)://\\|mailto:"))
+
 ;;; Interactive procedures
 ;;
 
 ;;; Interactive procedures
 ;;
 
@@ -1860,7 +1943,7 @@ When run interactively HOST-OR-URL is read from the minibuffer."
       (elpher-visit-page (elpher-make-page url (elpher-address-from-url url))))))
 
 (defun elpher-visit-gemini-numbered-link (n)
       (elpher-visit-page (elpher-make-page url (elpher-address-from-url url))))))
 
 (defun elpher-visit-gemini-numbered-link (n)
-  "Visit link designated by a number."
+  "Visit link designated by a number N."
   (interactive "nLink number: ")
   (if (or (> n (length elpher--gemini-page-links))
           (< n 1))
   (interactive "nLink number: ")
   (if (or (> n (length elpher--gemini-page-links))
           (< n 1))
@@ -2128,36 +2211,37 @@ When run interactively HOST-OR-URL is read from the minibuffer."
     (define-key map (kbd "F") 'elpher-forget-current-certificate)
     (define-key map (kbd "v") 'elpher-visit-gemini-numbered-link)
     (when (fboundp 'evil-define-key*)
     (define-key map (kbd "F") 'elpher-forget-current-certificate)
     (define-key map (kbd "v") 'elpher-visit-gemini-numbered-link)
     (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 "-") 'elpher-back
-        (kbd "^") 'elpher-back
-        (kbd "U") 'elpher-back-to-start
-        [mouse-3] 'elpher-back
-        (kbd "o") 'elpher-go
-        (kbd "O") 'elpher-go-current
-        (kbd "r") 'elpher-redraw
-        (kbd "R") 'elpher-reload
-        (kbd "T") 'elpher-toggle-tls
-        (kbd ".") 'elpher-view-raw
-        (kbd "d") 'elpher-download
-        (kbd "D") 'elpher-download-current
-        (kbd "J") 'elpher-jump
-        (kbd "i") 'elpher-info-link
-        (kbd "I") 'elpher-info-current
-        (kbd "c") 'elpher-copy-link-url
-        (kbd "C") 'elpher-copy-current-url
-        (kbd "a") 'elpher-bookmark-link
-        (kbd "A") 'elpher-bookmark-current
-        (kbd "x") 'elpher-unbookmark-link
-        (kbd "X") 'elpher-unbookmark-current
-        (kbd "B") 'elpher-bookmarks
-        (kbd "S") 'elpher-set-gopher-coding-system
-        (kbd "F") 'elpher-forget-current-certificate
-        (kbd "v") 'elpher-visit-gemini-numbered-link))
+      (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 "-") 'elpher-back
+       (kbd "^") 'elpher-back
+       (kbd "U") 'elpher-back-to-start
+       [mouse-3] 'elpher-back
+       (kbd "o") 'elpher-go
+       (kbd "O") 'elpher-go-current
+       (kbd "r") 'elpher-redraw
+       (kbd "R") 'elpher-reload
+       (kbd "T") 'elpher-toggle-tls
+       (kbd ".") 'elpher-view-raw
+       (kbd "d") 'elpher-download
+       (kbd "D") 'elpher-download-current
+       (kbd "J") 'elpher-jump
+       (kbd "i") 'elpher-info-link
+       (kbd "I") 'elpher-info-current
+       (kbd "c") 'elpher-copy-link-url
+       (kbd "C") 'elpher-copy-current-url
+       (kbd "a") 'elpher-bookmark-link
+       (kbd "A") 'elpher-bookmark-current
+       (kbd "x") 'elpher-unbookmark-link
+       (kbd "X") 'elpher-unbookmark-current
+       (kbd "B") 'elpher-bookmarks
+       (kbd "S") 'elpher-set-gopher-coding-system
+       (kbd "F") 'elpher-forget-current-certificate
+       (kbd "v") 'elpher-visit-gemini-numbered-link))
     map)
   "Keymap for gopher client.")
 
     map)
   "Keymap for gopher client.")
 
@@ -2189,24 +2273,25 @@ functions which initialize the gopher client, namely
 The buffer used for Elpher sessions is determined by the value of
 ‘elpher-buffer-name’.  If there is already an Elpher session active in
 that buffer, Emacs will simply switch to it.  Otherwise, a new session
 The buffer used for Elpher sessions is determined by the value of
 ‘elpher-buffer-name’.  If there is already an Elpher session active in
 that buffer, Emacs will simply switch to it.  Otherwise, a new session
-will begin.  A numeric prefix arg (as in ‘C-u 42 M-x elpher RET’)
-switches to the session with that number, creating it if necessary.  A
-nonnumeric prefix arg means to create a new session.  Returns the
-buffer selected (or created)."
+will begin.  A numeric prefix ARG (as in ‘\\[universal-argument] 42
+\\[execute-extended-command] elpher RET’) switches to the session with
+that number, creating it if necessary.  A non numeric prefix ARG means
+to create a new session.  Returns the buffer selected (or created)."
   (interactive "P")
   (let* ((name (default-value 'elpher-buffer-name))
   (interactive "P")
   (let* ((name (default-value 'elpher-buffer-name))
-        (buf (cond ((numberp arg)
-                    (get-buffer-create (format "%s<%d>" name arg)))
-                   (arg
-                    (generate-new-buffer name))
-                   (t
-                    (get-buffer-create name)))))
+         (buf (cond ((numberp arg)
+                     (get-buffer-create (format "%s<%d>" name arg)))
+                    (arg
+                     (generate-new-buffer name))
+                    (t
+                     (get-buffer-create name)))))
     (pop-to-buffer-same-window buf)
     (unless (buffer-modified-p)
       (elpher-mode)
     (pop-to-buffer-same-window buf)
     (unless (buffer-modified-p)
       (elpher-mode)
-      (let ((start-page (elpher-make-page "Elpher Start Page"
-                                         (elpher-make-special-address 'start))))
-       (elpher-visit-page start-page))
+      (let ((start-page (elpher-make-page
+                         "Elpher Start Page"
+                         (elpher-make-special-address 'start))))
+        (elpher-visit-page start-page))
       "Started Elpher."))); Otherwise (elpher) evaluates to start page string.
 
 ;;; elpher.el ends here
       "Started Elpher."))); Otherwise (elpher) evaluates to start page string.
 
 ;;; elpher.el ends here