Added news+acknowledgements to manual.
[elpher.git] / elpher.el
index 014ef6b..9d30254 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:
       (lambda (s)
         (let ((_xterm-color-render nil))
           (xterm-color-filter s)))
-    'ansi-color-filter-apply)
+    #'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)
+      #'xterm-color-filter
+    #'ansi-color-apply)
   "A function to apply ANSI escape sequences.")
 
 ;;; Global constants
   "Association list from types to getters, renderers, margin codes and index faces.")
 
 
-;;; Internal variables
-;;
-
-;; buffer-local
-(defvar elpher--gemini-page-headings nil
-  "List of headings on the page.")
-
-
 ;;; Declarations to avoid compiler warnings.
 ;;
 
@@ -397,11 +389,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)
@@ -501,6 +488,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))
@@ -608,7 +600,8 @@ previously-visited pages,unless NO-HISTORY is non-nil."
      (unless (eq major-mode 'elpher-mode)
        ;; avoid resetting buffer-local variables
        (elpher-mode))
-     (let ((inhibit-read-only t))
+     (let ((inhibit-read-only t)
+           (ansi-color-context nil)) ;; clean ansi interpreter state
        (setq-local network-security-level
                    (default-value 'network-security-level))
        (erase-buffer)
@@ -685,6 +678,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.
@@ -711,13 +713,17 @@ the host operating system and the local network capabilities.)"
     (condition-case nil
         (let* ((kill-buffer-query-functions nil)
                (port (elpher-address-port address))
-               (service (if (> port 0) port default-port))
                (host (elpher-address-host address))
-               (socks (or elpher-socks-always (string-suffix-p ".onion" host)))
+               (service (if (> port 0) port default-port))
                (response-string-parts nil)
                (bytes-received 0)
                (hkbytes-received 0)
-               (timer (run-at-time elpher-connection-timeout nil
+               (socks (or elpher-socks-always (string-suffix-p ".onion" host)))
+               (gnutls-params (list :type 'gnutls-x509pki
+                                    :hostname host
+                                    :keylist
+                                    (elpher-get-current-keylist address)))
+               (timer (elpher-make-network-timer
                                    (lambda ()
                                      (elpher-process-cleanup)
                                      (cond
@@ -740,8 +746,6 @@ the host operating system and the local network capabilities.)"
                                                                  nil force-ipv4))
                                       (t
                                        (elpher-network-error address "Connection time-out."))))))
-               (gnutls-params (list :type 'gnutls-x509pki :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
@@ -810,6 +814,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")))))
 
 
@@ -1066,7 +1071,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-]\\|\\[[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.")
+  "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."
@@ -1085,9 +1090,6 @@ If ADDRESS is not supplied or nil the record is rendered as an
                           'face 'button)))
     (buffer-string)))
 
-(defconst elpher-ansi-regex "\x1b\\[[^m]*m"
-  "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.
 Currently includes buttonifying URLs and processing ANSI escape codes."
@@ -1466,6 +1468,9 @@ treatment that a separate function is warranted."
         (insert (propertize display-string 'face 'elpher-unknown)))
       (insert "\n"))))
 
+(defvar elpher--gemini-page-headings nil
+  "List of headings on the page.")
+
 (defun elpher-gemini-insert-header (header-line)
   "Insert header described by HEADER-LINE into a text/gemini document.
 The gemini map file line describing the header is given
@@ -1658,7 +1663,7 @@ The result is rendered using RENDERER."
                          'face 'link
                          'action (lambda (_)
                                    (interactive)
-                                   (call-interactively #'bookmark-bmenu-list))
+                                   (call-interactively #'elpher-open-bookmarks))
                          'follow-link t
                          'help-echo help-string))
    (insert ".\n")
@@ -1666,26 +1671,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)))
 
@@ -1785,15 +1799,21 @@ record for the current elpher page."
   (let* ((url (cdr (assq 'location bookmark))))
     (elpher-go url)))
 
-(defun elpher-set-bookmark-no-overwrite ()
+(defun elpher-bookmark-link ()
   "Bookmark the link at point.
-To bookmark the current page, use \\[bookmark-set-no-overwrite]."
+To bookmark the current page, use \\[elpher-bookmark-current]."
   (interactive)
   (let ((elpher-bookmark-link t))
     (bookmark-set-no-overwrite)))
 
+(defun elpher-bookmark-current ()
+  "Bookmark the current page.
+To bookmark the link at point use \\[elpher-bookmark-link]."
+  (interactive)
+  (call-interactively #'bookmark-set-no-overwrite))
+
 (defun elpher-bookmark-import (file)
-  "Import Elpher bookmarks file FILE into Emacs bookmarks."
+  "Import legacy Elpher bookmarks file FILE into Emacs bookmarks."
   (interactive (list (if (and (boundp 'elpher-bookmarks-file)
                              (file-readable-p elpher-bookmarks-file))
                         elpher-bookmarks-file
@@ -1812,6 +1832,23 @@ To bookmark the current page, use \\[bookmark-set-no-overwrite]."
       (bookmark-store display-string (cdr record) t)))
   (bookmark-save))
 
+(defun elpher-open-bookmarks ()
+  "Display the current list of elpher bookmarks.
+This is just a call to `bookmark-bmenu-list', but we also check for a legacy
+bookmark file and offer to import it."
+  (interactive)
+  (let ((old-bookmarks-file (or (and (boundp 'elpher-bookmarks-file)
+                                     elpher-bookmarks-file)
+                                (locate-user-emacs-file "elpher-bookmarks"))))
+    (when (and (file-readable-p old-bookmarks-file)
+               (y-or-n-p (concat "Legacy elpher-bookmarks file \""
+                                 old-bookmarks-file
+                                 "\" found. Import now?")))
+      (elpher-bookmark-import old-bookmarks-file)
+      (rename-file old-bookmarks-file (concat old-bookmarks-file "-legacy"))))
+  (call-interactively #'bookmark-bmenu-list))
+
+
 ;;; Integrations
 ;;
 
@@ -2030,7 +2067,7 @@ When run interactively HOST-OR-URL is read from the minibuffer."
                        #'elpher-render-download
                        t)))
 
-(defun elpher-build-link-map ()
+(defun elpher--build-link-map ()
   "Build alist mapping link names to destination pages in current buffer."
   (let ((link-map nil)
         (b (next-button (point-min) t)))
@@ -2042,7 +2079,7 @@ When run interactively HOST-OR-URL is read from the minibuffer."
 (defun elpher-jump ()
   "Select a directory entry by name.  Similar to the info browser (m)enu command."
   (interactive)
-  (let* ((link-map (elpher-build-link-map)))
+  (let* ((link-map (elpher--build-link-map)))
     (if link-map
         (let ((key (let ((completion-ignore-case t))
                      (completing-read "Directory item/link: "
@@ -2149,9 +2186,9 @@ When run interactively HOST-OR-URL is read from the minibuffer."
     (define-key map (kbd "I") 'elpher-info-current)
     (define-key map (kbd "c") 'elpher-copy-link-url)
     (define-key map (kbd "C") 'elpher-copy-current-url)
-    (define-key map (kbd "a") 'elpher-set-bookmark-no-overwrite)
-    (define-key map (kbd "A") 'bookmark-set-no-overwrite)
-    (define-key map (kbd "B") 'bookmark-bmenu-list)
+    (define-key map (kbd "a") 'elpher-bookmark-link)
+    (define-key map (kbd "A") 'elpher-bookmark-current)
+    (define-key map (kbd "B") 'elpher-open-bookmarks)
     (define-key map (kbd "!") 'elpher-set-gopher-coding-system)
     (define-key map (kbd "F") 'elpher-forget-current-certificate)
     (when (fboundp 'evil-define-key*)
@@ -2181,9 +2218,9 @@ When run interactively HOST-OR-URL is read from the minibuffer."
        (kbd "I") 'elpher-info-current
        (kbd "c") 'elpher-copy-link-url
        (kbd "C") 'elpher-copy-current-url
-       (kbd "a") 'elpher-set-bookmark-no-overwrite
-       (kbd "A") 'bookmark-set-no-overwrite
-       (kbd "B") 'bookmark-bmenu-list
+       (kbd "a") 'elpher-bookmark-link
+       (kbd "A") 'elpher-bookmark-current
+       (kbd "B") 'elpher-open-bookmarks
        (kbd "!") 'elpher-set-gopher-coding-system
        (kbd "F") 'elpher-forget-current-certificate))
     map)