Merge #4726
4726: Groundwork for specifying the set of projects via config r=matklad a=matklad
bors r+
🤖
Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
9b3d4be421
@ -14,7 +14,7 @@
|
||||
use anyhow::{bail, Context, Result};
|
||||
use ra_cfg::CfgOptions;
|
||||
use ra_db::{CrateGraph, CrateName, Edition, Env, ExternSource, ExternSourceId, FileId};
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use serde_json::from_reader;
|
||||
|
||||
pub use crate::{
|
||||
@ -57,25 +57,25 @@ pub fn is_member(&self) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ProjectRoot {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
||||
pub enum ProjectManifest {
|
||||
ProjectJson(PathBuf),
|
||||
CargoToml(PathBuf),
|
||||
}
|
||||
|
||||
impl ProjectRoot {
|
||||
pub fn from_manifest_file(path: PathBuf) -> Result<ProjectRoot> {
|
||||
impl ProjectManifest {
|
||||
pub fn from_manifest_file(path: PathBuf) -> Result<ProjectManifest> {
|
||||
if path.ends_with("rust-project.json") {
|
||||
return Ok(ProjectRoot::ProjectJson(path));
|
||||
return Ok(ProjectManifest::ProjectJson(path));
|
||||
}
|
||||
if path.ends_with("Cargo.toml") {
|
||||
return Ok(ProjectRoot::CargoToml(path));
|
||||
return Ok(ProjectManifest::CargoToml(path));
|
||||
}
|
||||
bail!("project root must point to Cargo.toml or rust-project.json: {}", path.display())
|
||||
}
|
||||
|
||||
pub fn discover_single(path: &Path) -> Result<ProjectRoot> {
|
||||
let mut candidates = ProjectRoot::discover(path)?;
|
||||
pub fn discover_single(path: &Path) -> Result<ProjectManifest> {
|
||||
let mut candidates = ProjectManifest::discover(path)?;
|
||||
let res = match candidates.pop() {
|
||||
None => bail!("no projects"),
|
||||
Some(it) => it,
|
||||
@ -87,12 +87,12 @@ pub fn discover_single(path: &Path) -> Result<ProjectRoot> {
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn discover(path: &Path) -> io::Result<Vec<ProjectRoot>> {
|
||||
pub fn discover(path: &Path) -> io::Result<Vec<ProjectManifest>> {
|
||||
if let Some(project_json) = find_in_parent_dirs(path, "rust-project.json") {
|
||||
return Ok(vec![ProjectRoot::ProjectJson(project_json)]);
|
||||
return Ok(vec![ProjectManifest::ProjectJson(project_json)]);
|
||||
}
|
||||
return find_cargo_toml(path)
|
||||
.map(|paths| paths.into_iter().map(ProjectRoot::CargoToml).collect());
|
||||
.map(|paths| paths.into_iter().map(ProjectManifest::CargoToml).collect());
|
||||
|
||||
fn find_cargo_toml(path: &Path) -> io::Result<Vec<PathBuf>> {
|
||||
match find_in_parent_dirs(path, "Cargo.toml") {
|
||||
@ -128,16 +128,28 @@ fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<PathBuf> {
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn discover_all(paths: &[impl AsRef<Path>]) -> Vec<ProjectManifest> {
|
||||
let mut res = paths
|
||||
.iter()
|
||||
.filter_map(|it| ProjectManifest::discover(it.as_ref()).ok())
|
||||
.flatten()
|
||||
.collect::<FxHashSet<_>>()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
res.sort();
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl ProjectWorkspace {
|
||||
pub fn load(
|
||||
root: ProjectRoot,
|
||||
root: ProjectManifest,
|
||||
cargo_features: &CargoConfig,
|
||||
with_sysroot: bool,
|
||||
) -> Result<ProjectWorkspace> {
|
||||
let res = match root {
|
||||
ProjectRoot::ProjectJson(project_json) => {
|
||||
ProjectManifest::ProjectJson(project_json) => {
|
||||
let file = File::open(&project_json).with_context(|| {
|
||||
format!("Failed to open json file {}", project_json.display())
|
||||
})?;
|
||||
@ -148,7 +160,7 @@ pub fn load(
|
||||
})?,
|
||||
}
|
||||
}
|
||||
ProjectRoot::CargoToml(cargo_toml) => {
|
||||
ProjectManifest::CargoToml(cargo_toml) => {
|
||||
let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
|
@ -4,9 +4,14 @@
|
||||
mod args;
|
||||
|
||||
use lsp_server::Connection;
|
||||
use rust_analyzer::{cli, config::Config, from_json, Result};
|
||||
use rust_analyzer::{
|
||||
cli,
|
||||
config::{Config, LinkedProject},
|
||||
from_json, Result,
|
||||
};
|
||||
|
||||
use crate::args::HelpPrinted;
|
||||
use ra_project_model::ProjectManifest;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
setup_logging()?;
|
||||
@ -97,17 +102,6 @@ fn run_server() -> Result<()> {
|
||||
log::info!("Client '{}' {}", client_info.name, client_info.version.unwrap_or_default());
|
||||
}
|
||||
|
||||
let cwd = std::env::current_dir()?;
|
||||
let root = initialize_params.root_uri.and_then(|it| it.to_file_path().ok()).unwrap_or(cwd);
|
||||
|
||||
let workspace_roots = initialize_params
|
||||
.workspace_folders
|
||||
.map(|workspaces| {
|
||||
workspaces.into_iter().filter_map(|it| it.uri.to_file_path().ok()).collect::<Vec<_>>()
|
||||
})
|
||||
.filter(|workspaces| !workspaces.is_empty())
|
||||
.unwrap_or_else(|| vec![root]);
|
||||
|
||||
let config = {
|
||||
let mut config = Config::default();
|
||||
if let Some(value) = &initialize_params.initialization_options {
|
||||
@ -115,10 +109,31 @@ fn run_server() -> Result<()> {
|
||||
}
|
||||
config.update_caps(&initialize_params.capabilities);
|
||||
|
||||
if config.linked_projects.is_empty() {
|
||||
let cwd = std::env::current_dir()?;
|
||||
let root =
|
||||
initialize_params.root_uri.and_then(|it| it.to_file_path().ok()).unwrap_or(cwd);
|
||||
let workspace_roots = initialize_params
|
||||
.workspace_folders
|
||||
.map(|workspaces| {
|
||||
workspaces
|
||||
.into_iter()
|
||||
.filter_map(|it| it.uri.to_file_path().ok())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.filter(|workspaces| !workspaces.is_empty())
|
||||
.unwrap_or_else(|| vec![root]);
|
||||
|
||||
config.linked_projects = ProjectManifest::discover_all(&workspace_roots)
|
||||
.into_iter()
|
||||
.map(LinkedProject::from)
|
||||
.collect();
|
||||
}
|
||||
|
||||
config
|
||||
};
|
||||
|
||||
rust_analyzer::main_loop(workspace_roots, config, connection)?;
|
||||
rust_analyzer::main_loop(config, connection)?;
|
||||
|
||||
log::info!("shutting down IO...");
|
||||
io_threads.join()?;
|
||||
|
@ -8,7 +8,8 @@
|
||||
use ra_db::{ExternSourceId, FileId, SourceRootId};
|
||||
use ra_ide::{AnalysisChange, AnalysisHost};
|
||||
use ra_project_model::{
|
||||
get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectRoot, ProjectWorkspace,
|
||||
get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectManifest,
|
||||
ProjectWorkspace,
|
||||
};
|
||||
use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
@ -28,7 +29,7 @@ pub fn load_cargo(
|
||||
with_proc_macro: bool,
|
||||
) -> Result<(AnalysisHost, FxHashMap<SourceRootId, PackageRoot>)> {
|
||||
let root = std::env::current_dir()?.join(root);
|
||||
let root = ProjectRoot::discover_single(&root)?;
|
||||
let root = ProjectManifest::discover_single(&root)?;
|
||||
let ws = ProjectWorkspace::load(
|
||||
root,
|
||||
&CargoConfig { load_out_dirs_from_check, ..Default::default() },
|
||||
|
@ -12,14 +12,13 @@
|
||||
use lsp_types::ClientCapabilities;
|
||||
use ra_flycheck::FlycheckConfig;
|
||||
use ra_ide::{AssistConfig, CompletionConfig, InlayHintsConfig};
|
||||
use ra_project_model::CargoConfig;
|
||||
use ra_project_model::{CargoConfig, JsonProject, ProjectManifest};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Config {
|
||||
pub client_caps: ClientCapsConfig,
|
||||
|
||||
pub with_sysroot: bool,
|
||||
pub publish_diagnostics: bool,
|
||||
pub lru_capacity: Option<usize>,
|
||||
pub proc_macro_srv: Option<(PathBuf, Vec<OsString>)>,
|
||||
@ -35,6 +34,27 @@ pub struct Config {
|
||||
pub assist: AssistConfig,
|
||||
pub call_info_full: bool,
|
||||
pub lens: LensConfig,
|
||||
|
||||
pub with_sysroot: bool,
|
||||
pub linked_projects: Vec<LinkedProject>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LinkedProject {
|
||||
ProjectManifest(ProjectManifest),
|
||||
JsonProject(JsonProject),
|
||||
}
|
||||
|
||||
impl From<ProjectManifest> for LinkedProject {
|
||||
fn from(v: ProjectManifest) -> Self {
|
||||
LinkedProject::ProjectManifest(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsonProject> for LinkedProject {
|
||||
fn from(v: JsonProject) -> Self {
|
||||
LinkedProject::JsonProject(v)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@ -141,6 +161,7 @@ fn default() -> Self {
|
||||
assist: AssistConfig::default(),
|
||||
call_info_full: true,
|
||||
lens: LensConfig::default(),
|
||||
linked_projects: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,13 +12,11 @@
|
||||
fmt,
|
||||
ops::Range,
|
||||
panic,
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crossbeam_channel::{never, select, unbounded, RecvError, Sender};
|
||||
use itertools::Itertools;
|
||||
use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
|
||||
use lsp_types::{
|
||||
DidChangeTextDocumentParams, NumberOrString, TextDocumentContentChangeEvent, WorkDoneProgress,
|
||||
@ -36,7 +34,7 @@
|
||||
use threadpool::ThreadPool;
|
||||
|
||||
use crate::{
|
||||
config::{Config, FilesWatcher},
|
||||
config::{Config, FilesWatcher, LinkedProject},
|
||||
diagnostics::{to_proto::url_from_path_with_drive_lowercasing, DiagnosticTask},
|
||||
from_proto,
|
||||
global_state::{GlobalState, GlobalStateSnapshot},
|
||||
@ -70,7 +68,7 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
|
||||
impl Error for LspError {}
|
||||
|
||||
pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection) -> Result<()> {
|
||||
pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
|
||||
log::info!("initial config: {:#?}", config);
|
||||
|
||||
// Windows scheduler implements priority boosts: if thread waits for an
|
||||
@ -95,29 +93,24 @@ pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection)
|
||||
let mut loop_state = LoopState::default();
|
||||
let mut global_state = {
|
||||
let workspaces = {
|
||||
// FIXME: support dynamic workspace loading.
|
||||
let project_roots: FxHashSet<_> = ws_roots
|
||||
.iter()
|
||||
.filter_map(|it| ra_project_model::ProjectRoot::discover(it).ok())
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
if project_roots.is_empty() && config.notifications.cargo_toml_not_found {
|
||||
if config.linked_projects.is_empty() && config.notifications.cargo_toml_not_found {
|
||||
show_message(
|
||||
lsp_types::MessageType::Error,
|
||||
format!(
|
||||
"rust-analyzer failed to discover workspace, no Cargo.toml found, dirs searched: {}",
|
||||
ws_roots.iter().format_with(", ", |it, f| f(&it.display()))
|
||||
),
|
||||
"rust-analyzer failed to discover workspace".to_string(),
|
||||
&connection.sender,
|
||||
);
|
||||
};
|
||||
|
||||
project_roots
|
||||
.into_iter()
|
||||
config
|
||||
.linked_projects
|
||||
.iter()
|
||||
.filter_map(|project| match project {
|
||||
LinkedProject::ProjectManifest(it) => Some(it),
|
||||
LinkedProject::JsonProject(_) => None,
|
||||
})
|
||||
.filter_map(|root| {
|
||||
ra_project_model::ProjectWorkspace::load(
|
||||
root,
|
||||
root.clone(),
|
||||
&config.cargo,
|
||||
config.with_sysroot,
|
||||
)
|
||||
|
@ -19,8 +19,9 @@
|
||||
use tempfile::TempDir;
|
||||
use test_utils::{find_mismatch, parse_fixture};
|
||||
|
||||
use ra_project_model::ProjectManifest;
|
||||
use rust_analyzer::{
|
||||
config::{ClientCapsConfig, Config},
|
||||
config::{ClientCapsConfig, Config, LinkedProject},
|
||||
main_loop,
|
||||
};
|
||||
|
||||
@ -42,7 +43,7 @@ pub fn tmp_dir(mut self, tmp_dir: TempDir) -> Project<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn root(mut self, path: &str) -> Project<'a> {
|
||||
pub(crate) fn root(mut self, path: &str) -> Project<'a> {
|
||||
self.roots.push(path.into());
|
||||
self
|
||||
}
|
||||
@ -74,7 +75,16 @@ pub fn server(self) -> Server {
|
||||
paths.push((path, entry.text));
|
||||
}
|
||||
|
||||
let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect();
|
||||
let mut roots =
|
||||
self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect::<Vec<_>>();
|
||||
if roots.is_empty() {
|
||||
roots.push(tmp_dir.path().to_path_buf());
|
||||
}
|
||||
let linked_projects = roots
|
||||
.into_iter()
|
||||
.map(|it| ProjectManifest::discover_single(&it).unwrap())
|
||||
.map(LinkedProject::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut config = Config {
|
||||
client_caps: ClientCapsConfig {
|
||||
@ -84,6 +94,7 @@ pub fn server(self) -> Server {
|
||||
..Default::default()
|
||||
},
|
||||
with_sysroot: self.with_sysroot,
|
||||
linked_projects,
|
||||
..Config::default()
|
||||
};
|
||||
|
||||
@ -91,7 +102,7 @@ pub fn server(self) -> Server {
|
||||
f(&mut config)
|
||||
}
|
||||
|
||||
Server::new(tmp_dir, config, roots, paths)
|
||||
Server::new(tmp_dir, config, paths)
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,20 +120,12 @@ pub struct Server {
|
||||
}
|
||||
|
||||
impl Server {
|
||||
fn new(
|
||||
dir: TempDir,
|
||||
config: Config,
|
||||
roots: Vec<PathBuf>,
|
||||
files: Vec<(PathBuf, String)>,
|
||||
) -> Server {
|
||||
let path = dir.path().to_path_buf();
|
||||
|
||||
let roots = if roots.is_empty() { vec![path] } else { roots };
|
||||
fn new(dir: TempDir, config: Config, files: Vec<(PathBuf, String)>) -> Server {
|
||||
let (connection, client) = Connection::memory();
|
||||
|
||||
let _thread = jod_thread::Builder::new()
|
||||
.name("test server".to_string())
|
||||
.spawn(move || main_loop(roots, config, connection).unwrap())
|
||||
.spawn(move || main_loop(config, connection).unwrap())
|
||||
.expect("failed to spawn a thread");
|
||||
|
||||
let res =
|
||||
|
Loading…
Reference in New Issue
Block a user