New Upstream Release - elpher
Ready changes
Summary
Merged new upstream version: 3.5.0 (was: 3.4.2).
Resulting package
Built on 2023-05-22T09:53 (took 4m22s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases elpa-elpher
Lintian Result
Diff
diff --git a/ISSUES.org b/ISSUES.org
index 9923820..363c25f 100644
--- a/ISSUES.org
+++ b/ISSUES.org
@@ -1,10 +1,10 @@
#+TITLE: Issues and Dev Notes
#+TODO: OPEN(o!) | CLOSED(c!) INVALID(i@)
#+STARTUP: logdrawer
-
+
* Open Bugs
-** OPEN Sanitize certificate names
+** OPEN Sanitize certificate names :gemini:
:LOGBOOK:
- State "OPEN" from [2020-06-22 Mon 10:32]
:END:
@@ -24,6 +24,41 @@ user interaction that may appear during the initial connection setup.
E.g., asking for approval of uknown TLS certificates.
* Closed Bugs
+
+** CLOSED Downloads failing
+:LOGBOOK:
+- State "CLOSED" from "OPEN" [2022-08-09 Tue 10:38]
+:END:
+
+Downloads fail when focus is shifted away from
+the elpher buffer before the download has completed.
+
+** CLOSED Relative Gemini links processed improperly
+:LOGBOOK:
+- State "CLOSED" from "OPEN" [2021-08-04 Wed 15:54]
+- State "OPEN" from [2021-08-04 Wed 13:53]
+:END:
+
+Skyjake's gemlog at gemini://skyjake.fi/gemlog/ demonstrate's the
+issue. The link back to the root selector in the footer of that page
+is a relative link to the parent directory, i.e. "..". For some
+reason elpher combines this with the current URL and produces
+"gemini://skyjake.fi" as the destination of the link. Such URLs
+(i.e. without a filename) are allowed as input, but are assumed
+to not appear internally.
+
+To see why the internal distinction is important, consider a page
+where the current URL is gemini://example.com/a_page. The current
+directory in this case is "/", meaning a relative link to
+"another_page" results in a destination link of
+"gemini://example.com/another_page. On the other hand, if the current
+URL is gemini://example.com/a_page/, the same relative link is
+interpreted as refering to gemini://example.com/a_page/another_page.
+
+The fix will be to ensure gemini://skyjake.fi/gemlog/.. collapses to
+gemini://skyjake.fi/ rather than gemini://skyjake.fi.
+
+
** CLOSED Org mode faces are not present in recent emacs versions
Even 26.1 doesn't seem to have these. This means that, for many
@@ -90,6 +125,20 @@ pop the stack, meaning that subsequent "u" commands would succeed.
The fix is just to zero out the history list in the `elpher` function just as
`elpher-current-page` is cleared.
+** CLOSED Improve client certificate scope :gemini:
+:LOGBOOK:
+- State "CLOSED" from "OPEN" [2023-05-05 Fri 10:09]
+- State "OPEN" from [2022-10-12 Wed 09:33]
+:END:
+
+Once activated, elpher continues to use a client certificate
+for any connections to the host on which it was activated.
+However, it's now common to restrict certificates also to paths
+_below_ the path where the certificate was activated.
+
+I.e. gemini://example.com/~userA/ certificates are not applied
+automatically to gemini://example.com/~userB/.
+
* Open Enhancements
** OPEN Allow multiple elpher buffers [33%]
@@ -104,19 +153,6 @@ this can happen:
- [ ] make history stack variables buffer-local
- [ ] have elpher-with-clean-buffer select appropriate buffer
-** OPEN Replace support for user-specified starting pages
-This used to be available, but was removed during a refactor.
-
-** OPEN Make installing existing certificates easier
- :LOGBOOK:
- - State "OPEN" from "CLOSED" [2020-06-22 Mon 10:34]
- :END:
-
-It's naive to think that people don't have client certificates created
-outside of elpher. Thus we need some easy way to "install" these
-certificates, either by copying them or by referencing them in some
-way.
-
* Closed Enhancements
** CLOSED Turn on lexical scoping
@@ -277,3 +313,22 @@ call is just incredibly slow for some bizarre reason. Happily,
(url-portspec) is functionally equivalent and is orders of magnitude
faster. With this replacement, loading the above page takes ~2s
and there aren't any other hotspots.
+
+
+** CLOSED Replace support for user-specified starting pages
+:LOGBOOK:
+- State "CLOSED" from "OPEN" [2021-08-09 Mon 17:46]
+:END:
+This used to be available, but was removed during a refactor.
+
+
+** CLOSED Make installing existing certificates easier
+ :LOGBOOK:
+ - State "CLOSED" from "OPEN" [2023-05-05 Fri 10:10]
+ - State "OPEN" from "CLOSED" [2020-06-22 Mon 10:34]
+ :END:
+
+It's naive to think that people don't have client certificates created
+outside of elpher. Thus we need some easy way to "install" these
+certificates, either by copying them or by referencing them in some
+way.
diff --git a/README b/README
index 1ec85a2..6be2207 100644
--- a/README
+++ b/README
@@ -26,11 +26,17 @@ Any suggestions for improvements are welcome!
Installation
------------
-Elpher is available from MELPA (https://melpa.org). If you have
-never installed packages from this repository before, you'll need
-to follow the instructions at https://melpa.org/#/getting-started.
+Elpher is available on the non-GNU ELPA package archive. If you are
+using Emacs 28 or later, this archive should be available on your system
+by default. For Emacs 27, you'll need to follow the instructions at
+https://elpa.nongnu.org to make the archive accessible.
-To install Elpher, enter the following:
+Alternatively, Elpher is available from MELPA (https://melpa.org). If
+you have never installed packages from this repository before, you'll
+need to follow the instructions at https://melpa.org/#/getting-started.
+
+Once one of these package archives is installed, enter the following to
+install Elpher:
M-x package-install RET elpher RET
@@ -85,30 +91,29 @@ Info directory which can be displayed using "C-h i".
Contributors
------------
-Elpher was originally written by Tim Vaughan. Recent maintenance has
-been done by and with the help of Alex Schroeder. In addition, the
-following people (in alphabetical order) have generously provided
-assistance and/or patches:
-
-* Alexis
-* Christopher Brannon
-* Zhiwei Chen
-* condy0919
-* Étienne Deparis
-* Roy Koushik
-* Simon Nicolussi
-* Noodles!
-* Jens Östlund
-* Abhiseck Paira
-* F. Jason Park
-* Omar Polo
-* Koushk Roy
-* Michel Alexandre Salim
-* Alex Schroeder
-* Daniel Semyonov
-* Simon South
-* Bradley Thornton
-* Vee
+Elpher was originally written and is currently maintained by Tim Vaughan
+<plugd@thelambdalab.xyz>. Significant improvements and
+maintenance have also been contributed by and with the help of Alex
+Schroeder <alex@gnu.org>. In addition, the following people have
+all generously provided assistance and/or patches over the years:
+
+* Jens Östlund <jostlund@gmail.com>
+* F. Jason Park <jp@neverwas.me>
+* Christopher Brannon <chris@the-brannons.com>
+* Omar Polo <op@omarpolo.com>
+* Noodles! <nnoodle@chiru.no>
+* Abhiseck Paira <abhiseckpaira@disroot.org>
+* Zhiwei Chen <chenzhiwei03@kuaishou.com>
+* condy0919 <condy0919@gmail.com>
+* Alexis <flexibeast@gmail.com>
+* Étienne Deparis <etienne@depar.is>
+* Simon Nicolussi <sinic@sinic.name>
+* Michel Alexandre Salim <michel@michel-slm.name>
+* Koushk Roy <kroy@twilio.com>
+* Vee <vee@vnsf.xyz>
+* Simon South <simon@simonsouth.net>
+* Daniel Semyonov <daniel@dsemy.com>
+* Bradley Thornton <bradley@northtech.us>
License
-------
diff --git a/RELEASE b/RELEASE
index ade220d..1a08de8 100644
--- a/RELEASE
+++ b/RELEASE
@@ -7,9 +7,15 @@ When preparing a new release, set the version number:
2. in elpher.el: metadata at the top
3. in elpher.el: definition of elpher-version
4. in elpher-pkg.el: second argument to 'define-package'
+
+For anything besides a patch release, a note describing
+the changes should be added to the documentation. In
+the instance that the documentation itself is significantly
+changed, also update the documentation version:
+
5. in elpher.texi: 'settitle' declaration at the top
-Make sure the documentation is up to date and builds:
+After any documentation updates, make sure it builds:
make elpher.info elpher.html elpher.pdf
diff --git a/config.mk b/config.mk
index 0896d05..11af4ef 100644
--- a/config.mk
+++ b/config.mk
@@ -1,5 +1,5 @@
PKG = elpher
-VERSION = 3.2.2
+VERSION = 3.5.0
INSTALLINFO = install-info
MAKEINFO = makeinfo
diff --git a/debian/changelog b/debian/changelog
index 5bbe2a3..832152f 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,8 +1,15 @@
-elpher (3.2.2-1) UNRELEASED; urgency=medium
+elpher (3.5.0-1) UNRELEASED; urgency=medium
+ [ Dhavan Vaidya ]
* New upstream version 3.2.2
- -- Dhavan Vaidya <quark@codingquark.com> Tue, 14 Sep 2021 17:15:56 +0530
+ [ Debian Janitor ]
+ * New upstream release.
+ * New upstream release.
+ * New upstream release.
+ * New upstream release.
+
+ -- Dhavan Vaidya <quark@codingquark.com> Mon, 22 May 2023 09:50:03 -0000
elpher (2.10.2-2) unstable; urgency=medium
diff --git a/elpher-pkg.el b/elpher-pkg.el
index 4d60307..5cdbef2 100644
--- a/elpher-pkg.el
+++ b/elpher-pkg.el
@@ -1,4 +1,4 @@
-(define-package "elpher" "3.2.2" "A friendly gopher and gemini client"
+(define-package "elpher" "3.5.0" "A friendly gopher and gemini client"
'((emacs "27.1"))
:keywords ("convenience")
:authors (("Tim Vaughan" . "plugd@thelambdalab.xyz"))
diff --git a/elpher.el b/elpher.el
index 2e3c5ec..608f09d 100644
--- a/elpher.el
+++ b/elpher.el
@@ -1,27 +1,12 @@
;;; elpher.el --- A friendly gopher and gemini client -*- lexical-binding: t -*-
-;; Copyright (C) 2021 Jens Östlund <jostlund@gmail.com>
-;; Copyright (C) 2021 F. Jason Park <jp@neverwas.me>
-;; 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>
-;; Copyright (C) 2020 Alexis <flexibeast@gmail.com>
-;; Copyright (C) 2020 Étienne Deparis <etienne@depar.is>
-;; Copyright (C) 2020 Simon Nicolussi <sinic@sinic.name>
-;; Copyright (C) 2020 Michel Alexandre Salim <michel@michel-slm.name>
-;; Copyright (C) 2020 Koushk Roy <kroy@twilio.com>
-;; Copyright (C) 2020 Vee <vee@vnsf.xyz>
-;; Copyright (C) 2020 Simon South <simon@simonsouth.net>
-;; Copyright (C) 2019-2021 Tim Vaughan <plugd@thelambdalab.xyz>
+;; Copyright (C) 2019-2023 Tim Vaughan <plugd@thelambdalab.xyz>
+;; Copyright (C) 2020-2022 Elpher contributors (See info manual for full list)
;; Author: Tim Vaughan <plugd@thelambdalab.xyz>
;; Created: 11 April 2019
-;; Version: 3.2.2
-;; Keywords: comm gopher
+;; Version: 3.5.0
+;; Keywords: comm gopher gemini
;; Homepage: https://thelambdalab.xyz/elpher
;; Package-Requires: ((emacs "27.1"))
@@ -81,11 +66,12 @@
(require 'gnutls)
(require 'socks)
(require 'bookmark)
+(require 'rx)
;;; Global constants
;;
-(defconst elpher-version "3.2.2"
+(defconst elpher-version "3.5.0"
"Current version of elpher.")
(defconst elpher-margin-width 6
@@ -127,11 +113,12 @@
(declare-function org-link-store-props "ol")
(declare-function org-link-set-parameters "ol")
(defvar ansi-color-context)
+ (defvar xterm-color--current-fg)
+ (defvar xterm-color--current-bg)
(defvar bookmark-make-record-function)
(defvar mu4e~view-beginning-of-url-regexp)
(defvar eww-use-browse-url)
- (defvar thing-at-point-uri-schemes)
- (defvar xterm-color-preserve-properties))
+ (defvar thing-at-point-uri-schemes))
;;; Customization group
@@ -165,8 +152,8 @@ plain text without user input."
(defcustom elpher-filter-ansi-from-text nil
"If non-nil, filter ANSI escape sequences from text.
-The default behaviour is to use the ansi-color package to interpret these
-sequences."
+The default behaviour is to use the ansi-color package (or xterm-color if it is
+available) to interpret these sequences."
:type '(boolean))
(defcustom elpher-certificate-directory
@@ -236,6 +223,32 @@ Emacs bookmark menu being accessible via \\[elpher-show-bookmarks] from
the start page."
:type '(string))
+(defcustom elpher-gemini-hide-preformatted nil
+ "Cause elpher to hide preformatted gemini text by default.
+When this option is enabled, preformatted text in text/gemini documents
+is replaced with a button which can be used to toggle its display.
+
+This is intended to improve accessibility, as preformatted text often
+includes art which can be difficult for screen readers to interpret
+meaningfully."
+ :type '(boolean))
+
+(defcustom elpher-gemini-preformatted-toggle-bullet "‣ "
+ "Margin symbol used to distinguish the preformatted text toggle."
+ :type '(string))
+
+(defcustom elpher-gemini-preformatted-toggle-label "[Toggle Preformatted Text]"
+ "Label of button used to toggle formatted text."
+ :type '(string))
+
+(defcustom elpher-certificate-map nil
+ "Register client certificates to be used for gemini URLs.
+This variable contains an alist representing a mapping between gemini
+URLs and the names of client certificates which will be automatically
+activated for those URLs. Beware that the certificates will also be
+active for all subdirectories of the given URLs."
+ :type '(alist :key-type string :value-type string))
+
;; Face customizations
(defgroup elpher-faces nil
@@ -306,14 +319,18 @@ the start page."
'((t :inherit bold :height 1.2))
"Face used for gemini heading level 3.")
-(defface elpher-gemini-preformatted
- '((t :inherit fixed-pitch))
- "Face used for pre-formatted gemini text blocks.")
-
(defface elpher-gemini-quoted
'((t :inherit font-lock-doc-face))
"Face used for gemini quoted texts.")
+(defface elpher-gemini-preformatted
+ '((t :inherit default))
+ "Face used for gemini preformatted text.")
+
+(defface elpher-gemini-preformatted-toggle
+ '((t :inherit button))
+ "Face used for buttons used to toggle display of preformatted text.")
+
;;; Model
;;
@@ -324,15 +341,16 @@ the start page."
;; dynamically for and by elpher. All others represent pages which
;; rely on content retrieved over the network.
-(defun elpher-address-from-url (url-string)
- "Create a ADDRESS object corresponding to the given URL-STRING."
+(defun elpher-address-from-url (url-string &optional default-scheme)
+ "Create a ADDRESS object corresponding to the given URL-STRING.
+If DEFAULT-SCHEME is non-nil, this sets the scheme of the URL when one
+is not explicitly given."
(let ((data (match-data))) ; Prevent parsing clobbering match data
(unwind-protect
(let ((url (url-generic-parse-url url-string)))
(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) default-scheme))
(unless (url-host url)
(let ((p (split-string (url-filename url) "/" nil nil)))
(setf (url-host url) (car p))
@@ -340,6 +358,9 @@ the start page."
(if (cdr p)
(concat "/" (mapconcat #'identity (cdr p) "/"))
""))))
+ (when (not (string-empty-p (url-host url)))
+ (setf (url-fullness url) t)
+ (setf (url-host url) (puny-encode-domain (url-host url))))
(when (or (equal "gopher" (url-type url))
(equal "gophers" (url-type url)))
;; Gopher defaults
@@ -417,12 +438,12 @@ address refers to, via the table `elpher-type-map'."
(_ 'other-url)))
(defun elpher-address-about-p (address)
- "Return non-nil if ADDRESS is an about 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)))
+ "Return non-nil if ADDRESS object is a gopher address."
+ (pcase (elpher-address-type address) (`(gopher ,_) t)))
(defun elpher-address-protocol (address)
"Retrieve the transport protocol for ADDRESS."
@@ -435,7 +456,21 @@ For gopher addresses this is a combination of the selector type and selector."
(defun elpher-address-host (address)
"Retrieve host from ADDRESS object."
- (url-host address))
+ (pcase (url-host address)
+ ;; The following strips out square brackets which sometimes enclose IPv6
+ ;; addresses. Doing this here rather than at the parsing stage may seem
+ ;; weird, but this lets us way we avoid having to muck with both URL parsing
+ ;; and reconstruction. It's also more efficient, as this method is not
+ ;; called during page rendering.
+ ((rx (: "[" (let ipv6 (* (not "]"))) "]"))
+ ipv6)
+ ;; The following is a work-around for a parsing bug that causes
+ ;; URLs with empty (but not absent, see RFC 1738) usernames to have
+ ;; @ prepended to the hostname.
+ ((rx (: "@" (let rest (+ anything))))
+ rest)
+ (addr
+ addr)))
(defun elpher-address-user (address)
"Retrieve user from ADDRESS object."
@@ -443,8 +478,10 @@ 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.)"
- (url-port address))
+If no address is defined, returns 0. (This is for compatibility with
+the URL library.)"
+ (let ((port (url-portspec address))) ; (url-port) is too slow!
+ (if port port 0)))
(defun elpher-gopher-address-selector (address)
"Retrieve gopher selector from ADDRESS object."
@@ -498,12 +535,34 @@ 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)
+(defun elpher-page-from-url (url &optional default-scheme)
"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-decode (url-unhex-string url))
- (elpher-address-from-url url)))
+readability.
+
+If DEFAULT-SCHEME is non-nil, this scheme is applied to the URL
+in the instance that URL itself doesn't specify one."
+ (let ((address (elpher-address-from-url url default-scheme)))
+ (elpher-make-page (elpher-address-to-iri address) address)))
+
+(defun elpher-address-to-iri (address)
+ "Return an IRI for ADDRESS.
+Decode percent-escapes and handle punycode in the domain name.
+Drop the password, if any."
+ (let ((data (match-data)) ; Prevent parsing clobbering match data
+ (host (url-host address))
+ (pass (url-password address)))
+ (unwind-protect
+ (let* ((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
+ (elpher-decode (url-unhex-string (url-recreate-url address))))
+ (setf (url-host address) host)
+ (setf (url-password address) pass)
+ (set-match-data data))))
(defvar elpher-current-page nil
"The current page for this Elpher buffer.")
@@ -520,7 +579,7 @@ 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 history stack and the list of
-previously-visited pages,unless NO-HISTORY is non-nil."
+previously-visited pages, unless NO-HISTORY is non-nil."
(elpher-save-pos)
(elpher-process-cleanup)
(unless no-history
@@ -573,6 +632,19 @@ previously-visited pages,unless NO-HISTORY is non-nil."
(goto-char pos)
(goto-char (point-min)))))
+(defun elpher-get-default-url-scheme ()
+ "Suggest default URL scheme for visiting addresses based on the current page."
+ (if elpher-current-page
+ (let* ((address (elpher-page-address elpher-current-page))
+ (current-type (elpher-address-type address)))
+ (pcase current-type
+ ((or (and 'file (guard (not elpher-history)))
+ `(about ,_))
+ elpher-default-url-type)
+ (_
+ (url-type address))))
+ elpher-default-url-type))
+
;;; Buffer preparation
;;
@@ -603,7 +675,9 @@ previously-visited pages,unless NO-HISTORY is non-nil."
;; avoid resetting buffer-local variables
(elpher-mode))
(let ((inhibit-read-only t)
- (ansi-color-context nil)) ;; clean ansi interpreter state
+ (ansi-color-context nil)) ;; clean ansi interpreter state (also next 2 lines)
+ (setq-local xterm-color--current-fg nil)
+ (setq-local xterm-color--current-bg nil)
(setq-local network-security-level
(default-value 'network-security-level))
(erase-buffer)
@@ -668,7 +742,8 @@ away CRs and any terminating period."
'face 'button)))
(buffer-string)))
-;;; ANSI colors or XTerm colors (application and filtering)
+
+;; ANSI colors or XTerm colors (application and filtering)
(or (require 'xterm-color nil t)
(require 'ansi-color))
@@ -687,17 +762,25 @@ away CRs and any terminating period."
#'ansi-color-apply)
"A function to apply ANSI escape sequences.")
-;;; Processing text for display
+(defun elpher-text-has-ansi-escapes-p (string)
+ "Return non-nil if STRING includes an ANSI escape code."
+ (save-match-data
+ (string-match "\x1b\\[" string)))
+
+
+;; 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))))
+ (elpher-buttonify-urls (if (elpher-text-has-ansi-escapes-p string)
+ (if elpher-filter-ansi-from-text
+ (elpher-color-filter-apply string)
+ (elpher-color-apply string))
+ string)))
-;;; Network error reporting
+;;; General network communication
;;
(defun elpher-network-error (address error)
@@ -711,9 +794,6 @@ ERROR can be either an error object or a string."
"Press 'u' to return to the previous page.")))
-;;; General network communication
-;;
-
(defvar elpher-network-timer nil
"Timer used for network connections.")
@@ -798,7 +878,8 @@ the host operating system and the local network capabilities.)"
nil force-ipv4))
(t
(elpher-network-error address "Connection time-out."))))))
- (proc (if socks (socks-open-network-stream "elpher-process" nil host service)
+ (proc (if socks
+ (socks-open-network-stream "elpher-process" nil host service)
(make-network-process :name "elpher-process"
:host host
:family (and (or force-ipv4
@@ -812,6 +893,7 @@ the host operating system and the local network capabilities.)"
(cons 'gnutls-x509pki
(apply #'gnutls-boot-parameters
gnutls-params)))))))
+ (process-put proc 'elpher-buffer (current-buffer))
(setq elpher-network-timer timer)
(set-process-coding-system proc 'binary 'binary)
(set-process-query-on-exit-flag proc nil)
@@ -855,17 +937,19 @@ the host operating system and the local network capabilities.)"
response-processor
use-tls t))
(response-string-parts
- (elpher-with-clean-buffer
- (insert "Data received. Rendering..."))
- (funcall response-processor
- (apply #'concat (reverse response-string-parts)))
- (elpher-restore-pos))
+ (with-current-buffer (process-get proc 'elpher-buffer)
+ (elpher-with-clean-buffer
+ (insert "Data received. Rendering..."))
+ (funcall response-processor
+ (apply #'concat (reverse response-string-parts)))
+ (elpher-restore-pos)))
(t
(error "No response from server")))
(error
(elpher-network-error address the-error)))))
(when socks
- (if use-tls (apply #'gnutls-negotiate :process proc gnutls-params))
+ (if use-tls
+ (apply #'gnutls-negotiate :process proc gnutls-params))
(funcall (process-sentinel proc) proc "open\n")))
(error
(elpher-process-cleanup)
@@ -875,7 +959,8 @@ the host operating system and the local network capabilities.)"
;;; Client-side TLS Certificate Management
;;
-(defun elpher-generate-certificate (common-name key-file cert-file &optional temporary)
+(defun elpher-generate-certificate (common-name key-file cert-file url-prefix
+ &optional temporary)
"Generate a key and a self-signed client TLS certificate using openssl.
The Common Name field of the certificate is set to COMMON-NAME. The
@@ -889,7 +974,8 @@ when the certificate is no longer needed for the current session.
Otherwise, the certificate will be given a 100 year expiration period
and the files will not be deleted.
-The function returns a list containing the current host name, the
+The function returns a list containing the URL-PREFIX of addresses
+for which the certificate should be used in this session, the
temporary flag, and the key and cert file names in the form required
by `gnutls-boot-parameters`."
(let ((exp-key-file (expand-file-name key-file))
@@ -903,56 +989,70 @@ by `gnutls-boot-parameters`."
"-subj" (concat "/CN=" common-name)
"-keyout" exp-key-file
"-out" exp-cert-file)
- (list (elpher-address-host (elpher-page-address elpher-current-page))
- temporary exp-key-file exp-cert-file))
+ (list url-prefix temporary exp-key-file exp-cert-file))
(error
(message "Check that openssl is installed, or customize `elpher-openssl-command`.")
(error "Program 'openssl', required for certificate generation, not found")))))
-(defun elpher-generate-throwaway-certificate ()
+(defun elpher-generate-throwaway-certificate (url-prefix)
"Generate and return details of a throwaway certificate.
The key and certificate files will be deleted when they are no
-longer needed for this session."
+longer needed for this session.
+
+The certificate will be marked as applying to all addresses with URLs
+starting with URL-PREFIX."
(let* ((file-base (make-temp-name "elpher"))
(key-file (concat temporary-file-directory file-base ".key"))
(cert-file (concat temporary-file-directory file-base ".crt")))
- (elpher-generate-certificate file-base key-file cert-file t)))
+ (elpher-generate-certificate file-base key-file cert-file url-prefix t)))
-(defun elpher-generate-persistent-certificate (file-base common-name)
+(defun elpher-generate-persistent-certificate (file-base common-name url-prefix)
"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.
-The key and certificate files are written to in `elpher-certificate-directory'."
+The key and certificate files are written to in `elpher-certificate-directory'.
+
+In this session, the certificate will remain active for all addresses
+having URLs starting with URL-PREFIX."
(let* ((key-file (concat elpher-certificate-directory file-base ".key"))
(cert-file (concat elpher-certificate-directory file-base ".crt")))
- (elpher-generate-certificate common-name key-file cert-file)))
+ (elpher-generate-certificate common-name key-file cert-file url-prefix)))
-(defun elpher-get-existing-certificate (file-base)
+(defun elpher-get-existing-certificate (file-base url-prefix)
"Return a certificate object corresponding to an existing certificate.
It is assumed that the key files FILE-BASE.key and FILE-BASE.crt exist in
-the directory `elpher-certificate-directory'."
+the directory `elpher-certificate-directory'.
+
+In this session, the certificate will remain active for all addresses
+having URLs starting with URL-PREFIX."
(let* ((key-file (concat elpher-certificate-directory file-base ".key"))
(cert-file (concat elpher-certificate-directory file-base ".crt")))
- (list (elpher-address-host (elpher-page-address elpher-current-page))
+ (list url-prefix
nil
(expand-file-name key-file)
(expand-file-name cert-file))))
-(defun elpher-install-and-use-existing-certificate (key-file-src cert-file-src file-base)
+(defun elpher-install-certificate (key-file-src cert-file-src file-base url-prefix)
"Install a key+certificate file pair in `elpher-certificate-directory'.
The strings KEY-FILE-SRC and CERT-FILE-SRC are the existing key and
certificate files to install. The argument FILE-BASE is used as the
-base for the installed key and certificate files."
+base for the installed key and certificate files.
+
+In this session, the certificate will remain active for all addresses
+having URLs starting with URL-PREFIX."
(let* ((key-file (concat elpher-certificate-directory file-base ".key"))
(cert-file (concat elpher-certificate-directory file-base ".crt")))
(if (or (file-exists-p key-file)
(file-exists-p cert-file))
(error "A certificate with base name %s is already installed" file-base))
+ (unless (and (file-exists-p key-file-src)
+ (file-exists-p cert-file-src))
+ (error "Either of the key or certificate files do not exist"))
(copy-file key-file-src key-file)
(copy-file cert-file-src cert-file)
- (list (elpher-address-host (elpher-page-address elpher-current-page))
+ (list url-prefix
nil
(expand-file-name key-file)
(expand-file-name cert-file))))
@@ -978,7 +1078,7 @@ are also deleted."
(when (cadr elpher-client-certificate)
(delete-file (elt elpher-client-certificate 2))
(delete-file (elt elpher-client-certificate 3)))
- (setq elpher-client-certificate nil)
+ (setq-local elpher-client-certificate nil)
(if (called-interactively-p 'any)
(message "Client certificate forgotten.")))))
@@ -986,14 +1086,14 @@ are also deleted."
"Retrieve the `gnutls-boot-parameters'-compatable keylist.
This is obtained from the client certificate described by
-`elpher-current-certificate', if one is available and the host for
-that certificate matches the host in ADDRESS.
+`elpher-current-certificate', if one is available and the
+URL prefix for that certificate matches ADDRESS.
-If `elpher-current-certificate' is non-nil, and its host name doesn't
+If `elpher-current-certificate' is non-nil, and its URL prefix doesn't
match that of ADDRESS, the certificate is forgotten."
(if elpher-client-certificate
- (if (string= (car elpher-client-certificate)
- (elpher-address-host address))
+ (if (string-prefix-p (car elpher-client-certificate)
+ (elpher-address-to-url address))
(list (cddr elpher-client-certificate))
(elpher-forget-current-certificate)
(message "Disabling client certificate for new host")
@@ -1029,10 +1129,12 @@ once they are retrieved from the gopher server."
(error
(elpher-network-error address the-error))))))
-;; Index rendering
+
+;;; Gopher index rendering
+;;
(defun elpher-insert-margin (&optional type-name)
- "Insert index margin, optionally containing the TYPE-NAME, into the current buffer."
+ "Insert index margin, optionally containing the TYPE-NAME, into current buffer."
(if type-name
(progn
(insert (format (concat "%" (number-to-string (- elpher-margin-width 1)) "s")
@@ -1087,7 +1189,7 @@ If ADDRESS is not supplied or nil the record is rendered as an
(insert "\n")))
(defun elpher-click-link (button)
- "Function called when the gopher link BUTTON is activated (via mouse or keypress)."
+ "Function called when the gopher link BUTTON is activated."
(let ((page (button-get button 'elpher-page)))
(elpher-visit-page page)))
@@ -1113,7 +1215,9 @@ If ADDRESS is not supplied or nil the record is rendered as an
(elpher-cache-content (elpher-page-address elpher-current-page)
(buffer-string)))))
-;; Text rendering
+
+;;; Gopher text rendering
+;;
(defun elpher-render-text (data &optional _mime-type-string)
"Render DATA as text. MIME-TYPE-STRING is unused."
@@ -1125,7 +1229,9 @@ If ADDRESS is not supplied or nil the record is rendered as an
(elpher-page-address elpher-current-page)
(buffer-string)))))
-;; Image retrieval
+
+;;; Image retrieval
+;;
(defun elpher-render-image (data &optional _mime-type-string)
"Display DATA as image. MIME-TYPE-STRING is unused."
@@ -1134,17 +1240,21 @@ If ADDRESS is not supplied or nil the record is rendered as an
(if (display-images-p)
(let* ((image (create-image
data
- nil t))
- (window (get-buffer-window elpher-buffer-name)))
- (when 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)))
+ nil t)))
+ (if (not image)
+ (error "Unsupported image format")
+ (let ((window (get-buffer-window elpher-buffer-name)))
+ (when 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))))
(elpher-render-download data))))
-;; Search retrieval and rendering
+
+;;; Gopher search retrieval and rendering
+;;
(defun elpher-get-gopher-query-page (renderer)
"Getter for gopher addresses requiring input.
@@ -1173,7 +1283,9 @@ The response is rendered using the rendering function RENDERER."
(if aborted
(elpher-visit-previous-page))))))
-;; Raw server response rendering
+
+;;; Raw server response rendering
+;;
(defun elpher-render-raw (data &optional mime-type-string)
"Display raw DATA in buffer. MIME-TYPE-STRING is also displayed if provided."
@@ -1186,7 +1298,9 @@ The response is rendered using the rendering function RENDERER."
(goto-char (point-min)))
(message "Displaying raw server response. Reload or redraw to return to standard view.")))
-;; File save "rendering"
+
+;;; File save "rendering"
+;;
(defun elpher-render-download (data &optional _mime-type-string)
"Save DATA to file. MIME-TYPE-STRING is unused."
@@ -1208,7 +1322,9 @@ The response is rendered using the rendering function RENDERER."
(insert data)))
(message (format "Saved to file %s." filename))))))
-;; HTML rendering
+
+;;; HTML rendering
+;;
(defun elpher-render-html (data &optional _mime-type-string)
"Render DATA as HTML using shr. MIME-TYPE-STRING is unused."
@@ -1220,7 +1336,9 @@ The response is rendered using the rendering function RENDERER."
(libxml-parse-html-region (point-min) (point-max)))))
(shr-insert-document dom)))))
-;; Gemini page retrieval
+
+;;; Gemini page retrieval
+;;
(defvar elpher-gemini-redirect-chain)
@@ -1258,14 +1376,17 @@ that the response was malformed."
(elpher-with-clean-buffer
(insert "Gemini server is requesting input."))
(let* ((query-string
- (if (eq (elt response-code 1) ?1)
- (read-passwd (concat response-meta ": "))
- (read-string (concat response-meta ": "))))
+ (with-local-quit
+ (if (eq (elt response-code 1) ?1)
+ (read-passwd (concat response-meta ": "))
+ (read-string (concat response-meta ": ")))))
(query-address (seq-copy (elpher-page-address elpher-current-page)))
(old-fname (url-filename query-address)))
- (setf (url-filename query-address)
- (concat old-fname "?" (url-build-query-string `((,query-string)))))
- (elpher-get-gemini-response query-address renderer)))
+ (if (not query-string)
+ (elpher-visit-previous-page)
+ (setf (url-filename query-address)
+ (concat old-fname "?" (url-build-query-string `((,query-string)))))
+ (elpher-get-gemini-response query-address renderer))))
(?2 ; Normal response
(funcall renderer response-body response-meta))
(?3 ; Redirect
@@ -1294,28 +1415,55 @@ that the response was malformed."
(insert "Gemini server is requesting a valid TLS certificate:\n\n"))
(auto-fill-mode 1)
(elpher-gemini-insert-text response-meta))
- (let ((chosen-certificate (elpher-choose-client-certificate)))
+ (let ((chosen-certificate
+ (with-local-quit
+ (elpher-acquire-client-certificate
+ (elpher-address-to-url (elpher-page-address elpher-current-page))))))
(unless chosen-certificate
(error "Gemini server requires a client certificate and none was provided"))
- (setq elpher-client-certificate chosen-certificate))
+ (setq-local elpher-client-certificate chosen-certificate))
(elpher-with-clean-buffer)
(elpher-get-gemini-response (elpher-page-address elpher-current-page) renderer))
(_other
(error "Gemini server response unknown: %s %s"
response-code response-meta))))))
+(defun elpher-acquire-client-certificate (url-prefix)
+ "Select a pre-defined client certificate or prompt for one.
+In this case, \"pre-defined\" means a certificate provided by
+the `elpher-certificate-map' variable.
+
+For this session, the certificate will remain active for all addresses
+having URLs begining with URL-PREFIX."
+ (let ((entry (assoc url-prefix
+ elpher-certificate-map
+ #'string-prefix-p)))
+ (if entry
+ (let ((cert-url-prefix (car entry))
+ (cert-name (cadr entry)))
+ (message "Using certificate \"%s\" specified in elpher-certificate-map with prefix \"%s\""
+ cert-name cert-url-prefix)
+ (elpher-get-existing-certificate cert-name cert-url-prefix))
+ (elpher-prompt-for-client-certificate url-prefix))))
+
(defun elpher--read-answer-polyfill (question answers)
"Polyfill for `read-answer' in Emacs 26.1.
QUESTION is a string containing a question, and ANSWERS
-is a list of possible answers."
- (completing-read question (mapcar 'identity answers)))
+is a list of possible answers, or an alist whose keys
+are the possible answers."
+ (completing-read question answers))
(if (fboundp 'read-answer)
(defalias 'elpher-read-answer 'read-answer)
(defalias 'elpher-read-answer 'elpher--read-answer-polyfill))
-(defun elpher-choose-client-certificate ()
- "Prompt for a client certificate to use to establish a TLS connection."
+
+
+(defun elpher-prompt-for-client-certificate (url-prefix)
+ "Prompt for a client certificate to use to establish a TLS connection.
+
+In this session, the chosen certificate will remain active for all
+addresses with URLs matching URL-PREFIX."
(let* ((read-answer-short t))
(pcase (read-answer "What do you want to do? "
'(("throwaway" ?t
@@ -1325,7 +1473,7 @@ is a list of possible answers."
("abort" ?a
"stop immediately")))
("throwaway"
- (setq elpher-client-certificate (elpher-generate-throwaway-certificate)))
+ (setq-local elpher-client-certificate (elpher-generate-throwaway-certificate url-prefix)))
("persistent"
(let* ((existing-certificates (elpher-list-existing-certificates))
(file-base (completing-read
@@ -1334,8 +1482,8 @@ is a list of possible answers."
(if (string-empty-p (string-trim file-base))
nil
(if (member file-base existing-certificates)
- (setq elpher-client-certificate
- (elpher-get-existing-certificate file-base))
+ (setq-local elpher-client-certificate
+ (elpher-get-existing-certificate file-base url-prefix))
(pcase (read-answer "Generate new certificate or install externally-generated one? "
'(("new" ?n
"generate new certificate")
@@ -1348,15 +1496,16 @@ is a list of possible answers."
file-base)))
(message "New key and self-signed certificate written to %s"
elpher-certificate-directory)
- (elpher-generate-persistent-certificate file-base common-name)))
+ (elpher-generate-persistent-certificate file-base
+ common-name
+ url-prefix)))
("install"
(let* ((cert-file (read-file-name "Certificate file: " nil nil t))
(key-file (read-file-name "Key file: " nil nil t)))
(message "Key and certificate installed in %s for future use"
elpher-certificate-directory)
- (elpher-install-and-use-existing-certificate key-file
- cert-file
- file-base)))
+ (elpher-install-certificate key-file cert-file file-base
+ url-prefix)))
("abort" nil))))))
("abort" nil))))
@@ -1376,6 +1525,9 @@ is a list of possible answers."
(error
(elpher-network-error address the-error)))))
+;;; Gemini page rendering
+;;
+
(defun elpher-render-gemini (body &optional mime-type-string)
"Render gemini response BODY with rendering MIME-TYPE-STRING."
(if (not body)
@@ -1421,25 +1573,27 @@ Returns nil in the event that the contents of the line following the
(defun elpher-gemini-get-link-display-string (link-line)
"Extract the display string portion of LINK-LINE, a gemini map file link line.
-Returns the url portion in the event that the display-string portion is empty."
+Return nil if this portion is not provided."
(let* ((rest (string-trim (elt (split-string link-line "=>") 1)))
(idx (string-match "[ \t]" rest)))
- (string-trim (if idx
- (substring rest (+ idx 1))
- rest))))
+ (and idx
+ (elpher-color-filter-apply (string-trim (substring rest (+ idx 1)))))))
(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.
@@ -1449,16 +1603,18 @@ treatment that a separate function is warranted."
(let ((address (url-generic-parse-url url))
(current-address (elpher-page-address elpher-current-page)))
(unless (and (url-type address) (not (url-fullness address))) ;avoid mangling mailto: urls
- (setf (url-fullness address) t)
(if (url-host address) ;if there is an explicit host, filenames are absolute
(if (string-empty-p (url-filename address))
(setf (url-filename address) "/")) ;ensure empty filename is marked as absolute
(setf (url-host address) (url-host current-address))
+ (setf (url-fullness address) (url-host address)) ; set fullness to t if host is set
(setf (url-portspec address) (url-portspec current-address)) ; (url-port) too slow!
(unless (string-prefix-p "/" (url-filename address)) ;deal with relative links
(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) (url-type current-address)))
(when (equal (url-type address) "gemini")
@@ -1468,28 +1624,27 @@ treatment that a separate function is warranted."
(defun elpher-gemini-insert-link (link-line)
"Insert link described by LINK-LINE into a text/gemini document."
- (let* ((url (elpher-gemini-get-link-url link-line))
- (display-string (elpher-gemini-get-link-display-string link-line))
- (address (elpher-address-from-gemini-url url))
- (type (if address (elpher-address-type address) nil))
- (type-map-entry (cdr (assoc type elpher-type-map))))
- (when display-string
- (insert elpher-gemini-link-string)
- (if type-map-entry
+ (let ((url (elpher-gemini-get-link-url link-line)))
+ (when url
+ (let* ((given-display-string (elpher-gemini-get-link-display-string link-line))
+ (address (elpher-address-from-gemini-url url))
+ (type (if address (elpher-address-type address) nil))
+ (type-map-entry (cdr (assoc type elpher-type-map)))
+ (fill-prefix (make-string (+ 1 (length elpher-gemini-link-string)) ?\s)))
+ (when type-map-entry
+ (insert elpher-gemini-link-string)
(let* ((face (elt type-map-entry 3))
- (filtered-display-string (elpher-color-filter-apply display-string))
- (page (elpher-make-page filtered-display-string address)))
- (insert-text-button filtered-display-string
+ (display-string (or given-display-string
+ (elpher-address-to-iri address)))
+ (page (elpher-make-page display-string
+ address)))
+ (insert-text-button 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.")
+ (newline))))))
(defun elpher-gemini-insert-header (header-line)
"Insert header described by HEADER-LINE into a text/gemini document.
@@ -1507,11 +1662,12 @@ by HEADER-LINE."
(/ (* fill-column
(font-get (font-spec :name (face-font 'default)) :size))
(font-get (font-spec :name (face-font face)) :size)) fill-column)))
- (setq elpher--gemini-page-headings (cons (cons header (point))
- elpher--gemini-page-headings))
(unless (display-graphic-p)
(insert (make-string level ?#) " "))
- (insert (propertize header 'face face 'rear-nonsticky t))
+ (insert (propertize header
+ 'face face
+ 'gemini-heading t
+ 'rear-nonsticky t))
(newline))))
(defun elpher-gemini-insert-text (text-line)
@@ -1520,7 +1676,6 @@ This function uses Emacs' auto-fill to wrap text sensibly to a maximum
width defined by `elpher-gemini-max-fill-width'."
(string-match
(rx (: line-start
- (* (any " \t"))
(optional
(group (or (: "*" (+ (any " \t")))
(: ">" (* (any " \t"))))))))
@@ -1538,36 +1693,77 @@ width defined by `elpher-gemini-max-fill-width'."
(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)
+ (fill-prefix (if line-prefix
(make-string (length (match-string 0 text-line)) ?\s)
- nil)))
+ "")))
(insert (elpher-process-text-for-display processed-text-line))
(newline)))
+(defun elpher-gemini-pref-expand-collapse (button)
+ "Function called when the preformatted text toggle BUTTON is activated."
+ (let ((id (button-get button 'pref-id)))
+ (if (invisible-p id)
+ (remove-from-invisibility-spec id)
+ (add-to-invisibility-spec id))
+ (redraw-display)))
+
+(defun elpher-gemini-insert-preformatted-toggler (alt-text)
+ "Insert a button for toggling the visibility of preformatted text.
+If non-nil, ALT-TEXT is displayed alongside the button."
+ (let* ((url-string (url-recreate-url (elpher-page-address elpher-current-page)))
+ (pref-id (intern (concat "pref-"
+ (number-to-string (point))
+ "-"
+ url-string))))
+ (insert elpher-gemini-preformatted-toggle-bullet)
+ (when alt-text
+ (insert (propertize (concat alt-text " ")
+ 'face 'elpher-gemin-preformatted)))
+ (insert-text-button elpher-gemini-preformatted-toggle-label
+ 'action #'elpher-gemini-pref-expand-collapse
+ 'pref-id pref-id
+ 'face 'elpher-gemini-preformatted-toggle)
+ (add-to-invisibility-spec pref-id)
+ (newline)
+ pref-id))
+
+(defun elpher-gemini-insert-preformatted-line (line &optional pref-id)
+ "Insert a LINE of preformatted text.
+PREF-ID is the value assigned to the \"invisible\" text attribute, which
+can be used to toggle the display of the preformatted text."
+ (insert (propertize (concat (elpher-process-text-for-display
+ (propertize line 'face 'elpher-gemini-preformatted))
+ "\n")
+ 'invisible pref-id
+ 'rear-nonsticky t)))
+
(defun elpher-render-gemini-map (data _parameters)
"Render DATA as a gemini map file, PARAMETERS is currently unused."
(elpher-with-clean-buffer
- (setq elpher--gemini-page-headings nil)
- (let ((preformatted nil))
- (auto-fill-mode 1)
+ (auto-fill-mode 1)
+ (setq-local buffer-invisibility-spec nil)
+ (let ((preformatted nil)
+ (adaptive-fill-mode nil)) ;Prevent automatic setting of fill-prefix
(setq-local fill-column (min (window-width) elpher-gemini-max-fill-width))
(dolist (line (split-string data "\n"))
- (cond
- ((string-prefix-p "```" line) (setq preformatted (not preformatted)))
- (preformatted (insert (elpher-process-text-for-display
- (propertize line 'face 'elpher-gemini-preformatted))
- "\n"))
- ((string-prefix-p "=>" line)
- (elpher-gemini-insert-link line))
- ((string-prefix-p "#" line) (elpher-gemini-insert-header line))
- (t (elpher-gemini-insert-text line)))))
- (setq elpher--gemini-page-headings (nreverse elpher--gemini-page-headings))
+ (pcase line
+ ((rx (: string-start "```" (opt (let alt-text (+ any)))))
+ (setq preformatted
+ (if preformatted
+ nil
+ (if elpher-gemini-hide-preformatted
+ (elpher-gemini-insert-preformatted-toggler alt-text)
+ t))))
+ ((guard preformatted)
+ (elpher-gemini-insert-preformatted-line line preformatted))
+ ((pred (string-prefix-p "=>"))
+ (elpher-gemini-insert-link line))
+ ((pred (string-prefix-p "#"))
+ (elpher-gemini-insert-header line))
+ (_ (elpher-gemini-insert-text line))))
(elpher-cache-content
(elpher-page-address elpher-current-page)
- (buffer-string))))
+ (buffer-string)))))
(defun elpher-render-gemini-plain-text (data _parameters)
"Render DATA as plain text file. PARAMETERS is currently unused."
@@ -1577,8 +1773,23 @@ width defined by `elpher-gemini-max-fill-width'."
(elpher-page-address elpher-current-page)
(buffer-string))))
-
-;; Finger page connection
+(defun elpher-build-current-imenu-index ()
+ "Build imenu index for current elpher buffer."
+ (save-excursion
+ (goto-char (point-min))
+ (let ((match nil)
+ (headers nil))
+ (while (setq match (text-property-search-forward 'gemini-heading t t))
+ (push (cons
+ (buffer-substring-no-properties (prop-match-beginning match)
+ (prop-match-end match))
+ (prop-match-beginning match))
+ headers))
+ (reverse headers))))
+
+
+;;; Finger page connection
+;;
(defun elpher-get-finger-page (renderer)
"Opens a finger connection to the current page address.
@@ -1604,7 +1815,8 @@ The result is rendered using RENDERER."
(elpher-network-error address the-error))))))
-;; Telnet page connection
+;;; Telnet page connection
+;;
(defun elpher-get-telnet-page (renderer)
"Opens a telnet connection to the current page address (RENDERER must be nil)."
@@ -1620,10 +1832,12 @@ The result is rendered using RENDERER."
(telnet host))))
-;; Other URL page opening
+;;; Other URL page opening
+;;
(defun elpher-get-other-url-page (renderer)
- "Getter which attempts to open the URL specified by the current page (RENDERER must be nil)."
+ "Getter which attempts to open the URL specified by the current page.
+The RENDERER argument to this getter must be nil."
(when renderer
(elpher-visit-previous-page)
(error "Command not supported for general URLs"))
@@ -1635,7 +1849,9 @@ The result is rendered using RENDERER."
(browse-web url)
(browse-url url))))
-;; File page
+
+;;; File page
+;;
(defun elpher-get-file-page (renderer)
"Getter which renders a local file using RENDERER.
@@ -1644,10 +1860,10 @@ Assumes UTF-8 encoding for all text files."
(filename (elpher-address-filename address)))
(unless (file-exists-p filename)
(elpher-visit-previous-page)
- (error "File not found"))
+ (error "File not found"))
(unless (file-readable-p filename)
(elpher-visit-previous-page)
- (error "Could not read from file"))
+ (error "Could not read from file"))
(let ((body (with-temp-buffer
(let ((coding-system-for-read 'binary)
(coding-system-for-write 'binary))
@@ -1664,12 +1880,15 @@ Assumes UTF-8 encoding for all text files."
(elpher-render-text (decode-coding-string body 'utf-8)))
((or "jpg" "jpeg" "gif" "png" "bmp" "tif" "tiff")
(elpher-render-image body))
+ ((or "gopher" "gophermap")
+ (elpher-render-index (elpher-decode body)))
(_
(elpher-render-download body))))
(elpher-restore-pos))))
-;; Welcome page retrieval
+;;; Welcome page retrieval
+;;
(defun elpher-get-welcome-page (renderer)
"Getter which displays the welcome page (RENDERER must be nil)."
@@ -1716,15 +1935,14 @@ Assumes UTF-8 encoding for all text files."
(elpher-address-from-url "gemini://geminispace.info/search"))
(insert "\n"
"Your bookmarks are stored in your ")
- (let ((help-string "RET,mouse-1: Open bookmark list"))
- (insert-text-button "bookmark list"
- 'face 'link
- 'action #'elpher-click-link
- 'follow-link t
- 'help-echo #'elpher--page-button-help
- 'elpher-page
- (elpher-make-page "Elpher Bookmarks"
- (elpher-make-about-address 'bookmarks))))
+ (insert-text-button "bookmark list"
+ 'face 'link
+ 'action #'elpher-click-link
+ 'follow-link t
+ '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"
@@ -1757,12 +1975,15 @@ Assumes UTF-8 encoding for all text files."
'help-echo help-string))
(insert "\n")
(insert (propertize
- (concat "(These documents should be available if you have installed Elpher \n"
- " using MELPA. Otherwise you may have to install the manual yourself.)\n")
+ (concat "(These documents should be available if you have installed Elpher\n"
+ " from MELPA or non-GNU ELPA. Otherwise you may have to install the\n"
+ " manual yourself.)\n")
'face 'shadow))
(elpher-restore-pos)))
-;; History page retrieval
+
+;;; History page retrieval
+;;
(defun elpher-show-history ()
"Show the current contents of elpher's history stack.
@@ -1819,6 +2040,7 @@ This is rendered using `elpher-get-visited-pages-page' via `elpher-type-map'."
;;; Bookmarks
+;;
;; This code allows Elpher to use the standard Emacs bookmarks: `C-x r
;; m' to add a bookmark, `C-x r l' to list bookmarks (which is where
@@ -1863,10 +2085,11 @@ 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))
- (page (elpher-page-from-url cleaned-url)))
+ (page (elpher-page-from-url cleaned-url))
+ (buffer (get-buffer-create elpher-buffer-name)))
(elpher-with-clean-buffer
(elpher-visit-page page))
- (set-buffer (get-buffer elpher-buffer-name))
+ (set-buffer buffer)
nil))
(defun elpher-bookmark-link ()
@@ -2020,6 +2243,11 @@ supports the old protocol elpher, where the link is self-contained."
:export (lambda (link description format _plist)
(elpher-org-export-link link description format "gopher"))
:follow (lambda (link _arg) (elpher-org-follow-link link "gopher")))
+ (org-link-set-parameters
+ "gophers"
+ :export (lambda (link description format _plist)
+ (elpher-org-export-link link description format "gophers"))
+ :follow (lambda (link _arg) (elpher-org-follow-link link "gophers")))
(org-link-set-parameters
"finger"
:export (lambda (link description format _plist)
@@ -2028,7 +2256,7 @@ supports the old protocol elpher, where the link is self-contained."
(add-hook 'org-mode-hook #'elpher-org-mode-integration)
-;;; Browse URL
+;; Browse URL
;;;###autoload
(defun elpher-browse-url-elpher (url &rest _args)
@@ -2041,36 +2269,39 @@ supports the old protocol elpher, where the link is self-contained."
(if (boundp 'browse-url-default-handlers)
(add-to-list
'browse-url-default-handlers
- '("^\\(gopher\\|finger\\|gemini\\)://" . elpher-browse-url-elpher))
+ '("^\\(gopher\\|gophers\\|finger\\|gemini\\)://" . elpher-browse-url-elpher))
;; Patch `browse-url-browser-function' for older ones. The value of
;; that variable is `browse-url-default-browser' by default, so
- ;; that's the function that gets advised.
- (advice-add browse-url-browser-function :before-while
- (lambda (url &rest _args)
- "Handle gemini, gopher, and finger schemes using Elpher."
- (let ((scheme (downcase (car (split-string url ":" t)))))
- (if (member scheme '("gemini" "gopher" "finger"))
- ;; `elpher-go' always returns nil, which will stop the
- ;; advice chain here in a before-while
- (elpher-go url)
- ;; chain must continue, then return t.
- t)))))
+ ;; that's the function that gets advised. If the value is an alist,
+ ;; however, we don't know what to do. Better not interfere?
+ (when (and (symbolp browse-url-browser-function)
+ (fboundp browse-url-browser-function))
+ (advice-add browse-url-browser-function :before-while
+ (lambda (url &rest _args)
+ "Handle gemini, gopher, and finger schemes using Elpher."
+ (let ((scheme (downcase (car (split-string url ":" t)))))
+ (if (member scheme '("gemini" "gopher" "gophers" "finger"))
+ ;; `elpher-go' always returns nil, which will stop the
+ ;; advice chain here in a before-while
+ (elpher-go url)
+ ;; chain must continue, then return t.
+ t))))))
;; Register "gemini://" as a URI scheme so `browse-url' does the right thing
(with-eval-after-load 'thingatpt
(add-to-list 'thing-at-point-uri-schemes "gemini://"))
-;;; Mu4e:
+;; Mu4e:
;; Make mu4e aware of the gemini world
(setq mu4e~view-beginning-of-url-regexp
- "\\(?:https?\\|gopher\\|finger\\|gemini\\)://\\|mailto:")
+ "\\(?:https?\\|gopher\\|gophers\\|finger\\|gemini\\)://\\|mailto:")
-;;; eww:
+;; eww:
;; Let elpher handle gemini, gopher links in eww buffer.
(setq eww-use-browse-url
- "\\`mailto:\\|\\(\\`gemini\\|\\`gopher\\|\\`finger\\)://")
+ "\\`mailto:\\|\\(\\`gemini\\|\\`gopher\\|\\`gophers\\|\\`finger\\)://")
;;; Interactive procedures
@@ -2095,24 +2326,33 @@ supports the old protocol elpher, where the link is self-contained."
(defun elpher-go (host-or-url)
"Go to a particular gopher site HOST-OR-URL.
When run interactively HOST-OR-URL is read from the minibuffer."
- (interactive "sGopher or Gemini URL: ")
+ (interactive (list
+ (read-string (format "Visit URL (default scheme %s): "
+ (elpher-get-default-url-scheme)))))
(let ((trimmed-host-or-url (string-trim host-or-url)))
(unless (string-empty-p trimmed-host-or-url)
- (let ((page (elpher-page-from-url trimmed-host-or-url)))
- (switch-to-buffer elpher-buffer-name)
+ (let ((page (elpher-page-from-url trimmed-host-or-url
+ (elpher-get-default-url-scheme))))
+ (unless (get-buffer-window elpher-buffer-name t)
+ (switch-to-buffer elpher-buffer-name))
(elpher-with-clean-buffer
(elpher-visit-page page))
nil)))) ; non-nil value is displayed by eshell
(defun elpher-go-current ()
- "Go to a particular site read from the minibuffer, initialized with the current URL."
+ "Go to a particular URL which is read from the minibuffer.
+Unlike `elpher-go', the reader is initialized with the URL of the
+current page."
(interactive)
(let* ((address (elpher-page-address elpher-current-page))
- (url (read-string "Gopher or Gemini URL: "
- (unless (elpher-address-about-p address)
- (elpher-address-to-url address)))))
- (unless (string-empty-p (string-trim url))
- (elpher-visit-page (elpher-page-from-url url)))))
+ (url (read-string (format "Visit URL (default scheme %s): "
+ (elpher-get-default-url-scheme))
+ (elpher-address-to-url address))))
+ (let ((trimmed-url (string-trim url)))
+ (unless (string-empty-p trimmed-url)
+ (elpher-with-clean-buffer
+ (elpher-visit-page
+ (elpher-page-from-url trimmed-url (elpher-get-default-url-scheme))))))))
(defun elpher-redraw ()
"Redraw current page."
@@ -2175,9 +2415,7 @@ When run interactively HOST-OR-URL is read from the minibuffer."
(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
- (elpher-page-display-string elpher-current-page)
- (elpher-page-address elpher-current-page))
+ (elpher-visit-page elpher-current-page
#'elpher-render-download
t)))
@@ -2220,8 +2458,12 @@ When run interactively HOST-OR-URL is read from the minibuffer."
(defun elpher-info-page (page)
"Display URL of PAGE in minibuffer."
- (let ((address (elpher-page-address page)))
- (message "%s" (elpher-address-to-url address))))
+ (let* ((address (elpher-page-address page))
+ (url (elpher-address-to-url address))
+ (iri (elpher-address-to-iri address)))
+ (if (equal url iri)
+ (message "%s" url)
+ (message "%s (Raw: %s)" iri url))))
(defun elpher-info-link ()
"Display information on page corresponding to link at point."
@@ -2347,13 +2589,11 @@ When run interactively HOST-OR-URL is read from the minibuffer."
This mode is automatically enabled by the interactive
functions which initialize the client, namely
`elpher', and `elpher-go'."
- (setq-local elpher--gemini-page-headings nil)
(setq-local elpher-current-page nil)
(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 xterm-color-preserve-properties t))
+ (setq-local imenu-create-index-function #'elpher-build-current-imenu-index))
(when (fboundp 'evil-set-initial-state)
(evil-set-initial-state 'elpher-mode 'motion))
diff --git a/elpher.texi b/elpher.texi
index 5892db3..8b5c65d 100644
--- a/elpher.texi
+++ b/elpher.texi
@@ -1,7 +1,7 @@
\input texinfo @c -*-texinfo-*-
@setfilename elpher.info
-@settitle Elpher Manual v3.2.2
+@settitle Elpher Manual v3.5.0
@dircategory Emacs
@direntry
@@ -11,7 +11,7 @@
@copying
This manual documents Elpher, a gopher and gemini client for Emacs.
-Copyright @copyright{} 2019, 2020, 2021 Tim Vaughan@*
+Copyright @copyright{} 2019-2023 Tim Vaughan@*
Copyright @copyright{} 2021 Daniel Semyonov@*
Copyright @copyright{} 2021 Alex Schroeder
@@ -23,7 +23,7 @@ version 3, or (at your option) any later version.
Elpher is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-FITNElpher FOR A PARTICULAR PURPOSE. See the GNU General Public License in
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License in
the file COPYING in the same directory as this file for more details.
@end quotation
@end copying
@@ -57,20 +57,40 @@ the file COPYING in the same directory as this file for more details.
* Finger support:: Support for the Finger protocol
* Local files:: Opening local files in elpher
* About pages:: Special pages and how to reference them
+* ANSI support:: Notes in Elpher's ANSI support
* Customization:: How to customize various aspects of Elpher
* Command Index::
* News:: Changes introduced by major releases
-* Acknowledgements:: Contributors to Elpher
+* Contributors:: Contributors to Elpher
@detailmenu
--- The Detailed Node Listing ---
+Installation
+
+* Installing from ELPA or MELPA:: Installing from a package repository
+* Installing by hand:: Installing directly from the source
+
Navigation
* Within-page navigation:: Moving about within a page
* Between-page navigation:: Commands for moving between pages
* History and Caching:: Explanation of how Elpher represents history
+Gemini support
+
+* Client Certificates for Gemini:: Accessing secure gemini pages
+* Hiding preformatted text in text/gemini documents:: An accessibility option
+
+News
+
+* v3.5.0::
+* v3.4.0::
+* v3.3.0::
+* v3.2.0::
+* v3.1.0::
+* v3.0.0::
+
@end detailmenu
@end menu
@@ -122,11 +142,25 @@ have some ideas.
@node Installation, Quick Start, Introduction, Top
@chapter Installation
-Elpher is available from the MELPA package repository. If you have
+@menu
+* Installing from ELPA or MELPA:: Installing from a package repository
+* Installing by hand:: Installing directly from the source
+@end menu
+
+@node Installing from ELPA or MELPA, Installing by hand, Installation, Installation
+@section Installing from ELPA or MELPA
+
+Elpher is available on the non-GNU ELPA package archive. If you are
+using Emacs 28 or later, this archive should be available on your system
+by default. For Emacs 27, you'll need to follow the instructions at
+@url{https://elpa.nongnu.org} to make the archive accessible.
+
+Alternatively, Elpher is available from the MELPA package archive. If you have
never installed packages from this repository before, you'll need
to follow the instructions at @url{https://melpa.org/#/getting-started}.
-@noindent To install Elpher, enter the following:
+Once one of these package archives is installed, enter the following to
+install Elpher:
@example
@kbd{M-x package-install @key{RET} elpher @key{RET}}
@@ -138,6 +172,9 @@ to follow the instructions at @url{https://melpa.org/#/getting-started}.
@kbd{M-x package-delete @key{RET} elpher @key{RET}}.
@end example
+@node Installing by hand, , Installing from ELPA or MELPA, Installation
+@section Installing by hand
+
It is also possible to install Elpher directly by downloading the file
@file{elpher.el} from @url{gopher://thelambdalab.xyz/1/projects/elpher}
(you'll need to download the ``source archive'' and extract it), adding
@@ -310,7 +347,7 @@ causes Elpher to prompt for a filename in which to save the content.
@item
Following links of type `h' with a selector having the `URL:' prefix, or
-unsuported URLs in text files, will result in Elpher using an external
+unsupported URLs in text files, will result in Elpher using an external
programme to open the URL. This will be either the default system browser
or, if the @code{elpher-open-urls-with-eww} customization variable is non-nil,
Emacs' own EWW browser. (See @pxref{Customization}.)
@@ -563,6 +600,12 @@ I should emphasize however that, while it is definitely functional,
Elpher's gemini support is still experimental, and various aspects will
change as the protocol develops further.
+@menu
+* Client Certificates for Gemini:: Accessing secure gemini pages
+* Hiding preformatted text in text/gemini documents:: An accessibility option
+@end menu
+
+@node Client Certificates for Gemini, Hiding preformatted text in text/gemini documents, Gemini support, Gemini support
@section Client Certificates for Gemini
Gemini makes explicit use of the client certificate mechanism that TLS
@@ -613,13 +656,14 @@ Pressing the @key{n} key will cause Elpher to begin the process of
creating a new persistent certificate, using some additional
details for which you will be prompted.
Alternatively, pressing the @key{i} key will cause Elpher to ask for the
-locations of edisting key and certificate files to add to
+locations of existing key and certificate files to add to
@code{elpher-certificate-directory} under the chosen name.
-Once a certificate is selected, it will be used for all subsequent TLS
-transactions to the host for which the certificate was created.
-It is immediately ``forgotten'' when a TLS connection to another host
-is attempted, or the following command is issued:
+Once a certificate is selected, it will be used for all subsequent
+gemini requests involving URLs begining with the URL for for which the
+certificate was created. It is immediately ``forgotten'' when a TLS
+connection to a non-matching URL is attempted, or the following command
+is issued:
@table @asis
@keycmd{@key{F},elpher-forget-certificate}
@@ -630,6 +674,30 @@ In either case, ``forgetting'' means that the details of the key and
certificate file pair are erased from memory. Furthermore, in the case
of throw-away certificates, the corresponding files are deleted.
+Persistant client certificates can be added to the alist contained in the
+customization variable @code{elpher-certificate-map} so that they are
+automatically activated whenever a gemini page with the matching URL
+prefix is visited.
+
+@node Hiding preformatted text in text/gemini documents, , Client Certificates for Gemini, Gemini support
+@section Hiding preformatted text in text/gemini documents
+
+Preformatted text is often used to display ``ASCII art'' or other
+similar text-based artwork. While for many this is a fun way to
+introduce personality into their gemini documents, such text can
+pose difficulties for screen readers.
+
+Setting the @code{elpher-gemini-hide-preformatted} customization option
+to non-nil causes Elpher to hide all preformatted text blocks by
+default. In place of the preformatted text, Elpher instead displays the
+``alt text'' (if any is available), along with a button which can be
+used to make specific blocks visible as required.
+
+Other related customization options are
+@code{elpher-gemini-preformatted-toggle-label}, which is the label used
+for the toggle button, and
+@code{elpher-gemini-preformatted-toggle-bullet}, which is the margin
+string used to distinguish the line replacing the preformatted text.
@node Finger support, Local files, Gemini support, Top
@chapter Finger support
@@ -683,6 +751,11 @@ particular their extension. The current mappings are as follows:
Plain text documents. All local text files are assumed to be
UTF-8-encoded.
+@item @samp{gophermap},@samp{gopher}
+
+Gophermap files, i.e. files containing a valid directory list as specified
+by RFC 1436.
+
@item @samp{gemini},@samp{gmi}
Gemini documents (i.e. documents of MIME type ``text/gemini''). All
@@ -703,12 +776,8 @@ much that elpher can sensibly do with unknown binary files.)
@end table
-Gophermap files (i.e. files containing literally the intended output of
-querying a directory selector according to RFC 1436) cannot yet rendered
-using @samp{file:}.
-
-@node About pages, Customization, Local files, Top
+@node About pages, ANSI support, Local files, Top
@chapter About pages
Like other browsers, elpher makes certain internally-generated pages
@@ -719,7 +788,33 @@ the ``about:'' type.
This means that these pages can be bookmarked and, more usefully,
added to a local file to be rendered as a user-defined start page.
-@node Customization, Command Index, About pages, Top
+To see the address of a special page, simply visit the page and
+press @key{I}.
+
+@node ANSI support, Customization, About pages, Top
+@chapter ANSI support
+
+Depending on which parts of the gopher/gemini universe you frequent,
+you may occasionally stumble on sites which use ANSI escape codes to
+either produce specific characters or to colour text.
+
+By default, elpher uses Emacs' built-in ANSI rendering library,
+@samp{ansi-color}, to process ANSI codes. This robustly interprets
+the escape codes but only supports 8 colours. Any colours unsupported
+by the library are simply stripped, leaving uncoloured text in the
+majority of cases.
+
+To drastically improve the number of colours produced, install the
+@samp{xterm-color} package from MELPA. This package will be automatically
+used by elpher if it is available, and supports 256 colours.
+This should be sufficient to properly render many ANSI colour
+gopher holes and gemini capsules quite nicely.
+
+In case you prefer to completely strip out the ANSI escape codes entirely
+by customizing the @code{elpher-filter-ansi-from-text} variable.
+
+
+@node Customization, Command Index, ANSI support, Top
@chapter Customization
Various parts of Elpher can be customized via the
@@ -757,11 +852,70 @@ See the customization group itself for details.
@printindex fn
-@node News, Acknowledgements, Command Index, Top
+@node News, Contributors, Command Index, Top
@chapter News
This chapter documents the major changes introduced by Elpher releases.
+@menu
+* v3.5.0::
+* v3.4.0::
+* v3.3.0::
+* v3.2.0::
+* v3.1.0::
+* v3.0.0::
+@end menu
+
+@node v3.5.0, v3.4.0, News, News
+@section v3.5.0
+
+@subsection Automatic activation of client certificates in gemini
+
+This version introduces a new customization variable
+@code{elpher-certificate-map} which allows you to pre-specify
+a set of gemini URLs and the client certificates which should
+be used when accessing them.
+
+@xref{Client Certificates for Gemini} for more details.
+
+@node v3.4.0, v3.3.0, v3.5.0, News
+@section v3.4.0
+
+@subsection Toggling preformatted text visibility
+
+This version introduces the ability to hide preformatted text in
+text/gemini documents by default. To enable this, set the customization
+variable @code{elpher-gemini-hide-preformatted} to non-nil. This causes
+all preformated text blocks to be replaced with their ``alt-text'' (if
+any is available) and a button for toggling the visibility of the text
+block.
+
+This feature is intended to make it easier for people using screen
+readers to read text/gemini documents.
+
+@node v3.3.0, v3.2.0, v3.4.0, News
+@section v3.3.0
+
+This version includes lots of bug fixes, as well as a couple of new
+features.
+
+@subsection Local gophermaps
+
+Local gophermaps can now be displayed using @samp{file:}. To render
+correctly, they must have the suffix @samp{.gopher} or @samp{.gophermap}.
+
+@subsection IRI support
+
+Internationalized Resource Identifiers (IRI) are an extension of URLs
+(or, more accurately, URIs) to support a larger set of characters beyond
+those in the US-ASCII character set. They map directly onto URIs for
+backwards compatibility, but look like gibberish if not properly
+decoded. When displaying URLs, Elpher now automatically decodes any IRI
+characters and displays the decoded IRI. (For security reasons, the
+@code{elpher-info-current} command (@kbd{I}) always displays both the
+decoded IRI and the URI when they differ.)
+
+@node v3.2.0, v3.1.0, v3.3.0, News
@section v3.2.0
This version introduces several minor changes which, together, make it
@@ -771,7 +925,7 @@ possible to set up alternative start pages configured to your liking.
Special elpher pages such as the welcome page (previously ``start''
page), the bookmarks page, the browsing history stack and list of
-visited pages are now addressible via @samp{about:} URLs. For instance,
+visited pages are now addressable via @samp{about:} URLs. For instance,
the standard welcome page has the address @samp{about:welcome}.
@subsection Local files
@@ -788,6 +942,7 @@ of the document to be loaded as elpher's ``start page''. By default
this is set to @samp{about:welcome}, but any elpher-accessible URL is
valid. @pxref{Customization} for suggestions.
+@node v3.1.0, v3.0.0, v3.2.0, News
@section v3.1.0
@subsection Bookmarks system
@@ -810,6 +965,7 @@ use the customization variable @code{elpher-use-emacs-bookmark-menu}
to have the @key{B} key open the Emacs bookmark menu directly, as in
the previous release.
+@node v3.0.0, , v3.1.0, News
@section v3.0.0
@subsection Bookmarks system
@@ -853,35 +1009,33 @@ mail system.
Gemini document headers are now navigable via imenu.
For details, @xref{Imenu, , , emacs, The Emacs Editor}.
-@node Acknowledgements, , News, Top
-@chapter Acknowledgements
+@node Contributors, , News, Top
+@chapter Contributors
-Elpher was originally written by Tim Vaughan. Recent maintenance has
-been done by and with the help of Alex Schroeder. In addition, the
-following people (in alphabetical order) have generously provided
-assistance and/or patches:
+Elpher was originally written and is currently maintained by Tim Vaughan
+@email{plugd@@thelambdalab.xyz}. Significant improvements and
+maintenance have also been contributed by and with the help of Alex
+Schroeder @email{alex@@gnu.org}. In addition, the following people have
+all generously provided assistance and/or patches:
@itemize
-@item Alexis
-@item Christopher Brannon
-@item Zhiwei Chen
-@item condy0919
-@item Étienne Deparis
-@item Roy Koushik
-@item Simon Nicolussi
-@item Noodles!
-@item Jens Östlund
-@item Abhiseck Paira
-@item F. Jason Park
-@item Omar Polo
-@item Koushk Roy
-@item Michel Alexandre Salim
-@item Alex Schroeder
-@item Daniel Semyonov
-@item Simon South
-@item Bradley Thornton
-@item Vee
+@item Jens Östlund @email{jostlund@@gmail.com}
+@item F. Jason Park @email{jp@@neverwas.me}
+@item Christopher Brannon @email{chris@@the-brannons.com}
+@item Omar Polo @email{op@@omarpolo.com}
+@item Noodles! @email{nnoodle@@chiru.no}
+@item Abhiseck Paira @email{abhiseckpaira@@disroot.org}
+@item Zhiwei Chen @email{chenzhiwei03@@kuaishou.com}
+@item condy0919 @email{condy0919@@gmail.com}
+@item Alexis @email{flexibeast@@gmail.com}
+@item Étienne Deparis @email{etienne@@depar.is}
+@item Simon Nicolussi @email{sinic@@sinic.name}
+@item Michel Alexandre Salim @email{michel@@michel-slm.name}
+@item Koushk Roy @email{kroy@@twilio.com}
+@item Vee @email{vee@@vnsf.xyz}
+@item Simon South @email{simon@@simonsouth.net}
+@item Daniel Semyonov @email{daniel@@dsemy.com}
+@item Bradley Thornton @email{bradley@@northtech.us}
@end itemize
-
@bye
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/share/doc/elpa-elpher/README.gz -rw-r--r-- root/root /usr/share/emacs/site-lisp/elpa-src/elpher-3.5.0/elpher-autoloads.el -rw-r--r-- root/root /usr/share/emacs/site-lisp/elpa-src/elpher-3.5.0/elpher-pkg.el -rw-r--r-- root/root /usr/share/emacs/site-lisp/elpa-src/elpher-3.5.0/elpher.el
Files in first set of .debs but not in second
-rw-r--r-- root/root /usr/share/doc/elpa-elpher/README -rw-r--r-- root/root /usr/share/emacs/site-lisp/elpa-src/elpher-3.2.2/elpher-autoloads.el -rw-r--r-- root/root /usr/share/emacs/site-lisp/elpa-src/elpher-3.2.2/elpher-pkg.el -rw-r--r-- root/root /usr/share/emacs/site-lisp/elpa-src/elpher-3.2.2/elpher.el
No differences were encountered in the control files