;; Copyright (C) 2021 Christopher Brannon <chris@the-brannons.com>
;; Copyright (C) 2021 Omar Polo <op@omarpolo.com>
;; Copyright (C) 2021 Noodles! <nnoodle@chiru.no>
+;; Copyright (C) 2021 Abhiseck Paira <abhiseckpaira@disroot.org>
;; Copyright (C) 2020-2021 Alex Schroeder <alex@gnu.org>
;; Copyright (C) 2020 Zhiwei Chen <chenzhiwei03@kuaishou.com>
;; Copyright (C) 2020 condy0919 <condy0919@gmail.com>
;; Author: Tim Vaughan <plugd@thelambdalab.xyz>
;; Created: 11 April 2019
-;; Version: 3.0.0
+;; Version: 3.1.0
;; Keywords: comm gopher
;; Homepage: https://thelambdalab.xyz/elpher
;; Package-Requires: ((emacs "27.1"))
;; 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 'seq)
-(require 'pp)
(require 'shr)
(require 'url-util)
(require 'subr-x)
-(require 'dns)
(require 'nsm)
(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.")
+(require 'bookmark)
;;; Global constants
;;
-(defconst elpher-version "3.0.0"
+(defconst elpher-version "3.1.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 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))
"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.
;;
(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 ansi-color-context)
+ (defvar bookmark-make-record-function)
+ (defvar mu4e~view-beginning-of-url-regexp)
(defvar thing-at-point-uri-schemes)
- (defvar mu4e~view-beginning-of-url-regexp))
+ (defvar xterm-color-preserve-properties))
;;; Customization group
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."
+(defcustom elpher-use-emacs-bookmark-menu nil
+ "If non-nil, elpher will only use the native Emacs bookmark menu.
+Otherwise, \\[elpher-show-bookmarks] will visit a special elpher bookmark
+page within which all of the standard elpher keybindings are active."
:type '(boolean))
;; Face customizations
"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)
"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))
"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))
(defmacro elpher-with-clean-buffer (&rest args)
"Evaluate ARGS with a clean *elpher* buffer as current."
+ (declare (debug (body))) ;; Allow edebug to step through body
`(with-current-buffer elpher-buffer-name
(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)
(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
;;
(if (timerp elpher-network-timer)
(cancel-timer elpher-network-timer)))
+(defun elpher-make-network-timer (thunk)
+ "Create 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.
(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
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
(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")))))
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.
(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)
;; 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 buttinofy 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)))
-
-(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."
- (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
(if (not data)
nil
(if (display-images-p)
- (progn
- (let ((image (create-image
- data
- nil t)))
- (elpher-with-clean-buffer
- (insert-image image)
- (elpher-restore-pos))))
+ (let* ((image (create-image
+ data
+ 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)))
+ (elpher-with-clean-buffer
+ (insert-image image)
+ (elpher-restore-pos)))
(elpher-render-download data))))
;; Search retrieval and rendering
(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"))))
+(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
elpher--gemini-page-headings))
(unless (display-graphic-p)
(insert (make-string level ?#) " "))
- (insert (propertize header 'face face))
- (newline))))
+ (insert (format "%s\n" (propertize header 'face face))))))
(defun elpher-gemini-insert-text (text-line)
"Insert a plain non-preformatted TEXT-LINE into a text/gemini document.
(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)."
"\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"
(elpher-address-from-url "gemini://geminispace.info/search"))
(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))
+ (let ((help-string "RET,mouse-1: Open bookmark list"))
+ (insert-text-button "bookmark list"
+ 'face 'link
+ 'action (lambda (_)
+ (interactive)
+ (call-interactively #'elpher-show-bookmarks))
+ 'follow-link t
+ 'help-echo help-string))
(insert ".\n")
(insert (propertize
"(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)"))
- (elpher--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 "\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 "\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)))
;; 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 an entry or press 'u' to return to the previous page.")
+ (elpher-restore-pos))))
;;; Bookmarks
;;;###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-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
(read-file-name "Old Elpher bookmarks: "
user-emacs-directory nil t
"elpher-bookmarks"))))
- (require 'bookmark)
(dolist (bookmark (with-temp-buffer
(insert-file-contents file)
(read (current-buffer))))
(bookmark-store display-string (cdr record) t)))
(bookmark-save))
+(defun elpher-get-bookmarks-page (renderer)
+ "Getter which displays the history page (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"))))
+ (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"))))
+ (if elpher-use-emacs-bookmark-menu
+ (call-interactively #'bookmark-bmenu-list)
+ (elpher-visit-page
+ (elpher-make-page "Elpher Bookmarks"
+ (elpher-make-special-address 'bookmarks)))))
+
+
;;; Integrations
;;
(setq mu4e~view-beginning-of-url-regexp
"\\(?:https?\\|gopher\\|finger\\|gemini\\)://\\|mailto:")
+;;; eww:
+
+;; Let elpher handle gemini, gopher links in eww buffer.
+(setq eww-use-browse-url
+ "\\`mailto:\\|\\(\\`gemini\\|\\`gopher\\|\\`finger\\)://")
+
;;; Interactive procedures
;;
#'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)))
(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: "
(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)
(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)
(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)
(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-show-bookmarks)
(define-key map (kbd "!") 'elpher-set-gopher-coding-system)
(define-key map (kbd "F") 'elpher-forget-current-certificate)
(when (fboundp 'evil-define-key*)
(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
(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
(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-show-bookmarks
(kbd "!") 'elpher-set-gopher-coding-system
(kbd "F") 'elpher-forget-current-certificate))
map)
(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))