Skip to content

Commit a745084

Browse files
committed
Harden parsing of repl responses, fixes #83
This patch refactors the process response parsing code and adds more nil checks and ignore-errors around the reading of LISP expression for both completion and eldoc.
1 parent 74e8423 commit a745084

File tree

1 file changed

+78
-48
lines changed

1 file changed

+78
-48
lines changed

inf-clojure.el

Lines changed: 78 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -940,17 +940,22 @@ prefix argument PROMPT-FOR-SYMBOL, it prompts for a symbol name."
940940
(inf-clojure-symbol-at-point))))
941941
(comint-proc-query (inf-clojure-proc) (format (inf-clojure-var-source-form) var))))
942942

943+
;;;; Response parsing
944+
;;;; ================
945+
946+
(defvar inf-clojure--redirect-buffer-name " *Inf-Clojure Redirect Buffer*")
947+
943948
;; Originally from:
944949
;; https://github.com/glycerine/lush2/blob/master/lush2/etc/lush.el#L287
945-
(defun inf-clojure-results-from-process (process command &optional beg-string end-string)
946-
"Send COMMAND to PROCESS.
950+
(defun inf-clojure--process-response (command process &optional beg-string end-string)
951+
"Send COMMAND to PROCESS and return the response.
947952
Return the result of COMMAND starting with BEG-STRING and ending
948953
with END-STRING if non-nil. If BEG-STRING is nil, the result
949954
string will start from (point) in the results buffer. If
950955
END-STRING is nil, the result string will end at (point-max) in
951-
the results buffer. It cuts out the output from
952-
`inf-clojure-prompt` onwards unconditionally."
953-
(let ((work-buffer " *Inf-Clojure Redirect Work Buffer*"))
956+
the results buffer. It cuts out the output from and including
957+
the `inf-clojure-prompt`."
958+
(let ((work-buffer inf-clojure--redirect-buffer-name))
954959
(save-excursion
955960
(set-buffer (get-buffer-create work-buffer))
956961
(erase-buffer)
@@ -963,29 +968,68 @@ the results buffer. It cuts out the output from
963968
;; Collect the output
964969
(set-buffer work-buffer)
965970
(goto-char (point-min))
966-
;; Skip past the command, if it was echoed
967-
(and (looking-at command)
968-
(forward-line))
969-
(let* ((beg (if beg-string
970-
(progn (search-forward beg-string nil t) (match-beginning 0))
971-
(point)))
972-
(end (if end-string
973-
(search-forward end-string nil t)
974-
(point-max)))
975-
(buffer-string (buffer-substring-no-properties beg end)))
976-
(when (and buffer-string (string-match inf-clojure-prompt buffer-string))
977-
(substring buffer-string 0 (match-beginning 0)))))))
971+
(let* ((beg (or (when (and beg-string (search-forward beg-string nil t))
972+
(match-beginning 0))
973+
(point-min)))
974+
(end (or (when end-string
975+
(search-forward end-string nil t))
976+
(point-max)))
977+
(prompt (when (search-forward inf-clojure-prompt nil t)
978+
(match-beginning 0))))
979+
(buffer-substring-no-properties beg (or prompt end))))))
980+
981+
(defun inf-clojure--nil-string-match-p (string)
982+
"Return true iff STRING is not nil.
983+
This function also takes into consideration weird escape
984+
character and matches if nil is anywhere within the input
985+
string."
986+
(string-match-p "\\Ca*nil\\Ca*" string))
987+
988+
(defun inf-clojure--some (data)
989+
"Return DATA unless nil or includes \"nil\" as string."
990+
(cond
991+
((null data) nil)
992+
((and (stringp data)
993+
(inf-clojure--nil-string-match-p data)) nil)
994+
(t data)))
995+
996+
(defun inf-clojure--read-or-nil (response)
997+
"Read RESPONSE and return it as data.
998+
999+
If response is nil or includes the \"nil\" string return nil
1000+
instead.
1001+
1002+
Note that the read operation will always return the first
1003+
readable sexp only."
1004+
;; The following reads the first LISP expression
1005+
(inf-clojure--some
1006+
(when response
1007+
(ignore-errors (read response)))))
1008+
1009+
(defun inf-clojure--process-response-match-p (match-p proc form)
1010+
"Eval MATCH-P on the response of sending to PROC the input FORM.
1011+
Note that this function will add a \n to the end (or )f the string
1012+
for evaluation, therefore FORM should not include it."
1013+
(when-let ((response (inf-clojure--process-response form proc)))
1014+
(funcall match-p response)))
1015+
1016+
(defun inf-clojure--some-response-p (proc form)
1017+
"Return true iff PROC's response after evaluating FORM is not nil."
1018+
(inf-clojure--process-response-match-p
1019+
(lambda (string)
1020+
(not (inf-clojure--nil-string-match-p string)))
1021+
proc form))
1022+
1023+
;;;; Commands
1024+
;;;; ========
9781025

