* lisp/erc/erc-backend.el (erc-server-KICK, erc-server-PART): Use new
function `erc--remove-channel-user-but' instead of
`erc-remove-channel-users'. In `erc-server-KICK', remove sender's
channel membership data after displaying the message so that nicks are
buttonized. Return nil. In `erc-server-PART', don't run
`erc-remove-channel-member' when the client itself has parted.
* lisp/erc/erc-common.el (erc--remove-user-from-targets): New
function.
* lisp/erc/erc.el (erc-remove-server-user): Redo doc string.
(erc--forget-server-user-function): New variable.
(erc--forget-server-user): New function.
(erc--forget-server-user-ignoring-queries): New function, the default
value of `erc--forget-server-user-function'.
(erc-remove-channel-user): Defer to `erc--forget-server-user-function'
to do the actual removal.
(erc-remove-user): Defer to `erc--remove-user-from-targets'.
(erc-remove-channel-users): Redo doc
(erc--remove-channel-users-but): New function. The only use case thus
far is for protecting the client's own `erc-server-users' entry from
removal when draining `erc-channel-members' tables after the client
leaves a target buffer or quits.
(erc-kill-buffer-function): Don't remove own user from
`erc-server-users'.
* test/lisp/erc/erc-scenarios-base-renick.el
(erc-scenarios-base-renick-queries-solo): Assert own client parting
its only channel doesn't remove own user from server. Also assert
that another user parting their only channel removes them from all
queries. (Bug#70928)
(cherry picked from commit
5f84213c9802181b4d800615915e3c8dded7b94f)
(declare-function erc--open-target "erc" (target))
(declare-function erc--parse-nuh "erc" (string))
(declare-function erc--query-list "erc" ())
+(declare-function erc--remove-channel-users-but "erc" (nick))
(declare-function erc--target-from-string "erc" (string))
(declare-function erc--update-modes "erc" (raw-args))
(declare-function erc-active-buffer "erc" nil)
(buffer (erc-get-buffer ch proc)))
(pcase-let ((`(,nick ,login ,host)
(erc-parse-user (erc-response.sender parsed))))
- (erc-remove-channel-member buffer tgt)
(cond
((string= tgt (erc-current-nick))
(erc-display-message
(run-hook-with-args 'erc-kick-hook buffer)
(erc-with-buffer
(buffer)
- (erc-remove-channel-users))
+ (erc--remove-channel-users-but tgt))
(with-suppressed-warnings ((obsolete erc-delete-default-channel))
(erc-delete-default-channel ch buffer))
(erc-update-mode-line buffer))
((string= nick (erc-current-nick))
(erc-display-message
parsed 'notice buffer
- 'KICK-by-you ?k tgt ?c ch ?r reason))
+ 'KICK-by-you ?k tgt ?c ch ?r reason)
+ (erc-remove-channel-member buffer tgt))
(t (erc-display-message
- parsed 'notice buffer
- 'KICK ?k tgt ?n nick ?u login ?h host ?c ch ?r reason))))))
+ parsed 'notice buffer
+ 'KICK ?k tgt ?n nick ?u login ?h host ?c ch ?r reason)
+ (erc-remove-channel-member buffer tgt)))))
+ nil)
(define-erc-response-handler (MODE)
"Handle server mode changes." nil
;; When `buffer' is nil, `erc-remove-channel-member' and
;; `erc-remove-channel-users' do almost nothing, and the message
;; is displayed in the server buffer.
- (erc-remove-channel-member buffer nick)
(erc-display-message parsed 'notice buffer
'PART ?n nick ?u login
?h host ?c chnl ?r (or reason ""))
- (when (string= nick (erc-current-nick))
+ (cond
+ ((string= nick (erc-current-nick))
(run-hook-with-args 'erc-part-hook buffer)
(erc-with-buffer
(buffer)
- (erc-remove-channel-users))
+ (erc--remove-channel-users-but nick))
(with-suppressed-warnings ((obsolete erc-delete-default-channel))
(erc-delete-default-channel chnl buffer))
(erc-update-mode-line buffer)
(when (and erc-kill-buffer-on-part buffer)
(defvar erc-killing-buffer-on-part-p)
(let ((erc-killing-buffer-on-part-p t))
- (kill-buffer buffer))))))
+ (kill-buffer buffer))))
+ (t (erc-remove-channel-member buffer nick)))))
nil)
(define-erc-response-handler (PING)
(defun erc--get-server-user (nick)
(erc-get-server-user nick))
+(define-inline erc--remove-user-from-targets (downcased-nick buffers)
+ "Remove DOWNCASED-NICK from `erc-channel-members' in BUFFERS."
+ (inline-quote
+ (progn
+ (defvar erc-channel-members-changed-hook)
+ (dolist (buffer ,buffers)
+ (when (buffer-live-p buffer)
+ (with-current-buffer buffer
+ (remhash ,downcased-nick erc-channel-users)
+ (when erc-channel-members-changed-hook
+ (run-hooks 'erc-channel-members-changed-hook))))))))
+
(defmacro erc--with-dependent-type-match (type &rest features)
"Massage Custom :type TYPE with :match function that pre-loads FEATURES."
`(backquote-list* ',(car type)
erc-server-process))
(defun erc-remove-server-user (nick)
- "This function is for internal use only.
-
-Removes the user with nickname NICK from the `erc-server-users'
-hash table. This user is not removed from the
-`erc-channel-users' lists of other buffers.
-
-See also: `erc-remove-user'."
+ "Remove NICK from the session's `erc-server-users' table."
(erc-with-server-buffer
(remhash (erc-downcase nick) erc-server-users)))
(puthash (erc-downcase new-nick) cdata
erc-channel-users)))))))
-(defun erc-remove-channel-user (nick)
- "This function is for internal use only.
-
-Removes the user with nickname NICK from the `erc-channel-users'
-list for this channel. If this user is not in the
-`erc-channel-users' list of any other buffers, the user is also
-removed from the server's `erc-server-users' list.
+(defvar erc--forget-server-user-function
+ #'erc--forget-server-user-ignoring-queries
+ "Function to conditionally remove a user from `erc-server-users'.
+Called with a nick and its `erc-server-user' object.")
+
+(defun erc--forget-server-user (nick user)
+ "Remove NICK's USER from server table if they're not in any target buffers."
+ (unless (erc-server-user-buffers user)
+ (erc-remove-server-user nick)))
+
+(defun erc--forget-server-user-ignoring-queries (nick user)
+ "Remove NICK's USER from `erc-server-users' if they've parted all channels."
+ (let ((buffers (erc-server-user-buffers user)))
+ (when (or (null buffers)
+ (and (not erc--decouple-query-and-channel-membership-p)
+ (cl-every #'erc-query-buffer-p buffers)))
+ (when buffers
+ (erc--remove-user-from-targets (erc-downcase nick) buffers))
+ (erc-remove-server-user nick))))
-See also: `erc-remove-server-user' and `erc-remove-user'."
+(defun erc-remove-channel-user (nick)
+ "Remove NICK from the current target buffer's `erc-channel-members'.
+If this was their only target, also remove them from `erc-server-users'."
(let ((channel-data (erc-get-channel-user nick)))
(when channel-data
(let ((user (car channel-data)))
(delq (current-buffer)
(erc-server-user-buffers user)))
(remhash (erc-downcase nick) erc-channel-users)
- (if (null (erc-server-user-buffers user))
- (erc-remove-server-user nick))))))
+ (funcall erc--forget-server-user-function nick user)))))
(defun erc-remove-user (nick)
- "This function is for internal use only.
-
-Removes the user with nickname NICK from the `erc-server-users'
-list as well as from all `erc-channel-users' lists.
-
-See also: `erc-remove-server-user' and
-`erc-remove-channel-user'."
+ "Remove NICK from the server and all relevant channels tables."
(let ((user (erc-get-server-user nick)))
(when user
- (let ((buffers (erc-server-user-buffers user)))
- (dolist (buf buffers)
- (if (buffer-live-p buf)
- (with-current-buffer buf
- (remhash (erc-downcase nick) erc-channel-users)
- (run-hooks 'erc-channel-members-changed-hook)))))
+ (erc--remove-user-from-targets (erc-downcase nick)
+ (erc-server-user-buffers user))
(erc-remove-server-user nick))))
(defun erc-remove-channel-users ()
- "This function is for internal use only.
-
-Removes all users in the current channel. This is called by
-`erc-server-PART' and `erc-server-QUIT'."
+ "Drain current buffer's `erc-channel-members' table.
+Also remove members from the server table if this was their only buffer."
(when (erc--target-channel-p erc--target)
(setf (erc--target-channel-joined-p erc--target) nil))
(when (and erc-server-connected
erc-channel-users)
(clrhash erc-channel-users)))
+(defun erc--remove-channel-users-but (nick)
+ "Drain channel users and remove from server, sparing NICK."
+ (when-let ((users (erc-with-server-buffer erc-server-users))
+ (my-user (gethash (erc-downcase nick) users))
+ (original-function erc--forget-server-user-function)
+ (erc--forget-server-user-function
+ (if erc--decouple-query-and-channel-membership-p
+ erc--forget-server-user-function
+ (lambda (nick user)
+ (unless (eq user my-user)
+ (funcall original-function nick user))))))
+ (erc-remove-channel-users)))
+
(defmacro erc--define-channel-user-status-compat-getter (name c d)
"Define a gv getter for historical `erc-channel-user' status slot NAME.
Expect NAME to be a string, C to be its traditionally associated
`erc-kill-channel-hook' if a channel buffer was killed,
or `erc-kill-buffer-hook' if any other buffer."
(when (eq major-mode 'erc-mode)
- (erc-remove-channel-users)
+ (when-let ((erc--target)
+ (nick (erc-current-nick)))
+ (erc--remove-channel-users-but nick))
(cond
((eq (erc-server-buffer) (current-buffer))
(run-hooks 'erc-kill-server-hook))
(with-current-buffer (erc-d-t-wait-for 10 (get-buffer "Lal"))
(funcall expect 10 "<Lal> hello")
(erc-scenarios-common-say "hi")
+ (should-not (erc-get-channel-member "tester"))
(funcall expect 10 "is now known as Linguo")
;; No duplicate message.
(funcall expect -0.1 "is now known as Linguo")
;; No duplicate buffer.
(erc-d-t-wait-for 1 (equal (buffer-name) "Linguo"))
(should-not (get-buffer "Lal"))
+ ;; Channel member has been updated
+ (should-not (erc-get-channel-member "Lal"))
+ (should-not (erc-get-server-user "Lal"))
+ (should (erc-get-channel-member "Linguo"))
(erc-scenarios-common-say "howdy Linguo")))
(with-current-buffer "#foo"
(funcall expect 10 "is now known as Linguo")
(funcall expect -0.1 "is now known as Linguo")
+ (funcall expect 10 "has left"))
+
+ ;; User parting a common channel removes them from queries.
+ (with-current-buffer "Linguo"
+ (should-not (erc-get-channel-member "tester"))
+ (erc-d-t-wait-for 10 (null (erc-get-channel-member "Linguo")))
+ (should-not (erc-get-server-user "Linguo")))
+
+ ;; Leaving the client's only channel doesn't remove its user data
+ ;; from the server table (see below, after "get along ...").
+ (with-current-buffer "#foo"
(erc-scenarios-common-say "/part"))
+ ;; Server and "channel" user are *not* (re)created upon receiving
+ ;; a direct message for a user we already have an open query with
+ ;; but with whom we no longer share a channel.
(with-current-buffer "Linguo"
- (funcall expect 10 "get along"))))
+ (funcall expect 10 "get along")
+ (should-not (erc-get-channel-member "Linguo"))
+ (should-not (erc-get-channel-member "tester"))
+ (should (erc-get-server-user "tester")))))
;; Someone you have a query with disconnects and reconnects under a
;; new nick (perhaps due to their client appending a backtick or