From c0638afb98bd457e5ea811a09cc6f695ab91d86d Mon Sep 17 00:00:00 2001 From: Svilen Ivanov Date: Mon, 13 May 2024 13:03:40 +0300 Subject: [PATCH] Support running interactive programs --- .gitignore | 4 ++- README.md | 3 +++ rust-cargo-tests.el | 53 ++++++++++++++++++++++++++++++++++++++++ rust-cargo.el | 40 ++++++++++++++++++------------ test-project/src/main.rs | 25 +++++++++++++++++++ 5 files changed, 108 insertions(+), 17 deletions(-) create mode 100644 rust-cargo-tests.el create mode 100644 test-project/src/main.rs 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"); + } + } + } +}