Updated info documentation.
[elpher.git] / elpher.el
index f1e7fb8..ddcf10a 100644 (file)
--- a/elpher.el
+++ b/elpher.el
@@ -4,7 +4,7 @@
 
 ;; Author: Tim Vaughan <timv@ughan.xyz>
 ;; Created: 11 April 2019
-;; Version: 2.6.1
+;; Version: 2.7.0
 ;; Keywords: comm gopher
 ;; Homepage: http://thelambdalab.xyz/elpher
 ;; Package-Requires: ((emacs "26"))
 (require 'subr-x)
 (require 'dns)
 (require 'ansi-color)
+(require 'nsm)
 
 
 ;;; Global constants
 ;;
 
-(defconst elpher-version "2.6.1"
+(defconst elpher-version "2.7.0"
   "Current version of elpher.")
 
 (defconst elpher-margin-width 6
   "A gopher client."
   :group 'applications)
 
+;; General appearance and customizations
+
+(defcustom elpher-open-urls-with-eww nil
+  "If non-nil, open URL selectors using eww.
+Otherwise, use the system browser via the BROWSE-URL function."
+  :type '(boolean))
+
+(defcustom elpher-use-header t
+  "If non-nil, display current page information in buffer header."
+  :type '(boolean))
+
+(defcustom elpher-auto-disengage-TLS nil
+  "If non-nil, automatically disengage TLS following an unsuccessful connection.
+While enabling this may seem convenient, it is also potentially dangerous as it
+allows switching from an encrypted channel back to plain text without user input."
+  :type '(boolean))
+
+(defcustom elpher-connection-timeout 5
+  "Specifies the number of seconds to wait for a network connection to time out."
+  :type '(integer))
+
+(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."
+  :type '(boolean))
+
+(defcustom elpher-gemini-TLS-cert-checks nil
+  "If non-nil, verify gemini server TLS certificates using the default
+emacs security protocol. Otherwise, certificate verification is disabled.
+
+This defaults to off because it is standard practice for Gemini servers
+to use self-signed certificates, meaning that most servers provide what
+emacs considers to be an invalid certificate."
+  :type '(boolean))
+
+(defcustom elpher-gemini-max-fill-width 80
+  "Specify the maximum default width (in columns) of text/gemini documents.
+The actual width used is the minimum of this value and the window width at
+the time when the text is rendered."
+  :type '(integer))
+
 ;; Face customizations
 
+(defgroup elpher-faces nil
+  "Elpher face customizations."
+  :group 'elpher)
+
 (defface elpher-index
   '((t :inherit font-lock-keyword-face))
   "Face used for directory type directory records.")
 
 (defface elpher-gemini
   '((t :inherit font-lock-regexp-grouping-backslash))
-  "Face used for html type directory records.")
+  "Face used for Gemini type directory records.")
 
 (defface elpher-other-url
   '((t :inherit font-lock-comment-face))
   '((t :inherit shadow))
   "Face used for brackets around directory margin key.")
 
-;; Other customizations
-
-(defcustom elpher-open-urls-with-eww nil
-  "If non-nil, open URL selectors using eww.
-Otherwise, use the system browser via the BROWSE-URL function."
-  :type '(boolean))
-
-(defcustom elpher-use-header t
-  "If non-nil, display current page information in buffer header."
-  :type '(boolean))
-
-(defcustom elpher-auto-disengage-TLS nil
-  "If non-nil, automatically disengage TLS following an unsuccessful connection.
-While enabling this may seem convenient, it is also potentially dangerous as it
-allows switching from an encrypted channel back to plain text without user input."
-  :type '(boolean))
+(defface elpher-gemini-heading1
+  '((t :inherit bold :height 1.8))
+  "Face used for brackets around directory margin key.")
 
-(defcustom elpher-connection-timeout 5
-  "Specifies the number of seconds to wait for a network connection to time out."
-  :type '(integer))
+(defface elpher-gemini-heading2
+  '((t :inherit bold :height 1.5))
+  "Face used for brackets around directory margin key.")
 
-(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."
-  :type '(boolean))
+(defface elpher-gemini-heading3
+  '((t :inherit bold :height 1.2))
+  "Face used for brackets around directory margin key.")
 
 ;;; Model
 ;;
@@ -434,6 +466,8 @@ unless NO-HISTORY is non-nil."
   (list 'with-current-buffer "*elpher*"
         '(elpher-mode)
         (append (list 'let '((inhibit-read-only t))
+                      '(setq-local network-security-level
+                                   (default-value 'network-security-level))
                       '(erase-buffer)
                       '(elpher-update-header))
                 args)))
@@ -506,7 +540,7 @@ to ADDRESS."
       (let* ((kill-buffer-query-functions nil)
              (port (elpher-address-port address))
              (host (elpher-address-host address))
-             (selector-string "")
+             (selector-string-parts nil)
              (proc (open-network-stream "elpher-process"
                                         nil
                                         (if force-ipv4 (dns-query host) host)
@@ -538,8 +572,8 @@ to ADDRESS."
         (set-process-filter proc
                             (lambda (_proc string)
                               (cancel-timer timer)
-                              (setq selector-string
-                                    (concat selector-string string))))
+                              (setq selector-string-parts
+                                    (cons string selector-string-parts))))
         (set-process-sentinel proc
                               (lambda (_proc event)
                                 (condition-case the-error
@@ -553,7 +587,8 @@ to ADDRESS."
                                                  "\r\n"))))
                                      (t
                                       (cancel-timer timer)
-                                      (funcall renderer selector-string)
+                                      (funcall renderer (apply #'concat
+                                                               (reverse selector-string-parts)))
                                       (elpher-restore-pos)))
                                   (error
                                    (elpher-network-error address the-error))))))
@@ -803,6 +838,8 @@ The response is rendered using the rendering function RENDERER."
   "Retrieve gemini ADDRESS, then render using RENDERER.
 If FORCE-IPV4 is non-nil, explicitly look up and use IPv4 address corresponding
 to ADDRESS."
+  (unless elpher-gemini-TLS-cert-checks
+    (setq-local network-security-level 'low))
   (if (not (gnutls-available-p))
       (error "Cannot establish gemini connection: GnuTLS not available")
     (unless (< (elpher-address-port address) 65536)
@@ -1015,6 +1052,7 @@ For instance, the filename /a/b/../c/./d will reduce to /a/c/d"
     address))
 
 (defun elpher-gemini-insert-link (link-line)
+  "Insert link into a text/gemini document."
   (let* ((url (elpher-gemini-get-link-url link-line))
          (display-string (let ((s (elpher-gemini-get-link-display-string link-line)))
                            (if (string-empty-p s) url s)))
@@ -1036,22 +1074,34 @@ For instance, the filename /a/b/../c/./d will reduce to /a/c/d"
     (insert "\n")))
   
 (defun elpher-gemini-insert-header (header-line)
-  (insert header-line "\n"))
-
-(defun elpher--trim-prefix-p (prefix string)
-  (string-prefix-p prefix (string-trim-left string)))
+  "Insert header into a text/gemini document.
+The gemini map file line describing the header is given
+by HEADER-LINE."
+  (when (string-match "^\\(#+\\)[ \t]*" header-line)
+    (let ((level (length (match-string 1 header-line)))
+          (header (substring header-line (match-end 0))))
+      (unless (display-graphic-p)
+        (insert (make-string level ?#) " "))
+      (insert (propertize header 'face
+                          (case level
+                            ((1) 'elpher-gemini-heading1)
+                            ((2) 'elpher-gemini-heading2)
+                            ((3) 'elpher-gemini-heading3)
+                            (t 'default)))
+              "\n"))))
 
 (defun elpher-render-gemini-map (data _parameters)
   "Render DATA as a gemini map file, PARAMETERS is currently unused."
   (elpher-with-clean-buffer
    (let ((preformatted nil))
      (auto-fill-mode 1)
+     (setq-local fill-column (min (window-width) elpher-gemini-max-fill-width))
      (dolist (line (split-string data "\n"))
        (cond
-        ((elpher--trim-prefix-p "```" line) (setq preformatted (not preformatted)))
+        ((string-prefix-p "```" line) (setq preformatted (not preformatted)))
         (preformatted (insert (elpher-process-text-for-display line) "\n"))
-        ((elpher--trim-prefix-p "=>" line) (elpher-gemini-insert-link line))
-        ((elpher--trim-prefix-p "#" line) (elpher-gemini-insert-header line))
+        ((string-prefix-p "=>" line) (elpher-gemini-insert-link line))
+        ((string-prefix-p "#" line) (elpher-gemini-insert-header line))
         (t (insert (elpher-process-text-for-display line)) (newline)))))
    (elpher-cache-content
     (elpher-page-address elpher-current-page)
@@ -1086,7 +1136,7 @@ For instance, the filename /a/b/../c/./d will reduce to /a/c/d"
                  (port (let ((given-port (elpher-address-port address)))
                          (if (> given-port 0) given-port 79)))
                  (host (elpher-address-host address))
-                 (selector-string "")
+                 (selector-string-parts nil)
                  (proc (open-network-stream "elpher-process"
                                             nil
                                             (if force-ipv4 (dns-query host) host)
@@ -1107,8 +1157,8 @@ For instance, the filename /a/b/../c/./d will reduce to /a/c/d"
             (set-process-filter proc
                                 (lambda (_proc string)
                                   (cancel-timer timer)
-                                  (setq selector-string
-                                        (concat selector-string string))))
+                                  (setq selector-string-parts
+                                        (cons string selector-string-parts))))
             (set-process-sentinel proc
                                   (lambda (_proc event)
                                     (condition-case the-error
@@ -1121,7 +1171,8 @@ For instance, the filename /a/b/../c/./d will reduce to /a/c/d"
                                              (concat user "\r\n"))))
                                          (t
                                           (cancel-timer timer)
-                                          (funcall renderer selector-string)
+                                          (funcall renderer (apply #'concat
+                                                                   (reverse selector-string-parts)))
                                           (elpher-restore-pos)))))))
         (error
          (elpher-network-error address the-error))))))