X-Git-Url: https://thelambdalab.xyz/gitweb/index.cgi?p=elpher.git;a=blobdiff_plain;f=elpher.el;h=1dfdded8b0f75f5a9e360601f3baf65c4740e85e;hp=39e32a2d4a3e06a9443b5a6634411338892ef15c;hb=8eb8d6707f84064d3a3cd2947ca04fe17fc3f22e;hpb=6d80df84e2c13ffad263ec444ac724e7ec17735c diff --git a/elpher.el b/elpher.el index 39e32a2..1dfdded 100644 --- a/elpher.el +++ b/elpher.el @@ -20,7 +20,7 @@ ;; Author: Tim Vaughan ;; Created: 11 April 2019 -;; Version: 3.1.0 +;; Version: 3.2.2 ;; Keywords: comm gopher ;; Homepage: https://thelambdalab.xyz/elpher ;; Package-Requires: ((emacs "27.1")) @@ -85,7 +85,7 @@ ;;; Global constants ;; -(defconst elpher-version "3.1.0" +(defconst elpher-version "3.2.2" "Current version of elpher.") (defconst elpher-margin-width 6 @@ -110,10 +110,10 @@ (telnet elpher-get-telnet-page nil "tel" elpher-telnet) (other-url elpher-get-other-url-page nil "url" elpher-other-url) (file elpher-get-file-page nil "~" elpher-gemini) - ((special welcome) elpher-get-welcome-page nil "E" elpher-index) - ((special bookmarks) elpher-get-bookmarks-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)) + ((about welcome) elpher-get-welcome-page nil "E" elpher-index) + ((about bookmarks) elpher-get-bookmarks-page nil "E" elpher-index) + ((about history) elpher-get-history-page nil "E" elpher-index) + ((about visited-pages) elpher-get-visited-pages-page nil "E" elpher-index)) "Association list from types to getters, renderers, margin codes and index faces.") @@ -225,10 +225,16 @@ Otherwise, \\[elpher-show-bookmarks] will visit a special elpher bookmark page within which all of the standard elpher keybindings are active." :type '(boolean)) -(defcustom elpher-start-page "about:welcome" +(defcustom elpher-start-page-url "about:welcome" "Specify the page displayed initially by elpher. -The default welcome screen \"about:welcome\", while the bookmarks list -is \"about:bookmarks\". You can also specify local files via \"file:\".") +The default welcome screen is \"about:welcome\", while the bookmarks list +is \"about:bookmarks\". You can also specify local files via \"file:\". + +Beware that using \"about:bookmarks\" as a start page in combination with +the `elpher-use-bookmark-menu' variable set to non-nil will prevent the +Emacs bookmark menu being accessible via \\[elpher-show-bookmarks] from +the start page." + :type '(string)) ;; Face customizations @@ -314,7 +320,7 @@ is \"about:bookmarks\". You can also specify local files via \"file:\".") ;; Address ;; An elpher "address" object is either a url object or a symbol. -;; Symbol addresses are "special", corresponding to pages generated +;; Addresses with the "about" type, corresponding to pages generated ;; dynamically for and by elpher. All others represent pages which ;; rely on content retrieved over the network. @@ -334,6 +340,8 @@ is \"about:bookmarks\". You can also specify local files via \"file:\".") (if (cdr p) (concat "/" (mapconcat #'identity (cdr p) "/")) "")))) + (when (url-host url) + (setf (url-host url) (puny-encode-domain (url-host url)))) (when (or (equal "gopher" (url-type url)) (equal "gophers" (url-type url))) ;; Gopher defaults @@ -351,7 +359,7 @@ is \"about:bookmarks\". You can also specify local files via \"file:\".") "Remove redundant port specifiers from ADDRESS. Here 'redundant' means that the specified port matches the default for that protocol, eg 70 for gopher." - (if (and (not (elpher-address-special-p address)) + (if (and (not (elpher-address-about-p address)) (eq (url-portspec address) ; (url-port) is too slow! (pcase (url-type address) ("gemini" 1965) @@ -384,49 +392,48 @@ requiring gopher-over-TLS." "/" (string type) selector))))) -(defun elpher-make-special-address (type) - "Create an ADDRESS object corresponding to the given special address symbol TYPE." +(defun elpher-make-about-address (type) + "Create an ADDRESS object corresponding to the given about address TYPE." (elpher-address-from-url (concat "about:" (symbol-name type)))) (defun elpher-address-to-url (address) - "Get string representation of ADDRESS, or nil if ADDRESS is special." + "Get string representation of ADDRESS." (url-encode-url (url-recreate-url address))) (defun elpher-address-type (address) "Retrieve type of ADDRESS object. This is used to determine how to retrieve and render the document the address refers to, via the table `elpher-type-map'." - (let ((protocol (url-type address))) - (pcase (url-type address) - ("about" - (list 'special (intern (url-filename address)))) - ((or "gopher" "gophers") - (list 'gopher - (if (member (url-filename address) '("" "/")) - ?1 - (string-to-char (substring (url-filename address) 1))))) - ("gemini" 'gemini) - ("telnet" 'telnet) - ("finger" 'finger) - ("file" 'file) - (_ 'other-url)))) - -(defun elpher-address-special-p (address) - "Return non-nil if ADDRESS is a special address." - (pcase (elpher-address-type address) (`(special ,subtype) t))) + (pcase (url-type address) + ("about" + (list 'about (intern (url-filename address)))) + ((or "gopher" "gophers") + (list 'gopher + (if (member (url-filename address) '("" "/")) + ?1 + (string-to-char (substring (url-filename address) 1))))) + ("gemini" 'gemini) + ("telnet" 'telnet) + ("finger" 'finger) + ("file" 'file) + (_ 'other-url))) + +(defun elpher-address-about-p (address) + "Return non-nil if ADDRESS is an about address." + (pcase (elpher-address-type address) (`(about ,_) t))) + +(defun elpher-address-gopher-p (address) + "Return non-nill if ADDRESS object is a gopher address." + (eq 'gopher (elpher-address-type address))) (defun elpher-address-protocol (address) - "Retrieve the transport protocol for ADDRESS. This is nil for special addresses." - (if (elpher-address-special-p address) - nil - (url-type address))) + "Retrieve the transport protocol for ADDRESS." + (url-type address)) (defun elpher-address-filename (address) "Retrieve the filename component of ADDRESS. For gopher addresses this is a combination of the selector type and selector." - (if (symbolp address) - nil - (url-unhex-string (url-filename address)))) + (url-unhex-string (url-filename address))) (defun elpher-address-host (address) "Retrieve host from ADDRESS object." @@ -439,13 +446,7 @@ 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 (elpher-address-special-p address) - 0 - (url-port address))) - -(defun elpher-address-gopher-p (address) - "Return non-nill if ADDRESS object is a gopher address." - (eq 'gopher (elpher-address-type address))) + (url-port address)) (defun elpher-gopher-address-selector (address) "Retrieve gopher selector from ADDRESS object." @@ -483,9 +484,9 @@ If no address is defined, returns 0. (This is for compatibility with the URL li (list display-string address)) (defun elpher-make-start-page () - "Create the welcome page." + "Create the start page." (elpher-make-page "Start Page" - (elpher-address-from-url elpher-start-page))) + (elpher-address-from-url elpher-start-page-url))) (defun elpher-page-display-string (page) "Retrieve the display string corresponding to PAGE." @@ -499,6 +500,29 @@ If no address is defined, returns 0. (This is for compatibility with the URL li "Set the address corresponding to PAGE to NEW-ADDRESS." (setcar (cdr page) new-address)) +(defun elpher-page-from-url (url) + "Create a page with address and display string defined by URL. +The URL is unhexed prior to its use as a display string to improve +readability." + (elpher-make-page (elpher-url-to-iri url) + (elpher-address-from-url url))) + +(defun elpher-url-to-iri (url) + "Return an IRI for URL. +Decode percent-escapes and handle punycode in the domain name. +Drop the password, if any." + (let ((data (match-data))) ; Prevent parsing clobbering match data + (unwind-protect + (let* ((address (elpher-address-from-url (elpher-decode (url-unhex-string url)))) + (host (url-host address)) + (pass (url-password address))) + (when host + (setf (url-host address) (puny-decode-domain host))) + (when pass ; RFC 3986 says we should not render + (setf (url-password address) nil)) ; the password as clear text + (url-recreate-url address)) + (set-match-data data)))) + (defvar elpher-current-page nil "The current page for this Elpher buffer.") @@ -518,10 +542,11 @@ previously-visited pages,unless NO-HISTORY is non-nil." (elpher-save-pos) (elpher-process-cleanup) (unless no-history - (unless (equal (elpher-page-address elpher-current-page) - (elpher-page-address page)) + (unless (or (not elpher-current-page) + (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)) + (unless (or (elpher-address-about-p (elpher-page-address page)) (and elpher-visited-pages (equal page (car elpher-visited-pages)))) (push page elpher-visited-pages)))) @@ -545,10 +570,9 @@ previously-visited pages,unless NO-HISTORY is non-nil." (defun elpher-visit-previous-page () "Visit the previous page in the history." - (let ((previous-page (pop elpher-history))) - (if previous-page - (elpher-visit-page previous-page nil t) - (error "No previous page")))) + (if elpher-history + (elpher-visit-page (pop elpher-history) nil t) + (error "No previous page"))) (defun elpher-reload-current-page () "Reload the current page, discarding any existing cached content." @@ -576,15 +600,16 @@ previously-visited pages,unless NO-HISTORY is non-nil." (defun elpher-update-header () "If `elpher-use-header' is true, display current page info in window header." - (if elpher-use-header + (if (and elpher-use-header elpher-current-page) (let* ((display-string (elpher-page-display-string elpher-current-page)) + (sanitized-display-string (replace-regexp-in-string "%" "%%" display-string)) (address (elpher-page-address elpher-current-page)) - (tls-string (if (and (not (elpher-address-special-p address)) + (tls-string (if (and (not (elpher-address-about-p address)) (member (elpher-address-protocol address) '("gophers" "gemini"))) " [TLS encryption]" "")) - (header (concat display-string + (header (concat sanitized-display-string (propertize tls-string 'face 'bold)))) (setq header-line-format header)))) @@ -651,8 +676,7 @@ away CRs and any terminating period." (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))))) + (let ((page (elpher-page-from-url (substring-no-properties (match-string 0))))) (make-text-button (match-beginning 0) (match-end 0) 'elpher-page page @@ -774,7 +798,7 @@ the host operating system and the local network capabilities.)" (elpher-process-cleanup) (cond ; Try again with IPv4 - ((not (or force-ipv4 socks)) + ((not (or elpher-ipv4-always force-ipv4 socks)) (message "Connection timed out. Retrying with IPv4.") (elpher-get-host-response address default-port query-string @@ -795,7 +819,9 @@ the host operating system and the local network capabilities.)" (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) + :family (and (or force-ipv4 + elpher-ipv4-always) + 'ipv4) :service service :buffer nil :nowait t @@ -1023,25 +1049,6 @@ once they are retrieved from the gopher server." ;; Index rendering -(defun elpher-insert-index (string) - "Insert the index corresponding to STRING into the current buffer." - ;; Should be able to split directly on CRLF, but some non-conformant - ;; LF-only servers sadly exist, hence the following. - (let ((str-processed (elpher-preprocess-text-response string))) - (dolist (line (split-string str-processed "\n")) - (ignore-errors - (unless (= (length line) 0) - (let* ((type (elt line 0)) - (fields (split-string (substring line 1) "\t")) - (display-string (elt fields 0)) - (selector (elt fields 1)) - (host (elt fields 2)) - (port (if (elt fields 3) - (string-to-number (elt fields 3)) - nil)) - (address (elpher-make-gopher-address type selector host port))) - (elpher-insert-index-record display-string address))))))) - (defun elpher-insert-margin (&optional type-name) "Insert index margin, optionally containing the TYPE-NAME, into the current buffer." (if type-name @@ -1064,9 +1071,7 @@ displayed. The _WINDOW argument is currently unused." (when button (let* ((page (button-get button 'elpher-page)) (address (elpher-page-address page))) - (format "mouse-1, RET: open '%s'" (if (elpher-address-special-p address) - address - (elpher-address-to-url address)))))))) + (format "mouse-1, RET: open '%s'" (elpher-address-to-url address))))))) (defun elpher-insert-index-record (display-string &optional address) "Function to insert an index record into the current buffer. @@ -1109,7 +1114,20 @@ If ADDRESS is not supplied or nil the record is rendered as an (elpher-with-clean-buffer (if (not data) t - (elpher-insert-index data) + (let ((data-processed (elpher-preprocess-text-response data))) + (dolist (line (split-string data-processed "\n")) + (ignore-errors + (unless (= (length line) 0) + (let* ((type (elt line 0)) + (fields (split-string (substring line 1) "\t")) + (display-string (elt fields 0)) + (selector (elt fields 1)) + (host (elt fields 2)) + (port (if (elt fields 3) + (string-to-number (elt fields 3)) + nil)) + (address (elpher-make-gopher-address type selector host port))) + (elpher-insert-index-record display-string address)))))) (elpher-cache-content (elpher-page-address elpher-current-page) (buffer-string))))) @@ -1137,8 +1155,8 @@ If ADDRESS is not supplied or nil the record is rendered as an nil t)) (window (get-buffer-window elpher-buffer-name))) (when window - (setf (image-property image :max-width) (window-pixel-width window)) - (setf (image-property image :max-height) (window-pixel-height window))) + (setf (image-property image :max-width) (window-body-width window t)) + (setf (image-property image :max-height) (window-body-height window t))) (elpher-with-clean-buffer (insert-image image) (elpher-restore-pos))) @@ -1429,17 +1447,20 @@ Returns the url portion in the event that the display-string portion is empty." rest)))) (defun elpher-collapse-dot-sequences (filename) - "Collapse dot sequences in FILENAME. -For instance, the filename /a/b/../c/./d will reduce to /a/c/d" - (let* ((path (split-string filename "/")) + "Collapse dot sequences in the (absolute) FILENAME. +For instance, the filename \"/a/b/../c/./d\" will reduce to \"/a/c/d\"" + (let* ((path (split-string filename "/" t)) + (is-directory (string-match-p (rx (: (or "." ".." "/") line-end)) filename)) (path-reversed-normalized (seq-reduce (lambda (a b) - (cond ((and a (equal b "..") (cdr a))) - ((and (not a) (equal b "..")) a) ;leading .. are dropped + (cond ((equal b "..") (cdr a)) ((equal b ".") a) (t (cons b a)))) - path nil))) - (string-join (reverse path-reversed-normalized) "/"))) + path nil)) + (path-normalized (reverse path-reversed-normalized))) + (if path-normalized + (concat "/" (string-join path-normalized "/") (and is-directory "/")) + "/"))) (defun elpher-address-from-gemini-url (url) "Extract address from URL with defaults as per gemini map files. @@ -1459,8 +1480,10 @@ treatment that a separate function is warranted." (setf (url-filename address) (concat (file-name-directory (url-filename current-address)) (url-filename address))))) + (when (url-host address) + (setf (url-host address) (puny-encode-domain (url-host address)))) (unless (url-type address) - (setf (url-type address) "gemini")) + (setf (url-type address) (url-type current-address))) (when (equal (url-type address) "gemini") (setf (url-filename address) (elpher-collapse-dot-sequences (url-filename address))))) @@ -1511,15 +1534,21 @@ by HEADER-LINE." elpher--gemini-page-headings)) (unless (display-graphic-p) (insert (make-string level ?#) " ")) - (insert (propertize header 'face face)) + (insert (propertize header 'face face 'rear-nonsticky t)) (newline)))) (defun elpher-gemini-insert-text (text-line) "Insert a plain non-preformatted TEXT-LINE into a text/gemini document. This function uses Emacs' auto-fill to wrap text sensibly to a maximum width defined by `elpher-gemini-max-fill-width'." - (string-match "\\(^[ \t]*\\)\\(\\*[ \t]+\\|>[ \t]*\\)?" text-line) - (let* ((line-prefix (match-string 2 text-line)) + (string-match + (rx (: line-start + (* (any " \t")) + (optional + (group (or (: "*" (+ (any " \t"))) + (: ">" (* (any " \t")))))))) + text-line) + (let* ((line-prefix (match-string 1 text-line)) (processed-text-line (if line-prefix (cond ((string-prefix-p "*" line-prefix) @@ -1535,8 +1564,8 @@ width defined by `elpher-gemini-max-fill-width'." (adaptive-fill-mode t) ;; fill-prefix is important for adaptive-fill-mode: without ;; it, multi-line list items are not indented correct - (fill-prefix (if (match-string 2 text-line) - (replace-regexp-in-string "[>\*]" " " (match-string 0 text-line)) + (fill-prefix (if (match-string 1 text-line) + (make-string (length (match-string 0 text-line)) ?\s) nil))) (insert (elpher-process-text-for-display processed-text-line)) (newline))) @@ -1632,7 +1661,8 @@ The result is rendered using RENDERER." ;; File page (defun elpher-get-file-page (renderer) - "Getter which retrieves the contents of a local file and renders it using RENDERER." + "Getter which renders a local file using RENDERER. +Assumes UTF-8 encoding for all text files." (let* ((address (elpher-page-address elpher-current-page)) (filename (elpher-address-filename address))) (unless (file-exists-p filename) @@ -1641,23 +1671,25 @@ The result is rendered using RENDERER." (unless (file-readable-p filename) (elpher-visit-previous-page) (error "Could not read from file")) - (funcall - (if renderer - renderer - (pcase (file-name-extension filename) - ((or "gmi" "gemini") #'elpher-render-gemini-map) - ((or "htm" "html") #'elpher-render-html) - ((or "jpg" "jpeg" "gif" "png" "bmp" "tif" "tiff") - #'elpher-render-image) - ((or "txt" "") #'elpher-render-text) - (t - #'elpher-render-download))) - (with-temp-buffer + (let ((body (with-temp-buffer (let ((coding-system-for-read 'binary) (coding-system-for-write 'binary)) (insert-file-contents-literally filename) - (string-as-unibyte (buffer-string)))) - nil))) + (encode-coding-string (buffer-string) 'raw-text))))) + (if renderer + (funcall renderer body nil) + (pcase (file-name-extension filename) + ((or "gmi" "gemini") + (elpher-render-gemini-map (decode-coding-string body 'utf-8) nil)) + ((or "htm" "html") + (elpher-render-html (decode-coding-string body 'utf-8))) + ((or "txt" "") + (elpher-render-text (decode-coding-string body 'utf-8))) + ((or "jpg" "jpeg" "gif" "png" "bmp" "tif" "tiff") + (elpher-render-image body)) + (_ + (elpher-render-download body)))) + (elpher-restore-pos)))) ;; Welcome page retrieval @@ -1710,11 +1742,12 @@ The result is rendered using RENDERER." (let ((help-string "RET,mouse-1: Open bookmark list")) (insert-text-button "bookmark list" 'face 'link - 'action (lambda (_) - (interactive) - (call-interactively #'elpher-show-bookmarks)) + 'action #'elpher-click-link 'follow-link t - 'help-echo help-string)) + 'help-echo #'elpher--page-button-help + 'elpher-page + (elpher-make-page "Elpher Bookmarks" + (elpher-make-about-address 'bookmarks)))) (insert ".\n") (insert (propertize "(Bookmarks from legacy elpher-bookmarks files will be automatically imported.)\n" @@ -1761,7 +1794,7 @@ This is rendered using `elpher-get-history-page' via `elpher-type-map'." (interactive) (elpher-visit-page (elpher-make-page "Current History Stack" - (elpher-make-special-address 'history)))) + (elpher-make-about-address 'history)))) (defun elpher-show-visited-pages () "Show the all the pages you've visited using Elpher. @@ -1770,7 +1803,7 @@ This is rendered using `elpher-get-visited-pages-page' via `elpher-type-map'." (interactive) (elpher-visit-page (elpher-make-page "Elpher Visted Pages" - (elpher-make-special-address 'visited-pages)))) + (elpher-make-about-address 'visited-pages)))) (defun elpher-get-history-page (renderer) "Getter which displays the history page (RENDERER must be nil)." @@ -1786,7 +1819,7 @@ This is rendered using `elpher-get-visited-pages-page' via `elpher-type-map'." (error "Command not supported for history page")) (elpher-display-history-links (seq-filter (lambda (page) - (not (elpher-address-special-p (elpher-page-address page)))) + (not (elpher-address-about-p (elpher-page-address page)))) elpher-visited-pages) "All visited pages")) @@ -1827,20 +1860,22 @@ If `elpher-bookmark-link' is non-nil and point is on a link button, return a bookmark record for that link. Otherwise, return a bookmark record for the current elpher page." (let* ((button (and elpher-bookmark-link (button-at (point)))) - (page (if button - (button-get button 'elpher-page) - elpher-current-page)) - (address (elpher-page-address page)) - (url (elpher-address-to-url address)) - (display-string (elpher-page-display-string page)) - (pos (if button nil (point)))) - (if (elpher-address-special-p address) - (error "Cannot bookmark %s" display-string) - `(,display-string - (defaults . (,display-string)) - (position . ,pos) - (location . ,url) - (handler . elpher-bookmark-jump))))) + (page (if button + (button-get button 'elpher-page) + elpher-current-page))) + (unless page + (error "Cannot bookmark this link")) + (let* ((address (elpher-page-address page)) + (url (elpher-address-to-url address)) + (display-string (elpher-page-display-string page)) + (pos (if button nil (point)))) + (if (elpher-address-about-p address) + (error "Cannot bookmark %s" display-string) + `(,display-string + (defaults . (,display-string)) + (position . ,pos) + (location . ,url) + (handler . elpher-bookmark-jump)))))) ;;;###autoload (defun elpher-bookmark-jump (bookmark) @@ -1851,8 +1886,7 @@ 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))) + (page (elpher-page-from-url cleaned-url))) (elpher-with-clean-buffer (elpher-visit-page page)) (set-buffer (get-buffer elpher-buffer-name)) @@ -1891,38 +1925,11 @@ To bookmark the link at point use \\[elpher-bookmark-link]." (bookmark-save)) (defun elpher-get-bookmarks-page (renderer) - "Getter which displays the history page (RENDERER must be nil)." + "Getter which displays the bookmarks (RENDERER must be nil)." (when renderer (elpher-visit-previous-page) (error "Command not supported for bookmarks page")) - (elpher-with-clean-buffer - (insert " ---- Elpher Bookmarks ---- \n\n") - (bookmark-maybe-load-default-file) - (dolist (bookmark (bookmark-maybe-sort-alist)) - (when (eq #'elpher-bookmark-jump (alist-get 'handler (cdr bookmark))) - (let* ((name (car bookmark)) - (url (alist-get 'location (cdr bookmark))) - (address (elpher-address-from-url url))) - (elpher-insert-index-record name address)))) - (when (<= (line-number-at-pos) 3) - (insert "No bookmarked pages found.\n")) - (insert "\n --------------------------\n\n" - "Select an entry or press 'u' to return to the previous page.\n\n" - "Bookmarks can be renamed or deleted via the ") - (insert-text-button "Emacs bookmark menu" - 'action (lambda (_) - (interactive) - (call-interactively #'bookmark-bmenu-list)) - 'follow-link t - 'help-echo "RET,mouse-1: open Emacs bookmark menu") - (insert (substitute-command-keys - ",\nwhich can also be opened from anywhere using '\\[bookmark-bmenu-list]'.")) - (elpher-restore-pos))) -(defun elpher-show-bookmarks () - "Display the current list of elpher bookmarks. -This will 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")))) @@ -1932,11 +1939,42 @@ This will also check for a legacy bookmark file and offer to import it." "\" found. Import now?"))) (elpher-bookmark-import old-bookmarks-file) (rename-file old-bookmarks-file (concat old-bookmarks-file "-legacy")))) - (if elpher-use-emacs-bookmark-menu - (call-interactively #'bookmark-bmenu-list) - (elpher-visit-page - (elpher-make-page "Elpher Bookmarks" - (elpher-make-special-address 'bookmarks))))) + + (if (and elpher-use-emacs-bookmark-menu + elpher-history) + (progn + (elpher-visit-previous-page) + (call-interactively #'bookmark-bmenu-list)) + (elpher-with-clean-buffer + (insert " ---- Elpher Bookmarks ---- \n\n") + (bookmark-maybe-load-default-file) + (dolist (bookmark (bookmark-maybe-sort-alist)) + (when (eq #'elpher-bookmark-jump (alist-get 'handler (cdr bookmark))) + (let* ((name (car bookmark)) + (url (alist-get 'location (cdr bookmark))) + (address (elpher-address-from-url url))) + (elpher-insert-index-record name address)))) + (when (<= (line-number-at-pos) 3) + (insert "No bookmarked pages found.\n")) + (insert "\n --------------------------\n\n" + "Select an entry or press 'u' to return to the previous page.\n\n" + "Bookmarks can be renamed or deleted via the ") + (insert-text-button "Emacs bookmark menu" + 'action (lambda (_) + (interactive) + (call-interactively #'bookmark-bmenu-list)) + 'follow-link t + 'help-echo "RET,mouse-1: open Emacs bookmark menu") + (insert (substitute-command-keys + ",\nwhich can also be opened from anywhere using '\\[bookmark-bmenu-list]'.")) + (elpher-restore-pos)))) + +(defun elpher-show-bookmarks () + "Interactive function to display the current list of elpher bookmarks." + (interactive) + (elpher-visit-page + (elpher-make-page "Elpher Bookmarks" + (elpher-make-about-address 'bookmarks)))) ;;; Integrations @@ -2057,6 +2095,7 @@ supports the old protocol elpher, where the link is self-contained." (setq eww-use-browse-url "\\`mailto:\\|\\(\\`gemini\\|\\`gopher\\|\\`finger\\)://") + ;;; Interactive procedures ;; @@ -2082,8 +2121,7 @@ When run interactively HOST-OR-URL is read from the minibuffer." (interactive "sGopher or Gemini URL: ") (let ((trimmed-host-or-url (string-trim host-or-url))) (unless (string-empty-p trimmed-host-or-url) - (let* ((address (elpher-address-from-url trimmed-host-or-url)) - (page (elpher-make-page trimmed-host-or-url address))) + (let ((page (elpher-page-from-url trimmed-host-or-url))) (switch-to-buffer elpher-buffer-name) (elpher-with-clean-buffer (elpher-visit-page page)) @@ -2094,10 +2132,10 @@ When run interactively HOST-OR-URL is read from the minibuffer." (interactive) (let* ((address (elpher-page-address elpher-current-page)) (url (read-string "Gopher or Gemini URL: " - (unless (elpher-address-special-p address) + (unless (elpher-address-about-p address) (elpher-address-to-url address))))) (unless (string-empty-p (string-trim url)) - (elpher-visit-page (elpher-make-page url (elpher-address-from-url url)))))) + (elpher-visit-page (elpher-page-from-url url))))) (defun elpher-redraw () "Redraw current page." @@ -2123,7 +2161,7 @@ When run interactively HOST-OR-URL is read from the minibuffer." (defun elpher-view-raw () "View raw server response for current page." (interactive) - (if (elpher-address-special-p (elpher-page-address elpher-current-page)) + (if (elpher-address-about-p (elpher-page-address elpher-current-page)) (error "This page was not generated by a server") (elpher-visit-page elpher-current-page #'elpher-render-raw))) @@ -2148,7 +2186,7 @@ When run interactively HOST-OR-URL is read from the minibuffer." (let ((page (button-get button 'elpher-page))) (unless page (error "Not an elpher page")) - (when (elpher-address-special-p (elpher-page-address page)) + (when (elpher-address-about-p (elpher-page-address page)) (error "Cannot download %s" (elpher-page-display-string page))) (elpher-visit-page (button-get button 'elpher-page) #'elpher-render-download)) @@ -2157,7 +2195,7 @@ When run interactively HOST-OR-URL is read from the minibuffer." (defun elpher-download-current () "Download the current page." (interactive) - (if (elpher-address-special-p (elpher-page-address elpher-current-page)) + (if (elpher-address-about-p (elpher-page-address elpher-current-page)) (error "Cannot download %s" (elpher-page-display-string elpher-current-page)) (elpher-visit-page (elpher-make-page @@ -2192,7 +2230,7 @@ When run interactively HOST-OR-URL is read from the minibuffer." "Visit root of current server." (interactive) (let ((address (elpher-page-address elpher-current-page))) - (if (not (elpher-address-special-p address)) + (if (not (elpher-address-about-p address)) (if (or (member (url-filename address) '("/" "")) (and (elpher-address-gopher-p address) (= (length (elpher-gopher-address-selector address)) 0))) @@ -2204,9 +2242,8 @@ When run interactively HOST-OR-URL is read from the minibuffer." (error "Command invalid for %s" (elpher-page-display-string elpher-current-page))))) (defun elpher-info-page (page) - "Display information on PAGE." - (let ((display-string (elpher-page-display-string page)) - (address (elpher-page-address page))) + "Display URL of PAGE in minibuffer." + (let ((address (elpher-page-address page))) (message "%s" (elpher-address-to-url address)))) (defun elpher-info-link ()