mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 21:13:37 +00:00
Auto merge of #17058 - alibektas:13529/ratoml, r=Veykril
feat: TOML based config for rust-analyzer > Important > > We don't promise _**any**_ stability with this feature yet, any configs exposed may be removed again, the grouping may change etc. # TOML Based Config for RA This PR ( addresses #13529 and this is a follow-up PR on #16639 ) makes rust-analyzer configurable by configuration files called `rust-analyzer.toml`. Files **must** be named `rust-analyzer.toml`. There is not a strict rule regarding where the files should be placed, but it is recommended to put them near a file that triggers server to start (i.e., `Cargo.{toml,lock}`, `rust-project.json`). ## Configuration Types Previous configuration keys are now split into three different classes. 1. Client keys: These keys only make sense when set by the client (e.g., by setting them in `settings.json` in VSCode). They are but a small portion of this list. One such example is `rust_analyzer.files_watcher`, based on which either the client or the server will be responsible for watching for changes made to project files. 2. Global keys: These keys apply to the entire workspace and can only be set on the very top layers of the hierarchy. The next section gives instructions on which layers these are. 3. Local keys: Keys that can be changed for each crate if desired. ### How Am I Supposed To Know If A Config Is Gl/Loc/Cl ? #17101 ## Configuration Hierarchy There are 5 levels in the configuration hierarchy. When a key is searched for, it is searched in a bottom-up depth-first fashion. ### Default Configuration **Scope**: Global, Local, and Client This is a hard-coded set of configurations. When a configuration key could not be found, then its default value applies. ### User configuration **Scope**: Global, Local If you want your configurations to apply to **every** project you have, you can do so by setting them in your `$CONFIG_DIR/rust-analyzer/rust-analyzer.toml` file, where `$CONFIG_DIR` is : | Platform | Value | Example | | ------- | ------------------------------------- | ---------------------------------------- | | Linux | `$XDG_CONFIG_HOME` or `$HOME`/.config | /home/alice/.config | | macOS | `$HOME`/Library/Application Support | /Users/Alice/Library/Application Support | | Windows | `{FOLDERID_RoamingAppData}` | C:\Users\Alice\AppData\Roaming | ### Client configuration **Scope**: Global, Local, and Client Previously, the only way to configure rust-analyzer was to configure it from the settings of the Client you are using. This level corresponds to that. > With this PR, you don't need to port anything to benefit from new features. You can continue to use your old settings as they are. ### Workspace Root Configuration **Scope**: Global, Local Rust-analyzer already used the path of the workspace you opened in your Client. We used this information to create a configuration file that won't affect your other projects and define global level configurations at the same time. ### Local Configuration **Scope**: Local You can also configure rust-analyzer on a crate level. Although it is not an error to define global ( or client ) level keys in such files, they won't be taken into consideration by the server. Defined local keys will affect the crate in which they are defined and crate's descendants. Internally, a Rust project is split into what we call `SourceRoot`s. This, although with exceptions, is equal to splitting a project into crates. > You may choose to have more than one `rust-analyzer.toml` files within a `SourceRoot`, but among them, the one closer to the project root will be
This commit is contained in:
commit
7c5d496ef8
23 changed files with 2114 additions and 556 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -328,6 +328,15 @@ dependencies = [
|
|||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.4.1"
|
||||
|
@ -1665,6 +1674,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"cfg",
|
||||
"crossbeam-channel",
|
||||
"dirs",
|
||||
"dissimilar",
|
||||
"expect-test",
|
||||
"flycheck",
|
||||
|
|
|
@ -273,10 +273,17 @@ impl Analysis {
|
|||
self.with_db(|db| status::status(db, file_id))
|
||||
}
|
||||
|
||||
pub fn source_root(&self, file_id: FileId) -> Cancellable<SourceRootId> {
|
||||
pub fn source_root_id(&self, file_id: FileId) -> Cancellable<SourceRootId> {
|
||||
self.with_db(|db| db.file_source_root(file_id))
|
||||
}
|
||||
|
||||
pub fn is_local_source_root(&self, source_root_id: SourceRootId) -> Cancellable<bool> {
|
||||
self.with_db(|db| {
|
||||
let sr = db.source_root(source_root_id);
|
||||
!sr.is_library
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parallel_prime_caches<F>(&self, num_worker_threads: u8, cb: F) -> Cancellable<()>
|
||||
where
|
||||
F: Fn(ParallelPrimeCachesProgress) + Sync + std::panic::UnwindSafe,
|
||||
|
|
|
@ -272,7 +272,7 @@ impl SourceRootConfig {
|
|||
/// If a `SourceRoot` doesn't have a parent and is local then it is not contained in this mapping but it can be asserted that it is a root `SourceRoot`.
|
||||
pub fn source_root_parent_map(&self) -> FxHashMap<SourceRootId, SourceRootId> {
|
||||
let roots = self.fsc.roots();
|
||||
let mut map = FxHashMap::<SourceRootId, SourceRootId>::default();
|
||||
let mut i = 0;
|
||||
roots
|
||||
.iter()
|
||||
.enumerate()
|
||||
|
@ -280,17 +280,16 @@ impl SourceRootConfig {
|
|||
.filter_map(|(idx, (root, root_id))| {
|
||||
// We are interested in parents if they are also local source roots.
|
||||
// So instead of a non-local parent we may take a local ancestor as a parent to a node.
|
||||
roots.iter().take(idx).find_map(|(root2, root2_id)| {
|
||||
roots[..idx].iter().find_map(|(root2, root2_id)| {
|
||||
i += 1;
|
||||
if self.local_filesets.contains(root2_id) && root.starts_with(root2) {
|
||||
return Some((root_id, root2_id));
|
||||
}
|
||||
None
|
||||
})
|
||||
})
|
||||
.for_each(|(child, parent)| {
|
||||
map.insert(SourceRootId(*child as u32), SourceRootId(*parent as u32));
|
||||
});
|
||||
map
|
||||
.map(|(&child, &parent)| (SourceRootId(child as u32), SourceRootId(parent as u32)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -135,6 +135,24 @@ impl AbsPathBuf {
|
|||
pub fn pop(&mut self) -> bool {
|
||||
self.0.pop()
|
||||
}
|
||||
|
||||
/// Equivalent of [`PathBuf::push`] for `AbsPathBuf`.
|
||||
///
|
||||
/// Extends `self` with `path`.
|
||||
///
|
||||
/// If `path` is absolute, it replaces the current path.
|
||||
///
|
||||
/// On Windows:
|
||||
///
|
||||
/// * if `path` has a root but no prefix (e.g., `\windows`), it
|
||||
/// replaces everything except for the prefix (if any) of `self`.
|
||||
/// * if `path` has a prefix but no root, it replaces `self`.
|
||||
/// * if `self` has a verbatim prefix (e.g. `\\?\C:\windows`)
|
||||
/// and `path` is not empty, the new path is normalized: all references
|
||||
/// to `.` and `..` are removed.
|
||||
pub fn push<P: AsRef<Utf8Path>>(&mut self, suffix: P) {
|
||||
self.0.push(suffix)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AbsPathBuf {
|
||||
|
|
|
@ -22,6 +22,7 @@ path = "src/bin/main.rs"
|
|||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
crossbeam-channel = "0.5.5"
|
||||
dirs = "5.0.1"
|
||||
dissimilar.workspace = true
|
||||
itertools.workspace = true
|
||||
scip = "0.3.3"
|
||||
|
|
|
@ -15,7 +15,11 @@ use std::{env, fs, path::PathBuf, process::ExitCode, sync::Arc};
|
|||
|
||||
use anyhow::Context;
|
||||
use lsp_server::Connection;
|
||||
use rust_analyzer::{cli::flags, config::Config, from_json};
|
||||
use rust_analyzer::{
|
||||
cli::flags,
|
||||
config::{Config, ConfigChange, ConfigErrors},
|
||||
from_json,
|
||||
};
|
||||
use semver::Version;
|
||||
use tracing_subscriber::fmt::writer::BoxMakeWriter;
|
||||
use vfs::AbsPathBuf;
|
||||
|
@ -220,16 +224,22 @@ fn run_server() -> anyhow::Result<()> {
|
|||
.filter(|workspaces| !workspaces.is_empty())
|
||||
.unwrap_or_else(|| vec![root_path.clone()]);
|
||||
let mut config =
|
||||
Config::new(root_path, capabilities, workspace_roots, visual_studio_code_version);
|
||||
Config::new(root_path, capabilities, workspace_roots, visual_studio_code_version, None);
|
||||
if let Some(json) = initialization_options {
|
||||
if let Err(e) = config.update(json) {
|
||||
let mut change = ConfigChange::default();
|
||||
change.change_client_config(json);
|
||||
|
||||
let error_sink: ConfigErrors;
|
||||
(config, error_sink, _) = config.apply_change(change);
|
||||
|
||||
if !error_sink.is_empty() {
|
||||
use lsp_types::{
|
||||
notification::{Notification, ShowMessage},
|
||||
MessageType, ShowMessageParams,
|
||||
};
|
||||
let not = lsp_server::Notification::new(
|
||||
ShowMessage::METHOD.to_owned(),
|
||||
ShowMessageParams { typ: MessageType::WARNING, message: e.to_string() },
|
||||
ShowMessageParams { typ: MessageType::WARNING, message: error_sink.to_string() },
|
||||
);
|
||||
connection.sender.send(lsp_server::Message::Notification(not)).unwrap();
|
||||
}
|
||||
|
|
|
@ -10,9 +10,11 @@ use ide_db::LineIndexDatabase;
|
|||
use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use scip::types as scip_types;
|
||||
use tracing::error;
|
||||
|
||||
use crate::{
|
||||
cli::flags,
|
||||
config::ConfigChange,
|
||||
line_index::{LineEndings, LineIndex, PositionEncoding},
|
||||
};
|
||||
|
||||
|
@ -35,12 +37,20 @@ impl flags::Scip {
|
|||
lsp_types::ClientCapabilities::default(),
|
||||
vec![],
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
if let Some(p) = self.config_path {
|
||||
let mut file = std::io::BufReader::new(std::fs::File::open(p)?);
|
||||
let json = serde_json::from_reader(&mut file)?;
|
||||
config.update(json)?;
|
||||
let mut change = ConfigChange::default();
|
||||
change.change_client_config(json);
|
||||
|
||||
let error_sink;
|
||||
(config, error_sink, _) = config.apply_change(change);
|
||||
|
||||
// FIXME @alibektas : What happens to errors without logging?
|
||||
error!(?error_sink, "Config Error(s)");
|
||||
}
|
||||
let cargo_config = config.cargo();
|
||||
let (db, vfs, _) = load_workspace_at(
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -154,7 +154,7 @@ pub(crate) fn fetch_native_diagnostics(
|
|||
.copied()
|
||||
.filter_map(|file_id| {
|
||||
let line_index = snapshot.file_line_index(file_id).ok()?;
|
||||
let source_root = snapshot.analysis.source_root(file_id).ok()?;
|
||||
let source_root = snapshot.analysis.source_root_id(file_id).ok()?;
|
||||
|
||||
let diagnostics = snapshot
|
||||
.analysis
|
||||
|
|
|
@ -547,6 +547,7 @@ mod tests {
|
|||
ClientCapabilities::default(),
|
||||
Vec::new(),
|
||||
None,
|
||||
None,
|
||||
),
|
||||
);
|
||||
let snap = state.snapshot();
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
//!
|
||||
//! Each tick provides an immutable snapshot of the state as `WorldSnapshot`.
|
||||
|
||||
use std::time::Instant;
|
||||
use std::{ops::Not as _, time::Instant};
|
||||
|
||||
use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||
use flycheck::FlycheckHandle;
|
||||
use hir::ChangeWithProcMacros;
|
||||
use ide::{Analysis, AnalysisHost, Cancellable, FileId, SourceRootId};
|
||||
use ide_db::base_db::{CrateId, ProcMacroPaths};
|
||||
use ide_db::base_db::{CrateId, ProcMacroPaths, SourceDatabaseExt};
|
||||
use load_cargo::SourceRootConfig;
|
||||
use lsp_types::{SemanticTokens, Url};
|
||||
use nohash_hasher::IntMap;
|
||||
|
@ -25,13 +25,16 @@ use project_model::{
|
|||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use tracing::{span, Level};
|
||||
use triomphe::Arc;
|
||||
use vfs::{AnchoredPathBuf, Vfs};
|
||||
use vfs::{AnchoredPathBuf, ChangeKind, Vfs};
|
||||
|
||||
use crate::{
|
||||
config::{Config, ConfigError},
|
||||
config::{Config, ConfigChange, ConfigErrors},
|
||||
diagnostics::{CheckFixes, DiagnosticCollection},
|
||||
line_index::{LineEndings, LineIndex},
|
||||
lsp::{from_proto, to_proto::url_from_abs_path},
|
||||
lsp::{
|
||||
from_proto::{self},
|
||||
to_proto::url_from_abs_path,
|
||||
},
|
||||
lsp_ext,
|
||||
main_loop::Task,
|
||||
mem_docs::MemDocs,
|
||||
|
@ -65,13 +68,13 @@ pub(crate) struct GlobalState {
|
|||
pub(crate) fmt_pool: Handle<TaskPool<Task>, Receiver<Task>>,
|
||||
|
||||
pub(crate) config: Arc<Config>,
|
||||
pub(crate) config_errors: Option<ConfigError>,
|
||||
pub(crate) config_errors: Option<ConfigErrors>,
|
||||
pub(crate) analysis_host: AnalysisHost,
|
||||
pub(crate) diagnostics: DiagnosticCollection,
|
||||
pub(crate) mem_docs: MemDocs,
|
||||
pub(crate) source_root_config: SourceRootConfig,
|
||||
/// A mapping that maps a local source root's `SourceRootId` to it parent's `SourceRootId`, if it has one.
|
||||
pub(crate) local_roots_parent_map: FxHashMap<SourceRootId, SourceRootId>,
|
||||
pub(crate) local_roots_parent_map: Arc<FxHashMap<SourceRootId, SourceRootId>>,
|
||||
pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
|
||||
|
||||
// status
|
||||
|
@ -213,7 +216,7 @@ impl GlobalState {
|
|||
shutdown_requested: false,
|
||||
last_reported_status: None,
|
||||
source_root_config: SourceRootConfig::default(),
|
||||
local_roots_parent_map: FxHashMap::default(),
|
||||
local_roots_parent_map: Arc::new(FxHashMap::default()),
|
||||
config_errors: Default::default(),
|
||||
|
||||
proc_macro_clients: Arc::from_iter([]),
|
||||
|
@ -254,6 +257,14 @@ impl GlobalState {
|
|||
|
||||
pub(crate) fn process_changes(&mut self) -> bool {
|
||||
let _p = span!(Level::INFO, "GlobalState::process_changes").entered();
|
||||
|
||||
// We cannot directly resolve a change in a ratoml file to a format
|
||||
// that can be used by the config module because config talks
|
||||
// in `SourceRootId`s instead of `FileId`s and `FileId` -> `SourceRootId`
|
||||
// mapping is not ready until `AnalysisHost::apply_changes` has been called.
|
||||
let mut modified_ratoml_files: FxHashMap<FileId, (ChangeKind, vfs::VfsPath)> =
|
||||
FxHashMap::default();
|
||||
|
||||
let (change, modified_rust_files, workspace_structure_change) = {
|
||||
let mut change = ChangeWithProcMacros::new();
|
||||
let mut guard = self.vfs.write();
|
||||
|
@ -273,6 +284,11 @@ impl GlobalState {
|
|||
let mut modified_rust_files = vec![];
|
||||
for file in changed_files.into_values() {
|
||||
let vfs_path = vfs.file_path(file.file_id);
|
||||
if let Some(("rust-analyzer", Some("toml"))) = vfs_path.name_and_extension() {
|
||||
// Remember ids to use them after `apply_changes`
|
||||
modified_ratoml_files.insert(file.file_id, (file.kind(), vfs_path.clone()));
|
||||
}
|
||||
|
||||
if let Some(path) = vfs_path.as_path() {
|
||||
has_structure_changes |= file.is_created_or_deleted();
|
||||
|
||||
|
@ -310,12 +326,15 @@ impl GlobalState {
|
|||
bytes.push((file.file_id, text));
|
||||
}
|
||||
let (vfs, line_endings_map) = &mut *RwLockUpgradableReadGuard::upgrade(guard);
|
||||
bytes.into_iter().for_each(|(file_id, text)| match text {
|
||||
None => change.change_file(file_id, None),
|
||||
Some((text, line_endings)) => {
|
||||
line_endings_map.insert(file_id, line_endings);
|
||||
change.change_file(file_id, Some(text));
|
||||
}
|
||||
bytes.into_iter().for_each(|(file_id, text)| {
|
||||
let text = match text {
|
||||
None => None,
|
||||
Some((text, line_endings)) => {
|
||||
line_endings_map.insert(file_id, line_endings);
|
||||
Some(text)
|
||||
}
|
||||
};
|
||||
change.change_file(file_id, text);
|
||||
});
|
||||
if has_structure_changes {
|
||||
let roots = self.source_root_config.partition(vfs);
|
||||
|
@ -326,6 +345,63 @@ impl GlobalState {
|
|||
|
||||
let _p = span!(Level::INFO, "GlobalState::process_changes/apply_change").entered();
|
||||
self.analysis_host.apply_change(change);
|
||||
if !modified_ratoml_files.is_empty()
|
||||
|| !self.config.same_source_root_parent_map(&self.local_roots_parent_map)
|
||||
{
|
||||
let config_change = {
|
||||
let user_config_path = self.config.user_config_path();
|
||||
let root_ratoml_path = self.config.root_ratoml_path();
|
||||
let mut change = ConfigChange::default();
|
||||
let db = self.analysis_host.raw_database();
|
||||
|
||||
for (file_id, (_change_kind, vfs_path)) in modified_ratoml_files {
|
||||
if vfs_path == *user_config_path {
|
||||
change.change_user_config(Some(db.file_text(file_id)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if vfs_path == *root_ratoml_path {
|
||||
change.change_root_ratoml(Some(db.file_text(file_id)));
|
||||
continue;
|
||||
}
|
||||
|
||||
// If change has been made to a ratoml file that
|
||||
// belongs to a non-local source root, we will ignore it.
|
||||
// As it doesn't make sense a users to use external config files.
|
||||
let sr_id = db.file_source_root(file_id);
|
||||
let sr = db.source_root(sr_id);
|
||||
if !sr.is_library {
|
||||
if let Some((old_path, old_text)) = change.change_ratoml(
|
||||
sr_id,
|
||||
vfs_path.clone(),
|
||||
Some(db.file_text(file_id)),
|
||||
) {
|
||||
// SourceRoot has more than 1 RATOML files. In this case lexicographically smaller wins.
|
||||
if old_path < vfs_path {
|
||||
span!(Level::ERROR, "Two `rust-analyzer.toml` files were found inside the same crate. {vfs_path} has no effect.");
|
||||
// Put the old one back in.
|
||||
change.change_ratoml(sr_id, old_path, old_text);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Mapping to a SourceRoot should always end up in `Ok`
|
||||
span!(Level::ERROR, "Mapping to SourceRootId failed.");
|
||||
}
|
||||
}
|
||||
change.change_source_root_parent_map(self.local_roots_parent_map.clone());
|
||||
change
|
||||
};
|
||||
|
||||
let (config, e, should_update) = self.config.apply_change(config_change);
|
||||
self.config_errors = e.is_empty().not().then_some(e);
|
||||
|
||||
if should_update {
|
||||
self.update_configuration(config);
|
||||
} else {
|
||||
// No global or client level config was changed. So we can just naively replace config.
|
||||
self.config = Arc::new(config);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if !matches!(&workspace_structure_change, Some((.., true))) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! This module is responsible for implementing handlers for Language Server
|
||||
//! Protocol. This module specifically handles notifications.
|
||||
|
||||
use std::ops::Deref;
|
||||
use std::ops::{Deref, Not as _};
|
||||
|
||||
use itertools::Itertools;
|
||||
use lsp_types::{
|
||||
|
@ -13,7 +13,7 @@ use triomphe::Arc;
|
|||
use vfs::{AbsPathBuf, ChangeKind, VfsPath};
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
config::{Config, ConfigChange},
|
||||
global_state::GlobalState,
|
||||
lsp::{from_proto, utils::apply_document_changes},
|
||||
lsp_ext::{self, RunFlycheckParams},
|
||||
|
@ -71,6 +71,7 @@ pub(crate) fn handle_did_open_text_document(
|
|||
tracing::error!("duplicate DidOpenTextDocument: {}", path);
|
||||
}
|
||||
|
||||
tracing::info!("New file content set {:?}", params.text_document.text);
|
||||
state.vfs.write().0.set_file_contents(path, Some(params.text_document.text.into_bytes()));
|
||||
if state.config.notifications().unindexed_project {
|
||||
tracing::debug!("queuing task");
|
||||
|
@ -196,10 +197,14 @@ pub(crate) fn handle_did_change_configuration(
|
|||
}
|
||||
(None, Some(mut configs)) => {
|
||||
if let Some(json) = configs.get_mut(0) {
|
||||
// Note that json can be null according to the spec if the client can't
|
||||
// provide a configuration. This is handled in Config::update below.
|
||||
let mut config = Config::clone(&*this.config);
|
||||
this.config_errors = config.update(json.take()).err();
|
||||
let config = Config::clone(&*this.config);
|
||||
let mut change = ConfigChange::default();
|
||||
change.change_client_config(json.take());
|
||||
|
||||
let (config, e, _) = config.apply_change(change);
|
||||
this.config_errors = e.is_empty().not().then_some(e);
|
||||
|
||||
// Client config changes neccesitates .update_config method to be called.
|
||||
this.update_configuration(config);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ use crate::{
|
|||
hack_recover_crate_name,
|
||||
line_index::LineEndings,
|
||||
lsp::{
|
||||
ext::InternalTestingFetchConfigParams,
|
||||
from_proto, to_proto,
|
||||
utils::{all_edits_are_disjoint, invalid_params_error},
|
||||
LspError,
|
||||
|
@ -367,8 +368,7 @@ pub(crate) fn handle_join_lines(
|
|||
let _p = tracing::info_span!("handle_join_lines").entered();
|
||||
|
||||
let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
|
||||
let source_root = snap.analysis.source_root(file_id)?;
|
||||
let config = snap.config.join_lines(Some(source_root));
|
||||
let config = snap.config.join_lines();
|
||||
let line_index = snap.file_line_index(file_id)?;
|
||||
|
||||
let mut res = TextEdit::default();
|
||||
|
@ -949,7 +949,7 @@ pub(crate) fn handle_completion(
|
|||
let completion_trigger_character =
|
||||
context.and_then(|ctx| ctx.trigger_character).and_then(|s| s.chars().next());
|
||||
|
||||
let source_root = snap.analysis.source_root(position.file_id)?;
|
||||
let source_root = snap.analysis.source_root_id(position.file_id)?;
|
||||
let completion_config = &snap.config.completion(Some(source_root));
|
||||
// FIXME: We should fix up the position when retrying the cancelled request instead
|
||||
position.offset = position.offset.min(line_index.index.len());
|
||||
|
@ -997,7 +997,7 @@ pub(crate) fn handle_completion_resolve(
|
|||
let Ok(offset) = from_proto::offset(&line_index, resolve_data.position.position) else {
|
||||
return Ok(original_completion);
|
||||
};
|
||||
let source_root = snap.analysis.source_root(file_id)?;
|
||||
let source_root = snap.analysis.source_root_id(file_id)?;
|
||||
|
||||
let additional_edits = snap
|
||||
.analysis
|
||||
|
@ -1229,7 +1229,7 @@ pub(crate) fn handle_code_action(
|
|||
let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
|
||||
let line_index = snap.file_line_index(file_id)?;
|
||||
let frange = from_proto::file_range(&snap, ¶ms.text_document, params.range)?;
|
||||
let source_root = snap.analysis.source_root(file_id)?;
|
||||
let source_root = snap.analysis.source_root_id(file_id)?;
|
||||
|
||||
let mut assists_config = snap.config.assist(Some(source_root));
|
||||
assists_config.allowed = params
|
||||
|
@ -1307,7 +1307,7 @@ pub(crate) fn handle_code_action_resolve(
|
|||
let line_index = snap.file_line_index(file_id)?;
|
||||
let range = from_proto::text_range(&line_index, params.code_action_params.range)?;
|
||||
let frange = FileRange { file_id, range };
|
||||
let source_root = snap.analysis.source_root(file_id)?;
|
||||
let source_root = snap.analysis.source_root_id(file_id)?;
|
||||
|
||||
let mut assists_config = snap.config.assist(Some(source_root));
|
||||
assists_config.allowed = params
|
||||
|
@ -1460,7 +1460,7 @@ pub(crate) fn handle_document_highlight(
|
|||
let _p = tracing::info_span!("handle_document_highlight").entered();
|
||||
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
|
||||
let line_index = snap.file_line_index(position.file_id)?;
|
||||
let source_root = snap.analysis.source_root(position.file_id)?;
|
||||
let source_root = snap.analysis.source_root_id(position.file_id)?;
|
||||
|
||||
let refs = match snap
|
||||
.analysis
|
||||
|
@ -1511,13 +1511,12 @@ pub(crate) fn handle_inlay_hints(
|
|||
params.range,
|
||||
)?;
|
||||
let line_index = snap.file_line_index(file_id)?;
|
||||
let source_root = snap.analysis.source_root(file_id)?;
|
||||
let range = TextRange::new(
|
||||
range.start().min(line_index.index.len()),
|
||||
range.end().min(line_index.index.len()),
|
||||
);
|
||||
|
||||
let inlay_hints_config = snap.config.inlay_hints(Some(source_root));
|
||||
let inlay_hints_config = snap.config.inlay_hints();
|
||||
Ok(Some(
|
||||
snap.analysis
|
||||
.inlay_hints(&inlay_hints_config, file_id, Some(range))?
|
||||
|
@ -1553,9 +1552,8 @@ pub(crate) fn handle_inlay_hints_resolve(
|
|||
|
||||
let line_index = snap.file_line_index(file_id)?;
|
||||
let hint_position = from_proto::offset(&line_index, original_hint.position)?;
|
||||
let source_root = snap.analysis.source_root(file_id)?;
|
||||
|
||||
let mut forced_resolve_inlay_hints_config = snap.config.inlay_hints(Some(source_root));
|
||||
let mut forced_resolve_inlay_hints_config = snap.config.inlay_hints();
|
||||
forced_resolve_inlay_hints_config.fields_to_resolve = InlayFieldsToResolve::empty();
|
||||
let resolve_hints = snap.analysis.inlay_hints_resolve(
|
||||
&forced_resolve_inlay_hints_config,
|
||||
|
@ -1687,9 +1685,8 @@ pub(crate) fn handle_semantic_tokens_full(
|
|||
let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
|
||||
let text = snap.analysis.file_text(file_id)?;
|
||||
let line_index = snap.file_line_index(file_id)?;
|
||||
let source_root = snap.analysis.source_root(file_id)?;
|
||||
|
||||
let mut highlight_config = snap.config.highlighting_config(Some(source_root));
|
||||
let mut highlight_config = snap.config.highlighting_config();
|
||||
// Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet.
|
||||
highlight_config.syntactic_name_ref_highlighting =
|
||||
snap.workspaces.is_empty() || !snap.proc_macros_loaded;
|
||||
|
@ -1700,7 +1697,7 @@ pub(crate) fn handle_semantic_tokens_full(
|
|||
&line_index,
|
||||
highlights,
|
||||
snap.config.semantics_tokens_augments_syntax_tokens(),
|
||||
snap.config.highlighting_non_standard_tokens(Some(source_root)),
|
||||
snap.config.highlighting_non_standard_tokens(),
|
||||
);
|
||||
|
||||
// Unconditionally cache the tokens
|
||||
|
@ -1718,9 +1715,8 @@ pub(crate) fn handle_semantic_tokens_full_delta(
|
|||
let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
|
||||
let text = snap.analysis.file_text(file_id)?;
|
||||
let line_index = snap.file_line_index(file_id)?;
|
||||
let source_root = snap.analysis.source_root(file_id)?;
|
||||
|
||||
let mut highlight_config = snap.config.highlighting_config(Some(source_root));
|
||||
let mut highlight_config = snap.config.highlighting_config();
|
||||
// Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet.
|
||||
highlight_config.syntactic_name_ref_highlighting =
|
||||
snap.workspaces.is_empty() || !snap.proc_macros_loaded;
|
||||
|
@ -1731,7 +1727,7 @@ pub(crate) fn handle_semantic_tokens_full_delta(
|
|||
&line_index,
|
||||
highlights,
|
||||
snap.config.semantics_tokens_augments_syntax_tokens(),
|
||||
snap.config.highlighting_non_standard_tokens(Some(source_root)),
|
||||
snap.config.highlighting_non_standard_tokens(),
|
||||
);
|
||||
|
||||
let cached_tokens = snap.semantic_tokens_cache.lock().remove(¶ms.text_document.uri);
|
||||
|
@ -1762,9 +1758,8 @@ pub(crate) fn handle_semantic_tokens_range(
|
|||
let frange = from_proto::file_range(&snap, ¶ms.text_document, params.range)?;
|
||||
let text = snap.analysis.file_text(frange.file_id)?;
|
||||
let line_index = snap.file_line_index(frange.file_id)?;
|
||||
let source_root = snap.analysis.source_root(frange.file_id)?;
|
||||
|
||||
let mut highlight_config = snap.config.highlighting_config(Some(source_root));
|
||||
let mut highlight_config = snap.config.highlighting_config();
|
||||
// Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet.
|
||||
highlight_config.syntactic_name_ref_highlighting =
|
||||
snap.workspaces.is_empty() || !snap.proc_macros_loaded;
|
||||
|
@ -1775,7 +1770,7 @@ pub(crate) fn handle_semantic_tokens_range(
|
|||
&line_index,
|
||||
highlights,
|
||||
snap.config.semantics_tokens_augments_syntax_tokens(),
|
||||
snap.config.highlighting_non_standard_tokens(Some(source_root)),
|
||||
snap.config.highlighting_non_standard_tokens(),
|
||||
);
|
||||
Ok(Some(semantic_tokens.into()))
|
||||
}
|
||||
|
@ -1991,8 +1986,8 @@ fn goto_type_action_links(
|
|||
snap: &GlobalStateSnapshot,
|
||||
nav_targets: &[HoverGotoTypeData],
|
||||
) -> Option<lsp_ext::CommandLinkGroup> {
|
||||
if nav_targets.is_empty()
|
||||
|| !snap.config.hover_actions().goto_type_def
|
||||
if !snap.config.hover_actions().goto_type_def
|
||||
|| nav_targets.is_empty()
|
||||
|| !snap.config.client_commands().goto_location
|
||||
{
|
||||
return None;
|
||||
|
@ -2237,6 +2232,30 @@ pub(crate) fn fetch_dependency_list(
|
|||
Ok(FetchDependencyListResult { crates: crate_infos })
|
||||
}
|
||||
|
||||
pub(crate) fn internal_testing_fetch_config(
|
||||
state: GlobalStateSnapshot,
|
||||
params: InternalTestingFetchConfigParams,
|
||||
) -> anyhow::Result<serde_json::Value> {
|
||||
let source_root = params
|
||||
.text_document
|
||||
.map(|it| {
|
||||
state
|
||||
.analysis
|
||||
.source_root_id(from_proto::file_id(&state, &it.uri)?)
|
||||
.map_err(anyhow::Error::from)
|
||||
})
|
||||
.transpose()?;
|
||||
serde_json::to_value(match &*params.config {
|
||||
"local" => state.config.assist(source_root).assist_emit_must_use,
|
||||
"global" => matches!(
|
||||
state.config.rustfmt(),
|
||||
RustfmtConfig::Rustfmt { enable_range_formatting: true, .. }
|
||||
),
|
||||
_ => return Err(anyhow::anyhow!("Unknown test config key: {}", params.config)),
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Searches for the directory of a Rust crate given this crate's root file path.
|
||||
///
|
||||
/// # Arguments
|
||||
|
|
|
@ -18,7 +18,6 @@ mod cargo_target_spec;
|
|||
mod diagnostics;
|
||||
mod diff;
|
||||
mod dispatch;
|
||||
mod global_state;
|
||||
mod hack_recover_crate_name;
|
||||
mod line_index;
|
||||
mod main_loop;
|
||||
|
@ -40,6 +39,7 @@ pub mod tracing {
|
|||
}
|
||||
|
||||
pub mod config;
|
||||
mod global_state;
|
||||
pub mod lsp;
|
||||
use self::lsp::ext as lsp_ext;
|
||||
|
||||
|
|
|
@ -17,6 +17,20 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::line_index::PositionEncoding;
|
||||
|
||||
pub enum InternalTestingFetchConfig {}
|
||||
|
||||
impl Request for InternalTestingFetchConfig {
|
||||
type Params = InternalTestingFetchConfigParams;
|
||||
type Result = serde_json::Value;
|
||||
const METHOD: &'static str = "rust-analyzer-internal/internalTestingFetchConfig";
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InternalTestingFetchConfigParams {
|
||||
pub text_document: Option<TextDocumentIdentifier>,
|
||||
pub config: String,
|
||||
}
|
||||
pub enum AnalyzerStatus {}
|
||||
|
||||
impl Request for AnalyzerStatus {
|
||||
|
|
|
@ -186,6 +186,11 @@ impl GlobalState {
|
|||
scheme: None,
|
||||
pattern: Some("**/Cargo.lock".into()),
|
||||
},
|
||||
lsp_types::DocumentFilter {
|
||||
language: None,
|
||||
scheme: None,
|
||||
pattern: Some("**/rust-analyzer.toml".into()),
|
||||
},
|
||||
]),
|
||||
},
|
||||
};
|
||||
|
@ -474,6 +479,7 @@ impl GlobalState {
|
|||
|
||||
fn update_diagnostics(&mut self) {
|
||||
let db = self.analysis_host.raw_database();
|
||||
// spawn a task per subscription?
|
||||
let subscriptions = {
|
||||
let vfs = &self.vfs.read().0;
|
||||
self.mem_docs
|
||||
|
@ -971,6 +977,8 @@ impl GlobalState {
|
|||
.on::<NO_RETRY, lsp_ext::ExternalDocs>(handlers::handle_open_docs)
|
||||
.on::<NO_RETRY, lsp_ext::OpenCargoToml>(handlers::handle_open_cargo_toml)
|
||||
.on::<NO_RETRY, lsp_ext::MoveItem>(handlers::handle_move_item)
|
||||
//
|
||||
.on::<NO_RETRY, lsp_ext::InternalTestingFetchConfig>(handlers::internal_testing_fetch_config)
|
||||
.finish();
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ use ide_db::{
|
|||
};
|
||||
use itertools::Itertools;
|
||||
use load_cargo::{load_proc_macro, ProjectFolders};
|
||||
use lsp_types::FileSystemWatcher;
|
||||
use proc_macro_api::ProcMacroServer;
|
||||
use project_model::{ManifestPath, ProjectWorkspace, ProjectWorkspaceKind, WorkspaceBuildScripts};
|
||||
use stdx::{format_to, thread::ThreadIntent};
|
||||
|
@ -442,40 +443,59 @@ impl GlobalState {
|
|||
let filter =
|
||||
self.workspaces.iter().flat_map(|ws| ws.to_roots()).filter(|it| it.is_local);
|
||||
|
||||
let watchers = if self.config.did_change_watched_files_relative_pattern_support() {
|
||||
// When relative patterns are supported by the client, prefer using them
|
||||
filter
|
||||
.flat_map(|root| {
|
||||
root.include.into_iter().flat_map(|base| {
|
||||
[(base.clone(), "**/*.rs"), (base, "**/Cargo.{lock,toml}")]
|
||||
let mut watchers: Vec<FileSystemWatcher> =
|
||||
if self.config.did_change_watched_files_relative_pattern_support() {
|
||||
// When relative patterns are supported by the client, prefer using them
|
||||
filter
|
||||
.flat_map(|root| {
|
||||
root.include.into_iter().flat_map(|base| {
|
||||
[
|
||||
(base.clone(), "**/*.rs"),
|
||||
(base.clone(), "**/Cargo.{lock,toml}"),
|
||||
(base, "**/rust-analyzer.toml"),
|
||||
]
|
||||
})
|
||||
})
|
||||
})
|
||||
.map(|(base, pat)| lsp_types::FileSystemWatcher {
|
||||
glob_pattern: lsp_types::GlobPattern::Relative(
|
||||
lsp_types::RelativePattern {
|
||||
base_uri: lsp_types::OneOf::Right(
|
||||
lsp_types::Url::from_file_path(base).unwrap(),
|
||||
),
|
||||
pattern: pat.to_owned(),
|
||||
},
|
||||
),
|
||||
kind: None,
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
// When they're not, integrate the base to make them into absolute patterns
|
||||
filter
|
||||
.flat_map(|root| {
|
||||
root.include.into_iter().flat_map(|base| {
|
||||
[format!("{base}/**/*.rs"), format!("{base}/**/Cargo.{{lock,toml}}")]
|
||||
.map(|(base, pat)| lsp_types::FileSystemWatcher {
|
||||
glob_pattern: lsp_types::GlobPattern::Relative(
|
||||
lsp_types::RelativePattern {
|
||||
base_uri: lsp_types::OneOf::Right(
|
||||
lsp_types::Url::from_file_path(base).unwrap(),
|
||||
),
|
||||
pattern: pat.to_owned(),
|
||||
},
|
||||
),
|
||||
kind: None,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
// When they're not, integrate the base to make them into absolute patterns
|
||||
filter
|
||||
.flat_map(|root| {
|
||||
root.include.into_iter().flat_map(|base| {
|
||||
[
|
||||
format!("{base}/**/*.rs"),
|
||||
format!("{base}/**/Cargo.{{toml,lock}}"),
|
||||
format!("{base}/**/rust-analyzer.toml"),
|
||||
]
|
||||
})
|
||||
})
|
||||
.map(|glob_pattern| lsp_types::FileSystemWatcher {
|
||||
glob_pattern: lsp_types::GlobPattern::String(glob_pattern),
|
||||
kind: None,
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
watchers.extend(
|
||||
iter::once(self.config.user_config_path().to_string())
|
||||
.chain(iter::once(self.config.root_ratoml_path().to_string()))
|
||||
.map(|glob_pattern| lsp_types::FileSystemWatcher {
|
||||
glob_pattern: lsp_types::GlobPattern::String(glob_pattern),
|
||||
kind: None,
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
.collect::<Vec<FileSystemWatcher>>(),
|
||||
);
|
||||
|
||||
let registration_options =
|
||||
lsp_types::DidChangeWatchedFilesRegistrationOptions { watchers };
|
||||
|
@ -547,7 +567,7 @@ impl GlobalState {
|
|||
version: self.vfs_config_version,
|
||||
});
|
||||
self.source_root_config = project_folders.source_root_config;
|
||||
self.local_roots_parent_map = self.source_root_config.source_root_parent_map();
|
||||
self.local_roots_parent_map = Arc::new(self.source_root_config.source_root_parent_map());
|
||||
|
||||
self.recreate_crate_graph(cause);
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ use tracing_tree::HierarchicalLayer;
|
|||
|
||||
use crate::tracing::hprof;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Config<T> {
|
||||
pub writer: T,
|
||||
pub filter: String,
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#![warn(rust_2018_idioms, unused_lifetimes)]
|
||||
#![allow(clippy::disallowed_types)]
|
||||
|
||||
mod ratoml;
|
||||
#[cfg(not(feature = "in-rust-tree"))]
|
||||
mod sourcegen;
|
||||
mod support;
|
||||
|
@ -30,15 +31,15 @@ use lsp_types::{
|
|||
InlayHint, InlayHintLabel, InlayHintParams, PartialResultParams, Position, Range,
|
||||
RenameFilesParams, TextDocumentItem, TextDocumentPositionParams, WorkDoneProgressParams,
|
||||
};
|
||||
|
||||
use rust_analyzer::lsp::ext::{OnEnter, Runnables, RunnablesParams, UnindexedProject};
|
||||
use serde_json::json;
|
||||
use stdx::format_to_acc;
|
||||
use test_utils::skip_slow_tests;
|
||||
|
||||
use crate::{
|
||||
support::{project, Project},
|
||||
testdir::TestDir,
|
||||
};
|
||||
use test_utils::skip_slow_tests;
|
||||
use testdir::TestDir;
|
||||
|
||||
use crate::support::{project, Project};
|
||||
|
||||
#[test]
|
||||
fn completes_items_from_standard_library() {
|
||||
|
|
947
crates/rust-analyzer/tests/slow-tests/ratoml.rs
Normal file
947
crates/rust-analyzer/tests/slow-tests/ratoml.rs
Normal file
|
@ -0,0 +1,947 @@
|
|||
use crate::support::{Project, Server};
|
||||
use crate::testdir::TestDir;
|
||||
use lsp_types::{
|
||||
notification::{DidChangeTextDocument, DidOpenTextDocument, DidSaveTextDocument},
|
||||
DidChangeTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams,
|
||||
TextDocumentContentChangeEvent, TextDocumentIdentifier, TextDocumentItem, Url,
|
||||
VersionedTextDocumentIdentifier,
|
||||
};
|
||||
use paths::Utf8PathBuf;
|
||||
|
||||
use rust_analyzer::lsp::ext::{InternalTestingFetchConfig, InternalTestingFetchConfigParams};
|
||||
use serde_json::json;
|
||||
|
||||
enum QueryType {
|
||||
Local,
|
||||
/// A query whose config key is a part of the global configs, so that
|
||||
/// testing for changes to this config means testing if global changes
|
||||
/// take affect.
|
||||
Global,
|
||||
}
|
||||
|
||||
struct RatomlTest {
|
||||
urls: Vec<Url>,
|
||||
server: Server,
|
||||
tmp_path: Utf8PathBuf,
|
||||
user_config_dir: Utf8PathBuf,
|
||||
}
|
||||
|
||||
impl RatomlTest {
|
||||
const EMIT_MUST_USE: &'static str = r#"assist.emitMustUse = true"#;
|
||||
const EMIT_MUST_NOT_USE: &'static str = r#"assist.emitMustUse = false"#;
|
||||
|
||||
const GLOBAL_TRAIT_ASSOC_ITEMS_ZERO: &'static str = r#"hover.show.traitAssocItems = 0"#;
|
||||
|
||||
fn new(
|
||||
fixtures: Vec<&str>,
|
||||
roots: Vec<&str>,
|
||||
client_config: Option<serde_json::Value>,
|
||||
) -> Self {
|
||||
let tmp_dir = TestDir::new();
|
||||
let tmp_path = tmp_dir.path().to_owned();
|
||||
|
||||
let full_fixture = fixtures.join("\n");
|
||||
|
||||
let user_cnf_dir = TestDir::new();
|
||||
let user_config_dir = user_cnf_dir.path().to_owned();
|
||||
|
||||
let mut project =
|
||||
Project::with_fixture(&full_fixture).tmp_dir(tmp_dir).user_config_dir(user_cnf_dir);
|
||||
|
||||
for root in roots {
|
||||
project = project.root(root);
|
||||
}
|
||||
|
||||
if let Some(client_config) = client_config {
|
||||
project = project.with_config(client_config);
|
||||
}
|
||||
|
||||
let server = project.server().wait_until_workspace_is_loaded();
|
||||
|
||||
let mut case = Self { urls: vec![], server, tmp_path, user_config_dir };
|
||||
let urls = fixtures.iter().map(|fixture| case.fixture_path(fixture)).collect::<Vec<_>>();
|
||||
case.urls = urls;
|
||||
case
|
||||
}
|
||||
|
||||
fn fixture_path(&self, fixture: &str) -> Url {
|
||||
let mut lines = fixture.trim().split('\n');
|
||||
|
||||
let mut path =
|
||||
lines.next().expect("All files in a fixture are expected to have at least one line.");
|
||||
|
||||
if path.starts_with("//- minicore") {
|
||||
path = lines.next().expect("A minicore line must be followed by a path.")
|
||||
}
|
||||
|
||||
path = path.strip_prefix("//- ").expect("Path must be preceded by a //- prefix ");
|
||||
|
||||
let spl = path[1..].split('/');
|
||||
let mut path = self.tmp_path.clone();
|
||||
|
||||
let mut spl = spl.into_iter();
|
||||
if let Some(first) = spl.next() {
|
||||
if first == "$$CONFIG_DIR$$" {
|
||||
path = self.user_config_dir.clone();
|
||||
} else {
|
||||
path = path.join(first);
|
||||
}
|
||||
}
|
||||
for piece in spl {
|
||||
path = path.join(piece);
|
||||
}
|
||||
|
||||
Url::parse(
|
||||
format!(
|
||||
"file://{}",
|
||||
path.into_string().to_owned().replace("C:\\", "/c:/").replace('\\', "/")
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn create(&mut self, fixture_path: &str, text: String) {
|
||||
let url = self.fixture_path(fixture_path);
|
||||
|
||||
self.server.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
|
||||
text_document: TextDocumentItem {
|
||||
uri: url.clone(),
|
||||
language_id: "rust".to_owned(),
|
||||
version: 0,
|
||||
text: String::new(),
|
||||
},
|
||||
});
|
||||
|
||||
self.server.notification::<DidChangeTextDocument>(DidChangeTextDocumentParams {
|
||||
text_document: VersionedTextDocumentIdentifier { uri: url, version: 0 },
|
||||
content_changes: vec![TextDocumentContentChangeEvent {
|
||||
range: None,
|
||||
range_length: None,
|
||||
text,
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
fn delete(&mut self, file_idx: usize) {
|
||||
self.server.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
|
||||
text_document: TextDocumentItem {
|
||||
uri: self.urls[file_idx].clone(),
|
||||
language_id: "rust".to_owned(),
|
||||
version: 0,
|
||||
text: "".to_owned(),
|
||||
},
|
||||
});
|
||||
|
||||
// See if deleting ratoml file will make the config of interest to return to its default value.
|
||||
self.server.notification::<DidSaveTextDocument>(DidSaveTextDocumentParams {
|
||||
text_document: TextDocumentIdentifier { uri: self.urls[file_idx].clone() },
|
||||
text: Some("".to_owned()),
|
||||
});
|
||||
}
|
||||
|
||||
fn edit(&mut self, file_idx: usize, text: String) {
|
||||
self.server.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
|
||||
text_document: TextDocumentItem {
|
||||
uri: self.urls[file_idx].clone(),
|
||||
language_id: "rust".to_owned(),
|
||||
version: 0,
|
||||
text: String::new(),
|
||||
},
|
||||
});
|
||||
|
||||
self.server.notification::<DidChangeTextDocument>(DidChangeTextDocumentParams {
|
||||
text_document: VersionedTextDocumentIdentifier {
|
||||
uri: self.urls[file_idx].clone(),
|
||||
version: 0,
|
||||
},
|
||||
content_changes: vec![TextDocumentContentChangeEvent {
|
||||
range: None,
|
||||
range_length: None,
|
||||
text,
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
fn query(&self, query: QueryType, source_file_idx: usize) -> bool {
|
||||
let config = match query {
|
||||
QueryType::Local => "local".to_owned(),
|
||||
QueryType::Global => "global".to_owned(),
|
||||
};
|
||||
let res = self.server.send_request::<InternalTestingFetchConfig>(
|
||||
InternalTestingFetchConfigParams {
|
||||
text_document: Some(TextDocumentIdentifier {
|
||||
uri: self.urls[source_file_idx].clone(),
|
||||
}),
|
||||
config,
|
||||
},
|
||||
);
|
||||
res.as_bool().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
// /// Check if we are listening for changes in user's config file ( e.g on Linux `~/.config/rust-analyzer/.rust-analyzer.toml`)
|
||||
// #[test]
|
||||
// #[cfg(target_os = "windows")]
|
||||
// fn listen_to_user_config_scenario_windows() {
|
||||
// todo!()
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// #[cfg(target_os = "linux")]
|
||||
// fn listen_to_user_config_scenario_linux() {
|
||||
// todo!()
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// #[cfg(target_os = "macos")]
|
||||
// fn listen_to_user_config_scenario_macos() {
|
||||
// todo!()
|
||||
// }
|
||||
|
||||
/// Check if made changes have had any effect on
|
||||
/// the client config.
|
||||
#[test]
|
||||
fn ratoml_client_config_basic() {
|
||||
let server = RatomlTest::new(
|
||||
vec![
|
||||
r#"
|
||||
//- /p1/Cargo.toml
|
||||
[package]
|
||||
name = "p1"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
"#,
|
||||
r#"//- /p1/src/lib.rs
|
||||
enum Value {
|
||||
Number(i32),
|
||||
Text(String),
|
||||
}"#,
|
||||
],
|
||||
vec!["p1"],
|
||||
Some(json!({
|
||||
"assist" : {
|
||||
"emitMustUse" : true
|
||||
}
|
||||
})),
|
||||
);
|
||||
|
||||
assert!(server.query(QueryType::Local, 1));
|
||||
}
|
||||
|
||||
/// Checks if client config can be modified.
|
||||
/// FIXME @alibektas : This test is atm not valid.
|
||||
/// Asking for client config from the client is a 2 way communication
|
||||
/// which we cannot imitate with the current slow-tests infrastructure.
|
||||
/// See rust-analyzer::handlers::notifications#197
|
||||
// #[test]
|
||||
// fn client_config_update() {
|
||||
// setup();
|
||||
|
||||
// let server = RatomlTest::new(
|
||||
// vec![
|
||||
// r#"
|
||||
// //- /p1/Cargo.toml
|
||||
// [package]
|
||||
// name = "p1"
|
||||
// version = "0.1.0"
|
||||
// edition = "2021"
|
||||
// "#,
|
||||
// r#"
|
||||
// //- /p1/src/lib.rs
|
||||
// enum Value {
|
||||
// Number(i32),
|
||||
// Text(String),
|
||||
// }"#,
|
||||
// ],
|
||||
// vec!["p1"],
|
||||
// None,
|
||||
// );
|
||||
|
||||
// assert!(!server.query(QueryType::AssistEmitMustUse, 1));
|
||||
|
||||
// // a.notification::<DidChangeConfiguration>(DidChangeConfigurationParams {
|
||||
// // settings: json!({
|
||||
// // "assists" : {
|
||||
// // "emitMustUse" : true
|
||||
// // }
|
||||
// // }),
|
||||
// // });
|
||||
|
||||
// assert!(server.query(QueryType::AssistEmitMustUse, 1));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn ratoml_create_ratoml_basic() {
|
||||
// let server = RatomlTest::new(
|
||||
// vec![
|
||||
// r#"
|
||||
// //- /p1/Cargo.toml
|
||||
// [package]
|
||||
// name = "p1"
|
||||
// version = "0.1.0"
|
||||
// edition = "2021"
|
||||
// "#,
|
||||
// r#"
|
||||
// //- /p1/rust-analyzer.toml
|
||||
// assist.emitMustUse = true
|
||||
// "#,
|
||||
// r#"
|
||||
// //- /p1/src/lib.rs
|
||||
// enum Value {
|
||||
// Number(i32),
|
||||
// Text(String),
|
||||
// }
|
||||
// "#,
|
||||
// ],
|
||||
// vec!["p1"],
|
||||
// None,
|
||||
// );
|
||||
|
||||
// assert!(server.query(QueryType::AssistEmitMustUse, 2));
|
||||
// }
|
||||
|
||||
#[test]
|
||||
#[ignore = "the user config is currently not being watched on startup, fix this"]
|
||||
fn ratoml_user_config_detected() {
|
||||
let server = RatomlTest::new(
|
||||
vec![
|
||||
r#"
|
||||
//- /$$CONFIG_DIR$$/rust-analyzer/rust-analyzer.toml
|
||||
assist.emitMustUse = true
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/Cargo.toml
|
||||
[package]
|
||||
name = "p1"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
"#,
|
||||
r#"//- /p1/src/lib.rs
|
||||
enum Value {
|
||||
Number(i32),
|
||||
Text(String),
|
||||
}"#,
|
||||
],
|
||||
vec!["p1"],
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(server.query(QueryType::Local, 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "the user config is currently not being watched on startup, fix this"]
|
||||
fn ratoml_create_user_config() {
|
||||
let mut server = RatomlTest::new(
|
||||
vec![
|
||||
r#"
|
||||
//- /p1/Cargo.toml
|
||||
[package]
|
||||
name = "p1"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/src/lib.rs
|
||||
enum Value {
|
||||
Number(i32),
|
||||
Text(String),
|
||||
}"#,
|
||||
],
|
||||
vec!["p1"],
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(!server.query(QueryType::Local, 1));
|
||||
server.create(
|
||||
"//- /$$CONFIG_DIR$$/rust-analyzer/rust-analyzer.toml",
|
||||
RatomlTest::EMIT_MUST_USE.to_owned(),
|
||||
);
|
||||
assert!(server.query(QueryType::Local, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "the user config is currently not being watched on startup, fix this"]
|
||||
fn ratoml_modify_user_config() {
|
||||
let mut server = RatomlTest::new(
|
||||
vec![
|
||||
r#"
|
||||
//- /p1/Cargo.toml
|
||||
[package]
|
||||
name = "p1"
|
||||
version = "0.1.0"
|
||||
edition = "2021""#,
|
||||
r#"
|
||||
//- /p1/src/lib.rs
|
||||
enum Value {
|
||||
Number(i32),
|
||||
Text(String),
|
||||
}"#,
|
||||
r#"
|
||||
//- /$$CONFIG_DIR$$/rust-analyzer/rust-analyzer.toml
|
||||
assist.emitMustUse = true"#,
|
||||
],
|
||||
vec!["p1"],
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(server.query(QueryType::Local, 1));
|
||||
server.edit(2, String::new());
|
||||
assert!(!server.query(QueryType::Local, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "the user config is currently not being watched on startup, fix this"]
|
||||
fn ratoml_delete_user_config() {
|
||||
let mut server = RatomlTest::new(
|
||||
vec![
|
||||
r#"
|
||||
//- /p1/Cargo.toml
|
||||
[package]
|
||||
name = "p1"
|
||||
version = "0.1.0"
|
||||
edition = "2021""#,
|
||||
r#"
|
||||
//- /p1/src/lib.rs
|
||||
enum Value {
|
||||
Number(i32),
|
||||
Text(String),
|
||||
}"#,
|
||||
r#"
|
||||
//- /$$CONFIG_DIR$$/rust-analyzer/rust-analyzer.toml
|
||||
assist.emitMustUse = true"#,
|
||||
],
|
||||
vec!["p1"],
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(server.query(QueryType::Local, 1));
|
||||
server.delete(2);
|
||||
assert!(!server.query(QueryType::Local, 1));
|
||||
}
|
||||
// #[test]
|
||||
// fn delete_user_config() {
|
||||
// todo!()
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn modify_client_config() {
|
||||
// todo!()
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn ratoml_inherit_config_from_ws_root() {
|
||||
let server = RatomlTest::new(
|
||||
vec![
|
||||
r#"
|
||||
//- /p1/Cargo.toml
|
||||
workspace = { members = ["p2"] }
|
||||
[package]
|
||||
name = "p1"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/rust-analyzer.toml
|
||||
assist.emitMustUse = true
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/p2/Cargo.toml
|
||||
[package]
|
||||
name = "p2"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/p2/src/lib.rs
|
||||
enum Value {
|
||||
Number(i32),
|
||||
Text(String),
|
||||
}"#,
|
||||
r#"
|
||||
//- /p1/src/lib.rs
|
||||
pub fn add(left: usize, right: usize) -> usize {
|
||||
left + right
|
||||
}
|
||||
"#,
|
||||
],
|
||||
vec!["p1"],
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(server.query(QueryType::Local, 3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ratoml_modify_ratoml_at_ws_root() {
|
||||
let mut server = RatomlTest::new(
|
||||
vec![
|
||||
r#"
|
||||
//- /p1/Cargo.toml
|
||||
workspace = { members = ["p2"] }
|
||||
[package]
|
||||
name = "p1"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/rust-analyzer.toml
|
||||
assist.emitMustUse = false
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/p2/Cargo.toml
|
||||
[package]
|
||||
name = "p2"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/p2/src/lib.rs
|
||||
enum Value {
|
||||
Number(i32),
|
||||
Text(String),
|
||||
}"#,
|
||||
r#"
|
||||
//- /p1/src/lib.rs
|
||||
pub fn add(left: usize, right: usize) -> usize {
|
||||
left + right
|
||||
}
|
||||
"#,
|
||||
],
|
||||
vec!["p1"],
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(!server.query(QueryType::Local, 3));
|
||||
server.edit(1, "assist.emitMustUse = true".to_owned());
|
||||
assert!(server.query(QueryType::Local, 3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ratoml_delete_ratoml_at_ws_root() {
|
||||
let mut server = RatomlTest::new(
|
||||
vec![
|
||||
r#"
|
||||
//- /p1/Cargo.toml
|
||||
workspace = { members = ["p2"] }
|
||||
[package]
|
||||
name = "p1"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/rust-analyzer.toml
|
||||
assist.emitMustUse = true
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/p2/Cargo.toml
|
||||
[package]
|
||||
name = "p2"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/p2/src/lib.rs
|
||||
enum Value {
|
||||
Number(i32),
|
||||
Text(String),
|
||||
}"#,
|
||||
r#"
|
||||
//- /p1/src/lib.rs
|
||||
pub fn add(left: usize, right: usize) -> usize {
|
||||
left + right
|
||||
}
|
||||
"#,
|
||||
],
|
||||
vec!["p1"],
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(server.query(QueryType::Local, 3));
|
||||
server.delete(1);
|
||||
assert!(!server.query(QueryType::Local, 3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ratoml_add_immediate_child_to_ws_root() {
|
||||
let mut server = RatomlTest::new(
|
||||
vec![
|
||||
r#"
|
||||
//- /p1/Cargo.toml
|
||||
workspace = { members = ["p2"] }
|
||||
[package]
|
||||
name = "p1"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/rust-analyzer.toml
|
||||
assist.emitMustUse = true
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/p2/Cargo.toml
|
||||
[package]
|
||||
name = "p2"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/p2/src/lib.rs
|
||||
enum Value {
|
||||
Number(i32),
|
||||
Text(String),
|
||||
}"#,
|
||||
r#"
|
||||
//- /p1/src/lib.rs
|
||||
pub fn add(left: usize, right: usize) -> usize {
|
||||
left + right
|
||||
}
|
||||
"#,
|
||||
],
|
||||
vec!["p1"],
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(server.query(QueryType::Local, 3));
|
||||
server.create("//- /p1/p2/rust-analyzer.toml", RatomlTest::EMIT_MUST_NOT_USE.to_owned());
|
||||
assert!(!server.query(QueryType::Local, 3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ratoml_rm_ws_root_ratoml_child_has_client_as_parent_now() {
|
||||
let mut server = RatomlTest::new(
|
||||
vec![
|
||||
r#"
|
||||
//- /p1/Cargo.toml
|
||||
workspace = { members = ["p2"] }
|
||||
[package]
|
||||
name = "p1"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/rust-analyzer.toml
|
||||
assist.emitMustUse = true
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/p2/Cargo.toml
|
||||
[package]
|
||||
name = "p2"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/p2/src/lib.rs
|
||||
enum Value {
|
||||
Number(i32),
|
||||
Text(String),
|
||||
}"#,
|
||||
r#"
|
||||
//- /p1/src/lib.rs
|
||||
pub fn add(left: usize, right: usize) -> usize {
|
||||
left + right
|
||||
}
|
||||
"#,
|
||||
],
|
||||
vec!["p1"],
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(server.query(QueryType::Local, 3));
|
||||
server.delete(1);
|
||||
assert!(!server.query(QueryType::Local, 3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ratoml_crates_both_roots() {
|
||||
let server = RatomlTest::new(
|
||||
vec![
|
||||
r#"
|
||||
//- /p1/Cargo.toml
|
||||
workspace = { members = ["p2"] }
|
||||
[package]
|
||||
name = "p1"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/rust-analyzer.toml
|
||||
assist.emitMustUse = true
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/p2/Cargo.toml
|
||||
[package]
|
||||
name = "p2"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/p2/src/lib.rs
|
||||
enum Value {
|
||||
Number(i32),
|
||||
Text(String),
|
||||
}"#,
|
||||
r#"
|
||||
//- /p1/src/lib.rs
|
||||
enum Value {
|
||||
Number(i32),
|
||||
Text(String),
|
||||
}"#,
|
||||
],
|
||||
vec!["p1", "p2"],
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(server.query(QueryType::Local, 3));
|
||||
assert!(server.query(QueryType::Local, 4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ratoml_multiple_ratoml_in_single_source_root() {
|
||||
let server = RatomlTest::new(
|
||||
vec![
|
||||
r#"
|
||||
//- /p1/Cargo.toml
|
||||
[package]
|
||||
name = "p1"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/rust-analyzer.toml
|
||||
assist.emitMustUse = true
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/src/rust-analyzer.toml
|
||||
assist.emitMustUse = false
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/src/lib.rs
|
||||
enum Value {
|
||||
Number(i32),
|
||||
Text(String),
|
||||
}
|
||||
"#,
|
||||
],
|
||||
vec!["p1"],
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(server.query(QueryType::Local, 3));
|
||||
|
||||
let server = RatomlTest::new(
|
||||
vec![
|
||||
r#"
|
||||
//- /p1/Cargo.toml
|
||||
[package]
|
||||
name = "p1"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/src/rust-analyzer.toml
|
||||
assist.emitMustUse = false
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/rust-analyzer.toml
|
||||
assist.emitMustUse = true
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/src/lib.rs
|
||||
enum Value {
|
||||
Number(i32),
|
||||
Text(String),
|
||||
}
|
||||
"#,
|
||||
],
|
||||
vec!["p1"],
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(server.query(QueryType::Local, 3));
|
||||
}
|
||||
|
||||
/// If a root is non-local, so we cannot find what its parent is
|
||||
/// in our `config.local_root_parent_map`. So if any config should
|
||||
/// apply, it must be looked for starting from the client level.
|
||||
/// FIXME @alibektas : "locality" is according to ra that, which is simply in the file system.
|
||||
/// This doesn't really help us with what we want to achieve here.
|
||||
// #[test]
|
||||
// fn ratoml_non_local_crates_start_inheriting_from_client() {
|
||||
// let server = RatomlTest::new(
|
||||
// vec![
|
||||
// r#"
|
||||
// //- /p1/Cargo.toml
|
||||
// [package]
|
||||
// name = "p1"
|
||||
// version = "0.1.0"
|
||||
// edition = "2021"
|
||||
|
||||
// [dependencies]
|
||||
// p2 = { path = "../p2" }
|
||||
// #,
|
||||
// r#"
|
||||
// //- /p1/src/lib.rs
|
||||
// enum Value {
|
||||
// Number(i32),
|
||||
// Text(String),
|
||||
// }
|
||||
|
||||
// use p2;
|
||||
|
||||
// pub fn add(left: usize, right: usize) -> usize {
|
||||
// p2::add(left, right)
|
||||
// }
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
|
||||
// #[test]
|
||||
// fn it_works() {
|
||||
// let result = add(2, 2);
|
||||
// assert_eq!(result, 4);
|
||||
// }
|
||||
// }"#,
|
||||
// r#"
|
||||
// //- /p2/Cargo.toml
|
||||
// [package]
|
||||
// name = "p2"
|
||||
// version = "0.1.0"
|
||||
// edition = "2021"
|
||||
|
||||
// # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
// [dependencies]
|
||||
// "#,
|
||||
// r#"
|
||||
// //- /p2/rust-analyzer.toml
|
||||
// # DEF
|
||||
// assist.emitMustUse = true
|
||||
// "#,
|
||||
// r#"
|
||||
// //- /p2/src/lib.rs
|
||||
// enum Value {
|
||||
// Number(i32),
|
||||
// Text(String),
|
||||
// }"#,
|
||||
// ],
|
||||
// vec!["p1", "p2"],
|
||||
// None,
|
||||
// );
|
||||
|
||||
// assert!(!server.query(QueryType::AssistEmitMustUse, 5));
|
||||
// }
|
||||
|
||||
/// Having a ratoml file at the root of a project enables
|
||||
/// configuring global level configurations as well.
|
||||
#[test]
|
||||
fn ratoml_in_root_is_global() {
|
||||
let server = RatomlTest::new(
|
||||
vec![
|
||||
r#"
|
||||
//- /p1/Cargo.toml
|
||||
[package]
|
||||
name = "p1"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
"#,
|
||||
r#"
|
||||
//- /rust-analyzer.toml
|
||||
hover.show.traitAssocItems = 4
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/src/lib.rs
|
||||
trait RandomTrait {
|
||||
type B;
|
||||
fn abc() -> i32;
|
||||
fn def() -> i64;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let a = RandomTrait;
|
||||
}"#,
|
||||
],
|
||||
vec![],
|
||||
None,
|
||||
);
|
||||
|
||||
server.query(QueryType::Global, 2);
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
// #[test]
|
||||
// FIXME: Re-enable this test when we have a global config we can check again
|
||||
fn ratoml_root_is_updateable() {
|
||||
let mut server = RatomlTest::new(
|
||||
vec![
|
||||
r#"
|
||||
//- /p1/Cargo.toml
|
||||
[package]
|
||||
name = "p1"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
"#,
|
||||
r#"
|
||||
//- /rust-analyzer.toml
|
||||
hover.show.traitAssocItems = 4
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/src/lib.rs
|
||||
trait RandomTrait {
|
||||
type B;
|
||||
fn abc() -> i32;
|
||||
fn def() -> i64;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let a = RandomTrait;
|
||||
}"#,
|
||||
],
|
||||
vec![],
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(server.query(QueryType::Global, 2));
|
||||
server.edit(1, RatomlTest::GLOBAL_TRAIT_ASSOC_ITEMS_ZERO.to_owned());
|
||||
assert!(!server.query(QueryType::Global, 2));
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
// #[test]
|
||||
// FIXME: Re-enable this test when we have a global config we can check again
|
||||
fn ratoml_root_is_deletable() {
|
||||
let mut server = RatomlTest::new(
|
||||
vec![
|
||||
r#"
|
||||
//- /p1/Cargo.toml
|
||||
[package]
|
||||
name = "p1"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
"#,
|
||||
r#"
|
||||
//- /rust-analyzer.toml
|
||||
hover.show.traitAssocItems = 4
|
||||
"#,
|
||||
r#"
|
||||
//- /p1/src/lib.rs
|
||||
trait RandomTrait {
|
||||
type B;
|
||||
fn abc() -> i32;
|
||||
fn def() -> i64;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let a = RandomTrait;
|
||||
}"#,
|
||||
],
|
||||
vec![],
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(server.query(QueryType::Global, 2));
|
||||
server.delete(1);
|
||||
assert!(!server.query(QueryType::Global, 2));
|
||||
}
|
|
@ -9,7 +9,10 @@ use crossbeam_channel::{after, select, Receiver};
|
|||
use lsp_server::{Connection, Message, Notification, Request};
|
||||
use lsp_types::{notification::Exit, request::Shutdown, TextDocumentIdentifier, Url};
|
||||
use paths::{Utf8Path, Utf8PathBuf};
|
||||
use rust_analyzer::{config::Config, lsp, main_loop};
|
||||
use rust_analyzer::{
|
||||
config::{Config, ConfigChange, ConfigErrors},
|
||||
lsp, main_loop,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use serde_json::{json, to_string_pretty, Value};
|
||||
use test_utils::FixtureWithProjectMeta;
|
||||
|
@ -24,6 +27,7 @@ pub(crate) struct Project<'a> {
|
|||
roots: Vec<Utf8PathBuf>,
|
||||
config: serde_json::Value,
|
||||
root_dir_contains_symlink: bool,
|
||||
user_config_path: Option<Utf8PathBuf>,
|
||||
}
|
||||
|
||||
impl Project<'_> {
|
||||
|
@ -47,9 +51,15 @@ impl Project<'_> {
|
|||
}
|
||||
}),
|
||||
root_dir_contains_symlink: false,
|
||||
user_config_path: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn user_config_dir(mut self, config_path_dir: TestDir) -> Self {
|
||||
self.user_config_path = Some(config_path_dir.path().to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn tmp_dir(mut self, tmp_dir: TestDir) -> Self {
|
||||
self.tmp_dir = Some(tmp_dir);
|
||||
self
|
||||
|
@ -111,10 +121,17 @@ impl Project<'_> {
|
|||
assert!(proc_macro_names.is_empty());
|
||||
assert!(mini_core.is_none());
|
||||
assert!(toolchain.is_none());
|
||||
|
||||
for entry in fixture {
|
||||
let path = tmp_dir.path().join(&entry.path['/'.len_utf8()..]);
|
||||
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
fs::write(path.as_path(), entry.text.as_bytes()).unwrap();
|
||||
if let Some(pth) = entry.path.strip_prefix("/$$CONFIG_DIR$$") {
|
||||
let path = self.user_config_path.clone().unwrap().join(&pth['/'.len_utf8()..]);
|
||||
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
fs::write(path.as_path(), entry.text.as_bytes()).unwrap();
|
||||
} else {
|
||||
let path = tmp_dir.path().join(&entry.path['/'.len_utf8()..]);
|
||||
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
fs::write(path.as_path(), entry.text.as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let tmp_dir_path = AbsPathBuf::assert(tmp_dir.path().to_path_buf());
|
||||
|
@ -184,8 +201,16 @@ impl Project<'_> {
|
|||
},
|
||||
roots,
|
||||
None,
|
||||
self.user_config_path,
|
||||
);
|
||||
config.update(self.config).expect("invalid config");
|
||||
let mut change = ConfigChange::default();
|
||||
|
||||
change.change_client_config(self.config);
|
||||
|
||||
let error_sink: ConfigErrors;
|
||||
(config, error_sink, _) = config.apply_change(change);
|
||||
assert!(error_sink.is_empty(), "{error_sink:?}");
|
||||
|
||||
config.rediscover_workspaces();
|
||||
|
||||
Server::new(tmp_dir.keep(), config)
|
||||
|
|
|
@ -185,27 +185,6 @@ Zlib OR Apache-2.0 OR MIT
|
|||
}
|
||||
|
||||
fn check_test_attrs(path: &Path, text: &str) {
|
||||
let ignore_rule =
|
||||
"https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/style.md#ignore";
|
||||
let need_ignore: &[&str] = &[
|
||||
// This file.
|
||||
"slow-tests/tidy.rs",
|
||||
// Special case to run `#[ignore]` tests.
|
||||
"ide/src/runnables.rs",
|
||||
// A legit test which needs to be ignored, as it takes too long to run
|
||||
// :(
|
||||
"hir-def/src/nameres/collector.rs",
|
||||
// Long sourcegen test to generate lint completions.
|
||||
"ide-db/src/tests/sourcegen_lints.rs",
|
||||
// Obviously needs ignore.
|
||||
"ide-assists/src/handlers/toggle_ignore.rs",
|
||||
// See above.
|
||||
"ide-assists/src/tests/generated.rs",
|
||||
];
|
||||
if text.contains("#[ignore") && !need_ignore.iter().any(|p| path.ends_with(p)) {
|
||||
panic!("\ndon't `#[ignore]` tests, see:\n\n {ignore_rule}\n\n {}\n", path.display(),)
|
||||
}
|
||||
|
||||
let panic_rule =
|
||||
"https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/style.md#should_panic";
|
||||
let need_panic: &[&str] = &[
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<!---
|
||||
lsp/ext.rs hash: 1babf76a3c2cef3b
|
||||
lsp/ext.rs hash: a85ec97f07c6a2e3
|
||||
|
||||
If you need to change the above hash to make the test pass, please check if you
|
||||
need to adjust this doc as well and ping this issue:
|
||||
|
|
Loading…
Reference in a new issue