Improved behaviour of history-all/visited-pages.
[elpher.git] / elpher.el
index a7a3e54..014ef6b 100644 (file)
--- a/elpher.el
+++ b/elpher.el
@@ -19,7 +19,7 @@
 
 ;; Author: Tim Vaughan <plugd@thelambdalab.xyz>
 ;; Created: 11 April 2019
-;; Version: 2.11.0
+;; Version: 3.0.0
 ;; Keywords: comm gopher
 ;; Homepage: https://thelambdalab.xyz/elpher
 ;; Package-Requires: ((emacs "27.1"))
 ;;; Global constants
 ;;
 
-(defconst elpher-version "2.11.0"
+(defconst elpher-version "3.0.0"
   "Current version of elpher.")
 
 (defconst elpher-margin-width 6
     (finger elpher-get-finger-page elpher-render-text "txt" elpher-text)
     (telnet elpher-get-telnet-page nil "tel" elpher-telnet)
     (other-url elpher-get-other-url-page nil "url" elpher-other-url)
-    ((special start) elpher-get-start-page nil)
-    ((special history) elpher-get-history-page nil)
-    ((special history-all) elpher-get-history-all-page nil))
+    ((special start) elpher-get-start-page nil "E" elpher-index)
+    ((special history) elpher-get-history-page nil "E" elpher-index)
+    ((special visited-pages) elpher-get-visited-pages-page nil "E" elpher-index))
   "Association list from types to getters, renderers, margin codes and index faces.")
 
 
   "List of headings on the page.")
 
 
+;;; Declarations to avoid compiler warnings.
+;;
+
+(eval-when-compile
+  (defvar bookmark-make-record-function)
+  (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))
+
+
 ;;; Customization group
 ;;
 
@@ -227,11 +239,6 @@ some servers which do not support IPv6 can take a long time to time-out."
 Otherwise, the SOCKS proxy is only used for connections to onion services."
   :type '(boolean))
 
-(defcustom elpher-number-links nil
-  "If non-nil, number links in pages when rendering.
-Links can be followed numerically by pressing `M' and entering by the link number."
-  :type '(boolean))
-
 ;; Face customizations
 
 (defgroup elpher-faces nil
@@ -510,26 +517,28 @@ If no address is defined, returns 0.  (This is for compatibility with the URL li
   "The current page for this Elpher buffer.")
 
 (defvar elpher-history nil
-  "The local history for this Elpher buffer.
+  "The local history stack for this Elpher buffer.
 This variable is used by `elpher-back' and
 `elpher-show-history'.")
 
-(defvar elpher-history-all nil
+(defvar elpher-visited-pages nil
   "The global history for all Elpher buffers.
-This variable is used by `elpher-show-history-all'.")
+This variable is used by `elpher-show-visited-pages'.")
 
 (defun elpher-visit-page (page &optional renderer no-history)
   "Visit PAGE using its own renderer or RENDERER, if non-nil.
-Additionally, push PAGE onto the stack of previously-visited pages,
-unless NO-HISTORY is non-nil."
+Additionally, push PAGE onto the history stack and the list of
+previously-visited pages,unless NO-HISTORY is non-nil."
   (elpher-save-pos)
   (elpher-process-cleanup)
-  (elpher-reset-link-number-counter)
-  (unless (or no-history
-              (equal (elpher-page-address elpher-current-page)
-                     (elpher-page-address page)))
-    (push elpher-current-page elpher-history)
-    (push elpher-current-page elpher-history-all))
+  (unless no-history
+    (unless (equal (elpher-page-address elpher-current-page)
+                   (elpher-page-address page))
+      (push elpher-current-page elpher-history)
+      (unless (or (elpher-address-special-p (elpher-page-address page))
+                  (and elpher-visited-pages
+                       (equal page (car elpher-visited-pages))))
+        (push page elpher-visited-pages))))
   (setq-local elpher-current-page page)
   (let* ((address (elpher-page-address page))
          (type (elpher-address-type address))
@@ -1008,24 +1017,6 @@ displayed.  The _WINDOW argument is currently unused."
                                                 address
                                               (elpher-address-to-url address))))))))
 
-(defvar elpher--link-number-counter 0
-  "Used to number links on elpher pages.")
-(defun elpher-reset-link-number-counter ()
-  "Reset the link number counter."
-  (setq-local elpher--link-number-counter 0))
-
-(defun elpher--insert-text-button (label &rest properties)
-  "Insert a potentially-numbered button into the current buffer.
-The text for the button is provided by LABEL, while the button
-properties in PROPERTIES are as per `insert-text-button'."
-
-  (if elpher-number-links
-      (setq-local elpher--link-number-counter (+ elpher--link-number-counter 1)))
-  (let ((pref (if elpher-number-links
-                  (concat "[" (number-to-string elpher--link-number-counter) "] ")
-                "")))
-    (apply #'insert-text-button (cons (concat pref label) properties))))
-
 (defun elpher-insert-index-record (display-string &optional address)
   "Function to insert an index record into the current buffer.
 The contents of the record are dictated by DISPLAY-STRING and ADDRESS.
@@ -1039,12 +1030,12 @@ If ADDRESS is not supplied or nil the record is rendered as an
                (filtered-display-string (elpher-color-filter-apply display-string))
                (page (elpher-make-page filtered-display-string address)))
           (elpher-insert-margin margin-code)
-          (elpher--insert-text-button filtered-display-string
-                                      'face face
-                                      'elpher-page page
-                                      'action #'elpher-click-link
-                                      'follow-link t
-                                      'help-echo #'elpher--page-button-help))
+          (insert-text-button filtered-display-string
+                              'face face
+                              'elpher-page page
+                              'action #'elpher-click-link
+                              'follow-link t
+                              'help-echo #'elpher--page-button-help))
       (pcase type
         ('nil ;; Information
          (elpher-insert-margin)
@@ -1466,12 +1457,12 @@ treatment that a separate function is warranted."
           (let* ((face (elt type-map-entry 3))
                  (filtered-display-string (elpher-color-filter-apply display-string))
                  (page (elpher-make-page filtered-display-string address)))
-            (elpher--insert-text-button filtered-display-string
-                                        'face face
-                                        'elpher-page page
-                                        'action #'elpher-click-link
-                                        'follow-link t
-                                        'help-echo #'elpher--page-button-help))
+            (insert-text-button filtered-display-string
+                                'face face
+                                'elpher-page page
+                                'action #'elpher-click-link
+                                'follow-link t
+                                'help-echo #'elpher--page-button-help))
         (insert (propertize display-string 'face 'elpher-unknown)))
       (insert "\n"))))
 
