Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 56 additions & 12 deletions lisp/cider-eval.el
Original file line number Diff line number Diff line change
Expand Up @@ -412,47 +412,91 @@ result."
(insert (concat comment-prefix res (or comment-postfix "") "\n"))))
(cider--maybe-set-eval-register res)))))

(defun cider-maybe-delete-multiline-comment (comment-prefix continued-prefix comment-postfix)
"Delete the region after the point in the form of a multiline comment.
The region must begin with COMMENT-PREFIX, followed by multiple lines
beginning with CONTINUED-PREFIX at the same indentation,
and optionally end with COMMENT-POSTFIX."
(let ((pre-rgx (rx (group-n 1 (* (any " \t"))) ;; may be indented
(literal comment-prefix))))
(when (looking-at pre-rgx)
(let* ((cont-rgx (rx (literal (match-string 1)) ;; match indentation rigidly
(literal continued-prefix)))
(post-rgx (rx (literal (match-string 1))
;; trim the leading newline, NOTE string-trim messes with match data
(literal (if (string-prefix-p "\n" comment-postfix)
(substring comment-postfix 1)
comment-postfix))))
(start (point))
(end (save-excursion
(goto-char (match-end 0))
(if (string-prefix-p ";" comment-prefix)
;; line comment - skip past any similarly indented comments
(progn
(forward-line 1)
(while (and (not (eobp))
(looking-at-p cont-rgx))
(forward-line 1)))
;; otherwise it's probably some sort of discarded form like #_
(clojure-forward-logical-sexp 1))
(if (looking-at post-rgx) ;; skip past postfix
(match-end 0)
(point)))))
(delete-region start end)))))

(defun cider-maybe-insert-multiline-comment (result comment-prefix continued-prefix comment-postfix)
"Insert eval RESULT at current location if RESULT is not empty.
Returns bounds of the inserted text, or nil if nothing was inserted.
RESULT will be preceded by COMMENT-PREFIX.
CONTINUED-PREFIX is inserted for each additional line of output.
COMMENT-POSTFIX is inserted after final text output."
(unless (string= result "")
;; Make sure inserted comments are indented properly
(indent-according-to-mode)
(let ((lines (split-string result "[\n]+" t))
(let ((lines (split-string result "[\n]" nil))
(beg (point))
(col (current-indentation)))
;; only the first line gets the normal comment-prefix
(insert (concat comment-prefix (pop lines)))
(dolist (elem lines)
(insert (concat "\n" continued-prefix elem)))
(indent-rigidly beg (point) col)
(unless (string= comment-postfix "")
(insert comment-postfix)))))
(insert comment-postfix)
(indent-rigidly beg (line-end-position) col)
(list beg (point)))))

