mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-10 02:02:35 -05:00
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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
108
src/vs/workbench/api/browser/mainThreadCLICommands.ts
Normal file
108
src/vs/workbench/api/browser/mainThreadCLICommands.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
79
src/vs/workbench/api/browser/mainThreadEditorTabs.ts
Normal file
79
src/vs/workbench/api/browser/mainThreadEditorTabs.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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 [];
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
74
src/vs/workbench/api/browser/mainThreadSecretState.ts
Normal file
74
src/vs/workbench/api/browser/mainThreadSecretState.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
132
src/vs/workbench/api/browser/mainThreadUriOpeners.ts
Normal file
132
src/vs/workbench/api/browser/mainThreadUriOpeners.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user