;;; 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
;; 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:
;; 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
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
: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)
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)
: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
(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))
\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'.
(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'
(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)))
(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)
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)
(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)
(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
(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)
(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*")
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
"["
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))
(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)
(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.")
"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.
(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)
(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)
(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 ()
(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"))
(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))
: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)
: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,
(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
(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)
(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
(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))))))
(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)
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)
`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."
(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.
"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)
"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)
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)
(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)
(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))))
(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
;; 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)
(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))
(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))
(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)
(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)
(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