mirror of
https://github.com/rust-lang/rustlings.git
synced 2025-04-27 12:22:47 +02:00
Upgrade to edition 2024
This commit is contained in:
parent
298be671b9
commit
d9872f2615
@ -1,3 +1,10 @@
|
|||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Upgrade to Rust edition 2024
|
||||||
|
- Raise the minimum supported Rust version to `1.85`
|
||||||
|
|
||||||
<a name="6.4.0"></a>
|
<a name="6.4.0"></a>
|
||||||
|
|
||||||
## 6.4.0 (2024-11-11)
|
## 6.4.0 (2024-11-11)
|
||||||
|
@ -15,8 +15,8 @@ authors = [
|
|||||||
]
|
]
|
||||||
repository = "https://github.com/rust-lang/rustlings"
|
repository = "https://github.com/rust-lang/rustlings"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
edition = "2021" # On Update: Update the edition of the `rustfmt` command that checks the solutions.
|
edition = "2024" # On Update: Update the edition of the `rustfmt` command that checks the solutions.
|
||||||
rust-version = "1.80"
|
rust-version = "1.85"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
serde = { version = "1.0.217", features = ["derive"] }
|
serde = { version = "1.0.217", features = ["derive"] }
|
||||||
|
@ -192,7 +192,7 @@ bin = [
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "exercises"
|
name = "exercises"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
# Don't publish the exercises on crates.io!
|
# Don't publish the exercises on crates.io!
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use anyhow::{bail, Context, Error, Result};
|
use anyhow::{Context, Error, Result, bail};
|
||||||
use crossterm::{cursor, terminal, QueueableCommand};
|
use crossterm::{QueueableCommand, cursor, terminal};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
env,
|
env,
|
||||||
fs::{File, OpenOptions},
|
fs::{File, OpenOptions},
|
||||||
io::{Read, Seek, StdoutLock, Write},
|
io::{Read, Seek, StdoutLock, Write},
|
||||||
path::{Path, MAIN_SEPARATOR_STR},
|
path::{MAIN_SEPARATOR_STR, Path},
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicUsize, Ordering::Relaxed},
|
atomic::{AtomicUsize, Ordering::Relaxed},
|
||||||
@ -427,7 +427,8 @@ impl AppState {
|
|||||||
let next_exercise_ind = &next_exercise_ind;
|
let next_exercise_ind = &next_exercise_ind;
|
||||||
let slf = &self;
|
let slf = &self;
|
||||||
thread::Builder::new()
|
thread::Builder::new()
|
||||||
.spawn_scoped(s, move || loop {
|
.spawn_scoped(s, move || {
|
||||||
|
loop {
|
||||||
let exercise_ind = next_exercise_ind.fetch_add(1, Relaxed);
|
let exercise_ind = next_exercise_ind.fetch_add(1, Relaxed);
|
||||||
let Some(exercise) = slf.exercises.get(exercise_ind) else {
|
let Some(exercise) = slf.exercises.get(exercise_ind) else {
|
||||||
// No more exercises.
|
// No more exercises.
|
||||||
@ -454,6 +455,7 @@ impl AppState {
|
|||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.context("Failed to spawn a thread to check all exercises")?;
|
.context("Failed to spawn a thread to check all exercises")?;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{Context, Result, bail};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{
|
use std::{
|
||||||
io::Read,
|
io::Read,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{Context, Result, bail};
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use anyhow::{anyhow, bail, Context, Error, Result};
|
use anyhow::{Context, Error, Result, anyhow, bail};
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
fs::{self, read_dir, OpenOptions},
|
fs::{self, OpenOptions, read_dir},
|
||||||
io::{self, Read, Write},
|
io::{self, Read, Write},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
@ -10,11 +10,11 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cargo_toml::{append_bins, bins_start_end_ind, BINS_BUFFER_CAPACITY},
|
|
||||||
cmd::CmdRunner,
|
|
||||||
exercise::{RunnableExercise, OUTPUT_CAPACITY},
|
|
||||||
info_file::{ExerciseInfo, InfoFile},
|
|
||||||
CURRENT_FORMAT_VERSION,
|
CURRENT_FORMAT_VERSION,
|
||||||
|
cargo_toml::{BINS_BUFFER_CAPACITY, append_bins, bins_start_end_ind},
|
||||||
|
cmd::CmdRunner,
|
||||||
|
exercise::{OUTPUT_CAPACITY, RunnableExercise},
|
||||||
|
info_file::{ExerciseInfo, InfoFile},
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_N_EXERCISES: usize = 999;
|
const MAX_N_EXERCISES: usize = 999;
|
||||||
@ -42,10 +42,14 @@ fn check_cargo_toml(
|
|||||||
|
|
||||||
if old_bins != new_bins {
|
if old_bins != new_bins {
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
bail!("The file `dev/Cargo.toml` is outdated. Run `cargo run -- dev update` to update it. Then run `cargo run -- dev check` again");
|
bail!(
|
||||||
|
"The file `dev/Cargo.toml` is outdated. Run `cargo run -- dev update` to update it. Then run `cargo run -- dev check` again"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bail!("The file `Cargo.toml` is outdated. Run `rustlings dev update` to update it. Then run `rustlings dev check` again");
|
bail!(
|
||||||
|
"The file `Cargo.toml` is outdated. Run `rustlings dev update` to update it. Then run `rustlings dev check` again"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -63,7 +67,9 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
|
|||||||
bail!("Found an empty exercise name in `info.toml`");
|
bail!("Found an empty exercise name in `info.toml`");
|
||||||
}
|
}
|
||||||
if name.len() > MAX_EXERCISE_NAME_LEN {
|
if name.len() > MAX_EXERCISE_NAME_LEN {
|
||||||
bail!("The length of the exercise name `{name}` is bigger than the maximum {MAX_EXERCISE_NAME_LEN}");
|
bail!(
|
||||||
|
"The length of the exercise name `{name}` is bigger than the maximum {MAX_EXERCISE_NAME_LEN}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if let Some(c) = forbidden_char(name) {
|
if let Some(c) = forbidden_char(name) {
|
||||||
bail!("Char `{c}` in the exercise name `{name}` is not allowed");
|
bail!("Char `{c}` in the exercise name `{name}` is not allowed");
|
||||||
@ -79,7 +85,9 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if exercise_info.hint.trim_ascii().is_empty() {
|
if exercise_info.hint.trim_ascii().is_empty() {
|
||||||
bail!("The exercise `{name}` has an empty hint. Please provide a hint or at least tell the user why a hint isn't needed for this exercise");
|
bail!(
|
||||||
|
"The exercise `{name}` has an empty hint. Please provide a hint or at least tell the user why a hint isn't needed for this exercise"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !names.insert(name) {
|
if !names.insert(name) {
|
||||||
@ -96,20 +104,28 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
|
|||||||
.with_context(|| format!("Failed to read the file {path}"))?;
|
.with_context(|| format!("Failed to read the file {path}"))?;
|
||||||
|
|
||||||
if !file_buf.contains("fn main()") {
|
if !file_buf.contains("fn main()") {
|
||||||
bail!("The `main` function is missing in the file `{path}`.\nCreate at least an empty `main` function to avoid language server errors");
|
bail!(
|
||||||
|
"The `main` function is missing in the file `{path}`.\nCreate at least an empty `main` function to avoid language server errors"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !file_buf.contains("// TODO") {
|
if !file_buf.contains("// TODO") {
|
||||||
bail!("Didn't find any `// TODO` comment in the file `{path}`.\nYou need to have at least one such comment to guide the user.");
|
bail!(
|
||||||
|
"Didn't find any `// TODO` comment in the file `{path}`.\nYou need to have at least one such comment to guide the user."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let contains_tests = file_buf.contains("#[test]\n");
|
let contains_tests = file_buf.contains("#[test]\n");
|
||||||
if exercise_info.test {
|
if exercise_info.test {
|
||||||
if !contains_tests {
|
if !contains_tests {
|
||||||
bail!("The file `{path}` doesn't contain any tests. If you don't want to add tests to this exercise, set `test = false` for this exercise in the `info.toml` file");
|
bail!(
|
||||||
|
"The file `{path}` doesn't contain any tests. If you don't want to add tests to this exercise, set `test = false` for this exercise in the `info.toml` file"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if contains_tests {
|
} else if contains_tests {
|
||||||
bail!("The file `{path}` contains tests annotated with `#[test]` but the exercise `{name}` has `test = false` in the `info.toml` file");
|
bail!(
|
||||||
|
"The file `{path}` contains tests annotated with `#[test]` but the exercise `{name}` has `test = false` in the `info.toml` file"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
file_buf.clear();
|
file_buf.clear();
|
||||||
@ -125,7 +141,10 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
|
|||||||
// Only one level of directory nesting is allowed.
|
// Only one level of directory nesting is allowed.
|
||||||
fn check_unexpected_files(dir: &str, allowed_rust_files: &HashSet<PathBuf>) -> Result<()> {
|
fn check_unexpected_files(dir: &str, allowed_rust_files: &HashSet<PathBuf>) -> Result<()> {
|
||||||
let unexpected_file = |path: &Path| {
|
let unexpected_file = |path: &Path| {
|
||||||
anyhow!("Found the file `{}`. Only `README.md` and Rust files related to an exercise in `info.toml` are allowed in the `{dir}` directory", path.display())
|
anyhow!(
|
||||||
|
"Found the file `{}`. Only `README.md` and Rust files related to an exercise in `info.toml` are allowed in the `{dir}` directory",
|
||||||
|
path.display()
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
for entry in read_dir(dir).with_context(|| format!("Failed to open the `{dir}` directory"))? {
|
for entry in read_dir(dir).with_context(|| format!("Failed to open the `{dir}` directory"))? {
|
||||||
@ -154,7 +173,10 @@ fn check_unexpected_files(dir: &str, allowed_rust_files: &HashSet<PathBuf>) -> R
|
|||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
|
|
||||||
if !entry.file_type().unwrap().is_file() {
|
if !entry.file_type().unwrap().is_file() {
|
||||||
bail!("Found `{}` but expected only files. Only one level of exercise nesting is allowed", path.display());
|
bail!(
|
||||||
|
"Found `{}` but expected only files. Only one level of exercise nesting is allowed",
|
||||||
|
path.display()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let file_name = path.file_name().unwrap();
|
let file_name = path.file_name().unwrap();
|
||||||
@ -224,8 +246,12 @@ fn check_exercises_unsolved(
|
|||||||
|
|
||||||
fn check_exercises(info_file: &'static InfoFile, cmd_runner: &'static CmdRunner) -> Result<()> {
|
fn check_exercises(info_file: &'static InfoFile, cmd_runner: &'static CmdRunner) -> Result<()> {
|
||||||
match info_file.format_version.cmp(&CURRENT_FORMAT_VERSION) {
|
match info_file.format_version.cmp(&CURRENT_FORMAT_VERSION) {
|
||||||
Ordering::Less => bail!("`format_version` < {CURRENT_FORMAT_VERSION} (supported version)\nPlease migrate to the latest format version"),
|
Ordering::Less => bail!(
|
||||||
Ordering::Greater => bail!("`format_version` > {CURRENT_FORMAT_VERSION} (supported version)\nTry updating the Rustlings program"),
|
"`format_version` < {CURRENT_FORMAT_VERSION} (supported version)\nPlease migrate to the latest format version"
|
||||||
|
),
|
||||||
|
Ordering::Greater => bail!(
|
||||||
|
"`format_version` > {CURRENT_FORMAT_VERSION} (supported version)\nTry updating the Rustlings program"
|
||||||
|
),
|
||||||
Ordering::Equal => (),
|
Ordering::Equal => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{Context, Result, bail};
|
||||||
use std::{
|
use std::{
|
||||||
env::set_current_dir,
|
env::set_current_dir,
|
||||||
fs::{self, create_dir},
|
fs::{self, create_dir},
|
||||||
@ -6,7 +6,7 @@ use std::{
|
|||||||
process::Command,
|
process::Command,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{init::RUST_ANALYZER_TOML, CURRENT_FORMAT_VERSION};
|
use crate::{CURRENT_FORMAT_VERSION, init::RUST_ANALYZER_TOML};
|
||||||
|
|
||||||
// Create a directory relative to the current directory and print its path.
|
// Create a directory relative to the current directory and print its path.
|
||||||
fn create_rel_dir(dir_name: &str, current_dir: &str) -> Result<()> {
|
fn create_rel_dir(dir_name: &str, current_dir: &str) -> Result<()> {
|
||||||
@ -55,7 +55,9 @@ pub fn new(path: &Path, no_git: bool) -> Result<()> {
|
|||||||
write_rel_file(
|
write_rel_file(
|
||||||
"info.toml",
|
"info.toml",
|
||||||
&dir_path_str,
|
&dir_path_str,
|
||||||
format!("{INFO_FILE_BEFORE_FORMAT_VERSION}{CURRENT_FORMAT_VERSION}{INFO_FILE_AFTER_FORMAT_VERSION}"),
|
format!(
|
||||||
|
"{INFO_FILE_BEFORE_FORMAT_VERSION}{CURRENT_FORMAT_VERSION}{INFO_FILE_AFTER_FORMAT_VERSION}"
|
||||||
|
),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
write_rel_file("Cargo.toml", &dir_path_str, CARGO_TOML)?;
|
write_rel_file("Cargo.toml", &dir_path_str, CARGO_TOML)?;
|
||||||
@ -130,7 +132,7 @@ bin = []
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "exercises"
|
name = "exercises"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
# Don't publish the exercises on crates.io!
|
# Don't publish the exercises on crates.io!
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
|
|
||||||
QueueableCommand,
|
QueueableCommand,
|
||||||
|
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
|
||||||
};
|
};
|
||||||
use std::io::{self, StdoutLock, Write};
|
use std::io::{self, StdoutLock, Write};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cmd::CmdRunner,
|
cmd::CmdRunner,
|
||||||
term::{self, terminal_file_link, write_ansi, CountedWrite},
|
term::{self, CountedWrite, terminal_file_link, write_ansi},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The initial capacity of the output buffer.
|
/// The initial capacity of the output buffer.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use anyhow::{bail, Context, Error, Result};
|
use anyhow::{Context, Error, Result, bail};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{fs, io::ErrorKind};
|
use std::{fs, io::ErrorKind};
|
||||||
|
|
||||||
|
12
src/init.rs
12
src/init.rs
@ -1,7 +1,7 @@
|
|||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{Context, Result, bail};
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
|
|
||||||
QueueableCommand,
|
QueueableCommand,
|
||||||
|
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{
|
use std::{
|
||||||
@ -57,7 +57,9 @@ pub fn init() -> Result<()> {
|
|||||||
if !workspace_manifest_content.contains("[workspace]\n")
|
if !workspace_manifest_content.contains("[workspace]\n")
|
||||||
&& !workspace_manifest_content.contains("workspace.")
|
&& !workspace_manifest_content.contains("workspace.")
|
||||||
{
|
{
|
||||||
bail!("The current directory is already part of a Cargo project.\nPlease initialize Rustlings in a different directory");
|
bail!(
|
||||||
|
"The current directory is already part of a Cargo project.\nPlease initialize Rustlings in a different directory"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout.write_all(b"This command will create the directory `rustlings/` as a member of this Cargo workspace.\nPress ENTER to continue ")?;
|
stdout.write_all(b"This command will create the directory `rustlings/` as a member of this Cargo workspace.\nPress ENTER to continue ")?;
|
||||||
@ -75,7 +77,9 @@ pub fn init() -> Result<()> {
|
|||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
.status()?;
|
.status()?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
bail!("Failed to initialize a new Cargo workspace member.\nPlease initialize Rustlings in a different directory");
|
bail!(
|
||||||
|
"Failed to initialize a new Cargo workspace member.\nPlease initialize Rustlings in a different directory"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout.write_all(b"The directory `rustlings` has been added to `workspace.members` in the `Cargo.toml` file of this Cargo workspace.\n")?;
|
stdout.write_all(b"The directory `rustlings` has been added to `workspace.members` in the `Cargo.toml` file of this Cargo workspace.\n")?;
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
cursor,
|
QueueableCommand, cursor,
|
||||||
event::{
|
event::{
|
||||||
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, MouseEventKind,
|
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, MouseEventKind,
|
||||||
},
|
},
|
||||||
terminal::{
|
terminal::{
|
||||||
disable_raw_mode, enable_raw_mode, DisableLineWrap, EnableLineWrap, EnterAlternateScreen,
|
DisableLineWrap, EnableLineWrap, EnterAlternateScreen, LeaveAlternateScreen,
|
||||||
LeaveAlternateScreen,
|
disable_raw_mode, enable_raw_mode,
|
||||||
},
|
},
|
||||||
QueueableCommand,
|
|
||||||
};
|
};
|
||||||
use std::io::{self, StdoutLock, Write};
|
use std::io::{self, StdoutLock, Write};
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
|
QueueableCommand,
|
||||||
cursor::{MoveTo, MoveToNextLine},
|
cursor::{MoveTo, MoveToNextLine},
|
||||||
style::{
|
style::{
|
||||||
Attribute, Attributes, Color, ResetColor, SetAttribute, SetAttributes, SetForegroundColor,
|
Attribute, Attributes, Color, ResetColor, SetAttribute, SetAttributes, SetForegroundColor,
|
||||||
},
|
},
|
||||||
terminal::{self, BeginSynchronizedUpdate, Clear, ClearType, EndSynchronizedUpdate},
|
terminal::{self, BeginSynchronizedUpdate, Clear, ClearType, EndSynchronizedUpdate},
|
||||||
QueueableCommand,
|
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Write as _,
|
fmt::Write as _,
|
||||||
@ -15,7 +15,7 @@ use std::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
app_state::AppState,
|
app_state::AppState,
|
||||||
exercise::Exercise,
|
exercise::Exercise,
|
||||||
term::{progress_bar, CountedWrite, MaxLenWriter},
|
term::{CountedWrite, MaxLenWriter, progress_bar},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::scroll_state::ScrollState;
|
use super::scroll_state::ScrollState;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{Context, Result, bail};
|
||||||
use app_state::StateFileStatus;
|
use app_state::StateFileStatus;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
style::{Color, ResetColor, SetForegroundColor},
|
|
||||||
QueueableCommand,
|
QueueableCommand,
|
||||||
|
style::{Color, ResetColor, SetForegroundColor},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
io::{self, Write},
|
io::{self, Write},
|
||||||
@ -10,7 +10,7 @@ use std::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app_state::{AppState, ExercisesProgress},
|
app_state::{AppState, ExercisesProgress},
|
||||||
exercise::{solution_link_line, RunnableExercise, OUTPUT_CAPACITY},
|
exercise::{OUTPUT_CAPACITY, RunnableExercise, solution_link_line},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn run(app_state: &mut AppState) -> Result<ExitCode> {
|
pub fn run(app_state: &mut AppState) -> Result<ExitCode> {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use crossterm::{
|
use crossterm::{
|
||||||
|
Command, QueueableCommand,
|
||||||
cursor::MoveTo,
|
cursor::MoveTo,
|
||||||
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
|
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
|
||||||
terminal::{Clear, ClearType},
|
terminal::{Clear, ClearType},
|
||||||
Command, QueueableCommand,
|
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
fmt, fs,
|
fmt, fs,
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use notify::{
|
use notify::{
|
||||||
event::{AccessKind, AccessMode, MetadataKind, ModifyKind, RenameMode},
|
|
||||||
Event, EventKind,
|
Event, EventKind,
|
||||||
|
event::{AccessKind, AccessMode, MetadataKind, ModifyKind, RenameMode},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
sync::{
|
sync::{
|
||||||
atomic::Ordering::Relaxed,
|
atomic::Ordering::Relaxed,
|
||||||
mpsc::{sync_channel, RecvTimeoutError, Sender, SyncSender},
|
mpsc::{RecvTimeoutError, Sender, SyncSender, sync_channel},
|
||||||
},
|
},
|
||||||
thread,
|
thread,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{WatchEvent, EXERCISE_RUNNING};
|
use super::{EXERCISE_RUNNING, WatchEvent};
|
||||||
|
|
||||||
const DEBOUNCE_DURATION: Duration = Duration::from_millis(200);
|
const DEBOUNCE_DURATION: Duration = Duration::from_millis(200);
|
||||||
|
|
||||||
|
@ -1,24 +1,25 @@
|
|||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
|
QueueableCommand,
|
||||||
style::{
|
style::{
|
||||||
Attribute, Attributes, Color, ResetColor, SetAttribute, SetAttributes, SetForegroundColor,
|
Attribute, Attributes, Color, ResetColor, SetAttribute, SetAttributes, SetForegroundColor,
|
||||||
},
|
},
|
||||||
terminal, QueueableCommand,
|
terminal,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
io::{self, Read, StdoutLock, Write},
|
io::{self, Read, StdoutLock, Write},
|
||||||
sync::mpsc::{sync_channel, Sender, SyncSender},
|
sync::mpsc::{Sender, SyncSender, sync_channel},
|
||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app_state::{AppState, ExercisesProgress},
|
app_state::{AppState, ExercisesProgress},
|
||||||
clear_terminal,
|
clear_terminal,
|
||||||
exercise::{solution_link_line, RunnableExercise, OUTPUT_CAPACITY},
|
exercise::{OUTPUT_CAPACITY, RunnableExercise, solution_link_line},
|
||||||
term::progress_bar,
|
term::progress_bar,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{terminal_event::terminal_event_handler, InputPauseGuard, WatchEvent};
|
use super::{InputPauseGuard, WatchEvent, terminal_event::terminal_event_handler};
|
||||||
|
|
||||||
const HEADING_ATTRIBUTES: Attributes = Attributes::none()
|
const HEADING_ATTRIBUTES: Attributes = Attributes::none()
|
||||||
.with(Attribute::Bold)
|
.with(Attribute::Bold)
|
||||||
|
@ -4,7 +4,7 @@ use std::sync::{
|
|||||||
mpsc::{Receiver, Sender},
|
mpsc::{Receiver, Sender},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{WatchEvent, EXERCISE_RUNNING};
|
use super::{EXERCISE_RUNNING, WatchEvent};
|
||||||
|
|
||||||
pub enum InputEvent {
|
pub enum InputEvent {
|
||||||
Next,
|
Next,
|
||||||
|
@ -7,5 +7,5 @@ bin = [
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "test_exercises"
|
name = "test_exercises"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
publish = false
|
publish = false
|
||||||
|
Loading…
x
Reference in New Issue
Block a user