about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAli Bektas <bektasali@protonmail.com>2024-04-18 16:28:32 +0200
committerLukas Wirth <lukastw97@gmail.com>2024-06-05 10:45:19 +0200
commit53b5038b5409fc8a85e1a054cb18ce6da4a4431c (patch)
treef895576fcfca2b698841afa62fa43b715631aa46
parent4c007c86bb9e96bff51033c61f7c5d1a31472eae (diff)
downloadrust-53b5038b5409fc8a85e1a054cb18ce6da4a4431c.tar.gz
rust-53b5038b5409fc8a85e1a054cb18ce6da4a4431c.zip
Apply suggested changes
-rw-r--r--src/tools/rust-analyzer/Cargo.lock10
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/lib.rs9
-rw-r--r--src/tools/rust-analyzer/crates/paths/src/lib.rs18
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml1
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs16
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs10
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs760
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs2
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs1
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs108
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs10
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs26
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs2
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs5
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs117
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/tracing/config.rs1
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs1048
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs33
18 files changed, 1878 insertions, 299 deletions
diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock
index 3558c39bb32..d150c31c48b 100644
--- a/src/tools/rust-analyzer/Cargo.lock
+++ b/src/tools/rust-analyzer/Cargo.lock
@@ -329,6 +329,15 @@ dependencies = [
 ]
 
 [[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"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1665,6 +1674,7 @@ dependencies = [
  "anyhow",
  "cfg",
  "crossbeam-channel",
+ "dirs",
  "dissimilar",
  "expect-test",
  "flycheck",
diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs
index 431aa30e56f..e9408bf8976 100644
--- a/src/tools/rust-analyzer/crates/ide/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs
@@ -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,
diff --git a/src/tools/rust-analyzer/crates/paths/src/lib.rs b/src/tools/rust-analyzer/crates/paths/src/lib.rs
index 1dda02e3f10..7d7cf0220e6 100644
--- a/src/tools/rust-analyzer/crates/paths/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/paths/src/lib.rs
@@ -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(&mut self, suffix: &str) {
+        self.0.push(suffix)
+    }
 }
 
 impl fmt::Display for AbsPathBuf {
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml
index 34b3e493140..8ff7235b8fa 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml
@@ -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"
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs
index 9daae914d79..7e58cd70c49 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs
@@ -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, ConfigError},
+    from_json,
+};
 use semver::Version;
 use tracing_subscriber::fmt::writer::BoxMakeWriter;
 use vfs::AbsPathBuf;
@@ -220,16 +224,20 @@ 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 mut error_sink = ConfigError::default();
+        config = config.apply_change(change, &mut error_sink);
+        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();
         }
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs
index aef2c1be224..b2d70562890 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs
@@ -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, ConfigError},
     line_index::{LineEndings, LineIndex, PositionEncoding},
 };
 
@@ -35,12 +37,18 @@ 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 mut error_sink = ConfigError::default();
+            config = config.apply_change(change, &mut error_sink);
+            // 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(
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
index a8d1e72aed9..51664dd799e 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
@@ -1,14 +1,12 @@
 //! Config used by the language server.
 //!
-//! We currently get this config from `initialize` LSP request, which is not the
-//! best way to do it, but was the simplest thing we could implement.
-//!
 //! Of particular interest is the `feature_flags` hash map: while other fields
 //! configure the server itself, feature flags are passed into analysis, and
 //! tweak things like automatic insertion of `()` in completions.
 use std::{fmt, iter, ops::Not};
 
 use cfg::{CfgAtom, CfgDiff};
