1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-15 10:30:25 -08:00

Improve ERC's handling of multiline prompt input

* lisp/erc/erc.el (erc--pre-send-split-functions): Add new internal
hook allowing members to revise individual lines before sending.  This
was created with an eye toward possibly exporting it publicly as a
customizable option.
(erc-last-input-time): Tweak meaning of variable to match likely
original intent, which is that it's only updated on successful calls
to `erc-send-current-line'.
(erc--discard-trailing-multiline-nulls): Conditionally truncate list
of lines to be sent, skipping trailing blanks.  This constitutes a
behavioral change.  But, considering the nature of the bug being
fixed, it is thought to be justified.
(erc--input-split): Add new internal struct containing split input
lines and flag for command detection.
(erc--input-line-delim-regexp): Add regex var for splitting multiline
prompt input.
(erc--blank-in-multiline-p): Add helper for detecting blank lines.
(erc--check-prompt-input-for-multiline-blanks,
erc--check-prompt-input-for-point-in-bounds,
erc--check-prompt-input-for-running-process): New functions to
encapsulate logic for various pre-flight idiot checks.
(erc--check-prompt-input-functions): Add new hook for validating
prompt input prior to clearing it, internal for now.
(erc-send-current-line): Pre-screen for blank lines and bail out if
necessary.
(erc-send-input): Add optional param to skip checking for blank lines.
Call hook `erc--pre-send-split-functions'.

