Better grouping of text processing procedures.
[elpher.git] / elpher.el
index e3cf717..ef20de0 100644 (file)
--- a/elpher.el
+++ b/elpher.el
@@ -61,8 +61,8 @@
 ;; Full instructions can be found in the Elpher info manual.
 
 ;; Elpher is under active development.  Any suggestions for
-;; improvements are welcome, and can be made on the official
-;; project page, gopher://thelambdalab.xyz/elpher, or via the
+;; improvements are welcome, and can be made on the official project
+;; page, gopher://thelambdalab.xyz/1/projects/elpher, or via the
 ;; project mailing list at https://lists.sr.ht/~michel-slm/elpher.
 
 ;;; Code:
 (require 'gnutls)
 (require 'socks)
 
-;;; ANSI colors or XTerm colors
-
-(or (require 'xterm-color nil t)
-    (require 'ansi-color))
-
-(defalias 'elpher-color-filter-apply
-  (if (fboundp 'xterm-color-filter)
-      (lambda (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
-  (if (fboundp 'xterm-color-filter)
-      #'xterm-color-filter
-    #'ansi-color-apply)
-  "A function to apply ANSI escape sequences.")
-
 ;;; Global constants
 ;;
 
 ;;
 
 (eval-when-compile
-  (defvar bookmark-make-record-function)
+  (declare-function ansi-color-filter-apply "ansi-color")
+  (declare-function ansi-color-apply "ansi-color")
   (declare-function bookmark-store "bookmark")
   (declare-function org-link-store-props "ol")
   (declare-function org-link-set-parameters "ol")
-  (defvar thing-at-point-uri-schemes)
-  (defvar mu4e~view-beginning-of-url-regexp))
+  (defvar ansi-color-context)
+  (defvar bookmark-make-record-function)
+  (defvar mu4e~view-beginning-of-url-regexp)
+  (defvar thing-at-point-uri-schemes))
 
 
 ;;; Customization group
@@ -389,11 +373,6 @@ requiring gopher-over-TLS."
   "Create an ADDRESS object corresponding to the given special address symbol TYPE."
   type)
 
-(defun elpher-make-start-page ()
-  "Create the start page."
-  (elpher-make-page "Elpher Start Page"
-                    (elpher-make-special-address 'start)))
-
 (defun elpher-address-to-url (address)
   "Get string representation of ADDRESS, or nil if ADDRESS is special."
   (if (elpher-address-special-p address)
@@ -493,6 +472,11 @@ If no address is defined, returns 0.  (This is for compatibility with the URL li
   "Create a page with DISPLAY-STRING and ADDRESS."
   (list display-string address))
 
+(defun elpher-make-start-page ()
+  "Create the start page."
+  (elpher-make-page "Elpher Start Page"
+                    (elpher-make-special-address 'start)))
+
 (defun elpher-page-display-string (page)
   "Retrieve the display string corresponding to PAGE."
   (elt page 0))
@@ -644,6 +628,57 @@ away CRs and any terminating period."
   (elpher-decode (replace-regexp-in-string "\n\\.\n$" "\n"
                                            (replace-regexp-in-string "\r" "" string))))
 
+;;; Buttonify urls
+
+(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_~?/@|#-]\\)?\\)?"
+  "Regexp used to locate and buttonify URLs in text files loaded by elpher.")
+
+(defun elpher-buttonify-urls (string)
+  "Turn substrings which look like urls in STRING into clickable buttons."
+  (with-temp-buffer
+    (insert string)
+    (goto-char (point-min))
+    (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)))
+    (buffer-string)))
+
+;;; ANSI colors or XTerm colors (application and filtering)
+
+(or (require 'xterm-color nil t)
+    (require 'ansi-color))
+
+(defalias 'elpher-color-filter-apply
+  (if (fboundp 'xterm-color-filter)
+      (lambda (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
+  (if (fboundp 'xterm-color-filter)
+      #'xterm-color-filter
+    #'ansi-color-apply)
+  "A function to apply ANSI escape sequences.")
+
+;;; Processing text for display
+
+(defun elpher-process-text-for-display (string)
+  "Perform any desired processing of STRING prior to display as text.
+Currently includes buttonifying URLs and processing ANSI escape codes."
+  (elpher-buttonify-urls (if elpher-filter-ansi-from-text
+                             (elpher-color-filter-apply string)
+                           (elpher-color-apply string))))
+
 
 ;;; Network error reporting
 ;;
@@ -678,6 +713,15 @@ ERROR can be either an error object or a string."
   (if (timerp elpher-network-timer)
       (cancel-timer elpher-network-timer)))
 
+(defun elpher-make-network-timer (thunk)
+  "Creates a timer to run the THUNK after `elpher-connection-timeout' seconds.
+This is just a wraper around `run-at-time' which additionally sets the
+buffer-local variable `elpher-network-timer' to allow
+`elpher-process-cleanup' to also clear the timer."
+  (let ((timer (run-at-time elpher-connection-timeout nil thunk)))
+    (setq-local elpher-network-timer timer)
+    timer))
+
 (defun elpher-get-host-response (address default-port query-string response-processor
                                          &optional use-tls force-ipv4)
   "Generic function for retrieving data from ADDRESS.
@@ -714,19 +758,7 @@ the host operating system and the local network capabilities.)"
                                     :hostname host
                                     :keylist
                                     (elpher-get-current-keylist address)))
-               (proc (if socks (socks-open-network-stream "elpher-process" nil host service)
-                       (make-network-process :name "elpher-process"
-                                             :host host
-                                             :family (and force-ipv4 'ipv4)
-                                             :service service
-                                             :buffer nil
-                                             :nowait t
-                                             :tls-parameters
-                                             (and use-tls
-                                                  (cons 'gnutls-x509pki
-                                                        (apply #'gnutls-boot-parameters
-                                                               gnutls-params))))))
-               (timer (run-at-time elpher-connection-timeout nil
+               (timer (elpher-make-network-timer
                                    (lambda ()
                                      (elpher-process-cleanup)
                                      (cond
@@ -748,7 +780,19 @@ the host operating system and the local network capabilities.)"
                                                                  response-processor
                                                                  nil force-ipv4))
                                       (t
-                                       (elpher-network-error address "Connection time-out.")))))))
+                                       (elpher-network-error address "Connection time-out."))))))
+               (proc (if socks (socks-open-network-stream "elpher-process" nil host service)
+                       (make-network-process :name "elpher-process"
+                                             :host host
+                                             :family (and force-ipv4 'ipv4)
+                                             :service service
+                                             :buffer nil
+                                             :nowait t
+                                             :tls-parameters
+                                             (and use-tls
+                                                  (cons 'gnutls-x509pki
+                                                        (apply #'gnutls-boot-parameters
+                                                               gnutls-params)))))))
           (setq elpher-network-timer timer)
           (set-process-coding-system proc 'binary 'binary)
           (set-process-query-on-exit-flag proc nil)
@@ -805,6 +849,7 @@ the host operating system and the local network capabilities.)"
             (if use-tls (apply #'gnutls-negotiate :process proc gnutls-params))
             (funcall (process-sentinel proc) proc "open\n")))
       (error
+       (elpher-process-cleanup)
        (error "Error initiating connection to server")))))
 
 
@@ -1059,34 +1104,6 @@ If ADDRESS is not supplied or nil the record is rendered as an
 
 ;; 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_~?/@|#-]\\)?\\)?"
-  "Regexp used to locate and buttonify URLs in text files loaded by elpher.")
-
-(defun elpher-buttonify-urls (string)
-  "Turn substrings which look like urls in STRING into clickable buttons."
-  (with-temp-buffer
-    (insert string)
-    (goto-char (point-min))
-    (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)))
-    (buffer-string)))
-
-(defun elpher-process-text-for-display (string)
-  "Perform any desired processing of STRING prior to display as text.
-Currently includes buttonifying URLs and processing ANSI escape codes."
-  (elpher-buttonify-urls (if elpher-filter-ansi-from-text
-                             (elpher-color-filter-apply string)
-                           (elpher-color-apply string))))
-
 (defun elpher-render-text (data &optional _mime-type-string)
   "Render DATA as text.  MIME-TYPE-STRING is unused."
   (elpher-with-clean-buffer
@@ -1458,7 +1475,6 @@ treatment that a separate function is warranted."
         (insert (propertize display-string 'face 'elpher-unknown)))
       (insert "\n"))))
 
-;; buffer-local
 (defvar elpher--gemini-page-headings nil
   "List of headings on the page.")
 
@@ -1662,26 +1678,35 @@ The result is rendered using RENDERER."
             "(Bookmarks from legacy elpher-bookmarks files will be automatically imported.)\n"
             'face 'shadow))
    (insert "\n"
-           "For Elpher release news or to leave feedback, visit:\n")
+           "The gopher home of the Elpher project is here:\n")
    (elpher-insert-index-record "The Elpher Project Page"
                                (elpher-make-gopher-address ?1
                                                            "/projects/elpher/"
                                                            "thelambdalab.xyz"
                                                            70))
-   (insert "\n"
-           "** Refer to the ")
    (let ((help-string "RET,mouse-1: Open Elpher info manual (if available)"))
-     (insert-text-button "Elpher info manual"
+     (insert "\n"
+             "The following info documentation is available:\n"
+             "   - ")
+     (insert-text-button "Elpher Manual"
                          'face 'link
                          'action (lambda (_)
                                    (interactive)
                                    (info "(elpher)"))
                          'follow-link t
-                         'help-echo help-string))
-   (insert " for the full documentation. **\n")
+                         'help-echo help-string)
+     (insert "\n   - ")
+     (insert-text-button "Changes introduced by the latest release"
+                       'face 'link
+                       'action (lambda (_)
+                                 (interactive)
+                                 (info "(elpher)News"))
+                       'follow-link t
+                       'help-echo help-string))
+   (insert "\n")
    (insert (propertize
-            (concat "  (This should be available if you have installed Elpher using\n"
-                    "   MELPA. Otherwise you will have to install the manual yourself.)\n")
+            (concat "  (These documents should be available if you have installed Elpher \n"
+                    "   using MELPA. Otherwise you may have to install the manual yourself.)\n")
             'face 'shadow))
    (elpher-restore-pos)))
 
@@ -1777,9 +1802,19 @@ record for the current elpher page."
 
 ;;;###autoload
 (defun elpher-bookmark-jump (bookmark)
-  "Go to a particular BOOKMARK."
-  (let* ((url (cdr (assq 'location bookmark))))
-    (elpher-go url)))
+  "Handler used to open a bookmark using elpher.
+The argument BOOKMARK is a bookmark record passed to the function.
+This handler is responsible for loading the bookmark in some buffer,
+then making that buffer the current buffer.  It should not switch
+to the buffer."
+  (let* ((url (cdr (assq 'location bookmark)))
+         (cleaned-url (string-trim url))
+         (address (elpher-address-from-url cleaned-url))
+         (page (elpher-make-page cleaned-url address)))
+    (elpher-with-clean-buffer
+     (elpher-visit-page page))
+    (set-buffer (get-buffer elpher-buffer-name))
+    nil))
 
 (defun elpher-bookmark-link ()
   "Bookmark the link at point.
@@ -2219,7 +2254,8 @@ functions which initialize the client, namely
   (setq-local elpher-history nil)
   (setq-local elpher-buffer-name (buffer-name))
   (setq-local bookmark-make-record-function #'elpher-bookmark-make-record)
-  (setq-local imenu-create-index-function (lambda () elpher--gemini-page-headings)))
+  (setq-local imenu-create-index-function (lambda () elpher--gemini-page-headings))
+  (setq-local xterm-color-preserve-properties t))
 
 (when (fboundp 'evil-set-initial-state)
   (evil-set-initial-state 'elpher-mode 'motion))