X-Git-Url: https://thelambdalab.xyz/gitweb/index.cgi?p=elpher.git;a=blobdiff_plain;f=elpher.el;h=722027663cc10e4f57f62e94ce07325c2630a4d1;hp=11444b95692f4bf8b5040ca2f44bec664572f815;hb=285a073c36cad19dbf2e157d0314128f67473d4c;hpb=3416632554bfddf6dcdc7afbcf6b7295848944b7 diff --git a/elpher.el b/elpher.el index 11444b9..7220276 100644 --- a/elpher.el +++ b/elpher.el @@ -4,10 +4,10 @@ ;; Author: Tim Vaughan ;; Created: 11 April 2019 -;; Version: 1.4.5 +;; Version: 1.4.7 ;; Keywords: comm gopher ;; Homepage: https://github.com/tgvaughan/elpher -;; Package-Requires: ((emacs "25")) +;; Package-Requires: ((emacs "26")) ;; This file is not part of GNU Emacs. @@ -42,7 +42,7 @@ ;; page containing information on key bindings and suggested starting ;; points for your gopher exploration. -;; Further instructions can be found in the Elpher info manual. +;; Full instructions can be found in the Elpher info manual. ;; Elpher is under active development. Any suggestions for ;; improvements are welcome! @@ -50,59 +50,25 @@ ;;; Code: (provide 'elpher) + +;;; Dependencies +;; + (require 'seq) (require 'pp) (require 'shr) +(require 'url-util) + ;;; Global constants ;; -(defconst elpher-version "1.4.5" +(defconst elpher-version "1.4.7" "Current version of elpher.") (defconst elpher-margin-width 6 "Width of left-hand margin used when rendering indicies.") -(defconst elpher-start-index - (mapconcat - 'identity - (list "i\tfake\tfake\t1" - "i --------------------------------------------\tfake\tfake\t1" - "i Elpher Gopher Client \tfake\tfake\t1" - (format "i version %s\tfake\tfake\t1" elpher-version) - "i --------------------------------------------\tfake\tfake\t1" - "i\tfake\tfake\t1" - "iUsage:\tfake\tfake\t1" - "i\tfake\tfake\t1" - "i - tab/shift-tab: next/prev item on current page\tfake\tfake\t1" - "i - RET/mouse-1: open item under cursor\tfake\tfake\t1" - "i - m: select an item on current page by name (autocompletes)\tfake\tfake\t1" - "i - u: return to parent\tfake\tfake\t1" - "i - O: visit the root menu of the current server\tfake\tfake\t1" - "i - g: go to a particular gopher address\tfake\tfake\t1" - "i - i/I: info on item under cursor or current page\tfake\tfake\t1" - "i - c/C: copy URL representation of item under cursor or current page\tfake\tfake\t1" - "i - a/A: bookmark the item under cursor or current page\tfake\tfake\t1" - "i - x/X: remove bookmark for item under cursor or current page\tfake\tfake\t1" - "i - B: visit the bookmarks page\tfake\tfake\t1" - "i - r: redraw current page (using cached contents if available)\tfake\tfake\t1" - "i - R: reload current page (regenerates cache)\tfake\tfake\t1" - "i - T: toggle TLS mode\tfake\tfake\t1" - "i - d/D: download item under cursor or current page\tfake\tfake\t1" - "i - w: display the raw server response for the current page\tfake\tfake\t1" - "i - S: set an explicit character coding system (default is to autodetect)\tfake\tfake\t1" - "i\tfake\tfake\t1" - "iWhere to start exploring Gopherspace:\tfake\tfake\t1" - "i\tfake\tfake\t1" - "1Floodgap Systems Gopher Server\t/\tgopher.floodgap.com\t70" - "i\tfake\tfake\t1" - "iAlternatively, select the following item and enter some\tfake\tfake\t1" - "isearch terms:\tfake\tfake\t1" - "i\tfake\tfake\t1" - "7Veronica-2 Gopher Search Engine\t/v2/vs\tgopher.floodgap.com\t70" - ".\r\n") - "\r\n") - "Source for elpher start page.") (defconst elpher-type-map '((?0 elpher-get-text-node "txt" elpher-text) @@ -116,6 +82,8 @@ (?p elpher-get-image-node "img" elpher-image) (?I elpher-get-image-node "img" elpher-image) (?d elpher-get-node-download "doc" elpher-binary) + (?P elpher-get-node-download "doc" elpher-binary) + (?s elpher-get-node-download "snd" elpher-binary) (?h elpher-get-url-node "url" elpher-url) (bookmarks elpher-get-bookmarks-node "#" elpher-index) (start elpher-get-start-node "#" elpher-index)) @@ -241,6 +209,29 @@ before attempting to connect to the server." "Return non-nil if ADDRESS is special (e.g. start page, bookmarks page)." (not (elpher-address-host address))) +(defun elpher-get-address-url (address) + "Get URL representation of ADDRESS." + (let ((type (elpher-address-type address)) + (selector (elpher-address-selector address)) + (bare-host (elpher-address-host address)) + (port (elpher-address-port address))) + (url-encode-url + (let ((host (if (string-match-p ":" bare-host) + (concat "[" bare-host "]") + bare-host))) + (if (and (equal type ?h) + (string-prefix-p "URL:" selector)) + (elt (split-string selector "URL:") 1) + (concat "gopher" + (if (elpher-address-use-tls-p address) "s" "") + "://" + host + (if (equal port 70) + "" + (format ":%d" port)) + "/" (string type) + selector)))))) + ;; Node (defun elpher-make-node (display-string address &optional parent) @@ -313,8 +304,12 @@ unless PRESERVE-PARENT is non-nil." (if getter (funcall getter) (let* ((address (elpher-node-address node)) - (type (elpher-address-type address))) - (funcall (car (alist-get type elpher-type-map)))))) + (type (elpher-address-type address)) + (type-record (alist-get type elpher-type-map))) + (if type-record + (funcall (car type-record)) + (elpher-visit-parent-node) + (error "Unsupported gopher selector type '%c'" type))))) (defun elpher-visit-parent-node () "Visit the parent of the current node." @@ -547,7 +542,7 @@ up to the calling function." ;; Text retrieval (defconst elpher-url-regex - "\\([a-zA-Z]+\\)://\\([a-zA-Z0-9.\-]+\\)\\(?3::[0-9]+\\)?\\(?4:/[^ \r\n\t(),]*\\)?" + "\\([a-zA-Z]+\\)://\\([a-zA-Z0-9.\-]+\\|\[[a-zA-Z0-9:]+\]\\)\\(?3::[0-9]+\\)?\\(?4:/[^ \r\n\t(),]*\\)?" "Regexp used to locate and buttinofy URLs in text files loaded by elpher.") (defun elpher-make-node-from-matched-url (&optional string) @@ -559,7 +554,10 @@ calls, as is necessary if the match is performed by `string-match'." (protocol (downcase (match-string 1 string)))) (if (or (string= protocol "gopher") (string= protocol "gophers")) - (let* ((host (match-string 2 string)) + (let* ((bare-host (match-string 2 string)) + (host (if (string-prefix-p "[" bare-host) + (substring bare-host 1 (- (length bare-host) 1)) + bare-host)) (port (if (> (length (match-string 3 string)) 1) (string-to-number (substring (match-string 3 string) 1)) 70)) @@ -567,9 +565,11 @@ calls, as is necessary if the match is performed by `string-match'." (type (if (> (length type-and-selector) 1) (elt type-and-selector 1) ?1)) - (selector (if (> (length type-and-selector) 1) - (substring type-and-selector 2) - "")) + (selector (decode-coding-string + (url-unhex-string + (if (> (length type-and-selector) 1) + (substring type-and-selector 2) + "")) 'utf-8)) (use-tls (string= protocol "gophers")) (address (elpher-make-address type selector host port use-tls))) (elpher-make-node url address)) @@ -693,17 +693,12 @@ calls, as is necessary if the match is performed by `string-match'." (let ((address (elpher-node-address elpher-current-node))) (elpher-with-clean-buffer (insert "LOADING RAW SERVER RESPONSE... (use 'u' to cancel)")) - (if address - (elpher-get-selector address - (lambda (proc event) - (unless (string-prefix-p "deleted" event) - (elpher-with-clean-buffer - (insert elpher-selector-string) - (goto-char (point-min)))))) - (progn - (elpher-with-clean-buffer - (insert elpher-start-index)) - (goto-char (point-min))))) + (elpher-get-selector address + (lambda (proc event) + (unless (string-prefix-p "deleted" event) + (elpher-with-clean-buffer + (insert elpher-selector-string) + (goto-char (point-min))))))) (message "Displaying raw server response. Reload or redraw to return to standard view.")) ;; File export retrieval @@ -789,7 +784,53 @@ calls, as is necessary if the match is performed by `string-match'." (defun elpher-get-start-node () "Getter which displays the start page." (elpher-with-clean-buffer - (elpher-insert-index elpher-start-index) + (insert " --------------------------------------------\n" + " Elpher Gopher Client \n" + " version " elpher-version "\n" + " --------------------------------------------\n" + "\n" + "Default bindings:\n" + "\n" + " - TAB/Shift-TAB: next/prev item on current page\n" + " - RET/mouse-1: open item under cursor\n" + " - m: select an item on current page by name (autocompletes)\n" + " - u: return to previous page\n" + " - o/O: visit different selector or the root menu of the current server\n" + " - g: go to a particular gopher address\n" + " - i/I: info on item under cursor or current page\n" + " - c/C: copy URL representation of item under cursor or current page\n" + " - a/A: bookmark the item under cursor or current page\n" + " - x/X: remove bookmark for item under cursor or current page\n" + " - B: visit the bookmarks page\n" + " - r: redraw current page (using cached contents if available)\n" + " - R: reload current page (regenerates cache)\n" + " - T: toggle TLS mode\n" + " - d/D: download item under cursor or current page\n" + " - .: display the raw server response for the current page\n" + " - S: set an explicit character coding system (default is to autodetect)\n" + "\n" + "Start your exploration of gopher space:\n") + (elpher-insert-index-record "Floodgap Systems Gopher Server" + (elpher-make-address ?1 "" "gopher.floodgap.com" 70)) + (insert "\n" + "Alternatively, select the following item and enter some search terms:\n") + (elpher-insert-index-record "Veronica-2 Gopher Search Engine" + (elpher-make-address ?7 "/v2/vs" "gopher.floodgap.com" 70)) + (insert "\n" + "** Refer to the ") + (let ((help-string "RET,mouse-1: Open Elpher info manual (if available)")) + (insert-text-button "Elpher info manual" + 'face 'link + 'action (lambda (button) + (interactive) + (info "(elpher)")) + 'follow-link t + 'help-echo help-string)) + (insert " for the full documentation. **\n") + (insert (propertize + (concat " (This should be available if you have installed Elpher using\n" + " MELPA. Otherwise you will have to install the manual yourself.)") + 'face 'shadow)) (elpher-restore-pos))) ;; Bookmarks page node retrieval @@ -871,6 +912,7 @@ If ADDRESS is already bookmarked, update the label only." (not (equal (elpher-bookmark-address bookmark) address))) (elpher-load-bookmarks)))) + ;;; Interactive procedures ;; @@ -908,6 +950,19 @@ host, selector and port." (switch-to-buffer "*elpher*") (elpher-visit-node node))) +(defun elpher-go-current () + "Go to a particular site read from the minibuffer, initialized with the current URL." + (interactive) + (let ((address (elpher-node-address elpher-current-node))) + (if (elpher-address-special-p address) + (error "Command not valid for this page") + (let ((url (read-string "URL: " (elpher-get-address-url address)))) + (if (string-match elpher-url-regex url) + (let ((new-node (elpher-make-node-from-matched-url url))) + (unless (equal (elpher-node-address new-node) address) + (elpher-visit-node new-node))) + (error "Could not parse URL %s" url)))))) + (defun elpher-redraw () "Redraw current page." (interactive) @@ -1103,25 +1158,6 @@ host, selector and port." (interactive) (elpher-info-node elpher-current-node)) -(defun elpher-get-address-url (address) - "Get URL representation of ADDRESS." - (let ((type (elpher-address-type address)) - (selector (elpher-address-selector address)) - (host (elpher-address-host address)) - (port (elpher-address-port address))) - (if (and (equal type ?h) - (string-prefix-p "URL:" selector)) - (elt (split-string selector "URL:") 1) - (concat "gopher" - (if (elpher-address-use-tls-p address) "s" "") - "://" - host - (if (equal port 70) - "" - (format ":%d" port)) - "/" (string type) - selector)))) - (defun elpher-copy-node-url (node) "Copy URL representation of address of NODE to `kill-ring'." (let ((address (elpher-node-address node))) @@ -1153,6 +1189,7 @@ host, selector and port." (message "Coding system fixed to %s. (Reload to see effect)." system) (message "Coding system set to autodetect. (Reload to see effect).")))) + ;;; Mode and keymap ;; @@ -1163,10 +1200,11 @@ host, selector and port." (define-key map (kbd "u") '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 "r") 'elpher-redraw) (define-key map (kbd "R") 'elpher-reload) (define-key map (kbd "T") 'elpher-toggle-tls) - (define-key map (kbd "w") 'elpher-view-raw) + (define-key map (kbd ".") 'elpher-view-raw) (define-key map (kbd "d") 'elpher-download) (define-key map (kbd "D") 'elpher-download-current) (define-key map (kbd "m") 'elpher-jump) @@ -1188,10 +1226,11 @@ host, selector and port." (kbd "u") 'elpher-back (kbd "O") 'elpher-root-dir (kbd "g") 'elpher-go + (kbd "o") 'elpher-go-current (kbd "r") 'elpher-redraw (kbd "R") 'elpher-reload (kbd "T") 'elpher-toggle-tls - (kbd "w") 'elpher-view-raw + (kbd ".") 'elpher-view-raw (kbd "d") 'elpher-download (kbd "D") 'elpher-download-current (kbd "m") 'elpher-jump @@ -1218,6 +1257,7 @@ functions which initialize the gopher client, namely (when (fboundp 'evil-set-initial-state) (evil-set-initial-state 'elpher-mode 'motion)) + ;;; Main start procedure ;;