;; Author: Tim Vaughan <plugd@thelambdalab.xyz>
;; Created: 11 April 2019
-;; Version: 3.2.1
+;; Version: 3.2.2
;; Keywords: comm gopher
;; Homepage: https://thelambdalab.xyz/elpher
;; Package-Requires: ((emacs "27.1"))
;;; Global constants
;;
-(defconst elpher-version "3.2.1"
+(defconst elpher-version "3.2.2"
"Current version of elpher.")
(defconst elpher-margin-width 6
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 is \"about:welcome\", while the bookmarks list
is \"about:bookmarks\". You can also specify local files via \"file:\".
(unless (and (not (url-fullness url)) (url-type url))
(setf (url-fullness url) t)
(unless (url-type url)
- (setf (url-type url) elpher-default-url-type))
+ (setf (url-type url) (elpher-get-default-url-type)))
(unless (url-host url)
(let ((p (split-string (url-filename url) "/" nil nil)))
(setf (url-host url) (car p))
(elpher-remove-redundant-ports url))
(set-match-data data))))
+(defun elpher-get-default-url-type ()
+ "Get the current URL type or `elpher-default-url-type'.
+If no scheme is provided for a URL, the current context specifies
+the scheme to use, so if we're looking at a gemini page, then the
+default type is \"gemini\" even if `elpher-default-url-type' is
+\"gopher\"."
+ (or (and elpher-current-page
+ (symbol-name
+ (elpher-address-type
+ (elpher-page-address elpher-current-page))))
+ elpher-default-url-type))
+
(defun elpher-remove-redundant-ports (address)
"Remove redundant port specifiers from ADDRESS.
Here 'redundant' means that the specified port matches the default
(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."
"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* ((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)))
+
+(defun elpher-encode-url (iri)
+ "Return an URL for the IRI.
+Encode and use percent-escapes, use punycode for the domain name
+if necessary."
+ (let* ((address (url-generic-parse-url iri))
+ (host (url-host address)))
+ (when host
+ (setf (url-host address) (puny-encode-domain host)))
+ (url-recreate-url address)))
+
(defvar elpher-current-page nil
"The current page for this Elpher buffer.")
(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))
(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
(when button
(let* ((page (button-get button 'elpher-page))
(address (elpher-page-address page)))
- (format "mouse-1, RET: open '%s'" (if (elpher-address-about-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.
=> prefix are empty."
(let ((l (split-string (substring link-line 2))))
(if l
- (string-trim (elt l 0))
+ (elpher-encode-url (string-trim (elt l 0)))
nil)))
(defun elpher-gemini-get-link-display-string (link-line)
(idx (string-match "[ \t]" rest)))
(string-trim (if idx
(substring rest (+ idx 1))
- rest))))
+ (elpher-url-to-iri rest)))))
(defun elpher-collapse-dot-sequences (filename)
"Collapse dot sequences in FILENAME.
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'."
- (if (string-empty-p text-line)
- (insert "\n")
- (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)
- (concat
- (replace-regexp-in-string "\\*"
- elpher-gemini-bullet-string
- (match-string 0 text-line))
- (substring text-line (match-end 0))))
- ((string-prefix-p ">" line-prefix)
- (propertize text-line 'face 'elpher-gemini-quoted))
- (t text-line))
- text-line))
- (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 1 text-line)
- (make-string (length (match-string 0 text-line)) ?\s)
- nil)))
- (insert (elpher-process-text-for-display processed-text-line))
- (newline))))
+ (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)
+ (concat
+ (replace-regexp-in-string "\\*"
+ elpher-gemini-bullet-string
+ (match-string 0 text-line))
+ (substring text-line (match-end 0))))
+ ((string-prefix-p ">" line-prefix)
+ (propertize text-line 'face 'elpher-gemini-quoted))
+ (t text-line))
+ text-line))
+ (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 1 text-line)
+ (make-string (length (match-string 0 text-line)) ?\s)
+ nil)))
+ (insert (elpher-process-text-for-display processed-text-line))
+ (newline)))
(defun elpher-render-gemini-map (data _parameters)
"Render DATA as a gemini map file, PARAMETERS is currently unused."
(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"
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))
(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))
(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."