Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ts inline hints #113412

Merged
merged 23 commits into from
Jul 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions extensions/typescript-language-features/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,111 @@
"description": "%configuration.suggest.includeAutomaticOptionalChainCompletions%",
"scope": "resource"
},
"typescript.inlayHints.parameterNames.enabled": {
"type": "string",
"enum": [
"none",
"literals",
"all"
],
Comment on lines +275 to +279
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should add descriptions for these too.

  • None: Disable parameter name hints.
  • Literals: Enable parameter name hints only for literal arguments.
  • All: Enable parameter name hints for literal and non-literal arguments.

@DanielRosenwasser, can you think of a more approachable way to describe this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess if we can’t find a concise way to explain what literal expressions are or are not, these descriptions don’t add much.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

"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,
Expand Down
10 changes: 10 additions & 0 deletions extensions/typescript-language-features/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
Expand All @@ -188,6 +202,7 @@ export default class FileConfigurationManager extends Disposable {
// @ts-expect-error until 4.4
allowIncompleteCompletions: true,
displayPartsForJSDoc: true,
...getInlayHintsPreferences(config),
};

return preferences;
Expand All @@ -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';
Expand Down
Original file line number Diff line number Diff line change
@@ -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));
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -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))),
]);
}

Expand Down
1 change: 1 addition & 0 deletions extensions/typescript-language-features/src/utils/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down