diff options
Diffstat (limited to 'lisp/erc/erc-nicklist.el')
| -rw-r--r-- | lisp/erc/erc-nicklist.el | 417 |
1 files changed, 417 insertions, 0 deletions
diff --git a/lisp/erc/erc-nicklist.el b/lisp/erc/erc-nicklist.el new file mode 100644 index 00000000000..cc913c5fe93 --- /dev/null +++ b/lisp/erc/erc-nicklist.el | |||
| @@ -0,0 +1,417 @@ | |||
| 1 | ;;; erc-nicklist.el --- Display channel nicknames in a side buffer. | ||
| 2 | |||
| 3 | ;; Copyright (C) 2004, 2005, 2006, 2007, 2008, | ||
| 4 | ;; 2009 Free Software Foundation, Inc. | ||
| 5 | |||
| 6 | ;; Filename: erc-nicklist.el | ||
| 7 | ;; Author: Lawrence Mitchell <wence@gmx.li> | ||
| 8 | ;; Created: 2004-04-30 | ||
| 9 | ;; Keywords: IRC chat client Internet | ||
| 10 | |||
| 11 | ;; This file is part of GNU Emacs. | ||
| 12 | |||
| 13 | ;; GNU Emacs is free software; you can redistribute it and/or modify | ||
| 14 | ;; it under the terms of the GNU General Public License as published by | ||
| 15 | ;; the Free Software Foundation; either version 3, or (at your option) | ||
| 16 | ;; any later version. | ||
| 17 | |||
| 18 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 19 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 21 | ;; GNU General Public License for more details. | ||
| 22 | |||
| 23 | ;; You should have received a copy of the GNU General Public License | ||
| 24 | ;; along with GNU Emacs; see the file COPYING. If not, write to the | ||
| 25 | ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||
| 26 | ;; Boston, MA 02110-1301, USA. | ||
| 27 | |||
| 28 | ;;; Commentary: | ||
| 29 | ;; | ||
| 30 | ;; This provides a minimal mIRC style nicklist buffer for ERC. To | ||
| 31 | ;; activate, do M-x erc-nicklist RET in the channel buffer you want | ||
| 32 | ;; the nicklist to appear for. To close and quit the nicklist | ||
| 33 | ;; buffer, do M-x erc-nicklist-quit RET from within the nicklist buffer. | ||
| 34 | ;; | ||
| 35 | ;; TODO: | ||
| 36 | ;; o Somehow associate nicklist windows with channel windows so they | ||
| 37 | ;; appear together, and if one gets buried, then the other does. | ||
| 38 | ;; | ||
| 39 | ;; o Make "Query" and "Message" work. | ||
| 40 | ;; | ||
| 41 | ;; o Prettify the actual list of nicks in some way. | ||
| 42 | ;; | ||
| 43 | ;; o Add a proper erc-module that people can turn on and off, figure | ||
| 44 | ;; out a way of creating the nicklist window at an appropriate time | ||
| 45 | ;; --- probably in `erc-join-hook'. | ||
| 46 | ;; | ||
| 47 | ;; o Ensure XEmacs compatibility --- the mouse-menu support is likely | ||
| 48 | ;; broken. | ||
| 49 | ;; | ||
| 50 | ;; o Add option to display in a separate frame --- will again need to | ||
| 51 | ;; be able to associate the nicklist with the currently active | ||
| 52 | ;; channel buffer or something similar. | ||
| 53 | ;; | ||
| 54 | ;; o Allow toggling of visibility of nicklist via ERC commands. | ||
| 55 | |||
| 56 | ;;; History: | ||
| 57 | ;; | ||
| 58 | |||
| 59 | ;; Changes by Edgar Gonçalves <edgar.goncalves@inesc-id.pt> | ||
| 60 | ;; Jun 25 2005: | ||
| 61 | ;; - images are changed to a standard set of names. | ||
| 62 | ;; - /images now contain gaim's status icons. | ||
| 63 | ;; May 31 2005: | ||
| 64 | ;; - tooltips are improved. they try to access bbdb for a nice nick! | ||
| 65 | ;; Apr 26 2005: | ||
| 66 | ;; - erc-nicklist-channel-users-info was fixed (sorting bug) | ||
| 67 | ;; - Away names don't need parenthesis when using icons | ||
| 68 | ;; Apr 26 2005: | ||
| 69 | ;; - nicks can display icons of their connection type (msn, icq, for now) | ||
| 70 | ;; Mar 15 2005: | ||
| 71 | ;; - nicks now are different for unvoiced and op users | ||
| 72 | ;; - nicks now have tooltips displaying more info | ||
| 73 | ;; Mar 18 2005: | ||
| 74 | ;; - queries now work ok, both on menu and keyb shortcut RET. | ||
| 75 | ;; - nicklist is now sorted ignoring the case. Voiced nicks will | ||
| 76 | ;; appear according to `erc-nicklist-voiced-position'. | ||
| 77 | |||
| 78 | ;;; Code: | ||
| 79 | |||
| 80 | (require 'erc) | ||
| 81 | (condition-case nil | ||
| 82 | (require 'erc-bbdb) | ||
| 83 | (error nil)) | ||
| 84 | (eval-when-compile (require 'cl)) | ||
| 85 | |||
| 86 | (defgroup erc-nicklist nil | ||
| 87 | "Display a list of nicknames in a separate window." | ||
| 88 | :group 'erc) | ||
| 89 | |||
| 90 | (defcustom erc-nicklist-use-icons t | ||
| 91 | "*If non-nil, display an icon instead of the name of the chat medium. | ||
| 92 | By \"chat medium\", we mean IRC, AOL, MSN, ICQ, etc." | ||
| 93 | :group 'erc-nicklist | ||
| 94 | :type 'boolean) | ||
| 95 | |||
| 96 | (defcustom erc-nicklist-icons-directory | ||
| 97 | (let ((dir (locate-library "erc-nicklist.el"))) | ||
| 98 | (when dir | ||
| 99 | (concat (file-name-directory dir) "images/"))) | ||
| 100 | "*Directory of the PNG files for chat icons. | ||
| 101 | Icons are displayed if `erc-nicklist-use-icons' is non-nil." | ||
| 102 | :group 'erc-nicklist | ||
| 103 | :type 'directory) | ||
| 104 | |||
| 105 | (defcustom erc-nicklist-voiced-position 'bottom | ||
| 106 | "*Position of voiced nicks in the nicklist. | ||
| 107 | The value can be `top', `bottom' or nil (don't sort)." | ||
| 108 | :group 'erc-nicklist | ||
| 109 | :type '(choice | ||
| 110 | (const :tag "Top" top) | ||
| 111 | (const :tag "Bottom" bottom) | ||
| 112 | (const :tag "Mixed" nil))) | ||
| 113 | |||
| 114 | (defcustom erc-nicklist-window-size 20.0 | ||
| 115 | "*The size of the nicklist window. | ||
| 116 | |||
| 117 | This specifies a percentage of the channel window width. | ||
| 118 | |||
| 119 | A negative value means the nicklist window appears on the left of the | ||
| 120 | channel window, and vice versa." | ||
| 121 | :group 'erc-nicklist | ||
| 122 | :type 'float) | ||
| 123 | |||
| 124 | |||
| 125 | (defun erc-nicklist-buffer-name (&optional buffer) | ||
| 126 | "Return the buffer name for a nicklist associated with BUFFER. | ||
| 127 | |||
| 128 | If BUFFER is nil, use the value of `current-buffer'." | ||
| 129 | (format " *%s-nicklist*" (buffer-name (or buffer (current-buffer))))) | ||
| 130 | |||
| 131 | (defun erc-nicklist-make-window () | ||
| 132 | "Create an ERC nicklist window. | ||
| 133 | |||
| 134 | See also `erc-nicklist-window-size'." | ||
| 135 | (let ((width (floor (* (window-width) (/ erc-nicklist-window-size 100.0)))) | ||
| 136 | (buffer (erc-nicklist-buffer-name)) | ||
| 137 | window) | ||
| 138 | (split-window-horizontally (- width)) | ||
| 139 | (setq window (next-window)) | ||
| 140 | (set-window-buffer window (get-buffer-create buffer)) | ||
| 141 | (with-current-buffer buffer | ||
| 142 | (set-window-dedicated-p window t)))) | ||
| 143 | |||
| 144 | |||
| 145 | (defvar erc-nicklist-images-alist '() | ||
| 146 | "Alist that maps a connection type to an icon.") | ||
| 147 | |||
| 148 | (defun erc-nicklist-insert-medium-name-or-icon (host channel is-away) | ||
| 149 | "Inserts an icon or a string identifying the current host type. | ||
| 150 | This is configured using `erc-nicklist-use-icons' and | ||
| 151 | `erc-nicklist-icons-directory'." | ||
| 152 | ;; identify the network (for bitlebee usage): | ||
| 153 | (let ((bitlbee-p (save-match-data | ||
| 154 | (string-match "\\`&bitlbee\\b" | ||
| 155 | (buffer-name channel))))) | ||
| 156 | (cond ((and bitlbee-p | ||
| 157 | (string= "login.icq.com" host)) | ||
| 158 | (if erc-nicklist-use-icons | ||
| 159 | (if is-away | ||
| 160 | (insert-image (cdr (assoc 'icq-away | ||
| 161 | erc-nicklist-images-alist))) | ||
| 162 | (insert-image (cdr (assoc 'icq | ||
| 163 | erc-nicklist-images-alist)))) | ||
| 164 | (insert "ICQ"))) | ||
| 165 | (bitlbee-p | ||
| 166 | (if erc-nicklist-use-icons | ||
| 167 | (if is-away | ||
| 168 | (insert-image (cdr (assoc 'msn-away | ||
| 169 | erc-nicklist-images-alist))) | ||
| 170 | (insert-image (cdr (assoc 'msn | ||
| 171 | erc-nicklist-images-alist)))) | ||
| 172 | (insert "MSN"))) | ||
| 173 | (t | ||
| 174 | (if erc-nicklist-use-icons | ||
| 175 | (if is-away | ||
| 176 | (insert-image (cdr (assoc 'irc-away | ||
| 177 | erc-nicklist-images-alist))) | ||
| 178 | (insert-image (cdr (assoc 'irc | ||
| 179 | erc-nicklist-images-alist)))) | ||
| 180 | (insert "IRC")))) | ||
| 181 | (insert " "))) | ||
| 182 | |||
| 183 | (defun erc-nicklist-search-for-nick (finger-host) | ||
| 184 | "Return the bitlbee-nick field for this contact given FINGER-HOST. | ||
| 185 | Seach for the BBDB record of this contact. If not found, return nil." | ||
| 186 | (when (boundp 'erc-bbdb-bitlbee-name-field) | ||
| 187 | (let ((record (car | ||
| 188 | (erc-member-if | ||
| 189 | #'(lambda (r) | ||
| 190 | (let ((fingers (bbdb-record-finger-host r))) | ||
| 191 | (when fingers | ||
| 192 | (string-match finger-host | ||
| 193 | (car (bbdb-record-finger-host r)))))) | ||
| 194 | (bbdb-records))))) | ||
| 195 | (when record | ||
| 196 | (bbdb-get-field record erc-bbdb-bitlbee-name-field))))) | ||
| 197 | |||
| 198 | (defun erc-nicklist-insert-contents (channel) | ||
| 199 | "Insert the nicklist contents, with text properties and the optional images." | ||
| 200 | (setq buffer-read-only nil) | ||
| 201 | (erase-buffer) | ||
| 202 | (dolist (u (erc-nicklist-channel-users-info channel)) | ||
| 203 | (let* ((server-user (car u)) | ||
| 204 | (channel-user (cdr u)) | ||
| 205 | (nick (erc-server-user-nickname server-user)) | ||
| 206 | (host (erc-server-user-host server-user)) | ||
| 207 | (login (erc-server-user-login server-user)) | ||
| 208 | (full-name(erc-server-user-full-name server-user)) | ||
| 209 | (info (erc-server-user-info server-user)) | ||
| 210 | (channels (erc-server-user-buffers server-user)) | ||
| 211 | (op (erc-channel-user-op channel-user)) | ||
| 212 | (voice (erc-channel-user-voice channel-user)) | ||
| 213 | (bbdb-nick (or (erc-nicklist-search-for-nick | ||
| 214 | (concat login "@" host)) | ||
| 215 | "")) | ||
| 216 | (away-status (if voice "" "\n(Away)")) | ||
| 217 | (balloon-text (concat bbdb-nick (if (string= "" bbdb-nick) | ||
| 218 | "" "\n") | ||
| 219 | "Login: " login "@" host | ||
| 220 | away-status))) | ||
| 221 | (erc-nicklist-insert-medium-name-or-icon host channel (not voice)) | ||
| 222 | (unless (or voice erc-nicklist-use-icons) | ||
| 223 | (setq nick (concat "(" nick ")"))) | ||
| 224 | (when op | ||
| 225 | (setq nick (concat nick " (OP)"))) | ||
| 226 | (insert (erc-propertize nick | ||
| 227 | 'erc-nicklist-nick nick | ||
| 228 | 'mouse-face 'highlight | ||
| 229 | 'erc-nicklist-channel channel | ||
| 230 | 'help-echo balloon-text) | ||
| 231 | "\n"))) | ||
| 232 | (erc-nicklist-mode)) | ||
| 233 | |||
| 234 | |||
| 235 | (defun erc-nicklist () | ||
| 236 | "Create an ERC nicklist buffer." | ||
| 237 | (interactive) | ||
| 238 | (let ((channel (current-buffer))) | ||
| 239 | (unless (or (not erc-nicklist-use-icons) | ||
| 240 | erc-nicklist-images-alist) | ||
| 241 | (setq erc-nicklist-images-alist | ||
| 242 | `((msn . ,(create-image (concat erc-nicklist-icons-directory | ||
| 243 | "msn-online.png"))) | ||
| 244 | (msn-away . ,(create-image (concat erc-nicklist-icons-directory | ||
| 245 | "msn-offline.png"))) | ||
| 246 | (irc . ,(create-image (concat erc-nicklist-icons-directory | ||
| 247 | "irc-online.png"))) | ||
| 248 | (irc-away . ,(create-image (concat erc-nicklist-icons-directory | ||
| 249 | "irc-offline.png"))) | ||
| 250 | (icq . ,(create-image (concat erc-nicklist-icons-directory | ||
| 251 | "icq-online.png"))) | ||
| 252 | (icq-away . ,(create-image (concat erc-nicklist-icons-directory | ||
| 253 | "icq-offline.png")))))) | ||
| 254 | (erc-nicklist-make-window) | ||
| 255 | (with-current-buffer (get-buffer (erc-nicklist-buffer-name channel)) | ||
| 256 | (erc-nicklist-insert-contents channel))) | ||
| 257 | (add-hook 'erc-channel-members-changed-hook #'erc-nicklist-update)) | ||
| 258 | |||
| 259 | (defun erc-nicklist-update () | ||
| 260 | "Update the ERC nicklist buffer." | ||
| 261 | (let ((b (get-buffer (erc-nicklist-buffer-name))) | ||
| 262 | (channel (current-buffer))) | ||
| 263 | (when b | ||
| 264 | (with-current-buffer b | ||
| 265 | (erc-nicklist-insert-contents channel))))) | ||
| 266 | |||
| 267 | (defvar erc-nicklist-mode-map | ||
| 268 | (let ((map (make-sparse-keymap))) | ||
| 269 | (define-key map (kbd "<down-mouse-3>") 'erc-nicklist-menu) | ||
| 270 | (define-key map "\C-j" 'erc-nicklist-kbd-menu) | ||
| 271 | (define-key map "q" 'erc-nicklist-quit) | ||
| 272 | (define-key map (kbd "RET") 'erc-nicklist-kbd-cmd-QUERY) | ||
| 273 | map) | ||
| 274 | "Keymap for `erc-nicklist-mode'.") | ||
| 275 | |||
| 276 | (define-derived-mode erc-nicklist-mode fundamental-mode | ||
| 277 | "Nicklist" | ||
| 278 | "Major mode for the ERC nicklist buffer." | ||
| 279 | (setq buffer-read-only t)) | ||
| 280 | |||
| 281 | (defun erc-nicklist-call-erc-command (command point buffer window) | ||
| 282 | "Call an ERC COMMAND. | ||
| 283 | |||
| 284 | Depending on what COMMAND is, it's called with one of POINT, BUFFER, | ||
| 285 | or WINDOW as arguments." | ||
| 286 | (when command | ||
| 287 | (let* ((p (text-properties-at point)) | ||
| 288 | (b (plist-get p 'erc-nicklist-channel))) | ||
| 289 | (if (memq command '(erc-nicklist-quit ignore)) | ||
| 290 | (funcall command window) | ||
| 291 | ;; EEEK! Horrble, but it's the only way we can ensure the | ||
| 292 | ;; response goes to the correct buffer. | ||
| 293 | (erc-set-active-buffer b) | ||
| 294 | (switch-to-buffer-other-window b) | ||
| 295 | (funcall command (plist-get p 'erc-nicklist-nick)))))) | ||
| 296 | |||
| 297 | (defun erc-nicklist-cmd-QUERY (user &optional server) | ||
| 298 | "Opens a query buffer with USER." | ||
| 299 | ;; FIXME: find a way to switch to that buffer afterwards... | ||
| 300 | (let ((send (if server | ||
| 301 | (format "QUERY %s %s" user server) | ||
| 302 | (format "QUERY %s" user)))) | ||
| 303 | (erc-cmd-QUERY user) | ||
| 304 | t)) | ||
| 305 | |||
| 306 | (defun erc-nicklist-kbd-cmd-QUERY (&optional window) | ||
| 307 | (interactive) | ||
| 308 | (let* ((p (text-properties-at (point))) | ||
| 309 | (server (plist-get p 'erc-nicklist-channel)) | ||
| 310 | (nick (plist-get p 'erc-nicklist-nick)) | ||
| 311 | (nick (or (and (string-match "(\\(.*\\))" nick) | ||
| 312 | (match-string 1 nick)) | ||
| 313 | nick)) | ||
| 314 | (nick (or (and (string-match "\\+\\(.*\\)" nick) | ||
| 315 | (match-string 1 nick)) | ||
| 316 | nick)) | ||
| 317 | (send (format "QUERY %s %s" nick server))) | ||
| 318 | (switch-to-buffer-other-window server) | ||
| 319 | (erc-cmd-QUERY nick))) | ||
| 320 | |||
| 321 | |||
| 322 | (defvar erc-nicklist-menu | ||
| 323 | (let ((map (make-sparse-keymap "Action"))) | ||
| 324 | (define-key map [erc-cmd-WHOIS] | ||
| 325 | '("Whois" . erc-cmd-WHOIS)) | ||
| 326 | (define-key map [erc-cmd-DEOP] | ||
| 327 | '("Deop" . erc-cmd-DEOP)) | ||
| 328 | (define-key map [erc-cmd-MSG] | ||
| 329 | '("Message" . erc-cmd-MSG)) ;; TODO! | ||
| 330 | (define-key map [erc-nicklist-cmd-QUERY] | ||
| 331 | '("Query" . erc-nicklist-kbd-cmd-QUERY)) | ||
| 332 | (define-key map [ignore] | ||
| 333 | '("Cancel" . ignore)) | ||
| 334 | (define-key map [erc-nicklist-quit] | ||
| 335 | '("Close nicklist" . erc-nicklist-quit)) | ||
| 336 | map) | ||
| 337 | "Menu keymap for the ERC nicklist.") | ||
| 338 | |||
| 339 | (defun erc-nicklist-quit (&optional window) | ||
| 340 | "Delete the ERC nicklist. | ||
| 341 | |||
| 342 | Deletes WINDOW and stops updating the nicklist buffer." | ||
| 343 | (interactive) | ||
| 344 | (let ((b (window-buffer window))) | ||
| 345 | (with-current-buffer b | ||
| 346 | (set-buffer-modified-p nil) | ||
| 347 | (kill-this-buffer) | ||
| 348 | (remove-hook 'erc-channel-members-changed-hook 'erc-nicklist-update)))) | ||
| 349 | |||
| 350 | |||
| 351 | (defun erc-nicklist-kbd-menu () | ||
| 352 | "Show the ERC nicklist menu." | ||
| 353 | (interactive) | ||
| 354 | (let* ((point (point)) | ||
| 355 | (window (selected-window)) | ||
| 356 | (buffer (current-buffer))) | ||
| 357 | (with-current-buffer buffer | ||
| 358 | (erc-nicklist-call-erc-command | ||
| 359 | (car (x-popup-menu point | ||
| 360 | erc-nicklist-menu)) | ||
| 361 | point | ||
| 362 | buffer | ||
| 363 | window)))) | ||
| 364 | |||
| 365 | (defun erc-nicklist-menu (&optional arg) | ||
| 366 | "Show the ERC nicklist menu. | ||
| 367 | |||
| 368 | ARG is a parametrized event (see `interactive')." | ||
| 369 | (interactive "e") | ||
| 370 | (let* ((point (nth 1 (cadr arg))) | ||
| 371 | (window (car (cadr arg))) | ||
| 372 | (buffer (window-buffer window))) | ||
| 373 | (with-current-buffer buffer | ||
| 374 | (erc-nicklist-call-erc-command | ||
| 375 | (car (x-popup-menu arg | ||
| 376 | erc-nicklist-menu)) | ||
| 377 | point | ||
| 378 | buffer | ||
| 379 | window)))) | ||
| 380 | |||
| 381 | |||
| 382 | (defun erc-nicklist-channel-users-info (channel) | ||
| 383 | "Return a nick-sorted list of all users on CHANNEL. | ||
| 384 | Result are elements in the form (SERVER-USER . CHANNEL-USER). The | ||
| 385 | list has all the voiced users according to | ||
| 386 | `erc-nicklist-voiced-position'." | ||
| 387 | (let* ((nicks (erc-sort-channel-users-alphabetically | ||
| 388 | (with-current-buffer channel (erc-get-channel-user-list))))) | ||
| 389 | (if erc-nicklist-voiced-position | ||
| 390 | (let ((voiced-nicks (erc-remove-if-not | ||
| 391 | #'(lambda (x) | ||
| 392 | (null (erc-channel-user-voice (cdr x)))) | ||
| 393 | nicks)) | ||
| 394 | (devoiced-nicks (erc-remove-if-not | ||
| 395 | #'(lambda (x) | ||
| 396 | (erc-channel-user-voice | ||
| 397 | (cdr x))) | ||
| 398 | nicks))) | ||
| 399 | (cond ((eq erc-nicklist-voiced-position 'top) | ||
| 400 | (append devoiced-nicks voiced-nicks)) | ||
| 401 | ((eq erc-nicklist-voiced-position 'bottom) | ||
| 402 | (append voiced-nicks devoiced-nicks)))) | ||
| 403 | nicks))) | ||
| 404 | |||
| 405 | |||
| 406 | |||
| 407 | (provide 'erc-nicklist) | ||
| 408 | |||
| 409 | ;;; erc-nicklist.el ends here | ||
| 410 | ;; | ||
| 411 | ;; Local Variables: | ||
| 412 | ;; indent-tabs-mode: t | ||
| 413 | ;; tab-width: 8 | ||
| 414 | ;; coding: utf-8 | ||
| 415 | ;; End: | ||
| 416 | |||
| 417 | ;; arch-tag: db37a256-87a7-4544-bd90-e5f16c9f5ca5 | ||