Added URL to header.
[elpher.git] / elpher.el
index 9af26de..9c0138b 100644 (file)
--- a/elpher.el
+++ b/elpher.el
@@ -4,7 +4,7 @@
 
 ;; Author: Tim Vaughan <tgvaughan@gmail.com>
 ;; Created: 11 April 2019
 
 ;; Author: Tim Vaughan <tgvaughan@gmail.com>
 ;; Created: 11 April 2019
-;; Version: 2.0.0
+;; Version: 2.2.0
 ;; Keywords: comm gopher
 ;; Homepage: https://github.com/tgvaughan/elpher
 ;; Package-Requires: ((emacs "26"))
 ;; Keywords: comm gopher
 ;; Homepage: https://github.com/tgvaughan/elpher
 ;; Package-Requires: ((emacs "26"))
@@ -65,7 +65,7 @@
 ;;; Global constants
 ;;
 
 ;;; Global constants
 ;;
 
-(defconst elpher-version "2.0.0"
+(defconst elpher-version "2.2.0"
   "Current version of elpher.")
 
 (defconst elpher-margin-width 6
   "Current version of elpher.")
 
 (defconst elpher-margin-width 6
@@ -77,7 +77,7 @@
     ((gopher ?4) elpher-get-gopher-node elpher-render-download "bin" elpher-binary)
     ((gopher ?5) elpher-get-gopher-node elpher-render-download "bin" elpher-binary)
     ((gopher ?7) elpher-get-gopher-query-node elpher-render-index "?" elpher-search)
     ((gopher ?4) elpher-get-gopher-node elpher-render-download "bin" elpher-binary)
     ((gopher ?5) elpher-get-gopher-node elpher-render-download "bin" elpher-binary)
     ((gopher ?7) elpher-get-gopher-query-node elpher-render-index "?" elpher-search)
-    ((gopher ?9) elpher-get-gopher-node elpher-render-node-download "bin" elpher-binary)
+    ((gopher ?9) elpher-get-gopher-node elpher-render-download "bin" elpher-binary)
     ((gopher ?g) elpher-get-gopher-node elpher-render-image "img" elpher-image)
     ((gopher ?p) elpher-get-gopher-node elpher-render-image "img" elpher-image)
     ((gopher ?I) elpher-get-gopher-node elpher-render-image "img" elpher-image)
     ((gopher ?g) elpher-get-gopher-node elpher-render-image "img" elpher-image)
     ((gopher ?p) elpher-get-gopher-node elpher-render-image "img" elpher-image)
     ((gopher ?I) elpher-get-gopher-node elpher-render-image "img" elpher-image)
 Otherwise, use the system browser via the BROWSE-URL function."
   :type '(boolean))
 
 Otherwise, use the system browser via the BROWSE-URL function."
   :type '(boolean))
 
-(defcustom elpher-buttonify-urls-in-directories nil
+(defcustom elpher-buttonify-urls-in-directories t
   "If non-nil, turns URLs matched in directories into clickable buttons."
   :type '(boolean))
 
   "If non-nil, turns URLs matched in directories into clickable buttons."
   :type '(boolean))
 
@@ -191,13 +191,14 @@ allows switching from an encrypted channel back to plain text without user input
   (let ((data (match-data))) ; Prevent parsing clobbering match data
     (unwind-protect
         (let ((url (url-generic-parse-url url-string)))
   (let ((data (match-data))) ; Prevent parsing clobbering match data
     (unwind-protect
         (let ((url (url-generic-parse-url url-string)))
-          (setf (url-fullness url) t)
-          (setf (url-filename url)
-                (url-unhex-string (url-filename url)))
-          (unless (url-type url)
-            (setf (url-type url) "gopher"))
-          (when (or (equal "gopher" (url-type url))
-                    (equal "gophers" (url-type url)))
+          (unless (and (not (url-fullness url)) (url-type url))
+            (setf (url-fullness url) t)
+            (setf (url-filename url)
+                  (url-unhex-string (url-filename url)))
+            (unless (url-type url)
+              (setf (url-type url) "gopher"))
+            (when (or (equal "gopher" (url-type url))
+                      (equal "gophers" (url-type url)))
               ;; Gopher defaults
               (unless (url-host url)
                 (setf (url-host url) (url-filename url))
               ;; Gopher defaults
               (unless (url-host url)
                 (setf (url-host url) (url-filename url))
@@ -205,6 +206,10 @@ allows switching from an encrypted channel back to plain text without user input
               (when (or (equal (url-filename url) "")
                         (equal (url-filename url) "/"))
                 (setf (url-filename url) "/1")))
               (when (or (equal (url-filename url) "")
                         (equal (url-filename url) "/"))
                 (setf (url-filename url) "/1")))
+            (when (equal "gemini" (url-type url))
+              ;; Gemini defaults
+              (if (equal (url-filename url) "")
+                  (setf (url-filename url) "/"))))
           url)
       (set-match-data data))))
 
           url)
       (set-match-data data))))
 
@@ -213,15 +218,22 @@ allows switching from an encrypted channel back to plain text without user input
 The basic attributes include: TYPE, SELECTOR, HOST and PORT.
 If the optional attribute TLS is non-nil, the address will be marked as
 requiring gopher-over-TLS."
 The basic attributes include: TYPE, SELECTOR, HOST and PORT.
 If the optional attribute TLS is non-nil, the address will be marked as
 requiring gopher-over-TLS."
-  (if (and (equal type ?h)
-           (string-prefix-p "URL:" selector))
-      (elpher-address-from-url (elt (split-string selector "URL:") 1))
+  (cond
+   ((and (equal type ?h)
+         (string-prefix-p "URL:" selector))
+    (elpher-address-from-url (elt (split-string selector "URL:") 1)))
+   ((equal type ?8)
+    (elpher-address-from-url
+     (concat "telnet"
+             "://" host
+             ":" (number-to-string port))))
+   (t
     (elpher-address-from-url
      (concat "gopher" (if tls "s" "")
              "://" host
              ":" (number-to-string port)
              "/" (string type)
     (elpher-address-from-url
      (concat "gopher" (if tls "s" "")
              "://" host
              ":" (number-to-string port)
              "/" (string type)
-             selector))))
+             selector)))))
 
 (defun elpher-make-special-address (type)
   "Create an ADDRESS object corresponding to the given special page symbol TYPE."
 
 (defun elpher-make-special-address (type)
   "Create an ADDRESS object corresponding to the given special page symbol TYPE."
@@ -234,7 +246,9 @@ requiring gopher-over-TLS."
     nil))
 
 (defun elpher-address-type (address)
     nil))
 
 (defun elpher-address-type (address)
-  "Retrieve selector type from ADDRESS object."
+  "Retrieve type of ADDRESS object.
+This is used to determine how to retrieve and render the document the
+address refers to, via the table `elpher-type-map'."
   (if (symbolp address)
       (list 'special address)
     (let ((protocol (url-type address)))
   (if (symbolp address)
       (list 'special address)
     (let ((protocol (url-type address)))
@@ -246,6 +260,8 @@ requiring gopher-over-TLS."
                      (string-to-char (substring (url-filename address) 1)))))
             ((equal protocol "gemini")
              'gemini)
                      (string-to-char (substring (url-filename address) 1)))))
             ((equal protocol "gemini")
              'gemini)
+            ((equal protocol "telnet")
+             'telnet)
             (t 'other-url)))))
 
 (defun elpher-address-protocol (address)
             (t 'other-url)))))
 
 (defun elpher-address-protocol (address)
@@ -267,7 +283,15 @@ For gopher addresses this is a combination of the selector type and selector."
 
 (defun elpher-address-port (address)
   "Retrieve port from ADDRESS object."
 
 (defun elpher-address-port (address)
   "Retrieve port from ADDRESS object."
-  (url-port address))
+  (if (symbolp address)
+      nil)
+  (if (> (url-port address) 0)
+      (url-port address)
+    (or (and (or (equal (url-type address) "gopher")
+                 (equal (url-type address) "gophers"))
+             70)
+        (and (equal (url-type address) "gemini")
+             1965))))
 
 (defun elpher-address-special-p (address)
   "Return non-nil if ADDRESS object is special (e.g. start page, bookmarks page)."
 
 (defun elpher-address-special-p (address)
   "Return non-nil if ADDRESS object is special (e.g. start page, bookmarks page)."
@@ -400,7 +424,12 @@ unless PRESERVE-PARENT is non-nil."
 (defun elpher-update-header ()
   "If `elpher-use-header' is true, display current node info in window header."
   (if elpher-use-header
 (defun elpher-update-header ()
   "If `elpher-use-header' is true, display current node info in window header."
   (if elpher-use-header
-      (setq header-line-format (elpher-node-display-string elpher-current-node))))
+      (let* ((display-string (elpher-node-display-string elpher-current-node))
+             (address (elpher-node-address elpher-current-node))
+             (url-string (if (elpher-address-special-p address)
+                             ""
+                           (concat "  -  " (elpher-address-to-url address) ""))))
+        (setq header-line-format (list display-string url-string)))))
 
 (defmacro elpher-with-clean-buffer (&rest args)
   "Evaluate ARGS with a clean *elpher* buffer as current."
 
 (defmacro elpher-with-clean-buffer (&rest args)
   "Evaluate ARGS with a clean *elpher* buffer as current."
@@ -479,9 +508,7 @@ up to the calling function."
              (proc (open-network-stream "elpher-process"
                                        nil
                                        (elpher-address-host address)
              (proc (open-network-stream "elpher-process"
                                        nil
                                        (elpher-address-host address)
-                                       (if (> (elpher-address-port address) 0)
-                                           (elpher-address-port address)
-                                         70)
+                                       (elpher-address-port address)
                                        :type (if elpher-use-tls 'tls 'plain))))
         (set-process-coding-system proc 'binary)
         (set-process-filter proc
                                        :type (if elpher-use-tls 'tls 'plain))))
         (set-process-coding-system proc 'binary)
         (set-process-filter proc
@@ -587,11 +614,11 @@ If ADDRESS is not supplied or nil the record is rendered as an
       (pcase type
         ((or '(gopher ?i) 'nil) ;; Information
          (elpher-insert-margin)
       (pcase type
         ((or '(gopher ?i) 'nil) ;; Information
          (elpher-insert-margin)
-         (insert (propertize
-                  (if elpher-buttonify-urls-in-directories
-                      (elpher-buttonify-urls display-string)
-                    display-string)
-                  'face 'elpher-info)))
+         (let ((propertized-display-string
+                (propertize display-string 'face 'elpher-info)))
+           (insert (if elpher-buttonify-urls-in-directories
+                       (elpher-buttonify-urls propertized-display-string)
+                     propertized-display-string))))
         (`(gopher ,selector-type) ;; Unknown
          (elpher-insert-margin (concat (char-to-string selector-type) "?"))
          (insert (propertize display-string
         (`(gopher ,selector-type) ;; Unknown
          (elpher-insert-margin (concat (char-to-string selector-type) "?"))
          (insert (propertize display-string
@@ -604,7 +631,7 @@ If ADDRESS is not supplied or nil the record is rendered as an
     (elpher-visit-node node)))
 
 (defun elpher-render-index (data &optional _mime-type-string)
     (elpher-visit-node node)))
 
 (defun elpher-render-index (data &optional _mime-type-string)
-  "Render DATA as an index."
+  "Render DATA as an index.  MIME-TYPE-STRING is unused."
   (elpher-with-clean-buffer
    (if (not data)
        t
   (elpher-with-clean-buffer
    (if (not data)
        t
@@ -631,11 +658,12 @@ If ADDRESS is not supplied or nil the record is rendered as an
                             'elpher-node  node
                             'action #'elpher-click-link
                             'follow-link t
                             'elpher-node  node
                             'action #'elpher-click-link
                             'follow-link t
-                            'help-echo (elpher-node-button-help node))))
+                            'help-echo (elpher-node-button-help node)
+                            'face 'button)))
     (buffer-string)))
 
 (defun elpher-render-text (data &optional _mime-type-string)
     (buffer-string)))
 
 (defun elpher-render-text (data &optional _mime-type-string)
-  "Render DATA as text."
+  "Render DATA as text.  MIME-TYPE-STRING is unused."
   (elpher-with-clean-buffer
    (if (not data)
        t
   (elpher-with-clean-buffer
    (if (not data)
        t
@@ -647,7 +675,7 @@ If ADDRESS is not supplied or nil the record is rendered as an
 ;; Image retrieval
 
 (defun elpher-render-image (data &optional _mime-type-string)
 ;; Image retrieval
 
 (defun elpher-render-image (data &optional _mime-type-string)
-  "Display DATA as image."
+  "Display DATA as image.  MIME-TYPE-STRING is unused."
   (if (not data)
       nil
     (if (display-images-p)
   (if (not data)
       nil
     (if (display-images-p)
@@ -696,7 +724,7 @@ The response is rendered using the rendering function RENDERER."
 ;; Raw server response rendering
 
 (defun elpher-render-raw (data &optional _mime-type-string)
 ;; Raw server response rendering
 
 (defun elpher-render-raw (data &optional _mime-type-string)
-  "Display raw DATA in buffer."
+  "Display raw DATA in buffer.  MIME-TYPE-STRING is unused."
   (if (not data)
       nil
     (elpher-with-clean-buffer
   (if (not data)
       nil
     (elpher-with-clean-buffer
@@ -707,7 +735,7 @@ The response is rendered using the rendering function RENDERER."
 ;; File save "rendering"
 
 (defun elpher-render-download (data &optional _mime-type-string)
 ;; File save "rendering"
 
 (defun elpher-render-download (data &optional _mime-type-string)
-  "Save DATA to file."
+  "Save DATA to file.  MIME-TYPE-STRING is unused."
   (if (not data)
       nil
     (let* ((address (elpher-node-address elpher-current-node))
   (if (not data)
       nil
     (let* ((address (elpher-node-address elpher-current-node))
@@ -727,7 +755,7 @@ The response is rendered using the rendering function RENDERER."
 ;; HTML rendering
 
 (defun elpher-render-html (data &optional _mime-type-string)
 ;; HTML rendering
 
 (defun elpher-render-html (data &optional _mime-type-string)
-  "Render DATA as HTML using shr."
+  "Render DATA as HTML using shr.  MIME-TYPE-STRING is unused."
   (elpher-with-clean-buffer
    (if (not data)
        t
   (elpher-with-clean-buffer
    (if (not data)
        t
@@ -750,9 +778,7 @@ The response is stored in the variable ‘elpher-gemini-response’."
            (proc (open-network-stream "elpher-process"
                                       nil
                                       (elpher-address-host address)
            (proc (open-network-stream "elpher-process"
                                       nil
                                       (elpher-address-host address)
-                                      (if (> (elpher-address-port address) 0)
-                                          (elpher-address-port address)
-                                        1965)
+                                      (elpher-address-port address)
                                       :type 'tls)))
       (set-process-coding-system proc 'binary)
       (set-process-filter proc
                                       :type 'tls)))
       (set-process-coding-system proc 'binary)
       (set-process-filter proc
@@ -841,7 +867,7 @@ The response is assumed to be in the variable `elpher-gemini-response'."
                                       (string-empty-p mime-type-string))
                                   "text/gemini; charset=utf-8"
                                 mime-type-string))
                                       (string-empty-p mime-type-string))
                                   "text/gemini; charset=utf-8"
                                 mime-type-string))
-           (mime-type-split (split-string mime-type-string* ";"))
+           (mime-type-split (split-string mime-type-string* ";" t))
            (mime-type (string-trim (car mime-type-split)))
            (parameters (mapcar (lambda (s)
                                  (let ((key-val (split-string s "=")))
            (mime-type (string-trim (car mime-type-split)))
            (parameters (mapcar (lambda (s)
                                  (let ((key-val (split-string s "=")))
@@ -883,7 +909,7 @@ The response is assumed to be in the variable `elpher-gemini-response'."
   (let ((address (url-generic-parse-url url)))
     (unless (and (url-type address) (not (url-fullness address))) ;avoid mangling mailto: urls
       (setf (url-fullness address) t)
   (let ((address (url-generic-parse-url url)))
     (unless (and (url-type address) (not (url-fullness address))) ;avoid mangling mailto: urls
       (setf (url-fullness address) t)
-      (unless (url-host address) ;if there is an explicit host, filenames are explicit
+      (unless (url-host address) ;if there is an explicit host, filenames are absolute
         (setf (url-host address) (url-host (elpher-node-address elpher-current-node)))
         (unless (string-prefix-p "/" (url-filename address)) ;deal with relative links
           (setf (url-filename address)
         (setf (url-host address) (url-host (elpher-node-address elpher-current-node)))
         (unless (string-prefix-p "/" (url-filename address)) ;deal with relative links
           (setf (url-filename address)
@@ -911,7 +937,7 @@ The response is assumed to be in the variable `elpher-gemini-response'."
     (buffer-string))))
 
 (defun elpher-render-gemini-plain-text (data _parameters)
     (buffer-string))))
 
 (defun elpher-render-gemini-plain-text (data _parameters)
-  "Render DATA as plain text file."
+  "Render DATA as plain text file.  PARAMETERS is currently unused."
   (elpher-with-clean-buffer
    (insert (elpher-buttonify-urls data))
    (elpher-cache-content
   (elpher-with-clean-buffer
    (insert (elpher-buttonify-urls data))
    (elpher-cache-content
@@ -1208,7 +1234,7 @@ If ADDRESS is already bookmarked, update the label only."
   (let ((link-map nil)
         (b (next-button (point-min) t)))
     (while b
   (let ((link-map nil)
         (b (next-button (point-min) t)))
     (while b
-      (add-to-list 'link-map (cons (button-label b) b))
+      (push (cons (button-label b) b) link-map)
       (setq b (next-button (button-start b))))
     link-map))
 
       (setq b (next-button (button-start b))))
     link-map))
 
@@ -1368,6 +1394,7 @@ If ADDRESS is already bookmarked, update the label only."
     (define-key map (kbd "TAB") 'elpher-next-link)
     (define-key map (kbd "<backtab>") 'elpher-prev-link)
     (define-key map (kbd "u") 'elpher-back)
     (define-key map (kbd "TAB") 'elpher-next-link)
     (define-key map (kbd "<backtab>") 'elpher-prev-link)
     (define-key map (kbd "u") 'elpher-back)
+    (define-key map [mouse-3] 'elpher-back)
     (define-key map (kbd "O") 'elpher-root-dir)
     (define-key map (kbd "g") 'elpher-go)
     (define-key map (kbd "o") 'elpher-go-current)
     (define-key map (kbd "O") 'elpher-root-dir)
     (define-key map (kbd "g") 'elpher-go)
     (define-key map (kbd "o") 'elpher-go-current)
@@ -1394,6 +1421,7 @@ If ADDRESS is already bookmarked, update the label only."
         (kbd "C-") 'elpher-follow-current-link
         (kbd "C-t") 'elpher-back
         (kbd "u") 'elpher-back
         (kbd "C-") 'elpher-follow-current-link
         (kbd "C-t") 'elpher-back
         (kbd "u") 'elpher-back
+        [mouse-3] 'elpher-back
         (kbd "g") 'elpher-go
         (kbd "o") 'elpher-go-current
         (kbd "r") 'elpher-redraw
         (kbd "g") 'elpher-go
         (kbd "o") 'elpher-go-current
         (kbd "r") 'elpher-redraw
@@ -1417,7 +1445,7 @@ If ADDRESS is already bookmarked, update the label only."
   "Keymap for gopher client.")
 
 (define-derived-mode elpher-mode special-mode "elpher"
   "Keymap for gopher client.")
 
 (define-derived-mode elpher-mode special-mode "elpher"
-  "Major mode for elpher, an elisp gopher client.)))))))
+  "Major mode for elpher, an elisp gopher client.
 
 This mode is automatically enabled by the interactive
 functions which initialize the gopher client, namely
 
 This mode is automatically enabled by the interactive
 functions which initialize the gopher client, namely