Merge pull request #82 from Kingwl/inlayhints-support

Add inlay hints support
This commit is contained in:
Henning Dieterichs 2021-09-06 14:43:30 +02:00 committed by GitHub
commit 3ecea82819
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 31146 additions and 23803 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
/node_modules/
/out/
/release/
.DS_Store

16
monaco.d.ts vendored
View file

@ -154,6 +154,15 @@ declare namespace monaco.languages.typescript {
/** A full HTTP path to a JavaScript file which adds a function `customTSWorkerFactory` to the self inside a web-worker */
customWorkerPath?: string;
}
interface InlayHintsOptions {
readonly includeInlayParameterNameHints?: 'none' | 'literals' | 'all';
readonly includeInlayParameterNameHintsWhenArgumentMatchesName?: boolean;
readonly includeInlayFunctionParameterTypeHints?: boolean;
readonly includeInlayVariableTypeHints?: boolean;
readonly includeInlayPropertyDeclarationTypeHints?: boolean;
readonly includeInlayFunctionLikeReturnTypeHints?: boolean;
readonly includeInlayEnumMemberValueHints?: boolean;
}
interface IExtraLib {
content: string;
version: number;
@ -212,6 +221,7 @@ declare namespace monaco.languages.typescript {
*/
readonly onDidExtraLibsChange: IEvent<void>;
readonly workerOptions: WorkerOptions;
readonly inlayHintsOptions: InlayHintsOptions;
/**
* Get the current extra libs registered with the language service.
*/
@ -413,6 +423,12 @@ declare namespace monaco.languages.typescript {
errorCodes: number[],
formatOptions: any
): Promise<ReadonlyArray<any>>;
/**
* Get inlay hints in the range of the file.
* @param fileName
* @returns `Promise<typescript.InlayHint[]>`
*/
provideInlayHints(fileName: string, start: number, end: number): Promise<ReadonlyArray<any>>;
}
export const typescriptVersion: string;
export const typescriptDefaults: LanguageServiceDefaults;

6
package-lock.json generated
View file

@ -461,9 +461,9 @@
}
},
"typescript": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz",
"integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==",
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz",
"integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==",
"dev": true
},
"which": {

View file

@ -32,7 +32,7 @@
"pretty-quick": "^3.1.0",
"requirejs": "^2.3.6",
"terser": "^5.6.0",
"typescript": "^4.3.2"
"typescript": "^4.4.2"
},
"husky": {
"hooks": {

View file

@ -242,7 +242,9 @@ export class DiagnosticsAdapter extends Adapter {
}
};
this._disposables.push(editor.onDidCreateModel((model) => onModelAdd(<IInternalEditorModel>model)));
this._disposables.push(
editor.onDidCreateModel((model) => onModelAdd(<IInternalEditorModel>model))
);
this._disposables.push(editor.onWillDisposeModel(onModelRemoved));
this._disposables.push(
editor.onDidChangeModelLanguage((event) => {
@ -574,9 +576,9 @@ function tagToString(tag: ts.JSDocTagInfo): string {
if (tag.name === 'param' && tag.text) {
const [paramName, ...rest] = tag.text;
tagLabel += `\`${paramName.text}\``;
if (rest.length > 0) tagLabel += `${rest.map(r => r.text).join(' ')}`;
if (rest.length > 0) tagLabel += `${rest.map((r) => r.text).join(' ')}`;
} else if (Array.isArray(tag.text)) {
tagLabel += `${tag.text.map(r => r.text).join(' ')}`;
tagLabel += `${tag.text.map((r) => r.text).join(' ')}`;
} else if (tag.text) {
tagLabel += `${tag.text}`;
}
@ -793,16 +795,14 @@ export class DefinitionAdapter extends Adapter {
range: this._textSpanToRange(refModel, entry.textSpan)
});
} else {
const matchedLibFile = typescriptDefaults.getExtraLibs()[entry.fileName]
const matchedLibFile = typescriptDefaults.getExtraLibs()[entry.fileName];
if (matchedLibFile) {
const libModel = editor.createModel(matchedLibFile.content, 'typescript', uri);
return {
uri: uri,
range: this._textSpanToRange(libModel, entry.textSpan)
}
};
}
}
}
return result;
@ -894,7 +894,7 @@ export class OutlineAdapter extends Adapter implements languages.DocumentSymbolP
kind: <languages.SymbolKind>(outlineTypeTable[item.kind] || languages.SymbolKind.Variable),
range: this._textSpanToRange(model, item.spans[0]),
selectionRange: this._textSpanToRange(model, item.spans[0]),
tags: [],
tags: []
};
if (containerLabel) result.containerName = containerLabel;
@ -1221,3 +1221,49 @@ export class RenameAdapter extends Adapter implements languages.RenameProvider {
return { edits };
}
}
// --- inlay hints ----
export class InlayHintsAdapter extends Adapter implements languages.InlayHintsProvider {
public async provideInlayHints(
model: editor.ITextModel,
range: Range,
token: CancellationToken
): Promise<languages.InlayHint[]> {
const resource = model.uri;
const fileName = resource.toString();
const start = model.getOffsetAt({
lineNumber: range.startLineNumber,
column: range.startColumn
});
const end = model.getOffsetAt({
lineNumber: range.endLineNumber,
column: range.endColumn
});
const worker = await this._worker(resource);
if (model.isDisposed()) {
return [];
}
const hints = await worker.provideInlayHints(fileName, start, end);
return hints.map((hint) => {
return {
...hint,
position: model.getPositionAt(hint.position),
kind: this._convertHintKind(hint.kind)
};
});
}
private _convertHintKind(kind?: ts.InlayHintKind) {
switch (kind) {
case 'Parameter':
return languages.InlayHintKind.Parameter;
case 'Type':
return languages.InlayHintKind.Type;
default:
return languages.InlayHintKind.Other;
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -2,4 +2,4 @@
// **NOTE**: Do not edit directly! This file is generated using `npm run import-typescript`
//
export const typescriptVersion = "4.3.2";
export const typescriptVersion = "4.4.2";

View file

@ -169,6 +169,16 @@ export interface WorkerOptions {
customWorkerPath?: string;
}
interface InlayHintsOptions {
readonly includeInlayParameterNameHints?: 'none' | 'literals' | 'all';
readonly includeInlayParameterNameHintsWhenArgumentMatchesName?: boolean;
readonly includeInlayFunctionParameterTypeHints?: boolean;
readonly includeInlayVariableTypeHints?: boolean;
readonly includeInlayPropertyDeclarationTypeHints?: boolean;
readonly includeInlayFunctionLikeReturnTypeHints?: boolean;
readonly includeInlayEnumMemberValueHints?: boolean;
}
interface IExtraLib {
content: string;
version: number;
@ -230,6 +240,8 @@ export interface LanguageServiceDefaults {
readonly workerOptions: WorkerOptions;
readonly inlayHintsOptions: InlayHintsOptions;
/**
* Get the current extra libs registered with the language service.
*/
@ -452,6 +464,13 @@ export interface TypeScriptWorker {
errorCodes: number[],
formatOptions: any
): Promise<ReadonlyArray<any>>;
/**
* Get inlay hints in the range of the file.
* @param fileName
* @returns `Promise<typescript.InlayHint[]>`
*/
provideInlayHints(fileName: string, start: number, end: number): Promise<ReadonlyArray<any>>;
}
// --- TypeScript configuration and defaults ---------
@ -467,11 +486,13 @@ class LanguageServiceDefaultsImpl implements LanguageServiceDefaults {
private _diagnosticsOptions!: DiagnosticsOptions;
private _workerOptions!: WorkerOptions;
private _onDidExtraLibsChangeTimeout: number;
private _inlayHintsOptions!: InlayHintsOptions;
constructor(
compilerOptions: CompilerOptions,
diagnosticsOptions: DiagnosticsOptions,
workerOptions: WorkerOptions
workerOptions: WorkerOptions,
inlayHintsOptions: InlayHintsOptions
) {
this._extraLibs = Object.create(null);
this._removedExtraLibs = Object.create(null);
@ -479,6 +500,7 @@ class LanguageServiceDefaultsImpl implements LanguageServiceDefaults {
this.setCompilerOptions(compilerOptions);
this.setDiagnosticsOptions(diagnosticsOptions);
this.setWorkerOptions(workerOptions);
this.setInlayHintsOptions(inlayHintsOptions);
this._onDidExtraLibsChangeTimeout = -1;
}
@ -494,6 +516,10 @@ class LanguageServiceDefaultsImpl implements LanguageServiceDefaults {
return this._workerOptions;
}
get inlayHintsOptions(): InlayHintsOptions {
return this._inlayHintsOptions;
}
getExtraLibs(): IExtraLibs {
return this._extraLibs;
}
@ -604,6 +630,11 @@ class LanguageServiceDefaultsImpl implements LanguageServiceDefaults {
this._onDidChange.fire(undefined);
}
setInlayHintsOptions(options: InlayHintsOptions): void {
this._inlayHintsOptions = options || Object.create(null);
this._onDidChange.fire(undefined);
}
setMaximumWorkerIdleTime(value: number): void {}
setEagerModelSync(value: boolean) {
@ -622,12 +653,14 @@ export const typescriptVersion: string = tsversion;
export const typescriptDefaults: LanguageServiceDefaults = new LanguageServiceDefaultsImpl(
{ allowNonTsExtensions: true, target: ScriptTarget.Latest },
{ noSemanticValidation: false, noSyntaxValidation: false, onlyVisible: false },
{},
{}
);
export const javascriptDefaults: LanguageServiceDefaults = new LanguageServiceDefaultsImpl(
{ allowNonTsExtensions: true, allowJs: true, target: ScriptTarget.Latest },
{ noSemanticValidation: true, noSyntaxValidation: false, onlyVisible: false },
{},
{}
);

View file

@ -81,7 +81,7 @@ function setupMode(
);
languages.registerCodeActionProvider(modeId, new languageFeatures.CodeActionAdaptor(worker));
languages.registerRenameProvider(modeId, new languageFeatures.RenameAdapter(worker));
languages.registerInlayHintsProvider(modeId, new languageFeatures.InlayHintsAdapter(worker));
new languageFeatures.DiagnosticsAdapter(libFiles, defaults, modeId, worker);
return worker;
}

View file

@ -39,11 +39,13 @@ export class TypeScriptWorker implements ts.LanguageServiceHost, ITypeScriptWork
private _extraLibs: IExtraLibs = Object.create(null);
private _languageService = ts.createLanguageService(this);
private _compilerOptions: ts.CompilerOptions;
private _inlayHintsOptions?: ts.InlayHintsOptions;
constructor(ctx: worker.IWorkerContext, createData: ICreateData) {
this._ctx = ctx;
this._compilerOptions = createData.compilerOptions;
this._extraLibs = createData.extraLibs;
this._inlayHintsOptions = createData.inlayHintsOptions;
}
// --- language service host ---------------
@ -191,7 +193,9 @@ export class TypeScriptWorker implements ts.LanguageServiceHost, ITypeScriptWork
diagnostic.relatedInformation = [];
for (const tsRelatedDiagnostic of tsDiagnostic.relatedInformation) {
const relatedDiagnostic: DiagnosticRelatedInformation = { ...tsRelatedDiagnostic };
relatedDiagnostic.file = relatedDiagnostic.file ? { fileName: relatedDiagnostic.file.fileName } : undefined
relatedDiagnostic.file = relatedDiagnostic.file
? { fileName: relatedDiagnostic.file.fileName }
: undefined;
diagnostic.relatedInformation.push(relatedDiagnostic);
}
}
@ -253,7 +257,7 @@ export class TypeScriptWorker implements ts.LanguageServiceHost, ITypeScriptWork
entry,
undefined,
undefined,
undefined,
undefined,
undefined
);
}
@ -415,12 +419,34 @@ export class TypeScriptWorker implements ts.LanguageServiceHost, ITypeScriptWork
async updateExtraLibs(extraLibs: IExtraLibs): Promise<void> {
this._extraLibs = extraLibs;
}
async provideInlayHints(
fileName: string,
start: number,
end: number
): Promise<readonly ts.InlayHint[]> {
if (fileNameIsLib(fileName)) {
return [];
}
const preferences: ts.InlayHintsOptions = this._inlayHintsOptions ?? {};
const span: ts.TextSpan = {
start,
length: end - start
};
try {
return this._languageService.provideInlayHints(fileName, span, preferences);
} catch {
return [];
}
}
}
export interface ICreateData {
compilerOptions: ts.CompilerOptions;
extraLibs: IExtraLibs;
customWorkerPath?: string;
inlayHintsOptions?: ts.InlayHintsOptions;
}
/** The shape of the factory */

View file

@ -71,7 +71,8 @@ export class WorkerManager {
createData: {
compilerOptions: this._defaults.getCompilerOptions(),
extraLibs: this._defaults.getExtraLibs(),
customWorkerPath: this._defaults.workerOptions.customWorkerPath
customWorkerPath: this._defaults.workerOptions.customWorkerPath,
inlayHintsOptions: this._defaults.inlayHintsOptions
}
});

219
test/inlayHints.html Normal file
View file

@ -0,0 +1,219 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<link
rel="stylesheet"
data-name="vs/editor/editor.main"
href="../node_modules/monaco-editor-core/dev/vs/editor/editor.main.css"
/>
</head>
<body>
<h2>Monaco Editor TypeScript test page</h2>
<button id="resetBtn">Reset Sample</button>
<div id="container" style="width: 800px; height: 600px; border: 1px solid grey"></div>
<h3>Inlay Hints options</h3>
<textarea style="font-family: monospace" id="inlayHintsOpts" cols="60" rows="30"></textarea
><br />
<button id="updateInlayHintsOptionsBtn">Update Inaly Hints options</button>
<script>
const paths = {
'vs/basic-languages': '../node_modules/monaco-languages/release/dev',
'vs/language/typescript/fillers/monaco-editor-core':
'../out/amd/fillers/monaco-editor-core-amd',
'vs/language/typescript': '../out/amd',
vs: '../node_modules/monaco-editor-core/dev/vs'
};
if (document.location.protocol === 'http:') {
// Add support for running local http server
let testIndex = document.location.pathname.indexOf('/test/');
if (testIndex !== -1) {
let prefix = document.location.pathname.substr(0, testIndex);
paths['vs/language/typescript'] = prefix + '/out/amd';
}
}
self.require = {
paths: paths
};
</script>
<script src="../node_modules/monaco-editor-core/dev/vs/loader.js"></script>
<script src="../node_modules/monaco-editor-core/dev/vs/editor/editor.main.nls.js"></script>
<script src="../node_modules/monaco-editor-core/dev/vs/editor/editor.main.js"></script>
<script>
function getDefaultCode() {
return [
'/* Game of Life',
' * Implemented in TypeScript',
' * To learn more about TypeScript, please visit http://www.typescriptlang.org/',
' */',
'',
'module Conway {',
'',
' export class Cell {',
' public row: number;',
' public col: number;',
' public live: boolean;',
'',
' constructor(row: number, col: number, live: boolean) {',
' this.row = row;',
' this.col = col;',
' this.live = live',
' }',
' }',
'',
' export class GameOfLife {',
' private gridSize: number;',
' private canvasSize: number;',
' private lineColor: string;',
' private liveColor: string;',
' private deadColor: string;',
' private initialLifeProbability: number;',
' private animationRate: number;',
' private cellSize: number;',
' private context: CanvasRenderingContext2D;',
' private world;',
'',
'',
' constructor() {',
' this.gridSize = 50;',
' this.canvasSize = 600;',
" this.lineColor = '#cdcdcd';",
" this.liveColor = '#666';",
" this.deadColor = '#eee';",
' this.initialLifeProbability = 0.5;',
' this.animationRate = 60;',
' this.cellSize = 0;',
' this.world = this.createWorld();',
' this.circleOfLife();',
' }',
'',
' public createWorld() {',
' return this.travelWorld( (cell : Cell) => {',
' cell.live = Math.random() < this.initialLifeProbability;',
' return cell;',
' });',
' }',
'',
' public circleOfLife() : void {',
' this.world = this.travelWorld( (cell: Cell) => {',
' cell = this.world[cell.row][cell.col];',
' this.draw(cell);',
' return this.resolveNextGeneration(cell);',
' });',
' setTimeout( () => {this.circleOfLife()}, this.animationRate);',
' }',
'',
' public resolveNextGeneration(cell : Cell) {',
' var count = this.countNeighbors(cell);',
' var newCell = new Cell(cell.row, cell.col, cell.live);',
' if(count < 2 || count > 3) newCell.live = false;',
' else if(count == 3) newCell.live = true;',
' return newCell;',
' }',
'',
' public countNeighbors(cell : Cell) {',
' var neighbors = 0;',
' for(var row = -1; row <=1; row++) {',
' for(var col = -1; col <= 1; col++) {',
' if(row == 0 && col == 0) continue;',
' if(this.isAlive(cell.row + row, cell.col + col)) {',
' neighbors++;',
' }',
' }',
' }',
' return neighbors;',
' }',
'',
' public isAlive(row : number, col : number) {',
' if(row < 0 || col < 0 || row >= this.gridSize || col >= this.gridSize) return false;',
' return this.world[row][col].live;',
' }',
'',
' public travelWorld(callback) {',
' var result = [];',
' for(var row = 0; row < this.gridSize; row++) {',
' var rowData = [];',
' for(var col = 0; col < this.gridSize; col++) {',
' rowData.push(callback(new Cell(row, col, false)));',
' }',
' result.push(rowData);',
' }',
' return result;',
' }',
'',
' public draw(cell : Cell) {',
' if(this.context == null) this.context = this.createDrawingContext();',
' if(this.cellSize == 0) this.cellSize = this.canvasSize/this.gridSize;',
'',
' this.context.strokeStyle = this.lineColor;',
' this.context.strokeRect(cell.row * this.cellSize, cell.col*this.cellSize, this.cellSize, this.cellSize);',
' this.context.fillStyle = cell.live ? this.liveColor : this.deadColor;',
' this.context.fillRect(cell.row * this.cellSize, cell.col*this.cellSize, this.cellSize, this.cellSize);',
' }',
'',
' public createDrawingContext() {',
" var canvas = <HTMLCanvasElement> document.getElementById('conway-canvas');",
' if(canvas == null) {',
" canvas = document.createElement('canvas');",
" canvas.id = 'conway-canvas';",
' canvas.width = this.canvasSize;',
' canvas.height = this.canvasSize;',
' document.body.appendChild(canvas);',
' }',
" return canvas.getContext('2d');",
' }',
' }',
'}',
'',
'var game = new Conway.GameOfLife();'
].join('\n');
}
function getDefaultInlayHintsOpts() {
return {
includeInlayParameterNameHints: 'all',
includeInlayParameterNameHintsWhenArgumentMatchesName: true,
includeInlayFunctionParameterTypeHints: true,
includeInlayVariableTypeHints: true,
includeInlayPropertyDeclarationTypeHints: true,
includeInlayFunctionLikeReturnTypeHints: true,
includeInlayEnumMemberValueHints: true
};
}
require([
'vs/basic-languages/monaco.contribution',
'vs/language/typescript/monaco.contribution'
], () => {
const editor = monaco.editor.create(document.getElementById('container'), {
value: localStorage.getItem('code') || getDefaultCode(),
language: 'typescript',
lightbulb: { enabled: true }
});
editor.onDidChangeModelContent(() => {
const code = editor.getModel().getValue();
localStorage.setItem('code', code);
});
document.getElementById('resetBtn').onclick = () => {
editor.setValue(getDefaultCode());
};
const optsString =
localStorage.getItem('inlay-hints-opts') ||
JSON.stringify(getDefaultInlayHintsOpts(), null, 4);
document.getElementById('inlayHintsOpts').textContent = optsString;
monaco.languages.typescript.typescriptDefaults.setInlayHintsOptions(JSON.parse(optsString));
document.getElementById('updateInlayHintsOptionsBtn').onclick = () => {
const newOpts = document.getElementById('inlayHintsOpts').value;
monaco.languages.typescript.typescriptDefaults.setInlayHintsOptions(JSON.parse(newOpts));
localStorage.setItem('inlay-hints-opts', newOpts);
};
});
</script>
</body>
</html>