]> code.delx.au - gnu-emacs/blobdiff - lisp/net/rcirc.el
* lisp/net/rcirc.el (rcirc-sort-nicknames): New custom.
[gnu-emacs] / lisp / net / rcirc.el
index 14fda445e45cfea6369b396981120769828afa08..ba2d3f130c676c54ff7d8a96406c9c6e41091d45 100644 (file)
@@ -1,6 +1,7 @@
 ;;; rcirc.el --- default, simple IRC client.
 
-;; Copyright (C) 2005, 2006, 2007 Free Software Foundation, Inc.
+;; Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010
+;;   Free Software Foundation, Inc.
 
 ;; Author: Ryan Yeske
 ;; URL: http://www.nongnu.org/rcirc
@@ -8,20 +9,18 @@
 
 ;; This file is part of GNU Emacs.
 
-;; This file is free software; you can redistribute it and/or modify
+;; 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 3, or (at your option)
-;; any later version.
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
 
-;; This file is distributed in the hope that it will be useful,
+;; 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.
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
 
 ;;; Commentary:
 
@@ -30,7 +29,7 @@
 ;; communication in discussion forums called channels, but also allows
 ;; one-to-one communication.
 
-;; Rcirc has simple defaults and clear and consistent behaviour.
+;; Rcirc has simple defaults and clear and consistent behavior.
 ;; Message arrival timestamps, activity notification on the modeline,
 ;; message filling, nick completion, and keepalive pings are all
 ;; enabled by default, but can easily be adjusted or turned off.  Each
@@ -87,17 +86,22 @@ used.
 VALUE must be a string.  If absent, `rcirc-default-full-name' is
 used.
 
+`:pass'
+
+VALUE must be a string.
+
 `:channels'
 
 VALUE must be a list of strings describing which channels to join
 when connecting to this server.  If absent, no channels will be
 connected to automatically."
   :type '(alist :key-type string
-               :value-type (plist :options ((nick string)
-                                            (port integer)
-                                            (user-name string)
-                                            (full-name string)
-                                            (channels (repeat string)))))
+               :value-type (plist :options ((:nick string)
+                                            (:port integer)
+                                            (:user-name string)
+                                            (:full-name string)
+                                            (:pass string)
+                                            (:channels (repeat string)))))
   :group 'rcirc)
 
 (defcustom rcirc-default-port 6667
@@ -110,15 +114,15 @@ connected to automatically."
   :type 'string
   :group 'rcirc)
 
-(defcustom rcirc-default-user-name (user-login-name)
+(defcustom rcirc-default-user-name "user"
   "Your user name sent to the server when connecting."
+  :version "24.1"                       ; changed default
   :type 'string
   :group 'rcirc)
 
-(defcustom rcirc-default-full-name (if (string= (user-full-name) "")
-                                      rcirc-default-user-name
-                                    (user-full-name))
+(defcustom rcirc-default-full-name "unknown"
   "The full name sent to the server when connecting."
+  :version "24.1"                       ; changed default
   :type 'string
   :group 'rcirc)
 
@@ -195,15 +199,16 @@ and a method symbol followed by method specific arguments.
 The valid METHOD symbols are `nickserv', `chanserv' and
 `bitlbee'.
 
-The required ARGUMENTS for each METHOD symbol are:
-  `nickserv': NICK PASSWORD
+The ARGUMENTS for each METHOD symbol are:
+  `nickserv': NICK PASSWORD [NICKSERV-NICK]
   `chanserv': NICK CHANNEL PASSWORD
   `bitlbee': NICK PASSWORD
 
-Example:
+Examples:
  ((\"freenode\" nickserv \"bob\" \"p455w0rd\")
   (\"freenode\" chanserv \"bob\" \"#bobland\" \"passwd99\")
-  (\"bitlbee\" bitlbee \"robert\" \"sekrit\"))"
+  (\"bitlbee\" bitlbee \"robert\" \"sekrit\")
+  (\"dal.net\" nickserv \"bob\" \"sekrit\" \"NickServ@services.dal.net\"))"
   :type '(alist :key-type (string :tag "Server")
                :value-type (choice (list :tag "NickServ"
                                          (const nickserv)
@@ -276,6 +281,11 @@ Called with 5 arguments, PROCESS, SENDER, RESPONSE, TARGET and TEXT."
   :type 'hook
   :group 'rcirc)
 
+(defcustom rcirc-sort-nicknames nil
+  "If non-nil, sorts nickname listings."
+  :type 'boolean
+  :group 'rcirc)
+
 (defcustom rcirc-always-use-server-buffer-flag nil
   "Non-nil means messages without a channel target will go to the server buffer."
   :type 'boolean
@@ -323,6 +333,9 @@ and the cdr part is used for encoding."
 
 (defvar rcirc-nick-table nil)
 
+(defvar rcirc-recent-quit-alist nil
+  "Alist of nicks that have recently quit or parted the channel.")
+
 (defvar rcirc-nick-syntax-table
   (let ((table (make-syntax-table text-mode-syntax-table)))
     (mapc (lambda (c) (modify-syntax-entry c "w" table))
@@ -358,6 +371,18 @@ and the cdr part is used for encoding."
 \f
 (defvar rcirc-startup-channels nil)
 
+(defvar rcirc-server-name-history nil
+  "History variable for \\[rcirc] call.")
+
+(defvar rcirc-server-port-history nil
+  "History variable for \\[rcirc] call.")
+
+(defvar rcirc-nick-name-history nil
+  "History variable for \\[rcirc] call.")
+
+(defvar rcirc-user-name-history nil
+  "History variable for \\[rcirc] call.")
+
 ;;;###autoload
 (defun rcirc (arg)
   "Connect to all servers in `rcirc-server-alist'.
