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
 ;;
 
-(defconst elpher-version "1.0.0"
+(defconst elpher-version "1.1.0"
   "Current version of elpher.")
 
 (defconst elpher-margin-width 6
   (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"
@@ -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"
-         ".")
+         ".\r\n")
    "\r\n")
   "Source for elpher start page.")
 
 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))
@@ -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))
 
-
 ;;; Model
 ;;
 
@@ -197,6 +201,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.
 
@@ -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
-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."
@@ -238,7 +253,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."
@@ -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.
-  (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 +349,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 +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))))
-        (?.) ;; 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)))))
@@ -630,18 +649,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."