1 ;;; zenirc-complete.el --- complete commands, nicknames, etc. in ZenIRC
3 ;; Copyright (C) 1994, 1995 Noah S. Friedman
4 ;; Copyright (C) 1998 Per Persson
6 ;; Author: Noah Friedman <friedman@prep.ai.mit.edu>
7 ;; Maintainer: pp@sno.pp.se
8 ;; Keywords: zenirc, completion, extensions, oink
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)
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.
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.
28 ;; This code was inspired by the zenirc completion code written
29 ;; by Per Persson <pp@@solace.mh.se>, but is a new implementation.
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.
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.
42 ;; TODO: Add new hooks to add/delete nicks to/from cache.
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.")
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
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.")
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")
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")
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.
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)
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"
108 (set (intern (car l) zenirc-complete-table) (car l))
112 ;; The following macros are caching interfaces; they do
113 ;; canonicalization, checking for previous caching, etc.
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.
120 (defmacro zenirc-complete-cache (s)
121 (list 'set (list 'intern
122 (list 'zenirc-downcase-name s)
123 'zenirc-complete-table) s))
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)))
132 (defmacro zenirc-complete-cache-nick (s)
133 (list 'zenirc-complete-cache (list 'or (list 'zenirc-extract-nick s) s)))
135 (defmacro zenirc-complete-uncache-nick (s)
136 (list 'zenirc-complete-uncache (list 'or (list 'zenirc-extract-nick s) s)))
139 ;;; Interactive interface: /complete-cache and /complete-uncache
141 (defun zenirc-complete-command-cache (proc words)
144 (string= "" (cdr words)))
145 (zenirc-message proc 'complete-cache-no-wordlist))
147 (let ((wordlist (zenirc-parse-words (cdr words))))
149 (zenirc-complete-cache (car wordlist))
150 (setq wordlist (cdr wordlist)))
151 (zenirc-message proc 'complete-cache-added (cdr words))))))
153 (defun zenirc-complete-command-uncache (proc words)
156 (string= "" (cdr words)))
157 (zenirc-message proc 'complete-cache-no-wordlist))
159 (let ((wordlist (zenirc-parse-words (cdr words))))
161 (zenirc-complete-uncache (car wordlist))
162 (setq wordlist (cdr wordlist)))
163 (zenirc-message proc 'complete-cache-removed (cdr words))))))
166 (defun zenirc-complete ()
167 "Complete the current word at point for ZenIRC.
168 This completion cache comes from `zenirc-complete-table'.
170 Channel names, nicknames, server names, etc. are cached as ZenIRC happens
171 upon them; initially there are none to choose from."
173 (let* ((completion-ignore-case t)
175 (if (re-search-backward zenirc-complete-word-boundary-regexp
176 zenirc-process-mark t)
177 (1+ (match-beginning 0))
178 zenirc-process-mark)))
180 (if (re-search-forward zenirc-complete-word-boundary-regexp
184 (orig-word (buffer-substring bow eow))
185 (word (zenirc-downcase-name orig-word))
186 completion-list completion)
189 (zenirc-message nil 'complete-not-on-word))
191 ;; Don't check for new commands unless actually trying to complete a
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))
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)
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
210 (zenirc-message nil 'complete-unique)))
211 ((and (stringp (setq completion
212 (try-completion word zenirc-complete-table
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.
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)))
228 (zenirc-message nil 'complete-not-unique)))))))
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))
244 ((string-match "^zenirc-command-\\(.*\\)-hook$" (car completions))
245 (setq name (concat "/" (substring (car completions)
248 (zenirc-complete-cache name)))
249 (setq completions (cdr completions))))))
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"))))
265 ;;; Various methods to display completions
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))))
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))
291 (let ((standard-output buf)
292 (completion-setup-hook nil))
293 (display-completion-list completions))
295 ;; count number of lines
296 (goto-char (point-min))
298 (while (search-forward "\n" nil t)
299 (setq lines (1+ lines))))
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))))
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 " ")))
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)
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)
333 ;;; Cache nick and channel from invitations
335 (defun zenirc-complete-cache-INVITE (proc parsedmsg)
336 (zenirc-complete-cache-nick (aref parsedmsg 1))
337 (zenirc-complete-cache (aref parsedmsg 3)))
339 (zenirc-add-hook 'zenirc-server-INVITE-hook 'zenirc-complete-cache-INVITE)
342 ;; Cache nick and channel from JOIN replies.
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))))
351 (zenirc-add-hook 'zenirc-server-JOIN-hook 'zenirc-complete-cache-JOIN)
354 ;; Cache new nick from NICK change.
356 (defun zenirc-complete-cache-NICK (proc parsedmsg)
357 (zenirc-complete-cache-nick (aref parsedmsg 2)))
359 (zenirc-add-hook 'zenirc-server-NICK-hook 'zenirc-complete-cache-NICK)
362 ;; Cache nick and channel from PRIVMSGs and NOTICEs
364 (defun zenirc-complete-cache-msg (proc parsedmsg)
365 (zenirc-complete-cache-nick (aref parsedmsg 1))
366 (zenirc-complete-cache (aref parsedmsg 2)))
368 (zenirc-add-hook 'zenirc-server-PRIVMSG-hook 'zenirc-complete-cache-msg)
369 (zenirc-add-hook 'zenirc-server-NOTICE-hook 'zenirc-complete-cache-msg)
372 ;; [312] Cache nick and server names from /whois replies
374 (defun zenirc-complete-cache-312 (proc parsedmsg)
375 (zenirc-complete-cache-nick (aref parsedmsg 3))
376 (zenirc-complete-cache (aref parsedmsg 4)))
378 (zenirc-add-hook 'zenirc-server-312-hook 'zenirc-complete-cache-312)
381 ;; [319] Cache channel list from /whois replies
383 (defun zenirc-complete-cache-319 (proc parsedmsg)
385 (let ((str (aref parsedmsg 4))
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)))))))
392 (zenirc-add-hook 'zenirc-server-319-hook 'zenirc-complete-cache-319)
395 ;; [322] Cache channel names from /list replies
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))))
402 (zenirc-add-hook 'zenirc-server-322-hook 'zenirc-complete-cache-322)
405 ;; [352] Cache nick, server, and channel names from /who replies
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)))
412 (zenirc-add-hook 'zenirc-server-352-hook 'zenirc-complete-cache-352)
415 ;; [353] Cache nicknames from list after channel join (or NAMES command)
417 (defun zenirc-complete-cache-353 (proc parsedmsg)
419 (let ((str (aref parsedmsg 5))
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)))))))
428 (zenirc-add-hook 'zenirc-server-353-hook 'zenirc-complete-cache-353)
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
435 (defun zenirc-complete-uncache-40x (proc parsedmsg)
436 (zenirc-complete-uncache-nick (aref parsedmsg 3)))
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)
446 (provide 'zenirc-complete)
448 (define-key zenirc-mode-map "\t" 'zenirc-complete)
449 (zenirc-complete-install-message-catalogs)
451 ;; zenirc-complete.el ends here