Mercurial > hg > xemacs-beta
comparison lisp/packages/gopher.el @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | ec9a17fef872 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:376386a54a3c |
---|---|
1 ;;; gopher.el --- an emacs gopher client | |
2 | |
3 ;; Copyright (C) 1992 scott snyder | |
4 | |
5 ;; Author: scott snyder <snyder@fnald0.fnal.gov> | |
6 ;; Created: 29 Jun 1992 | |
7 ;; Version: 1.03 | |
8 ;; Keywords: gopher, comm | |
9 | |
10 ;; LCD Archive Entry: | |
11 ;; gopher|scott snyder|snyder@fnald0.fnal.gov| | |
12 ;; An emacs gopher client.| | |
13 ;; 20-Apr-1993|1.02|~/interfaces/gopher.el.Z| | |
14 | |
15 ;; This file is not part of GNU Emacs, but is distributed under the | |
16 ;; same conditions. | |
17 ;; | |
18 ;; GNU Emacs is distributed in the hope that it will be useful, | |
19 ;; but WITHOUT ANY WARRANTY. No author or distributor | |
20 ;; accepts responsibility to anyone for the consequences of using it | |
21 ;; or for whether it serves any particular purpose or works at all, | |
22 ;; unless he says so in writing. Refer to the GNU Emacs General Public | |
23 ;; License for full details. | |
24 ;; | |
25 ;; Everyone is granted permission to copy, modify and redistribute | |
26 ;; GNU Emacs, but only under the conditions described in the | |
27 ;; GNU Emacs General Public License. A copy of this license is | |
28 ;; supposed to have been given to you along with GNU Emacs so you | |
29 ;; can know your rights and responsibilities. It should be in a | |
30 ;; file named COPYING. Among other things, the copyright notice | |
31 ;; and this notice must be preserved on all copies. | |
32 ;; | |
33 ;;; Synched up with: Not in FSF. | |
34 ;; | |
35 ;; An emacs gopher client. Currently supports directory, text, CSO, | |
36 ;; index, image, and telnet objects. | |
37 ;; Requires forms.el and background.el. | |
38 ;; | |
39 ;; Written by scott snyder <snyder@fnald0.fnal.gov> | |
40 ;; Some code borrowed from GNUS (by Masanobu UMEDA). | |
41 ;; Some code (bookmarks, xterms, error handling) contributed | |
42 ;; by Stewart Clamen <clamen@cs.cmu.edu>. | |
43 | |
44 ;;; Commentary: | |
45 ;; OPERATING INSTRUCTIONS | |
46 ;; | |
47 ;; To use, `M-x gopher'. To specify a different root server, use | |
48 ;; `C-u M-x gopher'. If you want to use bookmarks, set the variable | |
49 ;; gopher-support-bookmarks appropriately. | |
50 ;; The command `M-x gopher-atpoint' will attempt to interpret the text | |
51 ;; around point as a gopher bookmark specification and will retrieve | |
52 ;; that item. | |
53 ;; | |
54 ;; Sample .emacs configuration: | |
55 ;; (autoload 'gopher "gopher") | |
56 ;; (autoload 'gopher-atpoint "gopher") | |
57 ;; (setq gopher-support-bookmarks t) | |
58 ;; | |
59 ;; In directory mode: | |
60 ;; Space, return, `f', or `e' selects the line point is on. | |
61 ;; With a numeric prefix argument, select that object. | |
62 ;; `q', `l', or `u' will return to the previous node. | |
63 ;; `n' and `p' to the next and previous lines. | |
64 ;; `a' will add an object to your bookmark list. | |
65 ;; `v' will display your bookmark list. | |
66 ;; `=' gives detailed information about an object. | |
67 ;; In the bookmark list, all of the above (except `a'), plus: | |
68 ;; `C-k' will delete an object from the bookmark list. | |
69 ;; `C-y' will yank the most recently deleted bookmark object back into | |
70 ;; the bookmark buffer. | |
71 ;; `s' will save the bookmark list. | |
72 ;; `Q' will quit gopher entirely, killing all gopher buffers. | |
73 ;; | |
74 ;; All commands which operate on a specific object can take an optional | |
75 ;; numeric prefix argument giving the index of the object on which | |
76 ;; to operate. | |
77 ;; | |
78 ;; In document mode: | |
79 ;; Space pages forward. | |
80 ;; Delete pages backward. | |
81 ;; `q', `l' or `u' returns to the last node. | |
82 ;; | |
83 ;; In the CSO entry form: | |
84 ;; `C-c RET' performs a look-up, based on the field contents | |
85 ;; you've filed in. | |
86 ;; `C-c l' returns to the previous node. | |
87 ;; | |
88 ;; Telnets: | |
89 ;; If you have an X server set, gopher will try to create an xterm | |
90 ;; running telnet. If not, the emacs-lisp telnet mode will be used. | |
91 ;; From the emacs-lisp telnet mode, use `C-c l' to kill the session | |
92 ;; and return to the previous node. | |
93 ;; See also the variable gopher-telnet-command. | |
94 ;; | |
95 ;; Images: | |
96 ;; Images are displayed using the command gopher-image-display-command. | |
97 ;; The default setting for this variable uses xv. | |
98 ;; | |
99 ;; Note: | |
100 ;; If gopher consistently hangs while trying to retrieve an object, | |
101 ;; try turning on gopher-buggy-accept (which see). | |
102 ;; | |
103 ;; VMS notes: | |
104 ;; To use this on VMS, you'll need my emacs subprocess patches (recently | |
105 ;; posted on gnu.emacs.sources; if you can't find them, send me mail). | |
106 ;; To be able to run telnet in a separate decterm, you'll also need | |
107 ;; to (setq shell-file-name "docmd") and create the file | |
108 ;; emacs_library:[etc]docmd.com containing the following: | |
109 ;; $ if p1 .eqs. "-C" then p1 = "" | |
110 ;; $ deass sys$input | |
111 ;; $ 'p1 'p2 'p3 'p4 'p5 'p6 'p7 'p8 | |
112 ;; $ eoj | |
113 | |
114 ;;; Change Log: | |
115 ;; | |
116 ;; Version 1.03 30-APR-1993 | |
117 ;; * Add a buffer-local variable gopher-obj to all gopher buffers bound | |
118 ;; to the description of the object contained in that buffer. | |
119 ;; | |
120 ;; Version 1.02 20-APR-1993 | |
121 ;; * Avoid using replace-regexp in gopher-clean-text. (Suggested by | |
122 ;; sbyrnes@rice.edu (Steven Byrnes)). | |
123 ;; * Added gopher-port-aliases. | |
124 ;; * Print ports as strings in gopher-directory-show-object. | |
125 ;; * Don't (ding) when the net stream closes unexpectedly. | |
126 ;; * Added image display (based on code from beldar@MicroUnity.com | |
127 ;; (Gardner Cohen)). | |
128 ;; * Attempt to improve error reporting. | |
129 ;; * Reworked gopher-parse-bookmark to handle out-of-order fields. | |
130 ;; * Added gopher-atpoint. | |
131 ;; * Moved bookmark init stuff to gopher-read-bookmarks. | |
132 ;; * Added gopher-quit (based on code from Thomas L|fgren | |
133 ;; <tde9104@abacus.hgs.se>). | |
134 ;; * Change usage of background for XEmacs. | |
135 ;; (Patch from "William M. Perry" <wmperry@raisin.ucs.indiana.edu>). | |
136 ;; * Added a (provide 'gopher) at end. | |
137 ;; * Added `f' and `e' bindings in directory mode. | |
138 ;; | |
139 ;; Version 1.01 | |
140 ;; * Added patch suggested by Humberto Ortiz-Zuazaga | |
141 ;; <zuazaga@ucunix.san.uc.EDU> to allow null Path= items in .gopherrc. | |
142 ;; | |
143 ;; Version 1.00 29-AUG-1992 | |
144 ;; * Added gopher-buggy-accept. | |
145 ;; * Reworked telnet stuff to use an arbitrary command string to start up | |
146 ;; the telnet process. This can be used to start the telnet in a | |
147 ;; separate terminal window. | |
148 ;; Based on code from Stewart Clamen. | |
149 ;; * Stewart Clamen <clamen@cs.cmu.edu> added bookmarks. | |
150 ;; * Added 's key binding to save bookmarks. | |
151 ;; * Added a prefix argument to gopher-directory-buffer and | |
152 ;; gopher-add-bookmark. | |
153 ;; * Added standard emacs-lisp header. | |
154 ;; * Stewart Clamen <clamen@cs.cmu.edu> added some error trapping and | |
155 ;; recovery (gopher-retrieve-document-cleanly). | |
156 ;; * Appended node description to the node's buffer's name. | |
157 ;; * Reformat bookmark buffers when returning to them via gopher-last-node | |
158 ;; or when an item is deleted. | |
159 ;; * Added gopher-yank-bookmark. | |
160 ;; * Added gopher-bookmark-modified-tick to prevent reformatting bookmark | |
161 ;; buffers needlessly. | |
162 ;; | |
163 ;; Version 0.92 27-JUL-1992 | |
164 ;; * Added gopher-hostname-aliases. | |
165 ;; | |
166 ;; Version 0.91 30-JUN-1992 | |
167 ;; * Deal with servers which send stuff after the CR. | |
168 ;; * Prevent gopher-directory-show-object from clearing the read-only flag. | |
169 ;; * Allow specification of port number in `C-u M-x gopher'. | |
170 ;; | |
171 ;; Version 0.9 29-JUN-1992 | |
172 ;; * Initial release. | |
173 | |
174 ;;; Code: | |
175 | |
176 (require 'electric) | |
177 (require 'forms) | |
178 | |
179 ;; background has the same name as an epoch function. | |
180 ;; Rename it to gopher-background... | |
181 ;; also, the version i got from the archive didn't have a provide... | |
182 (cond ((and (string-lessp "19" emacs-version) | |
183 (not (boundp 'epoch::version))) | |
184 ;; background is obsolete in emacs19: just add a & to shell-command. | |
185 (defun gopher-background (command) | |
186 (shell-command (concat command "&")))) | |
187 (t | |
188 ;; background has the same name as an epoch function. | |
189 ;; Rename it to gopher-background... | |
190 ;; also, the version i got from the archive didn't have a provide... | |
191 (if (not (fboundp 'gopher-background)) | |
192 (if (fboundp 'background) | |
193 (let ((old-background (symbol-function 'background))) | |
194 (load-library "background") | |
195 (fset 'gopher-background (symbol-function 'background)) | |
196 (fset 'background old-background)) | |
197 (load-library "background") | |
198 (fset 'gopher-background (symbol-function 'background)) | |
199 )) | |
200 )) | |
201 | |
202 (defvar gopher-root-node (vector ?1 "root" "" "ucs_gopher" 70) | |
203 "The root gopher server, as a gopher object.") | |
204 | |
205 (defvar gopher-directory-mode-hook nil | |
206 "*Invoked when entering a new gopher directory.") | |
207 (defvar gopher-directory-mode-map (make-keymap) | |
208 "Keymap for gopher-directory-mode.") | |
209 | |
210 (defvar gopher-document-mode-hook nil | |
211 "*Invoked when showing gopher document.") | |
212 (defvar gopher-document-mode-map (make-keymap) | |
213 "Keymap for gopher-document-mode.") | |
214 | |
215 (defvar gopher-form-mode-hooks nil | |
216 "*Invoked with entering a gopher form (i.e., for CSO).") | |
217 (defvar gopher-form-mode-map (make-keymap) | |
218 "Keymap for gopher-form-mode.") | |
219 | |
220 (defvar gopher-tmp-buf nil | |
221 "Buffer used to receive output from gopher.") | |
222 | |
223 (defvar gopher-debug-read t | |
224 "*If non-nil, show the current status about reading the gopher server output.") | |
225 | |
226 ;; On some systems (such as SGI Iris), accept-process-output doesn't seem | |
227 ;; to return for the last packet received on a connection. Turn this on | |
228 ;; to work around the problem, but does anyone know what causes this? | |
229 (defvar gopher-buggy-accept nil | |
230 "*If non-nil, use sit-for instead of accept-process-output. | |
231 If gopher consistently hangs while fetching an object, try turning this on.") | |
232 | |
233 (defvar gopher-hostname-aliases | |
234 '(("128.230.33.31" . "oliver.syr.edu")) | |
235 "Emacs can't deal with raw IP addresses used as a hostname. | |
236 Use this to work around...") | |
237 | |
238 (defvar gopher-port-aliases | |
239 '(("whois_port" . 43)) | |
240 "Some losing hosts send a port name instead of a number. | |
241 Use this table to convert...") | |
242 | |
243 | |
244 (defvar gopher-support-bookmarks nil | |
245 "*If nil, do not implement bookmarks. | |
246 If 'unix or t, read and write bookmarks to ~/.gopherrc. | |
247 If a filename, read and save vector from there directly (not implemented yet). | |
248 If a vector, treat as a built-in directory.") | |
249 | |
250 (defconst gopher-bookmarks nil "Internal bookmark directory.") | |
251 (defconst gopher-bookmarks-modified nil "Do bookmarks need to be saved?") | |
252 (defconst gopher-killed-bookmark nil "The last bookmark object to be killed") | |
253 (defconst gopher-bookmark-directory-p nil | |
254 "Is this buffer a bookmark directory? A buffer-local variable.") | |
255 | |
256 (defvar gopher-bookmark-modified-tick 0 | |
257 "Counts each time the bookmark vector is modified.") | |
258 | |
259 | |
260 (defvar gopher-telnet-command | |
261 (cond ((eq system-type 'vax-vms) | |
262 (if (getenv "DECW$DISPLAY") | |
263 "create/terminal/wait/window=(title=\"telnet\") telnet")) | |
264 (t | |
265 (if (getenv "DISPLAY") | |
266 "xterm -e telnet")) | |
267 ) | |
268 "*Command to use to start a telnet session. | |
269 If this is nil, the emacs-lisp telnet package will be used. | |
270 The default setting is to create a terminal window running telnet | |
271 if you've specified an X server, and to use the emacs-lisp telnet otherwise.") | |
272 | |
273 | |
274 (defvar gopher-image-display-command "xv -geometry +200+200" | |
275 "*The command used to try to display an image object.") | |
276 | |
277 | |
278 (defvar gopher-object-type-alist | |
279 '(( ?0 "" gopher-document-object) | |
280 ( ?1 "/" gopher-directory-object) | |
281 ( ?2 " <CSO>" gopher-cso-object) | |
282 ( ?3 " <error>" gopher-unimplemented-object) | |
283 ( ?4 " <binhex>" gopher-binary-object) | |
284 ( ?5 " <DOS>" gopher-binary-object) | |
285 ( ?6 " <UU>" gopher-binary-object) | |
286 ( ?7 " <?>" gopher-index-object) | |
287 ( ?8 " <TEL>" gopher-telnet-object) | |
288 ( ?9 " <bin>" gopher-binary-object) | |
289 ( ?T " <T>" gopher-unimplemented-object) | |
290 ( ?s " <)" gopher-binary-object) | |
291 ( ?M " <MIME>" gopher-unimplemented-object) | |
292 ( ?h " <html>" gopher-unimplemented-object) | |
293 ( ?I " <image>" gopher-image-object) | |
294 ( ?c " <cal>" gopher-unimplemented-object) | |
295 ( ?g " <GIF>" gopher-image-object) | |
296 ) | |
297 "*Alist describing the types of gopher objects this client know about. | |
298 The keys are the gopher type characters. | |
299 The second element in each list is the string to tag onto the end | |
300 of an object's description, to identify it to the user. | |
301 The third element is the function to use to retrieve the object. | |
302 It is called with two arguments: the gopher object to retrieve and | |
303 the buffer which should be returned to when the user is done | |
304 with this object.") | |
305 | |
306 | |
307 ;;; | |
308 ;;; The data structure describing a gopher object is a vector of five elements: | |
309 ;;; [ TYPE DESCR SELECTOR HOST PORT ] | |
310 ;;; | |
311 ;;; TYPE is the type character. | |
312 ;;; DESCR is the human-readable description of the object. | |
313 ;;; SELECTOR is the opaque selector to be sent to HOST to retrieve the obj. | |
314 ;;; HOST is the name of the Internet host on which the object resides. | |
315 ;;; PORT is the TCP/IP port on which the host is listening. | |
316 ;;; | |
317 ;;; The following macros set and fetch elements of this structure. | |
318 ;;; | |
319 | |
320 (defconst gopher-object-length 5) | |
321 | |
322 (defmacro gopher-object-type (object) | |
323 "Return the gopher type of OBJECT." | |
324 (` (aref (, object) 0))) | |
325 | |
326 (defmacro gopher-object-descr (object) | |
327 "Return the gopher description of OBJECT." | |
328 (` (aref (, object) 1))) | |
329 | |
330 (defmacro gopher-object-selector (object) | |
331 "Return the gopher selector string for OBJECT." | |
332 (` (aref (, object) 2))) | |
333 | |
334 (defmacro gopher-object-host (object) | |
335 "Return the gopher hostname for OBJECT." | |
336 (` (aref (, object) 3))) | |
337 | |
338 (defmacro gopher-object-port (object) | |
339 "Return the gopher TCP port number for OBJECT." | |
340 (` (aref (, object) 4))) | |
341 | |
342 | |
343 (defmacro gopher-set-object-type (object type) | |
344 "Set the gopher type of OBJECT to TYPE." | |
345 (` (aset (, object) 0 (, type)))) | |
346 | |
347 (defmacro gopher-set-object-descr (object descr) | |
348 "Set the gopher description of OBJECT to DESCR." | |
349 (` (aset (, object) 1 (, descr)))) | |
350 | |
351 (defmacro gopher-set-object-selector (object selector) | |
352 "Set the gopher selector string for OBJECT to SELECTOR." | |
353 (` (aset (, object) 2 (, selector)))) | |
354 | |
355 (defmacro gopher-set-object-host (object host) | |
356 "Set the gopher hostname for OBJECT to HOST." | |
357 (` (aset (, object) 3 (, host)))) | |
358 | |
359 (defmacro gopher-set-object-port (object port) | |
360 "Set the gopher TCP port number for OBJECT to PORT." | |
361 (` (aset (, object) 4 (, port)))) | |
362 | |
363 | |
364 (defmacro gopher-retrieve-document-cleanly (args handle &rest body) | |
365 "Call gopher-retrieve-document with condition-case wrapped around, | |
366 applying HANDLE if appropriate." | |
367 (` (condition-case nil | |
368 (progn | |
369 (gopher-retrieve-document (,@ args)) | |
370 (,@ body)) | |
371 (error (, handle))))) | |
372 | |
373 | |
374 ;; | |
375 ;; buffer-local variables. | |
376 ;; declared here to prevent warnings from the new byte-compiler. | |
377 ;; | |
378 | |
379 (defvar gopher-dir nil) | |
380 (defvar gopher-last nil) | |
381 (defvar gopher-obj nil) | |
382 (defvar gopher-telnet-process-name nil) | |
383 (defvar gopher-bookmark-buffer-tick nil) | |
384 (defvar forms-accept-action nil) | |
385 | |
386 | |
387 ;;;;-------------------------------------------------------------------------- | |
388 ;;;; main dispatching logic. | |
389 ;;;; | |
390 | |
391 ;;;###autoload | |
392 (defun gopher (&optional askserv) | |
393 "Start a gopher session. With C-u, prompt for a gopher server." | |
394 (interactive "P") | |
395 (if askserv | |
396 (progn | |
397 (gopher-set-object-host | |
398 gopher-root-node | |
399 (read-string "Gopher server: " | |
400 (gopher-object-host gopher-root-node))) | |
401 | |
402 (let (portstr port) | |
403 (while (not (numberp port)) | |
404 (setq portstr | |
405 (read-string "Port: " | |
406 (int-to-string | |
407 (gopher-object-port gopher-root-node)))) | |
408 | |
409 (setq port (condition-case nil | |
410 (car (read-from-string portstr)) | |
411 (error nil))) | |
412 | |
413 (if (not (numberp port)) | |
414 (progn | |
415 (ding) | |
416 (message "Port must be numeric") | |
417 (sit-for 1))) | |
418 ) | |
419 | |
420 (gopher-set-object-port gopher-root-node port)))) | |
421 | |
422 (gopher-read-bookmarks) | |
423 | |
424 (gopher-dispatch-object gopher-root-node nil)) | |
425 | |
426 | |
427 | |
428 ;;;###autoload | |
429 (defun gopher-atpoint nil | |
430 "Try to interpret the text around point as a gopher bookmark, and dispatch | |
431 to that object." | |
432 (interactive) | |
433 | |
434 (let (bkmk) | |
435 (save-excursion | |
436 (re-search-backward "^#[ \t]*$\\|^[ \t]*$\\|\\`") | |
437 (skip-chars-forward " \t\n") | |
438 (setq bkmk (gopher-parse-bookmark))) | |
439 (if bkmk | |
440 (progn | |
441 (gopher-read-bookmarks) | |
442 (gopher-dispatch-object bkmk nil)) | |
443 (error "Illformed bookmark")))) | |
444 | |
445 | |
446 (defun gopher-dispatch-object (obj lastbuf) | |
447 "Dispatch a gopher object depending on its type." | |
448 (let ((typedesc (assq (gopher-object-type obj) gopher-object-type-alist))) | |
449 | |
450 (if typedesc | |
451 (funcall (nth 2 typedesc) obj lastbuf) | |
452 (gopher-unimplemented-object obj lastbuf)))) | |
453 | |
454 | |
455 (defun gopher-unimplemented-object (obj lastbuf) | |
456 (error "unimplemented object type")) | |
457 | |
458 | |
459 ;;;;-------------------------------------------------------------------------- | |
460 ;;;; utilities | |
461 ;;;; | |
462 | |
463 (defun gopher-next-field nil | |
464 "Returns as a string all chars between point and the next tab or newline. | |
465 Point is advanced to after the tab (or to the end-of-line)." | |
466 | |
467 (let ((beg (point)) s) | |
468 (skip-chars-forward "^\t\n") | |
469 (setq s (buffer-substring beg (point))) | |
470 (if (eq (following-char) ?\t) | |
471 (forward-char)) | |
472 s)) | |
473 | |
474 | |
475 ;; from GNUS | |
476 (defun gopher-make-local-vars (&rest pairs) | |
477 ;; Take VARIABLE-VALUE pairs and makes local variables initialized to the | |
478 ;; value. | |
479 (while pairs | |
480 (make-local-variable (car pairs)) | |
481 (set (car pairs) (car (cdr pairs))) | |
482 (setq pairs (cdr (cdr pairs))))) | |
483 | |
484 | |
485 (defun gopher-get-tmp-buf nil | |
486 "Get a temporary buffer in which to receive gopher output." | |
487 (or (bufferp gopher-tmp-buf) | |
488 (progn | |
489 (setq gopher-tmp-buf (get-buffer-create " *gopher-tmp*")) | |
490 (buffer-flush-undo gopher-tmp-buf))) | |
491 gopher-tmp-buf) | |
492 | |
493 | |
494 (defun gopher-get-dir-buf (descr) | |
495 "Get a new buffer suitable for a gopher directory or document." | |
496 (let ((buf (generate-new-buffer (concat "*gopher*" descr)))) | |
497 (buffer-flush-undo buf) | |
498 buf)) | |
499 | |
500 (fset 'gopher-get-doc-buf (symbol-function 'gopher-get-dir-buf)) | |
501 | |
502 | |
503 (defun gopher-trim-blanks (str) | |
504 "Remove leading and trailing blanks from STR." | |
505 (string-match "\\`[ \t\n]*" str) | |
506 (substring str | |
507 (match-end 0) | |
508 (string-match "[ \t\n]*\\'" str (match-end 0)))) | |
509 | |
510 | |
511 ;;;;-------------------------------------------------------------------------- | |
512 ;;;; directory handling | |
513 ;;;; | |
514 | |
515 | |
516 (defun gopher-directory-object (obj oldbuf) | |
517 "Retrieve and display a gopher directory." | |
518 | |
519 (let ((tmpbuf (gopher-get-tmp-buf)) | |
520 (dirbuf (gopher-get-dir-buf (gopher-object-descr obj)))) | |
521 | |
522 ;; Get the directory... | |
523 (gopher-retrieve-document-cleanly (tmpbuf | |
524 (gopher-object-selector obj) | |
525 (gopher-object-host obj) | |
526 (gopher-object-port obj)) | |
527 | |
528 (progn | |
529 (kill-buffer dirbuf) | |
530 (error "Problems retrieving directory.")) | |
531 | |
532 ;; Parse it and store our internal representation in gopher-dir. | |
533 (switch-to-buffer dirbuf) | |
534 (gopher-make-local-vars | |
535 'gopher-dir (gopher-parse-directory tmpbuf) | |
536 'gopher-obj obj | |
537 'gopher-last oldbuf) | |
538 | |
539 ;; Format it for your viewing pleasure. | |
540 (gopher-format-directory gopher-dir dirbuf) | |
541 (goto-char (point-min)) | |
542 (if (> (- (point-max) (point)) 7) (forward-char 7)) | |
543 | |
544 ;; Turn on directory mode and put the description in the mode line. | |
545 (gopher-directory-mode) | |
546 (setq mode-line-buffer-identification (concat "Gopher: " | |
547 (gopher-object-descr obj))) | |
548 ))) | |
549 | |
550 | |
551 (defun gopher-parse-directory (buf) | |
552 "Parse the gopher directory in buffer BUF into our internal representation. | |
553 Returns a vector of gopher objects." | |
554 | |
555 (save-excursion | |
556 (set-buffer buf) | |
557 (goto-char (point-min)) | |
558 | |
559 (let* ((len (count-lines (point-min) (point-max))) | |
560 (dir (make-vector len nil)) | |
561 (i 0)) | |
562 | |
563 (while (not (eobp)) | |
564 (aset dir i (gopher-parse-directory-line)) | |
565 (setq i (1+ i)) | |
566 (forward-line 1)) | |
567 | |
568 dir))) | |
569 | |
570 | |
571 (defun gopher-parse-directory-line nil | |
572 "Parse the line containing point as a gopher directory entry. | |
573 Returns the corresponding gopher object." | |
574 | |
575 (let (type descr selector host port) | |
576 (beginning-of-line) | |
577 (setq type (following-char)) | |
578 (forward-char) | |
579 (setq descr (gopher-next-field)) | |
580 (setq selector (gopher-next-field)) | |
581 (setq host (gopher-next-field)) | |
582 (setq port (gopher-next-field)) | |
583 | |
584 (if (string-match "^[0-9]+$" port) | |
585 (setq port (string-to-int port))) | |
586 | |
587 (vector type descr selector host port))) | |
588 | |
589 | |
590 (defun gopher-format-directory (dir buf) | |
591 "Print the directory vector DIR into buffer BUF." | |
592 | |
593 (save-excursion | |
594 (set-buffer buf) | |
595 (erase-buffer) | |
596 (let ((i 0) | |
597 (len (length dir))) | |
598 (while (< i len) | |
599 (gopher-format-directory-line (aref dir i) (1+ i)) | |
600 (setq i (1+ i))) | |
601 | |
602 ))) | |
603 | |
604 | |
605 (defun gopher-format-directory-line (obj ndx) | |
606 "Insert a line describing the gopher object OBJ into the current buffer. | |
607 NDX is a numeric index to display to the left of the object description." | |
608 | |
609 (let ((ndx-str (int-to-string ndx)) | |
610 (typedesc (assq (gopher-object-type obj) gopher-object-type-alist))) | |
611 | |
612 ;; display the index number. use 5 digits, right-justified. | |
613 (if (< (length ndx-str) 5) | |
614 (insert (make-string (- 5 (length ndx-str)) ? ))) | |
615 (insert ndx-str) | |
616 (insert ". ") | |
617 | |
618 ;; add the object description. | |
619 (insert (gopher-object-descr obj)) | |
620 | |
621 ;; add a tag indicating the gopher object type. | |
622 (insert (if typedesc | |
623 (nth 1 typedesc) | |
624 (concat " ???" (char-to-string (gopher-object-type obj))))) | |
625 | |
626 (insert "\n"))) | |
627 | |
628 | |
629 (defun gopher-directory-mode nil | |
630 "Gopher directory mode. | |
631 | |
632 \\{gopher-directory-mode-map} | |
633 " | |
634 (use-local-map gopher-directory-mode-map) | |
635 (setq major-mode 'gopher-directory-mode) | |
636 (setq mode-name "gopher dir") | |
637 (run-hooks 'gopher-directory-mode-hook) | |
638 (setq buffer-read-only t) | |
639 | |
640 (require 'mode-motion) | |
641 (make-local-variable 'mode-motion-hook) | |
642 (setq mode-motion-hook 'mode-motion-highlight-line) | |
643 ) | |
644 | |
645 ;;; keymap for directory mode | |
646 (suppress-keymap gopher-directory-mode-map) | |
647 (define-key gopher-directory-mode-map "\r" 'gopher-directory-choose) | |
648 (define-key gopher-directory-mode-map " " 'gopher-directory-choose) | |
649 (define-key gopher-directory-mode-map "l" 'gopher-last-node) | |
650 (define-key gopher-directory-mode-map "q" 'gopher-last-node) | |
651 (define-key gopher-directory-mode-map "u" 'gopher-last-node) | |
652 (define-key gopher-directory-mode-map "=" 'gopher-directory-show-object) | |
653 (define-key gopher-directory-mode-map "Q" 'gopher-quit) | |
654 (define-key gopher-directory-mode-map "f" 'gopher-directory-choose) | |
655 (define-key gopher-directory-mode-map "e" 'gopher-directory-choose) | |
656 | |
657 ; Virginia Peck <vapeck@cs> Mon Aug 10 1992 | |
658 (define-key gopher-directory-mode-map "n" 'next-line) | |
659 (define-key gopher-directory-mode-map "p" 'previous-line) | |
660 ;;(define-key gopher-directory-mode-map "\C-xk" 'gopher-last-node) | |
661 | |
662 ; Stewart Clamen <clamen@cs.cmu.edu> Mon Aug 17 1992 | |
663 (define-key gopher-directory-mode-map "v" 'gopher-display-bookmarks) | |
664 (define-key gopher-directory-mode-map "a" 'gopher-add-bookmark) | |
665 (define-key gopher-directory-mode-map "\C-k" 'gopher-delete-bookmark) | |
666 (define-key gopher-directory-mode-map "s" 'gopher-directory-save-bookmarks) | |
667 (define-key gopher-directory-mode-map "\C-y" 'gopher-yank-bookmark) | |
668 | |
669 (define-key gopher-directory-mode-map 'button2 'gopher-mouse-directory-choose) | |
670 (define-key gopher-directory-mode-map 'button3 'gopher-directory-menu) | |
671 | |
672 | |
673 (defvar gopher-directory-menu | |
674 '("Gopher Commands" | |
675 ["Select This Link" gopher-directory-choose t] | |
676 ["Goto Last Node" gopher-last-node t] | |
677 ["Show Object Internals" gopher-directory-show-object t] | |
678 ["Quit" gopher-quit t] | |
679 )) | |
680 | |
681 (defun gopher-directory-menu (event) | |
682 (interactive "e") | |
683 (mouse-set-point event) | |
684 (beginning-of-line) | |
685 (popup-menu gopher-directory-menu)) | |
686 | |
687 (defun gopher-mouse-directory-choose (event arg) | |
688 (interactive "e\nP") | |
689 (mouse-set-point event) | |
690 (beginning-of-line) | |
691 (gopher-directory-choose arg)) | |
692 | |
693 | |
694 (defun gopher-directory-nth-obj (n) | |
695 "Returns the Nth object (starting at 1) in a gopher directory buffer." | |
696 (if (or (<= n 0) (> n (length gopher-dir))) | |
697 (error "Out of range.")) | |
698 (aref gopher-dir (1- n))) | |
699 | |
700 | |
701 (defun gopher-directory-n (arg) | |
702 "Return the index of the object specified by ARG (starting at 1). | |
703 If ARG is nil, this is the index of the current line. | |
704 Otherwise, it is the value of ARG (as a prefix argument)." | |
705 (if arg | |
706 (prefix-numeric-value arg) | |
707 (if (eq (point) (point-max)) | |
708 (1+ (count-lines (point-min) (point-max))) | |
709 (count-lines (point-min) (1+ (point)))))) | |
710 | |
711 | |
712 (defun gopher-directory-obj (arg) | |
713 "Return the gopher object given by prefix arg ARG. | |
714 If it is nil, return the object given by the line point is on. | |
715 Otherwise, ARG is the index of the object." | |
716 (gopher-directory-nth-obj (gopher-directory-n arg))) | |
717 | |
718 | |
719 (defun gopher-directory-choose (arg) | |
720 "Choose an item from the directory, and do whatever is appropriate | |
721 based on the object's type. Default is to choose the object given by the | |
722 line the cursor is on. With numeric prefix argument N, choose object N." | |
723 (interactive "P") | |
724 (gopher-dispatch-object (gopher-directory-obj arg) (current-buffer))) | |
725 | |
726 | |
727 (defun gopher-directory-show-object (arg) | |
728 "Dump the internal information in a gopher object. | |
729 With numeric prefix argument N, show information about the Nth object." | |
730 (interactive "P") | |
731 (let* ((obj (gopher-directory-obj arg)) | |
732 (type (gopher-object-type obj)) | |
733 (typespec (assq type gopher-object-type-alist)) | |
734 (typetag (if typespec (nth 1 typespec) "?")) | |
735 (typeproc (if typespec (nth 2 typespec) "?"))) | |
736 (with-output-to-temp-buffer "*Gopher object*" | |
737 (princ (format "Type : %c `%s' %s\n" type typetag typeproc)) | |
738 (princ (format "Description : %s\n" (gopher-object-descr obj))) | |
739 (princ (format "Selector : %s\n" (gopher-object-selector obj))) | |
740 (princ (format "Host : %s\n" (gopher-object-host obj))) | |
741 (princ (format "Port : %s\n" (gopher-object-port obj))) | |
742 (current-buffer) | |
743 )) | |
744 (shrink-window-if-larger-than-buffer (get-buffer-window "*Gopher object*")) | |
745 | |
746 ;; shrink-window-if-larger-than-buffer screws these up... | |
747 (set-buffer-modified-p nil) | |
748 (setq buffer-read-only t)) | |
749 | |
750 | |
751 (defun gopher-last-node nil | |
752 "Return to the previous gopher node. | |
753 By convention, a gopher buffer has the local variable gopher-last which | |
754 contains the buffer to which we should return." | |
755 (interactive) | |
756 (let ((oldbuf (current-buffer))) | |
757 (if gopher-last | |
758 (progn | |
759 (switch-to-buffer gopher-last) | |
760 (kill-buffer oldbuf) | |
761 (and (gopher-bookmark-directory-p) | |
762 (> gopher-bookmark-modified-tick gopher-bookmark-buffer-tick) | |
763 (let ((ppos (1- (gopher-directory-n nil)))) | |
764 (gopher-format-bookmarks) | |
765 (forward-line ppos) | |
766 (if (> (- (point-max) (point)) 7) (forward-char 7))))) | |
767 (if (and gopher-support-bookmarks | |
768 gopher-bookmarks-modified | |
769 (y-or-n-p | |
770 "Changes have been made to the Bookmark directory. Save? ")) | |
771 (gopher-save-bookmarks)) | |
772 (kill-buffer oldbuf)))) | |
773 | |
774 | |
775 (defun gopher-directory-save-bookmarks () | |
776 "Save the bookmark list." | |
777 (interactive) | |
778 | |
779 (if (not (gopher-bookmark-directory-p)) | |
780 (error "This isn't the bookmark directory.")) | |
781 | |
782 (gopher-save-bookmarks)) | |
783 | |
784 | |
785 | |
786 ;;; Gopher clean-up and quit. | |
787 ;;; Originally from Thomas L|fgren <tde9104@abacus.hgs.se> | |
788 | |
789 (defun gopher-quit nil | |
790 "Quit gopher, and kill all gopher buffers. | |
791 If there are unsaved changes to your bookmark directory, you will be | |
792 asked if you want to save them" | |
793 (interactive) | |
794 (if (y-or-n-p "Do you really want to kill all gopher buffers? ") | |
795 (progn | |
796 (if (and gopher-support-bookmarks | |
797 gopher-bookmarks-modified | |
798 (y-or-n-p | |
799 "Changes have been made to the Bookmark directory. Save? ")) | |
800 (gopher-save-bookmarks)) | |
801 (let ((buflist (buffer-list)) | |
802 (case-fold-search t)) | |
803 (while buflist | |
804 (if (eq (string-match "\\*gopher" (buffer-name (car buflist))) 0) | |
805 (kill-buffer (car buflist))) | |
806 (setq buflist (cdr buflist))))))) | |
807 | |
808 | |
809 ;;;;-------------------------------------------------------------------------- | |
810 ;;;; bookmarks (Implemented originally by clamen@cs.cmu.edu) | |
811 ;;;; | |
812 | |
813 | |
814 (defun gopher-read-bookmarks () | |
815 (cond ((null gopher-support-bookmarks)) | |
816 ((or (equal gopher-support-bookmarks 'unix) | |
817 (equal gopher-support-bookmarks t)) | |
818 (setq gopher-bookmarks | |
819 (gopher-read-unix-bookmarks))) | |
820 ((stringp gopher-support-bookmarks) | |
821 (gopher-read-lisp-bookmarks gopher-support-bookmarks)) | |
822 ((vectorp gopher-support-bookmarks) | |
823 (setq gopher-bookmarks gopher-support-bookmarks)) | |
824 (t | |
825 (message "Illformed gopher-bookmarks, assuming none")))) | |
826 | |
827 | |
828 (defun gopher-read-unix-bookmarks () | |
829 "Read bookmarks out of ~/.gopherrc file." | |
830 (let ((rcfile "~/.gopherrc")) | |
831 (if (file-exists-p rcfile) | |
832 (let* ((rcbuf (find-file-noselect rcfile)) | |
833 (bkmks (gopher-parse-bookmark-buffer rcbuf))) | |
834 (kill-buffer rcbuf) | |
835 (setq gopher-bookmarks-modified nil) | |
836 (setq gopher-bookmark-modified-tick | |
837 (1+ gopher-bookmark-modified-tick)) | |
838 bkmks) | |
839 (message "No %s exists." rcfile) | |
840 nil))) | |
841 | |
842 (defun gopher-parse-bookmark-buffer (buf) | |
843 "Read buffer containing bookmarks, formatted like ~.gopherrc | |
844 in UNIX gopher client." | |
845 (save-excursion | |
846 (set-buffer buf) | |
847 (goto-char (point-min)) | |
848 (if (re-search-forward "^bookmarks:\n" (point-max) t) | |
849 (let (bkmk bkmks) | |
850 (while (setq bkmk (gopher-parse-bookmark)) | |
851 (setq bkmks (cons bkmk bkmks))) | |
852 (apply 'vector (reverse bkmks)))))) | |
853 | |
854 (defun gopher-parse-bookmark-line (regexp end setf bkmk) | |
855 (save-excursion | |
856 (if (re-search-forward regexp end t) | |
857 (eval (list setf bkmk | |
858 (buffer-substring (match-beginning 1) (match-end 1)))) | |
859 ))) | |
860 | |
861 | |
862 (defun gopher-parse-bookmark () | |
863 "Read next bookmark. Return a directory object." | |
864 (if (looking-at "^#$") | |
865 (forward-line)) | |
866 (if (not (eobp)) | |
867 (let ((end (save-excursion | |
868 (forward-line 5) | |
869 (point))) | |
870 (bkmk (make-vector 5 nil))) | |
871 (prog1 | |
872 (and (gopher-parse-bookmark-line "^Type *= *\\(.+\\) *$" end | |
873 'gopher-set-object-type bkmk) | |
874 (gopher-parse-bookmark-line "^Name *= *\\(.*\\) *$" end | |
875 'gopher-set-object-descr bkmk) | |
876 (gopher-parse-bookmark-line "^Path *= *\\(.*\\) *$" end | |
877 'gopher-set-object-selector bkmk) | |
878 (gopher-parse-bookmark-line "^Host *= *\\(.+\\) *$" end | |
879 'gopher-set-object-host bkmk) | |
880 (gopher-parse-bookmark-line "^Port *= *\\(.+\\) *$" end | |
881 'gopher-set-object-port bkmk) | |
882 (progn | |
883 (gopher-set-object-type | |
884 bkmk (string-to-char (gopher-object-type bkmk))) | |
885 (gopher-set-object-port | |
886 bkmk (string-to-int (gopher-object-port bkmk))) | |
887 bkmk)) | |
888 (goto-char end)) | |
889 ))) | |
890 | |
891 (defun gopher-format-bookmarks () | |
892 "Make the current buffer (which is assumed to be a bookmark buffer) | |
893 contain an up-to-date listing of the bookmark list." | |
894 | |
895 (let ((buffer-read-only nil)) | |
896 (erase-buffer) | |
897 (setq gopher-dir gopher-bookmarks) | |
898 | |
899 ;; Format it for your viewing pleasure. | |
900 (gopher-format-directory gopher-dir (current-buffer)) | |
901 (goto-char (point-min)) | |
902 (if (> (- (point-max) (point)) 7) (forward-char 7)) | |
903 (setq gopher-bookmark-buffer-tick gopher-bookmark-modified-tick))) | |
904 | |
905 (defun gopher-display-bookmarks () | |
906 "Retrieve and display the gopher bookmark directory." | |
907 (interactive) | |
908 | |
909 (if (> (length gopher-bookmarks) 0) | |
910 (let ((oldbuf (current-buffer)) | |
911 (dirbuf (gopher-get-dir-buf "*Gopher Bookmarks*"))) | |
912 | |
913 ;; Store our internal representation in gopher-dir. | |
914 (switch-to-buffer dirbuf) | |
915 (gopher-make-local-vars | |
916 'gopher-dir gopher-bookmarks | |
917 'gopher-bookmark-directory-p t | |
918 'gopher-bookmark-buffer-tick gopher-bookmark-modified-tick | |
919 'gopher-obj 'bookmark | |
920 'gopher-last oldbuf) | |
921 | |
922 (gopher-format-bookmarks) | |
923 | |
924 ;; Turn on directory mode and put the description in the mode line. | |
925 (gopher-directory-mode) | |
926 (setq mode-line-buffer-identification (concat "Gopher: *Bookmarks*")) | |
927 ) | |
928 (error "No bookmarks supported."))) | |
929 | |
930 | |
931 (defun gopher-save-bookmarks () | |
932 "Save bookmarks." | |
933 (cond | |
934 ((or (equal gopher-support-bookmarks 'unix) | |
935 (equal gopher-support-bookmarks t)) | |
936 (gopher-save-unix-bookmarks)) | |
937 ((stringp gopher-support-bookmarks) | |
938 (gopher-save-lisp-bookmarks gopher-support-bookmarks)) | |
939 (t | |
940 (message "Illformed gopher-support-bookmarks, assuming none"))) | |
941 | |
942 (setq gopher-bookmarks-modified nil)) | |
943 | |
944 | |
945 (defun gopher-save-unix-bookmarks () | |
946 "Save bookmarks out to ~/.gopherrc file." | |
947 (save-excursion | |
948 (let* ((rcfile "~/.gopherrc") | |
949 (new-file-p (not (file-exists-p rcfile))) | |
950 (rcbuf (find-file-noselect rcfile))) | |
951 (set-buffer rcbuf) | |
952 (if new-file-p | |
953 (insert "bookmarks:\n") | |
954 (goto-char (point-min)) | |
955 (if (re-search-forward "^bookmarks:\n" nil t) | |
956 (delete-region (point) (point-max)) | |
957 (goto-char (point-max)) | |
958 (insert "bookmarks:\n"))) | |
959 | |
960 ;; Now, insert defined bookmarks into file | |
961 | |
962 (let ((obj-count 0)) | |
963 (while (< obj-count (length gopher-bookmarks)) | |
964 (let ((obj (aref gopher-bookmarks obj-count))) | |
965 (insert "#" | |
966 "\nType=" (gopher-object-type obj) | |
967 "\nName=" (gopher-object-descr obj) | |
968 "\nPath=" (gopher-object-selector obj) | |
969 "\nHost=" (gopher-object-host obj) | |
970 "\nPort=" (int-to-string (gopher-object-port obj)) | |
971 "\n") | |
972 (setq obj-count (1+ obj-count))))) | |
973 | |
974 (write-file rcfile)))) | |
975 | |
976 | |
977 (defun gopher-add-bookmark (arg) | |
978 "Add current object to menu of bookmarks. | |
979 With numeric prefix argument N, add Nth object." | |
980 (interactive "P") | |
981 (if (gopher-bookmark-directory-p) | |
982 (error "That item is already a bookmark!") | |
983 (let ((existing-bookmarks gopher-bookmarks) | |
984 (new-bookmarks (make-vector (1+ (length gopher-bookmarks)) nil)) | |
985 (obj (copy-sequence (gopher-directory-obj arg))) | |
986 (l (length gopher-bookmarks))) | |
987 (gopher-set-object-descr | |
988 obj | |
989 (read-from-minibuffer "Node Name: " | |
990 (gopher-object-descr obj))) | |
991 (aset new-bookmarks l obj) | |
992 (while (> l 0) | |
993 (progn (setq l (1- l)) | |
994 (aset new-bookmarks l (aref existing-bookmarks l)))) | |
995 (setq gopher-bookmarks new-bookmarks | |
996 gopher-bookmarks-modified t | |
997 gopher-bookmark-modified-tick (1+ gopher-bookmark-modified-tick)) | |
998 ))) | |
999 | |
1000 | |
1001 (defun gopher-delete-bookmark (arg) | |
1002 "Delete current bookmark. | |
1003 With numeric prefix argument N, delete Nth bookmark." | |
1004 (interactive "P") | |
1005 (if (not (gopher-bookmark-directory-p)) | |
1006 (error "Can only delete object in Bookmark directory.") | |
1007 (let ((new-bookmarks (make-vector (1- (length gopher-bookmarks)) nil)) | |
1008 (pos (1- (gopher-directory-n arg))) | |
1009 (l (length gopher-bookmarks)) | |
1010 (i 0)) | |
1011 (while (< i pos) | |
1012 (progn (aset new-bookmarks i (aref gopher-bookmarks i)) | |
1013 (setq i (1+ i)))) | |
1014 (while (< i (1- l)) | |
1015 (progn (aset new-bookmarks i (aref gopher-bookmarks (1+ i))) | |
1016 (setq i (1+ i)))) | |
1017 (setq gopher-killed-bookmark (aref gopher-bookmarks pos) | |
1018 gopher-bookmarks new-bookmarks | |
1019 gopher-dir new-bookmarks | |
1020 gopher-bookmarks-modified t | |
1021 gopher-bookmark-modified-tick (1+ gopher-bookmark-modified-tick)) | |
1022 (let ((ppos (1- (gopher-directory-n nil)))) | |
1023 (if (< pos ppos) | |
1024 (setq ppos (1- ppos))) | |
1025 (gopher-format-bookmarks) | |
1026 (goto-char (point-min)) | |
1027 (forward-line ppos) | |
1028 (forward-char 7))) | |
1029 (if (= (point) (point-max)) (previous-line 1)) | |
1030 ; (let ((buffer-read-only nil)) | |
1031 ; (beginning-of-line 1) | |
1032 ; (kill-line 1) | |
1033 ; (if (= (point) (point-max)) (previous-line 1))) | |
1034 (if (zerop (length gopher-bookmarks)) | |
1035 (gopher-last-node)))) | |
1036 | |
1037 | |
1038 (defun gopher-yank-bookmark (arg) | |
1039 "Yank the most recently killed bookmark at the current position. | |
1040 With numeric prefix argument N, yank into position N." | |
1041 (interactive "P") | |
1042 (cond ((not (gopher-bookmark-directory-p)) | |
1043 (error "Can only yank bookmark objects into bookmark directory.")) | |
1044 ((null gopher-killed-bookmark) | |
1045 (error "No killed bookmark object")) | |
1046 (t | |
1047 (let* ((len (length gopher-bookmarks)) | |
1048 (new-bookmarks (make-vector (1+ len) nil)) | |
1049 (pos (1- (gopher-directory-n arg))) | |
1050 i) | |
1051 | |
1052 (if (or (< pos 0) (> pos (length gopher-bookmarks))) | |
1053 (error "Out of range.")) | |
1054 | |
1055 (setq i (1- pos)) | |
1056 (while (>= i 0) | |
1057 (aset new-bookmarks i (aref gopher-bookmarks i)) | |
1058 (setq i (1- i))) | |
1059 | |
1060 (aset new-bookmarks pos gopher-killed-bookmark) | |
1061 | |
1062 (setq i pos) | |
1063 (while (< i len) | |
1064 (aset new-bookmarks (1+ i) (aref gopher-bookmarks i)) | |
1065 (setq i (1+ i))) | |
1066 | |
1067 (setq gopher-bookmarks new-bookmarks | |
1068 gopher-bookmarks-modified t | |
1069 gopher-killed-bookmark nil | |
1070 gopher-bookmark-modified-tick | |
1071 (1+ gopher-bookmark-modified-tick)) | |
1072 | |
1073 (let ((ppos (1- (gopher-directory-n nil)))) | |
1074 (if (<= pos ppos) | |
1075 (setq ppos (1+ ppos))) | |
1076 (gopher-format-bookmarks) | |
1077 (goto-char (point-min)) | |
1078 (forward-line ppos) | |
1079 (forward-char 7)) | |
1080 )))) | |
1081 | |
1082 | |
1083 (defun gopher-bookmark-directory-p () | |
1084 "Return T if currently displaying Bookmark directory." | |
1085 gopher-bookmark-directory-p) | |
1086 ; (equal gopher-dir gopher-bookmarks)) | |
1087 | |
1088 | |
1089 (defun gopher-read-lisp-bookmarks (fn) | |
1090 "currently unsupported" | |
1091 (error "gopher-read-lisp-bookmark is not yet supported. Sorry.")) | |
1092 | |
1093 (defun gopher-save-lisp-bookmarks (fn) | |
1094 "currently unsupported" | |
1095 (error "gopher-save-lisp-bookmark is not yet supported. Sorry.")) | |
1096 | |
1097 | |
1098 | |
1099 ;;;;-------------------------------------------------------------------------- | |
1100 ;;;; gopher documents | |
1101 ;;;; | |
1102 | |
1103 | |
1104 (defun gopher-document-object (obj oldbuf &optional end-regexp) | |
1105 "Retrieve and display a gopher document. | |
1106 Optional argument END-REGEXP is used if the data will not be ended by `.'." | |
1107 | |
1108 (let ((docbuf (gopher-get-doc-buf (gopher-object-descr obj)))) | |
1109 | |
1110 ;; Snarf the data into the buffer. | |
1111 (gopher-retrieve-document-cleanly (docbuf | |
1112 (gopher-object-selector obj) | |
1113 (gopher-object-host obj) | |
1114 (gopher-object-port obj) | |
1115 end-regexp) | |
1116 | |
1117 (progn | |
1118 (kill-buffer docbuf) | |
1119 (error "Problems retrieving document.")) | |
1120 | |
1121 ;; Turn on document mode and put the description in the mode line. | |
1122 (switch-to-buffer docbuf) | |
1123 (gopher-make-local-vars | |
1124 'gopher-obj obj | |
1125 'gopher-last oldbuf) | |
1126 (goto-char (point-min)) | |
1127 (gopher-document-mode) | |
1128 (setq mode-line-buffer-identification (concat "Gopher: " | |
1129 (gopher-object-descr obj))) | |
1130 ))) | |
1131 | |
1132 | |
1133 ;; keymap for document mode | |
1134 (suppress-keymap gopher-document-mode-map) | |
1135 | |
1136 ;Virginia Peck <vapeck@cs> Mon Aug 10 21:44:35 1992 | |
1137 ;;(define-key gopher-document-mode-map "\C-xk" 'gopher-last-node) | |
1138 | |
1139 (define-key gopher-document-mode-map "l" 'gopher-last-node) | |
1140 (define-key gopher-document-mode-map "q" 'gopher-last-node) | |
1141 (define-key gopher-document-mode-map "u" 'gopher-last-node) | |
1142 (define-key gopher-document-mode-map " " 'scroll-up) | |
1143 (define-key gopher-document-mode-map "\C-?" 'scroll-down) | |
1144 (define-key gopher-document-mode-map "\r" 'gopher-scroll-one-line-up) | |
1145 | |
1146 | |
1147 (defun gopher-document-mode nil | |
1148 "Gopher document mode. | |
1149 | |
1150 \\{gopher-document-mode-map} | |
1151 " | |
1152 (use-local-map gopher-document-mode-map) | |
1153 (setq major-mode 'gopher-document-mode) | |
1154 (setq mode-name "gopher doc") | |
1155 (run-hooks 'gopher-document-mode-hook) | |
1156 (setq buffer-read-only t)) | |
1157 | |
1158 | |
1159 ;; from gosmacs.el | |
1160 (defun gopher-scroll-one-line-up (&optional arg) | |
1161 "Scroll the selected window up (forward in the text) one line (or N lines)." | |
1162 (interactive "p") | |
1163 (scroll-up (or arg 1))) | |
1164 | |
1165 | |
1166 ;;;;-------------------------------------------------------------------------- | |
1167 ;;;; CSO handling. | |
1168 ;;;; | |
1169 ;;;; uses a subset of forms mode to handle data entry. | |
1170 ;;;; | |
1171 | |
1172 (defun gopher-cso-object (obj oldbuf) | |
1173 "Display a CSO lookup form." | |
1174 | |
1175 ;; The following will create a buffer displaying the form described | |
1176 ;; by the list in the last argument (cf. forms-mode). When the user | |
1177 ;; accepts the data in the form (by pressing `C-c RET'), the function | |
1178 ;; gopher-do-cso will be called with the data the user supplied. | |
1179 (gopher-form (gopher-object-descr obj) | |
1180 'gopher-do-cso | |
1181 4 | |
1182 '("====== phone directory lookup ======" | |
1183 "\n Press `C-c RET' to lookup, `C-c l' to return to the last gopher object." | |
1184 "\n (you must fill in at least one of the first three fields)" | |
1185 "\n" | |
1186 "Name : " 1 | |
1187 "\n" | |
1188 "Phone : " 2 | |
1189 "\n" | |
1190 "E-Mail : " 3 | |
1191 "\n" | |
1192 "Address : " 4 | |
1193 )) | |
1194 | |
1195 ;; Record gopher-last so gopher-last-node knows where to go. | |
1196 ;; Record gopher-obj so gopher-do-cso knows what server to query. | |
1197 (gopher-make-local-vars | |
1198 'gopher-last oldbuf | |
1199 'gopher-obj obj)) | |
1200 | |
1201 | |
1202 (defconst gopher-cso-fields '("name" "phone" "email" "address") | |
1203 "Field names to use in CSO queries.") | |
1204 | |
1205 (defun gopher-do-cso (vals) | |
1206 "Make a CSO query. VALS is the data the user entered in the form, | |
1207 as a list of strings." | |
1208 | |
1209 ;; Check that the required data was provided. | |
1210 (if (zerop (+ (length (nth 0 vals)) | |
1211 (length (nth 1 vals)) | |
1212 (length (nth 2 vals)))) | |
1213 (error "Must specify name, phone, or email.")) | |
1214 | |
1215 (let ((query "query") | |
1216 (fields gopher-cso-fields) | |
1217 (obj gopher-obj)) | |
1218 | |
1219 ;; Form the query string | |
1220 (while vals | |
1221 | |
1222 (if (not (zerop (length (car vals)))) | |
1223 (setq query (concat query " " (car fields) "=" (car vals)))) | |
1224 | |
1225 (setq vals (cdr vals)) | |
1226 (setq fields (cdr fields))) | |
1227 | |
1228 ;; Use this string as the object selector. | |
1229 (gopher-set-object-selector gopher-obj query) | |
1230 | |
1231 ;; Retrieve the data from the server. Unlike gopher, the CSO data | |
1232 ;; does not use `.' as a terminator. | |
1233 (gopher-document-object gopher-obj (current-buffer) "^[2-9]") | |
1234 | |
1235 ;; Strip CSO control information from the buffer. | |
1236 (gopher-clean-cso-buffer obj))) | |
1237 | |
1238 | |
1239 (defun gopher-clean-cso-buffer (obj) | |
1240 "Strip CSO control information from the current buffer." | |
1241 | |
1242 (let ((req "") | |
1243 (buffer-read-only nil) | |
1244 beg nreq) | |
1245 (goto-char (point-min)) | |
1246 (insert "\n") | |
1247 (while (not (eobp)) | |
1248 (cond ((and (>= (following-char) ?3) (<= (following-char) ?9)) | |
1249 (delete-char 4) | |
1250 (insert (concat (gopher-object-selector obj) "\n"))) | |
1251 | |
1252 ((eq (following-char) ?-) | |
1253 (delete-char 5) | |
1254 (setq beg (point)) | |
1255 (skip-chars-forward "^:") | |
1256 (setq nreq (buffer-substring beg (point))) | |
1257 (goto-char beg) | |
1258 (or (string= req nreq) | |
1259 (insert (concat "--------------------------" | |
1260 "-----------------------------\n"))) | |
1261 (setq req nreq) | |
1262 (setq beg (point)) | |
1263 (skip-chars-forward "^:") | |
1264 (forward-char) | |
1265 (delete-region beg (point))) | |
1266 | |
1267 (t | |
1268 (setq beg (point)) | |
1269 (forward-line 1) | |
1270 (delete-region beg (point)) | |
1271 (forward-line -1)) | |
1272 ) | |
1273 (forward-line 1)) | |
1274 | |
1275 (goto-char (point-min)) | |
1276 (delete-char 1))) | |
1277 | |
1278 | |
1279 ;;;;-------------------------------------------------------------------------- | |
1280 ;;;; indices. | |
1281 ;;;; | |
1282 ;;;; To query an index, the search string is appended to the selector. | |
1283 ;;;; The index returns a gopher directory. | |
1284 ;;;; | |
1285 | |
1286 | |
1287 (defun gopher-index-object (obj oldbuf) | |
1288 "Query a gopher directory object." | |
1289 | |
1290 ;; Get the search string from the user. | |
1291 (let ((str (read-from-minibuffer "Key: ")) | |
1292 (newobj (copy-sequence obj))) | |
1293 | |
1294 ;; Append it to the selector and retrieve the modified object | |
1295 ;; like a directory. | |
1296 (setq str (gopher-trim-blanks str)) | |
1297 (if (> (length str) 0) | |
1298 (progn | |
1299 (gopher-set-object-selector newobj | |
1300 (concat (gopher-object-selector obj) "\t" | |
1301 str)) | |
1302 (gopher-directory-object newobj (current-buffer))) | |
1303 ))) | |
1304 | |
1305 | |
1306 | |
1307 ;;;;-------------------------------------------------------------------------- | |
1308 ;;;; telneting. | |
1309 ;;;; | |
1310 | |
1311 (defun gopher-telnet-object (obj oldbuf) | |
1312 "Start a telnet session to a gopher object. | |
1313 If gopher-telnet-command is nonnil, then that is a command to start | |
1314 a telnet session in a subprocess. Otherwise, the emacs-lisp telnet | |
1315 package is used." | |
1316 | |
1317 ;; make the telnet argument string | |
1318 (let ((arg (gopher-object-host obj)) | |
1319 (port (gopher-object-port obj))) | |
1320 (if (not (zerop port)) | |
1321 (setq arg (concat arg | |
1322 (if (eq system-type 'vax-vms) | |
1323 "/port=" | |
1324 " ") | |
1325 port))) | |
1326 | |
1327 (if gopher-telnet-command | |
1328 | |
1329 ;; start up telnet as a separate process | |
1330 (save-window-excursion | |
1331 (gopher-background | |
1332 (concat gopher-telnet-command " " arg))) | |
1333 | |
1334 ;; use telnet-mode | |
1335 (telnet arg) | |
1336 ;; set things up so we can get back to the last node. | |
1337 (gopher-make-local-vars | |
1338 'gopher-obj obj | |
1339 'gopher-last oldbuf | |
1340 'gopher-telnet-process-name (concat arg "-telnet")) | |
1341 (local-set-key "\C-cl" 'gopher-telnet-quit) | |
1342 (local-set-key "\C-xk" 'gopher-telnet-quit) | |
1343 ) | |
1344 | |
1345 ;; show the login info to the user | |
1346 (if (not (zerop (length (gopher-object-selector obj)))) | |
1347 (progn | |
1348 (beep) | |
1349 (message (concat | |
1350 "Login as: " | |
1351 (gopher-object-selector obj) | |
1352 )) | |
1353 )) | |
1354 )) | |
1355 | |
1356 | |
1357 (defun gopher-telnet-quit nil | |
1358 "Clean up a telnet session and return to the previous gopher node." | |
1359 (interactive) | |
1360 (condition-case nil | |
1361 (delete-process gopher-telnet-process-name) | |
1362 (error t)) | |
1363 (gopher-last-node)) | |
1364 | |
1365 | |
1366 | |
1367 ;;;;-------------------------------------------------------------------------- | |
1368 ;;;; Images/sounds. | |
1369 ;;;; | |
1370 | |
1371 (defun gopher-image-object (obj oldbuf) | |
1372 "Retrieve what we hope is an image and show it." | |
1373 (let ( | |
1374 (showit (y-or-n-p "Display this item? ")) | |
1375 (fname) | |
1376 (buf (gopher-get-doc-buf (gopher-object-descr obj)))) | |
1377 (if showit | |
1378 (setq fname (make-temp-name "/tmp/gopherimg")) | |
1379 (setq fname(read-file-name "File to save in: "))) | |
1380 (gopher-retrieve-document-cleanly (buf | |
1381 (gopher-object-selector obj) | |
1382 (gopher-object-host obj) | |
1383 (gopher-object-port obj) | |
1384 'none) | |
1385 (progn | |
1386 (error "Problems retrieving object.") | |
1387 (kill-buffer buf)) | |
1388 | |
1389 (save-excursion | |
1390 (set-buffer buf) | |
1391 (write-file fname)) | |
1392 (kill-buffer buf) | |
1393 (if (and showit gopher-image-display-command) | |
1394 | |
1395 ;; Spawn a process to display the image. | |
1396 ;; But modify its sentinel so that the file we wrote | |
1397 ;; will get deleted when the process exits. | |
1398 (save-window-excursion | |
1399 (let ((p (gopher-background | |
1400 (concat gopher-image-display-command " " fname)))) | |
1401 (set-process-sentinel p | |
1402 (` (lambda (process msg) | |
1403 ((, (process-sentinel p)) process msg) | |
1404 (if (not (eq (process-status process) 'run)) | |
1405 (delete-file (, fname))) | |
1406 ))) | |
1407 )) | |
1408 )))) | |
1409 | |
1410 | |
1411 | |
1412 ;;;;-------------------------------------------------------------------------- | |
1413 ;;;; Various opaque objects. Just save them in a file for now. | |
1414 ;;;; | |
1415 | |
1416 (defun gopher-binary-object (obj oldbuf) | |
1417 "Retrieve a gopher object and save it to a file, | |
1418 without trying to interpret it in any way." | |
1419 (let ((fname (read-file-name "File to save in: ")) | |
1420 (buf (gopher-get-doc-buf (gopher-object-descr obj)))) | |
1421 | |
1422 (gopher-retrieve-document-cleanly (buf | |
1423 (gopher-object-selector obj) | |
1424 (gopher-object-host obj) | |
1425 (gopher-object-port obj) | |
1426 'none) | |
1427 | |
1428 (progn | |
1429 (error "Problems retrieving object.") | |
1430 (kill-buffer buf)) | |
1431 | |
1432 (save-excursion | |
1433 (set-buffer buf) | |
1434 (write-file fname)) | |
1435 (kill-buffer buf) | |
1436 ))) | |
1437 | |
1438 | |
1439 ;;;;-------------------------------------------------------------------------- | |
1440 ;;;; forms stuff | |
1441 ;;;; | |
1442 ;;;; Uses some of the internal routines from forms.el to present | |
1443 ;;;; a form which is not associated with a file. | |
1444 ;;;; | |
1445 | |
1446 (defun gopher-form (form-name accept-action number-of-fields format-list) | |
1447 "Display a buffer containing a form for the user to enter data. | |
1448 The form is described by NUMBER-OF-FIELDS and FORMAT-LIST (cf. forms-mode). | |
1449 FORM-NAME is a string to put in the modeline. | |
1450 When the user accepts the data in the form by pressing `C-c RET', the | |
1451 function ACCEPT-ACTION is called with a list of the strings which | |
1452 the user entered." | |
1453 | |
1454 (switch-to-buffer (generate-new-buffer "*gopher form*")) | |
1455 | |
1456 (gopher-make-local-vars | |
1457 'forms-format-list format-list | |
1458 'forms-number-of-fields number-of-fields | |
1459 'forms-field-sep "\t" | |
1460 'forms-read-only nil | |
1461 'forms-multi-line nil | |
1462 'forms--number-of-markers nil | |
1463 'forms--markers nil | |
1464 'forms--format nil | |
1465 'forms--parser nil | |
1466 'forms--dynamic-text nil | |
1467 'forms-fields nil | |
1468 'forms-the-record-list nil | |
1469 'forms-accept-action accept-action | |
1470 ) | |
1471 | |
1472 (forms--process-format-list) | |
1473 (forms--make-format) | |
1474 (forms--make-parser) | |
1475 | |
1476 (erase-buffer) | |
1477 | |
1478 ;; make local variables | |
1479 (make-local-variable 'forms--file-buffer) | |
1480 (make-local-variable 'forms--total-records) | |
1481 (make-local-variable 'forms--current-record) | |
1482 (make-local-variable 'forms--the-record-list) | |
1483 (make-local-variable 'forms--search-rexexp) | |
1484 | |
1485 ;; set the major mode indicator | |
1486 (setq major-mode 'gopher-form-mode) | |
1487 (setq mode-name "gopher form") | |
1488 | |
1489 (set-buffer-modified-p nil) | |
1490 | |
1491 (use-local-map gopher-form-mode-map) | |
1492 | |
1493 (forms--show-record (make-string (1- number-of-fields) ?\t)) | |
1494 | |
1495 (run-hooks 'gopher-form-mode-hooks)) | |
1496 | |
1497 | |
1498 (defun gopher-form-accept nil | |
1499 (interactive) | |
1500 | |
1501 (funcall forms-accept-action (forms--parse-form))) | |
1502 | |
1503 (define-key gopher-form-mode-map "\C-c\r" 'gopher-form-accept) | |
1504 (define-key gopher-form-mode-map "\C-cl" 'gopher-last-node) | |
1505 | |
1506 | |
1507 ;;;;-------------------------------------------------------------------------- | |
1508 ;;;; low-level communications routines | |
1509 ;;;; | |
1510 | |
1511 | |
1512 (defun gopher-retrieve-document (buf sel host port &optional end-regexp) | |
1513 "Retrieve a gopher object into BUF. | |
1514 The object is identified by a SEL HOST PORT triple. | |
1515 Optional argument END-REGEXP is used for data which is not `.'-terminated. | |
1516 If END-REGEXP is non-nil and not a string, then it is assumed that | |
1517 the data is binary, and reading will continue until the sender disconnects. | |
1518 Returns NIL if an error occured during the attempt to retrieve the | |
1519 document, otherwise T. | |
1520 " | |
1521 | |
1522 ;; Default is single period termination. | |
1523 (or end-regexp (setq end-regexp "^\\.\r$")) | |
1524 | |
1525 (save-excursion | |
1526 (set-buffer buf) | |
1527 (erase-buffer) | |
1528 | |
1529 (let ((h (assoc host gopher-hostname-aliases))) | |
1530 (if h (setq host (cdr h)))) | |
1531 | |
1532 ;; Open the connection to the server. | |
1533 ;; If we get an unknown service error, try looking the port up in | |
1534 ;; gopher-port-aliases. If we find it there, try the connect again | |
1535 ;; with that translation. | |
1536 (let (wait | |
1537 (gopher-server-process | |
1538 (let (p (try-again t)) | |
1539 (while try-again | |
1540 (setq try-again nil) | |
1541 (condition-case errinfo | |
1542 (setq p (open-network-stream "gopher" (current-buffer) | |
1543 host port)) | |
1544 (error (if (and (string-match "^Unknown service .*$" | |
1545 (nth 1 errinfo)) | |
1546 (setq port (cdr (assoc port | |
1547 gopher-port-aliases)))) | |
1548 (setq try-again t) | |
1549 (ding) | |
1550 (message (format "%s: %s" | |
1551 (nth 0 errinfo) | |
1552 (nth 1 errinfo))) | |
1553 )))) | |
1554 p))) | |
1555 | |
1556 (cond (gopher-server-process | |
1557 | |
1558 ;; keep the emacs end-of-process status line out of the buffer | |
1559 (set-process-sentinel gopher-server-process 'gopher-sentinel) | |
1560 | |
1561 ;; send the selector to the server | |
1562 (process-send-string gopher-server-process (concat sel "\r\n")) | |
1563 | |
1564 ;; receive the response from the server | |
1565 ;; based on nntp.el from GNUS | |
1566 (setq wait t) | |
1567 (while wait | |
1568 (if (stringp end-regexp) | |
1569 (progn | |
1570 (goto-char (point-max)) | |
1571 (forward-line -1))) | |
1572 (if (and (stringp end-regexp) | |
1573 (looking-at end-regexp)) | |
1574 (setq wait nil) | |
1575 (if (not (memq (process-status gopher-server-process) | |
1576 '(open run))) | |
1577 (progn | |
1578 (message "gopher: connection closed") | |
1579 (setq wait nil)) | |
1580 (if gopher-debug-read | |
1581 (message "gopher: Reading...")) | |
1582 (cond (gopher-buggy-accept | |
1583 (sit-for 1)) | |
1584 ((and (boundp 'epoch::version) epoch::version) | |
1585 (accept-process-output gopher-server-process 2)) | |
1586 (t | |
1587 (accept-process-output gopher-server-process)) | |
1588 ) | |
1589 (if gopher-debug-read | |
1590 (message " "))) | |
1591 )) | |
1592 | |
1593 ;; be sure the net connection has gone away... | |
1594 (condition-case nil | |
1595 (delete-process gopher-server-process) | |
1596 (error t)) | |
1597 | |
1598 ;; clean up the text buffer | |
1599 (if (stringp end-regexp) | |
1600 (gopher-clean-text)) | |
1601 | |
1602 t) | |
1603 | |
1604 (t nil)) | |
1605 ))) | |
1606 | |
1607 | |
1608 ;;; adapted from GNUS | |
1609 (defun gopher-clean-text () | |
1610 "Decode text transmitted by gopher. | |
1611 0. Delete status line. | |
1612 1. Delete `^M' at end of line. | |
1613 2. Delete `.' at end of buffer (end of text mark). | |
1614 3. Delete `.' at beginning of line. (does gopher want this?)" | |
1615 | |
1616 ;; Insert newline at end of buffer. | |
1617 (goto-char (point-max)) | |
1618 (if (not (bolp)) | |
1619 (insert "\n")) | |
1620 ;; Delete `^M' at end of line. | |
1621 (goto-char (point-min)) | |
1622 (while (re-search-forward "\r[^\n]*$" nil t) | |
1623 (replace-match "")) | |
1624 ; (goto-char (point-min)) | |
1625 ; (while (not (eobp)) | |
1626 ; (end-of-line) | |
1627 ; (if (= (preceding-char) ?\r) | |
1628 ; (delete-char -1)) | |
1629 ; (forward-line 1) | |
1630 ; ) | |
1631 ;; Delete `.' at end of buffer (end of text mark). | |
1632 (goto-char (point-max)) | |
1633 (forward-line -1) ;(beginning-of-line) | |
1634 (while (looking-at "^\\.$") | |
1635 (delete-region (point) (progn (forward-line 1) (point))) | |
1636 (forward-line -1)) | |
1637 ;; Replace `..' at beginning of line with `.'. | |
1638 (goto-char (point-min)) | |
1639 ;; (replace-regexp "^\\.\\." ".") | |
1640 (while (search-forward "\n.." nil t) | |
1641 (delete-char -1)) | |
1642 ) | |
1643 | |
1644 | |
1645 (defun gopher-sentinel (proc status) | |
1646 nil) | |
1647 | |
1648 (provide 'gopher) | |
1649 | |
1650 ;;; gopher.el ends here | |
1651 | |
1652 ;;;(gopher.el) Local Variables: | |
1653 ;;;(gopher.el) eval: (put 'gopher-retrieve-document-cleanly 'lisp-indent-hook 2) | |
1654 ;;;(gopher.el) End: | |
1655 |