Updated welcome page documentation to also mention non-GNU elpa.
[elpher.git] / elpher.el
index eb888b6..4498c6c 100644 (file)
--- a/elpher.el
+++ b/elpher.el
@@ -5,7 +5,7 @@
 
 ;; Author: Tim Vaughan <plugd@thelambdalab.xyz>
 ;; Created: 11 April 2019
-;; Version: 3.3.1
+;; Version: 3.4.0
 ;; Keywords: comm gopher
 ;; Homepage: https://thelambdalab.xyz/elpher
 ;; Package-Requires: ((emacs "27.1"))
@@ -70,7 +70,7 @@
 ;;; Global constants
 ;;
 
-(defconst elpher-version "3.3.1"
+(defconst elpher-version "3.4.0"
   "Current version of elpher.")
 
 (defconst elpher-margin-width 6
   (declare-function org-link-store-props "ol")
   (declare-function org-link-set-parameters "ol")
   (defvar ansi-color-context)
+  (defvar xterm-color--current-fg)
+  (defvar xterm-color--current-bg)
   (defvar bookmark-make-record-function)
   (defvar mu4e~view-beginning-of-url-regexp)
   (defvar eww-use-browse-url)
@@ -220,6 +222,24 @@ Emacs bookmark menu being accessible via \\[elpher-show-bookmarks] from
 the start page."
   :type '(string))
 
+(defcustom elpher-gemini-hide-preformatted nil
+  "Cause elpher to hide preformatted gemini text by default.
+When this option is enabled, preformatted text in text/gemini documents
+is replaced with a button which can be used to toggle its display.
+
+This is intended to improve accessibility, as preformatted text often
+includes art which can be difficult for screen readers to interpret
+meaningfully."
+  :type '(boolean))
+
+(defcustom elpher-gemini-preformatted-toggle-bullet "‣ "
+  "Margin symbol used to distinguish the preformatted text toggle."
+  :type '(string))
+
+(defcustom elpher-gemini-preformatted-toggle-label "[Toggle Preformatted Text]"
+  "Label of button used to toggle formatted text."
+  :type '(string))
+
 ;; Face customizations
 
 (defgroup elpher-faces nil
@@ -298,6 +318,10 @@ the start page."
   '((t :inherit font-lock-doc-face))
   "Face used for gemini quoted texts.")
 
+(defface elpher-gemini-preformatted-toggle
+  '((t :inherit button))
+  "Face used for buttons used to toggle display of preformatted text.")
+
 ;;; Model
 ;;
 
@@ -423,7 +447,17 @@ For gopher addresses this is a combination of the selector type and selector."
 
 (defun elpher-address-host (address)
   "Retrieve host from ADDRESS object."
-  (url-host address))
+  (let ((host-pre (url-host address)))
+    ;; The following strips out square brackets which sometimes enclose IPv6
+    ;; addresses.  Doing this here rather than at the parsing stage may seem
+    ;; weird, but this lets us way we avoid having to muck with both URL parsing
+    ;; and reconstruction.  It's also more efficient, as this method is not
+    ;; called during page rendering.
+    (if (and (> (length host-pre) 2)
+             (eq (elt host-pre 0) ?\[)
+             (eq (elt host-pre (- (length host-pre) 1)) ?\]))
+        (substring host-pre 1 (- (length host-pre) 1))
+      host-pre)))
 
 (defun elpher-address-user (address)
   "Retrieve user from ADDRESS object."
@@ -431,7 +465,8 @@ For gopher addresses this is a combination of the selector type and selector."
 
 (defun elpher-address-port (address)
   "Retrieve port from ADDRESS object.
-If no address is defined, returns 0.  (This is for compatibility with the URL library.)"
+If no address is defined, returns 0.  (This is for compatibility with
+the URL library.)"
   (url-port address))
 
 (defun elpher-gopher-address-selector (address)
@@ -584,7 +619,7 @@ previously-visited pages,unless NO-HISTORY is non-nil."
       (goto-char (point-min)))))
 
 (defun elpher-get-default-url-scheme ()
-  "Suggest a default URL scheme to use for visiting addresses based on the current page."
+  "Suggest default URL scheme for visiting addresses based on the current page."
   (if elpher-current-page
       (let* ((address (elpher-page-address elpher-current-page))
              (current-type (elpher-address-type address)))
@@ -592,8 +627,6 @@ previously-visited pages,unless NO-HISTORY is non-nil."
           ((or (and 'file (guard (not elpher-history)))
                `(about ,_))
            elpher-default-url-type)
-          (`(about ,_)
-           elpher-default-url-type)
           (_
            (url-type address))))
       elpher-default-url-type))
@@ -1059,7 +1092,7 @@ once they are retrieved from the gopher server."
 ;; Index rendering
 
 (defun elpher-insert-margin (&optional type-name)
-  "Insert index margin, optionally containing the TYPE-NAME, into the current buffer."
+  "Insert index margin, optionally containing the TYPE-NAME, into current buffer."
   (if type-name
       (progn
         (insert (format (concat "%" (number-to-string (- elpher-margin-width 1)) "s")
@@ -1114,7 +1147,7 @@ If ADDRESS is not supplied or nil the record is rendered as an
     (insert "\n")))
 
 (defun elpher-click-link (button)
-  "Function called when the gopher link BUTTON is activated (via mouse or keypress)."
+  "Function called when the gopher link BUTTON is activated."
   (let ((page (button-get button 'elpher-page)))
     (elpher-visit-page page)))
 
@@ -1572,30 +1605,74 @@ width defined by `elpher-gemini-max-fill-width'."
             text-line))
          (fill-prefix (if line-prefix
                           (make-string (length (match-string 0 text-line)) ?\s)
-                        nil)))
+                        "")))
     (insert (elpher-process-text-for-display processed-text-line))
     (newline)))
 
+(defun elpher-gemini-pref-expand-collapse (button)
+  "Function called when the preformatted text toggle BUTTON is activated."
+  (let ((id (button-get button 'pref-id)))
+    (if (invisible-p id)
+        (remove-from-invisibility-spec id)
+      (add-to-invisibility-spec id))
+    (redraw-display)))
+
+(defun elpher-gemini-insert-preformatted-toggler (alt-text)
+  "Insert a button for toggling the visibility of preformatted text.
+If non-nil, ALT-TEXT is displayed alongside the button."
+  (let* ((url-string (url-recreate-url (elpher-page-address elpher-current-page)))
+         (pref-id (intern (concat "pref-"
+                                  (number-to-string (point))
+                                  "-"
+                                  url-string))))
+    (insert elpher-gemini-preformatted-toggle-bullet)
+    (when alt-text
+      (insert (propertize (concat alt-text " ")
+                          'face 'elpher-gemin-preformatted)))
+    (insert-text-button elpher-gemini-preformatted-toggle-label
+                        'action #'elpher-gemini-pref-expand-collapse
+                        'pref-id pref-id
+                        'face 'elpher-gemini-preformatted-toggle)
+    (add-to-invisibility-spec pref-id)
+    (newline)
+    pref-id))
+
+(defun elpher-gemini-insert-preformatted-line (line &optional pref-id)
+  "Insert a LINE of preformatted text.
+PREF-ID is the value assigned to the \"invisible\" text attribute, which
+can be used to toggle the display of the preformatted text."
+  (insert (propertize (concat (elpher-process-text-for-display line) "\n")
+                      'face 'elpher-gemini-preformatted
+                      'invisible pref-id
+                      'rear-nonsticky t)))
+
 (defun elpher-render-gemini-map (data _parameters)
   "Render DATA as a gemini map file, PARAMETERS is currently unused."
   (elpher-with-clean-buffer
    (auto-fill-mode 1)
+   (setq-local buffer-invisibility-spec nil)
    (let ((preformatted nil)
          (adaptive-fill-mode nil)) ;Prevent automatic setting of fill-prefix
      (setq-local fill-column (min (window-width) elpher-gemini-max-fill-width))
      (dolist (line (split-string data "\n"))
-       (cond
-        ((string-prefix-p "```" line) (setq preformatted (not preformatted)))
-        (preformatted (insert (elpher-process-text-for-display
-                               (propertize line 'face 'elpher-gemini-preformatted))
-                              "\n"))
-        ((string-prefix-p "=>" line)
-         (elpher-gemini-insert-link line))
-        ((string-prefix-p "#" line) (elpher-gemini-insert-header line))
-        (t (elpher-gemini-insert-text line)))))
+       (pcase line
+         ((rx (: "```" (opt (let alt-text (+ any)))))
+          (setq preformatted
+                (if preformatted
+                    nil
+                  (if elpher-gemini-hide-preformatted
+                      (elpher-gemini-insert-preformatted-toggler alt-text)
+                    t))))
+         ((guard  preformatted)
+          (elpher-gemini-insert-preformatted-line line preformatted))
+         ((pred (string-prefix-p "=>"))
+          (elpher-gemini-insert-link line))
+         ((pred (string-prefix-p "#"))
+          (elpher-gemini-insert-header line))
+         (_ (elpher-gemini-insert-text line))))
    (elpher-cache-content
     (elpher-page-address elpher-current-page)
-    (buffer-string))))
+    (buffer-string)))))
 
 (defun elpher-render-gemini-plain-text (data _parameters)
   "Render DATA as plain text file.  PARAMETERS is currently unused."
@@ -1619,6 +1696,7 @@ width defined by `elpher-gemini-max-fill-width'."
               headers))
       (reverse headers))))
 
+
 ;; Finger page connection
 
 (defun elpher-get-finger-page (renderer)
@@ -1664,7 +1742,8 @@ The result is rendered using RENDERER."
 ;; Other URL page opening
 
 (defun elpher-get-other-url-page (renderer)
-  "Getter which attempts to open the URL specified by the current page (RENDERER must be nil)."
+  "Getter which attempts to open the URL specified by the current page.
+The RENDERER argument to this getter must be nil."
   (when renderer
     (elpher-visit-previous-page)
     (error "Command not supported for general URLs"))
@@ -1676,6 +1755,7 @@ The result is rendered using RENDERER."
         (browse-web url)
       (browse-url url))))
 
+
 ;; File page
 
 (defun elpher-get-file-page (renderer)
@@ -1799,11 +1879,13 @@ Assumes UTF-8 encoding for all text files."
                        'help-echo help-string))
    (insert "\n")
    (insert (propertize
-            (concat "(These documents should be available if you have installed Elpher \n"
-                    " using MELPA. Otherwise you may have to install the manual yourself.)\n")
+            (concat "(These documents should be available if you have installed Elpher\n"
+                    " from MELPA or non-GNU ELPA. Otherwise you may have to install the\n"
+                    " manual yourself.)\n")
             'face 'shadow))
    (elpher-restore-pos)))
 
+
 ;; History page retrieval
 
 (defun elpher-show-history ()
@@ -2154,7 +2236,9 @@ When run interactively HOST-OR-URL is read from the minibuffer."
         nil)))) ; non-nil value is displayed by eshell
 
 (defun elpher-go-current ()
-  "Go to a particular site read from the minibuffer, initialized with the current URL."
+  "Go to a particular URL which is read from the minibuffer.
+Unlike `elpher-go', the reader is initialized with the URL of the
+current page."
   (interactive)
   (let* ((address (elpher-page-address elpher-current-page))
          (url (read-string (format "Visit URL (default scheme %s): " (elpher-get-default-url-scheme))