aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/release.yaml2
-rw-r--r--.vscode/launch.json26
-rw-r--r--crates/ra_cli/src/analysis_bench.rs54
-rw-r--r--crates/ra_cli/src/main.rs34
-rw-r--r--crates/ra_hir_ty/src/infer/unify.rs2
-rw-r--r--crates/ra_hir_ty/src/tests/coercion.rs22
-rw-r--r--crates/ra_ide/src/snapshots/highlighting.html1
-rw-r--r--crates/ra_ide/src/snapshots/rainbow_highlighting.html1
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs1
-rw-r--r--crates/ra_lsp_server/src/main.rs7
-rw-r--r--crates/ra_prof/src/lib.rs7
-rw-r--r--docs/user/readme.adoc7
-rw-r--r--editors/code/package.json14
-rw-r--r--editors/code/src/client.ts2
-rw-r--r--editors/code/src/config.ts19
-rw-r--r--editors/code/src/ctx.ts4
-rw-r--r--editors/code/src/inlay_hints.ts2
-rw-r--r--editors/code/src/installation/download_artifact.ts58
-rw-r--r--editors/code/src/installation/fetch_artifact_release_info.ts (renamed from editors/code/src/installation/fetch_latest_artifact_release_info.ts)16
-rw-r--r--editors/code/src/installation/interfaces.ts13
-rw-r--r--editors/code/src/installation/server.ts168
21 files changed, 303 insertions, 157 deletions
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index ff7a95ee1..eae4fbcb5 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -190,4 +190,4 @@ jobs:
190 - name: Publish Extension 190 - name: Publish Extension
191 working-directory: ./editors/code 191 working-directory: ./editors/code
192 # token from https://dev.azure.com/rust-analyzer/ 192 # token from https://dev.azure.com/rust-analyzer/
193 run: ./node_modules/vsce/out/vsce publish 0.1.$(date +%Y%m%d) --pat ${{ secrets.MARKETPLACE_TOKEN }} 193 run: npx vsce publish 0.1.$(date +%Y%m%d) --pat ${{ secrets.MARKETPLACE_TOKEN }}
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 55a2f10f2..b1bd98d4a 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -2,39 +2,61 @@
2 // Use IntelliSense to learn about possible attributes. 2 // Use IntelliSense to learn about possible attributes.
3 // Hover to view descriptions of existing attributes. 3 // Hover to view descriptions of existing attributes.
4 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 4 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
6 // NOTE: --disable-extensions
7 // Disable all installed extensions to increase performance of the debug instance
8 // and prevent potential conflicts with other installed extensions.
9
5 "version": "0.2.0", 10 "version": "0.2.0",
6 "configurations": [ 11 "configurations": [
7 { 12 {
13 // Used for testing the extension with the installed LSP server.
8 "name": "Run Extension", 14 "name": "Run Extension",
9 "type": "extensionHost", 15 "type": "extensionHost",
10 "request": "launch", 16 "request": "launch",
11 "runtimeExecutable": "${execPath}", 17 "runtimeExecutable": "${execPath}",
12 "args": [ 18 "args": [
19 "--disable-extensions",
13 "--extensionDevelopmentPath=${workspaceFolder}/editors/code" 20 "--extensionDevelopmentPath=${workspaceFolder}/editors/code"
14 ], 21 ],
15 "outFiles": [ 22 "outFiles": [
16 "${workspaceFolder}/editors/code/out/**/*.js" 23 "${workspaceFolder}/editors/code/out/**/*.js"
17 ], 24 ],
18 "preLaunchTask": "Build Extension" 25 "preLaunchTask": "Build Extension",
26 "skipFiles": [
27 "<node_internals>/**/*.js"
28 ]
19 }, 29 },
20 { 30 {
31 // Used for testing the extension with a local build of the LSP server (in `target/debug`).
21 "name": "Run Extension (Dev Server)", 32 "name": "Run Extension (Dev Server)",
22 "type": "extensionHost", 33 "type": "extensionHost",
23 "request": "launch", 34 "request": "launch",
24 "runtimeExecutable": "${execPath}", 35 "runtimeExecutable": "${execPath}",
25 "args": [ 36 "args": [
37 "--disable-extensions",
26 "--extensionDevelopmentPath=${workspaceFolder}/editors/code" 38 "--extensionDevelopmentPath=${workspaceFolder}/editors/code"
27 ], 39 ],
28 "outFiles": [ 40 "outFiles": [
29 "${workspaceFolder}/editors/code/out/**/*.js" 41 "${workspaceFolder}/editors/code/out/**/*.js"
30 ], 42 ],
31 "preLaunchTask": "Build Extension", 43 "preLaunchTask": "Build Extension",
44 "skipFiles": [
45 "<node_internals>/**/*.js"
46 ],
32 "env": { 47 "env": {
33 "__RA_LSP_SERVER_DEBUG": "${workspaceFolder}/target/debug/ra_lsp_server" 48 "__RA_LSP_SERVER_DEBUG": "${workspaceFolder}/target/debug/ra_lsp_server"
34 } 49 }
35 }, 50 },
36 { 51 {
37 "name": "Debug Lsp Server", 52 // Used to attach LLDB to a running LSP server.
53 // NOTE: Might require root permissions. For this run:
54 //
55 // `echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope`
56 //
57 // Don't forget to set `debug = 2` in `Cargo.toml` before building the server
58
59 "name": "Attach To Server",
38 "type": "lldb", 60 "type": "lldb",
39 "request": "attach", 61 "request": "attach",
40 "program": "${workspaceFolder}/target/debug/ra_lsp_server", 62 "program": "${workspaceFolder}/target/debug/ra_lsp_server",
diff --git a/crates/ra_cli/src/analysis_bench.rs b/crates/ra_cli/src/analysis_bench.rs
index 5485a38ff..4835a68ce 100644
--- a/crates/ra_cli/src/analysis_bench.rs
+++ b/crates/ra_cli/src/analysis_bench.rs
@@ -2,6 +2,7 @@
2 2
3use std::{ 3use std::{
4 path::{Path, PathBuf}, 4 path::{Path, PathBuf},
5 str::FromStr,
5 sync::Arc, 6 sync::Arc,
6 time::Instant, 7 time::Instant,
7}; 8};
@@ -14,12 +15,35 @@ use ra_ide::{Analysis, AnalysisChange, AnalysisHost, FilePosition, LineCol};
14 15
15use crate::Result; 16use crate::Result;
16 17
18pub(crate) struct Position {
19 path: PathBuf,
20 line: u32,
21 column: u32,
22}
23
24impl FromStr for Position {
25 type Err = Box<dyn std::error::Error + Send + Sync>;
26 fn from_str(s: &str) -> Result<Self> {
27 let (path_line, column) = rsplit_at_char(s, ':')?;
28 let (path, line) = rsplit_at_char(path_line, ':')?;
29 Ok(Position { path: path.into(), line: line.parse()?, column: column.parse()? })
30 }
31}
32
33fn rsplit_at_char(s: &str, c: char) -> Result<(&str, &str)> {
34 let idx = s.rfind(':').ok_or_else(|| format!("no `{}` in {}", c, s))?;
35 Ok((&s[..idx], &s[idx + 1..]))
36}
37
17pub(crate) enum Op { 38pub(crate) enum Op {
18 Highlight { path: PathBuf }, 39 Highlight { path: PathBuf },
19 Complete { path: PathBuf, line: u32, column: u32 }, 40 Complete(Position),
41 GotoDef(Position),
20} 42}
21 43
22pub(crate) fn run(verbose: bool, path: &Path, op: Op) -> Result<()> { 44pub(crate) fn run(verbose: bool, path: &Path, op: Op) -> Result<()> {
45 ra_prof::init();
46
23 let start = Instant::now(); 47 let start = Instant::now();
24 eprint!("loading: "); 48 eprint!("loading: ");
25 let (mut host, roots) = ra_batch::load_cargo(path)?; 49 let (mut host, roots) = ra_batch::load_cargo(path)?;
@@ -29,7 +53,7 @@ pub(crate) fn run(verbose: bool, path: &Path, op: Op) -> Result<()> {
29 let file_id = { 53 let file_id = {
30 let path = match &op { 54 let path = match &op {
31 Op::Highlight { path } => path, 55 Op::Highlight { path } => path,
32 Op::Complete { path, .. } => path, 56 Op::Complete(pos) | Op::GotoDef(pos) => &pos.path,
33 }; 57 };
34 let path = std::env::current_dir()?.join(path).canonicalize()?; 58 let path = std::env::current_dir()?.join(path).canonicalize()?;
35 roots 59 roots
@@ -49,7 +73,7 @@ pub(crate) fn run(verbose: bool, path: &Path, op: Op) -> Result<()> {
49 .ok_or_else(|| format!("Can't find {:?}", path))? 73 .ok_or_else(|| format!("Can't find {:?}", path))?
50 }; 74 };
51 75
52 match op { 76 match &op {
53 Op::Highlight { .. } => { 77 Op::Highlight { .. } => {
54 let res = do_work(&mut host, file_id, |analysis| { 78 let res = do_work(&mut host, file_id, |analysis| {
55 analysis.diagnostics(file_id).unwrap(); 79 analysis.diagnostics(file_id).unwrap();
@@ -59,16 +83,30 @@ pub(crate) fn run(verbose: bool, path: &Path, op: Op) -> Result<()> {
59 println!("\n{}", res); 83 println!("\n{}", res);
60 } 84 }
61 } 85 }
62 Op::Complete { line, column, .. } => { 86 Op::Complete(pos) | Op::GotoDef(pos) => {
87 let is_completion = match op {
88 Op::Complete(..) => true,
89 _ => false,
90 };
91
63 let offset = host 92 let offset = host
64 .analysis() 93 .analysis()
65 .file_line_index(file_id)? 94 .file_line_index(file_id)?
66 .offset(LineCol { line, col_utf16: column }); 95 .offset(LineCol { line: pos.line - 1, col_utf16: pos.column });
67 let file_postion = FilePosition { file_id, offset }; 96 let file_postion = FilePosition { file_id, offset };
68 97
69 let res = do_work(&mut host, file_id, |analysis| analysis.completions(file_postion)); 98 if is_completion {
70 if verbose { 99 let res =
71 println!("\n{:#?}", res); 100 do_work(&mut host, file_id, |analysis| analysis.completions(file_postion));
101 if verbose {
102 println!("\n{:#?}", res);
103 }
104 } else {
105 let res =
106 do_work(&mut host, file_id, |analysis| analysis.goto_definition(file_postion));
107 if verbose {
108 println!("\n{:#?}", res);
109 }
72 } 110 }
73 } 111 }
74 } 112 }
diff --git a/crates/ra_cli/src/main.rs b/crates/ra_cli/src/main.rs
index 6a0e447b9..750cbab86 100644
--- a/crates/ra_cli/src/main.rs
+++ b/crates/ra_cli/src/main.rs
@@ -132,25 +132,16 @@ fn main() -> Result<()> {
132 } 132 }
133 let verbose = matches.contains(["-v", "--verbose"]); 133 let verbose = matches.contains(["-v", "--verbose"]);
134 let path: String = matches.opt_value_from_str("--path")?.unwrap_or_default(); 134 let path: String = matches.opt_value_from_str("--path")?.unwrap_or_default();
135 let highlight_path = matches.opt_value_from_str("--highlight")?; 135 let highlight_path: Option<String> = matches.opt_value_from_str("--highlight")?;
136 let complete_path = matches.opt_value_from_str("--complete")?; 136 let complete_path: Option<String> = matches.opt_value_from_str("--complete")?;
137 if highlight_path.is_some() && complete_path.is_some() { 137 let goto_def_path: Option<String> = matches.opt_value_from_str("--goto-def")?;
138 panic!("either --highlight or --complete must be set, not both") 138 let op = match (highlight_path, complete_path, goto_def_path) {
139 } 139 (Some(path), None, None) => analysis_bench::Op::Highlight { path: path.into() },
140 let op = if let Some(path) = highlight_path { 140 (None, Some(position), None) => analysis_bench::Op::Complete(position.parse()?),
141 let path: String = path; 141 (None, None, Some(position)) => analysis_bench::Op::GotoDef(position.parse()?),
142 analysis_bench::Op::Highlight { path: path.into() } 142 _ => panic!(
143 } else if let Some(path_line_col) = complete_path { 143 "exactly one of `--highlight`, `--complete` or `--goto-def` must be set"
144 let path_line_col: String = path_line_col; 144 ),
145 let (path_line, column) = rsplit_at_char(path_line_col.as_str(), ':')?;
146 let (path, line) = rsplit_at_char(path_line, ':')?;
147 analysis_bench::Op::Complete {
148 path: path.into(),
149 line: line.parse()?,
150 column: column.parse()?,
151 }
152 } else {
153 panic!("either --highlight or --complete must be set")
154 }; 145 };
155 matches.finish().or_else(handle_extra_flags)?; 146 matches.finish().or_else(handle_extra_flags)?;
156 analysis_bench::run(verbose, path.as_ref(), op)?; 147 analysis_bench::run(verbose, path.as_ref(), op)?;
@@ -183,8 +174,3 @@ fn read_stdin() -> Result<String> {
183 std::io::stdin().read_to_string(&mut buff)?; 174 std::io::stdin().read_to_string(&mut buff)?;
184 Ok(buff) 175 Ok(buff)
185} 176}
186
187fn rsplit_at_char(s: &str, c: char) -> Result<(&str, &str)> {
188 let idx = s.rfind(':').ok_or_else(|| format!("no `{}` in {}", c, s))?;
189 Ok((&s[..idx], &s[idx + 1..]))
190}
diff --git a/crates/ra_hir_ty/src/infer/unify.rs b/crates/ra_hir_ty/src/infer/unify.rs
index fe05642ae..1dc842f40 100644
--- a/crates/ra_hir_ty/src/infer/unify.rs
+++ b/crates/ra_hir_ty/src/infer/unify.rs
@@ -249,6 +249,8 @@ impl InferenceTable {
249 match (ty1, ty2) { 249 match (ty1, ty2) {
250 (Ty::Unknown, _) | (_, Ty::Unknown) => true, 250 (Ty::Unknown, _) | (_, Ty::Unknown) => true,
251 251
252 (Ty::Placeholder(p1), Ty::Placeholder(p2)) if *p1 == *p2 => true,
253
252 (Ty::Infer(InferTy::TypeVar(tv1)), Ty::Infer(InferTy::TypeVar(tv2))) 254 (Ty::Infer(InferTy::TypeVar(tv1)), Ty::Infer(InferTy::TypeVar(tv2)))
253 | (Ty::Infer(InferTy::IntVar(tv1)), Ty::Infer(InferTy::IntVar(tv2))) 255 | (Ty::Infer(InferTy::IntVar(tv1)), Ty::Infer(InferTy::IntVar(tv2)))
254 | (Ty::Infer(InferTy::FloatVar(tv1)), Ty::Infer(InferTy::FloatVar(tv2))) 256 | (Ty::Infer(InferTy::FloatVar(tv1)), Ty::Infer(InferTy::FloatVar(tv2)))
diff --git a/crates/ra_hir_ty/src/tests/coercion.rs b/crates/ra_hir_ty/src/tests/coercion.rs
index fc5ef36a5..42330b269 100644
--- a/crates/ra_hir_ty/src/tests/coercion.rs
+++ b/crates/ra_hir_ty/src/tests/coercion.rs
@@ -526,3 +526,25 @@ fn test() {
526 "### 526 "###
527 ); 527 );
528} 528}
529
530#[test]
531fn coerce_placeholder_ref() {
532 // placeholders should unify, even behind references
533 assert_snapshot!(
534 infer_with_mismatches(r#"
535struct S<T> { t: T }
536impl<TT> S<TT> {
537 fn get(&self) -> &TT {
538 &self.t
539 }
540}
541"#, true),
542 @r###"
543 [51; 55) 'self': &S<TT>
544 [64; 87) '{ ... }': &TT
545 [74; 81) '&self.t': &TT
546 [75; 79) 'self': &S<TT>
547 [75; 81) 'self.t': TT
548 "###
549 );
550}
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html
index 1cc55e78b..a02dbaf2f 100644
--- a/crates/ra_ide/src/snapshots/highlighting.html
+++ b/crates/ra_ide/src/snapshots/highlighting.html
@@ -16,6 +16,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
16.literal { color: #BFEBBF; } 16.literal { color: #BFEBBF; }
17.literal\.numeric { color: #6A8759; } 17.literal\.numeric { color: #6A8759; }
18.macro { color: #94BFF3; } 18.macro { color: #94BFF3; }
19.module { color: #AFD8AF; }
19.variable { color: #DCDCCC; } 20.variable { color: #DCDCCC; }
20.variable\.mut { color: #DCDCCC; text-decoration: underline; } 21.variable\.mut { color: #DCDCCC; text-decoration: underline; }
21 22
diff --git a/crates/ra_ide/src/snapshots/rainbow_highlighting.html b/crates/ra_ide/src/snapshots/rainbow_highlighting.html
index 918fd4b97..95f038f00 100644
--- a/crates/ra_ide/src/snapshots/rainbow_highlighting.html
+++ b/crates/ra_ide/src/snapshots/rainbow_highlighting.html
@@ -16,6 +16,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
16.literal { color: #BFEBBF; } 16.literal { color: #BFEBBF; }
17.literal\.numeric { color: #6A8759; } 17.literal\.numeric { color: #6A8759; }
18.macro { color: #94BFF3; } 18.macro { color: #94BFF3; }
19.module { color: #AFD8AF; }
19.variable { color: #DCDCCC; } 20.variable { color: #DCDCCC; }
20.variable\.mut { color: #DCDCCC; text-decoration: underline; } 21.variable\.mut { color: #DCDCCC; text-decoration: underline; }
21 22
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index 174e13595..20c414ca1 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -365,6 +365,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
365.literal { color: #BFEBBF; } 365.literal { color: #BFEBBF; }
366.literal\\.numeric { color: #6A8759; } 366.literal\\.numeric { color: #6A8759; }
367.macro { color: #94BFF3; } 367.macro { color: #94BFF3; }
368.module { color: #AFD8AF; }
368.variable { color: #DCDCCC; } 369.variable { color: #DCDCCC; }
369.variable\\.mut { color: #DCDCCC; text-decoration: underline; } 370.variable\\.mut { color: #DCDCCC; text-decoration: underline; }
370 371
diff --git a/crates/ra_lsp_server/src/main.rs b/crates/ra_lsp_server/src/main.rs
index c8a017c5c..ed2eaabd4 100644
--- a/crates/ra_lsp_server/src/main.rs
+++ b/crates/ra_lsp_server/src/main.rs
@@ -15,13 +15,8 @@ fn main() -> Result<()> {
15 15
16fn setup_logging() -> Result<()> { 16fn setup_logging() -> Result<()> {
17 std::env::set_var("RUST_BACKTRACE", "short"); 17 std::env::set_var("RUST_BACKTRACE", "short");
18
19 env_logger::try_init()?; 18 env_logger::try_init()?;
20 19 ra_prof::init();
21 ra_prof::set_filter(match std::env::var("RA_PROFILE") {
22 Ok(spec) => ra_prof::Filter::from_spec(&spec),
23 Err(_) => ra_prof::Filter::disabled(),
24 });
25 Ok(()) 20 Ok(())
26} 21}
27 22
diff --git a/crates/ra_prof/src/lib.rs b/crates/ra_prof/src/lib.rs
index d38ff397e..c0bfbc2ee 100644
--- a/crates/ra_prof/src/lib.rs
+++ b/crates/ra_prof/src/lib.rs
@@ -26,6 +26,13 @@ pub use crate::memory_usage::{Bytes, MemoryUsage};
26#[global_allocator] 26#[global_allocator]
27static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc; 27static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
28 28
29pub fn init() {
30 set_filter(match std::env::var("RA_PROFILE") {
31 Ok(spec) => Filter::from_spec(&spec),
32 Err(_) => Filter::disabled(),
33 });
34}
35
29/// Set profiling filter. It specifies descriptions allowed to profile. 36/// Set profiling filter. It specifies descriptions allowed to profile.
30/// This is helpful when call stack has too many nested profiling scopes. 37/// This is helpful when call stack has too many nested profiling scopes.
31/// Additionally filter can specify maximum depth of profiling scopes nesting. 38/// Additionally filter can specify maximum depth of profiling scopes nesting.
diff --git a/docs/user/readme.adoc b/docs/user/readme.adoc
index 867aae975..553687e78 100644
--- a/docs/user/readme.adoc
+++ b/docs/user/readme.adoc
@@ -27,8 +27,9 @@ https://github.com/rust-analyzer/rust-analyzer/tree/master/editors/code[in tree]
27 27
28You can install the latest release of the plugin from 28You can install the latest release of the plugin from
29https://marketplace.visualstudio.com/items?itemName=matklad.rust-analyzer[the marketplace]. 29https://marketplace.visualstudio.com/items?itemName=matklad.rust-analyzer[the marketplace].
30By default, the plugin will download the latest version of the server as well. 30By default, the plugin will download the matching version of the server as well.
31 31
32// FIXME: update the image (its text has changed)
32image::https://user-images.githubusercontent.com/36276403/74103174-a40df100-4b52-11ea-81f4-372c70797924.png[] 33image::https://user-images.githubusercontent.com/36276403/74103174-a40df100-4b52-11ea-81f4-372c70797924.png[]
33 34
34The server binary is stored in `~/.config/Code/User/globalStorage/matklad.rust-analyzer`. 35The server binary is stored in `~/.config/Code/User/globalStorage/matklad.rust-analyzer`.
@@ -37,9 +38,7 @@ Note that we only support the latest version of VS Code.
37 38
38==== Updates 39==== Updates
39 40
40The extension will be updated automatically as new versions become available. 41The extension will be updated automatically as new versions become available. It will ask your permission to download the matching language server version binary if needed.
41The server update functionality is in progress.
42For the time being, the workaround is to remove the binary from `globalStorage` and to restart the extension.
43 42
44==== Building From Source 43==== Building From Source
45 44
diff --git a/editors/code/package.json b/editors/code/package.json
index a607c2148..ed1cae2ab 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -6,7 +6,7 @@
6 "private": true, 6 "private": true,
7 "icon": "icon.png", 7 "icon": "icon.png",
8 "//": "The real version is in release.yaml, this one just needs to be bigger", 8 "//": "The real version is in release.yaml, this one just needs to be bigger",
9 "version": "0.2.0-dev", 9 "version": "0.2.20200211-dev",
10 "publisher": "matklad", 10 "publisher": "matklad",
11 "repository": { 11 "repository": {
12 "url": "https://github.com/rust-analyzer/rust-analyzer.git", 12 "url": "https://github.com/rust-analyzer/rust-analyzer.git",
@@ -233,11 +233,10 @@
233 "description": "Trace requests to the ra_lsp_server" 233 "description": "Trace requests to the ra_lsp_server"
234 }, 234 },
235 "rust-analyzer.lruCapacity": { 235 "rust-analyzer.lruCapacity": {
236 "type": [ 236 "type": [ "null", "integer" ],
237 "number",
238 "null"
239 ],
240 "default": null, 237 "default": null,
238 "minimum": 0,
239 "exclusiveMinimum": true,
241 "description": "Number of syntax trees rust-analyzer keeps in memory" 240 "description": "Number of syntax trees rust-analyzer keeps in memory"
242 }, 241 },
243 "rust-analyzer.displayInlayHints": { 242 "rust-analyzer.displayInlayHints": {
@@ -246,9 +245,10 @@
246 "description": "Display additional type and parameter information in the editor" 245 "description": "Display additional type and parameter information in the editor"
247 }, 246 },
248 "rust-analyzer.maxInlayHintLength": { 247 "rust-analyzer.maxInlayHintLength": {
249 "type": "number", 248 "type": [ "null", "integer" ],
250 "default": 20, 249 "default": 20,
251 "exclusiveMinimum": 0, 250 "minimum": 0,
251 "exclusiveMinimum": true,
252 "description": "Maximum length for inlay hints" 252 "description": "Maximum length for inlay hints"
253 }, 253 },
254 "rust-analyzer.cargoFeatures.noDefaultFeatures": { 254 "rust-analyzer.cargoFeatures.noDefaultFeatures": {
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index 12c97be2f..efef820ab 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -11,7 +11,7 @@ export async function createClient(config: Config): Promise<null | lc.LanguageCl
11 // It might be a good idea to test if the uri points to a file. 11 // It might be a good idea to test if the uri points to a file.
12 const workspaceFolderPath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? '.'; 12 const workspaceFolderPath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? '.';
13 13
14 const serverPath = await ensureServerBinary(config.serverBinarySource); 14 const serverPath = await ensureServerBinary(config.serverSource);
15 if (!serverPath) return null; 15 if (!serverPath) return null;
16 16
17 const run: lc.Executable = { 17 const run: lc.Executable = {
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index 7866ed7e1..70cb0a612 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -24,6 +24,19 @@ export class Config {
24 ] 24 ]
25 .map(opt => `${Config.rootSection}.${opt}`); 25 .map(opt => `${Config.rootSection}.${opt}`);
26 26
27 private static readonly extensionVersion: string = (() => {
28 const packageJsonVersion = vscode
29 .extensions
30 .getExtension("matklad.rust-analyzer")!
31 .packageJSON
32 .version as string; // n.n.YYYYMMDD
33
34 const realVersionRegexp = /^\d+\.\d+\.(\d{4})(\d{2})(\d{2})/;
35 const [, yyyy, mm, dd] = packageJsonVersion.match(realVersionRegexp)!;
36
37 return `${yyyy}-${mm}-${dd}`;
38 })();
39
27 private cfg!: vscode.WorkspaceConfiguration; 40 private cfg!: vscode.WorkspaceConfiguration;
28 41
29 constructor(private readonly ctx: vscode.ExtensionContext) { 42 constructor(private readonly ctx: vscode.ExtensionContext) {
@@ -98,7 +111,7 @@ export class Config {
98 } 111 }
99 } 112 }
100 113
101 get serverBinarySource(): null | BinarySource { 114 get serverSource(): null | BinarySource {
102 const serverPath = RA_LSP_DEBUG ?? this.cfg.get<null | string>("raLspServerPath"); 115 const serverPath = RA_LSP_DEBUG ?? this.cfg.get<null | string>("raLspServerPath");
103 116
104 if (serverPath) { 117 if (serverPath) {
@@ -116,6 +129,8 @@ export class Config {
116 type: BinarySource.Type.GithubRelease, 129 type: BinarySource.Type.GithubRelease,
117 dir: this.ctx.globalStoragePath, 130 dir: this.ctx.globalStoragePath,
118 file: prebuiltBinaryName, 131 file: prebuiltBinaryName,
132 storage: this.ctx.globalState,
133 version: Config.extensionVersion,
119 repo: { 134 repo: {
120 name: "rust-analyzer", 135 name: "rust-analyzer",
121 owner: "rust-analyzer", 136 owner: "rust-analyzer",
@@ -153,5 +168,5 @@ export class Config {
153 } 168 }
154 169
155 // for internal use 170 // for internal use
156 get withSysroot() { return this.cfg.get("withSysroot", false); } 171 get withSysroot() { return this.cfg.get("withSysroot", true) as boolean; }
157} 172}
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index 70042a479..9fcf2ec38 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -60,6 +60,10 @@ export class Ctx {
60 this.pushCleanup(d); 60 this.pushCleanup(d);
61 } 61 }
62 62
63 get globalState(): vscode.Memento {
64 return this.extCtx.globalState;
65 }
66
63 get subscriptions(): Disposable[] { 67 get subscriptions(): Disposable[] {
64 return this.extCtx.subscriptions; 68 return this.extCtx.subscriptions;
65 } 69 }
diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts
index 1c019a51b..c317a9213 100644
--- a/editors/code/src/inlay_hints.ts
+++ b/editors/code/src/inlay_hints.ts
@@ -13,7 +13,7 @@ export function activateInlayHints(ctx: Ctx) {
13 13
14 vscode.workspace.onDidChangeTextDocument( 14 vscode.workspace.onDidChangeTextDocument(
15 async event => { 15 async event => {
16 if (event.contentChanges.length !== 0) return; 16 if (event.contentChanges.length === 0) return;
17 if (event.document.languageId !== 'rust') return; 17 if (event.document.languageId !== 'rust') return;
18 await hintsUpdater.refresh(); 18 await hintsUpdater.refresh();
19 }, 19 },
diff --git a/editors/code/src/installation/download_artifact.ts b/editors/code/src/installation/download_artifact.ts
new file mode 100644
index 000000000..de655f8f4
--- /dev/null
+++ b/editors/code/src/installation/download_artifact.ts
@@ -0,0 +1,58 @@
1import * as vscode from "vscode";
2import * as path from "path";
3import { promises as fs } from "fs";
4import { strict as assert } from "assert";
5
6import { ArtifactReleaseInfo } from "./interfaces";
7import { downloadFile } from "./download_file";
8import { throttle } from "throttle-debounce";
9
10/**
11 * Downloads artifact from given `downloadUrl`.
12 * Creates `installationDir` if it is not yet created and put the artifact under
13 * `artifactFileName`.
14 * Displays info about the download progress in an info message printing the name
15 * of the artifact as `displayName`.
16 */
17export async function downloadArtifact(
18 {downloadUrl, releaseName}: ArtifactReleaseInfo,
19 artifactFileName: string,
20 installationDir: string,
21 displayName: string,
22) {
23 await fs.mkdir(installationDir).catch(err => assert.strictEqual(
24 err?.code,
25 "EEXIST",
26 `Couldn't create directory "${installationDir}" to download `+
27 `${artifactFileName} artifact: ${err.message}`
28 ));
29
30 const installationPath = path.join(installationDir, artifactFileName);
31
32 console.time(`Downloading ${artifactFileName}`);
33 await vscode.window.withProgress(
34 {
35 location: vscode.ProgressLocation.Notification,
36 cancellable: false, // FIXME: add support for canceling download?
37 title: `Downloading ${displayName} (${releaseName})`
38 },
39 async (progress, _cancellationToken) => {
40 let lastPrecentage = 0;
41 const filePermissions = 0o755; // (rwx, r_x, r_x)
42 await downloadFile(downloadUrl, installationPath, filePermissions, throttle(
43 200,
44 /* noTrailing: */ true,
45 (readBytes, totalBytes) => {
46 const newPercentage = (readBytes / totalBytes) * 100;
47 progress.report({
48 message: newPercentage.toFixed(0) + "%",
49 increment: newPercentage - lastPrecentage
50 });
51
52 lastPrecentage = newPercentage;
53 })
54 );
55 }
56 );
57 console.timeEnd(`Downloading ${artifactFileName}`);
58}
diff --git a/editors/code/src/installation/fetch_latest_artifact_release_info.ts b/editors/code/src/installation/fetch_artifact_release_info.ts
index 29ee029a7..7d497057a 100644
--- a/editors/code/src/installation/fetch_latest_artifact_release_info.ts
+++ b/editors/code/src/installation/fetch_artifact_release_info.ts
@@ -3,24 +3,30 @@ import { GithubRepo, ArtifactReleaseInfo } from "./interfaces";
3 3
4const GITHUB_API_ENDPOINT_URL = "https://api.github.com"; 4const GITHUB_API_ENDPOINT_URL = "https://api.github.com";
5 5
6
6/** 7/**
7 * Fetches the latest release from GitHub `repo` and returns metadata about 8 * Fetches the release with `releaseTag` (or just latest release when not specified)
8 * `artifactFileName` shipped with this release or `null` if no such artifact was published. 9 * from GitHub `repo` and returns metadata about `artifactFileName` shipped with
10 * this release or `null` if no such artifact was published.
9 */ 11 */
10export async function fetchLatestArtifactReleaseInfo( 12export async function fetchArtifactReleaseInfo(
11 repo: GithubRepo, artifactFileName: string 13 repo: GithubRepo, artifactFileName: string, releaseTag?: string
12): Promise<null | ArtifactReleaseInfo> { 14): Promise<null | ArtifactReleaseInfo> {
13 15
14 const repoOwner = encodeURIComponent(repo.owner); 16 const repoOwner = encodeURIComponent(repo.owner);
15 const repoName = encodeURIComponent(repo.name); 17 const repoName = encodeURIComponent(repo.name);
16 18
17 const apiEndpointPath = `/repos/${repoOwner}/${repoName}/releases/latest`; 19 const apiEndpointPath = releaseTag
20 ? `/repos/${repoOwner}/${repoName}/releases/tags/${releaseTag}`
21 : `/repos/${repoOwner}/${repoName}/releases/latest`;
22
18 const requestUrl = GITHUB_API_ENDPOINT_URL + apiEndpointPath; 23 const requestUrl = GITHUB_API_ENDPOINT_URL + apiEndpointPath;
19 24
20 // We skip runtime type checks for simplicity (here we cast from `any` to `GithubRelease`) 25 // We skip runtime type checks for simplicity (here we cast from `any` to `GithubRelease`)
21 26
22 console.log("Issuing request for released artifacts metadata to", requestUrl); 27 console.log("Issuing request for released artifacts metadata to", requestUrl);
23 28
29 // FIXME: handle non-ok response
24 const response: GithubRelease = await fetch(requestUrl, { 30 const response: GithubRelease = await fetch(requestUrl, {
25 headers: { Accept: "application/vnd.github.v3+json" } 31 headers: { Accept: "application/vnd.github.v3+json" }
26 }) 32 })
diff --git a/editors/code/src/installation/interfaces.ts b/editors/code/src/installation/interfaces.ts
index 93ea577d4..e40839e4b 100644
--- a/editors/code/src/installation/interfaces.ts
+++ b/editors/code/src/installation/interfaces.ts
@@ -1,3 +1,5 @@
1import * as vscode from "vscode";
2
1export interface GithubRepo { 3export interface GithubRepo {
2 name: string; 4 name: string;
3 owner: string; 5 owner: string;
@@ -50,6 +52,17 @@ export namespace BinarySource {
50 * and in local `.dir`. 52 * and in local `.dir`.
51 */ 53 */
52 file: string; 54 file: string;
55
56 /**
57 * Tag of github release that denotes a version required by this extension.
58 */
59 version: string;
60
61 /**
62 * Object that provides `get()/update()` operations to store metadata
63 * about the actual binary, e.g. its actual version.
64 */
65 storage: vscode.Memento;
53 } 66 }
54 67
55} 68}
diff --git a/editors/code/src/installation/server.ts b/editors/code/src/installation/server.ts
index 406e2299c..80cb719e3 100644
--- a/editors/code/src/installation/server.ts
+++ b/editors/code/src/installation/server.ts
@@ -1,63 +1,15 @@
1import * as vscode from "vscode"; 1import * as vscode from "vscode";
2import * as path from "path"; 2import * as path from "path";
3import { strict as assert } from "assert"; 3import { strict as assert } from "assert";
4import { promises as fs } from "fs";
5import { promises as dns } from "dns"; 4import { promises as dns } from "dns";
6import { spawnSync } from "child_process"; 5import { spawnSync } from "child_process";
7import { throttle } from "throttle-debounce";
8 6
9import { BinarySource } from "./interfaces"; 7import { BinarySource } from "./interfaces";
10import { fetchLatestArtifactReleaseInfo } from "./fetch_latest_artifact_release_info"; 8import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info";
11import { downloadFile } from "./download_file"; 9import { downloadArtifact } from "./download_artifact";
12
13export async function downloadLatestServer(
14 {file: artifactFileName, dir: installationDir, repo}: BinarySource.GithubRelease
15) {
16 const { releaseName, downloadUrl } = (await fetchLatestArtifactReleaseInfo(
17 repo, artifactFileName
18 ))!;
19
20 await fs.mkdir(installationDir).catch(err => assert.strictEqual(
21 err?.code,
22 "EEXIST",
23 `Couldn't create directory "${installationDir}" to download `+
24 `language server binary: ${err.message}`
25 ));
26
27 const installationPath = path.join(installationDir, artifactFileName);
28
29 console.time("Downloading ra_lsp_server");
30 await vscode.window.withProgress(
31 {
32 location: vscode.ProgressLocation.Notification,
33 cancellable: false, // FIXME: add support for canceling download?
34 title: `Downloading language server (${releaseName})`
35 },
36 async (progress, _cancellationToken) => {
37 let lastPrecentage = 0;
38 const filePermissions = 0o755; // (rwx, r_x, r_x)
39 await downloadFile(downloadUrl, installationPath, filePermissions, throttle(
40 200,
41 /* noTrailing: */ true,
42 (readBytes, totalBytes) => {
43 const newPercentage = (readBytes / totalBytes) * 100;
44 progress.report({
45 message: newPercentage.toFixed(0) + "%",
46 increment: newPercentage - lastPrecentage
47 });
48
49 lastPrecentage = newPercentage;
50 })
51 );
52 }
53 );
54 console.timeEnd("Downloading ra_lsp_server");
55}
56export async function ensureServerBinary(
57 serverSource: null | BinarySource
58): Promise<null | string> {
59 10
60 if (!serverSource) { 11export async function ensureServerBinary(source: null | BinarySource): Promise<null | string> {
12 if (!source) {
61 vscode.window.showErrorMessage( 13 vscode.window.showErrorMessage(
62 "Unfortunately we don't ship binaries for your platform yet. " + 14 "Unfortunately we don't ship binaries for your platform yet. " +
63 "You need to manually clone rust-analyzer repository and " + 15 "You need to manually clone rust-analyzer repository and " +
@@ -69,80 +21,104 @@ export async function ensureServerBinary(
69 return null; 21 return null;
70 } 22 }
71 23
72 switch (serverSource.type) { 24 switch (source.type) {
73 case BinarySource.Type.ExplicitPath: { 25 case BinarySource.Type.ExplicitPath: {
74 if (isBinaryAvailable(serverSource.path)) { 26 if (isBinaryAvailable(source.path)) {
75 return serverSource.path; 27 return source.path;
76 } 28 }
77 29
78 vscode.window.showErrorMessage( 30 vscode.window.showErrorMessage(
79 `Unable to run ${serverSource.path} binary. ` + 31 `Unable to run ${source.path} binary. ` +
80 `To use the pre-built language server, set "rust-analyzer.raLspServerPath" ` + 32 `To use the pre-built language server, set "rust-analyzer.raLspServerPath" ` +
81 "value to `null` or remove it from the settings to use it by default." 33 "value to `null` or remove it from the settings to use it by default."
82 ); 34 );
83 return null; 35 return null;
84 } 36 }
85 case BinarySource.Type.GithubRelease: { 37 case BinarySource.Type.GithubRelease: {
86 const prebuiltBinaryPath = path.join(serverSource.dir, serverSource.file); 38 const prebuiltBinaryPath = path.join(source.dir, source.file);
39
40 const installedVersion: null | string = getServerVersion(source.storage);
41 const requiredVersion: string = source.version;
87 42
88 if (isBinaryAvailable(prebuiltBinaryPath)) { 43 console.log("Installed version:", installedVersion, "required:", requiredVersion);
44
45 if (isBinaryAvailable(prebuiltBinaryPath) && installedVersion == requiredVersion) {
46 // FIXME: check for new releases and notify the user to update if possible
89 return prebuiltBinaryPath; 47 return prebuiltBinaryPath;
90 } 48 }
91 49
92 const userResponse = await vscode.window.showInformationMessage( 50 const userResponse = await vscode.window.showInformationMessage(
93 "Language server binary for rust-analyzer was not found. " + 51 `Language server version ${source.version} for rust-analyzer is not installed. ` +
94 "Do you want to download it now?", 52 "Do you want to download it now?",
95 "Download now", "Cancel" 53 "Download now", "Cancel"
96 ); 54 );
97 if (userResponse !== "Download now") return null; 55 if (userResponse !== "Download now") return null;
98 56
99 try { 57 if (!await downloadServer(source)) return null;
100 await downloadLatestServer(serverSource);
101 } catch (err) {
102 vscode.window.showErrorMessage(
103 `Failed to download language server from ${serverSource.repo.name} ` +
104 `GitHub repository: ${err.message}`
105 );
106 58
107 console.error(err); 59 return prebuiltBinaryPath;
60 }
61 }
62}
108 63
109 dns.resolve('example.com').then( 64async function downloadServer(source: BinarySource.GithubRelease): Promise<boolean> {
110 addrs => console.log("DNS resolution for example.com was successful", addrs), 65 try {
111 err => { 66 const releaseInfo = (await fetchArtifactReleaseInfo(source.repo, source.file, source.version))!;
112 console.error( 67
113 "DNS resolution for example.com failed, " + 68 await downloadArtifact(releaseInfo, source.file, source.dir, "language server");
114 "there might be an issue with Internet availability" 69 await setServerVersion(source.storage, releaseInfo.releaseName);
115 ); 70 } catch (err) {
116 console.error(err); 71 vscode.window.showErrorMessage(
117 } 72 `Failed to download language server from ${source.repo.name} ` +
118 ); 73 `GitHub repository: ${err.message}`
74 );
75
76 console.error(err);
119 77
120 return null; 78 dns.resolve('example.com').then(
79 addrs => console.log("DNS resolution for example.com was successful", addrs),
80 err => {
81 console.error(
82 "DNS resolution for example.com failed, " +
83 "there might be an issue with Internet availability"
84 );
85 console.error(err);
121 } 86 }
87 );
88 return false;
89 }
122 90
123 if (!isBinaryAvailable(prebuiltBinaryPath)) assert(false, 91 if (!isBinaryAvailable(path.join(source.dir, source.file))) assert(false,
124 `Downloaded language server binary is not functional.` + 92 `Downloaded language server binary is not functional.` +
125 `Downloaded from: ${JSON.stringify(serverSource)}` 93 `Downloaded from: ${JSON.stringify(source, null, 4)}`
126 ); 94 );
127 95
96 vscode.window.showInformationMessage(
97 "Rust analyzer language server was successfully installed 🦀"
98 );
128 99
129 vscode.window.showInformationMessage( 100 return true;
130 "Rust analyzer language server was successfully installed 🦀" 101}
131 );
132 102
133 return prebuiltBinaryPath; 103function isBinaryAvailable(binaryPath: string): boolean {
134 } 104 const res = spawnSync(binaryPath, ["--version"]);
135 }
136 105
137 function isBinaryAvailable(binaryPath: string) { 106 // ACHTUNG! `res` type declaration is inherently wrong, see
138 const res = spawnSync(binaryPath, ["--version"]); 107 // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/42221
139 108
140 // ACHTUNG! `res` type declaration is inherently wrong, see 109 console.log("Checked binary availablity via --version", res);
141 // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/42221 110 console.log(binaryPath, "--version output:", res.output?.map(String));
142 111
143 console.log("Checked binary availablity via --version", res); 112 return res.status === 0;
144 console.log(binaryPath, "--version output:", res.output?.map(String)); 113}
145 114
146 return res.status === 0; 115function getServerVersion(storage: vscode.Memento): null | string {
147 } 116 const version = storage.get<null | string>("server-version", null);
117 console.log("Get server-version:", version);
118 return version;
119}
120
121async function setServerVersion(storage: vscode.Memento, version: string): Promise<void> {
122 console.log("Set server-version:", version);
123 await storage.update("server-version", version.toString());
148} 124}