aboutsummaryrefslogtreecommitdiff
path: root/editors
diff options
context:
space:
mode:
Diffstat (limited to 'editors')
-rw-r--r--editors/code/package-lock.json151
-rw-r--r--editors/code/package.json48
-rw-r--r--editors/code/rollup.config.js4
-rw-r--r--editors/code/src/client.ts35
-rw-r--r--editors/code/src/color_theme.ts28
-rw-r--r--editors/code/src/commands/index.ts49
-rw-r--r--editors/code/src/commands/on_enter.ts45
-rw-r--r--editors/code/src/commands/syntax_tree.ts4
-rw-r--r--editors/code/src/config.ts81
-rw-r--r--editors/code/src/ctx.ts48
-rw-r--r--editors/code/src/highlighting.ts34
-rw-r--r--editors/code/src/inlay_hints.ts40
-rw-r--r--editors/code/src/installation/download_file.ts34
-rw-r--r--editors/code/src/installation/fetch_latest_artifact_metadata.ts46
-rw-r--r--editors/code/src/installation/interfaces.ts55
-rw-r--r--editors/code/src/installation/language_server.ts141
-rw-r--r--editors/code/src/main.ts8
-rw-r--r--editors/code/src/status_display.ts34
-rw-r--r--editors/code/tsconfig.json2
-rw-r--r--editors/code/tslint.json4
-rw-r--r--editors/emacs/rust-analyzer.el286
21 files changed, 627 insertions, 550 deletions
diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json
index b81cf3820..5c056463e 100644
--- a/editors/code/package-lock.json
+++ b/editors/code/package-lock.json
@@ -25,62 +25,72 @@
25 } 25 }
26 }, 26 },
27 "@rollup/plugin-commonjs": { 27 "@rollup/plugin-commonjs": {
28 "version": "11.0.0", 28 "version": "11.0.2",
29 "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.0.0.tgz", 29 "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.0.2.tgz",
30 "integrity": "sha512-jnm//T5ZWOZ6zmJ61fReSCBOif+Ax8dHVoVggA+d2NA7T4qCWgQ3KYr+zN2faGEYLpe1wa03IzvhR+sqVLxUWg==", 30 "integrity": "sha512-MPYGZr0qdbV5zZj8/2AuomVpnRVXRU5XKXb3HVniwRoRCreGlf5kOE081isNWeiLIi6IYkwTX9zE0/c7V8g81g==",
31 "dev": true, 31 "dev": true,
32 "requires": { 32 "requires": {
33 "@rollup/pluginutils": "^3.0.0", 33 "@rollup/pluginutils": "^3.0.0",
34 "estree-walker": "^0.6.1", 34 "estree-walker": "^1.0.1",
35 "is-reference": "^1.1.2", 35 "is-reference": "^1.1.2",
36 "magic-string": "^0.25.2", 36 "magic-string": "^0.25.2",
37 "resolve": "^1.11.0" 37 "resolve": "^1.11.0"
38 } 38 }
39 }, 39 },
40 "@rollup/plugin-node-resolve": { 40 "@rollup/plugin-node-resolve": {
41 "version": "6.0.0", 41 "version": "7.1.1",
42 "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-6.0.0.tgz", 42 "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.1.tgz",
43 "integrity": "sha512-GqWz1CfXOsqpeVMcoM315+O7zMxpRsmhWyhJoxLFHVSp9S64/u02i7len/FnbTNbmgYs+sZyilasijH8UiuboQ==", 43 "integrity": "sha512-14ddhD7TnemeHE97a4rLOhobfYvUVcaYuqTnL8Ti7Jxi9V9Jr5LY7Gko4HZ5k4h4vqQM0gBQt6tsp9xXW94WPA==",
44 "dev": true, 44 "dev": true,
45 "requires": { 45 "requires": {
46 "@rollup/pluginutils": "^3.0.0", 46 "@rollup/pluginutils": "^3.0.6",
47 "@types/resolve": "0.0.8", 47 "@types/resolve": "0.0.8",
48 "builtin-modules": "^3.1.0", 48 "builtin-modules": "^3.1.0",
49 "is-module": "^1.0.0", 49 "is-module": "^1.0.0",
50 "resolve": "^1.11.1" 50 "resolve": "^1.14.2"
51 } 51 },
52 }, 52 "dependencies": {
53 "@rollup/plugin-typescript": { 53 "resolve": {
54 "version": "2.0.1", 54 "version": "1.15.0",
55 "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-2.0.1.tgz", 55 "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.0.tgz",
56 "integrity": "sha512-UA/bN/DlHN19xdOllXmp7G7pM2ac9dQMg0q2T1rg4Bogzb7oHXj2WGafpiNpEm54PivcJdzGRJvRnI6zCISW3w==", 56 "integrity": "sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw==",
57 "dev": true, 57 "dev": true,
58 "requires": { 58 "requires": {
59 "@rollup/pluginutils": "^3.0.0", 59 "path-parse": "^1.0.6"
60 "resolve": "^1.12.2" 60 }
61 }
61 } 62 }
62 }, 63 },
63 "@rollup/pluginutils": { 64 "@rollup/pluginutils": {
64 "version": "3.0.1", 65 "version": "3.0.8",
65 "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.0.1.tgz", 66 "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.0.8.tgz",
66 "integrity": "sha512-PmNurkecagFimv7ZdKCVOfQuqKDPkrcpLFxRBcQ00LYr4HAjJwhCFxBiY2Xoletll2htTIiXBg6g0Yg21h2M3w==", 67 "integrity": "sha512-rYGeAc4sxcZ+kPG/Tw4/fwJODC3IXHYDH4qusdN/b6aLw5LPUbzpecYbEJh4sVQGPFJxd2dBU4kc1H3oy9/bnw==",
67 "dev": true, 68 "dev": true,
68 "requires": { 69 "requires": {
69 "estree-walker": "^0.6.1" 70 "estree-walker": "^1.0.1"
70 } 71 }
71 }, 72 },
72 "@types/estree": { 73 "@types/estree": {
73 "version": "0.0.41", 74 "version": "0.0.39",
74 "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.41.tgz", 75 "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
75 "integrity": "sha512-rIAmXyJlqw4KEBO7+u9gxZZSQHaCNnIzYrnNmYVpgfJhxTqO0brCX0SYpqUTkVI5mwwUwzmtspLBGBKroMeynA==", 76 "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
76 "dev": true 77 "dev": true
77 }, 78 },
78 "@types/node": { 79 "@types/node": {
79 "version": "12.12.22", 80 "version": "12.12.25",
80 "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.22.tgz", 81 "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.25.tgz",
81 "integrity": "sha512-r5i93jqbPWGXYXxianGATOxTelkp6ih/U0WVnvaqAvTqM+0U6J3kw6Xk6uq/dWNRkEVw/0SLcO5ORXbVNz4FMQ==", 82 "integrity": "sha512-nf1LMGZvgFX186geVZR1xMZKKblJiRfiASTHw85zED2kI1yDKHDwTKMdkaCbTlXoRKlGKaDfYywt+V0As30q3w==",
82 "dev": true 83 "dev": true
83 }, 84 },
85 "@types/node-fetch": {
86 "version": "2.5.4",
87 "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.4.tgz",
88 "integrity": "sha512-Oz6id++2qAOFuOlE1j0ouk1dzl3mmI1+qINPNBhi9nt/gVOz0G+13Ao6qjhdF0Ys+eOkhu6JnFmt38bR3H0POQ==",
89 "dev": true,
90 "requires": {
91 "@types/node": "*"
92 }
93 },
84 "@types/resolve": { 94 "@types/resolve": {
85 "version": "0.0.8", 95 "version": "0.0.8",
86 "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", 96 "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz",
@@ -90,10 +100,10 @@
90 "@types/node": "*" 100 "@types/node": "*"
91 } 101 }
92 }, 102 },
93 "@types/seedrandom": { 103 "@types/throttle-debounce": {
94 "version": "2.4.28", 104 "version": "2.1.0",
95 "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.28.tgz", 105 "resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz",
96 "integrity": "sha512-SMA+fUwULwK7sd/ZJicUztiPs8F1yCPwF3O23Z9uQ32ME5Ha0NmDK9+QTsYE4O2tHXChzXomSWWeIhCnoN1LqA==", 106 "integrity": "sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ==",
97 "dev": true 107 "dev": true
98 }, 108 },
99 "@types/vscode": { 109 "@types/vscode": {
@@ -340,9 +350,9 @@
340 "dev": true 350 "dev": true
341 }, 351 },
342 "estree-walker": { 352 "estree-walker": {
343 "version": "0.6.1", 353 "version": "1.0.1",
344 "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", 354 "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
345 "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", 355 "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
346 "dev": true 356 "dev": true
347 }, 357 },
348 "esutils": { 358 "esutils": {
@@ -429,14 +439,6 @@
429 "dev": true, 439 "dev": true,
430 "requires": { 440 "requires": {
431 "@types/estree": "0.0.39" 441 "@types/estree": "0.0.39"
432 },
433 "dependencies": {
434 "@types/estree": {
435 "version": "0.0.39",
436 "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
437 "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
438 "dev": true
439 }
440 } 442 }
441 }, 443 },
442 "js-tokens": { 444 "js-tokens": {
@@ -486,9 +488,9 @@
486 } 488 }
487 }, 489 },
488 "magic-string": { 490 "magic-string": {
489 "version": "0.25.4", 491 "version": "0.25.6",
490 "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.4.tgz", 492 "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.6.tgz",
491 "integrity": "sha512-oycWO9nEVAP2RVPbIoDoA4Y7LFIJ3xRYov93gAyJhZkET1tNuB0u7uWkZS2LpBWTJUWnmau/To8ECWRC+jKNfw==", 493 "integrity": "sha512-3a5LOMSGoCTH5rbqobC2HuDNRtE2glHZ8J7pK+QZYppyWA36yuNpsX994rIY2nCuyP7CZYy7lQq/X2jygiZ89g==",
492 "dev": true, 494 "dev": true,
493 "requires": { 495 "requires": {
494 "sourcemap-codec": "^1.4.4" 496 "sourcemap-codec": "^1.4.4"
@@ -549,6 +551,11 @@
549 "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", 551 "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
550 "dev": true 552 "dev": true
551 }, 553 },
554 "node-fetch": {
555 "version": "2.6.0",
556 "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
557 "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
558 },
552 "nth-check": { 559 "nth-check": {
553 "version": "1.0.2", 560 "version": "1.0.2",
554 "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", 561 "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
@@ -675,9 +682,9 @@
675 } 682 }
676 }, 683 },
677 "rollup": { 684 "rollup": {
678 "version": "1.27.14", 685 "version": "1.31.0",
679 "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.27.14.tgz", 686 "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.31.0.tgz",
680 "integrity": "sha512-DuDjEyn8Y79ALYXMt+nH/EI58L5pEw5HU9K38xXdRnxQhvzUTI/nxAawhkAHUQeudANQ//8iyrhVRHJBuR6DSQ==", 687 "integrity": "sha512-9C6ovSyNeEwvuRuUUmsTpJcXac1AwSL1a3x+O5lpmQKZqi5mmrjauLeqIjvREC+yNRR8fPdzByojDng+af3nVw==",
681 "dev": true, 688 "dev": true,
682 "requires": { 689 "requires": {
683 "@types/estree": "*", 690 "@types/estree": "*",
@@ -691,11 +698,6 @@
691 "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", 698 "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
692 "dev": true 699 "dev": true
693 }, 700 },
694 "seedrandom": {
695 "version": "3.0.5",
696 "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
697 "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
698 },
699 "semver": { 701 "semver": {
700 "version": "6.3.0", 702 "version": "6.3.0",
701 "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 703 "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@@ -708,9 +710,9 @@
708 "dev": true 710 "dev": true
709 }, 711 },
710 "sourcemap-codec": { 712 "sourcemap-codec": {
711 "version": "1.4.6", 713 "version": "1.4.8",
712 "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz", 714 "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
713 "integrity": "sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg==", 715 "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
714 "dev": true 716 "dev": true
715 }, 717 },
716 "sprintf-js": { 718 "sprintf-js": {
@@ -737,6 +739,11 @@
737 "has-flag": "^3.0.0" 739 "has-flag": "^3.0.0"
738 } 740 }
739 }, 741 },
742 "throttle-debounce": {
743 "version": "2.1.0",
744 "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-2.1.0.tgz",
745 "integrity": "sha512-AOvyNahXQuU7NN+VVvOOX+uW6FPaWdAOdRP5HfwYxAfCzXTFKRMoIMk+n+po318+ktcChx+F1Dd91G3YHeMKyg=="
746 },
740 "tmp": { 747 "tmp": {
741 "version": "0.0.29", 748 "version": "0.0.29",
742 "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", 749 "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz",
@@ -813,9 +820,9 @@
813 } 820 }
814 }, 821 },
815 "typescript": { 822 "typescript": {
816 "version": "3.7.4", 823 "version": "3.7.5",
817 "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.4.tgz", 824 "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz",
818 "integrity": "sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw==", 825 "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==",
819 "dev": true 826 "dev": true
820 }, 827 },
821 "typescript-formatter": { 828 "typescript-formatter": {
@@ -894,27 +901,27 @@
894 "integrity": "sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A==" 901 "integrity": "sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A=="
895 }, 902 },
896 "vscode-languageclient": { 903 "vscode-languageclient": {
897 "version": "6.0.1", 904 "version": "6.1.0",
898 "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-6.0.1.tgz", 905 "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-6.1.0.tgz",
899 "integrity": "sha512-7yZaSHichTJEyOJykI2RLQEECf9MqNLoklzC/1OVi/M8ioIsWQ1+lkN1nTsUhd6+F7p9ar9dNmPiEhL0i5uUBA==", 906 "integrity": "sha512-Tcp0VoOaa0YzxL4nEfK9tsmcy76Eo8jNLvFQZwh2c8oMm02luL8uGYPLQNAiZ3XGgegfcwiQFZMqbW7DNV0vxA==",
900 "requires": { 907 "requires": {
901 "semver": "^6.3.0", 908 "semver": "^6.3.0",
902 "vscode-languageserver-protocol": "^3.15.1" 909 "vscode-languageserver-protocol": "^3.15.2"
903 } 910 }
904 }, 911 },
905 "vscode-languageserver-protocol": { 912 "vscode-languageserver-protocol": {
906 "version": "3.15.1", 913 "version": "3.15.2",
907 "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.1.tgz", 914 "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.2.tgz",
908 "integrity": "sha512-wJAo06VM9ZBnRqslplDjfz6Tdive0O7z44yNxBFA3x0/YZkXBIL6I+9rwQ/9Y//0X0eCh12FQrj+KmEXf2L5eA==", 915 "integrity": "sha512-GdL05JKOgZ76RDg3suiGCl9enESM7iQgGw4x93ibTh4sldvZmakHmTeZ4iUApPPGKf6O3OVBtrsksBXnHYaxNg==",
909 "requires": { 916 "requires": {
910 "vscode-jsonrpc": "^5.0.1", 917 "vscode-jsonrpc": "^5.0.1",
911 "vscode-languageserver-types": "3.15.0" 918 "vscode-languageserver-types": "3.15.1"
912 } 919 }
913 }, 920 },
914 "vscode-languageserver-types": { 921 "vscode-languageserver-types": {
915 "version": "3.15.0", 922 "version": "3.15.1",
916 "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0.tgz", 923 "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz",
917 "integrity": "sha512-AXteNagMhBWnZ6gNN0UB4HTiD/7TajgfHl6jaM6O7qz3zDJw0H3Jf83w05phihnBRCML+K6Ockh8f8bL0OObPw==" 924 "integrity": "sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ=="
918 }, 925 },
919 "wrappy": { 926 "wrappy": {
920 "version": "1.0.2", 927 "version": "1.0.2",
diff --git a/editors/code/package.json b/editors/code/package.json
index cd9c99b35..f687eb8d4 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -8,7 +8,8 @@
8 "version": "0.1.0", 8 "version": "0.1.0",
9 "publisher": "matklad", 9 "publisher": "matklad",
10 "repository": { 10 "repository": {
11 "url": "https://github.com/matklad/rust-analyzer/" 11 "url": "https://github.com/rust-analyzer/rust-analyzer.git",
12 "type": "git"
12 }, 13 },
13 "categories": [ 14 "categories": [
14 "Other" 15 "Other"
@@ -17,27 +18,28 @@
17 "vscode": "^1.41.0" 18 "vscode": "^1.41.0"
18 }, 19 },
19 "scripts": { 20 "scripts": {
20 "vscode:prepublish": "rollup -c", 21 "vscode:prepublish": "tsc && rollup -c",
21 "package": "vsce package", 22 "package": "vsce package",
22 "watch": "tsc -watch -p ./", 23 "watch": "tsc --watch",
23 "fmt": "tsfmt -r && tslint -c tslint.json 'src/**/*.ts' --fix" 24 "fmt": "tsfmt -r && tslint -p tsconfig.json -c tslint.json 'src/**/*.ts' --fix"
24 }, 25 },
25 "dependencies": { 26 "dependencies": {
26 "jsonc-parser": "^2.1.0", 27 "jsonc-parser": "^2.1.0",
27 "seedrandom": "^3.0.5", 28 "node-fetch": "^2.6.0",
28 "vscode-languageclient": "^6.0.1" 29 "throttle-debounce": "^2.1.0",
30 "vscode-languageclient": "^6.1.0"
29 }, 31 },
30 "devDependencies": { 32 "devDependencies": {
31 "@rollup/plugin-commonjs": "^11.0.0", 33 "@rollup/plugin-commonjs": "^11.0.2",
32 "@rollup/plugin-node-resolve": "^6.0.0", 34 "@rollup/plugin-node-resolve": "^7.1.1",
33 "@rollup/plugin-typescript": "^2.0.1", 35 "@types/node": "^12.12.25",
34 "@types/node": "^12.12.21", 36 "@types/node-fetch": "^2.5.4",
35 "@types/seedrandom": "^2.4.28", 37 "@types/throttle-debounce": "^2.1.0",
36 "@types/vscode": "^1.41.0", 38 "@types/vscode": "^1.41.0",
37 "rollup": "^1.27.14", 39 "rollup": "^1.31.0",
38 "tslib": "^1.10.0", 40 "tslib": "^1.10.0",
39 "tslint": "^5.20.1", 41 "tslint": "^5.20.1",
40 "typescript": "^3.7.3", 42 "typescript": "^3.7.5",
41 "typescript-formatter": "^7.2.2", 43 "typescript-formatter": "^7.2.2",
42 "vsce": "^1.71.0" 44 "vsce": "^1.71.0"
43 }, 45 },
@@ -116,6 +118,11 @@
116 "command": "rust-analyzer.reload", 118 "command": "rust-analyzer.reload",
117 "title": "Restart server", 119 "title": "Restart server",
118 "category": "Rust Analyzer" 120 "category": "Rust Analyzer"
121 },
122 {
123 "command": "rust-analyzer.onEnter",
124 "title": "Enhanced enter key",
125 "category": "Rust Analyzer"
119 } 126 }
120 ], 127 ],
121 "keybindings": [ 128 "keybindings": [
@@ -138,6 +145,11 @@
138 "command": "rust-analyzer.run", 145 "command": "rust-analyzer.run",
139 "key": "ctrl+r", 146 "key": "ctrl+r",
140 "when": "editorTextFocus && editorLangId == rust" 147 "when": "editorTextFocus && editorLangId == rust"
148 },
149 {
150 "command": "rust-analyzer.onEnter",
151 "key": "enter",
152 "when": "editorTextFocus && !suggestWidgetVisible && editorLangId == rust"
141 } 153 }
142 ], 154 ],
143 "configuration": { 155 "configuration": {
@@ -159,17 +171,13 @@
159 "default": {}, 171 "default": {},
160 "description": "Fine grained feature flags to disable annoying features" 172 "description": "Fine grained feature flags to disable annoying features"
161 }, 173 },
162 "rust-analyzer.enableEnhancedTyping": {
163 "type": "boolean",
164 "default": true,
165 "description": "Enables enhanced typing. NOTE: If using a VIM extension, you should set this to false"
166 },
167 "rust-analyzer.raLspServerPath": { 174 "rust-analyzer.raLspServerPath": {
168 "type": [ 175 "type": [
176 "null",
169 "string" 177 "string"
170 ], 178 ],
171 "default": "ra_lsp_server", 179 "default": null,
172 "description": "Path to ra_lsp_server executable" 180 "description": "Path to ra_lsp_server executable (points to bundled binary by default)"
173 }, 181 },
174 "rust-analyzer.excludeGlobs": { 182 "rust-analyzer.excludeGlobs": {
175 "type": "array", 183 "type": "array",
diff --git a/editors/code/rollup.config.js b/editors/code/rollup.config.js
index de6a3b2b7..f8d320f46 100644
--- a/editors/code/rollup.config.js
+++ b/editors/code/rollup.config.js
@@ -1,12 +1,10 @@
1import typescript from '@rollup/plugin-typescript';
2import resolve from '@rollup/plugin-node-resolve'; 1import resolve from '@rollup/plugin-node-resolve';
3import commonjs from '@rollup/plugin-commonjs'; 2import commonjs from '@rollup/plugin-commonjs';
4import nodeBuiltins from 'builtin-modules'; 3import nodeBuiltins from 'builtin-modules';
5 4
6export default { 5export default {
7 input: 'src/main.ts', 6 input: 'out/main.js',
8 plugins: [ 7 plugins: [
9 typescript(),
10 resolve({ 8 resolve({
11 preferBuiltins: true 9 preferBuiltins: true
12 }), 10 }),
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index 1ff64a930..2e3d4aba2 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -1,25 +1,21 @@
1import { homedir } from 'os';
2import * as lc from 'vscode-languageclient'; 1import * as lc from 'vscode-languageclient';
3import { spawnSync } from 'child_process';
4 2
5import { window, workspace } from 'vscode'; 3import { window, workspace } from 'vscode';
6import { Config } from './config'; 4import { Config } from './config';
5import { ensureLanguageServerBinary } from './installation/language_server';
7 6
8export function createClient(config: Config): lc.LanguageClient { 7export async function createClient(config: Config): Promise<null | lc.LanguageClient> {
9 // '.' Is the fallback if no folder is open 8 // '.' Is the fallback if no folder is open
10 // TODO?: Workspace folders support Uri's (eg: file://test.txt). It might be a good idea to test if the uri points to a file. 9 // TODO?: Workspace folders support Uri's (eg: file://test.txt).
11 let folder: string = '.'; 10 // It might be a good idea to test if the uri points to a file.
12 if (workspace.workspaceFolders !== undefined) { 11 const workspaceFolderPath = workspace.workspaceFolders?.[0]?.uri.fsPath ?? '.';
13 folder = workspace.workspaceFolders[0].uri.fsPath.toString(); 12
14 } 13 const raLspServerPath = await ensureLanguageServerBinary(config.langServerSource);
14 if (!raLspServerPath) return null;
15 15
16 const command = expandPathResolving(config.raLspServerPath);
17 if (spawnSync(command, ["--version"]).status !== 0) {
18 window.showErrorMessage(`Unable to execute '${command} --version'`);
19 }
20 const run: lc.Executable = { 16 const run: lc.Executable = {
21 command, 17 command: raLspServerPath,
22 options: { cwd: folder }, 18 options: { cwd: workspaceFolderPath },
23 }; 19 };
24 const serverOptions: lc.ServerOptions = { 20 const serverOptions: lc.ServerOptions = {
25 run, 21 run,
@@ -37,8 +33,7 @@ export function createClient(config: Config): lc.LanguageClient {
37 cargoWatchEnable: config.cargoWatchOptions.enable, 33 cargoWatchEnable: config.cargoWatchOptions.enable,
38 cargoWatchArgs: config.cargoWatchOptions.arguments, 34 cargoWatchArgs: config.cargoWatchOptions.arguments,
39 cargoWatchCommand: config.cargoWatchOptions.command, 35 cargoWatchCommand: config.cargoWatchOptions.command,
40 cargoWatchAllTargets: 36 cargoWatchAllTargets: config.cargoWatchOptions.allTargets,
41 config.cargoWatchOptions.allTargets,
42 excludeGlobs: config.excludeGlobs, 37 excludeGlobs: config.excludeGlobs,
43 useClientWatching: config.useClientWatching, 38 useClientWatching: config.useClientWatching,
44 featureFlags: config.featureFlags, 39 featureFlags: config.featureFlags,
@@ -62,7 +57,7 @@ export function createClient(config: Config): lc.LanguageClient {
62 // This also requires considering our settings strategy, which is work which needs doing 57 // This also requires considering our settings strategy, which is work which needs doing
63 // @ts-ignore The tracer is private to vscode-languageclient, but we need access to it to not log publishDecorations requests 58 // @ts-ignore The tracer is private to vscode-languageclient, but we need access to it to not log publishDecorations requests
64 res._tracer = { 59 res._tracer = {
65 log: (messageOrDataObject: string | any, data?: string) => { 60 log: (messageOrDataObject: string | unknown, data?: string) => {
66 if (typeof messageOrDataObject === 'string') { 61 if (typeof messageOrDataObject === 'string') {
67 if ( 62 if (
68 messageOrDataObject.includes( 63 messageOrDataObject.includes(
@@ -86,9 +81,3 @@ export function createClient(config: Config): lc.LanguageClient {
86 res.registerProposedFeatures(); 81 res.registerProposedFeatures();
87 return res; 82 return res;
88} 83}
89function expandPathResolving(path: string) {
90 if (path.startsWith('~/')) {
91 return path.replace('~', homedir());
92 }
93 return path;
94}
diff --git a/editors/code/src/color_theme.ts b/editors/code/src/color_theme.ts
index cbad47f35..a6957a76e 100644
--- a/editors/code/src/color_theme.ts
+++ b/editors/code/src/color_theme.ts
@@ -28,9 +28,12 @@ export class ColorTheme {
28 static fromRules(rules: TextMateRule[]): ColorTheme { 28 static fromRules(rules: TextMateRule[]): ColorTheme {
29 const res = new ColorTheme(); 29 const res = new ColorTheme();
30 for (const rule of rules) { 30 for (const rule of rules) {
31 const scopes = typeof rule.scope === 'string' 31 const scopes = typeof rule.scope === 'undefined'
32 ? [rule.scope] 32 ? []
33 : rule.scope; 33 : typeof rule.scope === 'string'
34 ? [rule.scope]
35 : rule.scope;
36
34 for (const scope of scopes) { 37 for (const scope of scopes) {
35 res.rules.set(scope, rule.settings); 38 res.rules.set(scope, rule.settings);
36 } 39 }
@@ -59,7 +62,7 @@ export class ColorTheme {
59} 62}
60 63
61function loadThemeNamed(themeName: string): ColorTheme { 64function loadThemeNamed(themeName: string): ColorTheme {
62 function isTheme(extension: vscode.Extension<any>): boolean { 65 function isTheme(extension: vscode.Extension<unknown>): boolean {
63 return ( 66 return (
64 extension.extensionKind === vscode.ExtensionKind.UI && 67 extension.extensionKind === vscode.ExtensionKind.UI &&
65 extension.packageJSON.contributes && 68 extension.packageJSON.contributes &&
@@ -67,13 +70,13 @@ function loadThemeNamed(themeName: string): ColorTheme {
67 ); 70 );
68 } 71 }
69 72
70 let themePaths = vscode.extensions.all 73 const themePaths: string[] = vscode.extensions.all
71 .filter(isTheme) 74 .filter(isTheme)
72 .flatMap(ext => { 75 .flatMap(
73 return ext.packageJSON.contributes.themes 76 ext => ext.packageJSON.contributes.themes
74 .filter((it: any) => (it.id || it.label) === themeName) 77 .filter((it: any) => (it.id || it.label) === themeName)
75 .map((it: any) => path.join(ext.extensionPath, it.path)); 78 .map((it: any) => path.join(ext.extensionPath, it.path))
76 }); 79 );
77 80
78 const res = new ColorTheme(); 81 const res = new ColorTheme();
79 for (const themePath of themePaths) { 82 for (const themePath of themePaths) {
@@ -94,13 +97,12 @@ function loadThemeFile(themePath: string): ColorTheme {
94 return new ColorTheme(); 97 return new ColorTheme();
95 } 98 }
96 const obj = jsonc.parse(text); 99 const obj = jsonc.parse(text);
97 const tokenColors = obj?.tokenColors ?? []; 100 const tokenColors: TextMateRule[] = obj?.tokenColors ?? [];
98 const res = ColorTheme.fromRules(tokenColors); 101 const res = ColorTheme.fromRules(tokenColors);
99 102
100 for (const include in obj?.include ?? []) { 103 for (const include of obj?.include ?? []) {
101 const includePath = path.join(path.dirname(themePath), include); 104 const includePath = path.join(path.dirname(themePath), include);
102 const tmp = loadThemeFile(includePath); 105 res.mergeFrom(loadThemeFile(includePath));
103 res.mergeFrom(tmp);
104 } 106 }
105 107
106 return res; 108 return res;
diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts
index dc075aa82..aee969432 100644
--- a/editors/code/src/commands/index.ts
+++ b/editors/code/src/commands/index.ts
@@ -4,24 +4,24 @@ import * as lc from 'vscode-languageclient';
4import { Ctx, Cmd } from '../ctx'; 4import { Ctx, Cmd } from '../ctx';
5import * as sourceChange from '../source_change'; 5import * as sourceChange from '../source_change';
6 6
7import { analyzerStatus } from './analyzer_status'; 7export * from './analyzer_status';
8import { matchingBrace } from './matching_brace'; 8export * from './matching_brace';
9import { joinLines } from './join_lines'; 9export * from './join_lines';
10import { onEnter } from './on_enter'; 10export * from './on_enter';
11import { parentModule } from './parent_module'; 11export * from './parent_module';
12import { syntaxTree } from './syntax_tree'; 12export * from './syntax_tree';
13import { expandMacro } from './expand_macro'; 13export * from './expand_macro';
14import { run, runSingle } from './runnables'; 14export * from './runnables';
15 15
16function collectGarbage(ctx: Ctx): Cmd { 16export function collectGarbage(ctx: Ctx): Cmd {
17 return async () => { 17 return async () => {
18 ctx.client?.sendRequest<null>('rust-analyzer/collectGarbage', null); 18 ctx.client?.sendRequest<null>('rust-analyzer/collectGarbage', null);
19 }; 19 };
20} 20}
21 21
22function showReferences(ctx: Ctx): Cmd { 22export function showReferences(ctx: Ctx): Cmd {
23 return (uri: string, position: lc.Position, locations: lc.Location[]) => { 23 return (uri: string, position: lc.Position, locations: lc.Location[]) => {
24 let client = ctx.client; 24 const client = ctx.client;
25 if (client) { 25 if (client) {
26 vscode.commands.executeCommand( 26 vscode.commands.executeCommand(
27 'editor.action.showReferences', 27 'editor.action.showReferences',
@@ -33,13 +33,13 @@ function showReferences(ctx: Ctx): Cmd {
33 }; 33 };
34} 34}
35 35
36function applySourceChange(ctx: Ctx): Cmd { 36export function applySourceChange(ctx: Ctx): Cmd {
37 return async (change: sourceChange.SourceChange) => { 37 return async (change: sourceChange.SourceChange) => {
38 sourceChange.applySourceChange(ctx, change); 38 await sourceChange.applySourceChange(ctx, change);
39 }; 39 };
40} 40}
41 41
42function selectAndApplySourceChange(ctx: Ctx): Cmd { 42export function selectAndApplySourceChange(ctx: Ctx): Cmd {
43 return async (changes: sourceChange.SourceChange[]) => { 43 return async (changes: sourceChange.SourceChange[]) => {
44 if (changes.length === 1) { 44 if (changes.length === 1) {
45 await sourceChange.applySourceChange(ctx, changes[0]); 45 await sourceChange.applySourceChange(ctx, changes[0]);
@@ -51,26 +51,9 @@ function selectAndApplySourceChange(ctx: Ctx): Cmd {
51 }; 51 };
52} 52}
53 53
54function reload(ctx: Ctx): Cmd { 54export function reload(ctx: Ctx): Cmd {
55 return async () => { 55 return async () => {
56 vscode.window.showInformationMessage('Reloading rust-analyzer...'); 56 vscode.window.showInformationMessage('Reloading rust-analyzer...');
57 await ctx.restartServer(); 57 await ctx.restartServer();
58 }; 58 };
59} 59}
60
61export {
62 analyzerStatus,
63 expandMacro,
64 joinLines,
65 matchingBrace,
66 parentModule,
67 syntaxTree,
68 onEnter,
69 collectGarbage,
70 run,
71 runSingle,
72 showReferences,
73 applySourceChange,
74 selectAndApplySourceChange,
75 reload
76};
diff --git a/editors/code/src/commands/on_enter.ts b/editors/code/src/commands/on_enter.ts
index 6f61883cd..25eaebcbe 100644
--- a/editors/code/src/commands/on_enter.ts
+++ b/editors/code/src/commands/on_enter.ts
@@ -1,28 +1,35 @@
1import * as vscode from 'vscode';
1import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
2 3
3import { applySourceChange, SourceChange } from '../source_change'; 4import { applySourceChange, SourceChange } from '../source_change';
4import { Cmd, Ctx } from '../ctx'; 5import { Cmd, Ctx } from '../ctx';
5 6
6export function onEnter(ctx: Ctx): Cmd { 7async function handleKeypress(ctx: Ctx) {
7 return async (event: { text: string }) => { 8 const editor = ctx.activeRustEditor;
8 const editor = ctx.activeRustEditor; 9 const client = ctx.client;
9 const client = ctx.client; 10
10 if (!editor || event.text !== '\n') return false; 11 if (!editor || !client) return false;
11 if (!client) return false; 12
13 const request: lc.TextDocumentPositionParams = {
14 textDocument: { uri: editor.document.uri.toString() },
15 position: client.code2ProtocolConverter.asPosition(
16 editor.selection.active,
17 ),
18 };
19 const change = await client.sendRequest<undefined | SourceChange>(
20 'rust-analyzer/onEnter',
21 request,
22 );
23 if (!change) return false;
12 24
13 const request: lc.TextDocumentPositionParams = { 25 await applySourceChange(ctx, change);
14 textDocument: { uri: editor.document.uri.toString() }, 26 return true;
15 position: client.code2ProtocolConverter.asPosition( 27}
16 editor.selection.active, 28
17 ), 29export function onEnter(ctx: Ctx): Cmd {
18 }; 30 return async () => {
19 const change = await client.sendRequest<undefined | SourceChange>( 31 if (await handleKeypress(ctx)) return;
20 'rust-analyzer/onEnter',
21 request,
22 );
23 if (!change) return false;
24 32
25 await applySourceChange(ctx, change); 33 await vscode.commands.executeCommand('default:type', { text: '\n' });
26 return true;
27 }; 34 };
28} 35}
diff --git a/editors/code/src/commands/syntax_tree.ts b/editors/code/src/commands/syntax_tree.ts
index 02ea9f166..7dde66ad1 100644
--- a/editors/code/src/commands/syntax_tree.ts
+++ b/editors/code/src/commands/syntax_tree.ts
@@ -22,6 +22,7 @@ export function syntaxTree(ctx: Ctx): Cmd {
22 if (doc.languageId !== 'rust') return; 22 if (doc.languageId !== 'rust') return;
23 afterLs(() => tdcp.eventEmitter.fire(tdcp.uri)); 23 afterLs(() => tdcp.eventEmitter.fire(tdcp.uri));
24 }, 24 },
25 null,
25 ctx.subscriptions, 26 ctx.subscriptions,
26 ); 27 );
27 28
@@ -30,6 +31,7 @@ export function syntaxTree(ctx: Ctx): Cmd {
30 if (!editor || editor.document.languageId !== 'rust') return; 31 if (!editor || editor.document.languageId !== 'rust') return;
31 tdcp.eventEmitter.fire(tdcp.uri); 32 tdcp.eventEmitter.fire(tdcp.uri);
32 }, 33 },
34 null,
33 ctx.subscriptions, 35 ctx.subscriptions,
34 ); 36 );
35 37
@@ -55,7 +57,7 @@ export function syntaxTree(ctx: Ctx): Cmd {
55 57
56// We need to order this after LS updates, but there's no API for that. 58// We need to order this after LS updates, but there's no API for that.
57// Hence, good old setTimeout. 59// Hence, good old setTimeout.
58function afterLs(f: () => any) { 60function afterLs(f: () => void) {
59 setTimeout(f, 10); 61 setTimeout(f, 10);
60} 62}
61 63
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index fc21c8813..d5f3da2ed 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -1,4 +1,6 @@
1import * as os from "os";
1import * as vscode from 'vscode'; 2import * as vscode from 'vscode';
3import { BinarySource } from "./installation/interfaces";
2 4
3const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; 5const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG;
4 6
@@ -16,16 +18,17 @@ export interface CargoFeatures {
16} 18}
17 19
18export class Config { 20export class Config {
21 langServerSource!: null | BinarySource;
22
19 highlightingOn = true; 23 highlightingOn = true;
20 rainbowHighlightingOn = false; 24 rainbowHighlightingOn = false;
21 enableEnhancedTyping = true; 25 enableEnhancedTyping = true;
22 raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server';
23 lruCapacity: null | number = null; 26 lruCapacity: null | number = null;
24 displayInlayHints = true; 27 displayInlayHints = true;
25 maxInlayHintLength: null | number = null; 28 maxInlayHintLength: null | number = null;
26 excludeGlobs = []; 29 excludeGlobs: string[] = [];
27 useClientWatching = true; 30 useClientWatching = true;
28 featureFlags = {}; 31 featureFlags: Record<string, boolean> = {};
29 // for internal use 32 // for internal use
30 withSysroot: null | boolean = null; 33 withSysroot: null | boolean = null;
31 cargoWatchOptions: CargoWatchOptions = { 34 cargoWatchOptions: CargoWatchOptions = {
@@ -45,11 +48,72 @@ export class Config {
45 private prevCargoWatchOptions: null | CargoWatchOptions = null; 48 private prevCargoWatchOptions: null | CargoWatchOptions = null;
46 49
47 constructor(ctx: vscode.ExtensionContext) { 50 constructor(ctx: vscode.ExtensionContext) {
48 vscode.workspace.onDidChangeConfiguration(_ => this.refresh(), ctx.subscriptions); 51 vscode.workspace.onDidChangeConfiguration(_ => this.refresh(ctx), null, ctx.subscriptions);
49 this.refresh(); 52 this.refresh(ctx);
53 }
54
55 private static expandPathResolving(path: string) {
56 if (path.startsWith('~/')) {
57 return path.replace('~', os.homedir());
58 }
59 return path;
60 }
61
62 /**
63 * Name of the binary artifact for `ra_lsp_server` that is published for
64 * `platform` on GitHub releases. (It is also stored under the same name when
65 * downloaded by the extension).
66 */
67 private static prebuiltLangServerFileName(platform: NodeJS.Platform): null | string {
68 switch (platform) {
69 case "linux": return "ra_lsp_server-linux";
70 case "darwin": return "ra_lsp_server-mac";
71 case "win32": return "ra_lsp_server-windows.exe";
72
73 // Users on these platforms yet need to manually build from sources
74 case "aix":
75 case "android":
76 case "freebsd":
77 case "openbsd":
78 case "sunos":
79 case "cygwin":
80 case "netbsd": return null;
81 // The list of platforms is exhaustive (see `NodeJS.Platform` type definition)
82 }
83 }
84
85 private static langServerBinarySource(
86 ctx: vscode.ExtensionContext,
87 config: vscode.WorkspaceConfiguration
88 ): null | BinarySource {
89 const langServerPath = RA_LSP_DEBUG ?? config.get<null | string>("raLspServerPath");
90
91 if (langServerPath) {
92 return {
93 type: BinarySource.Type.ExplicitPath,
94 path: Config.expandPathResolving(langServerPath)
95 };
96 }
97
98 const prebuiltBinaryName = Config.prebuiltLangServerFileName(process.platform);
99
100 if (!prebuiltBinaryName) return null;
101
102 return {
103 type: BinarySource.Type.GithubRelease,
104 dir: ctx.globalStoragePath,
105 file: prebuiltBinaryName,
106 repo: {
107 name: "rust-analyzer",
108 owner: "rust-analyzer",
109 }
110 };
50 } 111 }
51 112
52 private refresh() { 113
114 // FIXME: revisit the logic for `if (.has(...)) config.get(...)` set default
115 // values only in one place (i.e. remove default values from non-readonly members declarations)
116 private refresh(ctx: vscode.ExtensionContext) {
53 const config = vscode.workspace.getConfiguration('rust-analyzer'); 117 const config = vscode.workspace.getConfiguration('rust-analyzer');
54 118
55 let requireReloadMessage = null; 119 let requireReloadMessage = null;
@@ -82,10 +146,7 @@ export class Config {
82 this.prevEnhancedTyping = this.enableEnhancedTyping; 146 this.prevEnhancedTyping = this.enableEnhancedTyping;
83 } 147 }
84 148
85 if (config.has('raLspServerPath')) { 149 this.langServerSource = Config.langServerBinarySource(ctx, config);
86 this.raLspServerPath =
87 RA_LSP_DEBUG || (config.get('raLspServerPath') as string);
88 }
89 150
90 if (config.has('cargo-watch.enable')) { 151 if (config.has('cargo-watch.enable')) {
91 this.cargoWatchOptions.enable = config.get<boolean>( 152 this.cargoWatchOptions.enable = config.get<boolean>(
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index a2a4e42a9..70042a479 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -1,5 +1,6 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
3
3import { Config } from './config'; 4import { Config } from './config';
4import { createClient } from './client'; 5import { createClient } from './client';
5 6
@@ -10,6 +11,9 @@ export class Ctx {
10 // deal with it. 11 // deal with it.
11 // 12 //
12 // Ideally, this should be replaced with async getter though. 13 // Ideally, this should be replaced with async getter though.
14 // FIXME: this actually needs syncronization of some kind (check how
15 // vscode deals with `deactivate()` call when extension has some work scheduled
16 // on the event loop to get a better picture of what we can do here)
13 client: lc.LanguageClient | null = null; 17 client: lc.LanguageClient | null = null;
14 private extCtx: vscode.ExtensionContext; 18 private extCtx: vscode.ExtensionContext;
15 private onDidRestartHooks: Array<(client: lc.LanguageClient) => void> = []; 19 private onDidRestartHooks: Array<(client: lc.LanguageClient) => void> = [];
@@ -20,12 +24,19 @@ export class Ctx {
20 } 24 }
21 25
22 async restartServer() { 26 async restartServer() {
23 let old = this.client; 27 const old = this.client;
24 if (old) { 28 if (old) {
25 await old.stop(); 29 await old.stop();
26 } 30 }
27 this.client = null; 31 this.client = null;
28 const client = createClient(this.config); 32 const client = await createClient(this.config);
33 if (!client) {
34 throw new Error(
35 "Rust Analyzer Language Server is not available. " +
36 "Please, ensure its [proper installation](https://github.com/rust-analyzer/rust-analyzer/tree/master/docs/user#vs-code)."
37 );
38 }
39
29 this.pushCleanup(client.start()); 40 this.pushCleanup(client.start());
30 await client.onReady(); 41 await client.onReady();
31 42
@@ -49,33 +60,11 @@ export class Ctx {
49 this.pushCleanup(d); 60 this.pushCleanup(d);
50 } 61 }
51 62
52 overrideCommand(name: string, factory: (ctx: Ctx) => Cmd) { 63 get subscriptions(): Disposable[] {
53 const defaultCmd = `default:${name}`;
54 const override = factory(this);
55 const original = (...args: any[]) =>
56 vscode.commands.executeCommand(defaultCmd, ...args);
57 try {
58 const d = vscode.commands.registerCommand(
59 name,
60 async (...args: any[]) => {
61 if (!(await override(...args))) {
62 return await original(...args);
63 }
64 },
65 );
66 this.pushCleanup(d);
67 } catch (_) {
68 vscode.window.showWarningMessage(
69 'Enhanced typing feature is disabled because of incompatibility with VIM extension, consider turning off rust-analyzer.enableEnhancedTyping: https://github.com/rust-analyzer/rust-analyzer/blob/master/docs/user/README.md#settings',
70 );
71 }
72 }
73
74 get subscriptions(): { dispose(): any }[] {
75 return this.extCtx.subscriptions; 64 return this.extCtx.subscriptions;
76 } 65 }
77 66
78 pushCleanup(d: { dispose(): any }) { 67 pushCleanup(d: Disposable) {
79 this.extCtx.subscriptions.push(d); 68 this.extCtx.subscriptions.push(d);
80 } 69 }
81 70
@@ -84,12 +73,15 @@ export class Ctx {
84 } 73 }
85} 74}
86 75
87export type Cmd = (...args: any[]) => any; 76export interface Disposable {
77 dispose(): void;
78}
79export type Cmd = (...args: any[]) => unknown;
88 80
89export async function sendRequestWithRetry<R>( 81export async function sendRequestWithRetry<R>(
90 client: lc.LanguageClient, 82 client: lc.LanguageClient,
91 method: string, 83 method: string,
92 param: any, 84 param: unknown,
93 token?: vscode.CancellationToken, 85 token?: vscode.CancellationToken,
94): Promise<R> { 86): Promise<R> {
95 for (const delay of [2, 4, 6, 8, 10, null]) { 87 for (const delay of [2, 4, 6, 8, 10, null]) {
diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts
index 014e96f75..4fbbe3ddc 100644
--- a/editors/code/src/highlighting.ts
+++ b/editors/code/src/highlighting.ts
@@ -1,7 +1,5 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
3import * as seedrandom_ from 'seedrandom';
4const seedrandom = seedrandom_; // https://github.com/jvandemo/generator-angular2-library/issues/221#issuecomment-355945207
5 3
6import { ColorTheme, TextMateRuleSettings } from './color_theme'; 4import { ColorTheme, TextMateRuleSettings } from './color_theme';
7 5
@@ -34,6 +32,7 @@ export function activateHighlighting(ctx: Ctx) {
34 32
35 vscode.workspace.onDidChangeConfiguration( 33 vscode.workspace.onDidChangeConfiguration(
36 _ => highlighter.removeHighlights(), 34 _ => highlighter.removeHighlights(),
35 null,
37 ctx.subscriptions, 36 ctx.subscriptions,
38 ); 37 );
39 38
@@ -41,7 +40,7 @@ export function activateHighlighting(ctx: Ctx) {
41 async (editor: vscode.TextEditor | undefined) => { 40 async (editor: vscode.TextEditor | undefined) => {
42 if (!editor || editor.document.languageId !== 'rust') return; 41 if (!editor || editor.document.languageId !== 'rust') return;
43 if (!ctx.config.highlightingOn) return; 42 if (!ctx.config.highlightingOn) return;
44 let client = ctx.client; 43 const client = ctx.client;
45 if (!client) return; 44 if (!client) return;
46 45
47 const params: lc.TextDocumentIdentifier = { 46 const params: lc.TextDocumentIdentifier = {
@@ -54,6 +53,7 @@ export function activateHighlighting(ctx: Ctx) {
54 ); 53 );
55 highlighter.setHighlights(editor, decorations); 54 highlighter.setHighlights(editor, decorations);
56 }, 55 },
56 null,
57 ctx.subscriptions, 57 ctx.subscriptions,
58 ); 58 );
59} 59}
@@ -71,9 +71,9 @@ interface Decoration {
71 71
72// Based on this HSL-based color generator: https://gist.github.com/bendc/76c48ce53299e6078a76 72// Based on this HSL-based color generator: https://gist.github.com/bendc/76c48ce53299e6078a76
73function fancify(seed: string, shade: 'light' | 'dark') { 73function fancify(seed: string, shade: 'light' | 'dark') {
74 const random = seedrandom(seed); 74 const random = randomU32Numbers(hashString(seed));
75 const randomInt = (min: number, max: number) => { 75 const randomInt = (min: number, max: number) => {
76 return Math.floor(random() * (max - min + 1)) + min; 76 return Math.abs(random()) % (max - min + 1) + min;
77 }; 77 };
78 78
79 const h = randomInt(0, 360); 79 const h = randomInt(0, 360);
@@ -107,7 +107,7 @@ class Highlighter {
107 } 107 }
108 108
109 public setHighlights(editor: vscode.TextEditor, highlights: Decoration[]) { 109 public setHighlights(editor: vscode.TextEditor, highlights: Decoration[]) {
110 let client = this.ctx.client; 110 const client = this.ctx.client;
111 if (!client) return; 111 if (!client) return;
112 // Initialize decorations if necessary 112 // Initialize decorations if necessary
113 // 113 //
@@ -176,7 +176,7 @@ function initDecorations(): Map<string, vscode.TextEditorDecorationType> {
176 const res = new Map(); 176 const res = new Map();
177 TAG_TO_SCOPES.forEach((scopes, tag) => { 177 TAG_TO_SCOPES.forEach((scopes, tag) => {
178 if (!scopes) throw `unmapped tag: ${tag}`; 178 if (!scopes) throw `unmapped tag: ${tag}`;
179 let rule = theme.lookup(scopes); 179 const rule = theme.lookup(scopes);
180 const decor = createDecorationFromTextmate(rule); 180 const decor = createDecorationFromTextmate(rule);
181 res.set(tag, decor); 181 res.set(tag, decor);
182 }); 182 });
@@ -247,3 +247,23 @@ const TAG_TO_SCOPES = new Map<string, string[]>([
247 ["keyword.unsafe", ["keyword.other.unsafe"]], 247 ["keyword.unsafe", ["keyword.other.unsafe"]],
248 ["keyword.control", ["keyword.control"]], 248 ["keyword.control", ["keyword.control"]],
249]); 249]);
250
251function randomU32Numbers(seed: number) {
252 let random = seed | 0;
253 return () => {
254 random ^= random << 13;
255 random ^= random >> 17;
256 random ^= random << 5;
257 random |= 0;
258 return random;
259 };
260}
261
262function hashString(str: string): number {
263 let res = 0;
264 for (let i = 0; i < str.length; ++i) {
265 const c = str.codePointAt(i)!;
266 res = (res * 31 + c) & ~0;
267 }
268 return res;
269}
diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts
index 6357e44f1..1c019a51b 100644
--- a/editors/code/src/inlay_hints.ts
+++ b/editors/code/src/inlay_hints.ts
@@ -5,19 +5,27 @@ import { Ctx, sendRequestWithRetry } from './ctx';
5 5
6export function activateInlayHints(ctx: Ctx) { 6export function activateInlayHints(ctx: Ctx) {
7 const hintsUpdater = new HintsUpdater(ctx); 7 const hintsUpdater = new HintsUpdater(ctx);
8 vscode.window.onDidChangeVisibleTextEditors(async _ => { 8 vscode.window.onDidChangeVisibleTextEditors(
9 await hintsUpdater.refresh(); 9 async _ => hintsUpdater.refresh(),
10 }, ctx.subscriptions); 10 null,
11 11 ctx.subscriptions
12 vscode.workspace.onDidChangeTextDocument(async e => { 12 );
13 if (e.contentChanges.length === 0) return; 13
14 if (e.document.languageId !== 'rust') return; 14 vscode.workspace.onDidChangeTextDocument(
15 await hintsUpdater.refresh(); 15 async event => {
16 }, ctx.subscriptions); 16 if (event.contentChanges.length !== 0) return;
17 17 if (event.document.languageId !== 'rust') return;
18 vscode.workspace.onDidChangeConfiguration(_ => { 18 await hintsUpdater.refresh();
19 hintsUpdater.setEnabled(ctx.config.displayInlayHints); 19 },
20 }, ctx.subscriptions); 20 null,
21 ctx.subscriptions
22 );
23
24 vscode.workspace.onDidChangeConfiguration(
25 async _ => hintsUpdater.setEnabled(ctx.config.displayInlayHints),
26 null,
27 ctx.subscriptions
28 );
21 29
22 ctx.onDidRestart(_ => hintsUpdater.setEnabled(ctx.config.displayInlayHints)); 30 ctx.onDidRestart(_ => hintsUpdater.setEnabled(ctx.config.displayInlayHints));
23} 31}
@@ -127,13 +135,13 @@ class HintsUpdater {
127 } 135 }
128 136
129 private async queryHints(documentUri: string): Promise<InlayHint[] | null> { 137 private async queryHints(documentUri: string): Promise<InlayHint[] | null> {
130 let client = this.ctx.client; 138 const client = this.ctx.client;
131 if (!client) return null; 139 if (!client) return null;
132 const request: InlayHintsParams = { 140 const request: InlayHintsParams = {
133 textDocument: { uri: documentUri }, 141 textDocument: { uri: documentUri },
134 }; 142 };
135 let tokenSource = new vscode.CancellationTokenSource(); 143 const tokenSource = new vscode.CancellationTokenSource();
136 let prev = this.pending.get(documentUri); 144 const prev = this.pending.get(documentUri);
137 if (prev) prev.cancel(); 145 if (prev) prev.cancel();
138 this.pending.set(documentUri, tokenSource); 146 this.pending.set(documentUri, tokenSource);
139 try { 147 try {
diff --git a/editors/code/src/installation/download_file.ts b/editors/code/src/installation/download_file.ts
new file mode 100644
index 000000000..b51602ef9
--- /dev/null
+++ b/editors/code/src/installation/download_file.ts
@@ -0,0 +1,34 @@
1import fetch from "node-fetch";
2import * as fs from "fs";
3import { strict as assert } from "assert";
4
5/**
6 * Downloads file from `url` and stores it at `destFilePath`.
7 * `onProgress` callback is called on recieveing each chunk of bytes
8 * to track the progress of downloading, it gets the already read and total
9 * amount of bytes to read as its parameters.
10 */
11export async function downloadFile(
12 url: string,
13 destFilePath: fs.PathLike,
14 onProgress: (readBytes: number, totalBytes: number) => void
15): Promise<void> {
16 const response = await fetch(url);
17
18 const totalBytes = Number(response.headers.get('content-length'));
19 assert(!Number.isNaN(totalBytes), "Sanity check of content-length protocol");
20
21 let readBytes = 0;
22
23 console.log("Downloading file of", totalBytes, "bytes size from", url, "to", destFilePath);
24
25 return new Promise<void>((resolve, reject) => response.body
26 .on("data", (chunk: Buffer) => {
27 readBytes += chunk.length;
28 onProgress(readBytes, totalBytes);
29 })
30 .on("end", resolve)
31 .on("error", reject)
32 .pipe(fs.createWriteStream(destFilePath))
33 );
34}
diff --git a/editors/code/src/installation/fetch_latest_artifact_metadata.ts b/editors/code/src/installation/fetch_latest_artifact_metadata.ts
new file mode 100644
index 000000000..7e3700603
--- /dev/null
+++ b/editors/code/src/installation/fetch_latest_artifact_metadata.ts
@@ -0,0 +1,46 @@
1import fetch from "node-fetch";
2import { GithubRepo, ArtifactMetadata } from "./interfaces";
3
4const GITHUB_API_ENDPOINT_URL = "https://api.github.com";
5
6/**
7 * Fetches the latest release from GitHub `repo` and returns metadata about
8 * `artifactFileName` shipped with this release or `null` if no such artifact was published.
9 */
10export async function fetchLatestArtifactMetadata(
11 repo: GithubRepo, artifactFileName: string
12): Promise<null | ArtifactMetadata> {
13
14 const repoOwner = encodeURIComponent(repo.owner);
15 const repoName = encodeURIComponent(repo.name);
16
17 const apiEndpointPath = `/repos/${repoOwner}/${repoName}/releases/latest`;
18 const requestUrl = GITHUB_API_ENDPOINT_URL + apiEndpointPath;
19
20 // We skip runtime type checks for simplicity (here we cast from `any` to `GithubRelease`)
21
22 console.log("Issuing request for released artifacts metadata to", requestUrl);
23
24 const response: GithubRelease = await fetch(requestUrl, {
25 headers: { Accept: "application/vnd.github.v3+json" }
26 })
27 .then(res => res.json());
28
29 const artifact = response.assets.find(artifact => artifact.name === artifactFileName);
30
31 if (!artifact) return null;
32
33 return {
34 releaseName: response.name,
35 downloadUrl: artifact.browser_download_url
36 };
37
38 // We omit declaration of tremendous amount of fields that we are not using here
39 interface GithubRelease {
40 name: string;
41 assets: Array<{
42 name: string;
43 browser_download_url: string;
44 }>;
45 }
46}
diff --git a/editors/code/src/installation/interfaces.ts b/editors/code/src/installation/interfaces.ts
new file mode 100644
index 000000000..8039d0b90
--- /dev/null
+++ b/editors/code/src/installation/interfaces.ts
@@ -0,0 +1,55 @@
1export interface GithubRepo {
2 name: string;
3 owner: string;
4}
5
6/**
7 * Metadata about particular artifact retrieved from GitHub releases.
8 */
9export interface ArtifactMetadata {
10 releaseName: string;
11 downloadUrl: string;
12}
13
14/**
15 * Represents the source of a binary artifact which is either specified by the user
16 * explicitly, or bundled by this extension from GitHub releases.
17 */
18export type BinarySource = BinarySource.ExplicitPath | BinarySource.GithubRelease;
19
20export namespace BinarySource {
21 /**
22 * Type tag for `BinarySource` discriminated union.
23 */
24 export const enum Type { ExplicitPath, GithubRelease }
25
26 export interface ExplicitPath {
27 type: Type.ExplicitPath;
28
29 /**
30 * Filesystem path to the binary specified by the user explicitly.
31 */
32 path: string;
33 }
34
35 export interface GithubRelease {
36 type: Type.GithubRelease;
37
38 /**
39 * Repository where the binary is stored.
40 */
41 repo: GithubRepo;
42
43 /**
44 * Directory on the filesystem where the bundled binary is stored.
45 */
46 dir: string;
47
48 /**
49 * Name of the binary file. It is stored under the same name on GitHub releases
50 * and in local `.dir`.
51 */
52 file: string;
53 }
54
55}
diff --git a/editors/code/src/installation/language_server.ts b/editors/code/src/installation/language_server.ts
new file mode 100644
index 000000000..1ce67b8b2
--- /dev/null
+++ b/editors/code/src/installation/language_server.ts
@@ -0,0 +1,141 @@
1import * as vscode from "vscode";
2import * as path from "path";
3import { strict as assert } from "assert";
4import { promises as fs } from "fs";
5import { promises as dns } from "dns";
6import { spawnSync } from "child_process";
7import { throttle } from "throttle-debounce";
8
9import { BinarySource } from "./interfaces";
10import { fetchLatestArtifactMetadata } from "./fetch_latest_artifact_metadata";
11import { downloadFile } from "./download_file";
12
13export async function downloadLatestLanguageServer(
14 {file: artifactFileName, dir: installationDir, repo}: BinarySource.GithubRelease
15) {
16 const { releaseName, downloadUrl } = (await fetchLatestArtifactMetadata(
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 await downloadFile(downloadUrl, installationPath, throttle(
39 200,
40 /* noTrailing: */ true,
41 (readBytes, totalBytes) => {
42 const newPercentage = (readBytes / totalBytes) * 100;
43 progress.report({
44 message: newPercentage.toFixed(0) + "%",
45 increment: newPercentage - lastPrecentage
46 });
47
48 lastPrecentage = newPercentage;
49 })
50 );
51 }
52 );
53 console.timeEnd("Downloading ra_lsp_server");
54
55 await fs.chmod(installationPath, 0o755); // Set (rwx, r_x, r_x) permissions
56}
57export async function ensureLanguageServerBinary(
58 langServerSource: null | BinarySource
59): Promise<null | string> {
60
61 if (!langServerSource) {
62 vscode.window.showErrorMessage(
63 "Unfortunately we don't ship binaries for your platform yet. " +
64 "You need to manually clone rust-analyzer repository and " +
65 "run `cargo xtask install --server` to build the language server from sources. " +
66 "If you feel that your platform should be supported, please create an issue " +
67 "about that [here](https://github.com/rust-analyzer/rust-analyzer/issues) and we " +
68 "will consider it."
69 );
70 return null;
71 }
72
73 switch (langServerSource.type) {
74 case BinarySource.Type.ExplicitPath: {
75 if (isBinaryAvailable(langServerSource.path)) {
76 return langServerSource.path;
77 }
78
79 vscode.window.showErrorMessage(
80 `Unable to run ${langServerSource.path} binary. ` +
81 `To use the pre-built language server, set "rust-analyzer.raLspServerPath" ` +
82 "value to `null` or remove it from the settings to use it by default."
83 );
84 return null;
85 }
86 case BinarySource.Type.GithubRelease: {
87 const prebuiltBinaryPath = path.join(langServerSource.dir, langServerSource.file);
88
89 if (isBinaryAvailable(prebuiltBinaryPath)) {
90 return prebuiltBinaryPath;
91 }
92
93 const userResponse = await vscode.window.showInformationMessage(
94 "Language server binary for rust-analyzer was not found. " +
95 "Do you want to download it now?",
96 "Download now", "Cancel"
97 );
98 if (userResponse !== "Download now") return null;
99
100 try {
101 await downloadLatestLanguageServer(langServerSource);
102 } catch (err) {
103 await vscode.window.showErrorMessage(
104 `Failed to download language server from ${langServerSource.repo.name} ` +
105 `GitHub repository: ${err.message}`
106 );
107
108 await dns.resolve('www.google.com').catch(err => {
109 console.error("DNS resolution failed, there might be an issue with Internet availability");
110 console.error(err);
111 });
112
113 return null;
114 }
115
116 if (!isBinaryAvailable(prebuiltBinaryPath)) assert(false,
117 `Downloaded language server binary is not functional.` +
118 `Downloaded from: ${JSON.stringify(langServerSource)}`
119 );
120
121
122 vscode.window.showInformationMessage(
123 "Rust analyzer language server was successfully installed 🦀"
124 );
125
126 return prebuiltBinaryPath;
127 }
128 }
129
130 function isBinaryAvailable(binaryPath: string) {
131 const res = spawnSync(binaryPath, ["--version"]);
132
133 // ACHTUNG! `res` type declaration is inherently wrong, see
134 // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/42221
135
136 console.log("Checked binary availablity via --version", res);
137 console.log(binaryPath, "--version output:", res.output?.map(String));
138
139 return res.status === 0;
140 }
141}
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 0494ccf63..5efce41f4 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -6,12 +6,12 @@ import { activateStatusDisplay } from './status_display';
6import { Ctx } from './ctx'; 6import { Ctx } from './ctx';
7import { activateHighlighting } from './highlighting'; 7import { activateHighlighting } from './highlighting';
8 8
9let ctx!: Ctx; 9let ctx: Ctx | undefined;
10 10
11export async function activate(context: vscode.ExtensionContext) { 11export async function activate(context: vscode.ExtensionContext) {
12 ctx = new Ctx(context); 12 ctx = new Ctx(context);
13 13
14 // Commands which invokes manually via command pallet, shortcut, etc. 14 // Commands which invokes manually via command palette, shortcut, etc.
15 ctx.registerCommand('analyzerStatus', commands.analyzerStatus); 15 ctx.registerCommand('analyzerStatus', commands.analyzerStatus);
16 ctx.registerCommand('collectGarbage', commands.collectGarbage); 16 ctx.registerCommand('collectGarbage', commands.collectGarbage);
17 ctx.registerCommand('matchingBrace', commands.matchingBrace); 17 ctx.registerCommand('matchingBrace', commands.matchingBrace);
@@ -21,6 +21,7 @@ export async function activate(context: vscode.ExtensionContext) {
21 ctx.registerCommand('expandMacro', commands.expandMacro); 21 ctx.registerCommand('expandMacro', commands.expandMacro);
22 ctx.registerCommand('run', commands.run); 22 ctx.registerCommand('run', commands.run);
23 ctx.registerCommand('reload', commands.reload); 23 ctx.registerCommand('reload', commands.reload);
24 ctx.registerCommand('onEnter', commands.onEnter);
24 25
25 // Internal commands which are invoked by the server. 26 // Internal commands which are invoked by the server.
26 ctx.registerCommand('runSingle', commands.runSingle); 27 ctx.registerCommand('runSingle', commands.runSingle);
@@ -28,9 +29,6 @@ export async function activate(context: vscode.ExtensionContext) {
28 ctx.registerCommand('applySourceChange', commands.applySourceChange); 29 ctx.registerCommand('applySourceChange', commands.applySourceChange);
29 ctx.registerCommand('selectAndApplySourceChange', commands.selectAndApplySourceChange); 30 ctx.registerCommand('selectAndApplySourceChange', commands.selectAndApplySourceChange);
30 31
31 if (ctx.config.enableEnhancedTyping) {
32 ctx.overrideCommand('type', commands.onEnter);
33 }
34 activateStatusDisplay(ctx); 32 activateStatusDisplay(ctx);
35 33
36 activateHighlighting(ctx); 34 activateHighlighting(ctx);
diff --git a/editors/code/src/status_display.ts b/editors/code/src/status_display.ts
index c75fddf9d..51dbf388b 100644
--- a/editors/code/src/status_display.ts
+++ b/editors/code/src/status_display.ts
@@ -1,6 +1,6 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2 2
3import { WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressReport, WorkDoneProgressEnd } from 'vscode-languageclient'; 3import { WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressReport, WorkDoneProgressEnd, Disposable } from 'vscode-languageclient';
4 4
5import { Ctx } from './ctx'; 5import { Ctx } from './ctx';
6 6
@@ -9,15 +9,17 @@ const spinnerFrames = ['â ‹', 'â ™', 'â ¹', 'â ¸', 'â ¼', 'â ´', 'â ¦', 'â §', '
9export function activateStatusDisplay(ctx: Ctx) { 9export function activateStatusDisplay(ctx: Ctx) {
10 const statusDisplay = new StatusDisplay(ctx.config.cargoWatchOptions.command); 10 const statusDisplay = new StatusDisplay(ctx.config.cargoWatchOptions.command);
11 ctx.pushCleanup(statusDisplay); 11 ctx.pushCleanup(statusDisplay);
12 ctx.onDidRestart(client => { 12 ctx.onDidRestart(client => ctx.pushCleanup(client.onProgress(
13 client.onProgress(WorkDoneProgress.type, 'rustAnalyzer/cargoWatcher', params => statusDisplay.handleProgressNotification(params)); 13 WorkDoneProgress.type,
14 }); 14 'rustAnalyzer/cargoWatcher',
15 params => statusDisplay.handleProgressNotification(params)
16 )));
15} 17}
16 18
17class StatusDisplay implements vscode.Disposable { 19class StatusDisplay implements Disposable {
18 packageName?: string; 20 packageName?: string;
19 21
20 private i = 0; 22 private i: number = 0;
21 private statusBarItem: vscode.StatusBarItem; 23 private statusBarItem: vscode.StatusBarItem;
22 private command: string; 24 private command: string;
23 private timer?: NodeJS.Timeout; 25 private timer?: NodeJS.Timeout;
@@ -37,11 +39,8 @@ class StatusDisplay implements vscode.Disposable {
37 this.timer = 39 this.timer =
38 this.timer || 40 this.timer ||
39 setInterval(() => { 41 setInterval(() => {
40 if (this.packageName) { 42 this.tick();
41 this.statusBarItem!.text = `${this.frame()} cargo ${this.command} [${this.packageName}]`; 43 this.refreshLabel();
42 } else {
43 this.statusBarItem!.text = `${this.frame()} cargo ${this.command}`;
44 }
45 }, 300); 44 }, 300);
46 45
47 this.statusBarItem.show(); 46 this.statusBarItem.show();
@@ -65,6 +64,14 @@ class StatusDisplay implements vscode.Disposable {
65 this.statusBarItem.dispose(); 64 this.statusBarItem.dispose();
66 } 65 }
67 66
67 refreshLabel() {
68 if (this.packageName) {
69 this.statusBarItem!.text = `${spinnerFrames[this.i]} cargo ${this.command} [${this.packageName}]`;
70 } else {
71 this.statusBarItem!.text = `${spinnerFrames[this.i]} cargo ${this.command}`;
72 }
73 }
74
68 handleProgressNotification(params: WorkDoneProgressBegin | WorkDoneProgressReport | WorkDoneProgressEnd) { 75 handleProgressNotification(params: WorkDoneProgressBegin | WorkDoneProgressReport | WorkDoneProgressEnd) {
69 switch (params.kind) { 76 switch (params.kind) {
70 case 'begin': 77 case 'begin':
@@ -74,6 +81,7 @@ class StatusDisplay implements vscode.Disposable {
74 case 'report': 81 case 'report':
75 if (params.message) { 82 if (params.message) {
76 this.packageName = params.message; 83 this.packageName = params.message;
84 this.refreshLabel();
77 } 85 }
78 break; 86 break;
79 87
@@ -83,7 +91,7 @@ class StatusDisplay implements vscode.Disposable {
83 } 91 }
84 } 92 }
85 93
86 private frame() { 94 private tick() {
87 return spinnerFrames[(this.i = ++this.i % spinnerFrames.length)]; 95 this.i = (this.i + 1) % spinnerFrames.length;
88 } 96 }
89} 97}
diff --git a/editors/code/tsconfig.json b/editors/code/tsconfig.json
index e60eb8e5e..0c7702974 100644
--- a/editors/code/tsconfig.json
+++ b/editors/code/tsconfig.json
@@ -6,6 +6,8 @@
6 "lib": [ 6 "lib": [
7 "es2019" 7 "es2019"
8 ], 8 ],
9 "esModuleInterop": true,
10 "allowSyntheticDefaultImports": true,
9 "sourceMap": true, 11 "sourceMap": true,
10 "rootDir": "src", 12 "rootDir": "src",
11 "strict": true, 13 "strict": true,
diff --git a/editors/code/tslint.json b/editors/code/tslint.json
index 318e02b4b..333e2a321 100644
--- a/editors/code/tslint.json
+++ b/editors/code/tslint.json
@@ -3,6 +3,8 @@
3 "semicolon": [ 3 "semicolon": [
4 true, 4 true,
5 "always" 5 "always"
6 ] 6 ],
7 "prefer-const": true,
8 "no-floating-promises": true
7 } 9 }
8} 10}
diff --git a/editors/emacs/rust-analyzer.el b/editors/emacs/rust-analyzer.el
deleted file mode 100644
index 06db4f15f..000000000
--- a/editors/emacs/rust-analyzer.el
+++ /dev/null
@@ -1,286 +0,0 @@
1;;; rust-analyzer.el --- Rust analyzer emacs bindings for emacs-lsp -*- lexical-binding: t; -*-
2;;; Code:
3
4(require 'lsp)
5(require 'dash)
6(require 'ht)
7
8;; This currently
9;; - sets up rust-analyzer with emacs-lsp, giving
10;; - code actions
11;; - completion (use company-lsp for proper snippet support)
12;; - imenu support
13;; - on-type formatting
14;; - 'hover' type information & documentation (with lsp-ui)
15;; - implements source changes (for code actions etc.), except for file system changes
16;; - implements joinLines (you need to bind rust-analyzer-join-lines to a key)
17;; - implements selectionRanges (either bind lsp-extend-selection to a key, or use expand-region)
18;; - provides rust-analyzer-inlay-hints-mode for inline type hints
19;; - provides rust-analyzer-expand-macro to expand macros
20
21;; What's missing:
22;; - file system changes in apply-source-change
23;; - semantic highlighting
24;; - onEnter, parentModule, findMatchingBrace
25;; - runnables
26;; - the debugging commands (syntaxTree and analyzerStatus)
27;; - more
28
29;; Also, there's a problem with company-lsp's caching being too eager, sometimes
30;; resulting in outdated completions.
31
32(defcustom rust-analyzer-command '("ra_lsp_server")
33 ""
34 :type '(repeat (string)))
35
36(defconst rust-analyzer--notification-handlers
37 '(("rust-analyzer/publishDecorations" . (lambda (_w _p)))))
38
39(defconst rust-analyzer--action-handlers
40 '(("rust-analyzer.applySourceChange" .
41 (lambda (p) (rust-analyzer--apply-source-change-command p)))))
42
43(defun rust-analyzer--uri-filename (text-document)
44 (lsp--uri-to-path (gethash "uri" text-document)))
45
46(defun rust-analyzer--goto-lsp-loc (loc)
47 (-let (((&hash "line" "character") loc))
48 (goto-line (1+ line))
49 (move-to-column character)))
50
51(defun rust-analyzer--apply-text-document-edit (edit)
52 "Like lsp--apply-text-document-edit, but it allows nil version."
53 (let* ((ident (gethash "textDocument" edit))
54 (filename (rust-analyzer--uri-filename ident))
55 (version (gethash "version" ident)))
56 (with-current-buffer (find-file-noselect filename)
57 (when (or (not version) (= version (lsp--cur-file-version)))
58 (lsp--apply-text-edits (gethash "edits" edit))))))
59
60(defun rust-analyzer--apply-source-change (data)
61 ;; TODO fileSystemEdits
62 (seq-doseq (it (-> data (ht-get "workspaceEdit") (ht-get "documentChanges")))
63 (rust-analyzer--apply-text-document-edit it))
64 (-when-let (cursor-position (ht-get data "cursorPosition"))
65 (let ((filename (rust-analyzer--uri-filename (ht-get cursor-position "textDocument")))
66 (position (ht-get cursor-position "position")))
67 (find-file filename)
68 (rust-analyzer--goto-lsp-loc position))))
69
70(defun rust-analyzer--apply-source-change-command (p)
71 (let ((data (-> p (ht-get "arguments") (lsp-seq-first))))
72 (rust-analyzer--apply-source-change data)))
73
74(lsp-register-client
75 (make-lsp-client
76 :new-connection (lsp-stdio-connection (lambda () rust-analyzer-command))
77 :notification-handlers (ht<-alist rust-analyzer--notification-handlers)
78 :action-handlers (ht<-alist rust-analyzer--action-handlers)
79 :major-modes '(rust-mode)
80 :ignore-messages nil
81 :server-id 'rust-analyzer))
82
83(defun rust-analyzer--initialized? ()
84 (when-let ((workspace (lsp-find-workspace 'rust-analyzer (buffer-file-name))))
85 (eq 'initialized (lsp--workspace-status workspace))))
86
87(with-eval-after-load 'company-lsp
88 ;; company-lsp provides a snippet handler for rust by default that adds () after function calls, which RA does better
89 (setq company-lsp--snippet-functions (cl-delete "rust" company-lsp--snippet-functions :key #'car :test #'equal)))
90
91;; join lines
92
93(defun rust-analyzer--join-lines-params ()
94 "Join lines params."
95 (list :textDocument (lsp--text-document-identifier)
96 :range (if (use-region-p)
97 (lsp--region-to-range (region-beginning) (region-end))
98 (lsp--region-to-range (point) (point)))))
99
100(defun rust-analyzer-join-lines ()
101 (interactive)
102 (->
103 (lsp-send-request (lsp-make-request "rust-analyzer/joinLines"
104 (rust-analyzer--join-lines-params)))
105 (rust-analyzer--apply-source-change)))
106
107;; selection ranges
108
109(defun rust-analyzer--add-er-expansion ()
110 (make-variable-buffer-local 'er/try-expand-list)
111 (setq er/try-expand-list (append
112 er/try-expand-list
113 '(lsp-extend-selection))))
114
115(with-eval-after-load 'expand-region
116 ;; add the expansion for all existing rust-mode buffers. If expand-region is
117 ;; loaded lazily, it might be loaded when the first rust buffer is opened, and
118 ;; then it's too late for the hook for that buffer
119 (dolist (buf (buffer-list))
120 (with-current-buffer buf
121 (when (eq 'rust-mode major-mode)
122 (rust-analyzer--add-er-expansion))))
123 (add-hook 'rust-mode-hook 'rust-analyzer--add-er-expansion))
124
125;; runnables
126(defvar rust-analyzer--last-runnable nil)
127
128(defun rust-analyzer--runnables-params ()
129 (list :textDocument (lsp--text-document-identifier)
130 :position (lsp--cur-position)))
131
132(defun rust-analyzer--runnables ()
133 (lsp-send-request (lsp-make-request "rust-analyzer/runnables"
134 (rust-analyzer--runnables-params))))
135
136(defun rust-analyzer--select-runnable ()
137 (lsp--completing-read
138 "Select runnable:"
139 (if rust-analyzer--last-runnable
140 (cons rust-analyzer--last-runnable (rust-analyzer--runnables))
141 (rust-analyzer--runnables))
142 (-lambda ((&hash "label")) label)))
143
144(defun rust-analyzer-run (runnable)
145 (interactive (list (rust-analyzer--select-runnable)))
146 (-let (((&hash "env" "bin" "args" "label") runnable))
147 (compilation-start
148 (string-join (append (list bin) args '()) " ")
149 ;; cargo-process-mode is nice, but try to work without it...
150 (if (functionp 'cargo-process-mode) 'cargo-process-mode nil)
151 (lambda (_) (concat "*" label "*")))
152 (setq rust-analyzer--last-runnable runnable)))
153
154(defun rust-analyzer-rerun (&optional runnable)
155 (interactive (list (or rust-analyzer--last-runnable
156 (rust-analyzer--select-runnable))))
157 (rust-analyzer-run (or runnable rust-analyzer--last-runnable)))
158
159;; analyzer status buffer
160(define-derived-mode rust-analyzer-status-mode special-mode "Rust-Analyzer-Status"
161 "Mode for the rust-analyzer status buffer.")
162
163(defvar-local rust-analyzer--status-buffer-workspace nil)
164
165(defun rust-analyzer-status ()
166 "Displays status information for rust-analyzer."
167 (interactive)
168 (let* ((workspace (lsp-find-workspace 'rust-analyzer (buffer-file-name)))
169 (buf (get-buffer-create (concat "*rust-analyzer status " (with-lsp-workspace workspace (lsp-workspace-root)) "*"))))
170 (with-current-buffer buf
171 (rust-analyzer-status-mode)
172 (setq rust-analyzer--status-buffer-workspace workspace)
173 (rust-analyzer-status-buffer-refresh))
174 (pop-to-buffer buf)))
175
176(defun rust-analyzer-status-buffer-refresh ()
177 (interactive)
178 (when rust-analyzer--status-buffer-workspace
179 (let ((inhibit-read-only t))
180 (erase-buffer)
181 (insert (with-lsp-workspace rust-analyzer--status-buffer-workspace
182 (lsp-send-request (lsp-make-request
183 "rust-analyzer/analyzerStatus")))))))
184
185
186(defun rust-analyzer--syntax-tree-params ()
187 "Syntax tree params."
188 (list :textDocument (lsp--text-document-identifier)
189 :range (if (use-region-p)
190 (lsp--region-to-range (region-beginning) (region-end))
191 (lsp--region-to-range (point-min) (point-max)))))
192
193(defun rust-analyzer-syntax-tree ()
194 "Displays syntax tree for current buffer."
195 (interactive)
196 (when (eq 'rust-mode major-mode)
197 (let* ((workspace (lsp-find-workspace 'rust-analyzer (buffer-file-name)))
198 (buf (get-buffer-create (concat "*rust-analyzer syntax tree " (with-lsp-workspace workspace (lsp-workspace-root)) "*"))))
199 (when workspace
200 (let ((parse-result (with-lsp-workspace workspace
201 (lsp-send-request (lsp-make-request
202 "rust-analyzer/syntaxTree"
203 (rust-analyzer--syntax-tree-params))))))
204 (with-current-buffer buf
205 (let ((inhibit-read-only t))
206 (erase-buffer)
207 (insert parse-result)))
208 (pop-to-buffer buf))))))
209
210;; inlay hints
211(defun rust-analyzer--update-inlay-hints (buffer)
212 (if (and (rust-analyzer--initialized?) (eq buffer (current-buffer)))
213 (lsp-request-async
214 "rust-analyzer/inlayHints"
215 (list :textDocument (lsp--text-document-identifier))
216 (lambda (res)
217 (remove-overlays (point-min) (point-max) 'rust-analyzer--inlay-hint t)
218 (dolist (hint res)
219 (-let* (((&hash "range" "label" "kind") hint)
220 ((beg . end) (lsp--range-to-region range))
221 (overlay (make-overlay beg end)))
222 (overlay-put overlay 'rust-analyzer--inlay-hint t)
223 (overlay-put overlay 'evaporate t)
224 (cond
225 ((string= kind "TypeHint")
226 (overlay-put overlay 'after-string (propertize (concat ": " label)
227 'font-lock-face 'font-lock-comment-face)))
228 ((string= kind "ParameterHint")
229 (overlay-put overlay 'before-string (propertize (concat label ": ")
230 'font-lock-face 'font-lock-comment-face)))
231 )
232 )))
233 :mode 'tick))
234 nil)
235
236(defvar-local rust-analyzer--inlay-hints-timer nil)
237
238(defun rust-analyzer--inlay-hints-change-handler (&rest rest)
239 (when rust-analyzer--inlay-hints-timer
240 (cancel-timer rust-analyzer--inlay-hints-timer))
241 (setq rust-analyzer--inlay-hints-timer
242 (run-with-idle-timer 0.1 nil #'rust-analyzer--update-inlay-hints (current-buffer))))
243
244(define-minor-mode rust-analyzer-inlay-hints-mode
245 "Mode for showing inlay hints."
246 nil nil nil
247 (cond
248 (rust-analyzer-inlay-hints-mode
249 (rust-analyzer--update-inlay-hints (current-buffer))
250 (add-hook 'lsp-after-initialize-hook #'rust-analyzer--inlay-hints-change-handler nil t)
251 (add-hook 'after-change-functions #'rust-analyzer--inlay-hints-change-handler nil t))
252 (t
253 (remove-overlays (point-min) (point-max) 'rust-analyzer--inlay-hint t)
254 (remove-hook 'lsp-after-initialize-hook #'rust-analyzer--inlay-hints-change-handler t)
255 (remove-hook 'after-change-functions #'rust-analyzer--inlay-hints-change-handler t))))
256
257
258
259;; expand macros
260(defun rust-analyzer-expand-macro ()
261 "Expands the macro call at point recursively."
262 (interactive)
263 (when (eq 'rust-mode major-mode)
264 (let* ((workspace (lsp-find-workspace 'rust-analyzer (buffer-file-name)))
265 (params (list :textDocument (lsp--text-document-identifier)
266 :position (lsp--cur-position))))
267 (when workspace
268 (let* ((response (with-lsp-workspace workspace
269 (lsp-send-request (lsp-make-request
270 "rust-analyzer/expandMacro"
271 params))))
272 (result (when response (ht-get response "expansion"))))
273 (if result
274 (let ((buf (get-buffer-create (concat "*rust-analyzer macro expansion " (with-lsp-workspace workspace (lsp-workspace-root)) "*"))))
275 (with-current-buffer buf
276 (let ((inhibit-read-only t))
277 (erase-buffer)
278 (insert result)
279 (setq buffer-read-only t)
280 (special-mode)))
281 (pop-to-buffer buf))
282 (message "No macro found at point, or it could not be expanded")))))))
283
284
285(provide 'rust-analyzer)
286;;; rust-analyzer.el ends here