diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 2d148a26089ab..f8f974df25b08 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -270,6 +270,111 @@ "description": "%configuration.suggest.includeAutomaticOptionalChainCompletions%", "scope": "resource" }, + "typescript.inlayHints.parameterNames.enabled": { + "type": "string", + "enum": [ + "none", + "literals", + "all" + ], + "enumDescriptions": [ + "%inlayHints.parameterNames.none%", + "%inlayHints.parameterNames.literals%", + "%inlayHints.parameterNames.all%" + ], + "default": "none", + "description": "%configuration.inlayHints.parameterNames.enabled%", + "scope": "resource" + }, + "typescript.inlayHints.parameterNames.suppressWhenArgumentMatchesName": { + "type": "boolean", + "default": true, + "description": "%configuration.inlayHints.parameterNames.suppressWhenArgumentMatchesName%", + "scope": "resource" + }, + "typescript.inlayHints.parameterTypes.enabled": { + "type": "boolean", + "default": false, + "description": "%configuration.inlayHints.parameterTypes.enabled%", + "scope": "resource" + }, + "typescript.inlayHints.variableTypes.enabled": { + "type": "boolean", + "default": false, + "description": "%configuration.inlayHints.variableTypes.enabled%", + "scope": "resource" + }, + + "typescript.inlayHints.propertyDeclarationTypes.enabled": { + "type": "boolean", + "default": false, + "description": "%configuration.inlayHints.propertyDeclarationTypes.enabled%", + "scope": "resource" + }, + "typescript.inlayHints.functionLikeReturnTypes.enabled": { + "type": "boolean", + "default": false, + "description": "%configuration.inlayHints.functionLikeReturnTypes.enabled%", + "scope": "resource" + }, + "typescript.inlayHints.enumMemberValues.enabled": { + "type": "boolean", + "default": false, + "description": "%configuration.inlayHints.enumMemberValues.enabled%", + "scope": "resource" + }, + "javascript.inlayHints.parameterNames.enabled": { + "type": "string", + "enum": [ + "none", + "literals", + "all" + ], + "enumDescriptions": [ + "%inlayHints.parameterNames.none%", + "%inlayHints.parameterNames.literals%", + "%inlayHints.parameterNames.all%" + ], + "default": "none", + "description": "%configuration.inlayHints.parameterNames.enabled%", + "scope": "resource" + }, + "javascript.inlayHints.parameterNames.suppressWhenArgumentMatchesName": { + "type": "boolean", + "default": true, + "description": "%configuration.inlayHints.parameterNames.suppressWhenArgumentMatchesName%", + "scope": "resource" + }, + "javascript.inlayHints.parameterTypes.enabled": { + "type": "boolean", + "default": false, + "description": "%configuration.inlayHints.parameterTypes.enabled%", + "scope": "resource" + }, + "javascript.inlayHints.variableTypes.enabled": { + "type": "boolean", + "default": false, + "description": "%configuration.inlayHints.variableTypes.enabled%", + "scope": "resource" + }, + "javascript.inlayHints.propertyDeclarationTypes.enabled": { + "type": "boolean", + "default": false, + "description": "%configuration.inlayHints.propertyDeclarationTypes.enabled%", + "scope": "resource" + }, + "javascript.inlayHints.functionLikeReturnTypes.enabled": { + "type": "boolean", + "default": false, + "description": "%configuration.inlayHints.functionLikeReturnTypes.enabled%", + "scope": "resource" + }, + "javascript.inlayHints.enumMemberValues.enabled": { + "type": "boolean", + "default": false, + "description": "%configuration.inlayHints.enumMemberValues.enabled%", + "scope": "resource" + }, "javascript.suggest.includeCompletionsForImportStatements": { "type": "boolean", "default": true, diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 85c896bef3c26..7340a1ef8a91d 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -74,6 +74,16 @@ "configuration.implicitProjectConfig.strictFunctionTypes": "Enable/disable [strict function types](https://www.typescriptlang.org/tsconfig#strictFunctionTypes) in JavaScript and TypeScript files that are not part of a project. Existing `jsconfig.json` or `tsconfig.json` files override this setting.", "configuration.suggest.jsdoc.generateReturns": "Enable/disable generating `@return` annotations for JSDoc templates. Requires using TypeScript 4.2+ in the workspace.", "configuration.suggest.autoImports": "Enable/disable auto import suggestions.", + "inlayHints.parameterNames.none": "Disable parameter name hints.", + "inlayHints.parameterNames.literals": "Enable parameter name hints only for literal arguments.", + "inlayHints.parameterNames.all": "Enable parameter name hints for literal and non-literal arguments.", + "configuration.inlayHints.parameterNames.enabled": "Enable/disable inlay hints of parameter names.", + "configuration.inlayHints.parameterNames.suppressWhenArgumentMatchesName": "Suppress parameter name hints on arguments whose text is identical to the parameter name.", + "configuration.inlayHints.parameterTypes.enabled": "Enable/disable inlay hints of parameter types.", + "configuration.inlayHints.variableTypes.enabled": "Enable/disable inlay hints of variable types.", + "configuration.inlayHints.propertyDeclarationTypes.enabled": "Enable/disable inlay hints of property declarations.", + "configuration.inlayHints.functionLikeReturnTypes.enabled": "Enable/disable inlay hints of return type for function signatures.", + "configuration.inlayHints.enumMemberValues.enabled": "Enable/disable inlay hints of enum member values.", "taskDefinition.tsconfig.description": "The tsconfig file that defines the TS build.", "javascript.suggestionActions.enabled": "Enable/disable suggestion diagnostics for JavaScript files in the editor.", "typescript.suggestionActions.enabled": "Enable/disable suggestion diagnostics for TypeScript files in the editor.", diff --git a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts index 6d9ce6cc9c0e5..a1faf287a3f32 100644 --- a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts +++ b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts @@ -13,6 +13,20 @@ import { isTypeScriptDocument } from '../utils/languageModeIds'; import { equals } from '../utils/objects'; import { ResourceMap } from '../utils/resourceMap'; +namespace ExperimentalProto { + export interface UserPreferences extends Proto.UserPreferences { + displayPartsForJSDoc: true + + includeInlayParameterNameHints?: 'none' | 'literals' | 'all'; + includeInlayParameterNameHintsWhenArgumentMatchesName?: boolean; + includeInlayFunctionParameterTypeHints?: boolean; + includeInlayVariableTypeHints?: boolean; + includeInlayPropertyDeclarationTypeHints?: boolean; + includeInlayFunctionLikeReturnTypeHints?: boolean; + includeInlayEnumMemberValueHints?: boolean; + } +} + interface FileConfiguration { readonly formatOptions: Proto.FormatCodeSettings; readonly preferences: Proto.UserPreferences; @@ -173,7 +187,7 @@ export default class FileConfigurationManager extends Disposable { isTypeScriptDocument(document) ? 'typescript.preferences' : 'javascript.preferences', document.uri); - const preferences: Proto.UserPreferences & { displayPartsForJSDoc: true } = { + const preferences: ExperimentalProto.UserPreferences = { quotePreference: this.getQuoteStylePreference(preferencesConfig), importModuleSpecifierPreference: getImportModuleSpecifierPreference(preferencesConfig), importModuleSpecifierEnding: getImportModuleSpecifierEndingPreference(preferencesConfig), @@ -188,6 +202,7 @@ export default class FileConfigurationManager extends Disposable { // @ts-expect-error until 4.4 allowIncompleteCompletions: true, displayPartsForJSDoc: true, + ...getInlayHintsPreferences(config), }; return preferences; @@ -202,6 +217,27 @@ export default class FileConfigurationManager extends Disposable { } } +export function getInlayHintsPreferences(config: vscode.WorkspaceConfiguration) { + return { + includeInlayParameterNameHints: getInlayParameterNameHintsPreference(config), + includeInlayParameterNameHintsWhenArgumentMatchesName: !config.get('inlayHints.parameterNames.suppressWhenArgumentMatchesName', true), + includeInlayFunctionParameterTypeHints: config.get('inlayHints.parameterTypes.enabled', false), + includeInlayVariableTypeHints: config.get('inlayHints.variableTypes.enabled', false), + includeInlayPropertyDeclarationTypeHints: config.get('inlayHints.propertyDeclarationTypes.enabled', false), + includeInlayFunctionLikeReturnTypeHints: config.get('inlayHints.functionLikeReturnTypes.enabled', false), + includeInlayEnumMemberValueHints: config.get('inlayHints.enumMemberValues.enabled', false), + } as const; +} + +function getInlayParameterNameHintsPreference(config: vscode.WorkspaceConfiguration) { + switch (config.get('inlayHints.parameterNames.enabled')) { + case 'none': return 'none'; + case 'literals': return 'literals'; + case 'all': return 'all'; + default: return undefined; + } +} + function getImportModuleSpecifierPreference(config: vscode.WorkspaceConfiguration) { switch (config.get('importModuleSpecifier')) { case 'project-relative': return 'project-relative'; diff --git a/extensions/typescript-language-features/src/languageFeatures/inlayHints.ts b/extensions/typescript-language-features/src/languageFeatures/inlayHints.ts new file mode 100644 index 0000000000000..c2039e9646b6f --- /dev/null +++ b/extensions/typescript-language-features/src/languageFeatures/inlayHints.ts @@ -0,0 +1,154 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import * as Proto from '../protocol'; +import { DocumentSelector } from '../utils/documentSelector'; +import { ClientCapability, ITypeScriptServiceClient, ServerResponse, ExecConfig } from '../typescriptService'; +import { Condition, conditionalRegistration, requireMinVersion, requireSomeCapability } from '../utils/dependentRegistration'; +import { Position } from '../utils/typeConverters'; +import FileConfigurationManager, { getInlayHintsPreferences } from './fileConfigurationManager'; +import API from '../utils/api'; + +namespace ExperimentalProto { + export const enum CommandTypes { + ProvideInlineHints = 'ProvideInlayHints' + } + + export interface InlayHintsArgs extends Proto.FileRequestArgs { + /** + * Start position of the span. + */ + start: number; + /** + * Length of the span. + */ + length: number; + } + + export interface InlineHintsRequest extends Proto.Request { + command: CommandTypes.ProvideInlineHints; + arguments: InlayHintsArgs; + } + + export enum InlayHintKind { + Type = 'Type', + Parameter = 'Parameter', + Enum = 'Enum' + } + + interface InlayHintItem { + text: string; + position: Proto.Location; + kind?: InlayHintKind; + whitespaceBefore?: boolean; + whitespaceAfter?: boolean; + } + + export interface InlayHintsResponse extends Proto.Response { + body?: InlayHintItem[]; + } + + export interface IExtendedTypeScriptServiceClient { + execute( + command: K, + args: ExtendedTsServerRequests[K][0], + token: vscode.CancellationToken, + config?: ExecConfig + ): Promise>; + } + + export interface ExtendedTsServerRequests { + 'provideInlayHints': [InlayHintsArgs, InlayHintsResponse]; + } + + export namespace InlayHintKind { + export function fromProtocolInlayHintKind(kind: InlayHintKind): vscode.InlayHintKind { + switch (kind) { + case InlayHintKind.Parameter: + return vscode.InlayHintKind.Parameter; + case InlayHintKind.Type: + return vscode.InlayHintKind.Type; + case InlayHintKind.Enum: + return vscode.InlayHintKind.Other; + default: + return vscode.InlayHintKind.Other; + } + } + } +} + +class TypeScriptInlayHintsProvider implements vscode.InlayHintsProvider { + public static readonly minVersion = API.v440; + + constructor( + private readonly client: ITypeScriptServiceClient, + private readonly fileConfigurationManager: FileConfigurationManager + ) { } + + async provideInlayHints(model: vscode.TextDocument, range: vscode.Range, token: vscode.CancellationToken): Promise { + const filepath = this.client.toOpenedFilePath(model); + if (!filepath) { + return []; + } + + await this.fileConfigurationManager.ensureConfigurationForDocument(model, token); + + const start = model.offsetAt(range.start); + const length = model.offsetAt(range.end) - start; + + const response = await (this.client as ExperimentalProto.IExtendedTypeScriptServiceClient).execute('provideInlayHints', { file: filepath, start, length }, token); + if (response.type !== 'response' || !response.success || !response.body) { + return []; + } + + return response.body.map(hint => { + const result = new vscode.InlayHint( + hint.text, + Position.fromLocation(hint.position), + hint.kind && ExperimentalProto.InlayHintKind.fromProtocolInlayHintKind(hint.kind) + ); + result.whitespaceBefore = hint.whitespaceBefore; + result.whitespaceAfter = hint.whitespaceAfter; + return result; + }); + } +} + +export function requireInlayHintsConfiguration( + language: string +) { + return new Condition( + () => { + const config = vscode.workspace.getConfiguration(language, null); + const preferences = getInlayHintsPreferences(config); + + return preferences.includeInlayParameterNameHints === 'literals' || + preferences.includeInlayParameterNameHints === 'all' || + preferences.includeInlayEnumMemberValueHints || + preferences.includeInlayFunctionLikeReturnTypeHints || + preferences.includeInlayFunctionParameterTypeHints || + preferences.includeInlayPropertyDeclarationTypeHints || + preferences.includeInlayVariableTypeHints; + }, + vscode.workspace.onDidChangeConfiguration + ); +} + +export function register( + selector: DocumentSelector, + modeId: string, + client: ITypeScriptServiceClient, + fileConfigurationManager: FileConfigurationManager +) { + return conditionalRegistration([ + requireInlayHintsConfiguration(modeId), + requireMinVersion(client, TypeScriptInlayHintsProvider.minVersion), + requireSomeCapability(client, ClientCapability.Semantic), + ], () => { + return vscode.languages.registerInlayHintsProvider(selector.semantic, + new TypeScriptInlayHintsProvider(client, fileConfigurationManager)); + }); +} diff --git a/extensions/typescript-language-features/src/languageProvider.ts b/extensions/typescript-language-features/src/languageProvider.ts index 3af617eafd6be..3159af1a4d603 100644 --- a/extensions/typescript-language-features/src/languageProvider.ts +++ b/extensions/typescript-language-features/src/languageProvider.ts @@ -83,6 +83,7 @@ export default class LanguageProvider extends Disposable { import('./languageFeatures/smartSelect').then(provider => this._register(provider.register(selector, this.client))), import('./languageFeatures/tagClosing').then(provider => this._register(provider.register(selector, this.description.id, this.client))), import('./languageFeatures/typeDefinitions').then(provider => this._register(provider.register(selector, this.client))), + import('./languageFeatures/inlayHints').then(provider => this._register(provider.register(selector, this.description.id, this.client, this.fileConfigurationManager))), ]); } diff --git a/extensions/typescript-language-features/src/utils/api.ts b/extensions/typescript-language-features/src/utils/api.ts index 5ff6572be29fb..61d9801ca710b 100644 --- a/extensions/typescript-language-features/src/utils/api.ts +++ b/extensions/typescript-language-features/src/utils/api.ts @@ -36,6 +36,7 @@ export default class API { public static readonly v401 = API.fromSimpleString('4.0.1'); public static readonly v420 = API.fromSimpleString('4.2.0'); public static readonly v430 = API.fromSimpleString('4.3.0'); + public static readonly v440 = API.fromSimpleString('4.4.0'); public static fromVersionString(versionString: string): API { let version = semver.valid(versionString);