X-Git-Url: https://thelambdalab.xyz/gitweb/index.cgi?a=blobdiff_plain;f=elpher.el;h=d4c582abce25604adc0847024f8ba3fcc9daa420;hb=d9adfcf3627ac5da0f8dad8f5cae28e8dad123c8;hp=25acb24af82b77f2dd0231d43465c32368d583f2;hpb=4fd7fc2502bbe99ea71632ab715cb3c54b5266ef;p=elpher.git diff --git a/elpher.el b/elpher.el index 25acb24..d4c582a 100644 --- a/elpher.el +++ b/elpher.el @@ -36,9 +36,8 @@ ;; - (m)enu key support, similar to Emacs' info browser, ;; - clickable web and gopher links in plain text. -;; The caching mechanism works by maintaining a hierarchy of visited -;; pages rather than a linear history, meaning that it is quick and -;; easy to navigate this history. +;; Visited pages are stored as a hierarchy rather than a linear history, +;; meaning that navigation between these pages is quick and easy. ;; To launch Elpher, simply use 'M-x elpher'. This will open a start ;; page containing information on key bindings and suggested starting @@ -54,7 +53,7 @@ ;;; Global constants ;; -(defconst elpher-version "1.0.0" +(defconst elpher-version "1.1.0" "Current version of elpher.") (defconst elpher-margin-width 6 @@ -64,17 +63,18 @@ (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" + "i Elpher Gopher Client \tfake\tfake\t1" + (format "i version %s\tfake\tfake\t1" elpher-version) + "i --------------------------------------------\tfake\tfake\t1" "i\tfake\tfake\t1" - "iBasic usage:\tfake\tfake\t1" + "iUsage:\tfake\tfake\t1" "i\tfake\tfake\t1" "i - tab/shift-tab: next/prev directory entry on current page\tfake\tfake\t1" "i - RET/mouse-1: open directory entry under cursor\tfake\tfake\t1" "i - m: select a directory entry by name (autocompletes)\tfake\tfake\t1" "i - u: return to parent directory entry\tfake\tfake\t1" + "i - O: visit the root directory of the current server\tfake\tfake\t1" "i - g: go to a particular 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" @@ -89,7 +89,7 @@ "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.") @@ -162,6 +162,10 @@ Otherwise, use the system browser via the BROWSE-URL function." :type '(boolean)) +(defcustom elpher-buttonify-urls-in-directories nil + "If non-nil, turns URLs matched in directories into clickable buttons." + :type '(boolean)) + (defcustom elpher-cache-images nil "If non-nil, cache images in memory in the same way as other content." :type '(boolean)) @@ -172,7 +176,6 @@ Otherwise, a list containing the selector, host and port of a directory to use as the start page." :type '(list string string integer)) - ;;; Model ;; @@ -196,6 +199,9 @@ use as the start page." ;; Node +(defvar elpher-seen-nodes (make-hash-table :test 'equal) + "Table mapping addresses to existing (seen) node objects.") + (defun elpher-make-node (parent address getter &optional content pos) "Create a node in the gopher page hierarchy. @@ -204,8 +210,16 @@ the gopher page, GETTER provides the getter function used to obtain this page. The optional arguments CONTENT and POS can be used to fill the cached -content and cursor position fields of the node." - (list parent address getter content pos)) +content and cursor position fields of the node. + +If the hash table `elpher-seen-nodes' contains a key equal to ADDRESS, +the node contained as its value will be returned instead." + (let ((existing-node (gethash address elpher-seen-nodes))) + (if existing-node + existing-node + (let ((new-node (list parent address getter content pos))) + (puthash address new-node elpher-seen-nodes) + new-node)))) (defun elpher-node-parent (node) "Retrieve the parent node of NODE." @@ -237,7 +251,7 @@ content and cursor position fields of the node." ;; Node graph traversal -(defvar elpher-current-node) +(defvar elpher-current-node nil) (defun elpher-visit-node (node &optional getter) "Visit NODE using its own getter or GETTER, if non-nil." @@ -276,8 +290,7 @@ content and cursor position fields of the node." (defmacro elpher-with-clean-buffer (&rest args) "Evaluate ARGS with a clean *elpher* buffer as current." - (list 'progn - '(switch-to-buffer "*elpher*") + (list 'with-current-buffer "*elpher*" '(elpher-mode) (append (list 'let '((inhibit-read-only t)) '(erase-buffer)) @@ -290,9 +303,11 @@ content and cursor position fields of the node." "Insert the index corresponding to STRING into the current buffer." ;; Should be able to split directly on CRLF, but some non-conformant ;; LF-only servers sadly exist, hence the following. - (dolist (line (split-string (replace-regexp-in-string "\r" "" string) "\n")) - (unless (= (length line) 0) - (elpher-insert-index-record line)))) + (let* ((str-no-period (replace-regexp-in-string "\r\n\.\r\n$" "\r\n" string)) + (str-no-cr (replace-regexp-in-string "\r" "" str-no-period))) + (dolist (line (split-string str-no-cr "\n")) + (unless (= (length line) 0) + (elpher-insert-index-record line))))) (defun elpher-insert-margin (&optional type-name) "Insert index margin, optionally containing the TYPE-NAME, into the current buffer." @@ -332,8 +347,11 @@ content and cursor position fields of the node." selector host port))) (pcase type (?i (elpher-insert-margin) ;; Information - (insert (propertize display-string - 'face 'elpher-info))) + (insert (propertize + (if elpher-buttonify-urls-in-directories + (elpher-buttonify-urls display-string) + display-string) + 'face 'elpher-info))) (?h (elpher-insert-margin "W") ;; Web link (let ((url (elt (split-string selector "URL:") 1))) (insert-text-button display-string @@ -342,7 +360,6 @@ content and cursor position fields of the node." 'action #'elpher-click-url 'follow-link t 'help-echo (format "mouse-1, RET: open url %s" url)))) - (?.) ;; Occurs at end of index, can safely ignore. (tp (elpher-insert-margin (concat (char-to-string tp) "?")) (insert (propertize display-string 'face 'elpher-unknown-face))))) @@ -382,8 +399,8 @@ The result is stored as a string in the variable ‘elpher-selector-string’." (if content (progn (elpher-with-clean-buffer - (insert content)) - (elpher-restore-pos)) + (insert content) + (elpher-restore-pos))) (if address (progn (elpher-with-clean-buffer @@ -392,16 +409,16 @@ The result is stored as a string in the variable ‘elpher-selector-string’." (lambda (proc event) (unless (string-prefix-p "deleted" event) (elpher-with-clean-buffer - (elpher-insert-index elpher-selector-string)) - (elpher-restore-pos) - (elpher-set-node-content elpher-current-node - (buffer-string)))))) + (elpher-insert-index elpher-selector-string) + (elpher-restore-pos) + (elpher-set-node-content elpher-current-node + (buffer-string))))))) (progn (elpher-with-clean-buffer - (elpher-insert-index elpher-start-index)) - (elpher-restore-pos) - (elpher-set-node-content elpher-current-node - (buffer-string))))))) + (elpher-insert-index elpher-start-index) + (elpher-restore-pos) + (elpher-set-node-content elpher-current-node + (buffer-string)))))))) ;; Text retrieval @@ -459,8 +476,8 @@ The result is stored as a string in the variable ‘elpher-selector-string’." (if content (progn (elpher-with-clean-buffer - (insert content)) - (elpher-restore-pos)) + (insert content) + (elpher-restore-pos))) (progn (elpher-with-clean-buffer (insert "LOADING TEXT...")) @@ -468,10 +485,10 @@ The result is stored as a string in the variable ‘elpher-selector-string’." (lambda (proc event) (unless (string-prefix-p "deleted" event) (elpher-with-clean-buffer - (insert (elpher-process-text elpher-selector-string))) - (elpher-restore-pos) - (elpher-set-node-content elpher-current-node - (buffer-string))))))))) + (insert (elpher-process-text elpher-selector-string)) + (elpher-restore-pos) + (elpher-set-node-content elpher-current-node + (buffer-string)))))))))) ;; Image retrieval @@ -482,9 +499,8 @@ The result is stored as a string in the variable ‘elpher-selector-string’." (if content (progn (elpher-with-clean-buffer - (insert-image content)) - (setq cursor-type nil) - (elpher-restore-pos)) + (insert-image content) + (elpher-restore-pos))) (if (display-images-p) (progn (elpher-with-clean-buffer @@ -497,9 +513,8 @@ The result is stored as a string in the variable ‘elpher-selector-string’." 'no-conversion) nil t))) (elpher-with-clean-buffer - (insert-image image)) - (setq cursor-type nil) - (elpher-restore-pos) + (insert-image image) + (elpher-restore-pos)) (if elpher-cache-images (elpher-set-node-content elpher-current-node image))))))) @@ -515,8 +530,8 @@ The result is stored as a string in the variable ‘elpher-selector-string’." (if content (progn (elpher-with-clean-buffer - (insert content)) - (elpher-restore-pos) + (insert content) + (elpher-restore-pos)) (message "Displaying cached search results. Reload to perform a new search.")) (unwind-protect (let* ((query-string (read-string "Query: ")) @@ -551,8 +566,8 @@ The result is stored as a string in the variable ‘elpher-selector-string’." (lambda (proc event) (unless (string-prefix-p "deleted" event) (elpher-with-clean-buffer - (insert elpher-selector-string)) - (goto-char (point-min))))) + (insert elpher-selector-string) + (goto-char (point-min)))))) (progn (elpher-with-clean-buffer (insert elpher-start-index)) @@ -618,6 +633,7 @@ The result is stored as a string in the variable ‘elpher-selector-string’." (defun elpher-go () "Go to a particular gopher site." (interactive) + (switch-to-buffer "*elpher*") (let* ( (hostname (read-string "Gopher host: ")) (selector (read-string "Selector (default none): " nil nil "")) @@ -631,18 +647,24 @@ The result is stored as a string in the variable ‘elpher-selector-string’." (defun elpher-redraw () "Redraw current page." (interactive) - (elpher-visit-node elpher-current-node)) + (if elpher-current-node + (elpher-visit-node elpher-current-node) + (message "No current site."))) (defun elpher-reload () "Reload current page." (interactive) - (elpher-reload-current-node)) + (if elpher-current-node + (elpher-reload-current-node) + (message "No current site."))) (defun elpher-view-raw () "View current page as plain text." (interactive) - (elpher-visit-node elpher-current-node - #'elpher-get-node-raw)) + (if elpher-current-node + (elpher-visit-node elpher-current-node + #'elpher-get-node-raw) + (message "No current site."))) (defun elpher-back () "Go to previous site." @@ -678,12 +700,29 @@ The result is stored as a string in the variable ‘elpher-selector-string’." (let* ((link-map (elpher-build-link-map))) (if link-map (let ((key (let ((completion-ignore-case t)) - (completing-read "Directory entry/link (tab to autocomplete): " link-map nil t)))) + (completing-read "Directory entry/link (tab to autocomplete): " + link-map nil t)))) (if (and key (> (length key) 0)) (let ((b (cdr (assoc key link-map)))) (goto-char (button-start b)) (button-activate b))))))) +(defun elpher-root-dir () + "Visit root of current server." + (interactive) + (let ((address (elpher-node-address elpher-current-node))) + (if address + (let ((host (elpher-address-host address)) + (selector (elpher-address-selector address)) + (port (elpher-address-port address))) + (if (> (length selector) 0) + (let ((root-address (elpher-make-address "" host port))) + (elpher-visit-node (elpher-make-node elpher-current-node + root-address + #'elpher-get-index-node))) + (message "Already at root directory of current server."))) + (message "Command invalid for Elpher start page.")))) + ;;; Mode and keymap ;; @@ -692,6 +731,7 @@ The result is stored as a string in the variable ‘elpher-selector-string’." (define-key map (kbd "TAB") 'elpher-next-link) (define-key map (kbd "") 'elpher-prev-link) (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 "r") 'elpher-redraw) (define-key map (kbd "R") 'elpher-reload) @@ -704,6 +744,7 @@ The result is stored as a string in the variable ‘elpher-selector-string’." (kbd "C-]") 'elpher-follow-current-link (kbd "C-t") 'elpher-back (kbd "u") 'elpher-back + (kbd "O") 'elpher-root-dir (kbd "g") 'elpher-go (kbd "r") 'elpher-redraw (kbd "R") 'elpher-reload @@ -724,11 +765,14 @@ The result is stored as a string in the variable ‘elpher-selector-string’." (defun elpher () "Start elpher with default landing page." (interactive) - (setq elpher-current-node nil) - (let ((start-node (elpher-make-node nil - elpher-start-address - #'elpher-get-index-node))) - (elpher-visit-node start-node)) + (if (get-buffer "*elpher*") + (switch-to-buffer "*elpher*") + (switch-to-buffer "*elpher*") + (setq elpher-current-node nil) + (let ((start-node (elpher-make-node nil + elpher-start-address + #'elpher-get-index-node))) + (elpher-visit-node start-node))) "Started Elpher.") ; Otherwise (elpher) evaluates to start page string. ;;; elpher.el ends here