From 410e03fcfff5609f7d8e9bb22138badc6a2c487c Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 8 Jan 2025 05:34:53 +0100 Subject: [PATCH] all --- frontend/hoverContribution.js | 88 ++++++ .../raw_apps/RawAppCodeEditor.svelte | 195 ++++++++++++ .../raw_apps/readonly_filesystem.ts | 191 ++++++++++++ .../src/lib/components/raw_apps/vscode.ts | 280 ++++++++++++++++++ 4 files changed, 754 insertions(+) create mode 100644 frontend/hoverContribution.js create mode 100644 frontend/src/lib/components/raw_apps/RawAppCodeEditor.svelte create mode 100644 frontend/src/lib/components/raw_apps/readonly_filesystem.ts create mode 100644 frontend/src/lib/components/raw_apps/vscode.ts diff --git a/frontend/hoverContribution.js b/frontend/hoverContribution.js new file mode 100644 index 0000000000000..003f3ca018e40 --- /dev/null +++ b/frontend/hoverContribution.js @@ -0,0 +1,88 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { + DecreaseHoverVerbosityLevel, + GoToBottomHoverAction, + GoToTopHoverAction, + HideContentHoverAction, + IncreaseHoverVerbosityLevel, + PageDownHoverAction, + PageUpHoverAction, + ScrollDownHoverAction, + ScrollLeftHoverAction, + ScrollRightHoverAction, + ScrollUpHoverAction, + ShowDefinitionPreviewHoverAction, + ShowOrFocusHoverAction +} from './hoverActions.js' +import { + EditorContributionInstantiation, + registerEditorAction, + registerEditorContribution +} from '../../../browser/editorExtensions.js' +import { editorHoverBorder } from '../../../../platform/theme/common/colorRegistry.js' +import { registerThemingParticipant } from '../../../../platform/theme/common/themeService.js' +import { HoverParticipantRegistry } from './hoverTypes.js' +import { MarkdownHoverParticipant } from './markdownHoverParticipant.js' +import { MarkerHoverParticipant } from './markerHoverParticipant.js' +import { ContentHoverController } from './contentHoverController.js' +import { GlyphHoverController } from './glyphHoverController.js' +import './hover.css' +import { AccessibleViewRegistry } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js' +import { + ExtHoverAccessibleView, + HoverAccessibilityHelp, + HoverAccessibleView +} from './hoverAccessibleViews.js' + +registerEditorContribution( + ContentHoverController.ID, + ContentHoverController, + EditorContributionInstantiation.BeforeFirstInteraction +) +registerEditorContribution( + GlyphHoverController.ID, + GlyphHoverController, + EditorContributionInstantiation.BeforeFirstInteraction +) +registerEditorAction(ShowOrFocusHoverAction) +registerEditorAction(ShowDefinitionPreviewHoverAction) +registerEditorAction(HideContentHoverAction) +registerEditorAction(ScrollUpHoverAction) +registerEditorAction(ScrollDownHoverAction) +registerEditorAction(ScrollLeftHoverAction) +registerEditorAction(ScrollRightHoverAction) +registerEditorAction(PageUpHoverAction) +registerEditorAction(PageDownHoverAction) +registerEditorAction(GoToTopHoverAction) +registerEditorAction(GoToBottomHoverAction) +registerEditorAction(IncreaseHoverVerbosityLevel) +registerEditorAction(DecreaseHoverVerbosityLevel) +HoverParticipantRegistry.register(MarkdownHoverParticipant) +HoverParticipantRegistry.register(MarkerHoverParticipant) + +// theming +registerThemingParticipant((theme, collector) => { + const hoverBorder = theme.getColor(editorHoverBorder) + if (hoverBorder) { + collector.addRule( + `.monaco-editor .monaco-hover .hover-row:not(:first-child):not(:empty) { border-top: 1px solid ${hoverBorder.transparent( + 0.5 + )}; }` + ) + collector.addRule( + `.monaco-editor .monaco-hover hr { border-top: 1px solid ${hoverBorder.transparent(0.5)}; }` + ) + collector.addRule( + `.monaco-editor .monaco-hover hr { border-bottom: 0px solid ${hoverBorder.transparent( + 0.5 + )}; }` + ) + } +}) +AccessibleViewRegistry.register(new HoverAccessibleView()) +AccessibleViewRegistry.register(new HoverAccessibilityHelp()) +AccessibleViewRegistry.register(new ExtHoverAccessibleView()) diff --git a/frontend/src/lib/components/raw_apps/RawAppCodeEditor.svelte b/frontend/src/lib/components/raw_apps/RawAppCodeEditor.svelte new file mode 100644 index 0000000000000..17f8d09db981a --- /dev/null +++ b/frontend/src/lib/components/raw_apps/RawAppCodeEditor.svelte @@ -0,0 +1,195 @@ + + +
+ {#if !mounted} +
+
+ Loading editor +
+
+ {/if} +
+
+
+
+ +
+
+
+
+
+
+
+ + diff --git a/frontend/src/lib/components/raw_apps/readonly_filesystem.ts b/frontend/src/lib/components/raw_apps/readonly_filesystem.ts new file mode 100644 index 0000000000000..35b157f170a0e --- /dev/null +++ b/frontend/src/lib/components/raw_apps/readonly_filesystem.ts @@ -0,0 +1,191 @@ +import { + type IFileSystemProviderWithFileReadWriteCapability, + FileType, + type IFileChange, + type IFileDeleteOptions, + type IFileOverwriteOptions, + type IFileWriteOptions, + type IStat, + type IWatchOptions, + FileSystemProviderError, + FileSystemProviderErrorCode +} from '@codingame/monaco-vscode-files-service-override' + +import { EventEmitter, Uri } from 'vscode' +import type { Event } from 'vscode/vscode/vs/base/common/event' +import type { IDisposable } from 'vscode/vscode/vs/base/common/lifecycle' +import type { URI } from 'vscode/vscode/vs/base/common/uri' +import type { FileSystemProviderCapabilities } from 'vscode/vscode/vs/platform/files/common/files' + +interface FileNode { + type: FileType + name: string + content?: string + children?: Map +} + +export class ReadOnlyMemoryFileSystemProvider + implements IFileSystemProviderWithFileReadWriteCapability +{ + private readonly _onDidChangeFile = new EventEmitter() + private readonly _onDidChangeCapabilities = new EventEmitter() + private readonly _root: FileNode + + constructor(private fileSystem: Record) { + this._root = { + type: FileType.Directory, + name: '', + children: new Map() + } + this._buildTree() + } + + public rebuildTree(fs?: Record): void { + this._root.children?.clear() + if (fs) { + this.fileSystem = fs + } + this._buildTree() + // Notify watchers that everything might have changed + this._onDidChangeFile.fire([{ type: 0, resource: Uri.file('/') }]) + } + + private _buildTree() { + const fileSystem = this.fileSystem + for (const [path, content] of Object.entries(fileSystem)) { + // Normalize path to always start with / and split into segments + const normalizedPath = path.startsWith('/') ? path.slice(1) : path + const segments = normalizedPath.split('/') + + let currentNode = this._root + + // Create/traverse path segments + for (let i = 0; i < segments.length; i++) { + const segment = segments[i] + const isLast = i === segments.length - 1 + + if (!currentNode.children) { + currentNode.children = new Map() + } + + if (isLast) { + // Add file + currentNode.children.set(segment, { + type: FileType.File, + name: segment, + content + }) + } else { + // Create/traverse directory + if (!currentNode.children.has(segment)) { + currentNode.children.set(segment, { + type: FileType.Directory, + name: segment, + children: new Map() + }) + } + currentNode = currentNode.children.get(segment)! + } + } + } + } + + private _findNode(path: string): FileNode | undefined { + console.log('FIND NODE', path) + if (path === '/' || path === '') { + return this._root + } + + const segments = path.startsWith('/') ? path.slice(1).split('/') : path.split('/') + let currentNode = this._root + for (const segment of segments) { + if (!currentNode.children?.has(segment)) { + return undefined + } + currentNode = currentNode.children.get(segment)! + } + + return currentNode + } + + // Required capabilities + capabilities: FileSystemProviderCapabilities = 2048 | 1024 + onDidChangeCapabilities: Event = this._onDidChangeCapabilities.event + onDidChangeFile: Event = this._onDidChangeFile.event + onDidWatchError?: Event + + async readFile(resource: URI): Promise { + const path = this.getPath(resource) + console.log('READ FILE', resource, path) + + const node = this._findNode(path) + if (!node || node.type !== FileType.File || !node.content) { + throw FileSystemProviderError.create( + 'File not found', + FileSystemProviderErrorCode.FileNotFound + ) + } + + return new TextEncoder().encode(node.content) + } + + async writeFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise { + throw new Error('Filesystem is read-only') + } + + async stat(resource: URI): Promise { + const path = this.getPath(resource) + console.log('STAT', resource, path) + + const node = this._findNode(path) + if (!node) { + throw FileSystemProviderError.create( + 'File not found', + FileSystemProviderErrorCode.FileNotFound + ) + // throw new Error(`Path not found: ${path}`) + } + + return { + type: node.type, + ctime: Date.now(), + mtime: Date.now(), + size: node.content ? new TextEncoder().encode(node.content).length : 0 + } + } + + async readdir(resource: URI): Promise<[string, FileType][]> { + const path = this.getPath(resource) + console.log('READDIR', path) + + const node = this._findNode(path) + if (!node || node.type !== FileType.Directory || !node.children) { + throw FileSystemProviderError.create( + 'Directory not found', + FileSystemProviderErrorCode.FileNotFound + ) + } + + return Array.from(node.children.entries()).map(([name, child]) => [name, child.type]) + } + + watch(resource: URI, opts: IWatchOptions): IDisposable { + return { dispose: () => {} } + } + + async mkdir(resource: URI): Promise { + throw new Error('Filesystem is read-only') + } + + async delete(resource: URI, opts: IFileDeleteOptions): Promise { + throw new Error('Filesystem is read-only') + } + + async rename(from: URI, to: URI, opts: IFileOverwriteOptions): Promise { + throw new Error('Filesystem is read-only') + } + + private getPath(uri: URI): string { + return uri.path + } +} diff --git a/frontend/src/lib/components/raw_apps/vscode.ts b/frontend/src/lib/components/raw_apps/vscode.ts new file mode 100644 index 0000000000000..05fe92eb34ab2 --- /dev/null +++ b/frontend/src/lib/components/raw_apps/vscode.ts @@ -0,0 +1,280 @@ +import * as vscode from 'vscode' + +import { getService, IWorkbenchLayoutService, LogLevel } from 'vscode/services' +import { type RegisterLocalProcessExtensionResult } from 'vscode/extensions' +import getConfigurationServiceOverride, { + type IStoredWorkspace +} from '@codingame/monaco-vscode-configuration-service-override' +import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override' +import getLifecycleServiceOverride from '@codingame/monaco-vscode-lifecycle-service-override' +import getExplorerServiceOverride from '@codingame/monaco-vscode-explorer-service-override' +import getSearchServiceOverride from '@codingame/monaco-vscode-search-service-override' +import { useWorkerFactory } from 'monaco-editor-wrapper/workerFactory' + +import { + RegisteredFileSystemProvider, + registerFileSystemOverlay, + RegisteredMemoryFile, + type IFileSystemProviderWithFileReadWriteCapability, + FileType, + type IFileChange, + type IFileDeleteOptions, + type IFileOverwriteOptions, + type IFileWriteOptions, + type IStat, + type IWatchOptions, + FileSystemProviderError, + FileSystemProviderErrorCode +} from '@codingame/monaco-vscode-files-service-override' +import { attachPart, type Parts } from '@codingame/monaco-vscode-views-service-override' +import getMarkersServiceOverride from '@codingame/monaco-vscode-markers-service-override' +import { type WrapperConfig } from 'monaco-editor-wrapper' +import { sendUserToast } from '$lib/toast' +import { EventEmitter, Uri } from 'vscode' +import type { CancellationToken } from 'vscode/vscode/vs/base/common/cancellation' +import type { Event } from 'vscode/vscode/vs/base/common/event' +import type { IDisposable } from 'vscode/vscode/vs/base/common/lifecycle' +import type { ReadableStreamEvents } from 'vscode/vscode/vs/base/common/stream' +import type { URI } from 'vscode/vscode/vs/base/common/uri' +import type { + IFileReadStreamOptions, + IFileOpenOptions, + FileSystemProviderCapabilities +} from 'vscode/vscode/vs/platform/files/common/files' +import { ReadOnlyMemoryFileSystemProvider } from './readonly_filesystem' + +export const configureMonacoWorkers = (logger?: any) => { + useWorkerFactory({ + workerOverrides: { + ignoreMapping: true, + workerLoaders: { + // editorWorkerService: () => { + // return new Worker( + // new URL('monaco-editor-wrapper/workers/module/editor', import.meta.url), + // { + // type: 'module' + // } + // ) + // }, + + TextEditorWorker: () => + new Worker(new URL('monaco-editor/esm/vs/editor/editor.worker.js', import.meta.url), { + type: 'module' + }), + css: () => { + return new Worker(new URL('monaco-editor-wrapper/workers/module/css', import.meta.url), { + type: 'module' + }) + }, + // typescript: () => { + // return new Worker(new URL('monaco-editor-wrapper/workers/module/ts', import.meta.url), { + // type: 'module' + // }) + // }, + TextMateWorker: () => + new Worker( + new URL('@codingame/monaco-vscode-textmate-service-override/worker', import.meta.url), + { type: 'module' } + ), + LocalFileSearchWorker: () => + new Worker( + new URL('@codingame/monaco-vscode-search-service-override/worker', import.meta.url), + { type: 'module' } + ) + } + }, + logger + }) +} + +export const runApplicationPlayground = async ( + wrapper, + activityBar, + sidebar, + editors, + panel, + node_modules +) => { + const workspaceFile = vscode.Uri.file('/.vscode/workspace.code-workspace') + + const defaultViewsInit = () => { + for (const config of [ + { + part: 'workbench.parts.activitybar', + element: activityBar + }, + { + part: 'workbench.parts.sidebar', + element: sidebar + }, + // { + // part: 'workbench.parts.auxiliarybar', + // element: auxiliaryBar + // }, + { part: 'workbench.parts.editor', element: editors }, + { part: 'workbench.parts.panel', element: panel } + ]) { + attachPart(config.part as Parts, config.element) + } + } + + const wrapperConfig: WrapperConfig = { + $type: 'extended', + id: 'AAP', + logLevel: LogLevel.Debug, + htmlContainer: document.body, + vscodeApiConfig: { + serviceOverrides: { + ...getConfigurationServiceOverride(), + ...getKeybindingsServiceOverride(), + ...getLifecycleServiceOverride(), + // ...getBannerServiceOverride(), + // ...getTitleBarServiceOverride(), + // ...getModelServiceOverride(), + ...getSearchServiceOverride(), + ...getExplorerServiceOverride(), + ...getMarkersServiceOverride() + // ...getEnvironmentServiceOverride(), + }, + enableExtHostWorker: true, + viewsConfig: { + viewServiceType: 'ViewsService', + viewsInitFunc: defaultViewsInit + }, + workspaceConfig: { + enableWorkspaceTrust: true, + windowIndicator: { + label: 'mlc-app-playground', + tooltip: '', + command: '' + }, + workspaceProvider: { + trusted: true, + async open() { + window.open(window.location.href) + return true + }, + workspace: { + workspaceUri: workspaceFile + } + }, + configurationDefaults: { + 'window.title': 'mlc-app-playground${separator}${dirty}${activeEditorShort}' + }, + productConfiguration: { + nameShort: 'mlc-app-playground', + nameLong: 'mlc-app-playground' + } + }, + userConfiguration: { + json: JSON.stringify({ + 'workbench.colorTheme': 'Default Dark Modern', + 'editor.wordBasedSuggestions': 'off', + 'typescript.tsserver.web.projectWideIntellisense.enabled': true, + 'typescript.tsserver.web.projectWideIntellisense.suppressSemanticErrors': false, + 'editor.guides.bracketPairsHorizontal': true, + 'editor.experimental.asyncTokenization': false + }) + } + }, + extensions: [ + { + config: { + name: 'mlc-app-playground', + publisher: 'TypeFox', + version: '1.0.0', + engines: { + vscode: '*' + } + } + } + ], + editorAppConfig: { + monacoWorkerFactory: configureMonacoWorkers + } + } + + // const fileSystemProvider = new RegisteredFileSystemProvider(true) + // fileSystemProvider.registerFile( + // new RegisteredMemoryFile(Uri.file('/foo'), `async function foo() {}`) + // ) + // // fileSystemProvider.registerFile(new RegisteredMemoryFile(testerTsUri, `async function foo() {}`)) + // fileSystemProvider.registerFile(createDefaultWorkspaceFile(workspaceFile, '/')) + // registerFileSystemOverlay(1, fileSystemProvider) + let staticFileSystemProvider = new ReadOnlyMemoryFileSystemProvider({ + '/.vscode/settings.json': JSON.stringify( + { + 'files.exclude': { + '.vscode/': true, + node_modules: false + } + }, + null, + 2 + ), + '/tsconfig.json': JSON.stringify( + { + compilerOptions: { + target: 'ES2020', + lib: ['ES2020', 'DOM', 'DOM.Iterable'], + module: 'NodeNext', + moduleResolution: 'NodeNext', + strict: true, + esModuleInterop: true, + skipLibCheck: true, + forceConsistentCasingInFileNames: true, + resolveJsonModule: true, + isolatedModules: true, + noUnusedLocals: true, + noUnusedParameters: true, + noImplicitReturns: true, + noFallthroughCasesInSwitch: true, + declaration: true, + sourceMap: true, + allowJs: true, + checkJs: true + }, + include: ['./**/*.ts', './**/*.tsx', './**/*.js', './**/*.jsx'], + exclude: ['node_modules'] + }, + null, + 2 + ), + [workspaceFile.path]: JSON.stringify( + { + folders: [ + { + path: '/' + } + ] + }, + null, + 2 + ) + }) + let nodeModulesfileSystemProvider = new ReadOnlyMemoryFileSystemProvider(node_modules) + registerFileSystemOverlay(2, staticFileSystemProvider) + registerFileSystemOverlay(1, nodeModulesfileSystemProvider) + + await wrapper.init(wrapperConfig) + const result = wrapper.getExtensionRegisterResult( + 'mlc-app-playground' + ) as RegisterLocalProcessExtensionResult + result.setAsDefaultApi() + + await getService(IWorkbenchLayoutService) + + // show explorer and not search + await vscode.commands.executeCommand('workbench.view.explorer') + sendUserToast('Welcome to the Monaco Language Client playground!') + + //necessary for hmr to work + try { + defaultViewsInit() + } catch (e) { + console.log(e) + } + + return nodeModulesfileSystemProvider + // await Promise.all([await vscode.window.showTextDocument(helloTsUri)]) +}