mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 17:19:48 +01:00
Merge branch 'main' into connor4312/issue244029
This commit is contained in:
@@ -9,7 +9,6 @@ import { validatedIpcMain } from '../../base/parts/ipc/electron-main/ipcMain.js'
|
||||
import { hostname, release } from 'os';
|
||||
import { VSBuffer } from '../../base/common/buffer.js';
|
||||
import { toErrorMessage } from '../../base/common/errorMessage.js';
|
||||
import { isSigPipeError, onUnexpectedError, setUnexpectedErrorHandler } from '../../base/common/errors.js';
|
||||
import { Event } from '../../base/common/event.js';
|
||||
import { parse } from '../../base/common/jsonc.js';
|
||||
import { getPathLabel } from '../../base/common/labels.js';
|
||||
@@ -122,6 +121,7 @@ import { INativeMcpDiscoveryHelperService, NativeMcpDiscoveryHelperChannelName }
|
||||
import { NativeMcpDiscoveryHelperService } from '../../platform/mcp/node/nativeMcpDiscoveryHelperService.js';
|
||||
import { IWebContentExtractorService } from '../../platform/webContentExtractor/common/webContentExtractor.js';
|
||||
import { NativeWebContentExtractorService } from '../../platform/webContentExtractor/electron-main/webContentExtractorService.js';
|
||||
import ErrorTelemetry from '../../platform/telemetry/electron-main/errorTelemetry.js';
|
||||
|
||||
/**
|
||||
* The main VS Code application. There will only ever be one instance,
|
||||
@@ -373,15 +373,6 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// We handle uncaught exceptions here to prevent electron from opening a dialog to the user
|
||||
setUnexpectedErrorHandler(error => this.onUnexpectedError(error));
|
||||
process.on('uncaughtException', error => {
|
||||
if (!isSigPipeError(error)) {
|
||||
onUnexpectedError(error);
|
||||
}
|
||||
});
|
||||
process.on('unhandledRejection', (reason: unknown) => onUnexpectedError(reason));
|
||||
|
||||
// Dispose on shutdown
|
||||
Event.once(this.lifecycleMainService.onWillShutdown)(() => this.dispose());
|
||||
|
||||
@@ -528,25 +519,6 @@ export class CodeApplication extends Disposable {
|
||||
//#endregion
|
||||
}
|
||||
|
||||
private onUnexpectedError(error: Error): void {
|
||||
if (error) {
|
||||
|
||||
// take only the message and stack property
|
||||
const friendlyError = {
|
||||
message: `[uncaught exception in main]: ${error.message}`,
|
||||
stack: error.stack
|
||||
};
|
||||
|
||||
// handle on client side
|
||||
this.windowsMainService?.sendToFocused('vscode:reportError', JSON.stringify(friendlyError));
|
||||
}
|
||||
|
||||
this.logService.error(`[uncaught exception in main]: ${error}`);
|
||||
if (error.stack) {
|
||||
this.logService.error(error.stack);
|
||||
}
|
||||
}
|
||||
|
||||
async startup(): Promise<void> {
|
||||
this.logService.debug('Starting VS Code');
|
||||
this.logService.debug(`from: ${this.environmentMainService.appRoot}`);
|
||||
@@ -603,6 +575,9 @@ export class CodeApplication extends Disposable {
|
||||
// Services
|
||||
const appInstantiationService = await this.initServices(machineId, sqmId, devDeviceId, sharedProcessReady);
|
||||
|
||||
// Error telemetry
|
||||
appInstantiationService.invokeFunction(accessor => this._register(new ErrorTelemetry(accessor.get(ILogService), accessor.get(ITelemetryService))));
|
||||
|
||||
// Auth Handler
|
||||
appInstantiationService.invokeFunction(accessor => accessor.get(IProxyAuthService));
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { isSigPipeError, onUnexpectedError, setUnexpectedErrorHandler } from '../../../base/common/errors.js';
|
||||
import BaseErrorTelemetry from '../common/errorTelemetry.js';
|
||||
import { ITelemetryService } from '../common/telemetry.js';
|
||||
import { ILogService } from '../../../platform/log/common/log.js';
|
||||
|
||||
export default class ErrorTelemetry extends BaseErrorTelemetry {
|
||||
constructor(
|
||||
private readonly logService: ILogService,
|
||||
@ITelemetryService telemetryService: ITelemetryService
|
||||
) {
|
||||
super(telemetryService);
|
||||
}
|
||||
|
||||
protected override installErrorListeners(): void {
|
||||
// We handle uncaught exceptions here to prevent electron from opening a dialog to the user
|
||||
setUnexpectedErrorHandler(error => this.onUnexpectedError(error));
|
||||
|
||||
process.on('uncaughtException', error => {
|
||||
if (!isSigPipeError(error)) {
|
||||
onUnexpectedError(error);
|
||||
}
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason: unknown) => onUnexpectedError(reason));
|
||||
}
|
||||
|
||||
private onUnexpectedError(error: Error): void {
|
||||
this.logService.error(`[uncaught exception in main]: ${error}`);
|
||||
if (error.stack) {
|
||||
this.logService.error(error.stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,10 @@ export function registerNewChatActions() {
|
||||
},
|
||||
menu: [{
|
||||
id: MenuId.ChatContext,
|
||||
group: 'z_clear'
|
||||
group: 'z_clear',
|
||||
when: ContextKeyExpr.and(
|
||||
ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel),
|
||||
ChatContextKeys.inUnifiedChat.negate()),
|
||||
},
|
||||
{
|
||||
id: MenuId.ViewTitle,
|
||||
@@ -244,14 +247,14 @@ export function registerNewChatActions() {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.action.chat.undoEdit',
|
||||
title: localize2('chat.undoEdit.label', "Undo Last Edit"),
|
||||
title: localize2('chat.undoEdit.label', "Undo Last Request"),
|
||||
category: CHAT_CATEGORY,
|
||||
icon: Codicon.discard,
|
||||
precondition: ContextKeyExpr.and(ChatContextKeys.chatEditingCanUndo, ChatContextKeys.enabled, ChatContextKeys.editingParticipantRegistered),
|
||||
f1: true,
|
||||
menu: [{
|
||||
id: MenuId.ViewTitle,
|
||||
when: ChatContextKeyExprs.inEditingMode,
|
||||
when: ChatContextKeyExprs.inEditsOrUnified,
|
||||
group: 'navigation',
|
||||
order: -3
|
||||
}]
|
||||
@@ -267,14 +270,14 @@ export function registerNewChatActions() {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.action.chat.redoEdit',
|
||||
title: localize2('chat.redoEdit.label', "Redo Last Edit"),
|
||||
title: localize2('chat.redoEdit.label', "Redo Last Request"),
|
||||
category: CHAT_CATEGORY,
|
||||
icon: Codicon.redo,
|
||||
precondition: ContextKeyExpr.and(ChatContextKeys.chatEditingCanRedo, ChatContextKeys.enabled, ChatContextKeys.editingParticipantRegistered),
|
||||
f1: true,
|
||||
menu: [{
|
||||
id: MenuId.ViewTitle,
|
||||
when: ChatContextKeyExprs.inEditingMode,
|
||||
when: ChatContextKeyExprs.inEditsOrUnified,
|
||||
group: 'navigation',
|
||||
order: -2
|
||||
}]
|
||||
|
||||
@@ -358,7 +358,7 @@ export function registerChatTitleActions() {
|
||||
f1: false,
|
||||
category: CHAT_CATEGORY,
|
||||
icon: Codicon.x,
|
||||
precondition: ChatContextKeys.chatMode.isEqualTo(ChatMode.Ask),
|
||||
precondition: ContextKeyExpr.and(ChatContextKeys.chatMode.isEqualTo(ChatMode.Ask), ChatContextKeyExprs.unifiedChatEnabled.negate()),
|
||||
keybinding: {
|
||||
primary: KeyCode.Delete,
|
||||
mac: {
|
||||
@@ -371,7 +371,7 @@ export function registerChatTitleActions() {
|
||||
id: MenuId.ChatMessageTitle,
|
||||
group: 'navigation',
|
||||
order: 2,
|
||||
when: ContextKeyExpr.and(ChatContextKeys.chatMode.isEqualTo(ChatMode.Ask), ChatContextKeys.isRequest)
|
||||
when: ContextKeyExpr.and(ChatContextKeys.chatMode.isEqualTo(ChatMode.Ask), ChatContextKeys.isRequest, ChatContextKeyExprs.unifiedChatEnabled.negate())
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -439,7 +439,7 @@ registerAction2(class RemoveAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.action.chat.undoEdits',
|
||||
title: localize2('chat.undoEdits.label', "Undo Edits"),
|
||||
title: localize2('chat.undoEdits.label', "Undo Requests"),
|
||||
f1: false,
|
||||
category: CHAT_CATEGORY,
|
||||
icon: Codicon.x,
|
||||
@@ -448,7 +448,7 @@ registerAction2(class RemoveAction extends Action2 {
|
||||
mac: {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.Backspace,
|
||||
},
|
||||
when: ContextKeyExpr.and(ChatContextKeys.chatMode.notEqualsTo(ChatMode.Ask), ChatContextKeys.inChatSession, EditorContextKeys.textInputFocus.negate()),
|
||||
when: ContextKeyExpr.and(ChatContextKeys.inChatSession, EditorContextKeys.textInputFocus.negate()),
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
},
|
||||
menu: [
|
||||
@@ -456,7 +456,7 @@ registerAction2(class RemoveAction extends Action2 {
|
||||
id: MenuId.ChatMessageTitle,
|
||||
group: 'navigation',
|
||||
order: 2,
|
||||
when: ContextKeyExpr.and(ChatContextKeys.chatMode.notEqualsTo(ChatMode.Ask), ChatContextKeys.isRequest)
|
||||
when: ChatContextKeys.isRequest
|
||||
}
|
||||
]
|
||||
});
|
||||
@@ -479,7 +479,7 @@ registerAction2(class RemoveAction extends Action2 {
|
||||
const chatEditingService = accessor.get(IChatEditingService);
|
||||
const chatService = accessor.get(IChatService);
|
||||
const chatModel = chatService.getSession(item.sessionId);
|
||||
if (chatModel?.initialLocation !== ChatAgentLocation.EditingSession) {
|
||||
if (!chatModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,13 @@ import { CancellationToken } from '../../../../../base/common/cancellation.js';
|
||||
import { MarkdownString } from '../../../../../base/common/htmlContent.js';
|
||||
import { IDisposable } from '../../../../../base/common/lifecycle.js';
|
||||
import { autorun } from '../../../../../base/common/observable.js';
|
||||
import { isEqual } from '../../../../../base/common/resources.js';
|
||||
import { URI, UriComponents } from '../../../../../base/common/uri.js';
|
||||
import { generateUuid } from '../../../../../base/common/uuid.js';
|
||||
import { localize } from '../../../../../nls.js';
|
||||
import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js';
|
||||
import { SaveReason } from '../../../../common/editor.js';
|
||||
import { GroupsOrder, IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js';
|
||||
import { ITextFileService } from '../../../../services/textfile/common/textfiles.js';
|
||||
import { CellUri } from '../../../notebook/common/notebookCommon.js';
|
||||
import { INotebookService } from '../../../notebook/common/notebookService.js';
|
||||
@@ -79,6 +81,7 @@ export class EditTool implements IToolImpl {
|
||||
@ILanguageModelIgnoredFilesService private readonly ignoredFilesService: ILanguageModelIgnoredFilesService,
|
||||
@ITextFileService private readonly textFileService: ITextFileService,
|
||||
@INotebookService private readonly notebookService: INotebookService,
|
||||
@IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService,
|
||||
) { }
|
||||
|
||||
async invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, token: CancellationToken): Promise<IToolResult> {
|
||||
@@ -88,8 +91,18 @@ export class EditTool implements IToolImpl {
|
||||
|
||||
const parameters = invocation.parameters as EditToolParams;
|
||||
const uri = URI.revive(parameters.file); // TODO@roblourens do revive in MainThreadLanguageModelTools
|
||||
|
||||
if (!this.workspaceContextService.isInsideWorkspace(uri)) {
|
||||
throw new Error(`File ${uri.fsPath} can't be edited because it's not inside the current workspace`);
|
||||
const groupsByLastActive = this.editorGroupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE);
|
||||
const uriIsOpenInSomeEditor = groupsByLastActive.some((group) => {
|
||||
return group.editors.some((editor) => {
|
||||
return isEqual(editor.resource, uri);
|
||||
});
|
||||
});
|
||||
|
||||
if (!uriIsOpenInSomeEditor) {
|
||||
throw new Error(`File ${uri.fsPath} can't be edited because it's not inside the current workspace`);
|
||||
}
|
||||
}
|
||||
|
||||
if (await this.ignoredFilesService.fileIsIgnored(uri, token)) {
|
||||
|
||||
@@ -59,14 +59,14 @@ export class FetchWebPageTool implements IToolImpl {
|
||||
|
||||
const contents = await this._readerModeService.extract(validUris);
|
||||
// Make an array that contains either the content or undefined for invalid URLs
|
||||
const contentsWithUndefined = new Map<string, string | undefined>();
|
||||
const contentsWithUndefined: (string | undefined)[] = [];
|
||||
let indexInContents = 0;
|
||||
parsedUriResults.forEach((uri, url) => {
|
||||
parsedUriResults.forEach((uri) => {
|
||||
if (uri) {
|
||||
contentsWithUndefined.set(url, contents[indexInContents]);
|
||||
contentsWithUndefined.push(contents[indexInContents]);
|
||||
indexInContents++;
|
||||
} else {
|
||||
contentsWithUndefined.set(url, undefined);
|
||||
contentsWithUndefined.push(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -154,21 +154,10 @@ export class FetchWebPageTool implements IToolImpl {
|
||||
return results;
|
||||
}
|
||||
|
||||
private _getPromptPartsForResults(results: Map<string, string | undefined>): IToolResultTextPart[] {
|
||||
const arr = new Array<IToolResultTextPart>();
|
||||
for (const [url, content] of results.entries()) {
|
||||
if (content) {
|
||||
arr.push({
|
||||
kind: 'text',
|
||||
value: `<!-- ${url} -->\n\n` + content
|
||||
});
|
||||
} else {
|
||||
arr.push({
|
||||
kind: 'text',
|
||||
value: `<!-- ${url} -->\n\n` + localize('fetchWebPage.invalidUrl', 'Invalid URL')
|
||||
});
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
private _getPromptPartsForResults(results: (string | undefined)[]): IToolResultTextPart[] {
|
||||
return results.map(value => ({
|
||||
kind: 'text',
|
||||
value: value || localize('fetchWebPage.invalidUrl', 'Invalid URL')
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import { McpRegistry } from '../common/mcpRegistry.js';
|
||||
import { IMcpRegistry } from '../common/mcpRegistryTypes.js';
|
||||
import { McpService } from '../common/mcpService.js';
|
||||
import { IMcpService } from '../common/mcpTypes.js';
|
||||
import { AddConfigurationAction, EditStoredInput, InstallFromActivation, ListMcpServerCommand, MCPServerActionRendering, McpServerOptionsCommand, RemoveStoredInput, ResetMcpCachedTools, ResetMcpTrustCommand, ShowOutput, StartServer, StopServer } from './mcpCommands.js';
|
||||
import { AddConfigurationAction, EditStoredInput, InstallFromActivation, ListMcpServerCommand, MCPServerActionRendering, McpServerOptionsCommand, RemoveStoredInput, ResetMcpCachedTools, ResetMcpTrustCommand, RestartServer, ShowOutput, StartServer, StopServer } from './mcpCommands.js';
|
||||
import { McpDiscovery } from './mcpDiscovery.js';
|
||||
import { McpLanguageFeatures } from './mcpLanguageFeatures.js';
|
||||
import { McpUrlHandler } from './mcpUrlHandler.js';
|
||||
@@ -50,6 +50,7 @@ registerAction2(StartServer);
|
||||
registerAction2(StopServer);
|
||||
registerAction2(ShowOutput);
|
||||
registerAction2(InstallFromActivation);
|
||||
registerAction2(RestartServer);
|
||||
|
||||
registerWorkbenchContribution2('mcpActionRendering', MCPServerActionRendering, WorkbenchPhase.BlockRestore);
|
||||
|
||||
|
||||
@@ -471,6 +471,26 @@ export class ShowOutput extends Action2 {
|
||||
}
|
||||
}
|
||||
|
||||
export class RestartServer extends Action2 {
|
||||
static readonly ID = 'workbench.mcp.restartServer';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: RestartServer.ID,
|
||||
title: localize2('mcp.command.restartServer', "Restart Server"),
|
||||
category,
|
||||
f1: false,
|
||||
});
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor, serverId: string) {
|
||||
const s = accessor.get(IMcpService).servers.get().find(s => s.definition.id === serverId);
|
||||
s?.showOutput();
|
||||
await s?.stop();
|
||||
await s?.start();
|
||||
}
|
||||
}
|
||||
|
||||
export class StartServer extends Action2 {
|
||||
static readonly ID = 'workbench.mcp.startServer';
|
||||
|
||||
@@ -485,7 +505,6 @@ export class StartServer extends Action2 {
|
||||
|
||||
async run(accessor: ServicesAccessor, serverId: string) {
|
||||
const s = accessor.get(IMcpService).servers.get().find(s => s.definition.id === serverId);
|
||||
await s?.stop();
|
||||
await s?.start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import { ConfigurationResolverExpression, IResolvedValue } from '../../../servic
|
||||
import { IMcpConfigPathsService } from '../common/mcpConfigPathsService.js';
|
||||
import { IMcpRegistry } from '../common/mcpRegistryTypes.js';
|
||||
import { IMcpService, McpConnectionState } from '../common/mcpTypes.js';
|
||||
import { EditStoredInput, RemoveStoredInput, ShowOutput, StartServer, StopServer } from './mcpCommands.js';
|
||||
import { EditStoredInput, RemoveStoredInput, RestartServer, ShowOutput, StartServer, StopServer } from './mcpCommands.js';
|
||||
|
||||
export class McpLanguageFeatures extends Disposable implements IWorkbenchContribution {
|
||||
private readonly _cachedMcpSection = this._register(new MutableDisposable<{ model: ITextModel; node: Node } & IDisposable>());
|
||||
@@ -113,7 +113,7 @@ export class McpLanguageFeatures extends Disposable implements IWorkbenchContrib
|
||||
}, {
|
||||
range,
|
||||
command: {
|
||||
id: StartServer.ID,
|
||||
id: RestartServer.ID,
|
||||
title: localize('mcp.restart', "Restart"),
|
||||
arguments: [server.definition.id],
|
||||
},
|
||||
@@ -147,19 +147,19 @@ export class McpLanguageFeatures extends Disposable implements IWorkbenchContrib
|
||||
}, {
|
||||
range,
|
||||
command: {
|
||||
id: StartServer.ID,
|
||||
id: RestartServer.ID,
|
||||
title: localize('mcp.restart', "Restart"),
|
||||
arguments: [server.definition.id],
|
||||
},
|
||||
}, {
|
||||
range,
|
||||
command: {
|
||||
id: 'workbench.action.chat.attachTools',
|
||||
id: '',
|
||||
title: localize('server.toolCount', '{0} tools', read(server.tools).length),
|
||||
},
|
||||
});
|
||||
break;
|
||||
case McpConnectionState.Kind.Stopped:
|
||||
case McpConnectionState.Kind.Stopped: {
|
||||
lenses.lenses.push({
|
||||
range,
|
||||
command: {
|
||||
@@ -168,6 +168,17 @@ export class McpLanguageFeatures extends Disposable implements IWorkbenchContrib
|
||||
arguments: [server.definition.id],
|
||||
},
|
||||
});
|
||||
const toolCount = read(server.tools).length;
|
||||
if (toolCount) {
|
||||
lenses.lenses.push({
|
||||
range,
|
||||
command: {
|
||||
id: '',
|
||||
title: localize('server.toolCountCached', '{0} cached tools', toolCount),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -293,7 +293,7 @@ export class McpRegistry extends Disposable implements IMcpRegistry {
|
||||
return await this._configurationResolverService.resolveAsync(folder, expr);
|
||||
}
|
||||
|
||||
public async resolveConnection({ collectionRef, definitionRef, forceTrust }: IMcpResolveConnectionOptions): Promise<IMcpServerConnection | undefined> {
|
||||
public async resolveConnection({ collectionRef, definitionRef, forceTrust, logger }: IMcpResolveConnectionOptions): Promise<IMcpServerConnection | undefined> {
|
||||
const collection = this._collections.get().find(c => c.id === collectionRef.id);
|
||||
const definition = collection?.serverDefinitions.get().find(s => s.id === definitionRef.id);
|
||||
if (!collection || !definition) {
|
||||
@@ -356,6 +356,7 @@ export class McpRegistry extends Disposable implements IMcpRegistry {
|
||||
definition,
|
||||
delegate,
|
||||
launch,
|
||||
logger,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { IDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { IObservable } from '../../../../base/common/observable.js';
|
||||
import { ConfigurationTarget } from '../../../../platform/configuration/common/configuration.js';
|
||||
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { ILogger } from '../../../../platform/log/common/log.js';
|
||||
import { StorageScope } from '../../../../platform/storage/common/storage.js';
|
||||
import { IWorkspaceFolderData } from '../../../../platform/workspace/common/workspace.js';
|
||||
import { IResolvedValue } from '../../../services/configurationResolver/common/configurationResolverExpression.js';
|
||||
@@ -32,6 +33,7 @@ export interface IMcpHostDelegate {
|
||||
}
|
||||
|
||||
export interface IMcpResolveConnectionOptions {
|
||||
logger: ILogger;
|
||||
collectionRef: McpCollectionReference;
|
||||
definitionRef: McpDefinitionReference;
|
||||
/** If set, the user will be asked to trust the collection even if they untrusted it previously */
|
||||
|
||||
@@ -10,9 +10,11 @@ import { LRUCache } from '../../../../base/common/map.js';
|
||||
import { autorun, autorunWithStore, derived, disposableObservableValue, IObservable, ITransaction, observableFromEvent, ObservablePromise, observableValue, transaction } from '../../../../base/common/observable.js';
|
||||
import { basename } from '../../../../base/common/resources.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { ILogger, ILoggerService } from '../../../../platform/log/common/log.js';
|
||||
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
|
||||
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
|
||||
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
|
||||
import { IOutputService } from '../../../services/output/common/output.js';
|
||||
import { mcpActivationEvent } from './mcpConfiguration.js';
|
||||
import { IMcpRegistry } from './mcpRegistryTypes.js';
|
||||
import { McpServerRequestHandler } from './mcpServerRequestHandler.js';
|
||||
@@ -130,6 +132,9 @@ export class McpServer extends Disposable implements IMcpServer {
|
||||
return fromServerResult.error ? (this.toolsFromCache ? McpServerToolsState.Cached : McpServerToolsState.Unknown) : McpServerToolsState.Live;
|
||||
});
|
||||
|
||||
private readonly _loggerId: string;
|
||||
private readonly _logger: ILogger;
|
||||
|
||||
public get trusted() {
|
||||
return this._mcpRegistry.getTrust(this.collection);
|
||||
}
|
||||
@@ -143,9 +148,17 @@ export class McpServer extends Disposable implements IMcpServer {
|
||||
@IMcpRegistry private readonly _mcpRegistry: IMcpRegistry,
|
||||
@IWorkspaceContextService workspacesService: IWorkspaceContextService,
|
||||
@IExtensionService private readonly _extensionService: IExtensionService,
|
||||
@ILoggerService private readonly _loggerService: ILoggerService,
|
||||
@IOutputService private readonly _outputService: IOutputService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._loggerId = `mcpServer/${definition.id}`;
|
||||
this._logger = this._register(_loggerService.createLogger(this._loggerId, { hidden: true, name: `MCP: ${definition.label}` }));
|
||||
// If the logger is disposed but not deregistered, then the disposed instance
|
||||
// is reused and no-ops. todo@sandy081 this seems like a bug.
|
||||
this._register(toDisposable(() => _loggerService.deregisterLogger(this._loggerId)));
|
||||
|
||||
// 1. Reflect workspaces into the MCP roots
|
||||
const workspaces = explicitRoots
|
||||
? observableValue(this, explicitRoots.map(uri => ({ uri, name: basename(uri) })))
|
||||
@@ -196,7 +209,8 @@ export class McpServer extends Disposable implements IMcpServer {
|
||||
}
|
||||
|
||||
public showOutput(): void {
|
||||
this._connection.get()?.showOutput();
|
||||
this._loggerService.setVisibility(this._loggerId, true);
|
||||
this._outputService.showChannel(this._loggerId);
|
||||
}
|
||||
|
||||
public start(isFromInteraction?: boolean): Promise<McpConnectionState> {
|
||||
@@ -222,6 +236,7 @@ export class McpServer extends Disposable implements IMcpServer {
|
||||
|
||||
if (!connection) {
|
||||
connection = await this._mcpRegistry.resolveConnection({
|
||||
logger: this._logger,
|
||||
collectionRef: this.collection,
|
||||
definitionRef: this.definition,
|
||||
forceTrust: isFromInteraction,
|
||||
|
||||
@@ -8,11 +8,10 @@ import { Disposable, DisposableStore, IReference, MutableDisposable, toDisposabl
|
||||
import { autorun, IObservable, observableValue } from '../../../../base/common/observable.js';
|
||||
import { localize } from '../../../../nls.js';
|
||||
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { ILogger, ILoggerService } from '../../../../platform/log/common/log.js';
|
||||
import { IOutputService } from '../../../services/output/common/output.js';
|
||||
import { ILogger } from '../../../../platform/log/common/log.js';
|
||||
import { IMcpHostDelegate, IMcpMessageTransport } from './mcpRegistryTypes.js';
|
||||
import { McpServerRequestHandler } from './mcpServerRequestHandler.js';
|
||||
import { McpCollectionDefinition, IMcpServerConnection, McpServerDefinition, McpConnectionState, McpServerLaunch } from './mcpTypes.js';
|
||||
import { IMcpServerConnection, McpCollectionDefinition, McpConnectionState, McpServerDefinition, McpServerLaunch } from './mcpTypes.js';
|
||||
|
||||
export class McpServerConnection extends Disposable implements IMcpServerConnection {
|
||||
private readonly _launch = this._register(new MutableDisposable<IReference<IMcpMessageTransport>>());
|
||||
@@ -22,8 +21,6 @@ export class McpServerConnection extends Disposable implements IMcpServerConnect
|
||||
public readonly state: IObservable<McpConnectionState> = this._state;
|
||||
public readonly handler: IObservable<McpServerRequestHandler | undefined> = this._requestHandler;
|
||||
|
||||
private readonly _loggerId: string;
|
||||
private readonly _logger: ILogger;
|
||||
private _launchId = 0;
|
||||
|
||||
constructor(
|
||||
@@ -31,22 +28,10 @@ export class McpServerConnection extends Disposable implements IMcpServerConnect
|
||||
public readonly definition: McpServerDefinition,
|
||||
private readonly _delegate: IMcpHostDelegate,
|
||||
public readonly launchDefinition: McpServerLaunch,
|
||||
@ILoggerService private readonly _loggerService: ILoggerService,
|
||||
@IOutputService private readonly _outputService: IOutputService,
|
||||
private readonly _logger: ILogger,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
this._loggerId = `mcpServer/${definition.id}`;
|
||||
this._logger = this._register(_loggerService.createLogger(this._loggerId, { hidden: true, name: `MCP: ${definition.label}` }));
|
||||
// If the logger is disposed but not deregistered, then the disposed instance
|
||||
// is reused and no-ops. todo@sandy081 this seems like a bug.
|
||||
this._register(toDisposable(() => _loggerService.deregisterLogger(this._loggerId)));
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public showOutput(): void {
|
||||
this._loggerService.setVisibility(this._loggerId, true);
|
||||
this._outputService.showChannel(this._loggerId);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
|
||||
@@ -320,11 +320,6 @@ export interface IMcpServerConnection extends IDisposable {
|
||||
readonly state: IObservable<McpConnectionState>;
|
||||
readonly handler: IObservable<McpServerRequestHandler | undefined>;
|
||||
|
||||
/**
|
||||
* Shows the current server output.
|
||||
*/
|
||||
showOutput(): void;
|
||||
|
||||
/**
|
||||
* Starts the server if it's stopped. Returns a promise that resolves once
|
||||
* server exits a 'starting' state.
|
||||
|
||||
@@ -14,7 +14,7 @@ import { ConfigurationTarget } from '../../../../../platform/configuration/commo
|
||||
import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';
|
||||
import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js';
|
||||
import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
|
||||
import { ILoggerService } from '../../../../../platform/log/common/log.js';
|
||||
import { ILogger, ILoggerService, NullLogger } from '../../../../../platform/log/common/log.js';
|
||||
import { IProductService } from '../../../../../platform/product/common/productService.js';
|
||||
import { ISecretStorageService } from '../../../../../platform/secrets/common/secrets.js';
|
||||
import { TestSecretStorageService } from '../../../../../platform/secrets/test/common/testSecretStorageService.js';
|
||||
@@ -120,6 +120,7 @@ suite('Workbench - MCP - Registry', () => {
|
||||
let testDialogService: TestDialogService;
|
||||
let testCollection: McpCollectionDefinition & { serverDefinitions: ISettableObservable<McpServerDefinition[]> };
|
||||
let baseDefinition: McpServerDefinition;
|
||||
let logger: ILogger;
|
||||
|
||||
setup(() => {
|
||||
testConfigResolverService = new TestConfigurationResolverService();
|
||||
@@ -136,6 +137,8 @@ suite('Workbench - MCP - Registry', () => {
|
||||
[IProductService, {}],
|
||||
);
|
||||
|
||||
logger = new NullLogger();
|
||||
|
||||
const instaService = store.add(new TestInstantiationService(services));
|
||||
registry = store.add(instaService.createInstance(McpRegistry));
|
||||
|
||||
@@ -211,7 +214,7 @@ suite('Workbench - MCP - Registry', () => {
|
||||
testCollection.serverDefinitions.set([definition], undefined);
|
||||
store.add(registry.registerCollection(testCollection));
|
||||
|
||||
const connection = await registry.resolveConnection({ collectionRef: testCollection, definitionRef: definition }) as McpServerConnection;
|
||||
const connection = await registry.resolveConnection({ collectionRef: testCollection, definitionRef: definition, logger }) as McpServerConnection;
|
||||
|
||||
assert.ok(connection);
|
||||
assert.strictEqual(connection.definition, definition);
|
||||
@@ -219,7 +222,7 @@ suite('Workbench - MCP - Registry', () => {
|
||||
assert.strictEqual((connection.launchDefinition as any).env.PATH, 'interactiveValue0');
|
||||
connection.dispose();
|
||||
|
||||
const connection2 = await registry.resolveConnection({ collectionRef: testCollection, definitionRef: definition }) as McpServerConnection;
|
||||
const connection2 = await registry.resolveConnection({ collectionRef: testCollection, definitionRef: definition, logger }) as McpServerConnection;
|
||||
|
||||
assert.ok(connection2);
|
||||
assert.strictEqual((connection2.launchDefinition as any).env.PATH, 'interactiveValue0');
|
||||
@@ -227,7 +230,7 @@ suite('Workbench - MCP - Registry', () => {
|
||||
|
||||
registry.clearSavedInputs(StorageScope.WORKSPACE);
|
||||
|
||||
const connection3 = await registry.resolveConnection({ collectionRef: testCollection, definitionRef: definition }) as McpServerConnection;
|
||||
const connection3 = await registry.resolveConnection({ collectionRef: testCollection, definitionRef: definition, logger }) as McpServerConnection;
|
||||
|
||||
assert.ok(connection3);
|
||||
assert.strictEqual((connection3.launchDefinition as any).env.PATH, 'interactiveValue4');
|
||||
@@ -245,7 +248,7 @@ suite('Workbench - MCP - Registry', () => {
|
||||
store.add(registry.registerCollection(testCollection));
|
||||
testCollection.serverDefinitions.set([definition], undefined);
|
||||
|
||||
const connection = await registry.resolveConnection({ collectionRef: testCollection, definitionRef: definition });
|
||||
const connection = await registry.resolveConnection({ collectionRef: testCollection, definitionRef: definition, logger });
|
||||
|
||||
assert.ok(connection);
|
||||
assert.strictEqual(testDialogService.promptSpy.called, false);
|
||||
@@ -265,6 +268,7 @@ suite('Workbench - MCP - Registry', () => {
|
||||
testDialogService.setPromptResult(true);
|
||||
|
||||
const connection = await registry.resolveConnection({
|
||||
logger,
|
||||
collectionRef: untrustedCollection,
|
||||
definitionRef: definition
|
||||
});
|
||||
@@ -275,6 +279,7 @@ suite('Workbench - MCP - Registry', () => {
|
||||
|
||||
testDialogService.promptSpy.resetHistory();
|
||||
const connection2 = await registry.resolveConnection({
|
||||
logger,
|
||||
collectionRef: untrustedCollection,
|
||||
definitionRef: definition
|
||||
});
|
||||
@@ -297,6 +302,7 @@ suite('Workbench - MCP - Registry', () => {
|
||||
testDialogService.setPromptResult(false);
|
||||
|
||||
const connection = await registry.resolveConnection({
|
||||
logger,
|
||||
collectionRef: untrustedCollection,
|
||||
definitionRef: definition
|
||||
});
|
||||
@@ -306,6 +312,7 @@ suite('Workbench - MCP - Registry', () => {
|
||||
|
||||
testDialogService.promptSpy.resetHistory();
|
||||
const connection2 = await registry.resolveConnection({
|
||||
logger,
|
||||
collectionRef: untrustedCollection,
|
||||
definitionRef: definition
|
||||
});
|
||||
@@ -327,6 +334,7 @@ suite('Workbench - MCP - Registry', () => {
|
||||
testDialogService.setPromptResult(false);
|
||||
|
||||
const connection1 = await registry.resolveConnection({
|
||||
logger,
|
||||
collectionRef: untrustedCollection,
|
||||
definitionRef: definition
|
||||
});
|
||||
@@ -337,6 +345,7 @@ suite('Workbench - MCP - Registry', () => {
|
||||
testDialogService.setPromptResult(true);
|
||||
|
||||
const connection2 = await registry.resolveConnection({
|
||||
logger,
|
||||
collectionRef: untrustedCollection,
|
||||
definitionRef: definition,
|
||||
forceTrust: true
|
||||
@@ -348,6 +357,7 @@ suite('Workbench - MCP - Registry', () => {
|
||||
|
||||
testDialogService.promptSpy.resetHistory();
|
||||
const connection3 = await registry.resolveConnection({
|
||||
logger,
|
||||
collectionRef: untrustedCollection,
|
||||
definitionRef: definition
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ import { URI } from '../../../../../base/common/uri.js';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
|
||||
import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js';
|
||||
import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
|
||||
import { ILogger, ILoggerService } from '../../../../../platform/log/common/log.js';
|
||||
import { ILogger, ILoggerService, NullLogger } from '../../../../../platform/log/common/log.js';
|
||||
import { IProductService } from '../../../../../platform/product/common/productService.js';
|
||||
import { IStorageService, StorageScope } from '../../../../../platform/storage/common/storage.js';
|
||||
import { IOutputService } from '../../../../services/output/common/output.js';
|
||||
@@ -127,7 +127,8 @@ suite('Workbench - MCP - ServerConnection', () => {
|
||||
collection,
|
||||
serverDefinition,
|
||||
delegate,
|
||||
serverDefinition.launch
|
||||
serverDefinition.launch,
|
||||
new NullLogger(),
|
||||
);
|
||||
store.add(connection);
|
||||
|
||||
@@ -154,7 +155,8 @@ suite('Workbench - MCP - ServerConnection', () => {
|
||||
collection,
|
||||
serverDefinition,
|
||||
delegate,
|
||||
serverDefinition.launch
|
||||
serverDefinition.launch,
|
||||
new NullLogger(),
|
||||
);
|
||||
store.add(connection);
|
||||
|
||||
@@ -172,7 +174,8 @@ suite('Workbench - MCP - ServerConnection', () => {
|
||||
collection,
|
||||
serverDefinition,
|
||||
delegate,
|
||||
serverDefinition.launch
|
||||
serverDefinition.launch,
|
||||
new NullLogger(),
|
||||
);
|
||||
store.add(connection);
|
||||
|
||||
@@ -197,7 +200,8 @@ suite('Workbench - MCP - ServerConnection', () => {
|
||||
collection,
|
||||
serverDefinition,
|
||||
delegate,
|
||||
serverDefinition.launch
|
||||
serverDefinition.launch,
|
||||
new NullLogger(),
|
||||
);
|
||||
store.add(connection);
|
||||
|
||||
@@ -220,7 +224,8 @@ suite('Workbench - MCP - ServerConnection', () => {
|
||||
collection,
|
||||
serverDefinition,
|
||||
delegate,
|
||||
serverDefinition.launch
|
||||
serverDefinition.launch,
|
||||
new NullLogger(),
|
||||
);
|
||||
store.add(connection);
|
||||
|
||||
@@ -251,7 +256,8 @@ suite('Workbench - MCP - ServerConnection', () => {
|
||||
collection,
|
||||
serverDefinition,
|
||||
delegate,
|
||||
serverDefinition.launch
|
||||
serverDefinition.launch,
|
||||
new NullLogger(),
|
||||
);
|
||||
|
||||
// Start the connection
|
||||
@@ -265,81 +271,24 @@ suite('Workbench - MCP - ServerConnection', () => {
|
||||
assert.strictEqual(connection.state.get().state, McpConnectionState.Kind.Stopped);
|
||||
});
|
||||
|
||||
test('showOutput should call logger and output services', () => {
|
||||
let channelShown = false;
|
||||
const outputService = {
|
||||
showChannel: (id: string) => {
|
||||
assert.strictEqual(id, `mcpServer/${serverDefinition.id}`);
|
||||
channelShown = true;
|
||||
}
|
||||
};
|
||||
|
||||
let loggerVisible = false;
|
||||
const loggerService = new class extends TestLoggerService {
|
||||
override setVisibility(id: string, visible: boolean): void {
|
||||
assert.strictEqual(id, `mcpServer/${serverDefinition.id}`);
|
||||
assert.strictEqual(visible, true);
|
||||
loggerVisible = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Override services
|
||||
const services = new ServiceCollection(
|
||||
[ILoggerService, store.add(loggerService)],
|
||||
[IOutputService, upcast(outputService)],
|
||||
[IStorageService, store.add(new TestStorageService())]
|
||||
);
|
||||
|
||||
const localInstantiationService = store.add(new TestInstantiationService(services));
|
||||
|
||||
// Create server connection
|
||||
const connection = localInstantiationService.createInstance(
|
||||
McpServerConnection,
|
||||
collection,
|
||||
serverDefinition,
|
||||
delegate,
|
||||
serverDefinition.launch
|
||||
);
|
||||
store.add(connection);
|
||||
|
||||
// Show output
|
||||
connection.showOutput();
|
||||
|
||||
assert.strictEqual(channelShown, true);
|
||||
assert.strictEqual(loggerVisible, true);
|
||||
});
|
||||
|
||||
test('should log transport messages', async () => {
|
||||
// Track logged messages
|
||||
const loggedMessages: string[] = [];
|
||||
const loggerService = new class extends TestLoggerService {
|
||||
override createLogger(id: string) {
|
||||
return {
|
||||
info: (message: string) => {
|
||||
loggedMessages.push(message);
|
||||
},
|
||||
error: () => { },
|
||||
dispose: () => { }
|
||||
} as Partial<ILogger> as ILogger;
|
||||
}
|
||||
};
|
||||
|
||||
// Override services
|
||||
const services = new ServiceCollection(
|
||||
[ILoggerService, store.add(loggerService)],
|
||||
[IOutputService, upcast({ showChannel: () => { } })],
|
||||
[IStorageService, store.add(new TestStorageService())]
|
||||
);
|
||||
|
||||
const localInstantiationService = store.add(new TestInstantiationService(services));
|
||||
|
||||
// Create server connection
|
||||
const connection = localInstantiationService.createInstance(
|
||||
const connection = instantiationService.createInstance(
|
||||
McpServerConnection,
|
||||
collection,
|
||||
serverDefinition,
|
||||
delegate,
|
||||
serverDefinition.launch
|
||||
serverDefinition.launch,
|
||||
{
|
||||
info: (message: string) => {
|
||||
loggedMessages.push(message);
|
||||
},
|
||||
error: () => { },
|
||||
dispose: () => { }
|
||||
} as Partial<ILogger> as ILogger,
|
||||
);
|
||||
store.add(connection);
|
||||
|
||||
@@ -355,6 +304,9 @@ suite('Workbench - MCP - ServerConnection', () => {
|
||||
|
||||
// Check that the message was logged
|
||||
assert.ok(loggedMessages.some(msg => msg === 'Test log message'));
|
||||
|
||||
connection.dispose();
|
||||
await timeout(10);
|
||||
});
|
||||
|
||||
test('should correctly handle transitions to and from error state', async () => {
|
||||
@@ -364,7 +316,8 @@ suite('Workbench - MCP - ServerConnection', () => {
|
||||
collection,
|
||||
serverDefinition,
|
||||
delegate,
|
||||
serverDefinition.launch
|
||||
serverDefinition.launch,
|
||||
new NullLogger(),
|
||||
);
|
||||
store.add(connection);
|
||||
|
||||
@@ -401,7 +354,8 @@ suite('Workbench - MCP - ServerConnection', () => {
|
||||
collection,
|
||||
serverDefinition,
|
||||
delegate,
|
||||
serverDefinition.launch
|
||||
serverDefinition.launch,
|
||||
new NullLogger(),
|
||||
);
|
||||
store.add(connection);
|
||||
|
||||
|
||||
@@ -929,9 +929,9 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
|
||||
|
||||
deltaCellOutputContainerClassNames(diffSide: DiffSide, cellId: string, added: string[], removed: string[]) {
|
||||
if (diffSide === DiffSide.Original) {
|
||||
this._originalWebview?.deltaCellContainerClassNames(cellId, added, removed);
|
||||
this._originalWebview?.deltaCellOutputContainerClassNames(cellId, added, removed);
|
||||
} else {
|
||||
this._modifiedWebview?.deltaCellContainerClassNames(cellId, added, removed);
|
||||
this._modifiedWebview?.deltaCellOutputContainerClassNames(cellId, added, removed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -807,7 +807,7 @@ export interface INotebookEditorDelegate extends INotebookEditor {
|
||||
* Hide the inset in the webview layer without removing it
|
||||
*/
|
||||
hideInset(output: IDisplayOutputViewModel): void;
|
||||
deltaCellContainerClassNames(cellId: string, added: string[], removed: string[]): void;
|
||||
deltaCellContainerClassNames(cellId: string, added: string[], removed: string[], cellKind: CellKind): void;
|
||||
}
|
||||
|
||||
export interface IActiveNotebookEditorDelegate extends INotebookEditorDelegate {
|
||||
|
||||
@@ -1617,21 +1617,21 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
|
||||
store.add(cell.onCellDecorationsChanged(e => {
|
||||
e.added.forEach(options => {
|
||||
if (options.className) {
|
||||
this.deltaCellContainerClassNames(cell.id, [options.className], []);
|
||||
this.deltaCellContainerClassNames(cell.id, [options.className], [], cell.cellKind);
|
||||
}
|
||||
|
||||
if (options.outputClassName) {
|
||||
this.deltaCellContainerClassNames(cell.id, [options.outputClassName], []);
|
||||
this.deltaCellContainerClassNames(cell.id, [options.outputClassName], [], cell.cellKind);
|
||||
}
|
||||
});
|
||||
|
||||
e.removed.forEach(options => {
|
||||
if (options.className) {
|
||||
this.deltaCellContainerClassNames(cell.id, [], [options.className]);
|
||||
this.deltaCellContainerClassNames(cell.id, [], [options.className], cell.cellKind);
|
||||
}
|
||||
|
||||
if (options.outputClassName) {
|
||||
this.deltaCellContainerClassNames(cell.id, [], [options.outputClassName]);
|
||||
this.deltaCellContainerClassNames(cell.id, [], [options.outputClassName], cell.cellKind);
|
||||
}
|
||||
});
|
||||
}));
|
||||
@@ -2285,8 +2285,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
|
||||
return ret;
|
||||
}
|
||||
|
||||
deltaCellContainerClassNames(cellId: string, added: string[], removed: string[]) {
|
||||
this._webview?.deltaCellContainerClassNames(cellId, added, removed);
|
||||
deltaCellContainerClassNames(cellId: string, added: string[], removed: string[], cellkind: CellKind): void {
|
||||
if (cellkind === CellKind.Markup) {
|
||||
this._webview?.deltaMarkupPreviewClassNames(cellId, added, removed);
|
||||
} else {
|
||||
this._webview?.deltaCellOutputContainerClassNames(cellId, added, removed);
|
||||
}
|
||||
}
|
||||
|
||||
changeModelDecorations<T>(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null {
|
||||
|
||||
@@ -72,11 +72,11 @@ export class CellDecorations extends CellContentPart {
|
||||
this.currentCell.getCellDecorations().forEach(options => {
|
||||
if (options.className && this.currentCell) {
|
||||
this.rootContainer.classList.add(options.className);
|
||||
this.notebookEditor.deltaCellContainerClassNames(this.currentCell.id, [options.className], []);
|
||||
this.notebookEditor.deltaCellContainerClassNames(this.currentCell.id, [options.className], [], this.currentCell.cellKind);
|
||||
}
|
||||
|
||||
if (options.outputClassName && this.currentCell) {
|
||||
this.notebookEditor.deltaCellContainerClassNames(this.currentCell.id, [options.outputClassName], []);
|
||||
this.notebookEditor.deltaCellContainerClassNames(this.currentCell.id, [options.outputClassName], [], this.currentCell.cellKind);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1855,14 +1855,24 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Themable {
|
||||
}
|
||||
|
||||
|
||||
deltaCellContainerClassNames(cellId: string, added: string[], removed: string[]) {
|
||||
deltaCellOutputContainerClassNames(cellId: string, added: string[], removed: string[]) {
|
||||
this._sendMessageToWebview({
|
||||
type: 'decorations',
|
||||
cellId,
|
||||
addedClassNames: added,
|
||||
removedClassNames: removed
|
||||
});
|
||||
}
|
||||
|
||||
deltaMarkupPreviewClassNames(cellId: string, added: string[], removed: string[]) {
|
||||
if (this.markupPreviewMapping.get(cellId)) {
|
||||
this._sendMessageToWebview({
|
||||
type: 'markupDecorations',
|
||||
cellId,
|
||||
addedClassNames: added,
|
||||
removedClassNames: removed
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateOutputRenderers() {
|
||||
|
||||
@@ -305,7 +305,7 @@ export interface IUpdateRenderersMessage {
|
||||
}
|
||||
|
||||
export interface IUpdateDecorationsMessage {
|
||||
readonly type: 'decorations';
|
||||
readonly type: 'decorations' | 'markupDecorations';
|
||||
readonly cellId: string;
|
||||
readonly addedClassNames: readonly string[];
|
||||
readonly removedClassNames: readonly string[];
|
||||
|
||||
@@ -1780,6 +1780,16 @@ async function webviewPreloads(ctx: PreloadContext) {
|
||||
outputContainer?.classList.remove(...event.data.removedClassNames);
|
||||
break;
|
||||
}
|
||||
case 'markupDecorations': {
|
||||
const markupCell = window.document.getElementById(event.data.cellId);
|
||||
// The cell may not have been added yet if it is out of view.
|
||||
// Decorations will be added when the cell is shown.
|
||||
if (markupCell) {
|
||||
markupCell?.classList.add(...event.data.addedClassNames);
|
||||
markupCell?.classList.remove(...event.data.removedClassNames);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'customKernelMessage':
|
||||
onDidReceiveKernelMessage.fire(event.data.message);
|
||||
break;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import './media/window.css';
|
||||
import { localize } from '../../nls.js';
|
||||
import { URI } from '../../base/common/uri.js';
|
||||
import { onUnexpectedError } from '../../base/common/errors.js';
|
||||
import { equals } from '../../base/common/objects.js';
|
||||
import { EventType, EventHelper, addDisposableListener, ModifierKeyEmitter, getActiveElement, hasWindow, getWindowById, getWindows, $ } from '../../base/browser/dom.js';
|
||||
import { Action, Separator, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from '../../base/common/actions.js';
|
||||
@@ -187,13 +186,6 @@ export class NativeWindow extends BaseWindow {
|
||||
}
|
||||
});
|
||||
|
||||
// Error reporting from main
|
||||
ipcRenderer.on('vscode:reportError', (event: unknown, error: string) => {
|
||||
if (error) {
|
||||
onUnexpectedError(JSON.parse(error));
|
||||
}
|
||||
});
|
||||
|
||||
// Shared Process crash reported from main
|
||||
ipcRenderer.on('vscode:reportSharedProcessCrash', (event: unknown, error: string) => {
|
||||
this.notificationService.prompt(
|
||||
|
||||
Reference in New Issue
Block a user