9791026
(defun inf-clojure-arglists (fn)
9801027
"Send a query to the inferior Clojure for the arglists for function FN.
9811028
See variable `inf-clojure-arglists-form'."
982-
(let* ((arglists-snippet (format (inf-clojure-arglists-form) fn))
983-
(arglists-result (inf-clojure-results-from-process (inf-clojure-proc) arglists-snippet))
984-
(arglists-data (when arglists-result (read arglists-result))))
985-
(cond
986-
((null arglists-data) nil)
987-
((stringp arglists-data) arglists-data)
988-
((listp arglists-data) arglists-result))))
1029+
(thread-first
1030+
(format (inf-clojure-arglists-form) fn)
1031+
(inf-clojure--process-response (inf-clojure-proc) "(" ")")
1032+
(inf-clojure--some)))
9891033

9901034
(defun inf-clojure-show-arglists (prompt-for-symbol)
9911035
"Show the arglists for function FN in the mini-buffer.
@@ -1051,26 +1095,18 @@ See variable `inf-clojure-buffer'."
10511095
(or proc
10521096
(error "No Clojure subprocess; see variable `inf-clojure-buffer'"))))
10531097

1098+
(defun inf-clojure--list-or-nil (data)
1099+
"Return DATA if and only if it is a list."
1100+
(when (listp data) data))
1101+
10541102
(defun inf-clojure-completions (expr)
10551103
"Return a list of completions for the Clojure expression starting with EXPR."
1056-
(let* ((proc (inf-clojure-proc))
1057-
(comint-filt (process-filter proc))
1058-
(kept "")
1059-
completions)
1060-
(set-process-filter proc (lambda (_proc string) (setq kept (concat kept string))))
1061-
(unwind-protect
1062-
(let ((completion-snippet
1063-
(format
1064-
(inf-clojure-completion-form) (substring-no-properties expr))))
1065-
(process-send-string proc completion-snippet)
1066-
(while (and (not (string-match inf-clojure-prompt kept))
1067-
(accept-process-output proc 2)))
1068-
(setq completions (read kept))
1069-
;; Subprocess echoes output on Windows and OS X.
1070-
(when (and completions (string= (concat (car completions) "\n") completion-snippet))
1071-
(setq completions (cdr completions))))
1072-
(set-process-filter proc comint-filt))
1073-
completions))
1104+
(when (not (string-blank-p expr))
1105+
(thread-first
1106+
(format (inf-clojure-completion-form) (substring-no-properties expr))
1107+
(inf-clojure--process-response (inf-clojure-proc) "(" ")")
1108+
(inf-clojure--read-or-nil)
1109+
(inf-clojure--list-or-nil))))
10741110

10751111
(defconst inf-clojure-clojure-expr-break-chars " \t\n\"\'`><,;|&{(")
10761112

@@ -1228,12 +1264,6 @@ to suppress the usage of the target buffer discovery logic."
12281264
(inf-clojure (inf-clojure-cmd (inf-clojure-project-type)))
12291265
(rename-buffer target-buffer-name)))
12301266

1231-
(defun inf-clojure--response-match-p (form match-p proc)
1232-
"Return MATCH-P on the result of sending FORM to PROC.
1233-
Note that this function will add a \n to the end of the string
1234-
for evaluation, therefore FORM should not include it."
1235-
(funcall match-p (inf-clojure-results-from-process proc form nil)))
1236-
12371267
;;;; Lumo
12381268
;;;; ====
12391269

0 commit comments

Comments
 (0)