mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-02 06:41:48 +00:00
Merge #5068
5068: Prep dynamic reload r=matklad a=matklad
bors r+
🤖
Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
6eb0349a7b
7 changed files with 309 additions and 289 deletions
|
@ -9,7 +9,7 @@ use ra_ide::{AnalysisChange, AnalysisHost};
|
||||||
use ra_project_model::{CargoConfig, ProcMacroClient, ProjectManifest, ProjectWorkspace};
|
use ra_project_model::{CargoConfig, ProcMacroClient, ProjectManifest, ProjectWorkspace};
|
||||||
use vfs::{loader::Handle, AbsPath};
|
use vfs::{loader::Handle, AbsPath};
|
||||||
|
|
||||||
use crate::global_state::{ProjectFolders, SourceRootConfig};
|
use crate::reload::{ProjectFolders, SourceRootConfig};
|
||||||
|
|
||||||
pub fn load_cargo(
|
pub fn load_cargo(
|
||||||
root: &Path,
|
root: &Path,
|
||||||
|
|
|
@ -30,7 +30,7 @@ pub struct Config {
|
||||||
|
|
||||||
pub cargo: CargoConfig,
|
pub cargo: CargoConfig,
|
||||||
pub rustfmt: RustfmtConfig,
|
pub rustfmt: RustfmtConfig,
|
||||||
pub check: Option<FlycheckConfig>,
|
pub flycheck: Option<FlycheckConfig>,
|
||||||
|
|
||||||
pub inlay_hints: InlayHintsConfig,
|
pub inlay_hints: InlayHintsConfig,
|
||||||
pub completion: CompletionConfig,
|
pub completion: CompletionConfig,
|
||||||
|
@ -147,7 +147,7 @@ impl Config {
|
||||||
|
|
||||||
cargo: CargoConfig::default(),
|
cargo: CargoConfig::default(),
|
||||||
rustfmt: RustfmtConfig::Rustfmt { extra_args: Vec::new() },
|
rustfmt: RustfmtConfig::Rustfmt { extra_args: Vec::new() },
|
||||||
check: Some(FlycheckConfig::CargoCommand {
|
flycheck: Some(FlycheckConfig::CargoCommand {
|
||||||
command: "check".to_string(),
|
command: "check".to_string(),
|
||||||
all_targets: true,
|
all_targets: true,
|
||||||
all_features: false,
|
all_features: false,
|
||||||
|
@ -227,14 +227,14 @@ impl Config {
|
||||||
|
|
||||||
if let Some(false) = get(value, "/checkOnSave/enable") {
|
if let Some(false) = get(value, "/checkOnSave/enable") {
|
||||||
// check is disabled
|
// check is disabled
|
||||||
self.check = None;
|
self.flycheck = None;
|
||||||
} else {
|
} else {
|
||||||
// check is enabled
|
// check is enabled
|
||||||
match get::<Vec<String>>(value, "/checkOnSave/overrideCommand") {
|
match get::<Vec<String>>(value, "/checkOnSave/overrideCommand") {
|
||||||
// first see if the user has completely overridden the command
|
// first see if the user has completely overridden the command
|
||||||
Some(mut args) if !args.is_empty() => {
|
Some(mut args) if !args.is_empty() => {
|
||||||
let command = args.remove(0);
|
let command = args.remove(0);
|
||||||
self.check = Some(FlycheckConfig::CustomCommand {
|
self.flycheck = Some(FlycheckConfig::CustomCommand {
|
||||||
command,
|
command,
|
||||||
args,
|
args,
|
||||||
});
|
});
|
||||||
|
@ -242,7 +242,7 @@ impl Config {
|
||||||
// otherwise configure command customizations
|
// otherwise configure command customizations
|
||||||
_ => {
|
_ => {
|
||||||
if let Some(FlycheckConfig::CargoCommand { command, extra_args, all_targets, all_features, features })
|
if let Some(FlycheckConfig::CargoCommand { command, extra_args, all_targets, all_features, features })
|
||||||
= &mut self.check
|
= &mut self.flycheck
|
||||||
{
|
{
|
||||||
set(value, "/checkOnSave/extraArgs", extra_args);
|
set(value, "/checkOnSave/extraArgs", extra_args);
|
||||||
set(value, "/checkOnSave/command", command);
|
set(value, "/checkOnSave/command", command);
|
||||||
|
|
|
@ -59,7 +59,7 @@ impl<'a> RequestDispatcher<'a> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.global_state.task_pool.0.spawn({
|
self.global_state.task_pool.handle.spawn({
|
||||||
let world = self.global_state.snapshot();
|
let world = self.global_state.snapshot();
|
||||||
move || {
|
move || {
|
||||||
let result = f(world, params);
|
let result = f(world, params);
|
||||||
|
|
|
@ -3,24 +3,25 @@
|
||||||
//!
|
//!
|
||||||
//! Each tick provides an immutable snapshot of the state as `WorldSnapshot`.
|
//! Each tick provides an immutable snapshot of the state as `WorldSnapshot`.
|
||||||
|
|
||||||
use std::{convert::TryFrom, sync::Arc};
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crossbeam_channel::{unbounded, Receiver, Sender};
|
use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||||
use flycheck::{FlycheckConfig, FlycheckHandle};
|
use flycheck::FlycheckHandle;
|
||||||
use lsp_types::Url;
|
use lsp_types::Url;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use ra_db::{CrateId, SourceRoot, VfsPath};
|
use ra_db::{CrateId, VfsPath};
|
||||||
use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId};
|
use ra_ide::{Analysis, AnalysisChange, AnalysisHost, FileId};
|
||||||
use ra_project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target};
|
use ra_project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target};
|
||||||
use stdx::format_to;
|
use stdx::format_to;
|
||||||
use vfs::{file_set::FileSetConfig, loader::Handle, AbsPath, AbsPathBuf};
|
use vfs::loader::Handle as _;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{Config, FilesWatcher},
|
config::Config,
|
||||||
diagnostics::{CheckFixes, DiagnosticCollection},
|
diagnostics::{CheckFixes, DiagnosticCollection},
|
||||||
from_proto,
|
from_proto,
|
||||||
line_endings::LineEndings,
|
line_endings::LineEndings,
|
||||||
main_loop::{ReqQueue, Task},
|
main_loop::{ReqQueue, Task},
|
||||||
|
reload::SourceRootConfig,
|
||||||
request_metrics::{LatestRequests, RequestMetrics},
|
request_metrics::{LatestRequests, RequestMetrics},
|
||||||
show_message,
|
show_message,
|
||||||
thread_pool::TaskPool,
|
thread_pool::TaskPool,
|
||||||
|
@ -29,26 +30,6 @@ use crate::{
|
||||||
};
|
};
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
|
||||||
fn create_flycheck(
|
|
||||||
workspaces: &[ProjectWorkspace],
|
|
||||||
config: &FlycheckConfig,
|
|
||||||
) -> Option<(FlycheckHandle, Receiver<flycheck::Message>)> {
|
|
||||||
// FIXME: Figure out the multi-workspace situation
|
|
||||||
workspaces.iter().find_map(move |w| match w {
|
|
||||||
ProjectWorkspace::Cargo { cargo, .. } => {
|
|
||||||
let (sender, receiver) = unbounded();
|
|
||||||
let sender = Box::new(move |msg| sender.send(msg).unwrap());
|
|
||||||
let cargo_project_root = cargo.workspace_root().to_path_buf();
|
|
||||||
let flycheck = FlycheckHandle::spawn(sender, config.clone(), cargo_project_root.into());
|
|
||||||
Some((flycheck, receiver))
|
|
||||||
}
|
|
||||||
ProjectWorkspace::Json { .. } => {
|
|
||||||
log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Eq, PartialEq)]
|
#[derive(Eq, PartialEq)]
|
||||||
pub(crate) enum Status {
|
pub(crate) enum Status {
|
||||||
Loading,
|
Loading,
|
||||||
|
@ -61,28 +42,35 @@ impl Default for Status {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enforces drop order
|
||||||
|
pub(crate) struct Handle<H, C> {
|
||||||
|
pub(crate) handle: H,
|
||||||
|
pub(crate) receiver: C,
|
||||||
|
}
|
||||||
|
|
||||||
/// `GlobalState` is the primary mutable state of the language server
|
/// `GlobalState` is the primary mutable state of the language server
|
||||||
///
|
///
|
||||||
/// The most interesting components are `vfs`, which stores a consistent
|
/// The most interesting components are `vfs`, which stores a consistent
|
||||||
/// snapshot of the file systems, and `analysis_host`, which stores our
|
/// snapshot of the file systems, and `analysis_host`, which stores our
|
||||||
/// incremental salsa database.
|
/// incremental salsa database.
|
||||||
|
///
|
||||||
|
/// Note that this struct has more than on impl in various modules!
|
||||||
pub(crate) struct GlobalState {
|
pub(crate) struct GlobalState {
|
||||||
sender: Sender<lsp_server::Message>,
|
sender: Sender<lsp_server::Message>,
|
||||||
|
pub(crate) task_pool: Handle<TaskPool<Task>, Receiver<Task>>,
|
||||||
|
pub(crate) loader: Handle<Box<dyn vfs::loader::Handle>, Receiver<vfs::loader::Message>>,
|
||||||
|
pub(crate) flycheck: Option<Handle<FlycheckHandle, Receiver<flycheck::Message>>>,
|
||||||
pub(crate) config: Config,
|
pub(crate) config: Config,
|
||||||
pub(crate) task_pool: (TaskPool<Task>, Receiver<Task>),
|
|
||||||
pub(crate) analysis_host: AnalysisHost,
|
pub(crate) analysis_host: AnalysisHost,
|
||||||
pub(crate) loader: Box<dyn vfs::loader::Handle>,
|
|
||||||
pub(crate) task_receiver: Receiver<vfs::loader::Message>,
|
|
||||||
pub(crate) flycheck: Option<(FlycheckHandle, Receiver<flycheck::Message>)>,
|
|
||||||
pub(crate) diagnostics: DiagnosticCollection,
|
pub(crate) diagnostics: DiagnosticCollection,
|
||||||
pub(crate) mem_docs: FxHashSet<VfsPath>,
|
pub(crate) mem_docs: FxHashSet<VfsPath>,
|
||||||
pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
|
pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
|
||||||
pub(crate) status: Status,
|
pub(crate) status: Status,
|
||||||
pub(crate) req_queue: ReqQueue,
|
pub(crate) req_queue: ReqQueue,
|
||||||
|
pub(crate) source_root_config: SourceRootConfig,
|
||||||
|
pub(crate) proc_macro_client: ProcMacroClient,
|
||||||
|
pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
|
||||||
latest_requests: Arc<RwLock<LatestRequests>>,
|
latest_requests: Arc<RwLock<LatestRequests>>,
|
||||||
source_root_config: SourceRootConfig,
|
|
||||||
_proc_macro_client: ProcMacroClient,
|
|
||||||
workspaces: Arc<Vec<ProjectWorkspace>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An immutable snapshot of the world's state at a point in time.
|
/// An immutable snapshot of the world's state at a point in time.
|
||||||
|
@ -98,102 +86,40 @@ pub(crate) struct GlobalStateSnapshot {
|
||||||
impl GlobalState {
|
impl GlobalState {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
sender: Sender<lsp_server::Message>,
|
sender: Sender<lsp_server::Message>,
|
||||||
workspaces: Vec<ProjectWorkspace>,
|
|
||||||
lru_capacity: Option<usize>,
|
lru_capacity: Option<usize>,
|
||||||
config: Config,
|
config: Config,
|
||||||
req_queue: ReqQueue,
|
|
||||||
) -> GlobalState {
|
) -> GlobalState {
|
||||||
let mut change = AnalysisChange::new();
|
let loader = {
|
||||||
|
let (sender, receiver) = unbounded::<vfs::loader::Message>();
|
||||||
let project_folders = ProjectFolders::new(&workspaces);
|
let handle =
|
||||||
|
vfs_notify::NotifyHandle::spawn(Box::new(move |msg| sender.send(msg).unwrap()));
|
||||||
let (task_sender, task_receiver) = unbounded::<vfs::loader::Message>();
|
let handle = Box::new(handle) as Box<dyn vfs::loader::Handle>;
|
||||||
let mut vfs = vfs::Vfs::default();
|
Handle { handle, receiver }
|
||||||
|
|
||||||
let proc_macro_client = match &config.proc_macro_srv {
|
|
||||||
None => ProcMacroClient::dummy(),
|
|
||||||
Some((path, args)) => match ProcMacroClient::extern_process(path.into(), args) {
|
|
||||||
Ok(it) => it,
|
|
||||||
Err(err) => {
|
|
||||||
log::error!(
|
|
||||||
"Failed to run ra_proc_macro_srv from path {}, error: {:?}",
|
|
||||||
path.display(),
|
|
||||||
err
|
|
||||||
);
|
|
||||||
ProcMacroClient::dummy()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut loader = {
|
|
||||||
let loader = vfs_notify::NotifyHandle::spawn(Box::new(move |msg| {
|
|
||||||
task_sender.send(msg).unwrap()
|
|
||||||
}));
|
|
||||||
Box::new(loader)
|
|
||||||
};
|
|
||||||
let watch = match config.files.watcher {
|
|
||||||
FilesWatcher::Client => vec![],
|
|
||||||
FilesWatcher::Notify => project_folders.watch,
|
|
||||||
};
|
|
||||||
loader.set_config(vfs::loader::Config { load: project_folders.load, watch });
|
|
||||||
|
|
||||||
// Create crate graph from all the workspaces
|
|
||||||
let mut crate_graph = CrateGraph::default();
|
|
||||||
let mut load = |path: &AbsPath| {
|
|
||||||
let contents = loader.load_sync(path);
|
|
||||||
let path = vfs::VfsPath::from(path.to_path_buf());
|
|
||||||
vfs.set_file_contents(path.clone(), contents);
|
|
||||||
vfs.file_id(&path)
|
|
||||||
};
|
|
||||||
for ws in workspaces.iter() {
|
|
||||||
crate_graph.extend(ws.to_crate_graph(
|
|
||||||
config.cargo.target.as_deref(),
|
|
||||||
&proc_macro_client,
|
|
||||||
&mut load,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
change.set_crate_graph(crate_graph);
|
|
||||||
|
|
||||||
let flycheck = config.check.as_ref().and_then(|c| create_flycheck(&workspaces, c));
|
|
||||||
|
|
||||||
let mut analysis_host = AnalysisHost::new(lru_capacity);
|
|
||||||
analysis_host.apply_change(change);
|
|
||||||
|
|
||||||
let task_pool = {
|
let task_pool = {
|
||||||
let (sender, receiver) = unbounded();
|
let (sender, receiver) = unbounded();
|
||||||
(TaskPool::new(sender), receiver)
|
let handle = TaskPool::new(sender);
|
||||||
|
Handle { handle, receiver }
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut res = GlobalState {
|
GlobalState {
|
||||||
sender,
|
sender,
|
||||||
config,
|
|
||||||
task_pool,
|
task_pool,
|
||||||
analysis_host,
|
|
||||||
loader,
|
loader,
|
||||||
task_receiver,
|
config,
|
||||||
flycheck,
|
analysis_host: AnalysisHost::new(lru_capacity),
|
||||||
|
flycheck: None,
|
||||||
diagnostics: Default::default(),
|
diagnostics: Default::default(),
|
||||||
mem_docs: FxHashSet::default(),
|
mem_docs: FxHashSet::default(),
|
||||||
vfs: Arc::new(RwLock::new((vfs, FxHashMap::default()))),
|
vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))),
|
||||||
status: Status::default(),
|
status: Status::default(),
|
||||||
req_queue,
|
req_queue: ReqQueue::default(),
|
||||||
|
source_root_config: SourceRootConfig::default(),
|
||||||
|
proc_macro_client: ProcMacroClient::dummy(),
|
||||||
|
workspaces: Arc::new(Vec::new()),
|
||||||
latest_requests: Default::default(),
|
latest_requests: Default::default(),
|
||||||
source_root_config: project_folders.source_root_config,
|
|
||||||
_proc_macro_client: proc_macro_client,
|
|
||||||
workspaces: Arc::new(workspaces),
|
|
||||||
};
|
|
||||||
res.process_changes();
|
|
||||||
res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn update_configuration(&mut self, config: Config) {
|
|
||||||
self.analysis_host.update_lru_capacity(config.lru_capacity);
|
|
||||||
if config.check != self.config.check {
|
|
||||||
self.flycheck =
|
|
||||||
config.check.as_ref().and_then(|it| create_flycheck(&self.workspaces, it));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.config = config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn process_changes(&mut self) -> bool {
|
pub(crate) fn process_changes(&mut self) -> bool {
|
||||||
|
@ -266,7 +192,7 @@ impl GlobalState {
|
||||||
self.send(response.into());
|
self.send(response.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub(crate) fn show_message(&mut self, typ: lsp_types::MessageType, message: String) {
|
pub(crate) fn show_message(&self, typ: lsp_types::MessageType, message: String) {
|
||||||
show_message(typ, message, &self.sender)
|
show_message(typ, message, &self.sender)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -343,78 +269,3 @@ pub(crate) fn file_id_to_url(vfs: &vfs::Vfs, id: FileId) -> Url {
|
||||||
let path = path.as_path().unwrap();
|
let path = path.as_path().unwrap();
|
||||||
url_from_abs_path(&path)
|
url_from_abs_path(&path)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub(crate) struct ProjectFolders {
|
|
||||||
pub(crate) load: Vec<vfs::loader::Entry>,
|
|
||||||
pub(crate) watch: Vec<usize>,
|
|
||||||
pub(crate) source_root_config: SourceRootConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProjectFolders {
|
|
||||||
pub(crate) fn new(workspaces: &[ProjectWorkspace]) -> ProjectFolders {
|
|
||||||
let mut res = ProjectFolders::default();
|
|
||||||
let mut fsc = FileSetConfig::builder();
|
|
||||||
let mut local_filesets = vec![];
|
|
||||||
|
|
||||||
for root in workspaces.iter().flat_map(|it| it.to_roots()) {
|
|
||||||
let path = root.path().to_owned();
|
|
||||||
|
|
||||||
let mut file_set_roots: Vec<VfsPath> = vec![];
|
|
||||||
|
|
||||||
let entry = if root.is_member() {
|
|
||||||
vfs::loader::Entry::local_cargo_package(path.to_path_buf())
|
|
||||||
} else {
|
|
||||||
vfs::loader::Entry::cargo_package_dependency(path.to_path_buf())
|
|
||||||
};
|
|
||||||
res.load.push(entry);
|
|
||||||
if root.is_member() {
|
|
||||||
res.watch.push(res.load.len() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(out_dir) = root.out_dir() {
|
|
||||||
let out_dir = AbsPathBuf::try_from(out_dir.to_path_buf()).unwrap();
|
|
||||||
res.load.push(vfs::loader::Entry::rs_files_recursively(out_dir.clone()));
|
|
||||||
if root.is_member() {
|
|
||||||
res.watch.push(res.load.len() - 1);
|
|
||||||
}
|
|
||||||
file_set_roots.push(out_dir.into());
|
|
||||||
}
|
|
||||||
file_set_roots.push(path.to_path_buf().into());
|
|
||||||
|
|
||||||
if root.is_member() {
|
|
||||||
local_filesets.push(fsc.len());
|
|
||||||
}
|
|
||||||
fsc.add_file_set(file_set_roots)
|
|
||||||
}
|
|
||||||
|
|
||||||
let fsc = fsc.build();
|
|
||||||
res.source_root_config = SourceRootConfig { fsc, local_filesets };
|
|
||||||
|
|
||||||
res
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
pub(crate) struct SourceRootConfig {
|
|
||||||
pub(crate) fsc: FileSetConfig,
|
|
||||||
pub(crate) local_filesets: Vec<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SourceRootConfig {
|
|
||||||
pub(crate) fn partition(&self, vfs: &vfs::Vfs) -> Vec<SourceRoot> {
|
|
||||||
self.fsc
|
|
||||||
.partition(vfs)
|
|
||||||
.into_iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(idx, file_set)| {
|
|
||||||
let is_local = self.local_filesets.contains(&idx);
|
|
||||||
if is_local {
|
|
||||||
SourceRoot::new_local(file_set)
|
|
||||||
} else {
|
|
||||||
SourceRoot::new_library(file_set)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ macro_rules! eprintln {
|
||||||
}
|
}
|
||||||
|
|
||||||
mod global_state;
|
mod global_state;
|
||||||
|
mod reload;
|
||||||
mod main_loop;
|
mod main_loop;
|
||||||
mod dispatch;
|
mod dispatch;
|
||||||
mod handlers;
|
mod handlers;
|
||||||
|
|
|
@ -11,17 +11,14 @@ use lsp_types::{notification::Notification as _, request::Request as _};
|
||||||
use ra_db::VfsPath;
|
use ra_db::VfsPath;
|
||||||
use ra_ide::{Canceled, FileId};
|
use ra_ide::{Canceled, FileId};
|
||||||
use ra_prof::profile;
|
use ra_prof::profile;
|
||||||
use ra_project_model::{PackageRoot, ProjectWorkspace};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{Config, FilesWatcher, LinkedProject},
|
config::Config,
|
||||||
dispatch::{NotificationDispatcher, RequestDispatcher},
|
dispatch::{NotificationDispatcher, RequestDispatcher},
|
||||||
from_proto,
|
from_proto,
|
||||||
global_state::{file_id_to_url, GlobalState, Status},
|
global_state::{file_id_to_url, GlobalState, Status},
|
||||||
handlers, lsp_ext,
|
handlers, lsp_ext,
|
||||||
lsp_utils::{
|
lsp_utils::{apply_document_changes, is_canceled, notification_is, notification_new},
|
||||||
apply_document_changes, is_canceled, notification_is, notification_new, show_message,
|
|
||||||
},
|
|
||||||
Result,
|
Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -47,81 +44,8 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
|
||||||
SetThreadPriority(thread, thread_priority_above_normal);
|
SetThreadPriority(thread, thread_priority_above_normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
let global_state = {
|
GlobalState::new(connection.sender.clone(), config.lru_capacity, config)
|
||||||
let workspaces = {
|
.run(connection.receiver)
|
||||||
if config.linked_projects.is_empty() && config.notifications.cargo_toml_not_found {
|
|
||||||
show_message(
|
|
||||||
lsp_types::MessageType::Error,
|
|
||||||
"rust-analyzer failed to discover workspace".to_string(),
|
|
||||||
&connection.sender,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
config
|
|
||||||
.linked_projects
|
|
||||||
.iter()
|
|
||||||
.filter_map(|project| match project {
|
|
||||||
LinkedProject::ProjectManifest(manifest) => {
|
|
||||||
ra_project_model::ProjectWorkspace::load(
|
|
||||||
manifest.clone(),
|
|
||||||
&config.cargo,
|
|
||||||
config.with_sysroot,
|
|
||||||
)
|
|
||||||
.map_err(|err| {
|
|
||||||
log::error!("failed to load workspace: {:#}", err);
|
|
||||||
show_message(
|
|
||||||
lsp_types::MessageType::Error,
|
|
||||||
format!("rust-analyzer failed to load workspace: {:#}", err),
|
|
||||||
&connection.sender,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
LinkedProject::InlineJsonProject(it) => {
|
|
||||||
Some(ra_project_model::ProjectWorkspace::Json { project: it.clone() })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut req_queue = ReqQueue::default();
|
|
||||||
|
|
||||||
if let FilesWatcher::Client = config.files.watcher {
|
|
||||||
let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions {
|
|
||||||
watchers: workspaces
|
|
||||||
.iter()
|
|
||||||
.flat_map(ProjectWorkspace::to_roots)
|
|
||||||
.filter(PackageRoot::is_member)
|
|
||||||
.map(|root| format!("{}/**/*.rs", root.path().display()))
|
|
||||||
.map(|glob_pattern| lsp_types::FileSystemWatcher { glob_pattern, kind: None })
|
|
||||||
.collect(),
|
|
||||||
};
|
|
||||||
let registration = lsp_types::Registration {
|
|
||||||
id: "file-watcher".to_string(),
|
|
||||||
method: "workspace/didChangeWatchedFiles".to_string(),
|
|
||||||
register_options: Some(serde_json::to_value(registration_options).unwrap()),
|
|
||||||
};
|
|
||||||
let params = lsp_types::RegistrationParams { registrations: vec![registration] };
|
|
||||||
let request = req_queue.outgoing.register(
|
|
||||||
lsp_types::request::RegisterCapability::METHOD.to_string(),
|
|
||||||
params,
|
|
||||||
DO_NOTHING,
|
|
||||||
);
|
|
||||||
connection.sender.send(request.into()).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
GlobalState::new(
|
|
||||||
connection.sender.clone(),
|
|
||||||
workspaces,
|
|
||||||
config.lru_capacity,
|
|
||||||
config,
|
|
||||||
req_queue,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
log::info!("server initialized, serving requests");
|
|
||||||
global_state.run(connection.receiver)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Event {
|
enum Event {
|
||||||
|
@ -176,36 +100,39 @@ impl GlobalState {
|
||||||
recv(inbox) -> msg =>
|
recv(inbox) -> msg =>
|
||||||
msg.ok().map(Event::Lsp),
|
msg.ok().map(Event::Lsp),
|
||||||
|
|
||||||
recv(self.task_pool.1) -> task =>
|
recv(self.task_pool.receiver) -> task =>
|
||||||
Some(Event::Task(task.unwrap())),
|
Some(Event::Task(task.unwrap())),
|
||||||
|
|
||||||
recv(self.task_receiver) -> task =>
|
recv(self.loader.receiver) -> task =>
|
||||||
Some(Event::Vfs(task.unwrap())),
|
Some(Event::Vfs(task.unwrap())),
|
||||||
|
|
||||||
recv(self.flycheck.as_ref().map_or(&never(), |it| &it.1)) -> task =>
|
recv(self.flycheck.as_ref().map_or(&never(), |it| &it.receiver)) -> task =>
|
||||||
Some(Event::Flycheck(task.unwrap())),
|
Some(Event::Flycheck(task.unwrap())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> {
|
fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> {
|
||||||
|
self.reload();
|
||||||
|
|
||||||
while let Some(event) = self.next_event(&inbox) {
|
while let Some(event) = self.next_event(&inbox) {
|
||||||
if let Event::Lsp(lsp_server::Message::Notification(not)) = &event {
|
if let Event::Lsp(lsp_server::Message::Notification(not)) = &event {
|
||||||
if not.method == lsp_types::notification::Exit::METHOD {
|
if not.method == lsp_types::notification::Exit::METHOD {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.loop_turn(event)?
|
self.handle_event(event)?
|
||||||
}
|
}
|
||||||
|
|
||||||
Err("client exited without proper shutdown sequence")?
|
Err("client exited without proper shutdown sequence")?
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loop_turn(&mut self, event: Event) -> Result<()> {
|
fn handle_event(&mut self, event: Event) -> Result<()> {
|
||||||
let loop_start = Instant::now();
|
let loop_start = Instant::now();
|
||||||
// NOTE: don't count blocking select! call as a loop-turn time
|
// NOTE: don't count blocking select! call as a loop-turn time
|
||||||
let _p = profile("main_loop_inner/loop-turn");
|
let _p = profile("GlobalState::handle_event");
|
||||||
|
|
||||||
log::info!("loop turn = {:?}", event);
|
log::info!("handle_event({:?})", event);
|
||||||
let queue_count = self.task_pool.0.len();
|
let queue_count = self.task_pool.handle.len();
|
||||||
if queue_count > 0 {
|
if queue_count > 0 {
|
||||||
log::info!("queued count = {}", queue_count);
|
log::info!("queued count = {}", queue_count);
|
||||||
}
|
}
|
||||||
|
@ -306,7 +233,7 @@ impl GlobalState {
|
||||||
let state_changed = self.process_changes();
|
let state_changed = self.process_changes();
|
||||||
if became_ready {
|
if became_ready {
|
||||||
if let Some(flycheck) = &self.flycheck {
|
if let Some(flycheck) = &self.flycheck {
|
||||||
flycheck.0.update();
|
flycheck.handle.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -443,7 +370,7 @@ impl GlobalState {
|
||||||
log::error!("orphan DidCloseTextDocument: {}", path)
|
log::error!("orphan DidCloseTextDocument: {}", path)
|
||||||
}
|
}
|
||||||
if let Some(path) = path.as_path() {
|
if let Some(path) = path.as_path() {
|
||||||
this.loader.invalidate(path.to_path_buf());
|
this.loader.handle.invalidate(path.to_path_buf());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let params = lsp_types::PublishDiagnosticsParams {
|
let params = lsp_types::PublishDiagnosticsParams {
|
||||||
|
@ -457,7 +384,7 @@ impl GlobalState {
|
||||||
})?
|
})?
|
||||||
.on::<lsp_types::notification::DidSaveTextDocument>(|this, _params| {
|
.on::<lsp_types::notification::DidSaveTextDocument>(|this, _params| {
|
||||||
if let Some(flycheck) = &this.flycheck {
|
if let Some(flycheck) = &this.flycheck {
|
||||||
flycheck.0.update();
|
flycheck.handle.update();
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})?
|
})?
|
||||||
|
@ -500,7 +427,7 @@ impl GlobalState {
|
||||||
.on::<lsp_types::notification::DidChangeWatchedFiles>(|this, params| {
|
.on::<lsp_types::notification::DidChangeWatchedFiles>(|this, params| {
|
||||||
for change in params.changes {
|
for change in params.changes {
|
||||||
if let Ok(path) = from_proto::abs_path(&change.uri) {
|
if let Ok(path) = from_proto::abs_path(&change.uri) {
|
||||||
this.loader.invalidate(path);
|
this.loader.handle.invalidate(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -513,7 +440,7 @@ impl GlobalState {
|
||||||
if self.config.publish_diagnostics {
|
if self.config.publish_diagnostics {
|
||||||
let snapshot = self.snapshot();
|
let snapshot = self.snapshot();
|
||||||
let subscriptions = subscriptions.clone();
|
let subscriptions = subscriptions.clone();
|
||||||
self.task_pool.0.spawn(move || {
|
self.task_pool.handle.spawn(move || {
|
||||||
let diagnostics = subscriptions
|
let diagnostics = subscriptions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|file_id| {
|
.filter_map(|file_id| {
|
||||||
|
@ -531,7 +458,7 @@ impl GlobalState {
|
||||||
Task::Diagnostics(diagnostics)
|
Task::Diagnostics(diagnostics)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
self.task_pool.0.spawn({
|
self.task_pool.handle.spawn({
|
||||||
let subs = subscriptions;
|
let subs = subscriptions;
|
||||||
let snap = self.snapshot();
|
let snap = self.snapshot();
|
||||||
move || {
|
move || {
|
||||||
|
|
241
crates/rust-analyzer/src/reload.rs
Normal file
241
crates/rust-analyzer/src/reload.rs
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
//! Project loading & configuration updates
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crossbeam_channel::unbounded;
|
||||||
|
use flycheck::FlycheckHandle;
|
||||||
|
use lsp_types::request::Request;
|
||||||
|
use ra_db::{CrateGraph, SourceRoot, VfsPath};
|
||||||
|
use ra_ide::AnalysisChange;
|
||||||
|
use ra_project_model::{PackageRoot, ProcMacroClient, ProjectWorkspace};
|
||||||
|
use vfs::{file_set::FileSetConfig, AbsPath};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::{Config, FilesWatcher, LinkedProject},
|
||||||
|
global_state::{GlobalState, Handle},
|
||||||
|
};
|
||||||
|
|
||||||
|
impl GlobalState {
|
||||||
|
pub(crate) fn update_configuration(&mut self, new_config: Config) {
|
||||||
|
self.analysis_host.update_lru_capacity(new_config.lru_capacity);
|
||||||
|
if new_config.flycheck != self.config.flycheck {
|
||||||
|
self.reload_flycheck();
|
||||||
|
}
|
||||||
|
self.config = new_config;
|
||||||
|
}
|
||||||
|
pub(crate) fn reload(&mut self) {
|
||||||
|
let workspaces = {
|
||||||
|
if self.config.linked_projects.is_empty()
|
||||||
|
&& self.config.notifications.cargo_toml_not_found
|
||||||
|
{
|
||||||
|
self.show_message(
|
||||||
|
lsp_types::MessageType::Error,
|
||||||
|
"rust-analyzer failed to discover workspace".to_string(),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.config
|
||||||
|
.linked_projects
|
||||||
|
.iter()
|
||||||
|
.filter_map(|project| match project {
|
||||||
|
LinkedProject::ProjectManifest(manifest) => {
|
||||||
|
ra_project_model::ProjectWorkspace::load(
|
||||||
|
manifest.clone(),
|
||||||
|
&self.config.cargo,
|
||||||
|
self.config.with_sysroot,
|
||||||
|
)
|
||||||
|
.map_err(|err| {
|
||||||
|
log::error!("failed to load workspace: {:#}", err);
|
||||||
|
self.show_message(
|
||||||
|
lsp_types::MessageType::Error,
|
||||||
|
format!("rust-analyzer failed to load workspace: {:#}", err),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
LinkedProject::InlineJsonProject(it) => {
|
||||||
|
Some(ra_project_model::ProjectWorkspace::Json { project: it.clone() })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let FilesWatcher::Client = self.config.files.watcher {
|
||||||
|
let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions {
|
||||||
|
watchers: workspaces
|
||||||
|
.iter()
|
||||||
|
.flat_map(ProjectWorkspace::to_roots)
|
||||||
|
.filter(PackageRoot::is_member)
|
||||||
|
.map(|root| format!("{}/**/*.rs", root.path().display()))
|
||||||
|
.map(|glob_pattern| lsp_types::FileSystemWatcher { glob_pattern, kind: None })
|
||||||
|
.collect(),
|
||||||
|
};
|
||||||
|
let registration = lsp_types::Registration {
|
||||||
|
id: "file-watcher".to_string(),
|
||||||
|
method: "workspace/didChangeWatchedFiles".to_string(),
|
||||||
|
register_options: Some(serde_json::to_value(registration_options).unwrap()),
|
||||||
|
};
|
||||||
|
let params = lsp_types::RegistrationParams { registrations: vec![registration] };
|
||||||
|
let request = self.req_queue.outgoing.register(
|
||||||
|
lsp_types::request::RegisterCapability::METHOD.to_string(),
|
||||||
|
params,
|
||||||
|
|_, _| (),
|
||||||
|
);
|
||||||
|
self.send(request.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut change = AnalysisChange::new();
|
||||||
|
|
||||||
|
let project_folders = ProjectFolders::new(&workspaces);
|
||||||
|
|
||||||
|
self.proc_macro_client = match &self.config.proc_macro_srv {
|
||||||
|
None => ProcMacroClient::dummy(),
|
||||||
|
Some((path, args)) => match ProcMacroClient::extern_process(path.into(), args) {
|
||||||
|
Ok(it) => it,
|
||||||
|
Err(err) => {
|
||||||
|
log::error!(
|
||||||
|
"Failed to run ra_proc_macro_srv from path {}, error: {:?}",
|
||||||
|
path.display(),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
ProcMacroClient::dummy()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let watch = match self.config.files.watcher {
|
||||||
|
FilesWatcher::Client => vec![],
|
||||||
|
FilesWatcher::Notify => project_folders.watch,
|
||||||
|
};
|
||||||
|
self.loader.handle.set_config(vfs::loader::Config { load: project_folders.load, watch });
|
||||||
|
|
||||||
|
// Create crate graph from all the workspaces
|
||||||
|
let crate_graph = {
|
||||||
|
let mut crate_graph = CrateGraph::default();
|
||||||
|
let vfs = &mut self.vfs.write().0;
|
||||||
|
let loader = &mut self.loader;
|
||||||
|
let mut load = |path: &AbsPath| {
|
||||||
|
let contents = loader.handle.load_sync(path);
|
||||||
|
let path = vfs::VfsPath::from(path.to_path_buf());
|
||||||
|
vfs.set_file_contents(path.clone(), contents);
|
||||||
|
vfs.file_id(&path)
|
||||||
|
};
|
||||||
|
for ws in workspaces.iter() {
|
||||||
|
crate_graph.extend(ws.to_crate_graph(
|
||||||
|
self.config.cargo.target.as_deref(),
|
||||||
|
&self.proc_macro_client,
|
||||||
|
&mut load,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
crate_graph
|
||||||
|
};
|
||||||
|
change.set_crate_graph(crate_graph);
|
||||||
|
|
||||||
|
self.source_root_config = project_folders.source_root_config;
|
||||||
|
self.workspaces = Arc::new(workspaces);
|
||||||
|
|
||||||
|
self.analysis_host.apply_change(change);
|
||||||
|
self.process_changes();
|
||||||
|
self.reload_flycheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reload_flycheck(&mut self) {
|
||||||
|
let config = match self.config.flycheck.clone() {
|
||||||
|
Some(it) => it,
|
||||||
|
None => {
|
||||||
|
self.flycheck = None;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// FIXME: Figure out the multi-workspace situation
|
||||||
|
self.flycheck = self.workspaces.iter().find_map(move |w| match w {
|
||||||
|
ProjectWorkspace::Cargo { cargo, .. } => {
|
||||||
|
let (sender, receiver) = unbounded();
|
||||||
|
let sender = Box::new(move |msg| sender.send(msg).unwrap());
|
||||||
|
let cargo_project_root = cargo.workspace_root().to_path_buf();
|
||||||
|
let handle =
|
||||||
|
FlycheckHandle::spawn(sender, config.clone(), cargo_project_root.into());
|
||||||
|
Some(Handle { handle, receiver })
|
||||||
|
}
|
||||||
|
ProjectWorkspace::Json { .. } => {
|
||||||
|
log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(crate) struct ProjectFolders {
|
||||||
|
pub(crate) load: Vec<vfs::loader::Entry>,
|
||||||
|
pub(crate) watch: Vec<usize>,
|
||||||
|
pub(crate) source_root_config: SourceRootConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProjectFolders {
|
||||||
|
pub(crate) fn new(workspaces: &[ProjectWorkspace]) -> ProjectFolders {
|
||||||
|
let mut res = ProjectFolders::default();
|
||||||
|
let mut fsc = FileSetConfig::builder();
|
||||||
|
let mut local_filesets = vec![];
|
||||||
|
|
||||||
|
for root in workspaces.iter().flat_map(|it| it.to_roots()) {
|
||||||
|
let path = root.path().to_owned();
|
||||||
|
|
||||||
|
let mut file_set_roots: Vec<VfsPath> = vec![];
|
||||||
|
|
||||||
|
let entry = if root.is_member() {
|
||||||
|
vfs::loader::Entry::local_cargo_package(path.to_path_buf())
|
||||||
|
} else {
|
||||||
|
vfs::loader::Entry::cargo_package_dependency(path.to_path_buf())
|
||||||
|
};
|
||||||
|
res.load.push(entry);
|
||||||
|
if root.is_member() {
|
||||||
|
res.watch.push(res.load.len() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(out_dir) = root.out_dir() {
|
||||||
|
let out_dir = out_dir.to_path_buf();
|
||||||
|
res.load.push(vfs::loader::Entry::rs_files_recursively(out_dir.clone()));
|
||||||
|
if root.is_member() {
|
||||||
|
res.watch.push(res.load.len() - 1);
|
||||||
|
}
|
||||||
|
file_set_roots.push(out_dir.into());
|
||||||
|
}
|
||||||
|
file_set_roots.push(path.to_path_buf().into());
|
||||||
|
|
||||||
|
if root.is_member() {
|
||||||
|
local_filesets.push(fsc.len());
|
||||||
|
}
|
||||||
|
fsc.add_file_set(file_set_roots)
|
||||||
|
}
|
||||||
|
|
||||||
|
let fsc = fsc.build();
|
||||||
|
res.source_root_config = SourceRootConfig { fsc, local_filesets };
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub(crate) struct SourceRootConfig {
|
||||||
|
pub(crate) fsc: FileSetConfig,
|
||||||
|
pub(crate) local_filesets: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceRootConfig {
|
||||||
|
pub(crate) fn partition(&self, vfs: &vfs::Vfs) -> Vec<SourceRoot> {
|
||||||
|
self.fsc
|
||||||
|
.partition(vfs)
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(idx, file_set)| {
|
||||||
|
let is_local = self.local_filesets.contains(&idx);
|
||||||
|
if is_local {
|
||||||
|
SourceRoot::new_local(file_set)
|
||||||
|
} else {
|
||||||
|
SourceRoot::new_library(file_set)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue