]> code.delx.au - gnu-emacs/blobdiff - lisp/erc/erc-backend.el
Merge emacs-25 into master (using imerge)
[gnu-emacs] / lisp / erc / erc-backend.el
index 90b96d7c763bb9425a04a77f70bffe70acbe38d5..e07dc90fcdcb1f05d2d48eb72fc23941c3889860 100644 (file)
@@ -1,10 +1,10 @@
 ;;; erc-backend.el --- Backend network communication for ERC
 
-;; Copyright (C) 2004-2012 Free Software Foundation, Inc.
+;; Copyright (C) 2004-2015 Free Software Foundation, Inc.
 
 ;; Filename: erc-backend.el
 ;; Author: Lawrence Mitchell <wence@gmx.li>
-;; Maintainer: FSF
+;; Maintainer: emacs-devel@gnu.org
 ;; Created: 2004-05-7
 ;; Keywords: IRC chat client internet
 
 ;;; Code:
 
 (require 'erc-compat)
-(eval-when-compile (require 'cl))
+(eval-when-compile (require 'cl-lib))
 ;; There's a fairly strong mutual dependency between erc.el and erc-backend.el.
 ;; Luckily, erc.el does not need erc-backend.el for macroexpansion whereas the
 ;; reverse is true:
-(eval-when-compile (provide 'erc-backend) (require 'erc))
+(provide 'erc-backend)
+(require 'erc)
 
 ;;;; Variables and options
 
 (defvar erc-server-responses (make-hash-table :test #'equal)
   "Hashtable mapping server responses to their handler hooks.")
 
-(defstruct (erc-response (:conc-name erc-response.))
+(cl-defstruct (erc-response (:conc-name erc-response.))
   (unparsed "" :type string)
   (sender "" :type string)
   (command "" :type string)
@@ -369,13 +370,13 @@ This overrides `erc-server-coding-system' depending on the
 current target as returned by `erc-default-target'.
 
 Example: If you know that the channel #linux-ru uses the coding-system
-`cyrillic-koi8', then add '(\"#linux-ru\" . cyrillic-koi8) to the
+`cyrillic-koi8', then add (\"#linux-ru\" . cyrillic-koi8) to the
 alist."
   :group 'erc-server
   :type '(repeat (cons (string :tag "Target")
                        coding-system)))
 
-(defcustom erc-server-connect-function 'open-network-stream
+(defcustom erc-server-connect-function 'erc-open-network-stream
   "Function used to initiate a connection.
 It should take same arguments as `open-network-stream' does."
   :group 'erc-server
@@ -492,62 +493,75 @@ The current buffer is given by BUFFER."
                                      4 erc-server-send-ping-interval
                                      #'erc-server-send-ping
                                      buffer))
-      (setq erc-server-ping-timer-alist (cons (cons buffer
-                                                    erc-server-ping-handler)
-                                              erc-server-ping-timer-alist)))))
 
-(defun erc-server-process-alive ()
-  "Return non-nil when `erc-server-process' is open or running."
-  (and erc-server-process
-       (processp erc-server-process)
-       (memq (process-status erc-server-process) '(run open))))
+      ;; I check the timer alist for an existing timer. If one exists,
+      ;; I get rid of it
+      (let ((timer-tuple (assq buffer erc-server-ping-timer-alist)))
+        (if timer-tuple
+            ;; this buffer already has a timer. Cancel it and set the new one
+            (progn
+              (erc-cancel-timer (cdr timer-tuple))
+              (setf (cdr (assq buffer erc-server-ping-timer-alist)) erc-server-ping-handler))
+
+          ;; no existing timer for this buffer. Add new one
+          (add-to-list 'erc-server-ping-timer-alist
+                       (cons buffer erc-server-ping-handler)))))))
+
+(defun erc-server-process-alive (&optional buffer)
+  "Return non-nil when BUFFER has an `erc-server-process' open or running."
+  (with-current-buffer (or buffer (current-buffer))
+    (and erc-server-process
+         (processp erc-server-process)
+         (memq (process-status erc-server-process) '(run open)))))
 
 ;;;; Connecting to a server
+(defun erc-open-network-stream (name buffer host service)
+  "As `open-network-stream', but does non-blocking IO"
+  (make-network-process :name name :buffer  buffer
+                        :host host :service service :nowait t))
 
 (defun erc-server-connect (server port buffer)
   "Perform the connection and login using the specified SERVER and PORT.
 We will store server variables in the buffer given by BUFFER."
-  (let ((msg (erc-format-message 'connect ?S server ?p port)))
+  (let ((msg (erc-format-message 'connect ?S server ?p port)) process)
     (message "%s" msg)
-    (let ((process (funcall erc-server-connect-function
-                            (format "erc-%s-%s" server port)
-                            nil server port)))
-      (unless (processp process)
-        (error "Connection attempt failed"))
+    (setq process (funcall erc-server-connect-function
+                           (format "erc-%s-%s" server port) nil server port))
+    (unless (processp process)
+      (error "Connection attempt failed"))
+    ;; Misc server variables
+    (with-current-buffer buffer
+      (setq erc-server-process process)
+      (setq erc-server-quitting nil)
+      (setq erc-server-reconnecting nil)
+      (setq erc-server-timed-out nil)
+      (setq erc-server-banned nil)
+      (setq erc-server-error-occurred nil)
+      (let ((time (erc-current-time)))
+        (setq erc-server-last-sent-time time)
+        (setq erc-server-last-ping-time time)
+        (setq erc-server-last-received-time time))
+      (setq erc-server-lines-sent 0)
+      ;; last peers (sender and receiver)
+      (setq erc-server-last-peers '(nil . nil)))
+    ;; we do our own encoding and decoding
+    (when (fboundp 'set-process-coding-system)
+      (set-process-coding-system process 'raw-text))
+    ;; process handlers
+    (set-process-sentinel process 'erc-process-sentinel)
+    (set-process-filter process 'erc-server-filter-function)
+    (set-process-buffer process buffer)
+    (erc-log "\n\n\n********************************************\n")
+    (message "%s" (erc-format-message
+                   'login ?n
+                   (with-current-buffer buffer (erc-current-nick))))
+    ;; wait with script loading until we receive a confirmation (first
+    ;; MOTD line)
+    (if (eq (process-status process) 'connect)
+        ;; waiting for a non-blocking connect - keep the user informed
+        (erc-display-message nil nil buffer "Opening connection..\n")
       (message "%s...done" msg)
-      ;; Misc server variables
-      (with-current-buffer buffer
-        (setq erc-server-process process)
-        (setq erc-server-quitting nil)
-        (setq erc-server-reconnecting nil)
-        (setq erc-server-timed-out nil)
-        (setq erc-server-banned nil)
-        (setq erc-server-error-occurred nil)
-        (let ((time (erc-current-time)))
-          (setq erc-server-last-sent-time time)
-          (setq erc-server-last-ping-time time)
-          (setq erc-server-last-received-time time))
-        (setq erc-server-lines-sent 0)
-        ;; last peers (sender and receiver)
-        (setq erc-server-last-peers '(nil . nil)))
-      ;; we do our own encoding and decoding
-      (when (fboundp 'set-process-coding-system)
-        (set-process-coding-system process 'raw-text))
-      ;; process handlers
-      (set-process-sentinel process 'erc-process-sentinel)
-      (set-process-filter process 'erc-server-filter-function)
-      (set-process-buffer process buffer)))
-  (erc-log "\n\n\n********************************************\n")
-  (message "%s" (erc-format-message
-            'login ?n
-            (with-current-buffer buffer (erc-current-nick))))
-  ;; wait with script loading until we receive a confirmation (first
-  ;; MOTD line)
-  (if (eq erc-server-connect-function 'open-network-stream-nowait)
-      ;; it's a bit unclear otherwise that it's attempting to establish a
-      ;; connection
-      (erc-display-message nil nil buffer "Opening connection..\n")
-    (erc-login)))
+      (erc-login)) ))
 
 (defun erc-server-reconnect ()
 "Reestablish the current IRC connection.
@@ -563,10 +577,15 @@ Make sure you are in an ERC buffer when running this."
       (setq erc-server-last-sent-time 0)
       (setq erc-server-lines-sent 0)
       (let ((erc-server-connect-function (or erc-session-connector
-                                             'open-network-stream)))
+                                             'erc-open-network-stream)))
         (erc-open erc-session-server erc-session-port erc-server-current-nick
                   erc-session-user-full-name t erc-session-password)))))
 
+(defun erc-server-delayed-reconnect (event buffer)
+  (if (buffer-live-p buffer)
+    (with-current-buffer buffer
+      (erc-server-reconnect))))
+
 (defun erc-server-filter-function (process string)
   "The process filter for the ERC server."
   (with-current-buffer (process-buffer process)
@@ -613,17 +632,16 @@ EVENT is the message received from the closed connection process."
            (or erc-server-timed-out
                (not (string-match "^deleted" event)))
            ;; open-network-stream-nowait error for connection refused
-           (not (string-match "^failed with code 111" event)))))
+           (if (string-match "^failed with code 111" event) 'nonblocking t))))
 
 (defun erc-process-sentinel-2 (event buffer)
   "Called when `erc-process-sentinel-1' has detected an unexpected disconnect."
   (if (not (buffer-live-p buffer))
       (erc-update-mode-line)
     (with-current-buffer buffer
-      (let ((reconnect-p (erc-server-reconnect-p event)))
-        (erc-display-message nil 'error (current-buffer)
-                             (if reconnect-p 'disconnected
-                               'disconnected-noreconnect))
+      (let ((reconnect-p (erc-server-reconnect-p event)) message delay)
+        (setq message (if reconnect-p 'disconnected 'disconnected-noreconnect))
+        (erc-display-message nil 'error (current-buffer) message)
         (if (not reconnect-p)
             ;; terminate, do not reconnect
             (progn
@@ -635,23 +653,16 @@ EVENT is the message received from the closed connection process."
           ;; reconnect
           (condition-case err
               (progn
-                (setq erc-server-reconnecting nil)
-                (erc-server-reconnect)
-                (setq erc-server-reconnect-count 0))
-            (error (when (buffer-live-p buffer)
-                     (set-buffer buffer)
-                     (if (integerp erc-server-reconnect-attempts)
-                         (setq erc-server-reconnect-count
-                               (1+ erc-server-reconnect-count))
-                       (message "%s ... %s"
-                                "Reconnecting until we succeed"
-                                "kill the ERC server buffer to stop"))
-                     (if (numberp erc-server-reconnect-timeout)
-                         (run-at-time erc-server-reconnect-timeout nil
-                                      #'erc-process-sentinel-2
-                                      event buffer)
-                       (error (concat "`erc-server-reconnect-timeout`"
-                                      " must be a number")))))))))))
+                (setq erc-server-reconnecting   nil
+                      erc-server-reconnect-count (1+ erc-server-reconnect-count))
+                (setq delay erc-server-reconnect-timeout)
+                (run-at-time delay nil
+                             #'erc-server-delayed-reconnect event buffer))
+            (error (unless (integerp erc-server-reconnect-attempts)
+                     (message "%s ... %s"
+                              "Reconnecting until we succeed"
+                              "kill the ERC server buffer to stop"))
+                   (erc-server-delayed-reconnect event buffer))))))))
 
 (defun erc-process-sentinel-1 (event buffer)
   "Called when `erc-process-sentinel' has decided that we're disconnecting.
@@ -677,7 +688,7 @@ Conditionally try to reconnect and take appropriate action."
     (when (buffer-live-p buf)
       (with-current-buffer buf
         (erc-log (format
-                  "SENTINEL: proc: %S   status: %S  event: %S (quitting: %S)"
+                  "SENTINEL: proc: %S    status: %S  event: %S (quitting: %S)"
                   cproc (process-status cproc) event erc-server-quitting))
         (if (string-match "^open" event)
             ;; newly opened connection (no wait)
@@ -690,6 +701,9 @@ Conditionally try to reconnect and take appropriate action."
                    (setq erc-server-ping-handler nil)))
           (run-hook-with-args 'erc-disconnected-hook
                               (erc-current-nick) (system-name) "")
+          (dolist (buf (erc-buffer-filter (lambda () (boundp 'erc-channel-users)) cproc))
+            (with-current-buffer buf
+              (setq erc-channel-users (make-hash-table :test 'equal))))
           ;; Remove the prompt
           (goto-char (or (marker-position erc-input-marker) (point-max)))
           (forward-line 0)
@@ -792,7 +806,9 @@ protection algorithm."
 (defun erc-server-send-ping (buf)
   "Send a ping to the IRC server buffer in BUF.
 Additionally, detect whether the IRC process has hung."
-  (if (buffer-live-p buf)
+  (if (and (buffer-live-p buf)
+           (with-current-buffer buf
+             erc-server-last-received-time))
       (with-current-buffer buf
         (if (and erc-server-send-ping-timeout
                  (>
@@ -950,7 +966,7 @@ PROCs `process-buffer' is `current-buffer' when this function is called."
         (push str (erc-response.command-args msg))))
 
     (setf (erc-response.contents msg)
-          (first (erc-response.command-args msg)))
+          (car (erc-response.command-args msg)))
 
     (setf (erc-response.command-args msg)
           (nreverse (erc-response.command-args msg)))
@@ -1045,7 +1061,7 @@ Finds hooks by looking in the `erc-server-responses' hashtable."
                (name &rest name)
                &optional sexp sexp def-body))
 
-(defmacro* define-erc-response-handler ((name &rest aliases)
+(cl-defmacro define-erc-response-handler ((name &rest aliases)
                                         &optional extra-fn-doc extra-var-doc
                                         &rest fn-body)
   "Define an ERC handler hook/function pair.
@@ -1080,7 +1096,7 @@ As an example:
 Would expand to:
 
   (prog2
-      (defvar erc-server-311-functions 'erc-server-311
+      (defvar erc-server-311-functions \\='erc-server-311
         \"Some non-generic variable documentation.
 
   Hook called upon receiving a 311 server response.
@@ -1098,12 +1114,12 @@ Would expand to:
   add things to `erc-server-311-functions' instead.\"
         (do-stuff-with-whois proc parsed))
 
-    (puthash \"311\" 'erc-server-311-functions erc-server-responses)
-    (puthash \"WHOIS\" 'erc-server-WHOIS-functions erc-server-responses)
-    (puthash \"WI\" 'erc-server-WI-functions erc-server-responses)
+    (puthash \"311\" \\='erc-server-311-functions erc-server-responses)
+    (puthash \"WHOIS\" \\='erc-server-WHOIS-functions erc-server-responses)
+    (puthash \"WI\" \\='erc-server-WI-functions erc-server-responses)
 
-    (defalias 'erc-server-WHOIS 'erc-server-311)
-    (defvar erc-server-WHOIS-functions 'erc-server-311
+    (defalias \\='erc-server-WHOIS \\='erc-server-311)
+    (defvar erc-server-WHOIS-functions \\='erc-server-311
       \"Some non-generic variable documentation.
 
   Hook called upon receiving a WHOIS server response.
@@ -1114,8 +1130,8 @@ Would expand to:
 
   See also `erc-server-311'.\")
 
-    (defalias 'erc-server-WI 'erc-server-311)
-    (defvar erc-server-WI-functions 'erc-server-311
+    (defalias \\='erc-server-WI \\='erc-server-311)
+    (defvar erc-server-WI-functions \\='erc-server-311
       \"Some non-generic variable documentation.
 
   Hook called upon receiving a WI server response.
@@ -1134,7 +1150,8 @@ Would expand to:
                         aliases))
   (let* ((hook-name (intern (format "erc-server-%s-functions" name)))
          (fn-name (intern (format "erc-server-%s" name)))
-         (hook-doc (format "%sHook called upon receiving a %%s server response.
+         (hook-doc (format-message "\
+%sHook called upon receiving a %%s server response.
 Each function is called with two arguments, the process associated
 with the response and the parsed response.  If the function returns
 non-nil, stop processing the hook.  Otherwise, continue.
@@ -1144,7 +1161,8 @@ See also `%s'."
                                (concat extra-var-doc "\n\n")
                              "")
                            fn-name))
-         (fn-doc (format "%sHandler for a %s server response.
+         (fn-doc (format-message "\
+%sHandler for a %s server response.
 PROC is the server process which returned the response.
 PARSED is the actual response as an `erc-response' struct.
 If you want to add responses don't modify this function, but rather
@@ -1154,14 +1172,17 @@ add things to `%s' instead."
                            "")
                          name hook-name))
          (fn-alternates
-          (loop for alias in aliases
-                collect (intern (format "erc-server-%s" alias))))
+          (cl-loop for alias in aliases
+                   collect (intern (format "erc-server-%s" alias))))
          (var-alternates
-          (loop for alias in aliases
-                collect (intern (format "erc-server-%s-functions" alias)))))
+          (cl-loop for alias in aliases
+                   collect (intern (format "erc-server-%s-functions" alias)))))
     `(prog2
-         ;; Normal hook variable.
-         (defvar ,hook-name ',fn-name ,(format hook-doc name))
+         ;; Normal hook variable.  The variable may already have a
+         ;; value at this point, so I default to nil, and (add-hook)
+         ;; unconditionally
+         (defvar ,hook-name nil ,(format hook-doc name))
+         (add-to-list ',hook-name ',fn-name)
          ;; Handler function
          (defun ,fn-name (proc parsed)
            ,fn-doc
@@ -1172,19 +1193,19 @@ add things to `%s' instead."
        (put ',hook-name 'definition-name ',name)
 
        ;; Hashtable map of responses to hook variables
-       ,@(loop for response in (cons name aliases)
-               for var in (cons hook-name var-alternates)
-               collect `(puthash ,(format "%s" response) ',var
-                                 erc-server-responses))
+       ,@(cl-loop for response in (cons name aliases)
+                  for var in (cons hook-name var-alternates)
+                  collect `(puthash ,(format "%s" response) ',var
+                                    erc-server-responses))
        ;; Alternates.
        ;; Functions are defaliased, hook variables are defvared so we
        ;; can add hooks to one alias, but not another.
-       ,@(loop for fn in fn-alternates
-               for var in var-alternates
-               for a in aliases
-               nconc (list `(defalias ',fn ',fn-name)
-                           `(defvar ,var ',fn-name ,(format hook-doc a))
-                           `(put ',var 'definition-name ',hook-name))))))
+       ,@(cl-loop for fn in fn-alternates
+                  for var in var-alternates
+                  for a in aliases
+                  nconc (list `(defalias ',fn ',fn-name)
+                              `(defvar ,var ',fn-name ,(format hook-doc a))
+                              `(put ',var 'definition-name ',hook-name))))))
 
 (define-erc-response-handler (ERROR)
   "Handle an ERROR command from the server." nil
@@ -1196,24 +1217,23 @@ add things to `%s' instead."
 (define-erc-response-handler (INVITE)
   "Handle invitation messages."
   nil
-  (let ((target (first (erc-response.command-args parsed)))
+  (let ((target (car (erc-response.command-args parsed)))
         (chnl (erc-response.contents parsed)))
-    (multiple-value-bind (nick login host)
-        (values-list (erc-parse-user (erc-response.sender parsed)))
+    (pcase-let ((`(,nick ,login ,host)
+                 (erc-parse-user (erc-response.sender parsed))))
       (setq erc-invitation chnl)
       (when (string= target (erc-current-nick))
         (erc-display-message
          parsed 'notice 'active
          'INVITE ?n nick ?u login ?h host ?c chnl)))))
 
-
 (define-erc-response-handler (JOIN)
   "Handle join messages."
   nil
   (let ((chnl (erc-response.contents parsed))
         (buffer nil))
-    (multiple-value-bind (nick login host)
-        (values-list (erc-parse-user (erc-response.sender parsed)))
+    (pcase-let ((`(,nick ,login ,host)
+                 (erc-parse-user (erc-response.sender parsed))))
       ;; strip the stupid combined JOIN facility (IRC 2.9)
       (if (string-match "^\\(.*\\)?\^g.*$" chnl)
           (setq chnl (match-string 1 chnl)))
@@ -1242,19 +1262,19 @@ add things to `%s' instead."
                        (erc-format-message
                         'JOIN ?n nick ?u login ?h host ?c chnl))))))
           (when buffer (set-buffer buffer))
-          (erc-update-channel-member chnl nick nick t nil nil host login)
+          (erc-update-channel-member chnl nick nick t nil nil nil nil nil host login)
           ;; on join, we want to stay in the new channel buffer
           ;;(set-buffer ob)
           (erc-display-message parsed nil buffer str))))))
 
 (define-erc-response-handler (KICK)
   "Handle kick messages received from the server." nil
-  (let* ((ch (first (erc-response.command-args parsed)))
-         (tgt (second (erc-response.command-args parsed)))
+  (let* ((ch (nth 0 (erc-response.command-args parsed)))
+         (tgt (nth 1 (erc-response.command-args parsed)))
          (reason (erc-trim-string (erc-response.contents parsed)))
          (buffer (erc-get-buffer ch proc)))
-    (multiple-value-bind (nick login host)
-        (values-list (erc-parse-user (erc-response.sender parsed)))
+    (pcase-let ((`(,nick ,login ,host)
+                 (erc-parse-user (erc-response.sender parsed))))
       (erc-remove-channel-member buffer tgt)
       (cond
        ((string= tgt (erc-current-nick))
@@ -1277,11 +1297,11 @@ add things to `%s' instead."
 
 (define-erc-response-handler (MODE)
   "Handle server mode changes." nil
-  (let ((tgt (first (erc-response.command-args parsed)))
+  (let ((tgt (car (erc-response.command-args parsed)))
         (mode (mapconcat 'identity (cdr (erc-response.command-args parsed))
                          " ")))
-    (multiple-value-bind (nick login host)
-        (values-list (erc-parse-user (erc-response.sender parsed)))
+    (pcase-let ((`(,nick ,login ,host)
+                 (erc-parse-user (erc-response.sender parsed))))
       (erc-log (format "MODE: %s -> %s: %s" nick tgt mode))
       ;; dirty hack
       (let ((buf (cond ((erc-channel-p tgt)
@@ -1305,8 +1325,8 @@ add things to `%s' instead."
   "Handle nick change messages." nil
   (let ((nn (erc-response.contents parsed))
         bufs)
-    (multiple-value-bind (nick login host)
-        (values-list (erc-parse-user (erc-response.sender parsed)))
+    (pcase-let ((`(,nick ,login ,host)
+                 (erc-parse-user (erc-response.sender parsed))))
       (setq bufs (erc-buffer-list-with-nick nick proc))
       (erc-log (format "NICK: %s -> %s" nick nn))
       ;; if we had a query with this user, make sure future messages will be
@@ -1340,11 +1360,11 @@ add things to `%s' instead."
 
 (define-erc-response-handler (PART)
   "Handle part messages." nil
-  (let* ((chnl (first (erc-response.command-args parsed)))
+  (let* ((chnl (car (erc-response.command-args parsed)))
          (reason (erc-trim-string (erc-response.contents parsed)))
          (buffer (erc-get-buffer chnl proc)))
-    (multiple-value-bind (nick login host)
-        (values-list (erc-parse-user (erc-response.sender parsed)))
+    (pcase-let ((`(,nick ,login ,host)
+                 (erc-parse-user (erc-response.sender parsed))))
       (erc-remove-channel-member buffer nick)
       (erc-display-message parsed 'notice buffer
                            'PART ?n nick ?u login
@@ -1361,7 +1381,7 @@ add things to `%s' instead."
 
 (define-erc-response-handler (PING)
   "Handle ping messages." nil
-  (let ((pinger (first (erc-response.command-args parsed))))
+  (let ((pinger (car (erc-response.command-args parsed))))
     (erc-log (format "PING: %s" pinger))
     ;; ping response to the server MUST be forced, or you can lose big
     (erc-server-send (format "PONG :%s" pinger) t)
@@ -1379,7 +1399,7 @@ add things to `%s' instead."
       (when erc-verbose-server-ping
         (erc-display-message
          parsed 'notice proc 'PONG
-         ?h (first (erc-response.command-args parsed)) ?i erc-server-lag
+         ?h (car (erc-response.command-args parsed)) ?i erc-server-lag
          ?s (if (/= erc-server-lag 1) "s" "")))
       (erc-update-mode-line))))
 
@@ -1411,7 +1431,7 @@ add things to `%s' instead."
             ;; message.  We will accumulate private identities indefinitely
             ;; at this point.
             (erc-update-channel-member (if privp nick tgt) nick nick
-                                       privp nil nil host login nil nil t)
+                                       privp nil nil nil nil nil host login nil nil t)
             (let ((cdata (erc-get-channel-user nick)))
               (setq fnick (funcall erc-format-nick-function
                                    (car cdata) (cdr cdata))))))
@@ -1451,8 +1471,8 @@ add things to `%s' instead."
   "Another user has quit IRC." nil
   (let ((reason (erc-response.contents parsed))
         bufs)
-    (multiple-value-bind (nick login host)
-        (values-list (erc-parse-user (erc-response.sender parsed)))
+    (pcase-let ((`(,nick ,login ,host)
+                 (erc-parse-user (erc-response.sender parsed))))
       (setq bufs (erc-buffer-list-with-nick nick proc))
       (erc-remove-user nick)
       (setq reason (erc-wash-quit-reason reason nick login host))
@@ -1462,13 +1482,12 @@ add things to `%s' instead."
 
 (define-erc-response-handler (TOPIC)
   "The channel topic has changed." nil
-  (let* ((ch (first (erc-response.command-args parsed)))
+  (let* ((ch (car (erc-response.command-args parsed)))
          (topic (erc-trim-string (erc-response.contents parsed)))
-         (time (format-time-string erc-server-timestamp-format
-                                   (current-time))))
-    (multiple-value-bind (nick login host)
-        (values-list (erc-parse-user (erc-response.sender parsed)))
-      (erc-update-channel-member ch nick nick nil nil nil host login)
+         (time (format-time-string erc-server-timestamp-format)))
+    (pcase-let ((`(,nick ,login ,host)
+                 (erc-parse-user (erc-response.sender parsed))))
+      (erc-update-channel-member ch nick nick nil nil nil nil nil nil host login)
       (erc-update-channel-topic ch (format "%s\C-o (%s, %s)" topic nick time))
       (erc-display-message parsed 'notice (erc-get-buffer ch proc)
                            'TOPIC ?n nick ?u login ?h host
@@ -1477,8 +1496,8 @@ add things to `%s' instead."
 (define-erc-response-handler (WALLOPS)
   "Display a WALLOPS message." nil
   (let ((message (erc-response.contents parsed)))
-    (multiple-value-bind (nick login host)
-        (values-list (erc-parse-user (erc-response.sender parsed)))
+    (pcase-let ((`(,nick ,login ,host)
+                 (erc-parse-user (erc-response.sender parsed))))
       (erc-display-message
        parsed 'notice nil
        'WALLOPS ?n nick ?m message))))
@@ -1486,7 +1505,7 @@ add things to `%s' instead."
 (define-erc-response-handler (001)
   "Set `erc-server-current-nick' to reflect server settings and display the welcome message."
   nil
-  (erc-set-current-nick (first (erc-response.command-args parsed)))
+  (erc-set-current-nick (car (erc-response.command-args parsed)))
   (erc-update-mode-line)                ; needed here?
   (setq erc-nick-change-attempt-count 0)
   (setq erc-default-nicks (if (consp erc-nick) erc-nick (list erc-nick)))
@@ -1507,16 +1526,16 @@ add things to `%s' instead."
 
 (define-erc-response-handler (004)
   "Display the server's identification." nil
-  (multiple-value-bind (server-name server-version)
-      (values-list (cdr (erc-response.command-args parsed)))
+  (pcase-let ((`(,server-name ,server-version)
+               (cdr (erc-response.command-args parsed))))
     (setq erc-server-version server-version)
     (setq erc-server-announced-name server-name)
     (erc-update-mode-line-buffer (process-buffer proc))
     (erc-display-message
      parsed 'notice proc
      's004 ?s server-name ?v server-version
-     ?U (fourth (erc-response.command-args parsed))
-     ?C (fifth (erc-response.command-args parsed)))))
+     ?U (nth 3 (erc-response.command-args parsed))
+     ?C (nth 4 (erc-response.command-args parsed)))))
 
 (define-erc-response-handler (005)
   "Set the variable `erc-server-parameters' and display the received message.
@@ -1536,7 +1555,7 @@ A server may send more than one 005 message."
     (while (erc-response.command-args parsed)
       (let ((section (pop (erc-response.command-args parsed))))
         ;; fill erc-server-parameters
-        (when (string-match "^\\([A-Z]+\\)\=\\(.*\\)$\\|^\\([A-Z]+\\)$"
+        (when (string-match "^\\([A-Z]+\\)=\\(.*\\)$\\|^\\([A-Z]+\\)$"
                             section)
           (add-to-list 'erc-server-parameters
                        `(,(or (match-string 1 section)
@@ -1547,7 +1566,7 @@ A server may send more than one 005 message."
 
 (define-erc-response-handler (221)
   "Display the current user modes." nil
-  (let* ((nick (first (erc-response.command-args parsed)))
+  (let* ((nick (car (erc-response.command-args parsed)))
          (modes (mapconcat 'identity
                            (cdr (erc-response.command-args parsed)) " ")))
     (erc-set-modes nick modes)
@@ -1556,17 +1575,17 @@ A server may send more than one 005 message."
 (define-erc-response-handler (252)
   "Display the number of IRC operators online." nil
   (erc-display-message parsed 'notice 'active 's252
-                       ?i (second (erc-response.command-args parsed))))
+                       ?i (cadr (erc-response.command-args parsed))))
 
 (define-erc-response-handler (253)
   "Display the number of unknown connections." nil
   (erc-display-message parsed 'notice 'active 's253
-                       ?i (second (erc-response.command-args parsed))))
+                       ?i (cadr (erc-response.command-args parsed))))
 
 (define-erc-response-handler (254)
   "Display the number of channels formed." nil
   (erc-display-message parsed 'notice 'active 's254
-                       ?i (second (erc-response.command-args parsed))))
+                       ?i (cadr (erc-response.command-args parsed))))
 
 (define-erc-response-handler (250 251 255 256 257 258 259 265 266 377 378)
   "Generic display of server messages as notices.
@@ -1576,8 +1595,8 @@ See `erc-display-server-message'." nil
 
 (define-erc-response-handler (275)
   "Display secure connection message." nil
-  (multiple-value-bind (nick user message)
-      (values-list (cdr (erc-response.command-args parsed)))
+  (pcase-let ((`(,nick ,user ,message)
+               (cdr (erc-response.command-args parsed))))
     (erc-display-message
      parsed 'notice 'active 's275
      ?n nick
@@ -1590,13 +1609,13 @@ See `erc-display-server-message'." nil
 (define-erc-response-handler (301)
   "AWAY notice." nil
   (erc-display-message parsed 'notice 'active 's301
-                       ?n (second (erc-response.command-args parsed))
+                       ?n (cadr (erc-response.command-args parsed))
                        ?r (erc-response.contents parsed)))
 
 (define-erc-response-handler (303)
   "ISON reply" nil
   (erc-display-message parsed 'notice 'active 's303
-                       ?n (second (erc-response.command-args parsed))))
+                       ?n (cadr (erc-response.command-args parsed))))
 
 (define-erc-response-handler (305)
   "Return from AWAYness." nil
@@ -1612,8 +1631,8 @@ See `erc-display-server-message'." nil
 
 (define-erc-response-handler (307)
   "Display nick-identified message." nil
-  (multiple-value-bind (nick user message)
-      (values-list (cdr (erc-response.command-args parsed)))
+  (pcase-let ((`(,nick ,user ,message)
+               (cdr (erc-response.command-args parsed))))
     (erc-display-message
      parsed 'notice 'active 's307
      ?n nick
@@ -1624,8 +1643,8 @@ See `erc-display-server-message'." nil
   "WHOIS/WHOWAS notices." nil
   (let ((fname (erc-response.contents parsed))
         (catalog-entry (intern (format "s%s" (erc-response.command parsed)))))
-    (multiple-value-bind (nick user host)
-        (values-list (cdr (erc-response.command-args parsed)))
+    (pcase-let ((`(,nick ,user ,host)
+                 (cdr (erc-response.command-args parsed))))
       (erc-update-user-nick nick nick host nil fname user)
       (erc-display-message
        parsed 'notice 'active catalog-entry
@@ -1633,8 +1652,8 @@ See `erc-display-server-message'." nil
 
 (define-erc-response-handler (312)
   "Server name response in WHOIS." nil
-  (multiple-value-bind (nick server-host)
-      (values-list (cdr (erc-response.command-args parsed)))
+  (pcase-let ((`(,nick ,server-host)
+              (cdr (erc-response.command-args parsed))))
     (erc-display-message
      parsed 'notice 'active 's312
      ?n nick ?s server-host ?c (erc-response.contents parsed))))
@@ -1643,7 +1662,7 @@ See `erc-display-server-message'." nil
   "IRC Operator response in WHOIS." nil
   (erc-display-message
    parsed 'notice 'active 's313
-   ?n (second (erc-response.command-args parsed))))
+   ?n (cadr (erc-response.command-args parsed))))
 
 (define-erc-response-handler (315 318 323 369)
   ;; 315 - End of WHO
@@ -1655,8 +1674,8 @@ See `erc-display-server-message'." nil
 
 (define-erc-response-handler (317)
   "IDLE notice." nil
-  (multiple-value-bind (nick seconds-idle on-since time)
-      (values-list (cdr (erc-response.command-args parsed)))
+  (pcase-let ((`(,nick ,seconds-idle ,on-since ,time)
+               (cdr (erc-response.command-args parsed))))
     (setq time (when on-since
                  (format-time-string erc-server-timestamp-format
                                      (erc-string-to-emacs-time on-since))))
@@ -1674,14 +1693,14 @@ See `erc-display-server-message'." nil
   "Channel names in WHOIS response." nil
   (erc-display-message
    parsed 'notice 'active 's319
-   ?n (second (erc-response.command-args parsed))
+   ?n (cadr (erc-response.command-args parsed))
    ?c (erc-response.contents parsed)))
 
 (define-erc-response-handler (320)
   "Identified user in WHOIS." nil
   (erc-display-message
    parsed 'notice 'active 's320
-   ?n (second (erc-response.command-args parsed))))
+   ?n (cadr (erc-response.command-args parsed))))
 
 (define-erc-response-handler (321)
   "LIST header." nil
@@ -1696,16 +1715,16 @@ See `erc-display-server-message'." nil
 (define-erc-response-handler (322)
   "LIST notice." nil
   (let ((topic (erc-response.contents parsed)))
-    (multiple-value-bind (channel num-users)
-        (values-list (cdr (erc-response.command-args parsed)))
+    (pcase-let ((`(,channel ,num-users)
+                 (cdr (erc-response.command-args parsed))))
       (add-to-list 'erc-channel-list (list channel))
       (erc-update-channel-topic channel topic))))
 
 (defun erc-server-322-message (proc parsed)
   "Display a message for the 322 event."
   (let ((topic (erc-response.contents parsed)))
-    (multiple-value-bind (channel num-users)
-        (values-list (cdr (erc-response.command-args parsed)))
+    (pcase-let ((`(,channel ,num-users)
+                 (cdr (erc-response.command-args parsed))))
       (erc-display-message
        parsed 'notice proc 's322
        ?c channel ?u num-users ?t (or topic "")))))
@@ -1713,7 +1732,7 @@ See `erc-display-server-message'." nil
 
 (define-erc-response-handler (324)
   "Channel or nick modes." nil
-  (let ((channel (second (erc-response.command-args parsed)))
+  (let ((channel (cadr (erc-response.command-args parsed)))
         (modes (mapconcat 'identity (cddr (erc-response.command-args parsed))
                           " ")))
     (erc-set-modes channel modes)
@@ -1723,16 +1742,16 @@ See `erc-display-server-message'." nil
 
 (define-erc-response-handler (328)
   "Channel URL (on freenode network)." nil
-  (let ((channel (second (erc-response.command-args parsed)))
+  (let ((channel (cadr (erc-response.command-args parsed)))
         (url (erc-response.contents parsed)))
     (erc-display-message parsed 'notice (erc-get-buffer channel proc)
                          's328 ?c channel ?u url)))
 
 (define-erc-response-handler (329)
   "Channel creation date." nil
-  (let ((channel (second (erc-response.command-args parsed)))
+  (let ((channel (cadr (erc-response.command-args parsed)))
         (time (erc-string-to-emacs-time
-               (third (erc-response.command-args parsed)))))
+               (nth 2 (erc-response.command-args parsed)))))
     (erc-display-message
      parsed 'notice (erc-get-buffer channel proc)
      's329 ?c channel ?t (format-time-string erc-server-timestamp-format
@@ -1748,22 +1767,22 @@ See `erc-display-server-message'." nil
   ;; authaccount == (aref parsed 4)
   ;; authmsg == (aref parsed 5)
   ;; The guesses below are, well, just that. -- Lawrence 2004/05/10
-  (let ((nick (second (erc-response.command-args parsed)))
-        (authaccount (third (erc-response.command-args parsed)))
+  (let ((nick (cadr (erc-response.command-args parsed)))
+        (authaccount (nth 2 (erc-response.command-args parsed)))
         (authmsg (erc-response.contents parsed)))
     (erc-display-message parsed 'notice 'active 's330
                          ?n nick ?a authmsg ?i authaccount)))
 
 (define-erc-response-handler (331)
   "No topic set for channel." nil
-  (let ((channel (second (erc-response.command-args parsed)))
+  (let ((channel (cadr (erc-response.command-args parsed)))
         (topic (erc-response.contents parsed)))
     (erc-display-message parsed 'notice (erc-get-buffer channel proc)
                          's331 ?c channel)))
 
 (define-erc-response-handler (332)
   "TOPIC notice." nil
-  (let ((channel (second (erc-response.command-args parsed)))
+  (let ((channel (cadr (erc-response.command-args parsed)))
         (topic (erc-response.contents parsed)))
     (erc-update-channel-topic channel topic)
     (erc-display-message parsed 'notice (erc-get-buffer channel proc)
@@ -1771,8 +1790,8 @@ See `erc-display-server-message'." nil
 
 (define-erc-response-handler (333)
   "Who set the topic, and when." nil
-  (multiple-value-bind (channel nick time)
-      (values-list (cdr (erc-response.command-args parsed)))
+  (pcase-let ((`(,channel ,nick ,time)
+               (cdr (erc-response.command-args parsed))))
     (setq time (format-time-string erc-server-timestamp-format
                                    (erc-string-to-emacs-time time)))
     (erc-update-channel-topic channel
@@ -1784,29 +1803,28 @@ See `erc-display-server-message'." nil
 (define-erc-response-handler (341)
   "Let user know when an INVITE attempt has been sent successfully."
   nil
-  (multiple-value-bind (nick channel)
-      (values-list (cdr (erc-response.command-args parsed)))
+  (pcase-let ((`(,nick ,channel)
+               (cdr (erc-response.command-args parsed))))
     (erc-display-message parsed 'notice (erc-get-buffer channel proc)
                          's341 ?n nick ?c channel)))
 
 (define-erc-response-handler (352)
   "WHO notice." nil
-  (multiple-value-bind (channel user host server nick away-flag)
-      (values-list (cdr (erc-response.command-args parsed)))
+  (pcase-let ((`(,channel ,user ,host ,server ,nick ,away-flag)
+               (cdr (erc-response.command-args parsed))))
     (let ((full-name (erc-response.contents parsed))
           hopcount)
       (when (string-match "\\(^[0-9]+ \\)\\(.*\\)$" full-name)
         (setq hopcount (match-string 1 full-name))
         (setq full-name (match-string 2 full-name)))
-      (erc-update-channel-member channel nick nick nil nil nil host
-                                 user full-name)
+      (erc-update-channel-member channel nick nick nil nil nil nil nil nil host user full-name)
       (erc-display-message parsed 'notice 'active 's352
                            ?c channel ?n nick ?a away-flag
                            ?u user ?h host ?f full-name))))
 
 (define-erc-response-handler (353)
   "NAMES notice." nil
-  (let ((channel (third (erc-response.command-args parsed)))
+  (let ((channel (nth 2 (erc-response.command-args parsed)))
         (users (erc-response.contents parsed)))
     (erc-display-message parsed 'notice (or (erc-get-buffer channel proc)
                                             'active)
@@ -1816,13 +1834,13 @@ See `erc-display-server-message'." nil
 
 (define-erc-response-handler (366)
   "End of NAMES." nil
-  (erc-with-buffer ((second (erc-response.command-args parsed)) proc)
+  (erc-with-buffer ((cadr (erc-response.command-args parsed)) proc)
     (erc-channel-end-receiving-names)))
 
 (define-erc-response-handler (367)
   "Channel ban list entries." nil
-  (multiple-value-bind (channel banmask setter time)
-      (values-list (cdr (erc-response.command-args parsed)))
+  (pcase-let ((`(,channel ,banmask ,setter ,time)
+               (cdr (erc-response.command-args parsed))))
     ;; setter and time are not standard
     (if setter
         (erc-display-message parsed 'notice 'active 's367-set-by
@@ -1836,7 +1854,7 @@ See `erc-display-server-message'." nil
 
 (define-erc-response-handler (368)
   "End of channel ban list." nil
-  (let ((channel (second (erc-response.command-args parsed))))
+  (let ((channel (cadr (erc-response.command-args parsed))))
     (erc-display-message parsed 'notice 'active 's368
                          ?c channel)))
 
@@ -1845,8 +1863,8 @@ See `erc-display-server-message'." nil
   ;; FIXME: Yet more magic numbers in original code, I'm guessing this
   ;; command takes two arguments, and doesn't have any "contents". --
   ;; Lawrence 2004/05/10
-  (multiple-value-bind (from to)
-      (values-list (cdr (erc-response.command-args parsed)))
+  (pcase-let ((`(,from ,to)
+               (cdr (erc-response.command-args parsed))))
     (erc-display-message parsed 'notice 'active
                          's379 ?c from ?f to)))
 
@@ -1854,12 +1872,12 @@ See `erc-display-server-message'." nil
   "Server's time string." nil
   (erc-display-message
    parsed 'notice 'active
-   's391 ?s (second (erc-response.command-args parsed))
-   ?t (third (erc-response.command-args parsed))))
+   's391 ?s (cadr (erc-response.command-args parsed))
+   ?t (nth 2 (erc-response.command-args parsed))))
 
 (define-erc-response-handler (401)
   "No such nick/channel." nil
-  (let ((nick/channel (second (erc-response.command-args parsed))))
+  (let ((nick/channel (cadr (erc-response.command-args parsed))))
     (when erc-whowas-on-nosuchnick
       (erc-log (format "cmd: WHOWAS: %s" nick/channel))
       (erc-server-send (format "WHOWAS %s 1" nick/channel)))
@@ -1869,23 +1887,23 @@ See `erc-display-server-message'." nil
 (define-erc-response-handler (403)
   "No such channel." nil
   (erc-display-message parsed '(notice error) 'active
-                       's403 ?c (second (erc-response.command-args parsed))))
+                       's403 ?c (cadr (erc-response.command-args parsed))))
 
 (define-erc-response-handler (404)
   "Cannot send to channel." nil
   (erc-display-message parsed '(notice error) 'active
-                       's404 ?c (second (erc-response.command-args parsed))))
+                       's404 ?c (cadr (erc-response.command-args parsed))))
 
 
 (define-erc-response-handler (405)
   "Can't join that many channels." nil
   (erc-display-message parsed '(notice error) 'active
-                       's405 ?c (second (erc-response.command-args parsed))))
+                       's405 ?c (cadr (erc-response.command-args parsed))))
 
 (define-erc-response-handler (406)
   "No such nick." nil
   (erc-display-message parsed '(notice error) 'active
-                       's406 ?n (second (erc-response.command-args parsed))))
+                       's406 ?n (cadr (erc-response.command-args parsed))))
 
 (define-erc-response-handler (412)
   "No text to send." nil
@@ -1894,33 +1912,33 @@ See `erc-display-server-message'." nil
 (define-erc-response-handler (421)
   "Unknown command." nil
   (erc-display-message parsed '(notice error) 'active 's421
-                       ?c (second (erc-response.command-args parsed))))
+                       ?c (cadr (erc-response.command-args parsed))))
 
 (define-erc-response-handler (432)
   "Bad nick." nil
   (erc-display-message parsed '(notice error) 'active 's432
-                       ?n (second (erc-response.command-args parsed))))
+                       ?n (cadr (erc-response.command-args parsed))))
 
 (define-erc-response-handler (433)
   "Login-time \"nick in use\"." nil
-  (erc-nickname-in-use (second (erc-response.command-args parsed))
+  (erc-nickname-in-use (cadr (erc-response.command-args parsed))
                        "already in use"))
 
 (define-erc-response-handler (437)
   "Nick temporarily unavailable (on IRCnet)." nil
-  (let ((nick/channel (second (erc-response.command-args parsed))))
+  (let ((nick/channel (cadr (erc-response.command-args parsed))))
     (unless (erc-channel-p nick/channel)
       (erc-nickname-in-use nick/channel "temporarily unavailable"))))
 
 (define-erc-response-handler (442)
   "Not on channel." nil
   (erc-display-message parsed '(notice error) 'active 's442
-                       ?c (second (erc-response.command-args parsed))))
+                       ?c (cadr (erc-response.command-args parsed))))
 
 (define-erc-response-handler (461)
   "Not enough parameters for command." nil
   (erc-display-message parsed '(notice error)  'active 's461
-                       ?c (second (erc-response.command-args parsed))
+                       ?c (cadr (erc-response.command-args parsed))
                        ?m (erc-response.contents parsed)))
 
 (define-erc-response-handler (465)
@@ -1936,37 +1954,37 @@ See `erc-display-server-message'." nil
   (erc-display-message parsed '(notice error) nil
                        (intern (format "s%s"
                                        (erc-response.command parsed)))
-                       ?c (second (erc-response.command-args parsed))))
+                       ?c (cadr (erc-response.command-args parsed))))
 
 (define-erc-response-handler (475)
   "Channel key needed." nil
   (erc-display-message parsed '(notice error) nil 's475
-                       ?c (second (erc-response.command-args parsed)))
+                       ?c (cadr (erc-response.command-args parsed)))
   (when erc-prompt-for-channel-key
-    (let ((channel (second (erc-response.command-args parsed)))
+    (let ((channel (cadr (erc-response.command-args parsed)))
           (key (read-from-minibuffer
                 (format "Channel %s is mode +k.  Enter key (RET to cancel): "
-                        (second (erc-response.command-args parsed))))))
+                        (cadr (erc-response.command-args parsed))))))
       (when (and key (> (length key) 0))
           (erc-cmd-JOIN channel key)))))
 
 (define-erc-response-handler (477)
   "Channel doesn't support modes." nil
-  (let ((channel (second (erc-response.command-args parsed)))
+  (let ((channel (cadr (erc-response.command-args parsed)))
         (message (erc-response.contents parsed)))
     (erc-display-message parsed 'notice (erc-get-buffer channel proc)
                          (format "%s: %s" channel message))))
 
 (define-erc-response-handler (482)
   "You need to be a channel operator to do that." nil
-  (let ((channel (second (erc-response.command-args parsed)))
+  (let ((channel (cadr (erc-response.command-args parsed)))
         (message (erc-response.contents parsed)))
     (erc-display-message parsed '(error notice) 'active 's482
                          ?c channel ?m message)))
 
 (define-erc-response-handler (671)
   "Secure connection response in WHOIS." nil
-  (let ((nick (second (erc-response.command-args parsed)))
+  (let ((nick (cadr (erc-response.command-args parsed)))
         (securemsg (erc-response.contents parsed)))
     (erc-display-message parsed 'notice 'active 's671
                          ?n nick ?a securemsg)))