Initial Commit
[packages] / xemacs-packages / zenirc / src / zenirc-complete.el
1 ;;; zenirc-complete.el --- complete commands, nicknames, etc. in ZenIRC
2
3 ;; Copyright (C) 1994, 1995 Noah S. Friedman
4 ;; Copyright (C) 1998 Per Persson
5
6 ;; Author: Noah Friedman <friedman@prep.ai.mit.edu>
7 ;; Maintainer: pp@sno.pp.se
8 ;; Keywords: zenirc, completion, extensions, oink
9 ;; Created: 1994-06-26
10
11 ;; This program is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; any later version.
15 ;;
16 ;; This program is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 ;; GNU General Public License for more details.
20 ;;
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with this program; if not, you can either send email to this
23 ;; program's maintainer or write to: The Free Software Foundation,
24 ;; Inc.; 675 Massachusetts Avenue; Cambridge, MA 02139, USA.
25
26 ;;; Commentary:
27
28 ;; This code was inspired by the zenirc completion code written
29 ;; by Per Persson <pp@@solace.mh.se>, but is a new implementation.
30
31 ;; I tied into a lot of hooks to make caching of nicks, channels, servers,
32 ;; etc. fairly insidious.  I think I've covered most of the useful ones;
33 ;; some server messages, like KICK, don't really need their own completion
34 ;; cache hooks because you can only see them on channels you've joined,
35 ;; and the JOIN and 353 hooks already cache all the nicknames.
36
37 ;; Putting the cache function on zenirc-format-nickuserhost-hook isn't
38 ;; very reliable.  Depending on load order, other functions which add to
39 ;; the hook might get called first and set zenirc-run-next-hook to nil.
40 ;; That hook is intended just used to return a formatted nickname.
41
42 ;; TODO: Add new hooks to add/delete nicks to/from cache.
43
44 ;;; Code:
45
46 \f
47 (require 'zenirc)
48
49 (defvar zenirc-complete-add-final-space-p t
50   "*If non-nil, add a trailing space after unique completions.
51 This is consistent with the behavior of completion in general, as a
52 way of making it clear that the word is fully completed.  However, if you
53 frequently add punctuation to completed words and the additional whitespace
54 is annoying, set this to nil.")
55
56 (defvar zenirc-complete-display-function
57   'zenirc-complete-display-echo-area-or-popup
58   "*Function to call to display matches for partial completions.
59 This function receives a list of completions as returned by
60 `all-completions'.")
61
62 (defvar zenirc-complete-word-boundary-regexp "[ \t\n\r]"
63   "*Regexp matching word boundaries in the ZenIRC buffer.
64 The regular expression should probably be a single character class list of
65 characters which are not considered part of a word, and thus server as a
66 boundary.  Whitespace is a good terminator; `#', `&', `/', etc. are not
67 good choices because that would prevent the possibility of completing
68 channel or command names.")
69
70 (defvar zenirc-command-complete-cache-hook '(zenirc-complete-command-cache)
71   "*Hook to call when a /complete-cache command is issued in ZenIRC.
72 This adds a word to the list of known completions in ZenIRC.
73 The syntax of the command is: /complete-cache word")
74
75 (defvar zenirc-command-complete-uncache-hook '(zenirc-complete-command-uncache)
76   "*Hook to call when a /complete-uncache command is issued in ZenIRC.
77 This removes a word from the list of known completions in ZenIRC.
78 The syntax of the command is: /complete-uncache word")
79
80 ;; 307 buckets should be a reasonable size for most people (remember to use
81 ;; a prime number to get good hashing characteristics).
82 ;; This is not the total number of completions you can cache, but just the
83 ;; number of "buckets" in which symbols can be stored.  If you regularly do
84 ;; `/who *' and `/list *' to get all the channels and nicks on IRC,
85 ;; increasing the size of this table might be helpful but is not necessary.
86 ;; (As of March 1995, there are typically 5000 users, 2000 channels on IRC.)
87 (defvar zenirc-complete-table (make-vector 307 0)
88   "Accumulated completion table for ZenIRC.
89 This table can include nicknames, channel names, server names, etc.
90
91 Completion is case-insensitive since there cannot be two nicknames, channel
92 names, etc. which differ only by case in zenirc.  However, when completing
93 a name uniquely, the case of the cached word is preserved.")
94 (make-variable-buffer-local 'zenirc-complete-table)
95
96 ;; Initialize table with server commands; these are defined by IRC servers
97 ;; but may not necessarily have any hooks in ZenIRC.  We could get these
98 ;; from the IRC server but it's too slow.
99 (let ((l '("/admin" "/away" "/close" "/connect" "/die" "/dns" "/error"
100            "/hash" "/help" "/info" "/invite" "/ison" "/join" "/kick"
101            "/kill" "/links" "/list" "/lusers" "/mode" "/motd" "/names"
102            "/nick" "/note" "/notice" "/oper" "/part" "/pass" "/ping"
103            "/pong" "/privmsg" "/quit" "/rehash" "/restart" "/server"
104            "/squit" "/stats" "/summon" "/time" "/topic" "/trace" "/user"
105            "/userhost" "/users" "/version" "/wallops" "/who" "/whois"
106            "/whowas")))
107   (while l
108     (set (intern (car l) zenirc-complete-table) (car l))
109     (setq l (cdr l))))
110
111 \f
112 ;; The following macros are caching interfaces; they do
113 ;; canonicalization, checking for previous caching, etc.
114
115 ;; Note that you can't call macros directly with apply, so you can't put
116 ;; these macros directly on hooks (zenirc-run-hook calls apply on the
117 ;; symbols in the hook).  If this package were only intended to work under
118 ;; Emacs 19, defsubst could have been used instead.
119
120 (defmacro zenirc-complete-cache (s)
121   (list 'set (list 'intern
122                    (list 'zenirc-downcase-name s)
123                    'zenirc-complete-table) s))
124
125 ;; You cannot actually unintern symbols from an obarray, but you can
126 ;; make them unbound.
127 (defmacro zenirc-complete-uncache (s)
128   (list 'makunbound (list 'intern
129                           (list 'zenirc-downcase-name s)
130                           'zenirc-complete-table)))
131
132 (defmacro zenirc-complete-cache-nick (s)
133   (list 'zenirc-complete-cache (list 'or (list 'zenirc-extract-nick s) s)))
134
135 (defmacro zenirc-complete-uncache-nick (s)
136   (list 'zenirc-complete-uncache (list 'or (list 'zenirc-extract-nick s) s)))
137
138 \f
139 ;;; Interactive interface: /complete-cache and /complete-uncache
140
141 (defun zenirc-complete-command-cache (proc words)
142   (cond
143    ((or (null words)
144         (string= "" (cdr words)))
145     (zenirc-message proc 'complete-cache-no-wordlist))
146    (t
147     (let ((wordlist (zenirc-parse-words (cdr words))))
148       (while wordlist
149         (zenirc-complete-cache (car wordlist))
150         (setq wordlist (cdr wordlist)))
151       (zenirc-message proc 'complete-cache-added (cdr words))))))
152
153 (defun zenirc-complete-command-uncache (proc words)
154   (cond
155    ((or (null words)
156         (string= "" (cdr words)))
157     (zenirc-message proc 'complete-cache-no-wordlist))
158    (t
159     (let ((wordlist (zenirc-parse-words (cdr words))))
160       (while wordlist
161         (zenirc-complete-uncache (car wordlist))
162         (setq wordlist (cdr wordlist)))
163       (zenirc-message proc 'complete-cache-removed (cdr words))))))
164
165 \f
166 (defun zenirc-complete ()
167   "Complete the current word at point for ZenIRC.
168 This completion cache comes from `zenirc-complete-table'.
169
170 Channel names, nicknames, server names, etc. are cached as ZenIRC happens
171 upon them; initially there are none to choose from."
172   (interactive)
173   (let* ((completion-ignore-case t)
174          (bow (save-excursion
175                 (if (re-search-backward zenirc-complete-word-boundary-regexp
176                                         zenirc-process-mark t)
177                     (1+ (match-beginning 0))
178                   zenirc-process-mark)))
179          (eow (save-excursion
180                 (if (re-search-forward zenirc-complete-word-boundary-regexp
181                                        (point-max) t)
182                     (match-beginning 0)
183                   (point-max))))
184          (orig-word (buffer-substring bow eow))
185          (word (zenirc-downcase-name orig-word))
186          completion-list completion)
187     (cond
188      ((string= word "")
189       (zenirc-message nil 'complete-not-on-word))
190      (t
191       ;; Don't check for new commands unless actually trying to complete a
192       ;; command name.
193       (and (eq (string-to-char word) ?/)
194            (zenirc-complete-cache-new-zenirc-commands))
195       (setq completion-list
196             (all-completions word zenirc-complete-table 'boundp))
197       (cond
198        ((null completion-list)
199         (zenirc-message nil 'complete-cant-find word))
200        ;; faster than (= (length completion-list) 1)
201        ((null (cdr completion-list))
202         (delete-region bow eow)
203         (goto-char bow)
204         ;; unique completion.  Insert the original string, rather than the
205         ;; key, in case the string has mixed case.
206         (insert (symbol-value (intern (car completion-list)
207                                       zenirc-complete-table)))
208         (if zenirc-complete-add-final-space-p
209             (insert " ")
210           (zenirc-message nil 'complete-unique)))
211        ((and (stringp (setq completion
212                             (try-completion word zenirc-complete-table
213                                             'boundp)))
214              (not (string= word completion)))
215         ;; This completion is just a partial match.
216         ;; Don't delete partial match already typed, just add new chars.
217         ;; This is to preserve the case of the letters typed so far since
218         ;; no proper change in case has been decided yet.
219         (goto-char eow)
220         (insert (substring completion (length word))))
221        ;; If we get this far, the partial completion so far is ambiguous.
222        ;; If completion-auto-help (a standard user option) is non-nil, show
223        ;; a table of possible completions.
224        (completion-auto-help
225         (funcall zenirc-complete-display-function
226                  (all-completions word zenirc-complete-table 'boundp)))
227        (t
228         (zenirc-message nil 'complete-not-unique)))))))
229
230 ;; Find all commands defined in ZenIRC itself, and attempt to add them to
231 ;; the cache.  Each command FOO has a zenirc-command-FOO-hook, so look for
232 ;; those symbols and extract FOO.  Don't actually consider them commands
233 ;; unless the hook is bound; zenirc itself has to intern symbols whenever
234 ;; it gets a command /foo, even if /foo isn't a command, and those interned
235 ;; symbols are nonsense.
236 ;; The return value of this function isn't meaningful.
237 (defun zenirc-complete-cache-new-zenirc-commands ()
238   ;; all-completions is vastly faster than mapatoms + a lisp function.
239   (let ((completions (all-completions "zenirc-command-" obarray 'boundp))
240         name)
241     (save-match-data
242       (while completions
243         (cond
244          ((string-match "^zenirc-command-\\(.*\\)-hook$" (car completions))
245           (setq name (concat "/" (substring (car completions)
246                                             (match-beginning 1)
247                                             (match-end 1))))
248           (zenirc-complete-cache name)))
249         (setq completions (cdr completions))))))
250
251 ;; TODO: add other languages besides english
252 (defun zenirc-complete-install-message-catalogs ()
253   (zenirc-lang-define-catalog 'english
254    '((complete-cache-no-wordlist . "[error] No completion cache.")
255      (complete-cache-added . "[info] Added to completion cache: %s")
256      (complete-cache-removed . "[info] Removed from completion cache: %s")
257      (complete-cache-unknown . "[error] Not in cache: %s")
258      (complete-cache-known . "[info] Already in cache: %s")
259      (complete-not-on-word . "No word at point")
260      (complete-cant-find . "Can't find completion for \"%s\"")
261      (complete-not-unique . "Next char not unique")
262      (complete-unique . "Unique completion"))))
263
264 \f
265 ;;; Various methods to display completions
266
267 (defun zenirc-complete-display-traditional (completions)
268   (with-output-to-temp-buffer " *Completions*"
269     ;; Since we're not using the minibuffer for completions, don't do
270     ;; the fancy completion setup emacs 19 uses to make mouse clicks
271     ;; return a completion; it just doesn't work here.  Bind
272     ;; completion-setup-hook to nil.
273     (let (completion-setup-hook)
274       (display-completion-list completions))))
275
276 ;; This is similar to zenirc-complete-display-in-traditional except that
277 ;; the new window is created by taking screen space from the zenirc window,
278 ;; rather than stealing some other buffer's window.  Plus, the window is
279 ;; made no larger than necessary to display all completions, with half the
280 ;; zenirc window height being the maximum allowed.
281 (defun zenirc-complete-display-in-popup (completions)
282   (let* ((orig-buffer (current-buffer))
283          (orig-win (selected-window))
284          (buf (get-buffer-create " *Completions*"))
285          (win (get-buffer-window buf))
286          (lines 0))
287     (unwind-protect
288         (progn
289           (set-buffer buf)
290           (erase-buffer)
291           (let ((standard-output buf)
292                 (completion-setup-hook nil))
293             (display-completion-list completions))
294
295           ;; count number of lines
296           (goto-char (point-min))
297           (save-match-data
298             (while (search-forward "\n" nil t)
299               (setq lines (1+ lines))))
300
301           (cond ((null win)
302                  (split-window orig-win
303                                (max (/ (window-height) 2)
304                                     (- (window-height) lines)))
305                  (select-window (next-window))
306                  (switch-to-buffer buf)
307                  (goto-char (point-min)))))
308       (select-window orig-win)
309       (set-buffer orig-buffer))))
310
311 ;; Always display completions in the minibuffer.  Note that excessively
312 ;; long lines will get truncated.
313 (defun zenirc-complete-display-in-echo-area (completions)
314   (message "%s" (mapconcat 'identity completions " ")))
315
316 ;; Display completions in the minibuffer if it will fit.
317 ;; Otherwise, use traditional completion display.
318 (defun zenirc-complete-display-echo-area-or-traditional (completions)
319   (let ((s (mapconcat 'identity completions " ")))
320     (if (> (length s) (window-width (minibuffer-window)))
321         (zenirc-complete-display-traditional completions)
322       (message "%s" s))))
323
324 ;; Display completions in the minibuffer if it will fit.
325 ;; Otherwise, use popup completion display.
326 (defun zenirc-complete-display-echo-area-or-popup (completions)
327   (let ((s (mapconcat 'identity completions " ")))
328     (if (> (length s) (window-width (minibuffer-window)))
329         (zenirc-complete-display-in-popup completions)
330       (message "%s" s))))
331
332 \f
333 ;;; Cache nick and channel from invitations
334
335 (defun zenirc-complete-cache-INVITE (proc parsedmsg)
336   (zenirc-complete-cache-nick (aref parsedmsg 1))
337   (zenirc-complete-cache (aref parsedmsg 3)))
338
339 (zenirc-add-hook 'zenirc-server-INVITE-hook 'zenirc-complete-cache-INVITE)
340
341 \f
342 ;; Cache nick and channel from JOIN replies.
343
344 (defun zenirc-complete-cache-JOIN (proc parsedmsg)
345   (zenirc-complete-cache-nick (aref parsedmsg 1))
346   (if (string-match "\a" (aref parsedmsg 2))
347       (zenirc-complete-cache (substring (aref parsedmsg 2) 0
348                                         (- (match-end 0) 1)))
349     (zenirc-complete-cache (aref parsedmsg 2))))
350
351 (zenirc-add-hook 'zenirc-server-JOIN-hook 'zenirc-complete-cache-JOIN)
352
353 \f
354 ;; Cache new nick from NICK change.
355
356 (defun zenirc-complete-cache-NICK (proc parsedmsg)
357   (zenirc-complete-cache-nick (aref parsedmsg 2)))
358
359 (zenirc-add-hook 'zenirc-server-NICK-hook 'zenirc-complete-cache-NICK)
360
361 \f
362 ;; Cache nick and channel from PRIVMSGs and NOTICEs
363
364 (defun zenirc-complete-cache-msg (proc parsedmsg)
365   (zenirc-complete-cache-nick (aref parsedmsg 1))
366   (zenirc-complete-cache (aref parsedmsg 2)))
367
368 (zenirc-add-hook 'zenirc-server-PRIVMSG-hook 'zenirc-complete-cache-msg)
369 (zenirc-add-hook 'zenirc-server-NOTICE-hook 'zenirc-complete-cache-msg)
370
371 \f
372 ;; [312] Cache nick and server names from /whois replies
373
374 (defun zenirc-complete-cache-312 (proc parsedmsg)
375   (zenirc-complete-cache-nick (aref parsedmsg 3))
376   (zenirc-complete-cache (aref parsedmsg 4)))
377
378 (zenirc-add-hook 'zenirc-server-312-hook 'zenirc-complete-cache-312)
379
380 \f
381 ;; [319] Cache channel list from /whois replies
382
383 (defun zenirc-complete-cache-319 (proc parsedmsg)
384   (save-match-data
385     (let ((str (aref parsedmsg 4))
386           chan)
387       (while (string-match "\\(#\\|&\\)\\([^ \t#&]*\\)" str)
388         (setq chan (substring str (match-beginning 1) (match-end 2)))
389         (zenirc-complete-cache chan)
390         (setq str (substring str (match-end 2)))))))
391
392 (zenirc-add-hook 'zenirc-server-319-hook 'zenirc-complete-cache-319)
393
394 \f
395 ;; [322] Cache channel names from /list replies
396
397 (defun zenirc-complete-cache-322 (proc parsedmsg)
398   ;; Channel `*' means private (we don't know the name); don't cache that.
399   (or (string= (aref parsedmsg 3) "*")
400       (zenirc-complete-cache (aref parsedmsg 3))))
401
402 (zenirc-add-hook 'zenirc-server-322-hook 'zenirc-complete-cache-322)
403
404 \f
405 ;; [352] Cache nick, server, and channel names from /who replies
406
407 (defun zenirc-complete-cache-352 (proc parsedmsg)
408   (zenirc-complete-cache (aref parsedmsg 3))
409   (zenirc-complete-cache (aref parsedmsg 6))
410   (zenirc-complete-cache-nick (aref parsedmsg 7)))
411
412 (zenirc-add-hook 'zenirc-server-352-hook 'zenirc-complete-cache-352)
413
414 \f
415 ;; [353] Cache nicknames from list after channel join (or NAMES command)
416
417 (defun zenirc-complete-cache-353 (proc parsedmsg)
418   (save-match-data
419     (let ((str (aref parsedmsg 5))
420           name)
421       (while (string-match "\\([^ \t@]+\\)" str)
422         (setq name (substring str (match-beginning 1) (match-end 1)))
423         ;; I think it's safe not to use zenirc-complete-cache-nick here
424         ;; because the supplied nicks here don't include !user@host.
425         (zenirc-complete-cache name)
426         (setq str (substring str (match-end 1)))))))
427
428 (zenirc-add-hook 'zenirc-server-353-hook 'zenirc-complete-cache-353)
429
430 \f
431 ;; [401] Remove invalid nick or channel from cache
432 ;; [402] Remove invalid server from cache
433 ;; [403] (No such channel) remove channel from cache
434
435 (defun zenirc-complete-uncache-40x (proc parsedmsg)
436   (zenirc-complete-uncache-nick (aref parsedmsg 3)))
437
438 ;; I chose not enable these hooks for now, although I defined the function,
439 ;; because nicks often go away due to netsplits, and uncaching them is
440 ;; simply annoying.  Perhaps people will give feedback to the contrary.
441 ;(zenirc-add-hook 'zenirc-server-401-hook 'zenirc-complete-uncache-40x)
442 ;(zenirc-add-hook 'zenirc-server-403-hook 'zenirc-complete-uncache-40x)
443 ;(zenirc-add-hook 'zenirc-server-402-hook 'zenirc-complete-uncache-40x)
444
445 \f
446 (provide 'zenirc-complete)
447
448 (define-key zenirc-mode-map "\t" 'zenirc-complete)
449 (zenirc-complete-install-message-catalogs)
450
451 ;; zenirc-complete.el ends here