Merge remote-tracking branch 'savannah/master' into master-android-1

This commit is contained in:
Po Lu 2023-10-03 08:59:31 +08:00
commit 3f0461e539
12 changed files with 330 additions and 141 deletions

View file

@ -221,6 +221,12 @@ atop any message. The new companion option 'erc-echo-timestamp-zone'
determines the default timezone when not specified with a prefix
argument.
** Option 'erc-warn-about-blank-lines' is more informative.
Enabled by default, this option now produces more useful feedback
whenever ERC rejects prompt input containing whitespace-only lines.
When paired with option 'erc-send-whitespace-lines', ERC echoes a
tally of blank lines padded and trailing blanks culled.
** Miscellaneous UX changes.
Some minor quality-of-life niceties have finally made their way to
ERC. For example, fool visibility has become togglable with the new
@ -281,7 +287,7 @@ For starters, the 'cursor-sensor-functions' property no longer
contains unique closures and thus no longer proves effective for
traversing messages. To compensate, a new property, 'erc-timestamp',
now spans message bodies but not the newlines delimiting them. Also
affecting the `stamp' module is the deprecation of the function
affecting the 'stamp' module is the deprecation of the function
'erc-insert-aligned' and its removal from client code. Additionally,
the module now merges its 'invisible' property with existing ones and
includes all white space around stamps when doing so.

View file

@ -60,6 +60,7 @@
((obsolete erc-send-this))
erc-send-this))))
(lines nil :type (list-of string))
(abortp nil :type (list-of symbol))
(cmdp nil :type boolean))
(cl-defstruct (erc-server-user (:type vector) :named)

View file

@ -444,6 +444,21 @@ If START or END is negative, it counts from the end."
(cons '("\\`irc6?s?://" . erc-compat--29-browse-url-irc)
existing))))))
;; We can't store (TICKS . HZ) style timestamps on 27 and 28 because
;; `time-less-p' and friends do
;;
;; message("obsolete timestamp with cdr ...", ...)
;; decode_lisp_time(_, WARN_OBSOLETE_TIMESTAMPS, ...)
;; lisp_time_struct(...)
;; time_cmp(...)
;;
;; which spams *Messages* (and stderr when running the test suite).
(defmacro erc-compat--current-lisp-time ()
"Return `current-time' as a (TICKS . HZ) pair on 29+."
(if (>= emacs-major-version 29)
'(let (current-time-list) (current-time))
'(current-time)))
(provide 'erc-compat)

View file

@ -158,9 +158,8 @@ You can put this on `erc-insert-modify-hook' and/or `erc-send-modify-hook'."
(when (or erc-fill--function erc-fill-function)
;; skip initial empty lines
(goto-char (point-min))
(save-match-data
(while (and (looking-at "[ \t\n]*$")
(= (forward-line 1) 0))))
(while (and (looking-at (rx bol (* (in " \t")) eol))
(zerop (forward-line 1))))
(unless (eobp)
(save-restriction
(narrow-to-region (point) (point-max))

View file

@ -128,6 +128,11 @@ may be nil, is the number of lines between `window-start' and
That is, ERC recalculates the window's start instead of blindly
restoring it.")
;; Unfortunately, this doesn't work when `erc-scrolltobottom-relaxed'
;; is enabled (scaling up still moves the prompt).
(defvar erc--scrolltobottom-post-ignore-commands '(text-scale-adjust)
"Commands to skip instead of force-scroll on `post-command-hook'.")
(defvar erc--scrolltobottom-relaxed-skip-commands
'(recenter-top-bottom scroll-down-command)
"Commands exempt from triggering a stash and restore of `window-start'.
@ -158,7 +163,8 @@ unnarrowed."
((= (nth 2 found)
(count-screen-lines (window-start) (point-max)))))
(set-window-start (selected-window) (nth 1 found))
(erc--scrolltobottom-confirm))
(unless (memq this-command erc--scrolltobottom-post-ignore-commands)
(erc--scrolltobottom-confirm)))
(setq erc--scrolltobottom-window-info nil)))
(defun erc--scrolltobottom-on-pre-command-relaxed ()
@ -372,7 +378,7 @@ Put this function on `erc-insert-post-hook' and/or `erc-send-post-hook'."
;;;###autoload(autoload 'erc-keep-place-mode "erc-goodies" nil t)
(define-erc-module keep-place nil
"Leave point above un-viewed text in other channels."
((add-hook 'erc-insert-pre-hook #'erc-keep-place 85))
((add-hook 'erc-insert-pre-hook #'erc-keep-place 65))
((remove-hook 'erc-insert-pre-hook #'erc-keep-place)))
(defcustom erc-keep-place-indicator-style t
@ -467,7 +473,7 @@ and `keep-place-indicator' in different buffers."
((memq 'keep-place erc-modules)
(erc-keep-place-mode +1))
;; Enable a local version of `keep-place-mode'.
(t (add-hook 'erc-insert-pre-hook #'erc-keep-place 85 t)))
(t (add-hook 'erc-insert-pre-hook #'erc-keep-place 65 t)))
(if (pcase erc-keep-place-indicator-buffer-type
('target erc--target)
('server (not erc--target))
@ -490,7 +496,7 @@ That is, ensure the local module can survive a user toggling the
global one."
(if erc-keep-place-mode
(remove-hook 'erc-insert-pre-hook #'erc-keep-place t)
(add-hook 'erc-insert-pre-hook #'erc-keep-place 85 t)))
(add-hook 'erc-insert-pre-hook #'erc-keep-place 65 t)))
(defun erc-keep-place-move (pos)
"Move keep-place indicator to current line or POS.

View file

@ -27,6 +27,9 @@
;; needs work. Usage: Type / C-e C-h when in Ibuffer-mode to see new
;; limiting commands
;; This library does not contain a module, but you can `require' it
;; after loading `erc' to make use of its functionality.
;;; Code:
(require 'ibuffer)
@ -118,11 +121,11 @@
(define-ibuffer-column
erc-members (:name "Users")
(if (and (eq major-mode 'erc-mode)
(boundp 'erc-channel-users)
(hash-table-p erc-channel-users)
(> (hash-table-size erc-channel-users) 0))
(number-to-string (hash-table-size erc-channel-users))
(if-let ((table (or erc-channel-users erc-server-users))
((hash-table-p table))
(count (hash-table-count table))
((> count 0)))
(number-to-string count)
""))
(define-ibuffer-column erc-away (:name "A")
@ -177,8 +180,7 @@
(defvar erc-ibuffer-limit-map nil
"Prefix keymap to use for ERC related limiting.")
(define-prefix-command 'erc-ibuffer-limit-map)
;; FIXME: Where is `ibuffer-limit-by-erc-server' defined?
(define-key 'erc-ibuffer-limit-map (kbd "s") 'ibuffer-limit-by-erc-server)
(define-key 'erc-ibuffer-limit-map (kbd "s") #'ibuffer-filter-by-erc-server)
(define-key ibuffer-mode-map (kbd "/ \C-e") 'erc-ibuffer-limit-map)
(provide 'erc-ibuffer)

View file

@ -215,7 +215,7 @@ the stamp passed to `erc-insert-timestamp-function'.")
(cl-defgeneric erc-stamp--current-time ()
"Return a lisp time object to associate with an IRC message.
This becomes the message's `erc-timestamp' text property."
(let (current-time-list) (current-time)))
(erc-compat--current-lisp-time))
(cl-defmethod erc-stamp--current-time :around ()
(or erc-stamp--current-time (cl-call-next-method)))

View file

@ -252,7 +252,14 @@ node `(auth) Top' and Info node `(erc) auth-source'.")
:type 'boolean)
(defcustom erc-warn-about-blank-lines t
"Warn the user if they attempt to send a blank line."
"Warn the user if they attempt to send a blank line.
When non-nil, ERC signals a `user-error' upon encountering prompt
input containing empty or whitespace-only lines. When nil, ERC
still inhibits sending but does so silently. With the companion
option `erc-send-whitespace-lines' enabled, ERC sends pending
input and prints a message in the echo area indicating the amount
of padding and/or stripping applied, if any. Setting this option
to nil suppresses such reporting."
:group 'erc
:type 'boolean)
@ -264,8 +271,8 @@ node `(auth) Top' and Info node `(erc) auth-source'.")
(defcustom erc-inhibit-multiline-input nil
"When non-nil, conditionally disallow input consisting of multiple lines.
Issue an error when the number of input lines submitted for
sending exceeds this value. The value t means disallow more
than 1 line of input."
sending meets or exceeds this value. The value t is synonymous
with a value of 2 and means disallow more than 1 line of input."
:package-version '(ERC . "5.5")
:group 'erc
:type '(choice integer boolean))
@ -1095,9 +1102,10 @@ subprotocols should probably be handled manually."
(define-obsolete-variable-alias 'erc--pre-send-split-functions
'erc--input-review-functions "30.1")
(defvar erc--input-review-functions '(erc--discard-trailing-multiline-nulls
erc--split-lines
erc--run-input-validation-checks)
(defvar erc--input-review-functions '(erc--split-lines
erc--run-input-validation-checks
erc--discard-trailing-multiline-nulls
erc--inhibit-slash-cmd-insertion)
"Special hook for reviewing and modifying prompt input.
ERC runs this before clearing the prompt and before running any
send-related hooks, such as `erc-pre-send-functions'. Thus, it's
@ -6424,20 +6432,6 @@ holds off on submitting it, for obvious reasons."
(defvar erc-command-regexp "^/\\([A-Za-z']+\\)\\(\\s-+.*\\|\\s-*\\)$"
"Regular expression used for matching commands in ERC.")
(defun erc--blank-in-multiline-input-p (lines)
"Detect whether LINES contains a blank line.
When `erc-send-whitespace-lines' is in effect, return nil if
LINES is multiline or the first line is non-empty. When
`erc-send-whitespace-lines' is nil, return non-nil when any line
is empty or consists of one or more spaces, tabs, or form-feeds."
(catch 'return
(let ((multilinep (cdr lines)))
(dolist (line lines)
(when (if erc-send-whitespace-lines
(and (string-empty-p line) (not multilinep))
(string-match (rx bot (* (in " \t\f")) eot) line))
(throw 'return t))))))
(defun erc--check-prompt-input-for-excess-lines (_ lines)
"Return non-nil when trying to send too many LINES."
(when erc-inhibit-multiline-input
@ -6457,13 +6451,78 @@ is empty or consists of one or more spaces, tabs, or form-feeds."
(y-or-n-p (concat "Send input " msg "?")))
(concat "Too many lines " msg))))))
(defun erc--check-prompt-input-for-multiline-blanks (_ lines)
"Return non-nil when multiline prompt input has blank LINES."
(when (erc--blank-in-multiline-input-p lines)
(defun erc--check-prompt-input-for-something (string _)
(when (string-empty-p string)
(if erc-warn-about-blank-lines
"Blank line - ignoring..."
'invalid)))
(defun erc--count-blank-lines (lines)
"Report on the number of whitespace-only and empty LINES.
Return a list of (BLANKS TO-PAD TO-STRIP). Expect caller to know
that BLANKS includes non-empty whitespace-only lines and that no
padding or stripping has yet occurred."
(let ((real 0) (total 0) (pad 0) (strip 0))
(dolist (line lines)
(if (string-match (rx bot (* (in " \t\f")) eot) line)
(progn
(cl-incf total)
(if (zerop (match-end 0))
(cl-incf strip)
(cl-incf pad strip)
(setq strip 0)))
(cl-incf real)
(unless (zerop strip)
(cl-incf pad strip)
(setq strip 0))))
(when (and (zerop real) (not (zerop total)) (= total (+ pad strip)))
(cl-incf strip (1- pad))
(setq pad 1))
(list total pad strip)))
(defvar erc--check-prompt-explanation nil
"List of strings to print if no validator returns non-nil.")
(defun erc--check-prompt-input-for-multiline-blanks (_ lines)
"Return non-nil when multiline prompt input has blank LINES.
Consider newlines to be intervening delimiters, meaning the empty
\"logical\" line between a trailing newline and `eob' constitutes
a separate message."
(pcase-let ((`(,total ,pad ,strip)(erc--count-blank-lines lines)))
(cond ((zerop total) nil)
((and erc-warn-about-blank-lines erc-send-whitespace-lines)
(let (msg args)
(unless (zerop strip)
(push "stripping (%d)" msg)
(push strip args))
(unless (zerop pad)
(when msg
(push "and" msg))
(push "padding (%d)" msg)
(push pad args))
(when msg
(push "blank" msg)
(push (if (> (apply #'+ args) 1) "lines" "line") msg))
(when msg
(setf msg (nreverse msg)
(car msg) (capitalize (car msg))))
(when msg
(push (apply #'format (string-join msg " ") (nreverse args))
erc--check-prompt-explanation)
nil)))
(erc-warn-about-blank-lines
(concat (if (= total 1)
(if (zerop strip) "Blank" "Trailing")
(if (= total strip)
(format "%d trailing" strip)
(format "%d blank" total)))
(and (> total 1) (/= total strip) (not (zerop strip))
(format " (%d trailing)" strip))
(if (= total 1) " line" " lines")
" detected (see `erc-send-whitespace-lines')"))
(erc-send-whitespace-lines nil)
(t 'invalid))))
(defun erc--check-prompt-input-for-point-in-bounds (_ _)
"Return non-nil when point is before prompt."
(when (< (point) (erc-beg-of-input-line))
@ -6484,25 +6543,39 @@ is empty or consists of one or more spaces, tabs, or form-feeds."
(defvar erc--check-prompt-input-functions
'(erc--check-prompt-input-for-point-in-bounds
erc--check-prompt-input-for-something
erc--check-prompt-input-for-multiline-command
erc--check-prompt-input-for-multiline-blanks
erc--check-prompt-input-for-running-process
erc--check-prompt-input-for-excess-lines
erc--check-prompt-input-for-multiline-command)
erc--check-prompt-input-for-excess-lines)
"Validators for user input typed at prompt.
Called with latest input string submitted by user and the list of
lines produced by splitting it. If any member function returns
non-nil, processing is abandoned and input is left untouched.
When the returned value is a string, ERC passes it to `erc-error'.")
Called with two arguments: the current input submitted by the
user, as a string, along with the same input as a list of
strings. If any member function returns non-nil, ERC abandons
processing and leaves pending input untouched in the prompt area.
When the returned value is a string, ERC passes it to
`user-error'. Any other non-nil value tells ERC to abort
silently. If all members return nil, and the variable
`erc--check-prompt-explanation' is a nonempty list of strings,
ERC prints them as a single message joined by newlines.")
(defun erc--run-input-validation-checks (state)
"Run input checkers from STATE, an `erc--input-split' object."
(when-let ((msg (run-hook-with-args-until-success
'erc--check-prompt-input-functions
(erc--input-split-string state)
(erc--input-split-lines state))))
(unless (stringp msg)
(setq msg (format "Input error: %S" msg)))
(user-error msg)))
(let* ((erc--check-prompt-explanation nil)
(msg (run-hook-with-args-until-success
'erc--check-prompt-input-functions
(erc--input-split-string state)
(erc--input-split-lines state))))
(cond ((stringp msg) (user-error msg))
(msg (push msg (erc--input-split-abortp state)))
(erc--check-prompt-explanation
(message "%s" (string-join (nreverse erc--check-prompt-explanation)
"\n"))))))
(defun erc--inhibit-slash-cmd-insertion (state)
"Don't insert STATE object's message if it's a \"slash\" command."
(when (erc--input-split-cmdp state)
(setf (erc--input-split-insertp state) nil)))
(defun erc-send-current-line ()
"Parse current line and send it to IRC."
@ -6526,8 +6599,9 @@ When the returned value is a string, ERC passes it to `erc-error'.")
str erc--input-line-delim-regexp)
:cmdp (string-match erc-command-regexp str))))
(run-hook-with-args 'erc--input-review-functions state)
(let ((inhibit-read-only t)
(old-buf (current-buffer)))
(when-let (((not (erc--input-split-abortp state)))
(inhibit-read-only t)
(old-buf (current-buffer)))
(progn ; unprogn this during next major surgery
(erc-set-active-buffer (current-buffer))
;; Kill the input and the prompt
@ -6556,12 +6630,11 @@ When the returned value is a string, ERC passes it to `erc-error'.")
(erc-end-of-input-line)))
(defun erc--discard-trailing-multiline-nulls (state)
"Ensure last line of STATE's string is non-null.
But only when `erc-send-whitespace-lines' is non-nil. STATE is
an `erc--input-split' object."
(when (and erc-send-whitespace-lines (erc--input-split-lines state))
"Remove trailing empty lines from STATE, an `erc--input-split' object.
When all lines are empty, remove all but the first."
(when (erc--input-split-lines state)
(let ((reversed (nreverse (erc--input-split-lines state))))
(while (and reversed (string-empty-p (car reversed)))
(while (and (cdr reversed) (string-empty-p (car reversed)))
(setq reversed (cdr reversed)))
(setf (erc--input-split-lines state) (nreverse reversed)))))
@ -6581,7 +6654,7 @@ multiline input. Optionally readjust lines to protocol length
limits and pad empty ones, knowing full well that additional
processing may still corrupt messages before they reach the send
queue. Expect LINES-OBJ to be an `erc--input-split' object."
(when (or erc-send-pre-hook erc-pre-send-functions)
(progn ; FIXME remove `progn' after code review.
(with-suppressed-warnings ((lexical str) (obsolete erc-send-this))
(defvar str) ; see note in string `erc-send-input'.
(let* ((str (string-join (erc--input-split-lines lines-obj) "\n"))
@ -6612,9 +6685,8 @@ queue. Expect LINES-OBJ to be an `erc--input-split' object."
"Send lines in `erc--input-split-lines' object LINES-OBJ."
(when (erc--input-split-sendp lines-obj)
(dolist (line (erc--input-split-lines lines-obj))
(unless (erc--input-split-cmdp lines-obj)
(when (erc--input-split-insertp lines-obj)
(erc-display-msg line)))
(when (erc--input-split-insertp lines-obj)
(erc-display-msg line))
(erc-process-input-line (concat line "\n")
(null erc-flood-protect)
(not (erc--input-split-cmdp lines-obj))))))

View file

@ -35,7 +35,7 @@
(should-not erc-scrolltobottom-all)
(erc-scenarios-scrolltobottom--normal
(erc-scenarios-common-scrolltobottom--normal
(lambda ()
(ert-info ("New insertion doesn't anchor prompt in other window")
(let ((w (next-window)))
@ -52,7 +52,7 @@
(let ((erc-scrolltobottom-all t))
(erc-scenarios-scrolltobottom--normal
(erc-scenarios-common-scrolltobottom--normal
(lambda ()
(ert-info ("New insertion anchors prompt in other window")
(let ((w (next-window)))

View file

@ -292,7 +292,7 @@
(cl-incf counter))))
erc-accidental-paste-threshold-seconds
erc-insert-modify-hook
erc--input-review-functions
(erc--input-review-functions erc--input-review-functions)
erc-send-completed-hook)
(ert-info ("Server buffer")
@ -357,6 +357,9 @@
(should (= (point) erc-input-marker))
(insert "/query bob")
(erc-send-current-line)
;; Last command not inserted
(save-excursion (forward-line -1)
(should (looking-at "<tester> Howdy")))
;; Query does not redraw (nor /help, only message input)
(should (looking-back "#chan@ServNet 11> "))
;; No sign of old prompts
@ -877,11 +880,12 @@
(with-current-buffer (get-buffer-create "*#fake*")
(erc-mode)
(erc-tests--send-prep)
(setq erc-server-current-nick "tester")
(setq-local erc-last-input-time 0)
(should-not (local-variable-if-set-p 'erc-send-completed-hook))
(set (make-local-variable 'erc-send-completed-hook) nil) ; skip t (globals)
;; Just in case erc-ring-mode is already on
(setq-local erc--input-review-functions nil)
(setq-local erc--input-review-functions erc--input-review-functions)
(add-hook 'erc--input-review-functions #'erc-add-to-input-ring)
;;
(cl-letf (((symbol-function 'erc-process-input-line)
@ -1056,43 +1060,6 @@
(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--input-review-functions
@ -1138,7 +1105,7 @@
(delete-region (point) (point-max))
(insert "one\n")
(let ((e (should-error (erc-send-current-line))))
(should (equal "Blank line - ignoring..." (cadr e))))
(should (string-prefix-p "Trailing line detected" (cadr e))))
(goto-char (point-max))
(ert-info ("Input remains untouched")
(should (save-excursion (goto-char erc-input-marker)
@ -1180,6 +1147,137 @@
(should (consp erc-last-input-time)))))
(ert-deftest erc--discard-trailing-multiline-nulls ()
(pcase-dolist (`(,input ,want) '((("") (""))
(("" "") (""))
(("a") ("a"))
(("a" "") ("a"))
(("" "a") ("" "a"))
(("" "a" "") ("" "a"))))
(ert-info ((format "Input: %S, want: %S" input want))
(let ((s (make-erc--input-split :lines input)))
(erc--discard-trailing-multiline-nulls s)
(should (equal (erc--input-split-lines s) want))))))
(ert-deftest erc--count-blank-lines ()
(pcase-dolist (`(,input ,want) '((() (0 0 0))
(("") (1 1 0))
(("" "") (2 1 1))
(("" "" "") (3 1 2))
((" " "") (2 0 1))
((" " "" "") (3 0 2))
(("" " " "") (3 1 1))
(("" "" " ") (3 2 0))
(("a") (0 0 0))
(("a" "") (1 0 1))
(("a" " " "") (2 0 1))
(("a" "" "") (2 0 2))
(("a" "b") (0 0 0))
(("a" "" "b") (1 1 0))
(("a" " " "b") (1 0 0))
(("" "a") (1 1 0))
((" " "a") (1 0 0))
(("" "a" "") (2 1 1))
(("" " " "a" "" " ") (4 2 0))
(("" " " "a" "" " " "") (5 2 1))))
(ert-info ((format "Input: %S, want: %S" input want))
(should (equal (erc--count-blank-lines input) want)))))
;; Opt `wb': `erc-warn-about-blank-lines'
;; Opt `sw': `erc-send-whitespace-lines'
;; `s': " \n",`a': "a\n",`b': "b\n"
(defvar erc-tests--check-prompt-input--expect
;; opts "" " " "\n" "\n " " \n" "\n\n" "a\n" "a\n " "a\n \nb"
'(((+wb -sw) err err err err err err err err err)
((-wb -sw) nop nop nop nop nop nop nop nop nop)
((+wb +sw) err (s) (0 s) (1 s s) (s) (0 s) (0 a) (a s) (a s b))
((-wb +sw) nop (s) (s) (s s) (s) (s) (a) (a s) (a s b))))
;; Help messages echoed (not IRC message) was emitted
(defvar erc-tests--check-prompt-input-messages
'("Stripping" "Padding"))
(ert-deftest erc--check-prompt-input-for-multiline-blanks ()
(erc-tests--with-process-input-spy
(lambda (next)
(erc-tests--set-fake-server-process "sleep" "1")
(should-not erc-send-whitespace-lines)
(should erc-warn-about-blank-lines)
(pcase-dolist (`((,wb ,sw) . ,ex) erc-tests--check-prompt-input--expect)
(let ((print-escape-newlines t)
(erc-warn-about-blank-lines (eq wb '+wb))
(erc-send-whitespace-lines (eq sw '+sw))
(samples '("" " " "\n" "\n " " \n" "\n\n"
"a\n" "a\n " "a\n \nb")))
(setq ex `(,@ex (a) (a b)) ; baseline, same for all combos
samples `(,@samples "a" "a\nb"))
(dolist (input samples)
(insert input)
(ert-info ((format "Opts: %S, Input: %S, want: %S"
(list wb sw) input (car ex)))
(ert-with-message-capture messages
(pcase-exhaustive (pop ex)
('err (let ((e (should-error (erc-send-current-line))))
(should (string-match (rx (| "trailing" "blank"))
(cadr e))))
(should (equal (erc-user-input) input))
(should-not (funcall next)))
('nop (erc-send-current-line)
(should (equal (erc-user-input) input))
(should-not (funcall next)))
('clr (erc-send-current-line)
(should (string-empty-p (erc-user-input)))
(should-not (funcall next)))
((and (pred consp) v)
(erc-send-current-line)
(should (string-empty-p (erc-user-input)))
(setq v (reverse v)) ; don't use `nreverse' here
(while v
(pcase (pop v)
((and (pred integerp) n)
(should (string-search
(nth n erc-tests--check-prompt-input-messages)
messages)))
('s (should (equal " \n" (car (funcall next)))))
('a (should (equal "a\n" (car (funcall next)))))
('b (should (equal "b\n" (car (funcall next)))))))
(should-not (funcall next))))))
(delete-region erc-input-marker (point-max))))))))
(ert-deftest erc--check-prompt-input-for-multiline-blanks/explanations ()
(should erc-warn-about-blank-lines)
(should-not erc-send-whitespace-lines)
(let ((erc-send-whitespace-lines t))
(pcase-dolist (`(,input ,msg)
'((("") "Padding (1) blank line")
(("" " ") "Padding (1) blank line")
((" " "") "Stripping (1) blank line")
(("a" "") "Stripping (1) blank line")
(("" "") "Stripping (1) and padding (1) blank lines")
(("" "" "") "Stripping (2) and padding (1) blank lines")
(("" "a" "" "b" "" "c" "" "")
"Stripping (2) and padding (3) blank lines")))
(ert-info ((format "Input: %S, Msg: %S" input msg))
(let (erc--check-prompt-explanation)
(should-not (erc--check-prompt-input-for-multiline-blanks nil input))
(should (equal (list msg) erc--check-prompt-explanation))))))
(pcase-dolist (`(,input ,msg)
'((("") "Blank line detected")
(("" " ") "2 blank lines detected")
((" " "") "2 blank (1 trailing) lines detected")
(("a" "") "Trailing line detected")
(("" "") "2 blank (1 trailing) lines detected")
(("a" "" "") "2 trailing lines detected")
(("" "a" "" "b" "" "c" "" "")
"5 blank (2 trailing) lines detected")))
(ert-info ((format "Input: %S, Msg: %S" input msg))
(let ((rv (erc--check-prompt-input-for-multiline-blanks nil input)))
(should (equal (concat msg " (see `erc-send-whitespace-lines')")
rv ))))))
(ert-deftest erc-send-whitespace-lines ()
(erc-tests--with-process-input-spy
(lambda (next)
@ -1196,7 +1294,7 @@
(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) '(" \n" nil t)))
(should (equal (funcall next) '("one\n" nil t))))
(ert-info ("Multiline hunk with trailing newline filtered")
@ -1218,17 +1316,12 @@
(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\n\n\n" "/a b\n")))
(dolist (p '("/a b" "/a b\n" "/a b\n\n" "/a b\n\n\n"))
(insert p)
(erc-send-current-line)
(erc-bol)
(should (eq (point) (point-max)))
(while q
(should (pcase (funcall next)
(`(,cmd ,_ nil) (equal cmd (pop q))))))
(should (pcase (funcall next) (`(,cmd ,_ nil) (equal cmd "/a b\n"))))
(should-not (funcall next))))
(ert-info ("Multiline command with non-blanks errors")

View file

@ -254,7 +254,7 @@ return a replacement.")
(ending (process-get process :dialog-ending))
(dialog (make-erc-d-dialog :name name
:process process
:queue (make-ring 5)
:queue (make-ring 10)
:exchanges (make-ring 10)
:match-handlers mat-h
:server-fqdn fqdn)))
@ -292,33 +292,27 @@ With int SKIP, advance past that many exchanges."
(defvar erc-d--m-debug (getenv "ERC_D_DEBUG"))
(defmacro erc-d--m (process format-string &rest args)
"Output ARGS using FORMAT-STRING somewhere depending on context.
PROCESS should be a client connection or a server network process."
`(let ((format-string (if erc-d--m-debug
(concat (format-time-string "%s.%N: ")
,format-string)
,format-string))
(want-insert (and ,process erc-d--in-process))
(buffer (process-buffer (process-get ,process :server))))
(when (and want-insert (buffer-live-p buffer))
(with-current-buffer buffer
(goto-char (point-max))
(insert (concat (format ,format-string ,@args) "\n"))))
(when (or erc-d--m-debug (not want-insert))
(message format-string ,@args))))
(defun erc-d--m (process format-string &rest args)
"Output ARGS using FORMAT-STRING to PROCESS's buffer or elsewhere."
(when erc-d--m-debug
(setq format-string (concat (format-time-string "%s.%N: ") format-string)))
(let ((insertp (and process erc-d--in-process))
(buffer (process-buffer (process-get process :server))))
(when (and insertp (buffer-live-p buffer))
(princ (concat (apply #'format format-string args) "\n") buffer))
(when (or erc-d--m-debug (not insertp))
(apply #'message format-string args))))
(defmacro erc-d--log (process string &optional outbound)
"Log STRING sent to (OUTBOUND) or received from PROCESS peer."
`(let ((id (or (process-get ,process :log-id)
(let ((port (erc-d-u--get-remote-port ,process)))
(process-put ,process :log-id port)
port)))
(name (erc-d-dialog-name (process-get ,process :dialog))))
(if ,outbound
(erc-d--m process "-> %s:%s %s" name id ,string)
(dolist (line (split-string ,string (process-get process :ending)))
(erc-d--m process "<- %s:%s %s" name id line)))))
(defun erc-d--log (process string &optional outbound)
"Log STRING received from or OUTBOUND to PROCESS peer."
(let ((id (or (process-get process :log-id)
(let ((port (erc-d-u--get-remote-port process)))
(process-put process :log-id port) port)))
(name (erc-d-dialog-name (process-get process :dialog))))
(if outbound
(erc-d--m process "-> %s:%s %s" name id string)
(dolist (line (split-string string (process-get process :ending)))
(erc-d--m process "<- %s:%s %s" name id line)))))
(defun erc-d--log-process-event (server process msg)
(erc-d--m server "%s: %s" process (string-trim-right msg)))

View file

@ -341,7 +341,7 @@ See Info node `(emacs) Term Mode' for the various commands."
;;;; Fixtures
(defun erc-scenarios-scrolltobottom--normal (test)
(defun erc-scenarios-common-scrolltobottom--normal (test)
(erc-scenarios-common-with-noninteractive-in-term
((erc-scenarios-common-dialog "scrolltobottom")
(dumb-server (erc-d-run "localhost" t 'help))
@ -402,6 +402,7 @@ See Info node `(emacs) Term Mode' for the various commands."
(erc-cmd-MSG "NickServ help register")
(save-excursion (erc-d-t-search-for 10 "End of NickServ"))
(should (= 1 (point)))
(redisplay)
(should (zerop (count-screen-lines (window-start) (window-point))))
(should (erc-scenarios-common--prompt-past-win-end-p)))