diff options
-rw-r--r-- | crates/ra_project_model/src/cargo_workspace.rs | 9 | ||||
-rw-r--r-- | crates/rust-analyzer/src/config.rs | 143 | ||||
-rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 48 | ||||
-rw-r--r-- | crates/rust-analyzer/src/main_loop/handlers.rs | 65 | ||||
-rw-r--r-- | crates/rust-analyzer/src/req.rs | 36 | ||||
-rw-r--r-- | docs/dev/README.md | 10 | ||||
-rw-r--r-- | docs/user/features.md | 6 | ||||
-rw-r--r-- | editors/code/package-lock.json | 6 | ||||
-rw-r--r-- | editors/code/package.json | 370 | ||||
-rw-r--r-- | editors/code/ra_syntax_tree.tmGrammar.json | 31 | ||||
-rw-r--r-- | editors/code/src/client.ts | 27 | ||||
-rw-r--r-- | editors/code/src/commands/runnables.ts | 4 | ||||
-rw-r--r-- | editors/code/src/commands/syntax_tree.ts | 111 | ||||
-rw-r--r-- | editors/code/src/config.ts | 32 | ||||
-rw-r--r-- | editors/code/src/ctx.ts | 3 | ||||
-rw-r--r-- | editors/code/src/highlighting.ts | 255 | ||||
-rw-r--r-- | editors/code/src/main.ts | 4 | ||||
-rw-r--r-- | editors/code/src/status_display.ts | 2 |
18 files changed, 458 insertions, 704 deletions
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs index c1b6e1ddc..b50cda06f 100644 --- a/crates/ra_project_model/src/cargo_workspace.rs +++ b/crates/ra_project_model/src/cargo_workspace.rs | |||
@@ -75,7 +75,7 @@ pub type Target = Idx<TargetData>; | |||
75 | 75 | ||
76 | #[derive(Debug, Clone)] | 76 | #[derive(Debug, Clone)] |
77 | pub struct PackageData { | 77 | pub struct PackageData { |
78 | pub id: String, | 78 | pub version: String, |
79 | pub name: String, | 79 | pub name: String, |
80 | pub manifest: PathBuf, | 80 | pub manifest: PathBuf, |
81 | pub targets: Vec<Target>, | 81 | pub targets: Vec<Target>, |
@@ -174,14 +174,15 @@ impl CargoWorkspace { | |||
174 | let ws_members = &meta.workspace_members; | 174 | let ws_members = &meta.workspace_members; |
175 | 175 | ||
176 | for meta_pkg in meta.packages { | 176 | for meta_pkg in meta.packages { |
177 | let cargo_metadata::Package { id, edition, name, manifest_path, .. } = meta_pkg; | 177 | let cargo_metadata::Package { id, edition, name, manifest_path, version, .. } = |
178 | meta_pkg; | ||
178 | let is_member = ws_members.contains(&id); | 179 | let is_member = ws_members.contains(&id); |
179 | let edition = edition | 180 | let edition = edition |
180 | .parse::<Edition>() | 181 | .parse::<Edition>() |
181 | .with_context(|| format!("Failed to parse edition {}", edition))?; | 182 | .with_context(|| format!("Failed to parse edition {}", edition))?; |
182 | let pkg = packages.alloc(PackageData { | 183 | let pkg = packages.alloc(PackageData { |
183 | name, | 184 | name, |
184 | id: id.to_string(), | 185 | version: version.to_string(), |
185 | manifest: manifest_path, | 186 | manifest: manifest_path, |
186 | targets: Vec::new(), | 187 | targets: Vec::new(), |
187 | is_member, | 188 | is_member, |
@@ -256,7 +257,7 @@ impl CargoWorkspace { | |||
256 | if self.is_unique(&*package.name) { | 257 | if self.is_unique(&*package.name) { |
257 | package.name.clone() | 258 | package.name.clone() |
258 | } else { | 259 | } else { |
259 | package.id.clone() | 260 | format!("{}:{}", package.name, package.version) |
260 | } | 261 | } |
261 | } | 262 | } |
262 | 263 | ||
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 3c8f55f1e..04f5bb473 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs | |||
@@ -16,21 +16,33 @@ use serde::Deserialize; | |||
16 | #[derive(Debug, Clone)] | 16 | #[derive(Debug, Clone)] |
17 | pub struct Config { | 17 | pub struct Config { |
18 | pub client_caps: ClientCapsConfig, | 18 | pub client_caps: ClientCapsConfig, |
19 | pub publish_decorations: bool, | 19 | |
20 | pub with_sysroot: bool, | ||
20 | pub publish_diagnostics: bool, | 21 | pub publish_diagnostics: bool, |
22 | pub lru_capacity: Option<usize>, | ||
23 | pub proc_macro_srv: Option<String>, | ||
24 | pub files: FilesConfig, | ||
21 | pub notifications: NotificationsConfig, | 25 | pub notifications: NotificationsConfig, |
26 | |||
27 | pub cargo: CargoConfig, | ||
28 | pub rustfmt: RustfmtConfig, | ||
29 | pub check: Option<FlycheckConfig>, | ||
30 | |||
22 | pub inlay_hints: InlayHintsConfig, | 31 | pub inlay_hints: InlayHintsConfig, |
23 | pub completion: CompletionConfig, | 32 | pub completion: CompletionConfig, |
24 | pub call_info_full: bool, | 33 | pub call_info_full: bool, |
25 | pub rustfmt: RustfmtConfig, | 34 | } |
26 | pub check: Option<FlycheckConfig>, | 35 | |
27 | pub vscode_lldb: bool, | 36 | #[derive(Debug, Clone)] |
28 | pub proc_macro_srv: Option<String>, | 37 | pub struct FilesConfig { |
29 | pub lru_capacity: Option<usize>, | 38 | pub watcher: FilesWatcher, |
30 | pub use_client_watching: bool, | 39 | pub exclude: Vec<String>, |
31 | pub exclude_globs: Vec<String>, | 40 | } |
32 | pub cargo: CargoConfig, | 41 | |
33 | pub with_sysroot: bool, | 42 | #[derive(Debug, Clone)] |
43 | pub enum FilesWatcher { | ||
44 | Client, | ||
45 | Notify, | ||
34 | } | 46 | } |
35 | 47 | ||
36 | #[derive(Debug, Clone)] | 48 | #[derive(Debug, Clone)] |
@@ -60,13 +72,26 @@ pub struct ClientCapsConfig { | |||
60 | impl Default for Config { | 72 | impl Default for Config { |
61 | fn default() -> Self { | 73 | fn default() -> Self { |
62 | Config { | 74 | Config { |
63 | publish_decorations: false, | 75 | client_caps: ClientCapsConfig::default(), |
76 | |||
77 | with_sysroot: true, | ||
64 | publish_diagnostics: true, | 78 | publish_diagnostics: true, |
79 | lru_capacity: None, | ||
80 | proc_macro_srv: None, | ||
81 | files: FilesConfig { watcher: FilesWatcher::Notify, exclude: Vec::new() }, | ||
65 | notifications: NotificationsConfig { | 82 | notifications: NotificationsConfig { |
66 | workspace_loaded: true, | 83 | workspace_loaded: true, |
67 | cargo_toml_not_found: true, | 84 | cargo_toml_not_found: true, |
68 | }, | 85 | }, |
69 | client_caps: ClientCapsConfig::default(), | 86 | |
87 | cargo: CargoConfig::default(), | ||
88 | rustfmt: RustfmtConfig::Rustfmt { extra_args: Vec::new() }, | ||
89 | check: Some(FlycheckConfig::CargoCommand { | ||
90 | command: "check".to_string(), | ||
91 | all_targets: true, | ||
92 | extra_args: Vec::new(), | ||
93 | }), | ||
94 | |||
70 | inlay_hints: InlayHintsConfig { | 95 | inlay_hints: InlayHintsConfig { |
71 | type_hints: true, | 96 | type_hints: true, |
72 | parameter_hints: true, | 97 | parameter_hints: true, |
@@ -79,19 +104,6 @@ impl Default for Config { | |||
79 | add_call_argument_snippets: true, | 104 | add_call_argument_snippets: true, |
80 | }, | 105 | }, |
81 | call_info_full: true, | 106 | call_info_full: true, |
82 | rustfmt: RustfmtConfig::Rustfmt { extra_args: Vec::new() }, | ||
83 | check: Some(FlycheckConfig::CargoCommand { | ||
84 | command: "check".to_string(), | ||
85 | all_targets: true, | ||
86 | extra_args: Vec::new(), | ||
87 | }), | ||
88 | vscode_lldb: false, | ||
89 | proc_macro_srv: None, | ||
90 | lru_capacity: None, | ||
91 | use_client_watching: false, | ||
92 | exclude_globs: Vec::new(), | ||
93 | cargo: CargoConfig::default(), | ||
94 | with_sysroot: true, | ||
95 | } | 107 | } |
96 | } | 108 | } |
97 | } | 109 | } |
@@ -105,46 +117,61 @@ impl Config { | |||
105 | *self = Default::default(); | 117 | *self = Default::default(); |
106 | self.client_caps = client_caps; | 118 | self.client_caps = client_caps; |
107 | 119 | ||
108 | set(value, "/publishDecorations", &mut self.publish_decorations); | 120 | set(value, "/withSysroot", &mut self.with_sysroot); |
109 | set(value, "/excludeGlobs", &mut self.exclude_globs); | 121 | set(value, "/featureFlags/lsp.diagnostics", &mut self.publish_diagnostics); |
110 | set(value, "/useClientWatching", &mut self.use_client_watching); | ||
111 | set(value, "/lruCapacity", &mut self.lru_capacity); | 122 | set(value, "/lruCapacity", &mut self.lru_capacity); |
112 | 123 | if let Some(watcher) = get::<String>(value, "/files/watcher") { | |
113 | set(value, "/inlayHintsType", &mut self.inlay_hints.type_hints); | 124 | self.files.watcher = match watcher.as_str() { |
114 | set(value, "/inlayHintsParameter", &mut self.inlay_hints.parameter_hints); | 125 | "client" => FilesWatcher::Client, |
115 | set(value, "/inlayHintsChaining", &mut self.inlay_hints.chaining_hints); | 126 | "notify"| _ => FilesWatcher::Notify, |
116 | set(value, "/inlayHintsMaxLength", &mut self.inlay_hints.max_length); | 127 | } |
117 | 128 | } | |
118 | if let Some(false) = get(value, "cargo_watch_enable") { | 129 | set(value, "/notifications/workspaceLoaded", &mut self.notifications.workspace_loaded); |
130 | set(value, "/notifications/cargoTomlNotFound", &mut self.notifications.cargo_toml_not_found); | ||
131 | |||
132 | set(value, "/cargo/noDefaultFeatures", &mut self.cargo.no_default_features); | ||
133 | set(value, "/cargo/allFeatures", &mut self.cargo.all_features); | ||
134 | set(value, "/cargo/features", &mut self.cargo.features); | ||
135 | set(value, "/cargo/loadOutDirsFromCheck", &mut self.cargo.load_out_dirs_from_check); | ||
136 | if let Some(mut args) = get::<Vec<String>>(value, "/rustfmt/overrideCommand") { | ||
137 | if !args.is_empty() { | ||
138 | let command = args.remove(0); | ||
139 | self.rustfmt = RustfmtConfig::CustomCommand { | ||
140 | command, | ||
141 | args, | ||
142 | } | ||
143 | } | ||
144 | } else if let RustfmtConfig::Rustfmt { extra_args } = &mut self.rustfmt { | ||
145 | set(value, "/rustfmt/extraArgs", extra_args); | ||
146 | } | ||
147 | if let Some(false) = get(value, "/checkOnSave/enable") { | ||
119 | self.check = None | 148 | self.check = None |
120 | } else { | 149 | } else { |
121 | if let Some(FlycheckConfig::CargoCommand { command, extra_args, all_targets }) = &mut self.check | 150 | if let Some(mut args) = get::<Vec<String>>(value, "/checkOnSave/overrideCommand") { |
151 | if !args.is_empty() { | ||
152 | let command = args.remove(0); | ||
153 | self.check = Some(FlycheckConfig::CustomCommand { | ||
154 | command, | ||
155 | args, | ||
156 | }) | ||
157 | } | ||
158 | |||
159 | } else if let Some(FlycheckConfig::CargoCommand { command, extra_args, all_targets }) = &mut self.check | ||
122 | { | 160 | { |
123 | set(value, "/cargoWatchArgs", extra_args); | 161 | set(value, "/checkOnSave/extraArgs", extra_args); |
124 | set(value, "/cargoWatchCommand", command); | 162 | set(value, "/checkOnSave/command", command); |
125 | set(value, "/cargoWatchAllTargets", all_targets); | 163 | set(value, "/checkOnSave/allTargets", all_targets); |
126 | } | 164 | } |
127 | }; | 165 | }; |
128 | 166 | ||
129 | set(value, "/withSysroot", &mut self.with_sysroot); | 167 | set(value, "/inlayHints/typeHints", &mut self.inlay_hints.type_hints); |
130 | if let RustfmtConfig::Rustfmt { extra_args } = &mut self.rustfmt { | 168 | set(value, "/inlayHints/parameterHints", &mut self.inlay_hints.parameter_hints); |
131 | set(value, "/rustfmtArgs", extra_args); | 169 | set(value, "/inlayHints/chainingHints", &mut self.inlay_hints.chaining_hints); |
132 | } | 170 | set(value, "/inlayHints/maxLength", &mut self.inlay_hints.max_length); |
133 | 171 | set(value, "/completion/postfix/enable", &mut self.completion.enable_postfix_completions); | |
134 | set(value, "/cargoFeatures/noDefaultFeatures", &mut self.cargo.no_default_features); | 172 | set(value, "/completion/addCallParenthesis", &mut self.completion.add_call_parenthesis); |
135 | set(value, "/cargoFeatures/allFeatures", &mut self.cargo.all_features); | 173 | set(value, "/completion/addCallArgumentSnippets", &mut self.completion.add_call_argument_snippets); |
136 | set(value, "/cargoFeatures/features", &mut self.cargo.features); | 174 | set(value, "/callInfo/full", &mut self.call_info_full); |
137 | set(value, "/cargoFeatures/loadOutDirsFromCheck", &mut self.cargo.load_out_dirs_from_check); | ||
138 | |||
139 | set(value, "/vscodeLldb", &mut self.vscode_lldb); | ||
140 | |||
141 | set(value, "/featureFlags/lsp.diagnostics", &mut self.publish_diagnostics); | ||
142 | set(value, "/featureFlags/notifications.workspace-loaded", &mut self.notifications.workspace_loaded); | ||
143 | set(value, "/featureFlags/notifications.cargo-toml-not-found", &mut self.notifications.cargo_toml_not_found); | ||
144 | set(value, "/featureFlags/completion.enable-postfix", &mut self.completion.enable_postfix_completions); | ||
145 | set(value, "/featureFlags/completion.insertion.add-call-parenthesis", &mut self.completion.add_call_parenthesis); | ||
146 | set(value, "/featureFlags/completion.insertion.add-argument-snippets", &mut self.completion.add_call_argument_snippets); | ||
147 | set(value, "/featureFlags/call-info.full", &mut self.call_info_full); | ||
148 | 175 | ||
149 | log::info!("Config::update() = {:#?}", self); | 176 | log::info!("Config::update() = {:#?}", self); |
150 | 177 | ||
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 45ae0ad9d..95e676e0f 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -30,7 +30,7 @@ use serde::{de::DeserializeOwned, Serialize}; | |||
30 | use threadpool::ThreadPool; | 30 | use threadpool::ThreadPool; |
31 | 31 | ||
32 | use crate::{ | 32 | use crate::{ |
33 | config::Config, | 33 | config::{Config, FilesWatcher}, |
34 | diagnostics::DiagnosticTask, | 34 | diagnostics::DiagnosticTask, |
35 | main_loop::{ | 35 | main_loop::{ |
36 | pending_requests::{PendingRequest, PendingRequests}, | 36 | pending_requests::{PendingRequest, PendingRequests}, |
@@ -40,7 +40,6 @@ use crate::{ | |||
40 | world::{WorldSnapshot, WorldState}, | 40 | world::{WorldSnapshot, WorldState}, |
41 | Result, | 41 | Result, |
42 | }; | 42 | }; |
43 | use req::ConfigurationParams; | ||
44 | 43 | ||
45 | #[derive(Debug)] | 44 | #[derive(Debug)] |
46 | pub struct LspError { | 45 | pub struct LspError { |
@@ -122,12 +121,13 @@ pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection) | |||
122 | }; | 121 | }; |
123 | 122 | ||
124 | let globs = config | 123 | let globs = config |
125 | .exclude_globs | 124 | .files |
125 | .exclude | ||
126 | .iter() | 126 | .iter() |
127 | .map(|glob| crate::vfs_glob::Glob::new(glob)) | 127 | .map(|glob| crate::vfs_glob::Glob::new(glob)) |
128 | .collect::<std::result::Result<Vec<_>, _>>()?; | 128 | .collect::<std::result::Result<Vec<_>, _>>()?; |
129 | 129 | ||
130 | if config.use_client_watching { | 130 | if let FilesWatcher::Client = config.files.watcher { |
131 | let registration_options = req::DidChangeWatchedFilesRegistrationOptions { | 131 | let registration_options = req::DidChangeWatchedFilesRegistrationOptions { |
132 | watchers: workspaces | 132 | watchers: workspaces |
133 | .iter() | 133 | .iter() |
@@ -153,7 +153,7 @@ pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection) | |||
153 | workspaces, | 153 | workspaces, |
154 | config.lru_capacity, | 154 | config.lru_capacity, |
155 | &globs, | 155 | &globs, |
156 | Watch(!config.use_client_watching), | 156 | Watch(matches!(config.files.watcher, FilesWatcher::Notify)), |
157 | config, | 157 | config, |
158 | ) | 158 | ) |
159 | }; | 159 | }; |
@@ -250,9 +250,7 @@ impl fmt::Debug for Event { | |||
250 | } | 250 | } |
251 | } | 251 | } |
252 | Event::Task(Task::Notify(not)) => { | 252 | Event::Task(Task::Notify(not)) => { |
253 | if notification_is::<req::PublishDecorations>(not) | 253 | if notification_is::<req::PublishDiagnostics>(not) { |
254 | || notification_is::<req::PublishDiagnostics>(not) | ||
255 | { | ||
256 | return debug_verbose_not(not, f); | 254 | return debug_verbose_not(not, f); |
257 | } | 255 | } |
258 | } | 256 | } |
@@ -427,7 +425,6 @@ fn loop_turn( | |||
427 | update_file_notifications_on_threadpool( | 425 | update_file_notifications_on_threadpool( |
428 | pool, | 426 | pool, |
429 | world_state.snapshot(), | 427 | world_state.snapshot(), |
430 | world_state.config.publish_decorations, | ||
431 | task_sender.clone(), | 428 | task_sender.clone(), |
432 | loop_state.subscriptions.subscriptions(), | 429 | loop_state.subscriptions.subscriptions(), |
433 | ) | 430 | ) |
@@ -508,7 +505,6 @@ fn on_request( | |||
508 | .on::<req::GotoTypeDefinition>(handlers::handle_goto_type_definition)? | 505 | .on::<req::GotoTypeDefinition>(handlers::handle_goto_type_definition)? |
509 | .on::<req::ParentModule>(handlers::handle_parent_module)? | 506 | .on::<req::ParentModule>(handlers::handle_parent_module)? |
510 | .on::<req::Runnables>(handlers::handle_runnables)? | 507 | .on::<req::Runnables>(handlers::handle_runnables)? |
511 | .on::<req::DecorationsRequest>(handlers::handle_decorations)? | ||
512 | .on::<req::Completion>(handlers::handle_completion)? | 508 | .on::<req::Completion>(handlers::handle_completion)? |
513 | .on::<req::CodeActionRequest>(handlers::handle_code_action)? | 509 | .on::<req::CodeActionRequest>(handlers::handle_code_action)? |
514 | .on::<req::CodeLensRequest>(handlers::handle_code_lens)? | 510 | .on::<req::CodeLensRequest>(handlers::handle_code_lens)? |
@@ -611,7 +607,12 @@ fn on_notification( | |||
611 | let request_id = loop_state.next_request_id(); | 607 | let request_id = loop_state.next_request_id(); |
612 | let request = request_new::<req::WorkspaceConfiguration>( | 608 | let request = request_new::<req::WorkspaceConfiguration>( |
613 | request_id.clone(), | 609 | request_id.clone(), |
614 | ConfigurationParams::default(), | 610 | req::ConfigurationParams { |
611 | items: vec![req::ConfigurationItem { | ||
612 | scope_uri: None, | ||
613 | section: Some("rust-analyzer".to_string()), | ||
614 | }], | ||
615 | }, | ||
615 | ); | 616 | ); |
616 | msg_sender.send(request.into())?; | 617 | msg_sender.send(request.into())?; |
617 | loop_state.configuration_request_id = Some(request_id); | 618 | loop_state.configuration_request_id = Some(request_id); |
@@ -884,15 +885,13 @@ where | |||
884 | fn update_file_notifications_on_threadpool( | 885 | fn update_file_notifications_on_threadpool( |
885 | pool: &ThreadPool, | 886 | pool: &ThreadPool, |
886 | world: WorldSnapshot, | 887 | world: WorldSnapshot, |
887 | publish_decorations: bool, | ||
888 | task_sender: Sender<Task>, | 888 | task_sender: Sender<Task>, |
889 | subscriptions: Vec<FileId>, | 889 | subscriptions: Vec<FileId>, |
890 | ) { | 890 | ) { |
891 | log::trace!("updating notifications for {:?}", subscriptions); | 891 | log::trace!("updating notifications for {:?}", subscriptions); |
892 | let publish_diagnostics = world.config.publish_diagnostics; | 892 | if world.config.publish_diagnostics { |
893 | pool.execute(move || { | 893 | pool.execute(move || { |
894 | for file_id in subscriptions { | 894 | for file_id in subscriptions { |
895 | if publish_diagnostics { | ||
896 | match handlers::publish_diagnostics(&world, file_id) { | 895 | match handlers::publish_diagnostics(&world, file_id) { |
897 | Err(e) => { | 896 | Err(e) => { |
898 | if !is_canceled(&e) { | 897 | if !is_canceled(&e) { |
@@ -904,21 +903,8 @@ fn update_file_notifications_on_threadpool( | |||
904 | } | 903 | } |
905 | } | 904 | } |
906 | } | 905 | } |
907 | if publish_decorations { | 906 | }) |
908 | match handlers::publish_decorations(&world, file_id) { | 907 | } |
909 | Err(e) => { | ||
910 | if !is_canceled(&e) { | ||
911 | log::error!("failed to compute decorations: {:?}", e); | ||
912 | } | ||
913 | } | ||
914 | Ok(params) => { | ||
915 | let not = notification_new::<req::PublishDecorations>(params); | ||
916 | task_sender.send(Task::Notify(not)).unwrap(); | ||
917 | } | ||
918 | } | ||
919 | } | ||
920 | } | ||
921 | }); | ||
922 | } | 908 | } |
923 | 909 | ||
924 | pub fn show_message(typ: req::MessageType, message: impl Into<String>, sender: &Sender<Message>) { | 910 | pub fn show_message(typ: req::MessageType, message: impl Into<String>, sender: &Sender<Message>) { |
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index 23e48c089..b207f0764 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs | |||
@@ -38,7 +38,7 @@ use crate::{ | |||
38 | }, | 38 | }, |
39 | diagnostics::DiagnosticTask, | 39 | diagnostics::DiagnosticTask, |
40 | from_json, | 40 | from_json, |
41 | req::{self, Decoration, InlayHint, InlayHintsParams}, | 41 | req::{self, InlayHint, InlayHintsParams}, |
42 | semantic_tokens::SemanticTokensBuilder, | 42 | semantic_tokens::SemanticTokensBuilder, |
43 | world::WorldSnapshot, | 43 | world::WorldSnapshot, |
44 | LspError, Result, | 44 | LspError, Result, |
@@ -389,15 +389,6 @@ pub fn handle_runnables( | |||
389 | Ok(res) | 389 | Ok(res) |
390 | } | 390 | } |
391 | 391 | ||
392 | pub fn handle_decorations( | ||
393 | world: WorldSnapshot, | ||
394 | params: TextDocumentIdentifier, | ||
395 | ) -> Result<Vec<Decoration>> { | ||
396 | let _p = profile("handle_decorations"); | ||
397 | let file_id = params.try_conv_with(&world)?; | ||
398 | highlight(&world, file_id) | ||
399 | } | ||
400 | |||
401 | pub fn handle_completion( | 392 | pub fn handle_completion( |
402 | world: WorldSnapshot, | 393 | world: WorldSnapshot, |
403 | params: req::CompletionParams, | 394 | params: req::CompletionParams, |
@@ -819,23 +810,21 @@ pub fn handle_code_lens( | |||
819 | }; | 810 | }; |
820 | lenses.push(lens); | 811 | lenses.push(lens); |
821 | 812 | ||
822 | if world.config.vscode_lldb { | 813 | if r.args[0] == "run" { |
823 | if r.args[0] == "run" { | 814 | r.args[0] = "build".into(); |
824 | r.args[0] = "build".into(); | 815 | } else { |
825 | } else { | 816 | r.args.push("--no-run".into()); |
826 | r.args.push("--no-run".into()); | ||
827 | } | ||
828 | let debug_lens = CodeLens { | ||
829 | range: r.range, | ||
830 | command: Some(Command { | ||
831 | title: "Debug".into(), | ||
832 | command: "rust-analyzer.debugSingle".into(), | ||
833 | arguments: Some(vec![to_value(r).unwrap()]), | ||
834 | }), | ||
835 | data: None, | ||
836 | }; | ||
837 | lenses.push(debug_lens); | ||
838 | } | 817 | } |
818 | let debug_lens = CodeLens { | ||
819 | range: r.range, | ||
820 | command: Some(Command { | ||
821 | title: "Debug".into(), | ||
822 | command: "rust-analyzer.debugSingle".into(), | ||
823 | arguments: Some(vec![to_value(r).unwrap()]), | ||
824 | }), | ||
825 | data: None, | ||
826 | }; | ||
827 | lenses.push(debug_lens); | ||
839 | } | 828 | } |
840 | 829 | ||
841 | // Handle impls | 830 | // Handle impls |
@@ -970,15 +959,6 @@ pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<Dia | |||
970 | Ok(DiagnosticTask::SetNative(file_id, diagnostics)) | 959 | Ok(DiagnosticTask::SetNative(file_id, diagnostics)) |
971 | } | 960 | } |
972 | 961 | ||
973 | pub fn publish_decorations( | ||
974 | world: &WorldSnapshot, | ||
975 | file_id: FileId, | ||
976 | ) -> Result<req::PublishDecorationsParams> { | ||
977 | let _p = profile("publish_decorations"); | ||
978 | let uri = world.file_id_to_uri(file_id)?; | ||
979 | Ok(req::PublishDecorationsParams { uri, decorations: highlight(&world, file_id)? }) | ||
980 | } | ||
981 | |||
982 | fn to_lsp_runnable( | 962 | fn to_lsp_runnable( |
983 | world: &WorldSnapshot, | 963 | world: &WorldSnapshot, |
984 | file_id: FileId, | 964 | file_id: FileId, |
@@ -1008,21 +988,6 @@ fn to_lsp_runnable( | |||
1008 | }) | 988 | }) |
1009 | } | 989 | } |
1010 | 990 | ||
1011 | fn highlight(world: &WorldSnapshot, file_id: FileId) -> Result<Vec<Decoration>> { | ||
1012 | let line_index = world.analysis().file_line_index(file_id)?; | ||
1013 | let res = world | ||
1014 | .analysis() | ||
1015 | .highlight(file_id)? | ||
1016 | .into_iter() | ||
1017 | .map(|h| Decoration { | ||
1018 | range: h.range.conv_with(&line_index), | ||
1019 | tag: h.highlight.to_string(), | ||
1020 | binding_hash: h.binding_hash.map(|x| x.to_string()), | ||
1021 | }) | ||
1022 | .collect(); | ||
1023 | Ok(res) | ||
1024 | } | ||
1025 | |||
1026 | pub fn handle_inlay_hints( | 991 | pub fn handle_inlay_hints( |
1027 | world: WorldSnapshot, | 992 | world: WorldSnapshot, |
1028 | params: InlayHintsParams, | 993 | params: InlayHintsParams, |
diff --git a/crates/rust-analyzer/src/req.rs b/crates/rust-analyzer/src/req.rs index 994f0ed61..b8b627e28 100644 --- a/crates/rust-analyzer/src/req.rs +++ b/crates/rust-analyzer/src/req.rs | |||
@@ -1,13 +1,13 @@ | |||
1 | //! Defines `rust-analyzer` specific custom messages. | 1 | //! Defines `rust-analyzer` specific custom messages. |
2 | 2 | ||
3 | use lsp_types::{Location, Position, Range, TextDocumentIdentifier, Url}; | 3 | use lsp_types::{Location, Position, Range, TextDocumentIdentifier}; |
4 | use rustc_hash::FxHashMap; | 4 | use rustc_hash::FxHashMap; |
5 | use serde::{Deserialize, Serialize}; | 5 | use serde::{Deserialize, Serialize}; |
6 | 6 | ||
7 | pub use lsp_types::{ | 7 | pub use lsp_types::{ |
8 | notification::*, request::*, ApplyWorkspaceEditParams, CodeActionParams, CodeLens, | 8 | notification::*, request::*, ApplyWorkspaceEditParams, CodeActionParams, CodeLens, |
9 | CodeLensParams, CompletionParams, CompletionResponse, ConfigurationParams, DiagnosticTag, | 9 | CodeLensParams, CompletionParams, CompletionResponse, ConfigurationItem, ConfigurationParams, |
10 | DidChangeConfigurationParams, DidChangeWatchedFilesParams, | 10 | DiagnosticTag, DidChangeConfigurationParams, DidChangeWatchedFilesParams, |
11 | DidChangeWatchedFilesRegistrationOptions, DocumentOnTypeFormattingParams, DocumentSymbolParams, | 11 | DidChangeWatchedFilesRegistrationOptions, DocumentOnTypeFormattingParams, DocumentSymbolParams, |
12 | DocumentSymbolResponse, FileSystemWatcher, Hover, InitializeResult, MessageType, | 12 | DocumentSymbolResponse, FileSystemWatcher, Hover, InitializeResult, MessageType, |
13 | PartialResultParams, ProgressParams, ProgressParamsValue, ProgressToken, | 13 | PartialResultParams, ProgressParams, ProgressParamsValue, ProgressToken, |
@@ -86,36 +86,6 @@ pub struct FindMatchingBraceParams { | |||
86 | pub offsets: Vec<Position>, | 86 | pub offsets: Vec<Position>, |
87 | } | 87 | } |
88 | 88 | ||
89 | pub enum DecorationsRequest {} | ||
90 | |||
91 | impl Request for DecorationsRequest { | ||
92 | type Params = TextDocumentIdentifier; | ||
93 | type Result = Vec<Decoration>; | ||
94 | const METHOD: &'static str = "rust-analyzer/decorationsRequest"; | ||
95 | } | ||
96 | |||
97 | pub enum PublishDecorations {} | ||
98 | |||
99 | impl Notification for PublishDecorations { | ||
100 | type Params = PublishDecorationsParams; | ||
101 | const METHOD: &'static str = "rust-analyzer/publishDecorations"; | ||
102 | } | ||
103 | |||
104 | #[derive(Deserialize, Serialize, Debug)] | ||
105 | #[serde(rename_all = "camelCase")] | ||
106 | pub struct PublishDecorationsParams { | ||
107 | pub uri: Url, | ||
108 | pub decorations: Vec<Decoration>, | ||
109 | } | ||
110 | |||
111 | #[derive(Deserialize, Serialize, Debug)] | ||
112 | #[serde(rename_all = "camelCase")] | ||
113 | pub struct Decoration { | ||
114 | pub range: Range, | ||
115 | pub tag: String, | ||
116 | pub binding_hash: Option<String>, | ||
117 | } | ||
118 | |||
119 | pub enum ParentModule {} | 89 | pub enum ParentModule {} |
120 | 90 | ||
121 | impl Request for ParentModule { | 91 | impl Request for ParentModule { |
diff --git a/docs/dev/README.md b/docs/dev/README.md index 8d7e18010..f230dc1db 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md | |||
@@ -155,6 +155,16 @@ There's also two VS Code commands which might be of interest: | |||
155 | 155 | ||
156 | * `Rust Analyzer: Syntax Tree` shows syntax tree of the current file/selection. | 156 | * `Rust Analyzer: Syntax Tree` shows syntax tree of the current file/selection. |
157 | 157 | ||
158 | You can hover over syntax nodes in the opened text file to see the appropriate | ||
159 | rust code that it refers to and the rust editor will also highlight the proper | ||
160 | text range. | ||
161 | |||
162 | If you press <kbd>Ctrl</kbd> (i.e. trigger goto definition) in the inspected | ||
163 | Rust source file the syntax tree read-only editor should scroll to and select the | ||
164 | appropriate syntax node token. | ||
165 | |||
166 |  | ||
167 | |||
158 | # Profiling | 168 | # Profiling |
159 | 169 | ||
160 | We have a built-in hierarchical profiler, you can enable it by using `RA_PROFILE` env-var: | 170 | We have a built-in hierarchical profiler, you can enable it by using `RA_PROFILE` env-var: |
diff --git a/docs/user/features.md b/docs/user/features.md index 8aeec2e81..56d2969fd 100644 --- a/docs/user/features.md +++ b/docs/user/features.md | |||
@@ -81,12 +81,6 @@ Join selected lines into one, smartly fixing up whitespace and trailing commas. | |||
81 | Shows the parse tree of the current file. It exists mostly for debugging | 81 | Shows the parse tree of the current file. It exists mostly for debugging |
82 | rust-analyzer itself. | 82 | rust-analyzer itself. |
83 | 83 | ||
84 | You can hover over syntax nodes in the opened text file to see the appropriate | ||
85 | rust code that it refers to and the rust editor will also highlight the proper | ||
86 | text range. | ||
87 | |||
88 | <img src="https://user-images.githubusercontent.com/36276403/78043783-7425e180-737c-11ea-8653-b02b773c5aa1.png" alt="demo" height="200px" > | ||
89 | |||
90 | #### Expand Macro Recursively | 84 | #### Expand Macro Recursively |
91 | 85 | ||
92 | Shows the full macro expansion of the macro at current cursor. | 86 | Shows the full macro expansion of the macro at current cursor. |
diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json index 955a05066..2d8b432a7 100644 --- a/editors/code/package-lock.json +++ b/editors/code/package-lock.json | |||
@@ -1399,9 +1399,9 @@ | |||
1399 | } | 1399 | } |
1400 | }, | 1400 | }, |
1401 | "rollup": { | 1401 | "rollup": { |
1402 | "version": "2.3.1", | 1402 | "version": "2.3.2", |
1403 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.3.1.tgz", | 1403 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.3.2.tgz", |
1404 | "integrity": "sha512-BRjzOauORe+R0U0I6SkMTSG22nYmtztR/TaBl0SvbXgc3VAxBDrZoB6HROiK0S5px1pUBnLnjBkbzmVuwC9Q1Q==", | 1404 | "integrity": "sha512-p66+fbfaUUOGE84sHXAOgfeaYQMslgAazoQMp//nlR519R61213EPFgrMZa48j31jNacJwexSAR1Q8V/BwGKBA==", |
1405 | "dev": true, | 1405 | "dev": true, |
1406 | "requires": { | 1406 | "requires": { |
1407 | "fsevents": "~2.1.2" | 1407 | "fsevents": "~2.1.2" |
diff --git a/editors/code/package.json b/editors/code/package.json index ba31c4e63..14bdec8c2 100644 --- a/editors/code/package.json +++ b/editors/code/package.json | |||
@@ -45,7 +45,7 @@ | |||
45 | "@typescript-eslint/eslint-plugin": "^2.26.0", | 45 | "@typescript-eslint/eslint-plugin": "^2.26.0", |
46 | "@typescript-eslint/parser": "^2.26.0", | 46 | "@typescript-eslint/parser": "^2.26.0", |
47 | "eslint": "^6.8.0", | 47 | "eslint": "^6.8.0", |
48 | "rollup": "^2.3.1", | 48 | "rollup": "^2.3.2", |
49 | "tslib": "^1.11.1", | 49 | "tslib": "^1.11.1", |
50 | "typescript": "^3.8.3", | 50 | "typescript": "^3.8.3", |
51 | "typescript-formatter": "^7.2.2", | 51 | "typescript-formatter": "^7.2.2", |
@@ -177,91 +177,70 @@ | |||
177 | "type": "object", | 177 | "type": "object", |
178 | "title": "Rust Analyzer", | 178 | "title": "Rust Analyzer", |
179 | "properties": { | 179 | "properties": { |
180 | "rust-analyzer.highlighting.semanticTokens": { | 180 | "rust-analyzer.diagnostics.enable": { |
181 | "type": "boolean", | 181 | "type": "boolean", |
182 | "default": false, | 182 | "default": true, |
183 | "description": "Use proposed semantic tokens API for syntax highlighting" | 183 | "markdownDescription": "Whether to show native rust-analyzer diagnostics." |
184 | }, | ||
185 | "rust-analyzer.highlightingOn": { | ||
186 | "type": "boolean", | ||
187 | "default": false, | ||
188 | "description": "Highlight Rust code (overrides built-in syntax highlighting)" | ||
189 | }, | ||
190 | "rust-analyzer.rainbowHighlightingOn": { | ||
191 | "type": "boolean", | ||
192 | "default": false, | ||
193 | "description": "When highlighting Rust code, use a unique color per identifier" | ||
194 | }, | 184 | }, |
195 | "rust-analyzer.featureFlags": { | 185 | "rust-analyzer.lruCapacity": { |
196 | "type": "object", | 186 | "type": [ |
197 | "default": {}, | 187 | "null", |
198 | "description": "Fine grained feature flags to disable annoying features", | 188 | "integer" |
199 | "properties": { | 189 | ], |
200 | "lsp.diagnostics": { | 190 | "default": null, |
201 | "type": "boolean", | 191 | "minimum": 0, |
202 | "markdownDescription": "Whether to show diagnostics from `cargo check`" | 192 | "exclusiveMinimum": true, |
203 | }, | 193 | "description": "Number of syntax trees rust-analyzer keeps in memory." |
204 | "completion.insertion.add-call-parenthesis": { | ||
205 | "type": "boolean", | ||
206 | "description": "Whether to add parenthesis when completing functions" | ||
207 | }, | ||
208 | "completion.insertion.add-argument-snippets": { | ||
209 | "type": "boolean", | ||
210 | "description": "Whether to add argument snippets when completing functions" | ||
211 | }, | ||
212 | "completion.enable-postfix": { | ||
213 | "type": "boolean", | ||
214 | "markdownDescription": "Whether to show postfix snippets like `dbg`, `if`, `not`, etc." | ||
215 | }, | ||
216 | "call-info.full": { | ||
217 | "type": "boolean", | ||
218 | "description": "Show function name and docs in parameter hints" | ||
219 | }, | ||
220 | "notifications.workspace-loaded": { | ||
221 | "type": "boolean", | ||
222 | "markdownDescription": "Whether to show `workspace loaded` message" | ||
223 | }, | ||
224 | "notifications.cargo-toml-not-found": { | ||
225 | "type": "boolean", | ||
226 | "markdownDescription": "Whether to show `can't find Cargo.toml` error message" | ||
227 | } | ||
228 | } | ||
229 | }, | 194 | }, |
230 | "rust-analyzer.updates.channel": { | 195 | "rust-analyzer.files.watcher": { |
231 | "type": "string", | 196 | "type": "string", |
232 | "enum": [ | 197 | "enum": [ |
233 | "stable", | 198 | "client", |
234 | "nightly" | 199 | "notify" |
235 | ], | ||
236 | "default": "stable", | ||
237 | "markdownEnumDescriptions": [ | ||
238 | "`\"stable\"` updates are shipped weekly, they don't contain cutting-edge features from VSCode proposed APIs but have less bugs in general", | ||
239 | "`\"nightly\"` updates are shipped daily (extension updates automatically by downloading artifacts directly from GitHub), they contain cutting-edge features and latest bug fixes. These releases help us get your feedback very quickly and speed up rust-analyzer development **drastically**" | ||
240 | ], | 200 | ], |
241 | "markdownDescription": "Choose `\"nightly\"` updates to get the latest features and bug fixes every day. While `\"stable\"` releases occur weekly and don't contain cutting-edge features from VSCode proposed APIs" | 201 | "default": "client", |
202 | "description": "Controls file watching implementation." | ||
242 | }, | 203 | }, |
243 | "rust-analyzer.updates.askBeforeDownload": { | 204 | "rust-analyzer.files.exclude": { |
205 | "type": "array", | ||
206 | "items": { | ||
207 | "type": "string" | ||
208 | }, | ||
209 | "default": [], | ||
210 | "description": "Paths to exclude from analysis." | ||
211 | }, | ||
212 | "rust-analyzer.notifications.workspaceLoaded": { | ||
244 | "type": "boolean", | 213 | "type": "boolean", |
245 | "default": true, | 214 | "markdownDescription": "Whether to show `workspace loaded` message." |
246 | "description": "Whether to ask for permission before downloading any files from the Internet" | ||
247 | }, | 215 | }, |
248 | "rust-analyzer.serverPath": { | 216 | "rust-analyzer.notifications.cargoTomlNotFound": { |
249 | "type": [ | 217 | "type": "boolean", |
250 | "null", | 218 | "markdownDescription": "Whether to show `can't find Cargo.toml` error message" |
251 | "string" | 219 | }, |
252 | ], | 220 | "rust-analyzer.cargo.noDefaultFeatures": { |
253 | "default": null, | 221 | "type": "boolean", |
254 | "description": "Path to rust-analyzer executable (points to bundled binary by default). If this is set, then \"rust-analyzer.updates.channel\" setting is not used" | 222 | "default": false, |
223 | "markdownDescription": "Do not activate the `default` feature" | ||
224 | }, | ||
225 | "rust-analyzer.cargo.allFeatures": { | ||
226 | "type": "boolean", | ||
227 | "default": true, | ||
228 | "description": "Activate all available features" | ||
255 | }, | 229 | }, |
256 | "rust-analyzer.excludeGlobs": { | 230 | "rust-analyzer.cargo.features": { |
257 | "type": "array", | 231 | "type": "array", |
258 | "items": { | 232 | "items": { |
259 | "type": "string" | 233 | "type": "string" |
260 | }, | 234 | }, |
261 | "default": [], | 235 | "default": [], |
262 | "description": "Paths to exclude from analysis" | 236 | "description": "List of features to activate" |
237 | }, | ||
238 | "rust-analyzer.cargo.loadOutDirsFromCheck": { | ||
239 | "type": "boolean", | ||
240 | "default": false, | ||
241 | "markdownDescription": "Run `cargo check` on startup to get the correct value for package OUT_DIRs" | ||
263 | }, | 242 | }, |
264 | "rust-analyzer.rustfmtArgs": { | 243 | "rust-analyzer.rustfmt.extraArgs": { |
265 | "type": "array", | 244 | "type": "array", |
266 | "items": { | 245 | "items": { |
267 | "type": "string" | 246 | "type": "string" |
@@ -269,64 +248,42 @@ | |||
269 | "default": [], | 248 | "default": [], |
270 | "description": "Additional arguments to rustfmt" | 249 | "description": "Additional arguments to rustfmt" |
271 | }, | 250 | }, |
272 | "rust-analyzer.useClientWatching": { | 251 | "rust-analyzer.rustfmt.overrideCommand": { |
273 | "type": "boolean", | 252 | "type": "array", |
274 | "default": true, | 253 | "items": { |
275 | "description": "client provided file watching instead of notify watching." | 254 | "type": "string" |
255 | }, | ||
256 | "markdownDescription": "Advanced option, fully override the command rust-analyzer uses for formatting." | ||
276 | }, | 257 | }, |
277 | "rust-analyzer.cargo-watch.enable": { | 258 | "rust-analyzer.checkOnSave.enable": { |
278 | "type": "boolean", | 259 | "type": "boolean", |
279 | "default": true, | 260 | "default": true, |
280 | "markdownDescription": "Run specified `cargo-watch` command for diagnostics on save" | 261 | "markdownDescription": "Run specified `cargo check` command for diagnostics on save" |
281 | }, | 262 | }, |
282 | "rust-analyzer.cargo-watch.arguments": { | 263 | "rust-analyzer.checkOnSave.extraArgs": { |
283 | "type": "array", | 264 | "type": "array", |
284 | "items": { | 265 | "items": { |
285 | "type": "string" | 266 | "type": "string" |
286 | }, | 267 | }, |
287 | "markdownDescription": "`cargo-watch` arguments. (e.g: `--features=\"shumway,pdf\"` will run as `cargo watch -x \"check --features=\"shumway,pdf\"\"` )", | 268 | "markdownDescription": "Extra arguments for `cargo check`", |
288 | "default": [] | 269 | "default": [] |
289 | }, | 270 | }, |
290 | "rust-analyzer.cargo-watch.command": { | 271 | "rust-analyzer.checkOnSave.command": { |
291 | "type": "string", | 272 | "type": "string", |
292 | "markdownDescription": "`cargo-watch` command. (e.g: `clippy` will run as `cargo watch -x clippy` )", | 273 | "default": "check", |
293 | "default": "check" | 274 | "markdownDescription": "Cargo command to use for `cargo check`" |
294 | }, | 275 | }, |
295 | "rust-analyzer.cargo-watch.allTargets": { | 276 | "rust-analyzer.checkOnSave.overrideCommand": { |
296 | "type": "boolean", | 277 | "type": "array", |
297 | "markdownDescription": "Check all targets and tests (will be passed as `--all-targets`)", | 278 | "items": { |
298 | "default": true | 279 | "type": "string" |
299 | }, | 280 | }, |
300 | "rust-analyzer.trace.server": { | 281 | "markdownDescription": "Advanced option, fully override the command rust-analyzer uses for checking. The command should include `--message=format=json` or similar option." |
301 | "type": "string", | ||
302 | "scope": "window", | ||
303 | "enum": [ | ||
304 | "off", | ||
305 | "messages", | ||
306 | "verbose" | ||
307 | ], | ||
308 | "enumDescriptions": [ | ||
309 | "No traces", | ||
310 | "Error only", | ||
311 | "Full log" | ||
312 | ], | ||
313 | "default": "off", | ||
314 | "description": "Trace requests to the rust-analyzer" | ||
315 | }, | 282 | }, |
316 | "rust-analyzer.trace.extension": { | 283 | "rust-analyzer.checkOnSave.allTargets": { |
317 | "description": "Enable logging of VS Code extensions itself", | ||
318 | "type": "boolean", | 284 | "type": "boolean", |
319 | "default": false | 285 | "default": true, |
320 | }, | 286 | "markdownDescription": "Check all targets and tests (will be passed as `--all-targets`)" |
321 | "rust-analyzer.lruCapacity": { | ||
322 | "type": [ | ||
323 | "null", | ||
324 | "integer" | ||
325 | ], | ||
326 | "default": null, | ||
327 | "minimum": 0, | ||
328 | "exclusiveMinimum": true, | ||
329 | "description": "Number of syntax trees rust-analyzer keeps in memory" | ||
330 | }, | 287 | }, |
331 | "rust-analyzer.inlayHints.typeHints": { | 288 | "rust-analyzer.inlayHints.typeHints": { |
332 | "type": "boolean", | 289 | "type": "boolean", |
@@ -353,28 +310,76 @@ | |||
353 | "exclusiveMinimum": true, | 310 | "exclusiveMinimum": true, |
354 | "description": "Maximum length for inlay hints" | 311 | "description": "Maximum length for inlay hints" |
355 | }, | 312 | }, |
356 | "rust-analyzer.cargoFeatures.noDefaultFeatures": { | 313 | "rust-analyzer.completion.addCallParenthesis": { |
357 | "type": "boolean", | 314 | "type": "boolean", |
358 | "default": false, | 315 | "default": true, |
359 | "markdownDescription": "Do not activate the `default` feature" | 316 | "description": "Whether to add parenthesis when completing functions" |
360 | }, | 317 | }, |
361 | "rust-analyzer.cargoFeatures.allFeatures": { | 318 | "rust-analyzer.completion.addCallArgumentSnippets": { |
362 | "type": "boolean", | 319 | "type": "boolean", |
363 | "default": true, | 320 | "default": true, |
364 | "description": "Activate all available features" | 321 | "description": "Whether to add argument snippets when completing functions" |
365 | }, | 322 | }, |
366 | "rust-analyzer.cargoFeatures.features": { | 323 | "rust-analyzer.completion.postfix.enable": { |
367 | "type": "array", | 324 | "type": "boolean", |
368 | "items": { | 325 | "default": true, |
369 | "type": "string" | 326 | "markdownDescription": "Whether to show postfix snippets like `dbg`, `if`, `not`, etc." |
370 | }, | ||
371 | "default": [], | ||
372 | "description": "List of features to activate" | ||
373 | }, | 327 | }, |
374 | "rust-analyzer.cargoFeatures.loadOutDirsFromCheck": { | 328 | "rust-analyzer.callInfo.full": { |
329 | "type": "boolean", | ||
330 | "description": "Show function name and docs in parameter hints" | ||
331 | }, | ||
332 | "rust-analyzer.highlighting.semanticTokens": { | ||
375 | "type": "boolean", | 333 | "type": "boolean", |
376 | "default": false, | 334 | "default": false, |
377 | "markdownDescription": "Run `cargo check` on startup to get the correct value for package OUT_DIRs" | 335 | "description": "Use proposed semantic tokens API for syntax highlighting" |
336 | }, | ||
337 | "rust-analyzer.updates.channel": { | ||
338 | "type": "string", | ||
339 | "enum": [ | ||
340 | "stable", | ||
341 | "nightly" | ||
342 | ], | ||
343 | "default": "stable", | ||
344 | "markdownEnumDescriptions": [ | ||
345 | "`\"stable\"` updates are shipped weekly, they don't contain cutting-edge features from VSCode proposed APIs but have less bugs in general", | ||
346 | "`\"nightly\"` updates are shipped daily (extension updates automatically by downloading artifacts directly from GitHub), they contain cutting-edge features and latest bug fixes. These releases help us get your feedback very quickly and speed up rust-analyzer development **drastically**" | ||
347 | ], | ||
348 | "markdownDescription": "Choose `\"nightly\"` updates to get the latest features and bug fixes every day. While `\"stable\"` releases occur weekly and don't contain cutting-edge features from VSCode proposed APIs" | ||
349 | }, | ||
350 | "rust-analyzer.updates.askBeforeDownload": { | ||
351 | "type": "boolean", | ||
352 | "default": true, | ||
353 | "description": "Whether to ask for permission before downloading any files from the Internet" | ||
354 | }, | ||
355 | "rust-analyzer.serverPath": { | ||
356 | "type": [ | ||
357 | "null", | ||
358 | "string" | ||
359 | ], | ||
360 | "default": null, | ||
361 | "description": "Path to rust-analyzer executable (points to bundled binary by default). If this is set, then \"rust-analyzer.updates.channel\" setting is not used" | ||
362 | }, | ||
363 | "rust-analyzer.trace.server": { | ||
364 | "type": "string", | ||
365 | "scope": "window", | ||
366 | "enum": [ | ||
367 | "off", | ||
368 | "messages", | ||
369 | "verbose" | ||
370 | ], | ||
371 | "enumDescriptions": [ | ||
372 | "No traces", | ||
373 | "Error only", | ||
374 | "Full log" | ||
375 | ], | ||
376 | "default": "off", | ||
377 | "description": "Trace requests to the rust-analyzer" | ||
378 | }, | ||
379 | "rust-analyzer.trace.extension": { | ||
380 | "description": "Enable logging of VS Code extensions itself", | ||
381 | "type": "boolean", | ||
382 | "default": false | ||
378 | } | 383 | } |
379 | } | 384 | } |
380 | }, | 385 | }, |
@@ -411,6 +416,21 @@ | |||
411 | ] | 416 | ] |
412 | } | 417 | } |
413 | ], | 418 | ], |
419 | "languages": [ | ||
420 | { | ||
421 | "id": "ra_syntax_tree", | ||
422 | "extensions": [ | ||
423 | ".rast" | ||
424 | ] | ||
425 | } | ||
426 | ], | ||
427 | "grammars": [ | ||
428 | { | ||
429 | "language": "ra_syntax_tree", | ||
430 | "scopeName": "source.ra_syntax_tree", | ||
431 | "path": "ra_syntax_tree.tmGrammar.json" | ||
432 | } | ||
433 | ], | ||
414 | "problemMatchers": [ | 434 | "problemMatchers": [ |
415 | { | 435 | { |
416 | "name": "rustc", | 436 | "name": "rustc", |
@@ -450,6 +470,15 @@ | |||
450 | "light": "#747474", | 470 | "light": "#747474", |
451 | "highContrast": "#BEBEBE" | 471 | "highContrast": "#BEBEBE" |
452 | } | 472 | } |
473 | }, | ||
474 | { | ||
475 | "id": "rust_analyzer.syntaxTreeBorder", | ||
476 | "description": "Color of the border displayed in the Rust source code for the selected syntax node (see \"Show Syntax Tree\" command)", | ||
477 | "defaults": { | ||
478 | "dark": "#ffffff", | ||
479 | "light": "#b700ff", | ||
480 | "highContrast": "#b700ff" | ||
481 | } | ||
453 | } | 482 | } |
454 | ], | 483 | ], |
455 | "semanticTokenTypes": [ | 484 | "semanticTokenTypes": [ |
@@ -492,65 +521,34 @@ | |||
492 | "description": "Style for unsafe operations" | 521 | "description": "Style for unsafe operations" |
493 | } | 522 | } |
494 | ], | 523 | ], |
495 | "semanticTokenStyleDefaults": [ | 524 | "semanticTokenScopes": [ |
496 | { | 525 | { |
497 | "selector": "attribute", | 526 | "language": "rust", |
498 | "scope": [ | 527 | "scopes": { |
499 | "meta.attribute" | 528 | "attribute": [ |
500 | ] | 529 | "meta.attribute" |
501 | }, | 530 | ], |
502 | { | 531 | "builtinType": [ |
503 | "selector": "builtinType", | 532 | "support.type.primitive" |
504 | "scope": [ | 533 | ], |
505 | "support.type.primitive" | 534 | "lifetime": [ |
506 | ] | 535 | "entity.name.lifetime.rust" |
507 | }, | 536 | ], |
508 | { | 537 | "typeAlias": [ |
509 | "selector": "lifetime", | 538 | "entity.name.typeAlias" |
510 | "scope": [ | 539 | ], |
511 | "entity.name.lifetime.rust" | 540 | "union": [ |
512 | ] | 541 | "entity.name.union" |
513 | }, | 542 | ], |
514 | { | 543 | "keyword.unsafe": [ |
515 | "selector": "typeAlias", | 544 | "keyword.other.unsafe" |
516 | "scope": [ | 545 | ], |
517 | "entity.name.typeAlias" | 546 | "keyword.control": [ |
518 | ] | 547 | "keyword.control" |
519 | }, | 548 | ], |
520 | { | 549 | "variable.constant": [ |
521 | "selector": "union", | 550 | "entity.name.constant" |
522 | "scope": [ | 551 | ] |
523 | "entity.name.union" | ||
524 | ] | ||
525 | }, | ||
526 | { | ||
527 | "selector": "keyword.unsafe", | ||
528 | "scope": [ | ||
529 | "keyword.other.unsafe" | ||
530 | ] | ||
531 | }, | ||
532 | { | ||
533 | "selector": "keyword.control", | ||
534 | "scope": [ | ||
535 | "keyword.control" | ||
536 | ] | ||
537 | }, | ||
538 | { | ||
539 | "selector": "variable.constant", | ||
540 | "scope": [ | ||
541 | "entity.name.constant" | ||
542 | ] | ||
543 | }, | ||
544 | { | ||
545 | "selector": "*.mutable", | ||
546 | "light": { | ||
547 | "fontStyle": "underline" | ||
548 | }, | ||
549 | "dark": { | ||
550 | "fontStyle": "underline" | ||
551 | }, | ||
552 | "highContrast": { | ||
553 | "fontStyle": "underline" | ||
554 | } | 552 | } |
555 | } | 553 | } |
556 | ] | 554 | ] |
diff --git a/editors/code/ra_syntax_tree.tmGrammar.json b/editors/code/ra_syntax_tree.tmGrammar.json new file mode 100644 index 000000000..0d72a3e36 --- /dev/null +++ b/editors/code/ra_syntax_tree.tmGrammar.json | |||
@@ -0,0 +1,31 @@ | |||
1 | { | ||
2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", | ||
3 | |||
4 | "scopeName": "source.ra_syntax_tree", | ||
5 | "patterns": [ | ||
6 | { "include": "#node_type" }, | ||
7 | { "include": "#node_range_index" }, | ||
8 | { "include": "#token_text" } | ||
9 | ], | ||
10 | "repository": { | ||
11 | "node_type": { | ||
12 | "match": "^\\s*([A-Z_]+?)@", | ||
13 | "captures": { | ||
14 | "1": { | ||
15 | "name": "entity.name.class" | ||
16 | } | ||
17 | } | ||
18 | }, | ||
19 | "node_range_index": { | ||
20 | "match": "\\d+", | ||
21 | "name": "constant.numeric" | ||
22 | }, | ||
23 | "token_text": { | ||
24 | "match": "\".+\"", | ||
25 | "name": "string" | ||
26 | } | ||
27 | }, | ||
28 | "fileTypes": [ | ||
29 | "rast" | ||
30 | ] | ||
31 | } | ||
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index f909f8db2..3b1d00bca 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts | |||
@@ -5,31 +5,6 @@ import { Config } from './config'; | |||
5 | import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.proposed'; | 5 | import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.proposed'; |
6 | import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed'; | 6 | import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed'; |
7 | 7 | ||
8 | export function configToServerOptions(config: Config) { | ||
9 | return { | ||
10 | publishDecorations: !config.highlightingSemanticTokens, | ||
11 | lruCapacity: config.lruCapacity, | ||
12 | |||
13 | inlayHintsType: config.inlayHints.typeHints, | ||
14 | inlayHintsParameter: config.inlayHints.parameterHints, | ||
15 | inlayHintsChaining: config.inlayHints.chainingHints, | ||
16 | inlayHintsMaxLength: config.inlayHints.maxLength, | ||
17 | |||
18 | cargoWatchEnable: config.cargoWatchOptions.enable, | ||
19 | cargoWatchArgs: config.cargoWatchOptions.arguments, | ||
20 | cargoWatchCommand: config.cargoWatchOptions.command, | ||
21 | cargoWatchAllTargets: config.cargoWatchOptions.allTargets, | ||
22 | |||
23 | excludeGlobs: config.excludeGlobs, | ||
24 | useClientWatching: config.useClientWatching, | ||
25 | featureFlags: config.featureFlags, | ||
26 | withSysroot: config.withSysroot, | ||
27 | cargoFeatures: config.cargoFeatures, | ||
28 | rustfmtArgs: config.rustfmtArgs, | ||
29 | vscodeLldb: vscode.extensions.getExtension("vadimcn.vscode-lldb") != null, | ||
30 | }; | ||
31 | } | ||
32 | |||
33 | export async function createClient(config: Config, serverPath: string, cwd: string): Promise<lc.LanguageClient> { | 8 | export async function createClient(config: Config, serverPath: string, cwd: string): Promise<lc.LanguageClient> { |
34 | // '.' Is the fallback if no folder is open | 9 | // '.' Is the fallback if no folder is open |
35 | // TODO?: Workspace folders support Uri's (eg: file://test.txt). | 10 | // TODO?: Workspace folders support Uri's (eg: file://test.txt). |
@@ -49,7 +24,7 @@ export async function createClient(config: Config, serverPath: string, cwd: stri | |||
49 | 24 | ||
50 | const clientOptions: lc.LanguageClientOptions = { | 25 | const clientOptions: lc.LanguageClientOptions = { |
51 | documentSelector: [{ scheme: 'file', language: 'rust' }], | 26 | documentSelector: [{ scheme: 'file', language: 'rust' }], |
52 | initializationOptions: configToServerOptions(config), | 27 | initializationOptions: vscode.workspace.getConfiguration("rust-analyzer"), |
53 | traceOutputChannel, | 28 | traceOutputChannel, |
54 | middleware: { | 29 | middleware: { |
55 | // Workaround for https://github.com/microsoft/vscode-languageserver-node/issues/576 | 30 | // Workaround for https://github.com/microsoft/vscode-languageserver-node/issues/576 |
diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts index 357155163..2635a1440 100644 --- a/editors/code/src/commands/runnables.ts +++ b/editors/code/src/commands/runnables.ts | |||
@@ -66,6 +66,10 @@ export function debugSingle(ctx: Ctx): Cmd { | |||
66 | return async (config: ra.Runnable) => { | 66 | return async (config: ra.Runnable) => { |
67 | const editor = ctx.activeRustEditor; | 67 | const editor = ctx.activeRustEditor; |
68 | if (!editor) return; | 68 | if (!editor) return; |
69 | if (!vscode.extensions.getExtension("vadimcn.vscode-lldb")) { | ||
70 | vscode.window.showErrorMessage("Install `vadimcn.vscode-lldb` extension for debugging"); | ||
71 | return; | ||
72 | } | ||
69 | 73 | ||
70 | const debugConfig = { | 74 | const debugConfig = { |
71 | type: "lldb", | 75 | type: "lldb", |
diff --git a/editors/code/src/commands/syntax_tree.ts b/editors/code/src/commands/syntax_tree.ts index 996c7a716..b7a397414 100644 --- a/editors/code/src/commands/syntax_tree.ts +++ b/editors/code/src/commands/syntax_tree.ts | |||
@@ -15,6 +15,9 @@ export function syntaxTree(ctx: Ctx): Cmd { | |||
15 | void new AstInspector(ctx); | 15 | void new AstInspector(ctx); |
16 | 16 | ||
17 | ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider(AST_FILE_SCHEME, tdcp)); | 17 | ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider(AST_FILE_SCHEME, tdcp)); |
18 | ctx.pushCleanup(vscode.languages.setLanguageConfiguration("ra_syntax_tree", { | ||
19 | brackets: [["[", ")"]], | ||
20 | })); | ||
18 | 21 | ||
19 | return async () => { | 22 | return async () => { |
20 | const editor = vscode.window.activeTextEditor; | 23 | const editor = vscode.window.activeTextEditor; |
@@ -36,7 +39,7 @@ export function syntaxTree(ctx: Ctx): Cmd { | |||
36 | } | 39 | } |
37 | 40 | ||
38 | class TextDocumentContentProvider implements vscode.TextDocumentContentProvider { | 41 | class TextDocumentContentProvider implements vscode.TextDocumentContentProvider { |
39 | readonly uri = vscode.Uri.parse('rust-analyzer://syntaxtree'); | 42 | readonly uri = vscode.Uri.parse('rust-analyzer://syntaxtree/tree.rast'); |
40 | readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>(); | 43 | readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>(); |
41 | 44 | ||
42 | 45 | ||
@@ -79,16 +82,41 @@ class TextDocumentContentProvider implements vscode.TextDocumentContentProvider | |||
79 | 82 | ||
80 | // FIXME: consider implementing this via the Tree View API? | 83 | // FIXME: consider implementing this via the Tree View API? |
81 | // https://code.visualstudio.com/api/extension-guides/tree-view | 84 | // https://code.visualstudio.com/api/extension-guides/tree-view |
82 | class AstInspector implements vscode.HoverProvider, Disposable { | 85 | class AstInspector implements vscode.HoverProvider, vscode.DefinitionProvider, Disposable { |
83 | private static readonly astDecorationType = vscode.window.createTextEditorDecorationType({ | 86 | private readonly astDecorationType = vscode.window.createTextEditorDecorationType({ |
84 | fontStyle: "normal", | 87 | borderColor: new vscode.ThemeColor('rust_analyzer.syntaxTreeBorder'), |
85 | border: "#ffffff 1px solid", | 88 | borderStyle: "solid", |
89 | borderWidth: "2px", | ||
90 | |||
86 | }); | 91 | }); |
87 | private rustEditor: undefined | RustEditor; | 92 | private rustEditor: undefined | RustEditor; |
88 | 93 | ||
94 | // Lazy rust token range -> syntax tree file range. | ||
95 | private readonly rust2Ast = new Lazy(() => { | ||
96 | const astEditor = this.findAstTextEditor(); | ||
97 | if (!this.rustEditor || !astEditor) return undefined; | ||
98 | |||
99 | const buf: [vscode.Range, vscode.Range][] = []; | ||
100 | for (let i = 0; i < astEditor.document.lineCount; ++i) { | ||
101 | const astLine = astEditor.document.lineAt(i); | ||
102 | |||
103 | // Heuristically look for nodes with quoted text (which are token nodes) | ||
104 | const isTokenNode = astLine.text.lastIndexOf('"') >= 0; | ||
105 | if (!isTokenNode) continue; | ||
106 | |||
107 | const rustRange = this.parseRustTextRange(this.rustEditor.document, astLine.text); | ||
108 | if (!rustRange) continue; | ||
109 | |||
110 | buf.push([rustRange, this.findAstNodeRange(astLine)]); | ||
111 | } | ||
112 | return buf; | ||
113 | }); | ||
114 | |||
89 | constructor(ctx: Ctx) { | 115 | constructor(ctx: Ctx) { |
90 | ctx.pushCleanup(vscode.languages.registerHoverProvider({ scheme: AST_FILE_SCHEME }, this)); | 116 | ctx.pushCleanup(vscode.languages.registerHoverProvider({ scheme: AST_FILE_SCHEME }, this)); |
117 | ctx.pushCleanup(vscode.languages.registerDefinitionProvider({ language: "rust" }, this)); | ||
91 | vscode.workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, ctx.subscriptions); | 118 | vscode.workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, ctx.subscriptions); |
119 | vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions); | ||
92 | vscode.window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, ctx.subscriptions); | 120 | vscode.window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, ctx.subscriptions); |
93 | 121 | ||
94 | ctx.pushCleanup(this); | 122 | ctx.pushCleanup(this); |
@@ -97,6 +125,12 @@ class AstInspector implements vscode.HoverProvider, Disposable { | |||
97 | this.setRustEditor(undefined); | 125 | this.setRustEditor(undefined); |
98 | } | 126 | } |
99 | 127 | ||
128 | private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) { | ||
129 | if (this.rustEditor && event.document.uri.toString() === this.rustEditor.document.uri.toString()) { | ||
130 | this.rust2Ast.reset(); | ||
131 | } | ||
132 | } | ||
133 | |||
100 | private onDidCloseTextDocument(doc: vscode.TextDocument) { | 134 | private onDidCloseTextDocument(doc: vscode.TextDocument) { |
101 | if (this.rustEditor && doc.uri.toString() === this.rustEditor.document.uri.toString()) { | 135 | if (this.rustEditor && doc.uri.toString() === this.rustEditor.document.uri.toString()) { |
102 | this.setRustEditor(undefined); | 136 | this.setRustEditor(undefined); |
@@ -104,38 +138,67 @@ class AstInspector implements vscode.HoverProvider, Disposable { | |||
104 | } | 138 | } |
105 | 139 | ||
106 | private onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]) { | 140 | private onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]) { |
107 | if (editors.every(suspect => suspect.document.uri.scheme !== AST_FILE_SCHEME)) { | 141 | if (!this.findAstTextEditor()) { |
108 | this.setRustEditor(undefined); | 142 | this.setRustEditor(undefined); |
109 | return; | 143 | return; |
110 | } | 144 | } |
111 | this.setRustEditor(editors.find(isRustEditor)); | 145 | this.setRustEditor(editors.find(isRustEditor)); |
112 | } | 146 | } |
113 | 147 | ||
148 | private findAstTextEditor(): undefined | vscode.TextEditor { | ||
149 | return vscode.window.visibleTextEditors.find(it => it.document.uri.scheme === AST_FILE_SCHEME); | ||
150 | } | ||
151 | |||
114 | private setRustEditor(newRustEditor: undefined | RustEditor) { | 152 | private setRustEditor(newRustEditor: undefined | RustEditor) { |
115 | if (newRustEditor !== this.rustEditor) { | 153 | if (this.rustEditor && this.rustEditor !== newRustEditor) { |
116 | this.rustEditor?.setDecorations(AstInspector.astDecorationType, []); | 154 | this.rustEditor.setDecorations(this.astDecorationType, []); |
155 | this.rust2Ast.reset(); | ||
117 | } | 156 | } |
118 | this.rustEditor = newRustEditor; | 157 | this.rustEditor = newRustEditor; |
119 | } | 158 | } |
120 | 159 | ||
160 | // additional positional params are omitted | ||
161 | provideDefinition(doc: vscode.TextDocument, pos: vscode.Position): vscode.ProviderResult<vscode.DefinitionLink[]> { | ||
162 | if (!this.rustEditor || doc.uri.toString() !== this.rustEditor.document.uri.toString()) return; | ||
163 | |||
164 | const astEditor = this.findAstTextEditor(); | ||
165 | if (!astEditor) return; | ||
166 | |||
167 | const rust2AstRanges = this.rust2Ast.get()?.find(([rustRange, _]) => rustRange.contains(pos)); | ||
168 | if (!rust2AstRanges) return; | ||
169 | |||
170 | const [rustFileRange, astFileRange] = rust2AstRanges; | ||
171 | |||
172 | astEditor.revealRange(astFileRange); | ||
173 | astEditor.selection = new vscode.Selection(astFileRange.start, astFileRange.end); | ||
174 | |||
175 | return [{ | ||
176 | targetRange: astFileRange, | ||
177 | targetUri: astEditor.document.uri, | ||
178 | originSelectionRange: rustFileRange, | ||
179 | targetSelectionRange: astFileRange, | ||
180 | }]; | ||
181 | } | ||
182 | |||
183 | // additional positional params are omitted | ||
121 | provideHover(doc: vscode.TextDocument, hoverPosition: vscode.Position): vscode.ProviderResult<vscode.Hover> { | 184 | provideHover(doc: vscode.TextDocument, hoverPosition: vscode.Position): vscode.ProviderResult<vscode.Hover> { |
122 | if (!this.rustEditor) return; | 185 | if (!this.rustEditor) return; |
123 | 186 | ||
124 | const astTextLine = doc.lineAt(hoverPosition.line); | 187 | const astFileLine = doc.lineAt(hoverPosition.line); |
125 | 188 | ||
126 | const rustTextRange = this.parseRustTextRange(this.rustEditor.document, astTextLine.text); | 189 | const rustFileRange = this.parseRustTextRange(this.rustEditor.document, astFileLine.text); |
127 | if (!rustTextRange) return; | 190 | if (!rustFileRange) return; |
128 | 191 | ||
129 | this.rustEditor.setDecorations(AstInspector.astDecorationType, [rustTextRange]); | 192 | this.rustEditor.setDecorations(this.astDecorationType, [rustFileRange]); |
130 | this.rustEditor.revealRange(rustTextRange); | 193 | this.rustEditor.revealRange(rustFileRange); |
131 | 194 | ||
132 | const rustSourceCode = this.rustEditor.document.getText(rustTextRange); | 195 | const rustSourceCode = this.rustEditor.document.getText(rustFileRange); |
133 | const astTextRange = this.findAstRange(astTextLine); | 196 | const astFileRange = this.findAstNodeRange(astFileLine); |
134 | 197 | ||
135 | return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astTextRange); | 198 | return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astFileRange); |
136 | } | 199 | } |
137 | 200 | ||
138 | private findAstRange(astLine: vscode.TextLine) { | 201 | private findAstNodeRange(astLine: vscode.TextLine) { |
139 | const lineOffset = astLine.range.start; | 202 | const lineOffset = astLine.range.start; |
140 | const begin = lineOffset.translate(undefined, astLine.firstNonWhitespaceCharacterIndex); | 203 | const begin = lineOffset.translate(undefined, astLine.firstNonWhitespaceCharacterIndex); |
141 | const end = lineOffset.translate(undefined, astLine.text.trimEnd().length); | 204 | const end = lineOffset.translate(undefined, astLine.text.trimEnd().length); |
@@ -151,3 +214,17 @@ class AstInspector implements vscode.HoverProvider, Disposable { | |||
151 | return new vscode.Range(begin, end); | 214 | return new vscode.Range(begin, end); |
152 | } | 215 | } |
153 | } | 216 | } |
217 | |||
218 | class Lazy<T> { | ||
219 | val: undefined | T; | ||
220 | |||
221 | constructor(private readonly compute: () => undefined | T) { } | ||
222 | |||
223 | get() { | ||
224 | return this.val ?? (this.val = this.compute()); | ||
225 | } | ||
226 | |||
227 | reset() { | ||
228 | this.val = undefined; | ||
229 | } | ||
230 | } | ||
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 501997fef..1f45f1de0 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts | |||
@@ -11,9 +11,8 @@ export class Config { | |||
11 | private readonly rootSection = "rust-analyzer"; | 11 | private readonly rootSection = "rust-analyzer"; |
12 | private readonly requiresReloadOpts = [ | 12 | private readonly requiresReloadOpts = [ |
13 | "serverPath", | 13 | "serverPath", |
14 | "cargoFeatures", | 14 | "cargo", |
15 | "excludeGlobs", | 15 | "files", |
16 | "useClientWatching", | ||
17 | "highlighting", | 16 | "highlighting", |
18 | "updates.channel", | 17 | "updates.channel", |
19 | ] | 18 | ] |
@@ -71,19 +70,8 @@ export class Config { | |||
71 | get channel() { return this.cfg.get<UpdatesChannel>("updates.channel")!; } | 70 | get channel() { return this.cfg.get<UpdatesChannel>("updates.channel")!; } |
72 | get askBeforeDownload() { return this.cfg.get<boolean>("updates.askBeforeDownload")!; } | 71 | get askBeforeDownload() { return this.cfg.get<boolean>("updates.askBeforeDownload")!; } |
73 | get highlightingSemanticTokens() { return this.cfg.get<boolean>("highlighting.semanticTokens")!; } | 72 | get highlightingSemanticTokens() { return this.cfg.get<boolean>("highlighting.semanticTokens")!; } |
74 | get highlightingOn() { return this.cfg.get<boolean>("highlightingOn")!; } | ||
75 | get rainbowHighlightingOn() { return this.cfg.get<boolean>("rainbowHighlightingOn")!; } | ||
76 | get lruCapacity() { return this.cfg.get<null | number>("lruCapacity")!; } | ||
77 | get excludeGlobs() { return this.cfg.get<string[]>("excludeGlobs")!; } | ||
78 | get useClientWatching() { return this.cfg.get<boolean>("useClientWatching")!; } | ||
79 | get featureFlags() { return this.cfg.get<Record<string, boolean>>("featureFlags")!; } | ||
80 | get rustfmtArgs() { return this.cfg.get<string[]>("rustfmtArgs")!; } | ||
81 | get loadOutDirsFromCheck() { return this.cfg.get<boolean>("loadOutDirsFromCheck")!; } | ||
82 | get traceExtension() { return this.cfg.get<boolean>("trace.extension")!; } | 73 | get traceExtension() { return this.cfg.get<boolean>("trace.extension")!; } |
83 | 74 | ||
84 | // for internal use | ||
85 | get withSysroot() { return this.cfg.get<boolean>("withSysroot", true)!; } | ||
86 | |||
87 | get inlayHints() { | 75 | get inlayHints() { |
88 | return { | 76 | return { |
89 | typeHints: this.cfg.get<boolean>("inlayHints.typeHints")!, | 77 | typeHints: this.cfg.get<boolean>("inlayHints.typeHints")!, |
@@ -93,21 +81,9 @@ export class Config { | |||
93 | }; | 81 | }; |
94 | } | 82 | } |
95 | 83 | ||
96 | get cargoWatchOptions() { | 84 | get checkOnSave() { |
97 | return { | ||
98 | enable: this.cfg.get<boolean>("cargo-watch.enable")!, | ||
99 | arguments: this.cfg.get<string[]>("cargo-watch.arguments")!, | ||
100 | allTargets: this.cfg.get<boolean>("cargo-watch.allTargets")!, | ||
101 | command: this.cfg.get<string>("cargo-watch.command")!, | ||
102 | }; | ||
103 | } | ||
104 | |||
105 | get cargoFeatures() { | ||
106 | return { | 85 | return { |
107 | noDefaultFeatures: this.cfg.get<boolean>("cargoFeatures.noDefaultFeatures")!, | 86 | command: this.cfg.get<string>("checkOnSave.command")!, |
108 | allFeatures: this.cfg.get<boolean>("cargoFeatures.allFeatures")!, | ||
109 | features: this.cfg.get<string[]>("cargoFeatures.features")!, | ||
110 | loadOutDirsFromCheck: this.cfg.get<boolean>("cargoFeatures.loadOutDirsFromCheck")!, | ||
111 | }; | 87 | }; |
112 | } | 88 | } |
113 | } | 89 | } |
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 86b5f3629..bd1c3de07 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts | |||
@@ -2,7 +2,7 @@ import * as vscode from 'vscode'; | |||
2 | import * as lc from 'vscode-languageclient'; | 2 | import * as lc from 'vscode-languageclient'; |
3 | 3 | ||
4 | import { Config } from './config'; | 4 | import { Config } from './config'; |
5 | import { createClient, configToServerOptions } from './client'; | 5 | import { createClient } from './client'; |
6 | import { isRustEditor, RustEditor } from './util'; | 6 | import { isRustEditor, RustEditor } from './util'; |
7 | 7 | ||
8 | export class Ctx { | 8 | export class Ctx { |
@@ -25,7 +25,6 @@ export class Ctx { | |||
25 | const res = new Ctx(config, extCtx, client, serverPath); | 25 | const res = new Ctx(config, extCtx, client, serverPath); |
26 | res.pushCleanup(client.start()); | 26 | res.pushCleanup(client.start()); |
27 | await client.onReady(); | 27 | await client.onReady(); |
28 | client.onRequest('workspace/configuration', _ => [configToServerOptions(config)]); | ||
29 | return res; | 28 | return res; |
30 | } | 29 | } |
31 | 30 | ||
diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts deleted file mode 100644 index ea2dfc0e3..000000000 --- a/editors/code/src/highlighting.ts +++ /dev/null | |||
@@ -1,255 +0,0 @@ | |||
1 | import * as vscode from 'vscode'; | ||
2 | import * as ra from './rust-analyzer-api'; | ||
3 | |||
4 | import { ColorTheme, TextMateRuleSettings } from './color_theme'; | ||
5 | |||
6 | import { Ctx } from './ctx'; | ||
7 | import { sendRequestWithRetry, isRustDocument } from './util'; | ||
8 | |||
9 | export function activateHighlighting(ctx: Ctx) { | ||
10 | const highlighter = new Highlighter(ctx); | ||
11 | |||
12 | ctx.client.onNotification(ra.publishDecorations, params => { | ||
13 | if (!ctx.config.highlightingOn) return; | ||
14 | |||
15 | const targetEditor = vscode.window.visibleTextEditors.find( | ||
16 | editor => { | ||
17 | const unescapedUri = unescape( | ||
18 | editor.document.uri.toString(), | ||
19 | ); | ||
20 | // Unescaped URI looks like: | ||
21 | // file:///c:/Workspace/ra-test/src/main.rs | ||
22 | return unescapedUri === params.uri; | ||
23 | }, | ||
24 | ); | ||
25 | if (!targetEditor) return; | ||
26 | |||
27 | highlighter.setHighlights(targetEditor, params.decorations); | ||
28 | }); | ||
29 | |||
30 | |||
31 | vscode.workspace.onDidChangeConfiguration( | ||
32 | _ => highlighter.removeHighlights(), | ||
33 | null, | ||
34 | ctx.subscriptions, | ||
35 | ); | ||
36 | |||
37 | vscode.window.onDidChangeActiveTextEditor( | ||
38 | async (editor: vscode.TextEditor | undefined) => { | ||
39 | if (!editor || !isRustDocument(editor.document)) return; | ||
40 | if (!ctx.config.highlightingOn) return; | ||
41 | const client = ctx.client; | ||
42 | if (!client) return; | ||
43 | |||
44 | const decorations = await sendRequestWithRetry( | ||
45 | client, | ||
46 | ra.decorationsRequest, | ||
47 | { uri: editor.document.uri.toString() }, | ||
48 | ); | ||
49 | highlighter.setHighlights(editor, decorations); | ||
50 | }, | ||
51 | null, | ||
52 | ctx.subscriptions, | ||
53 | ); | ||
54 | } | ||
55 | |||
56 | // Based on this HSL-based color generator: https://gist.github.com/bendc/76c48ce53299e6078a76 | ||
57 | function fancify(seed: string, shade: 'light' | 'dark') { | ||
58 | const random = randomU32Numbers(hashString(seed)); | ||
59 | const randomInt = (min: number, max: number) => { | ||
60 | return Math.abs(random()) % (max - min + 1) + min; | ||
61 | }; | ||
62 | |||
63 | const h = randomInt(0, 360); | ||
64 | const s = randomInt(42, 98); | ||
65 | const l = shade === 'light' ? randomInt(15, 40) : randomInt(40, 90); | ||
66 | return `hsl(${h},${s}%,${l}%)`; | ||
67 | } | ||
68 | |||
69 | class Highlighter { | ||
70 | private ctx: Ctx; | ||
71 | private decorations: Map< | ||
72 | string, | ||
73 | vscode.TextEditorDecorationType | ||
74 | > | null = null; | ||
75 | |||
76 | constructor(ctx: Ctx) { | ||
77 | this.ctx = ctx; | ||
78 | } | ||
79 | |||
80 | public removeHighlights() { | ||
81 | if (this.decorations == null) { | ||
82 | return; | ||
83 | } | ||
84 | |||
85 | // Decorations are removed when the object is disposed | ||
86 | for (const decoration of this.decorations.values()) { | ||
87 | decoration.dispose(); | ||
88 | } | ||
89 | |||
90 | this.decorations = null; | ||
91 | } | ||
92 | |||
93 | public setHighlights(editor: vscode.TextEditor, highlights: ra.Decoration[]) { | ||
94 | const client = this.ctx.client; | ||
95 | if (!client) return; | ||
96 | // Initialize decorations if necessary | ||
97 | // | ||
98 | // Note: decoration objects need to be kept around so we can dispose them | ||
99 | // if the user disables syntax highlighting | ||
100 | if (this.decorations == null) { | ||
101 | this.decorations = initDecorations(); | ||
102 | } | ||
103 | |||
104 | const byTag: Map<string, vscode.Range[]> = new Map(); | ||
105 | const colorfulIdents: Map< | ||
106 | string, | ||
107 | [vscode.Range[], boolean] | ||
108 | > = new Map(); | ||
109 | const rainbowTime = this.ctx.config.rainbowHighlightingOn; | ||
110 | |||
111 | for (const tag of this.decorations.keys()) { | ||
112 | byTag.set(tag, []); | ||
113 | } | ||
114 | |||
115 | for (const d of highlights) { | ||
116 | if (!byTag.get(d.tag)) { | ||
117 | continue; | ||
118 | } | ||
119 | |||
120 | if (rainbowTime && d.bindingHash) { | ||
121 | if (!colorfulIdents.has(d.bindingHash)) { | ||
122 | const mut = d.tag.endsWith('.mut'); | ||
123 | colorfulIdents.set(d.bindingHash, [[], mut]); | ||
124 | } | ||
125 | colorfulIdents | ||
126 | .get(d.bindingHash)![0] | ||
127 | .push( | ||
128 | client.protocol2CodeConverter.asRange(d.range), | ||
129 | ); | ||
130 | } else { | ||
131 | byTag | ||
132 | .get(d.tag)! | ||
133 | .push( | ||
134 | client.protocol2CodeConverter.asRange(d.range), | ||
135 | ); | ||
136 | } | ||
137 | } | ||
138 | |||
139 | for (const tag of byTag.keys()) { | ||
140 | const dec = this.decorations.get( | ||
141 | tag, | ||
142 | ) as vscode.TextEditorDecorationType; | ||
143 | const ranges = byTag.get(tag)!; | ||
144 | editor.setDecorations(dec, ranges); | ||
145 | } | ||
146 | |||
147 | for (const [hash, [ranges, mut]] of colorfulIdents.entries()) { | ||
148 | const textDecoration = mut ? 'underline' : undefined; | ||
149 | const dec = vscode.window.createTextEditorDecorationType({ | ||
150 | light: { color: fancify(hash, 'light'), textDecoration }, | ||
151 | dark: { color: fancify(hash, 'dark'), textDecoration }, | ||
152 | }); | ||
153 | editor.setDecorations(dec, ranges); | ||
154 | } | ||
155 | } | ||
156 | } | ||
157 | |||
158 | function initDecorations(): Map<string, vscode.TextEditorDecorationType> { | ||
159 | const theme = ColorTheme.load(); | ||
160 | const res = new Map(); | ||
161 | TAG_TO_SCOPES.forEach((scopes, tag) => { | ||
162 | // We are going to axe this soon, so don't try to detect unknown tags. | ||
163 | // Users should switch to the new semantic tokens implementation. | ||
164 | if (!scopes) return; | ||
165 | const rule = theme.lookup(scopes); | ||
166 | const decor = createDecorationFromTextmate(rule); | ||
167 | res.set(tag, decor); | ||
168 | }); | ||
169 | return res; | ||
170 | } | ||
171 | |||
172 | function createDecorationFromTextmate( | ||
173 | themeStyle: TextMateRuleSettings, | ||
174 | ): vscode.TextEditorDecorationType { | ||
175 | const decorationOptions: vscode.DecorationRenderOptions = {}; | ||
176 | decorationOptions.rangeBehavior = vscode.DecorationRangeBehavior.OpenOpen; | ||
177 | |||
178 | if (themeStyle.foreground) { | ||
179 | decorationOptions.color = themeStyle.foreground; | ||
180 | } | ||
181 | |||
182 | if (themeStyle.background) { | ||
183 | decorationOptions.backgroundColor = themeStyle.background; | ||
184 | } | ||
185 | |||
186 | if (themeStyle.fontStyle) { | ||
187 | const parts: string[] = themeStyle.fontStyle.split(' '); | ||
188 | parts.forEach(part => { | ||
189 | switch (part) { | ||
190 | case 'italic': | ||
191 | decorationOptions.fontStyle = 'italic'; | ||
192 | break; | ||
193 | case 'bold': | ||
194 | decorationOptions.fontWeight = 'bold'; | ||
195 | break; | ||
196 | case 'underline': | ||
197 | decorationOptions.textDecoration = 'underline'; | ||
198 | break; | ||
199 | default: | ||
200 | break; | ||
201 | } | ||
202 | }); | ||
203 | } | ||
204 | return vscode.window.createTextEditorDecorationType(decorationOptions); | ||
205 | } | ||
206 | |||
207 | // sync with tags from `syntax_highlighting.rs`. | ||
208 | const TAG_TO_SCOPES = new Map<string, string[]>([ | ||
209 | ["field", ["entity.name.field"]], | ||
210 | ["function", ["entity.name.function"]], | ||
211 | ["module", ["entity.name.module"]], | ||
212 | ["constant", ["entity.name.constant"]], | ||
213 | ["macro", ["entity.name.macro"]], | ||
214 | |||
215 | ["variable", ["variable"]], | ||
216 | ["variable.mutable", ["variable", "meta.mutable"]], | ||
217 | |||
218 | ["type", ["entity.name.type"]], | ||
219 | ["type.builtin", ["entity.name.type", "support.type.primitive"]], | ||
220 | ["type.self", ["entity.name.type.parameter.self"]], | ||
221 | ["type.param", ["entity.name.type.parameter", "entity.name.type.param.rust"]], | ||
222 | ["type.lifetime", ["entity.name.type.lifetime", "entity.name.lifetime.rust"]], | ||
223 | |||
224 | ["literal.byte", ["constant.character.byte"]], | ||
225 | ["literal.char", ["constant.character.rust"]], | ||
226 | ["numeric_literal", ["constant.numeric"]], | ||
227 | |||
228 | ["comment", ["comment"]], | ||
229 | ["string_literal", ["string.quoted"]], | ||
230 | ["attribute", ["meta.attribute.rust"]], | ||
231 | |||
232 | ["keyword", ["keyword"]], | ||
233 | ["keyword.unsafe", ["keyword.other.unsafe"]], | ||
234 | ["keyword.control", ["keyword.control"]], | ||
235 | ]); | ||
236 | |||
237 | function randomU32Numbers(seed: number) { | ||
238 | let random = seed | 0; | ||
239 | return () => { | ||
240 | random ^= random << 13; | ||
241 | random ^= random >> 17; | ||
242 | random ^= random << 5; | ||
243 | random |= 0; | ||
244 | return random; | ||
245 | }; | ||
246 | } | ||
247 | |||
248 | function hashString(str: string): number { | ||
249 | let res = 0; | ||
250 | for (let i = 0; i < str.length; ++i) { | ||
251 | const c = str.codePointAt(i)!; | ||
252 | res = (res * 31 + c) & ~0; | ||
253 | } | ||
254 | return res; | ||
255 | } | ||
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 7ba16120c..4f3b89f44 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts | |||
@@ -7,7 +7,6 @@ import * as commands from './commands'; | |||
7 | import { activateInlayHints } from './inlay_hints'; | 7 | import { activateInlayHints } from './inlay_hints'; |
8 | import { activateStatusDisplay } from './status_display'; | 8 | import { activateStatusDisplay } from './status_display'; |
9 | import { Ctx } from './ctx'; | 9 | import { Ctx } from './ctx'; |
10 | import { activateHighlighting } from './highlighting'; | ||
11 | import { Config, NIGHTLY_TAG } from './config'; | 10 | import { Config, NIGHTLY_TAG } from './config'; |
12 | import { log, assert } from './util'; | 11 | import { log, assert } from './util'; |
13 | import { PersistentState } from './persistent_state'; | 12 | import { PersistentState } from './persistent_state'; |
@@ -97,9 +96,6 @@ export async function activate(context: vscode.ExtensionContext) { | |||
97 | 96 | ||
98 | activateStatusDisplay(ctx); | 97 | activateStatusDisplay(ctx); |
99 | 98 | ||
100 | if (!ctx.config.highlightingSemanticTokens) { | ||
101 | activateHighlighting(ctx); | ||
102 | } | ||
103 | activateInlayHints(ctx); | 99 | activateInlayHints(ctx); |
104 | 100 | ||
105 | vscode.workspace.onDidChangeConfiguration( | 101 | vscode.workspace.onDidChangeConfiguration( |
diff --git a/editors/code/src/status_display.ts b/editors/code/src/status_display.ts index 0f5f6ef99..f9cadc8a2 100644 --- a/editors/code/src/status_display.ts +++ b/editors/code/src/status_display.ts | |||
@@ -7,7 +7,7 @@ import { Ctx } from './ctx'; | |||
7 | const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; | 7 | const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; |
8 | 8 | ||
9 | export function activateStatusDisplay(ctx: Ctx) { | 9 | export function activateStatusDisplay(ctx: Ctx) { |
10 | const statusDisplay = new StatusDisplay(ctx.config.cargoWatchOptions.command); | 10 | const statusDisplay = new StatusDisplay(ctx.config.checkOnSave.command); |
11 | ctx.pushCleanup(statusDisplay); | 11 | ctx.pushCleanup(statusDisplay); |
12 | const client = ctx.client; | 12 | const client = ctx.client; |
13 | if (client != null) { | 13 | if (client != null) { |