Skip to content

Support REPL flavors and Lumo #44

New issue

Have a question about this project? No Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “No Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? No Sign in to your account

Merged
merged 3 commits into from
Mar 6, 2017
Merged
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
128 changes: 114 additions & 14 deletions inf-clojure.el
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
(require 'thingatpt)
(require 'ansi-color)
(require 'cl-lib)
(require 'subr-x)


(defgroup inf-clojure nil
Expand Down Expand Up @@ -148,6 +149,7 @@ The following commands are available:

\\{inf-clojure-minor-mode-map}"
:lighter "" :keymap inf-clojure-minor-mode-map
(setq comint-input-sender 'inf-clojure--send-string)
(inf-clojure-eldoc-setup)
(make-local-variable 'completion-at-point-functions)
(add-to-list 'completion-at-point-functions
Expand All @@ -156,7 +158,7 @@ The following commands are available:
(defcustom inf-clojure-lein-cmd "lein repl"
"The command used to start a Clojure REPL for Leiningen projects.

Alternative you can specify a TCP connection cons pair, instead
Alternatively you can specify a TCP connection cons pair, instead
of command, consisting of a host and port
number (e.g. (\"localhost\" . 5555)). That's useful if you're
often connecting to a remote REPL process."
Expand All @@ -166,7 +168,7 @@ often connecting to a remote REPL process."
(defcustom inf-clojure-boot-cmd "boot repl"
"The command used to start a Clojure REPL for Boot projects.

Alternative you can specify a TCP connection cons pair, instead
Alternatively you can specify a TCP connection cons pair, instead
of command, consisting of a host and port
number (e.g. (\"localhost\" . 5555)). That's useful if you're
often connecting to a remote REPL process."
Expand All @@ -176,13 +178,37 @@ often connecting to a remote REPL process."
(defcustom inf-clojure-generic-cmd "lein repl"
"The command used to start a Clojure REPL outside Lein/Boot projects.

Alternative you can specify a TCP connection cons pair, instead
Alternatively you can specify a TCP connection cons pair, instead
of command, consisting of a host and port
number (e.g. (\"localhost\" . 5555)). That's useful if you're
often connecting to a remote REPL process."
:type '(choice (string)
(cons string integer)))

(defvar-local inf-clojure-repl-type nil
"Symbol to define your REPL type.
Its root binding is nil and it can be further customized using
either `setq-local` or an entry in `.dir-locals.el`." )

(defun inf-clojure--detect-type (proc)
"Identifies the current REPL type for PROC."
(cond
((inf-clojure--lumo-p proc) 'lumo)
(t 'clojure)))

(defun inf-clojure--set-repl-type (proc)
"Set the REPL type if has not already been set."
(when (not inf-clojure-repl-type)
(setq inf-clojure-repl-type (inf-clojure--detect-type proc))))

(defun inf-clojure--send-string (proc string)
"A custom `comint-input-sender` / `comint-send-string`.
Perform the required side effects on every send for PROC and
STRING (for example set the buffer local REPL type). It should
be used instead of `comint-send-string`."
(inf-clojure--set-repl-type proc)
(comint-simple-send proc string))

(defcustom inf-clojure-load-command "(clojure.core/load-file \"%s\")\n"
"Format-string for building a Clojure expression to load a file.
This format string should use `%s' to substitute a file name
Expand Down Expand Up @@ -296,6 +322,7 @@ If `comint-use-prompt-regexp' is nil (the default), \\[comint-insert-input] on o
Paragraphs are separated only by blank lines. Semicolons start comments.
If you accidentally suspend your process, use \\[comint-continue-subjob]
to continue it."
(setq comint-input-sender 'inf-clojure--send-string)
(setq comint-prompt-regexp inf-clojure-comint-prompt-regexp)
(setq mode-line-process '(":%s"))
(clojure-mode-variables)
Expand All @@ -305,6 +332,7 @@ to continue it."
(setq comint-input-filter #'inf-clojure-input-filter)
(setq-local comint-prompt-read-only inf-clojure-prompt-read-only)
(add-hook 'comint-preoutput-filter-functions #'inf-clojure-preoutput-filter nil t)
(add-hook 'comint-output-filter-functions 'inf-clojure--ansi-filter)
(add-hook 'completion-at-point-functions #'inf-clojure-completion-at-point nil t)
(ansi-color-for-comint-mode-on))

Expand All @@ -329,6 +357,19 @@ to continue it."
"Remove subprompts from STRING."
(replace-regexp-in-string inf-clojure-subprompt "" string))

(defconst inf-clojure--ansi-clear-line "\\[1G\\|\\[0J\\|\\[13G"
"Ansi codes sent by the lumo repl that we need to clear." )

(defun inf-clojure--ansi-filter (string)
"Filter unwanted ansi character from STRING."
(save-excursion
;; go to start of first line just inserted
(comint-goto-process-mark)
(goto-char (max (point-min) (- (point) (string-width string))))
(forward-line 0)
(while (re-search-forward inf-clojure--ansi-clear-line nil t)
(replace-match ""))))

(defun inf-clojure-preoutput-filter (str)
"Preprocess the output STR from interactive commands."
(cond
Expand Down Expand Up @@ -408,12 +449,12 @@ Prefix argument AND-GO means switch to the Clojure buffer afterwards."
(let ((str (replace-regexp-in-string
"[\n]*\\'" "\n"
(buffer-substring-no-properties start end))))
(comint-send-string (inf-clojure-proc) str))
(inf-clojure--send-string (inf-clojure-proc) str))
(if and-go (inf-clojure-switch-to-repl t)))

(defun inf-clojure-eval-string (code)
"Send the string CODE to the inferior Clojure process to be executed."
(comint-send-string (inf-clojure-proc) (concat code "\n")))
(inf-clojure--send-string (inf-clojure-proc) (concat code "\n")))

(defun inf-clojure-eval-defun (&optional and-go)
"Send the current defun to the inferior Clojure process.
Expand Down Expand Up @@ -504,8 +545,8 @@ The prefix argument SWITCH-TO-REPL controls whether to switch to REPL after the
(comint-check-source file-name) ; Check to see if buffer needs saved.
(setq inf-clojure-prev-l/c-dir/file (cons (file-name-directory file-name)
(file-name-nondirectory file-name)))
(comint-send-string (inf-clojure-proc)
(format inf-clojure-load-command file-name))
(inf-clojure--send-string (inf-clojure-proc)
(format inf-clojure-load-command file-name))
(when switch-to-repl
(inf-clojure-switch-to-repl t))))

Expand All @@ -521,11 +562,24 @@ The prefix argument SWITCH-TO-REPL controls whether to switch to REPL after the
;;; Command strings
;;; ===============

(defcustom inf-clojure-var-doc-command
(defcustom inf-clojure-var-doc-form
"(clojure.repl/doc %s)\n"
"Command to query inferior Clojure for a var's documentation."
:type 'string)

(defcustom inf-clojure-var-doc-form-lumo
"(lumo.repl/doc %s)\n"
"Lumo command to query inferior Clojure for a var's documentation."
:type 'string)

(defun inf-clojure-var-doc-form ()
"Return the form to query inferior Clojure for a var's documentation.
If you are using REPL types, it will pickup the most approapriate
`inf-clojure-var-doc-form` variant."
(pcase inf-clojure-repl-type
(lumo inf-clojure-var-doc-form-lumo)
(_ inf-clojure-var-doc-form)))

(defcustom inf-clojure-var-source-command
"(clojure.repl/source %s)\n"
"Command to query inferior Clojure for a var's source."
Expand All @@ -541,11 +595,24 @@ The prefix argument SWITCH-TO-REPL controls whether to switch to REPL after the
"Command to query inferior Clojure for a function's arglist."
:type 'string)

(defcustom inf-clojure-completion-command
(defcustom inf-clojure-completion-form
"(complete.core/completions \"%s\")\n"
"Command to query inferior Clojure for completion candidates."
:type 'string)

(defcustom inf-clojure-completion-form-lumo
"(doall (map str (lumo.repl/get-completions \"%s\")))\n"
"Lumo form to query inferior Clojure for completion candidates."
:type 'string)

(defun inf-clojure-completion-form ()
"Return the form to query inferior Clojure for a var's documentation.
If you are using REPL types, it will pickup the most approapriate
`inf-clojure-completion-form` variant."
(pcase inf-clojure-repl-type
(lumo inf-clojure-completion-form-lumo)
(_ inf-clojure-completion-form)))

(defcustom inf-clojure-ns-vars-command
"(clojure.repl/dir %s)\n"
"Command to show the public vars in a namespace."
Expand Down Expand Up @@ -618,9 +685,9 @@ The value is nil if it can't find one."

(defun inf-clojure-show-var-documentation (var)
"Send a command to the inferior Clojure to give documentation for VAR.
See variable `inf-clojure-var-doc-command'."
See function `inf-clojure-var-doc-form'."
(interactive (inf-clojure-symprompt "Var doc" (inf-clojure-var-at-pt)))
(comint-proc-query (inf-clojure-proc) (format inf-clojure-var-doc-command var)))
(comint-proc-query (inf-clojure-proc) (format (inf-clojure-var-doc-form) var)))

(defun inf-clojure-show-var-source (var)
"Send a command to the inferior Clojure to give source for VAR.
Expand Down Expand Up @@ -683,7 +750,7 @@ See variable `inf-clojure-macroexpand-command'.
With a prefix arg MACRO-1 uses `inf-clojure-macroexpand-1-command'."
(interactive "P")
(let ((last-sexp (buffer-substring-no-properties (save-excursion (backward-sexp) (point)) (point))))
(comint-send-string
(inf-clojure--send-string
(inf-clojure-proc)
(format (if macro-1
inf-clojure-macroexpand-1-command
Expand All @@ -710,7 +777,7 @@ See variable `inf-clojure-buffer'."
(unwind-protect
(let ((completion-snippet
(format
inf-clojure-completion-command (substring-no-properties expr))))
(inf-clojure-completion-form) (substring-no-properties expr))))
(process-send-string proc completion-snippet)
(while (and (not (string-match inf-clojure-prompt kept))
(accept-process-output proc 2)))
Expand Down Expand Up @@ -876,6 +943,39 @@ to suppress the usage of the target buffer discovery logic."
(inf-clojure (inf-clojure-cmd (inf-clojure-project-type)))
(rename-buffer target-buffer-name)))

(provide 'inf-clojure)
(defun inf-clojure--response-match-p (form match-p proc)
"Return MATCH-P on the result of sending FORM to PROC.
Note that this function will add a \n to the end of the string
for evaluation, therefore FORM should not include it."
(let* ((kept "")
(is-matching nil)
(prev-filter (process-filter proc)))
(set-process-filter proc (lambda (_ string) (setq kept (concat kept string))))
(unwind-protect
;; use the real comind-send-string in order to avoid stack overflows
(comint-send-string proc (concat form "\n"))
;; yey, loads of procedural code again
(while (and (not is-matching)
(not (string-match inf-clojure-prompt kept))
(accept-process-output proc 2))
(setq is-matching (funcall match-p kept)))
(set-process-filter proc prev-filter))
is-matching))

;;;; Lumo
;;;; ====

(defcustom inf-clojure--lumo-repl-form
"(js/global.hasOwnProperty \"$$LUMO_GLOBALS\")"
"Form to invoke in order to verify that we launched a Lumo REPL."
:type 'string)

(defalias 'inf-clojure--lumo-p
(apply-partially 'inf-clojure--response-match-p
inf-clojure--lumo-repl-form
(lambda (string)
(string-match-p (concat inf-clojure--lumo-repl-form "\\Ca*true\\Ca*") string)))
"Ascertain that PROC is a Lumo REPL.")

(provide 'inf-clojure)
;;; inf-clojure.el ends here