Use alternate screen and refactor terminal output. (#2665)
This commit is contained in:
parent
51854ba4df
commit
6999be9ab0
22
Cargo.lock
generated
22
Cargo.lock
generated
@ -495,6 +495,16 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82e95fbd621905b854affdc67943b043a0fbb6ed7385fd5a25650d19a8a6cfdf"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-url"
|
||||
version = "0.3.1"
|
||||
@ -1383,6 +1393,17 @@ dependencies = [
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "6.1.1"
|
||||
@ -2557,6 +2578,7 @@ dependencies = [
|
||||
"clap_mangen",
|
||||
"codespan-reporting",
|
||||
"comemo",
|
||||
"ctrlc",
|
||||
"dirs",
|
||||
"ecow",
|
||||
"env_proxy",
|
||||
|
@ -36,6 +36,7 @@ ciborium = "0.2.1"
|
||||
clap = { version = "4.4", features = ["derive", "env"] }
|
||||
clap_complete = "4.2.1"
|
||||
clap_mangen = "0.2.10"
|
||||
ctrlc = "3.4.1"
|
||||
codespan-reporting = "0.11"
|
||||
comemo = { git = "https://github.com/typst/comemo", rev = "ddb3773" }
|
||||
csv = "1"
|
||||
|
@ -30,6 +30,7 @@ chrono = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
codespan-reporting = { workspace = true }
|
||||
comemo = { workspace = true }
|
||||
ctrlc = { workspace = true }
|
||||
dirs = { workspace = true }
|
||||
ecow = { workspace = true }
|
||||
env_proxy = { workspace = true }
|
||||
|
@ -3,11 +3,10 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use chrono::{Datelike, Timelike};
|
||||
use codespan_reporting::diagnostic::{Diagnostic, Label};
|
||||
use codespan_reporting::term::{self, termcolor};
|
||||
use codespan_reporting::term;
|
||||
use ecow::{eco_format, EcoString};
|
||||
use parking_lot::RwLock;
|
||||
use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
|
||||
use termcolor::{ColorChoice, StandardStream};
|
||||
use typst::diag::{bail, At, Severity, SourceDiagnostic, StrResult};
|
||||
use typst::eval::Tracer;
|
||||
use typst::foundations::Datetime;
|
||||
@ -21,7 +20,7 @@ use crate::args::{CompileCommand, DiagnosticFormat, OutputFormat};
|
||||
use crate::timings::Timer;
|
||||
use crate::watch::Status;
|
||||
use crate::world::SystemWorld;
|
||||
use crate::{color_stream, set_failed};
|
||||
use crate::{set_failed, terminal};
|
||||
|
||||
type CodespanResult<T> = Result<T, CodespanError>;
|
||||
type CodespanError = codespan_reporting::files::Error;
|
||||
@ -313,11 +312,6 @@ pub fn print_diagnostics(
|
||||
warnings: &[SourceDiagnostic],
|
||||
diagnostic_format: DiagnosticFormat,
|
||||
) -> Result<(), codespan_reporting::files::Error> {
|
||||
let mut w = match diagnostic_format {
|
||||
DiagnosticFormat::Human => color_stream(),
|
||||
DiagnosticFormat::Short => StandardStream::stderr(ColorChoice::Never),
|
||||
};
|
||||
|
||||
let mut config = term::Config { tab_width: 2, ..Default::default() };
|
||||
if diagnostic_format == DiagnosticFormat::Short {
|
||||
config.display_style = term::DisplayStyle::Short;
|
||||
@ -338,7 +332,7 @@ pub fn print_diagnostics(
|
||||
)
|
||||
.with_labels(label(world, diagnostic.span).into_iter().collect());
|
||||
|
||||
term::emit(&mut w, &config, world, &diag)?;
|
||||
term::emit(&mut terminal::out(), &config, world, &diag)?;
|
||||
|
||||
// Stacktrace-like helper diagnostics.
|
||||
for point in &diagnostic.trace {
|
||||
@ -347,7 +341,7 @@ pub fn print_diagnostics(
|
||||
.with_message(message)
|
||||
.with_labels(label(world, point.span).into_iter().collect());
|
||||
|
||||
term::emit(&mut w, &config, world, &help)?;
|
||||
term::emit(&mut terminal::out(), &config, world, &help)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
// https://github.com/rust-lang/rustup/blob/master/src/cli/download_tracker.rs
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::io::{self, ErrorKind, Read, Stderr, Write};
|
||||
use std::io::{self, ErrorKind, Read, Write};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
@ -11,6 +11,8 @@ use native_tls::{Certificate, TlsConnector};
|
||||
use once_cell::sync::Lazy;
|
||||
use ureq::Response;
|
||||
|
||||
use crate::terminal;
|
||||
|
||||
/// Keep track of this many download speed samples.
|
||||
const SPEED_SAMPLES: usize = 5;
|
||||
|
||||
@ -72,8 +74,6 @@ struct RemoteReader {
|
||||
downloaded_last_few_secs: VecDeque<usize>,
|
||||
start_time: Instant,
|
||||
last_print: Option<Instant>,
|
||||
displayed_charcount: Option<usize>,
|
||||
stderr: Stderr,
|
||||
}
|
||||
|
||||
impl RemoteReader {
|
||||
@ -94,8 +94,6 @@ impl RemoteReader {
|
||||
downloaded_last_few_secs: VecDeque::with_capacity(SPEED_SAMPLES),
|
||||
start_time: Instant::now(),
|
||||
last_print: None,
|
||||
displayed_charcount: None,
|
||||
stderr: io::stderr(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,66 +144,52 @@ impl RemoteReader {
|
||||
self.downloaded_last_few_secs.push_front(self.downloaded_this_sec);
|
||||
self.downloaded_this_sec = 0;
|
||||
|
||||
if let Some(n) = self.displayed_charcount {
|
||||
self.erase_chars(n);
|
||||
}
|
||||
|
||||
self.display();
|
||||
let _ = write!(self.stderr, "\r");
|
||||
terminal::out().clear_last_line()?;
|
||||
self.display()?;
|
||||
self.last_print = Some(Instant::now());
|
||||
}
|
||||
}
|
||||
|
||||
self.display();
|
||||
let _ = writeln!(self.stderr);
|
||||
self.display()?;
|
||||
writeln!(&mut terminal::out())?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
/// Compile and format several download statistics and make an attempt at
|
||||
/// displaying them on standard error.
|
||||
fn display(&mut self) {
|
||||
fn display(&mut self) -> io::Result<()> {
|
||||
let sum: usize = self.downloaded_last_few_secs.iter().sum();
|
||||
let len = self.downloaded_last_few_secs.len();
|
||||
let speed = if len > 0 { sum / len } else { self.content_len.unwrap_or(0) };
|
||||
|
||||
let total = as_time_unit(self.total_downloaded, false);
|
||||
let speed_h = as_time_unit(speed, true);
|
||||
let total_downloaded = as_bytes_unit(self.total_downloaded);
|
||||
let speed_h = as_throughput_unit(speed);
|
||||
let elapsed =
|
||||
time_suffix(Instant::now().saturating_duration_since(self.start_time));
|
||||
|
||||
let output = match self.content_len {
|
||||
match self.content_len {
|
||||
Some(content_len) => {
|
||||
let percent = (self.total_downloaded as f64 / content_len as f64) * 100.;
|
||||
let remaining = content_len - self.total_downloaded;
|
||||
|
||||
format!(
|
||||
"{} / {} ({:3.0} %) {} in {} ETA: {}",
|
||||
total,
|
||||
as_time_unit(content_len, false),
|
||||
percent,
|
||||
speed_h,
|
||||
elapsed,
|
||||
time_suffix(Duration::from_secs(if speed == 0 {
|
||||
0
|
||||
} else {
|
||||
(remaining / speed) as u64
|
||||
}))
|
||||
)
|
||||
let download_size = as_bytes_unit(content_len);
|
||||
let eta = time_suffix(Duration::from_secs(if speed == 0 {
|
||||
0
|
||||
} else {
|
||||
(remaining / speed) as u64
|
||||
}));
|
||||
writeln!(
|
||||
&mut terminal::out(),
|
||||
"{total_downloaded} / {download_size} ({percent:3.0} %) {speed_h} in {elapsed} ETA: {eta}",
|
||||
)?;
|
||||
}
|
||||
None => format!("Total: {total} Speed: {speed_h} Elapsed: {elapsed}"),
|
||||
None => writeln!(
|
||||
&mut terminal::out(),
|
||||
"Total downloaded: {total_downloaded} Speed: {speed_h} Elapsed: {elapsed}",
|
||||
)?,
|
||||
};
|
||||
|
||||
let _ = write!(self.stderr, "{output}");
|
||||
|
||||
self.displayed_charcount = Some(output.chars().count());
|
||||
}
|
||||
|
||||
/// Erase each previously printed character and add a carriage return
|
||||
/// character, clearing the line for the next `display()` update.
|
||||
fn erase_chars(&mut self, count: usize) {
|
||||
let _ = write!(self.stderr, "{}", " ".repeat(count));
|
||||
let _ = write!(self.stderr, "\r");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,22 +215,24 @@ fn format_dhms(sec: u64) -> (u64, u8, u8, u8) {
|
||||
|
||||
/// Format a given size as a unit of time. Setting `include_suffix` to true
|
||||
/// appends a '/s' (per second) suffix.
|
||||
fn as_time_unit(size: usize, include_suffix: bool) -> String {
|
||||
fn as_bytes_unit(size: usize) -> String {
|
||||
const KI: f64 = 1024.0;
|
||||
const MI: f64 = KI * KI;
|
||||
const GI: f64 = KI * KI * KI;
|
||||
|
||||
let size = size as f64;
|
||||
|
||||
let suffix = if include_suffix { "/s" } else { "" };
|
||||
|
||||
if size >= GI {
|
||||
format!("{:5.1} GiB{}", size / GI, suffix)
|
||||
format!("{:5.1} GiB", size / GI)
|
||||
} else if size >= MI {
|
||||
format!("{:5.1} MiB{}", size / MI, suffix)
|
||||
format!("{:5.1} MiB", size / MI)
|
||||
} else if size >= KI {
|
||||
format!("{:5.1} KiB{}", size / KI, suffix)
|
||||
format!("{:5.1} KiB", size / KI)
|
||||
} else {
|
||||
format!("{size:3.0} B{suffix}")
|
||||
format!("{size:3.0} B")
|
||||
}
|
||||
}
|
||||
|
||||
fn as_throughput_unit(size: usize) -> String {
|
||||
as_bytes_unit(size) + "/s"
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ mod download;
|
||||
mod fonts;
|
||||
mod package;
|
||||
mod query;
|
||||
mod terminal;
|
||||
mod timings;
|
||||
#[cfg(feature = "self-update")]
|
||||
mod update;
|
||||
@ -11,13 +12,14 @@ mod watch;
|
||||
mod world;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::io::{self, IsTerminal, Write};
|
||||
use std::io::{self, Write};
|
||||
use std::process::ExitCode;
|
||||
|
||||
use clap::Parser;
|
||||
use codespan_reporting::term::{self, termcolor};
|
||||
use codespan_reporting::term;
|
||||
use codespan_reporting::term::termcolor::WriteColor;
|
||||
use ecow::eco_format;
|
||||
use once_cell::sync::Lazy;
|
||||
use termcolor::{ColorChoice, WriteColor};
|
||||
|
||||
use crate::args::{CliArguments, Command};
|
||||
use crate::timings::Timer;
|
||||
@ -33,6 +35,7 @@ static ARGS: Lazy<CliArguments> = Lazy::new(CliArguments::parse);
|
||||
/// Entry point.
|
||||
fn main() -> ExitCode {
|
||||
let timer = Timer::new(&ARGS);
|
||||
|
||||
let res = match &ARGS.command {
|
||||
Command::Compile(command) => crate::compile::compile(timer, command.clone()),
|
||||
Command::Watch(command) => crate::watch::watch(timer, command.clone()),
|
||||
@ -41,7 +44,13 @@ fn main() -> ExitCode {
|
||||
Command::Update(command) => crate::update::update(command),
|
||||
};
|
||||
|
||||
if let Err(msg) = res {
|
||||
// Leave the alternate screen if it was opened. This operation is done here
|
||||
// so that it is executed prior to printing the final error.
|
||||
let res_leave = terminal::out()
|
||||
.leave_alternate_screen()
|
||||
.map_err(|err| eco_format!("failed to leave alternate screen ({err})"));
|
||||
|
||||
if let Err(msg) = res.or(res_leave) {
|
||||
set_failed();
|
||||
print_error(&msg).expect("failed to print error");
|
||||
}
|
||||
@ -54,38 +63,23 @@ fn set_failed() {
|
||||
EXIT.with(|cell| cell.set(ExitCode::FAILURE));
|
||||
}
|
||||
|
||||
/// Print an application-level error (independent from a source file).
|
||||
fn print_error(msg: &str) -> io::Result<()> {
|
||||
let mut w = color_stream();
|
||||
let styles = term::Styles::default();
|
||||
|
||||
w.set_color(&styles.header_error)?;
|
||||
write!(w, "error")?;
|
||||
|
||||
w.reset()?;
|
||||
writeln!(w, ": {msg}.")
|
||||
}
|
||||
|
||||
/// Get stderr with color support if desirable.
|
||||
fn color_stream() -> termcolor::StandardStream {
|
||||
termcolor::StandardStream::stderr(match ARGS.color {
|
||||
clap::ColorChoice::Auto => {
|
||||
if std::io::stderr().is_terminal() {
|
||||
ColorChoice::Auto
|
||||
} else {
|
||||
ColorChoice::Never
|
||||
}
|
||||
}
|
||||
clap::ColorChoice::Always => ColorChoice::Always,
|
||||
clap::ColorChoice::Never => ColorChoice::Never,
|
||||
})
|
||||
}
|
||||
|
||||
/// Used by `args.rs`.
|
||||
fn typst_version() -> &'static str {
|
||||
env!("TYPST_VERSION")
|
||||
}
|
||||
|
||||
/// Print an application-level error (independent from a source file).
|
||||
fn print_error(msg: &str) -> io::Result<()> {
|
||||
let styles = term::Styles::default();
|
||||
|
||||
let mut output = terminal::out();
|
||||
output.set_color(&styles.header_error)?;
|
||||
write!(output, "error")?;
|
||||
|
||||
output.reset()?;
|
||||
writeln!(output, ": {msg}.")
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "self-update"))]
|
||||
mod update {
|
||||
use crate::args::UpdateCommand;
|
||||
|
@ -8,8 +8,8 @@ use termcolor::WriteColor;
|
||||
use typst::diag::{PackageError, PackageResult};
|
||||
use typst::syntax::PackageSpec;
|
||||
|
||||
use crate::color_stream;
|
||||
use crate::download::download_with_progress;
|
||||
use crate::terminal;
|
||||
|
||||
/// Make a package available in the on-disk cache.
|
||||
pub fn prepare_package(spec: &PackageSpec) -> PackageResult<PathBuf> {
|
||||
@ -69,12 +69,12 @@ fn download_package(spec: &PackageSpec, package_dir: &Path) -> PackageResult<()>
|
||||
|
||||
/// Print that a package downloading is happening.
|
||||
fn print_downloading(spec: &PackageSpec) -> io::Result<()> {
|
||||
let mut w = color_stream();
|
||||
let styles = term::Styles::default();
|
||||
|
||||
w.set_color(&styles.header_help)?;
|
||||
write!(w, "downloading")?;
|
||||
let mut term_out = terminal::out();
|
||||
term_out.set_color(&styles.header_help)?;
|
||||
write!(term_out, "downloading")?;
|
||||
|
||||
w.reset()?;
|
||||
writeln!(w, " {spec}")
|
||||
term_out.reset()?;
|
||||
writeln!(term_out, " {spec}")
|
||||
}
|
||||
|
162
crates/typst-cli/src/terminal.rs
Normal file
162
crates/typst-cli/src/terminal.rs
Normal file
@ -0,0 +1,162 @@
|
||||
use std::io::{self, IsTerminal, Write};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::Duration;
|
||||
|
||||
use codespan_reporting::term::termcolor;
|
||||
use ecow::eco_format;
|
||||
use once_cell::sync::Lazy;
|
||||
use termcolor::{ColorChoice, WriteColor};
|
||||
use typst::diag::StrResult;
|
||||
|
||||
use crate::ARGS;
|
||||
|
||||
/// Returns a handle to the optionally colored terminal output.
|
||||
pub fn out() -> TermOut {
|
||||
static OUTPUT: Lazy<TermOutInner> = Lazy::new(TermOutInner::new);
|
||||
TermOut { inner: &OUTPUT }
|
||||
}
|
||||
|
||||
/// The stuff that has to be shared between instances of [`TermOut`].
|
||||
struct TermOutInner {
|
||||
active: AtomicBool,
|
||||
stream: termcolor::StandardStream,
|
||||
in_alternate_screen: AtomicBool,
|
||||
}
|
||||
|
||||
impl TermOutInner {
|
||||
fn new() -> Self {
|
||||
let color_choice = match ARGS.color {
|
||||
clap::ColorChoice::Auto if std::io::stderr().is_terminal() => {
|
||||
ColorChoice::Auto
|
||||
}
|
||||
clap::ColorChoice::Always => ColorChoice::Always,
|
||||
_ => ColorChoice::Never,
|
||||
};
|
||||
|
||||
let stream = termcolor::StandardStream::stderr(color_choice);
|
||||
TermOutInner {
|
||||
active: AtomicBool::new(true),
|
||||
stream,
|
||||
in_alternate_screen: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A utility that allows users to write colored terminal output.
|
||||
/// If colors are not supported by the terminal, they are disabled.
|
||||
/// This type also allows for deletion of previously written lines.
|
||||
#[derive(Clone)]
|
||||
pub struct TermOut {
|
||||
inner: &'static TermOutInner,
|
||||
}
|
||||
|
||||
impl TermOut {
|
||||
/// Initialize a handler that listens for Ctrl-C signals.
|
||||
/// This is used to exit the alternate screen that might have been opened.
|
||||
pub fn init_exit_handler(&mut self) -> StrResult<()> {
|
||||
/// The duration the application may keep running after an exit signal was received.
|
||||
const MAX_TIME_TO_EXIT: Duration = Duration::from_millis(750);
|
||||
|
||||
// We can safely ignore the error as the only thing this handler would do
|
||||
// is leave an alternate screen if none was opened; not very important.
|
||||
let mut term_out = self.clone();
|
||||
ctrlc::set_handler(move || {
|
||||
term_out.inner.active.store(false, Ordering::Release);
|
||||
|
||||
// Wait for some time and if the application is still running, simply exit.
|
||||
// Not exiting immediately potentially allows destructors to run and file writes
|
||||
// to complete.
|
||||
std::thread::sleep(MAX_TIME_TO_EXIT);
|
||||
|
||||
// Leave alternate screen only after the timeout has expired.
|
||||
// This prevents console output intended only for within the alternate screen
|
||||
// from showing up outside it.
|
||||
// Remember that the alternate screen is also closed if the timeout is not reached,
|
||||
// just from a different location in code.
|
||||
let _ = term_out.leave_alternate_screen();
|
||||
|
||||
// Exit with the exit code standard for Ctrl-C exits[^1].
|
||||
// There doesn't seem to be another standard exit code for Windows,
|
||||
// so we just use the same one there.
|
||||
// [^1]: https://tldp.org/LDP/abs/html/exitcodes.html
|
||||
std::process::exit(128 + 2);
|
||||
})
|
||||
.map_err(|err| eco_format!("failed to initialize exit handler ({err})"))
|
||||
}
|
||||
|
||||
/// Whether this program is still active and was not stopped by the Ctrl-C handler.
|
||||
pub fn is_active(&self) -> bool {
|
||||
self.inner.active.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
/// Clears the entire screen.
|
||||
pub fn clear_screen(&mut self) -> io::Result<()> {
|
||||
// We don't want to clear anything that is not a TTY.
|
||||
if self.inner.stream.supports_color() {
|
||||
let mut stream = self.inner.stream.lock();
|
||||
// Clear the screen and then move the cursor to the top left corner.
|
||||
write!(stream, "\x1B[2J\x1B[1;1H")?;
|
||||
stream.flush()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Clears the previously written line.
|
||||
pub fn clear_last_line(&mut self) -> io::Result<()> {
|
||||
// We don't want to clear anything that is not a TTY.
|
||||
if self.inner.stream.supports_color() {
|
||||
// First, move the cursor up `lines` lines.
|
||||
// Then, clear everything between between the cursor to end of screen.
|
||||
let mut stream = self.inner.stream.lock();
|
||||
write!(stream, "\x1B[1F\x1B[0J")?;
|
||||
stream.flush()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Enters the alternate screen if none was opened already.
|
||||
pub fn enter_alternate_screen(&mut self) -> io::Result<()> {
|
||||
if !self.inner.in_alternate_screen.load(Ordering::Acquire) {
|
||||
let mut stream = self.inner.stream.lock();
|
||||
write!(stream, "\x1B[?1049h")?;
|
||||
stream.flush()?;
|
||||
self.inner.in_alternate_screen.store(true, Ordering::Release);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Leaves the alternate screen if it is already open.
|
||||
pub fn leave_alternate_screen(&mut self) -> io::Result<()> {
|
||||
if self.inner.in_alternate_screen.load(Ordering::Acquire) {
|
||||
let mut stream = self.inner.stream.lock();
|
||||
write!(stream, "\x1B[?1049l")?;
|
||||
stream.flush()?;
|
||||
self.inner.in_alternate_screen.store(false, Ordering::Release);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for TermOut {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.inner.stream.lock().write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.inner.stream.lock().flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteColor for TermOut {
|
||||
fn supports_color(&self) -> bool {
|
||||
self.inner.stream.supports_color()
|
||||
}
|
||||
|
||||
fn set_color(&mut self, spec: &termcolor::ColorSpec) -> io::Result<()> {
|
||||
self.inner.stream.lock().set_color(spec)
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> io::Result<()> {
|
||||
self.inner.stream.lock().reset()
|
||||
}
|
||||
}
|
@ -1,22 +1,28 @@
|
||||
use std::collections::HashMap;
|
||||
use std::io::{self, IsTerminal, Write};
|
||||
use std::io::{self, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use codespan_reporting::term::termcolor::WriteColor;
|
||||
use codespan_reporting::term::{self, termcolor};
|
||||
use ecow::eco_format;
|
||||
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
||||
use same_file::is_same_file;
|
||||
use termcolor::WriteColor;
|
||||
use typst::diag::StrResult;
|
||||
|
||||
use crate::args::CompileCommand;
|
||||
use crate::color_stream;
|
||||
use crate::compile::compile_once;
|
||||
use crate::terminal;
|
||||
use crate::timings::Timer;
|
||||
use crate::world::SystemWorld;
|
||||
|
||||
/// Execute a watching compilation command.
|
||||
pub fn watch(mut timer: Timer, mut command: CompileCommand) -> StrResult<()> {
|
||||
// Enter the alternate screen and handle Ctrl-C ourselves.
|
||||
terminal::out().init_exit_handler()?;
|
||||
terminal::out()
|
||||
.enter_alternate_screen()
|
||||
.map_err(|err| eco_format!("failed to enter alternate screen ({err})"))?;
|
||||
|
||||
// Create the world that serves sources, files, and fonts.
|
||||
let mut world = SystemWorld::new(&command.common)?;
|
||||
|
||||
@ -35,13 +41,9 @@ pub fn watch(mut timer: Timer, mut command: CompileCommand) -> StrResult<()> {
|
||||
// Handle events.
|
||||
let timeout = std::time::Duration::from_millis(100);
|
||||
let output = command.output();
|
||||
loop {
|
||||
while terminal::out().is_active() {
|
||||
let mut recompile = false;
|
||||
for event in rx
|
||||
.recv()
|
||||
.into_iter()
|
||||
.chain(std::iter::from_fn(|| rx.recv_timeout(timeout).ok()))
|
||||
{
|
||||
if let Ok(event) = rx.recv_timeout(timeout) {
|
||||
let event =
|
||||
event.map_err(|err| eco_format!("failed to watch directory ({err})"))?;
|
||||
|
||||
@ -77,6 +79,7 @@ pub fn watch(mut timer: Timer, mut command: CompileCommand) -> StrResult<()> {
|
||||
watch_dependencies(&mut world, &mut watcher, &mut watched)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adjust the file watching. Watches all new dependencies and unwatches
|
||||
@ -159,28 +162,24 @@ impl Status {
|
||||
let timestamp = chrono::offset::Local::now().format("%H:%M:%S");
|
||||
let color = self.color();
|
||||
|
||||
let mut w = color_stream();
|
||||
if std::io::stderr().is_terminal() {
|
||||
// Clear the terminal.
|
||||
let esc = 27 as char;
|
||||
write!(w, "{esc}[2J{esc}[1;1H")?;
|
||||
}
|
||||
let mut term_out = terminal::out();
|
||||
term_out.clear_screen()?;
|
||||
|
||||
w.set_color(&color)?;
|
||||
write!(w, "watching")?;
|
||||
w.reset()?;
|
||||
writeln!(w, " {}", command.common.input.display())?;
|
||||
term_out.set_color(&color)?;
|
||||
write!(term_out, "watching")?;
|
||||
term_out.reset()?;
|
||||
writeln!(term_out, " {}", command.common.input.display())?;
|
||||
|
||||
w.set_color(&color)?;
|
||||
write!(w, "writing to")?;
|
||||
w.reset()?;
|
||||
writeln!(w, " {}", output.display())?;
|
||||
term_out.set_color(&color)?;
|
||||
write!(term_out, "writing to")?;
|
||||
term_out.reset()?;
|
||||
writeln!(term_out, " {}", output.display())?;
|
||||
|
||||
writeln!(w)?;
|
||||
writeln!(w, "[{timestamp}] {}", self.message())?;
|
||||
writeln!(w)?;
|
||||
writeln!(term_out)?;
|
||||
writeln!(term_out, "[{timestamp}] {}", self.message())?;
|
||||
writeln!(term_out)?;
|
||||
|
||||
w.flush()
|
||||
term_out.flush()
|
||||
}
|
||||
|
||||
fn message(&self) -> String {
|
||||
|
Loading…
x
Reference in New Issue
Block a user