Prefer erc-target to erc-default-target

* etc/ERC-NEWS: Mention `erc-target' and new `erc-server-buffer-p'
alias.
* lisp/erc/erc-backend.el (erc-process-sentinel): Set `joined-p' slot
of `erc--target-channel' object to nil when applicable.
(erc-server-JOIN): Mark `erc--target-channel' object as being joined.
* lisp/erc/erc-common.el (erc--target-channel): Add `joined-p' slot.
Use hyphenated name so accessor function's name ends in "joined-p"
rather than "joinedp".  Note that this will always be nil when
disconnected.
(erc--target): Relocate here from erc.el.
(erc-target): New public API function to return the current buffer's
target as a string, even in channels that have been unjoined.
* lisp/erc/erc-networks.el (erc--default-target): Remove forward
declaration.
(erc-networks--id-reload): Use `erc-target' instead of
`erc--default-target' as predicate for visiting target buffers.
* lisp/erc/erc.el (erc-remove-channel-users): Set channel "joinedness"
to nil in `erc--target-channel' object, when applicable.
(erc--target): Move to erc-common.
(erc--default-target): Remove, replaced by new function `erc-target'.
(erc-query-buffer-p): Use `erc-target'.
(erc-after-connect): Revise doc string.
(erc-connection-established): Revise doc string and move
`erc-unhide-query-prompt' business before hook.
(erc--current-buffer-joined-p): Remove comment and use new `joined-p'
slot of `erc--target-channel' for determining "joinedness" of channel.
(erc-kill-buffer-function): Use `erc--target-channel-p' for detecting
whether the buffer is a channel buffer.
* test/lisp/erc/erc-networks-tests.el
(erc-networks--shrink-ids-and-buffer-names--hook-collapse-target):
Remove comment.
* test/lisp/erc/erc-scenarios-base-reuse-buffers.el
(erc-scenarios-common--base-reuse-buffers-channel-buffers):
Clarify assertion.
* test/lisp/erc/erc-tests.el (erc-with-all-buffers-of-server):
Replace `erc-default-recipients' with `erc--target'.
(erc--target-from-string): Update expected shape of
`erc--target-channel' struct with new `joined-p' slot.
(erc-message): Set `erc--target' in buffer "#chan".  (Bug#66578)
This commit is contained in:
F. Jason Park 2023-10-15 13:20:07 -07:00
parent 8cf66ab1e5
commit f109396fe3
8 changed files with 72 additions and 76 deletions

View file

@ -425,6 +425,24 @@ use of 'insert-before-markers' instead of 'insert'. As always, users
feeling unduly inconvenienced by these changes are encouraged to voice
their concerns on the bug list.
*** Introducing new ways to detect ERC buffer types.
The old standby 'erc-default-target' has served ERC well for over two
decades. But a lesser known gotcha affecting its use has always
haunted an unlucky few, that is, the function has always returned
non-nil in "unjoined" channel buffers (those that the client has
parted with or been kicked from). While perhaps not itself a major
footgun, recessive pitfalls rooted in this subtlety continue to affect
dependent functions, like 'erc-get-buffer'.
To discourage misuse of 'erc-default-target', ERC 5.6 offers an
alternative in the function 'erc-target', which is identical to the
former except for its disregard for "joinedness." As a related bonus,
the dependent function 'erc-server-buffer-p' is being rebranded as
'erc-server-or-unjoined-channel-buffer-p'. Unfortunately, this
release lacks a similar solution for detecting "joinedness" directly,
but users can turn to 'xor'-ing 'erc-default-target' and 'erc-target'
as a makeshift kludge.
*** Miscellaneous changes
Two helper macros from GNU ELPA's Compat library are now available to
third-party modules as 'erc-compat-call' and 'erc-compat-function'.

View file

@ -1103,7 +1103,7 @@ Change value of property `erc-prompt' from t to `hidden'."
(erc--register-connection)
;; assume event is 'failed
(erc-with-all-buffers-of-server cproc nil
(setq erc-server-connected nil))
(setq erc-server-connected nil))
(when erc-server-ping-handler
(progn (cancel-timer erc-server-ping-handler)
(setq erc-server-ping-handler nil)))
@ -1111,6 +1111,8 @@ Change value of property `erc-prompt' from t to `hidden'."
(erc-current-nick) (system-name) "")
(dolist (buf (erc-buffer-filter (lambda () (boundp 'erc-channel-users)) cproc))
(with-current-buffer buf
(when (erc--target-channel-p erc--target)
(setf (erc--target-channel-joined-p erc--target) nil))
(setq erc-channel-users (make-hash-table :test 'equal))))
;; Hide the prompt
(erc--hide-prompt cproc)
@ -1731,6 +1733,7 @@ add things to `%s' instead."
(with-suppressed-warnings
((obsolete erc-add-default-channel))
(erc-add-default-channel chnl))
(setf (erc--target-channel-joined-p erc--target) t)
(erc-server-send (format "MODE %s" chnl)))
(erc-with-buffer (chnl proc)
(erc-channel-begin-receiving-names))

View file

@ -81,16 +81,13 @@
(string "" :type string :documentation "Received name of target.")
(symbol nil :type symbol :documentation "Case-mapped name as symbol."))
;; At some point, it may make sense to add a query type with an
;; account field, which may help support reassociation across
;; reconnects and nick changes (likely requires v3 extensions).
;;
;; These channel variants should probably take on a `joined' field to
;; track "joinedness", which `erc-server-JOIN', `erc-server-PART',
;; etc. should toggle. Functions like `erc--current-buffer-joined-p'
;; may find it useful.
;; At some point, it may make sense to add a separate query type,
;; possibly with an account field to help reassociation across
;; reconnects and nick changes.
(cl-defstruct (erc--target-channel (:include erc--target))
(joined-p nil :type boolean :documentation "Whether channel is joined."))
(cl-defstruct (erc--target-channel (:include erc--target)))
(cl-defstruct (erc--target-channel-local (:include erc--target-channel)))
;; Beginning in 5.5/29.1, the `tags' field may take on one of two
@ -427,6 +424,13 @@ nil."
,@forms))
,process)))
(defvar-local erc--target nil
"A permanent `erc--target' struct instance in channel and query buffers.")
(define-inline erc-target ()
"Return target of current buffer, if any, as a string."
(inline-quote (and erc--target (erc--target-string erc--target))))
(defun erc-log-aux (string)
"Do the debug logging of STRING."
(let ((cb (current-buffer))

View file

@ -53,7 +53,6 @@
(defvar erc-server-process)
(defvar erc-session-server)
(declare-function erc--default-target "erc" nil)
(declare-function erc--get-isupport-entry "erc-backend" (key &optional single))
(declare-function erc-buffer-filter "erc" (predicate &optional proc))
(declare-function erc-current-nick "erc" nil)
@ -991,12 +990,11 @@ object."
(erc-networks--id-qualifying-len nid))
(erc-networks--rename-server-buffer (or proc erc-server-process) parsed)
(erc-networks--shrink-ids-and-buffer-names-any)
(erc-with-all-buffers-of-server
erc-server-process #'erc--default-target
(when-let* ((new-name (erc-networks--reconcile-buffer-names erc--target
nid))
((not (equal (buffer-name) new-name))))
(rename-buffer new-name 'unique))))
(erc-with-all-buffers-of-server erc-server-process #'erc-target
(when-let
((new-name (erc-networks--reconcile-buffer-names erc--target nid))
((not (equal (buffer-name) new-name))))
(rename-buffer new-name 'unique))))
(cl-defgeneric erc-networks--id-ensure-comparable (self other)
"Take measures to ensure two net identities are in comparable states.")

View file

@ -534,6 +534,8 @@ See also: `erc-remove-server-user' and
Removes all users in the current channel. This is called by
`erc-server-PART' and `erc-server-QUIT'."
(when (erc--target-channel-p erc--target)
(setf (erc--target-channel-joined-p erc--target) nil))
(when (and erc-server-connected
(erc-server-process-alive)
(hash-table-p erc-channel-users))
@ -1391,16 +1393,6 @@ buffer during local-module setup and `erc-mode-hook' activation.")
#'make-erc--target)
:string string :symbol (intern (erc-downcase string))))
(defvar-local erc--target nil
"Info about a buffer's target, if any.")
;; Temporary internal getter to ease transition to `erc--target'
;; everywhere. Will be replaced by updated `erc-default-target'.
(defun erc--default-target ()
"Return target string or nil."
(when erc--target
(erc--target-string erc--target)))
(defun erc-once-with-server-event (event f)
"Run function F the next time EVENT occurs in the `current-buffer'.
@ -1504,7 +1496,7 @@ If BUFFER is nil, the current buffer is used."
"Return non-nil if BUFFER is an ERC query buffer.
If BUFFER is nil, the current buffer is used."
(with-current-buffer (or buffer (current-buffer))
(let ((target (erc-default-target)))
(let ((target (erc-target)))
(and (eq major-mode 'erc-mode)
target
(not (memq (aref target 0) '(?# ?& ?+ ?!)))))))
@ -2492,10 +2484,13 @@ in here get called with three parameters, SERVER, PORT and NICK."
:type '(repeat function))
(defcustom erc-after-connect nil
"Functions called after connecting to a server.
This functions in this variable gets executed when an end of MOTD
has been received. All functions in here get called with the
parameters SERVER and NICK."
"Abnormal hook run upon establishing a logical IRC connection.
Runs on MOTD's end when `erc-server-connected' becomes non-nil.
ERC calls members with `erc-server-announced-name', falling back
to the 376/422 message's \"sender\", as well as the current nick,
as given by the 376/422 message's \"target\" parameter, which is
typically the same as that reported by `erc-current-nick'."
:package-version '(ERC . "5.6") ; FIXME sync on release
:group 'erc-hooks
:type '(repeat function))
@ -5749,9 +5744,7 @@ See also: `erc-echo-notice-in-user-buffers',
(erc-load-script f)))))
(defun erc-connection-established (proc parsed)
"Run just after connection.
Set user modes and run `erc-after-connect' hook."
"Set user mode and run `erc-after-connect' hook in server buffer."
(with-current-buffer (process-buffer proc)
(unless erc-server-connected ; only once per session
(let ((server (or erc-server-announced-name
@ -5770,14 +5763,11 @@ Set user modes and run `erc-after-connect' hook."
(erc-update-mode-line)
(erc-set-initial-user-mode nick buffer)
(erc-server-setup-periodical-ping buffer)
(run-hook-with-args 'erc-after-connect server nick))))
(when erc-unhide-query-prompt
(erc-with-all-buffers-of-server proc
nil ; FIXME use `erc--target' after bug#48598
(when (and (erc-default-target)
(not (erc-channel-p (car erc-default-recipients))))
(erc--unhide-prompt)))))
(when erc-unhide-query-prompt
(erc-with-all-buffers-of-server erc-server-process nil
(when (and erc--target (not (erc--target-channel-p erc--target)))
(erc--unhide-prompt))))
(run-hook-with-args 'erc-after-connect server nick)))))
(defun erc-set-initial-user-mode (nick buffer)
"If `erc-user-mode' is non-nil for NICK, set the user modes.
@ -7065,25 +7055,11 @@ See also `erc-downcase'."
;; default target handling
(defun erc--current-buffer-joined-p ()
"Return whether the current target buffer is joined."
;; This may be a reliable means of detecting subscription status,
;; but it's also roundabout and awkward. Perhaps it's worth
;; discussing adding a joined slot to `erc--target' for this.
"Return non-nil if the current buffer is a channel and is joined."
(cl-assert erc--target)
(and (erc--target-channel-p erc--target)
(erc-get-channel-user (erc-current-nick)) t))
;; While `erc-default-target' happens to return nil in channel buffers
;; you've parted or from which you've been kicked, using it to detect
;; whether a channel is currently joined may become unreliable in the
;; future. For now, third-party code can use
;;
;; (erc-get-channel-user (erc-current-nick))
;;
;; A predicate may be provided eventually. For retrieving a target's
;; name regardless of subscription or connection status, new library
;; code should use `erc--default-target'. Third-party code should
;; continue to use `erc-default-target'.
(erc--target-channel-joined-p erc--target)
t))
(defun erc-default-target ()
"Return the current channel or query target, if any.
@ -8315,6 +8291,7 @@ See also `kill-buffer'."
:group 'erc-hooks
:type 'hook)
;; FIXME alias and deprecate current *-function suffixed name.
(defun erc-kill-buffer-function ()
"Function to call when an ERC buffer is killed.
This function should be on `kill-buffer-hook'.
@ -8328,7 +8305,7 @@ or `erc-kill-buffer-hook' if any other buffer."
(cond
((eq (erc-server-buffer) (current-buffer))
(run-hooks 'erc-kill-server-hook))
((erc-channel-p (or (erc-default-target) (buffer-name)))
((erc--target-channel-p erc--target)
(run-hooks 'erc-kill-channel-hook))
(t
(run-hooks 'erc-kill-buffer-hook)))))

View file

@ -623,11 +623,6 @@
:symbol 'foonet/dummy
:parts [foonet "dummy"]
:len 2)
;; `erc-kill-buffer-function' uses legacy target detection
;; but falls back on buffer name, so no need for:
;;
;; erc-default-recipients '("#a")
;;
erc--target (erc--target-from-string "#a")
erc-server-process (with-temp-buffer
(erc-networks-tests--create-dead-proc)))

View file

@ -124,6 +124,7 @@ Adapted from scenario clash-of-chans/uniquify described in Bug#48598:
(erc-d-t-search-for 1 "shake my sword")
(erc-cmd-PART "#chan")
(funcall expect 3 "You have left channel #chan")
(should-not (erc-get-channel-user (erc-current-nick)))
(erc-cmd-JOIN "#chan")))
(ert-info ("Part #chan@barnet")
@ -139,6 +140,7 @@ Adapted from scenario clash-of-chans/uniquify described in Bug#48598:
(get-buffer "#chan/127.0.0.1<3>"))
(ert-info ("Activity continues in new, <n>-suffixed #chan@foonet buffer")
;; The first /JOIN did not cause the same buffer to be reused.
(with-current-buffer "#chan/127.0.0.1"
(should-not (erc-get-channel-user (erc-current-nick))))
(with-current-buffer "#chan/127.0.0.1<3>"

View file

@ -69,26 +69,25 @@
(with-current-buffer (get-buffer-create "#foo")
(erc-mode)
(setq erc-server-process proc-exnet)
(setq erc-default-recipients '("#foo")))
(setq erc--target (erc--target-from-string "#foo")))
(with-current-buffer (get-buffer-create "#spam")
(erc-mode)
(setq erc-server-process proc-onet)
(setq erc-default-recipients '("#spam")))
(setq erc--target (erc--target-from-string "#spam")))
(with-current-buffer (get-buffer-create "#bar")
(erc-mode)
(setq erc-server-process proc-onet)
(setq erc-default-recipients '("#bar")))
(setq erc--target (erc--target-from-string "#bar")))
(with-current-buffer (get-buffer-create "#baz")
(erc-mode)
(setq erc-server-process proc-exnet)
(setq erc-default-recipients '("#baz")))
(setq erc--target (erc--target-from-string "#baz")))
(should (eq (get-buffer-process "ExampleNet") proc-exnet))
(erc-with-all-buffers-of-server (get-buffer-process "ExampleNet")
nil
(erc-with-all-buffers-of-server (get-buffer-process "ExampleNet") nil
(kill-buffer))
(should-not (get-buffer "ExampleNet"))
@ -102,8 +101,7 @@
(calls 0)
(get-test (lambda () (cl-incf calls) test)))
(erc-with-all-buffers-of-server proc-onet
(funcall get-test)
(erc-with-all-buffers-of-server proc-onet (funcall get-test)
(kill-buffer))
(should (= calls 1)))
@ -812,7 +810,7 @@
(ert-deftest erc--target-from-string ()
(should (equal (erc--target-from-string "#chan")
#s(erc--target-channel "#chan" \#chan)))
#s(erc--target-channel "#chan" \#chan nil)))
(should (equal (erc--target-from-string "Bob")
#s(erc--target "Bob" bob)))
@ -820,7 +818,7 @@
(let ((erc--isupport-params (make-hash-table)))
(puthash 'CHANTYPES '("&#") erc--isupport-params)
(should (equal (erc--target-from-string "&Bitlbee")
#s(erc--target-channel-local "&Bitlbee" &bitlbee)))))
#s(erc--target-channel-local "&Bitlbee" &bitlbee nil)))))
(ert-deftest erc--modify-local-map ()
(when (and (bound-and-true-p erc-irccontrols-mode)
@ -1846,6 +1844,7 @@
(erc-mode)
(setq erc-server-process (buffer-local-value 'erc-server-process
(get-buffer "ExampleNet"))
erc--target (erc--target-from-string "#chan")
erc-default-recipients '("#chan")
erc-channel-users (make-hash-table :test 'equal)
erc-network 'ExampleNet)