Merge pull request #1026 from jackos/rust-analyzer-fix
Add lsp command to fix rust-analyzer
This commit is contained in:
commit
294ef8d92c
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,6 +5,7 @@ target/
|
|||||||
*.pdb
|
*.pdb
|
||||||
exercises/clippy/Cargo.toml
|
exercises/clippy/Cargo.toml
|
||||||
exercises/clippy/Cargo.lock
|
exercises/clippy/Cargo.lock
|
||||||
|
rust-project.json
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
*.iml
|
*.iml
|
||||||
|
19
Cargo.lock
generated
19
Cargo.lock
generated
@ -186,6 +186,15 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"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]]
|
[[package]]
|
||||||
name = "indicatif"
|
name = "indicatif"
|
||||||
version = "0.16.2"
|
version = "0.16.2"
|
||||||
@ -229,9 +238,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "0.4.8"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kernel32-sys"
|
name = "kernel32-sys"
|
||||||
@ -456,11 +465,13 @@ dependencies = [
|
|||||||
"assert_cmd",
|
"assert_cmd",
|
||||||
"console",
|
"console",
|
||||||
"glob",
|
"glob",
|
||||||
|
"home",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
"notify",
|
"notify",
|
||||||
"predicates",
|
"predicates",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -501,9 +512,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.66"
|
version = "1.0.81"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127"
|
checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
@ -12,6 +12,9 @@ notify = "4.0"
|
|||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
regex = "1.5"
|
regex = "1.5"
|
||||||
serde= { version = "1.0", features = ["derive"] }
|
serde= { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0.81"
|
||||||
|
home = "0.5.3"
|
||||||
|
glob = "0.3.0"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "rustlings"
|
name = "rustlings"
|
||||||
|
19
README.md
19
README.md
@ -126,24 +126,7 @@ After every couple of sections, there will be a quiz that'll test your knowledge
|
|||||||
|
|
||||||
## Enabling `rust-analyzer`
|
## Enabling `rust-analyzer`
|
||||||
|
|
||||||
`rust-analyzer` support is provided, but it depends on your editor
|
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.
|
||||||
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!_
|
|
||||||
|
|
||||||
## Continuing On
|
## Continuing On
|
||||||
|
|
||||||
|
31
src/main.rs
31
src/main.rs
@ -1,4 +1,5 @@
|
|||||||
use crate::exercise::{Exercise, ExerciseList};
|
use crate::exercise::{Exercise, ExerciseList};
|
||||||
|
use crate::project::RustAnalyzerProject;
|
||||||
use crate::run::run;
|
use crate::run::run;
|
||||||
use crate::verify::verify;
|
use crate::verify::verify;
|
||||||
use argh::FromArgs;
|
use argh::FromArgs;
|
||||||
@ -20,6 +21,7 @@ use std::time::Duration;
|
|||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
mod exercise;
|
mod exercise;
|
||||||
|
mod project;
|
||||||
mod run;
|
mod run;
|
||||||
mod verify;
|
mod verify;
|
||||||
|
|
||||||
@ -47,6 +49,7 @@ enum Subcommands {
|
|||||||
Run(RunArgs),
|
Run(RunArgs),
|
||||||
Hint(HintArgs),
|
Hint(HintArgs),
|
||||||
List(ListArgs),
|
List(ListArgs),
|
||||||
|
Lsp(LspArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromArgs, PartialEq, Debug)]
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
@ -77,6 +80,12 @@ struct HintArgs {
|
|||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
|
#[argh(subcommand, name = "lsp")]
|
||||||
|
/// Enable rust-analyzer for exercises
|
||||||
|
struct LspArgs {}
|
||||||
|
|
||||||
|
|
||||||
#[derive(FromArgs, PartialEq, Debug)]
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
#[argh(subcommand, name = "list")]
|
#[argh(subcommand, name = "list")]
|
||||||
/// Lists the exercises available in Rustlings
|
/// 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));
|
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) {
|
Subcommands::Watch(_subargs) => match watch(&exercises, verbose) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Error: Could not watch your progress. Error message was {:?}.", 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<Mutex<Option<String>>>, should_quit: Arc<AtomicBool>) {
|
fn spawn_watch_shell(failed_exercise_hint: &Arc<Mutex<Option<String>>>, should_quit: Arc<AtomicBool>) {
|
||||||
let failed_exercise_hint = Arc::clone(failed_exercise_hint);
|
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.");
|
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!
|
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,
|
(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!
|
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
|
Got all that? Great! To get started, run `rustlings watch` in order to get the first
|
||||||
exercise. Make sure to have your editor open!"#;
|
exercise. Make sure to have your editor open!"#;
|
||||||
|
90
src/project.rs
Normal file
90
src/project.rs
Normal file
@ -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<Crate>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Crate {
|
||||||
|
root_module: String,
|
||||||
|
edition: String,
|
||||||
|
deps: Vec<String>,
|
||||||
|
cfg: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<dyn Error>> {
|
||||||
|
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<dyn Error>> {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user