@@ -1615,7 +1606,7 @@ The result is rendered using RENDERER."
         (browse-url url)))))
 
 
-;; Start page page retrieval
+;; Start page retrieval
 
 (defun elpher-get-start-page (renderer)
   "Getter which displays the start page (RENDERER must be nil)."
@@ -1632,16 +1623,16 @@ The result is rendered using RENDERER."
            "\n"
            " - TAB/Shift-TAB: next/prev item on current page\n"
            " - RET/mouse-1: open item under cursor\n"
-           " - m/M: select an item on current page by name (autocompletes) or number\n"
+           " - m: select an item on current page by name (autocompletes)\n"
            " - u/mouse-3/U: return to previous page or to the start page\n"
-           " - o/O: visit different selector or the root menu of the current server\n"
            " - g: go to a particular address (gopher, gemini, finger)\n"
+           " - o/O: open a different address selector or the root menu of the current server\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"
            " - B: list all bookmarks\n"
-           " - s/S: show history of current buffer or for all buffers\n"
+           " - s/S: show current history stack or all previously visted pages\n"
            " - r: redraw current page (using cached contents if available)\n"
            " - R: reload current page (regenerates cache)\n"
            " - !: set character coding system for gopher (default is to autodetect)\n"
@@ -1663,13 +1654,13 @@ The result is rendered using RENDERER."
    (insert "\n"
            "Your bookmarks are stored in your ")
    (let ((help-string "RET,mouse-1: Open Emacs bookmark list"))
-     (elpher--insert-text-button "Emacs bookmark list"
-                                'face 'link
-                                'action (lambda (_)
-                                          (interactive)
-                                          (call-interactively #'bookmark-bmenu-list))
-                                'follow-link t
-                                'help-echo help-string))
+     (insert-text-button "Emacs bookmark list"
+                         'face 'link
+                         'action (lambda (_)
+                                   (interactive)
+                                   (call-interactively #'bookmark-bmenu-list))
+                         'follow-link t
+                         'help-echo help-string))
    (insert ".\n")
    (insert (propertize
             "(Bookmarks from legacy elpher-bookmarks files will be automatically imported.)\n"
@@ -1684,13 +1675,13 @@ The result is rendered using RENDERER."
    (insert "\n"
            "** Refer to the ")
    (let ((help-string "RET,mouse-1: Open Elpher info manual (if available)"))
-     (elpher--insert-text-button "Elpher info manual"
-                                 'face 'link
-                                 'action (lambda (_)
-                                           (interactive)
-                                           (info "(elpher)"))
-                                 'follow-link t
-                                 'help-echo help-string))
+     (insert-text-button "Elpher info manual"
+                         'face 'link
+                         'action (lambda (_)
+                                   (interactive)
+                                   (info "(elpher)"))
+                         'follow-link t
+                         'help-echo help-string))
    (insert " for the full documentation. **\n")
    (insert (propertize
             (concat "  (This should be available if you have installed Elpher using\n"
@@ -1700,50 +1691,58 @@ The result is rendered using RENDERER."
 
 ;; History page retrieval
 
-(defun elpher-history ()
-  "Show the history of pages leading to the current page in this buffer.
-Use \\[elpher-history-all] to see the entire history.
+(defun elpher-show-history ()
+  "Show the current contents of elpher's history stack.
+Use \\[elpher-show-visited-pages] to see the entire history.
 This is rendered using `elpher-get-history-page' via `elpher-type-map'."
   (interactive)
   (elpher-visit-page
-   (elpher-make-page "Elpher History Page"
+   (elpher-make-page "Current History Stack"
                     (elpher-make-special-address 'history))))
 
-(defun elpher-history-all ()
+(defun elpher-show-visited-pages ()
   "Show the all the pages you've visited using Elpher.
-Use \\[elpher-history] to see just the history for the current buffer.
-This is rendered using `elpher-get-history-all-page' via `elpher-type-map'."
+Use \\[elpher-show-history] to see just the current history stack.
+This is rendered using `elpher-get-visited-pages-page' via `elpher-type-map'."
   (interactive)
   (elpher-visit-page
-   (elpher-make-page "Elpher History Of All Seen Pages"
-                    (elpher-make-special-address 'history-all))))
+   (elpher-make-page "Elpher Visted Pages"
+                    (elpher-make-special-address 'visited-pages))))
 
 (defun elpher-get-history-page (renderer)
   "Getter which displays the history page (RENDERER must be nil)."
   (when renderer
     (elpher-visit-previous-page)
     (error "Command not supported for history page"))
-  (elpher-show-history elpher-history))
+  (elpher-display-history-links elpher-history "Current history stack"))
 
-(defun elpher-get-history-all-page (renderer)
-  "Getter which displays the history page (RENDERER must be nil)."
+(defun elpher-get-visited-pages-page (renderer)
+  "Getter which displays the list of visited pages (RENDERER must be nil)."
   (when renderer
     (elpher-visit-previous-page)
     (error "Command not supported for history page"))
-  (elpher-show-history elpher-history-all))
-
-(defun elpher-show-history (pages)
-  "Show all PAGES in the Elpher buffer."
-  (elpher-with-clean-buffer
-   (insert "---- Visited link history ----\n\n")
-   (if pages
-       (dolist (page pages)
-        (when page
-           (let ((display-string (elpher-page-display-string page))
-                (address (elpher-page-address page)))
-             (elpher-insert-index-record display-string address))))
-     (insert "No history items found.\n"))
-   (insert "\n----------------------------")))
+  (elpher-display-history-links
+   (seq-filter (lambda (page)
+                 (not (elpher-address-special-p (elpher-page-address page))))
+               elpher-visited-pages)
+   "All visited pages"))
+
+(defun elpher-display-history-links (pages title)
+  "Show all PAGES in an Elpher buffer with a given TITLE."
+  (let* ((title-line (concat "---- " title " ----"))
+         (footer-line (make-string (length title-line) ?-)))
+    (elpher-with-clean-buffer
+     (insert title-line "\n\n")
+     (if pages
+         (dolist (page pages)
+          (when page
+             (let ((display-string (elpher-page-display-string page))
+                  (address (elpher-page-address page)))
+               (elpher-insert-index-record display-string address))))
+       (insert "No history items found.\n"))
+     (insert "\n" footer-line "\n"
+             "Select and entry or press 'u' to return to the previous page.")
+     (elpher-restore-pos))))
 
 
 ;;; Bookmarks
@@ -1818,11 +1817,6 @@ To bookmark the current page, use \\[bookmark-set-no-overwrite]."
 
 ;;; 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.
 
@@ -1866,7 +1860,8 @@ supports the old protocol elpher, where the link is self-contained."
                (format "%s:%s" protocol link))))
     (elpher-go url)))
 
-(with-eval-after-load 'org
+(defun elpher-org-mode-integration ()
+  "Set up `elpher' integration for `org-mode'."
   (org-link-set-parameters
    "elpher"
    :store #'elpher-org-store-link
@@ -1889,11 +1884,9 @@ supports the old protocol elpher, where the link is self-contained."
              (elpher-org-export-link link description format "finger"))
    :follow (lambda (link _arg) (elpher-org-follow-link link "finger"))))
 
-;;; Browse URL
+(add-hook 'org-mode-hook #'elpher-org-mode-integration)
 
-;; Avoid byte compilation warnings.
-(eval-when-compile
-  (defvar thing-at-point-uri-schemes))
+;;; Browse URL
 
 ;;;###autoload
 (defun elpher-browse-url-elpher (url &rest _args)
@@ -1927,13 +1920,9 @@ supports the old protocol elpher, where the link is self-contained."
 
 ;;; 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:"))
+;; Make mu4e aware of the gemini world
+(setq mu4e~view-beginning-of-url-regexp
+      "\\(?:https?\\|gopher\\|finger\\|gemini\\)://\\|mailto:")
 
 ;;; Interactive procedures
 ;;
@@ -2063,17 +2052,6 @@ When run interactively HOST-OR-URL is read from the minibuffer."
                 (goto-char (button-start b))
                 (button-activate b)))))))
 
-(defun elpher-jump-to-number (n)
-  "Jump to directory entry number N."
-  (interactive "nDirectory item/link number: ")
-  (let* ((link-map (reverse (elpher-build-link-map))))
-    (if link-map
-        (if (<= 1 n (length link-map))
-            (let ((b (cdr (elt link-map (- n 1)))))
-              (goto-char (button-start b))
-              (button-activate b))
-          (error "No link with that number")))))
-
 (defun elpher-root-dir ()
   "Visit root of current server."
   (interactive)
@@ -2155,11 +2133,11 @@ When run interactively HOST-OR-URL is read from the minibuffer."
     (define-key map (kbd "^") 'elpher-back)
     (define-key map [mouse-3] 'elpher-back)
     (define-key map (kbd "U") 'elpher-back-to-start)
-    (define-key map (kbd "O") 'elpher-root-dir)
     (define-key map (kbd "g") 'elpher-go)
     (define-key map (kbd "o") 'elpher-go-current)
-    (define-key map (kbd "s") 'elpher-history)
-    (define-key map (kbd "S") 'elpher-history-all)
+    (define-key map (kbd "O") 'elpher-root-dir)
+    (define-key map (kbd "s") 'elpher-show-history)
+    (define-key map (kbd "S") 'elpher-show-visited-pages)
     (define-key map (kbd "r") 'elpher-redraw)
     (define-key map (kbd "R") 'elpher-reload)
     (define-key map (kbd "T") 'elpher-toggle-tls)
@@ -2167,7 +2145,6 @@ When run interactively HOST-OR-URL is read from the minibuffer."
     (define-key map (kbd "d") 'elpher-download)
     (define-key map (kbd "D") 'elpher-download-current)
     (define-key map (kbd "m") 'elpher-jump)
-    (define-key map (kbd "M") 'elpher-jump-to-number)
     (define-key map (kbd "i") 'elpher-info-link)
     (define-key map (kbd "I") 'elpher-info-current)
     (define-key map (kbd "c") 'elpher-copy-link-url)
@@ -2188,8 +2165,11 @@ When run interactively HOST-OR-URL is read from the minibuffer."
        (kbd "^") 'elpher-back
        [mouse-3] 'elpher-back
        (kbd "U") 'elpher-back-to-start
-       (kbd "o") 'elpher-go
-       (kbd "O") 'elpher-go-current
+       (kbd "g") 'elpher-go
+       (kbd "o") 'elpher-go-current
+       (kbd "O") 'elpher-root-dir
+       (kbd "s") 'elpher-show-history
+       (kbd "S") 'elpher-show-visited-pages
        (kbd "r") 'elpher-redraw
        (kbd "R") 'elpher-reload
        (kbd "T") 'elpher-toggle-tls
@@ -2197,7 +2177,6 @@ When run interactively HOST-OR-URL is read from the minibuffer."
        (kbd "d") 'elpher-download
        (kbd "D") 'elpher-download-current
        (kbd "m") 'elpher-jump
-       (kbd "M") 'elpher-jump-to-number
        (kbd "i") 'elpher-info-link
        (kbd "I") 'elpher-info-current
        (kbd "c") 'elpher-copy-link-url
@@ -2221,7 +2200,7 @@ 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 #'elpher--gemini-page-headings))
+  (setq-local imenu-create-index-function (lambda () elpher--gemini-page-headings)))
 
 (when (fboundp 'evil-set-initial-state)
   (evil-set-initial-state 'elpher-mode 'motion))