diff options
Diffstat (limited to 'editors')
-rw-r--r-- | editors/code/package-lock.json | 151 | ||||
-rw-r--r-- | editors/code/package.json | 48 | ||||
-rw-r--r-- | editors/code/rollup.config.js | 4 | ||||
-rw-r--r-- | editors/code/src/client.ts | 35 | ||||
-rw-r--r-- | editors/code/src/color_theme.ts | 28 | ||||
-rw-r--r-- | editors/code/src/commands/index.ts | 49 | ||||
-rw-r--r-- | editors/code/src/commands/on_enter.ts | 45 | ||||
-rw-r--r-- | editors/code/src/commands/syntax_tree.ts | 4 | ||||
-rw-r--r-- | editors/code/src/config.ts | 81 | ||||
-rw-r--r-- | editors/code/src/ctx.ts | 48 | ||||
-rw-r--r-- | editors/code/src/highlighting.ts | 34 | ||||
-rw-r--r-- | editors/code/src/inlay_hints.ts | 40 | ||||
-rw-r--r-- | editors/code/src/installation/download_file.ts | 34 | ||||
-rw-r--r-- | editors/code/src/installation/fetch_latest_artifact_metadata.ts | 46 | ||||
-rw-r--r-- | editors/code/src/installation/interfaces.ts | 55 | ||||
-rw-r--r-- | editors/code/src/installation/language_server.ts | 141 | ||||
-rw-r--r-- | editors/code/src/main.ts | 8 | ||||
-rw-r--r-- | editors/code/src/status_display.ts | 34 | ||||
-rw-r--r-- | editors/code/tsconfig.json | 2 | ||||
-rw-r--r-- | editors/code/tslint.json | 4 | ||||
-rw-r--r-- | editors/emacs/rust-analyzer.el | 286 |
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 @@ | |||
1 | import typescript from '@rollup/plugin-typescript'; | ||
2 | import resolve from '@rollup/plugin-node-resolve'; | 1 | import resolve from '@rollup/plugin-node-resolve'; |
3 | import commonjs from '@rollup/plugin-commonjs'; | 2 | import commonjs from '@rollup/plugin-commonjs'; |
4 | import nodeBuiltins from 'builtin-modules'; | 3 | import nodeBuiltins from 'builtin-modules'; |
5 | 4 | ||
6 | export default { | 5 | export 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 @@ | |||
1 | import { homedir } from 'os'; | ||
2 | import * as lc from 'vscode-languageclient'; | 1 | import * as lc from 'vscode-languageclient'; |
3 | import { spawnSync } from 'child_process'; | ||
4 | 2 | ||
5 | import { window, workspace } from 'vscode'; | 3 | import { window, workspace } from 'vscode'; |
6 | import { Config } from './config'; | 4 | import { Config } from './config'; |
5 | import { ensureLanguageServerBinary } from './installation/language_server'; | ||
7 | 6 | ||
8 | export function createClient(config: Config): lc.LanguageClient { | 7 | export 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 | } |
89 | function 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 | ||
61 | function loadThemeNamed(themeName: string): ColorTheme { | 64 | function 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'; | |||
4 | import { Ctx, Cmd } from '../ctx'; | 4 | import { Ctx, Cmd } from '../ctx'; |
5 | import * as sourceChange from '../source_change'; | 5 | import * as sourceChange from '../source_change'; |
6 | 6 | ||
7 | import { analyzerStatus } from './analyzer_status'; | 7 | export * from './analyzer_status'; |
8 | import { matchingBrace } from './matching_brace'; | 8 | export * from './matching_brace'; |
9 | import { joinLines } from './join_lines'; | 9 | export * from './join_lines'; |
10 | import { onEnter } from './on_enter'; | 10 | export * from './on_enter'; |
11 | import { parentModule } from './parent_module'; | 11 | export * from './parent_module'; |
12 | import { syntaxTree } from './syntax_tree'; | 12 | export * from './syntax_tree'; |
13 | import { expandMacro } from './expand_macro'; | 13 | export * from './expand_macro'; |
14 | import { run, runSingle } from './runnables'; | 14 | export * from './runnables'; |
15 | 15 | ||
16 | function collectGarbage(ctx: Ctx): Cmd { | 16 | export 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 | ||
22 | function showReferences(ctx: Ctx): Cmd { | 22 | export 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 | ||
36 | function applySourceChange(ctx: Ctx): Cmd { | 36 | export 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 | ||
42 | function selectAndApplySourceChange(ctx: Ctx): Cmd { | 42 | export 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 | ||
54 | function reload(ctx: Ctx): Cmd { | 54 | export 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 | |||
61 | export { | ||
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 @@ | |||
1 | import * as vscode from 'vscode'; | ||
1 | import * as lc from 'vscode-languageclient'; | 2 | import * as lc from 'vscode-languageclient'; |
2 | 3 | ||
3 | import { applySourceChange, SourceChange } from '../source_change'; | 4 | import { applySourceChange, SourceChange } from '../source_change'; |
4 | import { Cmd, Ctx } from '../ctx'; | 5 | import { Cmd, Ctx } from '../ctx'; |
5 | 6 | ||
6 | export function onEnter(ctx: Ctx): Cmd { | 7 | async 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 | ), | 29 | export 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. |
58 | function afterLs(f: () => any) { | 60 | function 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 @@ | |||
1 | import * as os from "os"; | ||
1 | import * as vscode from 'vscode'; | 2 | import * as vscode from 'vscode'; |
3 | import { BinarySource } from "./installation/interfaces"; | ||
2 | 4 | ||
3 | const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; | 5 | const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; |
4 | 6 | ||
@@ -16,16 +18,17 @@ export interface CargoFeatures { | |||
16 | } | 18 | } |
17 | 19 | ||
18 | export class Config { | 20 | export 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 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as lc from 'vscode-languageclient'; | 2 | import * as lc from 'vscode-languageclient'; |
3 | |||
3 | import { Config } from './config'; | 4 | import { Config } from './config'; |
4 | import { createClient } from './client'; | 5 | import { 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 | ||
87 | export type Cmd = (...args: any[]) => any; | 76 | export interface Disposable { |
77 | dispose(): void; | ||
78 | } | ||
79 | export type Cmd = (...args: any[]) => unknown; | ||
88 | 80 | ||
89 | export async function sendRequestWithRetry<R>( | 81 | export 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 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as lc from 'vscode-languageclient'; | 2 | import * as lc from 'vscode-languageclient'; |
3 | import * as seedrandom_ from 'seedrandom'; | ||
4 | const seedrandom = seedrandom_; // https://github.com/jvandemo/generator-angular2-library/issues/221#issuecomment-355945207 | ||
5 | 3 | ||
6 | import { ColorTheme, TextMateRuleSettings } from './color_theme'; | 4 | import { 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 |
73 | function fancify(seed: string, shade: 'light' | 'dark') { | 73 | function 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 | |||
251 | function 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 | |||
262 | function 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 | ||
6 | export function activateInlayHints(ctx: Ctx) { | 6 | export 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 @@ | |||
1 | import fetch from "node-fetch"; | ||
2 | import * as fs from "fs"; | ||
3 | import { 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 | */ | ||
11 | export 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 @@ | |||
1 | import fetch from "node-fetch"; | ||
2 | import { GithubRepo, ArtifactMetadata } from "./interfaces"; | ||
3 | |||
4 | const 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 | */ | ||
10 | export 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 @@ | |||
1 | export interface GithubRepo { | ||
2 | name: string; | ||
3 | owner: string; | ||
4 | } | ||
5 | |||
6 | /** | ||
7 | * Metadata about particular artifact retrieved from GitHub releases. | ||
8 | */ | ||
9 | export 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 | */ | ||
18 | export type BinarySource = BinarySource.ExplicitPath | BinarySource.GithubRelease; | ||
19 | |||
20 | export 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 @@ | |||
1 | import * as vscode from "vscode"; | ||
2 | import * as path from "path"; | ||
3 | import { strict as assert } from "assert"; | ||
4 | import { promises as fs } from "fs"; | ||
5 | import { promises as dns } from "dns"; | ||
6 | import { spawnSync } from "child_process"; | ||
7 | import { throttle } from "throttle-debounce"; | ||
8 | |||
9 | import { BinarySource } from "./interfaces"; | ||
10 | import { fetchLatestArtifactMetadata } from "./fetch_latest_artifact_metadata"; | ||
11 | import { downloadFile } from "./download_file"; | ||
12 | |||
13 | export 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 | } | ||
57 | export 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'; | |||
6 | import { Ctx } from './ctx'; | 6 | import { Ctx } from './ctx'; |
7 | import { activateHighlighting } from './highlighting'; | 7 | import { activateHighlighting } from './highlighting'; |
8 | 8 | ||
9 | let ctx!: Ctx; | 9 | let ctx: Ctx | undefined; |
10 | 10 | ||
11 | export async function activate(context: vscode.ExtensionContext) { | 11 | export 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 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | 2 | ||
3 | import { WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressReport, WorkDoneProgressEnd } from 'vscode-languageclient'; | 3 | import { WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressReport, WorkDoneProgressEnd, Disposable } from 'vscode-languageclient'; |
4 | 4 | ||
5 | import { Ctx } from './ctx'; | 5 | import { Ctx } from './ctx'; |
6 | 6 | ||
@@ -9,15 +9,17 @@ const spinnerFrames = ['â ‹', 'â ™', 'â ¹', 'â ¸', 'â ¼', 'â ´', 'â ¦', 'â §', ' | |||
9 | export function activateStatusDisplay(ctx: Ctx) { | 9 | export function activateStatusDisplay(ctx: Ctx) { |
10 | const statusDisplay = new StatusDisplay(ctx.config.cargoWatchOptions.command); | 10 | const statusDisplay = new StatusDisplay(ctx.config.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 | ||
17 | class StatusDisplay implements vscode.Disposable { | 19 | class 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 | ||