* test/lisp/erc/erc-tests.el (erc-ring-previous-command): Use new test
helper.
(erc--input-line-delim-regexp,
erc--blank-in-multiline-input-p): Add tests.
(erc-tests--send-prep, erc-tests--set-fake-server-process,
erc-tests--with-process-input-spy): Add test helpers.
(erc--check-prompt-input-functions, erc-send-current-line,
erc-send-whitespace-lines): Add tests.
(Bug#54536)
This commit is contained in:
F. Jason Park 2022-03-21 05:40:16 -07:00
parent a9d89d083a
commit f46547294d
2 changed files with 328 additions and 41 deletions

View file

@ -588,6 +588,214 @@
(kill-buffer "*erc-protocol*")
(should-not erc-debug-irc-protocol)))
(ert-deftest erc--input-line-delim-regexp ()
(let ((p erc--input-line-delim-regexp))
;; none
(should (equal '("a" "b") (split-string "a\r\nb" p)))
(should (equal '("a" "b") (split-string "a\nb" p)))
(should (equal '("a" "b") (split-string "a\rb" p)))
;; one
(should (equal '("") (split-string "" p)))
(should (equal '("a" "" "b") (split-string "a\r\rb" p)))
(should (equal '("a" "" "b") (split-string "a\n\rb" p)))
(should (equal '("a" "" "b") (split-string "a\n\nb" p)))
(should (equal '("a" "" "b") (split-string "a\r\r\nb" p)))
(should (equal '("a" "" "b") (split-string "a\n\r\nb" p)))
(should (equal '("a" "") (split-string "a\n" p)))
(should (equal '("a" "") (split-string "a\r" p)))
(should (equal '("a" "") (split-string "a\r\n" p)))
(should (equal '("" "b") (split-string "\nb" p)))
(should (equal '("" "b") (split-string "\rb" p)))
(should (equal '("" "b") (split-string "\r\nb" p)))
;; two
(should (equal '("" "") (split-string "\r" p)))
(should (equal '("" "") (split-string "\n" p)))
(should (equal '("" "") (split-string "\r\n" p)))
;; three
(should (equal '("" "" "") (split-string "\r\r" p)))
(should (equal '("" "" "") (split-string "\n\n" p)))
(should (equal '("" "" "") (split-string "\n\r" p)))))
(ert-deftest erc--blank-in-multiline-input-p ()
(let ((check (lambda (s)
(erc--blank-in-multiline-input-p
(split-string s erc--input-line-delim-regexp)))))
(ert-info ("With `erc-send-whitespace-lines'")
(let ((erc-send-whitespace-lines t))
(should (funcall check ""))
(should-not (funcall check "\na"))
(should-not (funcall check "/msg a\n")) ; real /cmd
(should-not (funcall check "a\n\nb")) ; "" allowed
(should-not (funcall check "/msg a\n\nb")) ; non-/cmd
(should-not (funcall check " "))
(should-not (funcall check "\t"))
(should-not (funcall check "a\nb"))
(should-not (funcall check "a\n "))
(should-not (funcall check "a\n \t"))
(should-not (funcall check "a\n \f"))
(should-not (funcall check "a\n \nb"))
(should-not (funcall check "a\n \t\nb"))
(should-not (funcall check "a\n \f\nb"))))
(should (funcall check ""))
(should (funcall check " "))
(should (funcall check "\t"))
(should (funcall check "a\n\nb"))
(should (funcall check "a\n\nb"))
(should (funcall check "a\n "))
(should (funcall check "a\n \t"))
(should (funcall check "a\n \f"))
(should (funcall check "a\n \nb"))
(should (funcall check "a\n \t\nb"))
(should-not (funcall check "a\rb"))
(should-not (funcall check "a\nb"))
(should-not (funcall check "a\r\nb"))))
(defun erc-tests--with-process-input-spy (test)
(with-current-buffer (get-buffer-create "FakeNet")
(let* ((erc-pre-send-functions
(remove #'erc-add-to-input-ring erc-pre-send-functions)) ; for now
(inhibit-message noninteractive)
(erc-server-current-nick "tester")
(erc-last-input-time 0)
erc-accidental-paste-threshold-seconds
;;
calls)
(cl-letf (((symbol-function 'erc-process-input-line)
(lambda (&rest r) (push r calls)))
((symbol-function 'erc-server-buffer)
(lambda () (current-buffer))))
(erc-tests--send-prep)
(funcall test (lambda () (pop calls)))))
(when noninteractive (kill-buffer))))
(ert-deftest erc--check-prompt-input-functions ()
(erc-tests--with-process-input-spy
(lambda (next)
(ert-info ("Errors when point not in prompt area") ; actually just dings
(insert "/msg #chan hi")
(forward-line -1)
(let ((e (should-error (erc-send-current-line))))
(should (equal "Point is not in the input area" (cadr e))))
(goto-char (point-max))
(ert-info ("Input remains untouched")
(should (save-excursion (erc-bol) (looking-at "/msg #chan hi")))))
(ert-info ("Errors when no process running")
(let ((e (should-error (erc-send-current-line))))
(should (equal "ERC: No process running" (cadr e))))
(ert-info ("Input remains untouched")
(should (save-excursion (erc-bol) (looking-at "/msg #chan hi")))))
(ert-info ("Errors when line contains empty newline")
(erc-bol)
(delete-region (point) (point-max))
(insert "one\n")
(let ((e (should-error (erc-send-current-line))))
(should (equal "Blank line - ignoring..." (cadr e))))
(goto-char (point-max))
(ert-info ("Input remains untouched")
(should (save-excursion (goto-char erc-input-marker)
(looking-at "one\n")))))
(should (= 0 erc-last-input-time))
(should-not (funcall next)))))
;; These also indirectly tests `erc-send-input'
(ert-deftest erc-send-current-line ()
(erc-tests--with-process-input-spy
(lambda (next)
(erc-tests--set-fake-server-process "sleep" "1")
(should (= 0 erc-last-input-time))
(ert-info ("Simple command")
(insert "/msg #chan hi")
(erc-send-current-line)
(ert-info ("Prompt restored")
(forward-line 0)
(should (looking-at-p erc-prompt)))
(ert-info ("Input cleared")
(erc-bol)
(should (eq (point) (point-max))))
;; Commands are forced (no flood protection)
(should (equal (funcall next) '("/msg #chan hi\n" t nil))))
(ert-info ("Simple non-command")
(insert "hi")
(erc-send-current-line)
(should (eq (point) (point-max)))
(should (save-excursion (forward-line -1)
(search-forward "<tester> hi")))
;; Non-ommands are forced only when `erc-flood-protect' is nil
(should (equal (funcall next) '("hi\n" nil t))))
(should (consp erc-last-input-time)))))
(ert-deftest erc-send-whitespace-lines ()
(erc-tests--with-process-input-spy
(lambda (next)
(erc-tests--set-fake-server-process "sleep" "1")
(setq-local erc-send-whitespace-lines t)
(ert-info ("Multiline hunk with blank line correctly split")
(insert "one\n\ntwo")
(erc-send-current-line)
(ert-info ("Prompt restored")
(forward-line 0)
(should (looking-at-p erc-prompt)))
(ert-info ("Input cleared")
(erc-bol)
(should (eq (point) (point-max))))
(should (equal (funcall next) '("two\n" nil t)))
(should (equal (funcall next) '("\n" nil t)))
(should (equal (funcall next) '("one\n" nil t))))
(ert-info ("Multiline hunk with trailing newline filtered")
(insert "hi\n")
(erc-send-current-line)
(ert-info ("Input cleared")
(erc-bol)
(should (eq (point) (point-max))))
(should (equal (funcall next) '("hi\n" nil t)))
(should-not (funcall next)))
(ert-info ("Multiline hunk with trailing carriage filtered")
(insert "hi\r")
(erc-send-current-line)
(ert-info ("Input cleared")
(erc-bol)
(should (eq (point) (point-max))))
(should (equal (funcall next) '("hi\n" nil t)))
(should-not (funcall next)))
(ert-info ("Multiline command with trailing blank filtered")
(pcase-dolist (`(,p . ,q)
'(("/a b\r" "/a b\n") ("/a b\n" "/a b\n")
("/a b\n\n" "/a b\n") ("/a b\r\n" "/a b\n")
("a b\nc\n\n" "c\n" "a b\n")
("/a b\nc\n\n" "c\n" "/a b\n")
("/a b\n\nc\n\n" "c\n" "\n" "/a b\n")))
(insert p)
(erc-send-current-line)
(erc-bol)
(should (eq (point) (point-max)))
(while q
(should (equal (funcall next) (list (pop q) nil t))))
(should-not (funcall next))))
(ert-info ("Multiline hunk with trailing whitespace not filtered")
(insert "there\n ")
(erc-send-current-line)
(should (equal (funcall next) '(" \n" nil t)))
(should (equal (funcall next) '("there\n" nil t)))
(should-not (funcall next))))))
;; The point of this test is to ensure output is handled identically
;; regardless of whether a command handler is summoned.