diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index 2fd9c9f5fe2d0..a834fb365870b 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -1,8 +1,8 @@ + /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { create } from 'vs/workbench/workbench.web.main'; import { URI } from 'vs/base/common/uri'; import { @@ -10,6 +10,12 @@ import { IWorkspace, } from 'vs/workbench/browser/web.api'; import { SecretStorageProvider } from 'vs/code/browser/workbench/membrane'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import type { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { mainWindow } from 'vs/base/browser/window'; + declare const window: any; type Writeable = { -readonly [P in keyof T]: T[P] }; @@ -30,7 +36,6 @@ type Writeable = { -readonly [P in keyof T]: T[P] }; ]); delete window.extensionToGazePort; - const isHttps = window.location.protocol === 'https:'; const isDev = window.location.hostname === 'localhost'; const extensionUrl = { @@ -46,13 +51,13 @@ type Writeable = { -readonly [P in keyof T]: T[P] }; // TODO: Somehow use product.json to configure that globally workspace: { workspaceUri: URI.parse('memfs:/membrane.code-workspace') }, payload: { - 'skipReleaseNotes': 'true', - 'skipWelcome': 'true', + skipReleaseNotes: 'true', + skipWelcome: 'true', }, trusted: true, open: async ( _workspace: IWorkspace, - _options?: { reuse?: boolean; payload?: object } + _options?: { reuse?: boolean; payload?: object }, ) => { return true; }, @@ -61,17 +66,16 @@ type Writeable = { -readonly [P in keyof T]: T[P] }; config.secretStorageProvider = SecretStorageProvider.getInstance(); config.commands = [ - // Used to refresh the page from the extension when a new version of the IDE is known to exist. { id: 'membrane.refreshPage', handler: () => window.location.reload() }, - // Invoked when the navigator finishes loading { - id: 'membrane.completeInitialization', handler: () => window.completeInitialization?.() + id: 'membrane.completeInitialization', + handler: () => window.completeInitialization?.(), }, - // For product tour, emit an event to advance to the next step { - id: 'membrane.advanceTour', handler: (cmdArgs) => window.dispatchEvent(new Event(`tour:${cmdArgs.trigger}`)) + id: 'membrane.advanceTour', + handler: (cmdArgs) => + window.dispatchEvent(new Event(`tour:${cmdArgs.trigger}`)), }, - // For extension panels to bubble up errors { id: 'membrane.reportError', handler: (cmdArgs) => { @@ -86,7 +90,7 @@ type Writeable = { -readonly [P in keyof T]: T[P] }; (window as any).SENTRY_REPORT_ISSUE({ source: cmdArgs.source, message: cmdArgs.message, - context: cmdArgs.context + context: cmdArgs.context, }); }, }, @@ -104,8 +108,9 @@ type Writeable = { -readonly [P in keyof T]: T[P] }; { id: 'membrane.getLaunchParams', handler: () => { - // eslint-disable-next-line no-restricted-syntax - const meta = document.querySelector('meta[name="membrane-launch-params"]') as HTMLMetaElement; + const meta = mainWindow.document.querySelector( + 'meta[name="membrane-launch-params"]', + ) as HTMLMetaElement; return meta?.content ?? ''; }, }, @@ -117,7 +122,118 @@ type Writeable = { -readonly [P in keyof T]: T[P] }; title: 'Membrane Home', }; - // eslint-disable-next-line no-restricted-syntax - const domElement = (window as any).vscodeTargetContainer || document.body; + const domElement = (window as any).vscodeTargetContainer || mainWindow.document.body; create(domElement, config); + + // Register Monaco commands for review decorations + // These allow the extension to add/remove view zones (e.g., showing removed lines during code review) + CommandsRegistry.registerCommand( + 'membrane.addMonacoViewZone', + async ( + accessor: ServicesAccessor, + args: { + uri: string; + afterLineNumber: number; + heightInPx: number; + lines: string[]; + }, + ) => { + const editorService = accessor.get(IEditorService); + const targetUri = URI.parse(args.uri); + + const activeControl = editorService.activeTextEditorControl; + if (!activeControl || !isCodeEditor(activeControl)) { + return null; + } + + const model = activeControl.getModel(); + if (!model) { + return null; + } + + // Verify we're in the correct file + const modelUri = model.uri; + if ( + modelUri.scheme !== targetUri.scheme || + modelUri.path !== targetUri.path + ) { + return null; + } + + let zoneId: string | null = null; + + activeControl.changeViewZones((accessor) => { + const container = document.createElement('div'); + container.style.cssText = ` + position: relative; + background: rgba(255, 0, 0, 0.15); + border-left: 1px solid rgba(255, 0, 0, 0.4); + font-family: var(--monaco-monospace-font, 'Menlo', 'Monaco', 'Courier New', monospace); + font-size: 12px; + line-height: 18px; + padding: 0; + color: rgba(255, 100, 100, 0.9); + `; + + args.lines.forEach((line: string, idx: number) => { + const lineDiv = document.createElement('div'); + lineDiv.style.cssText = ` + position: absolute; + top: ${idx * 18}px; + left: 0; + right: 0; + white-space: pre; + overflow: hidden; + text-overflow: ellipsis; + `; + + lineDiv.textContent = line; + container.appendChild(lineDiv); + }); + + zoneId = accessor.addZone({ + afterLineNumber: args.afterLineNumber, + heightInPx: args.heightInPx, + domNode: container, + suppressMouseDown: false, + }); + }); + + return { zoneId }; + }, + ); + + CommandsRegistry.registerCommand( + 'membrane.removeMonacoViewZone', + async ( + accessor: ServicesAccessor, + args: { uri: string; zoneId: string }, + ) => { + const editorService = accessor.get(IEditorService); + const targetUri = URI.parse(args.uri); + + const activeControl = editorService.activeTextEditorControl; + if (!activeControl || !isCodeEditor(activeControl)) { + return; + } + + const model = activeControl.getModel(); + if (!model) { + return; + } + + // Verify we're in the correct file + const modelUri = model.uri; + if ( + modelUri.scheme !== targetUri.scheme || + modelUri.path !== targetUri.path + ) { + return; + } + + activeControl.changeViewZones((accessor) => { + accessor.removeZone(args.zoneId); + }); + }, + ); })();