Skip to content

Commit c6bf334

Browse files
authored
Rewatch: improve watch output and add clear-screen (#8373)
* Rewatch: show warnings in completion output * Rewatch: use CompilationOutcome enum * feat(rewatch): add watch --clear-screen Make screen clearing opt-in so scripted stdout stays stable while interactive watch mode can drop stale warnings and errors between rebuilds. Also print explicit rebuild and failure markers so fixed vs broken state is easier to spot. Refs #8139 Signed-off-by: Paul Kim <paul.bushuo@gmail.com> * fix(rewatch): restore full rebuild plain output Signed-off-by: Paul Kim <paul.bushuo@gmail.com> --------- Signed-off-by: Paul Kim <paul.bushuo@gmail.com>
1 parent fe34ecd commit c6bf334

5 files changed

Lines changed: 217 additions & 57 deletions

File tree

rewatch/src/build.rs

Lines changed: 89 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,43 @@ pub struct CompilerArgs {
5656
pub parser_args: Vec<String>,
5757
}
5858

59+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60+
pub enum CompilationOutcome {
61+
Clean,
62+
Warnings,
63+
}
64+
65+
fn has_output(output: &str) -> bool {
66+
helpers::contains_ascii_characters(output)
67+
}
68+
69+
fn has_config_warnings(build_state: &BuildCommandState) -> bool {
70+
build_state.packages.iter().any(|(_, package)| {
71+
package.is_local_dep
72+
&& (!package.config.get_unsupported_fields().is_empty()
73+
|| !package.config.get_unknown_fields().is_empty())
74+
})
75+
}
76+
77+
pub fn format_finished_compilation_message(
78+
compilation_kind: Option<&str>,
79+
outcome: CompilationOutcome,
80+
duration: Duration,
81+
) -> String {
82+
let compilation_kind = compilation_kind
83+
.map(|kind| format!("{kind} "))
84+
.unwrap_or_default();
85+
let (status, warning_suffix) = match outcome {
86+
CompilationOutcome::Clean => (CHECKMARK, ""),
87+
CompilationOutcome::Warnings => (WARNING, " with warnings"),
88+
};
89+
90+
format!(
91+
"{LINE_CLEAR}{status}Finished {compilation_kind}compilation{warning_suffix} in {:.2}s",
92+
duration.as_secs_f64()
93+
)
94+
}
95+
5996
pub fn get_compiler_args(rescript_file_path: &Path) -> Result<String> {
6097
let filename = &helpers::get_abs_path(rescript_file_path);
6198
let current_package = helpers::get_abs_path(
@@ -246,7 +283,7 @@ pub fn incremental_build(
246283
only_incremental: bool,
247284
create_sourcedirs: bool,
248285
plain_output: bool,
249-
) -> Result<(), IncrementalBuildError> {
286+
) -> Result<CompilationOutcome, IncrementalBuildError> {
250287
let build_folder = build_state.root_folder.to_string_lossy().to_string();
251288

252289
let _lock = get_lock_or_exit(LockKind::Build, &build_folder);
@@ -264,7 +301,7 @@ pub fn incremental_build(
264301
ProgressStyle::with_template(&format!(
265302
"{} {}Parsing... {{spinner}} {{pos}}/{{len}} {{msg}}",
266303
format_step(current_step, total_steps),
267-
CODE
304+
PARSE
268305
))
269306
.unwrap(),
270307
);
@@ -314,13 +351,14 @@ pub fn incremental_build(
314351
"{}{} {}Parsed {} source files in {:.2}s",
315352
LINE_CLEAR,
316353
format_step(current_step, total_steps),
317-
CODE,
354+
PARSE,
318355
num_dirty_modules,
319356
default_timing.unwrap_or(timing_parse_total).as_secs_f64()
320357
);
321358
}
322359
}
323-
if helpers::contains_ascii_characters(&parse_warnings) {
360+
let has_parse_warnings = has_output(&parse_warnings);
361+
if has_parse_warnings {
324362
eprintln!("{}", &parse_warnings);
325363
}
326364

@@ -389,13 +427,13 @@ pub fn incremental_build(
389427
);
390428
}
391429
}
392-
if helpers::contains_ascii_characters(&compile_warnings) {
430+
if has_output(&compile_warnings) {
393431
eprintln!("{}", &compile_warnings);
394432
}
395433
if initial_build {
396434
log_config_warnings(build_state);
397435
}
398-
if helpers::contains_ascii_characters(&compile_errors) {
436+
if has_output(&compile_errors) {
399437
eprintln!("{}", &compile_errors);
400438
}
401439

@@ -406,6 +444,14 @@ pub fn incremental_build(
406444
plain_output,
407445
})
408446
} else {
447+
let has_compile_warnings = has_output(&compile_warnings);
448+
let has_config_warning_output = initial_build && has_config_warnings(build_state);
449+
let outcome = if has_parse_warnings || has_compile_warnings || has_config_warning_output {
450+
CompilationOutcome::Warnings
451+
} else {
452+
CompilationOutcome::Clean
453+
};
454+
409455
if show_progress {
410456
if plain_output {
411457
println!("Compiled {num_compiled_modules} modules")
@@ -421,7 +467,7 @@ pub fn incremental_build(
421467
}
422468
}
423469

424-
if helpers::contains_ascii_characters(&compile_warnings) {
470+
if has_compile_warnings {
425471
eprintln!("{}", &compile_warnings);
426472
}
427473
if initial_build {
@@ -432,7 +478,7 @@ pub fn incremental_build(
432478
write_compiler_info(build_state);
433479

434480
let _lock = drop_lock(LockKind::Build, &build_folder);
435-
Ok(())
481+
Ok(outcome)
436482
}
437483
}
438484

@@ -519,14 +565,16 @@ pub fn build(
519565
create_sourcedirs,
520566
plain_output,
521567
) {
522-
Ok(_) => {
568+
Ok(result) => {
523569
if !plain_output && show_progress {
524570
let timing_total_elapsed = timing_total.elapsed();
525571
println!(
526-
"\n{}{}Finished Compilation in {:.2}s",
527-
LINE_CLEAR,
528-
SPARKLES,
529-
default_timing.unwrap_or(timing_total_elapsed).as_secs_f64()
572+
"\n{}",
573+
format_finished_compilation_message(
574+
None,
575+
result,
576+
default_timing.unwrap_or(timing_total_elapsed),
577+
)
530578
);
531579
}
532580
clean::cleanup_after_build(&build_state);
@@ -540,3 +588,31 @@ pub fn build(
540588
}
541589
}
542590
}
591+
592+
#[cfg(test)]
593+
mod tests {
594+
use super::*;
595+
596+
#[test]
597+
fn formats_successful_completion_message() {
598+
assert_eq!(
599+
format_finished_compilation_message(None, CompilationOutcome::Clean, Duration::from_millis(1500),),
600+
format!("{LINE_CLEAR}{}Finished compilation in 1.50s", CHECKMARK)
601+
);
602+
}
603+
604+
#[test]
605+
fn formats_warning_completion_message() {
606+
assert_eq!(
607+
format_finished_compilation_message(
608+
Some("incremental"),
609+
CompilationOutcome::Warnings,
610+
Duration::from_millis(1500),
611+
),
612+
format!(
613+
"{LINE_CLEAR}{}Finished incremental compilation with warnings in 1.50s",
614+
WARNING
615+
)
616+
);
617+
}
618+
}

rewatch/src/cli.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,16 @@ mod tests {
383383
}
384384
}
385385

386+
#[test]
387+
fn watch_clear_screen_flag_is_parsed() {
388+
let cli = parse(&["rescript", "watch", "--clear-screen"]).expect("expected watch command");
389+
390+
match cli.command {
391+
Command::Watch(watch_args) => assert!(watch_args.clear_screen),
392+
other => panic!("expected watch command, got {other:?}"),
393+
}
394+
}
395+
386396
#[test]
387397
fn clean_prod_flag_is_parsed() {
388398
let cli = parse(&["rescript", "clean", "--prod"]).expect("expected clean command");
@@ -428,6 +438,10 @@ pub struct WatchArgs {
428438
#[command(flatten)]
429439
pub warn_error: WarnErrorArg,
430440

441+
/// Clear terminal screen before each rebuild in interactive watch mode.
442+
#[arg(long, default_value_t = false)]
443+
pub clear_screen: bool,
444+
431445
/// Skip dev-dependencies and dev sources (type: "dev")
432446
#[arg(long, default_value_t = false)]
433447
pub prod: bool,
@@ -440,6 +454,7 @@ impl From<BuildArgs> for WatchArgs {
440454
filter: build_args.filter,
441455
after_build: build_args.after_build,
442456
warn_error: build_args.warn_error,
457+
clear_screen: false,
443458
prod: build_args.prod,
444459
}
445460
}

rewatch/src/helpers.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ pub mod deserialize;
1818

1919
pub mod emojis {
2020
use console::Emoji;
21-
pub static COMMAND: Emoji<'_, '_> = Emoji("🏃 ", "");
22-
pub static SWEEP: Emoji<'_, '_> = Emoji("🧹 ", "");
23-
pub static CODE: Emoji<'_, '_> = Emoji("🧱 ", "");
24-
pub static SWORDS: Emoji<'_, '_> = Emoji("🤺 ", "");
25-
pub static CHECKMARK: Emoji<'_, '_> = Emoji("✅ ", "");
26-
pub static CROSS: Emoji<'_, '_> = Emoji(" ", "");
27-
pub static SPARKLES: Emoji<'_, '_> = Emoji(" ", "");
21+
pub static COMMAND: Emoji<'_, '_> = Emoji("🏃 ", "[run] ");
22+
pub static SWEEP: Emoji<'_, '_> = Emoji("🧹 ", "[clean] ");
23+
pub static PARSE: Emoji<'_, '_> = Emoji("🧱 ", "[parse] ");
24+
pub static SWORDS: Emoji<'_, '_> = Emoji("🤺 ", "[build] ");
25+
pub static CHECKMARK: Emoji<'_, '_> = Emoji("✅ ", "[ok] ");
26+
pub static WARNING: Emoji<'_, '_> = Emoji("⚠️ ", "[warn] ");
27+
pub static CROSS: Emoji<'_, '_> = Emoji(" ", "[error] ");
2828
pub static LINE_CLEAR: &str = "\x1b[2K\r";
2929
}
3030

rewatch/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ fn main() -> Result<()> {
7979
true, // create_sourcedirs is now always enabled
8080
plain_output,
8181
(*watch_args.warn_error).clone(),
82+
watch_args.clear_screen,
8283
watch_args.prod,
8384
) {
8485
Err(e) => {

0 commit comments

Comments
 (0)