Child node caches now retained after reload.
[elpher.git] / elpher.el
index 30ec292..449bef4 100644 (file)
--- a/elpher.el
+++ b/elpher.el
@@ -54,7 +54,7 @@
 ;;; Global constants
 ;;
 
 ;;; Global constants
 ;;
 
-(defconst elpher-version "1.0.0"
+(defconst elpher-version "1.1.0"
   "Current version of elpher.")
 
 (defconst elpher-margin-width 6
   "Current version of elpher.")
 
 (defconst elpher-margin-width 6
   (mapconcat
    'identity
    (list "i\tfake\tfake\t1"
   (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"
          "iUsage:\tfake\tfake\t1"
          "i\tfake\tfake\t1"
          "i\tfake\tfake\t1"
          "iUsage:\tfake\tfake\t1"
          "i\tfake\tfake\t1"
@@ -90,7 +90,7 @@
          "isearch terms:\tfake\tfake\t1"
          "i\tfake\tfake\t1"
          "7Veronica-2 Gopher Search Engine\t/v2/vs\tgopher.floodgap.com\t70"
          "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.")
 
    "\r\n")
   "Source for elpher start page.")
 
 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
+  "If non-nil, turns URLs matched in \"i\" item types 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))
 (defcustom elpher-cache-images nil
   "If non-nil, cache images in memory in the same way as other content."
   :type '(boolean))
@@ -173,7 +178,6 @@ Otherwise, a list containing the selector, host and port of a directory to
 use as the start page."
   :type '(list string string integer))
 
 use as the start page."
   :type '(list string string integer))
 
-
 ;;; Model
 ;;
 
 ;;; Model
 ;;
 
@@ -197,6 +201,9 @@ use as the start page."
 
 ;; Node
 
 
 ;; 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.
 
 (defun elpher-make-node (parent address getter &optional content pos)
   "Create a node in the gopher page hierarchy.
 
@@ -205,8 +212,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
 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."
 
 (defun elpher-node-parent (node)
   "Retrieve the parent node of NODE."
@@ -238,7 +253,7 @@ content and cursor position fields of the node."
 
 ;; Node graph traversal
 
 
 ;; 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."
 
 (defun elpher-visit-node (node &optional getter)
   "Visit NODE using its own getter or GETTER, if non-nil."
@@ -290,9 +305,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.
   "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."
 
 (defun elpher-insert-margin (&optional type-name)
   "Insert index margin, optionally containing the TYPE-NAME, into the current buffer."
@@ -332,8 +349,11 @@ content and cursor position fields of the node."
                                                  selector host port)))
       (pcase type
         (?i (elpher-insert-margin) ;; Information
                                                  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
         (?h (elpher-insert-margin "W") ;; Web link
             (let ((url (elt (split-string selector "URL:") 1)))
               (insert-text-button display-string
@@ -342,7 +362,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))))
                                   '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)))))
         (tp (elpher-insert-margin (concat (char-to-string tp) "?"))
             (insert (propertize display-string
                                 'face 'elpher-unknown-face)))))
@@ -630,18 +649,24 @@ The result is stored as a string in the variable ‘elpher-selector-string’."
 (defun  elpher-redraw ()
   "Redraw current page."
   (interactive)
 (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)
 
 (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)
 
 (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."
 
 (defun elpher-back ()
   "Go to previous site."