mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
Move LSP bits from flycheck to rust-analyzer
There should be only one place that knows about LSP, and that place is right before we spit JSON on stdout.
This commit is contained in:
parent
12d82687cd
commit
220813dcb0
18 changed files with 1505 additions and 1506 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -975,10 +975,8 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cargo_metadata",
|
"cargo_metadata",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"insta",
|
|
||||||
"jod-thread",
|
"jod-thread",
|
||||||
"log",
|
"log",
|
||||||
"lsp-types",
|
|
||||||
"ra_toolchain",
|
"ra_toolchain",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
@ -1378,9 +1376,11 @@ name = "rust-analyzer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"cargo_metadata",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"globset",
|
"globset",
|
||||||
|
"insta",
|
||||||
"itertools",
|
"itertools",
|
||||||
"jod-thread",
|
"jod-thread",
|
||||||
"log",
|
"log",
|
||||||
|
|
|
@ -9,12 +9,8 @@ doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
crossbeam-channel = "0.4.0"
|
crossbeam-channel = "0.4.0"
|
||||||
lsp-types = { version = "0.74.0", features = ["proposed"] }
|
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
cargo_metadata = "0.10.0"
|
cargo_metadata = "0.10.0"
|
||||||
serde_json = "1.0.48"
|
serde_json = "1.0.48"
|
||||||
jod-thread = "0.1.1"
|
jod-thread = "0.1.1"
|
||||||
ra_toolchain = { path = "../ra_toolchain" }
|
ra_toolchain = { path = "../ra_toolchain" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
insta = "0.16.0"
|
|
||||||
|
|
|
@ -1,341 +0,0 @@
|
||||||
//! This module provides the functionality needed to convert diagnostics from
|
|
||||||
//! `cargo check` json format to the LSP diagnostic format.
|
|
||||||
use cargo_metadata::diagnostic::{
|
|
||||||
Applicability, Diagnostic as RustDiagnostic, DiagnosticLevel, DiagnosticSpan,
|
|
||||||
DiagnosticSpanMacroExpansion,
|
|
||||||
};
|
|
||||||
use lsp_types::{
|
|
||||||
CodeAction, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag,
|
|
||||||
Location, NumberOrString, Position, Range, TextEdit, Url, WorkspaceEdit,
|
|
||||||
};
|
|
||||||
use std::{
|
|
||||||
collections::HashMap,
|
|
||||||
fmt::Write,
|
|
||||||
path::{Component, Path, PathBuf, Prefix},
|
|
||||||
str::FromStr,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test;
|
|
||||||
|
|
||||||
/// Converts a Rust level string to a LSP severity
|
|
||||||
fn map_level_to_severity(val: DiagnosticLevel) -> Option<DiagnosticSeverity> {
|
|
||||||
match val {
|
|
||||||
DiagnosticLevel::Ice => Some(DiagnosticSeverity::Error),
|
|
||||||
DiagnosticLevel::Error => Some(DiagnosticSeverity::Error),
|
|
||||||
DiagnosticLevel::Warning => Some(DiagnosticSeverity::Warning),
|
|
||||||
DiagnosticLevel::Note => Some(DiagnosticSeverity::Information),
|
|
||||||
DiagnosticLevel::Help => Some(DiagnosticSeverity::Hint),
|
|
||||||
DiagnosticLevel::Unknown => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check whether a file name is from macro invocation
|
|
||||||
fn is_from_macro(file_name: &str) -> bool {
|
|
||||||
file_name.starts_with('<') && file_name.ends_with('>')
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a Rust macro span to a LSP location recursively
|
|
||||||
fn map_macro_span_to_location(
|
|
||||||
span_macro: &DiagnosticSpanMacroExpansion,
|
|
||||||
workspace_root: &PathBuf,
|
|
||||||
) -> Option<Location> {
|
|
||||||
if !is_from_macro(&span_macro.span.file_name) {
|
|
||||||
return Some(map_span_to_location(&span_macro.span, workspace_root));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(expansion) = &span_macro.span.expansion {
|
|
||||||
return map_macro_span_to_location(&expansion, workspace_root);
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a Rust span to a LSP location, resolving macro expansion site if neccesary
|
|
||||||
fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Location {
|
|
||||||
if span.expansion.is_some() {
|
|
||||||
let expansion = span.expansion.as_ref().unwrap();
|
|
||||||
if let Some(macro_range) = map_macro_span_to_location(&expansion, workspace_root) {
|
|
||||||
return macro_range;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
map_span_to_location_naive(span, workspace_root)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a Rust span to a LSP location
|
|
||||||
fn map_span_to_location_naive(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Location {
|
|
||||||
let mut file_name = workspace_root.clone();
|
|
||||||
file_name.push(&span.file_name);
|
|
||||||
let uri = url_from_path_with_drive_lowercasing(file_name).unwrap();
|
|
||||||
|
|
||||||
let range = Range::new(
|
|
||||||
Position::new(span.line_start as u64 - 1, span.column_start as u64 - 1),
|
|
||||||
Position::new(span.line_end as u64 - 1, span.column_end as u64 - 1),
|
|
||||||
);
|
|
||||||
|
|
||||||
Location { uri, range }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a secondary Rust span to a LSP related information
|
|
||||||
///
|
|
||||||
/// If the span is unlabelled this will return `None`.
|
|
||||||
fn map_secondary_span_to_related(
|
|
||||||
span: &DiagnosticSpan,
|
|
||||||
workspace_root: &PathBuf,
|
|
||||||
) -> Option<DiagnosticRelatedInformation> {
|
|
||||||
if let Some(label) = &span.label {
|
|
||||||
let location = map_span_to_location(span, workspace_root);
|
|
||||||
Some(DiagnosticRelatedInformation { location, message: label.clone() })
|
|
||||||
} else {
|
|
||||||
// Nothing to label this with
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determines if diagnostic is related to unused code
|
|
||||||
fn is_unused_or_unnecessary(rd: &RustDiagnostic) -> bool {
|
|
||||||
if let Some(code) = &rd.code {
|
|
||||||
match code.code.as_str() {
|
|
||||||
"dead_code" | "unknown_lints" | "unreachable_code" | "unused_attributes"
|
|
||||||
| "unused_imports" | "unused_macros" | "unused_variables" => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determines if diagnostic is related to deprecated code
|
|
||||||
fn is_deprecated(rd: &RustDiagnostic) -> bool {
|
|
||||||
if let Some(code) = &rd.code {
|
|
||||||
match code.code.as_str() {
|
|
||||||
"deprecated" => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum MappedRustChildDiagnostic {
|
|
||||||
Related(DiagnosticRelatedInformation),
|
|
||||||
SuggestedFix(CodeAction),
|
|
||||||
MessageLine(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map_rust_child_diagnostic(
|
|
||||||
rd: &RustDiagnostic,
|
|
||||||
workspace_root: &PathBuf,
|
|
||||||
) -> MappedRustChildDiagnostic {
|
|
||||||
let spans: Vec<&DiagnosticSpan> = rd.spans.iter().filter(|s| s.is_primary).collect();
|
|
||||||
if spans.is_empty() {
|
|
||||||
// `rustc` uses these spanless children as a way to print multi-line
|
|
||||||
// messages
|
|
||||||
return MappedRustChildDiagnostic::MessageLine(rd.message.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut edit_map: HashMap<Url, Vec<TextEdit>> = HashMap::new();
|
|
||||||
for &span in &spans {
|
|
||||||
match (&span.suggestion_applicability, &span.suggested_replacement) {
|
|
||||||
(Some(Applicability::MachineApplicable), Some(suggested_replacement)) => {
|
|
||||||
let location = map_span_to_location(span, workspace_root);
|
|
||||||
let edit = TextEdit::new(location.range, suggested_replacement.clone());
|
|
||||||
edit_map.entry(location.uri).or_default().push(edit);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !edit_map.is_empty() {
|
|
||||||
MappedRustChildDiagnostic::SuggestedFix(CodeAction {
|
|
||||||
title: rd.message.clone(),
|
|
||||||
kind: Some("quickfix".to_string()),
|
|
||||||
diagnostics: None,
|
|
||||||
edit: Some(WorkspaceEdit::new(edit_map)),
|
|
||||||
command: None,
|
|
||||||
is_preferred: None,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
MappedRustChildDiagnostic::Related(DiagnosticRelatedInformation {
|
|
||||||
location: map_span_to_location(spans[0], workspace_root),
|
|
||||||
message: rd.message.clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct MappedRustDiagnostic {
|
|
||||||
pub location: Location,
|
|
||||||
pub diagnostic: Diagnostic,
|
|
||||||
pub fixes: Vec<CodeAction>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a Rust root diagnostic to LSP form
|
|
||||||
///
|
|
||||||
/// This flattens the Rust diagnostic by:
|
|
||||||
///
|
|
||||||
/// 1. Creating a LSP diagnostic with the root message and primary span.
|
|
||||||
/// 2. Adding any labelled secondary spans to `relatedInformation`
|
|
||||||
/// 3. Categorising child diagnostics as either `SuggestedFix`es,
|
|
||||||
/// `relatedInformation` or additional message lines.
|
|
||||||
///
|
|
||||||
/// If the diagnostic has no primary span this will return `None`
|
|
||||||
pub(crate) fn map_rust_diagnostic_to_lsp(
|
|
||||||
rd: &RustDiagnostic,
|
|
||||||
workspace_root: &PathBuf,
|
|
||||||
) -> Vec<MappedRustDiagnostic> {
|
|
||||||
let primary_spans: Vec<&DiagnosticSpan> = rd.spans.iter().filter(|s| s.is_primary).collect();
|
|
||||||
if primary_spans.is_empty() {
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
|
|
||||||
let severity = map_level_to_severity(rd.level);
|
|
||||||
|
|
||||||
let mut source = String::from("rustc");
|
|
||||||
let mut code = rd.code.as_ref().map(|c| c.code.clone());
|
|
||||||
if let Some(code_val) = &code {
|
|
||||||
// See if this is an RFC #2103 scoped lint (e.g. from Clippy)
|
|
||||||
let scoped_code: Vec<&str> = code_val.split("::").collect();
|
|
||||||
if scoped_code.len() == 2 {
|
|
||||||
source = String::from(scoped_code[0]);
|
|
||||||
code = Some(String::from(scoped_code[1]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut needs_primary_span_label = true;
|
|
||||||
let mut related_information = vec![];
|
|
||||||
let mut tags = vec![];
|
|
||||||
|
|
||||||
for secondary_span in rd.spans.iter().filter(|s| !s.is_primary) {
|
|
||||||
let related = map_secondary_span_to_related(secondary_span, workspace_root);
|
|
||||||
if let Some(related) = related {
|
|
||||||
related_information.push(related);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut fixes = vec![];
|
|
||||||
let mut message = rd.message.clone();
|
|
||||||
for child in &rd.children {
|
|
||||||
let child = map_rust_child_diagnostic(&child, workspace_root);
|
|
||||||
match child {
|
|
||||||
MappedRustChildDiagnostic::Related(related) => related_information.push(related),
|
|
||||||
MappedRustChildDiagnostic::SuggestedFix(code_action) => fixes.push(code_action),
|
|
||||||
MappedRustChildDiagnostic::MessageLine(message_line) => {
|
|
||||||
write!(&mut message, "\n{}", message_line).unwrap();
|
|
||||||
|
|
||||||
// These secondary messages usually duplicate the content of the
|
|
||||||
// primary span label.
|
|
||||||
needs_primary_span_label = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_unused_or_unnecessary(rd) {
|
|
||||||
tags.push(DiagnosticTag::Unnecessary);
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_deprecated(rd) {
|
|
||||||
tags.push(DiagnosticTag::Deprecated);
|
|
||||||
}
|
|
||||||
|
|
||||||
primary_spans
|
|
||||||
.iter()
|
|
||||||
.map(|primary_span| {
|
|
||||||
let location = map_span_to_location(&primary_span, workspace_root);
|
|
||||||
|
|
||||||
let mut message = message.clone();
|
|
||||||
if needs_primary_span_label {
|
|
||||||
if let Some(primary_span_label) = &primary_span.label {
|
|
||||||
write!(&mut message, "\n{}", primary_span_label).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If error occurs from macro expansion, add related info pointing to
|
|
||||||
// where the error originated
|
|
||||||
if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() {
|
|
||||||
let def_loc = map_span_to_location_naive(&primary_span, workspace_root);
|
|
||||||
related_information.push(DiagnosticRelatedInformation {
|
|
||||||
location: def_loc,
|
|
||||||
message: "Error originated from macro here".to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let diagnostic = Diagnostic {
|
|
||||||
range: location.range,
|
|
||||||
severity,
|
|
||||||
code: code.clone().map(NumberOrString::String),
|
|
||||||
source: Some(source.clone()),
|
|
||||||
message,
|
|
||||||
related_information: if !related_information.is_empty() {
|
|
||||||
Some(related_information.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
tags: if !tags.is_empty() { Some(tags.clone()) } else { None },
|
|
||||||
};
|
|
||||||
|
|
||||||
MappedRustDiagnostic { location, diagnostic, fixes: fixes.clone() }
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a `Url` object from a given path, will lowercase drive letters if present.
|
|
||||||
/// This will only happen when processing windows paths.
|
|
||||||
///
|
|
||||||
/// When processing non-windows path, this is essentially the same as `Url::from_file_path`.
|
|
||||||
pub fn url_from_path_with_drive_lowercasing(
|
|
||||||
path: impl AsRef<Path>,
|
|
||||||
) -> Result<Url, Box<dyn std::error::Error + Send + Sync>> {
|
|
||||||
let component_has_windows_drive = path.as_ref().components().any(|comp| {
|
|
||||||
if let Component::Prefix(c) = comp {
|
|
||||||
match c.kind() {
|
|
||||||
Prefix::Disk(_) | Prefix::VerbatimDisk(_) => return true,
|
|
||||||
_ => return false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
});
|
|
||||||
|
|
||||||
// VSCode expects drive letters to be lowercased, where rust will uppercase the drive letters.
|
|
||||||
if component_has_windows_drive {
|
|
||||||
let url_original = Url::from_file_path(&path)
|
|
||||||
.map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?;
|
|
||||||
|
|
||||||
let drive_partition: Vec<&str> = url_original.as_str().rsplitn(2, ':').collect();
|
|
||||||
|
|
||||||
// There is a drive partition, but we never found a colon.
|
|
||||||
// This should not happen, but in this case we just pass it through.
|
|
||||||
if drive_partition.len() == 1 {
|
|
||||||
return Ok(url_original);
|
|
||||||
}
|
|
||||||
|
|
||||||
let joined = drive_partition[1].to_ascii_lowercase() + ":" + drive_partition[0];
|
|
||||||
let url = Url::from_str(&joined).expect("This came from a valid `Url`");
|
|
||||||
|
|
||||||
Ok(url)
|
|
||||||
} else {
|
|
||||||
Ok(Url::from_file_path(&path)
|
|
||||||
.map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// `Url` is not able to parse windows paths on unix machines.
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
#[cfg(test)]
|
|
||||||
mod path_conversion_windows_tests {
|
|
||||||
use super::url_from_path_with_drive_lowercasing;
|
|
||||||
#[test]
|
|
||||||
fn test_lowercase_drive_letter_with_drive() {
|
|
||||||
let url = url_from_path_with_drive_lowercasing("C:\\Test").unwrap();
|
|
||||||
|
|
||||||
assert_eq!(url.to_string(), "file:///c:/Test");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_drive_without_colon_passthrough() {
|
|
||||||
let url = url_from_path_with_drive_lowercasing(r#"\\localhost\C$\my_dir"#).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(url.to_string(), "file://localhost/C$/my_dir");
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,10 +1,9 @@
|
||||||
//! cargo_check provides the functionality needed to run `cargo check` or
|
//! cargo_check provides the functionality needed to run `cargo check` or
|
||||||
//! another compatible command (f.x. clippy) in a background thread and provide
|
//! another compatible command (f.x. clippy) in a background thread and provide
|
||||||
//! LSP diagnostics based on the output of the command.
|
//! LSP diagnostics based on the output of the command.
|
||||||
mod conv;
|
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
io::{self, BufRead, BufReader},
|
io::{self, BufReader},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
time::Instant,
|
time::Instant,
|
||||||
|
@ -12,14 +11,6 @@ use std::{
|
||||||
|
|
||||||
use cargo_metadata::Message;
|
use cargo_metadata::Message;
|
||||||
use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender};
|
use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender};
|
||||||
use lsp_types::{
|
|
||||||
CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin,
|
|
||||||
WorkDoneProgressEnd, WorkDoneProgressReport,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic};
|
|
||||||
|
|
||||||
pub use crate::conv::url_from_path_with_drive_lowercasing;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum FlycheckConfig {
|
pub enum FlycheckConfig {
|
||||||
|
@ -61,10 +52,17 @@ pub enum CheckTask {
|
||||||
ClearDiagnostics,
|
ClearDiagnostics,
|
||||||
|
|
||||||
/// Request adding a diagnostic with fixes included to a file
|
/// Request adding a diagnostic with fixes included to a file
|
||||||
AddDiagnostic { url: Url, diagnostic: Diagnostic, fixes: Vec<CodeActionOrCommand> },
|
AddDiagnostic { workspace_root: PathBuf, diagnostic: cargo_metadata::diagnostic::Diagnostic },
|
||||||
|
|
||||||
/// Request check progress notification to client
|
/// Request check progress notification to client
|
||||||
Status(WorkDoneProgress),
|
Status(Status),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Status {
|
||||||
|
Being,
|
||||||
|
Progress(String),
|
||||||
|
End,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum CheckCommand {
|
pub enum CheckCommand {
|
||||||
|
@ -131,9 +129,7 @@ impl FlycheckThread {
|
||||||
|
|
||||||
fn clean_previous_results(&self, task_send: &Sender<CheckTask>) {
|
fn clean_previous_results(&self, task_send: &Sender<CheckTask>) {
|
||||||
task_send.send(CheckTask::ClearDiagnostics).unwrap();
|
task_send.send(CheckTask::ClearDiagnostics).unwrap();
|
||||||
task_send
|
task_send.send(CheckTask::Status(Status::End)).unwrap();
|
||||||
.send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd { message: None })))
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_recheck(&mut self) -> bool {
|
fn should_recheck(&mut self) -> bool {
|
||||||
|
@ -155,53 +151,25 @@ impl FlycheckThread {
|
||||||
fn handle_message(&self, msg: CheckEvent, task_send: &Sender<CheckTask>) {
|
fn handle_message(&self, msg: CheckEvent, task_send: &Sender<CheckTask>) {
|
||||||
match msg {
|
match msg {
|
||||||
CheckEvent::Begin => {
|
CheckEvent::Begin => {
|
||||||
task_send
|
task_send.send(CheckTask::Status(Status::Being)).unwrap();
|
||||||
.send(CheckTask::Status(WorkDoneProgress::Begin(WorkDoneProgressBegin {
|
|
||||||
title: "Running `cargo check`".to_string(),
|
|
||||||
cancellable: Some(false),
|
|
||||||
message: None,
|
|
||||||
percentage: None,
|
|
||||||
})))
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckEvent::End => {
|
CheckEvent::End => {
|
||||||
task_send
|
task_send.send(CheckTask::Status(Status::End)).unwrap();
|
||||||
.send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd {
|
|
||||||
message: None,
|
|
||||||
})))
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckEvent::Msg(Message::CompilerArtifact(msg)) => {
|
CheckEvent::Msg(Message::CompilerArtifact(msg)) => {
|
||||||
task_send
|
task_send.send(CheckTask::Status(Status::Progress(msg.target.name))).unwrap();
|
||||||
.send(CheckTask::Status(WorkDoneProgress::Report(WorkDoneProgressReport {
|
|
||||||
cancellable: Some(false),
|
|
||||||
message: Some(msg.target.name),
|
|
||||||
percentage: None,
|
|
||||||
})))
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckEvent::Msg(Message::CompilerMessage(msg)) => {
|
CheckEvent::Msg(Message::CompilerMessage(msg)) => {
|
||||||
let map_result = map_rust_diagnostic_to_lsp(&msg.message, &self.workspace_root);
|
|
||||||
if map_result.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for MappedRustDiagnostic { location, diagnostic, fixes } in map_result {
|
|
||||||
let fixes = fixes
|
|
||||||
.into_iter()
|
|
||||||
.map(|fix| {
|
|
||||||
CodeAction { diagnostics: Some(vec![diagnostic.clone()]), ..fix }.into()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
task_send
|
task_send
|
||||||
.send(CheckTask::AddDiagnostic { url: location.uri, diagnostic, fixes })
|
.send(CheckTask::AddDiagnostic {
|
||||||
|
workspace_root: self.workspace_root.clone(),
|
||||||
|
diagnostic: msg.message,
|
||||||
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {}
|
CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {}
|
||||||
CheckEvent::Msg(Message::BuildFinished(_)) => {}
|
CheckEvent::Msg(Message::BuildFinished(_)) => {}
|
||||||
|
@ -271,11 +239,11 @@ impl FlycheckThread {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
// #[derive(Debug)]
|
||||||
pub struct DiagnosticWithFixes {
|
// pub struct DiagnosticWithFixes {
|
||||||
diagnostic: Diagnostic,
|
// diagnostic: Diagnostic,
|
||||||
fixes: Vec<CodeAction>,
|
// fixes: Vec<CodeAction>,
|
||||||
}
|
// }
|
||||||
|
|
||||||
enum CheckEvent {
|
enum CheckEvent {
|
||||||
Begin,
|
Begin,
|
||||||
|
@ -300,15 +268,11 @@ fn run_cargo(
|
||||||
// erroneus output.
|
// erroneus output.
|
||||||
let stdout = BufReader::new(child.stdout.take().unwrap());
|
let stdout = BufReader::new(child.stdout.take().unwrap());
|
||||||
let mut read_at_least_one_message = false;
|
let mut read_at_least_one_message = false;
|
||||||
|
for message in cargo_metadata::Message::parse_stream(stdout) {
|
||||||
for line in stdout.lines() {
|
|
||||||
let line = line?;
|
|
||||||
|
|
||||||
let message = serde_json::from_str::<cargo_metadata::Message>(&line);
|
|
||||||
let message = match message {
|
let message = match message {
|
||||||
Ok(message) => message,
|
Ok(message) => message,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::error!("Invalid json from cargo check, ignoring ({}): {:?} ", err, line);
|
log::error!("Invalid json from cargo check, ignoring ({})", err);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -29,6 +29,7 @@ rustc-hash = "1.1.0"
|
||||||
serde = { version = "1.0.106", features = ["derive"] }
|
serde = { version = "1.0.106", features = ["derive"] }
|
||||||
serde_json = "1.0.48"
|
serde_json = "1.0.48"
|
||||||
threadpool = "1.7.1"
|
threadpool = "1.7.1"
|
||||||
|
cargo_metadata = "0.10.0"
|
||||||
|
|
||||||
stdx = { path = "../stdx" }
|
stdx = { path = "../stdx" }
|
||||||
|
|
||||||
|
@ -53,6 +54,7 @@ winapi = "0.3.8"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.1.0"
|
tempfile = "3.1.0"
|
||||||
|
insta = "0.16.0"
|
||||||
test_utils = { path = "../test_utils" }
|
test_utils = { path = "../test_utils" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//! Book keeping for keeping diagnostics easily in sync with the client.
|
//! Book keeping for keeping diagnostics easily in sync with the client.
|
||||||
|
pub(crate) mod to_proto;
|
||||||
|
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: crates/ra_flycheck/src/conv/test.rs
|
source: crates/rust-analyzer/src/diagnostics/to_proto.rs
|
||||||
expression: diag
|
expression: diag
|
||||||
---
|
---
|
||||||
[
|
[
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: crates/ra_flycheck/src/conv/test.rs
|
source: crates/rust-analyzer/src/diagnostics/to_proto.rs
|
||||||
expression: diag
|
expression: diag
|
||||||
---
|
---
|
||||||
[
|
[
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: crates/ra_flycheck/src/conv/test.rs
|
source: crates/rust-analyzer/src/diagnostics/to_proto.rs
|
||||||
expression: diag
|
expression: diag
|
||||||
---
|
---
|
||||||
[
|
[
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: crates/ra_flycheck/src/conv/test.rs
|
source: crates/rust-analyzer/src/diagnostics/to_proto.rs
|
||||||
expression: diag
|
expression: diag
|
||||||
---
|
---
|
||||||
[
|
[
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: crates/ra_flycheck/src/conv/test.rs
|
source: crates/rust-analyzer/src/diagnostics/to_proto.rs
|
||||||
expression: diag
|
expression: diag
|
||||||
---
|
---
|
||||||
[
|
[
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: crates/ra_flycheck/src/conv/test.rs
|
source: crates/rust-analyzer/src/diagnostics/to_proto.rs
|
||||||
expression: diag
|
expression: diag
|
||||||
---
|
---
|
||||||
[
|
[
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: crates/ra_flycheck/src/conv/test.rs
|
source: crates/rust-analyzer/src/diagnostics/to_proto.rs
|
||||||
expression: diag
|
expression: diag
|
||||||
---
|
---
|
||||||
[
|
[
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: crates/ra_flycheck/src/conv/test.rs
|
source: crates/rust-analyzer/src/diagnostics/to_proto.rs
|
||||||
expression: diag
|
expression: diag
|
||||||
---
|
---
|
||||||
[
|
[
|
1408
crates/rust-analyzer/src/diagnostics/to_proto.rs
Normal file
1408
crates/rust-analyzer/src/diagnostics/to_proto.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -25,7 +25,7 @@ use lsp_types::{
|
||||||
WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd,
|
WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd,
|
||||||
WorkDoneProgressReport,
|
WorkDoneProgressReport,
|
||||||
};
|
};
|
||||||
use ra_flycheck::{url_from_path_with_drive_lowercasing, CheckTask};
|
use ra_flycheck::{CheckTask, Status};
|
||||||
use ra_ide::{Canceled, FileId, LibraryData, LineIndex, SourceRootId};
|
use ra_ide::{Canceled, FileId, LibraryData, LineIndex, SourceRootId};
|
||||||
use ra_prof::profile;
|
use ra_prof::profile;
|
||||||
use ra_project_model::{PackageRoot, ProjectWorkspace};
|
use ra_project_model::{PackageRoot, ProjectWorkspace};
|
||||||
|
@ -37,7 +37,7 @@ use threadpool::ThreadPool;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{Config, FilesWatcher},
|
config::{Config, FilesWatcher},
|
||||||
diagnostics::DiagnosticTask,
|
diagnostics::{to_proto::url_from_path_with_drive_lowercasing, DiagnosticTask},
|
||||||
from_proto, lsp_ext,
|
from_proto, lsp_ext,
|
||||||
main_loop::{
|
main_loop::{
|
||||||
pending_requests::{PendingRequest, PendingRequests},
|
pending_requests::{PendingRequest, PendingRequests},
|
||||||
|
@ -736,22 +736,61 @@ fn on_check_task(
|
||||||
task_sender.send(Task::Diagnostic(DiagnosticTask::ClearCheck))?;
|
task_sender.send(Task::Diagnostic(DiagnosticTask::ClearCheck))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckTask::AddDiagnostic { url, diagnostic, fixes } => {
|
CheckTask::AddDiagnostic { workspace_root, diagnostic } => {
|
||||||
let path = url.to_file_path().map_err(|()| format!("invalid uri: {}", url))?;
|
let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp(
|
||||||
|
&diagnostic,
|
||||||
|
&workspace_root,
|
||||||
|
);
|
||||||
|
for diag in diagnostics {
|
||||||
|
let path = diag
|
||||||
|
.location
|
||||||
|
.uri
|
||||||
|
.to_file_path()
|
||||||
|
.map_err(|()| format!("invalid uri: {}", diag.location.uri))?;
|
||||||
let file_id = match world_state.vfs.read().path2file(&path) {
|
let file_id = match world_state.vfs.read().path2file(&path) {
|
||||||
Some(file) => FileId(file.0),
|
Some(file) => FileId(file.0),
|
||||||
None => {
|
None => {
|
||||||
log::error!("File with cargo diagnostic not found in VFS: {}", path.display());
|
log::error!(
|
||||||
|
"File with cargo diagnostic not found in VFS: {}",
|
||||||
|
path.display()
|
||||||
|
);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
task_sender
|
task_sender.send(Task::Diagnostic(DiagnosticTask::AddCheck(
|
||||||
.send(Task::Diagnostic(DiagnosticTask::AddCheck(file_id, diagnostic, fixes)))?;
|
file_id,
|
||||||
|
diag.diagnostic,
|
||||||
|
diag.fixes.into_iter().map(|it| it.into()).collect(),
|
||||||
|
)))?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckTask::Status(progress) => {
|
CheckTask::Status(status) => {
|
||||||
if world_state.config.client_caps.work_done_progress {
|
if world_state.config.client_caps.work_done_progress {
|
||||||
|
let progress = match status {
|
||||||
|
Status::Being => {
|
||||||
|
lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
|
||||||
|
title: "Running `cargo check`".to_string(),
|
||||||
|
cancellable: Some(false),
|
||||||
|
message: None,
|
||||||
|
percentage: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Status::Progress(target) => {
|
||||||
|
lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
|
||||||
|
cancellable: Some(false),
|
||||||
|
message: Some(target),
|
||||||
|
percentage: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Status::End => {
|
||||||
|
lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd {
|
||||||
|
message: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let params = lsp_types::ProgressParams {
|
let params = lsp_types::ProgressParams {
|
||||||
token: lsp_types::ProgressToken::String(
|
token: lsp_types::ProgressToken::String(
|
||||||
"rustAnalyzer/cargoWatcher".to_string(),
|
"rustAnalyzer/cargoWatcher".to_string(),
|
||||||
|
|
|
@ -11,7 +11,7 @@ use std::{
|
||||||
use crossbeam_channel::{unbounded, Receiver};
|
use crossbeam_channel::{unbounded, Receiver};
|
||||||
use lsp_types::Url;
|
use lsp_types::Url;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use ra_flycheck::{url_from_path_with_drive_lowercasing, Flycheck, FlycheckConfig};
|
use ra_flycheck::{Flycheck, FlycheckConfig};
|
||||||
use ra_ide::{
|
use ra_ide::{
|
||||||
Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData, SourceRootId,
|
Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData, SourceRootId,
|
||||||
};
|
};
|
||||||
|
@ -22,7 +22,9 @@ use stdx::format_to;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::Config,
|
config::Config,
|
||||||
diagnostics::{CheckFixes, DiagnosticCollection},
|
diagnostics::{
|
||||||
|
to_proto::url_from_path_with_drive_lowercasing, CheckFixes, DiagnosticCollection,
|
||||||
|
},
|
||||||
main_loop::pending_requests::{CompletedRequest, LatestRequests},
|
main_loop::pending_requests::{CompletedRequest, LatestRequests},
|
||||||
vfs_glob::{Glob, RustPackageFilterBuilder},
|
vfs_glob::{Glob, RustPackageFilterBuilder},
|
||||||
LspError, Result,
|
LspError, Result,
|
||||||
|
|
Loading…
Reference in a new issue