⬆️ rust-analyzer
This commit is contained in:
parent
26a413e015
commit
8807fc4cc3
64 changed files with 2244 additions and 1607 deletions
|
|
@ -118,7 +118,7 @@ impl CargoTargetSpec {
|
|||
global_state_snapshot: &GlobalStateSnapshot,
|
||||
file_id: FileId,
|
||||
) -> Result<Option<CargoTargetSpec>> {
|
||||
let crate_id = match &*global_state_snapshot.analysis.crate_for(file_id)? {
|
||||
let crate_id = match &*global_state_snapshot.analysis.crates_for(file_id)? {
|
||||
&[crate_id, ..] => crate_id,
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ use std::{
|
|||
use crate::line_index::{LineEndings, LineIndex, OffsetEncoding};
|
||||
use hir::Name;
|
||||
use ide::{
|
||||
LineCol, MonikerDescriptorKind, MonikerResult, StaticIndex, StaticIndexedFile, TextRange,
|
||||
TokenId,
|
||||
LineCol, MonikerDescriptorKind, StaticIndex, StaticIndexedFile, TextRange, TokenId,
|
||||
TokenStaticData,
|
||||
};
|
||||
use ide_db::LineIndexDatabase;
|
||||
use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
|
||||
|
|
@ -75,7 +75,7 @@ impl flags::Scip {
|
|||
let mut symbols_emitted: HashSet<TokenId> = HashSet::default();
|
||||
let mut tokens_to_symbol: HashMap<TokenId, String> = HashMap::new();
|
||||
|
||||
for file in si.files {
|
||||
for StaticIndexedFile { file_id, tokens, .. } in si.files {
|
||||
let mut local_count = 0;
|
||||
let mut new_local_symbol = || {
|
||||
let new_symbol = scip::types::Symbol::new_local(local_count);
|
||||
|
|
@ -84,7 +84,6 @@ impl flags::Scip {
|
|||
new_symbol
|
||||
};
|
||||
|
||||
let StaticIndexedFile { file_id, tokens, .. } = file;
|
||||
let relative_path = match get_relative_filepath(&vfs, &rootpath, file_id) {
|
||||
Some(relative_path) => relative_path,
|
||||
None => continue,
|
||||
|
|
@ -107,28 +106,20 @@ impl flags::Scip {
|
|||
|
||||
let mut occurrence = scip_types::Occurrence::default();
|
||||
occurrence.range = text_range_to_scip_range(&line_index, range);
|
||||
occurrence.symbol = match tokens_to_symbol.get(&id) {
|
||||
Some(symbol) => symbol.clone(),
|
||||
None => {
|
||||
let symbol = match &token.moniker {
|
||||
Some(moniker) => moniker_to_symbol(&moniker),
|
||||
None => new_local_symbol(),
|
||||
};
|
||||
|
||||
let symbol = scip::symbol::format_symbol(symbol);
|
||||
tokens_to_symbol.insert(id, symbol.clone());
|
||||
symbol
|
||||
}
|
||||
};
|
||||
occurrence.symbol = tokens_to_symbol
|
||||
.entry(id)
|
||||
.or_insert_with(|| {
|
||||
let symbol = token_to_symbol(&token).unwrap_or_else(&mut new_local_symbol);
|
||||
scip::symbol::format_symbol(symbol)
|
||||
})
|
||||
.clone();
|
||||
|
||||
if let Some(def) = token.definition {
|
||||
if def.range == range {
|
||||
occurrence.symbol_roles |= scip_types::SymbolRole::Definition as i32;
|
||||
}
|
||||
|
||||
if !symbols_emitted.contains(&id) {
|
||||
symbols_emitted.insert(id);
|
||||
|
||||
if symbols_emitted.insert(id) {
|
||||
let mut symbol_info = scip_types::SymbolInformation::default();
|
||||
symbol_info.symbol = occurrence.symbol.clone();
|
||||
if let Some(hover) = &token.hover {
|
||||
|
|
@ -207,9 +198,11 @@ fn new_descriptor(name: Name, suffix: scip_types::descriptor::Suffix) -> scip_ty
|
|||
///
|
||||
/// Only returns a Symbol when it's a non-local symbol.
|
||||
/// So if the visibility isn't outside of a document, then it will return None
|
||||
fn moniker_to_symbol(moniker: &MonikerResult) -> scip_types::Symbol {
|
||||
fn token_to_symbol(token: &TokenStaticData) -> Option<scip_types::Symbol> {
|
||||
use scip_types::descriptor::Suffix::*;
|
||||
|
||||
let moniker = token.moniker.as_ref()?;
|
||||
|
||||
let package_name = moniker.package_information.name.clone();
|
||||
let version = moniker.package_information.version.clone();
|
||||
let descriptors = moniker
|
||||
|
|
@ -233,7 +226,7 @@ fn moniker_to_symbol(moniker: &MonikerResult) -> scip_types::Symbol {
|
|||
})
|
||||
.collect();
|
||||
|
||||
scip_types::Symbol {
|
||||
Some(scip_types::Symbol {
|
||||
scheme: "rust-analyzer".into(),
|
||||
package: Some(scip_types::Package {
|
||||
manager: "cargo".to_string(),
|
||||
|
|
@ -244,19 +237,15 @@ fn moniker_to_symbol(moniker: &MonikerResult) -> scip_types::Symbol {
|
|||
.into(),
|
||||
descriptors,
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use hir::Semantics;
|
||||
use ide::{AnalysisHost, FilePosition};
|
||||
use ide_db::defs::IdentClass;
|
||||
use ide_db::{base_db::fixture::ChangeFixture, helpers::pick_best_token};
|
||||
use ide::{AnalysisHost, FilePosition, StaticIndex, TextSize};
|
||||
use ide_db::base_db::fixture::ChangeFixture;
|
||||
use scip::symbol::format_symbol;
|
||||
use syntax::SyntaxKind::*;
|
||||
use syntax::{AstNode, T};
|
||||
|
||||
fn position(ra_fixture: &str) -> (AnalysisHost, FilePosition) {
|
||||
let mut host = AnalysisHost::default();
|
||||
|
|
@ -273,53 +262,33 @@ mod test {
|
|||
fn check_symbol(ra_fixture: &str, expected: &str) {
|
||||
let (host, position) = position(ra_fixture);
|
||||
|
||||
let analysis = host.analysis();
|
||||
let si = StaticIndex::compute(&analysis);
|
||||
|
||||
let FilePosition { file_id, offset } = position;
|
||||
|
||||
let db = host.raw_database();
|
||||
let sema = &Semantics::new(db);
|
||||
let file = sema.parse(file_id).syntax().clone();
|
||||
let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
|
||||
IDENT
|
||||
| INT_NUMBER
|
||||
| LIFETIME_IDENT
|
||||
| T![self]
|
||||
| T![super]
|
||||
| T![crate]
|
||||
| T![Self]
|
||||
| COMMENT => 2,
|
||||
kind if kind.is_trivia() => 0,
|
||||
_ => 1,
|
||||
})
|
||||
.expect("OK OK");
|
||||
|
||||
let navs = sema
|
||||
.descend_into_macros(original_token.clone())
|
||||
.into_iter()
|
||||
.filter_map(|token| {
|
||||
IdentClass::classify_token(sema, &token).map(IdentClass::definitions).map(|it| {
|
||||
it.into_iter().flat_map(|def| {
|
||||
let module = def.module(db).unwrap();
|
||||
let current_crate = module.krate();
|
||||
|
||||
match MonikerResult::from_def(sema.db, def, current_crate) {
|
||||
Some(moniker_result) => Some(moniker_to_symbol(&moniker_result)),
|
||||
None => None,
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
let mut found_symbol = None;
|
||||
for file in &si.files {
|
||||
if file.file_id != file_id {
|
||||
continue;
|
||||
}
|
||||
for &(range, id) in &file.tokens {
|
||||
if range.contains(offset - TextSize::from(1)) {
|
||||
let token = si.tokens.get(id).unwrap();
|
||||
found_symbol = token_to_symbol(token);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if expected == "" {
|
||||
assert_eq!(0, navs.len(), "must have no symbols {:?}", navs);
|
||||
assert!(found_symbol.is_none(), "must have no symbols {:?}", found_symbol);
|
||||
return;
|
||||
}
|
||||
|
||||
assert_eq!(1, navs.len(), "must have one symbol {:?}", navs);
|
||||
|
||||
let res = navs.get(0).unwrap();
|
||||
let formatted = format_symbol(res.clone());
|
||||
assert!(found_symbol.is_some(), "must have one symbol {:?}", found_symbol);
|
||||
let res = found_symbol.unwrap();
|
||||
let formatted = format_symbol(res);
|
||||
assert_eq!(formatted, expected);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,19 @@ config_data! {
|
|||
cargo_autoreload: bool = "true",
|
||||
/// Run build scripts (`build.rs`) for more precise code analysis.
|
||||
cargo_buildScripts_enable: bool = "true",
|
||||
/// Specifies the working directory for running build scripts.
|
||||
/// - "workspace": run build scripts for a workspace in the workspace's root directory.
|
||||
/// This is incompatible with `#rust-analyzer.cargo.buildScripts.invocationStrategy#` set to `once`.
|
||||
/// - "root": run build scripts in the project's root directory.
|
||||
/// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`
|
||||
/// is set.
|
||||
cargo_buildScripts_invocationLocation: InvocationLocation = "\"workspace\"",
|
||||
/// Specifies the invocation strategy to use when running the build scripts command.
|
||||
/// If `per_workspace` is set, the command will be executed for each workspace.
|
||||
/// If `once` is set, the command will be executed once.
|
||||
/// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`
|
||||
/// is set.
|
||||
cargo_buildScripts_invocationStrategy: InvocationStrategy = "\"per_workspace\"",
|
||||
/// Override the command rust-analyzer uses to run build scripts and
|
||||
/// build procedural macros. The command is required to output json
|
||||
/// and should therefore include `--message-format=json` or a similar
|
||||
|
|
@ -122,6 +135,20 @@ config_data! {
|
|||
///
|
||||
/// Set to `"all"` to pass `--all-features` to Cargo.
|
||||
checkOnSave_features: Option<CargoFeaturesDef> = "null",
|
||||
/// Specifies the working directory for running checks.
|
||||
/// - "workspace": run checks for workspaces in the corresponding workspaces' root directories.
|
||||
// FIXME: Ideally we would support this in some way
|
||||
/// This falls back to "root" if `#rust-analyzer.cargo.checkOnSave.invocationStrategy#` is set to `once`.
|
||||
/// - "root": run checks in the project's root directory.
|
||||
/// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`
|
||||
/// is set.
|
||||
checkOnSave_invocationLocation: InvocationLocation = "\"workspace\"",
|
||||
/// Specifies the invocation strategy to use when running the checkOnSave command.
|
||||
/// If `per_workspace` is set, the command will be executed for each workspace.
|
||||
/// If `once` is set, the command will be executed once.
|
||||
/// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`
|
||||
/// is set.
|
||||
checkOnSave_invocationStrategy: InvocationStrategy = "\"per_workspace\"",
|
||||
/// Whether to pass `--no-default-features` to Cargo. Defaults to
|
||||
/// `#rust-analyzer.cargo.noDefaultFeatures#`.
|
||||
checkOnSave_noDefaultFeatures: Option<bool> = "null",
|
||||
|
|
@ -1056,6 +1083,16 @@ impl Config {
|
|||
rustc_source,
|
||||
unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()),
|
||||
wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper,
|
||||
invocation_strategy: match self.data.cargo_buildScripts_invocationStrategy {
|
||||
InvocationStrategy::Once => project_model::InvocationStrategy::Once,
|
||||
InvocationStrategy::PerWorkspace => project_model::InvocationStrategy::PerWorkspace,
|
||||
},
|
||||
invocation_location: match self.data.cargo_buildScripts_invocationLocation {
|
||||
InvocationLocation::Root => {
|
||||
project_model::InvocationLocation::Root(self.root_path.clone())
|
||||
}
|
||||
InvocationLocation::Workspace => project_model::InvocationLocation::Workspace,
|
||||
},
|
||||
run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(),
|
||||
extra_env: self.data.cargo_extraEnv.clone(),
|
||||
}
|
||||
|
|
@ -1087,6 +1124,18 @@ impl Config {
|
|||
command,
|
||||
args,
|
||||
extra_env: self.check_on_save_extra_env(),
|
||||
invocation_strategy: match self.data.checkOnSave_invocationStrategy {
|
||||
InvocationStrategy::Once => flycheck::InvocationStrategy::Once,
|
||||
InvocationStrategy::PerWorkspace => {
|
||||
flycheck::InvocationStrategy::PerWorkspace
|
||||
}
|
||||
},
|
||||
invocation_location: match self.data.checkOnSave_invocationLocation {
|
||||
InvocationLocation::Root => {
|
||||
flycheck::InvocationLocation::Root(self.root_path.clone())
|
||||
}
|
||||
InvocationLocation::Workspace => flycheck::InvocationLocation::Workspace,
|
||||
},
|
||||
}
|
||||
}
|
||||
Some(_) | None => FlycheckConfig::CargoCommand {
|
||||
|
|
@ -1587,6 +1636,20 @@ enum CargoFeaturesDef {
|
|||
Selected(Vec<String>),
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum InvocationStrategy {
|
||||
Once,
|
||||
PerWorkspace,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum InvocationLocation {
|
||||
Root,
|
||||
Workspace,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(untagged)]
|
||||
enum LifetimeElisionDef {
|
||||
|
|
@ -2001,6 +2064,22 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
|
|||
"Render annotations above the whole item, including documentation comments and attributes."
|
||||
],
|
||||
},
|
||||
"InvocationStrategy" => set! {
|
||||
"type": "string",
|
||||
"enum": ["per_workspace", "once"],
|
||||
"enumDescriptions": [
|
||||
"The command will be executed for each workspace.",
|
||||
"The command will be executed once."
|
||||
],
|
||||
},
|
||||
"InvocationLocation" => set! {
|
||||
"type": "string",
|
||||
"enum": ["workspace", "root"],
|
||||
"enumDescriptions": [
|
||||
"The command will be executed in the corresponding workspace root.",
|
||||
"The command will be executed in the project root."
|
||||
],
|
||||
},
|
||||
_ => panic!("missing entry for {}: {}", ty, default),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ impl<'a> RequestDispatcher<'a> {
|
|||
let _pctx = stdx::panic_context::enter(panic_context);
|
||||
f(self.global_state, params)
|
||||
};
|
||||
if let Ok(response) = result_to_response::<R>(req.id.clone(), result) {
|
||||
if let Ok(response) = result_to_response::<R>(req.id, result) {
|
||||
self.global_state.respond(response);
|
||||
}
|
||||
|
||||
|
|
@ -80,7 +80,7 @@ impl<'a> RequestDispatcher<'a> {
|
|||
f(global_state_snapshot, params)
|
||||
});
|
||||
|
||||
if let Ok(response) = thread_result_to_response::<R>(req.id.clone(), result) {
|
||||
if let Ok(response) = thread_result_to_response::<R>(req.id, result) {
|
||||
self.global_state.respond(response);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ pub(crate) struct GlobalState {
|
|||
pub(crate) source_root_config: SourceRootConfig,
|
||||
pub(crate) proc_macro_clients: Vec<Result<ProcMacroServer, String>>,
|
||||
|
||||
pub(crate) flycheck: Vec<FlycheckHandle>,
|
||||
pub(crate) flycheck: Arc<[FlycheckHandle]>,
|
||||
pub(crate) flycheck_sender: Sender<flycheck::Message>,
|
||||
pub(crate) flycheck_receiver: Receiver<flycheck::Message>,
|
||||
|
||||
|
|
@ -117,6 +117,7 @@ pub(crate) struct GlobalStateSnapshot {
|
|||
vfs: Arc<RwLock<(vfs::Vfs, NoHashHashMap<FileId, LineEndings>)>>,
|
||||
pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
|
||||
pub(crate) proc_macros_loaded: bool,
|
||||
pub(crate) flycheck: Arc<[FlycheckHandle]>,
|
||||
}
|
||||
|
||||
impl std::panic::UnwindSafe for GlobalStateSnapshot {}
|
||||
|
|
@ -155,7 +156,7 @@ impl GlobalState {
|
|||
source_root_config: SourceRootConfig::default(),
|
||||
proc_macro_clients: vec![],
|
||||
|
||||
flycheck: Vec::new(),
|
||||
flycheck: Arc::new([]),
|
||||
flycheck_sender,
|
||||
flycheck_receiver,
|
||||
|
||||
|
|
@ -295,6 +296,7 @@ impl GlobalState {
|
|||
mem_docs: self.mem_docs.clone(),
|
||||
semantic_tokens_cache: Arc::clone(&self.semantic_tokens_cache),
|
||||
proc_macros_loaded: !self.fetch_build_data_queue.last_op_result().0.is_empty(),
|
||||
flycheck: self.flycheck.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -398,6 +400,10 @@ impl GlobalStateSnapshot {
|
|||
url_from_abs_path(path)
|
||||
}
|
||||
|
||||
pub(crate) fn file_id_to_file_path(&self, file_id: FileId) -> vfs::VfsPath {
|
||||
self.vfs.read().0.file_path(file_id)
|
||||
}
|
||||
|
||||
pub(crate) fn cargo_target_for_crate_root(
|
||||
&self,
|
||||
crate_id: CrateId,
|
||||
|
|
|
|||
|
|
@ -658,7 +658,7 @@ pub(crate) fn handle_parent_module(
|
|||
|
||||
// check if invoked at the crate root
|
||||
let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
|
||||
let crate_id = match snap.analysis.crate_for(file_id)?.first() {
|
||||
let crate_id = match snap.analysis.crates_for(file_id)?.first() {
|
||||
Some(&crate_id) => crate_id,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
|
@ -1782,7 +1782,15 @@ fn run_rustfmt(
|
|||
) -> Result<Option<Vec<lsp_types::TextEdit>>> {
|
||||
let file_id = from_proto::file_id(snap, &text_document.uri)?;
|
||||
let file = snap.analysis.file_text(file_id)?;
|
||||
let crate_ids = snap.analysis.crate_for(file_id)?;
|
||||
|
||||
// find the edition of the package the file belongs to
|
||||
// (if it belongs to multiple we'll just pick the first one and pray)
|
||||
let edition = snap
|
||||
.analysis
|
||||
.relevant_crates_for(file_id)?
|
||||
.into_iter()
|
||||
.find_map(|crate_id| snap.cargo_target_for_crate_root(crate_id))
|
||||
.map(|(ws, target)| ws[ws[target].package].edition);
|
||||
|
||||
let line_index = snap.file_line_index(file_id)?;
|
||||
|
||||
|
|
@ -1808,9 +1816,7 @@ fn run_rustfmt(
|
|||
);
|
||||
}
|
||||
}
|
||||
if let Some(&crate_id) = crate_ids.first() {
|
||||
// Assume all crates are in the same edition
|
||||
let edition = snap.analysis.crate_edition(crate_id)?;
|
||||
if let Some(edition) = edition {
|
||||
cmd.arg("--edition");
|
||||
cmd.arg(edition.to_string());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ impl GlobalState {
|
|||
state: Progress,
|
||||
message: Option<String>,
|
||||
fraction: Option<f64>,
|
||||
cancel_token: Option<String>,
|
||||
) {
|
||||
if !self.config.work_done_progress() {
|
||||
return;
|
||||
|
|
@ -95,7 +96,10 @@ impl GlobalState {
|
|||
assert!((0.0..=1.0).contains(&f));
|
||||
(f * 100.0) as u32
|
||||
});
|
||||
let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", title));
|
||||
let cancellable = Some(cancel_token.is_some());
|
||||
let token = lsp_types::ProgressToken::String(
|
||||
cancel_token.unwrap_or_else(|| format!("rustAnalyzer/{}", title)),
|
||||
);
|
||||
let work_done_progress = match state {
|
||||
Progress::Begin => {
|
||||
self.send_request::<lsp_types::request::WorkDoneProgressCreate>(
|
||||
|
|
@ -105,14 +109,14 @@ impl GlobalState {
|
|||
|
||||
lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
|
||||
title: title.into(),
|
||||
cancellable: None,
|
||||
cancellable,
|
||||
message,
|
||||
percentage,
|
||||
})
|
||||
}
|
||||
Progress::Report => {
|
||||
lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
|
||||
cancellable: None,
|
||||
cancellable,
|
||||
message,
|
||||
percentage,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use std::{
|
|||
use always_assert::always;
|
||||
use crossbeam_channel::{select, Receiver};
|
||||
use flycheck::FlycheckHandle;
|
||||
use ide_db::base_db::{SourceDatabase, SourceDatabaseExt, VfsPath};
|
||||
use ide_db::base_db::{SourceDatabaseExt, VfsPath};
|
||||
use itertools::Itertools;
|
||||
use lsp_server::{Connection, Notification, Request};
|
||||
use lsp_types::notification::Notification as _;
|
||||
|
|
@ -191,7 +191,7 @@ impl GlobalState {
|
|||
// NOTE: don't count blocking select! call as a loop-turn time
|
||||
let _p = profile::span("GlobalState::handle_event");
|
||||
|
||||
tracing::debug!("handle_event({:?})", event);
|
||||
tracing::debug!("{:?} handle_event({:?})", loop_start, event);
|
||||
let task_queue_len = self.task_pool.handle.len();
|
||||
if task_queue_len > 0 {
|
||||
tracing::info!("task queue len: {}", task_queue_len);
|
||||
|
|
@ -257,7 +257,7 @@ impl GlobalState {
|
|||
}
|
||||
};
|
||||
|
||||
self.report_progress("Indexing", state, message, Some(fraction));
|
||||
self.report_progress("Indexing", state, message, Some(fraction), None);
|
||||
}
|
||||
}
|
||||
Event::Vfs(message) => {
|
||||
|
|
@ -465,7 +465,7 @@ impl GlobalState {
|
|||
}
|
||||
};
|
||||
|
||||
self.report_progress("Fetching", state, msg, None);
|
||||
self.report_progress("Fetching", state, msg, None, None);
|
||||
}
|
||||
Task::FetchBuildData(progress) => {
|
||||
let (state, msg) = match progress {
|
||||
|
|
@ -481,7 +481,7 @@ impl GlobalState {
|
|||
};
|
||||
|
||||
if let Some(state) = state {
|
||||
self.report_progress("Loading", state, msg, None);
|
||||
self.report_progress("Loading", state, msg, None, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -518,6 +518,7 @@ impl GlobalState {
|
|||
state,
|
||||
Some(format!("{}/{}", n_done, n_total)),
|
||||
Some(Progress::fraction(n_done, n_total)),
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -542,7 +543,10 @@ impl GlobalState {
|
|||
diag.fix,
|
||||
),
|
||||
Err(err) => {
|
||||
tracing::error!("File with cargo diagnostic not found in VFS: {}", err);
|
||||
tracing::error!(
|
||||
"flycheck {id}: File with cargo diagnostic not found in VFS: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -584,7 +588,13 @@ impl GlobalState {
|
|||
} else {
|
||||
format!("cargo check (#{})", id + 1)
|
||||
};
|
||||
self.report_progress(&title, state, message, None);
|
||||
self.report_progress(
|
||||
&title,
|
||||
state,
|
||||
message,
|
||||
None,
|
||||
Some(format!("rust-analyzer/checkOnSave/{}", id)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -698,7 +708,16 @@ impl GlobalState {
|
|||
this.cancel(id);
|
||||
Ok(())
|
||||
})?
|
||||
.on::<lsp_types::notification::WorkDoneProgressCancel>(|_this, _params| {
|
||||
.on::<lsp_types::notification::WorkDoneProgressCancel>(|this, params| {
|
||||
if let lsp_types::NumberOrString::String(s) = ¶ms.token {
|
||||
if let Some(id) = s.strip_prefix("rust-analyzer/checkOnSave/") {
|
||||
if let Ok(id) = u32::from_str_radix(id, 10) {
|
||||
if let Some(flycheck) = this.flycheck.get(id as usize) {
|
||||
flycheck.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Just ignore this. It is OK to continue sending progress
|
||||
// notifications for this token, as the client can't know when
|
||||
// we accepted notification.
|
||||
|
|
@ -711,7 +730,7 @@ impl GlobalState {
|
|||
.insert(path.clone(), DocumentData::new(params.text_document.version))
|
||||
.is_err();
|
||||
if already_exists {
|
||||
tracing::error!("duplicate DidOpenTextDocument: {}", path)
|
||||
tracing::error!("duplicate DidOpenTextDocument: {}", path);
|
||||
}
|
||||
this.vfs
|
||||
.write()
|
||||
|
|
@ -758,69 +777,7 @@ impl GlobalState {
|
|||
Ok(())
|
||||
})?
|
||||
.on::<lsp_types::notification::DidSaveTextDocument>(|this, params| {
|
||||
let mut updated = false;
|
||||
if let Ok(vfs_path) = from_proto::vfs_path(¶ms.text_document.uri) {
|
||||
let (vfs, _) = &*this.vfs.read();
|
||||
|
||||
// Trigger flychecks for all workspaces that depend on the saved file
|
||||
if let Some(file_id) = vfs.file_id(&vfs_path) {
|
||||
let analysis = this.analysis_host.analysis();
|
||||
// Crates containing or depending on the saved file
|
||||
let crate_ids: Vec<_> = analysis
|
||||
.crate_for(file_id)?
|
||||
.into_iter()
|
||||
.flat_map(|id| {
|
||||
this.analysis_host
|
||||
.raw_database()
|
||||
.crate_graph()
|
||||
.transitive_rev_deps(id)
|
||||
})
|
||||
.sorted()
|
||||
.unique()
|
||||
.collect();
|
||||
|
||||
let crate_root_paths: Vec<_> = crate_ids
|
||||
.iter()
|
||||
.filter_map(|&crate_id| {
|
||||
analysis
|
||||
.crate_root(crate_id)
|
||||
.map(|file_id| {
|
||||
vfs.file_path(file_id).as_path().map(ToOwned::to_owned)
|
||||
})
|
||||
.transpose()
|
||||
})
|
||||
.collect::<ide::Cancellable<_>>()?;
|
||||
let crate_root_paths: Vec<_> =
|
||||
crate_root_paths.iter().map(Deref::deref).collect();
|
||||
|
||||
// Find all workspaces that have at least one target containing the saved file
|
||||
let workspace_ids =
|
||||
this.workspaces.iter().enumerate().filter(|(_, ws)| match ws {
|
||||
project_model::ProjectWorkspace::Cargo { cargo, .. } => {
|
||||
cargo.packages().any(|pkg| {
|
||||
cargo[pkg].targets.iter().any(|&it| {
|
||||
crate_root_paths.contains(&cargo[it].root.as_path())
|
||||
})
|
||||
})
|
||||
}
|
||||
project_model::ProjectWorkspace::Json { project, .. } => project
|
||||
.crates()
|
||||
.any(|(c, _)| crate_ids.iter().any(|&crate_id| crate_id == c)),
|
||||
project_model::ProjectWorkspace::DetachedFiles { .. } => false,
|
||||
});
|
||||
|
||||
// Find and trigger corresponding flychecks
|
||||
for flycheck in &this.flycheck {
|
||||
for (id, _) in workspace_ids.clone() {
|
||||
if id == flycheck.id() {
|
||||
updated = true;
|
||||
flycheck.restart();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Re-fetch workspaces if a workspace related file has changed
|
||||
if let Some(abs_path) = vfs_path.as_path() {
|
||||
if reload::should_refresh_for_change(&abs_path, ChangeKind::Modify) {
|
||||
|
|
@ -828,13 +785,90 @@ impl GlobalState {
|
|||
.request_op(format!("DidSaveTextDocument {}", abs_path.display()));
|
||||
}
|
||||
}
|
||||
|
||||
let file_id = this.vfs.read().0.file_id(&vfs_path);
|
||||
if let Some(file_id) = file_id {
|
||||
let world = this.snapshot();
|
||||
let mut updated = false;
|
||||
let task = move || -> std::result::Result<(), ide::Cancelled> {
|
||||
// Trigger flychecks for all workspaces that depend on the saved file
|
||||
// Crates containing or depending on the saved file
|
||||
let crate_ids: Vec<_> = world
|
||||
.analysis
|
||||
.crates_for(file_id)?
|
||||
.into_iter()
|
||||
.flat_map(|id| world.analysis.transitive_rev_deps(id))
|
||||
.flatten()
|
||||
.sorted()
|
||||
.unique()
|
||||
.collect();
|
||||
|
||||
let crate_root_paths: Vec<_> = crate_ids
|
||||
.iter()
|
||||
.filter_map(|&crate_id| {
|
||||
world
|
||||
.analysis
|
||||
.crate_root(crate_id)
|
||||
.map(|file_id| {
|
||||
world
|
||||
.file_id_to_file_path(file_id)
|
||||
.as_path()
|
||||
.map(ToOwned::to_owned)
|
||||
})
|
||||
.transpose()
|
||||
})
|
||||
.collect::<ide::Cancellable<_>>()?;
|
||||
let crate_root_paths: Vec<_> =
|
||||
crate_root_paths.iter().map(Deref::deref).collect();
|
||||
|
||||
// Find all workspaces that have at least one target containing the saved file
|
||||
let workspace_ids =
|
||||
world.workspaces.iter().enumerate().filter(|(_, ws)| match ws {
|
||||
project_model::ProjectWorkspace::Cargo { cargo, .. } => {
|
||||
cargo.packages().any(|pkg| {
|
||||
cargo[pkg].targets.iter().any(|&it| {
|
||||
crate_root_paths.contains(&cargo[it].root.as_path())
|
||||
})
|
||||
})
|
||||
}
|
||||
project_model::ProjectWorkspace::Json { project, .. } => {
|
||||
project.crates().any(|(c, _)| {
|
||||
crate_ids.iter().any(|&crate_id| crate_id == c)
|
||||
})
|
||||
}
|
||||
project_model::ProjectWorkspace::DetachedFiles { .. } => false,
|
||||
});
|
||||
|
||||
// Find and trigger corresponding flychecks
|
||||
for flycheck in world.flycheck.iter() {
|
||||
for (id, _) in workspace_ids.clone() {
|
||||
if id == flycheck.id() {
|
||||
updated = true;
|
||||
flycheck.restart();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// No specific flycheck was triggered, so let's trigger all of them.
|
||||
if !updated {
|
||||
for flycheck in world.flycheck.iter() {
|
||||
flycheck.restart();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
this.task_pool.handle.spawn_with_sender(move |_| {
|
||||
if let Err(e) = std::panic::catch_unwind(task) {
|
||||
tracing::error!("DidSaveTextDocument flycheck task panicked: {e:?}")
|
||||
}
|
||||
});
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// No specific flycheck was triggered, so let's trigger all of them.
|
||||
if !updated {
|
||||
for flycheck in &this.flycheck {
|
||||
flycheck.restart();
|
||||
}
|
||||
for flycheck in this.flycheck.iter() {
|
||||
flycheck.restart();
|
||||
}
|
||||
Ok(())
|
||||
})?
|
||||
|
|
|
|||
|
|
@ -175,10 +175,8 @@ impl GlobalState {
|
|||
sender.send(Task::FetchBuildData(BuildDataProgress::Report(msg))).unwrap()
|
||||
}
|
||||
};
|
||||
let mut res = Vec::new();
|
||||
for ws in workspaces.iter() {
|
||||
res.push(ws.run_build_scripts(&config, &progress));
|
||||
}
|
||||
let res = ProjectWorkspace::run_all_build_scripts(&workspaces, &config, &progress);
|
||||
|
||||
sender.send(Task::FetchBuildData(BuildDataProgress::End((workspaces, res)))).unwrap();
|
||||
});
|
||||
}
|
||||
|
|
@ -468,39 +466,54 @@ impl GlobalState {
|
|||
let config = match self.config.flycheck() {
|
||||
Some(it) => it,
|
||||
None => {
|
||||
self.flycheck = Vec::new();
|
||||
self.flycheck = Arc::new([]);
|
||||
self.diagnostics.clear_check_all();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let sender = self.flycheck_sender.clone();
|
||||
self.flycheck = self
|
||||
.workspaces
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(id, w)| match w {
|
||||
ProjectWorkspace::Cargo { cargo, .. } => Some((id, cargo.workspace_root())),
|
||||
ProjectWorkspace::Json { project, .. } => {
|
||||
// Enable flychecks for json projects if a custom flycheck command was supplied
|
||||
// in the workspace configuration.
|
||||
match config {
|
||||
FlycheckConfig::CustomCommand { .. } => Some((id, project.path())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
ProjectWorkspace::DetachedFiles { .. } => None,
|
||||
})
|
||||
.map(|(id, root)| {
|
||||
let sender = sender.clone();
|
||||
FlycheckHandle::spawn(
|
||||
id,
|
||||
Box::new(move |msg| sender.send(msg).unwrap()),
|
||||
config.clone(),
|
||||
root.to_path_buf(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let invocation_strategy = match config {
|
||||
FlycheckConfig::CargoCommand { .. } => flycheck::InvocationStrategy::PerWorkspace,
|
||||
FlycheckConfig::CustomCommand { invocation_strategy, .. } => invocation_strategy,
|
||||
};
|
||||
|
||||
self.flycheck = match invocation_strategy {
|
||||
flycheck::InvocationStrategy::Once => vec![FlycheckHandle::spawn(
|
||||
0,
|
||||
Box::new(move |msg| sender.send(msg).unwrap()),
|
||||
config.clone(),
|
||||
self.config.root_path().clone(),
|
||||
)],
|
||||
flycheck::InvocationStrategy::PerWorkspace => {
|
||||
self.workspaces
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(id, w)| match w {
|
||||
ProjectWorkspace::Cargo { cargo, .. } => Some((id, cargo.workspace_root())),
|
||||
ProjectWorkspace::Json { project, .. } => {
|
||||
// Enable flychecks for json projects if a custom flycheck command was supplied
|
||||
// in the workspace configuration.
|
||||
match config {
|
||||
FlycheckConfig::CustomCommand { .. } => Some((id, project.path())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
ProjectWorkspace::DetachedFiles { .. } => None,
|
||||
})
|
||||
.map(|(id, root)| {
|
||||
let sender = sender.clone();
|
||||
FlycheckHandle::spawn(
|
||||
id,
|
||||
Box::new(move |msg| sender.send(msg).unwrap()),
|
||||
config.clone(),
|
||||
root.to_path_buf(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
.into();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue