1 ;;; elopher.el --- gopher client
5 ;; Simple gopher client in elisp.
12 (defconst elopher-version "1.0.0"
13 "Current version of elopher.")
15 (defconst elopher-margin-width 6
16 "Width of left-hand margin used when rendering indicies.")
18 (defconst elopher-start-page
19 (concat "i\tfake\tfake\t1\r\n"
20 "i--------------------------------------------\tfake\tfake\t1\r\n"
21 "i Elopher Gopher Client \tfake\tfake\t1\r\n"
22 (format "i version %s\tfake\tfake\t1\r\n" elopher-version)
23 "i--------------------------------------------\tfake\tfake\t1\r\n"
24 "i\tfake\tfake\t1\r\n"
25 "iBasic usage:\tfake\tfake\t1\r\n"
26 "i - tab/shift-tab: next/prev directory entry\tfake\tfake\t1\r\n"
27 "i - RET/mouse-1: open directory entry\tfake\tfake\t1\r\n"
28 "i - u: return to parent directory entry\tfake\tfake\t1\r\n"
29 "i - g: go to a particular site\tfake\tfake\t1\r\n"
30 "i\tfake\tfake\t1\r\n"
31 "iPlaces to start exploring Gopherspace:\tfake\tfake\t1\r\n"
32 "1Floodgap Systems Gopher Server\t\tgopher.floodgap.com\t70\r\n"
33 "1Super-Dimensional Fortress\t\tsdf.org\t70\r\n"
34 "i\tfake\tfake\t1\r\n"
35 "iTest entries:\tfake\tfake\t1\r\n"
36 "pXKCD comic image\t/fun/xkcd/comics/2130/2137/text_entry.png\tgopher.floodgap.com\t70\r\n"))
39 ;;; Customization group
43 "A simple gopher client."
46 (defcustom elopher-index-face '(foreground-color . "cyan")
47 "Face used for index records.")
48 (defcustom elopher-text-face '(foreground-color . "white")
49 "Face used for text records.")
50 (defcustom elopher-info-face '(foreground-color . "gray")
51 "Face used for info records.")
52 (defcustom elopher-image-face '(foreground-color . "green")
53 "Face used for image records.")
54 (defcustom elopher-unknown-face '(foreground-color . "red")
55 "Face used for unknown record types.")
62 (defun elopher-make-address (selector host port)
63 (list selector host port))
65 (defun elopher-address-selector (address)
68 (defun elopher-address-host (address)
71 (defun elopher-address-port (address)
76 (defun elopher-make-node (parent address getter &optional content)
77 (list parent address content))
79 (defun elopher-node-parent (node)
82 (defun elopher-node-address (node)
85 (defun elopher-node-getter (node)
88 (defun elopher-node-content (node)
91 (defvar elopher-start-node (elopher-make-node nil nil #'elopher-index-getter))
92 (defvar elopher-current-node elopher-start-node)
94 (defun elopher-set-current-node-content (content)
95 (setcar (elopher-node-content elopher-current-node)
98 (defun elopher-visit-node (node)
99 (elopher-prepare-buffer)
100 (setq elopher-current-node node)
101 (funcall (elopher-node-getter)))
103 (defun elopher-reload-current-node ()
104 (elopher-prepare-buffer)
105 (elopher-set-current-node-content nil)
106 (funcall (elopher-node-getter)))
109 ;;; Buffer preparation
112 (defun elopher-prepare-buffer ()
113 (switch-to-buffer "*elopher*")
120 (defun elopher-insert-index (string)
121 "Inserts the index corresponding to STRING into the current buffer."
122 (dolist (line (split-string string "\r\n"))
123 (elopher-insert-index-record line)))
125 (defun elopher-insert-margin (&optional type-name)
127 (insert (format (concat "%" (number-to-string elopher-margin-width) "s"
129 (propertize "[" 'face '(foreground-color . "blue"))
130 (propertize type-name 'face '(foreground-color . "white"))
131 (propertize "]" 'face '(foreground-color . "blue"))))))
132 (insert (make-string elopher-margin-width ?\s))))
134 (defun elopher-insert-index-record (line)
135 "Inserts the index record corresponding to LINE into the current buffer."
136 (let* ((type (elt line 0))
137 (fields (split-string (substring line 1) "\t"))
138 (display-string (elt fields 0))
139 (address (elopher-make-address (elt fields 1) (elt fields 2) (elt fields 3))))
141 (?i (elopher-insert-margin)
142 (insert (propertize display-string
143 'face elopher-info-face)))
144 (?0 (elopher-insert-margin "T")
145 (insert-text-button display-string
146 'face elopher-text-face
147 'elopher-node (elopher-make-node elopher-current-node
149 #'elopher-get-text-node)
150 'action #'elopher-click-link
152 (?1 (elopher-insert-margin "/")
153 (insert-text-button display-string
154 'face elopher-index-face
155 'elopher-node (elopher-make-node elopher-current-node
157 #'elopher-get-index-node)
158 'action #'elopher-click-link
160 (?.) ; Occurs at end of index, can safely ignore.
161 (tp (elopher-insert-margin (concat (char-to-string tp) "?"))
162 (insert (propertize display-string
163 'face elopher-unknown-face))))
167 ;;; Selector retrieval (all kinds)
170 (defvar elopher-selector-string)
172 (defun elopher-get-selector (address after)
173 (setq elopher-selector-string "")
174 (let ((p (get-process "elopher-process")))
175 (if p (delete-process p)))
176 (make-network-process
177 :name "elopher-process"
178 :host (elopher-address-host address)
179 :service (elopher-address-port address)
180 :filter (lambda (proc string)
181 (setq elopher-selector-string (concat elopher-selector-string string)))
183 (process-send-string "elopher-process"
184 (concat (elopher-address-selector address) "\n")))
188 (defun elopher-get-index-node ()
189 (let ((content (elopher-node-content elopher-node-current))
190 (address (elopher-node-address elopher-node-current)))
194 (elopher-get-selector address
196 (let ((inhibit-read-only t))
197 (elopher-insert-index elopher-selector-string))
198 (elopher-set-current-node-content (buffer-string))))
199 (elopher-insert-index elopher-start-page)))))
203 (defun elopher-get-text-node ()
204 (let ((content (elopher-node-content elopher-current-node))
205 (address (elopher-node-address elopher-current-node)))
207 (let ((inhibit-read-only t))
209 (elopher-get-selector address
211 (let ((inhibit-read-only t))
212 (insert elopher-selector-string))
213 (elopher-set-current-node-content elopher-selector-string))))))
215 ;;; Navigation methods
218 (defun elopher-next-link ()
222 (defun elopher-prev-link ()
226 (defun elopher-click-link (button)
227 (let ((node (button-get button 'elopher-node)))
228 (elopher-visit-node node)))
230 (defun elopher-follow-closest-link ()
235 "Go to a particular gopher site."
238 (hostname (read-from-minibuffer "Gopher host: "))
240 (address (list selector hostname port)))
242 (elopher-make-node elopher-current-node
244 #'elopher-index-getter))))
246 (defun elopher-reload ()
247 "Reload current site."
249 (elopher-reload-current-node))
252 ;;; Main start procedure
255 "Start elopher with default landing page."
257 (elopher-visit-node elopher-start-node))
263 (setq elopher-mode-map
264 (let ((map (make-sparse-keymap)))
265 (define-key map (kbd "<tab>") 'elopher-next-link)
266 (define-key map (kbd "<S-tab>") 'elopher-prev-link)
267 (define-key map (kbd "u") 'elopher-pop-history)
268 (define-key map (kbd "g") 'elopher-go)
269 (define-key map (kbd "r") 'elopher-reload)
270 (when (require 'evil nil t)
271 (evil-define-key 'normal map
272 (kbd "C-]") 'elopher-follow-closest-link
273 (kbd "C-t") 'elopher-pop-history
274 (kbd "u") 'elopher-pop-history
275 (kbd "g") 'elopher-go
276 (kbd "r") 'elopher-reload))
278 ;; "Keymap for gopher client.")
280 (define-derived-mode elopher-mode special-mode "elopher"
281 "Major mode for elopher, an elisp gopher client.")
284 ;;; elopher.el ends here