Mercurial > hg > xemacs-beta
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/packages/gopher.el Mon Aug 13 08:45:50 2007 +0200 @@ -0,0 +1,1655 @@ +;;; gopher.el --- an emacs gopher client + +;; Copyright (C) 1992 scott snyder + +;; Author: scott snyder <snyder@fnald0.fnal.gov> +;; Created: 29 Jun 1992 +;; Version: 1.03 +;; Keywords: gopher, comm + +;; LCD Archive Entry: +;; gopher|scott snyder|snyder@fnald0.fnal.gov| +;; An emacs gopher client.| +;; 20-Apr-1993|1.02|~/interfaces/gopher.el.Z| + +;; This file is not part of GNU Emacs, but is distributed under the +;; same conditions. +;; +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY. No author or distributor +;; accepts responsibility to anyone for the consequences of using it +;; or for whether it serves any particular purpose or works at all, +;; unless he says so in writing. Refer to the GNU Emacs General Public +;; License for full details. +;; +;; Everyone is granted permission to copy, modify and redistribute +;; GNU Emacs, but only under the conditions described in the +;; GNU Emacs General Public License. A copy of this license is +;; supposed to have been given to you along with GNU Emacs so you +;; can know your rights and responsibilities. It should be in a +;; file named COPYING. Among other things, the copyright notice +;; and this notice must be preserved on all copies. +;; +;;; Synched up with: Not in FSF. +;; +;; An emacs gopher client. Currently supports directory, text, CSO, +;; index, image, and telnet objects. +;; Requires forms.el and background.el. +;; +;; Written by scott snyder <snyder@fnald0.fnal.gov> +;; Some code borrowed from GNUS (by Masanobu UMEDA). +;; Some code (bookmarks, xterms, error handling) contributed +;; by Stewart Clamen <clamen@cs.cmu.edu>. + +;;; Commentary: +;; OPERATING INSTRUCTIONS +;; +;; To use, `M-x gopher'. To specify a different root server, use +;; `C-u M-x gopher'. If you want to use bookmarks, set the variable +;; gopher-support-bookmarks appropriately. +;; The command `M-x gopher-atpoint' will attempt to interpret the text +;; around point as a gopher bookmark specification and will retrieve +;; that item. +;; +;; Sample .emacs configuration: +;; (autoload 'gopher "gopher") +;; (autoload 'gopher-atpoint "gopher") +;; (setq gopher-support-bookmarks t) +;; +;; In directory mode: +;; Space, return, `f', or `e' selects the line point is on. +;; With a numeric prefix argument, select that object. +;; `q', `l', or `u' will return to the previous node. +;; `n' and `p' to the next and previous lines. +;; `a' will add an object to your bookmark list. +;; `v' will display your bookmark list. +;; `=' gives detailed information about an object. +;; In the bookmark list, all of the above (except `a'), plus: +;; `C-k' will delete an object from the bookmark list. +;; `C-y' will yank the most recently deleted bookmark object back into +;; the bookmark buffer. +;; `s' will save the bookmark list. +;; `Q' will quit gopher entirely, killing all gopher buffers. +;; +;; All commands which operate on a specific object can take an optional +;; numeric prefix argument giving the index of the object on which +;; to operate. +;; +;; In document mode: +;; Space pages forward. +;; Delete pages backward. +;; `q', `l' or `u' returns to the last node. +;; +;; In the CSO entry form: +;; `C-c RET' performs a look-up, based on the field contents +;; you've filed in. +;; `C-c l' returns to the previous node. +;; +;; Telnets: +;; If you have an X server set, gopher will try to create an xterm +;; running telnet. If not, the emacs-lisp telnet mode will be used. +;; From the emacs-lisp telnet mode, use `C-c l' to kill the session +;; and return to the previous node. +;; See also the variable gopher-telnet-command. +;; +;; Images: +;; Images are displayed using the command gopher-image-display-command. +;; The default setting for this variable uses xv. +;; +;; Note: +;; If gopher consistently hangs while trying to retrieve an object, +;; try turning on gopher-buggy-accept (which see). +;; +;; VMS notes: +;; To use this on VMS, you'll need my emacs subprocess patches (recently +;; posted on gnu.emacs.sources; if you can't find them, send me mail). +;; To be able to run telnet in a separate decterm, you'll also need +;; to (setq shell-file-name "docmd") and create the file +;; emacs_library:[etc]docmd.com containing the following: +;; $ if p1 .eqs. "-C" then p1 = "" +;; $ deass sys$input +;; $ 'p1 'p2 'p3 'p4 'p5 'p6 'p7 'p8 +;; $ eoj + +;;; Change Log: +;; +;; Version 1.03 30-APR-1993 +;; * Add a buffer-local variable gopher-obj to all gopher buffers bound +;; to the description of the object contained in that buffer. +;; +;; Version 1.02 20-APR-1993 +;; * Avoid using replace-regexp in gopher-clean-text. (Suggested by +;; sbyrnes@rice.edu (Steven Byrnes)). +;; * Added gopher-port-aliases. +;; * Print ports as strings in gopher-directory-show-object. +;; * Don't (ding) when the net stream closes unexpectedly. +;; * Added image display (based on code from beldar@MicroUnity.com +;; (Gardner Cohen)). +;; * Attempt to improve error reporting. +;; * Reworked gopher-parse-bookmark to handle out-of-order fields. +;; * Added gopher-atpoint. +;; * Moved bookmark init stuff to gopher-read-bookmarks. +;; * Added gopher-quit (based on code from Thomas L|fgren +;; <tde9104@abacus.hgs.se>). +;; * Change usage of background for XEmacs. +;; (Patch from "William M. Perry" <wmperry@raisin.ucs.indiana.edu>). +;; * Added a (provide 'gopher) at end. +;; * Added `f' and `e' bindings in directory mode. +;; +;; Version 1.01 +;; * Added patch suggested by Humberto Ortiz-Zuazaga +;; <zuazaga@ucunix.san.uc.EDU> to allow null Path= items in .gopherrc. +;; +;; Version 1.00 29-AUG-1992 +;; * Added gopher-buggy-accept. +;; * Reworked telnet stuff to use an arbitrary command string to start up +;; the telnet process. This can be used to start the telnet in a +;; separate terminal window. +;; Based on code from Stewart Clamen. +;; * Stewart Clamen <clamen@cs.cmu.edu> added bookmarks. +;; * Added 's key binding to save bookmarks. +;; * Added a prefix argument to gopher-directory-buffer and +;; gopher-add-bookmark. +;; * Added standard emacs-lisp header. +;; * Stewart Clamen <clamen@cs.cmu.edu> added some error trapping and +;; recovery (gopher-retrieve-document-cleanly). +;; * Appended node description to the node's buffer's name. +;; * Reformat bookmark buffers when returning to them via gopher-last-node +;; or when an item is deleted. +;; * Added gopher-yank-bookmark. +;; * Added gopher-bookmark-modified-tick to prevent reformatting bookmark +;; buffers needlessly. +;; +;; Version 0.92 27-JUL-1992 +;; * Added gopher-hostname-aliases. +;; +;; Version 0.91 30-JUN-1992 +;; * Deal with servers which send stuff after the CR. +;; * Prevent gopher-directory-show-object from clearing the read-only flag. +;; * Allow specification of port number in `C-u M-x gopher'. +;; +;; Version 0.9 29-JUN-1992 +;; * Initial release. + +;;; Code: + +(require 'electric) +(require 'forms) + +;; background has the same name as an epoch function. +;; Rename it to gopher-background... +;; also, the version i got from the archive didn't have a provide... +(cond ((and (string-lessp "19" emacs-version) + (not (boundp 'epoch::version))) + ;; background is obsolete in emacs19: just add a & to shell-command. + (defun gopher-background (command) + (shell-command (concat command "&")))) + (t + ;; background has the same name as an epoch function. + ;; Rename it to gopher-background... + ;; also, the version i got from the archive didn't have a provide... + (if (not (fboundp 'gopher-background)) + (if (fboundp 'background) + (let ((old-background (symbol-function 'background))) + (load-library "background") + (fset 'gopher-background (symbol-function 'background)) + (fset 'background old-background)) + (load-library "background") + (fset 'gopher-background (symbol-function 'background)) + )) + )) + +(defvar gopher-root-node (vector ?1 "root" "" "ucs_gopher" 70) + "The root gopher server, as a gopher object.") + +(defvar gopher-directory-mode-hook nil + "*Invoked when entering a new gopher directory.") +(defvar gopher-directory-mode-map (make-keymap) + "Keymap for gopher-directory-mode.") + +(defvar gopher-document-mode-hook nil + "*Invoked when showing gopher document.") +(defvar gopher-document-mode-map (make-keymap) + "Keymap for gopher-document-mode.") + +(defvar gopher-form-mode-hooks nil + "*Invoked with entering a gopher form (i.e., for CSO).") +(defvar gopher-form-mode-map (make-keymap) + "Keymap for gopher-form-mode.") + +(defvar gopher-tmp-buf nil + "Buffer used to receive output from gopher.") + +(defvar gopher-debug-read t + "*If non-nil, show the current status about reading the gopher server output.") + +;; On some systems (such as SGI Iris), accept-process-output doesn't seem +;; to return for the last packet received on a connection. Turn this on +;; to work around the problem, but does anyone know what causes this? +(defvar gopher-buggy-accept nil + "*If non-nil, use sit-for instead of accept-process-output. +If gopher consistently hangs while fetching an object, try turning this on.") + +(defvar gopher-hostname-aliases + '(("128.230.33.31" . "oliver.syr.edu")) + "Emacs can't deal with raw IP addresses used as a hostname. +Use this to work around...") + +(defvar gopher-port-aliases + '(("whois_port" . 43)) + "Some losing hosts send a port name instead of a number. +Use this table to convert...") + + +(defvar gopher-support-bookmarks nil + "*If nil, do not implement bookmarks. +If 'unix or t, read and write bookmarks to ~/.gopherrc. +If a filename, read and save vector from there directly (not implemented yet). +If a vector, treat as a built-in directory.") + +(defconst gopher-bookmarks nil "Internal bookmark directory.") +(defconst gopher-bookmarks-modified nil "Do bookmarks need to be saved?") +(defconst gopher-killed-bookmark nil "The last bookmark object to be killed") +(defconst gopher-bookmark-directory-p nil + "Is this buffer a bookmark directory? A buffer-local variable.") + +(defvar gopher-bookmark-modified-tick 0 + "Counts each time the bookmark vector is modified.") + + +(defvar gopher-telnet-command + (cond ((eq system-type 'vax-vms) + (if (getenv "DECW$DISPLAY") + "create/terminal/wait/window=(title=\"telnet\") telnet")) + (t + (if (getenv "DISPLAY") + "xterm -e telnet")) + ) + "*Command to use to start a telnet session. +If this is nil, the emacs-lisp telnet package will be used. +The default setting is to create a terminal window running telnet +if you've specified an X server, and to use the emacs-lisp telnet otherwise.") + + +(defvar gopher-image-display-command "xv -geometry +200+200" + "*The command used to try to display an image object.") + + +(defvar gopher-object-type-alist + '(( ?0 "" gopher-document-object) + ( ?1 "/" gopher-directory-object) + ( ?2 " <CSO>" gopher-cso-object) + ( ?3 " <error>" gopher-unimplemented-object) + ( ?4 " <binhex>" gopher-binary-object) + ( ?5 " <DOS>" gopher-binary-object) + ( ?6 " <UU>" gopher-binary-object) + ( ?7 " <?>" gopher-index-object) + ( ?8 " <TEL>" gopher-telnet-object) + ( ?9 " <bin>" gopher-binary-object) + ( ?T " <T>" gopher-unimplemented-object) + ( ?s " <)" gopher-binary-object) + ( ?M " <MIME>" gopher-unimplemented-object) + ( ?h " <html>" gopher-unimplemented-object) + ( ?I " <image>" gopher-image-object) + ( ?c " <cal>" gopher-unimplemented-object) + ( ?g " <GIF>" gopher-image-object) + ) + "*Alist describing the types of gopher objects this client know about. +The keys are the gopher type characters. +The second element in each list is the string to tag onto the end +of an object's description, to identify it to the user. +The third element is the function to use to retrieve the object. +It is called with two arguments: the gopher object to retrieve and +the buffer which should be returned to when the user is done +with this object.") + + +;;; +;;; The data structure describing a gopher object is a vector of five elements: +;;; [ TYPE DESCR SELECTOR HOST PORT ] +;;; +;;; TYPE is the type character. +;;; DESCR is the human-readable description of the object. +;;; SELECTOR is the opaque selector to be sent to HOST to retrieve the obj. +;;; HOST is the name of the Internet host on which the object resides. +;;; PORT is the TCP/IP port on which the host is listening. +;;; +;;; The following macros set and fetch elements of this structure. +;;; + +(defconst gopher-object-length 5) + +(defmacro gopher-object-type (object) + "Return the gopher type of OBJECT." + (` (aref (, object) 0))) + +(defmacro gopher-object-descr (object) + "Return the gopher description of OBJECT." + (` (aref (, object) 1))) + +(defmacro gopher-object-selector (object) + "Return the gopher selector string for OBJECT." + (` (aref (, object) 2))) + +(defmacro gopher-object-host (object) + "Return the gopher hostname for OBJECT." + (` (aref (, object) 3))) + +(defmacro gopher-object-port (object) + "Return the gopher TCP port number for OBJECT." + (` (aref (, object) 4))) + + +(defmacro gopher-set-object-type (object type) + "Set the gopher type of OBJECT to TYPE." + (` (aset (, object) 0 (, type)))) + +(defmacro gopher-set-object-descr (object descr) + "Set the gopher description of OBJECT to DESCR." + (` (aset (, object) 1 (, descr)))) + +(defmacro gopher-set-object-selector (object selector) + "Set the gopher selector string for OBJECT to SELECTOR." + (` (aset (, object) 2 (, selector)))) + +(defmacro gopher-set-object-host (object host) + "Set the gopher hostname for OBJECT to HOST." + (` (aset (, object) 3 (, host)))) + +(defmacro gopher-set-object-port (object port) + "Set the gopher TCP port number for OBJECT to PORT." + (` (aset (, object) 4 (, port)))) + + +(defmacro gopher-retrieve-document-cleanly (args handle &rest body) + "Call gopher-retrieve-document with condition-case wrapped around, +applying HANDLE if appropriate." + (` (condition-case nil + (progn + (gopher-retrieve-document (,@ args)) + (,@ body)) + (error (, handle))))) + + +;; +;; buffer-local variables. +;; declared here to prevent warnings from the new byte-compiler. +;; + +(defvar gopher-dir nil) +(defvar gopher-last nil) +(defvar gopher-obj nil) +(defvar gopher-telnet-process-name nil) +(defvar gopher-bookmark-buffer-tick nil) +(defvar forms-accept-action nil) + + +;;;;-------------------------------------------------------------------------- +;;;; main dispatching logic. +;;;; + +;;;###autoload +(defun gopher (&optional askserv) + "Start a gopher session. With C-u, prompt for a gopher server." + (interactive "P") + (if askserv + (progn + (gopher-set-object-host + gopher-root-node + (read-string "Gopher server: " + (gopher-object-host gopher-root-node))) + + (let (portstr port) + (while (not (numberp port)) + (setq portstr + (read-string "Port: " + (int-to-string + (gopher-object-port gopher-root-node)))) + + (setq port (condition-case nil + (car (read-from-string portstr)) + (error nil))) + + (if (not (numberp port)) + (progn + (ding) + (message "Port must be numeric") + (sit-for 1))) + ) + + (gopher-set-object-port gopher-root-node port)))) + + (gopher-read-bookmarks) + + (gopher-dispatch-object gopher-root-node nil)) + + + +;;;###autoload +(defun gopher-atpoint nil + "Try to interpret the text around point as a gopher bookmark, and dispatch +to that object." + (interactive) + + (let (bkmk) + (save-excursion + (re-search-backward "^#[ \t]*$\\|^[ \t]*$\\|\\`") + (skip-chars-forward " \t\n") + (setq bkmk (gopher-parse-bookmark))) + (if bkmk + (progn + (gopher-read-bookmarks) + (gopher-dispatch-object bkmk nil)) + (error "Illformed bookmark")))) + + +(defun gopher-dispatch-object (obj lastbuf) + "Dispatch a gopher object depending on its type." + (let ((typedesc (assq (gopher-object-type obj) gopher-object-type-alist))) + + (if typedesc + (funcall (nth 2 typedesc) obj lastbuf) + (gopher-unimplemented-object obj lastbuf)))) + + +(defun gopher-unimplemented-object (obj lastbuf) + (error "unimplemented object type")) + + +;;;;-------------------------------------------------------------------------- +;;;; utilities +;;;; + +(defun gopher-next-field nil + "Returns as a string all chars between point and the next tab or newline. +Point is advanced to after the tab (or to the end-of-line)." + + (let ((beg (point)) s) + (skip-chars-forward "^\t\n") + (setq s (buffer-substring beg (point))) + (if (eq (following-char) ?\t) + (forward-char)) + s)) + + +;; from GNUS +(defun gopher-make-local-vars (&rest pairs) + ;; Take VARIABLE-VALUE pairs and makes local variables initialized to the + ;; value. + (while pairs + (make-local-variable (car pairs)) + (set (car pairs) (car (cdr pairs))) + (setq pairs (cdr (cdr pairs))))) + + +(defun gopher-get-tmp-buf nil + "Get a temporary buffer in which to receive gopher output." + (or (bufferp gopher-tmp-buf) + (progn + (setq gopher-tmp-buf (get-buffer-create " *gopher-tmp*")) + (buffer-flush-undo gopher-tmp-buf))) + gopher-tmp-buf) + + +(defun gopher-get-dir-buf (descr) + "Get a new buffer suitable for a gopher directory or document." + (let ((buf (generate-new-buffer (concat "*gopher*" descr)))) + (buffer-flush-undo buf) + buf)) + +(fset 'gopher-get-doc-buf (symbol-function 'gopher-get-dir-buf)) + + +(defun gopher-trim-blanks (str) + "Remove leading and trailing blanks from STR." + (string-match "\\`[ \t\n]*" str) + (substring str + (match-end 0) + (string-match "[ \t\n]*\\'" str (match-end 0)))) + + +;;;;-------------------------------------------------------------------------- +;;;; directory handling +;;;; + + +(defun gopher-directory-object (obj oldbuf) + "Retrieve and display a gopher directory." + + (let ((tmpbuf (gopher-get-tmp-buf)) + (dirbuf (gopher-get-dir-buf (gopher-object-descr obj)))) + + ;; Get the directory... + (gopher-retrieve-document-cleanly (tmpbuf + (gopher-object-selector obj) + (gopher-object-host obj) + (gopher-object-port obj)) + + (progn + (kill-buffer dirbuf) + (error "Problems retrieving directory.")) + + ;; Parse it and store our internal representation in gopher-dir. + (switch-to-buffer dirbuf) + (gopher-make-local-vars + 'gopher-dir (gopher-parse-directory tmpbuf) + 'gopher-obj obj + 'gopher-last oldbuf) + + ;; Format it for your viewing pleasure. + (gopher-format-directory gopher-dir dirbuf) + (goto-char (point-min)) + (if (> (- (point-max) (point)) 7) (forward-char 7)) + + ;; Turn on directory mode and put the description in the mode line. + (gopher-directory-mode) + (setq mode-line-buffer-identification (concat "Gopher: " + (gopher-object-descr obj))) + ))) + + +(defun gopher-parse-directory (buf) + "Parse the gopher directory in buffer BUF into our internal representation. +Returns a vector of gopher objects." + + (save-excursion + (set-buffer buf) + (goto-char (point-min)) + + (let* ((len (count-lines (point-min) (point-max))) + (dir (make-vector len nil)) + (i 0)) + + (while (not (eobp)) + (aset dir i (gopher-parse-directory-line)) + (setq i (1+ i)) + (forward-line 1)) + + dir))) + + +(defun gopher-parse-directory-line nil + "Parse the line containing point as a gopher directory entry. +Returns the corresponding gopher object." + + (let (type descr selector host port) + (beginning-of-line) + (setq type (following-char)) + (forward-char) + (setq descr (gopher-next-field)) + (setq selector (gopher-next-field)) + (setq host (gopher-next-field)) + (setq port (gopher-next-field)) + + (if (string-match "^[0-9]+$" port) + (setq port (string-to-int port))) + + (vector type descr selector host port))) + + +(defun gopher-format-directory (dir buf) + "Print the directory vector DIR into buffer BUF." + + (save-excursion + (set-buffer buf) + (erase-buffer) + (let ((i 0) + (len (length dir))) + (while (< i len) + (gopher-format-directory-line (aref dir i) (1+ i)) + (setq i (1+ i))) + + ))) + + +(defun gopher-format-directory-line (obj ndx) + "Insert a line describing the gopher object OBJ into the current buffer. +NDX is a numeric index to display to the left of the object description." + + (let ((ndx-str (int-to-string ndx)) + (typedesc (assq (gopher-object-type obj) gopher-object-type-alist))) + + ;; display the index number. use 5 digits, right-justified. + (if (< (length ndx-str) 5) + (insert (make-string (- 5 (length ndx-str)) ? ))) + (insert ndx-str) + (insert ". ") + + ;; add the object description. + (insert (gopher-object-descr obj)) + + ;; add a tag indicating the gopher object type. + (insert (if typedesc + (nth 1 typedesc) + (concat " ???" (char-to-string (gopher-object-type obj))))) + + (insert "\n"))) + + +(defun gopher-directory-mode nil + "Gopher directory mode. + +\\{gopher-directory-mode-map} +" + (use-local-map gopher-directory-mode-map) + (setq major-mode 'gopher-directory-mode) + (setq mode-name "gopher dir") + (run-hooks 'gopher-directory-mode-hook) + (setq buffer-read-only t) + + (require 'mode-motion) + (make-local-variable 'mode-motion-hook) + (setq mode-motion-hook 'mode-motion-highlight-line) + ) + +;;; keymap for directory mode +(suppress-keymap gopher-directory-mode-map) +(define-key gopher-directory-mode-map "\r" 'gopher-directory-choose) +(define-key gopher-directory-mode-map " " 'gopher-directory-choose) +(define-key gopher-directory-mode-map "l" 'gopher-last-node) +(define-key gopher-directory-mode-map "q" 'gopher-last-node) +(define-key gopher-directory-mode-map "u" 'gopher-last-node) +(define-key gopher-directory-mode-map "=" 'gopher-directory-show-object) +(define-key gopher-directory-mode-map "Q" 'gopher-quit) +(define-key gopher-directory-mode-map "f" 'gopher-directory-choose) +(define-key gopher-directory-mode-map "e" 'gopher-directory-choose) + +; Virginia Peck <vapeck@cs> Mon Aug 10 1992 +(define-key gopher-directory-mode-map "n" 'next-line) +(define-key gopher-directory-mode-map "p" 'previous-line) +;;(define-key gopher-directory-mode-map "\C-xk" 'gopher-last-node) + +; Stewart Clamen <clamen@cs.cmu.edu> Mon Aug 17 1992 +(define-key gopher-directory-mode-map "v" 'gopher-display-bookmarks) +(define-key gopher-directory-mode-map "a" 'gopher-add-bookmark) +(define-key gopher-directory-mode-map "\C-k" 'gopher-delete-bookmark) +(define-key gopher-directory-mode-map "s" 'gopher-directory-save-bookmarks) +(define-key gopher-directory-mode-map "\C-y" 'gopher-yank-bookmark) + +(define-key gopher-directory-mode-map 'button2 'gopher-mouse-directory-choose) +(define-key gopher-directory-mode-map 'button3 'gopher-directory-menu) + + +(defvar gopher-directory-menu + '("Gopher Commands" + ["Select This Link" gopher-directory-choose t] + ["Goto Last Node" gopher-last-node t] + ["Show Object Internals" gopher-directory-show-object t] + ["Quit" gopher-quit t] + )) + +(defun gopher-directory-menu (event) + (interactive "e") + (mouse-set-point event) + (beginning-of-line) + (popup-menu gopher-directory-menu)) + +(defun gopher-mouse-directory-choose (event arg) + (interactive "e\nP") + (mouse-set-point event) + (beginning-of-line) + (gopher-directory-choose arg)) + + +(defun gopher-directory-nth-obj (n) + "Returns the Nth object (starting at 1) in a gopher directory buffer." + (if (or (<= n 0) (> n (length gopher-dir))) + (error "Out of range.")) + (aref gopher-dir (1- n))) + + +(defun gopher-directory-n (arg) + "Return the index of the object specified by ARG (starting at 1). +If ARG is nil, this is the index of the current line. +Otherwise, it is the value of ARG (as a prefix argument)." + (if arg + (prefix-numeric-value arg) + (if (eq (point) (point-max)) + (1+ (count-lines (point-min) (point-max))) + (count-lines (point-min) (1+ (point)))))) + + +(defun gopher-directory-obj (arg) + "Return the gopher object given by prefix arg ARG. +If it is nil, return the object given by the line point is on. +Otherwise, ARG is the index of the object." + (gopher-directory-nth-obj (gopher-directory-n arg))) + + +(defun gopher-directory-choose (arg) + "Choose an item from the directory, and do whatever is appropriate +based on the object's type. Default is to choose the object given by the +line the cursor is on. With numeric prefix argument N, choose object N." + (interactive "P") + (gopher-dispatch-object (gopher-directory-obj arg) (current-buffer))) + + +(defun gopher-directory-show-object (arg) + "Dump the internal information in a gopher object. +With numeric prefix argument N, show information about the Nth object." + (interactive "P") + (let* ((obj (gopher-directory-obj arg)) + (type (gopher-object-type obj)) + (typespec (assq type gopher-object-type-alist)) + (typetag (if typespec (nth 1 typespec) "?")) + (typeproc (if typespec (nth 2 typespec) "?"))) + (with-output-to-temp-buffer "*Gopher object*" + (princ (format "Type : %c `%s' %s\n" type typetag typeproc)) + (princ (format "Description : %s\n" (gopher-object-descr obj))) + (princ (format "Selector : %s\n" (gopher-object-selector obj))) + (princ (format "Host : %s\n" (gopher-object-host obj))) + (princ (format "Port : %s\n" (gopher-object-port obj))) + (current-buffer) + )) + (shrink-window-if-larger-than-buffer (get-buffer-window "*Gopher object*")) + + ;; shrink-window-if-larger-than-buffer screws these up... + (set-buffer-modified-p nil) + (setq buffer-read-only t)) + + +(defun gopher-last-node nil + "Return to the previous gopher node. +By convention, a gopher buffer has the local variable gopher-last which +contains the buffer to which we should return." + (interactive) + (let ((oldbuf (current-buffer))) + (if gopher-last + (progn + (switch-to-buffer gopher-last) + (kill-buffer oldbuf) + (and (gopher-bookmark-directory-p) + (> gopher-bookmark-modified-tick gopher-bookmark-buffer-tick) + (let ((ppos (1- (gopher-directory-n nil)))) + (gopher-format-bookmarks) + (forward-line ppos) + (if (> (- (point-max) (point)) 7) (forward-char 7))))) + (if (and gopher-support-bookmarks + gopher-bookmarks-modified + (y-or-n-p + "Changes have been made to the Bookmark directory. Save? ")) + (gopher-save-bookmarks)) + (kill-buffer oldbuf)))) + + +(defun gopher-directory-save-bookmarks () + "Save the bookmark list." + (interactive) + + (if (not (gopher-bookmark-directory-p)) + (error "This isn't the bookmark directory.")) + + (gopher-save-bookmarks)) + + + +;;; Gopher clean-up and quit. +;;; Originally from Thomas L|fgren <tde9104@abacus.hgs.se> + +(defun gopher-quit nil + "Quit gopher, and kill all gopher buffers. +If there are unsaved changes to your bookmark directory, you will be +asked if you want to save them" + (interactive) + (if (y-or-n-p "Do you really want to kill all gopher buffers? ") + (progn + (if (and gopher-support-bookmarks + gopher-bookmarks-modified + (y-or-n-p + "Changes have been made to the Bookmark directory. Save? ")) + (gopher-save-bookmarks)) + (let ((buflist (buffer-list)) + (case-fold-search t)) + (while buflist + (if (eq (string-match "\\*gopher" (buffer-name (car buflist))) 0) + (kill-buffer (car buflist))) + (setq buflist (cdr buflist))))))) + + +;;;;-------------------------------------------------------------------------- +;;;; bookmarks (Implemented originally by clamen@cs.cmu.edu) +;;;; + + +(defun gopher-read-bookmarks () + (cond ((null gopher-support-bookmarks)) + ((or (equal gopher-support-bookmarks 'unix) + (equal gopher-support-bookmarks t)) + (setq gopher-bookmarks + (gopher-read-unix-bookmarks))) + ((stringp gopher-support-bookmarks) + (gopher-read-lisp-bookmarks gopher-support-bookmarks)) + ((vectorp gopher-support-bookmarks) + (setq gopher-bookmarks gopher-support-bookmarks)) + (t + (message "Illformed gopher-bookmarks, assuming none")))) + + +(defun gopher-read-unix-bookmarks () + "Read bookmarks out of ~/.gopherrc file." + (let ((rcfile "~/.gopherrc")) + (if (file-exists-p rcfile) + (let* ((rcbuf (find-file-noselect rcfile)) + (bkmks (gopher-parse-bookmark-buffer rcbuf))) + (kill-buffer rcbuf) + (setq gopher-bookmarks-modified nil) + (setq gopher-bookmark-modified-tick + (1+ gopher-bookmark-modified-tick)) + bkmks) + (message "No %s exists." rcfile) + nil))) + +(defun gopher-parse-bookmark-buffer (buf) + "Read buffer containing bookmarks, formatted like ~.gopherrc +in UNIX gopher client." + (save-excursion + (set-buffer buf) + (goto-char (point-min)) + (if (re-search-forward "^bookmarks:\n" (point-max) t) + (let (bkmk bkmks) + (while (setq bkmk (gopher-parse-bookmark)) + (setq bkmks (cons bkmk bkmks))) + (apply 'vector (reverse bkmks)))))) + +(defun gopher-parse-bookmark-line (regexp end setf bkmk) + (save-excursion + (if (re-search-forward regexp end t) + (eval (list setf bkmk + (buffer-substring (match-beginning 1) (match-end 1)))) + ))) + + +(defun gopher-parse-bookmark () + "Read next bookmark. Return a directory object." + (if (looking-at "^#$") + (forward-line)) + (if (not (eobp)) + (let ((end (save-excursion + (forward-line 5) + (point))) + (bkmk (make-vector 5 nil))) + (prog1 + (and (gopher-parse-bookmark-line "^Type *= *\\(.+\\) *$" end + 'gopher-set-object-type bkmk) + (gopher-parse-bookmark-line "^Name *= *\\(.*\\) *$" end + 'gopher-set-object-descr bkmk) + (gopher-parse-bookmark-line "^Path *= *\\(.*\\) *$" end + 'gopher-set-object-selector bkmk) + (gopher-parse-bookmark-line "^Host *= *\\(.+\\) *$" end + 'gopher-set-object-host bkmk) + (gopher-parse-bookmark-line "^Port *= *\\(.+\\) *$" end + 'gopher-set-object-port bkmk) + (progn + (gopher-set-object-type + bkmk (string-to-char (gopher-object-type bkmk))) + (gopher-set-object-port + bkmk (string-to-int (gopher-object-port bkmk))) + bkmk)) + (goto-char end)) + ))) + +(defun gopher-format-bookmarks () + "Make the current buffer (which is assumed to be a bookmark buffer) +contain an up-to-date listing of the bookmark list." + + (let ((buffer-read-only nil)) + (erase-buffer) + (setq gopher-dir gopher-bookmarks) + + ;; Format it for your viewing pleasure. + (gopher-format-directory gopher-dir (current-buffer)) + (goto-char (point-min)) + (if (> (- (point-max) (point)) 7) (forward-char 7)) + (setq gopher-bookmark-buffer-tick gopher-bookmark-modified-tick))) + +(defun gopher-display-bookmarks () + "Retrieve and display the gopher bookmark directory." + (interactive) + + (if (> (length gopher-bookmarks) 0) + (let ((oldbuf (current-buffer)) + (dirbuf (gopher-get-dir-buf "*Gopher Bookmarks*"))) + + ;; Store our internal representation in gopher-dir. + (switch-to-buffer dirbuf) + (gopher-make-local-vars + 'gopher-dir gopher-bookmarks + 'gopher-bookmark-directory-p t + 'gopher-bookmark-buffer-tick gopher-bookmark-modified-tick + 'gopher-obj 'bookmark + 'gopher-last oldbuf) + + (gopher-format-bookmarks) + + ;; Turn on directory mode and put the description in the mode line. + (gopher-directory-mode) + (setq mode-line-buffer-identification (concat "Gopher: *Bookmarks*")) + ) + (error "No bookmarks supported."))) + + +(defun gopher-save-bookmarks () + "Save bookmarks." + (cond + ((or (equal gopher-support-bookmarks 'unix) + (equal gopher-support-bookmarks t)) + (gopher-save-unix-bookmarks)) + ((stringp gopher-support-bookmarks) + (gopher-save-lisp-bookmarks gopher-support-bookmarks)) + (t + (message "Illformed gopher-support-bookmarks, assuming none"))) + + (setq gopher-bookmarks-modified nil)) + + +(defun gopher-save-unix-bookmarks () + "Save bookmarks out to ~/.gopherrc file." + (save-excursion + (let* ((rcfile "~/.gopherrc") + (new-file-p (not (file-exists-p rcfile))) + (rcbuf (find-file-noselect rcfile))) + (set-buffer rcbuf) + (if new-file-p + (insert "bookmarks:\n") + (goto-char (point-min)) + (if (re-search-forward "^bookmarks:\n" nil t) + (delete-region (point) (point-max)) + (goto-char (point-max)) + (insert "bookmarks:\n"))) + + ;; Now, insert defined bookmarks into file + + (let ((obj-count 0)) + (while (< obj-count (length gopher-bookmarks)) + (let ((obj (aref gopher-bookmarks obj-count))) + (insert "#" + "\nType=" (gopher-object-type obj) + "\nName=" (gopher-object-descr obj) + "\nPath=" (gopher-object-selector obj) + "\nHost=" (gopher-object-host obj) + "\nPort=" (int-to-string (gopher-object-port obj)) + "\n") + (setq obj-count (1+ obj-count))))) + + (write-file rcfile)))) + + +(defun gopher-add-bookmark (arg) + "Add current object to menu of bookmarks. +With numeric prefix argument N, add Nth object." + (interactive "P") + (if (gopher-bookmark-directory-p) + (error "That item is already a bookmark!") + (let ((existing-bookmarks gopher-bookmarks) + (new-bookmarks (make-vector (1+ (length gopher-bookmarks)) nil)) + (obj (copy-sequence (gopher-directory-obj arg))) + (l (length gopher-bookmarks))) + (gopher-set-object-descr + obj + (read-from-minibuffer "Node Name: " + (gopher-object-descr obj))) + (aset new-bookmarks l obj) + (while (> l 0) + (progn (setq l (1- l)) + (aset new-bookmarks l (aref existing-bookmarks l)))) + (setq gopher-bookmarks new-bookmarks + gopher-bookmarks-modified t + gopher-bookmark-modified-tick (1+ gopher-bookmark-modified-tick)) + ))) + + +(defun gopher-delete-bookmark (arg) + "Delete current bookmark. +With numeric prefix argument N, delete Nth bookmark." + (interactive "P") + (if (not (gopher-bookmark-directory-p)) + (error "Can only delete object in Bookmark directory.") + (let ((new-bookmarks (make-vector (1- (length gopher-bookmarks)) nil)) + (pos (1- (gopher-directory-n arg))) + (l (length gopher-bookmarks)) + (i 0)) + (while (< i pos) + (progn (aset new-bookmarks i (aref gopher-bookmarks i)) + (setq i (1+ i)))) + (while (< i (1- l)) + (progn (aset new-bookmarks i (aref gopher-bookmarks (1+ i))) + (setq i (1+ i)))) + (setq gopher-killed-bookmark (aref gopher-bookmarks pos) + gopher-bookmarks new-bookmarks + gopher-dir new-bookmarks + gopher-bookmarks-modified t + gopher-bookmark-modified-tick (1+ gopher-bookmark-modified-tick)) + (let ((ppos (1- (gopher-directory-n nil)))) + (if (< pos ppos) + (setq ppos (1- ppos))) + (gopher-format-bookmarks) + (goto-char (point-min)) + (forward-line ppos) + (forward-char 7))) + (if (= (point) (point-max)) (previous-line 1)) +; (let ((buffer-read-only nil)) +; (beginning-of-line 1) +; (kill-line 1) +; (if (= (point) (point-max)) (previous-line 1))) + (if (zerop (length gopher-bookmarks)) + (gopher-last-node)))) + + +(defun gopher-yank-bookmark (arg) + "Yank the most recently killed bookmark at the current position. +With numeric prefix argument N, yank into position N." + (interactive "P") + (cond ((not (gopher-bookmark-directory-p)) + (error "Can only yank bookmark objects into bookmark directory.")) + ((null gopher-killed-bookmark) + (error "No killed bookmark object")) + (t + (let* ((len (length gopher-bookmarks)) + (new-bookmarks (make-vector (1+ len) nil)) + (pos (1- (gopher-directory-n arg))) + i) + + (if (or (< pos 0) (> pos (length gopher-bookmarks))) + (error "Out of range.")) + + (setq i (1- pos)) + (while (>= i 0) + (aset new-bookmarks i (aref gopher-bookmarks i)) + (setq i (1- i))) + + (aset new-bookmarks pos gopher-killed-bookmark) + + (setq i pos) + (while (< i len) + (aset new-bookmarks (1+ i) (aref gopher-bookmarks i)) + (setq i (1+ i))) + + (setq gopher-bookmarks new-bookmarks + gopher-bookmarks-modified t + gopher-killed-bookmark nil + gopher-bookmark-modified-tick + (1+ gopher-bookmark-modified-tick)) + + (let ((ppos (1- (gopher-directory-n nil)))) + (if (<= pos ppos) + (setq ppos (1+ ppos))) + (gopher-format-bookmarks) + (goto-char (point-min)) + (forward-line ppos) + (forward-char 7)) + )))) + + +(defun gopher-bookmark-directory-p () + "Return T if currently displaying Bookmark directory." + gopher-bookmark-directory-p) +; (equal gopher-dir gopher-bookmarks)) + + +(defun gopher-read-lisp-bookmarks (fn) + "currently unsupported" + (error "gopher-read-lisp-bookmark is not yet supported. Sorry.")) + +(defun gopher-save-lisp-bookmarks (fn) + "currently unsupported" + (error "gopher-save-lisp-bookmark is not yet supported. Sorry.")) + + + +;;;;-------------------------------------------------------------------------- +;;;; gopher documents +;;;; + + +(defun gopher-document-object (obj oldbuf &optional end-regexp) + "Retrieve and display a gopher document. +Optional argument END-REGEXP is used if the data will not be ended by `.'." + + (let ((docbuf (gopher-get-doc-buf (gopher-object-descr obj)))) + + ;; Snarf the data into the buffer. + (gopher-retrieve-document-cleanly (docbuf + (gopher-object-selector obj) + (gopher-object-host obj) + (gopher-object-port obj) + end-regexp) + + (progn + (kill-buffer docbuf) + (error "Problems retrieving document.")) + + ;; Turn on document mode and put the description in the mode line. + (switch-to-buffer docbuf) + (gopher-make-local-vars + 'gopher-obj obj + 'gopher-last oldbuf) + (goto-char (point-min)) + (gopher-document-mode) + (setq mode-line-buffer-identification (concat "Gopher: " + (gopher-object-descr obj))) + ))) + + +;; keymap for document mode +(suppress-keymap gopher-document-mode-map) + +;Virginia Peck <vapeck@cs> Mon Aug 10 21:44:35 1992 +;;(define-key gopher-document-mode-map "\C-xk" 'gopher-last-node) + +(define-key gopher-document-mode-map "l" 'gopher-last-node) +(define-key gopher-document-mode-map "q" 'gopher-last-node) +(define-key gopher-document-mode-map "u" 'gopher-last-node) +(define-key gopher-document-mode-map " " 'scroll-up) +(define-key gopher-document-mode-map "\C-?" 'scroll-down) +(define-key gopher-document-mode-map "\r" 'gopher-scroll-one-line-up) + + +(defun gopher-document-mode nil + "Gopher document mode. + +\\{gopher-document-mode-map} +" + (use-local-map gopher-document-mode-map) + (setq major-mode 'gopher-document-mode) + (setq mode-name "gopher doc") + (run-hooks 'gopher-document-mode-hook) + (setq buffer-read-only t)) + + +;; from gosmacs.el +(defun gopher-scroll-one-line-up (&optional arg) + "Scroll the selected window up (forward in the text) one line (or N lines)." + (interactive "p") + (scroll-up (or arg 1))) + + +;;;;-------------------------------------------------------------------------- +;;;; CSO handling. +;;;; +;;;; uses a subset of forms mode to handle data entry. +;;;; + +(defun gopher-cso-object (obj oldbuf) + "Display a CSO lookup form." + + ;; The following will create a buffer displaying the form described + ;; by the list in the last argument (cf. forms-mode). When the user + ;; accepts the data in the form (by pressing `C-c RET'), the function + ;; gopher-do-cso will be called with the data the user supplied. + (gopher-form (gopher-object-descr obj) + 'gopher-do-cso + 4 + '("====== phone directory lookup ======" + "\n Press `C-c RET' to lookup, `C-c l' to return to the last gopher object." + "\n (you must fill in at least one of the first three fields)" + "\n" + "Name : " 1 + "\n" + "Phone : " 2 + "\n" + "E-Mail : " 3 + "\n" + "Address : " 4 + )) + + ;; Record gopher-last so gopher-last-node knows where to go. + ;; Record gopher-obj so gopher-do-cso knows what server to query. + (gopher-make-local-vars + 'gopher-last oldbuf + 'gopher-obj obj)) + + +(defconst gopher-cso-fields '("name" "phone" "email" "address") + "Field names to use in CSO queries.") + +(defun gopher-do-cso (vals) + "Make a CSO query. VALS is the data the user entered in the form, +as a list of strings." + + ;; Check that the required data was provided. + (if (zerop (+ (length (nth 0 vals)) + (length (nth 1 vals)) + (length (nth 2 vals)))) + (error "Must specify name, phone, or email.")) + + (let ((query "query") + (fields gopher-cso-fields) + (obj gopher-obj)) + + ;; Form the query string + (while vals + + (if (not (zerop (length (car vals)))) + (setq query (concat query " " (car fields) "=" (car vals)))) + + (setq vals (cdr vals)) + (setq fields (cdr fields))) + + ;; Use this string as the object selector. + (gopher-set-object-selector gopher-obj query) + + ;; Retrieve the data from the server. Unlike gopher, the CSO data + ;; does not use `.' as a terminator. + (gopher-document-object gopher-obj (current-buffer) "^[2-9]") + + ;; Strip CSO control information from the buffer. + (gopher-clean-cso-buffer obj))) + + +(defun gopher-clean-cso-buffer (obj) + "Strip CSO control information from the current buffer." + + (let ((req "") + (buffer-read-only nil) + beg nreq) + (goto-char (point-min)) + (insert "\n") + (while (not (eobp)) + (cond ((and (>= (following-char) ?3) (<= (following-char) ?9)) + (delete-char 4) + (insert (concat (gopher-object-selector obj) "\n"))) + + ((eq (following-char) ?-) + (delete-char 5) + (setq beg (point)) + (skip-chars-forward "^:") + (setq nreq (buffer-substring beg (point))) + (goto-char beg) + (or (string= req nreq) + (insert (concat "--------------------------" + "-----------------------------\n"))) + (setq req nreq) + (setq beg (point)) + (skip-chars-forward "^:") + (forward-char) + (delete-region beg (point))) + + (t + (setq beg (point)) + (forward-line 1) + (delete-region beg (point)) + (forward-line -1)) + ) + (forward-line 1)) + + (goto-char (point-min)) + (delete-char 1))) + + +;;;;-------------------------------------------------------------------------- +;;;; indices. +;;;; +;;;; To query an index, the search string is appended to the selector. +;;;; The index returns a gopher directory. +;;;; + + +(defun gopher-index-object (obj oldbuf) + "Query a gopher directory object." + + ;; Get the search string from the user. + (let ((str (read-from-minibuffer "Key: ")) + (newobj (copy-sequence obj))) + + ;; Append it to the selector and retrieve the modified object + ;; like a directory. + (setq str (gopher-trim-blanks str)) + (if (> (length str) 0) + (progn + (gopher-set-object-selector newobj + (concat (gopher-object-selector obj) "\t" + str)) + (gopher-directory-object newobj (current-buffer))) + ))) + + + +;;;;-------------------------------------------------------------------------- +;;;; telneting. +;;;; + +(defun gopher-telnet-object (obj oldbuf) + "Start a telnet session to a gopher object. +If gopher-telnet-command is nonnil, then that is a command to start +a telnet session in a subprocess. Otherwise, the emacs-lisp telnet +package is used." + + ;; make the telnet argument string + (let ((arg (gopher-object-host obj)) + (port (gopher-object-port obj))) + (if (not (zerop port)) + (setq arg (concat arg + (if (eq system-type 'vax-vms) + "/port=" + " ") + port))) + + (if gopher-telnet-command + + ;; start up telnet as a separate process + (save-window-excursion + (gopher-background + (concat gopher-telnet-command " " arg))) + + ;; use telnet-mode + (telnet arg) + ;; set things up so we can get back to the last node. + (gopher-make-local-vars + 'gopher-obj obj + 'gopher-last oldbuf + 'gopher-telnet-process-name (concat arg "-telnet")) + (local-set-key "\C-cl" 'gopher-telnet-quit) + (local-set-key "\C-xk" 'gopher-telnet-quit) + ) + + ;; show the login info to the user + (if (not (zerop (length (gopher-object-selector obj)))) + (progn + (beep) + (message (concat + "Login as: " + (gopher-object-selector obj) + )) + )) + )) + + +(defun gopher-telnet-quit nil + "Clean up a telnet session and return to the previous gopher node." + (interactive) + (condition-case nil + (delete-process gopher-telnet-process-name) + (error t)) + (gopher-last-node)) + + + +;;;;-------------------------------------------------------------------------- +;;;; Images/sounds. +;;;; + +(defun gopher-image-object (obj oldbuf) + "Retrieve what we hope is an image and show it." + (let ( + (showit (y-or-n-p "Display this item? ")) + (fname) + (buf (gopher-get-doc-buf (gopher-object-descr obj)))) + (if showit + (setq fname (make-temp-name "/tmp/gopherimg")) + (setq fname(read-file-name "File to save in: "))) + (gopher-retrieve-document-cleanly (buf + (gopher-object-selector obj) + (gopher-object-host obj) + (gopher-object-port obj) + 'none) + (progn + (error "Problems retrieving object.") + (kill-buffer buf)) + + (save-excursion + (set-buffer buf) + (write-file fname)) + (kill-buffer buf) + (if (and showit gopher-image-display-command) + + ;; Spawn a process to display the image. + ;; But modify its sentinel so that the file we wrote + ;; will get deleted when the process exits. + (save-window-excursion + (let ((p (gopher-background + (concat gopher-image-display-command " " fname)))) + (set-process-sentinel p + (` (lambda (process msg) + ((, (process-sentinel p)) process msg) + (if (not (eq (process-status process) 'run)) + (delete-file (, fname))) + ))) + )) + )))) + + + +;;;;-------------------------------------------------------------------------- +;;;; Various opaque objects. Just save them in a file for now. +;;;; + +(defun gopher-binary-object (obj oldbuf) + "Retrieve a gopher object and save it to a file, +without trying to interpret it in any way." + (let ((fname (read-file-name "File to save in: ")) + (buf (gopher-get-doc-buf (gopher-object-descr obj)))) + + (gopher-retrieve-document-cleanly (buf + (gopher-object-selector obj) + (gopher-object-host obj) + (gopher-object-port obj) + 'none) + + (progn + (error "Problems retrieving object.") + (kill-buffer buf)) + + (save-excursion + (set-buffer buf) + (write-file fname)) + (kill-buffer buf) + ))) + + +;;;;-------------------------------------------------------------------------- +;;;; forms stuff +;;;; +;;;; Uses some of the internal routines from forms.el to present +;;;; a form which is not associated with a file. +;;;; + +(defun gopher-form (form-name accept-action number-of-fields format-list) + "Display a buffer containing a form for the user to enter data. +The form is described by NUMBER-OF-FIELDS and FORMAT-LIST (cf. forms-mode). +FORM-NAME is a string to put in the modeline. +When the user accepts the data in the form by pressing `C-c RET', the +function ACCEPT-ACTION is called with a list of the strings which +the user entered." + + (switch-to-buffer (generate-new-buffer "*gopher form*")) + + (gopher-make-local-vars + 'forms-format-list format-list + 'forms-number-of-fields number-of-fields + 'forms-field-sep "\t" + 'forms-read-only nil + 'forms-multi-line nil + 'forms--number-of-markers nil + 'forms--markers nil + 'forms--format nil + 'forms--parser nil + 'forms--dynamic-text nil + 'forms-fields nil + 'forms-the-record-list nil + 'forms-accept-action accept-action + ) + + (forms--process-format-list) + (forms--make-format) + (forms--make-parser) + + (erase-buffer) + + ;; make local variables + (make-local-variable 'forms--file-buffer) + (make-local-variable 'forms--total-records) + (make-local-variable 'forms--current-record) + (make-local-variable 'forms--the-record-list) + (make-local-variable 'forms--search-rexexp) + + ;; set the major mode indicator + (setq major-mode 'gopher-form-mode) + (setq mode-name "gopher form") + + (set-buffer-modified-p nil) + + (use-local-map gopher-form-mode-map) + + (forms--show-record (make-string (1- number-of-fields) ?\t)) + + (run-hooks 'gopher-form-mode-hooks)) + + +(defun gopher-form-accept nil + (interactive) + + (funcall forms-accept-action (forms--parse-form))) + +(define-key gopher-form-mode-map "\C-c\r" 'gopher-form-accept) +(define-key gopher-form-mode-map "\C-cl" 'gopher-last-node) + + +;;;;-------------------------------------------------------------------------- +;;;; low-level communications routines +;;;; + + +(defun gopher-retrieve-document (buf sel host port &optional end-regexp) + "Retrieve a gopher object into BUF. +The object is identified by a SEL HOST PORT triple. +Optional argument END-REGEXP is used for data which is not `.'-terminated. +If END-REGEXP is non-nil and not a string, then it is assumed that +the data is binary, and reading will continue until the sender disconnects. +Returns NIL if an error occured during the attempt to retrieve the +document, otherwise T. +" + + ;; Default is single period termination. + (or end-regexp (setq end-regexp "^\\.\r$")) + + (save-excursion + (set-buffer buf) + (erase-buffer) + + (let ((h (assoc host gopher-hostname-aliases))) + (if h (setq host (cdr h)))) + + ;; Open the connection to the server. + ;; If we get an unknown service error, try looking the port up in + ;; gopher-port-aliases. If we find it there, try the connect again + ;; with that translation. + (let (wait + (gopher-server-process + (let (p (try-again t)) + (while try-again + (setq try-again nil) + (condition-case errinfo + (setq p (open-network-stream "gopher" (current-buffer) + host port)) + (error (if (and (string-match "^Unknown service .*$" + (nth 1 errinfo)) + (setq port (cdr (assoc port + gopher-port-aliases)))) + (setq try-again t) + (ding) + (message (format "%s: %s" + (nth 0 errinfo) + (nth 1 errinfo))) + )))) + p))) + + (cond (gopher-server-process + + ;; keep the emacs end-of-process status line out of the buffer + (set-process-sentinel gopher-server-process 'gopher-sentinel) + + ;; send the selector to the server + (process-send-string gopher-server-process (concat sel "\r\n")) + + ;; receive the response from the server + ;; based on nntp.el from GNUS + (setq wait t) + (while wait + (if (stringp end-regexp) + (progn + (goto-char (point-max)) + (forward-line -1))) + (if (and (stringp end-regexp) + (looking-at end-regexp)) + (setq wait nil) + (if (not (memq (process-status gopher-server-process) + '(open run))) + (progn + (message "gopher: connection closed") + (setq wait nil)) + (if gopher-debug-read + (message "gopher: Reading...")) + (cond (gopher-buggy-accept + (sit-for 1)) + ((and (boundp 'epoch::version) epoch::version) + (accept-process-output gopher-server-process 2)) + (t + (accept-process-output gopher-server-process)) + ) + (if gopher-debug-read + (message " "))) + )) + + ;; be sure the net connection has gone away... + (condition-case nil + (delete-process gopher-server-process) + (error t)) + + ;; clean up the text buffer + (if (stringp end-regexp) + (gopher-clean-text)) + + t) + + (t nil)) + ))) + + +;;; adapted from GNUS +(defun gopher-clean-text () + "Decode text transmitted by gopher. +0. Delete status line. +1. Delete `^M' at end of line. +2. Delete `.' at end of buffer (end of text mark). +3. Delete `.' at beginning of line. (does gopher want this?)" + + ;; Insert newline at end of buffer. + (goto-char (point-max)) + (if (not (bolp)) + (insert "\n")) + ;; Delete `^M' at end of line. + (goto-char (point-min)) + (while (re-search-forward "\r[^\n]*$" nil t) + (replace-match "")) +; (goto-char (point-min)) +; (while (not (eobp)) +; (end-of-line) +; (if (= (preceding-char) ?\r) +; (delete-char -1)) +; (forward-line 1) +; ) + ;; Delete `.' at end of buffer (end of text mark). + (goto-char (point-max)) + (forward-line -1) ;(beginning-of-line) + (while (looking-at "^\\.$") + (delete-region (point) (progn (forward-line 1) (point))) + (forward-line -1)) + ;; Replace `..' at beginning of line with `.'. + (goto-char (point-min)) + ;; (replace-regexp "^\\.\\." ".") + (while (search-forward "\n.." nil t) + (delete-char -1)) + ) + + +(defun gopher-sentinel (proc status) + nil) + +(provide 'gopher) + +;;; gopher.el ends here + +;;;(gopher.el) Local Variables: +;;;(gopher.el) eval: (put 'gopher-retrieve-document-cleanly 'lisp-indent-hook 2) +;;;(gopher.el) End: +