+use dirs::config_dir;
 use flycheck::{CargoOptions, FlycheckConfig};
 use ide::{
     AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode,
@@ -29,9 +27,14 @@ use project_model::{
 };
 use rustc_hash::{FxHashMap, FxHashSet};
 use semver::Version;
-use serde::{de::DeserializeOwned, Deserialize, Serialize};
+use serde::{
+    de::{DeserializeOwned, Error},
+    ser::SerializeStruct,
+    Deserialize, Serialize,
+};
 use stdx::format_to_acc;
-use vfs::{AbsPath, AbsPathBuf};
+use triomphe::Arc;
+use vfs::{AbsPath, AbsPathBuf, FileId, VfsPath};
 
 use crate::{
     caps::completion_item_edit_resolve,
@@ -67,12 +70,6 @@ config_data! {
     ///
     /// A config is searched for by traversing a "config tree" in a bottom up fashion. It is chosen by the nearest first principle.
     global: struct GlobalDefaultConfigData <- GlobalConfigInput -> {
-        /// Whether to insert #[must_use] when generating `as_` methods
-        /// for enum variants.
-        assist_emitMustUse: bool               = false,
-        /// Placeholder expression to use for missing expressions in assists.
-        assist_expressionFillDefault: ExprFillDefaultDef              = ExprFillDefaultDef::Todo,
-
         /// Warm up caches on project load.
         cachePriming_enable: bool = true,
         /// How many worker threads to handle priming caches. The default `0` means to pick automatically.
@@ -250,6 +247,71 @@ config_data! {
         /// If false, `-p <package>` will be passed instead.
         check_workspace: bool = true,
 
+
+        /// Toggles the additional completions that automatically add imports when completed.
+        /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.
+        completion_autoimport_enable: bool       = true,
+        /// Toggles the additional completions that automatically show method calls and field accesses
+        /// with `self` prefixed to them when inside a method.
+        completion_autoself_enable: bool        = true,
+        /// Whether to add parenthesis and argument snippets when completing function.
+        completion_callable_snippets: CallableCompletionDef  = CallableCompletionDef::FillArguments,
+        /// Whether to show full function/method signatures in completion docs.
+        completion_fullFunctionSignatures_enable: bool = false,
+        /// Maximum number of completions to return. If `None`, the limit is infinite.
+        completion_limit: Option<usize> = None,
+        /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc.
+        completion_postfix_enable: bool         = true,
+        /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position.
+        completion_privateEditable_enable: bool = false,
+        /// Custom completion snippets.
+        // NOTE: we use IndexMap for deterministic serialization ordering
+        completion_snippets_custom: IndexMap<String, SnippetDef> = serde_json::from_str(r#"{
+            "Arc::new": {
+                "postfix": "arc",
+                "body": "Arc::new(${receiver})",
+                "requires": "std::sync::Arc",
+                "description": "Put the expression into an `Arc`",
+                "scope": "expr"
+            },
+            "Rc::new": {
+                "postfix": "rc",
+                "body": "Rc::new(${receiver})",
+                "requires": "std::rc::Rc",
+                "description": "Put the expression into an `Rc`",
+                "scope": "expr"
+            },
+            "Box::pin": {
+                "postfix": "pinbox",
+                "body": "Box::pin(${receiver})",
+                "requires": "std::boxed::Box",
+                "description": "Put the expression into a pinned `Box`",
+                "scope": "expr"
+            },
+            "Ok": {
+                "postfix": "ok",
+                "body": "Ok(${receiver})",
+                "description": "Wrap the expression in a `Result::Ok`",
+                "scope": "expr"
+            },
+            "Err": {
+                "postfix": "err",
+                "body": "Err(${receiver})",
+                "description": "Wrap the expression in a `Result::Err`",
+                "scope": "expr"
+            },
+            "Some": {
+                "postfix": "some",
+                "body": "Some(${receiver})",
+                "description": "Wrap the expression in an `Option::Some`",
+                "scope": "expr"
+            }
+        }"#).unwrap(),
+        /// Whether to enable term search based snippets like `Some(foo.bar().baz())`.
+        completion_termSearch_enable: bool = false,
+        /// Term search fuel in "units of work" for autocompletion (Defaults to 200).
+        completion_termSearch_fuel: usize = 200,
+
         /// List of rust-analyzer diagnostics to disable.
         diagnostics_disabled: FxHashSet<String> = FxHashSet::default(),
         /// Whether to show native rust-analyzer diagnostics.
@@ -451,76 +513,16 @@ config_data! {
 }
 
 config_data! {
-    /// Local configurations can be overridden for every crate by placing a `rust-analyzer.toml` on crate root.
-    /// A config is searched for by traversing a "config tree" in a bottom up fashion. It is chosen by the nearest first principle.
+    /// Local configurations can be defined per `SourceRoot`. This almost always corresponds to a `Crate`.
     local: struct LocalDefaultConfigData <- LocalConfigInput ->  {
+        /// Whether to insert #[must_use] when generating `as_` methods
+        /// for enum variants.
+        assist_emitMustUse: bool               = false,
+        /// Placeholder expression to use for missing expressions in assists.
+        assist_expressionFillDefault: ExprFillDefaultDef              = ExprFillDefaultDef::Todo,
         /// Term search fuel in "units of work" for assists (Defaults to 400).
         assist_termSearch_fuel: usize = 400,
 
-        /// Toggles the additional completions that automatically add imports when completed.
-        /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.
-        completion_autoimport_enable: bool       = true,
-        /// Toggles the additional completions that automatically show method calls and field accesses
-        /// with `self` prefixed to them when inside a method.
-        completion_autoself_enable: bool        = true,
-        /// Whether to add parenthesis and argument snippets when completing function.
-        completion_callable_snippets: CallableCompletionDef  = CallableCompletionDef::FillArguments,
-        /// Whether to show full function/method signatures in completion docs.
-        completion_fullFunctionSignatures_enable: bool = false,
-        /// Maximum number of completions to return. If `None`, the limit is infinite.
-        completion_limit: Option<usize> = None,
-        /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc.
-        completion_postfix_enable: bool         = true,
-        /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position.
-        completion_privateEditable_enable: bool = false,
-        /// Custom completion snippets.
-        // NOTE: we use IndexMap for deterministic serialization ordering
-        completion_snippets_custom: IndexMap<String, SnippetDef> = serde_json::from_str(r#"{
-            "Arc::new": {
-                "postfix": "arc",
-                "body": "Arc::new(${receiver})",
-                "requires": "std::sync::Arc",
-                "description": "Put the expression into an `Arc`",
-                "scope": "expr"
-            },
-            "Rc::new": {
-                "postfix": "rc",
-                "body": "Rc::new(${receiver})",
-                "requires": "std::rc::Rc",
-                "description": "Put the expression into an `Rc`",
-                "scope": "expr"
-            },
-            "Box::pin": {
-                "postfix": "pinbox",
-                "body": "Box::pin(${receiver})",
-                "requires": "std::boxed::Box",
-                "description": "Put the expression into a pinned `Box`",
-                "scope": "expr"
-            },
-            "Ok": {
-                "postfix": "ok",
-                "body": "Ok(${receiver})",
-                "description": "Wrap the expression in a `Result::Ok`",
-                "scope": "expr"
-            },
-            "Err": {
-                "postfix": "err",
-                "body": "Err(${receiver})",
-                "description": "Wrap the expression in a `Result::Err`",
-                "scope": "expr"
-            },
-            "Some": {
-                "postfix": "some",
-                "body": "Some(${receiver})",
-                "description": "Wrap the expression in an `Option::Some`",
-                "scope": "expr"
-            }
-        }"#).unwrap(),
-        /// Whether to enable term search based snippets like `Some(foo.bar().baz())`.
-        completion_termSearch_enable: bool = false,
-        /// Term search fuel in "units of work" for autocompletion (Defaults to 200).
-        completion_termSearch_fuel: usize = 200,
-
         /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
         highlightRelated_breakPoints_enable: bool = true,
         /// Enables highlighting of all captures of a closure while the cursor is on the `|` or move keyword of a closure.
@@ -659,23 +661,304 @@ pub struct Config {
     workspace_roots: Vec<AbsPathBuf>,
     caps: lsp_types::ClientCapabilities,
     root_path: AbsPathBuf,
-    detached_files: Vec<AbsPathBuf>,
     snippets: Vec<Snippet>,
     visual_studio_code_version: Option<Version>,
 
     default_config: DefaultConfigData,
-    client_config: FullConfigInput,
-    user_config: GlobalLocalConfigInput,
-    #[allow(dead_code)]
+    /// Config node that obtains its initial value during the server initialization and
+    /// by receiving a `lsp_types::notification::DidChangeConfiguration`.
+    client_config: ClientConfig,
+
+    /// Path to the root configuration file. This can be seen as a generic way to define what would be `$XDG_CONFIG_HOME/rust-analyzer/rust-analyzer.toml` in Linux.
+    /// If not specified by init of a `Config` object this value defaults to :
+    ///
+    /// |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           |
+    user_config_path: VfsPath,
+
+    /// FIXME @alibektas : Change this to sth better.
+    /// Config node whose values apply to **every** Rust project.
+    user_config: Option<RatomlNode>,
+
+    /// A special file for this session whose path is set to `self.root_path.join("rust-analyzer.toml")`
+    root_ratoml_path: VfsPath,
+
+    /// This file can be used to make global changes while having only a workspace-wide scope.
+    root_ratoml: Option<RatomlNode>,
+
+    /// For every `SourceRoot` there can be at most one RATOML file.
     ratoml_files: FxHashMap<SourceRootId, RatomlNode>,
+
+    /// Clone of the value that is stored inside a `GlobalState`.
+    source_root_parent_map: Arc<FxHashMap<SourceRootId, SourceRootId>>,
+
+    /// Changes made to client and global configurations will partially not be reflected even after `.apply_change()` was called.
+    /// This field signals that the `GlobalState` should call its `update_configuration()` method.
+    should_update: bool,
 }
 
 #[derive(Clone, Debug)]
 struct RatomlNode {
-    #[allow(dead_code)]
     node: GlobalLocalConfigInput,
+    file_id: FileId,
+}
+
+#[derive(Debug, Clone, Default)]
+struct ClientConfig {
+    node: FullConfigInput,
+    detached_files: Vec<AbsPathBuf>,
+}
+
+impl Serialize for RatomlNode {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        let mut s = serializer.serialize_struct("RatomlNode", 2)?;
+        s.serialize_field("file_id", &self.file_id.index())?;
+        s.serialize_field("config", &self.node)?;
+        s.end()
+    }
+}
+
+#[derive(Debug, Hash, Eq, PartialEq)]
+pub(crate) enum ConfigNodeKey {
+    Ratoml(SourceRootId),
+    Client,
+    User,
+}
+
+impl Serialize for ConfigNodeKey {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        match self {
+            ConfigNodeKey::Ratoml(source_root_id) => serializer.serialize_u32(source_root_id.0),
+            ConfigNodeKey::Client => serializer.serialize_str("client"),
+            ConfigNodeKey::User => serializer.serialize_str("user"),
+        }
+    }
+}
+
+#[derive(Debug, Serialize)]
+enum ConfigNodeValue<'a> {
+    /// `rust-analyzer::config` module works by setting
+    /// a mapping between `SourceRootId` and `ConfigInput`.
+    /// Storing a `FileId` is mostly for debugging purposes.
+    Ratoml(&'a RatomlNode),
+    Client(&'a FullConfigInput),
+}
+
+impl Config {
+    /// FIXME @alibektas : Before integration tests, I thought I would
+    /// get the debug output of the config tree and do assertions based on it.
+    /// The reason why I didn't delete this is that we may want to have a lsp_ext
+    /// like "DebugConfigTree" so that it is easier for users to get a snapshot of
+    /// the config state for us to debug.
     #[allow(dead_code)]
-    parent: Option<SourceRootId>,
+    /// Walk towards the root starting from a specified `ConfigNode`
+    fn traverse(
+        &self,
+        start: ConfigNodeKey,
+    ) -> impl Iterator<Item = (ConfigNodeKey, ConfigNodeValue<'_>)> {
+        let mut v = vec![];
+
+        if let ConfigNodeKey::Ratoml(start) = start {
+            let mut par: Option<SourceRootId> = Some(start);
+            while let Some(source_root_id) = par {
+                par = self.source_root_parent_map.get(&start).copied();
+                if let Some(config) = self.ratoml_files.get(&source_root_id) {
+                    v.push((
+                        ConfigNodeKey::Ratoml(source_root_id),
+                        ConfigNodeValue::Ratoml(config),
+                    ));
+                }
+            }
+        }
+
+        v.push((ConfigNodeKey::Client, ConfigNodeValue::Client(&self.client_config.node)));
+
+        if let Some(user_config) = self.user_config.as_ref() {
+            v.push((ConfigNodeKey::User, ConfigNodeValue::Ratoml(user_config)));
+        }
+
+        v.into_iter()
+    }
+
+    pub fn user_config_path(&self) -> &VfsPath {
+        &self.user_config_path
+    }
+
+    pub fn should_update(&self) -> bool {
+        self.should_update
+    }
+
+    // FIXME @alibektas : Server's health uses error sink but in other places it is not used atm.
+    pub fn apply_change(&self, change: ConfigChange, error_sink: &mut ConfigError) -> Config {
+        let mut config = self.clone();
+        let mut toml_errors = vec![];
+        let mut json_errors = vec![];
+
+        config.should_update = false;
+
+        if let Some((file_id, change)) = change.user_config_change {
+            config.user_config = Some(RatomlNode {
+                file_id,
+                node: GlobalLocalConfigInput::from_toml(
+                    toml::from_str(change.to_string().as_str()).unwrap(),
+                    &mut toml_errors,
+                ),
+            });
+            config.should_update = true;
+        }
+
+        if let Some(mut json) = change.client_config_change {
+            tracing::info!("updating config from JSON: {:#}", json);
+            if !(json.is_null() || json.as_object().map_or(false, |it| it.is_empty())) {
+                let detached_files = get_field::<Vec<Utf8PathBuf>>(
+                    &mut json,
+                    &mut json_errors,
+                    "detachedFiles",
+                    None,
+                )
+                .unwrap_or_default()
+                .into_iter()
+                .map(AbsPathBuf::assert)
+                .collect();
+
+                patch_old_style::patch_json_for_outdated_configs(&mut json);
+
+                config.client_config = ClientConfig {
+                    node: FullConfigInput::from_json(json, &mut json_errors),
+                    detached_files,
+                }
+            }
+            config.should_update = true;
+        }
+
+        if let Some((file_id, change)) = change.root_ratoml_change {
+            config.root_ratoml = Some(RatomlNode {
+                file_id,
+                node: GlobalLocalConfigInput::from_toml(
+                    toml::from_str(change.to_string().as_str()).unwrap(),
+                    &mut toml_errors,
+                ),
+            });
+            config.should_update = true;
+        }
+
+        if let Some(change) = change.ratoml_file_change {
+            for (source_root_id, (file_id, _, text)) in change {
+                if let Some(text) = text {
+                    config.ratoml_files.insert(
+                        source_root_id,
+                        RatomlNode {
+                            file_id,
+                            node: GlobalLocalConfigInput::from_toml(
+                                toml::from_str(&text).unwrap(),
+                                &mut toml_errors,
+                            ),
+                        },
+                    );
+                }
+            }
+        }
+
+        if let Some(source_root_map) = change.source_map_change {
+            config.source_root_parent_map = source_root_map;
+        }
+
+        let snips = self.completion_snippets_custom().to_owned();
+
+        for (name, def) in snips.iter() {
+            if def.prefix.is_empty() && def.postfix.is_empty() {
+                continue;
+            }
+            let scope = match def.scope {
+                SnippetScopeDef::Expr => SnippetScope::Expr,
+                SnippetScopeDef::Type => SnippetScope::Type,
+                SnippetScopeDef::Item => SnippetScope::Item,
+            };
+            match Snippet::new(
+                &def.prefix,
+                &def.postfix,
+                &def.body,
+                def.description.as_ref().unwrap_or(name),
+                &def.requires,
+                scope,
+            ) {
+                Some(snippet) => config.snippets.push(snippet),
+                None => error_sink.0.push(ConfigErrorInner::JsonError(
+                    format!("snippet {name} is invalid"),
+                    <serde_json::Error as serde::de::Error>::custom(
+                        "snippet path is invalid or triggers are missing",
+                    ),
+                )),
+            }
+        }
+
+        if config.check_command().is_empty() {
+            error_sink.0.push(ConfigErrorInner::JsonError(
+                "/check/command".to_owned(),
+                serde_json::Error::custom("expected a non-empty string"),
+            ));
+        }
+        config
+    }
+}
+
+#[derive(Default, Debug)]
+pub struct ConfigChange {
+    user_config_change: Option<(FileId, String)>,
+    root_ratoml_change: Option<(FileId, String)>,
+    client_config_change: Option<serde_json::Value>,
+    ratoml_file_change: Option<FxHashMap<SourceRootId, (FileId, VfsPath, Option<String>)>>,
+    source_map_change: Option<Arc<FxHashMap<SourceRootId, SourceRootId>>>,
+}
+
+impl ConfigChange {
+    pub fn change_ratoml(
+        &mut self,
+        source_root: SourceRootId,
+        file_id: FileId,
+        vfs_path: VfsPath,
+        content: Option<String>,
+    ) -> Option<(FileId, VfsPath, Option<String>)> {
+        if let Some(changes) = self.ratoml_file_change.as_mut() {
+            changes.insert(source_root, (file_id, vfs_path, content))
+        } else {
+            let mut map = FxHashMap::default();
+            map.insert(source_root, (file_id, vfs_path, content));
+            self.ratoml_file_change = Some(map);
+            None
+        }
+    }
+
+    pub fn change_user_config(&mut self, content: Option<(FileId, String)>) {
+        assert!(self.user_config_change.is_none()); // Otherwise it is a double write.
+        self.user_config_change = content;
+    }
+
+    pub fn change_root_ratoml(&mut self, content: Option<(FileId, String)>) {
+        assert!(self.user_config_change.is_none()); // Otherwise it is a double write.
+        self.root_ratoml_change = content;
+    }
+
+    pub fn change_client_config(&mut self, change: serde_json::Value) {
+        self.client_config_change = Some(change);
+    }
+
+    pub fn change_source_root_parent_map(
+        &mut self,
+        source_root_map: Arc<FxHashMap<SourceRootId, SourceRootId>>,
+    ) {
+        assert!(self.source_map_change.is_none());
+        self.source_map_change = Some(source_root_map.clone());
+    }
 }
 
 macro_rules! try_ {
@@ -866,23 +1149,37 @@ pub struct ClientCommandsConfig {
 }
 
 #[derive(Debug)]
-pub struct ConfigError {
-    errors: Vec<(String, serde_json::Error)>,
+pub enum ConfigErrorInner {
+    JsonError(String, serde_json::Error),
+    Toml(String, toml::de::Error),
 }
 
+#[derive(Debug, Default)]
+pub struct ConfigError(Vec<ConfigErrorInner>);
+
+impl ConfigError {
+    pub fn is_empty(&self) -> bool {
+        self.0.is_empty()
+    }
+}
+
+impl ConfigError {}
+
 impl fmt::Display for ConfigError {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let errors = self.errors.iter().format_with("\n", |(key, e), f| {
-            f(key)?;
-            f(&": ")?;
-            f(e)
+        let errors = self.0.iter().format_with("\n", |inner, f| match inner {
+            ConfigErrorInner::JsonError(key, e) => {
+                f(key)?;
+                f(&": ")?;
+                f(e)
+            }
+            ConfigErrorInner::Toml(key, e) => {
+                f(key)?;
+                f(&": ")?;
+                f(e)
+            }
         });
-        write!(
-            f,
-            "invalid config value{}:\n{}",
-            if self.errors.len() == 1 { "" } else { "s" },
-            errors
-        )
+        write!(f, "invalid config value{}:\n{}", if self.0.len() == 1 { "" } else { "s" }, errors)
     }
 }
 
@@ -894,19 +1191,45 @@ impl Config {
         caps: ClientCapabilities,
         workspace_roots: Vec<AbsPathBuf>,
         visual_studio_code_version: Option<Version>,
+        user_config_path: Option<Utf8PathBuf>,
     ) -> Self {
+        let user_config_path = if let Some(user_config_path) = user_config_path {
+            user_config_path.join("rust-analyzer").join("rust-analyzer.toml")
+        } else {
+            let p = config_dir()
+                .expect("A config dir is expected to existed on all platforms ra supports.")
+                .join("rust-analyzer")
+                .join("rust-analyzer.toml");
+            Utf8PathBuf::from_path_buf(p).expect("Config dir expected to be abs.")
+        };
+
+        // A user config cannot be a virtual path as rust-analyzer cannot support watching changes in virtual paths.
+        // See `GlobalState::process_changes` to get more info.
+        // FIXME @alibektas : Temporary solution. I don't think this is right as at some point we may allow users to specify
+        // custom USER_CONFIG_PATHs which may also be relative.
+        let user_config_path = VfsPath::from(AbsPathBuf::assert(user_config_path));
+        let root_ratoml_path = {
+            let mut p = root_path.clone();
+            p.push("rust-analyzer.toml");
+            VfsPath::new_real_path(p.to_string())
+        };
+
         Config {
             caps,
-            detached_files: Vec::new(),
             discovered_projects: Vec::new(),
             root_path,
             snippets: Default::default(),
             workspace_roots,
             visual_studio_code_version,
-            client_config: FullConfigInput::default(),
-            user_config: GlobalLocalConfigInput::default(),
+            client_config: ClientConfig::default(),
+            user_config: None,
             ratoml_files: FxHashMap::default(),
             default_config: DefaultConfigData::default(),
+            source_root_parent_map: Arc::new(FxHashMap::default()),
+            user_config_path,
+            root_ratoml: None,
+            root_ratoml_path,
+            should_update: false,
         }
     }
 
@@ -929,71 +1252,6 @@ impl Config {
         self.workspace_roots.extend(paths);
     }
 
-    pub fn update(&mut self, mut json: serde_json::Value) -> Result<(), ConfigError> {
-        tracing::info!("updating config from JSON: {:#}", json);
-        if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) {
-            return Ok(());
-        }
-        let mut errors = Vec::new();
-        self.detached_files =
-            get_field::<Vec<Utf8PathBuf>>(&mut json, &mut errors, "detachedFiles", None)
-                .unwrap_or_default()
-                .into_iter()
-                .map(AbsPathBuf::assert)
-                .collect();
-        patch_old_style::patch_json_for_outdated_configs(&mut json);
-        self.client_config = FullConfigInput::from_json(json, &mut errors);
-        tracing::debug!(?self.client_config, "deserialized config data");
-        self.snippets.clear();
-
-        let snips = self.completion_snippets_custom(None).to_owned();
-
-        for (name, def) in snips.iter() {
-            if def.prefix.is_empty() && def.postfix.is_empty() {
-                continue;
-            }
-            let scope = match def.scope {
-                SnippetScopeDef::Expr => SnippetScope::Expr,
-                SnippetScopeDef::Type => SnippetScope::Type,
-                SnippetScopeDef::Item => SnippetScope::Item,
-            };
-            match Snippet::new(
-                &def.prefix,
-                &def.postfix,
-                &def.body,
-                def.description.as_ref().unwrap_or(name),
-                &def.requires,
-                scope,
-            ) {
-                Some(snippet) => self.snippets.push(snippet),
-                None => errors.push((
-                    format!("snippet {name} is invalid"),
-                    <serde_json::Error as serde::de::Error>::custom(
-                        "snippet path is invalid or triggers are missing",
-                    ),
-                )),
-            }
-        }
-
-        self.validate(&mut errors);
-
-        if errors.is_empty() {
-            Ok(())
-        } else {
-            Err(ConfigError { errors })
-        }
-    }
-
-    fn validate(&self, error_sink: &mut Vec<(String, serde_json::Error)>) {
-        use serde::de::Error;
-        if self.check_command().is_empty() {
-            error_sink.push((
-                "/check/command".to_owned(),
-                serde_json::Error::custom("expected a non-empty string"),
-            ));
-        }
-    }
-
     pub fn json_schema() -> serde_json::Value {
         FullConfigInput::json_schema()
     }
@@ -1002,12 +1260,12 @@ impl Config {
         &self.root_path
     }
 
-    pub fn caps(&self) -> &lsp_types::ClientCapabilities {
-        &self.caps
+    pub fn root_ratoml_path(&self) -> &VfsPath {
+        &self.root_ratoml_path
     }
 
-    pub fn detached_files(&self) -> &[AbsPathBuf] {
-        &self.detached_files
+    pub fn caps(&self) -> &lsp_types::ClientCapabilities {
+        &self.caps
     }
 }
 
@@ -1018,7 +1276,7 @@ impl Config {
             allowed: None,
             insert_use: self.insert_use_config(source_root),
             prefer_no_std: self.imports_preferNoStd(source_root).to_owned(),
-            assist_emit_must_use: self.assist_emitMustUse().to_owned(),
+            assist_emit_must_use: self.assist_emitMustUse(source_root).to_owned(),
             prefer_prelude: self.imports_preferPrelude(source_root).to_owned(),
             term_search_fuel: self.assist_termSearch_fuel(source_root).to_owned() as u64,
         }
@@ -1026,17 +1284,13 @@ impl Config {
 
     pub fn completion(&self, source_root: Option<SourceRootId>) -> CompletionConfig {
         CompletionConfig {
-            enable_postfix_completions: self.completion_postfix_enable(source_root).to_owned(),
-            enable_imports_on_the_fly: self.completion_autoimport_enable(source_root).to_owned()
+            enable_postfix_completions: self.completion_postfix_enable().to_owned(),
+            enable_imports_on_the_fly: self.completion_autoimport_enable().to_owned()
                 && completion_item_edit_resolve(&self.caps),
-            enable_self_on_the_fly: self.completion_autoself_enable(source_root).to_owned(),
-            enable_private_editable: self.completion_privateEditable_enable(source_root).to_owned(),
-            enable_term_search: self.completion_termSearch_enable(source_root).to_owned(),
-            term_search_fuel: self.completion_termSearch_fuel(source_root).to_owned() as u64,
-            full_function_signatures: self
-                .completion_fullFunctionSignatures_enable(source_root)
-                .to_owned(),
-            callable: match self.completion_callable_snippets(source_root) {
+            enable_self_on_the_fly: self.completion_autoself_enable().to_owned(),
+            enable_private_editable: self.completion_privateEditable_enable().to_owned(),
+            full_function_signatures: self.completion_fullFunctionSignatures_enable().to_owned(),
+            callable: match self.completion_callable_snippets() {
                 CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments),
                 CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses),
                 CallableCompletionDef::None => None,
@@ -1055,10 +1309,18 @@ impl Config {
             prefer_no_std: self.imports_preferNoStd(source_root).to_owned(),
             prefer_prelude: self.imports_preferPrelude(source_root).to_owned(),
             snippets: self.snippets.clone().to_vec(),
-            limit: self.completion_limit(source_root).to_owned(),
+            limit: self.completion_limit().to_owned(),
+            enable_term_search: self.completion_termSearch_enable().to_owned(),
+            term_search_fuel: self.completion_termSearch_fuel().to_owned() as u64,
         }
     }
 
+    pub fn detached_files(&self) -> &Vec<AbsPathBuf> {
+        // FIXME @alibektas : This is the only config that is confusing. If it's a proper configuration
+        // why is it not among the others? If it's client only which I doubt it is current state should be alright
+        &self.client_config.detached_files
+    }
+
     pub fn diagnostics(&self, source_root: Option<SourceRootId>) -> DiagnosticsConfig {
         DiagnosticsConfig {
             enabled: *self.diagnostics_enable(),
@@ -1066,7 +1328,7 @@ impl Config {
             proc_macros_enabled: *self.procMacro_enable(),
             disable_experimental: !self.diagnostics_experimental_enable(),
             disabled: self.diagnostics_disabled().clone(),
-            expr_fill_default: match self.assist_expressionFillDefault() {
+            expr_fill_default: match self.assist_expressionFillDefault(source_root) {
                 ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
                 ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
             },
@@ -2016,7 +2278,7 @@ enum SnippetScopeDef {
 
 #[derive(Serialize, Deserialize, Debug, Clone, Default)]
 #[serde(default)]
-struct SnippetDef {
+pub(crate) struct SnippetDef {
     #[serde(with = "single_or_array")]
     #[serde(skip_serializing_if = "Vec::is_empty")]
     prefix: Vec<String>,
@@ -2111,7 +2373,7 @@ enum ImportGranularityDef {
 
 #[derive(Serialize, Deserialize, Debug, Copy, Clone)]
 #[serde(rename_all = "snake_case")]
-enum CallableCompletionDef {
+pub(crate) enum CallableCompletionDef {
     FillArguments,
     AddParentheses,
     None,
@@ -2318,15 +2580,30 @@ macro_rules! _impl_for_config_data {
             $(
                 $($doc)*
                 #[allow(non_snake_case)]
-                $vis fn $field(&self, _source_root: Option<SourceRootId>) -> &$ty {
-                    if let Some(v) = self.client_config.local.$field.as_ref() {
-                        return &v;
+                $vis fn $field(&self, source_root: Option<SourceRootId>) -> &$ty {
+
+                    if source_root.is_some() {
+                        let mut par: Option<SourceRootId> = source_root;
+                        while let Some(source_root_id) = par {
+                            par = self.source_root_parent_map.get(&source_root_id).copied();
+                            if let Some(config) = self.ratoml_files.get(&source_root_id) {
+                                if let Some(value) = config.node.local.$field.as_ref() {
+                                    return value;
+                                }
+                            }
+                        }
                     }
 
-                    if let Some(v) = self.user_config.local.$field.as_ref() {
+                    if let Some(v) = self.client_config.node.local.$field.as_ref() {
                         return &v;
                     }
 
+                    if let Some(user_config) = self.user_config.as_ref() {
+                        if let Some(v) = user_config.node.local.$field.as_ref() {
+                            return &v;
+                        }
+                    }
+
                     &self.default_config.local.$field
                 }
             )*
@@ -2342,14 +2619,23 @@ macro_rules! _impl_for_config_data {
                 $($doc)*
                 #[allow(non_snake_case)]
                 $vis fn $field(&self) -> &$ty {
-                    if let Some(v) = self.client_config.global.$field.as_ref() {
-                        return &v;
+
+                    if let Some(root_path_ratoml) = self.root_ratoml.as_ref() {
+                        if let Some(v) = root_path_ratoml.node.global.$field.as_ref() {
+                            return &v;
+                        }
                     }
 
-                    if let Some(v) = self.user_config.global.$field.as_ref() {
+                    if let Some(v) = self.client_config.node.global.$field.as_ref() {
                         return &v;
                     }
 
+                    if let Some(user_config) = self.user_config.as_ref() {
+                        if let Some(v) = user_config.node.global.$field.as_ref() {
+                            return &v;
+                        }
+                    }
+
                     &self.default_config.global.$field
                 }
             )*
@@ -2502,11 +2788,10 @@ struct DefaultConfigData {
 /// All of the config levels, all fields `Option<T>`, to describe fields that are actually set by
 /// some rust-analyzer.toml file or JSON blob. An empty rust-analyzer.toml corresponds to
 /// all fields being None.
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone, Default, Serialize)]
 struct FullConfigInput {
     global: GlobalConfigInput,
     local: LocalConfigInput,
-    #[allow(dead_code)]
     client: ClientConfigInput,
 }
 
@@ -2545,7 +2830,7 @@ impl FullConfigInput {
 /// All of the config levels, all fields `Option<T>`, to describe fields that are actually set by
 /// some rust-analyzer.toml file or JSON blob. An empty rust-analyzer.toml corresponds to
 /// all fields being None.
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone, Default, Serialize)]
 struct GlobalLocalConfigInput {
     global: GlobalConfigInput,
     local: LocalConfigInput,
@@ -3104,12 +3389,17 @@ mod tests {
             Default::default(),
             vec![],
             None,
+            None,
         );
-        config
-            .update(serde_json::json!({
-                "procMacro_server": null,
-            }))
-            .unwrap();
+
+        let mut change = ConfigChange::default();
+        change.change_client_config(serde_json::json!({
+            "procMacro" : {
+                "server": null,
+        }}));
+
+        let mut error_sink = ConfigError::default();
+        config = config.apply_change(change, &mut error_sink);
         assert_eq!(config.proc_macro_srv(), None);
     }
 
@@ -3120,12 +3410,16 @@ mod tests {
             Default::default(),
             vec![],
             None,
+            None,
         );
-        config
-            .update(serde_json::json!({
-                "procMacro": {"server": project_root().display().to_string()}
-            }))
-            .unwrap();
+        let mut change = ConfigChange::default();
+        change.change_client_config(serde_json::json!({
+        "procMacro" : {
+            "server": project_root().display().to_string(),
+        }}));
+
+        let mut error_sink = ConfigError::default();
+        config = config.apply_change(change, &mut error_sink);
         assert_eq!(config.proc_macro_srv(), Some(AbsPathBuf::try_from(project_root()).unwrap()));
     }
 
@@ -3136,12 +3430,19 @@ mod tests {
             Default::default(),
             vec![],
             None,
+            None,
         );
-        config
-            .update(serde_json::json!({
-                "procMacro": {"server": "./server"}
-            }))
-            .unwrap();
+
+        let mut change = ConfigChange::default();
+
+        change.change_client_config(serde_json::json!({
+        "procMacro" : {
+            "server": "./server"
+        }}));
+
+        let mut error_sink = ConfigError::default();
+        config = config.apply_change(change, &mut error_sink);
+
         assert_eq!(
             config.proc_macro_srv(),
             Some(AbsPathBuf::try_from(project_root().join("./server")).unwrap())
@@ -3155,12 +3456,17 @@ mod tests {
             Default::default(),
             vec![],
             None,
+            None,
         );
-        config
-            .update(serde_json::json!({
-                "rust": { "analyzerTargetDir": null }
-            }))
-            .unwrap();
+
+        let mut change = ConfigChange::default();
+
+        change.change_client_config(serde_json::json!({
+            "rust" : { "analyzerTargetDir" : null }
+        }));
+
+        let mut error_sink = ConfigError::default();
+        config = config.apply_change(change, &mut error_sink);
         assert_eq!(config.cargo_targetDir(), &None);
         assert!(
             matches!(config.flycheck(), FlycheckConfig::CargoCommand { options, .. } if options.target_dir.is_none())
@@ -3174,12 +3480,17 @@ mod tests {
             Default::default(),
             vec![],
             None,
+            None,
         );
-        config
-            .update(serde_json::json!({
-                "rust": { "analyzerTargetDir": true }
-            }))
-            .unwrap();
+
+        let mut change = ConfigChange::default();
+        change.change_client_config(serde_json::json!({
+            "rust" : { "analyzerTargetDir" : true }
+        }));
+
+        let mut error_sink = ConfigError::default();
+        config = config.apply_change(change, &mut error_sink);
+
         assert_eq!(config.cargo_targetDir(), &Some(TargetDirectory::UseSubdirectory(true)));
         assert!(
             matches!(config.flycheck(), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(Utf8PathBuf::from("target/rust-analyzer")))
@@ -3193,12 +3504,17 @@ mod tests {
             Default::default(),
             vec![],
             None,
+            None,
         );
-        config
-            .update(serde_json::json!({
-                "rust": { "analyzerTargetDir": "other_folder" }
-            }))
-            .unwrap();
+
+        let mut change = ConfigChange::default();
+        change.change_client_config(serde_json::json!({
+            "rust" : { "analyzerTargetDir" : "other_folder" }
+        }));
+
+        let mut error_sink = ConfigError::default();
+        config = config.apply_change(change, &mut error_sink);
+
         assert_eq!(
             config.cargo_targetDir(),
             &Some(TargetDirectory::Directory(Utf8PathBuf::from("other_folder")))
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs
index 65a9a491493..6dc608c7776 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs
@@ -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
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs
index 3d3f9440199..4832e8cab43 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -547,6 +547,7 @@ mod tests {
                 ClientCapabilities::default(),
                 Vec::new(),
                 None,
+                None,
             ),
         );
         let snap = state.snapshot();
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs
index f64e66183d1..2210dab0f57 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs
@@ -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, Vfs, VfsPath};
 
 use crate::{
-    config::{Config, ConfigError},
+    config::{Config, ConfigChange, ConfigError},
     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,
@@ -71,7 +74,7 @@ pub(crate) struct GlobalState {
     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,24 @@ 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, vfs::VfsPath> = FxHashMap::default();
+        let mut ratoml_text_map: FxHashMap<FileId, (vfs::VfsPath, Option<String>)> =
+            FxHashMap::default();
+
+        let mut user_config_file: Option<(FileId, Option<String>)> = None;
+        let mut root_path_ratoml: Option<(FileId, Option<String>)> = None;
+
+        let root_vfs_path = {
+            let mut root_vfs_path = self.config.root_path().to_path_buf();
+            root_vfs_path.push("rust-analyzer.toml");
+            VfsPath::new_real_path(root_vfs_path.to_string())
+        };
+
         let (change, modified_rust_files, workspace_structure_change) = {
             let mut change = ChangeWithProcMacros::new();
             let mut guard = self.vfs.write();
@@ -273,6 +294,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, vfs_path.clone());
+                }
+
                 if let Some(path) = vfs_path.as_path() {
                     has_structure_changes |= file.is_created_or_deleted();
 
@@ -311,10 +337,30 @@ impl GlobalState {
             }
             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),
+                None => {
+                    change.change_file(file_id, None);
+                    if let Some(vfs_path) = modified_ratoml_files.get(&file_id) {
+                        if vfs_path == self.config.user_config_path() {
+                            user_config_file = Some((file_id, None));
+                        } else if vfs_path == &root_vfs_path {
+                            root_path_ratoml = Some((file_id, None));
+                        } else {
+                            ratoml_text_map.insert(file_id, (vfs_path.clone(), None));
+                        }
+                    }
+                }
                 Some((text, line_endings)) => {
                     line_endings_map.insert(file_id, line_endings);
-                    change.change_file(file_id, Some(text));
+                    change.change_file(file_id, Some(text.clone()));
+                    if let Some(vfs_path) = modified_ratoml_files.get(&file_id) {
+                        if vfs_path == self.config.user_config_path() {
+                            user_config_file = Some((file_id, Some(text.clone())));
+                        } else if vfs_path == &root_vfs_path {
+                            root_path_ratoml = Some((file_id, Some(text.clone())));
+                        } else {
+                            ratoml_text_map.insert(file_id, (vfs_path.clone(), Some(text.clone())));
+                        }
+                    }
                 }
             });
             if has_structure_changes {
@@ -327,6 +373,54 @@ impl GlobalState {
         let _p = span!(Level::INFO, "GlobalState::process_changes/apply_change").entered();
         self.analysis_host.apply_change(change);
 
+        let config_change = {
+            let mut change = ConfigChange::default();
+            let snap = self.analysis_host.analysis();
+
+            for (file_id, (vfs_path, text)) in ratoml_text_map {
+                // 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.
+                if let Ok(source_root) = snap.source_root_id(file_id) {
+                    if let Ok(true) = snap.is_local_source_root(source_root) {
+                        if let Some((old_file, old_path, old_text)) =
+                            change.change_ratoml(source_root, file_id, vfs_path.clone(), text)
+                        {
+                            // 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(source_root, old_file, old_path, old_text);
+                            }
+                        }
+                    }
+                } else {
+                    // Mapping to a SourceRoot should always end up in `Ok`
+                    span!(Level::ERROR, "Mapping to SourceRootId failed.");
+                }
+            }
+
+            if let Some((file_id, Some(txt))) = user_config_file {
+                change.change_user_config(Some((file_id, txt)));
+            }
+
+            if let Some((file_id, Some(txt))) = root_path_ratoml {
+                change.change_root_ratoml(Some((file_id, txt)));
+            }
+
+            change
+        };
+
+        let mut error_sink = ConfigError::default();
+        let config = self.config.apply_change(config_change, &mut error_sink);
+
+        if config.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))) {
                 _ = self
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs
index 9d30063ccc8..123a9a06a3b 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs
@@ -13,7 +13,7 @@ use triomphe::Arc;
 use vfs::{AbsPathBuf, ChangeKind, VfsPath};
 
 use crate::{
-    config::Config,
+    config::{Config, ConfigChange, ConfigError},
     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,11 @@ 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 mut change = ConfigChange::default();
+                        change.change_client_config(json.take());
+                        let mut error_sink = ConfigError::default();
+                        config = config.apply_change(change, &mut error_sink);
                         this.update_configuration(config);
                     }
                 }
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs
index 1e24bf3aae3..ac80e7871cf 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs
@@ -367,7 +367,7 @@ pub(crate) fn handle_join_lines(
     let _p = tracing::span!(tracing::Level::INFO, "handle_join_lines").entered();
 
     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
-    let source_root = snap.analysis.source_root(file_id)?;
+    let source_root = snap.analysis.source_root_id(file_id)?;
     let config = snap.config.join_lines(Some(source_root));
     let line_index = snap.file_line_index(file_id)?;
 
@@ -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, &params.text_document.uri)?;
     let line_index = snap.file_line_index(file_id)?;
     let frange = from_proto::file_range(&snap, &params.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::span!(tracing::Level::INFO, "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,7 +1511,7 @@ 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 source_root = snap.analysis.source_root_id(file_id)?;
     let range = TextRange::new(
         range.start().min(line_index.index.len()),
         range.end().min(line_index.index.len()),
@@ -1553,7 +1553,7 @@ 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 source_root = snap.analysis.source_root_id(file_id)?;
 
     let mut forced_resolve_inlay_hints_config = snap.config.inlay_hints(Some(source_root));
     forced_resolve_inlay_hints_config.fields_to_resolve = InlayFieldsToResolve::empty();
@@ -1687,7 +1687,7 @@ pub(crate) fn handle_semantic_tokens_full(
     let file_id = from_proto::file_id(&snap, &params.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 source_root = snap.analysis.source_root_id(file_id)?;
 
     let mut highlight_config = snap.config.highlighting_config(Some(source_root));
     // Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet.
@@ -1718,7 +1718,7 @@ pub(crate) fn handle_semantic_tokens_full_delta(
     let file_id = from_proto::file_id(&snap, &params.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 source_root = snap.analysis.source_root_id(file_id)?;
 
     let mut highlight_config = snap.config.highlighting_config(Some(source_root));
     // Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet.
@@ -1762,7 +1762,7 @@ pub(crate) fn handle_semantic_tokens_range(
     let frange = from_proto::file_range(&snap, &params.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 source_root = snap.analysis.source_root_id(frange.file_id)?;
 
     let mut highlight_config = snap.config.highlighting_config(Some(source_root));
     // Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet.
@@ -1991,8 +1991,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;
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs
index 175ffa622ff..d9b31550c56 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs
@@ -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;
+pub mod global_state;
 pub mod lsp;
 use self::lsp::ext as lsp_ext;
 
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs
index 193b3fdd4a8..dd2104c9c37 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs
@@ -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()),
+                    },
                 ]),
             },
         };
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs
index 627be7e951a..e804ba3db95 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs
@@ -24,14 +24,16 @@ 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};
+use tracing::error;
 use triomphe::Arc;
 use vfs::{AbsPath, AbsPathBuf, ChangeKind};
 
 use crate::{
-    config::{Config, FilesWatcher, LinkedProject},
+    config::{Config, ConfigChange, ConfigError, FilesWatcher, LinkedProject},
     global_state::GlobalState,
     lsp_ext,
     main_loop::Task,
@@ -443,40 +445,61 @@ 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(|it| {
+                                [
+                                    format!("{it}/**/*.rs"),
+                                    // FIXME @alibektas : Following dbarsky's recomm I merged toml and lock patterns into one.
+                                    // Is this correct?
+                                    format!("{it}/**/Cargo.{{toml,lock}}"),
+                                    format!("{it}/**/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 };
@@ -548,7 +571,41 @@ 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());
+
+        let user_config_path = self.config.user_config_path();
+        let root_ratoml_path = self.config.root_ratoml_path();
+
+        {
+            let vfs = &mut self.vfs.write().0;
+            let loader = &mut self.loader;
+
+            if vfs.file_id(user_config_path).is_none() {
+                if let Some(user_cfg_abs) = user_config_path.as_path() {
+                    let contents = loader.handle.load_sync(user_cfg_abs);
+                    vfs.set_file_contents(user_config_path.clone(), contents);
+                } else {
+                    error!("Non-abs virtual path for user config.");
+                }
+            }
+
+            if vfs.file_id(root_ratoml_path).is_none() {
+                // FIXME @alibektas : Sometimes root_path_ratoml collide with a regular ratoml.
+                // Although this shouldn't be a problem because everything is mapped to a `FileId`.
+                // We may want to further think about this.
+                if let Some(root_ratoml_abs) = root_ratoml_path.as_path() {
+                    let contents = loader.handle.load_sync(root_ratoml_abs);
+                    vfs.set_file_contents(root_ratoml_path.clone(), contents);
+                } else {
+                    error!("Non-abs virtual path for user config.");
+                }
+            }
+        }
+
+        let mut config_change = ConfigChange::default();
+        config_change.change_source_root_parent_map(self.local_roots_parent_map.clone());
+        let mut error_sink = ConfigError::default();
+        self.config = Arc::new(self.config.apply_change(config_change, &mut error_sink));
 
         self.recreate_crate_graph(cause);
 
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/tracing/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/tracing/config.rs
index f77d9893304..fcdbf6c6949 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/tracing/config.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/tracing/config.rs
@@ -13,6 +13,7 @@ use tracing_tree::HierarchicalLayer;
 
 use crate::tracing::hprof;
 
+#[derive(Debug)]
 pub struct Config<T> {
     pub writer: T,
     pub filter: String,
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs
index 43a83050105..fd6f79abc1c 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs
@@ -17,28 +17,32 @@ mod support;
 mod testdir;
 mod tidy;
 
-use std::{collections::HashMap, path::PathBuf, time::Instant};
+use std::{collections::HashMap, path::PathBuf, sync::Once, time::Instant};
 
 use lsp_types::{
-    notification::DidOpenTextDocument,
+    notification::{DidChangeTextDocument, DidOpenTextDocument, DidSaveTextDocument},
     request::{
         CodeActionRequest, Completion, Formatting, GotoTypeDefinition, HoverRequest,
         InlayHintRequest, InlayHintResolveRequest, WillRenameFiles, WorkspaceSymbolRequest,
     },
-    CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams,
-    DocumentFormattingParams, FileRename, FormattingOptions, GotoDefinitionParams, HoverParams,
-    InlayHint, InlayHintLabel, InlayHintParams, PartialResultParams, Position, Range,
-    RenameFilesParams, TextDocumentItem, TextDocumentPositionParams, WorkDoneProgressParams,
+    CodeAction, CodeActionContext, CodeActionOrCommand, CodeActionParams, CodeActionResponse,
+    CompletionParams, DidChangeTextDocumentParams, DidOpenTextDocumentParams,
+    DidSaveTextDocumentParams, DocumentFormattingParams, FileRename, FormattingOptions,
+    GotoDefinitionParams, Hover, HoverParams, InlayHint, InlayHintLabel, InlayHintParams,
+    PartialResultParams, Position, Range, RenameFilesParams, TextDocumentContentChangeEvent,
+    TextDocumentIdentifier, TextDocumentItem, TextDocumentPositionParams, Url,
+    VersionedTextDocumentIdentifier, WorkDoneProgressParams,
 };
+use paths::Utf8PathBuf;
 use rust_analyzer::lsp::ext::{OnEnter, Runnables, RunnablesParams, UnindexedProject};
 use serde_json::json;
 use stdx::format_to_acc;
+use support::Server;
 use test_utils::skip_slow_tests;
+use testdir::TestDir;
+use tracing_subscriber::fmt::TestWriter;
 
-use crate::{
-    support::{project, Project},
-    testdir::TestDir,
-};
+use crate::support::{project, Project};
 
 #[test]
 fn completes_items_from_standard_library() {
@@ -1467,3 +1471,1027 @@ version = "0.0.0"
 
     server.request::<WorkspaceSymbolRequest>(Default::default(), json!([]));
 }
+
+enum QueryType {
+    AssistEmitMustUse,
+    /// 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.
+    GlobalHover,
+}
+
+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 EMIT_MUST_USE_SNIPPET: &'static str = r#"
+
+impl Value {
+    #[must_use]
+    fn as_text(&self) -> Option<&String> {
+        if let Self::Text(v) = self {
+            Some(v)
+        } else {
+            None
+        }
+    }
+}"#;
+
+    const GLOBAL_TRAIT_ASSOC_ITEMS_ZERO: &'static str = r#"hover.show.traitAssocItems = 0"#;
+    const GLOBAL_TRAIT_ASSOC_ITEMS_SNIPPET: &'static str = r#"
+```rust
+p1
+```
+
+```rust
+trait RandomTrait {
+    type B;
+    fn abc() -> i32;
+    fn def() -> i64;
+}
+```"#;
+
+    fn new(
+        fixtures: Vec<&str>,
+        roots: Vec<&str>,
+        client_config: Option<serde_json::Value>,
+    ) -> Self {
+        // setup();
+        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 {
+        match query {
+            QueryType::AssistEmitMustUse => {
+                let res = self.server.send_request::<CodeActionRequest>(CodeActionParams {
+                    text_document: TextDocumentIdentifier {
+                        uri: self.urls[source_file_idx].clone(),
+                    },
+                    range: lsp_types::Range {
+                        start: Position::new(2, 13),
+                        end: Position::new(2, 15),
+                    },
+                    context: CodeActionContext {
+                        diagnostics: vec![],
+                        only: None,
+                        trigger_kind: None,
+                    },
+                    work_done_progress_params: WorkDoneProgressParams { work_done_token: None },
+                    partial_result_params: lsp_types::PartialResultParams {
+                        partial_result_token: None,
+                    },
+                });
+
+                let res = serde_json::de::from_str::<CodeActionResponse>(res.to_string().as_str())
+                    .unwrap();
+
+                // The difference setting the new config key will cause can be seen in the lower layers of this nested response
+                // so here are some ugly unwraps and other obscure stuff.
+                let ca: CodeAction = res
+                    .into_iter()
+                    .find_map(|it| {
+                        if let CodeActionOrCommand::CodeAction(ca) = it {
+                            if ca.title.as_str() == "Generate an `as_` method for this enum variant"
+                            {
+                                return Some(ca);
+                            }
+                        }
+
+                        None
+                    })
+                    .unwrap();
+
+                if let lsp_types::DocumentChanges::Edits(edits) =
+                    ca.edit.unwrap().document_changes.unwrap()
+                {
+                    if let lsp_types::OneOf::Left(l) = &edits[0].edits[0] {
+                        return l.new_text.as_str() == RatomlTest::EMIT_MUST_USE_SNIPPET;
+                    }
+                }
+            }
+            QueryType::GlobalHover => {
+                let res = self.server.send_request::<HoverRequest>(HoverParams {
+                    work_done_progress_params: WorkDoneProgressParams { work_done_token: None },
+                    text_document_position_params: TextDocumentPositionParams {
+                        text_document: TextDocumentIdentifier {
+                            uri: self.urls[source_file_idx].clone(),
+                        },
+                        position: Position::new(7, 18),
+                    },
+                });
+                let res = serde_json::de::from_str::<Hover>(res.to_string().as_str()).unwrap();
+                assert!(matches!(res.contents, lsp_types::HoverContents::Markup(_)));
+                if let lsp_types::HoverContents::Markup(m) = res.contents {
+                    return m.value == RatomlTest::GLOBAL_TRAIT_ASSOC_ITEMS_SNIPPET;
+                }
+            }
+        }
+
+        panic!()
+    }
+}
+
+static INIT: Once = Once::new();
+
+fn setup() {
+    INIT.call_once(|| {
+        let trc = rust_analyzer::tracing::Config {
+            writer: TestWriter::default(),
+            // Deliberately enable all `error` logs if the user has not set RA_LOG, as there is usually
+            // useful information in there for debugging.
+            filter: std::env::var("RA_LOG").ok().unwrap_or_else(|| "error".to_owned()),
+            chalk_filter: std::env::var("CHALK_DEBUG").ok(),
+            profile_filter: std::env::var("RA_PROFILE").ok(),
+        };
+
+        trc.init().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::AssistEmitMustUse, 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]
+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::AssistEmitMustUse, 2));
+}
+
+#[test]
+fn ratoml_create_user_config() {
+    setup();
+    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::AssistEmitMustUse, 1));
+    server.create(
+        "//- /$$CONFIG_DIR$$/rust-analyzer/rust-analyzer.toml",
+        RatomlTest::EMIT_MUST_USE.to_owned(),
+    );
+    assert!(server.query(QueryType::AssistEmitMustUse, 1));
+}
+
+#[test]
+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::AssistEmitMustUse, 1));
+    server.edit(2, String::new());
+    assert!(!server.query(QueryType::AssistEmitMustUse, 1));
+}
+
+#[test]
+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::AssistEmitMustUse, 1));
+    server.delete(2);
+    assert!(!server.query(QueryType::AssistEmitMustUse, 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::AssistEmitMustUse, 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::AssistEmitMustUse, 3));
+    server.edit(1, "assist.emitMustUse = true".to_owned());
+    assert!(server.query(QueryType::AssistEmitMustUse, 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::AssistEmitMustUse, 3));
+    server.delete(1);
+    assert!(!server.query(QueryType::AssistEmitMustUse, 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::AssistEmitMustUse, 3));
+    server.create("//- /p1/p2/rust-analyzer.toml", RatomlTest::EMIT_MUST_NOT_USE.to_owned());
+    assert!(!server.query(QueryType::AssistEmitMustUse, 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::AssistEmitMustUse, 3));
+    server.delete(1);
+    assert!(!server.query(QueryType::AssistEmitMustUse, 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::AssistEmitMustUse, 3));
+    assert!(server.query(QueryType::AssistEmitMustUse, 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::AssistEmitMustUse, 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::AssistEmitMustUse, 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::GlobalHover, 2);
+}
+
+#[test]
+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::GlobalHover, 2));
+    server.edit(1, RatomlTest::GLOBAL_TRAIT_ASSOC_ITEMS_ZERO.to_owned());
+    assert!(!server.query(QueryType::GlobalHover, 2));
+}
+
+#[test]
+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::GlobalHover, 2));
+    server.delete(1);
+    assert!(!server.query(QueryType::GlobalHover, 2));
+}
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs
index cf27cc7eeff..17485ee3ae8 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs
@@ -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, ConfigError},
+    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,14 @@ 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 mut error_sink = ConfigError::default();
+        assert!(error_sink.is_empty());
+        config = config.apply_change(change, &mut error_sink);
         config.rediscover_workspaces();
 
         Server::new(tmp_dir.keep(), config)