aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-05-24 13:40:25 +0100
committerGitHub <[email protected]>2021-05-24 13:40:25 +0100
commit05fc97e31b1d04bf5d5885edd98a1510f0931a62 (patch)
tree27a99294690e75990250b0c1306ef99fad4558fa
parent31a19148e967163ea9ebb42e341944be76ce8960 (diff)
parent5c0369b1d0c5351672f2a16e9a0d17beee84bcbe (diff)
Merge #8955
8955: feature: Support standalone Rust files r=matklad a=SomeoneToIgnore ![standalone](https://user-images.githubusercontent.com/2690773/119277037-0b579380-bc26-11eb-8d77-20d46ab4916a.gif) Closes https://github.com/rust-analyzer/rust-analyzer/issues/6388 Caveats: * I've decided to support multiple detached files in the code (anticipating the scratch files), but I found no way to open multiple files in VSCode at once: running `code *.rs` makes the plugin to register in the `vscode.workspace.textDocuments` only the first file, while code actually displays all files later. Apparently what happens is the same as when you have VSCode open at some workplace already and then run `code some_other_file.rs`: it gets opened in the same workspace of the same VSCode with no server to support it. If there's a way to override it, I'd appreciate the pointer. * No way to toggle inlay hints, since the setting is updated for the workspace (which does not exist for a single file opened) > [2021-05-24 00:22:49.100] [exthost] [error] Error: Unable to write to Workspace Settings because no workspace is opened. Please open a workspace first and try again. * No runners/lens to run or check the code are implemented for this mode. In theory, we can detect `rustc`, run it on a file and run the resulting binary, but not sure if worth doing it at this stage. Otherwise imports, hints, completion and other features work. Co-authored-by: Kirill Bulatov <[email protected]>
-rw-r--r--crates/project_model/src/sysroot.rs4
-rw-r--r--crates/project_model/src/workspace.rs89
-rw-r--r--crates/rust-analyzer/src/bin/main.rs3
-rw-r--r--crates/rust-analyzer/src/config.rs19
-rw-r--r--crates/rust-analyzer/src/global_state.rs1
-rw-r--r--crates/rust-analyzer/src/handlers.rs35
-rw-r--r--crates/rust-analyzer/src/main_loop.rs1
-rw-r--r--crates/rust-analyzer/src/reload.rs9
-rw-r--r--editors/code/src/client.ts15
-rw-r--r--editors/code/src/ctx.ts14
-rw-r--r--editors/code/src/main.ts81
11 files changed, 210 insertions, 61 deletions
diff --git a/crates/project_model/src/sysroot.rs b/crates/project_model/src/sysroot.rs
index 3b0ff506d..4e39d6dd3 100644
--- a/crates/project_model/src/sysroot.rs
+++ b/crates/project_model/src/sysroot.rs
@@ -50,7 +50,9 @@ impl Sysroot {
50 50
51 pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> { 51 pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> {
52 log::debug!("Discovering sysroot for {}", cargo_toml.display()); 52 log::debug!("Discovering sysroot for {}", cargo_toml.display());
53 let current_dir = cargo_toml.parent().unwrap(); 53 let current_dir = cargo_toml.parent().ok_or_else(|| {
54 format_err!("Failed to find the parent directory for {}", cargo_toml.display())
55 })?;
54 let sysroot_dir = discover_sysroot_dir(current_dir)?; 56 let sysroot_dir = discover_sysroot_dir(current_dir)?;
55 let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir, current_dir)?; 57 let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir, current_dir)?;
56 let res = Sysroot::load(&sysroot_src_dir)?; 58 let res = Sysroot::load(&sysroot_src_dir)?;
diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs
index 607e62ea5..84990075f 100644
--- a/crates/project_model/src/workspace.rs
+++ b/crates/project_model/src/workspace.rs
@@ -4,7 +4,7 @@
4 4
5use std::{collections::VecDeque, fmt, fs, path::Path, process::Command}; 5use std::{collections::VecDeque, fmt, fs, path::Path, process::Command};
6 6
7use anyhow::{Context, Result}; 7use anyhow::{format_err, Context, Result};
8use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId, ProcMacro}; 8use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId, ProcMacro};
9use cargo_workspace::DepKind; 9use cargo_workspace::DepKind;
10use cfg::CfgOptions; 10use cfg::CfgOptions;
@@ -49,6 +49,18 @@ pub enum ProjectWorkspace {
49 }, 49 },
50 /// Project workspace was manually specified using a `rust-project.json` file. 50 /// Project workspace was manually specified using a `rust-project.json` file.
51 Json { project: ProjectJson, sysroot: Option<Sysroot>, rustc_cfg: Vec<CfgFlag> }, 51 Json { project: ProjectJson, sysroot: Option<Sysroot>, rustc_cfg: Vec<CfgFlag> },
52
53 // FIXME: The primary limitation of this approach is that the set of detached files needs to be fixed at the beginning.
54 // That's not the end user experience we should strive for.
55 // Ideally, you should be able to just open a random detached file in existing cargo projects, and get the basic features working.
56 // That needs some changes on the salsa-level though.
57 // In particular, we should split the unified CrateGraph (which currently has maximal durability) into proper crate graph, and a set of ad hoc roots (with minimal durability).
58 // Then, we need to hide the graph behind the queries such that most queries look only at the proper crate graph, and fall back to ad hoc roots only if there's no results.
59 // After this, we should be able to tweak the logic in reload.rs to add newly opened files, which don't belong to any existing crates, to the set of the detached files.
60 // //
61 /// Project with a set of disjoint files, not belonging to any particular workspace.
62 /// Backed by basic sysroot crates for basic completion and highlighting.
63 DetachedFiles { files: Vec<AbsPathBuf>, sysroot: Sysroot, rustc_cfg: Vec<CfgFlag> },
52} 64}
53 65
54impl fmt::Debug for ProjectWorkspace { 66impl fmt::Debug for ProjectWorkspace {
@@ -75,6 +87,12 @@ impl fmt::Debug for ProjectWorkspace {
75 debug_struct.field("n_rustc_cfg", &rustc_cfg.len()); 87 debug_struct.field("n_rustc_cfg", &rustc_cfg.len());
76 debug_struct.finish() 88 debug_struct.finish()
77 } 89 }
90 ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => f
91 .debug_struct("DetachedFiles")
92 .field("n_files", &files.len())
93 .field("n_sysroot_crates", &sysroot.crates().len())
94 .field("n_rustc_cfg", &rustc_cfg.len())
95 .finish(),
78 } 96 }
79 } 97 }
80} 98}
@@ -165,6 +183,14 @@ impl ProjectWorkspace {
165 Ok(ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg }) 183 Ok(ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg })
166 } 184 }
167 185
186 pub fn load_detached_files(detached_files: Vec<AbsPathBuf>) -> Result<ProjectWorkspace> {
187 let sysroot = Sysroot::discover(
188 &detached_files.first().ok_or_else(|| format_err!("No detached files to load"))?,
189 )?;
190 let rustc_cfg = rustc_cfg::get(None, None);
191 Ok(ProjectWorkspace::DetachedFiles { files: detached_files, sysroot, rustc_cfg })
192 }
193
168 /// Returns the roots for the current `ProjectWorkspace` 194 /// Returns the roots for the current `ProjectWorkspace`
169 /// The return type contains the path and whether or not 195 /// The return type contains the path and whether or not
170 /// the root is a member of the current workspace 196 /// the root is a member of the current workspace
@@ -224,6 +250,19 @@ impl ProjectWorkspace {
224 }) 250 })
225 })) 251 }))
226 .collect(), 252 .collect(),
253 ProjectWorkspace::DetachedFiles { files, sysroot, .. } => files
254 .into_iter()
255 .map(|detached_file| PackageRoot {
256 is_member: true,
257 include: vec![detached_file.clone()],
258 exclude: Vec::new(),
259 })
260 .chain(sysroot.crates().map(|krate| PackageRoot {
261 is_member: false,
262 include: vec![sysroot[krate].root_dir().to_path_buf()],
263 exclude: Vec::new(),
264 }))
265 .collect(),
227 } 266 }
228 } 267 }
229 268
@@ -234,6 +273,9 @@ impl ProjectWorkspace {
234 let rustc_package_len = rustc.as_ref().map_or(0, |rc| rc.packages().len()); 273 let rustc_package_len = rustc.as_ref().map_or(0, |rc| rc.packages().len());
235 cargo.packages().len() + sysroot.crates().len() + rustc_package_len 274 cargo.packages().len() + sysroot.crates().len() + rustc_package_len
236 } 275 }
276 ProjectWorkspace::DetachedFiles { sysroot, files, .. } => {
277 sysroot.crates().len() + files.len()
278 }
237 } 279 }
238 } 280 }
239 281
@@ -267,6 +309,9 @@ impl ProjectWorkspace {
267 rustc, 309 rustc,
268 rustc.as_ref().zip(build_data).and_then(|(it, map)| map.get(it.workspace_root())), 310 rustc.as_ref().zip(build_data).and_then(|(it, map)| map.get(it.workspace_root())),
269 ), 311 ),
312 ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => {
313 detached_files_to_crate_graph(rustc_cfg.clone(), load, files, sysroot)
314 }
270 }; 315 };
271 if crate_graph.patch_cfg_if() { 316 if crate_graph.patch_cfg_if() {
272 log::debug!("Patched std to depend on cfg-if") 317 log::debug!("Patched std to depend on cfg-if")
@@ -474,6 +519,48 @@ fn cargo_to_crate_graph(
474 crate_graph 519 crate_graph
475} 520}
476 521
522fn detached_files_to_crate_graph(
523 rustc_cfg: Vec<CfgFlag>,
524 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
525 detached_files: &[AbsPathBuf],
526 sysroot: &Sysroot,
527) -> CrateGraph {
528 let _p = profile::span("detached_files_to_crate_graph");
529 let mut crate_graph = CrateGraph::default();
530 let (public_deps, _libproc_macro) =
531 sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load);
532
533 let mut cfg_options = CfgOptions::default();
534 cfg_options.extend(rustc_cfg);
535
536 for detached_file in detached_files {
537 let file_id = match load(&detached_file) {
538 Some(file_id) => file_id,
539 None => {
540 log::error!("Failed to load detached file {:?}", detached_file);
541 continue;
542 }
543 };
544 let display_name = detached_file
545 .file_stem()
546 .and_then(|os_str| os_str.to_str())
547 .map(|file_stem| CrateDisplayName::from_canonical_name(file_stem.to_string()));
548 let detached_file_crate = crate_graph.add_crate_root(
549 file_id,
550 Edition::Edition2018,
551 display_name,
552 cfg_options.clone(),
553 Env::default(),
554 Vec::new(),
555 );
556
557 for (name, krate) in public_deps.iter() {
558 add_dep(&mut crate_graph, detached_file_crate, name.clone(), *krate);
559 }
560 }
561 crate_graph
562}
563
477fn handle_rustc_crates( 564fn handle_rustc_crates(
478 rustc_workspace: &CargoWorkspace, 565 rustc_workspace: &CargoWorkspace,
479 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, 566 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs
index f0abb5b15..6c883dd58 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -199,7 +199,7 @@ fn run_server() -> Result<()> {
199 config.update(json); 199 config.update(json);
200 } 200 }
201 201
202 if config.linked_projects().is_empty() { 202 if config.linked_projects().is_empty() && config.detached_files().is_empty() {
203 let workspace_roots = initialize_params 203 let workspace_roots = initialize_params
204 .workspace_folders 204 .workspace_folders
205 .map(|workspaces| { 205 .map(|workspaces| {
@@ -217,7 +217,6 @@ fn run_server() -> Result<()> {
217 if discovered.is_empty() { 217 if discovered.is_empty() {
218 log::error!("failed to find any projects in {:?}", workspace_roots); 218 log::error!("failed to find any projects in {:?}", workspace_roots);
219 } 219 }
220
221 config.discovered_projects = Some(discovered); 220 config.discovered_projects = Some(discovered);
222 } 221 }
223 222
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index b700d025f..7c02a507c 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -236,6 +236,7 @@ impl Default for ConfigData {
236pub struct Config { 236pub struct Config {
237 caps: lsp_types::ClientCapabilities, 237 caps: lsp_types::ClientCapabilities,
238 data: ConfigData, 238 data: ConfigData,
239 detached_files: Vec<AbsPathBuf>,
239 pub discovered_projects: Option<Vec<ProjectManifest>>, 240 pub discovered_projects: Option<Vec<ProjectManifest>>,
240 pub root_path: AbsPathBuf, 241 pub root_path: AbsPathBuf,
241} 242}
@@ -328,13 +329,23 @@ pub struct WorkspaceSymbolConfig {
328 329
329impl Config { 330impl Config {
330 pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self { 331 pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self {
331 Config { caps, data: ConfigData::default(), discovered_projects: None, root_path } 332 Config {
333 caps,
334 data: ConfigData::default(),
335 detached_files: Vec::new(),
336 discovered_projects: None,
337 root_path,
338 }
332 } 339 }
333 pub fn update(&mut self, json: serde_json::Value) { 340 pub fn update(&mut self, mut json: serde_json::Value) {
334 log::info!("updating config from JSON: {:#}", json); 341 log::info!("updating config from JSON: {:#}", json);
335 if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) { 342 if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) {
336 return; 343 return;
337 } 344 }
345 self.detached_files = get_field::<Vec<PathBuf>>(&mut json, "detachedFiles", None, "[]")
346 .into_iter()
347 .map(AbsPathBuf::assert)
348 .collect();
338 self.data = ConfigData::from_json(json); 349 self.data = ConfigData::from_json(json);
339 } 350 }
340 351
@@ -387,6 +398,10 @@ impl Config {
387 } 398 }
388 } 399 }
389 400
401 pub fn detached_files(&self) -> &[AbsPathBuf] {
402 &self.detached_files
403 }
404
390 pub fn did_save_text_document_dynamic_registration(&self) -> bool { 405 pub fn did_save_text_document_dynamic_registration(&self) -> bool {
391 let caps = 406 let caps =
392 try_or!(self.caps.text_document.as_ref()?.synchronization.clone()?, Default::default()); 407 try_or!(self.caps.text_document.as_ref()?.synchronization.clone()?, Default::default());
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 6f2f482c1..6a36d29d4 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -312,6 +312,7 @@ impl GlobalStateSnapshot {
312 cargo.target_by_root(&path).map(|it| (cargo, it)) 312 cargo.target_by_root(&path).map(|it| (cargo, it))
313 } 313 }
314 ProjectWorkspace::Json { .. } => None, 314 ProjectWorkspace::Json { .. } => None,
315 ProjectWorkspace::DetachedFiles { .. } => None,
315 }) 316 })
316 } 317 }
317} 318}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index aa12fd94b..f48210424 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -661,19 +661,28 @@ pub(crate) fn handle_runnables(
661 } 661 }
662 } 662 }
663 None => { 663 None => {
664 res.push(lsp_ext::Runnable { 664 if !snap.config.linked_projects().is_empty()
665 label: "cargo check --workspace".to_string(), 665 || !snap
666 location: None, 666 .config
667 kind: lsp_ext::RunnableKind::Cargo, 667 .discovered_projects
668 args: lsp_ext::CargoRunnable { 668 .as_ref()
669 workspace_root: None, 669 .map(|projects| projects.is_empty())
670 override_cargo: config.override_cargo, 670 .unwrap_or(true)
671 cargo_args: vec!["check".to_string(), "--workspace".to_string()], 671 {
672 cargo_extra_args: config.cargo_extra_args, 672 res.push(lsp_ext::Runnable {
673 executable_args: Vec::new(), 673 label: "cargo check --workspace".to_string(),
674 expect_test: None, 674 location: None,
675 }, 675 kind: lsp_ext::RunnableKind::Cargo,
676 }); 676 args: lsp_ext::CargoRunnable {
677 workspace_root: None,
678 override_cargo: config.override_cargo,
679 cargo_args: vec!["check".to_string(), "--workspace".to_string()],
680 cargo_extra_args: config.cargo_extra_args,
681 executable_args: Vec::new(),
682 expect_test: None,
683 },
684 });
685 }
677 } 686 }
678 } 687 }
679 Ok(res) 688 Ok(res)
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index f837b89dd..cb002f700 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -103,6 +103,7 @@ impl fmt::Debug for Event {
103impl GlobalState { 103impl GlobalState {
104 fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> { 104 fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> {
105 if self.config.linked_projects().is_empty() 105 if self.config.linked_projects().is_empty()
106 && self.config.detached_files().is_empty()
106 && self.config.notifications().cargo_toml_not_found 107 && self.config.notifications().cargo_toml_not_found
107 { 108 {
108 self.show_message( 109 self.show_message(
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 0ae2758cc..7a53e4a8b 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -147,6 +147,7 @@ impl GlobalState {
147 147
148 self.task_pool.handle.spawn_with_sender({ 148 self.task_pool.handle.spawn_with_sender({
149 let linked_projects = self.config.linked_projects(); 149 let linked_projects = self.config.linked_projects();
150 let detached_files = self.config.detached_files().to_vec();
150 let cargo_config = self.config.cargo(); 151 let cargo_config = self.config.cargo();
151 152
152 move |sender| { 153 move |sender| {
@@ -161,7 +162,7 @@ impl GlobalState {
161 162
162 sender.send(Task::FetchWorkspace(ProjectWorkspaceProgress::Begin)).unwrap(); 163 sender.send(Task::FetchWorkspace(ProjectWorkspaceProgress::Begin)).unwrap();
163 164
164 let workspaces = linked_projects 165 let mut workspaces = linked_projects
165 .iter() 166 .iter()
166 .map(|project| match project { 167 .map(|project| match project {
167 LinkedProject::ProjectManifest(manifest) => { 168 LinkedProject::ProjectManifest(manifest) => {
@@ -180,6 +181,11 @@ impl GlobalState {
180 }) 181 })
181 .collect::<Vec<_>>(); 182 .collect::<Vec<_>>();
182 183
184 if !detached_files.is_empty() {
185 workspaces
186 .push(project_model::ProjectWorkspace::load_detached_files(detached_files));
187 }
188
183 log::info!("did fetch workspaces {:?}", workspaces); 189 log::info!("did fetch workspaces {:?}", workspaces);
184 sender 190 sender
185 .send(Task::FetchWorkspace(ProjectWorkspaceProgress::End(workspaces))) 191 .send(Task::FetchWorkspace(ProjectWorkspaceProgress::End(workspaces)))
@@ -407,6 +413,7 @@ impl GlobalState {
407 _ => None, 413 _ => None,
408 } 414 }
409 } 415 }
416 ProjectWorkspace::DetachedFiles { .. } => None,
410 }) 417 })
411 .map(|(id, root)| { 418 .map(|(id, root)| {
412 let sender = sender.clone(); 419 let sender = sender.clone();
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index 116f41df6..69dbe2535 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -4,6 +4,7 @@ import * as ra from '../src/lsp_ext';
4import * as Is from 'vscode-languageclient/lib/common/utils/is'; 4import * as Is from 'vscode-languageclient/lib/common/utils/is';
5import { assert } from './util'; 5import { assert } from './util';
6import { WorkspaceEdit } from 'vscode'; 6import { WorkspaceEdit } from 'vscode';
7import { Workspace } from './ctx';
7 8
8export interface Env { 9export interface Env {
9 [name: string]: string; 10 [name: string]: string;
@@ -23,7 +24,7 @@ function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownStri
23 return result; 24 return result;
24} 25}
25 26
26export function createClient(serverPath: string, cwd: string, extraEnv: Env): lc.LanguageClient { 27export function createClient(serverPath: string, workspace: Workspace, extraEnv: Env): lc.LanguageClient {
27 // '.' Is the fallback if no folder is open 28 // '.' Is the fallback if no folder is open
28 // TODO?: Workspace folders support Uri's (eg: file://test.txt). 29 // TODO?: Workspace folders support Uri's (eg: file://test.txt).
29 // It might be a good idea to test if the uri points to a file. 30 // It might be a good idea to test if the uri points to a file.
@@ -31,6 +32,11 @@ export function createClient(serverPath: string, cwd: string, extraEnv: Env): lc
31 const newEnv = Object.assign({}, process.env); 32 const newEnv = Object.assign({}, process.env);
32 Object.assign(newEnv, extraEnv); 33 Object.assign(newEnv, extraEnv);
33 34
35 let cwd = undefined;
36 if (workspace.kind === "Workspace Folder") {
37 cwd = workspace.folder.fsPath;
38 };
39
34 const run: lc.Executable = { 40 const run: lc.Executable = {
35 command: serverPath, 41 command: serverPath,
36 options: { cwd, env: newEnv }, 42 options: { cwd, env: newEnv },
@@ -43,9 +49,14 @@ export function createClient(serverPath: string, cwd: string, extraEnv: Env): lc
43 'Rust Analyzer Language Server Trace', 49 'Rust Analyzer Language Server Trace',
44 ); 50 );
45 51
52 let initializationOptions = vscode.workspace.getConfiguration("rust-analyzer");
53 if (workspace.kind === "Detached Files") {
54 initializationOptions = { "detachedFiles": workspace.files.map(file => file.uri.fsPath), ...initializationOptions };
55 }
56
46 const clientOptions: lc.LanguageClientOptions = { 57 const clientOptions: lc.LanguageClientOptions = {
47 documentSelector: [{ scheme: 'file', language: 'rust' }], 58 documentSelector: [{ scheme: 'file', language: 'rust' }],
48 initializationOptions: vscode.workspace.getConfiguration("rust-analyzer"), 59 initializationOptions,
49 diagnosticCollectionName: "rustc", 60 diagnosticCollectionName: "rustc",
50 traceOutputChannel, 61 traceOutputChannel,
51 middleware: { 62 middleware: {
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index bd023f803..22c5f62a1 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -7,6 +7,16 @@ import { createClient } from './client';
7import { isRustEditor, RustEditor } from './util'; 7import { isRustEditor, RustEditor } from './util';
8import { ServerStatusParams } from './lsp_ext'; 8import { ServerStatusParams } from './lsp_ext';
9 9
10export type Workspace =
11 {
12 kind: 'Workspace Folder';
13 folder: vscode.Uri;
14 }
15 | {
16 kind: 'Detached Files';
17 files: vscode.TextDocument[];
18 };
19
10export class Ctx { 20export class Ctx {
11 private constructor( 21 private constructor(
12 readonly config: Config, 22 readonly config: Config,
@@ -22,9 +32,9 @@ export class Ctx {
22 config: Config, 32 config: Config,
23 extCtx: vscode.ExtensionContext, 33 extCtx: vscode.ExtensionContext,
24 serverPath: string, 34 serverPath: string,
25 cwd: string, 35 workspace: Workspace,
26 ): Promise<Ctx> { 36 ): Promise<Ctx> {
27 const client = createClient(serverPath, cwd, config.serverExtraEnv); 37 const client = createClient(serverPath, workspace, config.serverExtraEnv);
28 38
29 const statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); 39 const statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
30 extCtx.subscriptions.push(statusBar); 40 extCtx.subscriptions.push(statusBar);
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index aaedc2431..b735186fe 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -7,7 +7,7 @@ import * as commands from './commands';
7import { activateInlayHints } from './inlay_hints'; 7import { activateInlayHints } from './inlay_hints';
8import { Ctx } from './ctx'; 8import { Ctx } from './ctx';
9import { Config } from './config'; 9import { Config } from './config';
10import { log, assert, isValidExecutable } from './util'; 10import { log, assert, isValidExecutable, isRustDocument } from './util';
11import { PersistentState } from './persistent_state'; 11import { PersistentState } from './persistent_state';
12import { fetchRelease, download } from './net'; 12import { fetchRelease, download } from './net';
13import { activateTaskProvider } from './tasks'; 13import { activateTaskProvider } from './tasks';
@@ -28,26 +28,6 @@ export async function activate(context: vscode.ExtensionContext) {
28} 28}
29 29
30async function tryActivate(context: vscode.ExtensionContext) { 30async function tryActivate(context: vscode.ExtensionContext) {
31 // Register a "dumb" onEnter command for the case where server fails to
32 // start.
33 //
34 // FIXME: refactor command registration code such that commands are
35 // **always** registered, even if the server does not start. Use API like
36 // this perhaps?
37 //
38 // ```TypeScript
39 // registerCommand(
40 // factory: (Ctx) => ((Ctx) => any),
41 // fallback: () => any = () => vscode.window.showErrorMessage(
42 // "rust-analyzer is not available"
43 // ),
44 // )
45 const defaultOnEnter = vscode.commands.registerCommand(
46 'rust-analyzer.onEnter',
47 () => vscode.commands.executeCommand('default:type', { text: '\n' }),
48 );
49 context.subscriptions.push(defaultOnEnter);
50
51 const config = new Config(context); 31 const config = new Config(context);
52 const state = new PersistentState(context.globalState); 32 const state = new PersistentState(context.globalState);
53 const serverPath = await bootstrap(config, state).catch(err => { 33 const serverPath = await bootstrap(config, state).catch(err => {
@@ -67,14 +47,52 @@ async function tryActivate(context: vscode.ExtensionContext) {
67 47
68 const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; 48 const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
69 if (workspaceFolder === undefined) { 49 if (workspaceFolder === undefined) {
70 throw new Error("no folder is opened"); 50 const rustDocuments = vscode.workspace.textDocuments.filter(document => isRustDocument(document));
51 if (rustDocuments.length > 0) {
52 ctx = await Ctx.create(config, context, serverPath, { kind: 'Detached Files', files: rustDocuments });
53 } else {
54 throw new Error("no rust files are opened");
55 }
56 } else {
57 // Note: we try to start the server before we activate type hints so that it
58 // registers its `onDidChangeDocument` handler before us.
59 //
60 // This a horribly, horribly wrong way to deal with this problem.
61 ctx = await Ctx.create(config, context, serverPath, { kind: "Workspace Folder", folder: workspaceFolder.uri });
62 ctx.pushCleanup(activateTaskProvider(workspaceFolder, ctx.config));
71 } 63 }
64 await initCommonContext(context, ctx);
65
66 activateInlayHints(ctx);
67 warnAboutExtensionConflicts();
68
69 vscode.workspace.onDidChangeConfiguration(
70 _ => ctx?.client?.sendNotification('workspace/didChangeConfiguration', { settings: "" }),
71 null,
72 ctx.subscriptions,
73 );
74}
72 75
73 // Note: we try to start the server before we activate type hints so that it 76async function initCommonContext(context: vscode.ExtensionContext, ctx: Ctx) {
74 // registers its `onDidChangeDocument` handler before us. 77 // Register a "dumb" onEnter command for the case where server fails to
78 // start.
79 //
80 // FIXME: refactor command registration code such that commands are
81 // **always** registered, even if the server does not start. Use API like
82 // this perhaps?
75 // 83 //
76 // This a horribly, horribly wrong way to deal with this problem. 84 // ```TypeScript
77 ctx = await Ctx.create(config, context, serverPath, workspaceFolder.uri.fsPath); 85 // registerCommand(
86 // factory: (Ctx) => ((Ctx) => any),
87 // fallback: () => any = () => vscode.window.showErrorMessage(
88 // "rust-analyzer is not available"
89 // ),
90 // )
91 const defaultOnEnter = vscode.commands.registerCommand(
92 'rust-analyzer.onEnter',
93 () => vscode.commands.executeCommand('default:type', { text: '\n' }),
94 );
95 context.subscriptions.push(defaultOnEnter);
78 96
79 await setContextValue(RUST_PROJECT_CONTEXT_NAME, true); 97 await setContextValue(RUST_PROJECT_CONTEXT_NAME, true);
80 98
@@ -134,17 +152,6 @@ async function tryActivate(context: vscode.ExtensionContext) {
134 ctx.registerCommand('resolveCodeAction', commands.resolveCodeAction); 152 ctx.registerCommand('resolveCodeAction', commands.resolveCodeAction);
135 ctx.registerCommand('applyActionGroup', commands.applyActionGroup); 153 ctx.registerCommand('applyActionGroup', commands.applyActionGroup);
136 ctx.registerCommand('gotoLocation', commands.gotoLocation); 154 ctx.registerCommand('gotoLocation', commands.gotoLocation);
137
138 ctx.pushCleanup(activateTaskProvider(workspaceFolder, ctx.config));
139
140 activateInlayHints(ctx);
141 warnAboutExtensionConflicts();
142
143 vscode.workspace.onDidChangeConfiguration(
144 _ => ctx?.client?.sendNotification('workspace/didChangeConfiguration', { settings: "" }),
145 null,
146 ctx.subscriptions,
147 );
148} 155}
149 156
150export async function deactivate() { 157export async function deactivate() {