X-Git-Url: https://thelambdalab.xyz/gitweb/index.cgi?a=blobdiff_plain;f=elpher.el;h=56d82c4526a0d5aa5723f1ef26a353dc5da73d49;hb=7106f9887e1bd5b1f46fcbd8d0d7cdcedc9d14a6;hp=4b08a503845a23b1fa9acc5deb572edebd70a0e1;hpb=eb3e211429970e06f91443e6193fb33c5b237998;p=elpher.git diff --git a/elpher.el b/elpher.el index 4b08a50..56d82c4 100644 --- a/elpher.el +++ b/elpher.el @@ -4,7 +4,7 @@ ;; Author: Tim Vaughan ;; Created: 11 April 2019 -;; Version: 1.2.0 +;; Version: 1.3.0 ;; Keywords: comm gopher ;; Homepage: https://github.com/tgvaughan/elpher ;; Package-Requires: ((emacs "25")) @@ -57,7 +57,7 @@ ;;; Global constants ;; -(defconst elpher-version "1.2.0" +(defconst elpher-version "1.3.0" "Current version of elpher.") (defconst elpher-margin-width 6 @@ -79,7 +79,7 @@ "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 menu or item\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" @@ -89,6 +89,7 @@ "i - R: reload current page (regenerates cache)\tfake\tfake\t1" "i - d: download directory entry under cursor\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" @@ -103,18 +104,18 @@ "Source for elpher start page.") (defconst elpher-type-map - '((?0 elpher-get-text-node "T" elpher-text) + '((?0 elpher-get-text-node "txt" elpher-text) (?1 elpher-get-index-node "/" elpher-index) - (?4 elpher-get-node-download "B" elpher-binary) - (?5 elpher-get-node-download "B" elpher-binary) + (?4 elpher-get-node-download "bin" elpher-binary) + (?5 elpher-get-node-download "bin" elpher-binary) (?7 elpher-get-search-node "?" elpher-search) - (?8 elpher-get-telnet-node "?" elpher-telnet) - (?9 elpher-get-node-download "B" elpher-binary) - (?g elpher-get-image-node "im" elpher-image) - (?p elpher-get-image-node "im" elpher-image) - (?I elpher-get-image-node "im" elpher-image) - (?d elpher-get-node-download "d" elpher-binary) - (?h elpher-get-url-node "W" elpher-url) + (?8 elpher-get-telnet-node "tel" elpher-telnet) + (?9 elpher-get-node-download "bin" elpher-binary) + (?g elpher-get-image-node "img" elpher-image) + (?p elpher-get-image-node "img" elpher-image) + (?I elpher-get-image-node "img" elpher-image) + (?d elpher-get-node-download "doc" elpher-binary) + (?h elpher-get-url-node "web" elpher-url) (bookmarks elpher-get-bookmarks-node "#" elpher-index) (start elpher-get-start-node "#" elpher-index)) "Association list from types to getters, margin codes and index faces.") @@ -225,27 +226,35 @@ special address types, such as 'start for the start page." ;; Node -(defun elpher-make-node (display-string parent address) +(defun elpher-make-node (display-string address &optional parent) "Create a node in the gopher page hierarchy. DISPLAY-STRING records the display string used for the page. -PARENT specifies the parent of the node, and ADDRESS specifies the -address of the gopher page." - (list display-string parent address)) +ADDRESS specifies the address of the gopher page. + +The optional PARENT specifies the parent node in the hierarchy. +This is set every time the node is visited, so while it forms +an important part of the node data there is no need to set it +initially." + (list display-string address parent)) (defun elpher-node-display-string (node) "Retrieve the display string of NODE." (elt node 0)) -(defun elpher-node-parent (node) - "Retrieve the parent node of NODE." - (elt node 1)) - (defun elpher-node-address (node) "Retrieve the address of NODE." + (elt node 1)) + +(defun elpher-node-parent (node) + "Retrieve the parent node of NODE." (elt node 2)) +(defun elpher-set-node-parent (node parent) + "Set the parent node of NODE to be PARENT." + (setcar (cdr (cdr node)) parent)) + ;; Cache (defvar elpher-content-cache (make-hash-table :test 'equal)) @@ -271,10 +280,18 @@ address of the gopher page." (defvar elpher-current-node nil) -(defun elpher-visit-node (node &optional getter) - "Visit NODE using its own getter or GETTER, if non-nil." +(defun elpher-visit-node (node &optional getter preserve-parent) + "Visit NODE using its own getter or GETTER, if non-nil. +Additionally, set the parent of NODE to `elpher-current-node', +unless PRESERVE-PARENT is non-nil." (elpher-save-pos) (elpher-process-cleanup) + (unless preserve-parent + (if (and (elpher-node-parent elpher-current-node) + (equal (elpher-node-address elpher-current-node) + (elpher-node-address node))) + (elpher-set-node-parent node (elpher-node-parent elpher-current-node)) + (elpher-set-node-parent node elpher-current-node))) (setq elpher-current-node node) (if getter (funcall getter) @@ -286,7 +303,7 @@ address of the gopher page." "Visit the parent of the current node." (let ((parent-node (elpher-node-parent elpher-current-node))) (when parent-node - (elpher-visit-node parent-node)))) + (elpher-visit-node parent-node nil t)))) (defun elpher-reload-current-node () "Reload the current node, discarding any existing cached content." @@ -324,14 +341,28 @@ address of the gopher page." args))) -;;; Index rendering +;;; Text Processing ;; +(defvar elpher-user-coding-system nil + "User-specified coding system to use for decoding text responses.") + +(defun elpher-decode (string) + "Decode STRING using autodetected or user-specified coding system." + (decode-coding-string string + (if elpher-user-coding-system + elpher-user-coding-system + (detect-coding-string string t)))) + (defun elpher-preprocess-text-response (string) - "Clear away CRs and terminating period from STRING." - (replace-regexp-in-string "\n\.\n$" "\n" - (replace-regexp-in-string "\r" "" - string))) + "Preprocess text selector response contained in STRING. +This involes decoding the character representation, and clearing +away CRs and any terminating period." + (elpher-decode (replace-regexp-in-string "\n\.\n$" "\n" + (replace-regexp-in-string "\r" "" string)))) + +;;; Index rendering +;; (defun elpher-insert-index (string) "Insert the index corresponding to STRING into the current buffer." @@ -373,6 +404,7 @@ address of the gopher page." (elpher-address-host address) (elpher-address-port address))))) + (defun elpher-insert-index-record (display-string type selector host port) "Function to insert an index record into the current buffer. The contents of the record are dictated by TYPE, DISPLAY-STRING, SELECTOR, HOST @@ -382,7 +414,7 @@ and PORT." (if type-map-entry (let* ((margin-code (elt type-map-entry 1)) (face (elt type-map-entry 2)) - (node (elpher-make-node display-string elpher-current-node address))) + (node (elpher-make-node display-string address))) (elpher-insert-margin margin-code) (insert-text-button display-string 'face face @@ -429,6 +461,8 @@ The result is stored as a string in the variable ‘elpher-selector-string’." (make-network-process :name "elpher-process" :host (elpher-address-host address) :service (elpher-address-port address) + :coding 'no-conversion + :filter-multibyte nil :filter (lambda (proc string) (setq elpher-selector-string (concat elpher-selector-string string))) @@ -471,11 +505,9 @@ The result is stored as a string in the variable ‘elpher-selector-string’." "\\([a-zA-Z]+\\)://\\([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 (parent &optional string) +(defun elpher-make-node-from-matched-url (&optional string) "Convert most recent `elpher-url-regex' match to a node. -PARENT defines the node to set as the parent to the new node. - If STRING is non-nil, this is given as an argument to all `match-string' calls, as is necessary if the match is performed by `string-match'." (let ((url (match-string 0 string)) @@ -493,14 +525,14 @@ calls, as is necessary if the match is performed by `string-match'." (substring type-and-selector 2) "")) (address (elpher-make-address type selector host port))) - (elpher-make-node url elpher-current-node address)) + (elpher-make-node url address)) (let* ((host (match-string 2 string)) (port (if (> (length (match-string 3 string)) 1) (string-to-number (substring (match-string 3 string) 1)) 70)) (selector (concat "URL:" url)) (address (elpher-make-address ?h selector host port))) - (elpher-make-node url elpher-current-node address))))) + (elpher-make-node url address))))) (defun elpher-buttonify-urls (string) @@ -509,7 +541,7 @@ calls, as is necessary if the match is performed by `string-match'." (insert string) (goto-char (point-min)) (while (re-search-forward elpher-url-regex nil t) - (let ((node (elpher-make-node-from-matched-url elpher-current-node))) + (let ((node (elpher-make-node-from-matched-url))) (make-text-button (match-beginning 0) (match-end 0) 'elpher-node node @@ -561,9 +593,7 @@ calls, as is necessary if the match is performed by `string-match'." (lambda (proc event) (unless (string-prefix-p "deleted" event) (let ((image (create-image - (encode-coding-string - elpher-selector-string - 'no-conversion) + elpher-selector-string nil t))) (elpher-with-clean-buffer (insert-image image) @@ -820,14 +850,12 @@ host, selector and port." (let ((node (let ((host-or-url (read-string "Gopher host or URL: "))) (if (string-match elpher-url-regex host-or-url) - (elpher-make-node-from-matched-url elpher-current-node - host-or-url) + (elpher-make-node-from-matched-url host-or-url) (let ((selector (read-string "Selector (default none): " nil nil "")) (port-string (read-string "Port (default 70): " nil nil "70"))) (elpher-make-node (concat "gopher://" host-or-url ":" port-string "/1" selector) - elpher-current-node (elpher-make-address ?1 selector host-or-url (string-to-number port-string)))))))) (switch-to-buffer "*elpher*") @@ -913,7 +941,6 @@ host, selector and port." (elpher-make-node (concat "gopher://" host ":" (number-to-string port) "/1/") - elpher-current-node root-address))) (error "Already at root directory of current server"))) (error "Command invalid for this page")))) @@ -930,12 +957,14 @@ host, selector and port." (defun elpher-bookmark-current () "Bookmark the current node." (interactive) - (unless (elpher-bookmarks-current-p) - (let ((address (elpher-node-address elpher-current-node)) - (display-string (read-string "Bookmark display string: " - (elpher-node-display-string elpher-current-node)))) - (elpher-add-address-bookmark address display-string) - (message "Bookmark added.")))) + (let ((address (elpher-node-address elpher-current-node)) + (display-string (elpher-node-display-string elpher-current-node))) + (if (not (elpher-address-special-p address)) + (let ((bookmark-display-string (read-string "Bookmark display string: " + display-string))) + (elpher-add-address-bookmark address bookmark-display-string) + (message "Bookmark added.")) + (error "Cannot bookmark %s" display-string)))) (defun elpher-bookmark-link () "Bookmark the link at point." @@ -944,19 +973,23 @@ host, selector and port." (if button (let* ((node (button-get button 'elpher-node)) (address (elpher-node-address node)) - (display-string (read-string "Bookmark display string: " - (elpher-node-display-string node)))) - (elpher-add-address-bookmark address display-string) - (elpher-reload-bookmarks) - (message "Bookmark added.")) + (display-string (elpher-node-display-string node))) + (if (not (elpher-address-special-p address)) + (let ((bookmark-display-string (read-string "Bookmark display string: " + display-string))) + (elpher-add-address-bookmark address bookmark-display-string) + (elpher-reload-bookmarks) + (message "Bookmark added.")) + (error "Cannot bookmark %s" display-string))) (error "No link selected")))) (defun elpher-unbookmark-current () "Remove bookmark for the current node." (interactive) - (unless (elpher-bookmarks-current-p) - (elpher-remove-address-bookmark (elpher-node-address elpher-current-node)) - (message "Bookmark removed."))) + (let ((address (elpher-node-address elpher-current-node))) + (unless (elpher-address-special-p address) + (elpher-remove-address-bookmark address) + (message "Bookmark removed.")))) (defun elpher-unbookmark-link () "Remove bookmark for the link at point." @@ -974,15 +1007,13 @@ host, selector and port." (interactive) (switch-to-buffer "*elpher*") (elpher-visit-node - (elpher-make-node "Bookmarks" - elpher-current-node - (elpher-make-address 'bookmarks)))) + (elpher-make-node "Bookmarks Page" (elpher-make-address 'bookmarks)))) (defun elpher-info-node (node) "Display information on NODE." (let ((display-string (elpher-node-display-string node)) (address (elpher-node-address node))) - (if address + (if (not (elpher-address-special-p address)) (message "`%s' on %s port %s" (elpher-address-selector address) (elpher-address-host address) @@ -1004,23 +1035,29 @@ host, selector and port." (defun elpher-get-address-url (address) "Get URL representation of ADDRESS." - (concat "gopher://" - (elpher-address-host address) - (let ((port (elpher-address-port address))) - (if (equal port 70) - "" - (format ":%d" port))) - "/" (string (elpher-address-type address)) - (elpher-address-selector 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://" + 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))) - (if address - (let ((url (elpher-get-address-url address))) - (message url) - (kill-new url)) - (error (format "Cannot represent %s as URL" (elpher-node-display-string node)))))) + (if (elpher-address-special-p address) + (error (format "Cannot represent %s as URL" (elpher-node-display-string node))) + (let ((url (elpher-get-address-url address))) + (message "Copied \"%s\" to kill-ring/clipboard." url) + (kill-new url))))) (defun elpher-copy-link-url () "Copy URL of item at point to `kill-ring'." @@ -1035,6 +1072,15 @@ host, selector and port." (interactive) (elpher-copy-node-url elpher-current-node)) +(defun elpher-set-coding-system () + "Specify an explicit character coding system." + (interactive) + (let ((system (read-coding-system "Set coding system to use (default is to autodetect): " nil))) + (setq elpher-user-coding-system system) + (if system + (message "Coding system fixed to %s. (Reload to see effect)." system) + (message "Coding system set to autodetect. (Reload to see effect).")))) + ;;; Mode and keymap ;; @@ -1059,6 +1105,7 @@ host, selector and port." (define-key map (kbd "x") 'elpher-unbookmark-link) (define-key map (kbd "X") 'elpher-unbookmark-current) (define-key map (kbd "B") 'elpher-bookmarks) + (define-key map (kbd "S") 'elpher-set-coding-system) (when (fboundp 'evil-define-key) (evil-define-key 'motion map (kbd "TAB") 'elpher-next-link @@ -1080,7 +1127,8 @@ host, selector and port." (kbd "A") 'elpher-bookmark-current (kbd "x") 'elpher-unbookmark-link (kbd "X") 'elpher-unbookmark-current - (kbd "B") 'elpher-bookmarks)) + (kbd "B") 'elpher-bookmarks + (kbd "S") 'elpher-set-coding-system)) map) "Keymap for gopher client.") @@ -1101,7 +1149,8 @@ host, selector and port." (switch-to-buffer "*elpher*") (switch-to-buffer "*elpher*") (setq elpher-current-node nil) - (let ((start-node (elpher-make-node "Elpher Start Page" nil (elpher-make-address 'start)))) + (let ((start-node (elpher-make-node "Elpher Start Page" + (elpher-make-address 'start)))) (elpher-visit-node start-node))) "Started Elpher.") ; Otherwise (elpher) evaluates to start page string.