Skip to content

Commit b649559

Browse files
committed
Wiggle the cursor to produce a prompt and prevent hanging
This makes most cases resolve with similar output too
1 parent 123c87d commit b649559

File tree

2 files changed

+83
-34
lines changed

2 files changed

+83
-34
lines changed

js-comint-test.el

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,24 @@ Array.valueOf
267267
"Array.toLocaleString"
268268
"Array.valueOf"))))
269269

270+
(ert-deftest js-comint--process-completion-output/test-multiline-prefix ()
271+
"Completion when there is a '...' prefix."
272+
(should
273+
(equal
274+
(js-comint--process-completion-output
275+
"Array.
276+
Array.__proto__ Array.hasOwnProperty Array.isPrototypeOf Array.propertyIsEnumerable Array.toLocaleString
277+
Array.valueOf
278+
279+
... Array."
280+
"Array.")
281+
'("Array.__proto__"
282+
"Array.hasOwnProperty"
283+
"Array.isPrototypeOf"
284+
"Array.propertyIsEnumerable"
285+
"Array.toLocaleString"
286+
"Array.valueOf"))))
287+
270288
(ert-deftest js-comint--async-output-filter/test-no-callbacks ()
271289
"Output should be kept when no callbacks are active."
272290
(with-temp-buffer
@@ -345,11 +363,16 @@ Array.valueOf
345363
"Output should be saved until string match, then fail."
346364
(with-mock
347365
(stub js-comint--callback-active-p => 't)
348-
(mock (process-send-string * *) :times 2)
366+
;; 1 - complete foo
367+
;; 2 - finished test " \b"
368+
;; 3 - clear ""
369+
(mock (process-send-string * *) :times 3)
349370
(with-temp-buffer
350371
;; callback should be called with nil
351372
(js-comint--get-completion-async "foo" (lambda (arg) (should-not arg)))
352-
(dolist (output '("f" "oo"))
373+
(dolist (output '("f" "oo" ;; output in chunks
374+
"> foo" ;; response to " \b"
375+
))
353376
(should (string-empty-p (js-comint--async-output-filter output))))
354377
;; clear should be called
355378
(should (equal (plist-get (car js-comint--completion-callbacks) :type)
@@ -359,11 +382,18 @@ Array.valueOf
359382
"When completion fails on something that looks like an object don't hang."
360383
(with-mock
361384
(stub js-comint--callback-active-p => 't)
362-
(mock (process-send-string * *) :times 3)
385+
;; 1 - complete
386+
;; 2 - send another \t
387+
;; 3 - finished test " \b"
388+
;; 4 - clear
389+
(mock (process-send-string * *) :times 4)
363390
(with-temp-buffer
364391
;; callback should be called with nil
365-
(js-comint--get-completion-async "Array." (lambda (arg) (should-not arg)))
366-
(dolist (output '("A" "rray." "Array."))
392+
(js-comint--get-completion-async "scrog." (lambda (arg) (should-not arg)))
393+
(dolist (output '("s" "crog." ;; output in chunks
394+
"scrog." ;; response to repeat \t
395+
"> scrog." ;; response to " \b"
396+
))
367397
(should (string-empty-p (js-comint--async-output-filter output))))
368398
;; clear should be called
369399
(should (equal (plist-get (car js-comint--completion-callbacks) :type)
@@ -379,7 +409,9 @@ Array.valueOf
379409
(js-comint--get-completion-async "foo"
380410
(lambda (arg) (error "Broken user callback")))
381411
;; after output the erroring callback is called with nil
382-
(dolist (output '("f" "oo"))
412+
(dolist (output '("f" "oo"
413+
"> foo" ;; response to " \b"
414+
))
383415
(should (string-empty-p (js-comint--async-output-filter output))))
384416
;; clear should be called
385417
(should (equal (plist-get (car js-comint--completion-callbacks) :type)

js-comint.el

Lines changed: 45 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -352,34 +352,51 @@ ARGUMENTS is an optional list of arguments to pass."
352352
"Complete INPUT-STRING and register CALLBACK to recieve completion output."
353353
(js-comint--clear-completion-state)
354354
(js-comint--set-completion-callback
355-
(let ((retries 0))
356-
(lambda ()
357-
;; decide whether output is complete
358-
(cond
359-
((and (not (string-empty-p input-string))
360-
(js-comint--completion-looking-back-p input-string))
361-
;; Completions like Array. seem to need a second tab after the response
362-
(if (and (string-suffix-p "." input-string)
363-
(zerop retries))
364-
(prog1 nil ;; do not remove callback
365-
(cl-incf retries)
366-
(process-send-string
367-
(js-comint-get-process)
368-
"\t"))
369-
;; Otherwise there was no match, so reset
370-
(unwind-protect
371-
(funcall callback nil)
372-
(js-comint--clear-input-async))
373-
't))
374-
((js-comint--completion-looking-back-p "\\[[[:digit:]]+[AG]$")
375-
(let* ((completion-output (with-current-buffer js-comint--completion-buffer (buffer-string)))
376-
(completion-res (js-comint--process-completion-output
377-
completion-output
378-
input-string)))
379-
(unwind-protect
380-
(funcall callback completion-res)
381-
(js-comint--clear-input-async))
382-
't)))))
355+
;; callback closure
356+
(let (tab-sent ;; flag tracking repeat tabs: Array\t => \t
357+
check-sent) ;; flag tracking whether check has been sent
358+
(lambda ()
359+
(cond
360+
;; case: exact match to input-string in output
361+
((and (not (string-empty-p input-string))
362+
(js-comint--completion-looking-back-p input-string))
363+
;; Completions like "Array." need a second tab after the response
364+
(cond
365+
((and (string-suffix-p "." input-string)
366+
(not tab-sent))
367+
(prog1 nil ;; do not remove callback
368+
(setq tab-sent 't)
369+
(process-send-string (js-comint-get-process) "\t")))
370+
;; Output may sometimes be staggered "Arr|ay" so the completion appears stepwise
371+
((not check-sent)
372+
(prog1 nil ;; do not remove callback
373+
(setq check-sent 't)
374+
;; This "wiggles" the cursor making node repeat the prompt with current input
375+
(process-send-string (js-comint-get-process) " \b")))
376+
;; Otherwise there was no match (after retry).
377+
;; The wiggle should cause a prompt to be echoed, so this case likely does not occur.
378+
;; Probably useful to keep for edge cases though.
379+
('t
380+
(prog1 't ;; remove callback
381+
(unwind-protect
382+
(funcall callback nil)
383+
(js-comint--clear-input-async))))))
384+
;; case: found a control character (usually part of a prompt)
385+
((or check-sent
386+
(js-comint--completion-looking-back-p "\\[[[:digit:]]+[AG]$"))
387+
(let* ((completion-output (with-current-buffer js-comint--completion-buffer
388+
(buffer-string)))
389+
(completion-res (js-comint--process-completion-output
390+
completion-output
391+
input-string)))
392+
(unwind-protect
393+
(funcall callback completion-res)
394+
(js-comint--clear-input-async))
395+
't))
396+
;; all other cases
397+
('t
398+
;; expect that the callback will be removed
399+
nil))))
383400
'completion)
384401

385402
;; Need to send 2x tabs to trigger completion when there is no input

0 commit comments

Comments
 (0)