@@ -370,23 +395,32 @@ If ARG is non-nil, instead prompt for connection parameters."
       (let* ((server (completing-read "IRC Server: "
                                      rcirc-server-alist
                                      nil nil
-                                     (caar rcirc-server-alist)))
+                                     (caar rcirc-server-alist)
+                                     'rcirc-server-name-history))
             (server-plist (cdr (assoc-string server rcirc-server-alist)))
             (port (read-string "IRC Port: "
                                (number-to-string
-                                (or (plist-get server-plist 'port)
-                                    rcirc-default-port))))
+                                (or (plist-get server-plist :port)
+                                    rcirc-default-port))
+                               'rcirc-server-port-history))
             (nick (read-string "IRC Nick: "
-                               (or (plist-get server-plist 'nick)
-                                   rcirc-default-nick)))
+                               (or (plist-get server-plist :nick)
+                                   rcirc-default-nick)
+                               'rcirc-nick-name-history))
+            (user-name (read-string "IRC Username: "
+                                     (or (plist-get server-plist :user-name)
+                                         rcirc-default-user-name)
+                                     'rcirc-user-name-history))
+            (pass (read-passwd "IRC Password: " nil
+                               (plist-get server-plist :pass)))
             (channels (split-string
                        (read-string "IRC Channels: "
                                     (mapconcat 'identity
                                                (plist-get server-plist
-                                                          'channels)
+                                                          :channels)
                                                " "))
                        "[, ]+" t)))
-       (rcirc-connect server port nick rcirc-default-user-name
+       (rcirc-connect server port nick user-name pass
                       rcirc-default-full-name
                       channels))
     ;; connect to servers in `rcirc-server-alist'
@@ -397,6 +431,7 @@ If ARG is non-nil, instead prompt for connection parameters."
              (port (or (plist-get (cdr c) :port) rcirc-default-port))
              (user-name (or (plist-get (cdr c) :user-name)
                             rcirc-default-user-name))
+              (pass (plist-get (cdr c) :pass))
              (full-name (or (plist-get (cdr c) :full-name)
                             rcirc-default-full-name))
              (channels (plist-get (cdr c) :channels)))
@@ -407,7 +442,7 @@ If ARG is non-nil, instead prompt for connection parameters."
                  (setq connected p)))
              (if (not connected)
                  (condition-case e
-                     (rcirc-connect server port nick user-name
+                     (rcirc-connect server port nick user-name pass
                                     full-name channels)
                    (quit (message "Quit connecting to %s" server)))
                (with-current-buffer (process-buffer connected)
@@ -417,8 +452,11 @@ If ARG is non-nil, instead prompt for connection parameters."
                              connected-servers))))))))
       (when connected-servers
        (message "Already connected to %s"
-                (concat (mapconcat 'identity (butlast connected-servers) ", ")
-                        ", and " (car (last connected-servers))))))))
+                (if (cdr connected-servers)
+                    (concat (mapconcat 'identity (butlast connected-servers) ", ")
+                            ", and "
+                            (car (last connected-servers)))
+                  (car connected-servers)))))))
 
 ;;;###autoload
 (defalias 'irc 'rcirc)
@@ -436,8 +474,8 @@ If ARG is non-nil, instead prompt for connection parameters."
 (defvar rcirc-process nil)
 
 ;;;###autoload
-(defun rcirc-connect (server &optional port nick user-name full-name
-                            startup-channels)
+(defun rcirc-connect (server &optional port nick user-name pass
+                             full-name startup-channels)
   (save-excursion
     (message "Connecting to %s..." server)
     (let* ((inhibit-eol-conversion)
@@ -486,10 +524,11 @@ If ARG is non-nil, instead prompt for connection parameters."
       (add-hook 'auto-save-hook 'rcirc-log-write)
 
       ;; identify
+      (when pass
+        (rcirc-send-string process (concat "PASS " pass)))
       (rcirc-send-string process (concat "NICK " nick))
       (rcirc-send-string process (concat "USER " user-name
-                                      " hostname servername :"
-                                      full-name))
+                                         " 0 * :" full-name))
 
       ;; setup ping timer if necessary
       (unless rcirc-keepalive-timer
@@ -522,8 +561,10 @@ last ping."
                  (rcirc-send-string process
                                     (format "PRIVMSG %s :\C-aKEEPALIVE %f\C-a"
                                             rcirc-nick
-                                            (time-to-seconds
-                                             (current-time)))))))
+                                             (if (featurep 'xemacs)
+                                                 (time-to-seconds
+                                                  (current-time))
+                                               (float-time)))))))
             (rcirc-process-list))
     ;; no processes, clean up timer
     (cancel-timer rcirc-keepalive-timer)
@@ -531,7 +572,10 @@ last ping."
 
 (defun rcirc-handler-ctcp-KEEPALIVE (process target sender message)
   (with-rcirc-process-buffer process
-    (setq header-line-format (format "%f" (- (time-to-seconds (current-time))
+    (setq header-line-format (format "%f" (- (if (featurep 'xemacs)
+                                                 (time-to-seconds
+                                                  (current-time))
+                                               (float-time))
                                             (string-to-number message))))))
 
 (defvar rcirc-debug-buffer " *rcirc debug*")
@@ -542,8 +586,7 @@ last ping."
 Debug text is written to `rcirc-debug-buffer' if `rcirc-debug-flag'
 is non-nil."
   (when rcirc-debug-flag
-    (save-excursion
-      (set-buffer (get-buffer-create rcirc-debug-buffer))
+    (with-current-buffer (get-buffer-create rcirc-debug-buffer)
       (goto-char (point-max))
       (insert (concat
               "["
@@ -763,7 +806,6 @@ If SILENT is non-nil, do not print the message in any irc buffer."
                                          rcirc-target))))))
   (let ((completion (car rcirc-nick-completions)))
     (when completion
-      (rcirc-put-nick-channel (rcirc-buffer-process) completion rcirc-target)
       (delete-region (+ rcirc-prompt-end-marker
                        rcirc-nick-completion-start-offset)
                     (point))
@@ -799,6 +841,7 @@ If SILENT is non-nil, do not print the message in any irc buffer."
 (define-key rcirc-mode-map (kbd "C-c C-m") 'rcirc-cmd-msg)
 (define-key rcirc-mode-map (kbd "C-c C-r") 'rcirc-cmd-nick) ; rename
 (define-key rcirc-mode-map (kbd "C-c C-o") 'rcirc-omit-mode)
+(define-key rcirc-mode-map (kbd "M-o") 'rcirc-omit-mode)
 (define-key rcirc-mode-map (kbd "C-c C-p") 'rcirc-cmd-part)
 (define-key rcirc-mode-map (kbd "C-c C-q") 'rcirc-cmd-query)
 (define-key rcirc-mode-map (kbd "C-c C-t") 'rcirc-cmd-topic)
@@ -815,6 +858,7 @@ If SILENT is non-nil, do not print the message in any irc buffer."
 
 (define-key rcirc-browse-url-map (kbd "RET") 'rcirc-browse-url-at-point)
 (define-key rcirc-browse-url-map (kbd "<mouse-2>") 'rcirc-browse-url-at-mouse)
+(define-key rcirc-browse-url-map [follow-link] 'mouse-face)
 
 (defvar rcirc-short-buffer-name nil
   "Generated abbreviation to use to indicate buffer activity.")
@@ -828,6 +872,10 @@ If SILENT is non-nil, do not print the message in any irc buffer."
   "Alist of lines to log to disk when `rcirc-log-flag' is non-nil.
 Each element looks like (FILENAME . TEXT).")
 
+(defvar rcirc-current-line 0
+  "The current number of responses printed in this channel.
+This number is independent of the number of lines in the buffer.")
+
 (defun rcirc-mode (process target)
   "Major mode for IRC channel buffers.
 
@@ -850,12 +898,24 @@ Each element looks like (FILENAME . TEXT).")
   (setq rcirc-last-post-time (current-time))
   (make-local-variable 'fill-paragraph-function)
   (setq fill-paragraph-function 'rcirc-fill-paragraph)
+  (make-local-variable 'rcirc-recent-quit-alist)
+  (setq rcirc-recent-quit-alist nil)
+  (make-local-variable 'rcirc-current-line)
+  (setq rcirc-current-line 0)
 
   (make-local-variable 'rcirc-short-buffer-name)
   (setq rcirc-short-buffer-name nil)
   (make-local-variable 'rcirc-urls)
   (setq use-hard-newlines t)
 
+  ;; setup for omitting responses
+  (setq buffer-invisibility-spec '())
+  (setq buffer-display-table (make-display-table))
+  (set-display-table-slot buffer-display-table 4
+                         (let ((glyph (make-glyph-code 
+                                       ?. 'font-lock-keyword-face)))
+                           (make-vector 3 glyph)))
+
   (make-local-variable 'rcirc-decode-coding-system)
   (make-local-variable 'rcirc-encode-coding-system)
   (dolist (i rcirc-coding-system-alist)
@@ -879,8 +939,6 @@ Each element looks like (FILENAME . TEXT).")
   (setq overlay-arrow-position (make-marker))
   (set-marker overlay-arrow-position nil)
 
-  (setq buffer-invisibility-spec '(rcirc-ignored-user))
-
   ;; if the user changes the major mode or kills the buffer, there is
   ;; cleanup work to do
   (add-hook 'change-major-mode-hook 'rcirc-change-major-mode-hook nil t)
@@ -1005,8 +1063,9 @@ Create the buffer if it doesn't exist."
        (let ((new-buffer (get-buffer-create
                           (rcirc-generate-new-buffer-name process target))))
          (with-current-buffer new-buffer
-           (rcirc-mode process target))
-         (rcirc-put-nick-channel process (rcirc-nick process) target)
+           (rcirc-mode process target)
+           (rcirc-put-nick-channel process (rcirc-nick process) target 
+                                   rcirc-current-line))
          new-buffer)))))
 
 (defun rcirc-send-input ()
@@ -1030,8 +1089,8 @@ Create the buffer if it doesn't exist."
     (goto-char (point-max))
     (when (not (equal 0 (- (point) rcirc-prompt-end-marker)))
       ;; delete a trailing newline
-      (when (eq (point) (point-at-bol))
-       (delete-backward-char 1))
+      (when (bolp)
+       (delete-char -1))
       (let ((input (buffer-substring-no-properties
                    rcirc-prompt-end-marker (point))))
        (dolist (line (split-string input "\n"))
@@ -1090,7 +1149,8 @@ Create the buffer if it doesn't exist."
   (interactive)
   (let ((pos (1+ (- (point) rcirc-prompt-end-marker))))
     (goto-char (point-max))
-    (let ((text (buffer-substring rcirc-prompt-end-marker (point)))
+    (let ((text (buffer-substring-no-properties rcirc-prompt-end-marker 
+                                               (point)))
           (parent (buffer-name)))
       (delete-region rcirc-prompt-end-marker (point))
       (setq rcirc-window-configuration (current-window-configuration))
@@ -1187,7 +1247,7 @@ the of the following escape sequences replaced by the described values:
   :group 'rcirc)
 
 (defcustom rcirc-omit-responses
-  '("JOIN" "PART" "QUIT")
+  '("JOIN" "PART" "QUIT" "NICK")
   "Responses which will be hidden when `rcirc-omit-mode' is enabled."
   :type '(repeat string)
   :group 'rcirc)
@@ -1281,6 +1341,51 @@ Logfiles are kept in `rcirc-log-directory'."
   :type 'boolean
   :group 'rcirc)
 
+(defcustom rcirc-omit-threshold 100
+  "Number of lines since last activity from a nick before `rcirc-omit-responses' are omitted."
+  :type 'integer
+  :group 'rcirc)
+
+(defun rcirc-last-quit-line (process nick target)
+  "Return the line number where NICK left TARGET.
+Returns nil if the information is not recorded."
+  (let ((chanbuf (rcirc-get-buffer process target)))
+    (when chanbuf
+      (cdr (assoc-string nick (with-current-buffer chanbuf
+                               rcirc-recent-quit-alist))))))
+
+(defun rcirc-last-line (process nick target)
+  "Return the line from the last activity from NICK in TARGET."
+  (let* ((chanbuf (rcirc-get-buffer process target))
+        (line (or (cdr (assoc-string target
+                                     (gethash nick (with-rcirc-server-buffer
+                                                     rcirc-nick-table)) t))
+                  (rcirc-last-quit-line process nick target))))
+    (if line
+       line
+      ;;(message "line is nil for %s in %s" nick target)
+      nil)))
+
+(defun rcirc-elapsed-lines (process nick target)
+  "Return the number of lines since activity from NICK in TARGET."
+  (let ((last-activity-line (rcirc-last-line process nick target)))
+    (when (and last-activity-line
+              (> last-activity-line 0))
+      (- rcirc-current-line last-activity-line))))
+
+(defvar rcirc-markup-text-functions
+  '(rcirc-markup-attributes
+    rcirc-markup-my-nick
+    rcirc-markup-urls
+    rcirc-markup-keywords
+    rcirc-markup-bright-nicks)
+
+  "List of functions used to manipulate text before it is printed.
+
+Each function takes two arguments, SENDER, and RESPONSE.  The
+buffer is narrowed with the text to be printed and the point is
+at the beginning of the `rcirc-text' propertized text.")
+
 (defun rcirc-print (process sender response target text &optional activity)
   "Print TEXT in the buffer associated with TARGET.
 Format based on SENDER and RESPONSE.  If ACTIVITY is non-nil,
@@ -1291,7 +1396,8 @@ record activity."
                             (when (string-match "^\\([^/]\\w*\\)[:,]" text)
                               (match-string 1 text)))
                           rcirc-ignore-list))
-              (not (string= sender (rcirc-nick process))))
+              ;; do not ignore if we sent the message
+              (not (string= sender (rcirc-nick process))))    
     (let* ((buffer (rcirc-target-buffer process sender response target text))
           (inhibit-read-only t))
       (with-current-buffer buffer
@@ -1336,16 +1442,22 @@ record activity."
                  (save-excursion (rcirc-markup-timestamp sender response))
                  (dolist (fn rcirc-markup-text-functions)
                    (save-excursion (funcall fn sender response)))
-                 (save-excursion (rcirc-markup-fill sender response)))
+                 (when rcirc-fill-flag
+                   (save-excursion (rcirc-markup-fill sender response))))
 
                (when rcirc-read-only-flag
                  (add-text-properties (point-min) (point-max)
                                       '(read-only t front-sticky t))))
              ;; make text omittable
-             (when (and (member response rcirc-omit-responses)
-                        (> start (point-min)))
-               (put-text-property (1- start) (1- rcirc-prompt-start-marker)
-                                  'invisible 'rcirc-omit))))
+             (let ((last-activity-lines (rcirc-elapsed-lines process sender target)))
+               (if (and (not (string= (rcirc-nick process) sender))
+                        (member response rcirc-omit-responses)
+                        (or (not last-activity-lines)
+                            (< rcirc-omit-threshold last-activity-lines)))
+                   (put-text-property (1- start) (1- rcirc-prompt-start-marker)
+                                      'invisible 'rcirc-omit)
+                 ;; otherwise increment the line count
+                 (setq rcirc-current-line (1+ rcirc-current-line))))))
 
          (set-marker-insertion-type rcirc-prompt-start-marker nil)
          (set-marker-insertion-type rcirc-prompt-end-marker nil)
@@ -1406,33 +1518,57 @@ record activity."
        (run-hook-with-args 'rcirc-print-hooks
                            process sender response target text)))))
 
+(defcustom rcirc-log-filename-function 'rcirc-generate-new-buffer-name
+  "A function to generate the filename used by rcirc's logging facility.
+
+It is called with two arguments, PROCESS and TARGET (see
+`rcirc-generate-new-buffer-name' for their meaning), and should
+return the filename, or nil if no logging is desired for this
+session.
+
+If the returned filename is absolute (`file-name-absolute-p'
+returns true), then it is used as-is, otherwise the resulting
+file is put into `rcirc-log-directory'."
+  :group 'rcirc
+  :type 'function)
+
 (defun rcirc-log (process sender response target text)
   "Record line in `rcirc-log', to be later written to disk."
-  (let* ((filename (rcirc-generate-new-buffer-name process target))
-        (cell (assoc-string filename rcirc-log-alist))
-        (line (concat (format-time-string rcirc-time-format)
-                      (substring-no-properties
-                       (rcirc-format-response-string process sender
-                                                     response target text))
-                      "\n")))
-    (if cell
-       (setcdr cell (concat (cdr cell) line))
-      (setq rcirc-log-alist
-           (cons (cons filename line) rcirc-log-alist)))))
+  (let ((filename (funcall rcirc-log-filename-function process target)))
+    (unless (null filename)
+      (let ((cell (assoc-string filename rcirc-log-alist))
+           (line (concat (format-time-string rcirc-time-format)
+                         (substring-no-properties
+                          (rcirc-format-response-string process sender
+                                                        response target text))
+                         "\n")))
+       (if cell
+           (setcdr cell (concat (cdr cell) line))
+         (setq rcirc-log-alist
+               (cons (cons filename line) rcirc-log-alist)))))))
 
 (defun rcirc-log-write ()
   "Flush `rcirc-log-alist' data to disk.
 
-Log data is written to `rcirc-log-directory'."
-  (make-directory rcirc-log-directory t)
+Log data is written to `rcirc-log-directory', except for
+log-files with absolute names (see `rcirc-log-filename-function')."
   (dolist (cell rcirc-log-alist)
-    (with-temp-buffer
-      (insert (cdr cell))
-      (write-region (point-min) (point-max)
-                   (concat rcirc-log-directory "/" (car cell))
-                   t 'quiet)))
+    (let ((filename (expand-file-name (car cell) rcirc-log-directory))
+         (coding-system-for-write 'utf-8))
+      (make-directory (file-name-directory filename) t)
+      (with-temp-buffer
+       (insert (cdr cell))
+       (write-region (point-min) (point-max) filename t 'quiet))))
   (setq rcirc-log-alist nil))
 
+(defun rcirc-view-log-file ()
+  "View logfile corresponding to the current buffer."
+  (interactive)
+  (find-file-other-window 
+   (expand-file-name (funcall rcirc-log-filename-function 
+                             (rcirc-buffer-process) rcirc-target)
+                    rcirc-log-directory)))
+
 (defun rcirc-join-channels (process channels)
   "Join CHANNELS."
   (save-window-excursion
@@ -1456,15 +1592,19 @@ Log data is written to `rcirc-log-directory'."
     (mapcar (lambda (x) (car x))
            (gethash nick rcirc-nick-table))))
 
-(defun rcirc-put-nick-channel (process nick channel)
-  "Add CHANNEL to list associated with NICK."
+(defun rcirc-put-nick-channel (process nick channel &optional line)
+  "Add CHANNEL to list associated with NICK.
+Update the associated linestamp if LINE is non-nil.
+
+If the record doesn't exist, and LINE is nil, set the linestamp
+to zero."
   (let ((nick (rcirc-user-nick nick)))
     (with-rcirc-process-buffer process
       (let* ((chans (gethash nick rcirc-nick-table))
             (record (assoc-string channel chans t)))
        (if record
-           (setcdr record (current-time))
-         (puthash nick (cons (cons channel (current-time))
+           (when line (setcdr record line))
+         (puthash nick (cons (cons channel (or line 0))
                              chans)
                   rcirc-nick-table))))))
 
@@ -1500,7 +1640,10 @@ Log data is written to `rcirc-log-directory'."
                     (setq nicks (cons (cons k (cdr record)) nicks)))))
             rcirc-nick-table)
            (mapcar (lambda (x) (car x))
-                   (sort nicks (lambda (x y) (time-less-p (cdr y) (cdr x)))))))
+                   (sort nicks (lambda (x y)
+                                 (let ((lx (or (cdr x) 0))
+                                       (ly (or (cdr y) 0)))
+                                   (< ly lx)))))))
       (list target))))
 
 (defun rcirc-ignore-update-automatic (nick)
@@ -1512,11 +1655,42 @@ if NICK is also on `rcirc-ignore-list-automatic'."
            rcirc-ignore-list
            (delete nick rcirc-ignore-list))))
 \f
+(defun rcirc-nickname< (s1 s2)
+  "Compares two IRC nicknames.  Operator nicknames (@) are
+considered less than voiced nicknames (+).  Any other nicknames
+are greater than voiced nicknames.
+
+Returns t if S1 is less than S2, otherwise nil.
+
+The comparison is case-insensitive."
+  (setq s1 (downcase s1)
+        s2 (downcase s2))
+  (let* ((s1-op (eq ?@ (string-to-char s1)))
+         (s2-op (eq ?@ (string-to-char s2))))
+    (if s1-op
+        (if s2-op
+            (string< (substring s1 1) (substring s2 1))
+          t)
+      (if s2-op
+          nil
+        (string< s1 s2)))))
+
+(defun rcirc-sort-nicknames-join (input sep)
+  "Takes a string of nicknames and returns the string with the
+nicknames sorted.
+
+INPUT is a string containing nicknames separated by SEP.
+
+This function is non-destructive, sorting a copy of the input."
+  (let ((parts (split-string input sep t))
+        copy)
+    (setq copy (sort parts 'rcirc-nickname<))
+    (mapconcat 'identity copy sep)))
+\f
 ;;; activity tracking
 (defvar rcirc-track-minor-mode-map (make-sparse-keymap)
   "Keymap for rcirc track minor mode.")
 
-(define-key rcirc-track-minor-mode-map (kbd "C-c `") 'rcirc-next-active-buffer)
 (define-key rcirc-track-minor-mode-map (kbd "C-c C-@") 'rcirc-next-active-buffer)
 (define-key rcirc-track-minor-mode-map (kbd "C-c C-SPC") 'rcirc-next-active-buffer)
 
@@ -1579,15 +1753,13 @@ Uninteresting lines are those whose responses are listed in
 `rcirc-omit-responses'."
   (interactive)
   (setq rcirc-omit-mode (not rcirc-omit-mode))
-  (let ((line (1- (count-screen-lines (point) (window-start)))))
-    (if rcirc-omit-mode
-       (progn
-         (add-to-invisibility-spec 'rcirc-omit)
-         (message "Rcirc-Omit mode enabled"))
-      (remove-from-invisibility-spec 'rcirc-omit)
-      (message "Rcirc-Omit mode disabled"))
-    (recenter line))
-  (force-mode-line-update))
+  (if rcirc-omit-mode
+      (progn
+       (add-to-invisibility-spec '(rcirc-omit . nil))
+       (message "Rcirc-Omit mode enabled"))
+    (remove-from-invisibility-spec '(rcirc-omit . nil))
+    (message "Rcirc-Omit mode disabled"))
+    (recenter (when (> (point) rcirc-prompt-start-marker) -1)))
 
 (defun rcirc-switch-to-server-buffer ()
   "Switch to the server buffer associated with current channel buffer."
@@ -1622,16 +1794,19 @@ With prefix ARG, go to the next low priority buffer with activity."
         (hipri (cdr pair)))
     (if (or (and (not arg) hipri)
            (and arg lopri))
-       (switch-to-buffer (car (if arg lopri hipri)) t)
+       (progn
+         (switch-to-buffer (car (if arg lopri hipri)))
+         (when (> (point) rcirc-prompt-start-marker)
+           (recenter -1)))
       (if (eq major-mode 'rcirc-mode)
          (switch-to-buffer (rcirc-non-irc-buffer))
-       (message (concat
-                 "No IRC activity."
-                 (when lopri
-                   (concat
-                    "  Type C-u "
-                    (key-description (this-command-keys))
-                    " for low priority activity."))))))))
+       (message "%s" (concat
+                      "No IRC activity."
+                      (when lopri
+                        (concat
+                         "  Type C-u "
+                         (key-description (this-command-keys))
+                         " for low priority activity."))))))))
 
 (defvar rcirc-activity-hooks nil
   "Hook to be run when there is channel activity.
@@ -1901,7 +2076,7 @@ activity.  Only run if the buffer is not visible and
   "Display list of names in CHANNEL or in current channel if CHANNEL is nil.
 If called interactively, prompt for a channel when prefix arg is supplied."
   (interactive "P")
-  (if (interactive-p)
+  (if (called-interactively-p 'interactive)
       (if channel
           (setq channel (read-string "List names in channel: " target))))
   (let ((channel (if (> (length channel) 0)
@@ -1913,7 +2088,7 @@ If called interactively, prompt for a channel when prefix arg is supplied."
   "List TOPIC for the TARGET channel.
 With a prefix arg, prompt for new topic."
   (interactive "P")
-  (if (and (interactive-p) topic)
+  (if (and (called-interactively-p 'interactive) topic)
       (setq topic (read-string "New Topic: " rcirc-topic)))
   (rcirc-send-string process (concat "TOPIC " target
                                      (when (> (length topic) 0)
@@ -2039,23 +2214,24 @@ keywords when no KEYWORD is given."
     string))
 
 (defvar rcirc-url-regexp
-  (rx-to-string
-   `(and word-boundary
-        (or (and
-             (or (and (or "http" "https" "ftp" "file" "gopher" "news"
-                          "telnet" "wais" "mailto")
-                      "://")
-                 "www.")
-             (1+ (char "-a-zA-Z0-9_."))
-             (1+ (char "-a-zA-Z0-9_"))
-             (optional ":" (1+ (char "0-9"))))
-            (and (1+ (char "-a-zA-Z0-9_."))
-                 (or ".com" ".net" ".org")
-                 word-boundary))
-        (optional
-         (and "/"
-              (1+ (char "-a-zA-Z0-9_='!?#$\@~`%&*+|\\/:;.,{}[]()"))
-              (char "-a-zA-Z0-9_=#$\@~`%&*+|\\/:;{}[]()")))))
+  (concat
+   "\\b\\(\\(www\\.\\|\\(s?https?\\|ftp\\|file\\|gopher\\|"
+   "nntp\\|news\\|telnet\\|wais\\|mailto\\|info\\):\\)"
+   "\\(//[-a-z0-9_.]+:[0-9]*\\)?"
+   (if (string-match "[[:digit:]]" "1") ;; Support POSIX?
+       (let ((chars "-a-z0-9_=#$@~%&*+\\/[:word:]")
+            (punct "!?:;.,"))
+        (concat
+         "\\(?:"
+         ;; Match paired parentheses, e.g. in Wikipedia URLs:
+         "[" chars punct "]+" "(" "[" chars punct "]+" "[" chars "]*)" "[" chars "]"
+         "\\|"
+         "[" chars punct     "]+" "[" chars "]"
+         "\\)"))
+     (concat ;; XEmacs 21.4 doesn't support POSIX.
+      "\\([-a-z0-9_=!?#$@~%&*+\\/:;.,]\\|\\w\\)+"
+      "\\([-a-z0-9_=#$@~%&*+\\/]\\|\\w\\)"))
+   "\\)")
   "Regexp matching URLs.  Set to nil to disable URL features in rcirc.")
 
 (defun rcirc-browse-url (&optional arg)
@@ -2083,20 +2259,6 @@ keywords when no KEYWORD is given."
       (rcirc-browse-url-at-point (posn-point position)))))
 
 \f
-(defvar rcirc-markup-text-functions
-  '(rcirc-markup-attributes
-    rcirc-markup-my-nick
-    rcirc-markup-urls
-    rcirc-markup-keywords
-    rcirc-markup-bright-nicks
-    rcirc-markup-fill)
-
-  "List of functions used to manipulate text before it is printed.
-
-Each function takes two arguments, SENDER, RESPONSE.  The buffer
-is narrowed with the text to be printed and the point is at the
-beginning of the `rcirc-text' propertized text.")
-
 (defun rcirc-markup-timestamp (sender response)
   (goto-char (point-min))
   (insert (rcirc-facify (format-time-string rcirc-time-format)
@@ -2113,7 +2275,7 @@ beginning of the `rcirc-text' propertized text.")
     (when (not (eq ?\C-o (char-before (match-end 2))))
       (delete-region (match-beginning 2) (match-end 2)))
     (delete-region (match-beginning 1) (match-end 1))
-    (goto-char (1+ (match-beginning 1))))
+    (goto-char (match-beginning 1)))
   ;; remove the ^O characters now
   (while (re-search-forward "\C-o+" nil t)
     (delete-region (match-beginning 0) (match-end 0))))
@@ -2169,11 +2331,13 @@ beginning of the `rcirc-text' propertized text.")
     (let ((fill-prefix
           (or rcirc-fill-prefix
               (make-string (- (point) (line-beginning-position)) ?\s)))
-         (fill-column (cond ((eq rcirc-fill-column 'frame-width)
-                             (1- (frame-width)))
-                            (rcirc-fill-column
-                             rcirc-fill-column)
-                            (t fill-column))))
+         (fill-column (- (cond ((eq rcirc-fill-column 'frame-width)
+                                (1- (frame-width)))
+                               (rcirc-fill-column
+                                rcirc-fill-column)
+                               (t fill-column))
+                         ;; make sure ... doesn't cause line wrapping
+                         3)))          
       (fill-region (point) (point-max) nil t))))
 \f
 ;;; handlers
@@ -2183,7 +2347,6 @@ beginning of the `rcirc-text' propertized text.")
 ;; verbatim
 (defun rcirc-handler-001 (process sender args text)
   (rcirc-handler-generic process "001" sender args text)
-  ;; set the real server name
   (with-rcirc-process-buffer process
     (setq rcirc-connecting nil)
     (rcirc-reschedule-timeout process)
@@ -2201,9 +2364,9 @@ beginning of the `rcirc-text' propertized text.")
     (if (string-match "^\C-a\\(.*\\)\C-a$" message)
         (rcirc-handler-CTCP process target sender (match-string 1 message))
       (rcirc-print process sender "PRIVMSG" target message t))
-    ;; update nick timestamp
-    (if (member target (rcirc-nick-channels process sender))
-        (rcirc-put-nick-channel process sender target))))
+    ;; update nick linestamp
+    (with-current-buffer (rcirc-get-buffer process target t)
+      (rcirc-put-nick-channel process sender target rcirc-current-line))))
 
 (defun rcirc-handler-NOTICE (process sender args text)
   (let ((target (car args))
@@ -2228,21 +2391,29 @@ beginning of the `rcirc-text' propertized text.")
 
 (defun rcirc-handler-JOIN (process sender args text)
   (let ((channel (car args)))
-    (rcirc-get-buffer-create process channel)
+    (with-current-buffer (rcirc-get-buffer-create process channel)
+      ;; when recently rejoining, restore the linestamp
+      (rcirc-put-nick-channel process sender channel
+                             (let ((last-activity-lines
+                                    (rcirc-elapsed-lines process sender channel)))
+                               (when (and last-activity-lines
+                                          (< last-activity-lines rcirc-omit-threshold))
+                                 (rcirc-last-line process sender channel)))))
+
     (rcirc-print process sender "JOIN" channel "")
 
     ;; print in private chat buffer if it exists
     (when (rcirc-get-buffer (rcirc-buffer-process) sender)
-      (rcirc-print process sender "JOIN" sender channel))
-
-    (rcirc-put-nick-channel process sender channel)))
+      (rcirc-print process sender "JOIN" sender channel))))
 
 ;; PART and KICK are handled the same way
 (defun rcirc-handler-PART-or-KICK (process response channel sender nick args)
   (rcirc-ignore-update-automatic nick)
   (if (not (string= nick (rcirc-nick process)))
       ;; this is someone else leaving
-      (rcirc-remove-nick-channel process nick channel)
+      (progn
+       (rcirc-maybe-remember-nick-quit process nick channel)
+       (rcirc-remove-nick-channel process nick channel))
     ;; this is us leaving
     (mapc (lambda (n)
            (rcirc-remove-nick-channel process n channel))
@@ -2276,16 +2447,30 @@ beginning of the `rcirc-text' propertized text.")
 
     (rcirc-handler-PART-or-KICK process "KICK" channel sender nick reason)))
 
+(defun rcirc-maybe-remember-nick-quit (process nick channel)
+  "Remember NICK as leaving CHANNEL if they recently spoke."
+  (let ((elapsed-lines (rcirc-elapsed-lines process nick channel)))
+    (when (and elapsed-lines
+              (< elapsed-lines rcirc-omit-threshold))
+      (let ((buffer (rcirc-get-buffer process channel)))
+       (when buffer
+         (with-current-buffer buffer
+           (let ((record (assoc-string nick rcirc-recent-quit-alist t))
+                 (line (rcirc-last-line process nick channel)))
+             (if record
+                 (setcdr record line)
+               (setq rcirc-recent-quit-alist
+                     (cons (cons nick line)
+                           rcirc-recent-quit-alist))))))))))
+
 (defun rcirc-handler-QUIT (process sender args text)
   (rcirc-ignore-update-automatic sender)
   (mapc (lambda (channel)
-         (rcirc-print process sender "QUIT" channel (apply 'concat args)))
+         ;; broadcast quit message each channel
+         (rcirc-print process sender "QUIT" channel (apply 'concat args))
+         ;; record nick in quit table if they recently spoke
+         (rcirc-maybe-remember-nick-quit process sender channel))
        (rcirc-nick-channels process sender))
-
-  ;; print in private chat buffer if it exists
-  (when (rcirc-get-buffer (rcirc-buffer-process) sender)
-    (rcirc-print process sender "QUIT" sender (apply 'concat args)))
-
   (rcirc-nick-remove process sender))
 
 (defun rcirc-handler-NICK (process sender args text)
@@ -2406,7 +2591,10 @@ beginning of the `rcirc-text' propertized text.")
          (buffer (rcirc-get-temp-buffer-create process channel)))
     (with-current-buffer buffer
       (rcirc-print process sender "NAMES" channel
-                   (buffer-substring (point-min) (point-max))))
+                   (let ((content (buffer-substring (point-min) (point-max))))
+                     (if rcirc-sort-nicknames
+                         (rcirc-sort-nicknames-join content " ")
+                       content))))
     (kill-buffer buffer)))
 
 (defun rcirc-handler-433 (process sender args text)
@@ -2432,9 +2620,8 @@ Passwords are stored in `rcirc-authinfo' (which see)."
          (cond ((equal method 'nickserv)
                 (rcirc-send-string
                  process
-                 (concat
-                  "PRIVMSG nickserv :identify "
-                  (car args))))
+                 (concat "PRIVMSG " (or (cadr args) "nickserv")
+                          " :identify " (car args))))
                ((equal method 'chanserv)
                 (rcirc-send-string
                  process