Merge branch 'master' into bookmarks-history
authorTim Vaughan <plugd@thelambdalab.xyz>
Wed, 1 Jul 2020 19:21:53 +0000 (21:21 +0200)
committerTim Vaughan <plugd@thelambdalab.xyz>
Wed, 1 Jul 2020 19:21:53 +0000 (21:21 +0200)
1  2 
elpher.el

diff --combined elpher.el
+++ b/elpher.el
@@@ -4,7 -4,7 +4,7 @@@
  
  ;; Author: Tim Vaughan <plugd@thelambdalab.xyz>
  ;; Created: 11 April 2019
- ;; Version: 2.9.1
+ ;; Version: 2.10.0
  ;; Keywords: comm gopher
  ;; Homepage: http://thelambdalab.xyz/elpher
  ;; Package-Requires: ((emacs "26.2"))
@@@ -71,7 -71,7 +71,7 @@@
  ;;; Global constants
  ;;
  
- (defconst elpher-version "2.9.1"
+ (defconst elpher-version "2.10.0"
    "Current version of elpher.")
  
  (defconst elpher-margin-width 6
@@@ -209,7 -209,7 +209,7 @@@ some servers which do not support IPv6 
    "Face used for html type directory records.")
  
  (defface elpher-gemini
-   '((t :inherit font-lock-regexp-grouping-backslash))
+   '((t :inherit font-lock-constant-face))
    "Face used for Gemini type directory records.")
  
  (defface elpher-other-url
                  (setf (url-host url) (url-filename url))
                  (setf (url-filename url) ""))
                (when (or (equal (url-filename url) "")
 -                        (equal (url-filename url) "/"))
 -                (setf (url-filename url) "/1")))
 +                        (equal (url-filename url) "/1"))
 +                (setf (url-filename url) "/")))
              (when (equal "gemini" (url-type url))
                ;; Gemini defaults
                (if (equal (url-filename url) "")
@@@ -510,7 -510,7 +510,7 @@@ unless NO-HISTORY is non-nil.
                                            '("gophers" "gemini")))
                               " [TLS encryption]"
                             ""))
 -             (header (concat display-string
 +             (header (concat (replace-regexp-in-string "%" "%%" display-string)
                               (propertize tls-string 'face 'bold))))
          (setq header-line-format header))))
  
@@@ -763,8 -763,8 +763,8 @@@ longer needed for this session.
           (cert-file (concat temporary-file-directory file-base ".crt")))
      (elpher-generate-certificate file-base key-file cert-file t)))
  
