diff --git a/editors/code/.eslintrc.js b/editors/code/.eslintrc.js index c6bf410f4b56..82acd1580520 100644 --- a/editors/code/.eslintrc.js +++ b/editors/code/.eslintrc.js @@ -1,38 +1,52 @@ +// @ts-check + +/** @type { import('eslint').Linter.Config } */ module.exports = { - "env": { - "es6": true, - "node": true + env: { + es6: true, + node: true, }, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": "tsconfig.json", - "sourceType": "module" + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + sourceType: 'module', }, - "plugins": [ - "@typescript-eslint" + plugins: ['@typescript-eslint'], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking', + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + 'prettier', + 'prettier/@typescript-eslint', ], - "rules": { - "camelcase": ["error"], - "eqeqeq": ["error", "always", { "null": "ignore" }], - "no-console": ["error"], - "prefer-const": "error", - "@typescript-eslint/member-delimiter-style": [ - "error", + rules: { + camelcase: 'error', + eqeqeq: ['error', 'always', { null: 'ignore' }], + 'no-console': 'error', + 'prefer-const': 'error', + '@typescript-eslint/explicit-function-return-type': ['warn', { allowExpressions: true }], + '@typescript-eslint/member-delimiter-style': [ + 'error', { - "multiline": { - "delimiter": "semi", - "requireLast": true + multiline: { + delimiter: 'semi', + requireLast: true, }, - "singleline": { - "delimiter": "semi", - "requireLast": false - } - } - ], - "@typescript-eslint/semi": [ - "error", - "always" + singleline: { + delimiter: 'semi', + requireLast: false, + }, + }, ], - "@typescript-eslint/no-unnecessary-type-assertion": "error" - } + '@typescript-eslint/no-inferrable-types': 'off', + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-unnecessary-type-assertion': 'error', + '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/unbound-method': 'off', + '@typescript-eslint/semi': ['error', 'always'], + }, }; diff --git a/editors/code/.prettierignore b/editors/code/.prettierignore new file mode 100644 index 000000000000..1fcb1529f8e5 --- /dev/null +++ b/editors/code/.prettierignore @@ -0,0 +1 @@ +out diff --git a/editors/code/.prettierrc.js b/editors/code/.prettierrc.js new file mode 100644 index 000000000000..292f4825947d --- /dev/null +++ b/editors/code/.prettierrc.js @@ -0,0 +1,21 @@ +// @ts-check + +/** @type { import('prettier').Options } */ +module.exports = { + bracketSpacing: true, + jsxBracketSameLine: true, + printWidth: 100, + semi: true, + singleQuote: true, + tabWidth: 4, + trailingComma: 'all', + useTabs: false, + overrides: [ + { + files: '*.ts', + options: { + parser: 'typescript', + }, + }, + ], +}; diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json index 787f1b17a095..c9367bf4795a 100644 --- a/editors/code/package-lock.json +++ b/editors/code/package-lock.json @@ -50,6 +50,16 @@ "resolve": "^1.14.2" } }, + "@rollup/plugin-typescript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-4.0.0.tgz", + "integrity": "sha512-qA3r4WlR8JnTm+VdBzvQSIkfXt802keGxXuE4SAjUjRMKK3nMXTUCvOGSzFkav2qf0QiGv6yijfbjuf+bhwmZQ==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.0.1", + "resolve": "^1.14.1" + } + }, "@rollup/pluginutils": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.0.8.tgz", @@ -370,12 +380,6 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, - "commandpost": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/commandpost/-/commandpost-1.4.0.tgz", - "integrity": "sha512-aE2Y4MTFJ870NuB/+2z1cXBhSBBzRydVVjzhFC4gtenEhpnj15yu0qptWGJsO9YGrcPZ3ezX8AWb1VA391MKpQ==", - "dev": true - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -498,26 +502,6 @@ "domelementtype": "1" } }, - "editorconfig": { - "version": "0.15.3", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", - "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", - "dev": true, - "requires": { - "commander": "^2.19.0", - "lru-cache": "^4.1.5", - "semver": "^5.6.0", - "sigmund": "^1.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -589,6 +573,24 @@ } } }, + "eslint-config-prettier": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.10.0.tgz", + "integrity": "sha512-AtndijGte1rPILInUdHjvKEGbIV06NuvPrqlIEaEaWtbtvJh464mDeyGMdZEQMsGvC0ZVkiex1fSNcC4HAbRGg==", + "dev": true, + "requires": { + "get-stdin": "^6.0.0" + } + }, + "eslint-plugin-prettier": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz", + "integrity": "sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, "eslint-scope": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", @@ -684,6 +686,12 @@ "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", "dev": true }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -770,6 +778,12 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -1057,16 +1071,6 @@ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, "magic-string": { "version": "0.25.7", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", @@ -1309,18 +1313,27 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, + "prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -1446,12 +1459,6 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", - "dev": true - }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -1662,16 +1669,6 @@ "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", "dev": true }, - "typescript-formatter": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/typescript-formatter/-/typescript-formatter-7.2.2.tgz", - "integrity": "sha512-V7vfI9XArVhriOTYHPzMU2WUnm5IMdu9X/CPxs8mIMGxmTBFpDABlbkBka64PZJ9/xgQeRpK8KzzAG4MPzxBDQ==", - "dev": true, - "requires": { - "commandpost": "^1.0.0", - "editorconfig": "^0.15.0" - } - }, "uc.micro": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", @@ -1814,12 +1811,6 @@ "mkdirp": "^0.5.1" } }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - }, "yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", diff --git a/editors/code/package.json b/editors/code/package.json index 84642d11cd6f..d24392569922 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -28,8 +28,9 @@ "vscode:prepublish": "tsc && rollup -c", "package": "vsce package -o rust-analyzer.vsix", "watch": "tsc --watch", - "lint": "tsfmt --verify && eslint -c .eslintrc.js --ext ts ./src", - "fix": " tsfmt -r && eslint -c .eslintrc.js --ext ts ./src --fix" + "lint": "eslint 'src/**/*.{js,ts}'", + "fix": " eslint --fix 'src/**/*.{js,ts,tsx}'", + "format": "prettier --write '**/*.{js,json,ts,tsx,yml,yaml}'" }, "dependencies": { "jsonc-parser": "^2.2.1", @@ -39,16 +40,19 @@ "devDependencies": { "@rollup/plugin-commonjs": "^11.0.2", "@rollup/plugin-node-resolve": "^7.1.1", + "@rollup/plugin-typescript": "^4.0.0", "@types/node": "^12.12.30", "@types/node-fetch": "^2.5.5", "@types/vscode": "^1.43.0", "@typescript-eslint/eslint-plugin": "^2.24.0", "@typescript-eslint/parser": "^2.24.0", "eslint": "^6.8.0", + "eslint-config-prettier": "^6.10.0", + "eslint-plugin-prettier": "^3.1.2", + "prettier": "^1.19.1", "rollup": "^2.1.0", "tslib": "^1.11.1", "typescript": "^3.8.3", - "typescript-formatter": "^7.2.2", "vsce": "^1.74.0" }, "activationEvents": [ diff --git a/editors/code/rollup.config.js b/editors/code/rollup.config.js index 337385a2458e..276b4ac24dfa 100644 --- a/editors/code/rollup.config.js +++ b/editors/code/rollup.config.js @@ -6,19 +6,27 @@ export default { input: 'out/main.js', plugins: [ resolve({ - preferBuiltins: true + preferBuiltins: true, }), commonjs({ namedExports: { // squelch missing import warnings - 'vscode-languageclient': ['CreateFile', 'RenameFile', 'ErrorCodes', 'WorkDoneProgress', 'WorkDoneProgressBegin', 'WorkDoneProgressReport', 'WorkDoneProgressEnd'] - } - }) + 'vscode-languageclient': [ + 'CreateFile', + 'RenameFile', + 'ErrorCodes', + 'WorkDoneProgress', + 'WorkDoneProgressBegin', + 'WorkDoneProgressReport', + 'WorkDoneProgressEnd', + ], + }, + }), ], external: [...nodeBuiltins, 'vscode'], output: { file: './out/main.js', format: 'cjs', - exports: 'named' - } + exports: 'named', + }, }; diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 08d821dd0a86..c25cb656c98f 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -3,9 +3,12 @@ import * as vscode from 'vscode'; import { Config } from './config'; import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.proposed'; -import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed'; +import { + SemanticTokensFeature, + DocumentSemanticsTokensSignature, +} from 'vscode-languageclient/lib/semanticTokens.proposed'; -export async function createClient(config: Config, serverPath: string): Promise { +export function createClient(config: Config, serverPath: string): lc.LanguageClient { // '.' Is the fallback if no folder is open // 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. @@ -45,17 +48,21 @@ export async function createClient(config: Config, serverPath: string): Promise< withSysroot: config.withSysroot, cargoFeatures: config.cargoFeatures, rustfmtArgs: config.rustfmtArgs, - vscodeLldb: vscode.extensions.getExtension("vadimcn.vscode-lldb") != null, + vscodeLldb: vscode.extensions.getExtension('vadimcn.vscode-lldb') != null, }, traceOutputChannel, middleware: { // Workaround for https://github.com/microsoft/vscode-languageserver-node/issues/576 - async provideDocumentSemanticTokens(document: vscode.TextDocument, token: vscode.CancellationToken, next: DocumentSemanticsTokensSignature) { + async provideDocumentSemanticTokens( + document: vscode.TextDocument, + token: vscode.CancellationToken, + next: DocumentSemanticsTokensSignature, + ) { const res = await next(document, token); if (res === undefined) throw new Error('busy'); return res; - } - } as any + }, + } as lc.Middleware, }; const res = new lc.LanguageClient( @@ -65,6 +72,7 @@ export async function createClient(config: Config, serverPath: string): Promise< clientOptions, ); + /* eslint-disable @typescript-eslint/ban-ts-ignore */ // HACK: This is an awful way of filtering out the decorations notifications // However, pending proper support, this is the most effecitve approach // Proper support for this would entail a change to vscode-languageclient to allow not notifying on certain messages @@ -75,12 +83,8 @@ export async function createClient(config: Config, serverPath: string): Promise< log: (messageOrDataObject: string | unknown, data?: string) => { if (typeof messageOrDataObject === 'string') { if ( - messageOrDataObject.includes( - 'rust-analyzer/publishDecorations', - ) || - messageOrDataObject.includes( - 'rust-analyzer/decorationsRequest', - ) + messageOrDataObject.includes('rust-analyzer/publishDecorations') || + messageOrDataObject.includes('rust-analyzer/decorationsRequest') ) { // Don't log publish decorations requests } else { @@ -93,6 +97,7 @@ export async function createClient(config: Config, serverPath: string): Promise< } }, }; + /* eslint-enable @typescript-eslint/ban-ts-ignore */ // To turn on all proposed features use: res.registerProposedFeatures(); // Here we want to just enable CallHierarchyFeature since it is available on stable. diff --git a/editors/code/src/color_theme.ts b/editors/code/src/color_theme.ts index 5b9327b28a4a..19b801a9581e 100644 --- a/editors/code/src/color_theme.ts +++ b/editors/code/src/color_theme.ts @@ -14,9 +14,7 @@ export class ColorTheme { static load(): ColorTheme { // Find out current color theme - const themeName = vscode.workspace - .getConfiguration('workbench') - .get('colorTheme'); + const themeName = vscode.workspace.getConfiguration('workbench').get('colorTheme'); if (typeof themeName !== 'string') { // console.warn('workbench.colorTheme is', themeName) @@ -28,9 +26,10 @@ export class ColorTheme { static fromRules(rules: TextMateRule[]): ColorTheme { const res = new ColorTheme(); for (const rule of rules) { - const scopes = typeof rule.scope === 'undefined' - ? [] - : typeof rule.scope === 'string' + const scopes = + typeof rule.scope === 'undefined' + ? [] + : typeof rule.scope === 'string' ? [rule.scope] : rule.scope; @@ -53,7 +52,7 @@ export class ColorTheme { return res; } - mergeFrom(other: ColorTheme) { + mergeFrom(other: ColorTheme): void { other.rules.forEach((value, key) => { const merged = mergeRuleSettings(this.rules.get(key), value); this.rules.set(key, merged); @@ -72,10 +71,10 @@ function loadThemeNamed(themeName: string): ColorTheme { const themePaths: string[] = vscode.extensions.all .filter(isTheme) - .flatMap( - ext => ext.packageJSON.contributes.themes - .filter((it: any) => (it.id || it.label) === themeName) - .map((it: any) => path.join(ext.extensionPath, it.path)) + .flatMap(ext => + ext.packageJSON.contributes.themes + .filter((it: { id: string; label: string }) => (it.id || it.label) === themeName) + .map((it: { path: string }) => path.join(ext.extensionPath, it.path)), ); const res = new ColorTheme(); @@ -83,13 +82,20 @@ function loadThemeNamed(themeName: string): ColorTheme { res.mergeFrom(loadThemeFile(themePath)); } - const globalCustomizations: any = vscode.workspace.getConfiguration('editor').get('tokenColorCustomizations'); + const globalCustomizations: + | undefined + | { textMateRules: undefined | TextMateRule[] } = vscode.workspace + .getConfiguration('editor') + .get('tokenColorCustomizations'); res.mergeFrom(ColorTheme.fromRules(globalCustomizations?.textMateRules ?? [])); - const themeCustomizations: any = vscode.workspace.getConfiguration('editor.tokenColorCustomizations').get(`[${themeName}]`); + const themeCustomizations: + | undefined + | { textMateRules: undefined | TextMateRule[] } = vscode.workspace + .getConfiguration('editor.tokenColorCustomizations') + .get(`[${themeName}]`); res.mergeFrom(ColorTheme.fromRules(themeCustomizations?.textMateRules ?? [])); - return res; } diff --git a/editors/code/src/commands/analyzer_status.ts b/editors/code/src/commands/analyzer_status.ts index 1c6ea399b1c7..2d98c3de3df3 100644 --- a/editors/code/src/commands/analyzer_status.ts +++ b/editors/code/src/commands/analyzer_status.ts @@ -4,15 +4,12 @@ import * as ra from '../rust-analyzer-api'; import { Ctx, Cmd } from '../ctx'; // Shows status of rust-analyzer (for debugging) -export function analyzerStatus(ctx: Ctx): Cmd { +export function analyzerStatus(ctx: Ctx): Cmd<[], Promise> { let poller: NodeJS.Timer | null = null; const tdcp = new TextDocumentContentProvider(ctx); ctx.pushCleanup( - vscode.workspace.registerTextDocumentContentProvider( - 'rust-analyzer-status', - tdcp, - ), + vscode.workspace.registerTextDocumentContentProvider('rust-analyzer-status', tdcp), ); ctx.pushCleanup({ @@ -28,25 +25,17 @@ export function analyzerStatus(ctx: Ctx): Cmd { poller = setInterval(() => tdcp.eventEmitter.fire(tdcp.uri), 1000); } const document = await vscode.workspace.openTextDocument(tdcp.uri); - return vscode.window.showTextDocument( - document, - vscode.ViewColumn.Two, - true, - ); + return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true); }; } -class TextDocumentContentProvider - implements vscode.TextDocumentContentProvider { +class TextDocumentContentProvider implements vscode.TextDocumentContentProvider { uri = vscode.Uri.parse('rust-analyzer-status://status'); eventEmitter = new vscode.EventEmitter(); - constructor(private readonly ctx: Ctx) { - } + constructor(private readonly ctx: Ctx) {} - provideTextDocumentContent( - _uri: vscode.Uri, - ): vscode.ProviderResult { + provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult { const editor = vscode.window.activeTextEditor; const client = this.ctx.client; if (!editor || !client) return ''; diff --git a/editors/code/src/commands/expand_macro.ts b/editors/code/src/commands/expand_macro.ts index 23f2ef1d5e36..a35a076d8fc1 100644 --- a/editors/code/src/commands/expand_macro.ts +++ b/editors/code/src/commands/expand_macro.ts @@ -6,23 +6,14 @@ import { Ctx, Cmd } from '../ctx'; // Opens the virtual file that will show the syntax tree // // The contents of the file come from the `TextDocumentContentProvider` -export function expandMacro(ctx: Ctx): Cmd { +export function expandMacro(ctx: Ctx): Cmd<[], Promise> { const tdcp = new TextDocumentContentProvider(ctx); - ctx.pushCleanup( - vscode.workspace.registerTextDocumentContentProvider( - 'rust-analyzer', - tdcp, - ), - ); + ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider('rust-analyzer', tdcp)); return async () => { const document = await vscode.workspace.openTextDocument(tdcp.uri); tdcp.eventEmitter.fire(tdcp.uri); - return vscode.window.showTextDocument( - document, - vscode.ViewColumn.Two, - true, - ); + return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true); }; } @@ -35,13 +26,11 @@ function codeFormat(expanded: ra.ExpandedMacro): string { return result; } -class TextDocumentContentProvider - implements vscode.TextDocumentContentProvider { +class TextDocumentContentProvider implements vscode.TextDocumentContentProvider { uri = vscode.Uri.parse('rust-analyzer://expandMacro/[EXPANSION].rs'); eventEmitter = new vscode.EventEmitter(); - constructor(private readonly ctx: Ctx) { - } + constructor(private readonly ctx: Ctx) {} async provideTextDocumentContent(_uri: vscode.Uri): Promise { const editor = vscode.window.activeTextEditor; diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts index bdb7fc3b03b8..d405c3a7b6aa 100644 --- a/editors/code/src/commands/index.ts +++ b/editors/code/src/commands/index.ts @@ -16,12 +16,12 @@ export * from './runnables'; export * from './ssr'; export * from './server_version'; -export function collectGarbage(ctx: Ctx): Cmd { +export function collectGarbage(ctx: Ctx): Cmd<[], Promise> { return async () => ctx.client.sendRequest(ra.collectGarbage, null); } -export function showReferences(ctx: Ctx): Cmd { - return (uri: string, position: lc.Position, locations: lc.Location[]) => { +export function showReferences(ctx: Ctx): Cmd<[string, lc.Position, lc.Location[]], void> { + return (uri, position, locations) => { const client = ctx.client; if (client) { vscode.commands.executeCommand( @@ -34,14 +34,14 @@ export function showReferences(ctx: Ctx): Cmd { }; } -export function applySourceChange(ctx: Ctx): Cmd { - return async (change: ra.SourceChange) => { +export function applySourceChange(ctx: Ctx): Cmd<[ra.SourceChange], Promise> { + return async change => { await sourceChange.applySourceChange(ctx, change); }; } -export function selectAndApplySourceChange(ctx: Ctx): Cmd { - return async (changes: ra.SourceChange[]) => { +export function selectAndApplySourceChange(ctx: Ctx): Cmd<[ra.SourceChange[]], Promise> { + return async changes => { if (changes.length === 1) { await sourceChange.applySourceChange(ctx, changes[0]); } else if (changes.length > 0) { diff --git a/editors/code/src/commands/join_lines.ts b/editors/code/src/commands/join_lines.ts index de0614653d6b..25a6dc14dc0e 100644 --- a/editors/code/src/commands/join_lines.ts +++ b/editors/code/src/commands/join_lines.ts @@ -3,7 +3,7 @@ import * as ra from '../rust-analyzer-api'; import { Ctx, Cmd } from '../ctx'; import { applySourceChange } from '../source_change'; -export function joinLines(ctx: Ctx): Cmd { +export function joinLines(ctx: Ctx): Cmd<[], Promise> { return async () => { const editor = ctx.activeRustEditor; const client = ctx.client; diff --git a/editors/code/src/commands/matching_brace.ts b/editors/code/src/commands/matching_brace.ts index a60776e2d4d1..ee58925a60de 100644 --- a/editors/code/src/commands/matching_brace.ts +++ b/editors/code/src/commands/matching_brace.ts @@ -3,7 +3,7 @@ import * as ra from '../rust-analyzer-api'; import { Ctx, Cmd } from '../ctx'; -export function matchingBrace(ctx: Ctx): Cmd { +export function matchingBrace(ctx: Ctx): Cmd<[], Promise> { return async () => { const editor = ctx.activeRustEditor; const client = ctx.client; @@ -11,14 +11,10 @@ export function matchingBrace(ctx: Ctx): Cmd { const response = await client.sendRequest(ra.findMatchingBrace, { textDocument: { uri: editor.document.uri.toString() }, - offsets: editor.selections.map(s => - client.code2ProtocolConverter.asPosition(s.active), - ), + offsets: editor.selections.map(s => client.code2ProtocolConverter.asPosition(s.active)), }); editor.selections = editor.selections.map((sel, idx) => { - const active = client.protocol2CodeConverter.asPosition( - response[idx], - ); + const active = client.protocol2CodeConverter.asPosition(response[idx]); const anchor = sel.isEmpty ? active : sel.anchor; return new vscode.Selection(anchor, active); }); diff --git a/editors/code/src/commands/on_enter.ts b/editors/code/src/commands/on_enter.ts index 285849db7074..3ebe7b1fd5f8 100644 --- a/editors/code/src/commands/on_enter.ts +++ b/editors/code/src/commands/on_enter.ts @@ -4,28 +4,28 @@ import * as ra from '../rust-analyzer-api'; import { applySourceChange } from '../source_change'; import { Cmd, Ctx } from '../ctx'; -async function handleKeypress(ctx: Ctx) { +async function handleKeypress(ctx: Ctx): Promise { const editor = ctx.activeRustEditor; const client = ctx.client; if (!editor || !client) return false; - const change = await client.sendRequest(ra.onEnter, { - textDocument: { uri: editor.document.uri.toString() }, - position: client.code2ProtocolConverter.asPosition( - editor.selection.active, - ), - }).catch(_error => { - // client.logFailedRequest(OnEnterRequest.type, error); - return null; - }); + const change = await client + .sendRequest(ra.onEnter, { + textDocument: { uri: editor.document.uri.toString() }, + position: client.code2ProtocolConverter.asPosition(editor.selection.active), + }) + .catch(_error => { + // client.logFailedRequest(OnEnterRequest.type, error); + return null; + }); if (!change) return false; await applySourceChange(ctx, change); return true; } -export function onEnter(ctx: Ctx): Cmd { +export function onEnter(ctx: Ctx): Cmd<[], Promise> { return async () => { if (await handleKeypress(ctx)) return; diff --git a/editors/code/src/commands/parent_module.ts b/editors/code/src/commands/parent_module.ts index 8f78ddd71c63..9257b61be955 100644 --- a/editors/code/src/commands/parent_module.ts +++ b/editors/code/src/commands/parent_module.ts @@ -3,7 +3,7 @@ import * as ra from '../rust-analyzer-api'; import { Ctx, Cmd } from '../ctx'; -export function parentModule(ctx: Ctx): Cmd { +export function parentModule(ctx: Ctx): Cmd<[], Promise> { return async () => { const editor = ctx.activeRustEditor; const client = ctx.client; @@ -11,9 +11,7 @@ export function parentModule(ctx: Ctx): Cmd { const response = await client.sendRequest(ra.parentModule, { textDocument: { uri: editor.document.uri.toString() }, - position: client.code2ProtocolConverter.asPosition( - editor.selection.active, - ), + position: client.code2ProtocolConverter.asPosition(editor.selection.active), }); const loc = response[0]; if (loc == null) return; diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts index 357155163d76..166b568e00e6 100644 --- a/editors/code/src/commands/runnables.ts +++ b/editors/code/src/commands/runnables.ts @@ -4,7 +4,7 @@ import * as ra from '../rust-analyzer-api'; import { Ctx, Cmd } from '../ctx'; -export function run(ctx: Ctx): Cmd { +export function run(ctx: Ctx): Cmd<[], Promise> { let prevRunnable: RunnableQuickPick | undefined; return async () => { @@ -18,19 +18,14 @@ export function run(ctx: Ctx): Cmd { const runnables = await client.sendRequest(ra.runnables, { textDocument, - position: client.code2ProtocolConverter.asPosition( - editor.selection.active, - ), + position: client.code2ProtocolConverter.asPosition(editor.selection.active), }); const items: RunnableQuickPick[] = []; if (prevRunnable) { items.push(prevRunnable); } for (const r of runnables) { - if ( - prevRunnable && - JSON.stringify(prevRunnable.runnable) === JSON.stringify(r) - ) { + if (prevRunnable && JSON.stringify(prevRunnable.runnable) === JSON.stringify(r)) { continue; } items.push(new RunnableQuickPick(r)); @@ -45,8 +40,8 @@ export function run(ctx: Ctx): Cmd { }; } -export function runSingle(ctx: Ctx): Cmd { - return async (runnable: ra.Runnable) => { +export function runSingle(ctx: Ctx): Cmd<[ra.Runnable], Promise> { + return async runnable => { const editor = ctx.activeRustEditor; if (!editor) return; @@ -62,20 +57,20 @@ export function runSingle(ctx: Ctx): Cmd { }; } -export function debugSingle(ctx: Ctx): Cmd { - return async (config: ra.Runnable) => { +export function debugSingle(ctx: Ctx): Cmd<[ra.Runnable], Promise> { + return async config => { const editor = ctx.activeRustEditor; if (!editor) return; const debugConfig = { - type: "lldb", - request: "launch", + type: 'lldb', + request: 'launch', name: config.label, cargo: { args: config.args, }, args: config.extraArgs, - cwd: config.cwd + cwd: config.cwd, }; return vscode.debug.startDebugging(undefined, debugConfig); @@ -115,21 +110,10 @@ function createTask(spec: ra.Runnable): vscode.Task { cwd: spec.cwd || '.', env: definition.env, }; - const exec = new vscode.ShellExecution( - definition.command, - definition.args, - execOption, - ); + const exec = new vscode.ShellExecution(definition.command, definition.args, execOption); const f = vscode.workspace.workspaceFolders![0]; - const t = new vscode.Task( - definition, - f, - definition.label, - TASK_SOURCE, - exec, - ['$rustc'], - ); + const t = new vscode.Task(definition, f, definition.label, TASK_SOURCE, exec, ['$rustc']); t.presentationOptions.clear = true; return t; } diff --git a/editors/code/src/commands/server_version.ts b/editors/code/src/commands/server_version.ts index 83b1acf67709..7fd6b07c6e79 100644 --- a/editors/code/src/commands/server_version.ts +++ b/editors/code/src/commands/server_version.ts @@ -3,18 +3,20 @@ import { ensureServerBinary } from '../installation/server'; import { Ctx, Cmd } from '../ctx'; import { spawnSync } from 'child_process'; -export function serverVersion(ctx: Ctx): Cmd { +export function serverVersion(ctx: Ctx): Cmd<[], Promise> { return async () => { const binaryPath = await ensureServerBinary(ctx.config, ctx.state); if (binaryPath == null) { throw new Error( - "Rust Analyzer Language Server is not available. " + - "Please, ensure its [proper installation](https://rust-analyzer.github.io/manual.html#installation)." + String.prototype.concat( + 'Rust Analyzer Language Server is not available. ', + 'Please, ensure its [proper installation](https://rust-analyzer.github.io/manual.html#installation).', + ), ); } - const version = spawnSync(binaryPath, ["--version"], { encoding: "utf8" }).stdout; + const version = spawnSync(binaryPath, ['--version'], { encoding: 'utf8' }).stdout; vscode.window.showInformationMessage('rust-analyzer version : ' + version); }; } diff --git a/editors/code/src/commands/ssr.ts b/editors/code/src/commands/ssr.ts index 6fee051fdf66..d33cc5aaeec3 100644 --- a/editors/code/src/commands/ssr.ts +++ b/editors/code/src/commands/ssr.ts @@ -1,16 +1,16 @@ import * as vscode from 'vscode'; -import * as ra from "../rust-analyzer-api"; +import * as ra from '../rust-analyzer-api'; import { Ctx, Cmd } from '../ctx'; import { applySourceChange } from '../source_change'; -export function ssr(ctx: Ctx): Cmd { +export function ssr(ctx: Ctx): Cmd<[], Promise> { return async () => { const client = ctx.client; if (!client) return; const options: vscode.InputBoxOptions = { - value: "() ==>> ()", + value: '() ==>> ()', prompt: "EnteR request, for example 'Foo($a:expr) ==> Foo::new($a)' ", validateInput: async (x: string) => { try { @@ -19,7 +19,7 @@ export function ssr(ctx: Ctx): Cmd { return e.toString(); } return null; - } + }, }; const request = await vscode.window.showInputBox(options); diff --git a/editors/code/src/commands/syntax_tree.ts b/editors/code/src/commands/syntax_tree.ts index 2e08e8f115b6..b13fbb92e32c 100644 --- a/editors/code/src/commands/syntax_tree.ts +++ b/editors/code/src/commands/syntax_tree.ts @@ -7,15 +7,10 @@ import { isRustDocument } from '../util'; // Opens the virtual file that will show the syntax tree // // The contents of the file come from the `TextDocumentContentProvider` -export function syntaxTree(ctx: Ctx): Cmd { +export function syntaxTree(ctx: Ctx): Cmd<[], Promise> { const tdcp = new TextDocumentContentProvider(ctx); - ctx.pushCleanup( - vscode.workspace.registerTextDocumentContentProvider( - 'rust-analyzer', - tdcp, - ), - ); + ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider('rust-analyzer', tdcp)); vscode.workspace.onDidChangeTextDocument( (event: vscode.TextDocumentChangeEvent) => { @@ -40,35 +35,27 @@ export function syntaxTree(ctx: Ctx): Cmd { const editor = vscode.window.activeTextEditor; const rangeEnabled = !!(editor && !editor.selection.isEmpty); - const uri = rangeEnabled - ? vscode.Uri.parse(`${tdcp.uri.toString()}?range=true`) - : tdcp.uri; + const uri = rangeEnabled ? vscode.Uri.parse(`${tdcp.uri.toString()}?range=true`) : tdcp.uri; const document = await vscode.workspace.openTextDocument(uri); tdcp.eventEmitter.fire(uri); - return vscode.window.showTextDocument( - document, - vscode.ViewColumn.Two, - true, - ); + return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true); }; } // We need to order this after LS updates, but there's no API for that. // Hence, good old setTimeout. -function afterLs(f: () => void) { +function afterLs(f: () => void): void { setTimeout(f, 10); } - class TextDocumentContentProvider implements vscode.TextDocumentContentProvider { uri = vscode.Uri.parse('rust-analyzer://syntaxtree'); eventEmitter = new vscode.EventEmitter(); - constructor(private readonly ctx: Ctx) { - } + constructor(private readonly ctx: Ctx) {} provideTextDocumentContent(uri: vscode.Uri): vscode.ProviderResult { const editor = vscode.window.activeTextEditor; @@ -76,9 +63,10 @@ class TextDocumentContentProvider implements vscode.TextDocumentContentProvider if (!editor || !client) return ''; // When the range based query is enabled we take the range of the selection - const range = uri.query === 'range=true' && !editor.selection.isEmpty - ? client.code2ProtocolConverter.asRange(editor.selection) - : null; + const range = + uri.query === 'range=true' && !editor.selection.isEmpty + ? client.code2ProtocolConverter.asRange(editor.selection) + : null; return client.sendRequest(ra.syntaxTree, { textDocument: { uri: editor.document.uri.toString() }, diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index b45b14bef38d..3e666b7453ac 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -1,7 +1,7 @@ -import * as os from "os"; +import * as os from 'os'; import * as vscode from 'vscode'; -import { ArtifactSource } from "./installation/interfaces"; -import { log, vscodeReloadWindow } from "./util"; +import { ArtifactSource } from './installation/interfaces'; +import { log, vscodeReloadWindow } from './util'; const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; @@ -26,28 +26,24 @@ export interface CargoFeatures { } export const enum UpdatesChannel { - Stable = "stable", - Nightly = "nightly" + Stable = 'stable', + Nightly = 'nightly', } -export const NIGHTLY_TAG = "nightly"; +export const NIGHTLY_TAG = 'nightly'; export class Config { - readonly extensionId = "matklad.rust-analyzer"; + readonly extensionId = 'matklad.rust-analyzer'; - private readonly rootSection = "rust-analyzer"; + private readonly rootSection = 'rust-analyzer'; private readonly requiresReloadOpts = [ - "serverPath", - "cargoFeatures", - "cargo-watch", - "highlighting.semanticTokens", - "inlayHints", - ] - .map(opt => `${this.rootSection}.${opt}`); - - readonly packageJsonVersion = vscode - .extensions - .getExtension(this.extensionId)! - .packageJSON + 'serverPath', + 'cargoFeatures', + 'cargo-watch', + 'highlighting.semanticTokens', + 'inlayHints', + ].map(opt => `${this.rootSection}.${opt}`); + + readonly packageJsonVersion = vscode.extensions.getExtension(this.extensionId)!.packageJSON .version as string; // n.n.YYYYMMDD[-nightly] /** @@ -57,7 +53,7 @@ export class Config { if (this.packageJsonVersion.endsWith(NIGHTLY_TAG)) return NIGHTLY_TAG; const realVersionRegexp = /^\d+\.\d+\.(\d{4})(\d{2})(\d{2})/; - const [, yyyy, mm, dd] = this.packageJsonVersion.match(realVersionRegexp)!; + const [, yyyy, mm, dd] = realVersionRegexp.exec(this.packageJsonVersion)!; return `${yyyy}-${mm}-${dd}`; })(); @@ -69,38 +65,35 @@ export class Config { this.refreshConfig(); } - private refreshConfig() { + private refreshConfig(): void { this.cfg = vscode.workspace.getConfiguration(this.rootSection); - const enableLogging = this.cfg.get("trace.extension") as boolean; + const enableLogging = this.cfg.get('trace.extension') as boolean; log.setEnabled(enableLogging); - log.debug( - "Extension version:", this.packageJsonVersion, - "using configuration:", this.cfg - ); + log.debug('Extension version:', this.packageJsonVersion, 'using configuration:', this.cfg); } - private async onConfigChange(event: vscode.ConfigurationChangeEvent) { + private async onConfigChange(event: vscode.ConfigurationChangeEvent): Promise { this.refreshConfig(); - const requiresReloadOpt = this.requiresReloadOpts.find( - opt => event.affectsConfiguration(opt) + const requiresReloadOpt = this.requiresReloadOpts.find(opt => + event.affectsConfiguration(opt), ); if (!requiresReloadOpt) return; const userResponse = await vscode.window.showInformationMessage( `Changing "${requiresReloadOpt}" requires a reload`, - "Reload now" + 'Reload now', ); - if (userResponse === "Reload now") { + if (userResponse === 'Reload now') { await vscodeReloadWindow(); } } - private static replaceTildeWithHomeDir(path: string) { - if (path.startsWith("~/")) { - return os.homedir() + path.slice("~".length); + private static replaceTildeWithHomeDir(path: string): string { + if (path.startsWith('~/')) { + return os.homedir() + path.slice('~'.length); } return path; } @@ -115,27 +108,31 @@ export class Config { // https://nodejs.org/api/process.html#process_process_arch switch (process.platform) { - - case "linux": { + case 'linux': { switch (process.arch) { - case "arm": - case "arm64": return null; + case 'arm': + case 'arm64': + return null; - default: return "rust-analyzer-linux"; + default: + return 'rust-analyzer-linux'; } } - case "darwin": return "rust-analyzer-mac"; - case "win32": return "rust-analyzer-windows.exe"; + case 'darwin': + return 'rust-analyzer-mac'; + case 'win32': + return 'rust-analyzer-windows.exe'; // Users on these platforms yet need to manually build from sources - case "aix": - case "android": - case "freebsd": - case "openbsd": - case "sunos": - case "cygwin": - case "netbsd": return null; + case 'aix': + case 'android': + case 'freebsd': + case 'openbsd': + case 'sunos': + case 'cygwin': + case 'netbsd': + return null; // The list of platforms is exhaustive (see `NodeJS.Platform` type definition) } } @@ -152,7 +149,7 @@ export class Config { if (serverPath) { return { type: ArtifactSource.Type.ExplicitPath, - path: Config.replaceTildeWithHomeDir(serverPath) + path: Config.replaceTildeWithHomeDir(serverPath), }; } @@ -160,10 +157,7 @@ export class Config { if (!prebuiltBinaryName) return null; - return this.createGithubReleaseSource( - prebuiltBinaryName, - this.extensionReleaseTag - ); + return this.createGithubReleaseSource(prebuiltBinaryName, this.extensionReleaseTag); } private createGithubReleaseSource(file: string, tag: string): ArtifactSource.GithubRelease { @@ -173,57 +167,85 @@ export class Config { tag, dir: this.ctx.globalStoragePath, repo: { - name: "rust-analyzer", - owner: "rust-analyzer", - } + name: 'rust-analyzer', + owner: 'rust-analyzer', + }, }; } get nightlyVsixSource(): ArtifactSource.GithubRelease { - return this.createGithubReleaseSource("rust-analyzer.vsix", NIGHTLY_TAG); + return this.createGithubReleaseSource('rust-analyzer.vsix', NIGHTLY_TAG); } // We don't do runtime config validation here for simplicity. More on stackoverflow: // https://stackoverflow.com/questions/60135780/what-is-the-best-way-to-type-check-the-configuration-for-vscode-extension - private get serverPath() { return this.cfg.get("serverPath") as null | string; } - get updatesChannel() { return this.cfg.get("updates.channel") as UpdatesChannel; } - get askBeforeDownload() { return this.cfg.get("updates.askBeforeDownload") as boolean; } - get highlightingSemanticTokens() { return this.cfg.get("highlighting.semanticTokens") as boolean; } - get highlightingOn() { return this.cfg.get("highlightingOn") as boolean; } - get rainbowHighlightingOn() { return this.cfg.get("rainbowHighlightingOn") as boolean; } - get lruCapacity() { return this.cfg.get("lruCapacity") as null | number; } + /* eslint-disable @typescript-eslint/explicit-function-return-type */ + private get serverPath() { + return this.cfg.get('serverPath') as null | string; + } + get updatesChannel() { + return this.cfg.get('updates.channel') as UpdatesChannel; + } + get askBeforeDownload() { + return this.cfg.get('updates.askBeforeDownload') as boolean; + } + get highlightingSemanticTokens() { + return this.cfg.get('highlighting.semanticTokens') as boolean; + } + get highlightingOn() { + return this.cfg.get('highlightingOn') as boolean; + } + get rainbowHighlightingOn() { + return this.cfg.get('rainbowHighlightingOn') as boolean; + } + get lruCapacity() { + return this.cfg.get('lruCapacity') as null | number; + } get inlayHints(): InlayHintOptions { return { - typeHints: this.cfg.get("inlayHints.typeHints") as boolean, - parameterHints: this.cfg.get("inlayHints.parameterHints") as boolean, - maxLength: this.cfg.get("inlayHints.maxLength") as null | number, + typeHints: this.cfg.get('inlayHints.typeHints') as boolean, + parameterHints: this.cfg.get('inlayHints.parameterHints') as boolean, + maxLength: this.cfg.get('inlayHints.maxLength') as null | number, }; } - get excludeGlobs() { return this.cfg.get("excludeGlobs") as string[]; } - get useClientWatching() { return this.cfg.get("useClientWatching") as boolean; } - get featureFlags() { return this.cfg.get("featureFlags") as Record; } - get rustfmtArgs() { return this.cfg.get("rustfmtArgs") as string[]; } - get loadOutDirsFromCheck() { return this.cfg.get("loadOutDirsFromCheck") as boolean; } + get excludeGlobs() { + return this.cfg.get('excludeGlobs') as string[]; + } + get useClientWatching() { + return this.cfg.get('useClientWatching') as boolean; + } + get featureFlags() { + return this.cfg.get('featureFlags') as Record; + } + get rustfmtArgs() { + return this.cfg.get('rustfmtArgs') as string[]; + } + get loadOutDirsFromCheck() { + return this.cfg.get('loadOutDirsFromCheck') as boolean; + } get cargoWatchOptions(): CargoWatchOptions { return { - enable: this.cfg.get("cargo-watch.enable") as boolean, - arguments: this.cfg.get("cargo-watch.arguments") as string[], - allTargets: this.cfg.get("cargo-watch.allTargets") as boolean, - command: this.cfg.get("cargo-watch.command") as string, + enable: this.cfg.get('cargo-watch.enable') as boolean, + arguments: this.cfg.get('cargo-watch.arguments') as string[], + allTargets: this.cfg.get('cargo-watch.allTargets') as boolean, + command: this.cfg.get('cargo-watch.command') as string, }; } get cargoFeatures(): CargoFeatures { return { - noDefaultFeatures: this.cfg.get("cargoFeatures.noDefaultFeatures") as boolean, - allFeatures: this.cfg.get("cargoFeatures.allFeatures") as boolean, - features: this.cfg.get("cargoFeatures.features") as string[], - loadOutDirsFromCheck: this.cfg.get("cargoFeatures.loadOutDirsFromCheck") as boolean, + noDefaultFeatures: this.cfg.get('cargoFeatures.noDefaultFeatures') as boolean, + allFeatures: this.cfg.get('cargoFeatures.allFeatures') as boolean, + features: this.cfg.get('cargoFeatures.features') as string[], + loadOutDirsFromCheck: this.cfg.get('cargoFeatures.loadOutDirsFromCheck') as boolean, }; } // for internal use - get withSysroot() { return this.cfg.get("withSysroot", true) as boolean; } + get withSysroot() { + return this.cfg.get('withSysroot', true) as boolean; + } + /* eslint-enable @typescript-eslint/explicit-function-return-type */ } diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index c929ab0632d8..5d6bb0169071 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -11,13 +11,16 @@ export class Ctx { readonly config: Config, readonly state: PersistentState, private readonly extCtx: vscode.ExtensionContext, - readonly client: lc.LanguageClient - ) { - - } - - static async create(config: Config, state: PersistentState, extCtx: vscode.ExtensionContext, serverPath: string): Promise { - const client = await createClient(config, serverPath); + readonly client: lc.LanguageClient, + ) {} + + static async create( + config: Config, + state: PersistentState, + extCtx: vscode.ExtensionContext, + serverPath: string, + ): Promise { + const client = createClient(config, serverPath); const res = new Ctx(config, state, extCtx, client); res.pushCleanup(client.start()); await client.onReady(); @@ -26,16 +29,14 @@ export class Ctx { get activeRustEditor(): RustEditor | undefined { const editor = vscode.window.activeTextEditor; - return editor && isRustEditor(editor) - ? editor - : undefined; + return editor && isRustEditor(editor) ? editor : undefined; } get visibleRustEditors(): RustEditor[] { return vscode.window.visibleTextEditors.filter(isRustEditor); } - registerCommand(name: string, factory: (ctx: Ctx) => Cmd) { + registerCommand(name: string, factory: (ctx: Ctx) => Cmd): void { const fullName = `rust-analyzer.${name}`; const cmd = factory(this); const d = vscode.commands.registerCommand(fullName, cmd); @@ -50,7 +51,7 @@ export class Ctx { return this.extCtx.subscriptions; } - pushCleanup(d: Disposable) { + pushCleanup(d: Disposable): void { this.extCtx.subscriptions.push(d); } } @@ -58,4 +59,5 @@ export class Ctx { export interface Disposable { dispose(): void; } -export type Cmd = (...args: any[]) => unknown; + +export type Cmd = (...args: I) => O; diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts index ea2dfc0e39f6..38fa2d66f1d8 100644 --- a/editors/code/src/highlighting.ts +++ b/editors/code/src/highlighting.ts @@ -6,28 +6,23 @@ import { ColorTheme, TextMateRuleSettings } from './color_theme'; import { Ctx } from './ctx'; import { sendRequestWithRetry, isRustDocument } from './util'; -export function activateHighlighting(ctx: Ctx) { +export function activateHighlighting(ctx: Ctx): void { const highlighter = new Highlighter(ctx); ctx.client.onNotification(ra.publishDecorations, params => { if (!ctx.config.highlightingOn) return; - const targetEditor = vscode.window.visibleTextEditors.find( - editor => { - const unescapedUri = unescape( - editor.document.uri.toString(), - ); - // Unescaped URI looks like: - // file:///c:/Workspace/ra-test/src/main.rs - return unescapedUri === params.uri; - }, - ); + const targetEditor = vscode.window.visibleTextEditors.find(editor => { + const unescapedUri = unescape(editor.document.uri.toString()); + // Unescaped URI looks like: + // file:///c:/Workspace/ra-test/src/main.rs + return unescapedUri === params.uri; + }); if (!targetEditor) return; highlighter.setHighlights(targetEditor, params.decorations); }); - vscode.workspace.onDidChangeConfiguration( _ => highlighter.removeHighlights(), null, @@ -41,11 +36,9 @@ export function activateHighlighting(ctx: Ctx) { const client = ctx.client; if (!client) return; - const decorations = await sendRequestWithRetry( - client, - ra.decorationsRequest, - { uri: editor.document.uri.toString() }, - ); + const decorations = await sendRequestWithRetry(client, ra.decorationsRequest, { + uri: editor.document.uri.toString(), + }); highlighter.setHighlights(editor, decorations); }, null, @@ -54,10 +47,10 @@ export function activateHighlighting(ctx: Ctx) { } // Based on this HSL-based color generator: https://gist.github.com/bendc/76c48ce53299e6078a76 -function fancify(seed: string, shade: 'light' | 'dark') { +function fancify(seed: string, shade: 'light' | 'dark'): string { const random = randomU32Numbers(hashString(seed)); - const randomInt = (min: number, max: number) => { - return Math.abs(random()) % (max - min + 1) + min; + const randomInt = (min: number, max: number): number => { + return (Math.abs(random()) % (max - min + 1)) + min; }; const h = randomInt(0, 360); @@ -68,16 +61,13 @@ function fancify(seed: string, shade: 'light' | 'dark') { class Highlighter { private ctx: Ctx; - private decorations: Map< - string, - vscode.TextEditorDecorationType - > | null = null; + private decorations: Map | null = null; constructor(ctx: Ctx) { this.ctx = ctx; } - public removeHighlights() { + public removeHighlights(): void { if (this.decorations == null) { return; } @@ -90,7 +80,7 @@ class Highlighter { this.decorations = null; } - public setHighlights(editor: vscode.TextEditor, highlights: ra.Decoration[]) { + public setHighlights(editor: vscode.TextEditor, highlights: ra.Decoration[]): void { const client = this.ctx.client; if (!client) return; // Initialize decorations if necessary @@ -102,10 +92,7 @@ class Highlighter { } const byTag: Map = new Map(); - const colorfulIdents: Map< - string, - [vscode.Range[], boolean] - > = new Map(); + const colorfulIdents: Map = new Map(); const rainbowTime = this.ctx.config.rainbowHighlightingOn; for (const tag of this.decorations.keys()) { @@ -124,22 +111,14 @@ class Highlighter { } colorfulIdents .get(d.bindingHash)![0] - .push( - client.protocol2CodeConverter.asRange(d.range), - ); + .push(client.protocol2CodeConverter.asRange(d.range)); } else { - byTag - .get(d.tag)! - .push( - client.protocol2CodeConverter.asRange(d.range), - ); + byTag.get(d.tag)!.push(client.protocol2CodeConverter.asRange(d.range)); } } for (const tag of byTag.keys()) { - const dec = this.decorations.get( - tag, - ) as vscode.TextEditorDecorationType; + const dec = this.decorations.get(tag) as vscode.TextEditorDecorationType; const ranges = byTag.get(tag)!; editor.setDecorations(dec, ranges); } @@ -206,35 +185,35 @@ function createDecorationFromTextmate( // sync with tags from `syntax_highlighting.rs`. const TAG_TO_SCOPES = new Map([ - ["field", ["entity.name.field"]], - ["function", ["entity.name.function"]], - ["module", ["entity.name.module"]], - ["constant", ["entity.name.constant"]], - ["macro", ["entity.name.macro"]], - - ["variable", ["variable"]], - ["variable.mutable", ["variable", "meta.mutable"]], - - ["type", ["entity.name.type"]], - ["type.builtin", ["entity.name.type", "support.type.primitive"]], - ["type.self", ["entity.name.type.parameter.self"]], - ["type.param", ["entity.name.type.parameter", "entity.name.type.param.rust"]], - ["type.lifetime", ["entity.name.type.lifetime", "entity.name.lifetime.rust"]], - - ["literal.byte", ["constant.character.byte"]], - ["literal.char", ["constant.character.rust"]], - ["numeric_literal", ["constant.numeric"]], - - ["comment", ["comment"]], - ["string_literal", ["string.quoted"]], - ["attribute", ["meta.attribute.rust"]], - - ["keyword", ["keyword"]], - ["keyword.unsafe", ["keyword.other.unsafe"]], - ["keyword.control", ["keyword.control"]], + ['field', ['entity.name.field']], + ['function', ['entity.name.function']], + ['module', ['entity.name.module']], + ['constant', ['entity.name.constant']], + ['macro', ['entity.name.macro']], + + ['variable', ['variable']], + ['variable.mutable', ['variable', 'meta.mutable']], + + ['type', ['entity.name.type']], + ['type.builtin', ['entity.name.type', 'support.type.primitive']], + ['type.self', ['entity.name.type.parameter.self']], + ['type.param', ['entity.name.type.parameter', 'entity.name.type.param.rust']], + ['type.lifetime', ['entity.name.type.lifetime', 'entity.name.lifetime.rust']], + + ['literal.byte', ['constant.character.byte']], + ['literal.char', ['constant.character.rust']], + ['numeric_literal', ['constant.numeric']], + + ['comment', ['comment']], + ['string_literal', ['string.quoted']], + ['attribute', ['meta.attribute.rust']], + + ['keyword', ['keyword']], + ['keyword.unsafe', ['keyword.other.unsafe']], + ['keyword.control', ['keyword.control']], ]); -function randomU32Numbers(seed: number) { +function randomU32Numbers(seed: number): () => number { let random = seed | 0; return () => { random ^= random << 13; diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts index b19b09ad5619..eaef9c327708 100644 --- a/editors/code/src/inlay_hints.ts +++ b/editors/code/src/inlay_hints.ts @@ -1,66 +1,72 @@ -import * as lc from "vscode-languageclient"; +import * as lc from 'vscode-languageclient'; import * as vscode from 'vscode'; import * as ra from './rust-analyzer-api'; import { Ctx, Disposable } from './ctx'; import { sendRequestWithRetry, isRustDocument, RustDocument, RustEditor } from './util'; - -export function activateInlayHints(ctx: Ctx) { +export function activateInlayHints(ctx: Ctx): void { const maybeUpdater = { updater: null as null | HintsUpdater, - onConfigChange() { + onConfigChange(): void { if (!ctx.config.inlayHints.typeHints && !ctx.config.inlayHints.parameterHints) { return this.dispose(); } if (!this.updater) this.updater = new HintsUpdater(ctx); }, - dispose() { + dispose(): void { this.updater?.dispose(); this.updater = null; - } + }, }; ctx.pushCleanup(maybeUpdater); vscode.workspace.onDidChangeConfiguration( - maybeUpdater.onConfigChange, maybeUpdater, ctx.subscriptions + maybeUpdater.onConfigChange, + maybeUpdater, + ctx.subscriptions, ); maybeUpdater.onConfigChange(); } - const typeHints = { decorationType: vscode.window.createTextEditorDecorationType({ after: { color: new vscode.ThemeColor('rust_analyzer.inlayHint'), - fontStyle: "normal", - } + fontStyle: 'normal', + }, }), - toDecoration(hint: ra.InlayHint.TypeHint, conv: lc.Protocol2CodeConverter): vscode.DecorationOptions { + toDecoration( + hint: ra.InlayHint.TypeHint, + conv: lc.Protocol2CodeConverter, + ): vscode.DecorationOptions { return { range: conv.asRange(hint.range), - renderOptions: { after: { contentText: `: ${hint.label}` } } + renderOptions: { after: { contentText: `: ${hint.label}` } }, }; - } + }, }; const paramHints = { decorationType: vscode.window.createTextEditorDecorationType({ before: { color: new vscode.ThemeColor('rust_analyzer.inlayHint'), - fontStyle: "normal", - } + fontStyle: 'normal', + }, }), - toDecoration(hint: ra.InlayHint.ParamHint, conv: lc.Protocol2CodeConverter): vscode.DecorationOptions { + toDecoration( + hint: ra.InlayHint.ParamHint, + conv: lc.Protocol2CodeConverter, + ): vscode.DecorationOptions { return { range: conv.asRange(hint.range), - renderOptions: { before: { contentText: `${hint.label}: ` } } + renderOptions: { before: { contentText: `${hint.label}: ` } }, }; - } + }, }; class HintsUpdater implements Disposable { @@ -71,64 +77,69 @@ class HintsUpdater implements Disposable { vscode.window.onDidChangeVisibleTextEditors( this.onDidChangeVisibleTextEditors, this, - this.disposables + this.disposables, ); vscode.workspace.onDidChangeTextDocument( this.onDidChangeTextDocument, this, - this.disposables + this.disposables, ); // Set up initial cache shape - ctx.visibleRustEditors.forEach(editor => this.sourceFiles.set( - editor.document.uri.toString(), - { + ctx.visibleRustEditors.forEach(editor => + this.sourceFiles.set(editor.document.uri.toString(), { document: editor.document, inlaysRequest: null, - cachedDecorations: null - } - )); + cachedDecorations: null, + }), + ); this.syncCacheAndRenderHints(); } - dispose() { + dispose(): void { this.sourceFiles.forEach(file => file.inlaysRequest?.cancel()); - this.ctx.visibleRustEditors.forEach(editor => this.renderDecorations(editor, { param: [], type: [] })); + this.ctx.visibleRustEditors.forEach(editor => + this.renderDecorations(editor, { param: [], type: [] }), + ); this.disposables.forEach(d => d.dispose()); } - onDidChangeTextDocument({ contentChanges, document }: vscode.TextDocumentChangeEvent) { + onDidChangeTextDocument({ contentChanges, document }: vscode.TextDocumentChangeEvent): void { if (contentChanges.length === 0 || !isRustDocument(document)) return; this.syncCacheAndRenderHints(); } - private syncCacheAndRenderHints() { + private syncCacheAndRenderHints(): void { // FIXME: make inlayHints request pass an array of files? - this.sourceFiles.forEach((file, uri) => this.fetchHints(file).then(hints => { - if (!hints) return; - - file.cachedDecorations = this.hintsToDecorations(hints); - - for (const editor of this.ctx.visibleRustEditors) { - if (editor.document.uri.toString() === uri) { - this.renderDecorations(editor, file.cachedDecorations); - } - } - })); + this.sourceFiles.forEach( + (file, uri) => + void this.fetchHints(file).then(hints => { + if (!hints) return; + + file.cachedDecorations = this.hintsToDecorations(hints); + + for (const editor of this.ctx.visibleRustEditors) { + if (editor.document.uri.toString() === uri) { + this.renderDecorations(editor, file.cachedDecorations); + } + } + }), + ); } - onDidChangeVisibleTextEditors() { + onDidChangeVisibleTextEditors(): void { const newSourceFiles = new Map(); // Rerendering all, even up-to-date editors for simplicity + // eslint-disable-next-line @typescript-eslint/no-misused-promises this.ctx.visibleRustEditors.forEach(async editor => { const uri = editor.document.uri.toString(); const file = this.sourceFiles.get(uri) ?? { document: editor.document, inlaysRequest: null, - cachedDecorations: null + cachedDecorations: null, }; newSourceFiles.set(uri, file); @@ -153,7 +164,7 @@ class HintsUpdater implements Disposable { this.sourceFiles = newSourceFiles; } - private renderDecorations(editor: RustEditor, decorations: InlaysDecorations) { + private renderDecorations(editor: RustEditor, decorations: InlaysDecorations): void { editor.setDecorations(typeHints.decorationType, decorations.type); editor.setDecorations(paramHints.decorationType, decorations.param); } @@ -202,12 +213,12 @@ interface InlaysDecorations { interface RustSourceFile { /* - * Source of the token to cancel in-flight inlay hints request if any. - */ + * Source of the token to cancel in-flight inlay hints request if any. + */ inlaysRequest: null | vscode.CancellationTokenSource; /** - * Last applied decorations. - */ + * Last applied decorations. + */ cachedDecorations: null | InlaysDecorations; document: RustDocument; diff --git a/editors/code/src/installation/downloads.ts b/editors/code/src/installation/downloads.ts index 7ce2e2960f23..6bebc3f02c9a 100644 --- a/editors/code/src/installation/downloads.ts +++ b/editors/code/src/installation/downloads.ts @@ -1,11 +1,11 @@ -import fetch from "node-fetch"; -import * as vscode from "vscode"; -import * as path from "path"; -import * as fs from "fs"; -import * as stream from "stream"; -import * as util from "util"; -import { log, assert } from "../util"; -import { ArtifactReleaseInfo } from "./interfaces"; +import fetch from 'node-fetch'; +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as fs from 'fs'; +import * as stream from 'stream'; +import * as util from 'util'; +import { log, assert } from '../util'; +import { ArtifactReleaseInfo } from './interfaces'; const pipeline = util.promisify(stream.pipeline); @@ -19,24 +19,24 @@ export async function downloadFile( url: string, destFilePath: fs.PathLike, destFilePermissions: number, - onProgress: (readBytes: number, totalBytes: number) => void + onProgress: (readBytes: number, totalBytes: number) => void, ): Promise { const res = await fetch(url); if (!res.ok) { - log.error("Error", res.status, "while downloading file from", url); + log.error('Error', res.status, 'while downloading file from', url); log.error({ body: await res.text(), headers: res.headers }); throw new Error(`Got response ${res.status} when trying to download a file.`); } const totalBytes = Number(res.headers.get('content-length')); - assert(!Number.isNaN(totalBytes), "Sanity check of content-length protocol"); + assert(!Number.isNaN(totalBytes), 'Sanity check of content-length protocol'); - log.debug("Downloading file of", totalBytes, "bytes size from", url, "to", destFilePath); + log.debug('Downloading file of', totalBytes, 'bytes size from', url, 'to', destFilePath); let readBytes = 0; - res.body.on("data", (chunk: Buffer) => { + res.body.on('data', (chunk: Buffer) => { readBytes += chunk.length; onProgress(readBytes, totalBytes); }); @@ -45,7 +45,7 @@ export async function downloadFile( await pipeline(res.body, destFileStream); return new Promise(resolve => { - destFileStream.on("close", resolve); + destFileStream.on('close', resolve); destFileStream.destroy(); // Details on workaround: https://github.com/rust-analyzer/rust-analyzer/pull/3092#discussion_r378191131 @@ -65,12 +65,18 @@ export async function downloadArtifactWithProgressUi( artifactFileName: string, installationDir: string, displayName: string, -) { - await fs.promises.mkdir(installationDir).catch(err => assert( - err?.code === "EEXIST", - `Couldn't create directory "${installationDir}" to download ` + - `${artifactFileName} artifact: ${err?.message}` - )); +): Promise { + await fs.promises + .mkdir(installationDir) + .catch(err => + assert( + err?.code === 'EEXIST', + String.prototype.concat( + `Couldn't create directory "${installationDir}" to download `, + `${artifactFileName} artifact: ${err?.message}`, + ), + ), + ); const installationPath = path.join(installationDir, artifactFileName); @@ -78,20 +84,25 @@ export async function downloadArtifactWithProgressUi( { location: vscode.ProgressLocation.Notification, cancellable: false, // FIXME: add support for canceling download? - title: `Downloading rust-analyzer ${displayName} (${releaseName})` + title: `Downloading rust-analyzer ${displayName} (${releaseName})`, }, async (progress, _cancellationToken) => { let lastPrecentage = 0; const filePermissions = 0o755; // (rwx, r_x, r_x) - await downloadFile(downloadUrl, installationPath, filePermissions, (readBytes, totalBytes) => { - const newPercentage = (readBytes / totalBytes) * 100; - progress.report({ - message: newPercentage.toFixed(0) + "%", - increment: newPercentage - lastPrecentage - }); + await downloadFile( + downloadUrl, + installationPath, + filePermissions, + (readBytes, totalBytes) => { + const newPercentage = (readBytes / totalBytes) * 100; + progress.report({ + message: newPercentage.toFixed(0) + '%', + increment: newPercentage - lastPrecentage, + }); - lastPrecentage = newPercentage; - }); - } + lastPrecentage = newPercentage; + }, + ); + }, ); } diff --git a/editors/code/src/installation/extension.ts b/editors/code/src/installation/extension.ts index a1db96f052bf..d2d4e4c2ec96 100644 --- a/editors/code/src/installation/extension.ts +++ b/editors/code/src/installation/extension.ts @@ -1,13 +1,20 @@ -import * as vscode from "vscode"; -import * as path from "path"; +import * as vscode from 'vscode'; +import * as path from 'path'; import { promises as fs } from 'fs'; -import { vscodeReinstallExtension, vscodeReloadWindow, log, vscodeInstallExtensionFromVsix, assert, notReentrant } from "../util"; -import { Config, UpdatesChannel } from "../config"; -import { ArtifactReleaseInfo, ArtifactSource } from "./interfaces"; -import { downloadArtifactWithProgressUi } from "./downloads"; -import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info"; -import { PersistentState } from "../persistent_state"; +import { + vscodeReinstallExtension, + vscodeReloadWindow, + log, + vscodeInstallExtensionFromVsix, + assert, + notReentrant, +} from '../util'; +import { Config, UpdatesChannel } from '../config'; +import { ArtifactReleaseInfo, ArtifactSource } from './interfaces'; +import { downloadArtifactWithProgressUi } from './downloads'; +import { fetchArtifactReleaseInfo } from './fetch_artifact_release_info'; +import { PersistentState } from '../persistent_state'; const HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS = 25; @@ -15,7 +22,10 @@ const HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS = 25; * Installs `stable` or latest `nightly` version or does nothing if the current * extension version is what's needed according to `desiredUpdateChannel`. */ -export async function ensureProperExtensionVersion(config: Config, state: PersistentState): Promise { +export async function ensureProperExtensionVersion( + config: Config, + state: PersistentState, +): Promise { // User has built lsp server from sources, she should manage updates manually if (config.serverSource?.type === ArtifactSource.Type.ExplicitPath) return; @@ -31,14 +41,14 @@ export async function ensureProperExtensionVersion(config: Config, state: Persis // VSCode should handle updates for stable channel if (currentUpdChannel === UpdatesChannel.Stable) return; - if (!await askToDownloadProperExtensionVersion(config)) return; + if (!(await askToDownloadProperExtensionVersion(config))) return; await vscodeReinstallExtension(config.extensionId); await vscodeReloadWindow(); // never returns } if (currentUpdChannel === UpdatesChannel.Stable) { - if (!await askToDownloadProperExtensionVersion(config)) return; + if (!(await askToDownloadProperExtensionVersion(config))) return; return await tryDownloadNightlyExtension(config, state); } @@ -47,37 +57,51 @@ export async function ensureProperExtensionVersion(config: Config, state: Persis if (currentExtReleaseDate === null) { void vscode.window.showErrorMessage( - "Nightly release date must've been set during the installation. " + - "Did you download and install the nightly .vsix package manually?" + String.prototype.concat( + "Nightly release date must've been set during the installation. ", + 'Did you download and install the nightly .vsix package manually?', + ), ); - throw new Error("Nightly release date was not set in globalStorage"); + throw new Error('Nightly release date was not set in globalStorage'); } - const dateNow = new Date; + const dateNow = new Date(); const hoursSinceLastUpdate = diffInHours(currentExtReleaseDate, dateNow); log.debug( - "Current rust-analyzer nightly was downloaded", hoursSinceLastUpdate, - "hours ago, namely:", currentExtReleaseDate, "and now is", dateNow + 'Current rust-analyzer nightly was downloaded', + hoursSinceLastUpdate, + 'hours ago, namely:', + currentExtReleaseDate, + 'and now is', + dateNow, ); if (hoursSinceLastUpdate < HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS) { return; } - if (!await askToDownloadProperExtensionVersion(config, "The installed nightly version is most likely outdated. ")) { + if ( + !(await askToDownloadProperExtensionVersion( + config, + 'The installed nightly version is most likely outdated. ', + )) + ) { return; } await tryDownloadNightlyExtension(config, state, releaseInfo => { assert( - currentExtReleaseDate.getTime() === state.installedNightlyExtensionReleaseDate.get()?.getTime(), - "Other active VSCode instance has reinstalled the extension" + currentExtReleaseDate.getTime() === + state.installedNightlyExtensionReleaseDate.get()?.getTime(), + 'Other active VSCode instance has reinstalled the extension', ); if (releaseInfo.releaseDate.getTime() === currentExtReleaseDate.getTime()) { vscode.window.showInformationMessage( - "Whoops, it appears that your nightly version is up-to-date. " + - "There might be some problems with the upcomming nightly release " + - "or you traveled too far into the future. Sorry for that 😅! " + String.prototype.concat( + 'Whoops, it appears that your nightly version is up-to-date. ', + 'There might be some problems with the upcomming nightly release ', + 'or you traveled too far into the future. Sorry for that 😅! ', + ), ); return false; } @@ -85,10 +109,11 @@ export async function ensureProperExtensionVersion(config: Config, state: Persis }); } -async function askToDownloadProperExtensionVersion(config: Config, reason = "") { +async function askToDownloadProperExtensionVersion(config: Config, reason = ''): Promise { if (!config.askBeforeDownload) return true; - const stableOrNightly = config.updatesChannel === UpdatesChannel.Stable ? "stable" : "latest nightly"; + const stableOrNightly = + config.updatesChannel === UpdatesChannel.Stable ? 'stable' : 'latest nightly'; // In case of reentering this function and showing the same info message // (e.g. after we had shown this message, the user changed the config) @@ -96,11 +121,15 @@ async function askToDownloadProperExtensionVersion(config: Config, reason = "") // This behaviour is what we want, but likely it is not documented const userResponse = await vscode.window.showInformationMessage( - reason + `Do you want to download the ${stableOrNightly} rust-analyzer extension ` + - `version and reload the window now?`, - "Download now", "Cancel" + String.prototype.concat( + reason, + `Do you want to download the ${stableOrNightly} rust-analyzer extension `, + `version and reload the window now?`, + ), + 'Download now', + 'Cancel', ); - return userResponse === "Download now"; + return userResponse === 'Download now'; } /** @@ -110,30 +139,41 @@ async function askToDownloadProperExtensionVersion(config: Config, reason = "") * each of them would result in a ton of code (especially accounting for cross-process * shared mutable `globalState` access). Enforcing no reentrancy for this is best-effort. */ -const tryDownloadNightlyExtension = notReentrant(async ( - config: Config, - state: PersistentState, - shouldDownload: (releaseInfo: ArtifactReleaseInfo) => boolean = () => true -): Promise => { - const vsixSource = config.nightlyVsixSource; - try { - const releaseInfo = await fetchArtifactReleaseInfo(vsixSource.repo, vsixSource.file, vsixSource.tag); +const tryDownloadNightlyExtension = notReentrant( + async ( + config: Config, + state: PersistentState, + shouldDownload: (releaseInfo: ArtifactReleaseInfo) => boolean = () => true, + ): Promise => { + const vsixSource = config.nightlyVsixSource; + try { + const releaseInfo = await fetchArtifactReleaseInfo( + vsixSource.repo, + vsixSource.file, + vsixSource.tag, + ); - if (!shouldDownload(releaseInfo)) return; + if (!shouldDownload(releaseInfo)) return; - await downloadArtifactWithProgressUi(releaseInfo, vsixSource.file, vsixSource.dir, "nightly extension"); + await downloadArtifactWithProgressUi( + releaseInfo, + vsixSource.file, + vsixSource.dir, + 'nightly extension', + ); - const vsixPath = path.join(vsixSource.dir, vsixSource.file); + const vsixPath = path.join(vsixSource.dir, vsixSource.file); - await vscodeInstallExtensionFromVsix(vsixPath); - await state.installedNightlyExtensionReleaseDate.set(releaseInfo.releaseDate); - await fs.unlink(vsixPath); + await vscodeInstallExtensionFromVsix(vsixPath); + await state.installedNightlyExtensionReleaseDate.set(releaseInfo.releaseDate); + await fs.unlink(vsixPath); - await vscodeReloadWindow(); // never returns - } catch (err) { - log.downloadError(err, "nightly extension", vsixSource.repo.name); - } -}); + await vscodeReloadWindow(); // never returns + } catch (err) { + log.downloadError(err, 'nightly extension', vsixSource.repo.name); + } + }, +); function diffInHours(a: Date, b: Date): number { // Discard the time and time-zone information (to abstract from daylight saving time bugs) diff --git a/editors/code/src/installation/fetch_artifact_release_info.ts b/editors/code/src/installation/fetch_artifact_release_info.ts index 1ad3b8338ba9..bfc074c36824 100644 --- a/editors/code/src/installation/fetch_artifact_release_info.ts +++ b/editors/code/src/installation/fetch_artifact_release_info.ts @@ -1,8 +1,8 @@ -import fetch from "node-fetch"; -import { GithubRepo, ArtifactReleaseInfo } from "./interfaces"; -import { log } from "../util"; +import fetch from 'node-fetch'; +import { GithubRepo, ArtifactReleaseInfo } from './interfaces'; +import { log } from '../util'; -const GITHUB_API_ENDPOINT_URL = "https://api.github.com"; +const GITHUB_API_ENDPOINT_URL = 'https://api.github.com'; /** * Fetches the release with `releaseTag` from GitHub `repo` and @@ -14,9 +14,8 @@ const GITHUB_API_ENDPOINT_URL = "https://api.github.com"; export async function fetchArtifactReleaseInfo( repo: GithubRepo, artifactFileName: string, - releaseTag: string + releaseTag: string, ): Promise { - const repoOwner = encodeURIComponent(repo.owner); const repoName = encodeURIComponent(repo.name); @@ -24,12 +23,14 @@ export async function fetchArtifactReleaseInfo( const requestUrl = GITHUB_API_ENDPOINT_URL + apiEndpointPath; - log.debug("Issuing request for released artifacts metadata to", requestUrl); + log.debug('Issuing request for released artifacts metadata to', requestUrl); - const response = await fetch(requestUrl, { headers: { Accept: "application/vnd.github.v3+json" } }); + const response = await fetch(requestUrl, { + headers: { Accept: 'application/vnd.github.v3+json' }, + }); if (!response.ok) { - log.error("Error fetching artifact release info", { + log.error('Error fetching artifact release info', { requestUrl, releaseTag, artifactFileName, @@ -37,12 +38,12 @@ export async function fetchArtifactReleaseInfo( headers: response.headers, status: response.status, body: await response.text(), - } + }, }); throw new Error( `Got response ${response.status} when trying to fetch ` + - `"${artifactFileName}" artifact release info for ${releaseTag} release` + `"${artifactFileName}" artifact release info for ${releaseTag} release`, ); } @@ -52,15 +53,13 @@ export async function fetchArtifactReleaseInfo( const artifact = release.assets.find(artifact => artifact.name === artifactFileName); if (!artifact) { - throw new Error( - `Artifact ${artifactFileName} was not found in ${release.name} release!` - ); + throw new Error(`Artifact ${artifactFileName} was not found in ${release.name} release!`); } return { releaseName: release.name, releaseDate: new Date(release.published_at), - downloadUrl: artifact.browser_download_url + downloadUrl: artifact.browser_download_url, }; // We omit declaration of tremendous amount of fields that we are not using here diff --git a/editors/code/src/installation/interfaces.ts b/editors/code/src/installation/interfaces.ts index 1a8ea0884cb6..1b853bd92d90 100644 --- a/editors/code/src/installation/interfaces.ts +++ b/editors/code/src/installation/interfaces.ts @@ -22,7 +22,10 @@ export namespace ArtifactSource { /** * Type tag for `ArtifactSource` discriminated union. */ - export const enum Type { ExplicitPath, GithubRelease } + export const enum Type { + ExplicitPath, + GithubRelease, + } export interface ExplicitPath { type: Type.ExplicitPath; @@ -41,7 +44,6 @@ export namespace ArtifactSource { */ repo: GithubRepo; - // FIXME: add installationPath: string; /** diff --git a/editors/code/src/installation/server.ts b/editors/code/src/installation/server.ts index 05d32613158b..5520bcebce03 100644 --- a/editors/code/src/installation/server.ts +++ b/editors/code/src/installation/server.ts @@ -1,25 +1,30 @@ -import * as vscode from "vscode"; -import * as path from "path"; -import { spawnSync } from "child_process"; - -import { ArtifactSource } from "./interfaces"; -import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info"; -import { downloadArtifactWithProgressUi } from "./downloads"; -import { log, assert, notReentrant } from "../util"; -import { Config, NIGHTLY_TAG } from "../config"; -import { PersistentState } from "../persistent_state"; - -export async function ensureServerBinary(config: Config, state: PersistentState): Promise { +import * as vscode from 'vscode'; +import * as path from 'path'; +import { spawnSync } from 'child_process'; + +import { ArtifactSource } from './interfaces'; +import { fetchArtifactReleaseInfo } from './fetch_artifact_release_info'; +import { downloadArtifactWithProgressUi } from './downloads'; +import { log, assert, notReentrant } from '../util'; +import { Config, NIGHTLY_TAG } from '../config'; +import { PersistentState } from '../persistent_state'; + +export async function ensureServerBinary( + config: Config, + state: PersistentState, +): Promise { const source = config.serverSource; if (!source) { vscode.window.showErrorMessage( - "Unfortunately we don't ship binaries for your platform yet. " + - "You need to manually clone rust-analyzer repository and " + - "run `cargo xtask install --server` to build the language server from sources. " + - "If you feel that your platform should be supported, please create an issue " + - "about that [here](https://github.com/rust-analyzer/rust-analyzer/issues) and we " + - "will consider it." + String.prototype.concat( + "Unfortunately we don't ship binaries for your platform yet.", + 'You need to manually clone rust-analyzer repository and ', + 'run `cargo xtask install --server` to build the language server from sources. ', + 'If you feel that your platform should be supported, please create an issue ', + 'about that [here](https://github.com/rust-analyzer/rust-analyzer/issues) and we ', + 'will consider it.', + ), ); return null; } @@ -31,9 +36,11 @@ export async function ensureServerBinary(config: Config, state: PersistentState) } vscode.window.showErrorMessage( - `Unable to run ${source.path} binary. ` + - `To use the pre-built language server, set "rust-analyzer.serverPath" ` + - "value to `null` or remove it from the settings to use it by default." + String.prototype.concat( + `Unable to run ${source.path} binary. `, + `To use the pre-built language server, set "rust-analyzer.serverPath" `, + 'value to `null` or remove it from the settings to use it by default.', + ), ); return null; } @@ -44,11 +51,14 @@ export async function ensureServerBinary(config: Config, state: PersistentState) if (config.askBeforeDownload) { const userResponse = await vscode.window.showInformationMessage( - `Language server version ${source.tag} for rust-analyzer is not installed. ` + - "Do you want to download it now?", - "Download now", "Cancel" + String.prototype.concat( + `Language server version ${source.tag} for rust-analyzer is not installed. `, + 'Do you want to download it now?', + ), + 'Download now', + 'Cancel', ); - if (userResponse !== "Download now") return null; + if (userResponse !== 'Download now') return null; } return await downloadServer(state, source); @@ -64,21 +74,27 @@ function shouldDownloadServer( const installed = { tag: state.serverReleaseTag.get(), - date: state.serverReleaseDate.get() + date: state.serverReleaseDate.get(), }; const required = { tag: source.tag, - date: state.installedNightlyExtensionReleaseDate.get() + date: state.installedNightlyExtensionReleaseDate.get(), }; - log.debug("Installed server:", installed, "required:", required); + log.debug('Installed server:', installed, 'required:', required); if (required.tag !== NIGHTLY_TAG || installed.tag !== NIGHTLY_TAG) { return required.tag !== installed.tag; } - assert(required.date !== null, "Extension release date should have been saved during its installation"); - assert(installed.date !== null, "Server release date should have been saved during its installation"); + assert( + required.date !== null, + 'Extension release date should have been saved during its installation', + ); + assert( + installed.date !== null, + 'Server release date should have been saved during its installation', + ); return installed.date.getTime() !== required.date.getTime(); } @@ -86,46 +102,60 @@ function shouldDownloadServer( /** * Enforcing no reentrancy for this is best-effort. */ -const downloadServer = notReentrant(async ( - state: PersistentState, - source: ArtifactSource.GithubRelease, -): Promise => { - try { - const releaseInfo = await fetchArtifactReleaseInfo(source.repo, source.file, source.tag); - - await downloadArtifactWithProgressUi(releaseInfo, source.file, source.dir, "language server"); - await Promise.all([ - state.serverReleaseTag.set(releaseInfo.releaseName), - state.serverReleaseDate.set(releaseInfo.releaseDate) - ]); - } catch (err) { - log.downloadError(err, "language server", source.repo.name); - return null; - } +const downloadServer = notReentrant( + async ( + state: PersistentState, + source: ArtifactSource.GithubRelease, + ): Promise => { + try { + const releaseInfo = await fetchArtifactReleaseInfo( + source.repo, + source.file, + source.tag, + ); - const binaryPath = path.join(source.dir, source.file); + await downloadArtifactWithProgressUi( + releaseInfo, + source.file, + source.dir, + 'language server', + ); + await Promise.all([ + state.serverReleaseTag.set(releaseInfo.releaseName), + state.serverReleaseDate.set(releaseInfo.releaseDate), + ]); + } catch (err) { + log.downloadError(err, 'language server', source.repo.name); + return null; + } - assert(isBinaryAvailable(binaryPath), - `Downloaded language server binary is not functional.` + - `Downloaded from GitHub repo ${source.repo.owner}/${source.repo.name} ` + - `to ${binaryPath}` - ); + const binaryPath = path.join(source.dir, source.file); - vscode.window.showInformationMessage( - "Rust analyzer language server was successfully installed 🦀" - ); + assert( + isBinaryAvailable(binaryPath), + String.prototype.concat( + `Downloaded language server binary is not functional.`, + `Downloaded from GitHub repo ${source.repo.owner}/${source.repo.name} `, + `to ${binaryPath}`, + ), + ); + + vscode.window.showInformationMessage( + 'Rust analyzer language server was successfully installed 🦀', + ); - return binaryPath; -}); + return binaryPath; + }, +); function isBinaryAvailable(binaryPath: string): boolean { - const res = spawnSync(binaryPath, ["--version"]); + const res = spawnSync(binaryPath, ['--version']); // ACHTUNG! `res` type declaration is inherently wrong, see // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/42221 - log.debug("Checked binary availablity via --version", res); - log.debug(binaryPath, "--version output:", res.output?.map(String)); + log.debug('Checked binary availablity via --version', res); + log.debug(binaryPath, '--version output:', res.output?.map(String)); return res.status === 0; } diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 94ecd4dab64f..e036ffcc3d02 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -13,7 +13,7 @@ import { PersistentState } from './persistent_state'; let ctx: Ctx | undefined; -export async function activate(context: vscode.ExtensionContext) { +export async function activate(context: vscode.ExtensionContext): Promise { // Register a "dumb" onEnter command for the case where server fails to // start. // @@ -28,16 +28,17 @@ export async function activate(context: vscode.ExtensionContext) { // "rust-analyzer is not available" // ), // ) - const defaultOnEnter = vscode.commands.registerCommand( - 'rust-analyzer.onEnter', - () => vscode.commands.executeCommand('default:type', { text: '\n' }), + const defaultOnEnter = vscode.commands.registerCommand('rust-analyzer.onEnter', () => + vscode.commands.executeCommand('default:type', { text: '\n' }), ); context.subscriptions.push(defaultOnEnter); const config = new Config(context); const state = new PersistentState(context); - vscode.workspace.onDidChangeConfiguration(() => ensureProperExtensionVersion(config, state).catch(log.error)); + vscode.workspace.onDidChangeConfiguration(() => + ensureProperExtensionVersion(config, state).catch(log.error), + ); // Don't await the user response here, otherwise we will block the lsp server bootstrap void ensureProperExtensionVersion(config, state).catch(log.error); @@ -46,8 +47,8 @@ export async function activate(context: vscode.ExtensionContext) { if (serverPath == null) { throw new Error( - "Rust Analyzer Language Server is not available. " + - "Please, ensure its [proper installation](https://rust-analyzer.github.io/manual.html#installation)." + 'Rust Analyzer Language Server is not available. ' + + 'Please, ensure its [proper installation](https://rust-analyzer.github.io/manual.html#installation).', ); } @@ -58,8 +59,8 @@ export async function activate(context: vscode.ExtensionContext) { ctx = await Ctx.create(config, state, context, serverPath); // Commands which invokes manually via command palette, shortcut, etc. - ctx.registerCommand('reload', (ctx) => { - return async () => { + ctx.registerCommand('reload', ctx => { + return async (): Promise => { vscode.window.showInformationMessage('Reloading rust-analyzer...'); // @DanTup maneuver // https://github.com/microsoft/vscode/issues/45774#issuecomment-373423895 @@ -105,7 +106,7 @@ export async function activate(context: vscode.ExtensionContext) { activateInlayHints(ctx); } -export async function deactivate() { +export async function deactivate(): Promise { await ctx?.client?.stop(); ctx = undefined; } diff --git a/editors/code/src/persistent_state.ts b/editors/code/src/persistent_state.ts index 13095b806584..09802720fc79 100644 --- a/editors/code/src/persistent_state.ts +++ b/editors/code/src/persistent_state.ts @@ -1,33 +1,35 @@ import * as vscode from 'vscode'; -import { log } from "./util"; +import { log } from './util'; export class PersistentState { - constructor(private readonly ctx: vscode.ExtensionContext) { - } + constructor(private readonly ctx: vscode.ExtensionContext) {} readonly installedNightlyExtensionReleaseDate = new DateStorage( - "installed-nightly-extension-release-date", - this.ctx.globalState + 'installed-nightly-extension-release-date', + this.ctx.globalState, + ); + readonly serverReleaseDate = new DateStorage('server-release-date', this.ctx.globalState); + readonly serverReleaseTag = new Storage( + 'server-release-tag', + this.ctx.globalState, + null, ); - readonly serverReleaseDate = new DateStorage("server-release-date", this.ctx.globalState); - readonly serverReleaseTag = new Storage("server-release-tag", this.ctx.globalState, null); } - export class Storage { constructor( private readonly key: string, private readonly storage: vscode.Memento, - private readonly defaultVal: T - ) { } + private readonly defaultVal: T, + ) {} get(): T { const val = this.storage.get(this.key, this.defaultVal); - log.debug(this.key, "==", val); + log.debug(this.key, '==', val); return val; } - async set(val: T) { - log.debug(this.key, "=", val); + async set(val: T): Promise { + log.debug(this.key, '=', val); await this.storage.update(this.key, val); } } @@ -43,7 +45,7 @@ export class DateStorage { return dateStr ? new Date(dateStr) : null; } - async set(date: null | Date) { + async set(date: null | Date): Promise { await this.inner.set(date ? date.toString() : null); } } diff --git a/editors/code/src/rust-analyzer-api.ts b/editors/code/src/rust-analyzer-api.ts index 9846f7343176..db57610c37f7 100644 --- a/editors/code/src/rust-analyzer-api.ts +++ b/editors/code/src/rust-analyzer-api.ts @@ -2,32 +2,30 @@ * This file mirrors `crates/rust-analyzer/src/req.rs` declarations. */ -import * as lc from "vscode-languageclient"; +import * as lc from 'vscode-languageclient'; type Option = null | T; type Vec = T[]; type FxHashMap = Record; -function request(method: string) { - return new lc.RequestType(`rust-analyzer/${method}`); +function request( + method: string, +): lc.RequestType { + return new lc.RequestType(`rust-analyzer/${method}`); } -function notification(method: string) { - return new lc.NotificationType(method); +function notification(method: string): lc.NotificationType { + return new lc.NotificationType(method); } +export const analyzerStatus = request('analyzerStatus'); -export const analyzerStatus = request("analyzerStatus"); - - -export const collectGarbage = request("collectGarbage"); - +export const collectGarbage = request('collectGarbage'); export interface SyntaxTreeParams { textDocument: lc.TextDocumentIdentifier; range: Option; } -export const syntaxTree = request("syntaxTree"); - +export const syntaxTree = request('syntaxTree'); export interface ExpandMacroParams { textDocument: lc.TextDocumentIdentifier; @@ -37,15 +35,15 @@ export interface ExpandedMacro { name: string; expansion: string; } -export const expandMacro = request>("expandMacro"); - +export const expandMacro = request>('expandMacro'); export interface FindMatchingBraceParams { textDocument: lc.TextDocumentIdentifier; offsets: Vec; } -export const findMatchingBrace = request>("findMatchingBrace"); - +export const findMatchingBrace = request>( + 'findMatchingBrace', +); export interface PublishDecorationsParams { uri: string; @@ -56,20 +54,21 @@ export interface Decoration { tag: string; bindingHash: Option; } -export const decorationsRequest = request>("decorationsRequest"); - - -export const parentModule = request>("parentModule"); +export const decorationsRequest = request>( + 'decorationsRequest', +); +export const parentModule = request>( + 'parentModule', +); export interface JoinLinesParams { textDocument: lc.TextDocumentIdentifier; range: lc.Range; } -export const joinLines = request("joinLines"); - +export const joinLines = request('joinLines'); -export const onEnter = request>("onEnter"); +export const onEnter = request>('onEnter'); export interface RunnablesParams { textDocument: lc.TextDocumentIdentifier; @@ -84,14 +83,14 @@ export interface Runnable { env: FxHashMap; cwd: Option; } -export const runnables = request>("runnables"); +export const runnables = request>('runnables'); export type InlayHint = InlayHint.TypeHint | InlayHint.ParamHint; export namespace InlayHint { export const enum Kind { - TypeHint = "TypeHint", - ParamHint = "ParameterHint", + TypeHint = 'TypeHint', + ParamHint = 'ParameterHint', } interface Common { range: lc.Range; @@ -103,18 +102,15 @@ export namespace InlayHint { export interface InlayHintsParams { textDocument: lc.TextDocumentIdentifier; } -export const inlayHints = request>("inlayHints"); - +export const inlayHints = request>('inlayHints'); export interface SsrParams { query: string; parseOnly: boolean; } -export const ssr = request("ssr"); - - -export const publishDecorations = notification("publishDecorations"); +export const ssr = request('ssr'); +export const publishDecorations = notification('publishDecorations'); export interface SourceChange { label: string; diff --git a/editors/code/src/source_change.ts b/editors/code/src/source_change.ts index 399a150c6544..b75a72ab8166 100644 --- a/editors/code/src/source_change.ts +++ b/editors/code/src/source_change.ts @@ -4,13 +4,11 @@ import * as ra from './rust-analyzer-api'; import { Ctx } from './ctx'; -export async function applySourceChange(ctx: Ctx, change: ra.SourceChange) { +export async function applySourceChange(ctx: Ctx, change: ra.SourceChange): Promise { const client = ctx.client; if (!client) return; - const wsEdit = client.protocol2CodeConverter.asWorkspaceEdit( - change.workspaceEdit, - ); + const wsEdit = client.protocol2CodeConverter.asWorkspaceEdit(change.workspaceEdit); let created; let moved; if (change.workspaceEdit.documentChanges) { @@ -30,12 +28,8 @@ export async function applySourceChange(ctx: Ctx, change: ra.SourceChange) { const doc = await vscode.workspace.openTextDocument(toOpenUri); await vscode.window.showTextDocument(doc); } else if (toReveal) { - const uri = client.protocol2CodeConverter.asUri( - toReveal.textDocument.uri, - ); - const position = client.protocol2CodeConverter.asPosition( - toReveal.position, - ); + const uri = client.protocol2CodeConverter.asUri(toReveal.textDocument.uri); + const position = client.protocol2CodeConverter.asPosition(toReveal.position); const editor = vscode.window.activeTextEditor; if (!editor || editor.document.uri.toString() !== uri.toString()) { return; diff --git a/editors/code/src/status_display.ts b/editors/code/src/status_display.ts index 0f5f6ef99e64..1b610b93f58e 100644 --- a/editors/code/src/status_display.ts +++ b/editors/code/src/status_display.ts @@ -1,21 +1,27 @@ import * as vscode from 'vscode'; -import { WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressReport, WorkDoneProgressEnd, Disposable } from 'vscode-languageclient'; +import { + WorkDoneProgress, + WorkDoneProgressBegin, + WorkDoneProgressReport, + WorkDoneProgressEnd, + Disposable, +} from 'vscode-languageclient'; import { Ctx } from './ctx'; const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; -export function activateStatusDisplay(ctx: Ctx) { +export function activateStatusDisplay(ctx: Ctx): void { const statusDisplay = new StatusDisplay(ctx.config.cargoWatchOptions.command); ctx.pushCleanup(statusDisplay); const client = ctx.client; if (client != null) { - ctx.pushCleanup(client.onProgress( - WorkDoneProgress.type, - 'rustAnalyzer/cargoWatcher', - params => statusDisplay.handleProgressNotification(params) - )); + ctx.pushCleanup( + client.onProgress(WorkDoneProgress.type, 'rustAnalyzer/cargoWatcher', params => + statusDisplay.handleProgressNotification(params), + ), + ); } } @@ -28,15 +34,12 @@ class StatusDisplay implements Disposable { private timer?: NodeJS.Timeout; constructor(command: string) { - this.statusBarItem = vscode.window.createStatusBarItem( - vscode.StatusBarAlignment.Left, - 10, - ); + this.statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10); this.command = command; this.statusBarItem.hide(); } - show() { + show(): void { this.packageName = undefined; this.timer = @@ -49,7 +52,7 @@ class StatusDisplay implements Disposable { this.statusBarItem.show(); } - hide() { + hide(): void { if (this.timer) { clearInterval(this.timer); this.timer = undefined; @@ -58,7 +61,7 @@ class StatusDisplay implements Disposable { this.statusBarItem.hide(); } - dispose() { + dispose(): void { if (this.timer) { clearInterval(this.timer); this.timer = undefined; @@ -67,15 +70,19 @@ class StatusDisplay implements Disposable { this.statusBarItem.dispose(); } - refreshLabel() { + refreshLabel(): void { if (this.packageName) { - this.statusBarItem.text = `${spinnerFrames[this.i]} cargo ${this.command} [${this.packageName}]`; + this.statusBarItem.text = `${spinnerFrames[this.i]} cargo ${this.command} [${ + this.packageName + }]`; } else { this.statusBarItem.text = `${spinnerFrames[this.i]} cargo ${this.command}`; } } - handleProgressNotification(params: WorkDoneProgressBegin | WorkDoneProgressReport | WorkDoneProgressEnd) { + handleProgressNotification( + params: WorkDoneProgressBegin | WorkDoneProgressReport | WorkDoneProgressEnd, + ): void { switch (params.kind) { case 'begin': this.show(); @@ -94,7 +101,7 @@ class StatusDisplay implements Disposable { } } - private tick() { + private tick(): void { this.i = (this.i + 1) % spinnerFrames.length; } } diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts index 2bfc145e6fe5..21a2ca31b818 100644 --- a/editors/code/src/util.ts +++ b/editors/code/src/util.ts @@ -1,7 +1,7 @@ -import * as lc from "vscode-languageclient"; -import * as vscode from "vscode"; -import { promises as dns } from "dns"; -import { strict as nativeAssert } from "assert"; +import * as lc from 'vscode-languageclient'; +import * as vscode from 'vscode'; +import { promises as dns } from 'dns'; +import { strict as nativeAssert } from 'assert'; export function assert(condition: boolean, explanation: string): asserts condition { try { @@ -12,42 +12,46 @@ export function assert(condition: boolean, explanation: string): asserts conditi } } -export const log = new class { +export const log = new (class { private enabled = true; setEnabled(yes: boolean): void { log.enabled = yes; } - debug(message?: any, ...optionalParams: any[]): void { + debug(message?: unknown, ...optionalParams: unknown[]): void { if (!log.enabled) return; // eslint-disable-next-line no-console console.log(message, ...optionalParams); } - error(message?: any, ...optionalParams: any[]): void { + error(message?: unknown, ...optionalParams: unknown[]): void { if (!log.enabled) return; - debugger; - // eslint-disable-next-line no-console - console.error(message, ...optionalParams); + debugger; // eslint-disable-line no-debugger + console.error(message, ...optionalParams); // eslint-disable-line no-console } - downloadError(err: Error, artifactName: string, repoName: string) { + downloadError(err: Error, artifactName: string, repoName: string): void { vscode.window.showErrorMessage( - `Failed to download the rust-analyzer ${artifactName} from ${repoName} ` + - `GitHub repository: ${err.message}` + String.prototype.concat( + `Failed to download the rust-analyzer ${artifactName} from ${repoName} `, + `GitHub repository: ${err.message}`, + ), ); log.error(err); dns.resolve('example.com').then( - addrs => log.debug("DNS resolution for example.com was successful", addrs), - err => log.error( - "DNS resolution for example.com failed, " + - "there might be an issue with Internet availability", - err - ) + addrs => log.debug('DNS resolution for example.com was successful', addrs), + err => + log.error( + String.prototype.concat( + 'DNS resolution for example.com failed, ', + 'there might be an issue with Internet availability', + ), + err, + ), ); } -}; +})(); export async function sendRequestWithRetry( client: lc.LanguageClient, @@ -59,11 +63,10 @@ export async function sendRequestWithRetry( try { return await (token ? client.sendRequest(reqType, param, token) - : client.sendRequest(reqType, param) - ); + : client.sendRequest(reqType, param)); } catch (error) { if (delay === null) { - log.error("LSP request timed out", { method: reqType.method, param, error }); + log.error('LSP request timed out', { method: reqType.method, param, error }); throw error; } @@ -72,7 +75,7 @@ export async function sendRequestWithRetry( } if (error.code !== lc.ErrorCodes.ContentModified) { - log.error("LSP request failed", { method: reqType.method, param, error }); + log.error('LSP request failed', { method: reqType.method, param, error }); throw error; } @@ -82,29 +85,31 @@ export async function sendRequestWithRetry( throw 'unreachable'; } -function sleep(ms: number) { +function sleep(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } -export function notReentrant( - fn: (this: TThis, ...params: TParams) => Promise +export function notReentrant( + fn: (this: TThis, ...params: TParams) => Promise, ): typeof fn { let entered = false; - return function(...params) { + return function(...params): Promise { assert(!entered, `Reentrancy invariant for ${fn.name} is violated`); entered = true; - return fn.apply(this, params).finally(() => entered = false); + return fn.apply(this, params).finally(() => (entered = false)); }; } -export type RustDocument = vscode.TextDocument & { languageId: "rust" }; +export type RustDocument = vscode.TextDocument & { languageId: 'rust' }; export type RustEditor = vscode.TextEditor & { document: RustDocument; id: string }; export function isRustDocument(document: vscode.TextDocument): document is RustDocument { - return document.languageId === 'rust' + return ( + document.languageId === 'rust' && // SCM diff views have the same URI as the on-disk document but not the same content - && document.uri.scheme !== 'git' - && document.uri.scheme !== 'svn'; + document.uri.scheme !== 'git' && + document.uri.scheme !== 'svn' + ); } export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor { @@ -114,25 +119,25 @@ export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor { /** * @param extensionId The canonical extension identifier in the form of: `publisher.name` */ -export async function vscodeReinstallExtension(extensionId: string) { +export async function vscodeReinstallExtension(extensionId: string): Promise { // Unfortunately there is no straightforward way as of now, these commands // were found in vscode source code. - log.debug("Uninstalling extension", extensionId); - await vscode.commands.executeCommand("workbench.extensions.uninstallExtension", extensionId); - log.debug("Installing extension", extensionId); - await vscode.commands.executeCommand("workbench.extensions.installExtension", extensionId); + log.debug('Uninstalling extension', extensionId); + await vscode.commands.executeCommand('workbench.extensions.uninstallExtension', extensionId); + log.debug('Installing extension', extensionId); + await vscode.commands.executeCommand('workbench.extensions.installExtension', extensionId); } export async function vscodeReloadWindow(): Promise { - await vscode.commands.executeCommand("workbench.action.reloadWindow"); + await vscode.commands.executeCommand('workbench.action.reloadWindow'); - assert(false, "unreachable"); + assert(false, 'unreachable'); } -export async function vscodeInstallExtensionFromVsix(vsixPath: string) { +export async function vscodeInstallExtensionFromVsix(vsixPath: string): Promise { await vscode.commands.executeCommand( - "workbench.extensions.installExtension", - vscode.Uri.file(vsixPath) + 'workbench.extensions.installExtension', + vscode.Uri.file(vsixPath), ); } diff --git a/editors/code/tsconfig.json b/editors/code/tsconfig.json index 0c7702974a9c..e01b3762b338 100644 --- a/editors/code/tsconfig.json +++ b/editors/code/tsconfig.json @@ -3,9 +3,7 @@ "module": "commonjs", "target": "es2018", "outDir": "out", - "lib": [ - "es2019" - ], + "lib": ["es2019"], "esModuleInterop": true, "allowSyntheticDefaultImports": true, "sourceMap": true, @@ -17,7 +15,5 @@ "noFallthroughCasesInSwitch": true, "newLine": "LF" }, - "exclude": [ - "node_modules" - ] + "exclude": ["node_modules"] }