Merge from vscode bead496a613e475819f89f08e9e882b841bc1fe8 (#14883)

* Merge from vscode bead496a613e475819f89f08e9e882b841bc1fe8

* Bump distro

* Upgrade GCC to 4.9 due to yarn install errors

* Update build image

* Fix bootstrap base url

* Bump distro

* Fix build errors

* Update source map file

* Disable checkbox for blocking migration issues (#15131)

* disable checkbox for blocking issues

* wip

* disable checkbox fixes

* fix strings

* Remove duplicate tsec command

* Default to off for tab color if settings not present

* re-skip failing tests

* Fix mocha error

* Bump sqlite version & fix notebooks search view

* Turn off esbuild warnings

* Update esbuild log level

* Fix overflowactionbar tests

* Fix ts-ignore in dropdown tests

* cleanup/fixes

* Fix hygiene

* Bundle in entire zone.js module

* Remove extra constructor param

* bump distro for web compile break

* bump distro for web compile break v2

* Undo log level change

* New distro

* Fix integration test scripts

* remove the "no yarn.lock changes" workflow

* fix scripts v2

* Update unit test scripts

* Ensure ads-kerberos2 updates in .vscodeignore

* Try fix unit tests

* Upload crash reports

* remove nogpu

* always upload crashes

* Use bash script

* Consolidate data/ext dir names

* Create in tmp directory

Co-authored-by: chlafreniere <hichise@gmail.com>
Co-authored-by: Christopher Suh <chsuh@microsoft.com>
Co-authored-by: chgagnon <chgagnon@microsoft.com>
This commit is contained in:
Karl Burtram
2021-04-27 14:01:59 -07:00
committed by GitHub
parent 7e1c0076ba
commit 867a963882
1817 changed files with 81812 additions and 50843 deletions

View File

@@ -11,12 +11,14 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle
// --- other interested parties
import { JSONValidationExtensionPoint } from 'vs/workbench/api/common/jsonValidationExtensionPoint';
import { ColorExtensionPoint } from 'vs/workbench/services/themes/common/colorExtensionPoint';
import { IconExtensionPoint, IconFontExtensionPoint } from 'vs/workbench/services/themes/common/iconExtensionPoint';
import { TokenClassificationExtensionPoints } from 'vs/workbench/services/themes/common/tokenClassificationExtensionPoint';
import { LanguageConfigurationFileHandler } from 'vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint';
// --- mainThread participants
import './mainThreadBulkEdits';
import './mainThreadCodeInsets';
import './mainThreadCLICommands';
import './mainThreadClipboard';
import './mainThreadCommands';
import './mainThreadConfiguration';
@@ -30,6 +32,7 @@ import './mainThreadDocuments';
import './mainThreadDocumentsAndEditors';
import './mainThreadEditor';
import './mainThreadEditors';
import './mainThreadEditorTabs';
import './mainThreadErrors';
import './mainThreadExtensionService';
import './mainThreadFileSystem';
@@ -54,6 +57,7 @@ import './mainThreadTheming';
import './mainThreadTreeViews';
import './mainThreadDownloadService';
import './mainThreadUrls';
import './mainThreadUriOpeners';
import './mainThreadWindow';
import './mainThreadWebviewManager';
import './mainThreadWorkspace';
@@ -65,6 +69,7 @@ import './mainThreadTunnelService';
import './mainThreadAuthentication';
import './mainThreadTimeline';
import './mainThreadTesting';
import './mainThreadSecretState';
import 'vs/workbench/api/common/apiCommands';
export class ExtensionPoints implements IWorkbenchContribution {
@@ -75,6 +80,8 @@ export class ExtensionPoints implements IWorkbenchContribution {
// Classes that handle extension points...
this.instantiationService.createInstance(JSONValidationExtensionPoint);
this.instantiationService.createInstance(ColorExtensionPoint);
this.instantiationService.createInstance(IconExtensionPoint);
this.instantiationService.createInstance(IconFontExtensionPoint);
this.instantiationService.createInstance(TokenClassificationExtensionPoints);
this.instantiationService.createInstance(LanguageConfigurationFileHandler);
}

View File

@@ -7,70 +7,15 @@ import { Disposable } from 'vs/base/common/lifecycle';
import * as modes from 'vs/editor/common/modes';
import * as nls from 'vs/nls';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { IAuthenticationService, AllowedExtension, readAllowedExtensions, getAuthenticationProviderActivationEvent } from 'vs/workbench/services/authentication/browser/authenticationService';
import { IAuthenticationService, AllowedExtension, readAllowedExtensions, getAuthenticationProviderActivationEvent, addAccountUsage, readAccountUsages, removeAccountUsage } from 'vs/workbench/services/authentication/browser/authenticationService';
import { ExtHostAuthenticationShape, ExtHostContext, IExtHostContext, MainContext, MainThreadAuthenticationShape } from '../common/extHost.protocol';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import Severity from 'vs/base/common/severity';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { fromNow } from 'vs/base/common/date';
import { ActivationKind, IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { isWeb } from 'vs/base/common/platform';
import { IEncryptionService } from 'vs/workbench/services/encryption/common/encryptionService';
import { IProductService } from 'vs/platform/product/common/productService';
import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials';
const VSO_ALLOWED_EXTENSIONS = ['github.vscode-pull-request-github', 'github.vscode-pull-request-github-insiders', 'vscode.git', 'ms-vsonline.vsonline', 'vscode.github-browser', 'ms-vscode.github-browser', 'github.codespaces'];
interface IAccountUsage {
extensionId: string;
extensionName: string;
lastUsed: number;
}
function readAccountUsages(storageService: IStorageService, providerId: string, accountName: string,): IAccountUsage[] {
const accountKey = `${providerId}-${accountName}-usages`;
const storedUsages = storageService.get(accountKey, StorageScope.GLOBAL);
let usages: IAccountUsage[] = [];
if (storedUsages) {
try {
usages = JSON.parse(storedUsages);
} catch (e) {
// ignore
}
}
return usages;
}
function removeAccountUsage(storageService: IStorageService, providerId: string, accountName: string): void {
const accountKey = `${providerId}-${accountName}-usages`;
storageService.remove(accountKey, StorageScope.GLOBAL);
}
function addAccountUsage(storageService: IStorageService, providerId: string, accountName: string, extensionId: string, extensionName: string) {
const accountKey = `${providerId}-${accountName}-usages`;
const usages = readAccountUsages(storageService, providerId, accountName);
const existingUsageIndex = usages.findIndex(usage => usage.extensionId === extensionId);
if (existingUsageIndex > -1) {
usages.splice(existingUsageIndex, 1, {
extensionId,
extensionName,
lastUsed: Date.now()
});
} else {
usages.push({
extensionId,
extensionName,
lastUsed: Date.now()
});
}
storageService.store(accountKey, JSON.stringify(usages), StorageScope.GLOBAL, StorageTarget.MACHINE);
}
export class MainThreadAuthenticationProvider extends Disposable {
private _accounts = new Map<string, string[]>(); // Map account name to session ids
@@ -223,12 +168,8 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
@IDialogService private readonly dialogService: IDialogService,
@IStorageService private readonly storageService: IStorageService,
@INotificationService private readonly notificationService: INotificationService,
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IExtensionService private readonly extensionService: IExtensionService,
@ICredentialsService private readonly credentialsService: ICredentialsService,
@IEncryptionService private readonly encryptionService: IEncryptionService,
@IProductService private readonly productService: IProductService
@IExtensionService private readonly extensionService: IExtensionService
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostAuthentication);
@@ -250,17 +191,6 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
this._register(this.authenticationService.onDidChangeDeclaredProviders(e => {
this._proxy.$setProviders(e);
}));
// {{SQL CARBON EDIT}} - avoid null reference exception
if (this.credentialsService.onDidChangePassword) {
this._register(this.credentialsService.onDidChangePassword(_ => {
this._proxy.$onDidChangePassword();
}));
}
}
$getProviderIds(): Promise<string[]> {
return Promise.resolve(this.authenticationService.getProviderIds());
}
async $registerAuthenticationProvider(id: string, label: string, supportsMultipleAccounts: boolean): Promise<void> {
@@ -281,172 +211,17 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
this.authenticationService.sessionsUpdate(id, event);
}
$getSessions(id: string): Promise<ReadonlyArray<modes.AuthenticationSession>> {
return this.authenticationService.getSessions(id);
}
$login(providerId: string, scopes: string[]): Promise<modes.AuthenticationSession> {
return this.authenticationService.login(providerId, scopes);
}
$logout(providerId: string, sessionId: string): Promise<void> {
return this.authenticationService.logout(providerId, sessionId);
}
async $requestNewSession(providerId: string, scopes: string[], extensionId: string, extensionName: string): Promise<void> {
return this.authenticationService.requestNewSession(providerId, scopes, extensionId, extensionName);
}
async $getSession(providerId: string, scopes: string[], extensionId: string, extensionName: string, options: { createIfNone: boolean, clearSessionPreference: boolean }): Promise<modes.AuthenticationSession | undefined> {
const orderedScopes = scopes.sort().join(' ');
const sessions = (await this.$getSessions(providerId)).filter(session => session.scopes.slice().sort().join(' ') === orderedScopes);
const label = this.authenticationService.getLabel(providerId);
if (sessions.length) {
if (!this.authenticationService.supportsMultipleAccounts(providerId)) {
const session = sessions[0];
const allowed = await this.$getSessionsPrompt(providerId, session.account.label, label, extensionId, extensionName);
if (allowed) {
return session;
} else {
throw new Error('User did not consent to login.');
}
}
// On renderer side, confirm consent, ask user to choose between accounts if multiple sessions are valid
const selected = await this.$selectSession(providerId, label, extensionId, extensionName, sessions, scopes, !!options.clearSessionPreference);
return sessions.find(session => session.id === selected.id);
} else {
if (options.createIfNone) {
const isAllowed = await this.$loginPrompt(label, extensionName);
if (!isAllowed) {
throw new Error('User did not consent to login.');
}
const session = await this.authenticationService.login(providerId, scopes);
await this.$setTrustedExtensionAndAccountPreference(providerId, session.account.label, extensionId, extensionName, session.id);
return session;
} else {
await this.$requestNewSession(providerId, scopes, extensionId, extensionName);
return undefined;
}
}
}
async $selectSession(providerId: string, providerName: string, extensionId: string, extensionName: string, potentialSessions: modes.AuthenticationSession[], scopes: string[], clearSessionPreference: boolean): Promise<modes.AuthenticationSession> {
if (!potentialSessions.length) {
throw new Error('No potential sessions found');
}
if (clearSessionPreference) {
this.storageService.remove(`${extensionName}-${providerId}`, StorageScope.GLOBAL);
} else {
const existingSessionPreference = this.storageService.get(`${extensionName}-${providerId}`, StorageScope.GLOBAL);
if (existingSessionPreference) {
const matchingSession = potentialSessions.find(session => session.id === existingSessionPreference);
if (matchingSession) {
const allowed = await this.$getSessionsPrompt(providerId, matchingSession.account.label, providerName, extensionId, extensionName);
if (allowed) {
return matchingSession;
}
}
}
}
return new Promise((resolve, reject) => {
const quickPick = this.quickInputService.createQuickPick<{ label: string, session?: modes.AuthenticationSession }>();
quickPick.ignoreFocusOut = true;
const items: { label: string, session?: modes.AuthenticationSession }[] = potentialSessions.map(session => {
return {
label: session.account.label,
session
};
});
items.push({
label: nls.localize('useOtherAccount', "Sign in to another account")
});
quickPick.items = items;
quickPick.title = nls.localize(
{
key: 'selectAccount',
comment: ['The placeholder {0} is the name of an extension. {1} is the name of the type of account, such as Microsoft or GitHub.']
},
"The extension '{0}' wants to access a {1} account",
extensionName,
providerName);
quickPick.placeholder = nls.localize('getSessionPlateholder', "Select an account for '{0}' to use or Esc to cancel", extensionName);
quickPick.onDidAccept(async _ => {
const selected = quickPick.selectedItems[0];
const session = selected.session ?? await this.authenticationService.login(providerId, scopes);
const accountName = session.account.label;
const allowList = readAllowedExtensions(this.storageService, providerId, accountName);
if (!allowList.find(allowed => allowed.id === extensionId)) {
allowList.push({ id: extensionId, name: extensionName });
this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER);
}
this.storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.GLOBAL, StorageTarget.MACHINE);
quickPick.dispose();
resolve(session);
});
quickPick.onDidHide(_ => {
if (!quickPick.selectedItems[0]) {
reject('User did not consent to account access');
}
quickPick.dispose();
});
quickPick.show();
});
}
async $getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean> {
private isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean {
const allowList = readAllowedExtensions(this.storageService, providerId, accountName);
const extensionData = allowList.find(extension => extension.id === extensionId);
if (extensionData) {
addAccountUsage(this.storageService, providerId, accountName, extensionId, extensionName);
return true;
}
const remoteConnection = this.remoteAgentService.getConnection();
const isVSO = remoteConnection !== null
? remoteConnection.remoteAuthority.startsWith('vsonline')
: isWeb;
if (isVSO && VSO_ALLOWED_EXTENSIONS.includes(extensionId)) {
addAccountUsage(this.storageService, providerId, accountName, extensionId, extensionName);
return true;
}
const { choice } = await this.dialogService.show(
Severity.Info,
nls.localize('confirmAuthenticationAccess', "The extension '{0}' wants to access the {1} account '{2}'.", extensionName, providerName, accountName),
[nls.localize('allow', "Allow"), nls.localize('cancel', "Cancel")],
{
cancelId: 1
}
);
const allow = choice === 0;
if (allow) {
addAccountUsage(this.storageService, providerId, accountName, extensionId, extensionName);
allowList.push({ id: extensionId, name: extensionName });
this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER);
}
return allow;
return !!extensionData;
}
async $loginPrompt(providerName: string, extensionName: string): Promise<boolean> {
private async loginPrompt(providerName: string, extensionName: string): Promise<boolean> {
const { choice } = await this.dialogService.show(
Severity.Info,
nls.localize('confirmLogin', "The extension '{0}' wants to sign in using {1}.", extensionName, providerName),
@@ -459,7 +234,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
return choice === 0;
}
async $setTrustedExtensionAndAccountPreference(providerId: string, accountName: string, extensionId: string, extensionName: string, sessionId: string): Promise<void> {
private async setTrustedExtensionAndAccountPreference(providerId: string, accountName: string, extensionId: string, extensionName: string, sessionId: string): Promise<void> {
const allowList = readAllowedExtensions(this.storageService, providerId, accountName);
if (!allowList.find(allowed => allowed.id === extensionId)) {
allowList.push({ id: extensionId, name: extensionName });
@@ -467,48 +242,77 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
}
this.storageService.store(`${extensionName}-${providerId}`, sessionId, StorageScope.GLOBAL, StorageTarget.MACHINE);
addAccountUsage(this.storageService, providerId, accountName, extensionId, extensionName);
}
private getFullKey(extensionId: string): string {
return `${this.productService.urlProtocol}${extensionId}`;
}
private async selectSession(providerId: string, extensionId: string, extensionName: string, potentialSessions: modes.AuthenticationSession[], clearSessionPreference: boolean): Promise<modes.AuthenticationSession> {
if (!potentialSessions.length) {
throw new Error('No potential sessions found');
}
async $getPassword(extensionId: string, key: string): Promise<string | undefined> {
const fullKey = this.getFullKey(extensionId);
const password = await this.credentialsService.getPassword(fullKey, key);
const decrypted = password && await this.encryptionService.decrypt(password);
if (decrypted) {
try {
const value = JSON.parse(decrypted);
if (value.extensionId === extensionId) {
return value.content;
if (clearSessionPreference) {
this.storageService.remove(`${extensionName}-${providerId}`, StorageScope.GLOBAL);
} else {
const existingSessionPreference = this.storageService.get(`${extensionName}-${providerId}`, StorageScope.GLOBAL);
if (existingSessionPreference) {
const matchingSession = potentialSessions.find(session => session.id === existingSessionPreference);
if (matchingSession) {
const allowed = await this.authenticationService.showGetSessionPrompt(providerId, matchingSession.account.label, extensionId, extensionName);
if (allowed) {
return matchingSession;
}
}
} catch (_) {
throw new Error('Cannot get password');
}
}
return undefined;
return this.authenticationService.selectSession(providerId, extensionId, extensionName, potentialSessions);
}
async $setPassword(extensionId: string, key: string, value: string): Promise<void> {
const fullKey = this.getFullKey(extensionId);
const toEncrypt = JSON.stringify({
extensionId,
content: value
});
const encrypted = await this.encryptionService.encrypt(toEncrypt);
return this.credentialsService.setPassword(fullKey, key, encrypted);
}
async $getSession(providerId: string, scopes: string[], extensionId: string, extensionName: string, options: { createIfNone: boolean, clearSessionPreference: boolean }): Promise<modes.AuthenticationSession | undefined> {
const orderedScopes = scopes.sort().join(' ');
const sessions = (await this.authenticationService.getSessions(providerId)).filter(session => session.scopes.slice().sort().join(' ') === orderedScopes);
async $deletePassword(extensionId: string, key: string): Promise<void> {
try {
const fullKey = this.getFullKey(extensionId);
await this.credentialsService.deletePassword(fullKey, key);
} catch (_) {
throw new Error('Cannot delete password');
const silent = !options.createIfNone;
let session: modes.AuthenticationSession | undefined;
if (sessions.length) {
if (!this.authenticationService.supportsMultipleAccounts(providerId)) {
session = sessions[0];
const allowed = this.isAccessAllowed(providerId, session.account.label, extensionId);
if (!allowed) {
if (!silent) {
const didAcceptPrompt = await this.authenticationService.showGetSessionPrompt(providerId, session.account.label, extensionId, extensionName);
if (!didAcceptPrompt) {
throw new Error('User did not consent to login.');
}
} else {
this.authenticationService.requestSessionAccess(providerId, extensionId, extensionName, [session]);
}
}
} else {
if (!silent) {
session = await this.selectSession(providerId, extensionId, extensionName, sessions, !!options.clearSessionPreference);
} else {
this.authenticationService.requestSessionAccess(providerId, extensionId, extensionName, sessions);
}
}
} else {
if (!silent) {
const isAllowed = await this.loginPrompt(providerId, extensionName);
if (!isAllowed) {
throw new Error('User did not consent to login.');
}
session = await this.authenticationService.login(providerId, scopes);
await this.setTrustedExtensionAndAccountPreference(providerId, session.account.label, extensionId, extensionName, session.id);
} else {
await this.authenticationService.requestNewSession(providerId, scopes, extensionId, extensionName);
}
}
if (session) {
addAccountUsage(this.storageService, providerId, session.account.label, extensionId, extensionName);
}
return session;
}
}

View File

@@ -3,29 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IBulkEditService, ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
import { IExtHostContext, IWorkspaceEditDto, WorkspaceEditType, MainThreadBulkEditsShape, MainContext } from 'vs/workbench/api/common/extHost.protocol';
import { revive } from 'vs/base/common/marshalling';
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
function reviveWorkspaceEditDto2(data: IWorkspaceEditDto | undefined): ResourceEdit[] {
if (!data?.edits) {
return [];
}
const result: ResourceEdit[] = [];
for (let edit of revive<IWorkspaceEditDto>(data).edits) {
if (edit._type === WorkspaceEditType.File) {
result.push(new ResourceFileEdit(edit.oldUri, edit.newUri, edit.options, edit.metadata));
} else if (edit._type === WorkspaceEditType.Text) {
result.push(new ResourceTextEdit(edit.resource, edit.edit, edit.modelVersionId, edit.metadata));
} else if (edit._type === WorkspaceEditType.Cell) {
result.push(new ResourceNotebookCellEdit(edit.resource, edit.edit, edit.notebookVersionId, edit.metadata));
}
}
return result;
}
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { IExtHostContext, IWorkspaceEditDto, MainThreadBulkEditsShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { reviveWorkspaceEditDto2 } from 'vs/workbench/api/browser/mainThreadEditors';
@extHostNamedCustomer(MainContext.MainThreadBulkEdits)
export class MainThreadBulkEdits implements MainThreadBulkEditsShape {

View File

@@ -0,0 +1,108 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Schemas } from 'vs/base/common/network';
import { isString } from 'vs/base/common/types';
import { URI, UriComponents } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { CLIOutput, IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagementCLIService';
import { getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { ILabelService } from 'vs/platform/label/common/label';
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IProductService } from 'vs/platform/product/common/productService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { canExecuteOnWorkspace } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { IExtensionManifest } from 'vs/workbench/workbench.web.api';
// this class contains the commands that the CLI server is reying on
CommandsRegistry.registerCommand('_remoteCLI.openExternal', function (accessor: ServicesAccessor, uri: UriComponents, options: { allowTunneling?: boolean }) {
// TODO: discuss martin, ben where to put this
const openerService = accessor.get(IOpenerService);
openerService.open(URI.revive(uri), { openExternal: true, allowTunneling: options?.allowTunneling === true });
});
interface ManageExtensionsArgs {
list?: { showVersions?: boolean, category?: string; };
install?: (string | URI)[];
uninstall?: string[];
force?: boolean;
}
CommandsRegistry.registerCommand('_remoteCLI.manageExtensions', async function (accessor: ServicesAccessor, args: ManageExtensionsArgs) {
const instantiationService = accessor.get(IInstantiationService);
const extensionManagementServerService = accessor.get(IExtensionManagementServerService);
const remoteExtensionManagementService = extensionManagementServerService.remoteExtensionManagementServer?.extensionManagementService;
if (!remoteExtensionManagementService) {
return undefined;
}
const cliService = instantiationService.createChild(new ServiceCollection([IExtensionManagementService, remoteExtensionManagementService])).createInstance(RemoteExtensionCLIManagementService);
const lines: string[] = [];
const output = { log: lines.push.bind(lines), error: lines.push.bind(lines) };
if (args.list) {
await cliService.listExtensions(!!args.list.showVersions, args.list.category, output);
} else {
const revive = (inputs: (string | UriComponents)[]) => inputs.map(input => isString(input) ? input : URI.revive(input));
if (Array.isArray(args.install) && args.install.length) {
try {
await cliService.installExtensions(revive(args.install), [], true, !!args.force, output);
} catch (e) {
lines.push(e.message);
}
}
if (Array.isArray(args.uninstall) && args.uninstall.length) {
try {
await cliService.uninstallExtensions(revive(args.uninstall), !!args.force, output);
} catch (e) {
lines.push(e.message);
}
}
}
return lines.join('\n');
});
class RemoteExtensionCLIManagementService extends ExtensionManagementCLIService {
private _location: string | undefined;
constructor(
@IExtensionManagementService extensionManagementService: IExtensionManagementService,
@IProductService private readonly productService: IProductService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IExtensionGalleryService extensionGalleryService: IExtensionGalleryService,
@ILocalizationsService localizationsService: ILocalizationsService,
@ILabelService labelService: ILabelService,
@IWorkbenchEnvironmentService envService: IWorkbenchEnvironmentService
) {
super(extensionManagementService, extensionGalleryService, localizationsService);
const remoteAuthority = envService.remoteAuthority;
this._location = remoteAuthority ? labelService.getHostLabel(Schemas.vscodeRemote, remoteAuthority) : undefined;
}
protected get location(): string | undefined {
return this._location;
}
protected validateExtensionKind(manifest: IExtensionManifest, output: CLIOutput): boolean {
if (!canExecuteOnWorkspace(manifest, this.productService, this.configurationService)) {
output.log(localize('cannot be installed', "Cannot install the '{0}' extension because it is declared to not run in this setup.", getExtensionId(manifest.publisher, manifest.name)));
return false;
}
return true;
}
}

View File

@@ -473,7 +473,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments
if (!commentsViewAlreadyRegistered) {
const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry).registerViewContainer({
id: COMMENTS_VIEW_ID,
name: COMMENTS_VIEW_TITLE,
title: COMMENTS_VIEW_TITLE,
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [COMMENTS_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]),
storageId: COMMENTS_VIEW_TITLE,
hideIfEmpty: true,

View File

@@ -10,22 +10,18 @@ import { IRemoteConsoleLog, log } from 'vs/base/common/console';
import { logRemoteEntry } from 'vs/workbench/services/extensions/common/remoteConsoleUtil';
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
import { ILogService } from 'vs/platform/log/common/log';
import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug';
@extHostNamedCustomer(MainContext.MainThreadConsole)
export class MainThreadConsole implements MainThreadConsoleShape {
private readonly _isExtensionDevHost: boolean;
private readonly _isExtensionDevTestFromCli: boolean;
constructor(
extHostContext: IExtHostContext,
_extHostContext: IExtHostContext,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@ILogService private readonly _logService: ILogService,
@IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService,
) {
const devOpts = parseExtensionDevOptions(this._environmentService);
this._isExtensionDevHost = devOpts.isExtensionDevHost;
this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli;
}
@@ -43,10 +39,5 @@ export class MainThreadConsole implements MainThreadConsoleShape {
if (this._isExtensionDevTestFromCli) {
logRemoteEntry(this._logService, entry);
}
// Broadcast to other windows if we are in development mode
else if (this._environmentService.debugExtensionHost.debugId && (!this._environmentService.isBuilt || this._isExtensionDevHost)) {
this._extensionHostDebugService.logToSession(this._environmentService.debugExtensionHost.debugId, entry);
}
}
}

View File

@@ -3,15 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { multibyteAwareBtoa } from 'vs/base/browser/dom';
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, IDisposable, IReference } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore, dispose, IDisposable, IReference } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { basename } from 'vs/base/common/path';
import { isEqual, isEqualOrParent, toLocalResource } from 'vs/base/common/resources';
import { multibyteAwareBtoa } from 'vs/base/browser/dom';
import { URI, UriComponents } from 'vs/base/common/uri';
import * as modes from 'vs/editor/common/modes';
import { localize } from 'vs/nls';
@@ -95,10 +95,7 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc
dispose() {
super.dispose();
for (const disposable of this._editorProviders.values()) {
disposable.dispose();
}
dispose(this._editorProviders.values());
this._editorProviders.clear();
}

View File

@@ -75,8 +75,8 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
return Promise.resolve(this._proxy.$substituteVariables(folder ? folder.uri : undefined, config));
}
runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise<number | undefined> {
return this._proxy.$runInTerminal(args);
runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, sessionId: string): Promise<number | undefined> {
return this._proxy.$runInTerminal(args, sessionId);
}
// RPC methods (MainThreadDebugServiceShape)
@@ -327,7 +327,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
return {
id: sessionID,
type: session.configuration.type,
name: session.configuration.name,
name: session.name,
folderUri: session.root ? session.root.uri : undefined,
configuration: session.configuration
};

View File

@@ -23,12 +23,20 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape {
//
}
$showOpenDialog(options?: MainThreadDialogOpenOptions): Promise<URI[] | undefined> {
return Promise.resolve(this._fileDialogService.showOpenDialog(MainThreadDialogs._convertOpenOptions(options)));
async $showOpenDialog(options?: MainThreadDialogOpenOptions): Promise<URI[] | undefined> {
const convertedOptions = MainThreadDialogs._convertOpenOptions(options);
if (!convertedOptions.defaultUri) {
convertedOptions.defaultUri = await this._fileDialogService.defaultFilePath();
}
return Promise.resolve(this._fileDialogService.showOpenDialog(convertedOptions));
}
$showSaveDialog(options?: MainThreadDialogSaveOptions): Promise<URI | undefined> {
return Promise.resolve(this._fileDialogService.showSaveDialog(MainThreadDialogs._convertSaveOptions(options)));
async $showSaveDialog(options?: MainThreadDialogSaveOptions): Promise<URI | undefined> {
const convertedOptions = MainThreadDialogs._convertSaveOptions(options);
if (!convertedOptions.defaultUri) {
convertedOptions.defaultUri = await this._fileDialogService.defaultFilePath();
}
return Promise.resolve(this._fileDialogService.showSaveDialog(convertedOptions));
}
private static _convertOpenOptions(options?: MainThreadDialogOpenOptions): IOpenDialogOptions {

View File

@@ -79,7 +79,6 @@ export class MainThreadTextEditorProperties {
return {
insertSpaces: modelOptions.insertSpaces,
tabSize: modelOptions.tabSize,
indentSize: modelOptions.indentSize,
cursorStyle: cursorStyle,
lineNumbers: lineNumbers
};
@@ -146,7 +145,6 @@ export class MainThreadTextEditorProperties {
}
return (
a.tabSize === b.tabSize
&& a.indentSize === b.indentSize
&& a.insertSpaces === b.insertSpaces
&& a.cursorStyle === b.cursorStyle
&& a.lineNumbers === b.lineNumbers
@@ -377,13 +375,6 @@ export class MainThreadTextEditor {
if (typeof newConfiguration.tabSize !== 'undefined') {
newOpts.tabSize = newConfiguration.tabSize;
}
if (typeof newConfiguration.indentSize !== 'undefined') {
if (newConfiguration.indentSize === 'tabSize') {
newOpts.indentSize = newOpts.tabSize || creationOpts.tabSize;
} else {
newOpts.indentSize = newConfiguration.indentSize;
}
}
this._model.updateOptions(newOpts);
}

View File

@@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { ExtHostContext, IExtHostEditorTabsShape, IExtHostContext, MainContext, IEditorTabDto } from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { Verbosity } from 'vs/workbench/common/editor';
import { GroupChangeKind, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
export interface ITabInfo {
name: string;
resource: URI;
}
@extHostNamedCustomer(MainContext.MainThreadEditorTabs)
export class MainThreadEditorTabs {
private static _GroupEventFilter = new Set([GroupChangeKind.EDITOR_CLOSE, GroupChangeKind.EDITOR_OPEN]);
private readonly _dispoables = new DisposableStore();
private readonly _groups = new Map<IEditorGroup, IDisposable>();
private readonly _proxy: IExtHostEditorTabsShape;
constructor(
extHostContext: IExtHostContext,
@IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService,
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostEditorTabs);
this._editorGroupsService.groups.forEach(this._subscribeToGroup, this);
this._dispoables.add(_editorGroupsService.onDidAddGroup(this._subscribeToGroup, this));
this._dispoables.add(_editorGroupsService.onDidRemoveGroup(e => {
const subscription = this._groups.get(e);
if (subscription) {
subscription.dispose();
this._groups.delete(e);
this._pushEditorTabs();
}
}));
this._pushEditorTabs();
}
dispose(): void {
dispose(this._groups.values());
this._dispoables.dispose();
}
private _subscribeToGroup(group: IEditorGroup) {
this._groups.get(group)?.dispose();
const listener = group.onDidGroupChange(e => {
if (MainThreadEditorTabs._GroupEventFilter.has(e.kind)) {
this._pushEditorTabs();
}
});
this._groups.set(group, listener);
}
private _pushEditorTabs(): void {
const tabs: IEditorTabDto[] = [];
for (const group of this._editorGroupsService.groups) {
for (const editor of group.editors) {
if (editor.isDisposed() || !editor.resource) {
continue;
}
tabs.push({
group: group.id,
name: editor.getTitle(Verbosity.SHORT) ?? '',
resource: editor.resource
});
}
}
this._proxy.$acceptEditorTabs(tabs);
}
}

View File

@@ -27,7 +27,7 @@ import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/wo
import { revive } from 'vs/base/common/marshalling';
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
function reviveWorkspaceEditDto2(data: IWorkspaceEditDto | undefined): ResourceEdit[] {
export function reviveWorkspaceEditDto2(data: IWorkspaceEditDto | undefined): ResourceEdit[] {
if (!data?.edits) {
return [];
}

View File

@@ -7,7 +7,7 @@ import { SerializedError } from 'vs/base/common/errors';
import Severity from 'vs/base/common/severity';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { IExtHostContext, MainContext, MainThreadExtensionServiceShape } from 'vs/workbench/api/common/extHost.protocol';
import { IExtensionService, ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionService, ExtensionActivationError, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { localize } from 'vs/nls';
@@ -19,29 +19,23 @@ import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/com
import { CancellationToken } from 'vs/base/common/cancellation';
import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
import { ITimerService } from 'vs/workbench/services/timer/browser/timerService';
@extHostNamedCustomer(MainContext.MainThreadExtensionService)
export class MainThreadExtensionService implements MainThreadExtensionServiceShape {
private readonly _extensionService: IExtensionService;
private readonly _notificationService: INotificationService;
private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService;
private readonly _hostService: IHostService;
private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService;
private readonly _extensionHostKind: ExtensionHostKind;
constructor(
extHostContext: IExtHostContext,
@IExtensionService extensionService: IExtensionService,
@INotificationService notificationService: INotificationService,
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
@IHostService hostService: IHostService,
@IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService
@IExtensionService private readonly _extensionService: IExtensionService,
@INotificationService private readonly _notificationService: INotificationService,
@IExtensionsWorkbenchService private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService,
@IHostService private readonly _hostService: IHostService,
@IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService,
@ITimerService private readonly _timerService: ITimerService,
) {
this._extensionService = extensionService;
this._notificationService = notificationService;
this._extensionsWorkbenchService = extensionsWorkbenchService;
this._hostService = hostService;
this._extensionEnablementService = extensionEnablementService;
this._extensionHostKind = extHostContext.extensionHostKind;
}
public dispose(): void {
@@ -131,4 +125,14 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha
async $onExtensionHostExit(code: number): Promise<void> {
this._extensionService._onExtensionHostExit(code);
}
async $setPerformanceMarks(marks: PerformanceMark[]): Promise<void> {
if (this._extensionHostKind === ExtensionHostKind.LocalProcess) {
this._timerService.setPerformanceMarks('localExtHost', marks);
} else if (this._extensionHostKind === ExtensionHostKind.LocalWebWorker) {
this._timerService.setPerformanceMarks('workerExtHost', marks);
} else {
this._timerService.setPerformanceMarks('remoteExtHost', marks);
}
}
}

View File

@@ -4,23 +4,43 @@
*--------------------------------------------------------------------------------------------*/
import { DisposableStore } from 'vs/base/common/lifecycle';
import { FileChangeType, IFileService } from 'vs/platform/files/common/files';
import { FileChangeType, FileOperation, IFileService } from 'vs/platform/files/common/files';
import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ExtHostContext, FileSystemEvents, IExtHostContext } from '../common/extHost.protocol';
import { localize } from 'vs/nls';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IWorkingCopyFileOperationParticipant, IWorkingCopyFileService, SourceTargetPair, IFileOperationUndoRedoInfo } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { reviveWorkspaceEditDto2 } from 'vs/workbench/api/browser/mainThreadEditors';
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
import { raceCancellation } from 'vs/base/common/async';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import Severity from 'vs/base/common/severity';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@extHostCustomer
export class MainThreadFileSystemEventService {
static readonly MementoKeyAdditionalEdits = `file.particpants.additionalEdits`;
private readonly _listener = new DisposableStore();
constructor(
extHostContext: IExtHostContext,
@IFileService fileService: IFileService,
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService,
@IBulkEditService bulkEditService: IBulkEditService,
@IProgressService progressService: IProgressService,
@IDialogService dialogService: IDialogService,
@IStorageService storageService: IStorageService,
@ILogService logService: ILogService,
@IEnvironmentService envService: IEnvironmentService
) {
const proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemEventService);
@@ -53,14 +73,124 @@ export class MainThreadFileSystemEventService {
}));
// BEFORE file operation
this._listener.add(workingCopyFileService.addFileOperationParticipant({
participate: async (files, operation, undoRedoGroupId, isUndoing, _progress, timeout, token) => {
if (!isUndoing) {
return proxy.$onWillRunFileOperation(operation, files, undoRedoGroupId, timeout, token);
const fileOperationParticipant = new class implements IWorkingCopyFileOperationParticipant {
async participate(files: SourceTargetPair[], operation: FileOperation, undoInfo: IFileOperationUndoRedoInfo | undefined, timeout: number, token: CancellationToken) {
if (undoInfo?.isUndoing) {
return;
}
const cts = new CancellationTokenSource(token);
const timer = setTimeout(() => cts.cancel(), timeout);
const data = await progressService.withProgress({
location: ProgressLocation.Notification,
title: this._progressLabel(operation),
cancellable: true,
delay: Math.min(timeout / 2, 3000)
}, () => {
// race extension host event delivery against timeout AND user-cancel
const onWillEvent = proxy.$onWillRunFileOperation(operation, files, timeout, token);
return raceCancellation(onWillEvent, cts.token);
}, () => {
// user-cancel
cts.cancel();
}).finally(() => {
cts.dispose();
clearTimeout(timer);
});
if (!data) {
// cancelled or no reply
return;
}
const needsConfirmation = data.edit.edits.some(edit => edit.metadata?.needsConfirmation);
let showPreview = storageService.getBoolean(MainThreadFileSystemEventService.MementoKeyAdditionalEdits, StorageScope.GLOBAL);
if (envService.extensionTestsLocationURI) {
// don't show dialog in tests
showPreview = false;
}
if (showPreview === undefined) {
// show a user facing message
let message: string;
if (data.extensionNames.length === 1) {
if (operation === FileOperation.CREATE) {
message = localize('ask.1.create', "Extension '{0}' wants to make refactoring changes with this file creation", data.extensionNames[0]);
} else if (operation === FileOperation.COPY) {
message = localize('ask.1.copy', "Extension '{0}' wants to make refactoring changes with this file copy", data.extensionNames[0]);
} else if (operation === FileOperation.MOVE) {
message = localize('ask.1.move', "Extension '{0}' wants to make refactoring changes with this file move", data.extensionNames[0]);
} else /* if (operation === FileOperation.DELETE) */ {
message = localize('ask.1.delete', "Extension '{0}' wants to make refactoring changes with this file deletion", data.extensionNames[0]);
}
} else {
if (operation === FileOperation.CREATE) {
message = localize('ask.N.create', "{0} extensions want to make refactoring changes with this file creation", data.extensionNames.length);
} else if (operation === FileOperation.COPY) {
message = localize('ask.N.copy', "{0} extensions want to make refactoring changes with this file copy", data.extensionNames.length);
} else if (operation === FileOperation.MOVE) {
message = localize('ask.N.move', "{0} extensions want to make refactoring changes with this file move", data.extensionNames.length);
} else /* if (operation === FileOperation.DELETE) */ {
message = localize('ask.N.delete', "{0} extensions want to make refactoring changes with this file deletion", data.extensionNames.length);
}
}
if (needsConfirmation) {
// edit which needs confirmation -> always show dialog
const answer = await dialogService.show(Severity.Info, message, [localize('preview', "Show Preview"), localize('cancel', "Skip Changes")], { cancelId: 1 });
showPreview = true;
if (answer.choice === 1) {
// no changes wanted
return;
}
} else {
// choice
const answer = await dialogService.show(Severity.Info, message,
[localize('ok', "OK"), localize('preview', "Show Preview"), localize('cancel', "Skip Changes")],
{
cancelId: 2,
checkbox: { label: localize('again', "Don't ask again") }
}
);
if (answer.choice === 2) {
// no changes wanted, don't persist cancel option
return;
}
showPreview = answer.choice === 1;
if (answer.checkboxChecked /* && answer.choice !== 2 */) {
storageService.store(MainThreadFileSystemEventService.MementoKeyAdditionalEdits, showPreview, StorageScope.GLOBAL, StorageTarget.USER);
}
}
}
logService.info('[onWill-handler] applying additional workspace edit from extensions', data.extensionNames);
await bulkEditService.apply(
reviveWorkspaceEditDto2(data.edit),
{ undoRedoGroupId: undoInfo?.undoRedoGroupId, showPreview }
);
}
private _progressLabel(operation: FileOperation): string {
switch (operation) {
case FileOperation.CREATE:
return localize('msg-create', "Running 'File Create' participants...");
case FileOperation.MOVE:
return localize('msg-rename', "Running 'File Rename' participants...");
case FileOperation.COPY:
return localize('msg-copy', "Running 'File Copy' participants...");
case FileOperation.DELETE:
return localize('msg-delete', "Running 'File Delete' participants...");
}
}
}));
};
// BEFORE file operation
this._listener.add(workingCopyFileService.addFileOperationParticipant(fileOperationParticipant));
// AFTER file operation
this._listener.add(workingCopyFileService.onDidRunWorkingCopyFileOperation(e => proxy.$onDidRunFileOperation(e.operation, e.files)));
@@ -71,6 +201,19 @@ export class MainThreadFileSystemEventService {
}
}
registerAction2(class ResetMemento extends Action2 {
constructor() {
super({
id: 'files.participants.resetChoice',
title: localize('label', "Reset choice for 'File operation needs preview'"),
f1: true
});
}
run(accessor: ServicesAccessor) {
accessor.get(IStorageService).remove(MainThreadFileSystemEventService.MementoKeyAdditionalEdits, StorageScope.GLOBAL);
}
});
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
id: 'files',

View File

@@ -21,7 +21,7 @@ import { Selection } from 'vs/editor/common/core/selection';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { mixin } from 'vs/base/common/objects';
import { decodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokensDto';
import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto';
@extHostNamedCustomer(MainContext.MainThreadLanguageFeatures)
export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape {
@@ -498,6 +498,32 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
}));
}
// --- inline hints
$registerInlineHintsProvider(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void {
const provider = <modes.InlineHintsProvider>{
provideInlineHints: async (model: ITextModel, range: EditorRange, token: CancellationToken): Promise<modes.InlineHint[] | undefined> => {
const result = await this._proxy.$provideInlineHints(handle, model.uri, range, token);
return result?.hints;
}
};
if (typeof eventHandle === 'number') {
const emitter = new Emitter<void>();
this._registrations.set(eventHandle, emitter);
provider.onDidChangeInlineHints = emitter.event;
}
this._registrations.set(handle, modes.InlineHintsProviderRegistry.register(selector, provider));
}
$emitInlineHintsEvent(eventHandle: number, event?: any): void {
const obj = this._registrations.get(eventHandle);
if (obj instanceof Emitter) {
obj.fire(event);
}
}
// --- links
$registerDocumentLinkProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean): void {
@@ -663,7 +689,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
return {
beforeText: MainThreadLanguageFeatures._reviveRegExp(onEnterRule.beforeText),
afterText: onEnterRule.afterText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.afterText) : undefined,
oneLineAboveText: onEnterRule.oneLineAboveText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.oneLineAboveText) : undefined,
previousLineText: onEnterRule.previousLineText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.previousLineText) : undefined,
action: onEnterRule.action
};
}
@@ -702,7 +728,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
const languageIdentifier = this._modeService.getLanguageIdentifier(languageId);
if (languageIdentifier) {
this._registrations.set(handle, LanguageConfigurationRegistry.register(languageIdentifier, configuration));
this._registrations.set(handle, LanguageConfigurationRegistry.register(languageIdentifier, configuration, 100));
}
}

View File

@@ -33,7 +33,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape {
$showMessage(severity: Severity, message: string, options: MainThreadMessageOptions, commands: { title: string; isCloseAffordance: boolean; handle: number; }[]): Promise<number | undefined> {
if (options.modal) {
return this._showModalMessage(severity, message, commands);
return this._showModalMessage(severity, message, commands, options.useCustom);
} else {
return this._showMessage(severity, message, commands, options.extension);
}
@@ -97,7 +97,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape {
});
}
private async _showModalMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number; }[]): Promise<number | undefined> {
private async _showModalMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number; }[], useCustom?: boolean): Promise<number | undefined> {
let cancelId: number | undefined = undefined;
const buttons = commands.map((command, index) => {
@@ -118,7 +118,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape {
cancelId = buttons.length - 1;
}
const { choice } = await this._dialogService.show(severity, message, buttons, { cancelId });
const { choice } = await this._dialogService.show(severity, message, buttons, { cancelId, useCustom });
return choice === commands.length ? undefined : commands[choice].handle;
}
}

View File

@@ -10,14 +10,13 @@ import { Emitter } from 'vs/base/common/event';
import { IRelativePattern } from 'vs/base/common/glob';
import { combinedDisposable, Disposable, DisposableStore, dispose, IDisposable, IReference } from 'vs/base/common/lifecycle';
import { ResourceMap } from 'vs/base/common/map';
import { Schemas } from 'vs/base/common/network';
import { IExtUri } from 'vs/base/common/resources';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { EditorActivation, ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { viewColumnToEditorGroup } from 'vs/workbench/common/editor';
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
@@ -31,7 +30,6 @@ import { IEditorGroup, IEditorGroupsService, preferredSideBySideGroupDirection }
import { openEditorWith } from 'vs/workbench/services/editor/common/editorOpenWith';
import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookCellStatusBarEntryDto, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookModelAddedData, MainContext, MainThreadNotebookShape, NotebookEditorRevealType, NotebookExtensionDescription } from '../common/extHost.protocol';
class DocumentAndEditorState {
@@ -129,12 +127,11 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
@IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService,
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@ILogService private readonly logService: ILogService,
@INotebookCellStatusBarService private readonly cellStatusBarService: INotebookCellStatusBarService,
@IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService,
@INotebookEditorModelResolverService private readonly _notebookModelResolverService: INotebookEditorModelResolverService,
@IUriIdentityService private readonly _uriIdentityService: IUriIdentityService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook);
@@ -605,32 +602,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
return false;
}
$onUndoableContentChange(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void {
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
if (textModel) {
textModel.handleUnknownUndoableEdit(label, () => {
const isDirty = this._workingCopyService.isDirty(textModel.uri.with({ scheme: Schemas.vscodeNotebook }));
return this._proxy.$undoNotebook(textModel.viewType, textModel.uri, editId, isDirty);
}, () => {
const isDirty = this._workingCopyService.isDirty(textModel.uri.with({ scheme: Schemas.vscodeNotebook }));
return this._proxy.$redoNotebook(textModel.viewType, textModel.uri, editId, isDirty);
});
}
}
$onContentChange(resource: UriComponents, viewType: string): void {
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
if (textModel) {
textModel.applyEdits(textModel.versionId, [
{
editType: CellEditType.Unknown
}
], true, undefined, () => undefined, undefined);
}
}
async $tryRevealRange(id: string, range: ICellRange, revealType: NotebookEditorRevealType) {
const editor = this._notebookService.listNotebookEditors().find(editor => editor.getId() === id);
if (editor && editor.isNotebookEditor) {
@@ -646,14 +617,13 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
switch (revealType) {
case NotebookEditorRevealType.Default:
notebookEditor.revealInView(cell);
break;
return notebookEditor.revealCellRangeInView(range);
case NotebookEditorRevealType.InCenter:
notebookEditor.revealInCenter(cell);
break;
return notebookEditor.revealInCenter(cell);
case NotebookEditorRevealType.InCenterIfOutsideViewport:
notebookEditor.revealInCenterIfOutsideViewport(cell);
break;
return notebookEditor.revealInCenterIfOutsideViewport(cell);
case NotebookEditorRevealType.AtTop:
return notebookEditor.revealInViewAtTop(cell);
default:
break;
}
@@ -733,7 +703,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
const input = this.editorService.createEditorInput({ resource: URI.revive(resource), options: editorOptions });
// TODO: handle options.selection
const editorPane = await openEditorWith(input, viewType, options, group, this.editorService, this.configurationService, this.quickInputService);
const editorPane = await this._instantiationService.invokeFunction(openEditorWith, input, viewType, options, group);
const notebookEditor = (editorPane as unknown as { isNotebookEditor?: boolean })?.isNotebookEditor ? (editorPane!.getControl() as INotebookEditor) : undefined;
if (notebookEditor) {

View File

@@ -0,0 +1,74 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { IProductService } from 'vs/platform/product/common/productService';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials';
import { IEncryptionService } from 'vs/workbench/services/encryption/common/encryptionService';
import { IExtHostContext, MainContext, MainThreadSecretStateShape } from '../common/extHost.protocol';
@extHostNamedCustomer(MainContext.MainThreadSecretState)
export class MainThreadSecretState extends Disposable implements MainThreadSecretStateShape {
// private readonly _proxy: ExtHostSecretStateShape;
constructor(
extHostContext: IExtHostContext,
@ICredentialsService private readonly credentialsService: ICredentialsService,
@IEncryptionService private readonly encryptionService: IEncryptionService,
@IProductService private readonly productService: IProductService
) {
super();
// this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostSecretState);
// {{SQL CARBON EDIT}} - throws null
/* this._register(this.credentialsService.onDidChangePassword(e => {
const extensionId = e.service.substring(this.productService.urlProtocol.length);
this._proxy.$onDidChangePassword({ extensionId, key: e.account });
})); */
}
private getFullKey(extensionId: string): string {
return `${this.productService.urlProtocol}${extensionId}`;
}
async $getPassword(extensionId: string, key: string): Promise<string | undefined> {
const fullKey = this.getFullKey(extensionId);
const password = await this.credentialsService.getPassword(fullKey, key);
const decrypted = password && await this.encryptionService.decrypt(password);
if (decrypted) {
try {
const value = JSON.parse(decrypted);
if (value.extensionId === extensionId) {
return value.content;
}
} catch (_) {
throw new Error('Cannot get password');
}
}
return undefined;
}
async $setPassword(extensionId: string, key: string, value: string): Promise<void> {
const fullKey = this.getFullKey(extensionId);
const toEncrypt = JSON.stringify({
extensionId,
content: value
});
const encrypted = await this.encryptionService.encrypt(toEncrypt);
return this.credentialsService.setPassword(fullKey, key, encrypted);
}
async $deletePassword(extensionId: string, key: string): Promise<void> {
try {
const fullKey = this.getFullKey(extensionId);
await this.credentialsService.deletePassword(fullKey, key);
} catch (_) {
throw new Error('Cannot delete password');
}
}
}

View File

@@ -567,13 +567,19 @@ export class MainThreadTask implements MainThreadTaskShape {
if (!task) {
reject(new Error('Task not found'));
} else {
this._taskService.run(task).then(undefined, reason => {
// eat the error, it has already been surfaced to the user and we don't care about it here
});
const result: TaskExecutionDTO = {
id: value.id,
task: TaskDTO.from(task)
};
this._taskService.run(task).then(summary => {
// Ensure that the task execution gets cleaned up if the exit code is undefined
// This can happen when the task has dependent tasks and one of them failed
if ((summary?.exitCode === undefined) || (summary.exitCode !== 0)) {
this._proxy.$OnDidEndTask(result);
}
}, reason => {
// eat the error, it has already been surfaced to the user and we don't care about it here
});
resolve(result);
}
}, (_error) => {

View File

@@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { IShellLaunchConfig, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY, IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest } from 'vs/workbench/contrib/terminal/common/terminal';
import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, IShellLaunchConfigDto, TerminalLaunchConfig, ITerminalDimensionsDto } from 'vs/workbench/api/common/extHost.protocol';
import { IShellLaunchConfig, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, ITerminalDimensions, IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest, ITerminalConfiguration, TERMINAL_CONFIG_SECTION } from 'vs/workbench/contrib/terminal/common/terminal';
import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, IShellLaunchConfigDto, TerminalLaunchConfig, ITerminalDimensionsDto, TerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { URI } from 'vs/base/common/uri';
import { StopWatch } from 'vs/base/common/stopwatch';
@@ -16,11 +16,18 @@ import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/termi
import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
import { ILogService } from 'vs/platform/log/common/log';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@extHostNamedCustomer(MainContext.MainThreadTerminalService)
export class MainThreadTerminalService implements MainThreadTerminalServiceShape {
private _proxy: ExtHostTerminalServiceShape;
/**
* Stores a map from a temporary terminal id (a UUID generated on the extension host side)
* to a numeric terminal id (an id generated on the renderer side)
* This comes in play only when dealing with terminals created on the extension host side
*/
private _extHostTerminalIds = new Map<string, number>();
private _remoteAuthority: string | null;
private readonly _toDispose = new DisposableStore();
private readonly _terminalProcessProxies = new Map<number, ITerminalProcessExtHostProxy>();
@@ -40,6 +47,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@ILogService private readonly _logService: ILogService,
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService);
@@ -47,13 +55,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
// ITerminalService listeners
this._toDispose.add(_terminalService.onInstanceCreated((instance) => {
// Delay this message so the TerminalInstance constructor has a chance to finish and
// return the ID normally to the extension host. The ID that is passed here will be
// used to register non-extension API terminals in the extension host.
setTimeout(() => {
this._onTerminalOpened(instance);
this._onInstanceDimensionsChanged(instance);
}, EXT_HOST_CREATION_DELAY);
this._onTerminalOpened(instance);
this._onInstanceDimensionsChanged(instance);
}));
this._toDispose.add(_terminalService.onInstanceDisposed(instance => this._onTerminalDisposed(instance)));
@@ -100,7 +103,22 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
// when the extension host process goes down ?
}
public $createTerminal(launchConfig: TerminalLaunchConfig): Promise<{ id: number, name: string }> {
private _getTerminalId(id: TerminalIdentifier): number | undefined {
if (typeof id === 'number') {
return id;
}
return this._extHostTerminalIds.get(id);
}
private _getTerminalInstance(id: TerminalIdentifier): ITerminalInstance | undefined {
const rendererId = this._getTerminalId(id);
if (typeof rendererId === 'number') {
return this._terminalService.getInstanceFromId(rendererId);
}
return undefined;
}
public async $createTerminal(extHostTerminalId: string, launchConfig: TerminalLaunchConfig): Promise<void> {
const shellLaunchConfig: IShellLaunchConfig = {
name: launchConfig.name,
executable: launchConfig.shellPath,
@@ -112,39 +130,38 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
strictEnv: launchConfig.strictEnv,
hideFromUser: launchConfig.hideFromUser,
isExtensionTerminal: launchConfig.isExtensionTerminal,
extHostTerminalId: extHostTerminalId,
isFeatureTerminal: launchConfig.isFeatureTerminal
};
const terminal = this._terminalService.createTerminal(shellLaunchConfig);
return Promise.resolve({
id: terminal.id,
name: terminal.title
});
this._extHostTerminalIds.set(extHostTerminalId, terminal.id);
}
public $show(terminalId: number, preserveFocus: boolean): void {
const terminalInstance = this._terminalService.getInstanceFromId(terminalId);
public $show(id: TerminalIdentifier, preserveFocus: boolean): void {
const terminalInstance = this._getTerminalInstance(id);
if (terminalInstance) {
this._terminalService.setActiveInstance(terminalInstance);
this._terminalService.showPanel(!preserveFocus);
}
}
public $hide(terminalId: number): void {
public $hide(id: TerminalIdentifier): void {
const rendererId = this._getTerminalId(id);
const instance = this._terminalService.getActiveInstance();
if (instance && instance.id === terminalId) {
if (instance && instance.id === rendererId) {
this._terminalService.hidePanel();
}
}
public $dispose(terminalId: number): void {
const terminalInstance = this._terminalService.getInstanceFromId(terminalId);
public $dispose(id: TerminalIdentifier): void {
const terminalInstance = this._getTerminalInstance(id);
if (terminalInstance) {
terminalInstance.dispose();
}
}
public $sendText(terminalId: number, text: string, addNewLine: boolean): void {
const terminalInstance = this._terminalService.getInstanceFromId(terminalId);
public $sendText(id: TerminalIdentifier, text: string, addNewLine: boolean): void {
const terminalInstance = this._getTerminalInstance(id);
if (terminalInstance) {
terminalInstance.sendText(text, addNewLine);
}
@@ -204,6 +221,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
}
private _onTerminalOpened(terminalInstance: ITerminalInstance): void {
const extHostTerminalId = terminalInstance.shellLaunchConfig.extHostTerminalId;
const shellLaunchConfigDto: IShellLaunchConfigDto = {
name: terminalInstance.shellLaunchConfig.name,
executable: terminalInstance.shellLaunchConfig.executable,
@@ -212,13 +230,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
env: terminalInstance.shellLaunchConfig.env,
hideFromUser: terminalInstance.shellLaunchConfig.hideFromUser
};
if (terminalInstance.title) {
this._proxy.$acceptTerminalOpened(terminalInstance.id, terminalInstance.title, shellLaunchConfigDto);
} else {
terminalInstance.waitForTitle().then(title => {
this._proxy.$acceptTerminalOpened(terminalInstance.id, title, shellLaunchConfigDto);
});
}
this._proxy.$acceptTerminalOpened(terminalInstance.id, extHostTerminalId, terminalInstance.title, shellLaunchConfigDto);
}
private _onTerminalProcessIdReady(terminalInstance: ITerminalInstance): void {
@@ -249,7 +261,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
executable: request.shellLaunchConfig.executable,
args: request.shellLaunchConfig.args,
cwd: request.shellLaunchConfig.cwd,
env: request.shellLaunchConfig.env
env: request.shellLaunchConfig.env,
flowControl: this._configurationService.getValue<ITerminalConfiguration>(TERMINAL_CONFIG_SECTION).flowControl
};
this._logService.trace('Spawning ext host process', { terminalId: proxy.terminalId, shellLaunchConfigDto, request });
@@ -260,8 +273,9 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
request.cols,
request.rows,
request.isWorkspaceShellAllowed
).then(request.callback);
).then(request.callback, request.callback);
proxy.onAcknowledgeDataEvent(charCount => this._proxy.$acceptProcessAckDataEvent(proxy.terminalId, charCount));
proxy.onInput(data => this._proxy.$acceptProcessInput(proxy.terminalId, data));
proxy.onResize(dimensions => this._proxy.$acceptProcessResize(proxy.terminalId, dimensions.cols, dimensions.rows));
proxy.onShutdown(immediate => this._proxy.$acceptProcessShutdown(proxy.terminalId, immediate));
@@ -294,38 +308,59 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
}
public $sendProcessTitle(terminalId: number, title: string): void {
this._getTerminalProcess(terminalId).emitTitle(title);
const terminalProcess = this._terminalProcessProxies.get(terminalId);
if (terminalProcess) {
terminalProcess.emitTitle(title);
}
}
public $sendProcessData(terminalId: number, data: string): void {
this._getTerminalProcess(terminalId).emitData(data);
const terminalProcess = this._terminalProcessProxies.get(terminalId);
if (terminalProcess) {
terminalProcess.emitData(data);
}
}
public $sendProcessReady(terminalId: number, pid: number, cwd: string): void {
this._getTerminalProcess(terminalId).emitReady(pid, cwd);
const terminalProcess = this._terminalProcessProxies.get(terminalId);
if (terminalProcess) {
terminalProcess.emitReady(pid, cwd);
}
}
public $sendProcessExit(terminalId: number, exitCode: number | undefined): void {
this._getTerminalProcess(terminalId).emitExit(exitCode);
this._terminalProcessProxies.delete(terminalId);
const terminalProcess = this._terminalProcessProxies.get(terminalId);
if (terminalProcess) {
terminalProcess.emitExit(exitCode);
this._terminalProcessProxies.delete(terminalId);
}
}
public $sendOverrideDimensions(terminalId: number, dimensions: ITerminalDimensions | undefined): void {
this._getTerminalProcess(terminalId).emitOverrideDimensions(dimensions);
const terminalProcess = this._terminalProcessProxies.get(terminalId);
if (terminalProcess) {
terminalProcess.emitOverrideDimensions(dimensions);
}
}
public $sendProcessInitialCwd(terminalId: number, initialCwd: string): void {
this._getTerminalProcess(terminalId).emitInitialCwd(initialCwd);
const terminalProcess = this._terminalProcessProxies.get(terminalId);
if (terminalProcess) {
terminalProcess.emitInitialCwd(initialCwd);
}
}
public $sendProcessCwd(terminalId: number, cwd: string): void {
this._getTerminalProcess(terminalId).emitCwd(cwd);
const terminalProcess = this._terminalProcessProxies.get(terminalId);
if (terminalProcess) {
terminalProcess.emitCwd(cwd);
}
}
public $sendResolvedLaunchConfig(terminalId: number, shellLaunchConfig: IShellLaunchConfig): void {
const instance = this._terminalService.getInstanceFromId(terminalId);
if (instance) {
this._getTerminalProcess(terminalId).emitResolvedShellLaunchConfig(shellLaunchConfig);
this._getTerminalProcess(terminalId)?.emitResolvedShellLaunchConfig(shellLaunchConfig);
}
}
@@ -338,7 +373,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
sw.stop();
sum += sw.elapsed();
}
this._getTerminalProcess(terminalId).emitLatency(sum / COUNT);
this._getTerminalProcess(terminalId)?.emitLatency(sum / COUNT);
}
private _isPrimaryExtHost(): boolean {
@@ -363,10 +398,11 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
}
}
private _getTerminalProcess(terminalId: number): ITerminalProcessExtHostProxy {
private _getTerminalProcess(terminalId: number): ITerminalProcessExtHostProxy | undefined {
const terminal = this._terminalProcessProxies.get(terminalId);
if (!terminal) {
throw new Error(`Unknown terminal: ${terminalId}`);
this._logService.error(`Unknown terminal: ${terminalId}`);
return undefined;
}
return terminal;
}

View File

@@ -3,12 +3,31 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { getTestSubscriptionKey, RunTestsRequest, RunTestsResult, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ExtHostContext, ExtHostTestingResource, ExtHostTestingShape, IExtHostContext, MainContext, MainThreadTestingShape } from '../common/extHost.protocol';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { getTestSubscriptionKey, RunTestsRequest, RunTestsResult, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
import { ExtHostContext, ExtHostTestingResource, ExtHostTestingShape, IExtHostContext, MainContext, MainThreadTestingShape } from '../common/extHost.protocol';
const reviveDiff = (diff: TestsDiff) => {
for (const entry of diff) {
if (entry[0] === TestDiffOpType.Add || entry[0] === TestDiffOpType.Update) {
const item = entry[1];
if (item.item.location) {
item.item.location.uri = URI.revive(item.item.location.uri);
}
for (const message of item.item.state.messages) {
if (message.location) {
message.location.uri = URI.revive(message.location.uri);
}
}
}
}
};
@extHostNamedCustomer(MainContext.MainThreadTesting)
export class MainThreadTesting extends Disposable implements MainThreadTestingShape {
@@ -18,11 +37,28 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
constructor(
extHostContext: IExtHostContext,
@ITestService private readonly testService: ITestService,
@ITestResultService resultService: ITestResultService,
) {
super();
this.proxy = extHostContext.getProxy(ExtHostContext.ExtHostTesting);
this._register(this.testService.onShouldSubscribe(args => this.proxy.$subscribeToTests(args.resource, args.uri)));
this._register(this.testService.onShouldUnsubscribe(args => this.proxy.$unsubscribeFromTests(args.resource, args.uri)));
const testCompleteListener = this._register(new MutableDisposable());
this._register(resultService.onNewTestResult(results => {
testCompleteListener.value = results.onComplete(() => this.proxy.$publishTestResults({ tests: results.tests }));
}));
testService.updateRootProviderCount(1);
const lastCompleted = resultService.results.find(r => !r.isComplete);
if (lastCompleted) {
this.proxy.$publishTestResults({ tests: lastCompleted.tests });
}
for (const { resource, uri } of this.testService.subscriptions) {
this.proxy.$subscribeToTests(resource, uri);
}
}
/**
@@ -30,7 +66,8 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
*/
public $registerTestProvider(id: string) {
this.testService.registerTestController(id, {
runTests: req => this.proxy.$runTestsForProvider(req),
runTests: (req, token) => this.proxy.$runTestsForProvider(req, token),
lookupTest: test => this.proxy.$lookupTest(test),
});
}
@@ -64,14 +101,19 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
* @inheritdoc
*/
public $publishDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void {
reviveDiff(diff);
this.testService.publishDiff(resource, URI.revive(uri), diff);
}
public $runTests(req: RunTestsRequest): Promise<RunTestsResult> {
return this.testService.runTests(req);
public $runTests(req: RunTestsRequest, token: CancellationToken): Promise<RunTestsResult> {
return this.testService.runTests(req, token);
}
public dispose() {
// no-op
this.testService.updateRootProviderCount(-1);
for (const subscription of this.testSubscriptions.values()) {
subscription.dispose();
}
this.testSubscriptions.clear();
}
}

View File

@@ -33,7 +33,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTreeViews);
}
$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean }): void {
$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean; }): void {
this.logService.trace('MainThreadTreeViews#$registerTreeViewDataProvider', treeViewId, options);
this.extensionService.whenInstalledExtensionsRegistered().then(() => {
@@ -48,6 +48,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
viewer.dataProvider = dataProvider;
this.registerListeners(treeViewId, viewer);
this._proxy.$setVisible(treeViewId, viewer.visible);
// {{SQL CARBON EDIT}}
} else if (treeViewId.includes('connectionDialog')) {
this.connectionTreeService.registerTreeProvider(treeViewId, dataProvider);
} else {
@@ -56,7 +57,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
});
}
$reveal(treeViewId: string, itemInfo: { item: ITreeItem, parentChain: ITreeItem[] } | undefined, options: IRevealOptions): Promise<void> {
$reveal(treeViewId: string, itemInfo: { item: ITreeItem, parentChain: ITreeItem[]; } | undefined, options: IRevealOptions): Promise<void> {
this.logService.trace('MainThreadTreeViews#$reveal', treeViewId, itemInfo?.item, itemInfo?.parentChain, options);
return this.viewsService.openView(treeViewId, options.focus)
@@ -69,7 +70,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
});
}
$refresh(treeViewId: string, itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeItem }): Promise<void> {
$refresh(treeViewId: string, itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeItem; }): Promise<void> {
this.logService.trace('MainThreadTreeViews#$refresh', treeViewId, itemsToRefreshByHandle);
const viewer = this.getTreeView(treeViewId);
@@ -77,6 +78,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
if (viewer && dataProvider) {
const itemsToRefresh = dataProvider.getItemsToRefresh(itemsToRefreshByHandle);
return viewer.refresh(itemsToRefresh.length ? itemsToRefresh : undefined);
// {{SQL CARBON EDIT}}
} else if (treeViewId.includes('connectionDialog')) {
const itemsToRefresh = dataProvider.getItemsToRefresh(itemsToRefreshByHandle);
return this.connectionTreeService.view?.refresh(itemsToRefresh.length ? itemsToRefresh : undefined);
@@ -193,7 +195,7 @@ export class TreeViewDataProvider implements ITreeViewDataProvider {
}));
}
getItemsToRefresh(itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeItem }): ITreeItem[] {
getItemsToRefresh(itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeItem; }): ITreeItem[] {
const itemsToRefresh: ITreeItem[] = [];
if (itemsToRefreshByHandle) {
for (const treeItemHandle of Object.keys(itemsToRefreshByHandle)) {
@@ -235,8 +237,8 @@ export class TreeViewDataProvider implements ITreeViewDataProvider {
// {{SQL CARBON EDIT}} We rely on custom properties on the tree items in a number of places so creating a new item here was
// {{SQL CARBON EDIT}} clearing those. Revert to old behavior if the provider doesn't support a resolve (our normal case)
if (hasResolve) {
const resolvable = new ResolvableTreeItem(element, hasResolve ? () => {
return this._proxy.$resolve(this.treeViewId, element.handle);
const resolvable = new ResolvableTreeItem(element, hasResolve ? (token) => {
return this._proxy.$resolve(this.treeViewId, element.handle, token);
} : undefined);
this.itemsMap.set(element.handle, resolvable);
result.push(resolvable);
@@ -252,10 +254,14 @@ export class TreeViewDataProvider implements ITreeViewDataProvider {
private updateTreeItem(current: ITreeItem, treeItem: ITreeItem): void {
treeItem.children = treeItem.children ? treeItem.children : undefined;
if (current) {
const properties = distinct([...Object.keys(current), ...Object.keys(treeItem)]);
const properties = distinct([...Object.keys(current instanceof ResolvableTreeItem ? current.asTreeItem() : current),
...Object.keys(treeItem)]);
for (const property of properties) {
(<any>current)[property] = (<any>treeItem)[property];
}
if (current instanceof ResolvableTreeItem) {
current.resetResolve();
}
}
}
}

View File

@@ -3,22 +3,31 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol';
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { IRemoteExplorerService, makeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { CandidatePort, IRemoteExplorerService, makeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, RemoteTunnel, isPortPrivileged } from 'vs/platform/remote/common/tunnel';
import { Disposable } from 'vs/base/common/lifecycle';
import type { TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { PORT_AUTO_FORWARD_SETTING } from 'vs/workbench/contrib/remote/browser/tunnelView';
import { ILogService } from 'vs/platform/log/common/log';
@extHostNamedCustomer(MainContext.MainThreadTunnelService)
export class MainThreadTunnelService extends Disposable implements MainThreadTunnelServiceShape {
private readonly _proxy: ExtHostTunnelServiceShape;
private elevateionRetry: boolean = false;
constructor(
extHostContext: IExtHostContext,
@IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService,
@ITunnelService private readonly tunnelService: ITunnelService
@ITunnelService private readonly tunnelService: ITunnelService,
@INotificationService private readonly notificationService: INotificationService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@ILogService private readonly logService: ILogService
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTunnelService);
@@ -26,14 +35,50 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
this._register(tunnelService.onTunnelClosed(() => this._proxy.$onDidTunnelsChange()));
}
async $setCandidateFinder(): Promise<void> {
if (this.remoteExplorerService.portsFeaturesEnabled) {
this._proxy.$registerCandidateFinder(this.configurationService.getValue(PORT_AUTO_FORWARD_SETTING));
} else {
this._register(this.remoteExplorerService.onEnabledPortsFeatures(() => this._proxy.$registerCandidateFinder(this.configurationService.getValue(PORT_AUTO_FORWARD_SETTING))));
}
this._register(this.configurationService.onDidChangeConfiguration(async (e) => {
if (e.affectsConfiguration(PORT_AUTO_FORWARD_SETTING)) {
return this._proxy.$registerCandidateFinder((this.configurationService.getValue(PORT_AUTO_FORWARD_SETTING)));
}
}));
}
async $openTunnel(tunnelOptions: TunnelOptions, source: string): Promise<TunnelDto | undefined> {
const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remoteAddress, tunnelOptions.localAddressPort, tunnelOptions.label, source);
const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remoteAddress, tunnelOptions.localAddressPort, tunnelOptions.label, source, false);
if (tunnel) {
if (!this.elevateionRetry
&& (tunnelOptions.localAddressPort !== undefined)
&& (tunnel.tunnelLocalPort !== undefined)
&& isPortPrivileged(tunnelOptions.localAddressPort)
&& (tunnel.tunnelLocalPort !== tunnelOptions.localAddressPort)
&& this.tunnelService.canElevate) {
this.elevationPrompt(tunnelOptions, tunnel, source);
}
return TunnelDto.fromServiceTunnel(tunnel);
}
return undefined;
}
private async elevationPrompt(tunnelOptions: TunnelOptions, tunnel: RemoteTunnel, source: string) {
return this.notificationService.prompt(Severity.Info,
nls.localize('remote.tunnel.openTunnel', "The extension {0} has forwarded port {1}. You'll need to run as superuser to use port {2} locally.", source, tunnelOptions.remoteAddress.port, tunnelOptions.localAddressPort),
[{
label: nls.localize('remote.tunnelsView.elevationButton', "Use Port {0} as Sudo...", tunnel.tunnelRemotePort),
run: async () => {
this.elevateionRetry = true;
await this.remoteExplorerService.close({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort });
await this.remoteExplorerService.forward(tunnelOptions.remoteAddress, tunnelOptions.localAddressPort, tunnelOptions.label, source, true);
this.elevateionRetry = false;
}
}]);
}
async $closeTunnel(remote: { host: string, port: number }): Promise<void> {
return this.remoteExplorerService.close(remote);
}
@@ -47,27 +92,29 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
});
}
async $onFoundNewCandidates(candidates: { host: string, port: number, detail: string }[]): Promise<void> {
async $onFoundNewCandidates(candidates: CandidatePort[]): Promise<void> {
this.remoteExplorerService.onFoundNewCandidates(candidates);
}
async $tunnelServiceReady(): Promise<void> {
return this.remoteExplorerService.restore();
}
async $setTunnelProvider(): Promise<void> {
async $setTunnelProvider(features: TunnelProviderFeatures): Promise<void> {
const tunnelProvider: ITunnelProvider = {
forwardPort: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => {
const forward = this._proxy.$forwardPort(tunnelOptions, tunnelCreationOptions);
if (forward) {
return forward.then(tunnel => {
this.logService.trace(`MainThreadTunnelService: New tunnel established by tunnel provider: ${tunnel?.remoteAddress.host}:${tunnel?.remoteAddress.port}`);
if (!tunnel) {
return undefined;
}
return {
tunnelRemotePort: tunnel.remoteAddress.port,
tunnelRemoteHost: tunnel.remoteAddress.host,
localAddress: typeof tunnel.localAddress === 'string' ? tunnel.localAddress : makeAddress(tunnel.localAddress.host, tunnel.localAddress.port),
tunnelLocalPort: typeof tunnel.localAddress !== 'string' ? tunnel.localAddress.port : undefined,
dispose: (silent?: boolean) => {
this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port }, silent);
public: tunnel.public,
dispose: async (silent?: boolean) => {
this.logService.trace(`MainThreadTunnelService: Closing tunnel from tunnel provider: ${tunnel?.remoteAddress.host}:${tunnel?.remoteAddress.port}`);
return this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port }, silent);
}
};
});
@@ -75,9 +122,16 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
return undefined;
}
};
this.tunnelService.setTunnelProvider(tunnelProvider);
this.tunnelService.setTunnelProvider(tunnelProvider, features);
}
async $setCandidateFilter(): Promise<void> {
this.remoteExplorerService.setCandidateFilter((candidates: CandidatePort[]): Promise<CandidatePort[]> => {
return this._proxy.$applyCandidateFilter(candidates);
});
}
dispose(): void {
}

View File

@@ -0,0 +1,132 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Action } from 'vs/base/common/actions';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { Disposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ExtHostContext, ExtHostUriOpenersShape, IExtHostContext, MainContext, MainThreadUriOpenersShape } from 'vs/workbench/api/common/extHost.protocol';
import { defaultExternalUriOpenerId } from 'vs/workbench/contrib/externalUriOpener/common/configuration';
import { ContributedExternalUriOpenersStore } from 'vs/workbench/contrib/externalUriOpener/common/contributedOpeners';
import { IExternalOpenerProvider, IExternalUriOpener, IExternalUriOpenerService } from 'vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { extHostNamedCustomer } from '../common/extHostCustomers';
interface RegisteredOpenerMetadata {
readonly schemes: ReadonlySet<string>;
readonly extensionId: ExtensionIdentifier;
readonly label: string;
}
@extHostNamedCustomer(MainContext.MainThreadUriOpeners)
export class MainThreadUriOpeners extends Disposable implements MainThreadUriOpenersShape, IExternalOpenerProvider {
private readonly proxy: ExtHostUriOpenersShape;
private readonly _registeredOpeners = new Map<string, RegisteredOpenerMetadata>();
private readonly _contributedExternalUriOpenersStore: ContributedExternalUriOpenersStore;
constructor(
context: IExtHostContext,
@IStorageService storageService: IStorageService,
@IExternalUriOpenerService externalUriOpenerService: IExternalUriOpenerService,
@IExtensionService private readonly extensionService: IExtensionService,
@IOpenerService private readonly openerService: IOpenerService,
@INotificationService private readonly notificationService: INotificationService,
) {
super();
this.proxy = context.getProxy(ExtHostContext.ExtHostUriOpeners);
this._register(externalUriOpenerService.registerExternalOpenerProvider(this));
this._contributedExternalUriOpenersStore = this._register(new ContributedExternalUriOpenersStore(storageService, extensionService));
}
public async *getOpeners(targetUri: URI): AsyncIterable<IExternalUriOpener> {
// Currently we only allow openers for http and https urls
if (targetUri.scheme !== Schemas.http && targetUri.scheme !== Schemas.https) {
return;
}
await this.extensionService.activateByEvent(`onOpenExternalUri:${targetUri.scheme}`);
for (const [id, openerMetadata] of this._registeredOpeners) {
if (openerMetadata.schemes.has(targetUri.scheme)) {
yield this.createOpener(id, openerMetadata);
}
}
}
private createOpener(id: string, metadata: RegisteredOpenerMetadata): IExternalUriOpener {
return {
id: id,
label: metadata.label,
canOpen: (uri, token) => {
return this.proxy.$canOpenUri(id, uri, token);
},
openExternalUri: async (uri, ctx, token) => {
try {
await this.proxy.$openUri(id, { resolvedUri: uri, sourceUri: ctx.sourceUri }, token);
} catch (e) {
if (!isPromiseCanceledError(e)) {
const openDefaultAction = new Action('default', localize('openerFailedUseDefault', "Open using default opener"), undefined, undefined, () => {
return this.openerService.open(uri, {
allowTunneling: false,
allowContributedOpeners: defaultExternalUriOpenerId,
});
});
openDefaultAction.tooltip = uri.toString();
this.notificationService.notify({
severity: Severity.Error,
message: localize('openerFailedMessage', 'Could not open uri with \'{0}\': {1}', id, e.toString()),
actions: {
primary: [
openDefaultAction
]
}
});
}
}
return true;
},
};
}
async $registerUriOpener(
id: string,
schemes: readonly string[],
extensionId: ExtensionIdentifier,
label: string,
): Promise<void> {
if (this._registeredOpeners.has(id)) {
throw new Error(`Opener with id '${id}' already registered`);
}
this._registeredOpeners.set(id, {
schemes: new Set(schemes),
label,
extensionId,
});
this._contributedExternalUriOpenersStore.didRegisterOpener(id, extensionId.value);
}
async $unregisterUriOpener(id: string): Promise<void> {
this._registeredOpeners.delete(id);
this._contributedExternalUriOpenersStore.delete(id);
}
dispose(): void {
super.dispose();
this._registeredOpeners.clear();
}
}

View File

@@ -129,6 +129,9 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
dispose(this._editorProviders.values());
this._editorProviders.clear();
dispose(this._revivers.values());
this._revivers.clear();
}
public get webviewInputs(): Iterable<WebviewInput> { return this._webviewInputs; }
@@ -181,7 +184,6 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
webview.setName(value);
}
public $setIconPath(handle: extHostProtocol.WebviewHandle, value: { light: UriComponents, dark: UriComponents; } | undefined): void {
const webview = this.getWebviewInput(handle);
webview.iconPath = reviveWebviewIcon(value);
@@ -199,8 +201,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
}
}
public $registerSerializer(viewType: string)
: void {
public $registerSerializer(viewType: string): void {
if (this._revivers.has(viewType)) {
throw new Error(`Reviver for ${viewType} already registered`);
}
@@ -216,7 +217,6 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
return;
}
const handle = webviewInput.id;
this.addWebviewInput(handle, webviewInput);

View File

@@ -52,7 +52,11 @@ export class MainThreadWindow implements MainThreadWindowShape {
// called with URI or transformed -> use uri
target = uri;
}
return this.openerService.open(target, { openExternal: true, allowTunneling: options.allowTunneling });
return this.openerService.open(target, {
openExternal: true,
allowTunneling: options.allowTunneling,
allowContributedOpeners: options.allowContributedOpeners,
});
}
async $asExternalUri(uriComponents: UriComponents, options: IOpenUriOptions): Promise<UriComponents> {

View File

@@ -6,25 +6,25 @@
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { isNative } from 'vs/base/common/platform';
import { withNullAsUndefined } from 'vs/base/common/types';
import { URI, UriComponents } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { isNative } from 'vs/base/common/platform';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'vs/workbench/services/search/common/search';
import { IWorkspaceContextService, WorkbenchState, IWorkspace, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IRequestService } from 'vs/platform/request/common/request';
import { IWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { checkGlobFileExists } from 'vs/workbench/api/common/shared/workspaceContains';
import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'vs/workbench/services/search/common/search';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape, IWorkspaceData, ITextSearchComplete } from '../common/extHost.protocol';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IFileService } from 'vs/platform/files/common/files';
import { IRequestService } from 'vs/platform/request/common/request';
import { checkGlobFileExists } from 'vs/workbench/api/common/shared/workspaceContains';
import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, ITextSearchComplete, IWorkspaceData, MainContext, MainThreadWorkspaceShape } from '../common/extHost.protocol';
@extHostNamedCustomer(MainContext.MainThreadWorkspace)
export class MainThreadWorkspace implements MainThreadWorkspaceShape {
@@ -139,7 +139,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
}
const query = this._queryBuilder.file(
includeFolder ? [toWorkspaceFolder(includeFolder)] : workspace.folders,
includeFolder ? [includeFolder] : workspace.folders,
{
maxResults: withNullAsUndefined(maxResults),
disregardExcludeSettings: (excludePatternOrDisregardExcludes === false) || undefined,

View File

@@ -3,36 +3,31 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { coalesce } from 'vs/base/common/arrays';
import { forEach } from 'vs/base/common/collections';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import * as resources from 'vs/base/common/resources';
import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation, testViewIcon } from 'vs/workbench/common/views';
import { CustomTreeView, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { coalesce, } from 'vs/base/common/arrays';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { Registry } from 'vs/platform/registry/common/platform';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { VIEWLET_ID as EXPLORER } from 'vs/workbench/contrib/files/common/files';
import { VIEWLET_ID as SCM } from 'vs/workbench/contrib/scm/common/scm';
import { Registry } from 'vs/platform/registry/common/platform';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { CustomTreeView, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView';
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { Extensions as ViewletExtensions, ViewletRegistry } from 'vs/workbench/browser/viewlet';
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { Extensions as ViewContainerExtensions, ITreeViewDescriptor, IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views';
import { VIEWLET_ID as DEBUG } from 'vs/workbench/contrib/debug/common/debug';
import { VIEWLET_ID as EXPLORER } from 'vs/workbench/contrib/files/common/files';
import { VIEWLET_ID as REMOTE } from 'vs/workbench/contrib/remote/browser/remoteExplorer';
import { VIEWLET_ID as NOTEBOOK } from 'sql/workbench/contrib/notebook/browser/notebookExplorer/notebookExplorerViewlet'; // {{SQL CARBON EDIT}}
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { URI } from 'vs/base/common/uri';
import { ViewletRegistry, Extensions as ViewletExtensions, ShowViewletAction } from 'vs/workbench/browser/viewlet';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions, CATEGORIES } from 'vs/workbench/common/actions';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { VIEWLET_ID as SCM } from 'vs/workbench/contrib/scm/common/scm';
import { WebviewViewPane } from 'vs/workbench/contrib/webviewView/browser/webviewViewPane';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
export interface IUserFriendlyViewsContainerDescriptor {
id: string;
@@ -111,7 +106,7 @@ const viewDescriptor: IJSONSchema = {
defaultSnippets: [{ body: { id: '${1:id}', name: '${2:name}' } }],
properties: {
type: {
markdownDescription: localize('vscode.extension.contributes.view.type', "Type of the the view. This can either be `tree` for a tree view based view or `webview` for a webview based view. The default is `tree`."),
markdownDescription: localize('vscode.extension.contributes.view.type', "Type of the view. This can either be `tree` for a tree view based view or `webview` for a webview based view. The default is `tree`."),
type: 'string',
enum: [
'tree',
@@ -263,7 +258,8 @@ const viewsExtensionPoint: IExtensionPoint<ViewExtensionPointType> = ExtensionsR
jsonSchema: viewsContribution
});
const TEST_VIEW_CONTAINER_ORDER = 6;
const CUSTOM_VIEWS_START_ORDER = 7;
class ViewsExtensionHandler implements IWorkbenchContribution {
private viewContainersRegistry: IViewContainersRegistry;
@@ -279,7 +275,6 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
}
private handleAndRegisterCustomViewContainers() {
this.registerTestViewContainer();
viewsContainersExtensionPoint.setHandler((extensions, { added, removed }) => {
if (removed.length) {
this.removeCustomViewContainers(removed);
@@ -292,7 +287,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
private addCustomViewContainers(extensionPoints: readonly IExtensionPointUser<ViewContainerExtensionPointType>[], existingViewContainers: ViewContainer[]): void {
const viewContainersRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
let activityBarOrder = TEST_VIEW_CONTAINER_ORDER + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Sidebar).length + 1;
let activityBarOrder = CUSTOM_VIEWS_START_ORDER + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Sidebar).length;
let panelOrder = 5 + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Panel).length + 1;
for (let { value, collector, description } of extensionPoints) {
forEach(value, entry => {
@@ -326,13 +321,6 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
}
}
private registerTestViewContainer(): void {
const title = localize('test', "Test");
const icon = testViewIcon;
this.registerCustomViewContainer(TEST_VIEW_CONTAINER_ID, title, icon, TEST_VIEW_CONTAINER_ORDER, undefined, ViewContainerLocation.Sidebar);
}
private isValidViewsContainer(viewsContainersDescriptors: IUserFriendlyViewsContainerDescriptor[], collector: ExtensionMessageCollector): boolean {
if (!Array.isArray(viewsContainersDescriptors)) {
collector.error(localize('viewcontainer requirearray', "views containers must be an array"));
@@ -392,7 +380,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
viewContainer = this.viewContainersRegistry.registerViewContainer({
id,
name: title, extensionId,
title, extensionId,
ctorDescriptor: new SyncDescriptor(
ViewPaneContainer,
[id, { mergeViewWithContainerWhenSingleView: true }]
@@ -402,23 +390,6 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
icon,
}, location);
// Register Action to Open Viewlet
class OpenCustomViewletAction extends ShowViewletAction {
constructor(
id: string, label: string,
@IViewletService viewletService: IViewletService,
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService
) {
super(id, label, id, viewletService, editorGroupService, layoutService);
}
}
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(
SyncActionDescriptor.create(OpenCustomViewletAction, id, localize('showViewlet', "Show {0}", title)),
`View: Show ${title}`,
CATEGORIES.View.value
);
}
return viewContainer;
@@ -499,7 +470,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
name: item.name,
when: ContextKeyExpr.deserialize(item.when),
containerIcon: icon || viewContainer?.icon,
containerTitle: item.contextualTitle || viewContainer?.name,
containerTitle: item.contextualTitle || viewContainer?.title,
canToggleVisibility: true,
canMoveView: viewContainer?.id !== REMOTE,
treeView: type === ViewType.Tree ? this.instantiationService.createInstance(CustomTreeView, item.id, item.name) : undefined,
@@ -509,7 +480,8 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
originalContainerId: entry.key,
group: item.group,
remoteAuthority: item.remoteName || (<any>item).remoteAuthority, // TODO@roblou - delete after remote extensions are updated
hideByDefault: initialVisibility === InitialVisibility.Hidden
hideByDefault: initialVisibility === InitialVisibility.Hidden,
workspace: viewContainer?.id === REMOTE ? true : undefined
};