diff --git a/.gitignore b/.gitignore
index 3d9cc85..414084e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,6 @@
*-autoloads.el
/rust-mode-pkg.el
.eask
-/dist
\ No newline at end of file
+/dist
+test-project/Cargo.lock
+test-project/target/
diff --git a/README.md b/README.md
index 2b77636..17aaa59 100644
--- a/README.md
+++ b/README.md
@@ -173,6 +173,9 @@ By default these are bound to:
- C-c C-c C-t `rust-test`
- C-c C-c C-r `rust-run`
+To run programs requiring user input use universal argument when invoking
+ `rust-run` (C-u C-c C-c C-r).
+
### Clippy
`rust-run-clippy` runs
diff --git a/rust-cargo-tests.el b/rust-cargo-tests.el
new file mode 100644
index 0000000..f49cba0
--- /dev/null
+++ b/rust-cargo-tests.el
@@ -0,0 +1,53 @@
+;;; rust-cargo-tests.el --- ERT tests for rust-cargo.el -*- lexical-binding: t; -*-
+(require 'rust-cargo)
+(require 'ert)
+
+(defun rust-test--wait-process-exit ()
+ "Wait for comint process for current buffer to exit."
+ (let ((proc (get-buffer-process (current-buffer))))
+ (while (not (eq (process-status proc) 'exit))
+ (sit-for 0.2))))
+
+(defun rust-test--send-process-string (string)
+ "Send STRING to comint process for current buffer."
+ (let ((proc (get-buffer-process (current-buffer))))
+ (comint-send-string proc string)))
+
+(defmacro rust-test--with-main-file-buffer (expr)
+ `(let* ((test-dir (expand-file-name "test-project/" default-directory))
+ (main-file (expand-file-name "src/main.rs" test-dir)))
+ (save-current-buffer
+ (find-file main-file)
+ ,expr)))
+
+(defun rust-test--find-string (string)
+ "Find STRING in current buffer."
+ (goto-char (point-min))
+ (not (null (search-forward string nil t))))
+
+
+(ert-deftest rust-test-compile ()
+ (skip-unless (executable-find rust-cargo-bin))
+ (setq rust-cargo-default-arguments "")
+ (rust-test--with-main-file-buffer
+ (with-current-buffer (rust-compile)
+ (rust-test--wait-process-exit)
+ (should (rust-test--find-string "Compilation finished")))))
+
+(ert-deftest rust-test-run ()
+ (skip-unless (executable-find rust-cargo-bin))
+ (setq rust-cargo-default-arguments "")
+ (rust-test--with-main-file-buffer
+ (with-current-buffer (rust-run)
+ (rust-test--wait-process-exit)
+ (should (rust-test--find-string "***run not interactive")))))
+
+(ert-deftest rust-test-run-interactive ()
+ (skip-unless (executable-find rust-cargo-bin))
+ (setq rust-cargo-default-arguments "interactive")
+ (rust-test--with-main-file-buffer
+ ;; '(4) is default value when called interactively with universal argument
+ (with-current-buffer (rust-run '(4))
+ (rust-test--send-process-string "1234\n")
+ (rust-test--wait-process-exit)
+ (should (rust-test--find-string "***run interactive: 1234")))))
diff --git a/rust-cargo.el b/rust-cargo.el
index bda23d8..cd2214a 100644
--- a/rust-cargo.el
+++ b/rust-cargo.el
@@ -67,46 +67,54 @@
;;; Internal
-(defun rust--compile (format-string &rest args)
+(defun rust--compile (comint format-string &rest args)
(when (null rust-buffer-project)
(rust-update-buffer-project))
(let ((default-directory
(or (and rust-buffer-project
(file-name-directory rust-buffer-project))
- default-directory)))
- (compile (apply #'format format-string args))))
+ default-directory))
+ ;; make sure comint is a boolean value
+ (comint (not (not comint))))
+ (compile (apply #'format format-string args) comint)))
;;; Commands
(defun rust-check ()
"Compile using `cargo check`"
(interactive)
- (rust--compile "%s check %s" rust-cargo-bin rust-cargo-default-arguments))
+ (rust--compile nil "%s check %s" rust-cargo-bin rust-cargo-default-arguments))
(defun rust-compile ()
"Compile using `cargo build`"
(interactive)
- (rust--compile "%s build %s" rust-cargo-bin rust-cargo-default-arguments))
+ (rust--compile nil "%s build %s" rust-cargo-bin rust-cargo-default-arguments))
(defun rust-compile-release ()
"Compile using `cargo build --release`"
(interactive)
- (rust--compile "%s build --release" rust-cargo-bin))
+ (rust--compile nil "%s build --release" rust-cargo-bin))
-(defun rust-run ()
- "Run using `cargo run`"
- (interactive)
- (rust--compile "%s run %s" rust-cargo-bin rust-cargo-default-arguments))
+(defun rust-run (&optional comint)
+ "Run using `cargo run`
-(defun rust-run-release ()
- "Run using `cargo run --release`"
- (interactive)
- (rust--compile "%s run --release" rust-cargo-bin))
+If optional arg COMINT is t or invoked with universal prefix arg,
+output buffer will be in comint mode, i.e. interactive."
+ (interactive "P")
+ (rust--compile comint "%s run %s" rust-cargo-bin rust-cargo-default-arguments))
+
+(defun rust-run-release (&optional comint)
+ "Run using `cargo run --release`
+
+If optional arg COMINT is t or invoked with universal prefix arg,
+output buffer will be in comint mode, i.e. interactive."
+ (interactive "P")
+ (rust--compile comint "%s run --release" rust-cargo-bin))
(defun rust-test ()
"Test using `cargo test`"
(interactive)
- (rust--compile "%s test %s" rust-cargo-bin rust-cargo-default-arguments))
+ (rust--compile nil "%s test %s" rust-cargo-bin rust-cargo-default-arguments))
(defun rust-run-clippy ()
"Run `cargo clippy'."
@@ -118,7 +126,7 @@
;; set `compile-command' temporarily so `compile' doesn't
;; clobber the existing value
(compile-command (mapconcat #'shell-quote-argument args " ")))
- (rust--compile compile-command)))
+ (rust--compile nil compile-command)))
;;; _
(provide 'rust-cargo)
diff --git a/test-project/src/main.rs b/test-project/src/main.rs
new file mode 100644
index 0000000..4d22e98
--- /dev/null
+++ b/test-project/src/main.rs
@@ -0,0 +1,25 @@
+use std::{env, io};
+
+fn main() {
+ let mut args = env::args();
+
+ if args.len() == 1 {
+ println!("***run not interactive");
+ } else {
+ match args.nth(1).unwrap().as_str() {
+ "interactive" => {
+ let mut line = String::new();
+
+ io::stdin()
+ .read_line(&mut line)
+ .expect("Failed to read line");
+
+ println!("***run interactive: {line}");
+ }
+
+ _ => {
+ panic!("unexpected argument");
+ }
+ }
+ }
+}