Skip to content

Commit b1898f6

Browse files
committed
Use queue instead of Stylize
1 parent d29e9e7 commit b1898f6

File tree

8 files changed

+189
-139
lines changed

8 files changed

+189
-139
lines changed

src/app_state.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ impl AppState {
338338
/// Mark the current exercise as done and move on to the next pending exercise if one exists.
339339
/// If all exercises are marked as done, run all of them to make sure that they are actually
340340
/// done. If an exercise which is marked as done fails, mark it as pending and continue on it.
341-
pub fn done_current_exercise(&mut self, writer: &mut StdoutLock) -> Result<ExercisesProgress> {
341+
pub fn done_current_exercise(&mut self, stdout: &mut StdoutLock) -> Result<ExercisesProgress> {
342342
let exercise = &mut self.exercises[self.current_exercise_ind];
343343
if !exercise.done {
344344
exercise.done = true;
@@ -350,7 +350,7 @@ impl AppState {
350350
return Ok(ExercisesProgress::NewPending);
351351
}
352352

353-
writer.write_all(RERUNNING_ALL_EXERCISES_MSG)?;
353+
stdout.write_all(RERUNNING_ALL_EXERCISES_MSG)?;
354354

355355
let n_exercises = self.exercises.len();
356356

@@ -368,12 +368,12 @@ impl AppState {
368368
.collect::<Vec<_>>();
369369

370370
for (exercise_ind, handle) in handles.into_iter().enumerate() {
371-
write!(writer, "\rProgress: {exercise_ind}/{n_exercises}")?;
372-
writer.flush()?;
371+
write!(stdout, "\rProgress: {exercise_ind}/{n_exercises}")?;
372+
stdout.flush()?;
373373

374374
let success = handle.join().unwrap()?;
375375
if !success {
376-
writer.write_all(b"\n\n")?;
376+
stdout.write_all(b"\n\n")?;
377377
return Ok(Some(exercise_ind));
378378
}
379379
}
@@ -395,13 +395,13 @@ impl AppState {
395395
// Write that the last exercise is done.
396396
self.write()?;
397397

398-
clear_terminal(writer)?;
399-
writer.write_all(FENISH_LINE.as_bytes())?;
398+
clear_terminal(stdout)?;
399+
stdout.write_all(FENISH_LINE.as_bytes())?;
400400

401401
let final_message = self.final_message.trim_ascii();
402402
if !final_message.is_empty() {
403-
writer.write_all(final_message.as_bytes())?;
404-
writer.write_all(b"\n")?;
403+
stdout.write_all(final_message.as_bytes())?;
404+
stdout.write_all(b"\n")?;
405405
}
406406

407407
Ok(ExercisesProgress::AllDone)

src/exercise.rs

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
11
use anyhow::Result;
2-
use crossterm::style::{style, StyledContent, Stylize};
3-
use std::{
4-
fmt::{self, Display, Formatter},
5-
io::Write,
2+
use crossterm::{
3+
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
4+
QueueableCommand,
65
};
6+
use std::io::{self, StdoutLock, Write};
77

8-
use crate::{cmd::CmdRunner, terminal_link::TerminalFileLink};
8+
use crate::{
9+
cmd::CmdRunner,
10+
term::{terminal_file_link, write_ansi},
11+
};
912

1013
/// The initial capacity of the output buffer.
1114
pub const OUTPUT_CAPACITY: usize = 1 << 14;
1215

16+
pub fn solution_link_line(stdout: &mut StdoutLock, solution_path: &str) -> io::Result<()> {
17+
stdout.queue(SetAttribute(Attribute::Bold))?;
18+
stdout.write_all(b"Solution")?;
19+
stdout.queue(ResetColor)?;
20+
stdout.write_all(b" for comparison: ")?;
21+
terminal_file_link(stdout, solution_path, Color::Cyan)?;
22+
stdout.write_all(b"\n")
23+
}
24+
1325
// Run an exercise binary and append its output to the `output` buffer.
1426
// Compilation must be done before calling this method.
1527
fn run_bin(
@@ -18,7 +30,9 @@ fn run_bin(
1830
cmd_runner: &CmdRunner,
1931
) -> Result<bool> {
2032
if let Some(output) = output.as_deref_mut() {
21-
writeln!(output, "{}", "Output".underlined())?;
33+
write_ansi(output, SetAttribute(Attribute::Underlined));
34+
output.extend_from_slice(b"Output\n");
35+
write_ansi(output, ResetColor);
2236
}
2337

2438
let success = cmd_runner.run_debug_bin(bin_name, output.as_deref_mut())?;
@@ -28,13 +42,10 @@ fn run_bin(
2842
// This output is important to show the user that something went wrong.
2943
// Otherwise, calling something like `exit(1)` in an exercise without further output
3044
// leaves the user confused about why the exercise isn't done yet.
31-
writeln!(
32-
output,
33-
"{}",
34-
"The exercise didn't run successfully (nonzero exit code)"
35-
.bold()
36-
.red(),
37-
)?;
45+
write_ansi(output, SetAttribute(Attribute::Bold));
46+
write_ansi(output, SetForegroundColor(Color::Red));
47+
output.extend_from_slice(b"The exercise didn't run successfully (nonzero exit code)\n");
48+
write_ansi(output, ResetColor);
3849
}
3950
}
4051

@@ -53,18 +64,6 @@ pub struct Exercise {
5364
pub done: bool,
5465
}
5566

56-
impl Exercise {
57-
pub fn terminal_link(&self) -> StyledContent<TerminalFileLink<'_>> {
58-
style(TerminalFileLink(self.path)).underlined().blue()
59-
}
60-
}
61-
62-
impl Display for Exercise {
63-
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
64-
self.path.fmt(f)
65-
}
66-
}
67-
6867
pub trait RunnableExercise {
6968
fn name(&self) -> &str;
7069
fn strict_clippy(&self) -> bool;

src/init.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use anyhow::{bail, Context, Result};
2-
use crossterm::style::Stylize;
2+
use crossterm::{
3+
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
4+
QueueableCommand,
5+
};
36
use serde::Deserialize;
47
use std::{
58
env::set_current_dir,
@@ -144,12 +147,13 @@ pub fn init() -> Result<()> {
144147
.status();
145148
}
146149

147-
writeln!(
148-
stdout,
149-
"{}\n\n{}",
150-
"Initialization done ✓".green(),
151-
POST_INIT_MSG.bold(),
152-
)?;
150+
stdout.queue(SetForegroundColor(Color::Green))?;
151+
stdout.write_all("Initialization done ✓\n\n".as_bytes())?;
152+
stdout
153+
.queue(ResetColor)?
154+
.queue(SetAttribute(Attribute::Bold))?;
155+
stdout.write_all(POST_INIT_MSG)?;
156+
stdout.queue(ResetColor)?;
153157

154158
Ok(())
155159
}
@@ -182,5 +186,6 @@ You probably already initialized Rustlings.
182186
Run `cd rustlings`
183187
Then run `rustlings` again";
184188

185-
const POST_INIT_MSG: &str = "Run `cd rustlings` to go into the generated directory.
186-
Then run `rustlings` to get started.";
189+
const POST_INIT_MSG: &[u8] = b"Run `cd rustlings` to go into the generated directory.
190+
Then run `rustlings` to get started.
191+
";

src/main.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ mod init;
2222
mod list;
2323
mod run;
2424
mod term;
25-
mod terminal_link;
2625
mod watch;
2726

2827
const CURRENT_FORMAT_VERSION: u8 = 1;

src/run.rs

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1-
use anyhow::{bail, Result};
2-
use crossterm::style::{style, Stylize};
3-
use std::io::{self, Write};
1+
use anyhow::Result;
2+
use crossterm::{
3+
style::{Color, ResetColor, SetForegroundColor},
4+
QueueableCommand,
5+
};
6+
use std::{
7+
io::{self, Write},
8+
process::exit,
9+
};
410

511
use crate::{
612
app_state::{AppState, ExercisesProgress},
7-
exercise::{RunnableExercise, OUTPUT_CAPACITY},
8-
terminal_link::TerminalFileLink,
13+
exercise::{solution_link_line, RunnableExercise, OUTPUT_CAPACITY},
14+
term::terminal_file_link,
915
};
1016

1117
pub fn run(app_state: &mut AppState) -> Result<()> {
@@ -19,35 +25,31 @@ pub fn run(app_state: &mut AppState) -> Result<()> {
1925
if !success {
2026
app_state.set_pending(app_state.current_exercise_ind())?;
2127

22-
bail!(
23-
"Ran {} with errors",
24-
app_state.current_exercise().terminal_link(),
25-
);
28+
stdout.write_all(b"Ran ")?;
29+
terminal_file_link(&mut stdout, app_state.current_exercise().path, Color::Blue)?;
30+
stdout.write_all(b" with errors\n")?;
31+
exit(1);
2632
}
2733

28-
writeln!(
29-
stdout,
30-
"{}{}",
31-
"✓ Successfully ran ".green(),
32-
exercise.path.green(),
33-
)?;
34+
stdout.queue(SetForegroundColor(Color::Green))?;
35+
stdout.write_all("✓ Successfully ran ".as_bytes())?;
36+
stdout.write_all(exercise.path.as_bytes())?;
37+
stdout.queue(ResetColor)?;
38+
stdout.write_all(b"\n")?;
3439

3540
if let Some(solution_path) = app_state.current_solution_path()? {
36-
writeln!(
37-
stdout,
38-
"\n{} for comparison: {}\n",
39-
"Solution".bold(),
40-
style(TerminalFileLink(&solution_path)).underlined().cyan(),
41-
)?;
41+
stdout.write_all(b"\n")?;
42+
solution_link_line(&mut stdout, &solution_path)?;
43+
stdout.write_all(b"\n")?;
4244
}
4345

4446
match app_state.done_current_exercise(&mut stdout)? {
47+
ExercisesProgress::CurrentPending | ExercisesProgress::NewPending => {
48+
stdout.write_all(b"Next exercise: ")?;
49+
terminal_file_link(&mut stdout, app_state.current_exercise().path, Color::Blue)?;
50+
stdout.write_all(b"\n")?;
51+
}
4552
ExercisesProgress::AllDone => (),
46-
ExercisesProgress::CurrentPending | ExercisesProgress::NewPending => writeln!(
47-
stdout,
48-
"Next exercise: {}",
49-
app_state.current_exercise().terminal_link(),
50-
)?,
5153
}
5254

5355
Ok(())

src/term.rs

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
use std::io::{self, BufRead, StdoutLock, Write};
1+
use std::{
2+
fmt, fs,
3+
io::{self, BufRead, StdoutLock, Write},
4+
};
25

36
use crossterm::{
47
cursor::MoveTo,
5-
style::{Color, ResetColor, SetForegroundColor},
8+
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
69
terminal::{Clear, ClearType},
7-
QueueableCommand,
10+
Command, QueueableCommand,
811
};
912

1013
/// Terminal progress bar to be used when not using Ratataui.
@@ -68,3 +71,43 @@ pub fn press_enter_prompt(stdout: &mut StdoutLock) -> io::Result<()> {
6871
stdout.write_all(b"\n")?;
6972
Ok(())
7073
}
74+
75+
pub fn terminal_file_link(stdout: &mut StdoutLock, path: &str, color: Color) -> io::Result<()> {
76+
let canonical_path = fs::canonicalize(path).ok();
77+
78+
let Some(canonical_path) = canonical_path.as_deref().and_then(|p| p.to_str()) else {
79+
return stdout.write_all(path.as_bytes());
80+
};
81+
82+
// Windows itself can't handle its verbatim paths.
83+
#[cfg(windows)]
84+
let canonical_path = if canonical_path.len() > 5 && &canonical_path[0..4] == r"\\?\" {
85+
&canonical_path[4..]
86+
} else {
87+
canonical_path
88+
};
89+
90+
stdout
91+
.queue(SetForegroundColor(color))?
92+
.queue(SetAttribute(Attribute::Underlined))?;
93+
write!(
94+
stdout,
95+
"\x1b]8;;file://{canonical_path}\x1b\\{path}\x1b]8;;\x1b\\",
96+
)?;
97+
stdout.queue(ResetColor)?;
98+
99+
Ok(())
100+
}
101+
102+
pub fn write_ansi(output: &mut Vec<u8>, command: impl Command) {
103+
struct FmtWriter<'a>(&'a mut Vec<u8>);
104+
105+
impl fmt::Write for FmtWriter<'_> {
106+
fn write_str(&mut self, s: &str) -> fmt::Result {
107+
self.0.extend_from_slice(s.as_bytes());
108+
Ok(())
109+
}
110+
}
111+
112+
let _ = command.write_ansi(&mut FmtWriter(output));
113+
}

src/terminal_link.rs

Lines changed: 0 additions & 26 deletions
This file was deleted.

0 commit comments

Comments
 (0)