diff options
| -rw-r--r-- | chess-ics.el | 133 | ||||
| -rw-r--r-- | ics.el | 1620 |
2 files changed, 1726 insertions, 27 deletions
diff --git a/chess-ics.el b/chess-ics.el index 69eae89..cffe278 100644 --- a/chess-ics.el +++ b/chess-ics.el @@ -14,39 +14,118 @@ (defvar chess-ics-ensure-ics12 nil) (make-variable-buffer-local 'chess-ics-ensure-ics12) -;; ICS12 format: -;; <12> rnbqkbnr pppppppp -------- -------- -------- -------- PPPPPPPP RNBQKBNR W -1 1 1 1 1 0 65 jwiegley GuestZYNJ 1 5 0 39 39 300 300 1 none (0:00) none 0 0 0 +;; ICS12 format (with artificial line breaks): +;; +;; <12> rnbqkbnr pppppppp -------- -------- \ +;; -------- -------- PPPPPPPP RNBQKBNR W \ +;; -1 1 1 1 1 0 65 jwiegley GuestZYNJ \ +;; 1 5 0 39 39 300 300 1 P/e2-e4 (0:00) e4 0 0 0 + +(defun chess-ics12-parse (string) + "Parse an ICS12 format string, and return a list of its info. +The list is comprised of: the ply the string represents, who is white, +who is black." + (let ((parts (split-string string " ")) + (position (chess-pos-create t)) + white black ply) + + (assert (= (length parts) 31)) + + ;; first, handle the layout of the position + (dotimes (i 8) + (dotimes (j 8) + (let ((piece (aref (car parts) j))) + (unless (= piece ?-) + (chess-pos-set-piece position (chess-rf-to-index i j) + piece)))) + (setq parts (cdr parts))) + + ;; next, the "side to move + (chess-pos-set-side-to-move position (string= (car parts) "W")) + (setq parts (cdr parts)) + + ;; unknown + (setq parts (cdr parts)) + + ;; castling rights? + (if (string= (car parts) "1") + (chess-pos-set-can-castle position ?K t)) + (setq parts (cdr parts)) + (if (string= (car parts) "1") + (chess-pos-set-can-castle position ?Q t)) + (setq parts (cdr parts)) + (if (string= (car parts) "1") + (chess-pos-set-can-castle position ?k t)) + (setq parts (cdr parts)) + (if (string= (car parts) "1") + (chess-pos-set-can-castle position ?q t)) + (setq parts (cdr parts)) + + ;; unknown + (setq parts (cdr parts)) + (setq parts (cdr parts)) + + ;; white player, black player + (setq white (car parts)) + (setq parts (cdr parts)) + (setq black (car parts)) + (setq parts (cdr parts)) + + ;; unknown + (setq parts (cdr parts)) + (setq parts (cdr parts)) + (setq parts (cdr parts)) + + ;; material values for each side + (setq parts (cdr parts)) + (setq parts (cdr parts)) + + ;; starting time each side + (setq parts (cdr parts)) + (setq parts (cdr parts)) + + ;; unknown + (setq parts (cdr parts)) + + ;; move in elaborated notation + (setq parts (cdr parts)) + + ;; time elapsed + (setq parts (cdr parts)) + + ;; move in algebraic notation + (setq ply (if (string= (car parts) "none") + (chess-ply-create position) + (chess-algebraic-to-ply position (car parts)))) + (setq parts (cdr parts)) + + ;; unknown + (setq parts (cdr parts)) + (setq parts (cdr parts)) + (setq parts (cdr parts)) + + (list ply white black))) (defun chess-ics-handle-move () (let ((begin (match-beginning 1)) (end (match-end 1)) - (color (string= (match-string 2) "W")) - (white (match-string 3)) - (move (match-string 4))) - (if (and (not (string= white ics-handle)) - (= 0 (chess-game-index (chess-engine-game nil)))) - (chess-game-run-hooks (chess-engine-game nil) 'pass) - (if (eq color (chess-pos-side-to-move - (chess-engine-position nil))) - (funcall chess-engine-response-handler - 'move move)) - (delete-region begin end)))) + (info (chess-ics12-parse (match-string 2)))) + (if (> (chess-game-index (chess-engine-game nil)) 0) + (if (eq color (chess-pos-side-to-move (chess-engine-position nil))) + (chess-engine-do-move (car ply-info))) + (chess-game-set-plies (chess-engine-game nil) + (list (car ply-info))) + (unless (string= (cadr ply-info) ics-handle) + (chess-game-run-hooks (chess-engine-game nil) 'pass))) + (delete-region begin end))) (defvar chess-ics-regexp-alist - (list - (cons (concat "\\(<12> \\S-+ \\S-+ \\S-+ \\S-+ \\S-+ \\S-+ \\S-+ \\S-+ " - "\\([BW]\\) [-0-9]+ " - "[-0-9]+ [-0-9]+ [-0-9]+ [-0-9]+ [-0-9]+ " - "[-0-9]+ \\(\\S-+\\) \\S-+ " - "[-0-9]+ [-0-9]+ [-0-9]+ " - "[-0-9]+ [-0-9]+ [-0-9]+ [-0-9]+ " - "[-0-9]+ \\S-+ \\S-+ \\(\\S-+\\)\\)") - 'chess-ics-handle-move) - (cons "You accept the match offer from \\([^\\.]+\\)." - (function - (lambda () - (funcall chess-engine-response-handler 'connect - (match-string 1))))))) + (list (cons "\\(<12> \\(.+\\)\\)" 'chess-ics-handle-move) + (cons "You accept the match offer from \\([^\\.]+\\)." + (function + (lambda () + (funcall chess-engine-response-handler 'connect + (match-string 1))))))) (defun chess-ics-handler (event &rest args) (cond @@ -0,0 +1,1620 @@ +;;; ics.el a major mode for communicating with Internet Chess Servers +;; +;; Author: Mark Oakden <mark.oakden@camembert.freeserve.co.uk> +;; +;; (ICC,FICS,BICS,EICS:Sheridan) +;; +;;;;;;;;;;;;;; +;; +;; Copyright (C) 1995-2000 Mark Oakden +;; +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2 of the License, or +;; (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with this program; if not, write to the Free Software +;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +;;---------------------------------------------------------------------- +;; +;; how to use it! +;; +;; (see http://www.camembert.freeserve.co.uk/mark/icsel/ for more +;; detail) +;; +;; you'll need to load or e.g. autoload it in .emacs:--- +;; +;; (autoload 'ics "ics-0.3.7alpha" "ics package" t) +;; +;; an example ics-startup-hook... +;; +;;(add-hook 'ics-startup-hook +;; (function (lambda () +;; ;; handle and a funky regexp for highlighting +;; (setq ics-default-handle "Sheridan") +;; (setq ics-handle-regexp +;; '("[Ss]herr?[iy]?[\\- _]*\\([Dd]an\\)?" 0)) +;; ;; I want it to send my handle +;; (setq ics-send-handle t) +;; ;; I like the buttons even if they are buggy +;; (setq ics-add-buttons t) +;; ;; I like abbrevs +;; (setq ics-use-abbrev-mode t) +;; ;; my emacs windows are light coloured backgrounds +;; (setq ics-background-mode 'light) +;; ;; reset the aliases list to use timestamp/timeseal +;; ;; where possible +;; (setq ics-servers-alist +;; '(("i" "ICC" "192.231.221.16" "5000" +;; "timestamp") ; chess.lm.com +;; ("f" "A-FICS" "164.58.253.10" "5000" +;; "timeseal") ; ics.onenet.net +;; ("e" "E-FICS" "130.225.18.157" "5000" +;; "telnet") ; krypton.daimi.aau.dk +;; ("d" "D-FICS" "193.78.33.69" "5000" +;; "telnet") ; dds.hacktic.nl +;; ("b" "B-FICS" "137.205.192.12" "5000" +;; "telnet") ; holly.csv.warwick.ac.uk +;; ("g" "G-FICS" "131.246.89.3" "5000" +;; "telnet") ; chess.unix-ag.uni-kl.de +;; ("m" "M-FICS" "132.76.80.77" "5000" +;; "telnet") ; wisdom.weizmann.ac.il +;; ))))) +;; +;; I have used IP addrs in my hook since timeseal does not seem +;; to look up hosts by name (at least under linux) +;; +;; Buttons are "buggy" in the sense that +;; 1) my regexps aren't currently exclusive enough... any number +;; followed by a word gets recognised as a "player". this I know how +;; to handle (don't buttonify the output unless it is output from an +;; input command matching a regexp e.g. ics-who-regexp. then wait till +;; all the output is there (i.e. another prompt has been seen) before +;; highlighting and buttonising the region.) +;;; +;; +;; * ChangeLog * +;; +;; ics.el devel +;; +;; changes since 0.3.7: +;; * Multiple small tidyups+regexp tweaks including:- +;; * tweaked regexp to make seekads/sought output work on ICC +;; * tweaked regexp in example auto-command-alist to know about bracketed +;; junk after handles e.g. (U) (*) etc. +;; * added knowledge of "'" at end of handle into emote regexp +;; * re-ordered some button regexps to help larger buttons take precedence +;; * tidied up ics-button-alist regexps using concat +;; * replaced literal ^M with \r, literal ^G with \a +;; * fixed gamelist regexp to know that there can be more than one space in +;; front of the gamenumber +;; * Fixed history list regexp to know about more "ending" flags +;; * Added some button regexps for dealing with stored games +;; * ics.el now sets the interface variable at ICS servers to show ics.el +;; version and the sub-interface in use. Currently only works at FICS +;; since ICC seems to lock the interface variable once it is set. +;; +;; changes since 0.3.6: +;; * better shout default colour for dark backgrounds (turquise). +;; * better handle default colour for dark backgrounds (orange). +;; * better kibitz default colour for dark backgrounds (LightSeaGreen) +;; (can anybody tell I now use dark backgrounds in my emacs???) +;; * fixed type in ics-shout-face (turquiose1 -> turquoise1) +;; * fixed handle matching regexp in ics-button-alist to know that +;; "-" is now allowed in names. +;; * fixed sundry button regexps to know that titles in brackets exist +;; * rehashed the highlighting/buttonising code to remove +;; the long-standing bug which caused virtually everything +;; to turn 'bold in the buffer if it buttonised some unfinished output +;; from the server. +;; * added knowledge of seek ads and sought output to the buttonisation +;; code. Clicking button issues appropriate "play" command +;; * added selected function tracing if ics-version matches *devel* +;; +;; ics.el v0.3.6 alpha +;; +;; changes since 0.3.5: +;; * added a line into ics-watch-for-login-and-send-handle to remove it +;; from comint-output-filter-functions once it has run, to avoid +;; endless loop when an invalid handle is entered. +;; * removed all the ics-wakeup stuff. It got broken between my +;; prerelease version of 0.3.5 and the released version. This has +;; been replaced by a more general ics-auto-command feature, along +;; with a boolean variable ics-idle-p to determine if user is idle. +;; A sample ics-auto-command-alist shows how to use these to simulate +;; the broken 0.3.5 ics-wakeup feature, along with another +;; auto-command (auto greet people on notify list) +;; +;; changes since 0.3.4: +;; * added timeout variable to ics-wakeup function. Wakeup will now +;; only produce alarm beeps if the elapsed time since the last beeps +;; is greater than the user settable timeout period +;; ics-wakeup-alarm-timeout +;; * added some messages to let user know what ics.el is doing +;; e.g. Waiting for login prompt ... etc... +;; * ics-send-password and ics-password added. +;; * added wakeup function. If ics-wakeup is t then any ics output +;; matching ics-wakeup-regexp will cause emacs to (ding t) +;; ics-wakeup-number-of-beeps times, separated by +;; ics-wakeup-beep-interval. +;; * Changed (ding) in ics-output-filter-function to (ding t) to +;; prevent it from terminating keyboard macros. +;; * wrapped long regexp strings over multiple lines. Must be careful not +;; to reformat these sections of code! +;; +;; changes since 0.3.3: +;; * CHANGED variable ics-handle to ics-default-handle. ics-handle is +;; now used internally. +;; * added variable ics-wait-for-login-prompt. If this is t (default) and +;; ics-send-handle is t, ics.el will wait for the login prompt to appear +;; before sending handle. +;; * added button to observe gnotify notification games. +;; * added button on "Type next/more" messages. +;; * modified buttonisation code to re-search each button regexp +;; separately to get around the problem with overflowing the regexp +;; matcher's stack. (minor changes to +;; ics-add-buttons-to-region). This modification may make the +;; buttonising too slow. If so I'll have to group the regexps in +;; batches of, say, 5 regexps and do it the old way for each +;; grouping. Thankfully, though, at present the soplution appears +;; quick enough on both my linux box (i486) and DEC alphas. +;; * uncommented some button entries which were commented because of +;; above problem. +;; * corrected a couple of doc strings. +;; +;; changes since 0.3.2: +;; * changed ics-mouse-push-button to ics-mouse-push-button-or-yank +;; which does a yank if there is no button at the clicked spot. +;; * more buttons (finger buttons in shouts etc.) +;; * fixed bug which would always use the default handle even if you +;; entered a different one. +;; * changed defaults for dark backgrounds. +;; * tweaked regexps +;; * moved the variables that most need user customisation nearer the +;; top of the file so users might see them if browsing the head of the +;; file. +;; +;; changes since 0.3.1: +;; * minor tweakings of regexps to work better across different ics servers +;; * new startup screen. +;; * examine button for history list entries +;; * new hooks ics-pre-connect-hook ics-post-connect-hook and ics-mode-hook +;; +;; +;; +;; +(require 'comint) + +(defconst ics-version "0.4.0" + "ics.el version devel") + +(defvar ics-default-handle "RubberChicken" + "*Default handle to use for ICS login. This is a fine thing to set +up in your ics-startup-hook.") + +(defvar ics-send-handle nil + "*If t send ics handle at the start of login session.") + +(defvar ics-wait-for-login-prompt t + "*Should ics.el wait for the login prompt before sending the user's +handle if ics-send-handle is t? + +non-nil means wait for login prompt.") + +(defvar ics-send-password nil + "*If t send ics-password just after sending handle. Will only send +password if ics-send-handle is t") + +(defvar ics-password "barf" + "*Password to send to ICS for login. If you require different passwords +for different servers, you will have to modify this in ics-pre-connect-hook +conditionally on server-name or similar.") + +(defvar ics-default-port "5000" + "*Default port to use to connect to ics.") + +(defvar ics-default-connect-method "telnet" + "*Default connect method. e.g. telnet, or timeseal.") + +(defvar ics-prompt-regexp "^[A-Za-z ]*[#$%>] *" + "Regexp to match ics prompt.") + +(defvar ics-login-prompt-regexp "^[Ll]ogin: " + "regexp to match login prompt at ICC/ICS") + +(defvar ics-password-prompt-regexp "^[Pp]assword: " + "regexp to match ics password prompt.") + +(defvar ics-inhibit-startup-screen nil) + +(defvar ics-set-interface-variable t + "If true, set the interface variable to ics.el vX.X.X") + +(defvar ics-interface-variable-set nil + "If true, the interface variable at ics is set to the string +\"ics.el vX.X.X (using board-interface)\" +where board-interface is the interface spawned by ics.el e.g. xboard") + +;;; Abbrev. table. +(defvar ics-mode-abbrev-table nil + "ICS-mode abbreviation table.") + +(define-abbrev-table 'ics-mode-abbrev-table ()) + +(defvar ics-use-abbrev-mode nil + "If non-nil, ics-mode will automatically enter abbrev-mode upon startup, +and read the user's abbreviations file, if it exists.") + +(defvar ics-abbrev-file (or (concat (getenv "HOME") "/.abbrev_defs") + (expand-file-name "~/.abbrev_defs")) + "Name to use for saving and loading ics-mode abbreviations table. +This can be set in your .emacs file, in ics-startup-hooks add e.g. + \(setq ics-abbrev-file \"/foo/bar/.ics_abbreviations\"\) +To switch on abbrev-mode this session in ics buffer use \"M-x abbrev-mode\". +If you wish to have abbrev mode set and your abbreviations file read +automatically when you enter ics-mode, add the line + \(setq ics-use-abbrev-mode t\) +to your ics-startup-hook in .emacs file. + +See \"C-h f abbrev-mode\" for more about abbreviation mode. + +*Note: since write-abbrev-file saves ALL mode abbrev tables along with the +global abbrev table, it is probably better not to use a separate file for +ics-mode abbrev. saving. Perhaps best to use a common abbrev file e.g. +.abbrev_defs for ALL your favourite abbrevs, and restrict ics abbrevs to the +ics-mode-table, that way, other buffers in different modes in the same +emacs session as your ics buffer will still get your favourite abbrevs for +those modes set up.") + +(defvar ics-mode-map '()) + +(defvar ics-startup-hook '() + "*Hook for customising ICS mode. +You should set sensible values for ics-default-handle and ics-handle-regexp +in this hook in your .emacs file. +e.g. mine might read:- +\(add-hook 'ics-startup-hook + \(function \(lambda \(\) + \(setq ics-default-handle \"Sheridan\"\) + \(setq ics-handle-regexp \"[Ss]heridan\"\)\)\)\)") + +(defvar ics-mode-hook '() + "*Hook run when ics-mode is switched on.") + +(defvar ics-pre-connect-hook '() + "*Hook run just before ics.el attempts to connect to server.") + +(defvar ics-post-connect-hook '() + "*Hook run just after ics.el connects to server.") +;; +;; alist of server names, and other information. +;; numeric addresses have been used since many versions of +;; timestamp and timeseal require them. +;; +(defvar ics-servers-alist '(("i" "ICC" "192.231.221.16" "5000" + "telnet") ; chess.lm.com + ("f" "A-FICS" "164.58.253.10" "5000" + "telnet") ; ics.onenet.net + ("e" "E-FICS" "130.225.18.157" "5000" + "telnet") ; krypton.daimi.aau.dk + ("d" "D-FICS" "193.78.33.69" "5000" + "telnet") ; dds.hacktic.nl + ("b" "B-FICS" "137.205.192.12" "5000" + "telnet") ; holly.csv.warwick.ac.uk + ("g" "G-FICS" "131.246.89.3" "5000" + "telnet") ; chess.unix-ag.uni-kl.de + ("m" "M-FICS" "132.76.80.77" "5000" + "telnet") ; wisdom.weizmann.ac.il + ) + "* Alist of server information. Each entry in the list has the form + +\(ABBREV SHORTNAME ADDRESS PORT CONNECTMETHOD\) + +With PORT and CONNECTMETHOD optional. + +For a list of current addresses see \"help addresses\" on A-FICS. + +Where ABBREV is an abbreviation which you can enter at the ics server +prompt. SHORTNAME is the name by which the server will be referred in +the buffername in the Emacs modeline. ADDRESS is the address \(or the +IP number\) of the server. \(If you are going to use timestamp \(help +\"timestamp\" on ICC\) or timeseal \(help \"timeseal\" on FICS\) then +for most versions, it is necessary to use the IP number.\) This will +be assigned to the (buffer-local) variable ics-address before calling +the interface program specified in ics-interface with arguments +specified in ics-interface-args. + +PORT and CONNECTMETHOD are both optional. If they are set, they will +be assigned to the variables ics-port and ics-connect-method, +respectively. These variables may then be used in the \(user +specified\) ics-interface-args variable to specify hairy connect +methods. See the documentation on ics-interface-args for an example +of how to use this to use these variables to specify the use of +timestamp/timeseal with xboard when connecting to ICC/FICS. + +Typical entries might look like + +\(\"icc\" \"ICC\" \"chess.lm.com\" \"5000\" \"timestamp\"\) + +or + +\(\"bics\" \"BICS\" \"holly.csv.warwick.ac.uk\" \"5000\" \"telnet\"\) + +Where the first specifies \"icc\" as an abbreviation for the ICC +server at chess.lm.com. The second defines bics as an abbrev for BICS +at holly.csv.warwick.ac.uk.") + +(defvar ics-interface "xboard" + "* Interface to connect to ics servers with. Typically this will +have the value \"xboard\" or \"xics\", although if you just want to +use ics-mode's highlighting abilities with an ASCII connection, you +may also use telnet for your \"interface\". You should set up +ics-interface-args to an appropriate value for your interface.") + +(defvar ics-interface-args '(list "-ics" "-icshost" ics-address "-icsport" + ics-port "-telnet" "-telnetProgram" + ics-connect-method "-size" "medium") + "* List of arguments to be supplied to the interface program to +connect to an ics server. + +The variables ics-address, ics-port, and ics-connect-method may be +used in this list. These variables will be initialised from +ics-servers-alist. If there is no match in ics-servers-alist for the +address input by the user, it will be assumed to be an IP address or +number, and assigned to ics-address. In this case, the user will be +prompted for the port number. + +Typical values for ics-interface-args :-- + +e.g. If you are an xboard user, wishing to use timestamp/timeseal with +ICC/FICS, you should have + +\(setq ics-interface \"xboard\"\) +\(setq ics-interface-args '\(list \"-ics\" \"-icshost\" ics-address + \"-icsport\" ics-port \"-telnet\" + \"-telnetProgram\" ics-connect-method + \"-size\" \"medium\"\) + +in your ics-startup-hook, and make sure that the CONNECT-METHOD +entries in ics-servers-alist correspond to the names of your +timestamp/timeseal programs for each server (or telnet where +timestamp/timeseal are unavailable). While an xics (or plain telnet) +user might use + +\(setq ics-interface \"xics\"\) +\(setq ics-interface-args '\(list ics-address ics-port\)\) ") +;; +;; auto command stuff +;; +(defvar ics-auto-command-alist + '(("^\\([a-zA-Z0-9-]+\\)\\(([^)]*)\\)* tells you: " 1 ics-idle-p + ics-wakeup) + ("^Notification: \\([^ ]+\\) has arrived" 1 t + "i > (ics-auto-greet '%s)")) + "alist of auto-command info + +'\(\(REGEXP LEVEL CONDITION COMMAND\) + \(REGEXP LEVEL CONDITION COMMAND\) +...\) + +the REGEXPs are applied in turn to ics output until one matches. If a +match is found, and CONDITION evaluates to t, COMMAND is exectued. +COMMAND may be either a lisp function (or variable whose value is a +function name) which should take one string +argument, which is executed with match-level LEVEL of the regexp +match as its argument, or a string, which is passed to format with +the match level LEVEL'th substring of the regexp match, and sent as +an ics command.") + +(defvar ics-idle-time 300 + "number of seconds one must be idle before ics-idle-p gets set to + t") + +(defvar ics-do-auto-commands nil + "*If this is t, automatic commands in ics-auto-command-alist will + be executed.") +;; +;; User variables for ics-wakeup auto-command feature +;; +(defvar ics-wakeup-alarm-timeout 120 + "*The time, in seconds, that must have elapsed since last alarm bell +before another bell will be sounded.") + +(defvar ics-wakeup-command (concat "tell %s sorry, I am idle " + "(or playing) at present. " + "I have been notified of your " + "tell with an alarm signal. " + "[This tell was generated " + "automatically]") + "command to be sent to ICS when ics-wakeup is called , after +passing through + \(format ics-wakeup-command trigger-name\)") + +(defvar ics-wakeup-number-of-beeps 3 + "*Default number of times to beep to wake user up") + +(defvar ics-wakeup-beep-interval 1.0 + "*Interval in seconds between beeps in wakeup alarm") +;; +;; internal variables for auto-command-related stuff... +;; +(defvar ics-idle-p nil + "t if last ics command issued was more than ics-idle-time seconds + ago, nil otherwise") +(defvar ics-last-command-time 0 + "time in seconds of last command issued") +(defvar ics-wakeup-last-alarm-time 0 + "Time when last alarm was sounded") +;; +;; end of auto-command variables... +;;;;;;;;;;;;;;;;; +;; highlighting etc. +;; +(defvar ics-highlight t + "* If t then highlight tells, says etc.") + +(defvar ics-background-mode 'light + "* Type of background that you use with Emacs... either 'light or 'dark") + +(defvar ics-highlight-items + '("continuation-line" "game-request" "game-notification" "pinform" + "notification" "kibitz" "s-shout" "channel" "shout" "emote" "tell" + "stored-here" "stored-not-here" "adjudicate-notification" + "sought-lightning" "sought-suicide" "sought-blitz" "sought-standard" + "handle") + "*List of types of item to highlight. The ics-highlight-alist +variable is initialised according to the value of this variable, +which has the form of a list of strings. + +e.g. + +'\(\"continuation-line\" \"game-request\" \"game-notification\" \"pinform\" + \"notification\" \"kibitz\" \"s-shout\" \"channel\" \"shout\" + \"emote\" \"tell\" \"handle\"\) + +and ics-highlight-alist is set up from the variables ics-X-regexp and +ics-X-face where X ranges through each item on the list. Highlighting +is done in the order in which the items appear in ics-highlight-items. + +NB No checking is done to ensure that all the variables ics-X-regexp +and ics-X-face exist and have the proper format. If any of them do +not exist, or are incorrectly formed, ics.el will bomb.") + +(defvar ics-last-highlight-end nil + "internal, buffer-local, used to track where we last highlighted") + +(defvar ics-last-add-buttons-end nil + "internal, buffer-local, used to track where we added buttons") + + +;;;;;;;; +;;regexps and face info... +;; +;; ics-X-face is a list of 3 items each of which is either a valid +;; face name or a list of the form '(fg bg stipple bold italic +;; underline) ics-X-regexp is a list containing the regexp to match to +;; and an integer specifying the match level to highlight: 0 to +;; highlight the whole regexp, 1 to highlight the first parenthesis, +;; etc. + +(defvar ics-handle-regexp '("RubberChicken" 0)) +(defvar ics-handle-face '(("blue" nil nil t t nil) ; light + ("orange" nil nil t t nil) ; dark + bold)) ; mono + +(defvar ics-tell-regexp '("^[^ ]+ \\(tells you\\|says\\):.*$" 0) + "Regexp to match ICS tells and says.") +(defvar ics-tell-face '(("red" nil nil nil nil nil) + ("LavenderBlush4" nil nil nil nil nil) + bold)) + +(defvar ics-emote-regexp '("^--> [^ ]+[ '].*$" 0) + "Regexp to match ICS emotes. \(\"i\" commands\).") +(defvar ics-emote-face '(("blue" nil nil nil t nil) + ("turquoise1" nil nil nil t nil) + italic)) + +(defvar ics-stored-here-regexp + '(" *\\([0-9]+: [BW] \\([a-zA-Z]+[-a-zA-Z0-9]*\\) +Y \\[.+\\]\\) .*$" 0) + "Regexp to match stored list entries for players who are here") +(defvar ics-stored-here-face '(("forest green" nil nil nil nil nil) + ("green" nil nil nil nil nil) + italic)) + +(defvar ics-stored-not-here-regexp + '(" *\\([0-9]+: [BW] \\([a-zA-Z]+[-a-zA-Z0-9]*\\) +N \\[.+\\]\\) .*$" 0) + "Regexp to match stored list entries for players who are here") +(defvar ics-stored-not-here-face '(("firebrick" nil nil nil nil nil) + ("turquoise1" nil nil nil nil nil) + italic)) + +(defvar ics-sought-suicide-regexp + (list + (concat + "^ *\\(\\([0-9]+\\) +\\([0-9]+\\|----\\|++++\\) +" + "\\([a-zA-Z]+[-a-zA-Z0-9]*\\(([^)]+)\\)*\\)\\) +" + "\\([a-z0-9-()]*\\)? +[0-9]+ +[0-9]+ +\\(un\\)?rated" + " +suicide.*$") + 0) + "Regexp to match suicide games in sought output") +(defvar ics-sought-suicide-face '(("firebrick" nil nil nil nil nil) + ("LightSeaGreen" nil nil nil nil nil) + underline)) + +(defvar ics-sought-lightning-regexp + (list + (concat + "^ *\\(\\([0-9]+\\) +\\([0-9]+\\|----\\|++++\\) +" + "\\([a-zA-Z]+[-a-zA-Z0-9]*\\(([^)]+)\\)*\\)\\) +" + "\\([a-z0-9-()]*\\)? +[0-9]+ +[0-9]+ +\\(un\\)?rated" + " +lightning.*$") + 0) + "Regexp to match lightning games in sought output") +(defvar ics-sought-lightning-face '(("blue" nil nil nil nil nil) + ("orange" nil nil nil nil nil) + italic)) + +(defvar ics-sought-blitz-regexp + (list + (concat + "^ *\\(\\([0-9]+\\) +\\([0-9]+\\|----\\|++++\\) +" + "\\([a-zA-Z]+[-a-zA-Z0-9]*\\(([^)]+)\\)*\\)\\) +" + "\\([a-z0-9-()]*\\)? +[0-9]+ +[0-9]+ +\\(un\\)?rated" + " +blitz.*$") + 0) + "Regexp to match blitz games in sought output") +(defvar ics-sought-blitz-face '(("forest green" nil nil nil nil nil) + ("yellow" nil nil nil nil nil) + bold)) + +(defvar ics-sought-standard-regexp + (list + (concat + "^ *\\(\\([0-9]+\\) +\\([0-9]+\\|----\\|++++\\) +" + "\\([a-zA-Z]+[-a-zA-Z0-9]*\\(([^)]+)\\)*\\)\\) +" + "\\([a-z0-9-()]*\\)? +[0-9]+ +[0-9]+ +\\(un\\)?rated" + " +standard.*$") + 0) + "Regexp to match blitz games in sought output") +(defvar ics-sought-standard-face '(("chocolate" nil nil nil nil nil) + ("green" nil nil nil nil nil) + bold-italic)) + +(defvar ics-s-shout-regexp '("^[^ ]+ \\([cs]-?shouts\\|queries\\):.*$" 0) + "Regexp to match ICS s-shouts.") +(defvar ics-s-shout-face '(("red" nil nil t t nil) + ("turquoise1" nil nil t t nil) + bold-italic)) + +(defvar ics-game-request-regexp + '("^Challenge: .*$\\|^Ignoring\\(\\[\\|[ ](\\).*$\\|Issuing: .*$" 0) + "Regexp to match game requests \(it is perhaps useful to have this +also match game requests that are ignored due to e.g. not being open, +or formula clashes etc.\)") +(defvar ics-game-request-face '(("black" "tan" nil t nil nil) + ("white" "navy" nil t nil nil) + modeline)) + +(defvar ics-game-notification-regexp + '("^Game notification:.*$\\|^{Game .*}.*$" 0) + "Regexp to match ICS game notification messages.") +(defvar ics-game-notification-face '(("forest green" nil nil nil nil nil) + ("forest green" nil nil nil nil nil) + italic)) + +(defvar ics-kibitz-regexp '("^[^ ]+ \\(kibitzes\\|whispers\\):.*$" 0) + "Regexp to match ICS kibitzes/whispers.") +(defvar ics-kibitz-face '(("firebrick" nil nil nil t nil) + ("LightSeaGreen" nil nil nil t nil) + italic)) + +(defvar ics-channel-regexp '("^[^ ]+([0-9]+):.*$" 0) + "Regexp to match ics channel tells.") +(defvar ics-channel-face '(("firebrick" nil nil nil t nil) + ("LightSeaGreen" nil nil nil t nil) + italic)) + +(defvar ics-shout-regexp '("^[^ ]+ shouts:.*$" 0)) +(defvar ics-shout-face '(("blue" nil nil nil t nil) + ("turquoise1" nil nil nil t nil) + italic)) + +(defvar ics-pinform-regexp + '("^\\[[a-zA-Z0-9]+ +\\((.+)\\)? *has \\(dis\\)?connected.\\]$" 0)) +(defvar ics-pinform-face '(("royal blue" nil nil nil nil nil) + ("orange" nil nil nil nil nil) + default)) + +(defvar ics-notification-regexp + '("^Notification: [^ ]+ has \\(arrived\\|departed\\).$\\|\ +^Present company includes.*$\\|^Your arrival was noted by: .*$" 0)) +(defvar ics-notification-face '(("ForestGreen" nil nil t nil nil) + ("ForestGreen" nil nil t nil nil) + bold)) + +(defvar ics-adjudicate-notification-regexp + '("^You have [0-9]+ adjourned games." 0 ) + "Regexp to match initial storegames notifaction") +(defvar ics-adjudicate-notification-face '(("ForestGreen" nil nil nil nil nil) + ("ForestGreen" nil nil nil nil nil) + bold)) + +(defvar ics-continuation-line-regexp '("^\\\\ .*$" 0)) +(defvar ics-continuation-line-face '(("grey50" nil nil t nil nil) + ("grey50" nil nil t nil nil) + default)) +;;;; + +(defvar ics-highlight-alist nil + "Alist of regexps and highlighting info. Don't set this variable up +yourself unless you know what you are doing. It is initialised +automatically from ics-highlight-items, ics-X-regexp and ics-X-face +variables. If you set this variable yourself, this initialisation +will not take place. + +To modify the highlighting, change ics-highlight-items and the +ics-X-regexp and ics-X-face variables.") + +;;;; +;; button stuff (many concepts and much code is lifted from gnus 5.0 +;; by Lars Magne Ingebrigtson) +;; +;; button functions have been slightly rewritten, since we have many +;; buttons. + +(defvar ics-add-buttons nil + "* If t then add buttons to ics buffer.") + +(defvar ics-button-face 'bold) +(defvar ics-mouse-face 'highlight) + +(defvar ics-players-command "players" + "*Command to issue to get a player list") + +(defvar ics-who-command "who" + "*Command to issue to get a who listing") + +(defvar ics-observe-command "observe %s" + "*Command to observe a game") + +(defvar ics-play-command "play %s" + "*Command to issue to accept a seek request") + +(defvar ics-finger-command "finger %s" + "*Command to issue when a button in the who-list is pressed. +e.g. \"finger %s\" or \"match %s\"") + +(defvar ics-match-command "match %s" + "*Command to issue when a button in a player's finger stats is pressed. +e.g. \"match %s\"") + +(defvar ics-button-url + (cond ((boundp 'browse-url-browser-function) browse-url-browser-function) + ((fboundp 'w3-fetch) 'w3-fetch) + ((eq window-system 'x) 'ics-netscape-open-url)) + "function to fetch URL.") + +(defvar ics-button-gamelist 'ics-observe + "function to call on gamelist buttons") + +(defvar ics-button-pinform 'ics-finger + "function to call on pinform buttons") + +(defvar ics-button-ginform 'ics-observe + "function to call on ginform buttons") + +(defvar ics-button-historylist 'ics-examine-from-historylist + "funstion to call on historylist buttons") + +(defvar ics-button-seeklist 'ics-play + "function to call on seeklist buttons") + +(defvar ics-button-storedgame 'ics-match + "function to be called on stored game buttons") + +(defvar ics-button-adjourned 'ics-stored + "function to be called on notification of stored games") + +(defvar ics-button-wholist 'ics-finger + "Function to call on who list buttons. e.g. 'ics-finger or 'ics-match.") + +(defvar ics-button-fingerstat 'ics-match + "Function to call on name button in finger stats. e.g. 'ics-match.") + +(defvar ics-button-match-offer 'ics-send-command + "*Function to call on accept/decline buttons in match offers.") + +(defvar ics-button-alist + (list + ;; who button... highlight just the player name... + (list + (concat + "\\(^\\|[ \t]+\\)\\([0-9]+\\|----\\|++++\\)[ #:.^]" + "\\([a-zA-Z]+[-a-zA-Z0-9]*\\)") + 3 t 'ics-button-wholist 3) + ;; pinform button + (list + "^\\[\\([a-zA-Z0-9]+\\) +\\((.+)\\)? *has \\(dis\\)?connected.\\]$" 1 t + ics-button-pinform 1) + ;; ginform button + (list + (concat + "^{Game \\([0-9]+\\) (\\([a-zA-Z0-9]+ vs. [a-zA-Z0-9]+\\))" + " \\(Creating\\|Continuing\\).*$") + 2 t ics-button-ginform 1) + ;; ICC gnotify button (FICS gnotify has same format as ginform so + ;; is already handled by above regexp + (list + (concat + "^Game notification: \\(\\([^ \t\n]+\\) (.+) vs\\. " + "\\([^ \t\n]+\\) (.+)\\).*$") + 1 t 'ics-button-ginform 2) + ;; shouts should have a finger button... + (list + "^\\([^ \t\n(]+\\)\\(([^)]+)\\)* \\(c-\\|s-\\)?shouts: .*$" + 1 t 'ics-finger 1) + ;; emotes too... + (list + "^--> \\([^ \t\n(\'%:\"#.]+\\)\\(([^)]+)\\)*.*$" 1 t 'ics-finger 1) + ;; accept and decline buttons in match offers (are these too loose??) + ;; ICC version... + (list + "\"\\(\\(accept \\|decline \\)[a-zA-Z][a-zA-Z0-9]+\\)\"" 1 t + 'ics-button-match-offer 1) + ;; FICS version (works with fics 1.2.23) + (list + "\\(^You can \"\\| or \"\\)\\(accept\\|decline\\)\"" 2 t + 'ics-button-match-offer 2) + ;; gamelist button + (list + "^ *\\([0-9]+\\) \\(.* \\[.*\\]\\) .* \\(W\\|B\\): +[0-9]+$" + 2 t 'ics-button-gamelist 1) + ;; history list entries. + (list + (concat + "^ ?\\([0-9]+\\): [-+=a] \\(.* \\[.*\\]\\)" + " \\(-+\\|[A-E][0-9][0-9]\\) +" + "\\(Res\\|Fla\\|Mat\\|Rep\\|Agr\\|Sta\\|NM\\|TM\\|Adj" + "\\|Dis\\|WQ\\|BQ\\|WNM\\|WLM\\|50\\) +.*$") + 2 t 'ics-button-historylist 1) + ;; seeklist button + (list + (concat + "^ *\\(\\([0-9]+\\) +\\([0-9]+\\|----\\|++++\\) +" + "\\([a-zA-Z]+[-a-zA-Z0-9]*\\(([^)]+)\\)*\\)\\) +" + "\\([a-z0-9-()]*\\)? +[0-9]+ +[0-9]+ +\\(un\\)?rated.*$" ) + 1 t 'ics-button-seeklist 2) + ;; seek ad button + (list + "\"play \\([0-9]+\\)\" to respond" 0 t 'ics-button-seeklist 1) + ;; stored games list button + (list + (concat + " *\\([0-9]+: [BW] \\([a-zA-Z]+[-a-zA-Z0-9]*\\) +[Y] \\[.+\\]\\)" + " +[0-9]+-[0-9+]") + 1 t 'ics-button-storedgame 2) + ;; stored game number notification + (list + "You have [0-9]+ adjourned games." + 0 t 'ics-button-adjourned 0) + ;; stored opponent arrival notification + (list + (concat + "^Notification: \\([a-zA-Z]+[-a-zA-Z0-9]*\\)" + ", with whom you have an adjourned game," + " has \\(arrived\\|departed\\).") + 1 t 'ics-button-storedgame 1) + ;; name in finger stats + (list + "Statistics for \\([^ \t(]+\\)" 1 t 'ics-button-fingerstat 1) + ;; more/next page of output button + (list + "^Type \\[\\(next\\)\\] to see next page\\." + 1 t 'ics-send-command 1) + (list + "^\\[Type \"\\(more\\)\" to see more\\.\\]" + 1 t 'ics-send-command 1) + ;; This is how URLs _should_ be embedded in text... + (list + "<URL:\\([^\n\r>]*\\)>" 0 t 'ics-button-url 1) + ;; farm mail addresses off to emacs mail command... + (list + "[^ \t\n@<>(\":]+@[^ \t\n@><)\"]+" 0 t 'ics-button-mail 0) + ;; Next regexp stolen from highlight-headers.el. + ;; Modified by Vladimir Alexiev. + ;; modified by MNO (removed mailto button ... "\\|mailto" after wais) + ;; since it seemed more aesthetically pleasing to use Emacs to + ;; do the mailing, by matching the email address with above regexp. + (list + (concat + "\\b\\(s?https?\\|ftp\\|file\\|gopher\\|news\\|telnet\\|wais\\)" + ":\\(//[-a-zA-Z0-9_.]+:[0-9]*\\)?[-a-zA-Z0-9_=?#$@~`%&*+|\\/.,]*" + "[-a-zA-Z0-9_=#$@~`%&*+|\\/]") + 0 t 'ics-button-url 0)) + "Alist of regexps matching buttons in the ICS buffer. + +Each entry has the form (REGEXP BUTTON FORM CALLBACK PAR...), where +REGEXP: is the string matching text around the button, +BUTTON: is the number of the regexp grouping actually matching the button, +FORM: is a lisp expression which must eval to true for the button to +be added, +CALLBACK: is the function to call when the user push this button, and each +PAR: is a number of a regexp grouping whose text will be passed to CALLBACK. + +CALLBACK can also be a variable, in that case the value of that +variable it the real callback function.") +;; +;; end of button variables +;; +;; +;; variable to keep track of ics handle in use. NOT A USER VARIABLE!! +(defvar ics-handle "BarfieIsCool" + "this variable is NOT the place to set your default handle. this is used +internally in the watch for login prompt.") +;; +;; + +;;; Function definitions + +(defun ics () + "Connect to internet chess servers." + (interactive) + (cond ((not ics-mode-map) + (setq ics-mode-map (copy-keymap comint-mode-map)) + ;;(define-key ics-mode-map "\C-c\C-z" 'ics-insert-zippyism) + (define-key ics-mode-map "\C-c\C-w" 'ics-who) + (define-key ics-mode-map "\C-c\C-p" 'ics-players) + (define-key ics-mode-map "\C-c\C-f" 'ics-finger) + (define-key ics-mode-map "\C-c\C-m" 'ics-match) + (define-key ics-mode-map "\C-c\C-c" 'ics-confirm-quit))) + (run-hooks 'ics-startup-hook) + (if ics-add-buttons + (define-key ics-mode-map [mouse-2] 'ics-mouse-push-button-or-yank)) + (if (string-match ".*devel.*" ics-version) + ;; development version so trace the "hot" functions + (let ((tracebuff (get-buffer-create "*ICS functrace*"))) + (trace-function-background 'ics-add-buttons-to-last-output tracebuff) + (trace-function-background 'ics-add-buttons-to-region tracebuff))) + (ics-connect)) + +(defun ics-connect () + "Function to query user for ics server to use and either connect to +that server in a new buffer, or in an existing buffer for that server, +or switch to any existing buffer running the ics conenction to that +server." + (or ics-inhibit-startup-screen + (progn + (switch-to-buffer (get-buffer-create "*ICS Connect*")) + (erase-buffer) + (setq scroll-step 1) + ;; many thanks to Alefith for permission to use his ASCII + ;; knight and rook pictures here! + (insert (format " + ^^__ _ _ _ + / - \\_ Emacs-ICS | || || | + <| __< |_______| + <| \\ A text window manager for ICS sessions. \\__ ___ / + <| \\ |___|_| + <|______\\ (C) 1995-1999 Mark Oakden |_|___| + _|____|_ |___|_| + (________) Issued with NO WARRANTY under the terms of the GNU (_______) + /________\\ public licence [C-h C-c to see a copy of the GPL] /_______\\ + +Email any problems, bugs, enhancement requests etc. to the author at +<mark.oakden@camembert.freeserve.co.uk> + +ics.el homepage at http://www.camembert.freeserve.co.uk/mark/icsel/ + +Emacs-ICS Version: %s +Interface Program: %s + Default Handle: %s + +Aliases defined: + + Alias | Shortname | Address +-------+-----------+--------------- +" ics-version ics-interface ics-default-handle)) + (let* ((alist ics-servers-alist)) + (while (car alist) + (insert (format "%6s |%10s | %s\n" (car (car alist)) + (car (cdr (car alist))) + (car (cdr (cdr (car alist)))))) + (setq alist (cdr alist)))) + )) + ;; + ;; now prompt for address, handle and start the process + ;; + (let* ((address-or-alias (read-from-minibuffer + "ICS Server address or alias: ")) + (server-info-list (cdr (assoc address-or-alias + ics-servers-alist))) + (ics-address (or (car (cdr server-info-list)) + address-or-alias)) + (ics-connect-method (or (car (nthcdr 3 server-info-list)) + ics-default-connect-method)) + (server-name (or (car server-info-list) + address-or-alias)) + (ics-port (or (car (nthcdr 2 server-info-list)) + (read-from-minibuffer "ICS port: " ics-default-port))) + (interface ics-interface) + (handle (read-from-minibuffer "ICS Handle: " + ics-default-handle)) + (proc (concat server-name ":" handle)) + (buffer (concat "*" proc "*"))) + (setq ics-handle handle) ; save value of + ; handle in a global variable + (or ics-inhibit-startup-screen + (kill-buffer "*ICS Connect*")) + (if (not (comint-check-proc buffer)) + (progn + (run-hooks 'ics-pre-connect-hook) + (set-buffer + (apply 'make-comint proc interface nil + (eval ics-interface-args))) + (run-hooks 'ics-post-connect-hook) + (ics-mode))) + (switch-to-buffer buffer) + (set (make-variable-buffer-local 'ics-last-command-time) + (ics-current-time)) + (set (make-variable-buffer-local 'ics-idle-p) nil) + (set (make-variable-buffer-local 'ics-interface-variable-set) nil) + (set (make-variable-buffer-local 'ics-wakeup-last-alarm-time) + (ics-current-time)) + (set (make-variable-buffer-local 'ics-last-highlight-end) nil) + (set (make-variable-buffer-local 'ics-last-add-buttons-end) nil) + (if (and ics-send-handle (not ics-wait-for-login-prompt)) + (progn + (message "Sending ICS handle...") + (comint-simple-send (get-buffer-process (current-buffer)) + ics-handle) + (message "Sending ICS handle... done.") + (if ics-send-password + (message "Sending password...") + (comint-simple-send (get-buffer-process (current-buffer)) + ics-password) + (message "Sending password...done")))))) + +(defun ics-mode () + "Major mode for communication with ICS. + +Uses comint-mode and so shares some functionality and key bindings with +that. Useful commands include M-p and M-n to recall previous and next +commands in the history ring. +Customisation: Entry to this mode runs hooks on 'ics-startup-hook' and +'comint-mode-hook' (in that order)." + (interactive) + (comint-mode) + (auto-save-mode -1) ; turn off auto save + (setq major-mode 'ics-mode) + (setq mode-name "ICS") + (setq local-abbrev-table ics-mode-abbrev-table) + (use-local-map ics-mode-map) + ;; + ;; some ics specific comint setup + (setq comint-prompt-regexp ics-prompt-regexp) + (setq comint-password-prompt-regexp ics-password-prompt-regexp) + ;; + ;; some nice settings (should these be default here or left for the + ;; user to set for himself in a hook?) + ;; + ;(setq comint-scroll-to-bottom-on-input t) + ;(setq comint-scroll-show-maximum-output t) + ;; + ;; if ics-highlight is non-nil, add highlight function into + ;; comint-output-filter-functions + ;; + (or (null ics-highlight) + (progn + (add-hook 'comint-output-filter-functions + 'ics-highlight-last-output nil t) + (ics-highlight-buffer))) + (or (null ics-add-buttons) + (progn + (add-hook 'comint-output-filter-functions + 'ics-add-buttons-to-last-output nil t) + (ics-add-buttons-to-buffer))) + ;; + ;; idle time resetter + ;; + (add-hook 'comint-input-filter-functions + 'ics-reset-last-command-time nil t) + ;; + ;; abbrev mode on? + ;; + (setq abbrev-file-name ics-abbrev-file) + (or (null ics-use-abbrev-mode) + (and (not (abbrev-mode 1)) + (if (file-readable-p ics-abbrev-file) + (read-abbrev-file) + (message "No abbrev file found.")))) + ;; should we wait for login prompt before sending handle? + (if (and ics-send-handle ics-wait-for-login-prompt) + (progn + (message "Waiting for login prompt from ICS...") + (add-hook 'comint-output-filter-functions + 'ics-watch-for-login-and-send-handle nil t))) + ;; + ;; add in the general purpose ics-output-filter + ;; + (add-hook 'comint-output-filter-functions 'ics-output-filter nil t) + (if (not ics-send-password) + (add-hook 'comint-output-filter-functions + 'comint-watch-for-password-prompt nil t)) + (run-hooks 'ics-mode-hook)) + +(defun ics-output-filter (&optional string) + "Eventually, this function might parse ICS output. + +At present, this merely strips literal ^G chars from the buffer and calls +\(ding t\) for each one stripped. Included in comint-output-filter-functions +to enable bell rings when ics outputs ^G. Also now strips ^M chars, useful +if you use telnet to connect. + +Updates variable ics-idle-p if player is idle. + +Also performs ics-auto-command duties, if ics-do-auto-commands is t" + (interactive) + (let ((pmark (process-mark (get-buffer-process (current-buffer))))) + (save-excursion + (goto-char + (if (interactive-p) comint-last-input-end comint-last-output-start)) + (while (re-search-forward "\a" pmark t) + (replace-match "" t t) + (ding t))) + (save-excursion + (goto-char + (if (interactive-p) comint-last-input-end comint-last-output-start)) + (while (re-search-forward "\r" pmark t) + (replace-match "" t t))) + ;; update the interface variable at ICS if necessary + (if (and ics-set-interface-variable + (not ics-interface-variable-set) + (save-excursion + (beginning-of-line) + (looking-at ics-prompt-regexp))) + (ics-update-interface-variable)) + ;; reset idle flag + (setq ics-idle-p (< ics-idle-time (ics-time-since + ics-last-command-time))) + ;; and process the user defined automatic commands + (if ics-do-auto-commands + (let ((alist ics-auto-command-alist)) + (while alist + (let* ((entry (car alist)) + (rest (cdr alist)) + (regexp (car entry)) + (level (nth 1 entry)) + (condition (nth 2 entry)) + (command (nth 3 entry))) + (setq alist rest) + ;; + ;; is condition true and did last output match regexp? + ;; + (if (and (eval condition) + (string-match regexp string)) + (progn + (setq alist nil) + (let ((data (match-string level string))) + (cond ((stringp command) + (ics-send-command (format command data))) + ((fboundp command) + (funcall command data)) + ((and (boundp command) + (fboundp (symbol-value command))) + (funcall (symbol-value command) data)) + (t + (message + "ICS Autocommand must be a string or\ + function")))))))))))) + +;; +;; utility function for ics-idle-p determination +;; +(defun ics-reset-last-command-time (&optional string) + "reset last command time..." + (setq ics-last-command-time (ics-current-time))) + +(defun ics-update-interface-variable () + ;; set interface string if the feature is switched on and + ;; we have not already set it + (setq ics-interface-variable-set t) + (ics-send-command + (format "set interface ics.el %s (using %s)" + ics-version ics-interface))) +;; +;; wakeup functions +;; +(defun ics-wakeup (name) + "Function to wakeup user and send a command (e.g. a tell to whoever +triggered the wakeup alarm) to ics. Useful for ics-auto-command-alist +entries along with the condition ics-idle-p to simulate the (broken) +0.3.5 ics-wakeup \"feature\"" + ;; send the command + (ics-send-command (format ics-wakeup-command name)) + ;; if we haven't had alarm beeps recently, send them again + (if (> (ics-time-since ics-wakeup-last-alarm-time) + ics-wakeup-alarm-timeout) + (ics-wakeup-sound-alarm))) + +(defun ics-wakeup-sound-alarm () + (let ((repeats ics-wakeup-number-of-beeps) + (interval ics-wakeup-beep-interval) + (iloop 0)) + (while (< iloop repeats) + (ding t) + (sleep-for interval) + (setq iloop (1+ iloop))) + (setq ics-wakeup-last-alarm-time (ics-current-time)))) +;;;;;;;; +;; +;; buttons & highlighting +;; +;; Highlighting functions +;; +(defun ics-setup-highlight-alist () + "make the alist from its components" + (setq itemlist ics-highlight-items + ics-highlight-alist nil) + (while itemlist + (setq item (car itemlist) + itemlist (cdr itemlist) + re (symbol-value (intern-soft (concat "ics-" item "-regexp"))) + facelist (symbol-value (intern-soft (concat "ics-" item "-face"))) + face (cond ((not (x-display-color-p)) + (nth 2 facelist)) + ((eq ics-background-mode 'dark) + (nth 1 facelist)) + (t ;if all else fails assume 'light + (car facelist))) + ics-highlight-alist (cons (append + re (list (if (facep face) + face + (apply 'ics-face-lookup + face)))) + ics-highlight-alist)))) + +(defun ics-highlight-buffer () + "Highlight the whole ICS buffer. Useful for highlighting ics session +log files for easier reading.." + (interactive) + (save-excursion + (ics-highlight-region (point-min) (point-max)))) +;; +(defun ics-highlight-last-output (&optional string) + "Highlight the last piece of ics output" + ;; as a first approximation, use the end of the last highlighting + ;; for the start of this batch and the process mark in the current + ;; buffer for the end. If ics-last-highlight-end is nil then + ;; we haven't highlighted anything yet so use point-min + (let ((start (if (null ics-last-highlight-end) + (point-min) + ics-last-highlight-end)) + (end (process-mark (get-buffer-process + (current-buffer))))) + ;; but end could be in the middle of some output from the server + ;; so we go to the start of the line... + (save-excursion + (goto-char end) + (beginning-of-line) + (setq end (point))) + ;; only call ics-highlight-region if we have some output to do... + ;; ics-highlight-region will update ics-last-highlight-end when + ;; it is finished + (if (> end start) + (ics-highlight-region start end)))) + +(defun ics-highlight-region (start end) + "Highlights between start and end" + ;; + ;; first check if setup has been run yet and run it if not... + ;; + (if (null ics-highlight-alist) + (ics-setup-highlight-alist)) + (let ((alist ics-highlight-alist)) + (while alist + (let ((re (car (car alist))) + (level (car (cdr (car alist)))) + (theface (car (nthcdr 2 (car alist))))) + (save-excursion + (goto-char start) + (while (re-search-forward re end t) + (put-text-property (match-beginning level) + (match-end level) 'face theface)))) + (setq alist (cdr alist)))) + (setq ics-last-highlight-end end)) + +;; +;; Button functions, mostly stolen/modified from gnus-vis.el in the gnus 5.0 +;; distribution. (originally written by Per Abrahamsen) +;; +(defvar ics-button-regexp-list nil) +(defvar ics-button-regexp nil) +(defvar ics-button-last nil) +;; +(defun ics-add-buttons-to-last-output (&optional string) + "Add buttons to the last piece of ics output" + ;; as a first approximation, use the end of the last highlighting + ;; for the start of this batch and the process mark in the current + ;; buffer for the end. If ics-last-add-buttons-end is nil then use + ;; point-min since we haven't highlighted yet... + (let ((start (if (null ics-last-add-buttons-end) + (point-min) + ics-last-add-buttons-end)) + (end (process-mark (get-buffer-process + (current-buffer))))) + ;; but end could be in the middle of some output from the server + ;; so we go to the start of the line... + (save-excursion + (goto-char end) + (beginning-of-line) + (setq end (point))) + ;; only call ics-add-buttons-to-region if we have some output to do... + ;; ics-add-buttons-to-region will update ics-last-add-buttons-end when + ;; it is finished + (if (> end start) + (ics-add-buttons-to-region start end)))) + +(defun ics-add-buttons-to-buffer () + "Adds buttons to the whole ICS buffer. " + (interactive) + (save-excursion + (ics-add-buttons-to-region (point-min) (point-max)))) + +(defun ics-add-buttons-to-region (regstart regend) + "Find external references in region regstart to regend and make them into +buttons. + +External references are things like who list entries and URLs, as +specified by `ics-button-alist'." + (if (eq ics-button-last ics-button-alist) + () + (setq ics-button-regexp (mapconcat 'car ics-button-alist "\\|") + ics-button-last ics-button-alist)) + (save-excursion + (let ((buffer-read-only nil) + (inhibit-point-motion-hooks t) + (case-fold-search t) + (alist ics-button-alist)) + (while alist + (setq entry (car alist)) + (setq ics-button-regexp (car entry)) + (setq alist (cdr alist)) + (goto-char regstart) + (while (re-search-forward ics-button-regexp regend t) + (goto-char (match-beginning 0)) + (let* ((from (point)) + (start (and entry (match-beginning (nth 1 entry)))) + (end (and entry (match-end (nth 1 entry)))) + (form (nth 2 entry))) + (if (not entry) + () + (goto-char (match-end 0)) + (if (eval form) + (ics-add-button start end 'ics-button-push + (set-marker (make-marker) + from)))))))))) + + +(defun ics-add-button (from to fun &optional data) + "Create a button between FROM and TO with callback FUN and data DATA." + (and ics-button-face + (overlay-put (make-overlay from to) + 'face ics-button-face)) + (add-text-properties from to + (append (and ics-mouse-face + (list 'mouse-face ics-mouse-face)) + (list 'ics-callback fun) + (and data (list 'ics-data data)))) + (setq ics-last-add-buttons-end to)) + +(defun ics-button-entry () + ;; Return the first entry in `ics-button-alist' matching this place. + (let ((alist ics-button-alist) + (entry nil)) + (while alist + (setq entry (car alist) + alist (cdr alist)) + (if (looking-at (car entry)) + (setq alist nil) + (setq entry nil))) + entry)) + +(defun ics-mouse-push-button-or-yank (event) + "Check text under the mouse pointer for a callback function. +If the text under the mouse pointer has a `ics-callback' property, +call it with the value of the `ics-data' text property." + (interactive "e") + (set-buffer (window-buffer (posn-window (event-start event)))) + (let* ((pos (posn-point (event-start event))) + (data (get-text-property pos 'ics-data)) + (fun (get-text-property pos 'ics-callback))) + (if fun (funcall fun data) + (mouse-yank-at-click event nil)))) + + +(defun ics-button-push (marker) + ;; Push button starting at MARKER. + (save-excursion + (goto-char marker) + (let* ((entry (ics-button-entry)) + (inhibit-point-motion-hooks t) + (fun (nth 3 entry)) + (args (mapcar (lambda (group) + (let ((string (buffer-substring + (match-beginning group) + (match-end group)))) + (set-text-properties 0 (length string) nil string) + string)) + (nthcdr 4 entry)))) + (cond ((fboundp fun) + (apply fun args)) + ((and (boundp fun) + (fboundp (symbol-value fun))) + (apply (symbol-value fun) args)) + (t + (message "You must define `%S' to use this button" + (cons fun args))))))) + +;; +;; functions for ics buffer buttons +;; +;; +(defun ics-send-command (command) + "send COMMAND to ics process" + (comint-simple-send (get-buffer-process (current-buffer)) command)) + +(defun ics-button-mail (toaddr) + "Function to start emacs mailer on email addresses." + (mail-other-window nil toaddr)) + +(defun ics-play (index) + "issue a play command to play a game in the seek list" + (interactive "sWhich index? ") + (comint-simple-send (get-buffer-process (current-buffer)) + (format ics-play-command index))) + +(defun ics-finger (player) + "Function to finger a player." + (interactive "sFinger who? ") + (comint-simple-send (get-buffer-process (current-buffer)) + (format ics-finger-command player))) + +(defun ics-observe (game) + "Function to observe a game." + (interactive "sWhich game? ") + (comint-simple-send (get-buffer-process (current-buffer)) + (format ics-observe-command game))) + +(defun ics-examine-from-historylist (game) + "Function to examine a game from history list" + (save-excursion + (or (re-search-backward + "\\(Recent games of\\|History for\\) \\([a-zA-Z0-9]+\\):" + (point-min) t) + (error "Couldn't identify player")) + (let ((player (buffer-substring (match-beginning 2) (match-end 2)))) + (comint-simple-send (get-buffer-process (current-buffer)) + (format "examine %s %s" player game))))) + +(defun ics-match (player) + "Function to challenge a player." + (interactive "sMatch arguments: ") + (comint-simple-send (get-buffer-process (current-buffer)) + (format ics-match-command player))) + +(defun ics-stored (dummy) + "Function to send the command stored to the server" + (interactive) + (comint-simple-send (get-buffer-process (current-buffer)) + "stored")) + +(defun ics-who () + "Function to get who listing." + (interactive) + (comint-simple-send (get-buffer-process (current-buffer)) ics-who-command)) + +(defun ics-players () + "Function to get players listing." + (interactive) + (comint-simple-send (get-buffer-process (current-buffer)) + ics-players-command)) + +;;; URL netscape functions.. from gnus 5.0 source + +(defun ics-netscape-open-url (url) + "Open URL in netscape, or start new scape with URL." + (let ((process (start-process (concat "netscape " url) + nil + "netscape" + "-remote" + (concat "openUrl(" url ")'")))) + (set-process-sentinel process + (` (lambda (process change) + (or (eq (process-exit-status process) 0) + (ics-netscape-start-url (, url)))))))) + +(defun ics-netscape-start-url (url) + "Start netscape with URL." + (start-process (concat "netscape" url) nil "netscape" url)) + + +;;;;;;;; +;; +;; some "utility" functions... +;; +(defun ics-watch-for-login-and-send-handle (string) + "function to send handle only after login prompt appears." + (if (string-match ics-login-prompt-regexp string) + (progn + ;; remove the hook ... this function has to be "onetrip" since + ;; otherwise, entering an invalid handle results in a loop, + ;; with ics.el sending the handle repeatedly. + (remove-hook 'comint-output-filter-functions + 'ics-watch-for-login-and-send-handle t) + (message "Sending ICS handle...") + (comint-simple-send (get-buffer-process (current-buffer)) + ics-handle) + (message "Sending ICS handle... done.") + (if ics-send-password + (progn + (message "Sending ICS password...") + (comint-simple-send (get-buffer-process (current-buffer)) + ics-password) + (message "Sending ICS password... done."))) +))) +;; +;; some time related functions... +;; +(defun ics-current-time () + "Returns the second integer in current-time" + (car (cdr (current-time)))) + +(defun ics-time-since (prev-time) + "Return time in seconds since PTIME" + (let* ((c-time (ics-current-time)) + (diff-time (- c-time prev-time))) + diff-time)) +;; +;; quit function... +;; +(defun ics-confirm-quit () + "Checks with the user before quitting. Bound to \"C-c C-c\" by default" + (interactive) + (if (y-or-n-p "Really send quit signal? ") + (comint-quit-subjob))) +;; +;; Zippyism function +;; +(defvar zippyism-start-string "i Zippy-ises: " + "string to pre-pend to Zippy-isms") + +(defun ics-insert-zippyism () + "inserts a zippy-ism sans any embedded newlines into the current +buffer, with zippyism-start-string prepended" + (interactive) + (save-excursion + (let ((start (point))) + (insert-string zippyism-start-string) + (yow 1) + (while (> (point) start) + (if (search-backward "\n" start "foo") + (replace-match ""))))) + (end-of-line)) + +;;; +;; compatibility function for 19.28... +;; +(or (fboundp 'match-string) + (defun match-string (level &optional string) + "compatibility funstion for 19.28... returns string matched +at level NUM by last regexp match. + +(match-string NUM &optional STRING) + +Return string of text matched by last search. +NUM specifies which parenthesized expression in the last regexp. + Value is nil if NUMth pair didn't match, or there were less than NUM pairs. +Zero means the entire text matched by the whole regexp or whole string. +STRING should be given if the last search was by `string-match' on STRING." + (if string + (substring string (match-beginning level) (match-end level)) + (buffer-substring (match-beginning level) (match-end level))))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; next stuff snaffled from Per Abrahamsen's custom.el v0.5 +;; +(or (fboundp 'facep) + ;; Introduced in Emacs 19.29. + (defun facep (x) + "Return t if X is a face name or an internal face vector." + (and (or (and (fboundp 'internal-facep) (internal-facep x)) + (and + (symbolp x) + (assq x (and (boundp 'global-face-data) global-face-data)))) + t))) + +(or (and (fboundp 'modify-face) (not (featurep 'face-lock))) + ;; Introduced in Emacs 19.29. Incompatible definition also introduced + ;; by face-lock.el version 3.00 and above for Emacs 19.28 and below. + ;; face-lock does not call modify-face, so we can safely redefine it. + (defun modify-face (face foreground background stipple + bold-p italic-p underline-p) + "Change the display attributes for face FACE. +FOREGROUND and BACKGROUND should be color strings or nil. +STIPPLE should be a stipple pattern name or nil. +BOLD-P, ITALIC-P, and UNDERLINE-P specify whether the face should be set bold, +in italic, and underlined, respectively. (Yes if non-nil.) +If called interactively, prompts for a face and face attributes." + (interactive + (let* ((completion-ignore-case t) + (face (symbol-name (read-face-name "Modify face: "))) + (colors (mapcar 'list x-colors)) + (stipples (mapcar 'list + (apply 'nconc + (mapcar 'directory-files + x-bitmap-file-path)))) + (foreground (modify-face-read-string + face (face-foreground (intern face)) + "foreground" colors)) + (background (modify-face-read-string + face (face-background (intern face)) + "background" colors)) + (stipple (modify-face-read-string + face (face-stipple (intern face)) + "stipple" stipples)) + (bold-p (y-or-n-p (concat "Set face " face " bold "))) + (italic-p (y-or-n-p (concat "Set face " face " italic "))) + (underline-p (y-or-n-p (concat "Set face " face " underline ")))) + (message "Face %s: %s" face + (mapconcat 'identity + (delq nil + (list (and foreground (concat (downcase foreground) " foreground")) + (and background (concat (downcase background) " background")) + (and stipple (concat (downcase stipple) " stipple")) + (and bold-p "bold") (and italic-p "italic") + (and underline-p "underline"))) ", ")) + (list (intern face) foreground background stipple + bold-p italic-p underline-p))) + (condition-case nil (set-face-foreground face foreground) (error nil)) + (condition-case nil (set-face-background face background) (error nil)) + (condition-case nil (set-face-stipple face stipple) (error nil)) + (if (string-match "XEmacs" emacs-version) + (progn + (funcall (if bold-p 'make-face-bold 'make-face-unbold) face) + (funcall (if italic-p 'make-face-italic 'make-face-unitalic) face)) + (funcall (if bold-p 'make-face-bold 'make-face-unbold) face nil t) + (funcall (if italic-p 'make-face-italic 'make-face-unitalic) face nil t)) + (set-face-underline-p face underline-p) + (and (interactive-p) (redraw-display)))) + + +(defun ics-face-lookup (fg bg stipple bold italic underline) + "Lookup or create a face with specified attributes. +FG BG STIPPLE BOLD ITALIC UNDERLINE" + (let ((name (intern (format "ics-face-%s-%s-%s-%S-%S-%S" + (or fg "default") + (or bg "default") + (or stipple "default") + bold italic underline)))) + (if (and (facep name) + (fboundp 'make-face)) + () + (make-face name) + (modify-face name + (if (string-equal fg "default") nil fg) + (if (string-equal bg "default") nil bg) + (if (string-equal stipple "default") nil stipple) + bold italic underline)) + name)) +;; +;; end of snaffled stuff +;;;;;;;;;;;;;;;;;; + +(provide 'ics) + +;;; end of ics.el |
