Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 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
94 changes: 94 additions & 0 deletions extensions/typescript-language-features/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,100 @@
"description": "%configuration.suggest.includeAutomaticOptionalChainCompletions%",
"scope": "resource"
},
"typescript.inlayHints.includeInlayParameterNameHints": {
"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.

"default": "none",
"description": "%configuration.inlayHints.includeInlayParameterNameHints%",
"scope": "resource"
},
"typescript.inlayHints.includeInlayFunctionParameterTypeHints": {
"type": "boolean",
"default": false,
"description": "%configuration.inlayHints.includeInlayFunctionParameterTypeHints%",
"scope": "resource"
},
"typescript.inlayHints.includeInlayVariableTypeHints": {
"type": "boolean",
"default": false,
"description": "%configuration.inlayHints.includeInlayVariableTypeHints%",
"scope": "resource"
},
"typescript.inlayHints.includeInlayParameterNameHintsWhenArgumentMatchesName": {
"type": "boolean",
"default": false,
"description": "%configuration.inlayHints.includeInlayParameterNameHintsWhenArgumentMatchesName%",
"scope": "resource"
},
"typescript.inlayHints.includeInlayPropertyDeclarationTypeHints": {
"type": "boolean",
"default": false,
"description": "%configuration.inlayHints.includeInlayPropertyDeclarationTypeHints%",
"scope": "resource"
},
"typescript.inlayHints.includeInlayFunctionLikeReturnTypeHints": {
"type": "boolean",
"default": false,
"description": "%configuration.inlayHints.includeInlayFunctionLikeReturnTypeHints%",
"scope": "resource"
},
"typescript.inlayHints.includeInlayEnumMemberValueHints": {
"type": "boolean",
"default": false,
"description": "%configuration.inlayHints.includeInlayEnumMemberValueHints%",
"scope": "resource"
},
"javascript.inlayHints.includeInlayParameterNameHints": {
"type": "string",
"enum": [
"none",
"literals",
"all"
],
"default": "none",
"description": "%configuration.inlayHints.includeInlayParameterNameHints%",
"scope": "resource"
},
"javascript.inlayHints.includeInlayFunctionParameterTypeHints": {
"type": "boolean",
"default": false,
"description": "%configuration.inlayHints.includeInlayFunctionParameterTypeHints%",
"scope": "resource"
},
"javascript.inlayHints.includeInlayVariableTypeHints": {
"type": "boolean",
"default": false,
"description": "%configuration.inlayHints.includeInlayVariableTypeHints%",
"scope": "resource"
},
"javascript.inlayHints.includeInlayParameterNameHintsWhenArgumentMatchesName": {
"type": "boolean",
"default": false,
"description": "%configuration.inlayHints.includeInlayParameterNameHintsWhenArgumentMatchesName%",
"scope": "resource"
},
"javascript.inlayHints.includeInlayPropertyDeclarationTypeHints": {
"type": "boolean",
"default": false,
"description": "%configuration.inlayHints.includeInlayPropertyDeclarationTypeHints%",
"scope": "resource"
},
"javascript.inlayHints.includeInlayFunctionLikeReturnTypeHints": {
"type": "boolean",
"default": false,
"description": "%configuration.inlayHints.includeInlayFunctionLikeReturnTypeHints%",
"scope": "resource"
},
"javascript.inlayHints.includeInlayEnumMemberValueHints": {
"type": "boolean",
"default": false,
"description": "%configuration.inlayHints.includeInlayEnumMemberValueHints%",
"scope": "resource"
},
"javascript.suggest.includeCompletionsForImportStatements": {
"type": "boolean",
"default": true,
Expand Down
7 changes: 7 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,13 @@
"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.",
"configuration.inlayHints.includeInlayParameterNameHints": "Enable/disable inlay hints of parameter name.",
"configuration.inlayHints.includeInlayFunctionParameterTypeHints": "Enable/disable inlay hints of function parameter name.",
"configuration.inlayHints.includeInlayVariableTypeHints": "Enable/disable inlay hints of variable type.",
"configuration.inlayHints.includeInlayParameterNameHintsWhenArgumentMatchesName": "Enable/disable inlay hints when argument matchs the parameter name.",
"configuration.inlayHints.includeInlayPropertyDeclarationTypeHints": "Enable/disable inlay hints of property declaration.",
"configuration.inlayHints.includeInlayFunctionLikeReturnTypeHints": "Enable/disable inlay hints of return type for function like.",
"configuration.inlayHints.includeInlayEnumMemberValueHints": "Enable/disable inlay hints of enum member.",
"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 @@ -186,6 +200,7 @@ export default class FileConfigurationManager extends Disposable {
includeCompletionsForImportStatements: config.get<boolean>('suggest.includeCompletionsForImportStatements', true),
includeCompletionsWithSnippetText: config.get<boolean>('suggest.includeCompletionsWithSnippetText', true),
displayPartsForJSDoc: true,
...getInlayHintsPreferences(config),
};

