summaryrefslogtreecommitdiff
path: root/lisp/erc/erc-list.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/erc/erc-list.el')
-rw-r--r--lisp/erc/erc-list.el396
1 files changed, 396 insertions, 0 deletions
diff --git a/lisp/erc/erc-list.el b/lisp/erc/erc-list.el
new file mode 100644
index 00000000000..785e4d19750
--- /dev/null
+++ b/lisp/erc/erc-list.el
@@ -0,0 +1,396 @@
+;;; erc-list.el --- Provide a faster channel listing mechanism
+
+;; Copyright (C) 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
+;; Copyright (C) 2004 Brian Palmer
+
+;; Author: Mario Lang <mlang@lexx.delysid.org>
+;; Keywords: comm
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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, or (at your option)
+;; any later version.
+
+;; GNU Emacs 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 GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
+
+;;; Commentary:
+
+;; This file provides a simple derived mode for viewing Channel lists.
+;; It also serves as a demonstration of how the new server hook facility
+;; can be used.
+
+;;; Code:
+
+(require 'erc)
+(require 'erc-nets)
+(require 'sort)
+(unless (fboundp 'make-overlay)
+ (require 'overlay))
+(eval-when-compile (require 'cl))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; User customizable variables.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defgroup erc-list nil
+ "Display IRC channels in another window when using /LIST"
+ :group 'erc)
+
+(defcustom erc-chanlist-progress-message t
+ "*Show progress message while accumulating channel list."
+ :group 'erc-list
+ :type 'boolean)
+
+(defcustom erc-no-list-networks nil
+ "*A list of network names on which the /LIST command refuses to work."
+ :group 'erc-list
+ :type '(repeat string))
+
+(defcustom erc-chanlist-frame-parameters nil
+ "*If nil, the channel list is displayed in a new window; if non-nil,
+this variable holds the frame parameters used to make a frame to
+display the channel list."
+ :group 'erc-list
+ :type 'list)
+
+(defcustom erc-chanlist-hide-modeline nil
+ "*If nil, the channel list buffer has a modeline, otherwise the modeline is hidden."
+ :group 'erc-list
+ :type 'boolean)
+
+(defface erc-chanlist-header-face '((t (:bold t)))
+ "Face used for the headers in erc's channel list."
+ :group 'erc-faces)
+
+(defface erc-chanlist-odd-line-face '((t (:inverse-video t)))
+ "Face used for the odd lines in erc's channel list."
+ :group 'erc-faces)
+
+(defface erc-chanlist-even-line-face '((t (:inverse-video nil)))
+ "Face used for the even lines in erc's channel list."
+ :group 'erc-faces)
+
+(defface erc-chanlist-highlight '((t (:foreground "red")))
+ "Face used to highlight the current line in the channel list."
+ :group 'erc-faces)
+
+;; This should perhaps be a defface that inherits values from the highlight face
+;; but xemacs does not support inheritance
+(defcustom erc-chanlist-highlight-face 'erc-chanlist-highlight
+ "Face used for highlighting the current line in a list."
+ :type 'face
+ :group 'erc-faces)
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; All variables below this line are for internal use only.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defvar erc-chanlist-channel-line-regexp "^\\([#&\\*][^ \t\n]*\\)\\s-+[0-9]+"
+ "Regexp that matches a channel line in the channel list buffer.")
+
+(defvar erc-chanlist-buffer nil)
+(make-variable-buffer-local 'erc-chanlist-buffer)
+
+(defvar erc-chanlist-last-time 0
+ "A time value used to throttle the progress indicator.")
+
+(defvar erc-chanlist-frame nil
+ "The frame displaying the most recent channel list buffer.")
+
+(defvar erc-chanlist-sort-state 'channel
+ "The sort mode of the channel list buffer. Either 'channel or 'users.")
+(make-variable-buffer-local 'erc-chanlist-sort-state)
+
+(defvar erc-chanlist-highlight-overlay nil
+ "The overlay used for erc chanlist highlighting")
+(make-variable-buffer-local 'erc-chanlist-highlight-overlay)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Define erc-chanlist-mode.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defcustom erc-chanlist-mode-hook nil
+ "Hook run by erc-chanlist-mode."
+ :group 'erc-list
+ :type 'hook)
+
+(define-derived-mode erc-chanlist-mode fundamental-mode "ERC Channel List"
+ "Mode for viewing a channel list of a particular server.
+
+\\{erc-chanlist-mode-map}"
+ (local-set-key "\C-c\C-j" 'erc-join-channel)
+ (local-set-key "j" 'erc-chanlist-join-channel)
+ (local-set-key "n" 'next-line)
+ (local-set-key "p" 'previous-line)
+ (local-set-key "q" 'erc-chanlist-quit)
+ (local-set-key "s" 'erc-chanlist-toggle-sort-state)
+ (local-set-key "t" 'toggle-truncate-lines)
+ (setq erc-chanlist-sort-state 'channel)
+ (setq truncate-lines t)
+ (add-hook 'post-command-hook 'erc-chanlist-post-command-hook 'append 'local))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Functions.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;###autoload
+(defun erc-cmd-LIST (&rest channel)
+ "Display a buffer containing a list of channels on the current server.
+Optional argument CHANNEL specifies a single channel to list (instead of every
+available channel)."
+ (interactive
+ (remove "" (split-string
+ (read-from-minibuffer "List channels (RET for all): ") " ")))
+ (if (and (null channel)
+ (erc-member-ignore-case (erc-network-name) erc-no-list-networks))
+ (erc-display-line "ERC is configured not to allow the /LIST command on this network!"
+ (current-buffer))
+ (erc-display-line (erc-make-notice (concat "Listing channel"
+ (if channel
+ "."
+ "s. This may take a while."))))
+ (erc-chanlist channel))
+ t)
+
+;;;###autoload
+(defun erc-chanlist (&optional channels)
+ "Show a channel listing of the current server in a special mode.
+Please note that this function only works with IRC servers which conform
+to RFC and send the LIST header (#321) at start of list transmission."
+ (interactive)
+ (with-current-buffer (erc-server-buffer)
+ (erc-once-with-server-event
+ 321
+ '(progn
+ (add-hook 'erc-server-322-functions 'erc-chanlist-322 nil t)
+
+ (erc-once-with-server-event
+ 323
+ '(progn
+ (remove-hook 'erc-server-322-functions 'erc-chanlist-322 t)
+ (let ((buf erc-chanlist-buffer))
+ (if (not (buffer-live-p buf))
+ (error "`erc-chanlist-buffer' does not refer to a live buffer"))
+
+ (set-buffer buf)
+ (buffer-disable-undo)
+ (let (buffer-read-only
+ (sort-fold-case t))
+ (sort-lines nil (point-min) (point-max))
+ (setq erc-chanlist-sort-state 'channel)
+
+ (let ((sum (count-lines (point-min) (point-max))))
+ (goto-char (point-min))
+ (insert (substitute-command-keys
+ (concat "'\\[erc-chanlist-toggle-sort-state]' toggle sort mode.\n"
+ "'\\[erc-chanlist-quit]' kill this buffer.\n"
+ "'\\[toggle-truncate-lines]' toggle line truncation.\n"
+ "'\\[erc-chanlist-join-channel]' join the channel listed on the current line.\n\n")))
+ (insert (format "%d channels (sorted by %s).\n\n"
+ sum (if (eq erc-chanlist-sort-state 'channel)
+ "channel name"
+ "number of users"))))
+
+ (insert (format "%-25s%5s %s\n------------------------ ----- ----------------------------\n"
+ "Channel"
+ "Users"
+ "Topic"))
+
+ ;; Display the channel list buffer.
+ (if erc-chanlist-frame-parameters
+ (progn
+ (if (or (null erc-chanlist-frame)
+ (not (frame-live-p erc-chanlist-frame)))
+ (setq erc-chanlist-frame
+ (make-frame `((name . ,(format "Channels on %s"
+ erc-session-server))
+ ,@erc-chanlist-frame-parameters))))
+ (select-frame erc-chanlist-frame)
+ (switch-to-buffer buf)
+ (erc-prettify-channel-list))
+ (pop-to-buffer buf)
+ (erc-prettify-channel-list))))
+ (goto-char (point-min))
+ (search-forward-regexp "^------" nil t)
+ (forward-line 1)
+ (erc-chanlist-highlight-line)
+ (message "")
+ t))
+
+ (setq erc-chanlist-buffer (get-buffer-create
+ (format "*Channels on %s*"
+ (erc-response.sender parsed))))
+ (with-current-buffer erc-chanlist-buffer
+ (setq buffer-read-only nil)
+ (erase-buffer)
+ (erc-chanlist-mode)
+ (setq erc-server-process proc)
+ (if erc-chanlist-hide-modeline
+ (setq mode-line-format nil))
+ (setq buffer-read-only t))
+ t))
+
+ ;; Now that we've setup our callbacks, pull the trigger.
+ (if (interactive-p)
+ (message "Collecting channel list for server %s" erc-session-server))
+ (erc-server-send (if (null channels)
+ "LIST"
+ (concat "LIST "
+ (mapconcat #'identity channels ","))))))
+
+(defun erc-chanlist-322 (proc parsed)
+ "Process an IRC 322 message.
+
+The message carries information about one channel for the LIST
+command."
+ (multiple-value-bind (channel num-users)
+ (cdr (erc-response.command-args parsed))
+ (let ((topic (erc-response.contents parsed)))
+ (with-current-buffer erc-chanlist-buffer
+ (save-excursion
+ (goto-char (point-max))
+ (let (buffer-read-only)
+ (insert (format "%-26s%4s %s\n" (erc-controls-strip channel)
+ num-users
+ (erc-controls-strip topic))))
+
+ ;; Maybe display a progress indicator in the minibuffer.
+ (when (and erc-chanlist-progress-message
+ (> (erc-time-diff
+ erc-chanlist-last-time (erc-current-time))
+ 3))
+ (setq erc-chanlist-last-time (erc-current-time))
+ (message "Accumulating channel list ... %c"
+ (aref [?/ ?| ?\\ ?- ?! ?O ?o] (random 7))))
+
+ ;; Return success to prevent other hook functions from being run.
+ t)))))
+
+(defun erc-chanlist-post-command-hook ()
+ "Keep the current line highlighted."
+ (ignore-errors
+ (save-excursion
+ (beginning-of-line)
+ (if (looking-at erc-chanlist-channel-line-regexp)
+ (erc-chanlist-highlight-line)
+ (erc-chanlist-dehighlight-line)))))
+
+(defun erc-chanlist-highlight-line ()
+ "Highlight the current line."
+ (unless erc-chanlist-highlight-overlay
+ (setq erc-chanlist-highlight-overlay
+ (make-overlay (point-min) (point-min)))
+ ;; Detach it from the buffer.
+ (delete-overlay erc-chanlist-highlight-overlay)
+ (overlay-put erc-chanlist-highlight-overlay
+ 'face erc-chanlist-highlight-face)
+ ;; Expressly put it at a higher priority than the text
+ ;; properties used for faces later on. Gnu emacs promises that
+ ;; right now overlays are higher priority than text properties,
+ ;; but why take chances?
+ (overlay-put erc-chanlist-highlight-overlay 'priority 1))
+ (move-overlay erc-chanlist-highlight-overlay (point) (1+ (point-at-eol))))
+
+(defun erc-chanlist-dehighlight-line ()
+ "Remove the line highlighting."
+ (delete-overlay erc-chanlist-highlight-overlay))
+
+(defun erc-prettify-channel-list ()
+ "Make the channel list buffer look pretty.
+When this function runs, the current buffer must be the channel
+list buffer, or it does nothing."
+ (if (eq major-mode 'erc-chanlist-mode)
+ (save-excursion
+ (let ((inhibit-read-only t))
+ (goto-char (point-min))
+ (when (search-forward-regexp "^-------" nil t)
+ (add-text-properties
+ (point-min) (1+ (point-at-eol)) '(face erc-chanlist-header-face))
+ (forward-line 1))
+
+ (while (not (eobp))
+ (add-text-properties
+ (point) (1+ (point-at-eol)) '(face erc-chanlist-odd-line-face))
+ (forward-line 1)
+ (unless (eobp)
+ (add-text-properties
+ (point) (1+ (point-at-eol)) '(face erc-chanlist-even-line-face)))
+ (forward-line 1))))))
+
+(defun erc-chanlist-toggle-sort-state ()
+ "Toggle the channel list buffer sorting method.
+Either sort by channel names or by number of users in each channel."
+ (interactive)
+ (let ((inhibit-read-only t)
+ (sort-fold-case t))
+ (save-excursion
+ (goto-char (point-min))
+ (search-forward-regexp "^-----" nil t)
+ (forward-line 1)
+ (unless (eobp)
+ (if (eq erc-chanlist-sort-state 'channel)
+ (progn
+ (sort-numeric-fields 2 (point) (point-max))
+ (reverse-region (point) (point-max))
+ (setq erc-chanlist-sort-state 'users))
+ (sort-lines nil (point) (point-max))
+ (setq erc-chanlist-sort-state 'channel))
+
+ (goto-char (point-min))
+ (if (search-forward-regexp "^[0-9]+ channels (sorted by \\(.*\\)).$"
+ nil t)
+ (replace-match (if (eq erc-chanlist-sort-state 'channel)
+ "channel name"
+ "number of users")
+ nil nil nil 1))
+
+ (goto-char (point-min))
+ (search-forward-regexp "^-----" nil t)
+ (forward-line 1)
+ (recenter -1)
+
+ (erc-prettify-channel-list)))))
+
+(defun erc-chanlist-quit ()
+ "Quit Chanlist mode.
+Kill the channel list buffer, window, and frame (if there's a frame
+devoted to the channel list)."
+ (interactive)
+ (kill-buffer (current-buffer))
+ (if (eq (selected-frame) erc-chanlist-frame)
+ (delete-frame)
+ (delete-window)))
+
+(defun erc-chanlist-join-channel ()
+ "Join the channel listed on the current line of the channel list buffer.
+Private channels, which are shown as asterisks (*), are ignored."
+ (interactive)
+ (save-excursion
+ (beginning-of-line)
+ (when (looking-at erc-chanlist-channel-line-regexp)
+ (let ((channel-name (match-string 1)))
+ (when (and (stringp channel-name)
+ (not (string= channel-name "*")))
+ (run-at-time 0.5 nil 'erc-join-channel channel-name))))))
+
+(provide 'erc-list)
+
+;;; erc-list.el ends here
+;;
+;; Local Variables:
+;; indent-tabs-mode: t
+;; tab-width: 8
+;; End:
+
+;; arch-tag: 4a13196a-a61b-465a-9926-044dfbc7e5ff