- (defun elpher-generate-permanent-certificate (file-base common-name)
-   "Generate and return details of a persistant certificate.
+ (defun elpher-generate-persistent-certificate (file-base common-name)
+   "Generate and return details of a persistent certificate.
  The argument FILE-BASE is used as the base for the key and certificate
  files, while COMMON-NAME specifies the common name field of the
  certificate.
@@@ -803,7 -803,7 +803,7 @@@ base for the installed key and certific
            (expand-file-name cert-file))))
  
  (defun elpher-list-existing-certificates ()
-   "Return a list of the persistant certificates in `elpher-certificate-directory'."
+   "Return a list of the persistent certificates in `elpher-certificate-directory'."
    (mapcar
     (lambda (file)
       (file-name-sans-extension file))
@@@ -1190,13 -1190,13 +1190,13 @@@ that the response was malformed.
      (pcase (read-answer "What do you want to do? "
                          '(("throwaway" ?t
                             "generate and use throw-away certificate")
-                           ("persistant" ?p
-                            "generate new or use existing persistant certificate")
+                           ("persistent" ?p
+                            "generate new or use existing persistent certificate")
                            ("abort" ?a
                             "stop immediately")))
        ("throwaway"
         (setq elpher-client-certificate (elpher-generate-throwaway-certificate)))
-       ("persistant"
+       ("persistent"
         (let* ((existing-certificates (elpher-list-existing-certificates))
                (file-base (completing-read
                            "Nickname for new or existing certificate (autocompletes, empty response aborts): "
                                                  file-base)))
                    (message "New key and self-signed certificate written to %s"
                             elpher-certificate-directory)
-                   (elpher-generate-permanent-certificate file-base common-name)))
+                   (elpher-generate-persistent-certificate file-base common-name)))
                 ("install"
                  (let* ((cert-file (read-file-name "Certificate file: " nil nil t))
                         (key-file (read-file-name "Key file: " nil nil t)))
@@@ -1553,101 -1553,22 +1553,101 @@@ The result is rendered using RENDERER.
              'face 'shadow))
     (elpher-restore-pos)))
  
 +
  ;; Bookmarks page page retrieval
  
 +(defvar elpher-bookmark-group-status (make-hash-table :test 'equal))
 +
 +(defun elpher-make-bookmark-group-status (group-path)
 +  (let ((existing (gethash group-path elpher-bookmark-group-status)))
 +    (unless existing
 +      (puthash group-path (list nil nil nil) elpher-bookmark-group-status))))
 +
 +(defun elpher-bookmark-group-start (group-path)
 +    (car (gethash group-path elpher-bookmark-group-status)))
 +
 +(defun elpher-bookmark-group-end (group-path)
 +    (cadr (gethash group-path elpher-bookmark-group-status)))
 +
 +(defun elpher-bookmark-group-expanded-p (group-path)
 +    (caddr (gethash group-path elpher-bookmark-group-status)))
 +
 +(defun elpher-bookmark-group-expanded-toggle (group-path)
 +  (setcar (cddr (gethash group-path elpher-bookmark-group-status))
 +          (not (elpher-bookmark-group-expanded-p group-path))))
 +
 +(defun elpher--bookmark-indent (group-path)
 +  (insert (make-string (+ 1 (* 2 (length group-path))) ?\s)))
 +
 +(defun elpher--bookmark-group-starts-here (group-path)
 +  (setcar (gethash group-path elpher-bookmark-group-status) (point)))
 +
 +(defun elpher--bookmark-group-ends-here (group-path)
 +  (setcar (cdr (gethash group-path elpher-bookmark-group-status)) (point)))
 +
 +(defun elpher--update-bookmark-group-visibility (group-path)
 +  (let ((start (elpher-bookmark-group-start group-path))
 +        (end (elpher-bookmark-group-end group-path))
 +        (inhibit-read-only t))
 +    (put-text-property start end 'invisible
 +                       (not (elpher-bookmark-group-expanded-p group-path)))))
 +
 +(defun elpher--expand-or-collapse-bookmark-group (button)
 +  (let ((group-path (button-get button 'elpher-bookmark-group-path)))
 +    (elpher-bookmark-group-expanded-toggle group-path)
 +    (elpher--update-bookmark-group-visibility group-path)))
 +    
 +(defun elpher--insert-bookmark (bookmark &optional group-path)
 +  (let* ((display-string (elpher-bookmark-display-string bookmark))
 +         (address (elpher-address-from-url (elpher-bookmark-url bookmark)))
 +         (type (if address (elpher-address-type address) nil))
 +         (type-map-entry (cdr (assoc type elpher-type-map))))
 +    (if type-map-entry
 +        (let* ((face (elt type-map-entry 3))
 +               (filtered-display-string (ansi-color-filter-apply display-string))
 +               (page (elpher-make-page filtered-display-string address)))
 +          (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")))
 +
 +(defun elpher--insert-bookmark-group (group-entries &optional group-path)
 +  (if group-entries
 +      (dolist (entry group-entries)
 +        (elpher--bookmark-indent group-path)
 +        (if (elpher-bookmark-p entry)
 +            (elpher--insert-bookmark entry group-path)
 +          (let* ((subgroup-name (elpher-bookmark-group-name entry))
 +                 (subgroup-entries (elpher-bookmark-group-entries entry))
 +                 (subgroup-path (cons subgroup-name group-path)))
 +            (elpher-make-bookmark-group-status subgroup-path)
 +            (insert-text-button subgroup-name
 +                                'action #'elpher--expand-or-collapse-bookmark-group
 +                                'elpher-bookmark-group-path subgroup-path
 +                                'follow-link t
 +                                'help-echo "Expand or collapse group.")
 +            (insert "\n")
 +            (elpher--bookmark-group-starts-here subgroup-path)
 +            (elpher--insert-bookmark-group subgroup-entries subgroup-path)
 +            (elpher--bookmark-group-ends-here subgroup-path)
 +            (elpher--update-bookmark-group-visibility subgroup-path))))
 +    (elpher--bookmark-indent group-path)
 +    (insert "No bookmarks found.\n")))
 +
  (defun elpher-get-bookmarks-page (renderer)
    "Getter to load and display the current bookmark list (RENDERER must be nil)."
    (when renderer
      (elpher-visit-previous-page)
      (error "Command not supported for bookmarks page"))
    (elpher-with-clean-buffer
 +   ;; (setq buffer-invisibility-spec '((expanded . t)))
     (insert "---- Bookmark list ----\n\n")
     (let ((bookmarks (elpher-load-bookmarks)))
 -     (if bookmarks
 -         (dolist (bookmark bookmarks)
 -           (let ((display-string (elpher-bookmark-display-string bookmark))
 -                 (address (elpher-address-from-url (elpher-bookmark-url bookmark))))
 -             (elpher-insert-index-record display-string address)))
 -       (insert "No bookmarks found.\n")))
 +     (elpher--insert-bookmark-group bookmarks))
     (insert "\n-----------------------\n"
             "\n"
             "- u: return to previous page\n"
                           'follow-link t
                           'help-echo help-string))
     (insert "\n")
 +   (insert "\n--- Recently visited ---\n\n")
 +   (maphash (lambda (address _)
 +              (unless (elpher-address-special-p address)
 +                (let ((url (elpher-address-to-url address)))
 +                  (elpher--insert-bookmark (elpher-make-bookmark url url)))))
 +            elpher-content-cache)
 +   (insert "\n------------------------\n")
     (elpher-restore-pos)))
    
  
  DISPLAY-STRING determines how the bookmark will appear in the
  bookmark list, while URL is the url of the entry."
    (list display-string url))
 -  
 +
  (defun elpher-bookmark-display-string (bookmark)
    "Get the display string of BOOKMARK."
    (elt bookmark 0))
    "Get the address for BOOKMARK."
    (elt bookmark 1))
  
 +(defun elpher-bookmark-p (entry)
 +  "Determine if entry describes a bookmark.
 +Otherwise, it will be treated as a bookmark group."
 +  (and (listp entry)
 +       (= (length entry) 2)
 +       (stringp (cadr entry))))
 +
 +(defun elpher-make-bookmark-group (group-name &optional bookmarks)
 +  "Make an elpher bookmark group.
 +GROUP-NAME is the name of the group.  If non-nil, BOOKMARKS is a
 +list of one or more bookmarks or subgroups to appear within this
 +group."
 +  (cons diplay-string bookmarks))
 +
 +(defun elpher-bookmark-group-name (group)
 +  "Returns the name of bookmark group GROUP."
 +  (car group))
 +
 +(defun elpher-bookmark-group-entries (group)
 +  "Returns the entries held in bookmark group GROUP."
 +  (cdr group))
 +
  (defun elpher-save-bookmarks (bookmarks)
    "Record the bookmark list BOOKMARKS to the user's bookmark file.
  Beware that this completely replaces the existing contents of the file."
               (insert-file-contents elpher-bookmarks-file)
               (goto-char (point-min))
               (read (current-buffer))))))
 -    (if (and bookmarks (listp (cadar bookmarks)))
 -        (progn
 -          (message "Reading old bookmark file. (Will be updated on write.)")
 -          (mapcar (lambda (old-bm)
 -                    (list (car old-bm)
 -                          (elpher-address-to-url (apply #'elpher-make-gopher-address
 -                                                        (cadr old-bm)))))
 -                  bookmarks))
 -      bookmarks)))
 +    ;; (if (and bookmarks (listp (cadar bookmarks)))
 +    ;;     (progn
 +    ;;       (message "Reading old bookmark file. (Will be updated on write.)")
 +    ;;       (mapcar (lambda (old-bm)
 +    ;;                 (list (car old-bm)
 +    ;;                       (elpher-address-to-url (apply #'elpher-make-gopher-address
 +    ;;                                                     (cadr old-bm)))))
 +    ;;               bookmarks))
 +      bookmarks))
  
  (defun elpher-add-address-bookmark (address display-string)
    "Save a bookmark for ADDRESS with label DISPLAY-STRING.)))
@@@ -1772,18 -1664,12 +1772,18 @@@ If ADDRESS is already bookmarked, updat
  (defun elpher-next-link ()
    "Move point to the next link on the current page."
    (interactive)
 -  (forward-button 1))
 +  (while
 +      (let ((next-button (forward-button 1)))
 +        (or (not next-button)
 +            (button-get next-button 'invisible)))))
  
  (defun elpher-prev-link ()
    "Move point to the previous link on the current page."
    (interactive)
 -  (backward-button 1))
 +  (while
 +      (let ((prev-button (backward-button 1)))
 +        (or (not prev-button)
 +            (button-get prev-button 'invisible)))))
  
  (defun elpher-follow-current-link ()
    "Open the link or url at point."
@@@ -1995,10 -1881,7 +1995,10 @@@ When run interactively HOST-OR-URL is r
    (interactive)
    (let ((button (button-at (point))))
      (if button
 -        (elpher-info-page (button-get button 'elpher-page))
 +        (let ((page (button-get button 'elpher-page)))
 +          (if page
 +              (elpher-info-page (button-get button 'elpher-page))
 +            (error "Not an Elpher page")))
        (error "No item selected"))))
    
  (defun elpher-info-current ()