Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discover config improvements #18480

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion crates/rust-analyzer/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,9 @@ config_data! {
/// is used to determine which build system-specific files should be watched in order to
/// reload rust-analyzer.
///
/// Optionally, `run_at_startup` can be set so that project discovery runs when the workspace
/// is opened. Note that at startup no `DiscoverArgument` (described below) gets passed.
///
/// Below is an example of a valid configuration:
/// ```json
/// "rust-analyzer.workspace.discoverConfig": {
Expand All @@ -329,7 +332,8 @@ config_data! {
/// "progressLabel": "rust-analyzer",
/// "filesToWatch": [
/// "BUCK"
/// ]
/// ],
/// "runAtStartup": true
/// }
/// ```
///
Expand Down Expand Up @@ -400,6 +404,9 @@ config_data! {
/// }
/// ```
///
/// This argument is not passed to the startup discovery which is enabled by the
/// `runAtStartup` setting.
///
/// `DiscoverArgument::Path` is used to find and generate a `rust-project.json`,
/// and therefore, a workspace, whereas `DiscoverArgument::buildfile` is used to
/// to update an existing workspace. As a reference for implementors,
Expand Down Expand Up @@ -1138,6 +1145,8 @@ pub struct DiscoverWorkspaceConfig {
pub command: Vec<String>,
pub progress_label: String,
pub files_to_watch: Vec<String>,
#[serde(default)]
pub run_at_startup: bool,
}

pub struct CallInfoConfig {
Expand Down
7 changes: 5 additions & 2 deletions crates/rust-analyzer/src/discover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,17 @@ impl DiscoverCommand {
}

/// Spawn the command inside [Discover] and report progress, if any.
pub(crate) fn spawn(&self, discover_arg: DiscoverArgument) -> io::Result<DiscoverHandle> {
pub(crate) fn spawn(
&self,
discover_arg: Option<DiscoverArgument>,
) -> io::Result<DiscoverHandle> {
let command = &self.command[0];
let args = &self.command[1..];

let args: Vec<String> = args
.iter()
.map(|arg| {
if arg == ARG_PLACEHOLDER {
if arg == ARG_PLACEHOLDER && discover_arg.is_some() {
serde_json::to_string(&discover_arg).expect("Unable to serialize args")
} else {
arg.to_owned()
Expand Down
19 changes: 18 additions & 1 deletion crates/rust-analyzer/src/main_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,23 @@ impl GlobalState {
self.register_did_save_capability(additional_patterns);
}

if let Some(cfg) = self.config.discover_workspace_config() {
if cfg.run_at_startup && !self.discover_workspace_queue.op_in_progress() {
// the clone is unfortunately necessary to avoid a borrowck error when
// `self.report_progress` is called later
let title = &cfg.progress_label.clone();
let command = cfg.command.clone();
let discover = DiscoverCommand::new(self.discover_sender.clone(), command);

self.report_progress(title, Progress::Begin, None, None, None);
self.discover_workspace_queue.request_op("startup discovery".to_owned(), ());
let _ = self.discover_workspace_queue.should_start_op();

let handle = discover.spawn(None).unwrap();
self.discover_handle = Some(handle);
}
}

if self.config.discover_workspace_config().is_none() {
self.fetch_workspaces_queue.request_op(
"startup".to_owned(),
Expand Down Expand Up @@ -740,7 +757,7 @@ impl GlobalState {
DiscoverProjectParam::Path(it) => DiscoverArgument::Path(it),
};

let handle = discover.spawn(arg).unwrap();
let handle = discover.spawn(Some(arg)).unwrap();
self.discover_handle = Some(handle);
}
}
Expand Down
40 changes: 18 additions & 22 deletions crates/rust-analyzer/src/reload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -530,26 +530,37 @@ impl GlobalState {
.filter(|it| it.is_local)
.map(|it| it.include);

let additional_filters = self
.config
.discover_workspace_config()
.into_iter()
.flat_map(|cfg| cfg.files_to_watch.iter())
.flat_map(|pat| self.workspaces.iter().map(move |ws| (ws.workspace_root(), pat)));

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(|include| {
include.into_iter().flat_map(|base| {
[
(base.clone(), "**/*.rs"),
(base.clone(), "**/Cargo.{lock,toml}"),
(base, "**/rust-analyzer.toml"),
(base.clone(), "**/*.rs".to_owned()),
(base.clone(), "**/Cargo.{lock,toml}".to_owned()),
(base.clone(), "**/rust-analyzer.toml".to_owned()),
]
})
})
.map(|(base, pat)| lsp_types::FileSystemWatcher {
.chain(
additional_filters
.map(|(base, pat)| (base.to_owned(), format!("**/{pat}"))),
)
.map(|(base, pattern)| 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(),
pattern,
},
),
kind: None,
Expand All @@ -567,30 +578,14 @@ impl GlobalState {
]
})
})
.chain(additional_filters.map(|(base, pat)| format!("{base}/**/{pat}")))
.map(|glob_pattern| lsp_types::FileSystemWatcher {
glob_pattern: lsp_types::GlobPattern::String(glob_pattern),
kind: None,
})
.collect()
};

// Also explicitly watch any build files configured in JSON project files.
for ws in self.workspaces.iter() {
if let ProjectWorkspaceKind::Json(project_json) = &ws.kind {
for (_, krate) in project_json.crates() {
let Some(build) = &krate.build else {
continue;
};
watchers.push(lsp_types::FileSystemWatcher {
glob_pattern: lsp_types::GlobPattern::String(
build.build_file.to_string(),
),
kind: None,
});
}
}
}

watchers.extend(
iter::once(Config::user_config_path())
.chain(self.workspaces.iter().map(|ws| ws.manifest().map(ManifestPath::as_ref)))
Expand All @@ -608,6 +603,7 @@ impl GlobalState {
method: "workspace/didChangeWatchedFiles".to_owned(),
register_options: Some(serde_json::to_value(registration_options).unwrap()),
};

self.send_request::<lsp_types::request::RegisterCapability>(
lsp_types::RegistrationParams { registrations: vec![registration] },
|_, _| (),
Expand Down
9 changes: 8 additions & 1 deletion docs/user/generated_config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,9 @@ Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command
is used to determine which build system-specific files should be watched in order to
reload rust-analyzer.

Optionally, `run_at_startup` can be set so that project discovery runs when the workspace
is opened. Note that at startup no `DiscoverArgument` (described below) gets passed.

Below is an example of a valid configuration:
```json
"rust-analyzer.workspace.discoverConfig": {
Expand All @@ -1011,7 +1014,8 @@ Below is an example of a valid configuration:
"progressLabel": "rust-analyzer",
"filesToWatch": [
"BUCK"
]
],
"runAtStartup": true
}
```

Expand Down Expand Up @@ -1082,6 +1086,9 @@ Similarly, the JSON representation of `DiscoverArgument::Buildfile` is:
}
```

This argument is not passed to the startup discovery which is enabled by the
`runAtStartup` setting.

`DiscoverArgument::Path` is used to find and generate a `rust-project.json`,
and therefore, a workspace, whereas `DiscoverArgument::buildfile` is used to
to update an existing workspace. As a reference for implementors,
Expand Down
2 changes: 1 addition & 1 deletion editors/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2559,7 +2559,7 @@
"title": "workspace",
"properties": {
"rust-analyzer.workspace.discoverConfig": {
"markdownDescription": "Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`].\n\n[`DiscoverWorkspaceConfig`] also requires setting `progress_label` and `files_to_watch`.\n`progress_label` is used for the title in progress indicators, whereas `files_to_watch`\nis used to determine which build system-specific files should be watched in order to\nreload rust-analyzer.\n\nBelow is an example of a valid configuration:\n```json\n\"rust-analyzer.workspace.discoverConfig\": {\n \"command\": [\n \"rust-project\",\n \"develop-json\"\n ],\n \"progressLabel\": \"rust-analyzer\",\n \"filesToWatch\": [\n \"BUCK\"\n ]\n}\n```\n\n## On `DiscoverWorkspaceConfig::command`\n\n**Warning**: This format is provisional and subject to change.\n\n[`DiscoverWorkspaceConfig::command`] *must* return a JSON object\ncorresponding to `DiscoverProjectData::Finished`:\n\n```norun\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[serde(tag = \"kind\")]\n#[serde(rename_all = \"snake_case\")]\nenum DiscoverProjectData {\n Finished { buildfile: Utf8PathBuf, project: ProjectJsonData },\n Error { error: String, source: Option<String> },\n Progress { message: String },\n}\n```\n\nAs JSON, `DiscoverProjectData::Finished` is:\n\n```json\n{\n // the internally-tagged representation of the enum.\n \"kind\": \"finished\",\n // the file used by a non-Cargo build system to define\n // a package or target.\n \"buildfile\": \"rust-analyzer/BUILD\",\n // the contents of a rust-project.json, elided for brevity\n \"project\": {\n \"sysroot\": \"foo\",\n \"crates\": []\n }\n}\n```\n\nIt is encouraged, but not required, to use the other variants on\n`DiscoverProjectData` to provide a more polished end-user experience.\n\n`DiscoverWorkspaceConfig::command` may *optionally* include an `{arg}`,\nwhich will be substituted with the JSON-serialized form of the following\nenum:\n\n```norun\n#[derive(PartialEq, Clone, Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub enum DiscoverArgument {\n Path(AbsPathBuf),\n Buildfile(AbsPathBuf),\n}\n```\n\nThe JSON representation of `DiscoverArgument::Path` is:\n\n```json\n{\n \"path\": \"src/main.rs\"\n}\n```\n\nSimilarly, the JSON representation of `DiscoverArgument::Buildfile` is:\n\n```\n{\n \"buildfile\": \"BUILD\"\n}\n```\n\n`DiscoverArgument::Path` is used to find and generate a `rust-project.json`,\nand therefore, a workspace, whereas `DiscoverArgument::buildfile` is used to\nto update an existing workspace. As a reference for implementors,\nbuck2's `rust-project` will likely be useful:\nhttps://github.com/facebook/buck2/tree/main/integrations/rust-project.",
"markdownDescription": "Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`].\n\n[`DiscoverWorkspaceConfig`] also requires setting `progress_label` and `files_to_watch`.\n`progress_label` is used for the title in progress indicators, whereas `files_to_watch`\nis used to determine which build system-specific files should be watched in order to\nreload rust-analyzer.\n\nOptionally, `run_at_startup` can be set so that project discovery runs when the workspace\nis opened. Note that at startup no `DiscoverArgument` (described below) gets passed.\n\nBelow is an example of a valid configuration:\n```json\n\"rust-analyzer.workspace.discoverConfig\": {\n \"command\": [\n \"rust-project\",\n \"develop-json\"\n ],\n \"progressLabel\": \"rust-analyzer\",\n \"filesToWatch\": [\n \"BUCK\"\n ],\n \"runAtStartup\": true\n}\n```\n\n## On `DiscoverWorkspaceConfig::command`\n\n**Warning**: This format is provisional and subject to change.\n\n[`DiscoverWorkspaceConfig::command`] *must* return a JSON object\ncorresponding to `DiscoverProjectData::Finished`:\n\n```norun\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[serde(tag = \"kind\")]\n#[serde(rename_all = \"snake_case\")]\nenum DiscoverProjectData {\n Finished { buildfile: Utf8PathBuf, project: ProjectJsonData },\n Error { error: String, source: Option<String> },\n Progress { message: String },\n}\n```\n\nAs JSON, `DiscoverProjectData::Finished` is:\n\n```json\n{\n // the internally-tagged representation of the enum.\n \"kind\": \"finished\",\n // the file used by a non-Cargo build system to define\n // a package or target.\n \"buildfile\": \"rust-analyzer/BUILD\",\n // the contents of a rust-project.json, elided for brevity\n \"project\": {\n \"sysroot\": \"foo\",\n \"crates\": []\n }\n}\n```\n\nIt is encouraged, but not required, to use the other variants on\n`DiscoverProjectData` to provide a more polished end-user experience.\n\n`DiscoverWorkspaceConfig::command` may *optionally* include an `{arg}`,\nwhich will be substituted with the JSON-serialized form of the following\nenum:\n\n```norun\n#[derive(PartialEq, Clone, Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub enum DiscoverArgument {\n Path(AbsPathBuf),\n Buildfile(AbsPathBuf),\n}\n```\n\nThe JSON representation of `DiscoverArgument::Path` is:\n\n```json\n{\n \"path\": \"src/main.rs\"\n}\n```\n\nSimilarly, the JSON representation of `DiscoverArgument::Buildfile` is:\n\n```\n{\n \"buildfile\": \"BUILD\"\n}\n```\n\nThis argument is not passed to the startup discovery which is enabled by the\n`runAtStartup` setting.\n\n`DiscoverArgument::Path` is used to find and generate a `rust-project.json`,\nand therefore, a workspace, whereas `DiscoverArgument::buildfile` is used to\nto update an existing workspace. As a reference for implementors,\nbuck2's `rust-project` will likely be useful:\nhttps://github.com/facebook/buck2/tree/main/integrations/rust-project.",
"default": null,
"anyOf": [
{
Expand Down