diff --git a/Cargo.lock b/Cargo.lock index 3527dad82..5e293db4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,6 +70,7 @@ dependencies = [ "rand", "regex", "serde_json", + "termion", "thiserror", "uplc", ] @@ -1643,6 +1644,17 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "libredox" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" +dependencies = [ + "bitflags 2.4.2", + "libc", + "redox_syscall", +] + [[package]] name = "libz-sys" version = "1.1.15" @@ -1924,6 +1936,12 @@ dependencies = [ "libc", ] +[[package]] +name = "numtoa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" + [[package]] name = "object" version = "0.32.2" @@ -2572,6 +2590,12 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_termios" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" + [[package]] name = "redox_users" version = "0.4.4" @@ -2579,7 +2603,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ "getrandom", - "libredox", + "libredox 0.0.1", "thiserror", ] @@ -3148,6 +3172,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "termion" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417813675a504dfbbf21bfde32c03e5bf9f2413999962b479023c02848c1c7a5" +dependencies = [ + "libc", + "libredox 0.0.2", + "numtoa", + "redox_termios", +] + [[package]] name = "textwrap" version = "0.15.2" diff --git a/crates/aiken-project/src/watch.rs b/crates/aiken-project/src/watch.rs index fff15cb14..9940445dd 100644 --- a/crates/aiken-project/src/watch.rs +++ b/crates/aiken-project/src/watch.rs @@ -162,6 +162,7 @@ where /// ``` pub fn watch_project( directory: Option<&Path>, + rebuild: Option>, filter: F, debounce: u32, mut action: A, @@ -225,8 +226,14 @@ where // release the lock here, in case other events come in drop(queue); + let force_rebuild = if let Some(ref r) = rebuild { + r.try_recv().is_ok() + } else { + false + }; + // If we have an event that survived the filter, then we can construct the project and invoke the action - if latest.is_some() { + if latest.is_some() || force_rebuild { print!("{esc}c", esc = 27 as char); eprint!("{esc}c", esc = 27 as char); eprintln!( diff --git a/crates/aiken/Cargo.toml b/crates/aiken/Cargo.toml index 3c9cf3bdb..6b50c87a3 100644 --- a/crates/aiken/Cargo.toml +++ b/crates/aiken/Cargo.toml @@ -30,6 +30,8 @@ regex = "1.7.1" serde_json = "1.0.94" thiserror = "1.0.39" +termion = "3.0.0" + aiken-lang = { path = "../aiken-lang", version = "1.0.24-alpha" } aiken-lsp = { path = "../aiken-lsp", version = "1.0.24-alpha" } aiken-project = { path = '../aiken-project', version = "1.0.24-alpha" } diff --git a/crates/aiken/src/cmd/build.rs b/crates/aiken/src/cmd/build.rs index a5379e0cf..39b5848af 100644 --- a/crates/aiken/src/cmd/build.rs +++ b/crates/aiken/src/cmd/build.rs @@ -51,15 +51,21 @@ pub fn exec( }: Args, ) -> miette::Result<()> { let result = if watch { - watch_project(directory.as_deref(), watch::default_filter, 500, |p| { - p.build( - uplc, - match filter_traces { - Some(filter_traces) => filter_traces(trace_level), - None => Tracing::All(trace_level), - }, - ) - }) + watch_project( + directory.as_deref(), + None, + watch::default_filter, + 500, + |p| { + p.build( + uplc, + match filter_traces { + Some(filter_traces) => filter_traces(trace_level), + None => Tracing::All(trace_level), + }, + ) + }, + ) } else { with_project(directory.as_deref(), deny, |p| { p.build( diff --git a/crates/aiken/src/cmd/check.rs b/crates/aiken/src/cmd/check.rs index 02fc71897..1bc938a93 100644 --- a/crates/aiken/src/cmd/check.rs +++ b/crates/aiken/src/cmd/check.rs @@ -4,8 +4,15 @@ use aiken_project::{ test_framework::PropertyTest, watch::{self, watch_project, with_project}, }; +use owo_colors::{OwoColorize, Stream::Stderr}; use rand::prelude::*; -use std::{path::PathBuf, process}; +use std::{ + path::PathBuf, + process, + sync::{Arc, Mutex}, + thread, +}; +use termion::input::TermRead; #[derive(clap::Args)] /// Type-check an Aiken project @@ -87,20 +94,79 @@ pub fn exec( let seed = seed.unwrap_or_else(|| rng.gen()); let result = if watch { - watch_project(directory.as_deref(), watch::default_filter, 500, |p| { - p.check( - skip_tests, - match_tests.clone(), - debug, - exact_match, - seed, - max_success, - match filter_traces { - Some(filter_traces) => filter_traces(trace_level), - None => Tracing::All(trace_level), - }, - ) - }) + let mut stdin = termion::async_stdin().keys(); + + let (sender, receiver) = std::sync::mpsc::channel(); + let filter = Arc::new(Mutex::new("".to_string())); + { + let filter = filter.clone(); + thread::spawn(move || { + let mut working = "".to_string(); + loop { + if let Some(c) = stdin.next() { + match c { + Ok(termion::event::Key::Backspace) => { + if !working.is_empty() { + working = working[..working.len() - 1].to_string(); + } + } + Ok(termion::event::Key::Char('\n')) => { + let mut filt = filter.lock().unwrap(); + *filt = working; + working = "".to_string(); + sender.send(()).unwrap(); + } + Ok(termion::event::Key::Char(c)) => { + working.push(c); + } + _ => {} + } + } + } + }); + } + + watch_project( + directory.as_deref(), + Some(receiver), + watch::default_filter, + 500, + |p| { + let filt = filter.lock().unwrap(); + let final_match_tests = match match_tests.clone() { + Some(existing) if filt.is_empty() => Some(existing), + Some(existing) => { + let mut e = existing.clone(); + e.push(filt.clone()); + Some(e) + } + None if !filt.is_empty() => Some(vec![filt.clone()]), + None => None, + }; + let result = p.check( + skip_tests, + final_match_tests.clone(), + debug, + exact_match, + seed, + max_success, + match filter_traces { + Some(filter_traces) => filter_traces(trace_level), + None => Tracing::All(trace_level), + }, + ); + if let Some(fmt) = final_match_tests { + println!( + " {} {}", + "Filtered by:" + .if_supports_color(Stderr, |s| s.bold()) + .if_supports_color(Stderr, |s| s.purple()), + fmt.join(", ") + ); + } + result + }, + ) } else { with_project(directory.as_deref(), deny, |p| { p.check( diff --git a/crates/aiken/src/cmd/docs.rs b/crates/aiken/src/cmd/docs.rs index fbc3ac182..ba75b60f9 100644 --- a/crates/aiken/src/cmd/docs.rs +++ b/crates/aiken/src/cmd/docs.rs @@ -34,9 +34,13 @@ pub fn exec( }: Args, ) -> miette::Result<()> { let result = if watch { - watch_project(directory.as_deref(), watch::default_filter, 500, |p| { - p.docs(destination.clone(), include_dependencies) - }) + watch_project( + directory.as_deref(), + None, + watch::default_filter, + 500, + |p| p.docs(destination.clone(), include_dependencies), + ) } else { with_project(directory.as_deref(), deny, |p| { p.docs(destination.clone(), include_dependencies)