From: SNYDER@d0sb10.fnal.gov (scott snyder)
Subject: a gopher client for emacs   1/2
Organization: Source only  Discussion and requests in gnu.emacs.help.
Distribution: gnu
Date: Sat, 29 Aug 1992 23:26:34 GMT


This is a gopher client for GNU emacs.
Gopher is a world-wide document retrieval and information delivery service.
(See the comp.infosystems.gopher FAQ for more information.)
It presently supports directory, text, index, telnet, and CSO phonebook
objects.  Other object types can be downloaded as binary files, but
not viewed from within emacs.

Operating instructions are contained in the header comments;
in brief, load this file and do `M-x gopher'.  You'll also need
to have forms.el and background.el (available at an emacs-lisp
archive site near you).

If gopher consistently hangs while trying to read data from a server,
try setting gopher-buggy-accept to non-nil.  I've observed this problem
on a SGI Iris; it may be present on other systems as well.

Feedback, corrections, and additions are welcome...

enjoy!
sss

#!/bin/sh
# This is a shell archive (produced by shar 3.49)
# To extract the files from this archive, save it to a file, remove
# everything above the "!/bin/sh" line above, and type "sh file_name".
#
# made 08/30/1992 09:01 UTC by snyder@d0sgi3.fnal.gov
# Source directory /usr1/snyder
#
# existing files will NOT be overwritten unless -c is specified
#
# This is part 1 of a multipart archive                                    
# do not concatenate these parts, unpack them in order with /bin/sh        
#
# This shar contains:
# length  mode       name
# ------ ---------- ------------------------------------------
#  47673 -rw-r--r-- gopher.el
#
if test -r _shar_seq_.tmp; then
	echo 'Must unpack archives in sequence!'
	echo Please unpack part `cat _shar_seq_.tmp` next
	exit 1
fi
# ============= gopher.el ==============
if test -f 'gopher.el' -a X"$1" != X"-c"; then
	echo 'x - skipping gopher.el (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting gopher.el (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'gopher.el' &&
X;;; gopher.el --- an emacs gopher client
X
X;; Copyright (C) 1992 scott snyder
X
X;; Author: scott snyder <snyder@d0gsc.fnal.gov>
X;; Created: 29 Jun 1992
X;; Version: 1.0
X;; Keywords: gopher
X
X;; This file is not part of GNU Emacs, but is distributed under the
X;; same conditions.
X;;
X;; GNU Emacs is distributed in the hope that it will be useful,
X;; but WITHOUT ANY WARRANTY.  No author or distributor
X;; accepts responsibility to anyone for the consequences of using it
X;; or for whether it serves any particular purpose or works at all,
X;; unless he says so in writing.  Refer to the GNU Emacs General Public
X;; License for full details.
X;;
X;; Everyone is granted permission to copy, modify and redistribute
X;; GNU Emacs, but only under the conditions described in the
X;; GNU Emacs General Public License.   A copy of this license is
X;; supposed to have been given to you along with GNU Emacs so you
X;; can know your rights and responsibilities.  It should be in a
X;; file named COPYING.  Among other things, the copyright notice
X;; and this notice must be preserved on all copies.
X;;
X;;
X;; An emacs gopher client.  Currently supports directory, text, CSO,
X;; index, and telnet objects.
X;; Requires forms.el and background.el.
X;; 
X;; Written by scott snyder <snyder@d0gsc.fnal.gov>
X;; Some code borrowed from GNUS (by Masanobu UMEDA).
X;; Some code (bookmarks, xterms, error handling) contributed
X;; by Stewart Clamen <clamen@cs.cmu.edu>.
X
X;;; Commentary:
X;; OPERATING INSTRUCTIONS
X;;
X;; To use, `M-x gopher'.  To specify a different root server, use
X;; `C-u M-x gopher'.  If you want to use bookmarks, set the variable
X;; gopher-support-bookmarks appropriately.
X;;
X;; In directory mode:
X;;   Space or return selects the line point is on.
X;;   With a numeric prefix argument, select that object.
X;;   `q', `l', or `u' will return to the previous node.
X;;   `n' and `p' to the next and previous lines.
X;;   `a' will add an object to your bookmark list.
X;;   `v' will display your bookmark list.
X;;   `=' gives detailed information about an object.
X;;  In the bookmark list, all of the above (except `a'), plus:
X;;   `C-k' will delete an object from the bookmark list.
X;;   `C-y' will yank the most recently deleted bookmark object back into
X;;         the bookmark buffer.
X;;   `s' will save the bookmark list.
X;;
X;;   All commands which operate on a specific object can take an optional
X;;   numeric prefix argument giving the index of the object on which
X;;   to operate.
X;;
X;; In document mode:
X;;   Space pages forward.
X;;   Delete pages backward.
X;;   `q', `l' or `u' returns to the last node.
X;;
X;; In the CSO entry form:
X;;    `C-c RET' performs a look-up, based on the field contents
X;;      you've filed in.
X;;    `C-c l' returns to the previous node.
X;;
X;; Telnets:
X;;    If you have an X server set, gopher will try to create an xterm
X;;    running telnet.  If not, the emacs-lisp telnet mode will be used.
X;;    From the emacs-lisp telnet mode, use `C-c l' to kill the session
X;;    and return to the previous node.
X;;    See also the variable gopher-telnet-command.
X;;
X;; Note:
X;;   If gopher consistently hangs while trying to retrieve an object,
X;;   try turning on gopher-buggy-accept (which see).
X;;
X;; VMS notes:
X;;   To use this on VMS, you'll need my emacs subprocess patches (recently
X;;   posted on gnu.emacs.sources; if you can't find them, send me mail).
X;;   To be able to run telnet in a separate decterm, you'll also need to
X;;   to (setq shell-file-name "docmd") and create the file
X;;   emacs_library:[etc]docmd.com containing the following:
X;;     $ if p1 .eqs. "-C" then p1 = ""
X;;     $ deass sys$input
X;;     $ 'p1 'p2 'p3 'p4 'p5 'p6 'p7 'p8
X;;     $ eoj
X
X;;; Change Log:
X;;
X;; Version 1.00    29-AUG-1992
X;;  * Added gopher-buggy-accept.
X;;  * Reworked telnet stuff to use an arbitrary command string to start up
X;;    the telnet process.  This can be used to start the telnet in a
X;;    separate terminal window.
X;;    Based on code from Stewart Clamen.
X;;  * Stewart Clamen <clamen@cs.cmu.edu> added bookmarks.
X;;  * Added 's key binding to save bookmarks.
X;;  * Added a prefix argument to gopher-directory-buffer and
X;;    gopher-add-bookmark.
X;;  * Added standard emacs-lisp header.
X;;  * Stewart Clamen <clamen@cs.cmu.edu> added some error trapping and
X;;    recovery (gopher-retrieve-document-cleanly).
X;;  * Appended node description to the node's buffer's name.
X;;  * Reformat bookmark buffers when returning to them via gopher-last-node
X;;    or when an item is deleted.
X;;  * Added gopher-yank-bookmark.
X;;  * Added gopher-bookmark-modified-tick to prevent reformatting bookmark
X;;    buffers needlessly.
X;;
X;; Version 0.92    27-JUL-1992
X;;  * Added gopher-hostname-aliases.
X;;
X;; Version 0.91    30-JUN-1992
X;;  * Deal with servers which send stuff after the CR.
X;;  * Prevent gopher-directory-show-object from clearing the read-only flag.
X;;  * Allow specification of port number in `C-u M-x gopher'.
X;;
X;; Version 0.9     29-JUN-1992
X;;  * Initial release.
X
X;;; Code:
X
X(require 'electric)
X(require 'forms)
X
X;; background has the same name as an epoch function. 
X;; Rename it to gopher-background...
X;; also, the version i got from the archive didn't have a provide...
X(if (not (fboundp 'gopher-background))
X    (if (fboundp 'background)
X        (let ((old-background (symbol-function 'background)))
X          (load-library "background")
X          (fset 'gopher-background (symbol-function 'background))
X          (fset 'background old-background))
X      (load-library "background")
X      (fset 'gopher-background (symbol-function 'background))
X      ))
X
X(defvar gopher-root-node (vector ?1 "root" "" "gopher.micro.umn.edu" 70)
X  "The root gopher server, as a gopher object.")
X
X(defvar gopher-directory-mode-hook nil
X  "*Invoked when entering a new gopher directory.")
X(defvar gopher-directory-mode-map (make-keymap)
X  "Keymap for gopher-directory-mode.")
X
X(defvar gopher-document-mode-hook nil
X  "*Invoked when showing gopher document.")
X(defvar gopher-document-mode-map (make-keymap)
X  "Keymap for gopher-document-mode.")
X
X(defvar gopher-form-mode-hooks nil
X  "*Invoked with entering a gopher form (i.e., for CSO).")
X(defvar gopher-form-mode-map (make-keymap)
X  "Keymap for gopher-form-mode.")
X
X(defvar gopher-tmp-buf nil
X  "Buffer used to receive output from gopher.")
X
X(defvar gopher-debug-read t
X  "*If non-nil, show the current status about reading the gopher server output.")
X
X;; On some systems (such as SGI Iris), accept-process-output doesn't seem
X;; to return for the last packet received on a connection.  Turn this on
X;; to work around the problem, but does anyone know what causes this?
X(defvar gopher-buggy-accept nil
X  "*If non-nil, use sit-for instead of accept-process-output.
XIf gopher consistently hangs while fetching an object, try turning this on.")
X
X(defvar gopher-hostname-aliases
X  '(("128.230.33.31" . "oliver.syr.edu"))
X  "Emacs can't deal with raw IP addresses used as a hostname.
XUse this to work around...")
X
X
X(defvar gopher-support-bookmarks nil
X  "*If nil, do not implement bookmarks. 
XIf 'unix or t, read and write bookmarks to ~/.gopherrc. 
XIf a filename, read and save vector from there directly (not implemented yet).
XIf a vector, treat as a built-in directory.")
X
X(defconst gopher-bookmarks nil "Internal bookmark directory.")
X(defconst gopher-bookmarks-modified nil "Do bookmarks need to be saved?")
X(defconst gopher-killed-bookmark nil "The last bookmark object to be killed")
X(defconst gopher-bookmark-directory-p nil
X  "Is this buffer a bookmark directory?  A buffer-local variable.")
X
X(defvar gopher-bookmark-modified-tick 0
X  "Counts each time the bookmark vector is modified.")
X
X
X(defvar gopher-telnet-command
X  (cond ((eq system-type 'vax-vms)
X         (if (getenv "DECW$DISPLAY")
X             "create/terminal/wait/window=(title=\"telnet\") telnet"))
X        (t
X         (if (getenv "DISPLAY")
X             "xterm -e telnet"))
X        )
X  "*Command to use to start a telnet session.
XIf this is nil, the emacs-lisp telnet package will be used.
XThe default setting is to create a terminal window running telnet
Xif you've specified an X server, and to use the emacs-lisp telnet otherwise.")
X
X
X(defvar gopher-object-type-alist
X  '(( ?0   ""          gopher-document-object)
X    ( ?1   "/"         gopher-directory-object)
X    ( ?2   " <CSO>"    gopher-cso-object)
X    ( ?3   " <error>"  gopher-unimplemented-object)
X    ( ?4   " <binhex>" gopher-binary-object)
X    ( ?5   " <DOS>"    gopher-binary-object)
X    ( ?6   " <UU>"     gopher-binary-object)
X    ( ?7   " <?>"      gopher-index-object)
X    ( ?8   " <TEL>"    gopher-telnet-object)
X    ( ?9   " <bin>"    gopher-binary-object)
X    ( ?T   " <T>"      gopher-unimplemented-object)
X    ( ?s   " <)"       gopher-binary-object)
X    ( ?M   " <MIME>"   gopher-unimplemented-object)
X    ( ?h   " <html>"   gopher-unimplemented-object)
X    ( ?I   " <image>"  gopher-unimplemented-object)
X    ( ?c   " <cal>"    gopher-unimplemented-object)
X    ( ?g   " <GIF>"    gopher-binary-object)
X    )
X  "*Alist describing the types of gopher objects this client know about.
XThe keys are the gopher type characters.
XThe second element in each list is the string to tag onto the end
Xof an object's description, to identify it to the user.
XThe third element is the function to use to retrieve the object.
XIt is called with two arguments: the gopher object to retrieve and
Xthe buffer which should be returned to when the user is done
Xwith this object.")
X
X
X;;;
X;;; The data structure describing a gopher object is a vector of five elements:
X;;;    [ TYPE DESCR SELECTOR HOST PORT ]
X;;;
X;;;  TYPE is the type character.
X;;;  DESCR is the human-readable description of the object.
X;;;  SELECTOR is the opaque selector to be sent to HOST to retrieve the obj.
X;;;  HOST is the name of the Internet host on which the object resides.
X;;;  PORT is the TCP/IP port on which the host is listening.
X;;;
X;;;  The following macros set and fetch elements of this structure.
X;;;  
X
X(defmacro gopher-object-type (object)
X  "Return the gopher type of OBJECT."
X  (` (aref (, object) 0)))
X
X(defmacro gopher-object-descr (object)
X  "Return the gopher description of OBJECT."
X  (` (aref (, object) 1)))
X
X(defmacro gopher-object-selector (object)
X  "Return the gopher selector string for OBJECT."
X  (` (aref (, object) 2)))
X
X(defmacro gopher-object-host (object)
X  "Return the gopher hostname for OBJECT."
X  (` (aref (, object) 3)))
X
X(defmacro gopher-object-port (object)
X  "Return the gopher TCP port number for OBJECT."
X  (` (aref (, object) 4)))
X
X
X(defmacro gopher-set-object-type (object type)
X  "Set the gopher type of OBJECT to TYPE."
X  (` (aset (, object) 0 (, type))))
X
X(defmacro gopher-set-object-descr (object descr)
X  "Set the gopher description of OBJECT to DESCR."
X  (` (aset (, object) 1 (, descr))))
X
X(defmacro gopher-set-object-selector (object selector)
X  "Set the gopher selector string for OBJECT to SELECTOR."
X  (` (aset (, object) 2 (, selector))))
X
X(defmacro gopher-set-object-host (object host)
X  "Set the gopher hostname for OBJECT to HOST."
X  (` (aset (, object) 3 (, host))))
X
X(defmacro gopher-set-object-port (object port)
X  "Set the gopher TCP port number for OBJECT to PORT."
X  (` (aset (, object) 4 (, port))))
X
X
X(defmacro gopher-retrieve-document-cleanly (args handle &rest body)
X  "Call gopher-retrieve-document with condition-case wrapped around, 
Xapplying HANDLE if appropriate."
X  (` (condition-case nil
X         (progn
X           (gopher-retrieve-document (,@ args))
X           (,@ body))
X       (error (, handle)))))
X
X
X;;
X;; buffer-local variables.
X;; declared here to prevent warnings from the new byte-compiler.
X;;
X
X(defvar gopher-dir nil)
X(defvar gopher-last nil)
X(defvar gopher-obj nil)
X(defvar gopher-telnet-process-name nil)
X(defvar gopher-bookmark-buffer-tick nil)
X(defvar forms-accept-action nil)        
X
X
X;;;;--------------------------------------------------------------------------
X;;;; main dispatching logic.
X;;;;
X
X(defun gopher (&optional askserv)
X  "Start a gopher session.  With C-u, prompt for a gopher server."
X  (interactive "P")
X  (if askserv
X      (progn
X        (gopher-set-object-host
X         gopher-root-node
X         (read-string "Gopher server: "
X                      (gopher-object-host gopher-root-node)))
X
X        (let (portstr port)
X          (while (not (numberp port))
X            (setq portstr
X                  (read-string "Port: "
X                               (int-to-string
X                                (gopher-object-port gopher-root-node))))
X
X            (setq port (condition-case nil
X                           (car (read-from-string portstr))
X                         (error nil)))
X
X            (if (not (numberp port))
X                (progn
X                  (ding)
X                  (message "Port must be numeric")
X                  (sit-for 1)))
X            )
X
X          (gopher-set-object-port gopher-root-node port))))
X
X  (cond ((null gopher-support-bookmarks))
X        ((or (equal gopher-support-bookmarks 'unix)
X             (equal gopher-support-bookmarks t))
X         (setq gopher-bookmarks 
X               (gopher-read-unix-bookmarks)))
X        ((stringp gopher-support-bookmarks)
X         (gopher-read-lisp-bookmarks gopher-support-bookmarks))
X        ((vectorp gopher-support-bookmarks)
X         (setq gopher-bookmarks gopher-support-bookmarks))
X        (t
X         (message "Illformed gopher-bookmarks, assuming none")))
X
X  (gopher-dispatch-object gopher-root-node nil))
X  
X
X
X(defun gopher-dispatch-object (obj lastbuf)
X  "Dispatch a gopher object depending on its type."
X  (let ((typedesc (assq (gopher-object-type obj) gopher-object-type-alist)))
X
X    (if typedesc
X        (funcall (nth 2 typedesc) obj lastbuf)
X      (gopher-unimplemented-object obj lastbuf))))
X
X
X(defun gopher-unimplemented-object (obj lastbuf)
X  (error "unimplemented object type"))
X
X
X;;;;--------------------------------------------------------------------------
X;;;; utilities
X;;;;
X
X(defun gopher-next-field nil
X  "Returns as a string all chars between point and the next tab or newline.
XPoint is advanced to after the tab (or to the end-of-line)."
X
X  (let ((beg (point)) s)
X    (skip-chars-forward "^\t\n")
X    (setq s (buffer-substring beg (point)))
X    (if (eq (following-char) ?\t)
X        (forward-char))
X    s))
X
X
X;; from GNUS
X(defun gopher-make-local-vars (&rest pairs)
X  ;; Take VARIABLE-VALUE pairs and makes local variables initialized to the
X  ;; value.
X  (while pairs
X    (make-local-variable (car pairs))
X    (set (car pairs) (car (cdr pairs)))
X    (setq pairs (cdr (cdr pairs)))))
X
X
X(defun gopher-get-tmp-buf nil
X  "Get a temporary buffer in which to receive gopher output."
X  (or (bufferp gopher-tmp-buf)
X      (progn
X        (setq gopher-tmp-buf (get-buffer-create " *gopher-tmp*"))
X        (buffer-flush-undo gopher-tmp-buf)))
X  gopher-tmp-buf)
X
X
X(defun gopher-get-dir-buf (descr)
X  "Get a new buffer suitable for a gopher directory or document."
X  (let ((buf (generate-new-buffer (concat "*gopher*" descr))))
X    (buffer-flush-undo buf)
X    buf))
X
X(fset 'gopher-get-doc-buf (symbol-function 'gopher-get-dir-buf))
X
X
X(defun gopher-trim-blanks (str)
X  "Remove leading and trailing blanks from STR."
X  (string-match "\\`[ \t\n]*" str)
X  (substring str
X             (match-end 0)
X             (string-match "[ \t\n]*\\'" str (match-end 0))))
X
X
X;;;;--------------------------------------------------------------------------
X;;;; directory handling
X;;;;
X
X
X(defun gopher-directory-object (obj oldbuf)
X  "Retrieve and display a gopher directory."
X
X  (let ((tmpbuf (gopher-get-tmp-buf))
X        (dirbuf (gopher-get-dir-buf (gopher-object-descr obj))))
X
X    ;; Get the directory...
X    (gopher-retrieve-document-cleanly (tmpbuf
X                                       (gopher-object-selector obj)
X                                       (gopher-object-host     obj)
X                                       (gopher-object-port     obj))
X        
X        (progn
X          (kill-buffer dirbuf)
X          (error "Problems retrieving directory."))
X
X      ;; Parse it and store our internal representation in gopher-dir.
X      (switch-to-buffer dirbuf)
X      (gopher-make-local-vars
X       'gopher-dir (gopher-parse-directory tmpbuf)
X       'gopher-last oldbuf)
X
X      ;; Format it for your viewing pleasure.
X      (gopher-format-directory gopher-dir dirbuf)
X      (goto-char (point-min))
X      (if (> (- (point-max) (point)) 7) (forward-char 7))
X
X      ;; Turn on directory mode and put the description in the mode line.
X      (gopher-directory-mode)
X      (setq mode-line-buffer-identification (concat "Gopher: "
X                                                    (gopher-object-descr obj)))
X      )))
X
X
X(defun gopher-parse-directory (buf)
X  "Parse the gopher directory in buffer BUF into our internal representation.
XReturns a vector of gopher objects."
X
X  (save-excursion
X    (set-buffer buf)
X    (goto-char (point-min))
X
X    (let* ((len (count-lines (point-min) (point-max)))
X           (dir (make-vector len nil))
X           (i 0))
X
X      (while (not (eobp))
X        (aset dir i (gopher-parse-directory-line))
X        (setq i (1+ i))
X        (forward-line 1))
X
X      dir)))
X
X
X(defun gopher-parse-directory-line nil
X  "Parse the line containing point as a gopher directory entry.
XReturns the corresponding gopher object."
X
X  (let (type descr selector host port)
X    (beginning-of-line)
X    (setq type (following-char))
X    (forward-char)
X    (setq descr (gopher-next-field))
X    (setq selector (gopher-next-field))
X    (setq host (gopher-next-field))
X    (setq port (gopher-next-field))
X
X    (if (string-match "^[0-9]+$" port)
X        (setq port (string-to-int port)))
X
X    (vector type descr selector host port)))
X
X
X(defun gopher-format-directory (dir buf)
X  "Print the directory vector DIR into buffer BUF."
X
X  (save-excursion
X    (set-buffer buf)
X    (erase-buffer)
X    (let ((i 0)
X          (len (length dir)))
X      (while (< i len)
X        (gopher-format-directory-line (aref dir i) (1+ i))
X        (setq i (1+ i)))
X
X      )))
X
X
X(defun gopher-format-directory-line (obj ndx)
X  "Insert a line describing the gopher object OBJ into the current buffer.
XNDX is a numeric index to display to the left of the object description."
X
X  (let ((ndx-str (int-to-string ndx))
X        (typedesc (assq (gopher-object-type obj) gopher-object-type-alist)))
X
X    ;; display the index number.  use 5 digits, right-justified.
X    (if (< (length ndx-str) 5)
X        (insert (make-string (- 5 (length ndx-str)) ? )))
X    (insert ndx-str)
X    (insert ". ")
X
X    ;; add the object description.
X    (insert (gopher-object-descr obj))
X
X    ;; add a tag indicating the gopher object type.
X    (insert (if typedesc
X                (nth 1 typedesc)
X              (concat " ???" (char-to-string (gopher-object-type obj)))))
X
X    (insert "\n")))
X
X
X(defun gopher-directory-mode nil
X  "Gopher directory mode.
X
X\\{gopher-directory-mode-map}
X"
X  (use-local-map gopher-directory-mode-map)
X  (setq major-mode 'gopher-directory-mode)
X  (setq mode-name "gopher dir")
X  (run-hooks 'gopher-directory-mode-hook)
X  (setq buffer-read-only t))
X
X
X;;; keymap for directory mode
X(suppress-keymap gopher-directory-mode-map)
X(define-key gopher-directory-mode-map "\r" 'gopher-directory-choose)
X(define-key gopher-directory-mode-map " " 'gopher-directory-choose)
X(define-key gopher-directory-mode-map "l"  'gopher-last-node)
X(define-key gopher-directory-mode-map "q"  'gopher-last-node)
X(define-key gopher-directory-mode-map "u"  'gopher-last-node)
X(define-key gopher-directory-mode-map "="  'gopher-directory-show-object)
X
X; Virginia Peck <vapeck@cs>  Mon Aug 10 1992
X(define-key gopher-directory-mode-map "n"  'next-line)
X(define-key gopher-directory-mode-map "p"  'previous-line)
X;;(define-key gopher-directory-mode-map "\C-xk"  'gopher-last-node)
X
X; Stewart Clamen <clamen@cs.cmu.edu>  Mon Aug 17 1992
X(define-key gopher-directory-mode-map "v"  'gopher-display-bookmarks)
X(define-key gopher-directory-mode-map "a"  'gopher-add-bookmark)
X(define-key gopher-directory-mode-map "\C-k"  'gopher-delete-bookmark)
X(define-key gopher-directory-mode-map "s" 'gopher-directory-save-bookmarks)
X(define-key gopher-directory-mode-map "\C-y" 'gopher-yank-bookmark)
X
X
X(defun gopher-directory-nth-obj (n)
X  "Returns the Nth object (starting at 1) in a gopher directory buffer."
X  (if (or (<= n 0) (> n (length gopher-dir)))
X      (error "Out of range."))
X  (aref gopher-dir (1- n)))
X
X
X(defun gopher-directory-n (arg)
X  "Return the index of the object specified by ARG (starting at 1).
XIf ARG is nil, this is the index of the current line.
XOtherwise, it is the value of ARG (as a prefix argument)."
X  (if arg
X      (prefix-numeric-value arg)
X    (if (eq (point) (point-max))
X	(1+ (count-lines (point-min) (point-max)))
X      (count-lines (point-min) (1+ (point))))))
X
X
X(defun gopher-directory-obj (arg)
X  "Return the gopher object given by prefix arg ARG.
XIf it is nil, return the object given by the line point is on.
XOtherwise, ARG is the index of the object."
X  (gopher-directory-nth-obj (gopher-directory-n arg)))
X
X
X(defun gopher-directory-choose (arg)
X  "Choose an item from the directory, and do whatever is appropriate
Xbased on the object's type.  Default is to choose the object given by the
Xline the cursor is on.  With numeric prefix argument N, choose object N."
X  (interactive "P")
X  (gopher-dispatch-object (gopher-directory-obj arg) (current-buffer)))
X
X
X(defun gopher-directory-show-object (arg)
X  "Dump the internal information in a gopher object.
XWith numeric prefix argument N, show information about the Nth object."
X  (interactive "P")
X  (let* ((obj (gopher-directory-obj arg))
X         (type (gopher-object-type obj))
X         (typespec (assq type gopher-object-type-alist))
X         (typetag (if typespec (nth 1 typespec) "?"))
X         (typeproc (if typespec (nth 2 typespec) "?")))
X    (with-output-to-temp-buffer "*Gopher object*"
X      (princ (format "Type        : %c   `%s'   %s\n" type typetag typeproc))
X      (princ (format "Description : %s\n" (gopher-object-descr    obj)))
X      (princ (format "Selector    : %s\n" (gopher-object-selector obj)))
X      (princ (format "Host        : %s\n" (gopher-object-host     obj)))
X      (princ (format "Port        : %d\n" (gopher-object-port     obj)))
X      (current-buffer)
X      ))
X  (shrink-window-if-larger-than-buffer (get-buffer-window "*Gopher object*"))
X
X  ;; shrink-window-if-larger-than-buffer screws these up...
X  (set-buffer-modified-p nil)
X  (setq buffer-read-only t))
X
X
X(defun gopher-last-node nil
X  "Return to the previous gopher node.
XBy convention, a gopher buffer has the local variable gopher-last which
Xcontains the buffer to which we should return."
X  (interactive)
X  (let ((oldbuf (current-buffer)))
X    (if gopher-last
X        (progn
X          (switch-to-buffer gopher-last)
X          (kill-buffer oldbuf)
X	  (and (gopher-bookmark-directory-p)
X	       (> gopher-bookmark-modified-tick gopher-bookmark-buffer-tick)
X	       (let ((ppos (1- (gopher-directory-n nil))))
X		 (gopher-format-bookmarks)
X		 (forward-line ppos)
X		 (if (> (- (point-max) (point)) 7) (forward-char 7)))))
X      (if (and gopher-support-bookmarks
X               gopher-bookmarks-modified
X               (y-or-n-p
X                "Changes have been made to the Bookmark directory.  Save? "))
X          (gopher-save-bookmarks))
X      (kill-buffer oldbuf))))
X
X
X(defun gopher-directory-save-bookmarks ()
X  "Save the bookmark list."
X  (interactive)
X
X  (if (not (gopher-bookmark-directory-p))
X      (error "This isn't the bookmark directory."))
X
X  (gopher-save-bookmarks))
X
X
X;;;;--------------------------------------------------------------------------
X;;;; bookmarks (Implemented by clamen@cs.cmu.edu)
X;;;;
X
X(defun gopher-read-unix-bookmarks ()
X  "Read bookmarks out of ~/.gopherrc file."
X  (let ((rcfile "~/.gopherrc"))
X    (if (file-exists-p rcfile)
X        (let* ((rcbuf (find-file-noselect rcfile))
X               (bkmks (gopher-parse-bookmark-buffer rcbuf)))
X          (kill-buffer rcbuf)
X          (setq gopher-bookmarks-modified nil)
X	  (setq gopher-bookmark-modified-tick
X		(1+ gopher-bookmark-modified-tick))
X          bkmks)
X      (message "No %s exists." rcfile)
X      nil)))
X
X(defun gopher-parse-bookmark-buffer (buf)
X  "Read buffer containing bookmarks, formatted like ~.gopherrc 
Xin UNIX gopher client."
X  (save-excursion
X    (set-buffer buf)
X    (goto-char (point-min))
X    (if (re-search-forward "^bookmarks:\n" (point-max) t)
X        (let (bkmk bkmks)
X          (while (setq bkmk (gopher-parse-bookmark))
X            (setq bkmks (cons bkmk bkmks)))
X          (apply 'vector (reverse bkmks))))))
X
X(defun gopher-parse-bookmark ()
X  "Read next bookmark.  Return a directory object."
X  (if (re-search-forward
X       "^#$\nType=\\(.+\\)\nName=\\(.+\\)\nPath=\\(.+\\)\nHost=\\(.+\\)\nPort=\\([0-9]+\\)\n" 
X       (point-max) t)
X      (vector (string-to-char
X               (buffer-substring (match-beginning 1) (match-end 1)))
X              (buffer-substring (match-beginning 2) (match-end 2))
X              (buffer-substring (match-beginning 3) (match-end 3))
X              (buffer-substring (match-beginning 4) (match-end 4))
X              (string-to-int
X               (buffer-substring (match-beginning 5) (match-end 5))))))
X
X(defun gopher-format-bookmarks ()
X  "Make the current buffer (which is assumed to be a bookmark buffer)
Xcontain an up-to-date listing of the bookmark list."
X
X  (let ((buffer-read-only nil))
X    (erase-buffer)
X    (setq gopher-dir gopher-bookmarks)
X
X    ;; Format it for your viewing pleasure.
X    (gopher-format-directory gopher-dir (current-buffer))
X    (goto-char (point-min))
X    (if (> (- (point-max) (point)) 7) (forward-char 7))
X    (setq gopher-bookmark-buffer-tick gopher-bookmark-modified-tick)))
X
X(defun gopher-display-bookmarks ()
X  "Retrieve and display the gopher bookmark directory."
X  (interactive)
X
X  (if (> (length gopher-bookmarks) 0)
X      (let ((oldbuf (current-buffer))
X            (dirbuf (gopher-get-dir-buf "*Gopher Bookmarks*")))
X
X        ;; Store our internal representation in gopher-dir.
X        (switch-to-buffer dirbuf)
X        (gopher-make-local-vars
X         'gopher-dir gopher-bookmarks
X	 'gopher-bookmark-directory-p t
X	 'gopher-bookmark-buffer-tick gopher-bookmark-modified-tick
X         'gopher-last oldbuf)
X
X	(gopher-format-bookmarks)
X
X        ;; Turn on directory mode and put the description in the mode line.
X        (gopher-directory-mode)
X        (setq mode-line-buffer-identification (concat "Gopher: *Bookmarks*"))
X        )
X    (error "No bookmarks supported.")))
X
X
X(defun gopher-save-bookmarks ()
X  "Save bookmarks."
X  (cond 
X   ((or (equal gopher-support-bookmarks 'unix)
X        (equal gopher-support-bookmarks t))
X    (gopher-save-unix-bookmarks))
X   ((stringp gopher-support-bookmarks)
X    (gopher-save-lisp-bookmarks gopher-support-bookmarks))
X   (t
X    (message "Illformed gopher-support-bookmarks, assuming none")))
X
X  (setq gopher-bookmarks-modified nil))
X
X
X(defun gopher-save-unix-bookmarks ()
X  "Save bookmarks out to ~/.gopherrc file."
X  (save-excursion
X    (let* ((rcfile "~/.gopherrc")
X           (new-file-p (not (file-exists-p rcfile)))
X           (rcbuf (find-file-noselect rcfile)))
X      (set-buffer rcbuf)
X      (if new-file-p
X          (insert "bookmarks:\n")
X        (goto-char (point-min))
X        (if (re-search-forward "^bookmarks:\n" nil t)
X            (delete-region (point) (point-max))
X          (goto-char (point-max))
X          (insert "bookmarks:\n")))
X
X      ;; Now, insert defined bookmarks into file
X  
X      (let ((obj-count 0))
X        (while (< obj-count (length gopher-bookmarks))
X          (let ((obj (aref gopher-bookmarks obj-count)))
X            (insert "#"
X                    "\nType=" (gopher-object-type obj)
X                    "\nName=" (gopher-object-descr obj)
X                    "\nPath=" (gopher-object-selector obj)
X                    "\nHost=" (gopher-object-host obj)
X                    "\nPort=" (int-to-string (gopher-object-port obj))
X                    "\n")
X            (setq obj-count (1+ obj-count)))))
X
X      (write-file rcfile))))
X
X
X(defun gopher-add-bookmark (arg)
X  "Add current object to menu of bookmarks.
XWith numeric prefix argument N, add Nth object."
X  (interactive "P")
X  (if (gopher-bookmark-directory-p)
X      (error "That item is already a bookmark!")
X    (let ((existing-bookmarks gopher-bookmarks)
X          (new-bookmarks (make-vector (1+ (length gopher-bookmarks)) nil))
X          (obj (copy-sequence (gopher-directory-obj arg)))
X          (l (length gopher-bookmarks)))
X      (gopher-set-object-descr
X       obj
X       (read-from-minibuffer "Node Name: "
X                             (gopher-object-descr obj)))
X      (aset new-bookmarks l obj)
X      (while (> l 0)
X        (progn (setq l (1- l))
X               (aset new-bookmarks l (aref existing-bookmarks l))))
X      (setq gopher-bookmarks new-bookmarks
X            gopher-bookmarks-modified t
X	    gopher-bookmark-modified-tick (1+ gopher-bookmark-modified-tick))
X      )))
X
X
X(defun gopher-delete-bookmark (arg)
X  "Delete current bookmark.
XWith numeric prefix argument N, delete Nth bookmark."
X  (interactive "P")
X  (if (not (gopher-bookmark-directory-p))
X      (error "Can only delete object in Bookmark directory.")
X    (let ((new-bookmarks (make-vector (1- (length gopher-bookmarks)) nil))
X          (pos (1- (gopher-directory-n arg)))
X          (l (length gopher-bookmarks))
X          (i 0))
X      (while (< i pos)
X        (progn (aset new-bookmarks i (aref gopher-bookmarks i))
X               (setq i (1+ i))))
X      (while (< i (1- l))
X        (progn (aset new-bookmarks i (aref gopher-bookmarks (1+ i)))
X               (setq i (1+ i))))
X      (setq gopher-killed-bookmark (aref gopher-bookmarks pos)
X	    gopher-bookmarks new-bookmarks
X            gopher-dir new-bookmarks
X            gopher-bookmarks-modified t
X	    gopher-bookmark-modified-tick (1+ gopher-bookmark-modified-tick))
X      (let ((ppos (1- (gopher-directory-n nil))))
X	(if (< pos ppos)
X	    (setq ppos (1- ppos)))
X	(gopher-format-bookmarks)
X	(goto-char (point-min))
X	(forward-line ppos)
X	(forward-char 7)))
X    (if (= (point) (point-max)) (previous-line 1))
X;    (let ((buffer-read-only nil))
X;      (beginning-of-line 1)
X;      (kill-line 1)
X;      (if (= (point) (point-max)) (previous-line 1)))
X    (if (zerop (length gopher-bookmarks))
X        (gopher-last-node))))
X
X
X(defun gopher-yank-bookmark (arg)
X  "Yank the most recently killed bookmark at the current position.
XWith numeric prefix argument N, yank into position N."
X  (interactive "P")
X  (cond ((not (gopher-bookmark-directory-p))
X	 (error "Can only yank bookmark objects into bookmark directory."))
X	((null gopher-killed-bookmark)
X	 (error "No killed bookmark object"))
X	(t
X	 (let* ((len (length gopher-bookmarks))
X		(new-bookmarks (make-vector (1+ len) nil))
X		(pos (1- (gopher-directory-n arg)))
X		i)
X
X	   (if (or (< pos 0) (> pos (length gopher-bookmarks)))
X	       (error "Out of range."))
X
X	   (setq i (1- pos))
X	   (while (>= i 0)
X	     (aset new-bookmarks i (aref gopher-bookmarks i))
X	     (setq i (1- i)))
X
X	   (aset new-bookmarks pos gopher-killed-bookmark)
X
X	   (setq i pos)
X	   (while (< i len)
X	     (aset new-bookmarks (1+ i) (aref gopher-bookmarks i))
X	     (setq i (1+ i)))
X
X	   (setq gopher-bookmarks new-bookmarks
X		 gopher-bookmarks-modified t
X		 gopher-killed-bookmark nil
X		 gopher-bookmark-modified-tick
X		   (1+ gopher-bookmark-modified-tick))
X
X	   (let ((ppos (1- (gopher-directory-n nil))))
X	     (if (<= pos ppos)
X		 (setq ppos (1+ ppos)))
X	     (gopher-format-bookmarks)
X	     (goto-char (point-min))
X	     (forward-line ppos)
X	     (forward-char 7))
X	   ))))
X	   
X
X(defun gopher-bookmark-directory-p ()
X  "Return T if currently displaying Bookmark directory."
X  gopher-bookmark-directory-p)
X;  (equal gopher-dir gopher-bookmarks))
X
X
X(defun gopher-read-lisp-bookmarks (fn)
X  "currently unsupported"
X  (error "gopher-read-lisp-bookmark is not yet supported.  Sorry."))
X
X(defun gopher-save-lisp-bookmarks (fn)
X  "currently unsupported"
X  (error "gopher-save-lisp-bookmark is not yet supported.  Sorry."))
X
X
X
X;;;;--------------------------------------------------------------------------
X;;;; gopher documents
X;;;;
X
X
X(defun gopher-document-object (obj oldbuf &optional end-regexp)
X  "Retrieve and display a gopher document.
XOptional argument END-REGEXP is used if the data will not be ended by `.'."
X
X  (let ((docbuf (gopher-get-doc-buf (gopher-object-descr obj))))
X
X    ;; Snarf the data into the buffer.
X    (gopher-retrieve-document-cleanly (docbuf
X                                       (gopher-object-selector obj)
X                                       (gopher-object-host     obj)
X                                       (gopher-object-port     obj)
X                                       end-regexp)
X
X        (progn
X          (kill-buffer docbuf)
X          (error "Problems retrieving document."))
X      
X      ;; Turn on document mode and put the description in the mode line.
X      (switch-to-buffer docbuf)
X      (gopher-make-local-vars
X       'gopher-last oldbuf)
X      (goto-char (point-min))
X      (gopher-document-mode)
X      (setq mode-line-buffer-identification (concat "Gopher: "
X                                                    (gopher-object-descr obj)))
X      )))
X
X
X;; keymap for document mode
X(suppress-keymap gopher-document-mode-map)
X
X;Virginia Peck <vapeck@cs>  Mon Aug 10 21:44:35 1992
X;;(define-key gopher-document-mode-map "\C-xk"  'gopher-last-node)
X
X(define-key gopher-document-mode-map "l"  'gopher-last-node)
X(define-key gopher-document-mode-map "q"  'gopher-last-node)
X(define-key gopher-document-mode-map "u"  'gopher-last-node)
X(define-key gopher-document-mode-map " "  'scroll-up)
X(define-key gopher-document-mode-map "\C-?"  'scroll-down)
X(define-key gopher-document-mode-map "\r"  'gopher-scroll-one-line-up)
X
X
X(defun gopher-document-mode nil
X  "Gopher document mode.
X
X\\{gopher-document-mode-map}
X"
X  (use-local-map gopher-document-mode-map)
X  (setq major-mode 'gopher-document-mode)
X  (setq mode-name "gopher doc")
X  (run-hooks 'gopher-document-mode-hook)
X  (setq buffer-read-only t))
X
X
X;; from gosmacs.el
X(defun gopher-scroll-one-line-up (&optional arg)
X  "Scroll the selected window up (forward in the text) one line (or N lines)."
X  (interactive "p")
X  (scroll-up (or arg 1)))
X
X
X;;;;--------------------------------------------------------------------------
X;;;; CSO handling.
X;;;;
X;;;; uses a subset of forms mode to handle data entry.
X;;;;
X
X(defun gopher-cso-object (obj oldbuf)
X  "Display a CSO lookup form."
X
X  ;; The following will create a buffer displaying the form described
X  ;; by the list in the last argument (cf. forms-mode).  When the user
X  ;; accepts the data in the form (by pressing `C-c RET'), the function
X  ;; gopher-do-cso will be called with the data the user supplied.
X  (gopher-form (gopher-object-descr obj)
X               'gopher-do-cso
X               4
X               '("====== phone directory lookup ======"
X                 "\n Press `C-c RET' to lookup, `C-c l' to return to the last gopher object."
X                 "\n (you must fill in at least one of the first three fields)"
X                 "\n"
X                 "Name    : "   1
X                 "\n"
X                 "Phone   : "   2
X                 "\n"
X                 "E-Mail  : "   3
X                 "\n"
X                 "Address : "   4
X                 ))
X
X  ;; Record gopher-last so we gopher-last-node knows where to go.
X  ;; Record gopher-obj so gopher-do-cso knows what server to query.
X  (gopher-make-local-vars
X   'gopher-last oldbuf
X   'gopher-obj  obj))
X
X
X(defconst gopher-cso-fields '("name" "phone" "email" "address")
X  "Field names to use in CSO queries.")
X
X(defun gopher-do-cso (vals)
X  "Make a CSO query.  VALS is the data the user entered in the form,
Xas a list of strings."
X
X  ;; Check that the required data was provided.
X  (if (zerop (+ (length (nth 0 vals))
X                (length (nth 1 vals))
X                (length (nth 2 vals))))
X      (error "Must specify name, phone, or email."))
X
X  (let ((query "query")
X        (fields gopher-cso-fields)
SHAR_EOF
true || echo 'restore of gopher.el failed'
fi
echo 'End of  part 1'
echo 'File gopher.el is continued in part 2'
echo 2 > _shar_seq_.tmp
exit 0
Newsgroups: gnu.emacs.sources