return preferences;
Expand All @@ -200,6 +215,27 @@ export default class FileConfigurationManager extends Disposable {
}
}

export function getInlayHintsPreferences(config: vscode.WorkspaceConfiguration) {
return {
includeInlayParameterNameHints: getInlayParameterNameHintsPreference(config),
includeInlayParameterNameHintsWhenArgumentMatchesName: config.get<boolean>('inlayHints.includeInlayParameterNameHintsWhenArgumentMatchesName', false),
includeInlayFunctionParameterTypeHints: config.get<boolean>('inlayHints.includeInlayFunctionParameterTypeHints', false),
includeInlayVariableTypeHints: config.get<boolean>('inlayHints.includeInlayVariableTypeHints', false),
includeInlayPropertyDeclarationTypeHints: config.get<boolean>('inlayHints.includeInlayPropertyDeclarationTypeHints', false),
includeInlayFunctionLikeReturnTypeHints: config.get<boolean>('inlayHints.includeInlayFunctionLikeReturnTypeHints', false),
includeInlayEnumMemberValueHints: config.get<boolean>('inlayHints.includeInlayEnumMemberValueHints', false),
} as const;
}

function getInlayParameterNameHintsPreference(config: vscode.WorkspaceConfiguration) {
switch (config.get<string>('inlayHints.includeInlayParameterNameHints')) {
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,143 @@
/*---------------------------------------------------------------------------------------------
* 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 { conditionalRegistration, requireMinVersion, requireSomeCapability } from '../utils/dependentRegistration';
import { Position } from '../utils/typeConverters';
import FileConfigurationManager, { getInlayHintsPreferences } from './fileConfigurationManager';
import API from '../utils/api';
import { isTypeScriptDocument } from '../utils/languageModeIds';

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

if (!this.someInlayHintsEnabled(model)) {
return [];
}

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

private someInlayHintsEnabled(model: vscode.TextDocument) {
const config = vscode.workspace.getConfiguration(isTypeScriptDocument(model) ? 'typescript' : 'javascript', model.uri);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Also, we typically register two providers: one for TS files and one for JS files. This can make registering and unregistering them a bit simpler

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not pretty sure but I have some changes.

const preferences = getInlayHintsPreferences(config);
return Object.values(preferences).some(Boolean);
}
}

export function register(
selector: DocumentSelector,
client: ITypeScriptServiceClient,
fileConfigurationManager: FileConfigurationManager
) {
return conditionalRegistration([
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.client, this.fileConfigurationManager))),
]);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,25 @@ import { TelemetryProperties, TelemetryReporter, VSCodeTelemetryReporter } from
import Tracer from './utils/tracer';
import { inferredProjectCompilerOptions, ProjectType } from './utils/tsconfig';

namespace ExperimentalProto {
export interface UserPreferences extends Proto.UserPreferences {
includeInlineParameterNameHints?: boolean;
includeInlineFunctionParameterTypeHints?: boolean;
includeInlineVariableTypeHints?: boolean;
includeInlineNonLiteralParameterNameHints?: boolean;
includeInlineDuplicatedParameterNameHints?: boolean;
includeInlineRequireAssignedVariableTypeHints?: boolean;
includeInlinePropertyDeclarationTypeHints?: boolean;
includeInlineFunctionLikeReturnTypeHints?: boolean;
includeInlineEnumMemberValueHints?: boolean;
includeInlineCallChainsHints?: boolean;
}

export interface ConfigureRequestArguments extends Proto.ConfigureRequestArguments {
preferences?: UserPreferences;
}
}

const localize = nls.loadMessageBundle();

export interface TsDiagnostics {
Expand Down Expand Up @@ -538,7 +557,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
? this.configuration.watchOptions
: undefined;

const configureOptions: Proto.ConfigureRequestArguments = {
const configureOptions: ExperimentalProto.ConfigureRequestArguments = {
hostInfo: 'vscode',
preferences: {
providePrefixAndSuffixTextForRename: true,
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