diff --git a/.all-contributorsrc b/.all-contributorsrc
index 00566c7e..a5975dc6 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -1227,6 +1227,15 @@
"contributions": [
"content"
]
+ },
+ {
+ "login": "jackos",
+ "name": "Jack Clayton",
+ "avatar_url": "https://avatars.githubusercontent.com/u/77730378?v=4",
+ "profile": "http://rustnote.com",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 8,
diff --git a/.gitignore b/.gitignore
index 253f8f33..253d85bd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@ target/
*.pdb
exercises/clippy/Cargo.toml
exercises/clippy/Cargo.lock
+rust-project.json
.idea
.vscode
*.iml
diff --git a/AUTHORS.md b/AUTHORS.md
index a4b6bdf2..4dd7f7b5 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -175,6 +175,7 @@ authors.
 pwygab 💻 |
 Lucas Grigolon Varela 🖋 |
 Bufo 🖋 |
+  Jack Clayton 💻 |
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1fc12506..095877b6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,24 @@
+
+## 4.8.0 (2022-07-01)
+
+#### Features
+
+- Added a progress indicator for `rustlings watch`.
+- The installation script now checks for Rustup being installed.
+- Added a `rustlings lsp` command to enable `rust-analyzer`.
+
+#### Bug Fixes
+
+- **move_semantics5**: Replaced "in vogue" with "in scope" in hint.
+- **if2**: Fixed a typo in the hint.
+- **variables1**: Fixed an incorrect line reference in the hint.
+- Fixed an out of bounds check in the installation Bash script.
+
+#### Housekeeping
+
+- Replaced the git.io URL with the fully qualified URL because of git.io's sunsetting.
+- Removed the deprecated Rust GitPod extension.
+
## 4.7.1 (2022-04-20)
diff --git a/Cargo.lock b/Cargo.lock
index 27521e7c..6737d29c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -186,6 +186,15 @@ dependencies = [
"unicode-segmentation",
]
+[[package]]
+name = "home"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654"
+dependencies = [
+ "winapi 0.3.9",
+]
+
[[package]]
name = "indicatif"
version = "0.16.2"
@@ -229,9 +238,9 @@ dependencies = [
[[package]]
name = "itoa"
-version = "0.4.8"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
+checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
[[package]]
name = "kernel32-sys"
@@ -433,9 +442,9 @@ dependencies = [
[[package]]
name = "regex"
-version = "1.5.4"
+version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
+checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
dependencies = [
"aho-corasick",
"memchr",
@@ -456,11 +465,13 @@ dependencies = [
"assert_cmd",
"console",
"glob",
+ "home",
"indicatif",
"notify",
"predicates",
"regex",
"serde",
+ "serde_json",
"toml",
]
@@ -501,9 +512,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.66"
+version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127"
+checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
dependencies = [
"itoa",
"ryu",
diff --git a/Cargo.toml b/Cargo.toml
index befdd6e8..f7a1ffb8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "rustlings"
-version = "4.7.1"
+version = "4.8.0"
authors = ["mokou ", "Carol (Nichols || Goulding) "]
edition = "2021"
@@ -12,6 +12,9 @@ notify = "4.0"
toml = "0.5"
regex = "1.5"
serde= { version = "1.0", features = ["derive"] }
+serde_json = "1.0.81"
+home = "0.5.3"
+glob = "0.3.0"
[[bin]]
name = "rustlings"
diff --git a/README.md b/README.md
index dd96a59c..12a77810 100644
--- a/README.md
+++ b/README.md
@@ -126,24 +126,7 @@ After every couple of sections, there will be a quiz that'll test your knowledge
## Enabling `rust-analyzer`
-`rust-analyzer` support is provided, but it depends on your editor
-whether it's enabled by default. (RLS support is not provided)
-
-To enable `rust-analyzer`, you'll need to make Cargo build the project
-with the `exercises` feature, which will automatically include the `exercises/`
-subfolder in the project. The easiest way to do this is to tell your editor to
-build the project with all features (the equivalent of `cargo build --all-features`).
-For specific editor instructions:
-
-- **VSCode**: Add a `.vscode/settings.json` file with the following:
-```json
-{
- "rust-analyzer.cargo.features": ["exercises"]
-}
-```
-- **IntelliJ-based Editors**: Using the Rust plugin, everything should work
- by default.
-- _Missing your editor? Feel free to contribute more instructions!_
+Run the command `rustlings lsp` which will generate a `rust-project.json` at the root of the project, this allows [rust-analyzer](https://rust-analyzer.github.io/) to parse each exercise.
## Continuing On
diff --git a/src/main.rs b/src/main.rs
index af3dffbc..15c3095e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,5 @@
use crate::exercise::{Exercise, ExerciseList};
+use crate::project::RustAnalyzerProject;
use crate::run::run;
use crate::verify::verify;
use argh::FromArgs;
@@ -20,11 +21,12 @@ use std::time::Duration;
mod ui;
mod exercise;
+mod project;
mod run;
mod verify;
// In sync with crate version
-const VERSION: &str = "4.7.1";
+const VERSION: &str = "4.8.0";
#[derive(FromArgs, PartialEq, Debug)]
/// Rustlings is a collection of small exercises to get you used to writing and reading Rust code
@@ -47,6 +49,7 @@ enum Subcommands {
Run(RunArgs),
Hint(HintArgs),
List(ListArgs),
+ Lsp(LspArgs),
}
#[derive(FromArgs, PartialEq, Debug)]
@@ -77,6 +80,12 @@ struct HintArgs {
name: String,
}
+#[derive(FromArgs, PartialEq, Debug)]
+#[argh(subcommand, name = "lsp")]
+/// Enable rust-analyzer for exercises
+struct LspArgs {}
+
+
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand, name = "list")]
/// Lists the exercises available in Rustlings
@@ -206,6 +215,25 @@ fn main() {
verify(&exercises, (0, exercises.len()), verbose).unwrap_or_else(|_| std::process::exit(1));
}
+ Subcommands::Lsp(_subargs) => {
+ let mut project = RustAnalyzerProject::new();
+ project
+ .get_sysroot_src()
+ .expect("Couldn't find toolchain path, do you have `rustc` installed?");
+ project
+ .exercies_to_json()
+ .expect("Couldn't parse rustlings exercises files");
+
+ if project.crates.is_empty() {
+ println!("Failed find any exercises, make sure you're in the `rustlings` folder");
+ } else if project.write_to_disk().is_err() {
+ println!("Failed to write rust-project.json to disk for rust-analyzer");
+ } else {
+ println!("Successfully generated rust-project.json");
+ println!("rust-analyzer will now parse exercises, restart your language server or editor")
+ }
+ }
+
Subcommands::Watch(_subargs) => match watch(&exercises, verbose) {
Err(e) => {
println!("Error: Could not watch your progress. Error message was {:?}.", e);
@@ -224,6 +252,7 @@ fn main() {
}
}
+
fn spawn_watch_shell(failed_exercise_hint: &Arc>>, should_quit: Arc) {
let failed_exercise_hint = Arc::clone(failed_exercise_hint);
println!("Welcome to watch mode! You can type 'help' to get an overview of the commands you can use here.");
@@ -367,6 +396,8 @@ started, here's a couple of notes about how Rustlings operates:
4. If an exercise doesn't make sense to you, feel free to open an issue on GitHub!
(https://github.com/rust-lang/rustlings/issues/new). We look at every issue,
and sometimes, other learners do too so you can help each other out!
+5. If you want to use `rust-analyzer` with exercises, which provides features like
+ autocompletion, run the command `rustlings lsp`.
Got all that? Great! To get started, run `rustlings watch` in order to get the first
exercise. Make sure to have your editor open!"#;
diff --git a/src/project.rs b/src/project.rs
new file mode 100644
index 00000000..0df00b9a
--- /dev/null
+++ b/src/project.rs
@@ -0,0 +1,90 @@
+use glob::glob;
+use serde::{Deserialize, Serialize};
+use std::error::Error;
+use std::process::Command;
+
+/// Contains the structure of resulting rust-project.json file
+/// and functions to build the data required to create the file
+#[derive(Serialize, Deserialize)]
+pub struct RustAnalyzerProject {
+ sysroot_src: String,
+ pub crates: Vec,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct Crate {
+ root_module: String,
+ edition: String,
+ deps: Vec,
+ cfg: Vec,
+}
+
+impl RustAnalyzerProject {
+ pub fn new() -> RustAnalyzerProject {
+ RustAnalyzerProject {
+ sysroot_src: String::new(),
+ crates: Vec::new(),
+ }
+ }
+
+ /// Write rust-project.json to disk
+ pub fn write_to_disk(&self) -> Result<(), std::io::Error> {
+ std::fs::write(
+ "./rust-project.json",
+ serde_json::to_vec(&self).expect("Failed to serialize to JSON"),
+ )?;
+ Ok(())
+ }
+
+ /// If path contains .rs extension, add a crate to `rust-project.json`
+ fn path_to_json(&mut self, path: String) {
+ if let Some((_, ext)) = path.split_once('.') {
+ if ext == "rs" {
+ self.crates.push(Crate {
+ root_module: path,
+ edition: "2021".to_string(),
+ deps: Vec::new(),
+ // This allows rust_analyzer to work inside #[test] blocks
+ cfg: vec!["test".to_string()],
+ })
+ }
+ }
+ }
+
+ /// Parse the exercises folder for .rs files, any matches will create
+ /// a new `crate` in rust-project.json which allows rust-analyzer to
+ /// treat it like a normal binary
+ pub fn exercies_to_json(&mut self) -> Result<(), Box> {
+ for e in glob("./exercises/**/*")? {
+ let path = e?.to_string_lossy().to_string();
+ self.path_to_json(path);
+ }
+ Ok(())
+ }
+
+ /// Use `rustc` to determine the default toolchain
+ pub fn get_sysroot_src(&mut self) -> Result<(), Box> {
+ let toolchain = Command::new("rustc")
+ .arg("--print")
+ .arg("sysroot")
+ .output()?
+ .stdout;
+
+ let toolchain = String::from_utf8_lossy(&toolchain);
+ let mut whitespace_iter = toolchain.split_whitespace();
+
+ let toolchain = whitespace_iter.next().unwrap_or(&toolchain);
+
+ println!("Determined toolchain: {}\n", &toolchain);
+
+ self.sysroot_src = (std::path::Path::new(&*toolchain)
+ .join("lib")
+ .join("rustlib")
+ .join("src")
+ .join("rust")
+ .join("library")
+ .to_string_lossy())
+ .to_string();
+ Ok(())
+ }
+}