aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_lsp_server/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_lsp_server/src')
-rw-r--r--crates/ra_lsp_server/src/caps.rs20
-rw-r--r--crates/ra_lsp_server/src/config.rs5
-rw-r--r--crates/ra_lsp_server/src/conv.rs7
-rw-r--r--crates/ra_lsp_server/src/main.rs25
-rw-r--r--crates/ra_lsp_server/src/main_loop.rs19
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs18
-rw-r--r--crates/ra_lsp_server/src/main_loop/pending_requests.rs2
-rw-r--r--crates/ra_lsp_server/src/main_loop/subscriptions.rs2
-rw-r--r--crates/ra_lsp_server/src/markdown.rs2
-rw-r--r--crates/ra_lsp_server/src/req.rs29
-rw-r--r--crates/ra_lsp_server/src/world.rs75
11 files changed, 143 insertions, 61 deletions
diff --git a/crates/ra_lsp_server/src/caps.rs b/crates/ra_lsp_server/src/caps.rs
index eea0965ed..eeca67ee1 100644
--- a/crates/ra_lsp_server/src/caps.rs
+++ b/crates/ra_lsp_server/src/caps.rs
@@ -1,11 +1,12 @@
1//! FIXME: write short doc here 1//! Advertizes the capabilities of the LSP Server.
2 2
3use lsp_types::{ 3use lsp_types::{
4 CodeActionProviderCapability, CodeLensOptions, CompletionOptions, 4 CodeActionProviderCapability, CodeLensOptions, CompletionOptions,
5 DocumentOnTypeFormattingOptions, FoldingRangeProviderCapability, GenericCapability, 5 DocumentOnTypeFormattingOptions, FoldingRangeProviderCapability,
6 ImplementationProviderCapability, RenameOptions, RenameProviderCapability, ServerCapabilities, 6 ImplementationProviderCapability, RenameOptions, RenameProviderCapability,
7 SignatureHelpOptions, TextDocumentSyncCapability, TextDocumentSyncKind, 7 SelectionRangeProviderCapability, ServerCapabilities, SignatureHelpOptions,
8 TextDocumentSyncOptions, TypeDefinitionProviderCapability, 8 TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
9 TypeDefinitionProviderCapability, WorkDoneProgressOptions,
9}; 10};
10 11
11pub fn server_capabilities() -> ServerCapabilities { 12pub fn server_capabilities() -> ServerCapabilities {
@@ -21,10 +22,14 @@ pub fn server_capabilities() -> ServerCapabilities {
21 completion_provider: Some(CompletionOptions { 22 completion_provider: Some(CompletionOptions {
22 resolve_provider: None, 23 resolve_provider: None,
23 trigger_characters: Some(vec![":".to_string(), ".".to_string()]), 24 trigger_characters: Some(vec![":".to_string(), ".".to_string()]),
25 work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
24 }), 26 }),
25 signature_help_provider: Some(SignatureHelpOptions { 27 signature_help_provider: Some(SignatureHelpOptions {
26 trigger_characters: Some(vec!["(".to_string(), ",".to_string(), ")".to_string()]), 28 trigger_characters: Some(vec!["(".to_string(), ",".to_string()]),
29 retrigger_characters: None,
30 work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
27 }), 31 }),
32 declaration_provider: None,
28 definition_provider: Some(true), 33 definition_provider: Some(true),
29 type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)), 34 type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)),
30 implementation_provider: Some(ImplementationProviderCapability::Simple(true)), 35 implementation_provider: Some(ImplementationProviderCapability::Simple(true)),
@@ -40,10 +45,11 @@ pub fn server_capabilities() -> ServerCapabilities {
40 first_trigger_character: "=".to_string(), 45 first_trigger_character: "=".to_string(),
41 more_trigger_character: Some(vec![".".to_string(), ">".to_string()]), 46 more_trigger_character: Some(vec![".".to_string(), ">".to_string()]),
42 }), 47 }),
43 selection_range_provider: Some(GenericCapability::default()), 48 selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)),
44 folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)), 49 folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
45 rename_provider: Some(RenameProviderCapability::Options(RenameOptions { 50 rename_provider: Some(RenameProviderCapability::Options(RenameOptions {
46 prepare_provider: Some(true), 51 prepare_provider: Some(true),
52 work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
47 })), 53 })),
48 document_link_provider: None, 54 document_link_provider: None,
49 color_provider: None, 55 color_provider: None,
diff --git a/crates/ra_lsp_server/src/config.rs b/crates/ra_lsp_server/src/config.rs
index 8045f3d60..67942aa41 100644
--- a/crates/ra_lsp_server/src/config.rs
+++ b/crates/ra_lsp_server/src/config.rs
@@ -9,6 +9,7 @@
9 9
10use rustc_hash::FxHashMap; 10use rustc_hash::FxHashMap;
11 11
12use ra_project_model::CargoFeatures;
12use serde::{Deserialize, Deserializer}; 13use serde::{Deserialize, Deserializer};
13 14
14/// Client provided initialization options 15/// Client provided initialization options
@@ -37,6 +38,9 @@ pub struct ServerConfig {
37 38
38 /// Fine grained feature flags to disable specific features. 39 /// Fine grained feature flags to disable specific features.
39 pub feature_flags: FxHashMap<String, bool>, 40 pub feature_flags: FxHashMap<String, bool>,
41
42 /// Cargo feature configurations.
43 pub cargo_features: CargoFeatures,
40} 44}
41 45
42impl Default for ServerConfig { 46impl Default for ServerConfig {
@@ -49,6 +53,7 @@ impl Default for ServerConfig {
49 max_inlay_hint_length: None, 53 max_inlay_hint_length: None,
50 with_sysroot: true, 54 with_sysroot: true,
51 feature_flags: FxHashMap::default(), 55 feature_flags: FxHashMap::default(),
56 cargo_features: Default::default(),
52 } 57 }
53 } 58 }
54} 59}
diff --git a/crates/ra_lsp_server/src/conv.rs b/crates/ra_lsp_server/src/conv.rs
index b13093cfe..e93d4ea33 100644
--- a/crates/ra_lsp_server/src/conv.rs
+++ b/crates/ra_lsp_server/src/conv.rs
@@ -1,4 +1,4 @@
1//! FIXME: write short doc here 1//! Convenience module responsible for translating between rust-analyzer's types and LSP types.
2 2
3use lsp_types::{ 3use lsp_types::{
4 self, CreateFile, DiagnosticSeverity, DocumentChangeOperation, DocumentChanges, Documentation, 4 self, CreateFile, DiagnosticSeverity, DocumentChangeOperation, DocumentChanges, Documentation,
@@ -130,6 +130,11 @@ impl ConvWith<(&LineIndex, LineEndings)> for CompletionItem {
130 deprecated: Some(self.deprecated()), 130 deprecated: Some(self.deprecated()),
131 ..Default::default() 131 ..Default::default()
132 }; 132 };
133
134 if self.deprecated() {
135 res.tags = Some(vec![lsp_types::CompletionItemTag::Deprecated])
136 }
137
133 res.insert_text_format = Some(match self.insert_text_format() { 138 res.insert_text_format = Some(match self.insert_text_format() {
134 InsertTextFormat::Snippet => lsp_types::InsertTextFormat::Snippet, 139 InsertTextFormat::Snippet => lsp_types::InsertTextFormat::Snippet,
135 InsertTextFormat::PlainText => lsp_types::InsertTextFormat::PlainText, 140 InsertTextFormat::PlainText => lsp_types::InsertTextFormat::PlainText,
diff --git a/crates/ra_lsp_server/src/main.rs b/crates/ra_lsp_server/src/main.rs
index e13c8ca14..cdd925c9f 100644
--- a/crates/ra_lsp_server/src/main.rs
+++ b/crates/ra_lsp_server/src/main.rs
@@ -1,24 +1,22 @@
1//! `ra_lsp_server` binary 1//! `ra_lsp_server` binary
2 2
3use flexi_logger::{Duplicate, Logger};
4use lsp_server::Connection; 3use lsp_server::Connection;
5use ra_lsp_server::{show_message, Result, ServerConfig}; 4use ra_lsp_server::{show_message, Result, ServerConfig};
6use ra_prof; 5use ra_prof;
7 6
8fn main() -> Result<()> { 7fn main() -> Result<()> {
9 setup_logging()?; 8 setup_logging()?;
10 run_server()?; 9 match Args::parse()? {
10 Args::Version => println!("rust-analyzer {}", env!("REV")),
11 Args::Run => run_server()?,
12 }
11 Ok(()) 13 Ok(())
12} 14}
13 15
14fn setup_logging() -> Result<()> { 16fn setup_logging() -> Result<()> {
15 std::env::set_var("RUST_BACKTRACE", "short"); 17 std::env::set_var("RUST_BACKTRACE", "short");
16 18
17 let logger = Logger::with_env_or_str("error").duplicate_to_stderr(Duplicate::All); 19 env_logger::try_init()?;
18 match std::env::var("RA_LOG_DIR") {
19 Ok(ref v) if v == "1" => logger.log_to_file().directory("log").start()?,
20 _ => logger.start()?,
21 };
22 20
23 ra_prof::set_filter(match std::env::var("RA_PROFILE") { 21 ra_prof::set_filter(match std::env::var("RA_PROFILE") {
24 Ok(spec) => ra_prof::Filter::from_spec(&spec), 22 Ok(spec) => ra_prof::Filter::from_spec(&spec),
@@ -27,6 +25,19 @@ fn setup_logging() -> Result<()> {
27 Ok(()) 25 Ok(())
28} 26}
29 27
28enum Args {
29 Version,
30 Run,
31}
32
33impl Args {
34 fn parse() -> Result<Args> {
35 let res =
36 if std::env::args().any(|it| it == "--version") { Args::Version } else { Args::Run };
37 Ok(res)
38 }
39}
40
30fn run_server() -> Result<()> { 41fn run_server() -> Result<()> {
31 log::info!("lifecycle: server started"); 42 log::info!("lifecycle: server started");
32 43
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs
index 83845f1e0..dda318e43 100644
--- a/crates/ra_lsp_server/src/main_loop.rs
+++ b/crates/ra_lsp_server/src/main_loop.rs
@@ -1,4 +1,5 @@
1//! FIXME: write short doc here 1//! The main loop of `ra_lsp_server` responsible for dispatching LSP requests/replies and
2//! notifications back to the client.
2 3
3mod handlers; 4mod handlers;
4mod subscriptions; 5mod subscriptions;
@@ -67,6 +68,7 @@ pub fn main_loop(
67 let workspace = ra_project_model::ProjectWorkspace::discover_with_sysroot( 68 let workspace = ra_project_model::ProjectWorkspace::discover_with_sysroot(
68 ws_root.as_path(), 69 ws_root.as_path(),
69 config.with_sysroot, 70 config.with_sysroot,
71 &config.cargo_features,
70 ); 72 );
71 match workspace { 73 match workspace {
72 Ok(workspace) => loaded_workspaces.push(workspace), 74 Ok(workspace) => loaded_workspaces.push(workspace),
@@ -130,7 +132,7 @@ pub fn main_loop(
130 let feature_flags = { 132 let feature_flags = {
131 let mut ff = FeatureFlags::default(); 133 let mut ff = FeatureFlags::default();
132 for (flag, value) in config.feature_flags { 134 for (flag, value) in config.feature_flags {
133 if let Err(_) = ff.set(flag.as_str(), value) { 135 if ff.set(flag.as_str(), value).is_err() {
134 log::error!("unknown feature flag: {:?}", flag); 136 log::error!("unknown feature flag: {:?}", flag);
135 show_message( 137 show_message(
136 req::MessageType::Error, 138 req::MessageType::Error,
@@ -303,7 +305,6 @@ fn loop_turn(
303 log::info!("queued count = {}", queue_count); 305 log::info!("queued count = {}", queue_count);
304 } 306 }
305 307
306 let mut state_changed = false;
307 match event { 308 match event {
308 Event::Task(task) => { 309 Event::Task(task) => {
309 on_task(task, &connection.sender, &mut loop_state.pending_requests, world_state); 310 on_task(task, &connection.sender, &mut loop_state.pending_requests, world_state);
@@ -311,7 +312,6 @@ fn loop_turn(
311 } 312 }
312 Event::Vfs(task) => { 313 Event::Vfs(task) => {
313 world_state.vfs.write().handle_task(task); 314 world_state.vfs.write().handle_task(task);
314 state_changed = true;
315 } 315 }
316 Event::Lib(lib) => { 316 Event::Lib(lib) => {
317 world_state.add_lib(lib); 317 world_state.add_lib(lib);
@@ -336,7 +336,6 @@ fn loop_turn(
336 &mut loop_state.subscriptions, 336 &mut loop_state.subscriptions,
337 not, 337 not,
338 )?; 338 )?;
339 state_changed = true;
340 } 339 }
341 Message::Response(resp) => { 340 Message::Response(resp) => {
342 let removed = loop_state.pending_responses.remove(&resp.id); 341 let removed = loop_state.pending_responses.remove(&resp.id);
@@ -347,7 +346,12 @@ fn loop_turn(
347 }, 346 },
348 }; 347 };
349 348
350 loop_state.pending_libraries.extend(world_state.process_changes()); 349 let mut state_changed = false;
350 if let Some(changes) = world_state.process_changes() {
351 state_changed = true;
352 loop_state.pending_libraries.extend(changes);
353 }
354
351 while loop_state.in_flight_libraries < MAX_IN_FLIGHT_LIBS 355 while loop_state.in_flight_libraries < MAX_IN_FLIGHT_LIBS
352 && !loop_state.pending_libraries.is_empty() 356 && !loop_state.pending_libraries.is_empty()
353 { 357 {
@@ -520,7 +524,8 @@ fn on_notification(
520 if let Some(file_id) = state.vfs.write().remove_file_overlay(path.as_path()) { 524 if let Some(file_id) = state.vfs.write().remove_file_overlay(path.as_path()) {
521 subs.remove_sub(FileId(file_id.0)); 525 subs.remove_sub(FileId(file_id.0));
522 } 526 }
523 let params = req::PublishDiagnosticsParams { uri, diagnostics: Vec::new() }; 527 let params =
528 req::PublishDiagnosticsParams { uri, diagnostics: Vec::new(), version: None };
524 let not = notification_new::<req::PublishDiagnostics>(params); 529 let not = notification_new::<req::PublishDiagnostics>(params);
525 msg_sender.send(not.into()).unwrap(); 530 msg_sender.send(not.into()).unwrap();
526 return Ok(()); 531 return Ok(());
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index c81fa7f67..39eb3df3e 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -1,4 +1,5 @@
1//! FIXME: write short doc here 1//! This module is responsible for implementing handlers for Lanuage Server Protocol.
2//! The majority of requests are fulfilled by calling into the `ra_ide` crate.
2 3
3use std::{fmt::Write as _, io::Write as _}; 4use std::{fmt::Write as _, io::Write as _};
4 5
@@ -164,7 +165,7 @@ pub fn handle_on_type_formatting(
164 165
165 // in `ra_ide`, the `on_type` invariant is that 166 // in `ra_ide`, the `on_type` invariant is that
166 // `text.char_at(position) == typed_char`. 167 // `text.char_at(position) == typed_char`.
167 position.offset = position.offset - TextUnit::of_char('.'); 168 position.offset -= TextUnit::of_char('.');
168 let char_typed = params.ch.chars().next().unwrap_or('\0'); 169 let char_typed = params.ch.chars().next().unwrap_or('\0');
169 170
170 // We have an assist that inserts ` ` after typing `->` in `fn foo() ->{`, 171 // We have an assist that inserts ` ` after typing `->` in `fn foo() ->{`,
@@ -480,8 +481,6 @@ pub fn handle_prepare_rename(
480 let _p = profile("handle_prepare_rename"); 481 let _p = profile("handle_prepare_rename");
481 let position = params.try_conv_with(&world)?; 482 let position = params.try_conv_with(&world)?;
482 483
483 // We support renaming references like handle_rename does.
484 // In the future we may want to reject the renaming of things like keywords here too.
485 let optional_change = world.analysis().rename(position, "dummy")?; 484 let optional_change = world.analysis().rename(position, "dummy")?;
486 let range = match optional_change { 485 let range = match optional_change {
487 None => return Ok(None), 486 None => return Ok(None),
@@ -557,12 +556,18 @@ pub fn handle_formatting(
557 let _p = profile("handle_formatting"); 556 let _p = profile("handle_formatting");
558 let file_id = params.text_document.try_conv_with(&world)?; 557 let file_id = params.text_document.try_conv_with(&world)?;
559 let file = world.analysis().file_text(file_id)?; 558 let file = world.analysis().file_text(file_id)?;
559 let crate_ids = world.analysis().crate_for(file_id)?;
560 560
561 let file_line_index = world.analysis().file_line_index(file_id)?; 561 let file_line_index = world.analysis().file_line_index(file_id)?;
562 let end_position = TextUnit::of_str(&file).conv_with(&file_line_index); 562 let end_position = TextUnit::of_str(&file).conv_with(&file_line_index);
563 563
564 use std::process; 564 use std::process;
565 let mut rustfmt = process::Command::new("rustfmt"); 565 let mut rustfmt = process::Command::new("rustfmt");
566 if let Some(&crate_id) = crate_ids.first() {
567 // Assume all crates are in the same edition
568 let edition = world.analysis().crate_edition(crate_id)?;
569 rustfmt.args(&["--edition", &edition.to_string()]);
570 }
566 rustfmt.stdin(process::Stdio::piped()).stdout(process::Stdio::piped()); 571 rustfmt.stdin(process::Stdio::piped()).stdout(process::Stdio::piped());
567 572
568 if let Ok(path) = params.text_document.uri.to_file_path() { 573 if let Ok(path) = params.text_document.uri.to_file_path() {
@@ -644,6 +649,7 @@ pub fn handle_code_action(
644 diagnostics: None, 649 diagnostics: None,
645 edit: None, 650 edit: None,
646 command: Some(command), 651 command: Some(command),
652 is_preferred: None,
647 }; 653 };
648 res.push(action.into()); 654 res.push(action.into());
649 } 655 }
@@ -666,6 +672,7 @@ pub fn handle_code_action(
666 diagnostics: None, 672 diagnostics: None,
667 edit: None, 673 edit: None,
668 command: Some(command), 674 command: Some(command),
675 is_preferred: None,
669 }; 676 };
670 res.push(action.into()); 677 res.push(action.into());
671 } 678 }
@@ -824,9 +831,10 @@ pub fn publish_diagnostics(
824 source: Some("rust-analyzer".to_string()), 831 source: Some("rust-analyzer".to_string()),
825 message: d.message, 832 message: d.message,
826 related_information: None, 833 related_information: None,
834 tags: None,
827 }) 835 })
828 .collect(); 836 .collect();
829 Ok(req::PublishDiagnosticsParams { uri, diagnostics }) 837 Ok(req::PublishDiagnosticsParams { uri, diagnostics, version: None })
830} 838}
831 839
832pub fn publish_decorations( 840pub fn publish_decorations(
diff --git a/crates/ra_lsp_server/src/main_loop/pending_requests.rs b/crates/ra_lsp_server/src/main_loop/pending_requests.rs
index e7ea7aa5b..2d2213464 100644
--- a/crates/ra_lsp_server/src/main_loop/pending_requests.rs
+++ b/crates/ra_lsp_server/src/main_loop/pending_requests.rs
@@ -1,4 +1,4 @@
1//! FIXME: write short doc here 1//! Datastructures that keep track of inflight requests.
2 2
3use std::time::{Duration, Instant}; 3use std::time::{Duration, Instant};
4 4
diff --git a/crates/ra_lsp_server/src/main_loop/subscriptions.rs b/crates/ra_lsp_server/src/main_loop/subscriptions.rs
index 609b2adcc..b0bae90f5 100644
--- a/crates/ra_lsp_server/src/main_loop/subscriptions.rs
+++ b/crates/ra_lsp_server/src/main_loop/subscriptions.rs
@@ -1,4 +1,4 @@
1//! FIXME: write short doc here 1//! Keeps track of file subscriptions.
2 2
3use ra_ide::FileId; 3use ra_ide::FileId;
4use rustc_hash::FxHashSet; 4use rustc_hash::FxHashSet;
diff --git a/crates/ra_lsp_server/src/markdown.rs b/crates/ra_lsp_server/src/markdown.rs
index f51fc4ade..76bef45cc 100644
--- a/crates/ra_lsp_server/src/markdown.rs
+++ b/crates/ra_lsp_server/src/markdown.rs
@@ -1,4 +1,4 @@
1//! FIXME: write short doc here 1//! Transforms markdown
2 2
3pub(crate) fn format_docs(src: &str) -> String { 3pub(crate) fn format_docs(src: &str) -> String {
4 let mut processed_lines = Vec::new(); 4 let mut processed_lines = Vec::new();
diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs
index 39361b7e8..b34e6f9b8 100644
--- a/crates/ra_lsp_server/src/req.rs
+++ b/crates/ra_lsp_server/src/req.rs
@@ -1,4 +1,4 @@
1//! FIXME: write short doc here 1//! Defines `rust-analyzer` specific custom messages.
2 2
3use lsp_types::{Location, Position, Range, TextDocumentIdentifier, Url}; 3use lsp_types::{Location, Position, Range, TextDocumentIdentifier, Url};
4use rustc_hash::FxHashMap; 4use rustc_hash::FxHashMap;
@@ -10,8 +10,9 @@ pub use lsp_types::{
10 DidChangeWatchedFilesParams, DidChangeWatchedFilesRegistrationOptions, 10 DidChangeWatchedFilesParams, DidChangeWatchedFilesRegistrationOptions,
11 DocumentOnTypeFormattingParams, DocumentSymbolParams, DocumentSymbolResponse, 11 DocumentOnTypeFormattingParams, DocumentSymbolParams, DocumentSymbolResponse,
12 FileSystemWatcher, Hover, InitializeResult, MessageType, PublishDiagnosticsParams, 12 FileSystemWatcher, Hover, InitializeResult, MessageType, PublishDiagnosticsParams,
13 ReferenceParams, Registration, RegistrationParams, ShowMessageParams, SignatureHelp, 13 ReferenceParams, Registration, RegistrationParams, SelectionRange, SelectionRangeParams,
14 TextDocumentEdit, TextDocumentPositionParams, TextEdit, WorkspaceEdit, WorkspaceSymbolParams, 14 ShowMessageParams, SignatureHelp, TextDocumentEdit, TextDocumentPositionParams, TextEdit,
15 WorkspaceEdit, WorkspaceSymbolParams,
15}; 16};
16 17
17pub enum AnalyzerStatus {} 18pub enum AnalyzerStatus {}
@@ -67,28 +68,6 @@ pub struct ExpandMacroParams {
67 pub position: Option<Position>, 68 pub position: Option<Position>,
68} 69}
69 70
70pub enum SelectionRangeRequest {}
71
72impl Request for SelectionRangeRequest {
73 type Params = SelectionRangeParams;
74 type Result = Vec<SelectionRange>;
75 const METHOD: &'static str = "textDocument/selectionRange";
76}
77
78#[derive(Deserialize, Debug)]
79#[serde(rename_all = "camelCase")]
80pub struct SelectionRangeParams {
81 pub text_document: TextDocumentIdentifier,
82 pub positions: Vec<Position>,
83}
84
85#[derive(Serialize, Debug)]
86#[serde(rename_all = "camelCase")]
87pub struct SelectionRange {
88 pub range: Range,
89 pub parent: Option<Box<SelectionRange>>,
90}
91
92pub enum FindMatchingBrace {} 71pub enum FindMatchingBrace {}
93 72
94impl Request for FindMatchingBrace { 73impl Request for FindMatchingBrace {
diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs
index 927449b45..79431e7e6 100644
--- a/crates/ra_lsp_server/src/world.rs
+++ b/crates/ra_lsp_server/src/world.rs
@@ -1,4 +1,7 @@
1//! FIXME: write short doc here 1//! The context or environment in which the language server functions.
2//! In our server implementation this is know as the `WorldState`.
3//!
4//! Each tick provides an immutable snapshot of the state as `WorldSnapshot`.
2 5
3use std::{ 6use std::{
4 path::{Path, PathBuf}, 7 path::{Path, PathBuf},
@@ -17,11 +20,13 @@ use ra_project_model::{get_rustc_cfg_options, ProjectWorkspace};
17use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch}; 20use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch};
18use ra_vfs_glob::{Glob, RustPackageFilterBuilder}; 21use ra_vfs_glob::{Glob, RustPackageFilterBuilder};
19use relative_path::RelativePathBuf; 22use relative_path::RelativePathBuf;
23use std::path::{Component, Prefix};
20 24
21use crate::{ 25use crate::{
22 main_loop::pending_requests::{CompletedRequest, LatestRequests}, 26 main_loop::pending_requests::{CompletedRequest, LatestRequests},
23 LspError, Result, 27 LspError, Result,
24}; 28};
29use std::str::FromStr;
25 30
26#[derive(Debug, Clone)] 31#[derive(Debug, Clone)]
27pub struct Options { 32pub struct Options {
@@ -140,10 +145,10 @@ impl WorldState {
140 /// FIXME: better API here 145 /// FIXME: better API here
141 pub fn process_changes( 146 pub fn process_changes(
142 &mut self, 147 &mut self,
143 ) -> Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)> { 148 ) -> Option<Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>> {
144 let changes = self.vfs.write().commit_changes(); 149 let changes = self.vfs.write().commit_changes();
145 if changes.is_empty() { 150 if changes.is_empty() {
146 return Vec::new(); 151 return None;
147 } 152 }
148 let mut libs = Vec::new(); 153 let mut libs = Vec::new();
149 let mut change = AnalysisChange::new(); 154 let mut change = AnalysisChange::new();
@@ -177,7 +182,7 @@ impl WorldState {
177 } 182 }
178 } 183 }
179 self.analysis_host.apply_change(change); 184 self.analysis_host.apply_change(change);
180 libs 185 Some(libs)
181 } 186 }
182 187
183 pub fn add_lib(&mut self, data: LibraryData) { 188 pub fn add_lib(&mut self, data: LibraryData) {
@@ -233,8 +238,8 @@ impl WorldSnapshot {
233 238
234 pub fn file_id_to_uri(&self, id: FileId) -> Result<Url> { 239 pub fn file_id_to_uri(&self, id: FileId) -> Result<Url> {
235 let path = self.vfs.read().file2path(VfsFile(id.0)); 240 let path = self.vfs.read().file2path(VfsFile(id.0));
236 let url = Url::from_file_path(&path) 241 let url = url_from_path_with_drive_lowercasing(path)?;
237 .map_err(|_| format!("can't convert path to url: {}", path.display()))?; 242
238 Ok(url) 243 Ok(url)
239 } 244 }
240 245
@@ -279,3 +284,61 @@ impl WorldSnapshot {
279 self.analysis.feature_flags() 284 self.analysis.feature_flags()
280 } 285 }
281} 286}
287
288/// Returns a `Url` object from a given path, will lowercase drive letters if present.
289/// This will only happen when processing windows paths.
290///
291/// When processing non-windows path, this is essentially the same as `Url::from_file_path`.
292fn url_from_path_with_drive_lowercasing(path: impl AsRef<Path>) -> Result<Url> {
293 let component_has_windows_drive = path.as_ref().components().any(|comp| {
294 if let Component::Prefix(c) = comp {
295 match c.kind() {
296 Prefix::Disk(_) | Prefix::VerbatimDisk(_) => return true,
297 _ => return false,
298 }
299 }
300 false
301 });
302
303 // VSCode expects drive letters to be lowercased, where rust will uppercase the drive letters.
304 if component_has_windows_drive {
305 let url_original = Url::from_file_path(&path)
306 .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?;
307
308 let drive_partition: Vec<&str> = url_original.as_str().rsplitn(2, ':').collect();
309
310 // There is a drive partition, but we never found a colon.
311 // This should not happen, but in this case we just pass it through.
312 if drive_partition.len() == 1 {
313 return Ok(url_original);
314 }
315
316 let joined = drive_partition[1].to_ascii_lowercase() + ":" + drive_partition[0];
317 let url = Url::from_str(&joined).expect("This came from a valid `Url`");
318
319 Ok(url)
320 } else {
321 Ok(Url::from_file_path(&path)
322 .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?)
323 }
324}
325
326// `Url` is not able to parse windows paths on unix machines.
327#[cfg(target_os = "windows")]
328#[cfg(test)]
329mod path_conversion_windows_tests {
330 use super::url_from_path_with_drive_lowercasing;
331 #[test]
332 fn test_lowercase_drive_letter_with_drive() {
333 let url = url_from_path_with_drive_lowercasing("C:\\Test").unwrap();
334
335 assert_eq!(url.to_string(), "file:///c:/Test");
336 }
337
338 #[test]
339 fn test_drive_without_colon_passthrough() {
340 let url = url_from_path_with_drive_lowercasing(r#"\\localhost\C$\my_dir"#).unwrap();
341
342 assert_eq!(url.to_string(), "file://localhost/C$/my_dir");
343 }
344}