Add ts inline hints (#113412)

* Add ts server for inline hints

* Add some feature related configure

* Add more config

* Rename all options

* Support range

* use new interface

* Fix cr issues

* Update inlay hints for ts plugin

* Avoid call chain hints

* Avoid more option

* Update protos

* Update extensions/typescript-language-features/package.nls.json

Co-authored-by: Daniel Rosenwasser <DanielRosenwasser@users.noreply.github.com>

* Use suppress for some option

* Update CR issues

* Fix missing typedef

* Avoid changes

Co-authored-by: Daniel Rosenwasser <DanielRosenwasser@users.noreply.github.com>
This commit is contained in:
Wenlu Wang 2021-07-08 00:21:20 +08:00 committed by GitHub
parent c5f12c7226
commit e144d6e951
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 308 additions and 1 deletions

View file

@ -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,

View file

@ -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.",

View file

@ -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<boolean>('inlayHints.parameterNames.suppressWhenArgumentMatchesName', true),
includeInlayFunctionParameterTypeHints: config.get<boolean>('inlayHints.parameterTypes.enabled', false),
includeInlayVariableTypeHints: config.get<boolean>('inlayHints.variableTypes.enabled', false),
includeInlayPropertyDeclarationTypeHints: config.get<boolean>('inlayHints.propertyDeclarationTypes.enabled', false),
includeInlayFunctionLikeReturnTypeHints: config.get<boolean>('inlayHints.functionLikeReturnTypes.enabled', false),
includeInlayEnumMemberValueHints: config.get<boolean>('inlayHints.enumMemberValues.enabled', false),
} as const;
}
function getInlayParameterNameHintsPreference(config: vscode.WorkspaceConfiguration) {
switch (config.get<string>('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<string>('importModuleSpecifier')) {
case 'project-relative': return 'project-relative';

View file

@ -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<K extends keyof ExtendedTsServerRequests>(
command: K,
args: ExtendedTsServerRequests[K][0],
token: vscode.CancellationToken,
config?: ExecConfig
): Promise<ServerResponse.Response<ExtendedTsServerRequests[K][1]>>;
}
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<vscode.InlayHint[]> {
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));
});
}

View file

@ -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))),
]);
}

View file

@ -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);