From 84cdef085d049f020066cbf8a443d59b3df82b16 Mon Sep 17 00:00:00 2001 From: auvred Date: Sun, 24 Dec 2023 10:31:30 +0000 Subject: [PATCH 1/6] chore(website): [playground] add twoslash queries --- .../editor/createProvideTwoslashInlay.ts | 100 ++++++++++++++++++ .../components/editor/useSandboxServices.ts | 8 ++ 2 files changed, 108 insertions(+) create mode 100644 packages/website/src/components/editor/createProvideTwoslashInlay.ts diff --git a/packages/website/src/components/editor/createProvideTwoslashInlay.ts b/packages/website/src/components/editor/createProvideTwoslashInlay.ts new file mode 100644 index 000000000000..01720c8fd56e --- /dev/null +++ b/packages/website/src/components/editor/createProvideTwoslashInlay.ts @@ -0,0 +1,100 @@ +// The following code is adapted from the code in microsoft/TypeScript-Website. +// Source: https://github.com/microsoft/TypeScript-Website/blob/c8b2ea8c8fc216c163fe293650b2666c8563a67d/packages/playground/src/twoslashInlays.ts +// License: https://github.com/microsoft/TypeScript-Website/blob/c8b2ea8c8fc216c163fe293650b2666c8563a67d/LICENSE-CODE + +import type Monaco from 'monaco-editor'; +import type * as ts from 'typescript'; + +import type { SandboxInstance } from './useSandboxServices'; + +const twoslashQueryRegex = /^(\s*\/\/\s*\^\?)\s*$/gm; +function findTwoshashQueries(code: string): RegExpExecArray[] { + let match: RegExpExecArray | null = null; + const matches: RegExpExecArray[] = []; + while ((match = twoslashQueryRegex.exec(code))) { + matches.push(match); + } + return matches; +} + +export function createTwoslashInlayProvider( + sandbox: SandboxInstance, +): Monaco.languages.InlayHintsProvider { + return { + provideInlayHints: async ( + model, + _, + cancel, + ): Promise => { + const worker = await sandbox.getWorkerProcess(); + if (model.isDisposed() || cancel.isCancellationRequested) { + return { + hints: [], + dispose(): void { + /* nop */ + }, + }; + } + + const queryMatches = findTwoshashQueries(model.getValue()); + + const results: Monaco.languages.InlayHint[] = []; + + for (const result of await Promise.all( + queryMatches.map(q => resolveInlayHint(q)), + )) { + if (result) { + results.push(result); + } + } + + return { + hints: results, + dispose(): void { + /* nop */ + }, + }; + + async function resolveInlayHint( + queryMatch: RegExpExecArray, + ): Promise { + const end = queryMatch.index + queryMatch[1].length - 1; + const endPos = model.getPositionAt(end); + const inspectionPos = new sandbox.monaco.Position( + endPos.lineNumber - 1, + endPos.column, + ); + const inspectionOff = model.getOffsetAt(inspectionPos); + + const hint = await (worker.getQuickInfoAtPosition( + 'file://' + model.uri.path, + inspectionOff, + ) as Promise); + if (!hint?.displayParts) { + return; + } + + // Make a one-liner + let text = hint.displayParts + .map(d => d.text) + .join('') + .replace(/\r?\n\s*/g, ' '); + if (text.length > 120) { + text = text.slice(0, 119) + '...'; + } + + const inlay: Monaco.languages.InlayHint = { + kind: sandbox.monaco.languages.InlayHintKind.Type, + position: new sandbox.monaco.Position( + endPos.lineNumber, + endPos.column + 1, + ), + label: text, + paddingLeft: true, + }; + + return inlay; + } + }, + }; +} diff --git a/packages/website/src/components/editor/useSandboxServices.ts b/packages/website/src/components/editor/useSandboxServices.ts index 997cc1cc6058..709f4a79fd81 100644 --- a/packages/website/src/components/editor/useSandboxServices.ts +++ b/packages/website/src/components/editor/useSandboxServices.ts @@ -10,6 +10,7 @@ import { createFileSystem } from '../linter/bridge'; import { type CreateLinter, createLinter } from '../linter/createLinter'; import type { PlaygroundSystem } from '../linter/types'; import type { RuleDetails } from '../types'; +import { createTwoslashInlayProvider } from './createProvideTwoslashInlay'; import { editorEmbedId } from './EditorEmbed'; import { sandboxSingleton } from './loadSandbox'; import type { CommonEditorProps } from './types'; @@ -71,6 +72,13 @@ export const useSandboxServices = ( colorMode === 'dark' ? 'vs-dark' : 'vs-light', ); + // registerInlayHintsProvider may not be present for older TS versions + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + sandboxInstance.monaco.languages.registerInlayHintsProvider?.( + sandboxInstance.language, + createTwoslashInlayProvider(sandboxInstance), + ); + const system = createFileSystem(props, sandboxInstance.tsvfs); const worker = await sandboxInstance.getWorkerProcess(); From 1b3178e73c8d95a8f4effbc248233fe328ff2ad6 Mon Sep 17 00:00:00 2001 From: auvred <61150013+auvred@users.noreply.github.com> Date: Thu, 1 Feb 2024 19:01:23 +0300 Subject: [PATCH 2/6] Update packages/website/src/components/editor/useSandboxServices.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Josh Goldberg ✨ --- packages/website/src/components/editor/useSandboxServices.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/src/components/editor/useSandboxServices.ts b/packages/website/src/components/editor/useSandboxServices.ts index 709f4a79fd81..bc8d1ac0582d 100644 --- a/packages/website/src/components/editor/useSandboxServices.ts +++ b/packages/website/src/components/editor/useSandboxServices.ts @@ -72,7 +72,7 @@ export const useSandboxServices = ( colorMode === 'dark' ? 'vs-dark' : 'vs-light', ); - // registerInlayHintsProvider may not be present for older TS versions + // registerInlayHintsProvider was added in TS 4.4 and isn't in TS <= 4.3. // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition sandboxInstance.monaco.languages.registerInlayHintsProvider?.( sandboxInstance.language, From 2e2f1deaf98514ea63e0c29162d810fb72e40d25 Mon Sep 17 00:00:00 2001 From: auvred <61150013+auvred@users.noreply.github.com> Date: Thu, 1 Feb 2024 19:04:37 +0300 Subject: [PATCH 3/6] Update packages/website/src/components/editor/createProvideTwoslashInlay.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Josh Goldberg ✨ --- .../website/src/components/editor/createProvideTwoslashInlay.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/website/src/components/editor/createProvideTwoslashInlay.ts b/packages/website/src/components/editor/createProvideTwoslashInlay.ts index 01720c8fd56e..9ce88b297ddb 100644 --- a/packages/website/src/components/editor/createProvideTwoslashInlay.ts +++ b/packages/website/src/components/editor/createProvideTwoslashInlay.ts @@ -74,7 +74,6 @@ export function createTwoslashInlayProvider( return; } - // Make a one-liner let text = hint.displayParts .map(d => d.text) .join('') From b65a948a7ecdd111666f4adda87833e4989fdfff Mon Sep 17 00:00:00 2001 From: auvred Date: Thu, 1 Feb 2024 19:05:39 +0300 Subject: [PATCH 4/6] refactor: move twoslash regex closer to .exec --- .../src/components/editor/createProvideTwoslashInlay.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/website/src/components/editor/createProvideTwoslashInlay.ts b/packages/website/src/components/editor/createProvideTwoslashInlay.ts index 9ce88b297ddb..7dbd5ed1da30 100644 --- a/packages/website/src/components/editor/createProvideTwoslashInlay.ts +++ b/packages/website/src/components/editor/createProvideTwoslashInlay.ts @@ -7,11 +7,11 @@ import type * as ts from 'typescript'; import type { SandboxInstance } from './useSandboxServices'; -const twoslashQueryRegex = /^(\s*\/\/\s*\^\?)\s*$/gm; function findTwoshashQueries(code: string): RegExpExecArray[] { let match: RegExpExecArray | null = null; const matches: RegExpExecArray[] = []; - while ((match = twoslashQueryRegex.exec(code))) { + // RegExp that matches '^//?$' + while ((match = /^(\s*\/\/\s*\^\?)\s*$/gm.exec(code))) { matches.push(match); } return matches; From e5130b43ba92b40f30357ddde8fc13c68dacfeb4 Mon Sep 17 00:00:00 2001 From: auvred Date: Thu, 1 Feb 2024 19:06:05 +0300 Subject: [PATCH 5/6] refactor: return without declaring variable --- .../src/components/editor/createProvideTwoslashInlay.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/website/src/components/editor/createProvideTwoslashInlay.ts b/packages/website/src/components/editor/createProvideTwoslashInlay.ts index 7dbd5ed1da30..c55c76a90a11 100644 --- a/packages/website/src/components/editor/createProvideTwoslashInlay.ts +++ b/packages/website/src/components/editor/createProvideTwoslashInlay.ts @@ -82,7 +82,7 @@ export function createTwoslashInlayProvider( text = text.slice(0, 119) + '...'; } - const inlay: Monaco.languages.InlayHint = { + return { kind: sandbox.monaco.languages.InlayHintKind.Type, position: new sandbox.monaco.Position( endPos.lineNumber, @@ -91,8 +91,6 @@ export function createTwoslashInlayProvider( label: text, paddingLeft: true, }; - - return inlay; } }, }; From 90d2e78c0be52bbecea0f9a864dc4df54f97d7fe Mon Sep 17 00:00:00 2001 From: auvred Date: Thu, 1 Feb 2024 19:22:08 +0300 Subject: [PATCH 6/6] --- .../src/components/editor/createProvideTwoslashInlay.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/website/src/components/editor/createProvideTwoslashInlay.ts b/packages/website/src/components/editor/createProvideTwoslashInlay.ts index c55c76a90a11..4a2c6d2653a3 100644 --- a/packages/website/src/components/editor/createProvideTwoslashInlay.ts +++ b/packages/website/src/components/editor/createProvideTwoslashInlay.ts @@ -11,7 +11,8 @@ function findTwoshashQueries(code: string): RegExpExecArray[] { let match: RegExpExecArray | null = null; const matches: RegExpExecArray[] = []; // RegExp that matches '^//?$' - while ((match = /^(\s*\/\/\s*\^\?)\s*$/gm.exec(code))) { + const twoslashQueryRegex = /^(\s*\/\/\s*\^\?)\s*$/gm; + while ((match = twoslashQueryRegex.exec(code))) { matches.push(match); } return matches;