(defun cider-eval-pprint-with-multiline-comment-handler (buffer location comment-prefix continued-prefix comment-postfix)
"Make a handler for evaluating and inserting results in BUFFER.
The inserted text is pretty-printed and region will be commented.
LOCATION is the location marker at which to insert.
Any existing eval comment on the following line is replaced.
COMMENT-PREFIX is the comment prefix for the first line of output.
CONTINUED-PREFIX is the comment prefix to use for the remaining lines.
COMMENT-POSTFIX is the text to output after the last line."
(let ((res ""))
(let ((res "")
(stderr "")
(location (copy-marker location)))
(cider-make-eval-handler
:buffer buffer
:on-value (lambda (value) (setq res (concat res value)))
;; Forward stdout to the usual interactive sink so output like the
;; `time' macro's "Elapsed time" line isn't silently dropped (#3732).
:on-stdout #'cider-emit-interactive-eval-output
:on-stderr (lambda (err) (setq res (concat res err)))
:on-stderr (lambda (err) (setq stderr (concat stderr err)))
:on-done (lambda ()
(with-current-buffer buffer
(save-excursion
(goto-char (marker-position location))
;; Replace an existing eval comment on the following line
(when (eolp) (forward-line 1))
(cider-maybe-delete-multiline-comment comment-prefix continued-prefix comment-postfix)
;; edge case: defun at eob
(unless (bolp) (insert "\n"))
(cider-maybe-insert-multiline-comment
res comment-prefix continued-prefix comment-postfix)))
(if (or (string-blank-p res)
(string-blank-p stderr))
(string-trim (concat res stderr))
(concat res "\n" (string-trim stderr)))
comment-prefix continued-prefix comment-postfix)))
(cider--maybe-set-eval-register res)))))

(defun cider-popup-eval-handler (&optional buffer _bounds source-buffer)
Expand Down Expand Up @@ -711,7 +755,7 @@ With the prefix arg INSERT-BEFORE, insert before the form, otherwise afterwards.
(cider-interactive-eval nil
(cider-eval-print-with-comment-handler
(current-buffer)
(set-marker (make-marker) insertion-point)
insertion-point
prefix
postfix)
bounds
Expand All @@ -725,18 +769,18 @@ The comment style is controlled by `cider-comment-style'. For the default
`line' style the formatting is further controlled via the `cider-comment-prefix',
`cider-comment-continued-prefix' and `cider-comment-postfix' options.

If INSERT-BEFORE is non-nil, insert before the form, otherwise afterwards."
If INSERT-BEFORE is non-nil, insert before the form, otherwise afterwards.
Any existing eval comment is replaced."
(pcase-let* ((bounds (funcall form-fn 'bounds))
(insertion-point (nth (if insert-before 0 1) bounds))
(`(,prefix ,continued ,postfix) (cider--comment-format))
;; when insert-before, we need a newline after the output to
;; avoid commenting the first line of the form
(comment-postfix (concat postfix
(if insert-before "\n" ""))))
(comment-postfix (concat postfix (if insert-before "\n" ""))))
(cider-interactive-eval nil
(cider-eval-pprint-with-multiline-comment-handler
(current-buffer)
(set-marker (make-marker) insertion-point)
insertion-point
prefix
continued
comment-postfix)
Expand Down
103 changes: 102 additions & 1 deletion test/cider-eval-tests.el
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,108 @@
(current-buffer) (point-marker) ";; => " ";; " "")))
(funcall handler (nrepl-dict "out" "Elapsed time: 0.042 msecs\n"))
(expect 'cider-emit-interactive-eval-output
:to-have-been-called-with "Elapsed time: 0.042 msecs\n")))))
:to-have-been-called-with "Elapsed time: 0.042 msecs\n"))))

(it "tracks async buffer edits using markers (#2607)"
(with-temp-buffer
(setq-local nrepl-pending-requests (make-hash-table :test 'equal))
(insert "(+ 1 2)")
(let* ((indent-line-function #'ignore)
(insertion-point (point-max))
(handler (cider-eval-pprint-with-multiline-comment-handler
(current-buffer) insertion-point ";; => " ";; " "")))
;; simulate user editing BEFORE the eval result arrives:
(goto-char 1)
(insert "(ns repro)\n\n")
(funcall handler (nrepl-dict "value" "3"))
(funcall handler (nrepl-dict "status" '("done")))
(expect (buffer-string) :to-equal
(concat "(ns repro)\n\n"
"(+ 1 2)\n"
";; => 3")))))

;; When eval produces both a value and stderr output (e.g. a reflection warning),
;; the two must not be interleaved in the inserted comment.
(it "keeps value and stderr separate when both are present"
(with-temp-buffer
(setq-local nrepl-pending-requests (make-hash-table :test 'equal))
(let* ((indent-line-function #'ignore)
(handler (cider-eval-pprint-with-multiline-comment-handler
(current-buffer) (point-marker) ";; => " ";; " "")))
;; Simulate nREPL responses: stderr chunk interleaved with value chunks
(funcall handler (nrepl-dict "value" "{:a 1, "))
(funcall handler (nrepl-dict "err" "Reflection warning, user.clj:5:1 - reference to field foo can't be resolved.\n"))
(funcall handler (nrepl-dict "value" ":b 2}"))
(funcall handler (nrepl-dict "status" '("done")))
;; Value and stderr should be separated by a newline, not mashed together
(expect (string-trim (buffer-string))
:to-equal
(concat ";; => {:a 1, :b 2}\n"
";; Reflection warning, user.clj:5:1 - reference to field foo can't be resolved.")))))

(it "inserts only the value when no stderr is produced"
(with-temp-buffer
(setq-local nrepl-pending-requests (make-hash-table :test 'equal))
(let* ((indent-line-function #'ignore)
(handler (cider-eval-pprint-with-multiline-comment-handler
(current-buffer) (point-marker) ";; => " ";; " "")))
(funcall handler (nrepl-dict "value" "42"))
(funcall handler (nrepl-dict "status" '("done")))
(expect (string-trim (buffer-string)) :to-equal ";; => 42"))))

(it "inserts only stderr when no value is produced"
(with-temp-buffer
(setq-local nrepl-pending-requests (make-hash-table :test 'equal))
(let* ((indent-line-function #'ignore)
(handler (cider-eval-pprint-with-multiline-comment-handler
(current-buffer) (point-marker) ";; => " ";; " "")))
(funcall handler (nrepl-dict "err" "Syntax error compiling at (user.clj:5:1)\nUnable to resolve symbol: oops in this context\n"))
(funcall handler (nrepl-dict "status" '("done")))
(expect (string-trim (buffer-string)) :to-equal
(concat ";; => Syntax error compiling at (user.clj:5:1)\n"
";; Unable to resolve symbol: oops in this context")))))

(it "replaces an existing multiline comment on re-eval"
(with-temp-buffer
(setq-local nrepl-pending-requests (make-hash-table :test 'equal))
(let ((indent-line-function #'ignore))
(insert "(inc 0)")
(let ((h (cider-eval-pprint-with-multiline-comment-handler
(current-buffer) (point) ";; => " ";; " "")))
(funcall h (nrepl-dict "value" "1"))
(funcall h (nrepl-dict "status" '("done")))
(expect (buffer-string) :to-equal "(inc 0)\n;; => 1"))
;; Simulate an edit and re-eval
(goto-char (point-min))
(kill-line)
(insert "(dec 3)")
(let ((h (cider-eval-pprint-with-multiline-comment-handler
(current-buffer) (point) ";; => " ";; " "")))
(funcall h (nrepl-dict "value" "2"))
(funcall h (nrepl-dict "status" '("done")))
(expect (buffer-string) :to-equal "(dec 3)\n;; => 2")))))

(it "replaces a single-line comment with a multiline result"
(with-temp-buffer
(setq-local nrepl-pending-requests (make-hash-table :test 'equal))
(let ((indent-line-function #'ignore)
loc)
(insert "(range 3)")
(setq loc (point))
(let ((h1 (cider-eval-pprint-with-multiline-comment-handler
(current-buffer) loc ";; => " ";; " "")))
(funcall h1 (nrepl-dict "value" "(0 1 2)"))
(funcall h1 (nrepl-dict "status" '("done")))
(expect (buffer-string) :to-equal "(range 3)\n;; => (0 1 2)"))
(let ((h2 (cider-eval-pprint-with-multiline-comment-handler
(current-buffer) loc ";; => " ";; " "")))
(funcall h2 (nrepl-dict "value" "(0\n 1\n 2)"))
(funcall h2 (nrepl-dict "status" '("done")))
(expect (buffer-string) :to-equal
(concat "(range 3)\n"
";; => (0\n"
";; 1\n"
";; 2)")))))))

(describe "cider--comment-format"
(it "returns the configured prefixes for the `line' style"
Expand Down
Loading