Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -340,9 +340,13 @@ export class WorkspaceChangeExtHostRelauncher extends Disposable implements IWor
// Restart extension host if first root folder changed (impact on deprecated workspace.rootPath API)
const newFirstFolderResource = workspace.folders.length > 0 ? workspace.folders[0].uri : undefined;
if (!isEqual(this.firstFolderResource, newFirstFolderResource)) {
const hadFirstFolder = !!this.firstFolderResource;
this.firstFolderResource = newFirstFolderResource;

this.extensionHostRestarter.schedule(); // buffer calls to extension host restart
// Skip 0→1 folders (#319944): same as adding a folder in an existing workspace
if (hadFirstFolder) {
this.extensionHostRestarter.schedule(); // buffer calls to extension host restart
Comment on lines +347 to +348
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { localize } from '../../../../nls.js';
import { IWorkspaceEditingService } from '../common/workspaceEditing.js';
import { URI } from '../../../../base/common/uri.js';
import { hasWorkspaceFileExtension, isUntitledWorkspace, isWorkspaceIdentifier, IWorkspaceContextService, toWorkspaceIdentifier } from '../../../../platform/workspace/common/workspace.js';
import { hasWorkspaceFileExtension, isUntitledWorkspace, isWorkspaceIdentifier, IWorkspaceContextService, toWorkspaceIdentifier, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';
import { IJSONEditingService } from '../../configuration/common/jsonEditing.js';
import { IWorkspacesService } from '../../../../platform/workspaces/common/workspaces.js';
import { WorkspaceService } from '../../configuration/browser/configurationService.js';
Expand Down Expand Up @@ -176,9 +176,14 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi
}

async enterWorkspace(workspaceUri: URI): Promise<void> {
const stopped = await this.extensionService.stopExtensionHosts(localize('restartExtensionHost.reason', "Opening a multi-root workspace"));
if (!stopped) {
return;
// Skip extension host restart when coming from an empty window (#319944)
const fromEmptyWorkspace = this.contextService.getWorkbenchState() === WorkbenchState.EMPTY;

if (!fromEmptyWorkspace) {
const stopped = await this.extensionService.stopExtensionHosts(localize('restartExtensionHost.reason', "Opening a multi-root workspace"));
if (!stopped) {
return;
}
}

const oldWorkspace = toWorkspaceIdentifier(this.contextService.getWorkspace());
Expand All @@ -205,7 +210,7 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi

// Restart the extension host: entering a workspace means a new location for
// storage and potentially a change in the workspace.rootPath property.
else {
else if (!fromEmptyWorkspace) {
this.extensionService.startExtensionHosts();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import assert from 'assert';
import { URI } from '../../../../../base/common/uri.js';
import { mock } from '../../../../../base/test/common/mock.js';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
import { IWorkspaceContextService, toWorkspaceFolder, WorkbenchState } from '../../../../../platform/workspace/common/workspace.js';
import { Workspace } from '../../../../../platform/workspace/test/common/testWorkspace.js';
import { IJSONEditingService } from '../../../configuration/common/jsonEditing.js';
import { IExtensionService } from '../../../extensions/common/extensions.js';
import { NativeWorkspaceEditingService } from '../../electron-browser/workspaceEditingService.js';
import { TestContextService } from '../../../../test/common/workbenchTestServices.js';
import { workbenchInstantiationService } from '../../../../test/electron-browser/workbenchTestServices.js';
import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';

class TestNativeWorkspaceEditingService extends NativeWorkspaceEditingService {
protected override async doEnterWorkspace(workspaceUri: URI) {
return { workspace: { id: 'test-workspace', configPath: workspaceUri } };
}
}

suite('NativeWorkspaceEditingService', () => {

const disposables = ensureNoDisposablesAreLeakedInTestSuite();

let instantiationService: TestInstantiationService;
const workspaceUri = URI.file('/test/Untitled-1.code-workspace');

setup(() => {
disposables.add(instantiationService = workbenchInstantiationService(undefined, disposables));
instantiationService.stub(IJSONEditingService, new class extends mock<IJSONEditingService>() { });
});

function createExtensionService(): { stopCalls: number; startCalls: number } {
const calls = { stopCalls: 0, startCalls: 0 };
instantiationService.stub(IExtensionService, new class extends mock<IExtensionService>() {
override stopExtensionHosts() {
calls.stopCalls++;
return Promise.resolve(true);
}
override startExtensionHosts() {
calls.startCalls++;
return Promise.resolve();
}
});
return calls;
}

test('enterWorkspace from empty window does not restart extension host', async () => {
const contextService = new TestContextService(new Workspace('empty-workspace'));
assert.strictEqual(contextService.getWorkbenchState(), WorkbenchState.EMPTY);
instantiationService.stub(IWorkspaceContextService, contextService);

const extensionService = createExtensionService();
const service = disposables.add(instantiationService.createInstance(TestNativeWorkspaceEditingService));
await service.enterWorkspace(workspaceUri);

assert.strictEqual(extensionService.stopCalls, 0);
assert.strictEqual(extensionService.startCalls, 0);
});

test('enterWorkspace from folder window restarts extension host', async () => {
const contextService = new TestContextService(new Workspace('folder-workspace', [toWorkspaceFolder(URI.file('/test/folder'))]));
assert.strictEqual(contextService.getWorkbenchState(), WorkbenchState.FOLDER);
instantiationService.stub(IWorkspaceContextService, contextService);

const extensionService = createExtensionService();
const service = disposables.add(instantiationService.createInstance(TestNativeWorkspaceEditingService));
await service.enterWorkspace(workspaceUri);

assert.strictEqual(extensionService.stopCalls, 1);
assert.strictEqual(extensionService.startCalls, 1);
});
});