Merge remote-tracking branch 'upstream/master' into scm-resource-context

This commit is contained in:
mjcrouch
2020-08-04 17:29:01 +01:00
2089 changed files with 93710 additions and 48133 deletions

View File

@@ -41,6 +41,7 @@ import './mainThreadMessageService';
import './mainThreadOutputService';
import './mainThreadProgress';
import './mainThreadQuickOpen';
import './mainThreadRemoteConnectionData';
import './mainThreadSaveParticipant';
import './mainThreadSCM';
import './mainThreadSearch';

View File

@@ -3,71 +3,87 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
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 } from 'vs/workbench/services/authentication/browser/authenticationService';
import { IAuthenticationService, AllowedExtension, readAllowedExtensions, getAuthenticationProviderActivationEvent } 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 } from 'vs/platform/storage/common/storage';
import Severity from 'vs/base/common/severity';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { fromNow } from 'vs/base/common/date';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { Platform, platform } from 'vs/base/common/platform';
interface AllowedExtension {
id: string;
name: string;
const VSO_ALLOWED_EXTENSIONS = ['github.vscode-pull-request-github', 'github.vscode-pull-request-github-insiders', 'vscode.git', 'ms-vsonline.vsonline', 'vscode.github-browser'];
interface IAccountUsage {
extensionId: string;
extensionName: string;
lastUsed: number;
}
const accountUsages = new Map<string, { [accountName: string]: string[] }>();
const VSO_ALLOWED_EXTENSIONS = ['github.vscode-pull-request-github', 'github.vscode-pull-request-github-insiders', 'vscode.git'];
function addAccountUsage(providerId: string, accountName: string, extensionOrFeatureName: string) {
const providerAccountUsage = accountUsages.get(providerId);
if (!providerAccountUsage) {
accountUsages.set(providerId, { [accountName]: [extensionOrFeatureName] });
} else {
if (providerAccountUsage[accountName]) {
if (!providerAccountUsage[accountName].includes(extensionOrFeatureName)) {
providerAccountUsage[accountName].push(extensionOrFeatureName);
}
} else {
providerAccountUsage[accountName] = [extensionOrFeatureName];
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
}
accountUsages.set(providerId, providerAccountUsage);
}
return usages;
}
function readAllowedExtensions(storageService: IStorageService, providerId: string, accountName: string): AllowedExtension[] {
let trustedExtensions: AllowedExtension[] = [];
try {
const trustedExtensionSrc = storageService.get(`${providerId}-${accountName}`, StorageScope.GLOBAL);
if (trustedExtensionSrc) {
trustedExtensions = JSON.parse(trustedExtensionSrc);
}
} catch (err) { }
function removeAccountUsage(storageService: IStorageService, providerId: string, accountName: string): void {
const accountKey = `${providerId}-${accountName}-usages`;
storageService.remove(accountKey, StorageScope.GLOBAL);
}
return trustedExtensions;
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);
}
export class MainThreadAuthenticationProvider extends Disposable {
private _sessionMenuItems = new Map<string, IDisposable[]>();
private _accounts = new Map<string, string[]>(); // Map account name to session ids
private _sessions = new Map<string, string>(); // Map account id to name
constructor(
private readonly _proxy: ExtHostAuthenticationShape,
public readonly id: string,
public readonly displayName: string,
public readonly label: string,
public readonly supportsMultipleAccounts: boolean,
private readonly notificationService: INotificationService,
private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService
private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
private readonly storageService: IStorageService,
private readonly quickInputService: IQuickInputService,
private readonly dialogService: IDialogService
) {
super();
}
@@ -80,13 +96,18 @@ export class MainThreadAuthenticationProvider extends Disposable {
return !!this._sessions.size;
}
private manageTrustedExtensions(quickInputService: IQuickInputService, storageService: IStorageService, accountName: string) {
const quickPick = quickInputService.createQuickPick<{ label: string, extension: AllowedExtension }>();
public manageTrustedExtensions(accountName: string) {
const quickPick = this.quickInputService.createQuickPick<{ label: string, description: string, extension: AllowedExtension }>();
quickPick.canSelectMany = true;
const allowedExtensions = readAllowedExtensions(storageService, this.id, accountName);
const allowedExtensions = readAllowedExtensions(this.storageService, this.id, accountName);
const usages = readAccountUsages(this.storageService, this.id, accountName);
const items = allowedExtensions.map(extension => {
const usage = usages.find(usage => extension.id === usage.extensionId);
return {
label: extension.name,
description: usage
? nls.localize({ key: 'accountLastUsedDate', comment: ['The placeholder {0} is a string with time information, such as "3 days ago"'] }, "Last used this account {0}", fromNow(usage.lastUsed, true))
: nls.localize('notUsed', "Has not used this account"),
extension
};
});
@@ -98,7 +119,7 @@ export class MainThreadAuthenticationProvider extends Disposable {
quickPick.onDidAccept(() => {
const updatedAllowedList = quickPick.selectedItems.map(item => item.extension);
storageService.store(`${this.id}-${accountName}`, JSON.stringify(updatedAllowedList), StorageScope.GLOBAL);
this.storageService.store(`${this.id}-${accountName}`, JSON.stringify(updatedAllowedList), StorageScope.GLOBAL);
quickPick.dispose();
});
@@ -110,128 +131,44 @@ export class MainThreadAuthenticationProvider extends Disposable {
quickPick.show();
}
private showUsage(quickInputService: IQuickInputService, accountName: string) {
const quickPick = quickInputService.createQuickPick();
const providerUsage = accountUsages.get(this.id);
const accountUsage = (providerUsage || {})[accountName] || [];
quickPick.items = accountUsage.map(extensionOrFeature => {
return {
label: extensionOrFeature
};
});
quickPick.onDidHide(() => {
quickPick.dispose();
});
quickPick.show();
}
private async registerCommandsAndContextMenuItems(): Promise<void> {
const sessions = await this._proxy.$getSessions(this.id);
sessions.forEach(session => this.registerSession(session));
}
private registerSession(session: modes.AuthenticationSession) {
this._sessions.set(session.id, session.account.displayName);
this._sessions.set(session.id, session.account.label);
const existingSessionsForAccount = this._accounts.get(session.account.displayName);
const existingSessionsForAccount = this._accounts.get(session.account.label);
if (existingSessionsForAccount) {
this._accounts.set(session.account.displayName, existingSessionsForAccount.concat(session.id));
this._accounts.set(session.account.label, existingSessionsForAccount.concat(session.id));
return;
} else {
this._accounts.set(session.account.displayName, [session.id]);
this._accounts.set(session.account.label, [session.id]);
}
const menuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, {
group: '1_accounts',
command: {
id: `configureSessions${session.id}`,
title: `${session.account.displayName} (${this.displayName})`
},
order: 3
});
this.storageKeysSyncRegistryService.registerStorageKey({ key: `${this.id}-${session.account.displayName}`, version: 1 });
const manageCommand = CommandsRegistry.registerCommand({
id: `configureSessions${session.id}`,
handler: (accessor, args) => {
const quickInputService = accessor.get(IQuickInputService);
const storageService = accessor.get(IStorageService);
const dialogService = accessor.get(IDialogService);
const quickPick = quickInputService.createQuickPick();
const showUsage = nls.localize('showUsage', "Show Extensions and Features Using This Account");
const manage = nls.localize('manageTrustedExtensions', "Manage Trusted Extensions");
const signOut = nls.localize('signOut', "Sign Out");
const items = ([{ label: showUsage }, { label: manage }, { label: signOut }]);
quickPick.items = items;
quickPick.onDidAccept(e => {
const selected = quickPick.selectedItems[0];
if (selected.label === signOut) {
this.signOut(dialogService, session);
}
if (selected.label === manage) {
this.manageTrustedExtensions(quickInputService, storageService, session.account.displayName);
}
if (selected.label === showUsage) {
this.showUsage(quickInputService, session.account.displayName);
}
quickPick.dispose();
});
quickPick.onDidHide(_ => {
quickPick.dispose();
});
quickPick.show();
},
});
this._sessionMenuItems.set(session.account.displayName, [menuItem, manageCommand]);
this.storageKeysSyncRegistryService.registerStorageKey({ key: `${this.id}-${session.account.label}`, version: 1 });
}
async signOut(dialogService: IDialogService, session: modes.AuthenticationSession): Promise<void> {
const providerUsage = accountUsages.get(this.id);
const accountUsage = (providerUsage || {})[session.account.displayName] || [];
const sessionsForAccount = this._accounts.get(session.account.displayName);
async signOut(accountName: string): Promise<void> {
const accountUsages = readAccountUsages(this.storageService, this.id, accountName);
const sessionsForAccount = this._accounts.get(accountName);
// Skip dialog if nothing is using the account
if (!accountUsage.length) {
accountUsages.set(this.id, { [session.account.displayName]: [] });
sessionsForAccount?.forEach(sessionId => this.logout(sessionId));
return;
}
const result = await dialogService.confirm({
title: nls.localize('signOutConfirm', "Sign out of {0}", session.account.displayName),
message: nls.localize('signOutMessage', "The account {0} is currently used by: \n\n{1}\n\n Sign out of these features?", session.account.displayName, accountUsage.join('\n'))
const result = await this.dialogService.confirm({
title: nls.localize('signOutConfirm', "Sign out of {0}", accountName),
message: accountUsages.length
? nls.localize('signOutMessagve', "The account {0} has been used by: \n\n{1}\n\n Sign out of these features?", accountName, accountUsages.map(usage => usage.extensionName).join('\n'))
: nls.localize('signOutMessageSimple', "Sign out of {0}?", accountName)
});
if (result.confirmed) {
accountUsages.set(this.id, { [session.account.displayName]: [] });
sessionsForAccount?.forEach(sessionId => this.logout(sessionId));
removeAccountUsage(this.storageService, this.id, accountName);
}
}
async getSessions(): Promise<ReadonlyArray<modes.AuthenticationSession>> {
return (await this._proxy.$getSessions(this.id)).map(session => {
return {
id: session.id,
account: session.account,
getAccessToken: () => {
addAccountUsage(this.id, session.account.displayName, nls.localize('sync', "Preferences Sync"));
return this._proxy.$getSessionAccessToken(this.id, session.id);
}
};
});
return this._proxy.$getSessions(this.id);
}
async updateSessionItems(event: modes.AuthenticationSessionsChangeEvent): Promise<void> {
@@ -248,11 +185,6 @@ export class MainThreadAuthenticationProvider extends Disposable {
sessionsForAccount.splice(sessionIndex);
if (!sessionsForAccount.length) {
const disposeables = this._sessionMenuItems.get(accountName);
if (disposeables) {
disposeables.forEach(disposeable => disposeable.dispose());
this._sessionMenuItems.delete(accountName);
}
this._accounts.delete(accountName);
}
}
@@ -262,25 +194,13 @@ export class MainThreadAuthenticationProvider extends Disposable {
}
login(scopes: string[]): Promise<modes.AuthenticationSession> {
return this._proxy.$login(this.id, scopes).then(session => {
return {
id: session.id,
account: session.account,
getAccessToken: () => this._proxy.$getSessionAccessToken(this.id, session.id)
};
});
return this._proxy.$login(this.id, scopes);
}
async logout(sessionId: string): Promise<void> {
await this._proxy.$logout(this.id, sessionId);
this.notificationService.info(nls.localize('signedOut', "Successfully signed out."));
}
dispose(): void {
super.dispose();
this._sessionMenuItems.forEach(item => item.forEach(d => d.dispose()));
this._sessionMenuItems.clear();
}
}
@extHostNamedCustomer(MainContext.MainThreadAuthentication)
@@ -294,14 +214,32 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
@IStorageService private readonly storageService: IStorageService,
@INotificationService private readonly notificationService: INotificationService,
@IStorageKeysSyncRegistryService private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IExtensionService private readonly extensionService: IExtensionService
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostAuthentication);
this._register(this.authenticationService.onDidChangeSessions(e => {
this._proxy.$onDidChangeAuthenticationSessions(e.providerId, e.label, e.event);
}));
this._register(this.authenticationService.onDidRegisterAuthenticationProvider(info => {
this._proxy.$onDidChangeAuthenticationProviders([info], []);
}));
this._register(this.authenticationService.onDidUnregisterAuthenticationProvider(info => {
this._proxy.$onDidChangeAuthenticationProviders([], [info]);
}));
}
async $registerAuthenticationProvider(id: string, displayName: string): Promise<void> {
const provider = new MainThreadAuthenticationProvider(this._proxy, id, displayName, this.notificationService, this.storageKeysSyncRegistryService);
$getProviderIds(): Promise<string[]> {
return Promise.resolve(this.authenticationService.getProviderIds());
}
async $registerAuthenticationProvider(id: string, label: string, supportsMultipleAccounts: boolean): Promise<void> {
const provider = new MainThreadAuthenticationProvider(this._proxy, id, label, supportsMultipleAccounts, this.notificationService, this.storageKeysSyncRegistryService, this.storageService, this.quickInputService, this.dialogService);
await provider.initialize();
this.authenticationService.registerAuthenticationProvider(id, provider);
}
@@ -310,21 +248,157 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
this.authenticationService.unregisterAuthenticationProvider(id);
}
$onDidChangeSessions(id: string, event: modes.AuthenticationSessionsChangeEvent): void {
$ensureProvider(id: string): Promise<void> {
return this.extensionService.activateByEvent(getAuthenticationProviderActivationEvent(id));
}
$sendDidChangeSessions(id: string, event: modes.AuthenticationSessionsChangeEvent): void {
this.authenticationService.sessionsUpdate(id, event);
}
async $getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean> {
addAccountUsage(providerId, accountName, extensionName);
$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);
}
this.storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.GLOBAL);
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> {
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();
if (remoteConnection && remoteConnection.remoteAuthority && remoteConnection.remoteAuthority.startsWith('vsonline') && VSO_ALLOWED_EXTENSIONS.includes(extensionId)) {
const isVSO = remoteConnection !== null
? remoteConnection.remoteAuthority.startsWith('vsonline')
: platform === Platform.Web;
if (isVSO && VSO_ALLOWED_EXTENSIONS.includes(extensionId)) {
addAccountUsage(this.storageService, providerId, accountName, extensionId, extensionName);
return true;
}
@@ -339,6 +413,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
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);
}
@@ -359,11 +434,13 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
return choice === 0;
}
async $setTrustedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise<void> {
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 });
this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL);
}
this.storageService.store(`${extensionName}-${providerId}`, sessionId, StorageScope.GLOBAL);
}
}

View File

@@ -94,8 +94,7 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape {
}, {
allowScripts: options.enableScripts,
localResourceRoots: options.localResourceRoots ? options.localResourceRoots.map(uri => URI.revive(uri)) : undefined
});
webview.extension = { id: extensionId, location: URI.revive(extensionLocation) };
}, { id: extensionId, location: URI.revive(extensionLocation) });
const webviewZone = new EditorWebviewZone(editor, line, height, webview);
@@ -128,12 +127,15 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape {
$setOptions(handle: number, options: modes.IWebviewOptions): void {
const inset = this.getInset(handle);
inset.webview.contentOptions = options;
inset.webview.contentOptions = {
...options,
localResourceRoots: options.localResourceRoots?.map(components => URI.from(components)),
};
}
async $postMessage(handle: number, value: any): Promise<boolean> {
const inset = this.getInset(handle);
inset.webview.sendMessage(value);
inset.webview.postMessage(value);
return true;
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { ICommandService, CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ExtHostContext, MainThreadCommandsShape, ExtHostCommandsShape, MainContext, IExtHostContext } from '../common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { revive } from 'vs/base/common/marshalling';
@@ -28,7 +28,7 @@ export class MainThreadCommands implements MainThreadCommandsShape {
}
dispose() {
this._commandRegistrations.forEach(value => value.dispose());
dispose(this._commandRegistrations.values());
this._commandRegistrations.clear();
this._generateCommandsDocumentationRegistration.dispose();

View File

@@ -6,7 +6,6 @@
import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { keys } from 'vs/base/common/map';
import { URI, UriComponents } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { IRange } from 'vs/editor/common/core/range';
@@ -21,6 +20,7 @@ import { COMMENTS_VIEW_ID, COMMENTS_VIEW_TITLE } from 'vs/workbench/contrib/comm
import { ViewContainer, IViewContainersRegistry, Extensions as ViewExtensions, ViewContainerLocation, IViewsRegistry, IViewsService, IViewDescriptorService } from 'vs/workbench/common/views';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { Codicon } from 'vs/base/common/codicons';
export class MainThreadCommentThread implements modes.CommentThread {
@@ -283,7 +283,7 @@ export class MainThreadCommentController {
async getDocumentComments(resource: URI, token: CancellationToken) {
let ret: modes.CommentThread[] = [];
for (let thread of keys(this._threads)) {
for (let thread of [...this._threads.keys()]) {
const commentThread = this._threads.get(thread)!;
if (commentThread.resource === resource.toString()) {
ret.push(commentThread);
@@ -314,7 +314,7 @@ export class MainThreadCommentController {
getAllComments(): MainThreadCommentThread[] {
let ret: MainThreadCommentThread[] = [];
for (let thread of keys(this._threads)) {
for (let thread of [...this._threads.keys()]) {
ret.push(this._threads.get(thread)!);
}
@@ -458,6 +458,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [COMMENTS_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]),
storageId: COMMENTS_VIEW_TITLE,
hideIfEmpty: true,
icon: Codicon.commentDiscussion.classNames,
order: 10,
}, ViewContainerLocation.Panel);
@@ -467,6 +468,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments
canToggleVisibility: false,
ctorDescriptor: new SyncDescriptor(CommentsPanel),
canMoveView: true,
containerIcon: Codicon.commentDiscussion.classNames,
focusCommand: {
id: 'workbench.action.focusCommentsPanel'
}
@@ -483,7 +485,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments
if (!commentsPanelAlreadyConstructed && !this._openViewListener) {
this._openViewListener = this._viewsService.onDidChangeViewVisibility(e => {
if (e.id === COMMENTS_VIEW_ID && e.visible) {
keys(this._commentControllers).forEach(handle => {
[...this._commentControllers.keys()].forEach(handle => {
let threads = this._commentControllers.get(handle)!.getAllComments();
if (threads.length) {

View File

@@ -228,10 +228,13 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
public $startDebugging(folder: UriComponents | undefined, nameOrConfig: string | IDebugConfiguration, options: IStartDebuggingOptions): Promise<boolean> {
const folderUri = folder ? uri.revive(folder) : undefined;
const launch = this.debugService.getConfigurationManager().getLaunch(folderUri);
const parentSession = this.getSession(options.parentSessionID);
const debugOptions: IDebugSessionOptions = {
noDebug: false,
parentSession: this.getSession(options.parentSessionID),
repl: options.repl
noDebug: options.noDebug,
parentSession,
repl: options.repl,
compact: options.compact,
compoundRoot: parentSession?.compoundRoot
};
return this.debugService.startDebugging(launch, nameOrConfig, debugOptions).then(success => {
return success;
@@ -261,6 +264,18 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
return Promise.reject(new Error('debug session not found'));
}
public $stopDebugging(sessionId: DebugSessionUUID | undefined): Promise<void> {
if (sessionId) {
const session = this.debugService.getModel().getSession(sessionId, true);
if (session) {
return this.debugService.stopSession(session);
}
} else { // stop all
return this.debugService.stopSession(undefined);
}
return Promise.reject(new Error('debug session not found'));
}
public $appendDebugConsole(value: string): void {
// Use warning as severity to get the orange color for messages coming from the debug extension
const session = this.debugService.getViewModel().focusedSession;

View File

@@ -9,33 +9,33 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ExtHostContext, MainContext, IExtHostContext, MainThreadDecorationsShape, ExtHostDecorationsShape, DecorationData, DecorationRequest } from '../common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { IDecorationsService, IDecorationData } from 'vs/workbench/services/decorations/browser/decorations';
import { values } from 'vs/base/common/collections';
import { CancellationToken } from 'vs/base/common/cancellation';
class DecorationRequestsQueue {
private _idPool = 0;
private _requests: { [id: number]: DecorationRequest } = Object.create(null);
private _resolver: { [id: number]: (data: DecorationData) => any } = Object.create(null);
private _requests = new Map<number, DecorationRequest>();
private _resolver = new Map<number, (data: DecorationData) => any>();
private _timer: any;
constructor(
private readonly _proxy: ExtHostDecorationsShape
private readonly _proxy: ExtHostDecorationsShape,
private readonly _handle: number
) {
//
}
enqueue(handle: number, uri: URI, token: CancellationToken): Promise<DecorationData> {
enqueue(uri: URI, token: CancellationToken): Promise<DecorationData> {
const id = ++this._idPool;
const result = new Promise<DecorationData>(resolve => {
this._requests[id] = { id, handle, uri };
this._resolver[id] = resolve;
this._requests.set(id, { id, uri });
this._resolver.set(id, resolve);
this._processQueue();
});
token.onCancellationRequested(() => {
delete this._requests[id];
delete this._resolver[id];
this._requests.delete(id);
this._resolver.delete(id);
});
return result;
}
@@ -49,15 +49,15 @@ class DecorationRequestsQueue {
// make request
const requests = this._requests;
const resolver = this._resolver;
this._proxy.$provideDecorations(values(requests), CancellationToken.None).then(data => {
for (const id in resolver) {
resolver[id](data[id]);
this._proxy.$provideDecorations(this._handle, [...requests.values()], CancellationToken.None).then(data => {
for (let [id, resolve] of resolver) {
resolve(data[id]);
}
});
// reset
this._requests = [];
this._resolver = [];
this._requests = new Map();
this._resolver = new Map();
this._timer = undefined;
}, 0);
}
@@ -68,14 +68,12 @@ export class MainThreadDecorations implements MainThreadDecorationsShape {
private readonly _provider = new Map<number, [Emitter<URI[]>, IDisposable]>();
private readonly _proxy: ExtHostDecorationsShape;
private readonly _requestQueue: DecorationRequestsQueue;
constructor(
context: IExtHostContext,
@IDecorationsService private readonly _decorationsService: IDecorationsService
) {
this._proxy = context.getProxy(ExtHostContext.ExtHostDecorations);
this._requestQueue = new DecorationRequestsQueue(this._proxy);
}
dispose() {
@@ -85,23 +83,23 @@ export class MainThreadDecorations implements MainThreadDecorationsShape {
$registerDecorationProvider(handle: number, label: string): void {
const emitter = new Emitter<URI[]>();
const queue = new DecorationRequestsQueue(this._proxy, handle);
const registration = this._decorationsService.registerDecorationsProvider({
label,
onDidChange: emitter.event,
provideDecorations: (uri, token) => {
return this._requestQueue.enqueue(handle, uri, token).then(data => {
if (!data) {
return undefined;
}
const [weight, bubble, tooltip, letter, themeColor] = data;
return <IDecorationData>{
weight: weight || 0,
bubble: bubble || false,
color: themeColor && themeColor.id,
tooltip,
letter
};
});
provideDecorations: async (uri, token) => {
const data = await queue.enqueue(uri, token);
if (!data) {
return undefined;
}
const [weight, bubble, tooltip, letter, themeColor] = data;
return <IDecorationData>{
weight: weight ?? 0,
bubble: bubble ?? false,
color: themeColor?.id,
tooltip,
letter
};
}
});
this._provider.set(handle, [emitter, registration]);

View File

@@ -8,6 +8,7 @@ import { URI, UriComponents } from 'vs/base/common/uri';
import { MainThreadDiagnosticsShape, MainContext, IExtHostContext, ExtHostDiagnosticsShape, ExtHostContext } from '../common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
@extHostNamedCustomer(MainContext.MainThreadDiagnostics)
export class MainThreadDiagnostics implements MainThreadDiagnosticsShape {
@@ -15,15 +16,15 @@ export class MainThreadDiagnostics implements MainThreadDiagnosticsShape {
private readonly _activeOwners = new Set<string>();
private readonly _proxy: ExtHostDiagnosticsShape;
private readonly _markerService: IMarkerService;
private readonly _markerListener: IDisposable;
constructor(
extHostContext: IExtHostContext,
@IMarkerService markerService: IMarkerService
@IMarkerService private readonly _markerService: IMarkerService,
@IUriIdentityService private readonly _uriIdentService: IUriIdentityService,
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDiagnostics);
this._markerService = markerService;
this._markerListener = this._markerService.onMarkerChanged(this._forwardMarkers, this);
}
@@ -59,7 +60,7 @@ export class MainThreadDiagnostics implements MainThreadDiagnosticsShape {
}
}
}
this._markerService.changeOne(owner, URI.revive(uri), markers);
this._markerService.changeOne(owner, this._uriIdentService.asCanonicalUri(URI.revive(uri)), markers);
}
this._activeOwners.add(owner);
}

View File

@@ -23,37 +23,37 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape {
//
}
$showOpenDialog(options: MainThreadDialogOpenOptions): Promise<URI[] | undefined> {
$showOpenDialog(options?: MainThreadDialogOpenOptions): Promise<URI[] | undefined> {
return Promise.resolve(this._fileDialogService.showOpenDialog(MainThreadDialogs._convertOpenOptions(options)));
}
$showSaveDialog(options: MainThreadDialogSaveOptions): Promise<URI | undefined> {
$showSaveDialog(options?: MainThreadDialogSaveOptions): Promise<URI | undefined> {
return Promise.resolve(this._fileDialogService.showSaveDialog(MainThreadDialogs._convertSaveOptions(options)));
}
private static _convertOpenOptions(options: MainThreadDialogOpenOptions): IOpenDialogOptions {
private static _convertOpenOptions(options?: MainThreadDialogOpenOptions): IOpenDialogOptions {
const result: IOpenDialogOptions = {
openLabel: options.openLabel || undefined,
canSelectFiles: options.canSelectFiles || (!options.canSelectFiles && !options.canSelectFolders),
canSelectFolders: options.canSelectFolders,
canSelectMany: options.canSelectMany,
defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined,
title: options.title || undefined
openLabel: options?.openLabel || undefined,
canSelectFiles: options?.canSelectFiles || (!options?.canSelectFiles && !options?.canSelectFolders),
canSelectFolders: options?.canSelectFolders,
canSelectMany: options?.canSelectMany,
defaultUri: options?.defaultUri ? URI.revive(options.defaultUri) : undefined,
title: options?.title || undefined
};
if (options.filters) {
if (options?.filters) {
result.filters = [];
forEach(options.filters, entry => result.filters!.push({ name: entry.key, extensions: entry.value }));
}
return result;
}
private static _convertSaveOptions(options: MainThreadDialogSaveOptions): ISaveDialogOptions {
private static _convertSaveOptions(options?: MainThreadDialogSaveOptions): ISaveDialogOptions {
const result: ISaveDialogOptions = {
defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined,
saveLabel: options.saveLabel || undefined,
title: options.title || undefined
defaultUri: options?.defaultUri ? URI.revive(options.defaultUri) : undefined,
saveLabel: options?.saveLabel || undefined,
title: options?.title || undefined
};
if (options.filters) {
if (options?.filters) {
result.filters = [];
forEach(options.filters, entry => result.filters!.push({ name: entry.key, extensions: entry.value }));
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { onUnexpectedError } from 'vs/base/common/errors';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Range } from 'vs/editor/common/core/range';
@@ -35,8 +35,8 @@ export class MainThreadDocumentContentProviders implements MainThreadDocumentCon
}
dispose(): void {
this._resourceContentProvider.forEach(p => p.dispose());
this._pendingUpdate.forEach(source => source.dispose());
dispose(this._resourceContentProvider.values());
dispose(this._pendingUpdate.values());
}
$registerTextContentProvider(handle: number, scheme: string): void {

View File

@@ -4,28 +4,32 @@
*--------------------------------------------------------------------------------------------*/
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { IDisposable, IReference, dispose, DisposableStore } from 'vs/base/common/lifecycle';
import { IReference, dispose, Disposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ITextModel } from 'vs/editor/common/model';
import { IModelService, shouldSynchronizeModel } from 'vs/editor/common/services/modelService';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { IFileService } from 'vs/platform/files/common/files';
import { IFileService, FileOperation } from 'vs/platform/files/common/files';
import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/browser/mainThreadDocumentsAndEditors';
import { ExtHostContext, ExtHostDocumentsShape, IExtHostContext, MainThreadDocumentsShape } from 'vs/workbench/api/common/extHost.protocol';
import { ITextEditorModel } from 'vs/workbench/common/editor';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { toLocalResource } from 'vs/base/common/resources';
import { toLocalResource, extUri, IExtUri } from 'vs/base/common/resources';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { Emitter } from 'vs/base/common/event';
export class BoundModelReferenceCollection {
private _data = new Array<{ length: number, dispose(): void }>();
private _data = new Array<{ uri: URI, length: number, dispose(): void }>();
private _length = 0;
constructor(
private readonly _extUri: IExtUri,
private readonly _maxAge: number = 1000 * 60 * 3,
private readonly _maxLength: number = 1024 * 1024 * 80
private readonly _maxLength: number = 1024 * 1024 * 80,
) {
//
}
@@ -34,10 +38,18 @@ export class BoundModelReferenceCollection {
this._data = dispose(this._data);
}
add(ref: IReference<ITextEditorModel>): void {
remove(uri: URI): void {
for (const entry of [...this._data] /* copy array because dispose will modify it */) {
if (this._extUri.isEqualOrParent(entry.uri, uri)) {
entry.dispose();
}
}
}
add(uri: URI, ref: IReference<ITextEditorModel>): void {
const length = ref.object.textEditorModel.getValueLength();
let handle: any;
let entry: { length: number, dispose(): void };
let entry: { uri: URI, length: number, dispose(): void };
const dispose = () => {
const idx = this._data.indexOf(entry);
if (idx >= 0) {
@@ -48,7 +60,7 @@ export class BoundModelReferenceCollection {
}
};
handle = setTimeout(dispose, this._maxAge);
entry = { length, dispose };
entry = { uri, length, dispose };
this._data.push(entry);
this._length += length;
@@ -62,19 +74,48 @@ export class BoundModelReferenceCollection {
}
}
export class MainThreadDocuments implements MainThreadDocumentsShape {
class ModelTracker extends Disposable {
private _knownVersionId: number;
constructor(
private readonly _model: ITextModel,
private readonly _onIsCaughtUpWithContentChanges: Emitter<URI>,
private readonly _proxy: ExtHostDocumentsShape,
private readonly _textFileService: ITextFileService,
) {
super();
this._knownVersionId = this._model.getVersionId();
this._register(this._model.onDidChangeContent((e) => {
this._knownVersionId = e.versionId;
this._proxy.$acceptModelChanged(this._model.uri, e, this._textFileService.isDirty(this._model.uri));
if (this.isCaughtUpWithContentChanges()) {
this._onIsCaughtUpWithContentChanges.fire(this._model.uri);
}
}));
}
public isCaughtUpWithContentChanges(): boolean {
return (this._model.getVersionId() === this._knownVersionId);
}
}
export class MainThreadDocuments extends Disposable implements MainThreadDocumentsShape {
private _onIsCaughtUpWithContentChanges = this._register(new Emitter<URI>());
public readonly onIsCaughtUpWithContentChanges = this._onIsCaughtUpWithContentChanges.event;
private readonly _modelService: IModelService;
private readonly _textModelResolverService: ITextModelService;
private readonly _textFileService: ITextFileService;
private readonly _fileService: IFileService;
private readonly _environmentService: IWorkbenchEnvironmentService;
private readonly _uriIdentityService: IUriIdentityService;
private readonly _toDispose = new DisposableStore();
private _modelToDisposeMap: { [modelUrl: string]: IDisposable; };
private _modelTrackers: { [modelUrl: string]: ModelTracker; };
private readonly _proxy: ExtHostDocumentsShape;
private readonly _modelIsSynced = new Set<string>();
private _modelReferenceCollection = new BoundModelReferenceCollection();
private readonly _modelReferenceCollection: BoundModelReferenceCollection;
constructor(
documentsAndEditors: MainThreadDocumentsAndEditors,
@@ -83,41 +124,64 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
@ITextFileService textFileService: ITextFileService,
@IFileService fileService: IFileService,
@ITextModelService textModelResolverService: ITextModelService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IUriIdentityService uriIdentityService: IUriIdentityService,
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService
) {
super();
this._modelService = modelService;
this._textModelResolverService = textModelResolverService;
this._textFileService = textFileService;
this._fileService = fileService;
this._environmentService = environmentService;
this._uriIdentityService = uriIdentityService;
this._modelReferenceCollection = this._register(new BoundModelReferenceCollection(uriIdentityService.extUri));
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocuments);
this._toDispose.add(documentsAndEditors.onDocumentAdd(models => models.forEach(this._onModelAdded, this)));
this._toDispose.add(documentsAndEditors.onDocumentRemove(urls => urls.forEach(this._onModelRemoved, this)));
this._toDispose.add(this._modelReferenceCollection);
this._toDispose.add(modelService.onModelModeChanged(this._onModelModeChanged, this));
this._register(documentsAndEditors.onDocumentAdd(models => models.forEach(this._onModelAdded, this)));
this._register(documentsAndEditors.onDocumentRemove(urls => urls.forEach(this._onModelRemoved, this)));
this._register(modelService.onModelModeChanged(this._onModelModeChanged, this));
this._toDispose.add(textFileService.files.onDidSave(e => {
this._register(textFileService.files.onDidSave(e => {
if (this._shouldHandleFileEvent(e.model.resource)) {
this._proxy.$acceptModelSaved(e.model.resource);
}
}));
this._toDispose.add(textFileService.files.onDidChangeDirty(m => {
this._register(textFileService.files.onDidChangeDirty(m => {
if (this._shouldHandleFileEvent(m.resource)) {
this._proxy.$acceptDirtyStateChanged(m.resource, m.isDirty());
}
}));
this._modelToDisposeMap = Object.create(null);
this._register(workingCopyFileService.onDidRunWorkingCopyFileOperation(e => {
if (e.operation === FileOperation.MOVE || e.operation === FileOperation.DELETE) {
for (const { source } of e.files) {
if (source) {
this._modelReferenceCollection.remove(source);
}
}
}
}));
this._modelTrackers = Object.create(null);
}
public dispose(): void {
Object.keys(this._modelToDisposeMap).forEach((modelUrl) => {
this._modelToDisposeMap[modelUrl].dispose();
Object.keys(this._modelTrackers).forEach((modelUrl) => {
this._modelTrackers[modelUrl].dispose();
});
this._modelToDisposeMap = Object.create(null);
this._toDispose.dispose();
this._modelTrackers = Object.create(null);
super.dispose();
}
public isCaughtUpWithContentChanges(resource: URI): boolean {
const modelUrl = resource.toString();
if (this._modelTrackers[modelUrl]) {
return this._modelTrackers[modelUrl].isCaughtUpWithContentChanges();
}
return true;
}
private _shouldHandleFileEvent(resource: URI): boolean {
@@ -133,9 +197,7 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
}
const modelUrl = model.uri;
this._modelIsSynced.add(modelUrl.toString());
this._modelToDisposeMap[modelUrl.toString()] = model.onDidChangeContent((e) => {
this._proxy.$acceptModelChanged(modelUrl, e, this._textFileService.isDirty(modelUrl));
});
this._modelTrackers[modelUrl.toString()] = new ModelTracker(model, this._onIsCaughtUpWithContentChanges, this._proxy, this._textFileService);
}
private _onModelModeChanged(event: { model: ITextModel; oldModeId: string; }): void {
@@ -153,8 +215,8 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
return;
}
this._modelIsSynced.delete(strModelUrl);
this._modelToDisposeMap[strModelUrl].dispose();
delete this._modelToDisposeMap[strModelUrl];
this._modelTrackers[strModelUrl].dispose();
delete this._modelTrackers[strModelUrl];
}
// --- from extension host process
@@ -163,33 +225,37 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
return this._textFileService.save(URI.revive(uri)).then(target => !!target);
}
$tryOpenDocument(_uri: UriComponents): Promise<any> {
const uri = URI.revive(_uri);
if (!uri.scheme || !(uri.fsPath || uri.authority)) {
$tryOpenDocument(uriData: UriComponents): Promise<URI> {
const inputUri = URI.revive(uriData);
if (!inputUri.scheme || !(inputUri.fsPath || inputUri.authority)) {
return Promise.reject(new Error(`Invalid uri. Scheme and authority or path must be set.`));
}
let promise: Promise<boolean>;
switch (uri.scheme) {
const canonicalUri = this._uriIdentityService.asCanonicalUri(inputUri);
let promise: Promise<URI>;
switch (canonicalUri.scheme) {
case Schemas.untitled:
promise = this._handleUntitledScheme(uri);
promise = this._handleUntitledScheme(canonicalUri);
break;
case Schemas.file:
default:
promise = this._handleAsResourceInput(uri);
promise = this._handleAsResourceInput(canonicalUri);
break;
}
return promise.then(success => {
if (!success) {
return Promise.reject(new Error('cannot open ' + uri.toString()));
} else if (!this._modelIsSynced.has(uri.toString())) {
return Promise.reject(new Error('cannot open ' + uri.toString() + '. Detail: Files above 50MB cannot be synchronized with extensions.'));
return promise.then(documentUri => {
if (!documentUri) {
return Promise.reject(new Error(`cannot open ${canonicalUri.toString()}`));
} else if (!extUri.isEqual(documentUri, canonicalUri)) {
return Promise.reject(new Error(`cannot open ${canonicalUri.toString()}. Detail: Actual document opened as ${documentUri.toString()}`));
} else if (!this._modelIsSynced.has(canonicalUri.toString())) {
return Promise.reject(new Error(`cannot open ${canonicalUri.toString()}. Detail: Files above 50MB cannot be synchronized with extensions.`));
} else {
return undefined;
return canonicalUri;
}
}, err => {
return Promise.reject(new Error('cannot open ' + uri.toString() + '. Detail: ' + toErrorMessage(err)));
return Promise.reject(new Error(`cannot open ${canonicalUri.toString()}. Detail: ${toErrorMessage(err)}`));
});
}
@@ -197,21 +263,20 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
return this._doCreateUntitled(undefined, options ? options.language : undefined, options ? options.content : undefined);
}
private _handleAsResourceInput(uri: URI): Promise<boolean> {
private _handleAsResourceInput(uri: URI): Promise<URI> {
return this._textModelResolverService.createModelReference(uri).then(ref => {
this._modelReferenceCollection.add(ref);
const result = !!ref.object;
return result;
this._modelReferenceCollection.add(uri, ref);
return ref.object.textEditorModel.uri;
});
}
private _handleUntitledScheme(uri: URI): Promise<boolean> {
private _handleUntitledScheme(uri: URI): Promise<URI> {
const asLocalUri = toLocalResource(uri, this._environmentService.configuration.remoteAuthority);
return this._fileService.resolve(asLocalUri).then(stats => {
// don't create a new file ontop of an existing file
return Promise.reject(new Error('file already exists'));
}, err => {
return this._doCreateUntitled(Boolean(uri.path) ? uri : undefined).then(resource => !!resource);
return this._doCreateUntitled(Boolean(uri.path) ? uri : undefined);
});
}

View File

@@ -27,38 +27,41 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
namespace delta {
export function ofSets<T>(before: Set<T>, after: Set<T>): { removed: T[], added: T[] } {
const removed: T[] = [];
const added: T[] = [];
before.forEach(element => {
for (let element of before) {
if (!after.has(element)) {
removed.push(element);
}
});
after.forEach(element => {
}
for (let element of after) {
if (!before.has(element)) {
added.push(element);
}
});
}
return { removed, added };
}
export function ofMaps<K, V>(before: Map<K, V>, after: Map<K, V>): { removed: V[], added: V[] } {
const removed: V[] = [];
const added: V[] = [];
before.forEach((value, index) => {
for (let [index, value] of before) {
if (!after.has(index)) {
removed.push(value);
}
});
after.forEach((value, index) => {
}
for (let [index, value] of after) {
if (!before.has(index)) {
added.push(value);
}
});
}
return { removed, added };
}
}
@@ -263,11 +266,11 @@ class MainThreadDocumentAndEditorStateComputer {
}
if (candidate) {
editors.forEach(snapshot => {
for (const snapshot of editors.values()) {
if (candidate === snapshot.editor) {
activeEditor = snapshot.id;
}
});
}
}
}
@@ -303,6 +306,7 @@ export class MainThreadDocumentsAndEditors {
private readonly _toDispose = new DisposableStore();
private readonly _proxy: ExtHostDocumentsAndEditorsShape;
private readonly _mainThreadDocuments: MainThreadDocuments;
private readonly _textEditors = new Map<string, MainThreadTextEditor>();
private readonly _onTextEditorAdd = new Emitter<MainThreadTextEditor[]>();
@@ -326,12 +330,15 @@ export class MainThreadDocumentsAndEditors {
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
@IBulkEditService bulkEditService: IBulkEditService,
@IPanelService panelService: IPanelService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService,
@IUriIdentityService uriIdentityService: IUriIdentityService,
@IClipboardService private readonly _clipboardService: IClipboardService,
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentsAndEditors);
const mainThreadDocuments = this._toDispose.add(new MainThreadDocuments(this, extHostContext, this._modelService, this._textFileService, fileService, textModelResolverService, environmentService));
extHostContext.set(MainContext.MainThreadDocuments, mainThreadDocuments);
this._mainThreadDocuments = this._toDispose.add(new MainThreadDocuments(this, extHostContext, this._modelService, this._textFileService, fileService, textModelResolverService, environmentService, uriIdentityService, workingCopyFileService));
extHostContext.set(MainContext.MainThreadDocuments, this._mainThreadDocuments);
const mainThreadTextEditors = this._toDispose.add(new MainThreadTextEditors(this, extHostContext, codeEditorService, bulkEditService, this._editorService, this._editorGroupService));
extHostContext.set(MainContext.MainThreadTextEditors, mainThreadTextEditors);
@@ -361,7 +368,7 @@ export class MainThreadDocumentsAndEditors {
// added editors
for (const apiEditor of delta.addedEditors) {
const mainThreadEditor = new MainThreadTextEditor(apiEditor.id, apiEditor.editor.getModel(),
apiEditor.editor, { onGainedFocus() { }, onLostFocus() { } }, this._modelService);
apiEditor.editor, { onGainedFocus() { }, onLostFocus() { } }, this._mainThreadDocuments, this._modelService, this._clipboardService);
this._textEditors.set(apiEditor.id, mainThreadEditor);
addedEditors.push(mainThreadEditor);

View File

@@ -17,6 +17,10 @@ import { IApplyEditsOptions, IEditorPropertiesChangeData, IResolvedTextEditorCon
import { IEditorPane } from 'vs/workbench/common/editor';
import { withNullAsUndefined } from 'vs/base/common/types';
import { equals } from 'vs/base/common/arrays';
import { CodeEditorStateFlag, EditorState } from 'vs/editor/browser/core/editorState';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { SnippetParser } from 'vs/editor/contrib/snippet/snippetParser';
import { MainThreadDocuments } from 'vs/workbench/api/browser/mainThreadDocuments';
export interface IFocusTracker {
onGainedFocus(): void;
@@ -157,8 +161,10 @@ export class MainThreadTextEditorProperties {
export class MainThreadTextEditor {
private readonly _id: string;
private _model: ITextModel;
private readonly _model: ITextModel;
private readonly _mainThreadDocuments: MainThreadDocuments;
private readonly _modelService: IModelService;
private readonly _clipboardService: IClipboardService;
private readonly _modelListeners = new DisposableStore();
private _codeEditor: ICodeEditor | null;
private readonly _focusTracker: IFocusTracker;
@@ -172,14 +178,18 @@ export class MainThreadTextEditor {
model: ITextModel,
codeEditor: ICodeEditor,
focusTracker: IFocusTracker,
modelService: IModelService
mainThreadDocuments: MainThreadDocuments,
modelService: IModelService,
clipboardService: IClipboardService,
) {
this._id = id;
this._model = model;
this._codeEditor = null;
this._properties = null;
this._focusTracker = focusTracker;
this._mainThreadDocuments = mainThreadDocuments;
this._modelService = modelService;
this._clipboardService = clipboardService;
this._onPropertiesChanged = new Emitter<IEditorPropertiesChangeData>();
@@ -192,7 +202,6 @@ export class MainThreadTextEditor {
}
public dispose(): void {
this._model = null!;
this._modelListeners.dispose();
this._codeEditor = null;
this._codeEditorListeners.dispose();
@@ -251,21 +260,66 @@ export class MainThreadTextEditor {
this._focusTracker.onLostFocus();
}));
let nextSelectionChangeSource: string | null = null;
this._codeEditorListeners.add(this._mainThreadDocuments.onIsCaughtUpWithContentChanges((uri) => {
if (uri.toString() === this._model.uri.toString()) {
const selectionChangeSource = nextSelectionChangeSource;
nextSelectionChangeSource = null;
this._updatePropertiesNow(selectionChangeSource);
}
}));
const isValidCodeEditor = () => {
// Due to event timings, it is possible that there is a model change event not yet delivered to us.
// > e.g. a model change event is emitted to a listener which then decides to update editor options
// > In this case the editor configuration change event reaches us first.
// So simply check that the model is still attached to this code editor
return (this._codeEditor && this._codeEditor.getModel() === this._model);
};
const updateProperties = (selectionChangeSource: string | null) => {
// Some editor events get delivered faster than model content changes. This is
// problematic, as this leads to editor properties reaching the extension host
// too soon, before the model content change that was the root cause.
//
// If this case is identified, then let's update editor properties on the next model
// content change instead.
if (this._mainThreadDocuments.isCaughtUpWithContentChanges(this._model.uri)) {
nextSelectionChangeSource = null;
this._updatePropertiesNow(selectionChangeSource);
} else {
// update editor properties on the next model content change
nextSelectionChangeSource = selectionChangeSource;
}
};
this._codeEditorListeners.add(this._codeEditor.onDidChangeCursorSelection((e) => {
// selection
this._updatePropertiesNow(e.source);
if (!isValidCodeEditor()) {
return;
}
updateProperties(e.source);
}));
this._codeEditorListeners.add(this._codeEditor.onDidChangeConfiguration(() => {
this._codeEditorListeners.add(this._codeEditor.onDidChangeConfiguration((e) => {
// options
this._updatePropertiesNow(null);
if (!isValidCodeEditor()) {
return;
}
updateProperties(null);
}));
this._codeEditorListeners.add(this._codeEditor.onDidLayoutChange(() => {
// visibleRanges
this._updatePropertiesNow(null);
if (!isValidCodeEditor()) {
return;
}
updateProperties(null);
}));
this._codeEditorListeners.add(this._codeEditor.onDidScrollChange(() => {
// visibleRanges
this._updatePropertiesNow(null);
if (!isValidCodeEditor()) {
return;
}
updateProperties(null);
}));
this._updatePropertiesNow(null);
}
@@ -454,12 +508,23 @@ export class MainThreadTextEditor {
return true;
}
insertSnippet(template: string, ranges: readonly IRange[], opts: IUndoStopOptions) {
async insertSnippet(template: string, ranges: readonly IRange[], opts: IUndoStopOptions) {
if (!this._codeEditor) {
if (!this._codeEditor || !this._codeEditor.hasModel()) {
return false;
}
// check if clipboard is required and only iff read it (async)
let clipboardText: string | undefined;
const needsTemplate = SnippetParser.guessNeedsClipboard(template);
if (needsTemplate) {
const state = new EditorState(this._codeEditor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);
clipboardText = await this._clipboardService.readText();
if (!state.validate(this._codeEditor)) {
return false;
}
}
const snippetController = SnippetController2.get(this._codeEditor);
// // cancel previous snippet mode
@@ -471,7 +536,11 @@ export class MainThreadTextEditor {
this._codeEditor.focus();
// make modifications
snippetController.insert(template, { overwriteBefore: 0, overwriteAfter: 0, undoStopBefore: opts.undoStopBefore, undoStopAfter: opts.undoStopAfter });
snippetController.insert(template, {
overwriteBefore: 0, overwriteAfter: 0,
undoStopBefore: opts.undoStopBefore, undoStopAfter: opts.undoStopAfter,
clipboardText
});
return true;
}

View File

@@ -24,9 +24,11 @@ import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, IExtHostContex
import { EditorViewColumn, editorGroupToViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { openEditorWith } from 'vs/workbench/contrib/files/common/openWith';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { openEditorWith } from 'vs/workbench/services/editor/common/editorOpenWith';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
export class MainThreadTextEditors implements MainThreadTextEditorsShape {
@@ -125,7 +127,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
// preserve pre 1.38 behaviour to not make group active when preserveFocus: true
// but make sure to restore the editor to fix https://github.com/microsoft/vscode/issues/79633
activation: options.preserveFocus ? EditorActivation.RESTORE : undefined,
ignoreOverrides: true
override: false
};
const input: IResourceEditorInput = {
@@ -276,7 +278,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
// --- commands
CommandsRegistry.registerCommand('_workbench.open', function (accessor: ServicesAccessor, args: [URI, IEditorOptions, EditorViewColumn, string?]) {
CommandsRegistry.registerCommand('_workbench.open', async function (accessor: ServicesAccessor, args: [URI, IEditorOptions, EditorViewColumn, string?]) {
const editorService = accessor.get(IEditorService);
const editorGroupService = accessor.get(IEditorGroupsService);
const openerService = accessor.get(IOpenerService);
@@ -285,16 +287,17 @@ CommandsRegistry.registerCommand('_workbench.open', function (accessor: Services
if (options || typeof position === 'number') {
// use editor options or editor view column as a hint to use the editor service for opening
return editorService.openEditor({ resource, options, label }, viewColumnToEditorGroup(editorGroupService, position)).then(_ => undefined);
await editorService.openEditor({ resource, options, label }, viewColumnToEditorGroup(editorGroupService, position));
return;
}
if (resource && resource.scheme === 'command') {
// do not allow to execute commands from here
return Promise.resolve(undefined);
return;
}
// finally, delegate to opener service
return openerService.open(resource).then(_ => undefined);
await openerService.open(resource);
});
CommandsRegistry.registerCommand('_workbench.openWith', (accessor: ServicesAccessor, args: [URI, string, ITextEditorOptions | undefined, EditorViewColumn | undefined]) => {
@@ -306,14 +309,14 @@ CommandsRegistry.registerCommand('_workbench.openWith', (accessor: ServicesAcces
const [resource, id, options, position] = args;
const group = editorGroupsService.getGroup(viewColumnToEditorGroup(editorGroupsService, position)) ?? editorGroupsService.activeGroup;
const textOptions = options ? { ...options, ignoreOverrides: true } : { ignoreOverrides: true };
const textOptions: ITextEditorOptions = options ? { ...options, override: false } : { override: false };
const input = editorService.createEditorInput({ resource });
return openEditorWith(input, id, textOptions, group, editorService, configurationService, quickInputService);
});
CommandsRegistry.registerCommand('_workbench.diff', function (accessor: ServicesAccessor, args: [URI, URI, string, string, IEditorOptions, EditorViewColumn]) {
CommandsRegistry.registerCommand('_workbench.diff', async function (accessor: ServicesAccessor, args: [URI, URI, string, string, IEditorOptions, EditorViewColumn]) {
const editorService = accessor.get(IEditorService);
const editorGroupService = accessor.get(IEditorGroupsService);
@@ -329,5 +332,17 @@ CommandsRegistry.registerCommand('_workbench.diff', function (accessor: Services
label = localize('diffLeftRightLabel', "{0} ⟷ {1}", leftResource.toString(true), rightResource.toString(true));
}
return editorService.openEditor({ leftResource, rightResource, label, description, options }, viewColumnToEditorGroup(editorGroupService, position)).then(() => undefined);
await editorService.openEditor({ leftResource, rightResource, label, description, options }, viewColumnToEditorGroup(editorGroupService, position));
});
CommandsRegistry.registerCommand('_workbench.revertAllDirty', async function (accessor: ServicesAccessor) {
const environmentService = accessor.get(IEnvironmentService);
if (!environmentService.extensionTestsLocationURI) {
throw new Error('Command is only available when running extension tests.');
}
const workingCopyService = accessor.get(IWorkingCopyService);
for (const workingCopy of workingCopyService.dirtyWorkingCopies) {
await workingCopy.revert({ soft: true });
}
});

View File

@@ -128,7 +128,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha
}
}
$onExtensionHostExit(code: number): void {
async $onExtensionHostExit(code: number): Promise<void> {
this._extensionService._onExtensionHostExit(code);
}
}

View File

@@ -25,7 +25,7 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape {
}
dispose(): void {
this._fileProvider.forEach(value => value.dispose());
dispose(this._fileProvider.values());
this._fileProvider.clear();
}

View File

@@ -4,16 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import { DisposableStore } from 'vs/base/common/lifecycle';
import { FileChangeType, IFileService, FileOperation } from 'vs/platform/files/common/files';
import { FileChangeType, IFileService } from 'vs/platform/files/common/files';
import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ExtHostContext, FileSystemEvents, IExtHostContext } from '../common/extHost.protocol';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { localize } from 'vs/nls';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
@extHostCustomer
@@ -24,10 +20,6 @@ export class MainThreadFileSystemEventService {
constructor(
extHostContext: IExtHostContext,
@IFileService fileService: IFileService,
@ITextFileService textFileService: ITextFileService,
@IProgressService progressService: IProgressService,
@IConfigurationService configService: IConfigurationService,
@ILogService logService: ILogService,
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService
) {
@@ -63,14 +55,13 @@ export class MainThreadFileSystemEventService {
// BEFORE file operation
workingCopyFileService.addFileOperationParticipant({
participate: (target, source, operation, progress, timeout, token) => {
return proxy.$onWillRunFileOperation(operation, target, source, timeout, token);
participate: (files, operation, progress, timeout, token) => {
return proxy.$onWillRunFileOperation(operation, files, timeout, token);
}
});
// AFTER file operation
this._listener.add(textFileService.onDidCreateTextFile(e => proxy.$onDidRunFileOperation(FileOperation.CREATE, e.resource, undefined)));
this._listener.add(workingCopyFileService.onDidRunWorkingCopyFileOperation(e => proxy.$onDidRunFileOperation(e.operation, e.target, e.source)));
this._listener.add(workingCopyFileService.onDidRunWorkingCopyFileOperation(e => proxy.$onDidRunFileOperation(e.operation, e.files)));
}
dispose(): void {

View File

@@ -8,6 +8,9 @@ import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { MainThreadLanguagesShape, MainContext, IExtHostContext } from '../common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { IPosition } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { StandardTokenType } from 'vs/editor/common/modes';
@extHostNamedCustomer(MainContext.MainThreadLanguages)
export class MainThreadLanguages implements MainThreadLanguagesShape {
@@ -40,4 +43,19 @@ export class MainThreadLanguages implements MainThreadLanguagesShape {
this._modelService.setMode(model, this._modeService.create(languageId));
return Promise.resolve(undefined);
}
async $tokensAtPosition(resource: UriComponents, position: IPosition): Promise<undefined | { type: StandardTokenType, range: IRange }> {
const uri = URI.revive(resource);
const model = this._modelService.getModel(uri);
if (!model) {
return undefined;
}
model.tokenizeIfCheap(position.lineNumber);
const tokens = model.getLineTokens(position.lineNumber);
const idx = tokens.findTokenIndexAtOffset(position.column - 1);
return {
type: tokens.getStandardTokenType(idx),
range: new Range(position.lineNumber, 1 + tokens.getStartOffset(idx), position.lineNumber, 1 + tokens.getEndOffset(idx))
};
}
}

View File

@@ -3,18 +3,24 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as DOM from 'vs/base/browser/dom';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IExtHostContext, ExtHostNotebookShape, ExtHostContext } from '../common/extHost.protocol';
import { Disposable } from 'vs/base/common/lifecycle';
import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IExtHostContext, ExtHostNotebookShape, ExtHostContext, INotebookDocumentsAndEditorsDelta } from '../common/extHost.protocol';
import { Disposable, IDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService';
import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, INotebookKernelInfo, INotebookKernelInfoDto, IEditor, INotebookRendererInfo, IOutputRenderRequest, IOutputRenderResponse, INotebookDocumentFilter } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IRelativePattern } from 'vs/base/common/glob';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { Emitter } from 'vs/base/common/event';
export class MainThreadNotebookDocument extends Disposable {
private _textModel: NotebookTextModel;
@@ -27,37 +33,148 @@ export class MainThreadNotebookDocument extends Disposable {
private readonly _proxy: ExtHostNotebookShape,
public handle: number,
public viewType: string,
public uri: URI
public supportBackup: boolean,
public uri: URI,
@INotebookService readonly notebookService: INotebookService,
@IUndoRedoService readonly undoRedoService: IUndoRedoService,
@ITextModelService modelService: ITextModelService
) {
super();
this._textModel = new NotebookTextModel(handle, viewType, uri);
this._register(this._textModel.onDidModelChange(e => {
this._textModel = new NotebookTextModel(handle, viewType, supportBackup, uri, undoRedoService, modelService);
this._register(this._textModel.onDidModelChangeProxy(e => {
this._proxy.$acceptModelChanged(this.uri, e);
this._proxy.$acceptEditorPropertiesChanged(uri, { selections: { selections: this._textModel.selections }, metadata: null });
}));
this._register(this._textModel.onDidSelectionChange(e => {
const selectionsChange = e ? { selections: e } : null;
this._proxy.$acceptEditorPropertiesChanged(uri, { selections: selectionsChange });
this._proxy.$acceptEditorPropertiesChanged(uri, { selections: selectionsChange, metadata: null });
}));
}
applyEdit(modelVersionId: number, edits: ICellEditOperation[]): boolean {
return this._textModel.applyEdit(modelVersionId, edits);
}
updateRenderers(renderers: number[]) {
this._textModel.updateRenderers(renderers);
}
dispose() {
this._textModel.dispose();
// this._textModel.dispose();
super.dispose();
}
}
class DocumentAndEditorState {
static ofSets<T>(before: Set<T>, after: Set<T>): { removed: T[], added: T[] } {
const removed: T[] = [];
const added: T[] = [];
before.forEach(element => {
if (!after.has(element)) {
removed.push(element);
}
});
after.forEach(element => {
if (!before.has(element)) {
added.push(element);
}
});
return { removed, added };
}
static ofMaps<K, V>(before: Map<K, V>, after: Map<K, V>): { removed: V[], added: V[] } {
const removed: V[] = [];
const added: V[] = [];
before.forEach((value, index) => {
if (!after.has(index)) {
removed.push(value);
}
});
after.forEach((value, index) => {
if (!before.has(index)) {
added.push(value);
}
});
return { removed, added };
}
static compute(before: DocumentAndEditorState | undefined, after: DocumentAndEditorState): INotebookDocumentsAndEditorsDelta {
if (!before) {
const apiEditors = [];
for (let id in after.textEditors) {
const editor = after.textEditors.get(id)!;
apiEditors.push({ id, documentUri: editor.uri!, selections: editor!.textModel!.selections });
}
return {
addedDocuments: [],
addedEditors: apiEditors,
visibleEditors: [...after.visibleEditors].map(editor => editor[0])
};
}
const documentDelta = DocumentAndEditorState.ofSets(before.documents, after.documents);
const editorDelta = DocumentAndEditorState.ofMaps(before.textEditors, after.textEditors);
const addedAPIEditors = editorDelta.added.map(add => ({
id: add.getId(),
documentUri: add.uri!,
selections: add.textModel!.selections || []
}));
const removedAPIEditors = editorDelta.removed.map(removed => removed.getId());
// const oldActiveEditor = before.activeEditor !== after.activeEditor ? before.activeEditor : undefined;
const newActiveEditor = before.activeEditor !== after.activeEditor ? after.activeEditor : undefined;
const visibleEditorDelta = DocumentAndEditorState.ofMaps(before.visibleEditors, after.visibleEditors);
return {
addedDocuments: documentDelta.added.map(e => {
return {
viewType: e.viewType,
handle: e.handle,
uri: e.uri,
metadata: e.metadata,
versionId: e.versionId,
cells: e.cells.map(cell => ({
handle: cell.handle,
uri: cell.uri,
source: cell.textBuffer.getLinesContent(),
eol: cell.textBuffer.getEOL(),
language: cell.language,
cellKind: cell.cellKind,
outputs: cell.outputs,
metadata: cell.metadata
})),
// attachedEditor: editorId ? {
// id: editorId,
// selections: document.textModel.selections
// } : undefined
};
}),
removedDocuments: documentDelta.removed.map(e => e.uri),
addedEditors: addedAPIEditors,
removedEditors: removedAPIEditors,
newActiveEditor: newActiveEditor,
visibleEditors: visibleEditorDelta.added.length === 0 && visibleEditorDelta.removed.length === 0
? undefined
: [...after.visibleEditors].map(editor => editor[0])
};
}
constructor(
readonly documents: Set<NotebookTextModel>,
readonly textEditors: Map<string, IEditor>,
readonly activeEditor: string | null | undefined,
readonly visibleEditors: Map<string, IEditor>
) {
//
}
}
@extHostNamedCustomer(MainContext.MainThreadNotebook)
export class MainThreadNotebooks extends Disposable implements MainThreadNotebookShape {
private readonly _notebookProviders = new Map<string, MainThreadNotebookController>();
private readonly _notebookProviders = new Map<string, IMainNotebookController>();
private readonly _notebookKernels = new Map<string, MainThreadNotebookKernel>();
private readonly _notebookKernelProviders = new Map<number, { extension: NotebookExtensionDescription, emitter: Emitter<void>, provider: IDisposable }>();
private readonly _notebookRenderers = new Map<string, MainThreadNotebookRenderer>();
private readonly _proxy: ExtHostNotebookShape;
private _toDisposeOnEditorRemove = new Map<string, IDisposable>();
private _currentState?: DocumentAndEditorState;
private _editorEventListenersMapping: Map<string, DisposableStore> = new Map();
constructor(
extHostContext: IExtHostContext,
@@ -73,20 +190,123 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
}
async $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise<boolean> {
let controller = this._notebookProviders.get(viewType);
if (controller) {
return controller.tryApplyEdits(resource, modelVersionId, edits, renderers);
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
if (textModel) {
await this._notebookService.transformEditsOutputs(textModel, edits);
return textModel.$applyEdit(modelVersionId, edits, true);
}
return false;
}
async removeNotebookTextModel(uri: URI): Promise<void> {
// TODO@rebornix, remove cell should use emitDelta as well to ensure document/editor events are sent together
await this._proxy.$acceptDocumentAndEditorsDelta({ removedDocuments: [uri] });
let textModelDisposableStore = this._editorEventListenersMapping.get(uri.toString());
textModelDisposableStore?.dispose();
this._editorEventListenersMapping.delete(URI.from(uri).toString());
}
private _isDeltaEmpty(delta: INotebookDocumentsAndEditorsDelta) {
if (delta.addedDocuments !== undefined && delta.addedDocuments.length > 0) {
return false;
}
if (delta.removedDocuments !== undefined && delta.removedDocuments.length > 0) {
return false;
}
if (delta.addedEditors !== undefined && delta.addedEditors.length > 0) {
return false;
}
if (delta.removedEditors !== undefined && delta.removedEditors.length > 0) {
return false;
}
if (delta.visibleEditors !== undefined && delta.visibleEditors.length > 0) {
return false;
}
if (delta.newActiveEditor !== undefined) {
return false;
}
return true;
}
private _emitDelta(delta: INotebookDocumentsAndEditorsDelta) {
if (this._isDeltaEmpty(delta)) {
return;
}
return this._proxy.$acceptDocumentAndEditorsDelta(delta);
}
registerListeners() {
this._notebookService.listNotebookEditors().forEach((e) => {
this._addNotebookEditor(e);
});
this._register(this._notebookService.onDidChangeActiveEditor(e => {
this._proxy.$acceptDocumentAndEditorsDelta({
newActiveEditor: e.uri
this._updateState();
}));
this._register(this._notebookService.onDidChangeVisibleEditors(e => {
if (this._notebookProviders.size > 0) {
if (!this._currentState) {
// no current state means we didn't even create editors in ext host yet.
return;
}
// we can't simply update visibleEditors as we need to check if we should create editors first.
this._updateState();
}
}));
this._register(this._notebookService.onNotebookEditorAdd(editor => {
this._addNotebookEditor(editor);
}));
this._register(this._notebookService.onNotebookEditorsRemove(editors => {
this._removeNotebookEditor(editors);
}));
this._register(this._notebookService.onNotebookDocumentAdd((documents) => {
documents.forEach(doc => {
if (!this._editorEventListenersMapping.has(doc.toString())) {
const disposableStore = new DisposableStore();
const textModel = this._notebookService.getNotebookTextModel(doc);
disposableStore.add(textModel!.onDidModelChangeProxy(e => {
this._proxy.$acceptModelChanged(textModel!.uri, e);
this._proxy.$acceptEditorPropertiesChanged(doc, { selections: { selections: textModel!.selections }, metadata: null });
}));
disposableStore.add(textModel!.onDidSelectionChange(e => {
const selectionsChange = e ? { selections: e } : null;
this._proxy.$acceptEditorPropertiesChanged(doc, { selections: selectionsChange, metadata: null });
}));
this._editorEventListenersMapping.set(textModel!.uri.toString(), disposableStore);
}
});
this._updateState();
}));
this._register(this._notebookService.onNotebookDocumentRemove((documents) => {
documents.forEach(doc => {
this._editorEventListenersMapping.get(doc.toString())?.dispose();
this._editorEventListenersMapping.delete(doc.toString());
});
this._updateState();
}));
this._register(this._notebookService.onDidChangeNotebookActiveKernel(e => {
this._proxy.$acceptNotebookActiveKernelChange(e);
}));
this._register(this._notebookService.onNotebookDocumentSaved(e => {
this._proxy.$acceptModelSaved(e);
}));
const updateOrder = () => {
@@ -108,210 +328,353 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
this._register(this.accessibilityService.onDidChangeScreenReaderOptimized(() => {
updateOrder();
}));
const activeEditorPane = this.editorService.activeEditorPane as any | undefined;
const notebookEditor = activeEditorPane?.isNotebookEditor ? activeEditorPane.getControl() : undefined;
this._updateState(notebookEditor);
}
async $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise<void> {
this._notebookService.registerNotebookRenderer(handle, extension, type, selectors, preloads.map(uri => URI.revive(uri)));
private _addNotebookEditor(e: IEditor) {
this._toDisposeOnEditorRemove.set(e.getId(), combinedDisposable(
e.onDidChangeModel(() => this._updateState()),
e.onDidFocusEditorWidget(() => {
this._updateState(e);
}),
));
const activeEditorPane = this.editorService.activeEditorPane as any | undefined;
const notebookEditor = activeEditorPane?.isNotebookEditor ? activeEditorPane.getControl() : undefined;
this._updateState(notebookEditor);
}
async $unregisterNotebookRenderer(handle: number): Promise<void> {
this._notebookService.unregisterNotebookRenderer(handle);
private _removeNotebookEditor(editors: IEditor[]) {
editors.forEach(e => {
const sub = this._toDisposeOnEditorRemove.get(e.getId());
if (sub) {
this._toDisposeOnEditorRemove.delete(e.getId());
sub.dispose();
}
});
this._updateState();
}
async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise<void> {
let controller = new MainThreadNotebookController(this._proxy, this, viewType);
this._notebookProviders.set(viewType, controller);
this._notebookService.registerNotebookController(viewType, extension, controller);
private async _updateState(focusedNotebookEditor?: IEditor) {
let activeEditor: string | null = null;
const activeEditorPane = this.editorService.activeEditorPane as any | undefined;
if (activeEditorPane?.isNotebookEditor) {
const notebookEditor = (activeEditorPane.getControl() as INotebookEditor);
activeEditor = notebookEditor && notebookEditor.hasModel() ? notebookEditor!.getId() : null;
}
const documentEditorsMap = new Map<string, IEditor>();
const editors = new Map<string, IEditor>();
this._notebookService.listNotebookEditors().forEach(editor => {
if (editor.hasModel()) {
editors.set(editor.getId(), editor);
documentEditorsMap.set(editor.textModel!.uri.toString(), editor);
}
});
const visibleEditorsMap = new Map<string, IEditor>();
this.editorService.visibleEditorPanes.forEach(editor => {
if ((editor as any).isNotebookEditor) {
const nbEditorWidget = (editor as any).getControl() as INotebookEditor;
if (nbEditorWidget && editors.has(nbEditorWidget.getId())) {
visibleEditorsMap.set(nbEditorWidget.getId(), nbEditorWidget);
}
}
});
const documents = new Set<NotebookTextModel>();
this._notebookService.listNotebookDocuments().forEach(document => {
documents.add(document);
});
if (!activeEditor && focusedNotebookEditor && focusedNotebookEditor.hasModel()) {
activeEditor = focusedNotebookEditor.getId();
}
// editors always have view model attached, which means there is already a document in exthost.
const newState = new DocumentAndEditorState(documents, editors, activeEditor, visibleEditorsMap);
const delta = DocumentAndEditorState.compute(this._currentState, newState);
// const isEmptyChange = (!delta.addedDocuments || delta.addedDocuments.length === 0)
// && (!delta.removedDocuments || delta.removedDocuments.length === 0)
// && (!delta.addedEditors || delta.addedEditors.length === 0)
// && (!delta.removedEditors || delta.removedEditors.length === 0)
// && (delta.newActiveEditor === undefined)
// if (!isEmptyChange) {
this._currentState = newState;
await this._emitDelta(delta);
// }
}
async $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: UriComponents[]): Promise<void> {
const renderer = new MainThreadNotebookRenderer(this._proxy, type, extension.id, URI.revive(extension.location), selectors, preloads.map(uri => URI.revive(uri)));
this._notebookRenderers.set(type, renderer);
this._notebookService.registerNotebookRenderer(type, renderer);
}
async $unregisterNotebookRenderer(id: string): Promise<void> {
this._notebookService.unregisterNotebookRenderer(id);
}
async $registerNotebookProvider(_extension: NotebookExtensionDescription, _viewType: string, _supportBackup: boolean, _kernel: INotebookKernelInfoDto | undefined): Promise<void> {
const controller: IMainNotebookController = {
kernel: _kernel,
supportBackup: _supportBackup,
reloadNotebook: async (mainthreadTextModel: NotebookTextModel) => {
const data = await this._proxy.$resolveNotebookData(_viewType, mainthreadTextModel.uri);
if (!data) {
return;
}
mainthreadTextModel.languages = data.languages;
mainthreadTextModel.metadata = data.metadata;
const edits: ICellEditOperation[] = [
{ editType: CellEditType.Delete, count: mainthreadTextModel.cells.length, index: 0 },
{ editType: CellEditType.Insert, index: 0, cells: data.cells }
];
await this._notebookService.transformEditsOutputs(mainthreadTextModel, edits);
await new Promise(resolve => {
DOM.scheduleAtNextAnimationFrame(() => {
const ret = mainthreadTextModel!.$applyEdit(mainthreadTextModel!.versionId, edits, true);
resolve(ret);
});
});
},
createNotebook: async (textModel: NotebookTextModel, backupId?: string) => {
// open notebook document
const data = await this._proxy.$resolveNotebookData(textModel.viewType, textModel.uri, backupId);
if (!data) {
return;
}
textModel.languages = data.languages;
textModel.metadata = data.metadata;
if (data.cells.length) {
textModel.initialize(data!.cells);
} else {
const mainCell = textModel.createCellTextModel([''], textModel.languages.length ? textModel.languages[0] : '', CellKind.Code, [], undefined);
textModel.insertTemplateCell(mainCell);
}
this._proxy.$acceptEditorPropertiesChanged(textModel.uri, { selections: null, metadata: textModel.metadata });
return;
},
resolveNotebookEditor: async (viewType: string, uri: URI, editorId: string) => {
await this._proxy.$resolveNotebookEditor(viewType, uri, editorId);
},
executeNotebookByAttachedKernel: async (viewType: string, uri: URI) => {
return this.executeNotebookByAttachedKernel(viewType, uri);
},
cancelNotebookByAttachedKernel: async (viewType: string, uri: URI) => {
return this.cancelNotebookByAttachedKernel(viewType, uri);
},
onDidReceiveMessage: (editorId: string, rendererType: string | undefined, message: unknown) => {
this._proxy.$onDidReceiveMessage(editorId, rendererType, message);
},
removeNotebookDocument: async (uri: URI) => {
return this.removeNotebookTextModel(uri);
},
executeNotebookCell: async (uri: URI, handle: number) => {
return this._proxy.$executeNotebookByAttachedKernel(_viewType, uri, handle);
},
cancelNotebookCell: async (uri: URI, handle: number) => {
return this._proxy.$cancelNotebookByAttachedKernel(_viewType, uri, handle);
},
save: async (uri: URI, token: CancellationToken) => {
return this._proxy.$saveNotebook(_viewType, uri, token);
},
saveAs: async (uri: URI, target: URI, token: CancellationToken) => {
return this._proxy.$saveNotebookAs(_viewType, uri, target, token);
},
backup: async (uri: URI, token: CancellationToken) => {
return this._proxy.$backup(_viewType, uri, token);
}
};
this._notebookProviders.set(_viewType, controller);
this._notebookService.registerNotebookController(_viewType, _extension, controller);
return;
}
async $onNotebookChange(viewType: string, uri: UriComponents): Promise<void> {
const textModel = this._notebookService.getNotebookTextModel(URI.from(uri));
textModel?.handleUnknownChange();
}
async $unregisterNotebookProvider(viewType: string): Promise<void> {
this._notebookProviders.delete(viewType);
this._notebookService.unregisterNotebookProvider(viewType);
return;
}
async $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise<void> {
let controller = this._notebookProviders.get(viewType);
async $registerNotebookKernel(extension: NotebookExtensionDescription, id: string, label: string, selectors: (string | IRelativePattern)[], preloads: UriComponents[]): Promise<void> {
const kernel = new MainThreadNotebookKernel(this._proxy, id, label, selectors, extension.id, URI.revive(extension.location), preloads.map(preload => URI.revive(preload)));
this._notebookKernels.set(id, kernel);
this._notebookService.registerNotebookKernel(kernel);
return;
}
if (controller) {
controller.updateLanguages(resource, languages);
async $unregisterNotebookKernel(id: string): Promise<void> {
this._notebookKernels.delete(id);
this._notebookService.unregisterNotebookKernel(id);
return;
}
async $registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise<void> {
const emitter = new Emitter<void>();
const that = this;
const provider = this._notebookService.registerNotebookKernelProvider({
providerExtensionId: extension.id.value,
providerDescription: extension.description,
onDidChangeKernels: emitter.event,
selector: documentFilter,
provideKernels: async (uri: URI, token: CancellationToken) => {
const kernels = await that._proxy.$provideNotebookKernels(handle, uri, token);
return kernels.map(kernel => {
return {
...kernel,
providerHandle: handle
};
});
},
resolveKernel: (editorId: string, uri: URI, kernelId: string, token: CancellationToken) => {
return that._proxy.$resolveNotebookKernel(handle, editorId, uri, kernelId, token);
},
executeNotebook: (uri: URI, kernelId: string, cellHandle: number | undefined) => {
return that._proxy.$executeNotebookKernelFromProvider(handle, uri, kernelId, cellHandle);
},
cancelNotebook: (uri: URI, kernelId: string, cellHandle: number | undefined) => {
return that._proxy.$cancelNotebookKernelFromProvider(handle, uri, kernelId, cellHandle);
},
});
this._notebookKernelProviders.set(handle, {
extension,
emitter,
provider
});
return;
}
async $unregisterNotebookKernelProvider(handle: number): Promise<void> {
const entry = this._notebookKernelProviders.get(handle);
if (entry) {
entry.emitter.dispose();
entry.provider.dispose();
this._notebookKernelProviders.delete(handle);
}
}
$onNotebookKernelChange(handle: number): void {
const entry = this._notebookKernelProviders.get(handle);
entry?.emitter.fire();
}
async $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise<void> {
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
textModel?.updateLanguages(languages);
}
async $updateNotebookMetadata(viewType: string, resource: UriComponents, metadata: NotebookDocumentMetadata): Promise<void> {
let controller = this._notebookProviders.get(viewType);
if (controller) {
controller.updateNotebookMetadata(resource, metadata);
}
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
textModel?.updateNotebookMetadata(metadata);
}
async $updateNotebookCellMetadata(viewType: string, resource: UriComponents, handle: number, metadata: NotebookCellMetadata): Promise<void> {
let controller = this._notebookProviders.get(viewType);
if (controller) {
controller.updateNotebookCellMetadata(resource, handle, metadata);
}
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
textModel?.updateNotebookCellMetadata(handle, metadata);
}
async $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise<void> {
let controller = this._notebookProviders.get(viewType);
controller?.spliceNotebookCellOutputs(resource, cellHandle, splices, renderers);
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
if (textModel) {
await this._notebookService.transformSpliceOutputs(textModel, splices);
textModel.$spliceNotebookCellOutputs(cellHandle, splices);
}
}
async executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise<void> {
return this._proxy.$executeNotebook(viewType, uri, undefined, token);
async executeNotebookByAttachedKernel(viewType: string, uri: URI): Promise<void> {
return this._proxy.$executeNotebookByAttachedKernel(viewType, uri, undefined);
}
async $postMessage(handle: number, value: any): Promise<boolean> {
async cancelNotebookByAttachedKernel(viewType: string, uri: URI): Promise<void> {
return this._proxy.$cancelNotebookByAttachedKernel(viewType, uri, undefined);
}
const activeEditorPane = this.editorService.activeEditorPane as any | undefined;
if (activeEditorPane?.isNotebookEditor) {
const notebookEditor = (activeEditorPane as INotebookEditor);
if (notebookEditor.viewModel?.handle === handle) {
notebookEditor.postMessage(value);
return true;
}
async $postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise<boolean> {
const editor = this._notebookService.getNotebookEditor(editorId) as INotebookEditor | undefined;
if (editor?.isNotebookEditor) {
editor.postMessage(forRendererId, value);
return true;
}
return false;
}
$onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void {
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
if (textModel) {
textModel.$handleEdit(label, () => {
return this._proxy.$undoNotebook(textModel.viewType, textModel.uri, editId, textModel.isDirty);
}, () => {
return this._proxy.$redoNotebook(textModel.viewType, textModel.uri, editId, textModel.isDirty);
});
}
}
$onContentChange(resource: UriComponents, viewType: string): void {
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
textModel?.handleUnknownChange();
}
}
export class MainThreadNotebookController implements IMainNotebookController {
private _mapping: Map<string, MainThreadNotebookDocument> = new Map();
static documentHandle: number = 0;
export class MainThreadNotebookKernel implements INotebookKernelInfo {
constructor(
private readonly _proxy: ExtHostNotebookShape,
private _mainThreadNotebook: MainThreadNotebooks,
private _viewType: string
readonly id: string,
readonly label: string,
readonly selectors: (string | IRelativePattern)[],
readonly extension: ExtensionIdentifier,
readonly extensionLocation: URI,
readonly preloads: URI[]
) {
}
async createNotebook(viewType: string, uri: URI, forBackup: boolean, forceReload: boolean): Promise<NotebookTextModel | undefined> {
let mainthreadNotebook = this._mapping.get(URI.from(uri).toString());
if (mainthreadNotebook) {
if (forceReload) {
const data = await this._proxy.$resolveNotebookData(viewType, uri);
if (!data) {
return;
}
mainthreadNotebook.textModel.languages = data.languages;
mainthreadNotebook.textModel.metadata = data.metadata;
mainthreadNotebook.textModel.applyEdit(mainthreadNotebook.textModel.versionId, [
{ editType: CellEditType.Delete, count: mainthreadNotebook.textModel.cells.length, index: 0 },
{ editType: CellEditType.Insert, index: 0, cells: data.cells }
]);
}
return mainthreadNotebook.textModel;
}
let document = new MainThreadNotebookDocument(this._proxy, MainThreadNotebookController.documentHandle++, viewType, uri);
await this.createNotebookDocument(document);
if (forBackup) {
return document.textModel;
}
// open notebook document
const data = await this._proxy.$resolveNotebookData(viewType, uri);
if (!data) {
return;
}
document.textModel.languages = data.languages;
document.textModel.metadata = data.metadata;
document.textModel.initialize(data!.cells);
return document.textModel;
}
async tryApplyEdits(resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise<boolean> {
let mainthreadNotebook = this._mapping.get(URI.from(resource).toString());
if (mainthreadNotebook) {
mainthreadNotebook.updateRenderers(renderers);
return mainthreadNotebook.applyEdit(modelVersionId, edits);
}
return false;
}
spliceNotebookCellOutputs(resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): void {
let mainthreadNotebook = this._mapping.get(URI.from(resource).toString());
mainthreadNotebook?.textModel.updateRenderers(renderers);
mainthreadNotebook?.textModel.$spliceNotebookCellOutputs(cellHandle, splices);
}
async executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise<void> {
this._mainThreadNotebook.executeNotebook(viewType, uri, token);
}
onDidReceiveMessage(uri: UriComponents, message: any): void {
this._proxy.$onDidReceiveMessage(uri, message);
}
async createNotebookDocument(document: MainThreadNotebookDocument): Promise<void> {
this._mapping.set(document.uri.toString(), document);
await this._proxy.$acceptDocumentAndEditorsDelta({
addedDocuments: [{
viewType: document.viewType,
handle: document.handle,
uri: document.uri
}]
});
}
async removeNotebookDocument(notebook: INotebookTextModel): Promise<void> {
let document = this._mapping.get(URI.from(notebook.uri).toString());
if (!document) {
return;
}
await this._proxy.$acceptDocumentAndEditorsDelta({ removedDocuments: [notebook.uri] });
document.dispose();
this._mapping.delete(URI.from(notebook.uri).toString());
}
// Methods for ExtHost
updateLanguages(resource: UriComponents, languages: string[]) {
let document = this._mapping.get(URI.from(resource).toString());
document?.textModel.updateLanguages(languages);
}
updateNotebookMetadata(resource: UriComponents, metadata: NotebookDocumentMetadata) {
let document = this._mapping.get(URI.from(resource).toString());
document?.textModel.updateNotebookMetadata(metadata);
}
updateNotebookCellMetadata(resource: UriComponents, handle: number, metadata: NotebookCellMetadata) {
let document = this._mapping.get(URI.from(resource).toString());
document?.textModel.updateNotebookCellMetadata(handle, metadata);
}
updateNotebookRenderers(resource: UriComponents, renderers: number[]): void {
let document = this._mapping.get(URI.from(resource).toString());
document?.textModel.updateRenderers(renderers);
}
async executeNotebookCell(uri: URI, handle: number, token: CancellationToken): Promise<void> {
return this._proxy.$executeNotebook(this._viewType, uri, handle, token);
}
async save(uri: URI, token: CancellationToken): Promise<boolean> {
return this._proxy.$saveNotebook(this._viewType, uri, token);
}
async saveAs(uri: URI, target: URI, token: CancellationToken): Promise<boolean> {
return this._proxy.$saveNotebookAs(this._viewType, uri, target, token);
async executeNotebook(viewType: string, uri: URI, handle: number | undefined): Promise<void> {
return this._proxy.$executeNotebook2(this.id, viewType, uri, handle);
}
}
export class MainThreadNotebookRenderer implements INotebookRendererInfo {
constructor(
private readonly _proxy: ExtHostNotebookShape,
readonly id: string,
readonly extensionId: ExtensionIdentifier,
readonly extensionLocation: URI,
readonly selectors: INotebookMimeTypeSelector,
readonly preloads: URI[]
) {
}
render(uri: URI, request: IOutputRenderRequest<UriComponents>): Promise<IOutputRenderResponse<UriComponents> | undefined> {
return this._proxy.$renderOutputs(uri, this.id, request);
}
render2<T>(uri: URI, request: IOutputRenderRequest<T>): Promise<IOutputRenderResponse<T> | undefined> {
return this._proxy.$renderOutputs2(uri, this.id, request);
}
}

View File

@@ -0,0 +1,35 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ExtHostContext, IExtHostContext, ExtHostExtensionServiceShape } from '../common/extHost.protocol';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@extHostCustomer
export class MainThreadRemoteConnectionData extends Disposable {
private readonly _proxy: ExtHostExtensionServiceShape;
constructor(
extHostContext: IExtHostContext,
@IWorkbenchEnvironmentService protected readonly _environmentService: IWorkbenchEnvironmentService,
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostExtensionService);
const remoteAuthority = this._environmentService.configuration.remoteAuthority;
if (remoteAuthority) {
this._register(remoteAuthorityResolverService.onDidChangeConnectionData(() => {
const connectionData = remoteAuthorityResolverService.getConnectionData(remoteAuthority);
if (connectionData) {
this._proxy.$updateRemoteConnectionData(connectionData);
}
}));
}
}
}

View File

@@ -6,7 +6,7 @@
import { URI, UriComponents } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { assign } from 'vs/base/common/objects';
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IDisposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle';
import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations, IInputValidation } from 'vs/workbench/contrib/scm/common/scm';
import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResourceSplices, SCMGroupFeatures, MainContext, IExtHostContext } from '../common/extHost.protocol';
import { Command } from 'vs/editor/common/modes';
@@ -266,7 +266,7 @@ export class MainThreadSCM implements MainThreadSCMShape {
private readonly _proxy: ExtHostSCMShape;
private _repositories = new Map<number, ISCMRepository>();
private _inputDisposables = new Map<number, IDisposable>();
private _repositoryDisposables = new Map<number, IDisposable>();
private readonly _disposables = new DisposableStore();
constructor(
@@ -274,17 +274,14 @@ export class MainThreadSCM implements MainThreadSCMShape {
@ISCMService private readonly scmService: ISCMService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostSCM);
Event.debounce(scmService.onDidChangeSelectedRepositories, (_, e) => e, 100)
(this.onDidChangeSelectedRepositories, this, this._disposables);
}
dispose(): void {
this._repositories.forEach(r => r.dispose());
this._repositories.clear();
this._inputDisposables.forEach(d => d.dispose());
this._inputDisposables.clear();
this._repositoryDisposables.forEach(d => d.dispose());
this._repositoryDisposables.clear();
this._disposables.dispose();
}
@@ -294,8 +291,16 @@ export class MainThreadSCM implements MainThreadSCMShape {
const repository = this.scmService.registerSCMProvider(provider);
this._repositories.set(handle, repository);
const inputDisposable = repository.input.onDidChange(value => this._proxy.$onInputBoxValueChange(handle, value));
this._inputDisposables.set(handle, inputDisposable);
const disposable = combinedDisposable(
Event.filter(repository.onDidChangeSelection, selected => selected)(_ => this._proxy.$setSelectedSourceControl(handle)),
repository.input.onDidChange(value => this._proxy.$onInputBoxValueChange(handle, value))
);
if (repository.selected) {
setTimeout(() => this._proxy.$setSelectedSourceControl(handle), 0);
}
this._repositoryDisposables.set(handle, disposable);
}
$updateSourceControl(handle: number, features: SCMProviderFeatures): void {
@@ -316,8 +321,8 @@ export class MainThreadSCM implements MainThreadSCMShape {
return;
}
this._inputDisposables.get(handle)!.dispose();
this._inputDisposables.delete(handle);
this._repositoryDisposables.get(handle)!.dispose();
this._repositoryDisposables.delete(handle);
repository.dispose();
this._repositories.delete(handle);
@@ -424,12 +429,4 @@ export class MainThreadSCM implements MainThreadSCMShape {
repository.input.validateInput = async () => undefined;
}
}
private onDidChangeSelectedRepositories(repositories: ISCMRepository[]): void {
const handles = repositories
.filter(r => r.provider instanceof MainThreadSCMProvider)
.map(r => (r.provider as MainThreadSCMProvider).handle);
this._proxy.$setSelectedSourceControls(handles);
}
}

View File

@@ -5,7 +5,6 @@
import { CancellationToken } from 'vs/base/common/cancellation';
import { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { values } from 'vs/base/common/map';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
@@ -139,7 +138,7 @@ class RemoteSearchProvider implements ISearchResultProvider, IDisposable {
return Promise.resolve(searchP).then((result: ISearchCompleteStats) => {
this._searches.delete(search.id);
return { results: values(search.matches), stats: result.stats, limitHit: result.limitHit };
return { results: Array.from(search.matches.values()), stats: result.stats, limitHit: result.limitHit };
}, err => {
this._searches.delete(search.id);
return Promise.reject(err);

View File

@@ -9,11 +9,13 @@ import { ThemeColor } from 'vs/platform/theme/common/themeService';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { dispose } from 'vs/base/common/lifecycle';
import { Command } from 'vs/editor/common/modes';
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
@extHostNamedCustomer(MainContext.MainThreadStatusBar)
export class MainThreadStatusBar implements MainThreadStatusBarShape {
private readonly entries: Map<number, { accessor: IStatusbarEntryAccessor, alignment: MainThreadStatusBarAlignment, priority: number }> = new Map();
static readonly CODICON_REGEXP = /\$\((.*?)\)/g;
constructor(
_extHostContext: IExtHostContext,
@@ -25,10 +27,17 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape {
this.entries.clear();
}
$setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: Command | undefined, color: string | ThemeColor | undefined, alignment: MainThreadStatusBarAlignment, priority: number | undefined): void {
$setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: Command | undefined, color: string | ThemeColor | undefined, alignment: MainThreadStatusBarAlignment, priority: number | undefined, accessibilityInformation: IAccessibilityInformation): void {
// if there are icons in the text use the tooltip for the aria label
const ariaLabel = text.indexOf('$(') === -1 ? text : tooltip || text;
const entry: IStatusbarEntry = { text, tooltip, command, color, ariaLabel };
let ariaLabel: string;
let role: string | undefined = undefined;
if (accessibilityInformation) {
ariaLabel = accessibilityInformation.label;
role = accessibilityInformation.role;
} else {
ariaLabel = text ? text.replace(MainThreadStatusBar.CODICON_REGEXP, (_match, codiconName) => codiconName) : '';
}
const entry: IStatusbarEntry = { text, tooltip, command, color, ariaLabel, role };
if (typeof priority === 'undefined') {
priority = 0;

View File

@@ -7,7 +7,6 @@ import * as nls from 'vs/nls';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import * as Objects from 'vs/base/common/objects';
import * as Types from 'vs/base/common/types';
import * as Platform from 'vs/base/common/platform';
import { IStringDictionary, forEach } from 'vs/base/common/collections';
@@ -64,7 +63,7 @@ namespace TaskProcessEndedDTO {
namespace TaskDefinitionDTO {
export function from(value: KeyedTaskIdentifier): TaskDefinitionDTO {
const result = Objects.assign(Object.create(null), value);
const result = Object.assign(Object.create(null), value);
delete result._key;
return result;
}
@@ -85,13 +84,13 @@ namespace TaskPresentationOptionsDTO {
if (value === undefined || value === null) {
return undefined;
}
return Objects.assign(Object.create(null), value);
return Object.assign(Object.create(null), value);
}
export function to(value: TaskPresentationOptionsDTO | undefined): PresentationOptions {
if (value === undefined || value === null) {
return PresentationOptions.defaults;
}
return Objects.assign(Object.create(null), PresentationOptions.defaults, value);
return Object.assign(Object.create(null), PresentationOptions.defaults, value);
}
}
@@ -100,13 +99,13 @@ namespace RunOptionsDTO {
if (value === undefined || value === null) {
return undefined;
}
return Objects.assign(Object.create(null), value);
return Object.assign(Object.create(null), value);
}
export function to(value: RunOptionsDTO | undefined): RunOptions {
if (value === undefined || value === null) {
return RunOptions.defaults;
}
return Objects.assign(Object.create(null), RunOptions.defaults, value);
return Object.assign(Object.create(null), RunOptions.defaults, value);
}
}
@@ -328,10 +327,10 @@ namespace TaskDTO {
result.detail = task.configurationProperties.detail;
}
if (!ConfiguringTask.is(task) && task.command) {
if (task.command.runtime === RuntimeType.Process) {
result.execution = ProcessExecutionDTO.from(task.command);
} else if (task.command.runtime === RuntimeType.Shell) {
result.execution = ShellExecutionDTO.from(task.command);
switch (task.command.runtime) {
case RuntimeType.Process: result.execution = ProcessExecutionDTO.from(task.command); break;
case RuntimeType.Shell: result.execution = ShellExecutionDTO.from(task.command); break;
case RuntimeType.CustomExecution: result.execution = CustomExecutionDTO.from(task.command); break;
}
}
if (task.configurationProperties.problemMatchers) {
@@ -368,7 +367,7 @@ namespace TaskDTO {
const label = nls.localize('task.label', '{0}: {1}', source.label, task.name);
const definition = TaskDefinitionDTO.to(task.definition, executeOnly)!;
const id = `${task.source.extensionId}.${definition._key}`;
const id = (CustomExecutionDTO.is(task.execution!) && task._id) ? task._id : `${task.source.extensionId}.${definition._key}`;
const result: ContributedTask = new ContributedTask(
id, // uuidMap.getUUID(identifier)
source,
@@ -510,6 +509,32 @@ export class MainThreadTask implements MainThreadTaskShape {
});
}
public async $getTaskExecution(value: TaskHandleDTO | TaskDTO): Promise<TaskExecutionDTO> {
if (TaskHandleDTO.is(value)) {
const workspaceFolder = typeof value.workspaceFolder === 'string' ? value.workspaceFolder : this._workspaceContextServer.getWorkspaceFolder(URI.revive(value.workspaceFolder));
if (workspaceFolder) {
const task = await this._taskService.getTask(workspaceFolder, value.id, true);
if (task) {
return {
id: task._id,
task: TaskDTO.from(task)
};
}
throw new Error('Task not found');
} else {
throw new Error('No workspace folder');
}
} else {
const task = TaskDTO.to(value, this._workspaceContextServer, true)!;
return {
id: task._id,
task: TaskDTO.from(task)
};
}
}
// Passing in a TaskHandleDTO will cause the task to get re-resolved, which is important for tasks are coming from the core,
// such as those gotten from a fetchTasks, since they can have missing configuration properties.
public $executeTask(value: TaskHandleDTO | TaskDTO): Promise<TaskExecutionDTO> {
return new Promise<TaskExecutionDTO>((resolve, reject) => {
if (TaskHandleDTO.is(value)) {
@@ -548,6 +573,7 @@ export class MainThreadTask implements MainThreadTaskShape {
});
}
public $customExecutionComplete(id: string, result?: number): Promise<void> {
return new Promise<void>((resolve, reject) => {
this._taskService.getActiveTasks().then((tasks) => {
@@ -587,6 +613,9 @@ export class MainThreadTask implements MainThreadTaskShape {
public $registerTaskSystem(key: string, info: TaskSystemInfoDTO): void {
let platform: Platform.Platform;
switch (info.platform) {
case 'Web':
platform = Platform.Platform.Web;
break;
case 'win32':
platform = Platform.Platform.Windows;
break;
@@ -605,7 +634,7 @@ export class MainThreadTask implements MainThreadTaskShape {
return URI.parse(`${info.scheme}://${info.authority}${path}`);
},
context: this._extHostContext,
resolveVariables: (workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet, target: ConfigurationTarget): Promise<ResolvedVariables> => {
resolveVariables: (workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet, target: ConfigurationTarget): Promise<ResolvedVariables | undefined> => {
const vars: string[] = [];
toResolve.variables.forEach(item => vars.push(item));
return Promise.resolve(this._proxy.$resolveVariables(workspaceFolder.uri, { process: toResolve.process, variables: vars })).then(values => {
@@ -613,8 +642,12 @@ export class MainThreadTask implements MainThreadTaskShape {
forEach(values.variables, (entry) => {
partiallyResolvedVars.push(entry.value);
});
return new Promise<ResolvedVariables>((resolve, reject) => {
return new Promise<ResolvedVariables | undefined>((resolve, reject) => {
this._configurationResolverService.resolveWithInteraction(workspaceFolder, partiallyResolvedVars, 'tasks', undefined, target).then(resolvedVars => {
if (!resolvedVars) {
resolve(undefined);
}
const result: ResolvedVariables = {
process: undefined,
variables: new Map<string, string>()
@@ -642,7 +675,15 @@ export class MainThreadTask implements MainThreadTaskShape {
},
getDefaultShellAndArgs: (): Promise<{ shell: string, args: string[] | string | undefined }> => {
return Promise.resolve(this._proxy.$getDefaultShellAndArgs());
},
findExecutable: (command: string, cwd?: string, paths?: string[]): Promise<string | undefined> => {
return this._proxy.$findExecutable(command, cwd, paths);
}
});
}
async $registerSupportedExecutions(custom?: boolean, shell?: boolean, process?: boolean): Promise<void> {
return this._taskService.registerSupportedExecutions(custom, shell, process);
}
}

View File

@@ -9,12 +9,13 @@ import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceS
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { URI } from 'vs/base/common/uri';
import { StopWatch } from 'vs/base/common/stopwatch';
import { ITerminalInstanceService, ITerminalService, ITerminalInstance, ITerminalBeforeHandleLinkEvent } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ITerminalInstanceService, ITerminalService, ITerminalInstance, ITerminalBeforeHandleLinkEvent, ITerminalExternalLinkProvider, ITerminalLink } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering';
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';
@extHostNamedCustomer(MainContext.MainThreadTerminalService)
export class MainThreadTerminalService implements MainThreadTerminalServiceShape {
@@ -22,10 +23,16 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
private _proxy: ExtHostTerminalServiceShape;
private _remoteAuthority: string | null;
private readonly _toDispose = new DisposableStore();
private readonly _terminalProcesses = new Map<number, Promise<ITerminalProcessExtHostProxy>>();
private readonly _terminalProcessesReady = new Map<number, (proxy: ITerminalProcessExtHostProxy) => void>();
private readonly _terminalProcessProxies = new Map<number, ITerminalProcessExtHostProxy>();
private _dataEventTracker: TerminalDataEventTracker | undefined;
private _linkHandler: IDisposable | undefined;
/**
* A single shared terminal link provider for the exthost. When an ext registers a link
* provider, this is registered with the terminal on the renderer side and all links are
* provided through this, even from multiple ext link providers. Xterm should remove lower
* priority intersecting links itself.
*/
private _linkProvider: IDisposable | undefined;
constructor(
extHostContext: IExtHostContext,
@@ -34,6 +41,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService,
@ILogService private readonly _logService: ILogService,
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService);
this._remoteAuthority = extHostContext.remoteAuthority;
@@ -82,11 +90,13 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
this._proxy.$initEnvironmentVariableCollections(serializedCollections);
}
this._terminalService.extHostReady(extHostContext.remoteAuthority);
this._terminalService.extHostReady(extHostContext.remoteAuthority!); // TODO@Tyriar: remove null assertion
}
public dispose(): void {
this._toDispose.dispose();
this._linkHandler?.dispose();
this._linkProvider?.dispose();
// TODO@Daniel: Should all the previously created terminals be disposed
// when the extension host process goes down ?
@@ -106,7 +116,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
isExtensionTerminal: launchConfig.isExtensionTerminal
};
const terminal = this._terminalService.createTerminal(shellLaunchConfig);
this._terminalProcesses.set(terminal.id, new Promise<ITerminalProcessExtHostProxy>(r => this._terminalProcessesReady.set(terminal.id, r)));
return Promise.resolve({
id: terminal.id,
name: terminal.title
@@ -164,6 +173,17 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
public $stopHandlingLinks(): void {
this._linkHandler?.dispose();
this._linkHandler = undefined;
}
public $startLinkProvider(): void {
this._linkProvider?.dispose();
this._linkProvider = this._terminalService.registerLinkProvider(new ExtensionTerminalLinkProvider(this._proxy));
}
public $stopLinkProvider(): void {
this._linkProvider?.dispose();
this._linkProvider = undefined;
}
private async _handleLink(e: ITerminalBeforeHandleLinkEvent): Promise<boolean> {
@@ -199,7 +219,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
executable: terminalInstance.shellLaunchConfig.executable,
args: terminalInstance.shellLaunchConfig.args,
cwd: terminalInstance.shellLaunchConfig.cwd,
env: terminalInstance.shellLaunchConfig.env
env: terminalInstance.shellLaunchConfig.env,
hideFromUser: terminalInstance.shellLaunchConfig.hideFromUser
};
if (terminalInstance.title) {
this._proxy.$acceptTerminalOpened(terminalInstance.id, terminalInstance.title, shellLaunchConfigDto);
@@ -232,13 +253,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
}
const proxy = request.proxy;
const ready = this._terminalProcessesReady.get(proxy.terminalId);
if (ready) {
ready(proxy);
this._terminalProcessesReady.delete(proxy.terminalId);
} else {
this._terminalProcesses.set(proxy.terminalId, Promise.resolve(proxy));
}
this._terminalProcessProxies.set(proxy.terminalId, proxy);
const shellLaunchConfigDto: IShellLaunchConfigDto = {
name: request.shellLaunchConfig.name,
executable: request.shellLaunchConfig.executable,
@@ -246,7 +261,17 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
cwd: request.shellLaunchConfig.cwd,
env: request.shellLaunchConfig.env
};
this._proxy.$spawnExtHostProcess(proxy.terminalId, shellLaunchConfigDto, request.activeWorkspaceRootUri, request.cols, request.rows, request.isWorkspaceShellAllowed);
this._logService.trace('Spawning ext host process', { terminalId: proxy.terminalId, shellLaunchConfigDto, request });
this._proxy.$spawnExtHostProcess(
proxy.terminalId,
shellLaunchConfigDto,
request.activeWorkspaceRootUri,
request.cols,
request.rows,
request.isWorkspaceShellAllowed
).then(request.callback);
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));
@@ -257,13 +282,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
private _onRequestStartExtensionTerminal(request: IStartExtensionTerminalRequest): void {
const proxy = request.proxy;
const ready = this._terminalProcessesReady.get(proxy.terminalId);
if (!ready) {
this._terminalProcesses.set(proxy.terminalId, Promise.resolve(proxy));
} else {
ready(proxy);
this._terminalProcessesReady.delete(proxy.terminalId);
}
this._terminalProcessProxies.set(proxy.terminalId, proxy);
// Note that onReisze is not being listened to here as it needs to fire when max dimensions
// change, excluding the dimension override
@@ -271,7 +290,12 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
columns: request.cols,
rows: request.rows
} : undefined;
this._proxy.$startExtensionTerminal(proxy.terminalId, initialDimensions);
this._proxy.$startExtensionTerminal(
proxy.terminalId,
initialDimensions
).then(request.callback);
proxy.onInput(data => this._proxy.$acceptProcessInput(proxy.terminalId, data));
proxy.onShutdown(immediate => this._proxy.$acceptProcessShutdown(proxy.terminalId, immediate));
proxy.onRequestCwd(() => this._proxy.$acceptProcessRequestCwd(proxy.terminalId));
@@ -280,38 +304,38 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
}
public $sendProcessTitle(terminalId: number, title: string): void {
this._getTerminalProcess(terminalId).then(e => e.emitTitle(title));
this._getTerminalProcess(terminalId).emitTitle(title);
}
public $sendProcessData(terminalId: number, data: string): void {
this._getTerminalProcess(terminalId).then(e => e.emitData(data));
this._getTerminalProcess(terminalId).emitData(data);
}
public $sendProcessReady(terminalId: number, pid: number, cwd: string): void {
this._getTerminalProcess(terminalId).then(e => e.emitReady(pid, cwd));
this._getTerminalProcess(terminalId).emitReady(pid, cwd);
}
public $sendProcessExit(terminalId: number, exitCode: number | undefined): void {
this._getTerminalProcess(terminalId).then(e => e.emitExit(exitCode));
this._terminalProcesses.delete(terminalId);
this._getTerminalProcess(terminalId).emitExit(exitCode);
this._terminalProcessProxies.delete(terminalId);
}
public $sendOverrideDimensions(terminalId: number, dimensions: ITerminalDimensions | undefined): void {
this._getTerminalProcess(terminalId).then(e => e.emitOverrideDimensions(dimensions));
this._getTerminalProcess(terminalId).emitOverrideDimensions(dimensions);
}
public $sendProcessInitialCwd(terminalId: number, initialCwd: string): void {
this._getTerminalProcess(terminalId).then(e => e.emitInitialCwd(initialCwd));
this._getTerminalProcess(terminalId).emitInitialCwd(initialCwd);
}
public $sendProcessCwd(terminalId: number, cwd: string): void {
this._getTerminalProcess(terminalId).then(e => e.emitCwd(cwd));
this._getTerminalProcess(terminalId).emitCwd(cwd);
}
public $sendResolvedLaunchConfig(terminalId: number, shellLaunchConfig: IShellLaunchConfig): void {
const instance = this._terminalService.getInstanceFromId(terminalId);
if (instance) {
this._getTerminalProcess(terminalId).then(e => e.emitResolvedShellLaunchConfig(shellLaunchConfig));
this._getTerminalProcess(terminalId).emitResolvedShellLaunchConfig(shellLaunchConfig);
}
}
@@ -324,7 +348,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
sw.stop();
sum += sw.elapsed();
}
this._getTerminalProcess(terminalId).then(e => e.emitLatency(sum / COUNT));
this._getTerminalProcess(terminalId).emitLatency(sum / COUNT);
}
private _isPrimaryExtHost(): boolean {
@@ -349,8 +373,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
}
}
private _getTerminalProcess(terminalId: number): Promise<ITerminalProcessExtHostProxy> {
const terminal = this._terminalProcesses.get(terminalId);
private _getTerminalProcess(terminalId: number): ITerminalProcessExtHostProxy {
const terminal = this._terminalProcessProxies.get(terminalId);
if (!terminal) {
throw new Error(`Unknown terminal: ${terminalId}`);
}
@@ -395,3 +419,22 @@ class TerminalDataEventTracker extends Disposable {
this._register(this._bufferer.startBuffering(instance.id, instance.onData));
}
}
class ExtensionTerminalLinkProvider implements ITerminalExternalLinkProvider {
constructor(
private readonly _proxy: ExtHostTerminalServiceShape
) {
}
async provideLinks(instance: ITerminalInstance, line: string): Promise<ITerminalLink[] | undefined> {
const proxy = this._proxy;
const extHostLinks = await proxy.$provideLinks(instance.id, line);
return extHostLinks.map(dto => ({
id: dto.id,
startIndex: dto.startIndex,
length: dto.length,
label: dto.label,
activate: () => proxy.$activateLink(instance.id, dto.id)
}));
}
}

View File

@@ -25,6 +25,7 @@ export class MainThreadTheming implements MainThreadThemingShape {
this._themeChangeListener = this._themeService.onDidColorThemeChange(e => {
this._proxy.$onColorThemeChange(this._themeService.getColorTheme().type);
});
this._proxy.$onColorThemeChange(this._themeService.getColorTheme().type);
}
dispose(): void {

View File

@@ -5,7 +5,7 @@
import { Disposable } from 'vs/base/common/lifecycle';
import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainContext, IExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
import { ITreeViewDataProvider, ITreeItem, IViewsService, ITreeView, IViewsRegistry, ITreeViewDescriptor, IRevealOptions, Extensions } from 'vs/workbench/common/views';
import { ITreeViewDataProvider, ITreeItem, IViewsService, ITreeView, IViewsRegistry, ITreeViewDescriptor, IRevealOptions, Extensions, ResolvableTreeItem } from 'vs/workbench/common/views';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { distinct } from 'vs/base/common/arrays';
import { INotificationService } from 'vs/platform/notification/common/notification';
@@ -163,11 +163,13 @@ type TreeItemHandle = string;
class TreeViewDataProvider implements ITreeViewDataProvider {
private readonly itemsMap: Map<TreeItemHandle, ITreeItem> = new Map<TreeItemHandle, ITreeItem>();
private hasResolve: Promise<boolean>;
constructor(private readonly treeViewId: string,
private readonly _proxy: ExtHostTreeViewsShape,
private readonly notificationService: INotificationService
) {
this.hasResolve = this._proxy.$hasResolve(this.treeViewId);
}
getChildren(treeItem?: ITreeItem): Promise<ITreeItem[]> {
@@ -214,12 +216,16 @@ class TreeViewDataProvider implements ITreeViewDataProvider {
return this.itemsMap.size === 0;
}
private postGetChildren(elements: ITreeItem[]): ITreeItem[] {
const result: ITreeItem[] = [];
private async postGetChildren(elements: ITreeItem[]): Promise<ResolvableTreeItem[]> {
const result: ResolvableTreeItem[] = [];
const hasResolve = await this.hasResolve;
if (elements) {
for (const element of elements) {
this.itemsMap.set(element.handle, element);
result.push(element);
const resolvable = new ResolvableTreeItem(element, hasResolve ? () => {
return this._proxy.$resolve(this.treeViewId, element.handle);
} : undefined);
this.itemsMap.set(element.handle, resolvable);
result.push(resolvable);
}
}
return result;

View File

@@ -5,17 +5,18 @@
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { onUnexpectedError } from 'vs/base/common/errors';
import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
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 { isWeb } from 'vs/base/common/platform';
import { isEqual, isEqualOrParent } from 'vs/base/common/resources';
import { isEqual, isEqualOrParent, toLocalResource } from 'vs/base/common/resources';
import { escape } from 'vs/base/common/strings';
import { URI, UriComponents } from 'vs/base/common/uri';
import * as modes from 'vs/editor/common/modes';
import { localize } from 'vs/nls';
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -38,6 +39,7 @@ import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOption
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IWorkingCopy, IWorkingCopyBackup, IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
@@ -141,7 +143,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
this._register(_editorService.onDidActiveEditorChange(() => {
const activeInput = this._editorService.activeEditor;
if (activeInput instanceof DiffEditorInput && activeInput.master instanceof WebviewInput && activeInput.details instanceof WebviewInput) {
if (activeInput instanceof DiffEditorInput && activeInput.primary instanceof WebviewInput && activeInput.secondary instanceof WebviewInput) {
this.registerWebviewFromDiffEditorListeners(activeInput);
}
@@ -185,6 +187,15 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
});
}
dispose() {
super.dispose();
for (const disposable of this._editorProviders.values()) {
disposable.dispose();
}
this._editorProviders.clear();
}
public $createWebviewPanel(
extensionData: extHostProtocol.WebviewExtensionDescription,
handle: extHostProtocol.WebviewPanelHandle,
@@ -252,7 +263,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
public async $postMessage(handle: extHostProtocol.WebviewPanelHandle, message: any): Promise<boolean> {
const webview = this.getWebviewInput(handle);
webview.webview.sendMessage(message);
webview.webview.postMessage(message);
return true;
}
@@ -280,8 +291,8 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
if (webviewInput.webview.state) {
try {
state = JSON.parse(webviewInput.webview.state);
} catch {
// noop
} catch (e) {
console.error('Could not load webview state', e, webviewInput.webview.state);
}
}
@@ -320,7 +331,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
options: modes.IWebviewPanelOptions,
capabilities: extHostProtocol.CustomTextEditorCapabilities,
supportsMultipleEditorsPerDocument: boolean,
): DisposableStore {
): void {
if (this._editorProviders.has(viewType)) {
throw new Error(`Provider for ${viewType} already registered`);
}
@@ -361,6 +372,17 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
}
webviewInput.webview.onDispose(() => {
// If the model is still dirty, make sure we have time to save it
if (modelRef.object.isDirty()) {
const sub = modelRef.object.onDidChangeDirty(() => {
if (!modelRef.object.isDirty()) {
sub.dispose();
modelRef.dispose();
}
});
return;
}
modelRef.dispose();
});
@@ -385,8 +407,6 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
}));
this._editorProviders.set(viewType, disposables);
return disposables;
}
public $unregisterEditorProvider(viewType: string): void {
@@ -457,22 +477,22 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
}
private registerWebviewFromDiffEditorListeners(diffEditorInput: DiffEditorInput): void {
const master = diffEditorInput.master as WebviewInput;
const details = diffEditorInput.details as WebviewInput;
const primary = diffEditorInput.primary as WebviewInput;
const secondary = diffEditorInput.secondary as WebviewInput;
if (this._webviewFromDiffEditorHandles.has(master.id) || this._webviewFromDiffEditorHandles.has(details.id)) {
if (this._webviewFromDiffEditorHandles.has(primary.id) || this._webviewFromDiffEditorHandles.has(secondary.id)) {
return;
}
this._webviewFromDiffEditorHandles.add(master.id);
this._webviewFromDiffEditorHandles.add(details.id);
this._webviewFromDiffEditorHandles.add(primary.id);
this._webviewFromDiffEditorHandles.add(secondary.id);
const disposables = new DisposableStore();
disposables.add(master.webview.onDidFocus(() => this.updateWebviewViewStates(master)));
disposables.add(details.webview.onDidFocus(() => this.updateWebviewViewStates(details)));
disposables.add(primary.webview.onDidFocus(() => this.updateWebviewViewStates(primary)));
disposables.add(secondary.webview.onDidFocus(() => this.updateWebviewViewStates(secondary)));
disposables.add(diffEditorInput.onDispose(() => {
this._webviewFromDiffEditorHandles.delete(master.id);
this._webviewFromDiffEditorHandles.delete(details.id);
this._webviewFromDiffEditorHandles.delete(primary.id);
this._webviewFromDiffEditorHandles.delete(secondary.id);
dispose(disposables);
}));
}
@@ -504,8 +524,8 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
for (const group of this._editorGroupService.groups) {
for (const input of group.editors) {
if (input instanceof DiffEditorInput) {
updateViewStatesForInput(group, input, input.master);
updateViewStatesForInput(group, input, input.details);
updateViewStatesForInput(group, input, input.primary);
updateViewStatesForInput(group, input, input.secondary);
} else {
updateViewStatesForInput(group, input, input);
}
@@ -643,17 +663,20 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
fromBackup: boolean,
private readonly _editable: boolean,
private readonly _getEditors: () => CustomEditorInput[],
@IWorkingCopyService workingCopyService: IWorkingCopyService,
@ILabelService private readonly _labelService: ILabelService,
@IFileDialogService private readonly _fileDialogService: IFileDialogService,
@IFileService private readonly _fileService: IFileService,
@ILabelService private readonly _labelService: ILabelService,
@IUndoRedoService private readonly _undoService: IUndoRedoService,
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
@IWorkingCopyService workingCopyService: IWorkingCopyService,
) {
super();
this._fromBackup = fromBackup;
if (_editable) {
this._register(workingCopyService.registerWorkingCopy(this));
}
this._fromBackup = fromBackup;
}
get editorResource() {
@@ -711,7 +734,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
//#endregion
public isReadonly() {
return this._editable;
return !this._editable;
}
public get viewType() {
@@ -826,11 +849,20 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
return !!await this.saveCustomEditor(options);
}
public async saveCustomEditor(_options?: ISaveOptions): Promise<URI | undefined> {
public async saveCustomEditor(options?: ISaveOptions): Promise<URI | undefined> {
if (!this._editable) {
return undefined;
}
// TODO: handle save untitled case
if (this._editorResource.scheme === Schemas.untitled) {
const targetUri = await this.suggestUntitledSavePath(options);
if (!targetUri) {
return undefined;
}
await this.saveCustomEditorAs(this._editorResource, targetUri, options);
return targetUri;
}
const savePromise = createCancelablePromise(token => this._proxy.$onSave(this._editorResource, this.viewType, token));
this._ongoingSave?.cancel();
@@ -852,6 +884,18 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
return this._editorResource;
}
private suggestUntitledSavePath(options: ISaveOptions | undefined): Promise<URI | undefined> {
if (this._editorResource.scheme !== Schemas.untitled) {
throw new Error('Resource is not untitled');
}
const remoteAuthority = this._environmentService.configuration.remoteAuthority;
const localResrouce = toLocalResource(this._editorResource, remoteAuthority);
return this._fileDialogService.pickFileToSave(localResrouce, options?.availableFileSystems);
}
public async saveCustomEditorAs(resource: URI, targetResource: URI, _options?: ISaveOptions): Promise<boolean> {
if (this._editable) {
// TODO: handle cancellation
@@ -913,7 +957,12 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
this._backupId = backupId;
}
} catch (e) {
// Make sure state has not changed in the meantime
if (isPromiseCanceledError(e)) {
// This is expected
throw e;
}
// Otherwise it could be a real error. Make sure state has not changed in the meantime.
if (this._hotExitState === pendingState) {
this._hotExitState = HotExitState.NotAllowed;
}

View File

@@ -24,6 +24,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
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';
@extHostNamedCustomer(MainContext.MainThreadWorkspace)
export class MainThreadWorkspace implements MainThreadWorkspaceShape {
@@ -188,26 +189,8 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
return search;
}
$checkExists(folders: UriComponents[], includes: string[], token: CancellationToken): Promise<boolean> {
const queryBuilder = this._instantiationService.createInstance(QueryBuilder);
const query = queryBuilder.file(folders.map(folder => toWorkspaceFolder(URI.revive(folder))), {
_reason: 'checkExists',
includePattern: includes.join(', '),
expandPatterns: true,
exists: true
});
return this._searchService.fileSearch(query, token).then(
result => {
return !!result.limitHit;
},
err => {
if (!isPromiseCanceledError(err)) {
return Promise.reject(err);
}
return false;
});
$checkExists(folders: readonly UriComponents[], includes: string[], token: CancellationToken): Promise<boolean> {
return this._instantiationService.invokeFunction((accessor) => checkGlobFileExists(accessor, folders, includes, token));
}
// --- save & edit resources ---

View File

@@ -9,7 +9,7 @@ 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 } from 'vs/workbench/common/views';
import { TreeViewPane, CustomTreeView } from 'vs/workbench/browser/parts/views/treeView';
import { TreeViewPane } from 'vs/workbench/browser/parts/views/treeView';
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';
@@ -31,6 +31,7 @@ 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 { Codicon } from 'vs/base/common/codicons';
import { CustomTreeView } from 'vs/workbench/contrib/views/browser/treeView';
export interface IUserFriendlyViewsContainerDescriptor {
id: string;
@@ -80,11 +81,21 @@ interface IUserFriendlyViewDescriptor {
name: string;
when?: string;
icon?: string;
contextualTitle?: string;
visibility?: string;
// From 'remoteViewDescriptor' type
group?: string;
remoteName?: string | string[];
}
enum InitialVisibility {
Visible = 'visible',
Hidden = 'hidden',
Collapsed = 'collapsed'
}
const viewDescriptor: IJSONSchema = {
type: 'object',
properties: {
@@ -100,6 +111,28 @@ const viewDescriptor: IJSONSchema = {
description: localize('vscode.extension.contributes.view.when', 'Condition which must be true to show this view'),
type: 'string'
},
icon: {
description: localize('vscode.extension.contributes.view.icon', "Path to the view icon. View icons are displayed when the name of the view cannot be shown. It is recommended that icons be in SVG, though any image file type is accepted."),
type: 'string'
},
contextualTitle: {
description: localize('vscode.extension.contributes.view.contextualTitle', "Human-readable context for when the view is moved out of its original location. By default, the view's container name will be used. Will be shown"),
type: 'string'
},
visibility: {
description: localize('vscode.extension.contributes.view.initialState', "Initial state of the view when the extension is first installed. Once the user has changed the view state by collapsing, moving, or hiding the view, the initial state will not be used again."),
type: 'string',
enum: [
'visible',
'hidden',
'collapsed'
],
enumDescriptions: [
localize('vscode.extension.contributes.view.initialState.visible', "The default initial state for view. The view will be expanded. This may have different behavior when the view container that the view is in is built in."),
localize('vscode.extension.contributes.view.initialState.hidden', "The view will not be shown in the view container, but will be discoverable through the views menu and other view entry points and can be un-hidden by the user."),
localize('vscode.extension.contributes.view.initialState.collapsed', "The view will show in the view container, but will be collapsed.")
]
}
}
};
@@ -406,22 +439,25 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
? container.viewOrderDelegate.getOrder(item.group)
: undefined;
const icon = item.icon ? resources.joinPath(extension.description.extensionLocation, item.icon) : undefined;
const initialVisibility = this.convertInitialVisibility(item.visibility);
const viewDescriptor = <ICustomViewDescriptor>{
id: item.id,
name: item.name,
ctorDescriptor: new SyncDescriptor(TreeViewPane),
when: ContextKeyExpr.deserialize(item.when),
containerIcon: viewContainer?.icon,
containerTitle: viewContainer?.name,
containerIcon: icon || viewContainer?.icon,
containerTitle: item.contextualTitle || viewContainer?.name,
canToggleVisibility: true,
canMoveView: true,
treeView: this.instantiationService.createInstance(CustomTreeView, item.id, item.name),
collapsed: this.showCollapsed(container),
collapsed: this.showCollapsed(container) || initialVisibility === InitialVisibility.Collapsed,
order: order,
extensionId: extension.description.identifier,
originalContainerId: entry.key,
group: item.group,
remoteAuthority: item.remoteName || (<any>item).remoteAuthority // TODO@roblou - delete after remote extensions are updated
remoteAuthority: item.remoteName || (<any>item).remoteAuthority, // TODO@roblou - delete after remote extensions are updated
hideByDefault: initialVisibility === InitialVisibility.Hidden
};
viewIds.add(viewDescriptor.id);
@@ -450,6 +486,13 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
}
}
private convertInitialVisibility(value: any): InitialVisibility | undefined {
if (Object.values(InitialVisibility).includes(value)) {
return value;
}
return undefined;
}
private isValidViewDescriptors(viewDescriptors: IUserFriendlyViewDescriptor[], collector: ExtensionMessageCollector): boolean {
if (!Array.isArray(viewDescriptors)) {
collector.error(localize('requirearray', "views must be an array"));
@@ -469,6 +512,18 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when'));
return false;
}
if (descriptor.icon && typeof descriptor.icon !== 'string') {
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'icon'));
return false;
}
if (descriptor.contextualTitle && typeof descriptor.contextualTitle !== 'string') {
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'contextualTitle'));
return false;
}
if (descriptor.visibility && !this.convertInitialVisibility(descriptor.visibility)) {
collector.error(localize('optenum', "property `{0}` can be omitted or must be one of {1}", 'visibility', Object.values(InitialVisibility).join(', ')));
return false;
}
}
return true;

View File

@@ -97,12 +97,12 @@ CommandsRegistry.registerCommand({
export class DiffAPICommand {
public static readonly ID = 'vscode.diff';
public static execute(executor: ICommandsExecutor, left: URI, right: URI, label: string, options?: vscode.TextDocumentShowOptions): Promise<any> {
public static execute(executor: ICommandsExecutor, left: URI, right: URI, label: string, options?: typeConverters.TextEditorOpenOptions): Promise<any> {
return executor.executeCommand('_workbench.diff', [
left, right,
label,
undefined,
typeConverters.TextEditorOptions.from(options),
typeConverters.TextEditorOpenOptions.from(options),
options ? typeConverters.ViewColumn.from(options.viewColumn) : undefined
]);
}
@@ -111,7 +111,7 @@ CommandsRegistry.registerCommand(DiffAPICommand.ID, adjustHandler(DiffAPICommand
export class OpenAPICommand {
public static readonly ID = 'vscode.open';
public static execute(executor: ICommandsExecutor, resource: URI, columnOrOptions?: vscode.ViewColumn | vscode.TextDocumentShowOptions, label?: string): Promise<any> {
public static execute(executor: ICommandsExecutor, resource: URI, columnOrOptions?: vscode.ViewColumn | typeConverters.TextEditorOpenOptions, label?: string): Promise<any> {
let options: ITextEditorOptions | undefined;
let position: EditorViewColumn | undefined;
@@ -119,7 +119,7 @@ export class OpenAPICommand {
if (typeof columnOrOptions === 'number') {
position = typeConverters.ViewColumn.from(columnOrOptions);
} else {
options = typeConverters.TextEditorOptions.from(columnOrOptions);
options = typeConverters.TextEditorOpenOptions.from(columnOrOptions);
position = typeConverters.ViewColumn.from(columnOrOptions.viewColumn);
}
}
@@ -136,14 +136,14 @@ CommandsRegistry.registerCommand(OpenAPICommand.ID, adjustHandler(OpenAPICommand
export class OpenWithAPICommand {
public static readonly ID = 'vscode.openWith';
public static execute(executor: ICommandsExecutor, resource: URI, viewType: string, columnOrOptions?: vscode.ViewColumn | vscode.TextDocumentShowOptions): Promise<any> {
public static execute(executor: ICommandsExecutor, resource: URI, viewType: string, columnOrOptions?: vscode.ViewColumn | typeConverters.TextEditorOpenOptions): Promise<any> {
let options: ITextEditorOptions | undefined;
let position: EditorViewColumn | undefined;
if (typeof columnOrOptions === 'number') {
position = typeConverters.ViewColumn.from(columnOrOptions);
} else if (typeof columnOrOptions !== 'undefined') {
options = typeConverters.TextEditorOptions.from(columnOrOptions);
options = typeConverters.TextEditorOpenOptions.from(columnOrOptions);
}
return executor.executeCommand('_workbench.openWith', [

View File

@@ -8,11 +8,12 @@ import * as objects from 'vs/base/common/objects';
import { Registry } from 'vs/platform/registry/common/platform';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { IConfigurationNode, IConfigurationRegistry, Extensions, resourceLanguageSettingsSchemaId, IDefaultConfigurationExtension, validateProperty, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IConfigurationNode, IConfigurationRegistry, Extensions, resourceLanguageSettingsSchemaId, validateProperty, ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { workspaceSettingsSchemaId, launchSchemaId, tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration';
import { isObject } from 'vs/base/common/types';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IStringDictionary } from 'vs/base/common/collections';
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
@@ -92,34 +93,31 @@ const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint<I
description: nls.localize('vscode.extension.contributes.defaultConfiguration', 'Contributes default editor configuration settings by language.'),
type: 'object',
patternProperties: {
'\\[.*\\]$': {
'^\\[.*\\]$': {
type: 'object',
default: {},
$ref: resourceLanguageSettingsSchemaId,
}
}
},
errorMessage: nls.localize('config.property.defaultConfiguration.languageExpected', "Language selector expected (e.g. [\"java\"])"),
additionalProperties: false
}
});
defaultConfigurationExtPoint.setHandler((extensions, { added, removed }) => {
if (removed.length) {
const removedDefaultConfigurations: IDefaultConfigurationExtension[] = removed.map(extension => {
const id = extension.description.identifier;
const name = extension.description.name;
const defaults = objects.deepClone(extension.value);
return <IDefaultConfigurationExtension>{
id, name, defaults
};
});
const removedDefaultConfigurations = removed.map<IStringDictionary<any>>(extension => objects.deepClone(extension.value));
configurationRegistry.deregisterDefaultConfigurations(removedDefaultConfigurations);
}
if (added.length) {
const addedDefaultConfigurations = added.map(extension => {
const id = extension.description.identifier;
const name = extension.description.name;
const defaults = objects.deepClone(extension.value);
return <IDefaultConfigurationExtension>{
id, name, defaults
};
const addedDefaultConfigurations = added.map<IStringDictionary<any>>(extension => {
const defaults: IStringDictionary<any> = objects.deepClone(extension.value);
for (const key of Object.keys(defaults)) {
if (!OVERRIDE_PROPERTY_PATTERN.test(key) || typeof defaults[key] !== 'object') {
extension.collector.warn(nls.localize('config.property.defaultConfiguration.warning', "Cannot register configuration defaults for '{0}'. Only defaults for language specific settings are supported.", key));
delete defaults[key];
}
}
return defaults;
});
configurationRegistry.registerDefaultConfigurations(addedDefaultConfigurations);
}

View File

@@ -46,7 +46,7 @@ import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
import { ExtHostUrls } from 'vs/workbench/api/common/extHostUrls';
import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview';
import { ExtHostWindow } from 'vs/workbench/api/common/extHostWindow';
import { IExtHostWindow } from 'vs/workbench/api/common/extHostWindow';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { throwProposedApiError, checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier';
@@ -75,6 +75,7 @@ import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentica
import { ExtHostTimeline } from 'vs/workbench/api/common/extHostTimeline';
import { ExtHostNotebookConcatDocument } from 'vs/workbench/api/common/extHostNotebookConcatDocument';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import { IExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer';
export interface IExtensionApiFactory {
(extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode;
@@ -87,6 +88,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
// services
const initData = accessor.get(IExtHostInitDataService);
const extHostConsumerFileSystem = accessor.get(IExtHostConsumerFileSystem);
const extensionService = accessor.get(IExtHostExtensionService);
const extHostWorkspace = accessor.get(IExtHostWorkspace);
const extHostConfiguration = accessor.get(IExtHostConfiguration);
@@ -97,6 +99,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostLogService = accessor.get(ILogService);
const extHostTunnelService = accessor.get(IExtHostTunnelService);
const extHostApiDeprecation = accessor.get(IExtHostApiDeprecationService);
const extHostWindow = accessor.get(IExtHostWindow);
// register addressable instances
rpcProtocol.set(ExtHostContext.ExtHostLogService, <ExtHostLogServiceShape><any>extHostLogService);
@@ -105,6 +108,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService);
rpcProtocol.set(ExtHostContext.ExtHostStorage, extHostStorage);
rpcProtocol.set(ExtHostContext.ExtHostTunnelService, extHostTunnelService);
rpcProtocol.set(ExtHostContext.ExtHostWindow, extHostWindow);
// automatically create and register addressable instances
const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, accessor.get(IExtHostDecorations));
@@ -131,10 +135,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands));
const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostLogService));
const extHostComment = rpcProtocol.set(ExtHostContext.ExtHostComments, new ExtHostComments(rpcProtocol, extHostCommands, extHostDocuments));
const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol));
const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress)));
const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHosLabelService, new ExtHostLabelService(rpcProtocol));
const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors));
const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, initData.uiKind === UIKind.Web ? new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, initData.environment) : new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, initData.environment, extensionStoragePaths));
const extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol));
const extHostAuthentication = rpcProtocol.set(ExtHostContext.ExtHostAuthentication, new ExtHostAuthentication(rpcProtocol));
const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol, extHostCommands));
@@ -193,19 +196,22 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
get onDidChangeAuthenticationProviders(): Event<vscode.AuthenticationProvidersChangeEvent> {
return extHostAuthentication.onDidChangeAuthenticationProviders;
},
getProviderIds(): Thenable<ReadonlyArray<string>> {
return extHostAuthentication.getProviderIds();
},
get providerIds(): string[] {
return extHostAuthentication.providerIds;
},
getSessions(providerId: string, scopes: string[]): Thenable<readonly vscode.AuthenticationSession[]> {
return extHostAuthentication.getSessions(extension, providerId, scopes);
get providers(): ReadonlyArray<vscode.AuthenticationProviderInformation> {
return extHostAuthentication.providers;
},
login(providerId: string, scopes: string[]): Thenable<vscode.AuthenticationSession> {
return extHostAuthentication.login(extension, providerId, scopes);
getSession(providerId: string, scopes: string[], options: vscode.AuthenticationGetSessionOptions) {
return extHostAuthentication.getSession(extension, providerId, scopes, options as any);
},
logout(providerId: string, sessionId: string): Thenable<void> {
return extHostAuthentication.logout(providerId, sessionId);
},
get onDidChangeSessions(): Event<{ [providerId: string]: vscode.AuthenticationSessionsChangeEvent }> {
get onDidChangeSessions(): Event<vscode.AuthenticationSessionsChangeEvent> {
return extHostAuthentication.onDidChangeSessions;
},
};
@@ -262,7 +268,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
get sessionId() { return initData.telemetryInfo.sessionId; },
get language() { return initData.environment.appLanguage; },
get appName() { return initData.environment.appName; },
get appRoot() { return initData.environment.appRoot!.fsPath; },
get appRoot() { return initData.environment.appRoot?.fsPath ?? ''; },
get uriScheme() { return initData.environment.appUriScheme; },
get logLevel() {
checkProposedApiEnabled(extension);
@@ -425,6 +431,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
},
setLanguageConfiguration: (language: string, configuration: vscode.LanguageConfiguration): vscode.Disposable => {
return extHostLanguageFeatures.setLanguageConfiguration(extension, language, configuration);
},
getTokenInformationAtPosition(doc: vscode.TextDocument, pos: vscode.Position) {
checkProposedApiEnabled(extension);
return extHostLanguages.tokenAtPosition(doc, pos);
}
};
@@ -442,16 +452,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
get terminals() {
return extHostTerminalService.terminals;
},
showTextDocument(documentOrUri: vscode.TextDocument | vscode.Uri, columnOrOptions?: vscode.ViewColumn | vscode.TextDocumentShowOptions, preserveFocus?: boolean): Thenable<vscode.TextEditor> {
let documentPromise: Promise<vscode.TextDocument>;
if (URI.isUri(documentOrUri)) {
documentPromise = Promise.resolve(workspace.openTextDocument(documentOrUri));
} else {
documentPromise = Promise.resolve(<vscode.TextDocument>documentOrUri);
}
return documentPromise.then(document => {
return extHostEditors.showTextDocument(document, columnOrOptions, preserveFocus);
});
async showTextDocument(documentOrUri: vscode.TextDocument | vscode.Uri, columnOrOptions?: vscode.ViewColumn | vscode.TextDocumentShowOptions, preserveFocus?: boolean): Promise<vscode.TextEditor> {
const document = await (URI.isUri(documentOrUri)
? Promise.resolve(workspace.openTextDocument(documentOrUri))
: Promise.resolve(<vscode.TextDocument>documentOrUri));
return extHostEditors.showTextDocument(document, columnOrOptions, preserveFocus);
},
createTextEditorDecorationType(options: vscode.DecorationRenderOptions): vscode.TextEditorDecorationType {
return extHostEditors.createTextEditorDecorationType(options);
@@ -525,12 +531,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
let id: string;
let name: string;
let alignment: number | undefined;
let accessibilityInformation: vscode.AccessibilityInformation | undefined = undefined;
if (alignmentOrOptions && typeof alignmentOrOptions !== 'number') {
id = alignmentOrOptions.id;
name = alignmentOrOptions.name;
alignment = alignmentOrOptions.alignment;
priority = alignmentOrOptions.priority;
accessibilityInformation = alignmentOrOptions.accessibilityInformation;
} else {
id = extension.identifier.value;
name = nls.localize('extensionLabel', "{0} (Extension)", extension.displayName || extension.name);
@@ -538,7 +546,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
priority = priority;
}
return extHostStatusBar.createStatusBarEntry(id, name, alignment, priority);
return extHostStatusBar.createStatusBarEntry(id, name, alignment, priority, accessibilityInformation);
},
setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable<any>): vscode.Disposable {
return extHostStatusBar.setStatusBarMessage(text, timeoutOrThenable);
@@ -575,6 +583,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension);
return extHostTerminalService.registerLinkHandler(handler);
},
registerTerminalLinkProvider(handler: vscode.TerminalLinkProvider): vscode.Disposable {
checkProposedApiEnabled(extension);
return extHostTerminalService.registerLinkProvider(handler);
},
registerTreeDataProvider(viewId: string, treeDataProvider: vscode.TreeDataProvider<any>): vscode.Disposable {
return extHostTreeViews.registerTreeDataProvider(viewId, treeDataProvider, extension);
},
@@ -584,11 +596,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
registerWebviewPanelSerializer: (viewType: string, serializer: vscode.WebviewPanelSerializer) => {
return extHostWebviews.registerWebviewPanelSerializer(extension, viewType, serializer);
},
registerCustomEditorProvider: (viewType: string, provider: vscode.CustomTextEditorProvider, options: { webviewOptions?: vscode.WebviewPanelOptions } = {}) => {
return extHostWebviews.registerCustomEditorProvider(extension, viewType, provider, options);
},
registerCustomEditorProvider2: (viewType: string, provider: vscode.CustomReadonlyEditorProvider, options: { webviewOptions?: vscode.WebviewPanelOptions, supportsMultipleEditorsPerDocument?: boolean } = {}) => {
checkProposedApiEnabled(extension);
registerCustomEditorProvider: (viewType: string, provider: vscode.CustomTextEditorProvider | vscode.CustomReadonlyEditorProvider, options: { webviewOptions?: vscode.WebviewPanelOptions, supportsMultipleEditorsPerDocument?: boolean } = {}) => {
return extHostWebviews.registerCustomEditorProvider(extension, viewType, provider, options);
},
registerDecorationProvider(provider: vscode.DecorationProvider) {
@@ -700,8 +708,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
}
return uriPromise.then(uri => {
return extHostDocuments.ensureDocumentData(uri).then(() => {
return extHostDocuments.getDocument(uri);
return extHostDocuments.ensureDocumentData(uri).then(documentData => {
return documentData.document;
});
});
},
@@ -740,7 +748,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
return extHostFileSystem.registerFileSystemProvider(scheme, provider, options);
},
get fs() {
return extHostFileSystem.fileSystem;
return extHostConsumerFileSystem;
},
registerFileSearchProvider: (scheme: string, provider: vscode.FileSearchProvider) => {
checkProposedApiEnabled(extension);
@@ -862,6 +870,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
}
return extHostDebugService.startDebugging(folder, nameOrConfig, parentSessionOrOptions || {});
},
stopDebugging(session: vscode.DebugSession | undefined) {
return extHostDebugService.stopDebugging(session);
},
addBreakpoints(breakpoints: vscode.Breakpoint[]) {
return extHostDebugService.addBreakpoints(breakpoints);
},
@@ -910,25 +921,65 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension);
return extHostNotebook.onDidCloseNotebookDocument;
},
get onDidSaveNotebookDocument(): Event<vscode.NotebookDocument> {
checkProposedApiEnabled(extension);
return extHostNotebook.onDidSaveNotebookDocument;
},
get notebookDocuments(): vscode.NotebookDocument[] {
checkProposedApiEnabled(extension);
return extHostNotebook.notebookDocuments;
},
get visibleNotebookEditors() {
checkProposedApiEnabled(extension);
return extHostNotebook.visibleNotebookEditors;
},
get onDidChangeVisibleNotebookEditors() {
checkProposedApiEnabled(extension);
return extHostNotebook.onDidChangeVisibleNotebookEditors;
},
get onDidChangeActiveNotebookKernel() {
checkProposedApiEnabled(extension);
return extHostNotebook.onDidChangeActiveNotebookKernel;
},
registerNotebookContentProvider: (viewType: string, provider: vscode.NotebookContentProvider) => {
checkProposedApiEnabled(extension);
return extHostNotebook.registerNotebookContentProvider(extension, viewType, provider);
},
registerNotebookKernel: (id: string, selector: vscode.GlobPattern[], kernel: vscode.NotebookKernel) => {
checkProposedApiEnabled(extension);
return extHostNotebook.registerNotebookKernel(extension, id, selector, kernel);
},
registerNotebookKernelProvider: (selector: vscode.NotebookDocumentFilter, provider: vscode.NotebookKernelProvider) => {
checkProposedApiEnabled(extension);
return extHostNotebook.registerNotebookKernelProvider(extension, selector, provider);
},
registerNotebookOutputRenderer: (type: string, outputFilter: vscode.NotebookOutputSelector, renderer: vscode.NotebookOutputRenderer) => {
checkProposedApiEnabled(extension);
return extHostNotebook.registerNotebookOutputRenderer(type, extension, outputFilter, renderer);
},
get activeNotebookDocument(): vscode.NotebookDocument | undefined {
checkProposedApiEnabled(extension);
return extHostNotebook.activeNotebookDocument;
},
get activeNotebookEditor(): vscode.NotebookEditor | undefined {
checkProposedApiEnabled(extension);
return extHostNotebook.activeNotebookEditor;
},
onDidChangeNotebookDocument(listener, thisArgs?, disposables?) {
onDidChangeActiveNotebookEditor(listener, thisArgs?, disposables?) {
checkProposedApiEnabled(extension);
return extHostNotebook.onDidChangeNotebookDocument(listener, thisArgs, disposables);
return extHostNotebook.onDidChangeActiveNotebookEditor(listener, thisArgs, disposables);
},
onDidChangeNotebookCells(listener, thisArgs?, disposables?) {
checkProposedApiEnabled(extension);
return extHostNotebook.onDidChangeNotebookCells(listener, thisArgs, disposables);
},
onDidChangeCellOutputs(listener, thisArgs?, disposables?) {
checkProposedApiEnabled(extension);
return extHostNotebook.onDidChangeCellOutputs(listener, thisArgs, disposables);
},
onDidChangeCellLanguage(listener, thisArgs?, disposables?) {
checkProposedApiEnabled(extension);
return extHostNotebook.onDidChangeCellLanguage(listener, thisArgs, disposables);
},
onDidChangeCellMetadata(listener, thisArgs?, disposables?) {
checkProposedApiEnabled(extension);
return extHostNotebook.onDidChangeCellMetadata(listener, thisArgs, disposables);
},
createConcatTextDocument(notebook, selector) {
checkProposedApiEnabled(extension);
@@ -1028,6 +1079,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
SnippetString: extHostTypes.SnippetString,
SourceBreakpoint: extHostTypes.SourceBreakpoint,
SourceControlInputBoxValidationType: extHostTypes.SourceControlInputBoxValidationType,
StandardTokenType: extHostTypes.StandardTokenType,
StatusBarAlignment: extHostTypes.StatusBarAlignment,
SymbolInformation: extHostTypes.SymbolInformation,
SymbolKind: extHostTypes.SymbolKind,
@@ -1064,7 +1116,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
TimelineItem: extHostTypes.TimelineItem,
CellKind: extHostTypes.CellKind,
CellOutputKind: extHostTypes.CellOutputKind,
NotebookCellRunState: extHostTypes.NotebookCellRunState
NotebookCellRunState: extHostTypes.NotebookCellRunState,
NotebookRunState: extHostTypes.NotebookRunState
};
};
}

View File

@@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IExtHostOutputService, ExtHostOutputService } from 'vs/workbench/api/common/extHostOutput';
import { IExtHostWorkspace, ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { IExtHostDecorations, ExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations';
import { IExtHostConfiguration, ExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration';
import { IExtHostCommands, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { IExtHostTerminalService, WorkerExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService';
import { IExtHostTask, WorkerExtHostTask } from 'vs/workbench/api/common/extHostTask';
import { IExtHostDebugService, WorkerExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService';
import { IExtHostSearch, ExtHostSearch } from 'vs/workbench/api/common/extHostSearch';
import { IExtensionStoragePaths, ExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import { IExtHostStorage, ExtHostStorage } from 'vs/workbench/api/common/extHostStorage';
import { IExtHostTunnelService, ExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
import { IExtHostApiDeprecationService, ExtHostApiDeprecationService, } from 'vs/workbench/api/common/extHostApiDeprecationService';
import { IExtHostWindow, ExtHostWindow } from 'vs/workbench/api/common/extHostWindow';
import { ExtHostConsumerFileSystem, IExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer';
registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths);
registerSingleton(IExtHostApiDeprecationService, ExtHostApiDeprecationService);
registerSingleton(IExtHostCommands, ExtHostCommands);
registerSingleton(IExtHostConfiguration, ExtHostConfiguration);
registerSingleton(IExtHostConsumerFileSystem, ExtHostConsumerFileSystem);
registerSingleton(IExtHostDebugService, WorkerExtHostDebugService);
registerSingleton(IExtHostDecorations, ExtHostDecorations);
registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors);
registerSingleton(IExtHostOutputService, ExtHostOutputService);
registerSingleton(IExtHostSearch, ExtHostSearch);
registerSingleton(IExtHostStorage, ExtHostStorage);
registerSingleton(IExtHostTask, WorkerExtHostTask);
registerSingleton(IExtHostTerminalService, WorkerExtHostTerminalService);
registerSingleton(IExtHostTunnelService, ExtHostTunnelService);
registerSingleton(IExtHostWindow, ExtHostWindow);
registerSingleton(IExtHostWorkspace, ExtHostWorkspace);

View File

@@ -31,7 +31,7 @@ import { LogLevel } from 'vs/platform/log/common/log';
import { IMarkerData } from 'vs/platform/markers/common/markers';
import { IProgressOptions, IProgressStep } from 'vs/platform/progress/common/progress';
import * as quickInput from 'vs/platform/quickinput/common/quickInput';
import { RemoteAuthorityResolverErrorCode, ResolverResult, TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { RemoteAuthorityResolverErrorCode, ResolverResult, TunnelDescription, IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver';
import * as statusbar from 'vs/workbench/services/statusbar/common/statusbar';
import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings';
import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry';
@@ -41,7 +41,7 @@ import * as tasks from 'vs/workbench/api/common/shared/tasks';
import { IRevealOptions, ITreeItem } from 'vs/workbench/common/views';
import { IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench/contrib/debug/common/debug';
import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder';
import { ITerminalDimensions, IShellLaunchConfig } from 'vs/workbench/contrib/terminal/common/terminal';
import { ITerminalDimensions, IShellLaunchConfig, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
import { ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions';
import { createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId, IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import * as search from 'vs/workbench/services/search/common/search';
@@ -51,11 +51,12 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline';
import { revive } from 'vs/base/common/marshalling';
import { INotebookMimeTypeSelector, IOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookMimeTypeSelector, IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto, INotebookKernelInfoDto, IMainCellDto, IOutputRenderRequest, IOutputRenderResponse, INotebookDocumentFilter, INotebookKernelInfoDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { Dto } from 'vs/base/common/types';
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/api/common/extHostTypes';
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
export interface IEnvironment {
isExtensionDevelopmentDebug: boolean;
@@ -63,11 +64,10 @@ export interface IEnvironment {
appRoot?: URI;
appLanguage: string;
appUriScheme: string;
appSettingsHome?: URI;
extensionDevelopmentLocationURI?: URI[];
extensionTestsLocationURI?: URI;
globalStorageHome: URI;
userHome: URI;
workspaceStorageHome: URI;
webviewResourceRoot: string;
webviewCspSource: string;
useHostProxy?: boolean;
@@ -98,7 +98,7 @@ export interface IInitData {
logsLocation: URI;
logFile: URI;
autoStart: boolean;
remote: { isRemote: boolean; authority: string | undefined; };
remote: { isRemote: boolean; authority: string | undefined; connectionData: IRemoteConnectionData | null; };
uiKind: UIKind;
}
@@ -107,7 +107,7 @@ export interface IConfigurationInitData extends IConfigurationData {
}
export interface IExtHostContext extends IRPCProtocol {
remoteAuthority: string;
remoteAuthority: string | null;
}
export interface IMainContext extends IRPCProtocol {
@@ -157,12 +157,21 @@ export interface MainThreadCommentsShape extends IDisposable {
}
export interface MainThreadAuthenticationShape extends IDisposable {
$registerAuthenticationProvider(id: string, displayName: string): void;
$registerAuthenticationProvider(id: string, label: string, supportsMultipleAccounts: boolean): void;
$unregisterAuthenticationProvider(id: string): void;
$onDidChangeSessions(providerId: string, event: modes.AuthenticationSessionsChangeEvent): void;
$ensureProvider(id: string): Promise<void>;
$getProviderIds(): Promise<string[]>;
$sendDidChangeSessions(providerId: string, event: modes.AuthenticationSessionsChangeEvent): void;
$getSession(providerId: string, scopes: string[], extensionId: string, extensionName: string, options: { createIfNone?: boolean, clearSessionPreference?: boolean }): Promise<modes.AuthenticationSession | undefined>;
$selectSession(providerId: string, providerName: string, extensionId: string, extensionName: string, potentialSessions: modes.AuthenticationSession[], scopes: string[], clearSessionPreference: boolean): Promise<modes.AuthenticationSession>;
$getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean>;
$loginPrompt(providerName: string, extensionName: string): Promise<boolean>;
$setTrustedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise<void>;
$setTrustedExtensionAndAccountPreference(providerId: string, accountName: string, extensionId: string, extensionName: string, sessionId: string): Promise<void>;
$requestNewSession(providerId: string, scopes: string[], extensionId: string, extensionName: string): Promise<void>;
$getSessions(providerId: string): Promise<ReadonlyArray<modes.AuthenticationSession>>;
$login(providerId: string, scopes: string[]): Promise<modes.AuthenticationSession>;
$logout(providerId: string, sessionId: string): Promise<void>;
}
export interface MainThreadConfigurationShape extends IDisposable {
@@ -193,8 +202,8 @@ export interface MainThreadDialogSaveOptions {
}
export interface MainThreadDiaglogsShape extends IDisposable {
$showOpenDialog(options: MainThreadDialogOpenOptions): Promise<UriComponents[] | undefined>;
$showSaveDialog(options: MainThreadDialogSaveOptions): Promise<UriComponents | undefined>;
$showOpenDialog(options?: MainThreadDialogOpenOptions): Promise<UriComponents[] | undefined>;
$showSaveDialog(options?: MainThreadDialogSaveOptions): Promise<UriComponents | undefined>;
}
export interface MainThreadDecorationsShape extends IDisposable {
@@ -211,7 +220,7 @@ export interface MainThreadDocumentContentProvidersShape extends IDisposable {
export interface MainThreadDocumentsShape extends IDisposable {
$tryCreateDocument(options?: { language?: string; content?: string; }): Promise<UriComponents>;
$tryOpenDocument(uri: UriComponents): Promise<void>;
$tryOpenDocument(uri: UriComponents): Promise<UriComponents>;
$trySaveDocument(uri: UriComponents): Promise<boolean>;
}
@@ -390,6 +399,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable {
export interface MainThreadLanguagesShape extends IDisposable {
$getLanguages(): Promise<string[]>;
$changeLanguage(resource: UriComponents, languageId: string): Promise<void>;
$tokensAtPosition(resource: UriComponents, position: IPosition): Promise<undefined | { type: modes.StandardTokenType, range: IRange }>;
}
export interface MainThreadMessageOptions {
@@ -440,6 +450,8 @@ export interface MainThreadTerminalServiceShape extends IDisposable {
$stopSendingDataEvents(): void;
$startHandlingLinks(): void;
$stopHandlingLinks(): void;
$startLinkProvider(): void;
$stopLinkProvider(): void;
$setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: ISerializableEnvironmentVariableCollection | undefined): void;
// Process
@@ -543,7 +555,7 @@ export interface MainThreadQuickOpenShape extends IDisposable {
}
export interface MainThreadStatusBarShape extends IDisposable {
$setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: ICommandDto | undefined, color: string | ThemeColor | undefined, alignment: statusbar.StatusbarAlignment, priority: number | undefined): void;
$setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: ICommandDto | undefined, color: string | ThemeColor | undefined, alignment: statusbar.StatusbarAlignment, priority: number | undefined, accessibilityInformation: IAccessibilityInformation | undefined): void;
$dispose(id: number): void;
}
@@ -586,6 +598,7 @@ export interface WebviewExtensionDescription {
export interface NotebookExtensionDescription {
readonly id: ExtensionIdentifier;
readonly location: UriComponents;
readonly description?: string;
}
export enum WebviewEditorCapabilities {
@@ -670,7 +683,7 @@ export interface ICellDto {
source: string[];
language: string;
cellKind: CellKind;
outputs: IOutput[];
outputs: IProcessedOutput[];
metadata?: NotebookCellMetadata;
}
@@ -683,20 +696,29 @@ export type NotebookCellsSplice = [
export type NotebookCellOutputsSplice = [
number /* start */,
number /* delete count */,
IOutput[]
IProcessedOutput[]
];
export interface MainThreadNotebookShape extends IDisposable {
$registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise<void>;
$registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, kernelInfoDto: INotebookKernelInfoDto | undefined): Promise<void>;
$onNotebookChange(viewType: string, resource: UriComponents): Promise<void>;
$unregisterNotebookProvider(viewType: string): Promise<void>;
$registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise<void>;
$unregisterNotebookRenderer(handle: number): Promise<void>;
$registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: UriComponents[]): Promise<void>;
$unregisterNotebookRenderer(id: string): Promise<void>;
$registerNotebookKernel(extension: NotebookExtensionDescription, id: string, label: string, selectors: (string | IRelativePattern)[], preloads: UriComponents[]): Promise<void>;
$registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise<void>;
$unregisterNotebookKernelProvider(handle: number): Promise<void>;
$onNotebookKernelChange(handle: number): void;
$unregisterNotebookKernel(id: string): Promise<void>;
$tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise<boolean>;
$updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise<void>;
$updateNotebookMetadata(viewType: string, resource: UriComponents, metadata: NotebookDocumentMetadata): Promise<void>;
$updateNotebookCellMetadata(viewType: string, resource: UriComponents, handle: number, metadata: NotebookCellMetadata | undefined): Promise<void>;
$spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise<void>;
$postMessage(handle: number, value: any): Promise<boolean>;
$postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise<boolean>;
$onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void;
$onContentChange(resource: UriComponents, viewType: string): void;
}
export interface MainThreadUrlsShape extends IDisposable {
@@ -716,7 +738,7 @@ export interface ITextSearchComplete {
export interface MainThreadWorkspaceShape extends IDisposable {
$startFileSearch(includePattern: string | null, includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false | null, maxResults: number | null, token: CancellationToken): Promise<UriComponents[] | null>;
$startTextSearch(query: search.IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise<ITextSearchComplete | null>;
$checkExists(folders: UriComponents[], includes: string[], token: CancellationToken): Promise<boolean>;
$checkExists(folders: readonly UriComponents[], includes: string[], token: CancellationToken): Promise<boolean>;
$saveAll(includeUntitled?: boolean): Promise<boolean>;
$updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents, name?: string; }[]): Promise<void>;
$resolveProxy(url: string): Promise<string | undefined>;
@@ -761,10 +783,12 @@ export interface MainThreadTaskShape extends IDisposable {
$registerTaskProvider(handle: number, type: string): Promise<void>;
$unregisterTaskProvider(handle: number): Promise<void>;
$fetchTasks(filter?: tasks.TaskFilterDTO): Promise<tasks.TaskDTO[]>;
$getTaskExecution(value: tasks.TaskHandleDTO | tasks.TaskDTO): Promise<tasks.TaskExecutionDTO>;
$executeTask(task: tasks.TaskHandleDTO | tasks.TaskDTO): Promise<tasks.TaskExecutionDTO>;
$terminateTask(id: string): Promise<void>;
$registerTaskSystem(scheme: string, info: tasks.TaskSystemInfoDTO): void;
$customExecutionComplete(id: string, result?: number): Promise<void>;
$registerSupportedExecutions(custom?: boolean, shell?: boolean, process?: boolean): Promise<void>;
}
export interface MainThreadExtensionServiceShape extends IDisposable {
@@ -773,7 +797,7 @@ export interface MainThreadExtensionServiceShape extends IDisposable {
$onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void;
$onExtensionActivationError(extensionId: ExtensionIdentifier, error: ExtensionActivationError): Promise<void>;
$onExtensionRuntimeError(extensionId: ExtensionIdentifier, error: SerializedError): void;
$onExtensionHostExit(code: number): void;
$onExtensionHostExit(code: number): Promise<void>;
}
export interface SCMProviderFeatures {
@@ -839,6 +863,8 @@ export interface IDebugConfiguration {
export interface IStartDebuggingOptions {
parentSessionID?: DebugSessionUUID;
repl?: IDebugSessionReplMode;
noDebug?: boolean;
compact?: boolean;
}
export interface MainThreadDebugServiceShape extends IDisposable {
@@ -852,6 +878,7 @@ export interface MainThreadDebugServiceShape extends IDisposable {
$unregisterDebugConfigurationProvider(handle: number): void;
$unregisterDebugAdapterDescriptorFactory(handle: number): void;
$startDebugging(folder: UriComponents | undefined, nameOrConfig: string | IDebugConfiguration, options: IStartDebuggingOptions): Promise<boolean>;
$stopDebugging(sessionId: DebugSessionUUID | undefined): Promise<void>;
$setDebugSessionName(id: DebugSessionUUID, name: string): void;
$customDebugAdapterRequest(id: DebugSessionUUID, command: string, args: any): Promise<any>;
$appendDebugConsole(value: string): void;
@@ -968,6 +995,8 @@ export interface ExtHostTreeViewsShape {
$setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void;
$setSelection(treeViewId: string, treeItemHandles: string[]): void;
$setVisible(treeViewId: string, visible: boolean): void;
$hasResolve(treeViewId: string): Promise<boolean>;
$resolve(treeViewId: string, treeItemHandle: string): Promise<ITreeItem | undefined>;
}
export interface ExtHostWorkspaceShape {
@@ -1002,6 +1031,8 @@ export interface ExtHostAuthenticationShape {
$getSessionAccessToken(id: string, sessionId: string): Promise<string>;
$login(id: string, scopes: string[]): Promise<modes.AuthenticationSession>;
$logout(id: string, sessionId: string): Promise<void>;
$onDidChangeAuthenticationSessions(id: string, label: string, event: modes.AuthenticationSessionsChangeEvent): Promise<void>;
$onDidChangeAuthenticationProviders(added: modes.AuthenticationProviderInformation[], removed: modes.AuthenticationProviderInformation[]): Promise<void>;
}
export interface ExtHostSearchShape {
@@ -1032,6 +1063,7 @@ export interface ExtHostExtensionServiceShape {
$activateByEvent(activationEvent: string): Promise<void>;
$activate(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<boolean>;
$setRemoteEnvironment(env: { [key: string]: string | null; }): Promise<void>;
$updateRemoteConnectionData(connectionData: IRemoteConnectionData): Promise<void>;
$deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise<void>;
@@ -1046,10 +1078,15 @@ export interface FileSystemEvents {
deleted: UriComponents[];
}
export interface SourceTargetPair {
source?: UriComponents;
target: UriComponents;
}
export interface ExtHostFileSystemEventServiceShape {
$onFileEvent(events: FileSystemEvents): void;
$onWillRunFileOperation(operation: files.FileOperation, target: UriComponents, source: UriComponents | undefined, timeout: number, token: CancellationToken): Promise<any>;
$onDidRunFileOperation(operation: files.FileOperation, target: UriComponents, source: UriComponents | undefined): void;
$onWillRunFileOperation(operation: files.FileOperation, files: SourceTargetPair[], timeout: number, token: CancellationToken): Promise<any>;
$onDidRunFileOperation(operation: files.FileOperation, files: SourceTargetPair[]): void;
}
export interface ObjectIdentifier {
@@ -1348,6 +1385,7 @@ export interface IShellLaunchConfigDto {
args?: string[] | string;
cwd?: string | UriComponents;
env?: { [key: string]: string | null; };
hideFromUser?: boolean;
}
export interface IShellDefinitionDto {
@@ -1360,6 +1398,17 @@ export interface IShellAndArgsDto {
args: string[] | string | undefined;
}
export interface ITerminalLinkDto {
/** The ID of the link to enable activation and disposal. */
id: number;
/** The startIndex of the link in the line. */
startIndex: number;
/** The length of the link in the line. */
length: number;
/** The descriptive label for what the link does when activated. */
label?: string;
}
export interface ITerminalDimensionsDto {
columns: number;
rows: number;
@@ -1374,8 +1423,8 @@ export interface ExtHostTerminalServiceShape {
$acceptTerminalTitleChange(id: number, name: string): void;
$acceptTerminalDimensions(id: number, cols: number, rows: number): void;
$acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): void;
$spawnExtHostProcess(id: number, shellLaunchConfig: IShellLaunchConfigDto, activeWorkspaceRootUri: UriComponents | undefined, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void;
$startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): void;
$spawnExtHostProcess(id: number, shellLaunchConfig: IShellLaunchConfigDto, activeWorkspaceRootUri: UriComponents | undefined, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise<ITerminalLaunchError | undefined>;
$startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise<ITerminalLaunchError | undefined>;
$acceptProcessInput(id: number, data: string): void;
$acceptProcessResize(id: number, cols: number, rows: number): void;
$acceptProcessShutdown(id: number, immediate: boolean): void;
@@ -1386,6 +1435,8 @@ export interface ExtHostTerminalServiceShape {
$getAvailableShells(): Promise<IShellDefinitionDto[]>;
$getDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto>;
$handleLink(id: number, link: string): Promise<boolean>;
$provideLinks(id: number, line: string): Promise<ITerminalLinkDto[]>;
$activateLink(id: number, linkId: number): void;
$initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void;
}
@@ -1394,7 +1445,7 @@ export interface ExtHostSCMShape {
$onInputBoxValueChange(sourceControlHandle: number, value: string): void;
$executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number, preserveFocus: boolean): Promise<void>;
$validateInput(sourceControlHandle: number, value: string, cursorPosition: number): Promise<[string, number] | undefined>;
$setSelectedSourceControls(selectedSourceControlHandles: number[]): Promise<void>;
$setSelectedSourceControl(selectedSourceControlHandle: number | undefined): Promise<void>;
}
export interface ExtHostTaskShape {
@@ -1407,6 +1458,7 @@ export interface ExtHostTaskShape {
$resolveVariables(workspaceFolder: UriComponents, toResolve: { process?: { name: string; cwd?: string; }, variables: string[]; }): Promise<{ process?: string; variables: { [key: string]: string; }; }>;
$getDefaultShellAndArgs(): Thenable<{ shell: string, args: string[] | string | undefined; }>;
$jsonTasksSupported(): Thenable<boolean>;
$findExecutable(command: string, cwd?: string, paths?: string[]): Promise<string | undefined>;
}
export interface IBreakpointDto {
@@ -1490,7 +1542,6 @@ export interface ExtHostDebugServiceShape {
export interface DecorationRequest {
readonly id: number;
readonly handle: number;
readonly uri: UriComponents;
}
@@ -1498,7 +1549,7 @@ export type DecorationData = [number, boolean, string, string, ThemeColor];
export type DecorationReply = { [id: number]: DecorationData; };
export interface ExtHostDecorationsShape {
$provideDecorations(requests: DecorationRequest[], token: CancellationToken): Promise<DecorationReply>;
$provideDecorations(handle: number, requests: DecorationRequest[], token: CancellationToken): Promise<DecorationReply>;
}
export interface ExtHostWindowShape {
@@ -1536,33 +1587,59 @@ export interface INotebookSelectionChangeEvent {
export interface INotebookEditorPropertiesChangeData {
selections: INotebookSelectionChangeEvent | null;
metadata: NotebookDocumentMetadata | null;
}
export interface INotebookModelAddedData {
uri: UriComponents;
handle: number;
// versionId: number;
versionId: number;
cells: IMainCellDto[],
viewType: string;
metadata?: NotebookDocumentMetadata;
attachedEditor?: { id: string; selections: number[]; }
}
export interface INotebookEditorAddData {
id: string;
documentUri: UriComponents;
selections: number[];
}
export interface INotebookDocumentsAndEditorsDelta {
removedDocuments?: UriComponents[];
addedDocuments?: INotebookModelAddedData[];
// removedEditors?: string[];
// addedEditors?: ITextEditorAddData[];
newActiveEditor?: UriComponents | null;
removedEditors?: string[];
addedEditors?: INotebookEditorAddData[];
newActiveEditor?: string | null;
visibleEditors?: string[];
}
export interface ExtHostNotebookShape {
$resolveNotebookData(viewType: string, uri: UriComponents): Promise<NotebookDataDto | undefined>;
$executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise<void>;
$resolveNotebookData(viewType: string, uri: UriComponents, backupId?: string): Promise<NotebookDataDto | undefined>;
$resolveNotebookEditor(viewType: string, uri: UriComponents, editorId: string): Promise<void>;
$provideNotebookKernels(handle: number, uri: UriComponents, token: CancellationToken): Promise<INotebookKernelInfoDto2[]>;
$resolveNotebookKernel(handle: number, editorId: string, uri: UriComponents, kernelId: string, token: CancellationToken): Promise<void>;
$executeNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise<void>;
$cancelNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise<void>;
$executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise<void>;
$cancelNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise<void>;
$executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise<void>;
$saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise<boolean>;
$saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise<boolean>;
$backup(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise<string | undefined>;
$acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void;
$onDidReceiveMessage(uri: UriComponents, message: any): void;
$acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelId: string | undefined }): void;
$renderOutputs(uriComponents: UriComponents, id: string, request: IOutputRenderRequest<UriComponents>): Promise<IOutputRenderResponse<UriComponents> | undefined>;
$renderOutputs2<T>(uriComponents: UriComponents, id: string, request: IOutputRenderRequest<T>): Promise<IOutputRenderResponse<T> | undefined>;
$onDidReceiveMessage(editorId: string, rendererId: string | undefined, message: unknown): void;
$acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void;
$acceptModelSaved(uriComponents: UriComponents): void;
$acceptEditorPropertiesChanged(uriComponents: UriComponents, data: INotebookEditorPropertiesChangeData): void;
$acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): Promise<void>;
$undoNotebook(viewType: string, uri: UriComponents, editId: number, isDirty: boolean): Promise<void>;
$redoNotebook(viewType: string, uri: UriComponents, editId: number, isDirty: boolean): Promise<void>;
}
export interface ExtHostStorageShape {

View File

@@ -215,6 +215,20 @@ const newCommands: ApiCommand[] = [
[ApiCommandArgument.CallHierarchyItem],
new ApiCommandResult<IOutgoingCallDto[], types.CallHierarchyOutgoingCall[]>('A CallHierarchyItem or undefined', v => v.map(typeConverters.CallHierarchyOutgoingCall.to))
),
// --- rename
new ApiCommand(
'vscode.executeDocumentRenameProvider', '_executeDocumentRenameProvider', 'Execute rename provider.',
[ApiCommandArgument.Uri, ApiCommandArgument.Position, new ApiCommandArgument('newName', 'The new symbol name', v => typeof v === 'string', v => v)],
new ApiCommandResult<IWorkspaceEditDto, types.WorkspaceEdit | undefined>('A promise that resolves to a WorkspaceEdit.', value => {
if (!value) {
return undefined;
}
if (value.rejectReason) {
throw new Error(value.rejectReason);
}
return typeConverters.WorkspaceEdit.to(value);
})
)
];
@@ -238,15 +252,6 @@ export class ExtHostApiCommands {
}
registerCommands() {
this._register('vscode.executeDocumentRenameProvider', this._executeDocumentRenameProvider, {
description: 'Execute rename provider.',
args: [
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
{ name: 'position', description: 'Position in a text document', constraint: types.Position },
{ name: 'newName', description: 'The new symbol name', constraint: String }
],
returns: 'A promise that resolves to a WorkspaceEdit.'
});
this._register('vscode.executeSignatureHelpProvider', this._executeSignatureHelpProvider, {
description: 'Execute signature help provider.',
args: [
@@ -376,23 +381,6 @@ export class ExtHostApiCommands {
this._disposables.add(disposable);
}
private _executeDocumentRenameProvider(resource: URI, position: types.Position, newName: string): Promise<types.WorkspaceEdit> {
const args = {
resource,
position: position && typeConverters.Position.from(position),
newName
};
return this._commands.executeCommand<IWorkspaceEditDto>('_executeDocumentRenameProvider', args).then(value => {
if (!value) {
return undefined;
}
if (value.rejectReason) {
return Promise.reject<any>(new Error(value.rejectReason));
}
return typeConverters.WorkspaceEdit.to(value);
});
}
private _executeSignatureHelpProvider(resource: URI, position: types.Position, triggerCharacter: string): Promise<types.SignatureHelp | undefined> {
const args = {
resource,

View File

@@ -10,7 +10,7 @@ import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
export interface IExtHostApiDeprecationService {
_serviceBrand: undefined;
readonly _serviceBrand: undefined;
report(apiId: string, extension: IExtensionDescription, migrationSuggestion: string): void;
}
@@ -19,7 +19,7 @@ export const IExtHostApiDeprecationService = createDecorator<IExtHostApiDeprecat
export class ExtHostApiDeprecationService implements IExtHostApiDeprecationService {
_serviceBrand: undefined;
declare readonly _serviceBrand: undefined;
private readonly _reportedUsages = new Set<string>();
private readonly _telemetryShape: extHostProtocol.MainThreadTelemetryShape;
@@ -63,7 +63,7 @@ export class ExtHostApiDeprecationService implements IExtHostApiDeprecationServi
export const NullApiDeprecationService = Object.freeze(new class implements IExtHostApiDeprecationService {
_serviceBrand: undefined;
declare readonly _serviceBrand: undefined;
public report(_apiId: string, _extension: IExtensionDescription, _warningMessage: string): void {
// noop

View File

@@ -14,98 +14,81 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
private _proxy: MainThreadAuthenticationShape;
private _authenticationProviders: Map<string, vscode.AuthenticationProvider> = new Map<string, vscode.AuthenticationProvider>();
private _providerIds: string[] = [];
private _providers: vscode.AuthenticationProviderInformation[] = [];
private _onDidChangeAuthenticationProviders = new Emitter<vscode.AuthenticationProvidersChangeEvent>();
readonly onDidChangeAuthenticationProviders: Event<vscode.AuthenticationProvidersChangeEvent> = this._onDidChangeAuthenticationProviders.event;
private _onDidChangeSessions = new Emitter<{ [providerId: string]: vscode.AuthenticationSessionsChangeEvent }>();
readonly onDidChangeSessions: Event<{ [providerId: string]: vscode.AuthenticationSessionsChangeEvent }> = this._onDidChangeSessions.event;
private _onDidChangeSessions = new Emitter<vscode.AuthenticationSessionsChangeEvent>();
readonly onDidChangeSessions: Event<vscode.AuthenticationSessionsChangeEvent> = this._onDidChangeSessions.event;
constructor(mainContext: IMainContext) {
this._proxy = mainContext.getProxy(MainContext.MainThreadAuthentication);
}
getProviderIds(): Promise<ReadonlyArray<string>> {
return this._proxy.$getProviderIds();
}
get providerIds(): string[] {
const ids: string[] = [];
this._authenticationProviders.forEach(provider => {
ids.push(provider.id);
});
return ids;
return this._providerIds;
}
async getSessions(requestingExtension: IExtensionDescription, providerId: string, scopes: string[]): Promise<readonly vscode.AuthenticationSession[]> {
const provider = this._authenticationProviders.get(providerId);
if (!provider) {
throw new Error(`No authentication provider with id '${providerId}' is currently registered.`);
}
const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier);
const orderedScopes = scopes.sort().join(' ');
return (await provider.getSessions())
.filter(session => session.scopes.sort().join(' ') === orderedScopes)
.map(session => {
return {
id: session.id,
account: session.account,
scopes: session.scopes,
getAccessToken: async () => {
const isAllowed = await this._proxy.$getSessionsPrompt(
provider.id,
session.account.displayName,
provider.displayName,
extensionId,
requestingExtension.displayName || requestingExtension.name);
if (!isAllowed) {
throw new Error('User did not consent to token access.');
}
return session.getAccessToken();
}
};
});
get providers(): ReadonlyArray<vscode.AuthenticationProviderInformation> {
return Object.freeze(this._providers.slice());
}
async login(requestingExtension: IExtensionDescription, providerId: string, scopes: string[]): Promise<vscode.AuthenticationSession> {
async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: string[], options: vscode.AuthenticationGetSessionOptions & { createIfNone: true }): Promise<vscode.AuthenticationSession>;
async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: string[], options: vscode.AuthenticationGetSessionOptions): Promise<vscode.AuthenticationSession | undefined> {
await this._proxy.$ensureProvider(providerId);
const provider = this._authenticationProviders.get(providerId);
if (!provider) {
throw new Error(`No authentication provider with id '${providerId}' is currently registered.`);
}
const extensionName = requestingExtension.displayName || requestingExtension.name;
const isAllowed = await this._proxy.$loginPrompt(provider.displayName, extensionName);
if (!isAllowed) {
throw new Error('User did not consent to login.');
const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier);
if (!provider) {
return this._proxy.$getSession(providerId, scopes, extensionId, extensionName, options);
}
const session = await provider.login(scopes);
await this._proxy.$setTrustedExtension(provider.id, session.account.displayName, ExtensionIdentifier.toKey(requestingExtension.identifier), extensionName);
return {
id: session.id,
account: session.account,
scopes: session.scopes,
getAccessToken: async () => {
const isAllowed = await this._proxy.$getSessionsPrompt(
provider.id,
session.account.displayName,
provider.displayName,
ExtensionIdentifier.toKey(requestingExtension.identifier),
requestingExtension.displayName || requestingExtension.name);
const orderedScopes = scopes.sort().join(' ');
const sessions = (await provider.getSessions()).filter(session => session.scopes.slice().sort().join(' ') === orderedScopes);
if (sessions.length) {
if (!provider.supportsMultipleAccounts) {
const session = sessions[0];
const allowed = await this._proxy.$getSessionsPrompt(providerId, session.account.label, provider.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._proxy.$selectSession(providerId, provider.label, extensionId, extensionName, sessions, scopes, !!options.clearSessionPreference);
return sessions.find(session => session.id === selected.id);
} else {
if (options.createIfNone) {
const isAllowed = await this._proxy.$loginPrompt(provider.label, extensionName);
if (!isAllowed) {
throw new Error('User did not consent to token access.');
throw new Error('User did not consent to login.');
}
return session.getAccessToken();
const session = await provider.login(scopes);
await this._proxy.$setTrustedExtensionAndAccountPreference(providerId, session.account.label, extensionId, extensionName, session.id);
return session;
} else {
await this._proxy.$requestNewSession(providerId, scopes, extensionId, extensionName);
return undefined;
}
};
}
}
async logout(providerId: string, sessionId: string): Promise<void> {
const provider = this._authenticationProviders.get(providerId);
if (!provider) {
throw new Error(`No authentication provider with id '${providerId}' is currently registered.`);
return this._proxy.$logout(providerId, sessionId);
}
return provider.logout(sessionId);
@@ -117,20 +100,37 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
}
this._authenticationProviders.set(provider.id, provider);
if (!this._providerIds.includes(provider.id)) {
this._providerIds.push(provider.id);
}
if (!this._providers.find(p => p.id === provider.id)) {
this._providers.push({
id: provider.id,
label: provider.label
});
}
const listener = provider.onDidChangeSessions(e => {
this._proxy.$onDidChangeSessions(provider.id, e);
this._onDidChangeSessions.fire({ [provider.id]: e });
this._proxy.$sendDidChangeSessions(provider.id, e);
});
this._proxy.$registerAuthenticationProvider(provider.id, provider.displayName);
this._onDidChangeAuthenticationProviders.fire({ added: [provider.id], removed: [] });
this._proxy.$registerAuthenticationProvider(provider.id, provider.label, provider.supportsMultipleAccounts);
return new Disposable(() => {
listener.dispose();
this._authenticationProviders.delete(provider.id);
const index = this._providerIds.findIndex(id => id === provider.id);
if (index > -1) {
this._providerIds.splice(index);
}
const i = this._providers.findIndex(p => p.id === provider.id);
if (i > -1) {
this._providers.splice(i);
}
this._proxy.$unregisterAuthenticationProvider(provider.id);
this._onDidChangeAuthenticationProviders.fire({ added: [], removed: [provider.id] });
});
}
@@ -167,7 +167,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
const sessions = await authProvider.getSessions();
const session = sessions.find(session => session.id === sessionId);
if (session) {
return session.getAccessToken();
return session.accessToken;
}
throw new Error(`Unable to find session with id: ${sessionId}`);
@@ -175,4 +175,27 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
throw new Error(`Unable to find authentication provider with handle: ${providerId}`);
}
$onDidChangeAuthenticationSessions(id: string, label: string, event: modes.AuthenticationSessionsChangeEvent) {
this._onDidChangeSessions.fire({ provider: { id, label }, ...event });
return Promise.resolve();
}
$onDidChangeAuthenticationProviders(added: modes.AuthenticationProviderInformation[], removed: modes.AuthenticationProviderInformation[]) {
added.forEach(id => {
if (!this._providers.includes(id)) {
this._providers.push(id);
}
});
removed.forEach(p => {
const index = this._providers.findIndex(provider => provider.id === p.id);
if (index > -1) {
this._providers.splice(index);
}
});
this._onDidChangeAuthenticationProviders.fire({ added, removed });
return Promise.resolve();
}
}

View File

@@ -28,11 +28,11 @@ export class ExtHostEditorInsets implements ExtHostEditorInsetsShape {
// dispose editor inset whenever the hosting editor goes away
this._disposables.add(_editors.onDidChangeVisibleTextEditors(() => {
const visibleEditor = _editors.getVisibleTextEditors();
this._insets.forEach(value => {
for (const value of this._insets.values()) {
if (visibleEditor.indexOf(value.editor) < 0) {
value.inset.dispose(); // will remove from `this._insets`
}
});
}
}));
}

View File

@@ -204,12 +204,12 @@ export class ExtHostCommands implements ExtHostCommandsShape {
$getContributedCommandHandlerDescriptions(): Promise<{ [id: string]: string | ICommandHandlerDescription }> {
const result: { [id: string]: string | ICommandHandlerDescription } = Object.create(null);
this._commands.forEach((command, id) => {
for (let [id, command] of this._commands) {
let { description } = command;
if (description) {
result[id] = description;
}
});
}
return Promise.resolve(result);
}
}

View File

@@ -51,13 +51,14 @@ export interface IExtHostDebugService extends ExtHostDebugServiceShape {
addBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise<void>;
removeBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise<void>;
startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, options: vscode.DebugSessionOptions): Promise<boolean>;
stopDebugging(session: vscode.DebugSession | undefined): Promise<void>;
registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider, trigger: vscode.DebugConfigurationProviderTriggerKind): vscode.Disposable;
registerDebugAdapterDescriptorFactory(extension: IExtensionDescription, type: string, factory: vscode.DebugAdapterDescriptorFactory): vscode.Disposable;
registerDebugAdapterTrackerFactory(type: string, factory: vscode.DebugAdapterTrackerFactory): vscode.Disposable;
asDebugSourceUri(source: vscode.DebugProtocolSource, session?: vscode.DebugSession): vscode.Uri;
}
export class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDebugServiceShape {
export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDebugServiceShape {
readonly _serviceBrand: undefined;
@@ -295,10 +296,16 @@ export class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDeb
public startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, options: vscode.DebugSessionOptions): Promise<boolean> {
return this._debugServiceProxy.$startDebugging(folder ? folder.uri : undefined, nameOrConfig, {
parentSessionID: options.parentSession ? options.parentSession.id : undefined,
repl: options.consoleMode === DebugConsoleMode.MergeWithParent ? 'mergeWithParent' : 'separate'
repl: options.consoleMode === DebugConsoleMode.MergeWithParent ? 'mergeWithParent' : 'separate',
noDebug: options.noDebug,
compact: options.compact
});
}
public stopDebugging(session: vscode.DebugSession | undefined): Promise<void> {
return this._debugServiceProxy.$stopDebugging(session ? session.id : undefined);
}
public registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider, trigger: vscode.DebugConfigurationProviderTriggerKind): vscode.Disposable {
if (!provider) {
@@ -372,9 +379,7 @@ export class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDeb
return Promise.resolve(undefined);
}
protected createVariableResolver(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider): AbstractVariableResolverService {
return new ExtHostVariableResolverService(folders, editorService, configurationService);
}
protected abstract createVariableResolver(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider): AbstractVariableResolverService;
public async $substituteVariables(folderUri: UriComponents | undefined, config: IConfig): Promise<IConfig> {
if (!this._variableResolver) {
@@ -973,7 +978,7 @@ export class ExtHostDebugConsole implements vscode.DebugConsole {
export class ExtHostVariableResolverService extends AbstractVariableResolverService {
constructor(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider, env?: IProcessEnvironment) {
constructor(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors | undefined, configurationService: ExtHostConfigProvider, env?: IProcessEnvironment) {
super({
getFolderUri: (folderName: string): URI | undefined => {
const found = folders.filter(f => f.name === folderName);
@@ -992,27 +997,33 @@ export class ExtHostVariableResolverService extends AbstractVariableResolverServ
return env ? env['VSCODE_EXEC_PATH'] : undefined;
},
getFilePath: (): string | undefined => {
const activeEditor = editorService.activeEditor();
if (activeEditor) {
return path.normalize(activeEditor.document.uri.fsPath);
if (editorService) {
const activeEditor = editorService.activeEditor();
if (activeEditor) {
return path.normalize(activeEditor.document.uri.fsPath);
}
}
return undefined;
},
getSelectedText: (): string | undefined => {
const activeEditor = editorService.activeEditor();
if (activeEditor && !activeEditor.selection.isEmpty) {
return activeEditor.document.getText(activeEditor.selection);
if (editorService) {
const activeEditor = editorService.activeEditor();
if (activeEditor && !activeEditor.selection.isEmpty) {
return activeEditor.document.getText(activeEditor.selection);
}
}
return undefined;
},
getLineNumber: (): string | undefined => {
const activeEditor = editorService.activeEditor();
if (activeEditor) {
return String(activeEditor.selection.end.line + 1);
if (editorService) {
const activeEditor = editorService.activeEditor();
if (activeEditor) {
return String(activeEditor.selection.end.line + 1);
}
}
return undefined;
}
}, env);
}, env, !editorService);
}
}
@@ -1103,4 +1114,8 @@ export class WorkerExtHostDebugService extends ExtHostDebugServiceBase {
) {
super(extHostRpcService, workspaceService, extensionService, editorsService, configurationService, commandService);
}
protected createVariableResolver(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider): AbstractVariableResolverService {
return new ExtHostVariableResolverService(folders, editorService, configurationService);
}
}

View File

@@ -9,10 +9,10 @@ import { MainContext, ExtHostDecorationsShape, MainThreadDecorationsShape, Decor
import { Disposable, Decoration } from 'vs/workbench/api/common/extHostTypes';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { asArray } from 'vs/base/common/arrays';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { ILogService } from 'vs/platform/log/common/log';
import { asArray } from 'vs/base/common/arrays';
interface ProviderData {
provider: vscode.DecorationProvider;
@@ -40,7 +40,9 @@ export class ExtHostDecorations implements IExtHostDecorations {
this._proxy.$registerDecorationProvider(handle, extensionId.value);
const listener = provider.onDidChangeDecorations(e => {
this._proxy.$onDidChange(handle, !e ? null : asArray(e));
this._proxy.$onDidChange(handle, !e || (Array.isArray(e) && e.length > 250)
? null
: asArray(e));
});
return new Disposable(() => {
@@ -50,17 +52,20 @@ export class ExtHostDecorations implements IExtHostDecorations {
});
}
$provideDecorations(requests: DecorationRequest[], token: CancellationToken): Promise<DecorationReply> {
async $provideDecorations(handle: number, requests: DecorationRequest[], token: CancellationToken): Promise<DecorationReply> {
if (!this._provider.has(handle)) {
// might have been unregistered in the meantime
return Object.create(null);
}
const result: DecorationReply = Object.create(null);
return Promise.all(requests.map(request => {
const { handle, uri, id } = request;
const entry = this._provider.get(handle);
if (!entry) {
// might have been unregistered in the meantime
return undefined;
}
const { provider, extensionId } = entry;
return Promise.resolve(provider.provideDecoration(URI.revive(uri), token)).then(data => {
const { provider, extensionId } = this._provider.get(handle)!;
await Promise.all(requests.map(async request => {
try {
const { uri, id } = request;
const data = await Promise.resolve(provider.provideDecoration(URI.revive(uri), token));
if (!data) {
return;
}
@@ -70,13 +75,12 @@ export class ExtHostDecorations implements IExtHostDecorations {
} catch (e) {
this._logService.warn(`INVALID decoration from extension '${extensionId.value}': ${e}`);
}
}, err => {
} catch (err) {
this._logService.error(err);
});
}
}));
})).then(() => {
return result;
});
return result;
}
}

View File

@@ -173,9 +173,9 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection {
forEach(callback: (uri: URI, diagnostics: ReadonlyArray<vscode.Diagnostic>, collection: DiagnosticCollection) => any, thisArg?: any): void {
this._checkDisposed();
this._data.forEach((value, uri) => {
for (let uri of this._data.keys()) {
callback.apply(thisArg, [uri, this.get(uri), this]);
});
}
}
get(uri: URI): ReadonlyArray<vscode.Diagnostic> {
@@ -307,7 +307,7 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape {
} else {
const index = new Map<string, number>();
const res: [vscode.Uri, vscode.Diagnostic[]][] = [];
this._collections.forEach(collection => {
for (const collection of this._collections.values()) {
collection.forEach((uri, diagnostics) => {
let idx = index.get(uri.toString());
if (typeof idx === 'undefined') {
@@ -317,18 +317,18 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape {
}
res[idx][1] = res[idx][1].concat(...diagnostics);
});
});
}
return res;
}
}
private _getDiagnostics(resource: vscode.Uri): ReadonlyArray<vscode.Diagnostic> {
let res: vscode.Diagnostic[] = [];
this._collections.forEach(collection => {
for (let collection of this._collections.values()) {
if (collection.has(resource)) {
res = res.concat(collection.get(resource));
}
});
}
return res;
}

View File

@@ -15,13 +15,13 @@ export class ExtHostDialogs {
this._proxy = mainContext.getProxy(MainContext.MainThreadDialogs);
}
showOpenDialog(options: vscode.OpenDialogOptions): Promise<URI[] | undefined> {
showOpenDialog(options?: vscode.OpenDialogOptions): Promise<URI[] | undefined> {
return this._proxy.$showOpenDialog(options).then(filepaths => {
return filepaths ? filepaths.map(p => URI.revive(p)) : undefined;
});
}
showSaveDialog(options: vscode.SaveDialogOptions): Promise<URI | undefined> {
showSaveDialog(options?: vscode.SaveDialogOptions): Promise<URI | undefined> {
return this._proxy.$showSaveDialog(options).then(filepath => {
return filepath ? URI.revive(filepath) : undefined;
});

View File

@@ -84,9 +84,10 @@ export class ExtHostDocuments implements ExtHostDocumentsShape {
let promise = this._documentLoader.get(uri.toString());
if (!promise) {
promise = this._proxy.$tryOpenDocument(uri).then(() => {
promise = this._proxy.$tryOpenDocument(uri).then(uriData => {
this._documentLoader.delete(uri.toString());
return assertIsDefined(this._documentsAndEditors.getDocument(uri));
const canonicalUri = URI.revive(uriData);
return assertIsDefined(this._documentsAndEditors.getDocument(canonicalUri));
}, err => {
this._documentLoader.delete(uri.toString());
return Promise.reject(err);

View File

@@ -14,6 +14,7 @@ import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor';
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import { ILogService } from 'vs/platform/log/common/log';
import { ResourceMap } from 'vs/base/common/map';
export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsShape {
@@ -22,7 +23,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
private _activeEditorId: string | null = null;
private readonly _editors = new Map<string, ExtHostTextEditor>();
private readonly _documents = new Map<string, ExtHostDocumentData>();
private readonly _documents = new ResourceMap<ExtHostDocumentData>();
private readonly _onDidAddDocuments = new Emitter<ExtHostDocumentData[]>();
private readonly _onDidRemoveDocuments = new Emitter<ExtHostDocumentData[]>();
@@ -48,9 +49,8 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
if (delta.removedDocuments) {
for (const uriComponent of delta.removedDocuments) {
const uri = URI.revive(uriComponent);
const id = uri.toString();
const data = this._documents.get(id);
this._documents.delete(id);
const data = this._documents.get(uri);
this._documents.delete(uri);
if (data) {
removedDocuments.push(data);
}
@@ -60,7 +60,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
if (delta.addedDocuments) {
for (const data of delta.addedDocuments) {
const resource = URI.revive(data.uri);
assert.ok(!this._documents.has(resource.toString()), `document '${resource} already exists!'`);
assert.ok(!this._documents.has(resource), `document '${resource} already exists!'`);
const documentData = new ExtHostDocumentData(
this._extHostRpc.getProxy(MainContext.MainThreadDocuments),
@@ -71,7 +71,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
data.versionId,
data.isDirty
);
this._documents.set(resource.toString(), documentData);
this._documents.set(resource, documentData);
addedDocuments.push(documentData);
}
}
@@ -89,10 +89,10 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
if (delta.addedEditors) {
for (const data of delta.addedEditors) {
const resource = URI.revive(data.documentUri);
assert.ok(this._documents.has(resource.toString()), `document '${resource}' does not exist`);
assert.ok(this._documents.has(resource), `document '${resource}' does not exist`);
assert.ok(!this._editors.has(data.id), `editor '${data.id}' already exists!`);
const documentData = this._documents.get(resource.toString())!;
const documentData = this._documents.get(resource)!;
const editor = new ExtHostTextEditor(
data.id,
this._extHostRpc.getProxy(MainContext.MainThreadTextEditors),
@@ -132,13 +132,11 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
}
getDocument(uri: URI): ExtHostDocumentData | undefined {
return this._documents.get(uri.toString());
return this._documents.get(uri);
}
allDocuments(): ExtHostDocumentData[] {
const result: ExtHostDocumentData[] = [];
this._documents.forEach(data => result.push(data));
return result;
return [...this._documents.values()];
}
getEditor(id: string): ExtHostTextEditor | undefined {
@@ -154,9 +152,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
}
allEditors(): ExtHostTextEditor[] {
const result: ExtHostTextEditor[] = [];
this._editors.forEach(data => result.push(data));
return result;
return [...this._editors.values()];
}
}

View File

@@ -5,9 +5,10 @@
import * as nls from 'vs/nls';
import * as path from 'vs/base/common/path';
import * as platform from 'vs/base/common/platform';
import { originalFSPath, joinPath } from 'vs/base/common/resources';
import { Barrier } from 'vs/base/common/async';
import { dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Barrier, timeout } from 'vs/base/common/async';
import { dispose, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
import { TernarySearchTree } from 'vs/base/common/map';
import { URI } from 'vs/base/common/uri';
import { ILogService } from 'vs/platform/log/common/log';
@@ -18,7 +19,6 @@ import { ExtHostStorage, IExtHostStorage } from 'vs/workbench/api/common/extHost
import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { ExtensionActivationError, checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import * as errors from 'vs/base/common/errors';
import type * as vscode from 'vscode';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
@@ -26,7 +26,7 @@ import { Schemas } from 'vs/base/common/network';
import { VSBuffer } from 'vs/base/common/buffer';
import { ExtensionMemento } from 'vs/workbench/api/common/extHostMemento';
import { RemoteAuthorityResolverError, ExtensionMode } from 'vs/workbench/api/common/extHostTypes';
import { ResolvedAuthority, ResolvedOptions, RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { ResolvedAuthority, ResolvedOptions, RemoteAuthorityResolverErrorCode, IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
@@ -34,6 +34,8 @@ import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService';
import { Emitter, Event } from 'vs/base/common/event';
import { IExtensionActivationHost, checkActivateWorkspaceContainsExtension } from 'vs/workbench/api/common/shared/workspaceContains';
interface ITestRunner {
/** Old test runner API, as exported from `vscode/lib/testrunner` */
@@ -48,7 +50,7 @@ interface INewTestRunner {
export const IHostUtils = createDecorator<IHostUtils>('IHostUtils');
export interface IHostUtils {
_serviceBrand: undefined;
readonly _serviceBrand: undefined;
exit(code?: number): void;
exists(path: string): Promise<boolean>;
realpath(path: string): Promise<string>;
@@ -65,11 +67,13 @@ type TelemetryActivationEventFragment = {
reasonId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
};
export abstract class AbstractExtHostExtensionService implements ExtHostExtensionServiceShape {
export abstract class AbstractExtHostExtensionService extends Disposable implements ExtHostExtensionServiceShape {
readonly _serviceBrand: undefined;
private static readonly WORKSPACE_CONTAINS_TIMEOUT = 7000;
private readonly _onDidChangeRemoteConnectionData = this._register(new Emitter<void>());
public readonly onDidChangeRemoteConnectionData = this._onDidChangeRemoteConnectionData.event;
protected readonly _hostUtils: IHostUtils;
protected readonly _initData: IInitData;
@@ -97,6 +101,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
private readonly _resolvers: { [authorityPrefix: string]: vscode.RemoteAuthorityResolver; };
private _started: boolean;
private _remoteConnectionData: IRemoteConnectionData | null;
private readonly _disposables: DisposableStore;
@@ -112,6 +117,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
@IExtHostTunnelService extHostTunnelService: IExtHostTunnelService,
@IExtHostTerminalService extHostTerminalService: IExtHostTerminalService
) {
super();
this._hostUtils = hostUtils;
this._extHostContext = extHostContext;
this._initData = initData;
@@ -164,6 +170,11 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
this._extensionPathIndex = null;
this._resolvers = Object.create(null);
this._started = false;
this._remoteConnectionData = this._initData.remote.connectionData;
}
public getRemoteConnectionData(): IRemoteConnectionData | null {
return this._remoteConnectionData;
}
public async initialize(): Promise<void> {
@@ -183,8 +194,6 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
}
}
protected abstract _beforeAlmostReadyToRunExtensions(): Promise<void>;
public async deactivateAll(): Promise<void> {
let allPromises: Promise<void>[] = [];
try {
@@ -244,7 +253,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
if (!this._extensionPathIndex) {
const tree = TernarySearchTree.forPaths<IExtensionDescription>();
const extensions = this._registry.getAllExtensionDescriptions().map(ext => {
if (!ext.main) {
if (!this._getEntryPoint(ext)) {
return undefined;
}
return this._hostUtils.realpath(ext.extensionLocation.fsPath).then(value => tree.set(URI.file(value).fsPath, ext));
@@ -335,7 +344,8 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
const event = getTelemetryActivationEvent(extensionDescription, reason);
type ActivatePluginClassification = {} & TelemetryActivationEventFragment;
this._mainThreadTelemetryProxy.$publicLog2<TelemetryActivationEvent, ActivatePluginClassification>('activatePlugin', event);
if (!extensionDescription.main) {
const entryPoint = this._getEntryPoint(extensionDescription);
if (!entryPoint) {
// Treat the extension as being empty => NOT AN ERROR CASE
return Promise.resolve(new EmptyExtension(ExtensionActivationTimes.NONE));
}
@@ -345,22 +355,20 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup);
return Promise.all([
this._loadCommonJSModule<IExtensionModule>(joinPath(extensionDescription.extensionLocation, extensionDescription.main), activationTimesBuilder),
this._loadCommonJSModule<IExtensionModule>(joinPath(extensionDescription.extensionLocation, entryPoint), activationTimesBuilder),
this._loadExtensionContext(extensionDescription)
]).then(values => {
return AbstractExtHostExtensionService._callActivate(this._logService, extensionDescription.identifier, values[0], values[1], activationTimesBuilder);
});
}
protected abstract _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise<vscode.ExtensionContext> {
const globalState = new ExtensionMemento(extensionDescription.identifier.value, true, this._storage);
const workspaceState = new ExtensionMemento(extensionDescription.identifier.value, false, this._storage);
const extensionMode = extensionDescription.isUnderDevelopment
? (this._initData.environment.extensionTestsLocationURI ? ExtensionMode.Test : ExtensionMode.Development)
: ExtensionMode.Release;
: ExtensionMode.Production;
this._logService.trace(`ExtensionService#loadExtensionContext ${extensionDescription.identifier.value}`);
@@ -376,14 +384,30 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
subscriptions: [],
get extensionUri() { return extensionDescription.extensionLocation; },
get extensionPath() { return extensionDescription.extensionLocation.fsPath; },
get storagePath() { return that._storagePath.workspaceValue(extensionDescription); },
get globalStoragePath() { return that._storagePath.globalValue(extensionDescription); },
asAbsolutePath(relativePath: string) { return path.join(extensionDescription.extensionLocation.fsPath, relativePath); },
get logPath() { return path.join(that._initData.logsLocation.fsPath, extensionDescription.identifier.value); },
get extensionMode() {
checkProposedApiEnabled(extensionDescription);
return extensionMode;
asAbsolutePath(relativePath: string) {
if (platform.isWeb) {
// web worker
return URI.joinPath(extensionDescription.extensionLocation, relativePath).toString();
} else {
return path.join(extensionDescription.extensionLocation.fsPath, relativePath);
}
},
get storagePath() { return that._storagePath.workspaceValue(extensionDescription)?.fsPath; },
get globalStoragePath() { return that._storagePath.globalValue(extensionDescription).fsPath; },
get logPath() { return path.join(that._initData.logsLocation.fsPath, extensionDescription.identifier.value); },
get logUri() {
checkProposedApiEnabled(extensionDescription);
return URI.joinPath(that._initData.logsLocation, extensionDescription.identifier.value);
},
get storageUri() {
checkProposedApiEnabled(extensionDescription);
return that._storagePath.workspaceValue(extensionDescription);
},
get globalStorageUri() {
checkProposedApiEnabled(extensionDescription);
return that._storagePath.globalValue(extensionDescription);
},
get extensionMode() { return extensionMode; },
get environmentVariableCollection() { return that._extHostTerminalService.getEnvironmentVariableCollection(extensionDescription); }
});
});
@@ -426,15 +450,44 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
// -- eager activation
private _activateOneStartupFinished(desc: IExtensionDescription, activationEvent: string): void {
this._activateById(desc.identifier, {
startup: false,
extensionId: desc.identifier,
activationEvent: activationEvent
}).then(undefined, (err) => {
this._logService.error(err);
});
}
private _activateAllStartupFinished(): void {
for (const desc of this._registry.getAllExtensionDescriptions()) {
if (desc.activationEvents) {
for (const activationEvent of desc.activationEvents) {
if (activationEvent === 'onStartupFinished') {
this._activateOneStartupFinished(desc, activationEvent);
}
}
}
}
}
// Handle "eager" activation extensions
private _handleEagerExtensions(): Promise<void> {
this._activateByEvent('*', true).then(undefined, (err) => {
const starActivation = this._activateByEvent('*', true).then(undefined, (err) => {
this._logService.error(err);
});
this._disposables.add(this._extHostWorkspace.onDidChangeWorkspace((e) => this._handleWorkspaceContainsEagerExtensions(e.added)));
const folders = this._extHostWorkspace.workspace ? this._extHostWorkspace.workspace.folders : [];
return this._handleWorkspaceContainsEagerExtensions(folders);
const workspaceContainsActivation = this._handleWorkspaceContainsEagerExtensions(folders);
const eagerExtensionsActivation = Promise.all([starActivation, workspaceContainsActivation]).then(() => { });
Promise.race([eagerExtensionsActivation, timeout(10000)]).then(() => {
this._activateAllStartupFinished();
});
return eagerExtensionsActivation;
}
private _handleWorkspaceContainsEagerExtensions(folders: ReadonlyArray<vscode.WorkspaceFolder>): Promise<void> {
@@ -449,94 +502,28 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
).then(() => { });
}
private _handleWorkspaceContainsEagerExtension(folders: ReadonlyArray<vscode.WorkspaceFolder>, desc: IExtensionDescription): Promise<void> {
const activationEvents = desc.activationEvents;
if (!activationEvents) {
return Promise.resolve(undefined);
}
private async _handleWorkspaceContainsEagerExtension(folders: ReadonlyArray<vscode.WorkspaceFolder>, desc: IExtensionDescription): Promise<void> {
if (this.isActivated(desc.identifier)) {
return Promise.resolve(undefined);
return;
}
const fileNames: string[] = [];
const globPatterns: string[] = [];
const localWithRemote = !this._initData.remote.isRemote && !!this._initData.remote.authority;
for (const activationEvent of activationEvents) {
if (/^workspaceContains:/.test(activationEvent)) {
const fileNameOrGlob = activationEvent.substr('workspaceContains:'.length);
if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0 || localWithRemote) {
globPatterns.push(fileNameOrGlob);
} else {
fileNames.push(fileNameOrGlob);
}
}
const host: IExtensionActivationHost = {
folders: folders.map(folder => folder.uri),
forceUsingSearch: localWithRemote,
exists: (path) => this._hostUtils.exists(path),
checkExists: (folders, includes, token) => this._mainThreadWorkspaceProxy.$checkExists(folders, includes, token)
};
const result = await checkActivateWorkspaceContainsExtension(host, desc);
if (!result) {
return;
}
if (fileNames.length === 0 && globPatterns.length === 0) {
return Promise.resolve(undefined);
}
const fileNamePromise = Promise.all(fileNames.map((fileName) => this._activateIfFileName(folders, desc.identifier, fileName))).then(() => { });
const globPatternPromise = this._activateIfGlobPatterns(folders, desc.identifier, globPatterns);
return Promise.all([fileNamePromise, globPatternPromise]).then(() => { });
}
private async _activateIfFileName(folders: ReadonlyArray<vscode.WorkspaceFolder>, extensionId: ExtensionIdentifier, fileName: string): Promise<void> {
// find exact path
for (const { uri } of folders) {
if (await this._hostUtils.exists(path.join(URI.revive(uri).fsPath, fileName))) {
// the file was found
return (
this._activateById(extensionId, { startup: true, extensionId, activationEvent: `workspaceContains:${fileName}` })
.then(undefined, err => this._logService.error(err))
);
}
}
return undefined;
}
private async _activateIfGlobPatterns(folders: ReadonlyArray<vscode.WorkspaceFolder>, extensionId: ExtensionIdentifier, globPatterns: string[]): Promise<void> {
this._logService.trace(`extensionHostMain#activateIfGlobPatterns: fileSearch, extension: ${extensionId.value}, entryPoint: workspaceContains`);
if (globPatterns.length === 0) {
return Promise.resolve(undefined);
}
const tokenSource = new CancellationTokenSource();
const searchP = this._mainThreadWorkspaceProxy.$checkExists(folders.map(folder => folder.uri), globPatterns, tokenSource.token);
const timer = setTimeout(async () => {
tokenSource.cancel();
this._activateById(extensionId, { startup: true, extensionId, activationEvent: `workspaceContainsTimeout:${globPatterns.join(',')}` })
.then(undefined, err => this._logService.error(err));
}, AbstractExtHostExtensionService.WORKSPACE_CONTAINS_TIMEOUT);
let exists: boolean = false;
try {
exists = await searchP;
} catch (err) {
if (!errors.isPromiseCanceledError(err)) {
this._logService.error(err);
}
}
tokenSource.dispose();
clearTimeout(timer);
if (exists) {
// a file was found matching one of the glob patterns
return (
this._activateById(extensionId, { startup: true, extensionId, activationEvent: `workspaceContains:${globPatterns.join(',')}` })
.then(undefined, err => this._logService.error(err))
);
}
return Promise.resolve(undefined);
return (
this._activateById(desc.identifier, { startup: true, extensionId: desc.identifier, activationEvent: result.activationEvent })
.then(undefined, err => this._logService.error(err))
);
}
private _handleExtensionTests(): Promise<void> {
@@ -575,7 +562,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
}
// after tests have run, we shutdown the host
this._gracefulExit(error || (typeof failures === 'number' && failures > 0) ? 1 /* ERROR */ : 0 /* OK */);
this._testRunnerExit(error || (typeof failures === 'number' && failures > 0) ? 1 /* ERROR */ : 0 /* OK */);
};
const runResult = testRunner!.run(extensionTestsPath, oldTestRunnerCallback);
@@ -585,11 +572,11 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
runResult
.then(() => {
c();
this._gracefulExit(0);
this._testRunnerExit(0);
})
.catch((err: Error) => {
e(err.toString());
this._gracefulExit(1);
this._testRunnerExit(1);
});
}
});
@@ -597,24 +584,20 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
// Otherwise make sure to shutdown anyway even in case of an error
else {
this._gracefulExit(1 /* ERROR */);
this._testRunnerExit(1 /* ERROR */);
}
return Promise.reject(new Error(requireError ? requireError.toString() : nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", extensionTestsPath)));
}
private _gracefulExit(code: number): void {
// to give the PH process a chance to flush any outstanding console
// messages to the main process, we delay the exit() by some time
setTimeout(() => {
// If extension tests are running, give the exit code to the renderer
if (this._initData.remote.isRemote && !!this._initData.environment.extensionTestsLocationURI) {
this._mainThreadExtensionsProxy.$onExtensionHostExit(code);
return;
}
private _testRunnerExit(code: number): void {
// wait at most 5000ms for the renderer to confirm our exit request and for the renderer socket to drain
// (this is to ensure all outstanding messages reach the renderer)
const exitPromise = this._mainThreadExtensionsProxy.$onExtensionHostExit(code);
const drainPromise = this._extHostContext.drain();
Promise.race([Promise.all([exitPromise, drainPromise]), timeout(5000)]).then(() => {
this._hostUtils.exit(code);
}, 500);
});
}
private _startExtensionHost(): Promise<void> {
@@ -764,6 +747,14 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
return buff;
}
public async $updateRemoteConnectionData(connectionData: IRemoteConnectionData): Promise<void> {
this._remoteConnectionData = connectionData;
this._onDidChangeRemoteConnectionData.fire();
}
protected abstract _beforeAlmostReadyToRunExtensions(): Promise<void>;
protected abstract _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined;
protected abstract _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
public abstract async $setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void>;
}
@@ -798,7 +789,7 @@ function getTelemetryActivationEvent(extensionDescription: IExtensionDescription
export const IExtHostExtensionService = createDecorator<IExtHostExtensionService>('IExtHostExtensionService');
export interface IExtHostExtensionService extends AbstractExtHostExtensionService {
_serviceBrand: undefined;
readonly _serviceBrand: undefined;
initialize(): Promise<void>;
isActivated(extensionId: ExtensionIdentifier): boolean;
activateByIdWithErrors(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void>;
@@ -807,4 +798,7 @@ export interface IExtHostExtensionService extends AbstractExtHostExtensionServic
getExtensionRegistry(): Promise<ExtensionDescriptionRegistry>;
getExtensionPathIndex(): Promise<TernarySearchTree<string, IExtensionDescription>>;
registerRemoteAuthorityResolver(authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver): vscode.Disposable;
onDidChangeRemoteConnectionData: Event<void>;
getRemoteConnectionData(): IRemoteConnectionData | null;
}

View File

@@ -8,7 +8,7 @@ import { MainContext, IMainContext, ExtHostFileSystemShape, MainThreadFileSystem
import type * as vscode from 'vscode';
import * as files from 'vs/platform/files/common/files';
import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle';
import { FileChangeType, FileSystemError } from 'vs/workbench/api/common/extHostTypes';
import { FileChangeType } from 'vs/workbench/api/common/extHostTypes';
import * as typeConverter from 'vs/workbench/api/common/extHostTypeConverters';
import { ExtHostLanguageFeatures } from 'vs/workbench/api/common/extHostLanguageFeatures';
import { Schemas } from 'vs/base/common/network';
@@ -108,59 +108,6 @@ class FsLinkProvider {
}
}
class ConsumerFileSystem implements vscode.FileSystem {
constructor(private _proxy: MainThreadFileSystemShape) { }
stat(uri: vscode.Uri): Promise<vscode.FileStat> {
return this._proxy.$stat(uri).catch(ConsumerFileSystem._handleError);
}
readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> {
return this._proxy.$readdir(uri).catch(ConsumerFileSystem._handleError);
}
createDirectory(uri: vscode.Uri): Promise<void> {
return this._proxy.$mkdir(uri).catch(ConsumerFileSystem._handleError);
}
async readFile(uri: vscode.Uri): Promise<Uint8Array> {
return this._proxy.$readFile(uri).then(buff => buff.buffer).catch(ConsumerFileSystem._handleError);
}
writeFile(uri: vscode.Uri, content: Uint8Array): Promise<void> {
return this._proxy.$writeFile(uri, VSBuffer.wrap(content)).catch(ConsumerFileSystem._handleError);
}
delete(uri: vscode.Uri, options?: { recursive?: boolean; useTrash?: boolean; }): Promise<void> {
return this._proxy.$delete(uri, { ...{ recursive: false, useTrash: false }, ...options }).catch(ConsumerFileSystem._handleError);
}
rename(oldUri: vscode.Uri, newUri: vscode.Uri, options?: { overwrite?: boolean; }): Promise<void> {
return this._proxy.$rename(oldUri, newUri, { ...{ overwrite: false }, ...options }).catch(ConsumerFileSystem._handleError);
}
copy(source: vscode.Uri, destination: vscode.Uri, options?: { overwrite?: boolean }): Promise<void> {
return this._proxy.$copy(source, destination, { ...{ overwrite: false }, ...options }).catch(ConsumerFileSystem._handleError);
}
private static _handleError(err: any): never {
// generic error
if (!(err instanceof Error)) {
throw new FileSystemError(String(err));
}
// no provider (unknown scheme) error
if (err.name === 'ENOPRO') {
throw FileSystemError.Unavailable(err.message);
}
// file system error
switch (err.name) {
case files.FileSystemProviderErrorCode.FileExists: throw FileSystemError.FileExists(err.message);
case files.FileSystemProviderErrorCode.FileNotFound: throw FileSystemError.FileNotFound(err.message);
case files.FileSystemProviderErrorCode.FileNotADirectory: throw FileSystemError.FileNotADirectory(err.message);
case files.FileSystemProviderErrorCode.FileIsADirectory: throw FileSystemError.FileIsADirectory(err.message);
case files.FileSystemProviderErrorCode.NoPermissions: throw FileSystemError.NoPermissions(err.message);
case files.FileSystemProviderErrorCode.Unavailable: throw FileSystemError.Unavailable(err.message);
default: throw new FileSystemError(err.message, err.name as files.FileSystemProviderErrorCode);
}
}
}
export class ExtHostFileSystem implements ExtHostFileSystemShape {
private readonly _proxy: MainThreadFileSystemShape;
@@ -172,11 +119,8 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape {
private _linkProviderRegistration?: IDisposable;
private _handlePool: number = 0;
readonly fileSystem: vscode.FileSystem;
constructor(mainContext: IMainContext, private _extHostLanguageFeatures: ExtHostLanguageFeatures) {
this._proxy = mainContext.getProxy(MainContext.MainThreadFileSystem);
this.fileSystem = new ConsumerFileSystem(this._proxy);
// register used schemes
Object.keys(Schemas).forEach(scheme => this._usedSchemes.add(scheme));

View File

@@ -0,0 +1,74 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { MainThreadFileSystemShape, MainContext } from './extHost.protocol';
import * as vscode from 'vscode';
import * as files from 'vs/platform/files/common/files';
import { FileSystemError } from 'vs/workbench/api/common/extHostTypes';
import { VSBuffer } from 'vs/base/common/buffer';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
export class ExtHostConsumerFileSystem implements vscode.FileSystem {
readonly _serviceBrand: undefined;
private readonly _proxy: MainThreadFileSystemShape;
constructor(@IExtHostRpcService extHostRpc: IExtHostRpcService) {
this._proxy = extHostRpc.getProxy(MainContext.MainThreadFileSystem);
}
stat(uri: vscode.Uri): Promise<vscode.FileStat> {
return this._proxy.$stat(uri).catch(ExtHostConsumerFileSystem._handleError);
}
readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> {
return this._proxy.$readdir(uri).catch(ExtHostConsumerFileSystem._handleError);
}
createDirectory(uri: vscode.Uri): Promise<void> {
return this._proxy.$mkdir(uri).catch(ExtHostConsumerFileSystem._handleError);
}
async readFile(uri: vscode.Uri): Promise<Uint8Array> {
return this._proxy.$readFile(uri).then(buff => buff.buffer).catch(ExtHostConsumerFileSystem._handleError);
}
writeFile(uri: vscode.Uri, content: Uint8Array): Promise<void> {
return this._proxy.$writeFile(uri, VSBuffer.wrap(content)).catch(ExtHostConsumerFileSystem._handleError);
}
delete(uri: vscode.Uri, options?: { recursive?: boolean; useTrash?: boolean; }): Promise<void> {
return this._proxy.$delete(uri, { ...{ recursive: false, useTrash: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError);
}
rename(oldUri: vscode.Uri, newUri: vscode.Uri, options?: { overwrite?: boolean; }): Promise<void> {
return this._proxy.$rename(oldUri, newUri, { ...{ overwrite: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError);
}
copy(source: vscode.Uri, destination: vscode.Uri, options?: { overwrite?: boolean; }): Promise<void> {
return this._proxy.$copy(source, destination, { ...{ overwrite: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError);
}
private static _handleError(err: any): never {
// generic error
if (!(err instanceof Error)) {
throw new FileSystemError(String(err));
}
// no provider (unknown scheme) error
if (err.name === 'ENOPRO') {
throw FileSystemError.Unavailable(err.message);
}
// file system error
switch (err.name) {
case files.FileSystemProviderErrorCode.FileExists: throw FileSystemError.FileExists(err.message);
case files.FileSystemProviderErrorCode.FileNotFound: throw FileSystemError.FileNotFound(err.message);
case files.FileSystemProviderErrorCode.FileNotADirectory: throw FileSystemError.FileNotADirectory(err.message);
case files.FileSystemProviderErrorCode.FileIsADirectory: throw FileSystemError.FileIsADirectory(err.message);
case files.FileSystemProviderErrorCode.NoPermissions: throw FileSystemError.NoPermissions(err.message);
case files.FileSystemProviderErrorCode.Unavailable: throw FileSystemError.Unavailable(err.message);
default: throw new FileSystemError(err.message, err.name as files.FileSystemProviderErrorCode);
}
}
}
export interface IExtHostConsumerFileSystem extends ExtHostConsumerFileSystem { }
export const IExtHostConsumerFileSystem = createDecorator<IExtHostConsumerFileSystem>('IExtHostConsumerFileSystem');

View File

@@ -5,10 +5,10 @@
import { AsyncEmitter, Emitter, Event, IWaitUntil } from 'vs/base/common/event';
import { IRelativePattern, parse } from 'vs/base/common/glob';
import { URI, UriComponents } from 'vs/base/common/uri';
import { URI } from 'vs/base/common/uri';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import type * as vscode from 'vscode';
import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, MainThreadTextEditorsShape, IWorkspaceFileEditDto, IWorkspaceTextEditDto } from './extHost.protocol';
import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, MainThreadTextEditorsShape, IWorkspaceFileEditDto, IWorkspaceTextEditDto, SourceTargetPair } from './extHost.protocol';
import * as typeConverter from './extHostTypeConverters';
import { Disposable, WorkspaceEdit } from './extHostTypes';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
@@ -142,16 +142,16 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
//--- file operations
$onDidRunFileOperation(operation: FileOperation, target: UriComponents, source: UriComponents | undefined): void {
$onDidRunFileOperation(operation: FileOperation, files: SourceTargetPair[]): void {
switch (operation) {
case FileOperation.MOVE:
this._onDidRenameFile.fire(Object.freeze({ files: [{ oldUri: URI.revive(source!), newUri: URI.revive(target) }] }));
this._onDidRenameFile.fire(Object.freeze({ files: files.map(f => ({ oldUri: URI.revive(f.source!), newUri: URI.revive(f.target) })) }));
break;
case FileOperation.DELETE:
this._onDidDeleteFile.fire(Object.freeze({ files: [URI.revive(target)] }));
this._onDidDeleteFile.fire(Object.freeze({ files: files.map(f => URI.revive(f.target)) }));
break;
case FileOperation.CREATE:
this._onDidCreateFile.fire(Object.freeze({ files: [URI.revive(target)] }));
this._onDidCreateFile.fire(Object.freeze({ files: files.map(f => URI.revive(f.target)) }));
break;
default:
//ignore, dont send
@@ -179,16 +179,16 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
};
}
async $onWillRunFileOperation(operation: FileOperation, target: UriComponents, source: UriComponents | undefined, timeout: number, token: CancellationToken): Promise<any> {
async $onWillRunFileOperation(operation: FileOperation, files: SourceTargetPair[], timeout: number, token: CancellationToken): Promise<any> {
switch (operation) {
case FileOperation.MOVE:
await this._fireWillEvent(this._onWillRenameFile, { files: [{ oldUri: URI.revive(source!), newUri: URI.revive(target) }] }, timeout, token);
await this._fireWillEvent(this._onWillRenameFile, { files: files.map(f => ({ oldUri: URI.revive(f.source!), newUri: URI.revive(f.target) })) }, timeout, token);
break;
case FileOperation.DELETE:
await this._fireWillEvent(this._onWillDeleteFile, { files: [URI.revive(target)] }, timeout, token);
await this._fireWillEvent(this._onWillDeleteFile, { files: files.map(f => URI.revive(f.target)) }, timeout, token);
break;
case FileOperation.CREATE:
await this._fireWillEvent(this._onWillCreateFile, { files: [URI.revive(target)] }, timeout, token);
await this._fireWillEvent(this._onWillCreateFile, { files: files.map(f => URI.revive(f.target)) }, timeout, token);
break;
default:
//ignore, dont send

View File

@@ -9,6 +9,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
export const IExtHostInitDataService = createDecorator<IExtHostInitDataService>('IExtHostInitDataService');
export interface IExtHostInitDataService extends Readonly<IInitData> {
_serviceBrand: undefined;
readonly _serviceBrand: undefined;
}

View File

@@ -73,7 +73,7 @@ class DocumentSymbolAdapter {
const element: modes.DocumentSymbol = {
name: info.name || '!!MISSING: name!!',
kind: typeConvert.SymbolKind.from(info.kind),
tags: info.tags ? info.tags.map(typeConvert.SymbolTag.from) : [],
tags: info.tags?.map(typeConvert.SymbolTag.from) || [],
detail: '',
containerName: info.containerName,
range: typeConvert.Range.from(info.location.range),
@@ -857,16 +857,11 @@ class SuggestAdapter {
private _cache = new Cache<vscode.CompletionItem>('CompletionItem');
private _disposables = new Map<number, DisposableStore>();
private _didWarnMust: boolean = false;
private _didWarnShould: boolean = false;
constructor(
private readonly _documents: ExtHostDocuments,
private readonly _commands: CommandsConverter,
private readonly _provider: vscode.CompletionItemProvider,
private readonly _logService: ILogService,
private readonly _apiDeprecation: IExtHostApiDeprecationService,
private readonly _telemetry: extHostProtocol.MainThreadTelemetryShape,
private readonly _extension: IExtensionDescription,
) { }
@@ -930,41 +925,12 @@ class SuggestAdapter {
return undefined;
}
const _mustNotChange = SuggestAdapter._mustNotChangeHash(item);
const _mayNotChange = SuggestAdapter._mayNotChangeHash(item);
const resolvedItem = await asPromise(() => this._provider.resolveCompletionItem!(item, token));
if (!resolvedItem) {
return undefined;
}
type BlameExtension = {
extensionId: string;
kind: string;
index: string;
};
type BlameExtensionMeta = {
extensionId: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
kind: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
index: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
};
let _mustNotChangeIndex = !this._didWarnMust && SuggestAdapter._mustNotChangeDiff(_mustNotChange, resolvedItem);
if (typeof _mustNotChangeIndex === 'string') {
this._logService.warn(`[${this._extension.identifier.value}] INVALID result from 'resolveCompletionItem', extension MUST NOT change any of: label, sortText, filterText, insertText, or textEdit`);
this._telemetry.$publicLog2<BlameExtension, BlameExtensionMeta>('badresolvecompletion', { extensionId: this._extension.identifier.value, kind: 'must', index: _mustNotChangeIndex });
this._didWarnMust = true;
}
let _mayNotChangeIndex = !this._didWarnShould && SuggestAdapter._mayNotChangeDiff(_mayNotChange, resolvedItem);
if (typeof _mayNotChangeIndex === 'string') {
this._logService.info(`[${this._extension.identifier.value}] UNSAVE result from 'resolveCompletionItem', extension SHOULD NOT change any of: additionalTextEdits, or command`);
this._telemetry.$publicLog2<BlameExtension, BlameExtensionMeta>('badresolvecompletion', { extensionId: this._extension.identifier.value, kind: 'should', index: _mayNotChangeIndex });
this._didWarnShould = true;
}
return this._convertCompletionItem(resolvedItem, id);
}
@@ -1035,45 +1001,6 @@ class SuggestAdapter {
return result;
}
private static _mustNotChangeHash(item: vscode.CompletionItem) {
const res = JSON.stringify([item.label, item.sortText, item.filterText, item.insertText, item.range]);
return res;
}
private static _mustNotChangeDiff(hash: string, item: vscode.CompletionItem): string | void {
const thisArr = [item.label, item.sortText, item.filterText, item.insertText, item.range];
const thisHash = JSON.stringify(thisArr);
if (hash === thisHash) {
return;
}
const arr = JSON.parse(hash);
for (let i = 0; i < 6; i++) {
if (JSON.stringify(arr[i] !== JSON.stringify(thisArr[i]))) {
return i.toString();
}
}
return 'unknown';
}
private static _mayNotChangeHash(item: vscode.CompletionItem) {
return JSON.stringify([item.additionalTextEdits, item.command]);
}
private static _mayNotChangeDiff(hash: string, item: vscode.CompletionItem): string | void {
const thisArr = [item.additionalTextEdits, item.command];
const thisHash = JSON.stringify(thisArr);
if (hash === thisHash) {
return;
}
const arr = JSON.parse(hash);
for (let i = 0; i < 6; i++) {
if (JSON.stringify(arr[i] !== JSON.stringify(thisArr[i]))) {
return i.toString();
}
}
return 'unknown';
}
}
class SignatureHelpAdapter {
@@ -1360,6 +1287,7 @@ class CallHierarchyAdapter {
uri: item.uri,
range: typeConvert.Range.from(item.range),
selectionRange: typeConvert.Range.from(item.selectionRange),
tags: item.tags?.map(typeConvert.SymbolTag.from)
};
map.set(dto._itemId, item);
return dto;
@@ -1392,7 +1320,6 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
private readonly _uriTransformer: IURITransformer | null;
private readonly _proxy: extHostProtocol.MainThreadLanguageFeaturesShape;
private readonly _telemetryShape: extHostProtocol.MainThreadTelemetryShape;
private _documents: ExtHostDocuments;
private _commands: ExtHostCommands;
private _diagnostics: ExtHostDiagnostics;
@@ -1411,7 +1338,6 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
) {
this._uriTransformer = uriTransformer;
this._proxy = mainContext.getProxy(extHostProtocol.MainContext.MainThreadLanguageFeatures);
this._telemetryShape = mainContext.getProxy(extHostProtocol.MainContext.MainThreadTelemetry);
this._documents = documents;
this._commands = commands;
this._diagnostics = diagnostics;
@@ -1780,7 +1706,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
// --- suggestion
registerCompletionItemProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, triggerCharacters: string[]): vscode.Disposable {
const handle = this._addNewAdapter(new SuggestAdapter(this._documents, this._commands.converter, provider, this._logService, this._apiDeprecation, this._telemetryShape, extension), extension);
const handle = this._addNewAdapter(new SuggestAdapter(this._documents, this._commands.converter, provider, this._apiDeprecation, extension), extension);
this._proxy.$registerSuggestSupport(handle, this._transformDocumentSelector(selector), triggerCharacters, SuggestAdapter.supportsResolving(provider), extension.identifier);
return this._createDisposable(handle);
}

View File

@@ -6,6 +6,8 @@
import { MainContext, MainThreadLanguagesShape, IMainContext } from './extHost.protocol';
import type * as vscode from 'vscode';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters';
import { StandardTokenType, Range, Position } from 'vs/workbench/api/common/extHostTypes';
export class ExtHostLanguages {
@@ -32,4 +34,31 @@ export class ExtHostLanguages {
}
return data.document;
}
async tokenAtPosition(document: vscode.TextDocument, position: vscode.Position): Promise<vscode.TokenInformation> {
const versionNow = document.version;
const pos = typeConvert.Position.from(position);
const info = await this._proxy.$tokensAtPosition(document.uri, pos);
const defaultRange = {
type: StandardTokenType.Other,
range: document.getWordRangeAtPosition(position) ?? new Range(position.line, position.character, position.line, position.character)
};
if (!info) {
// no result
return defaultRange;
}
const result = {
range: typeConvert.Range.to(info.range),
type: typeConvert.TokenType.to(info.type)
};
if (!result.range.contains(<Position>position)) {
// bogous result
return defaultRange;
}
if (versionNow !== document.version) {
// concurrent change
return defaultRange;
}
return result;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,9 @@ import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'
import { DisposableStore } from 'vs/base/common/lifecycle';
import { score } from 'vs/editor/common/modes/languageSelector';
import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { isEqual } from 'vs/base/common/resources';
import { ResourceMap } from 'vs/base/common/map';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextDocument {
@@ -20,6 +22,7 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD
private _isClosed = false;
private _cells!: ExtHostCell[];
private _cellUris!: ResourceMap<number>;
private _cellLengths!: PrefixSumComputer;
private _cellLines!: PrefixSumComputer;
private _versionId = 0;
@@ -27,6 +30,8 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD
private readonly _onDidChange = new Emitter<void>();
readonly onDidChange: Event<void> = this._onDidChange.event;
readonly uri = URI.from({ scheme: 'vscode-concat-doc', path: generateUuid() });
constructor(
extHostNotebooks: ExtHostNotebookController,
extHostDocuments: ExtHostDocuments,
@@ -36,21 +41,24 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD
this._init();
this._disposables.add(extHostDocuments.onDidChangeDocument(e => {
let cellIdx = this._cells.findIndex(cell => isEqual(cell.uri, e.document.uri));
if (cellIdx >= 0) {
const cellIdx = this._cellUris.get(e.document.uri);
if (cellIdx !== undefined) {
this._cellLengths.changeValue(cellIdx, this._cells[cellIdx].document.getText().length + 1);
this._cellLines.changeValue(cellIdx, this._cells[cellIdx].document.lineCount);
this._versionId += 1;
this._onDidChange.fire(undefined);
}
}));
this._disposables.add(extHostNotebooks.onDidChangeNotebookDocument(e => {
if (e.document === this._notebook) {
const documentChange = (document: vscode.NotebookDocument) => {
if (document === this._notebook) {
this._init();
this._versionId += 1;
this._onDidChange.fire(undefined);
}
}));
};
this._disposables.add(extHostNotebooks.onDidChangeCellLanguage(e => documentChange(e.document)));
this._disposables.add(extHostNotebooks.onDidChangeNotebookCells(e => documentChange(e.document)));
}
dispose(): void {
@@ -64,10 +72,12 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD
private _init() {
this._cells = [];
this._cellUris = new ResourceMap();
const cellLengths: number[] = [];
const cellLineCounts: number[] = [];
for (let cell of this._notebook.cells) {
for (const cell of this._notebook.cells) {
if (cell.cellKind === CellKind.Code && (!this._selector || score(this._selector, cell.uri, cell.language, true))) {
this._cellUris.set(cell.uri, this._cells.length);
this._cells.push(<ExtHostCell>cell);
cellLengths.push(cell.document.getText().length + 1);
cellLineCounts.push(cell.document.lineCount);
@@ -84,7 +94,7 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD
getText(range?: vscode.Range): string {
if (!range) {
let result = '';
for (let cell of this._cells) {
for (const cell of this._cells) {
result += cell.document.getText() + '\n';
}
// remove last newline again
@@ -99,16 +109,16 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD
// get start and end locations and create substrings
const start = this.locationAt(range.start);
const end = this.locationAt(range.end);
const startCell = this._cells.find(cell => isEqual(cell.uri, start.uri));
const endCell = this._cells.find(cell => isEqual(cell.uri, end.uri));
const startCell = this._cells[this._cellUris.get(start.uri) ?? -1];
const endCell = this._cells[this._cellUris.get(end.uri) ?? -1];
if (!startCell || !endCell) {
return '';
} else if (startCell === endCell) {
return startCell.document.getText(new types.Range(start.range.start, end.range.end));
} else {
let a = startCell.document.getText(new types.Range(start.range.start, new types.Position(startCell.document.lineCount, 0)));
let b = endCell.document.getText(new types.Range(new types.Position(0, 0), end.range.end));
const a = startCell.document.getText(new types.Range(start.range.start, new types.Position(startCell.document.lineCount, 0)));
const b = endCell.document.getText(new types.Range(new types.Position(0, 0), end.range.end));
return a + '\n' + b;
}
}
@@ -127,9 +137,9 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD
return this._cells[idx.index].document.positionAt(idx.remainder).translate(lineCount);
}
const idx = this._cells.findIndex(cell => isEqual(cell.uri, locationOrOffset.uri));
if (idx >= 0) {
let line = this._cellLines.getAccumulatedValue(idx - 1);
const idx = this._cellUris.get(locationOrOffset.uri);
if (idx !== undefined) {
const line = this._cellLines.getAccumulatedValue(idx - 1);
return new types.Position(line + locationOrOffset.range.start.line, locationOrOffset.range.start.character);
}
// do better?
@@ -148,11 +158,31 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD
endIdx = this._cellLines.getIndexOf(positionOrRange.end.line);
}
let startPos = new types.Position(startIdx.remainder, positionOrRange.start.character);
let endPos = new types.Position(endIdx.remainder, positionOrRange.end.character);
let range = new types.Range(startPos, endPos);
const startPos = new types.Position(startIdx.remainder, positionOrRange.start.character);
const endPos = new types.Position(endIdx.remainder, positionOrRange.end.character);
const range = new types.Range(startPos, endPos);
const startCell = this._cells[startIdx.index];
return new types.Location(startCell.uri, <types.Range>startCell.document.validateRange(range));
}
contains(uri: vscode.Uri): boolean {
return this._cellUris.has(uri);
}
validateRange(range: vscode.Range): vscode.Range {
const start = this.validatePosition(range.start);
const end = this.validatePosition(range.end);
return range.with(start, end);
}
validatePosition(position: vscode.Position): vscode.Position {
const startIdx = this._cellLines.getIndexOf(position.line);
const cellPosition = new types.Position(startIdx.remainder, position.character);
const validCellPosition = this._cells[startIdx.index].document.validatePosition(cellPosition);
const line = this._cellLines.getAccumulatedValue(startIdx.index - 1);
return new types.Position(line + validCellPosition.line, validCellPosition.character);
}
}

View File

@@ -9,7 +9,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
export const IExtHostRpcService = createDecorator<IExtHostRpcService>('IExtHostRpcService');
export interface IExtHostRpcService extends IRPCProtocol {
_serviceBrand: undefined;
readonly _serviceBrand: undefined;
}
export class ExtHostRpcService implements IExtHostRpcService {
@@ -18,12 +18,12 @@ export class ExtHostRpcService implements IExtHostRpcService {
readonly getProxy: <T>(identifier: ProxyIdentifier<T>) => T;
readonly set: <T, R extends T> (identifier: ProxyIdentifier<T>, instance: R) => R;
readonly assertRegistered: (identifiers: ProxyIdentifier<any>[]) => void;
readonly drain: () => Promise<void>;
constructor(rpcProtocol: IRPCProtocol) {
this.getProxy = rpcProtocol.getProxy.bind(rpcProtocol);
this.set = rpcProtocol.set.bind(rpcProtocol);
this.assertRegistered = rpcProtocol.assertRegistered.bind(rpcProtocol);
this.drain = rpcProtocol.drain.bind(rpcProtocol);
}
}

View File

@@ -537,7 +537,7 @@ export class ExtHostSCM implements ExtHostSCMShape {
private readonly _onDidChangeActiveProvider = new Emitter<vscode.SourceControl>();
get onDidChangeActiveProvider(): Event<vscode.SourceControl> { return this._onDidChangeActiveProvider.event; }
private _selectedSourceControlHandles = new Set<number>();
private _selectedSourceControlHandle: number | undefined;
constructor(
mainContext: IMainContext,
@@ -682,40 +682,18 @@ export class ExtHostSCM implements ExtHostSCMShape {
});
}
$setSelectedSourceControls(selectedSourceControlHandles: number[]): Promise<void> {
this.logService.trace('ExtHostSCM#$setSelectedSourceControls', selectedSourceControlHandles);
$setSelectedSourceControl(selectedSourceControlHandle: number | undefined): Promise<void> {
this.logService.trace('ExtHostSCM#$setSelectedSourceControl', selectedSourceControlHandle);
const set = new Set<number>();
for (const handle of selectedSourceControlHandles) {
set.add(handle);
if (selectedSourceControlHandle !== undefined) {
this._sourceControls.get(selectedSourceControlHandle)?.setSelectionState(true);
}
set.forEach(handle => {
if (!this._selectedSourceControlHandles.has(handle)) {
const sourceControl = this._sourceControls.get(handle);
if (this._selectedSourceControlHandle !== undefined) {
this._sourceControls.get(this._selectedSourceControlHandle)?.setSelectionState(false);
}
if (!sourceControl) {
return;
}
sourceControl.setSelectionState(true);
}
});
this._selectedSourceControlHandles.forEach(handle => {
if (!set.has(handle)) {
const sourceControl = this._sourceControls.get(handle);
if (!sourceControl) {
return;
}
sourceControl.setSelectionState(false);
}
});
this._selectedSourceControlHandles = set;
this._selectedSourceControlHandle = selectedSourceControlHandle;
return Promise.resolve(undefined);
}
}

View File

@@ -35,8 +35,9 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
private _timeoutHandle: any;
private _proxy: MainThreadStatusBarShape;
private _commands: CommandsConverter;
private _accessibilityInformation?: vscode.AccessibilityInformation;
constructor(proxy: MainThreadStatusBarShape, commands: CommandsConverter, id: string, name: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number) {
constructor(proxy: MainThreadStatusBarShape, commands: CommandsConverter, id: string, name: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number, accessibilityInformation?: vscode.AccessibilityInformation) {
this._id = ExtHostStatusBarEntry.ID_GEN++;
this._proxy = proxy;
this._commands = commands;
@@ -44,6 +45,7 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
this._statusName = name;
this._alignment = alignment;
this._priority = priority;
this._accessibilityInformation = accessibilityInformation;
}
public get id(): number {
@@ -74,6 +76,10 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
return this._command?.fromApi;
}
public get accessibilityInformation(): vscode.AccessibilityInformation | undefined {
return this._accessibilityInformation;
}
public set text(text: string) {
this._text = text;
this.update();
@@ -111,6 +117,11 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
this.update();
}
public set accessibilityInformation(accessibilityInformation: vscode.AccessibilityInformation | undefined) {
this._accessibilityInformation = accessibilityInformation;
this.update();
}
public show(): void {
this._visible = true;
this.update();
@@ -136,7 +147,7 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
// Set to status bar
this._proxy.$setEntry(this.id, this._statusId, this._statusName, this.text, this.tooltip, this._command?.internal, this.color,
this._alignment === ExtHostStatusBarAlignment.Left ? MainThreadStatusBarAlignment.LEFT : MainThreadStatusBarAlignment.RIGHT,
this._priority);
this._priority, this._accessibilityInformation);
}, 0);
}
@@ -196,8 +207,8 @@ export class ExtHostStatusBar {
this._statusMessage = new StatusBarMessage(this);
}
createStatusBarEntry(id: string, name: string, alignment?: ExtHostStatusBarAlignment, priority?: number): vscode.StatusBarItem {
return new ExtHostStatusBarEntry(this._proxy, this._commands, id, name, alignment, priority);
createStatusBarEntry(id: string, name: string, alignment?: ExtHostStatusBarAlignment, priority?: number, accessibilityInformation?: vscode.AccessibilityInformation): vscode.StatusBarItem {
return new ExtHostStatusBarEntry(this._proxy, this._commands, id, name, alignment, priority, accessibilityInformation);
}
setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable<any>): Disposable {

View File

@@ -5,12 +5,83 @@
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { ILogService } from 'vs/platform/log/common/log';
import { IEnvironment, IStaticWorkspaceData } from 'vs/workbench/api/common/extHost.protocol';
import { IExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer';
import { URI } from 'vs/base/common/uri';
export const IExtensionStoragePaths = createDecorator<IExtensionStoragePaths>('IExtensionStoragePaths');
export interface IExtensionStoragePaths {
_serviceBrand: undefined;
readonly _serviceBrand: undefined;
whenReady: Promise<any>;
workspaceValue(extension: IExtensionDescription): string | undefined;
globalValue(extension: IExtensionDescription): string;
workspaceValue(extension: IExtensionDescription): URI | undefined;
globalValue(extension: IExtensionDescription): URI;
}
export class ExtensionStoragePaths implements IExtensionStoragePaths {
readonly _serviceBrand: undefined;
private readonly _workspace?: IStaticWorkspaceData;
private readonly _environment: IEnvironment;
readonly whenReady: Promise<URI | undefined>;
private _value?: URI;
constructor(
@IExtHostInitDataService initData: IExtHostInitDataService,
@ILogService private readonly _logService: ILogService,
@IExtHostConsumerFileSystem private readonly _extHostFileSystem: IExtHostConsumerFileSystem
) {
this._workspace = initData.workspace ?? undefined;
this._environment = initData.environment;
this.whenReady = this._getOrCreateWorkspaceStoragePath().then(value => this._value = value);
}
private async _getOrCreateWorkspaceStoragePath(): Promise<URI | undefined> {
if (!this._workspace) {
return Promise.resolve(undefined);
}
const storageName = this._workspace.id;
const storageUri = URI.joinPath(this._environment.workspaceStorageHome, storageName);
try {
await this._extHostFileSystem.stat(storageUri);
this._logService.trace('[ExtHostStorage] storage dir already exists', storageUri);
return storageUri;
} catch {
// doesn't exist, that's OK
}
try {
this._logService.trace('[ExtHostStorage] creating dir and metadata-file', storageUri);
await this._extHostFileSystem.createDirectory(storageUri);
await this._extHostFileSystem.writeFile(
URI.joinPath(storageUri, 'meta.json'),
new TextEncoder().encode(JSON.stringify({
id: this._workspace.id,
configuration: URI.revive(this._workspace.configuration)?.toString(),
name: this._workspace.name
}, undefined, 2))
);
return storageUri;
} catch (e) {
this._logService.error('[ExtHostStorage]', e);
return undefined;
}
}
workspaceValue(extension: IExtensionDescription): URI | undefined {
if (this._value) {
return URI.joinPath(this._value, extension.identifier.value);
}
return undefined;
}
globalValue(extension: IExtensionDescription): URI {
return URI.joinPath(this._environment.globalStorageHome, extension.identifier.value.toLowerCase());
}
}

View File

@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { URI, UriComponents } from 'vs/base/common/uri';
import * as Objects from 'vs/base/common/objects';
import { asPromise } from 'vs/base/common/async';
import { Event, Emitter } from 'vs/base/common/event';
@@ -27,6 +26,7 @@ import * as Platform from 'vs/base/common/platform';
import { ILogService } from 'vs/platform/log/common/log';
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
import { USER_TASKS_GROUP_KEY } from 'vs/workbench/contrib/tasks/common/taskService';
import { NotSupportedError } from 'vs/base/common/errors';
export interface IExtHostTask extends ExtHostTaskShape {
@@ -325,7 +325,7 @@ export namespace TaskFilterDTO {
if (!value) {
return undefined;
}
return Objects.assign(Object.create(null), value);
return Object.assign(Object.create(null), value);
}
}
@@ -371,7 +371,7 @@ export interface HandlerData {
extension: IExtensionDescription;
}
export abstract class ExtHostTaskBase implements ExtHostTaskShape {
export abstract class ExtHostTaskBase implements ExtHostTaskShape, IExtHostTask {
readonly _serviceBrand: undefined;
protected readonly _proxy: MainThreadTaskShape;
@@ -384,6 +384,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape {
protected _handleCounter: number;
protected _handlers: Map<number, HandlerData>;
protected _taskExecutions: Map<string, TaskExecutionImpl>;
protected _taskExecutionPromises: Map<string, Promise<TaskExecutionImpl>>;
protected _providedCustomExecutions2: Map<string, types.CustomExecution>;
private _notProvidedCustomExecutions: Set<string>; // Used for custom executions tasks that are created and run through executeTask.
protected _activeCustomExecutions2: Map<string, types.CustomExecution>;
@@ -412,11 +413,13 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape {
this._handleCounter = 0;
this._handlers = new Map<number, HandlerData>();
this._taskExecutions = new Map<string, TaskExecutionImpl>();
this._taskExecutionPromises = new Map<string, Promise<TaskExecutionImpl>>();
this._providedCustomExecutions2 = new Map<string, types.CustomExecution>();
this._notProvidedCustomExecutions = new Set<string>();
this._activeCustomExecutions2 = new Map<string, types.CustomExecution>();
this._logService = logService;
this._deprecationService = deprecationService;
this._proxy.$registerSupportedExecutions(true);
}
public registerTaskProvider(extension: IExtensionDescription, type: string, provider: vscode.TaskProvider): vscode.Disposable {
@@ -496,6 +499,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape {
public async $OnDidEndTask(execution: tasks.TaskExecutionDTO): Promise<void> {
const _execution = await this.getTaskExecution(execution);
this._taskExecutionPromises.delete(execution.id);
this._taskExecutions.delete(execution.id);
this.customExecutionComplete(execution);
this._onDidTerminateTask.fire({
@@ -509,12 +513,10 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape {
public async $onDidStartTaskProcess(value: tasks.TaskProcessStartedDTO): Promise<void> {
const execution = await this.getTaskExecution(value.id);
if (execution) {
this._onDidTaskProcessStarted.fire({
execution: execution,
processId: value.processId
});
}
this._onDidTaskProcessStarted.fire({
execution: execution,
processId: value.processId
});
}
public get onDidEndTaskProcess(): Event<vscode.TaskProcessEndEvent> {
@@ -523,12 +525,10 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape {
public async $onDidEndTaskProcess(value: tasks.TaskProcessEndedDTO): Promise<void> {
const execution = await this.getTaskExecution(value.id);
if (execution) {
this._onDidTaskProcessEnded.fire({
execution: execution,
exitCode: value.exitCode
});
}
this._onDidTaskProcessEnded.fire({
execution: execution,
exitCode: value.exitCode
});
}
protected abstract provideTasksInternal(validTypes: { [key: string]: boolean; }, taskIdPromises: Promise<void>[], handler: HandlerData, value: vscode.Task[] | null | undefined): { tasks: tasks.TaskDTO[], extension: IExtensionDescription };
@@ -619,24 +619,33 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape {
protected async getTaskExecution(execution: tasks.TaskExecutionDTO | string, task?: vscode.Task): Promise<TaskExecutionImpl> {
if (typeof execution === 'string') {
const taskExecution = this._taskExecutions.get(execution);
const taskExecution = this._taskExecutionPromises.get(execution);
if (!taskExecution) {
throw new Error('Unexpected: The specified task is missing an execution');
}
return taskExecution;
}
let result: TaskExecutionImpl | undefined = this._taskExecutions.get(execution.id);
let result: Promise<TaskExecutionImpl> | undefined = this._taskExecutionPromises.get(execution.id);
if (result) {
return result;
}
const taskToCreate = task ? task : await TaskDTO.to(execution.task, this._workspaceProvider);
if (!taskToCreate) {
throw new Error('Unexpected: Task does not exist.');
}
const createdResult: TaskExecutionImpl = new TaskExecutionImpl(this, execution.id, taskToCreate);
this._taskExecutions.set(execution.id, createdResult);
return createdResult;
const createdResult: Promise<TaskExecutionImpl> = new Promise(async (resolve, reject) => {
const taskToCreate = task ? task : await TaskDTO.to(execution.task, this._workspaceProvider);
if (!taskToCreate) {
reject('Unexpected: Task does not exist.');
} else {
resolve(new TaskExecutionImpl(this, execution.id, taskToCreate));
}
});
this._taskExecutionPromises.set(execution.id, createdResult);
return createdResult.then(executionCreatedResult => {
this._taskExecutions.set(execution.id, executionCreatedResult);
return executionCreatedResult;
}, rejected => {
return Promise.reject(rejected);
});
}
protected checkDeprecation(task: vscode.Task, handler: HandlerData) {
@@ -671,7 +680,9 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape {
}
}
public abstract async $jsonTasksSupported(): Promise<boolean>;
public abstract $jsonTasksSupported(): Promise<boolean>;
public abstract $findExecutable(command: string, cwd?: string | undefined, paths?: string[] | undefined): Promise<string | undefined>;
}
export class WorkerExtHostTask extends ExtHostTaskBase {
@@ -686,19 +697,17 @@ export class WorkerExtHostTask extends ExtHostTaskBase {
@IExtHostApiDeprecationService deprecationService: IExtHostApiDeprecationService
) {
super(extHostRpc, initData, workspaceService, editorService, configurationService, extHostTerminalService, logService, deprecationService);
if (initData.remote.isRemote && initData.remote.authority) {
this.registerTaskSystem(Schemas.vscodeRemote, {
scheme: Schemas.vscodeRemote,
authority: initData.remote.authority,
platform: Platform.PlatformToString(Platform.Platform.Web)
});
}
this.registerTaskSystem(Schemas.vscodeRemote, {
scheme: Schemas.vscodeRemote,
authority: '',
platform: Platform.PlatformToString(Platform.Platform.Web)
});
}
public async executeTask(extension: IExtensionDescription, task: vscode.Task): Promise<vscode.TaskExecution> {
const dto = TaskDTO.from(task, extension);
if (dto === undefined) {
return Promise.reject(new Error('Task is not valid'));
throw new Error('Task is not valid');
}
// If this task is a custom execution, then we need to save it away
@@ -707,10 +716,13 @@ export class WorkerExtHostTask extends ExtHostTaskBase {
if (CustomExecutionDTO.is(dto.execution)) {
await this.addCustomExecution(dto, task, false);
} else {
throw new Error('Not implemented');
throw new NotSupportedError();
}
return this._proxy.$executeTask(dto).then(value => this.getTaskExecution(value, task));
// Always get the task execution first to prevent timing issues when retrieving it later
const execution = await this.getTaskExecution(await this._proxy.$getTaskExecution(dto), task);
this._proxy.$executeTask(dto).catch(error => { throw new Error(error); });
return execution;
}
protected provideTasksInternal(validTypes: { [key: string]: boolean; }, taskIdPromises: Promise<void>[], handler: HandlerData, value: vscode.Task[] | null | undefined): { tasks: tasks.TaskDTO[], extension: IExtensionDescription } {
@@ -764,6 +776,10 @@ export class WorkerExtHostTask extends ExtHostTaskBase {
public async $jsonTasksSupported(): Promise<boolean> {
return false;
}
public async $findExecutable(command: string, cwd?: string | undefined, paths?: string[] | undefined): Promise<string | undefined> {
return undefined;
}
}
export const IExtHostTask = createDecorator<IExtHostTask>('IExtHostTask');

View File

@@ -5,11 +5,11 @@
import type * as vscode from 'vscode';
import { Event, Emitter } from 'vs/base/common/event';
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto, ITerminalDimensionsDto } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto, ITerminalDimensionsDto, ITerminalLinkDto } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ITerminalChildProcess, ITerminalDimensions, EXT_HOST_CREATION_DELAY } from 'vs/workbench/contrib/terminal/common/terminal';
import { ITerminalChildProcess, ITerminalDimensions, EXT_HOST_CREATION_DELAY, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
import { timeout } from 'vs/base/common/async';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering';
@@ -17,10 +17,14 @@ import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Disposable as VSCodeDisposable, EnvironmentVariableMutatorType } from './extHostTypes';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { localize } from 'vs/nls';
import { NotSupportedError } from 'vs/base/common/errors';
import { serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
export interface IExtHostTerminalService extends ExtHostTerminalServiceShape {
_serviceBrand: undefined;
readonly _serviceBrand: undefined;
activeTerminal: vscode.Terminal | undefined;
terminals: vscode.Terminal[];
@@ -38,6 +42,7 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape {
getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string;
getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string;
registerLinkHandler(handler: vscode.TerminalLinkHandler): vscode.Disposable;
registerLinkProvider(provider: vscode.TerminalLinkProvider): vscode.Disposable;
getEnvironmentVariableCollection(extension: IExtensionDescription, persistent?: boolean): vscode.EnvironmentVariableCollection;
}
@@ -243,6 +248,10 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess {
constructor(private readonly _pty: vscode.Pseudoterminal) { }
async start(): Promise<undefined> {
return undefined;
}
shutdown(): void {
this._pty.close();
}
@@ -288,6 +297,13 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess {
}
}
let nextLinkId = 1;
interface ICachedLinkEntry {
provider: vscode.TerminalLinkProvider;
link: vscode.TerminalLink;
}
export abstract class BaseExtHostTerminalService implements IExtHostTerminalService, ExtHostTerminalServiceShape {
readonly _serviceBrand: undefined;
@@ -299,9 +315,13 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
protected _terminalProcessDisposables: { [id: number]: IDisposable } = {};
protected _extensionTerminalAwaitingStart: { [id: number]: { initialDimensions: ITerminalDimensionsDto | undefined } | undefined } = {};
protected _getTerminalPromises: { [id: number]: Promise<ExtHostTerminal> } = {};
protected _environmentVariableCollections: Map<string, EnvironmentVariableCollection> = new Map();
private readonly _bufferer: TerminalDataBufferer;
private readonly _linkHandlers: Set<vscode.TerminalLinkHandler> = new Set();
private readonly _linkProviders: Set<vscode.TerminalLinkProvider> = new Set();
private readonly _terminalLinkCache: Map<number, Map<number, ICachedLinkEntry>> = new Map();
private readonly _terminalLinkCancellationSource: Map<number, CancellationTokenSource> = new Map();
public get activeTerminal(): ExtHostTerminal | undefined { return this._activeTerminal; }
public get terminals(): ExtHostTerminal[] { return this._terminals; }
@@ -332,12 +352,10 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
public abstract createTerminalFromOptions(options: vscode.TerminalOptions): vscode.Terminal;
public abstract getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string;
public abstract getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string;
public abstract $spawnExtHostProcess(id: number, shellLaunchConfigDto: IShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise<void>;
public abstract $spawnExtHostProcess(id: number, shellLaunchConfigDto: IShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise<ITerminalLaunchError | undefined>;
public abstract $getAvailableShells(): Promise<IShellDefinitionDto[]>;
public abstract $getDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto>;
public abstract $acceptWorkspacePermissionsChanged(isAllowed: boolean): void;
public abstract getEnvironmentVariableCollection(extension: IExtensionDescription, persistent?: boolean): vscode.EnvironmentVariableCollection;
public abstract $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void;
public createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal {
const terminal = new ExtHostTerminal(this._proxy, options, options.name);
@@ -439,7 +457,8 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
shellPath: shellLaunchConfigDto.executable,
shellArgs: shellLaunchConfigDto.args,
cwd: typeof shellLaunchConfigDto.cwd === 'string' ? shellLaunchConfigDto.cwd : URI.revive(shellLaunchConfigDto.cwd),
env: shellLaunchConfigDto.env
env: shellLaunchConfigDto.env,
hideFromUser: shellLaunchConfigDto.hideFromUser
};
const terminal = new ExtHostTerminal(this._proxy, creationOptions, name, id);
this._terminals.push(terminal);
@@ -454,20 +473,17 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
}
}
public async $startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise<void> {
public async $startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise<ITerminalLaunchError | undefined> {
// Make sure the ExtHostTerminal exists so onDidOpenTerminal has fired before we call
// Pseudoterminal.start
const terminal = await this._getTerminalByIdEventually(id);
if (!terminal) {
return;
return { message: localize('launchFail.idMissingOnExtHost', "Could not find the terminal with id {0} on the extension host", id) };
}
// Wait for onDidOpenTerminal to fire
let openPromise: Promise<void>;
if (terminal.isOpen) {
openPromise = Promise.resolve();
} else {
openPromise = new Promise<void>(r => {
if (!terminal.isOpen) {
await new Promise<void>(r => {
// Ensure open is called after onDidOpenTerminal
const listener = this.onDidOpenTerminal(async e => {
if (e === terminal) {
@@ -477,7 +493,6 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
});
});
}
await openPromise;
if (this._terminalProcesses[id]) {
(this._terminalProcesses[id] as ExtHostPseudoterminal).startSendingEvents(initialDimensions);
@@ -486,6 +501,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
this._extensionTerminalAwaitingStart[id] = { initialDimensions };
}
return undefined;
}
protected _setupExtHostProcessListeners(id: number, p: ITerminalChildProcess): IDisposable {
@@ -545,17 +561,30 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
public registerLinkHandler(handler: vscode.TerminalLinkHandler): vscode.Disposable {
this._linkHandlers.add(handler);
if (this._linkHandlers.size === 1) {
if (this._linkHandlers.size === 1 && this._linkProviders.size === 0) {
this._proxy.$startHandlingLinks();
}
return new VSCodeDisposable(() => {
this._linkHandlers.delete(handler);
if (this._linkHandlers.size === 0) {
if (this._linkHandlers.size === 0 && this._linkProviders.size === 0) {
this._proxy.$stopHandlingLinks();
}
});
}
public registerLinkProvider(provider: vscode.TerminalLinkProvider): vscode.Disposable {
this._linkProviders.add(provider);
if (this._linkProviders.size === 1) {
this._proxy.$startLinkProvider();
}
return new VSCodeDisposable(() => {
this._linkProviders.delete(provider);
if (this._linkProviders.size === 0) {
this._proxy.$stopLinkProvider();
}
});
}
public async $handleLink(id: number, link: string): Promise<boolean> {
const terminal = this._getTerminalById(id);
if (!terminal) {
@@ -575,6 +604,75 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
return false;
}
public async $provideLinks(terminalId: number, line: string): Promise<ITerminalLinkDto[]> {
const terminal = this._getTerminalById(terminalId);
if (!terminal) {
return [];
}
// Discard any cached links the terminal has been holding, currently all links are released
// when new links are provided.
this._terminalLinkCache.delete(terminalId);
const oldToken = this._terminalLinkCancellationSource.get(terminalId);
if (oldToken) {
oldToken.dispose(true);
}
const cancellationSource = new CancellationTokenSource();
this._terminalLinkCancellationSource.set(terminalId, cancellationSource);
const result: ITerminalLinkDto[] = [];
const context: vscode.TerminalLinkContext = { terminal, line };
const promises: vscode.ProviderResult<{ provider: vscode.TerminalLinkProvider, links: vscode.TerminalLink[] }>[] = [];
for (const provider of this._linkProviders) {
promises.push(new Promise(async r => {
cancellationSource.token.onCancellationRequested(() => r({ provider, links: [] }));
const links = (await provider.provideTerminalLinks(context, cancellationSource.token)) || [];
if (!cancellationSource.token.isCancellationRequested) {
r({ provider, links });
}
}));
}
const provideResults = await Promise.all(promises);
if (cancellationSource.token.isCancellationRequested) {
return [];
}
const cacheLinkMap = new Map<number, ICachedLinkEntry>();
for (const provideResult of provideResults) {
if (provideResult && provideResult.links.length > 0) {
result.push(...provideResult.links.map(providerLink => {
const link = {
id: nextLinkId++,
startIndex: providerLink.startIndex,
length: providerLink.length,
label: providerLink.tooltip
};
cacheLinkMap.set(link.id, {
provider: provideResult.provider,
link: providerLink
});
return link;
}));
}
}
this._terminalLinkCache.set(terminalId, cacheLinkMap);
return result;
}
$activateLink(terminalId: number, linkId: number): void {
const cachedLink = this._terminalLinkCache.get(terminalId)?.get(linkId);
if (!cachedLink) {
return;
}
cachedLink.provider.handleTerminalLink(cachedLink.link);
}
private _onProcessExit(id: number, exitCode: number | undefined): void {
this._bufferer.stopBuffering(id);
@@ -640,6 +738,39 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
});
return index;
}
public getEnvironmentVariableCollection(extension: IExtensionDescription): vscode.EnvironmentVariableCollection {
let collection = this._environmentVariableCollections.get(extension.identifier.value);
if (!collection) {
collection = new EnvironmentVariableCollection();
this._setEnvironmentVariableCollection(extension.identifier.value, collection);
}
return collection;
}
private _syncEnvironmentVariableCollection(extensionIdentifier: string, collection: EnvironmentVariableCollection): void {
const serialized = serializeEnvironmentVariableCollection(collection.map);
this._proxy.$setEnvironmentVariableCollection(extensionIdentifier, collection.persistent, serialized.length === 0 ? undefined : serialized);
}
public $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void {
collections.forEach(entry => {
const extensionIdentifier = entry[0];
const collection = new EnvironmentVariableCollection(entry[1]);
this._setEnvironmentVariableCollection(extensionIdentifier, collection);
});
}
private _setEnvironmentVariableCollection(extensionIdentifier: string, collection: EnvironmentVariableCollection): void {
this._environmentVariableCollections.set(extensionIdentifier, collection);
collection.onDidChangeCollection(() => {
// When any collection value changes send this immediately, this is done to ensure
// following calls to createTerminal will be created with the new environment. It will
// result in more noise by sending multiple updates when called but collections are
// expected to be small.
this._syncEnvironmentVariableCollection(extensionIdentifier, collection!);
});
}
}
export class EnvironmentVariableCollection implements vscode.EnvironmentVariableCollection {
@@ -706,43 +837,35 @@ export class EnvironmentVariableCollection implements vscode.EnvironmentVariable
export class WorkerExtHostTerminalService extends BaseExtHostTerminalService {
public createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal {
throw new Error('Not implemented');
throw new NotSupportedError();
}
public createTerminalFromOptions(options: vscode.TerminalOptions): vscode.Terminal {
throw new Error('Not implemented');
throw new NotSupportedError();
}
public getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string {
throw new Error('Not implemented');
// Return the empty string to avoid throwing
return '';
}
public getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string {
throw new Error('Not implemented');
throw new NotSupportedError();
}
public $spawnExtHostProcess(id: number, shellLaunchConfigDto: IShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise<void> {
throw new Error('Not implemented');
public $spawnExtHostProcess(id: number, shellLaunchConfigDto: IShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise<ITerminalLaunchError | undefined> {
throw new NotSupportedError();
}
public $getAvailableShells(): Promise<IShellDefinitionDto[]> {
throw new Error('Not implemented');
throw new NotSupportedError();
}
public async $getDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto> {
throw new Error('Not implemented');
throw new NotSupportedError();
}
public $acceptWorkspacePermissionsChanged(isAllowed: boolean): void {
// No-op for web worker ext host as workspace permissions aren't used
}
public getEnvironmentVariableCollection(extension: IExtensionDescription, persistent?: boolean): vscode.EnvironmentVariableCollection {
// This is not implemented so worker ext host extensions cannot influence terminal envs
throw new Error('Not implemented');
}
public $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void {
// No-op for web worker ext host as collections aren't used
}
}

View File

@@ -54,7 +54,7 @@ export class ExtHostEditors implements ExtHostEditorsShape {
showTextDocument(document: vscode.TextDocument, column: vscode.ViewColumn, preserveFocus: boolean): Promise<vscode.TextEditor>;
showTextDocument(document: vscode.TextDocument, options: { column: vscode.ViewColumn, preserveFocus: boolean, pinned: boolean }): Promise<vscode.TextEditor>;
showTextDocument(document: vscode.TextDocument, columnOrOptions: vscode.ViewColumn | vscode.TextDocumentShowOptions | undefined, preserveFocus?: boolean): Promise<vscode.TextEditor>;
showTextDocument(document: vscode.TextDocument, columnOrOptions: vscode.ViewColumn | vscode.TextDocumentShowOptions | undefined, preserveFocus?: boolean): Promise<vscode.TextEditor> {
async showTextDocument(document: vscode.TextDocument, columnOrOptions: vscode.ViewColumn | vscode.TextDocumentShowOptions | undefined, preserveFocus?: boolean): Promise<vscode.TextEditor> {
let options: ITextDocumentShowOptions;
if (typeof columnOrOptions === 'number') {
options = {
@@ -74,14 +74,18 @@ export class ExtHostEditors implements ExtHostEditorsShape {
};
}
return this._proxy.$tryShowTextDocument(document.uri, options).then(id => {
const editor = id && this._extHostDocumentsAndEditors.getEditor(id);
if (editor) {
return editor;
} else {
throw new Error(`Failed to show text document ${document.uri.toString()}, should show in editor #${id}`);
}
});
const editorId = await this._proxy.$tryShowTextDocument(document.uri, options);
const editor = editorId && this._extHostDocumentsAndEditors.getEditor(editorId);
if (editor) {
return editor;
}
// we have no editor... having an id means that we had an editor
// on the main side and that it isn't the current editor anymore...
if (editorId) {
throw new Error(`Could NOT open editor for "${document.uri.toString()}" because another editor opened in the meantime.`);
} else {
throw new Error(`Could NOT open editor for "${document.uri.toString()}".`);
}
}
createTextEditorDecorationType(options: vscode.DecorationRenderOptions): vscode.TextEditorDecorationType {

View File

@@ -22,7 +22,7 @@ export interface IExtHostTimeline extends ExtHostTimelineShape {
export const IExtHostTimeline = createDecorator<IExtHostTimeline>('IExtHostTimeline');
export class ExtHostTimeline implements IExtHostTimeline {
_serviceBrand: undefined;
declare readonly _serviceBrand: undefined;
private _proxy: MainThreadTimelineShape;
@@ -78,9 +78,7 @@ export class ExtHostTimeline implements IExtHostTimeline {
}
const result = await provider.provideTimeline(uri, options, token);
// Intentional == we don't know how a provider will respond
// eslint-disable-next-line eqeqeq
if (result == null) {
if (result === undefined || result === null) {
return undefined;
}
@@ -152,7 +150,8 @@ export class ExtHostTimeline implements IExtHostTimeline {
command: item.command ? commandConverter.toInternal(item.command, disposables) : undefined,
icon: icon,
iconDark: iconDark,
themeIcon: themeIcon
themeIcon: themeIcon,
accessibilityInformation: item.accessibilityInformation
};
};
};
@@ -188,4 +187,3 @@ export class ExtHostTimeline implements IExtHostTimeline {
function getUriKey(uri: URI | undefined): string | undefined {
return uri?.toString();
}

View File

@@ -13,12 +13,14 @@ import { ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.proto
import { ITreeItem, TreeViewItemHandleArg, ITreeItemLabel, IRevealOptions } from 'vs/workbench/common/views';
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands';
import { asPromise } from 'vs/base/common/async';
import { TreeItemCollapsibleState, ThemeIcon } from 'vs/workbench/api/common/extHostTypes';
import { TreeItemCollapsibleState, ThemeIcon, MarkdownString as MarkdownStringType } from 'vs/workbench/api/common/extHostTypes';
import { isUndefinedOrNull, isString } from 'vs/base/common/types';
import { equals, coalesce } from 'vs/base/common/arrays';
import { ILogService } from 'vs/platform/log/common/log';
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { MarkdownString } from 'vs/workbench/api/common/extHostTypeConverters';
import { IMarkdownString } from 'vs/base/common/htmlContent';
type TreeItemHandle = string;
@@ -117,6 +119,22 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
return treeView.getChildren(treeItemHandle);
}
async $hasResolve(treeViewId: string): Promise<boolean> {
const treeView = this.treeViews.get(treeViewId);
if (!treeView) {
throw new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId));
}
return treeView.hasResolve;
}
$resolve(treeViewId: string, treeItemHandle: string): Promise<ITreeItem | undefined> {
const treeView = this.treeViews.get(treeViewId);
if (!treeView) {
throw new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId));
}
return treeView.resolveTreeItem(treeItemHandle);
}
$setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void {
const treeView = this.treeViews.get(treeViewId);
if (!treeView) {
@@ -158,6 +176,7 @@ type TreeData<T> = { message: boolean, element: T | Root | false };
interface TreeNode extends IDisposable {
item: ITreeItem;
extensionItem: vscode.TreeItem2;
parent: TreeNode | Root;
children?: TreeNode[];
}
@@ -327,6 +346,27 @@ class ExtHostTreeView<T> extends Disposable {
}
}
get hasResolve(): boolean {
return !!this.dataProvider.resolveTreeItem;
}
async resolveTreeItem(treeItemHandle: string): Promise<ITreeItem | undefined> {
if (!this.dataProvider.resolveTreeItem) {
return;
}
const element = this.elements.get(treeItemHandle);
if (element) {
const node = this.nodes.get(element);
if (node) {
const resolve = await this.dataProvider.resolveTreeItem(element, node.extensionItem);
// Resolvable elements. Currently only tooltip.
node.item.tooltip = this.getTooltip(resolve.tooltip);
return node.item;
}
}
return;
}
private resolveUnknownParentChain(element: T): Promise<TreeNode[]> {
return this.resolveParent(element)
.then((parent) => {
@@ -486,7 +526,15 @@ class ExtHostTreeView<T> extends Disposable {
return node;
}
private createTreeNode(element: T, extensionTreeItem: vscode.TreeItem, parent: TreeNode | Root): TreeNode {
private getTooltip(tooltip?: string | vscode.MarkdownString): string | IMarkdownString | undefined {
if (MarkdownStringType.isMarkdownString(tooltip)) {
checkProposedApiEnabled(this.extension);
return MarkdownString.from(tooltip);
}
return tooltip;
}
private createTreeNode(element: T, extensionTreeItem: vscode.TreeItem2, parent: TreeNode | Root): TreeNode {
const disposable = new DisposableStore();
const handle = this.createHandle(element, extensionTreeItem, parent);
const icon = this.getLightIconPath(extensionTreeItem);
@@ -496,17 +544,19 @@ class ExtHostTreeView<T> extends Disposable {
label: toTreeItemLabel(extensionTreeItem.label, this.extension),
description: extensionTreeItem.description,
resourceUri: extensionTreeItem.resourceUri,
tooltip: typeof extensionTreeItem.tooltip === 'string' ? extensionTreeItem.tooltip : undefined,
tooltip: this.getTooltip(extensionTreeItem.tooltip),
command: extensionTreeItem.command ? this.commands.toInternal(extensionTreeItem.command, disposable) : undefined,
contextValue: extensionTreeItem.contextValue,
icon,
iconDark: this.getDarkIconPath(extensionTreeItem) || icon,
themeIcon: extensionTreeItem.iconPath instanceof ThemeIcon ? { id: extensionTreeItem.iconPath.id } : undefined,
collapsibleState: isUndefinedOrNull(extensionTreeItem.collapsibleState) ? TreeItemCollapsibleState.None : extensionTreeItem.collapsibleState
collapsibleState: isUndefinedOrNull(extensionTreeItem.collapsibleState) ? TreeItemCollapsibleState.None : extensionTreeItem.collapsibleState,
accessibilityInformation: extensionTreeItem.accessibilityInformation
};
return {
item,
extensionItem: extensionTreeItem,
parent,
children: undefined,
dispose(): void { disposable.dispose(); }

View File

@@ -41,7 +41,7 @@ export interface IExtHostTunnelService extends ExtHostTunnelServiceShape {
export const IExtHostTunnelService = createDecorator<IExtHostTunnelService>('IExtHostTunnelService');
export class ExtHostTunnelService implements IExtHostTunnelService {
_serviceBrand: undefined;
declare readonly _serviceBrand: undefined;
onDidChangeTunnels: vscode.Event<void> = (new Emitter<void>()).event;
private readonly _proxy: MainThreadTunnelServiceShape;

View File

@@ -95,11 +95,22 @@ export namespace Range {
}
}
export namespace TokenType {
export function to(type: modes.StandardTokenType): types.StandardTokenType {
switch (type) {
case modes.StandardTokenType.Comment: return types.StandardTokenType.Comment;
case modes.StandardTokenType.Other: return types.StandardTokenType.Other;
case modes.StandardTokenType.RegEx: return types.StandardTokenType.RegEx;
case modes.StandardTokenType.String: return types.StandardTokenType.String;
}
}
}
export namespace Position {
export function to(position: IPosition): types.Position {
return new types.Position(position.lineNumber - 1, position.column - 1);
}
export function from(position: types.Position): IPosition {
export function from(position: types.Position | vscode.Position): IPosition {
return { lineNumber: position.line + 1, column: position.character + 1 };
}
}
@@ -635,7 +646,7 @@ export namespace DocumentSymbol {
range: Range.from(info.range),
selectionRange: Range.from(info.selectionRange),
kind: SymbolKind.from(info.kind),
tags: info.tags ? info.tags.map(SymbolTag.from) : []
tags: info.tags?.map(SymbolTag.from) ?? []
};
if (info.children) {
result.children = info.children.map(from);
@@ -900,7 +911,7 @@ export namespace CompletionItem {
result.insertText = suggestion.insertText;
result.kind = CompletionItemKind.to(suggestion.kind);
result.tags = suggestion.tags && suggestion.tags.map(CompletionItemTag.to);
result.tags = suggestion.tags?.map(CompletionItemTag.to);
result.detail = suggestion.detail;
result.documentation = htmlContent.isMarkdownString(suggestion.documentation) ? MarkdownString.to(suggestion.documentation) : suggestion.documentation;
result.sortText = suggestion.sortText;
@@ -1153,15 +1164,20 @@ export namespace FoldingRangeKind {
}
}
export namespace TextEditorOptions {
export interface TextEditorOpenOptions extends vscode.TextDocumentShowOptions {
background?: boolean;
}
export function from(options?: vscode.TextDocumentShowOptions): ITextEditorOptions | undefined {
export namespace TextEditorOpenOptions {
export function from(options?: TextEditorOpenOptions): ITextEditorOptions | undefined {
if (options) {
return {
pinned: typeof options.preview === 'boolean' ? !options.preview : undefined,
inactive: options.background,
preserveFocus: options.preserveFocus,
selection: typeof options.selection === 'object' ? Range.from(options.selection) : undefined
} as ITextEditorOptions;
selection: typeof options.selection === 'object' ? Range.from(options.selection) : undefined,
};
}
return undefined;

View File

@@ -1252,7 +1252,7 @@ export class MarkdownString {
// escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash
this.value += (this.supportThemeIcons ? escapeCodicons(value) : value)
.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&')
.replace('\n', '\n\n');
.replace(/\n/, '\n\n');
return this;
}
@@ -1271,6 +1271,13 @@ export class MarkdownString {
this.value += '\n```\n';
return this;
}
static isMarkdownString(thing: any): thing is vscode.MarkdownString {
if (thing instanceof MarkdownString) {
return true;
}
return thing && thing.appendCodeblock && thing.appendMarkdown && thing.appendText && (thing.value !== undefined);
}
}
@es5ClassCompat
@@ -2101,7 +2108,7 @@ export class TreeItem {
iconPath?: string | URI | { light: string | URI; dark: string | URI; };
command?: vscode.Command;
contextValue?: string;
tooltip?: string;
tooltip?: string | vscode.MarkdownString;
constructor(label: string | vscode.TreeItemLabel, collapsibleState?: vscode.TreeItemCollapsibleState);
constructor(resourceUri: URI, collapsibleState?: vscode.TreeItemCollapsibleState);
@@ -2732,6 +2739,11 @@ export enum NotebookCellRunState {
Error = 4
}
export enum NotebookRunState {
Running = 1,
Idle = 2
}
//#endregion
//#region Timeline
@@ -2750,7 +2762,7 @@ export enum ExtensionMode {
* The extension is installed normally (for example, from the marketplace
* or VSIX) in VS Code.
*/
Release = 1,
Production = 1,
/**
* The extension is running from an `--extensionDevelopmentPath` provided
@@ -2766,3 +2778,10 @@ export enum ExtensionMode {
}
//#endregion ExtensionContext
export enum StandardTokenType {
Other = 0,
Comment = 1,
String = 2,
RegEx = 4
}

View File

@@ -8,13 +8,13 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { URI, UriComponents } from 'vs/base/common/uri';
export interface IURITransformerService extends IURITransformer {
_serviceBrand: undefined;
readonly _serviceBrand: undefined;
}
export const IURITransformerService = createDecorator<IURITransformerService>('IURITransformerService');
export class URITransformerService implements IURITransformerService {
_serviceBrand: undefined;
declare readonly _serviceBrand: undefined;
transformIncoming: (uri: UriComponents) => UriComponents;
transformOutgoing: (uri: UriComponents) => UriComponents;

View File

@@ -273,7 +273,7 @@ class CustomDocumentStoreEntry {
constructor(
public readonly document: vscode.CustomDocument,
private readonly _storagePath: string,
private readonly _storagePath: URI | undefined,
) { }
private readonly _edits = new Cache<vscode.CustomDocumentEditEvent>('custom documents');
@@ -305,7 +305,11 @@ class CustomDocumentStoreEntry {
}
getNewBackupUri(): URI {
return joinPath(URI.file(this._storagePath), hashPath(this.document.uri) + (this._backupCounter++));
if (!this._storagePath) {
throw new Error('Backup requires a valid storage path');
}
const fileName = hashPath(this.document.uri) + (this._backupCounter++);
return joinPath(this._storagePath, fileName);
}
updateBackup(backup: vscode.CustomDocumentBackup): void {
@@ -334,7 +338,7 @@ class CustomDocumentStore {
return this._documents.get(this.key(viewType, resource));
}
public add(viewType: string, document: vscode.CustomDocument, storagePath: string): CustomDocumentStoreEntry {
public add(viewType: string, document: vscode.CustomDocument, storagePath: URI | undefined): CustomDocumentStoreEntry {
const key = this.key(viewType, document.uri);
if (this._documents.has(key)) {
throw new Error(`Document already exists for viewType:${viewType} resource:${document.uri}`);
@@ -573,7 +577,7 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
}
const { serializer, extension } = entry;
const webview = new ExtHostWebview(webviewHandle, this._proxy, options, this.initData, this.workspace, extension, this._deprecationService);
const webview = new ExtHostWebview(webviewHandle, this._proxy, reviveOptions(options), this.initData, this.workspace, extension, this._deprecationService);
const revivedPanel = new ExtHostWebviewEditor(webviewHandle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview);
this._webviewPanels.set(webviewHandle, revivedPanel);
await serializer.deserializeWebviewPanel(revivedPanel, state);
@@ -592,8 +596,11 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
const revivedResource = URI.revive(resource);
const document = await entry.provider.openCustomDocument(revivedResource, { backupId }, cancellation);
const storageRoot = this._extensionStoragePaths?.workspaceValue(entry.extension) ?? this._extensionStoragePaths?.globalValue(entry.extension);
this._documents.add(viewType, document, storageRoot!);
let storageRoot: URI | undefined;
if (this.supportEditing(entry.provider) && this._extensionStoragePaths) {
storageRoot = this._extensionStoragePaths.workspaceValue(entry.extension) ?? this._extensionStoragePaths.globalValue(entry.extension);
}
this._documents.add(viewType, document, storageRoot);
return { editable: this.supportEditing(entry.provider) };
}
@@ -628,7 +635,7 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
throw new Error(`No provider found for '${viewType}'`);
}
const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, entry.extension, this._deprecationService);
const webview = new ExtHostWebview(handle, this._proxy, reviveOptions(options), this.initData, this.workspace, entry.extension, this._deprecationService);
const revivedPanel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview);
this._webviewPanels.set(handle, revivedPanel);
@@ -761,6 +768,15 @@ function convertWebviewOptions(
};
}
function reviveOptions(
options: modes.IWebviewOptions & modes.IWebviewPanelOptions
): vscode.WebviewOptions {
return {
...options,
localResourceRoots: options.localResourceRoots?.map(components => URI.from(components)),
};
}
function getDefaultLocalResourceRoots(
extension: IExtensionDescription,
workspace: IExtHostWorkspace | undefined,

View File

@@ -4,13 +4,15 @@
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event';
import { ExtHostWindowShape, MainContext, MainThreadWindowShape, IMainContext, IOpenUriOptions } from './extHost.protocol';
import { ExtHostWindowShape, MainContext, MainThreadWindowShape, IOpenUriOptions } from './extHost.protocol';
import { WindowState } from 'vscode';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
export class ExtHostWindow implements ExtHostWindowShape {
export class ExtHostWindow implements IExtHostWindow {
private static InitialState: WindowState = {
focused: true
@@ -24,8 +26,8 @@ export class ExtHostWindow implements ExtHostWindowShape {
private _state = ExtHostWindow.InitialState;
get state(): WindowState { return this._state; }
constructor(mainContext: IMainContext) {
this._proxy = mainContext.getProxy(MainContext.MainThreadWindow);
constructor(@IExtHostRpcService extHostRpc: IExtHostRpcService) {
this._proxy = extHostRpc.getProxy(MainContext.MainThreadWindow);
this._proxy.$getWindowVisibility().then(isFocused => this.$onDidChangeWindowFocus(isFocused));
}
@@ -67,3 +69,6 @@ export class ExtHostWindow implements ExtHostWindowShape {
return URI.from(result);
}
}
export const IExtHostWindow = createDecorator<IExtHostWindow>('IExtHostWindow');
export interface IExtHostWindow extends ExtHostWindow, ExtHostWindowShape { }

View File

@@ -10,14 +10,166 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { forEach } from 'vs/base/common/collections';
import { IExtensionPointUser, ExtensionMessageCollector, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { MenuId, MenuRegistry, ILocalizedString, IMenuItem, ICommandAction } from 'vs/platform/actions/common/actions';
import { MenuId, MenuRegistry, ILocalizedString, IMenuItem, ICommandAction, ISubmenuItem } from 'vs/platform/actions/common/actions';
import { URI } from 'vs/base/common/uri';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { Iterable } from 'vs/base/common/iterator';
import { index } from 'vs/base/common/arrays';
interface IAPIMenu {
readonly key: string;
readonly id: MenuId;
readonly description: string;
readonly proposed?: boolean; // defaults to false
readonly supportsSubmenus?: boolean; // defaults to true
}
const apiMenus: IAPIMenu[] = [
{
key: 'commandPalette',
id: MenuId.CommandPalette,
description: localize('menus.commandPalette', "The Command Palette"),
supportsSubmenus: false
},
{
key: 'touchBar',
id: MenuId.TouchBarContext,
description: localize('menus.touchBar', "The touch bar (macOS only)"),
supportsSubmenus: false
},
{
key: 'editor/title',
id: MenuId.EditorTitle,
description: localize('menus.editorTitle', "The editor title menu")
},
{
key: 'editor/context',
id: MenuId.EditorContext,
description: localize('menus.editorContext', "The editor context menu")
},
{
key: 'explorer/context',
id: MenuId.ExplorerContext,
description: localize('menus.explorerContext', "The file explorer context menu")
},
{
key: 'editor/title/context',
id: MenuId.EditorTitleContext,
description: localize('menus.editorTabContext', "The editor tabs context menu")
},
{
key: 'debug/callstack/context',
id: MenuId.DebugCallStackContext,
description: localize('menus.debugCallstackContext', "The debug callstack context menu")
},
{
key: 'debug/toolBar',
id: MenuId.DebugToolBar,
description: localize('menus.debugToolBar', "The debug toolbar menu")
},
{
key: 'menuBar/webNavigation',
id: MenuId.MenubarWebNavigationMenu,
description: localize('menus.webNavigation', "The top level navigational menu (web only)"),
proposed: true,
supportsSubmenus: false
},
{
key: 'scm/title',
id: MenuId.SCMTitle,
description: localize('menus.scmTitle', "The Source Control title menu")
},
{
key: 'scm/sourceControl',
id: MenuId.SCMSourceControl,
description: localize('menus.scmSourceControl', "The Source Control menu")
},
{
key: 'scm/resourceState/context',
id: MenuId.SCMResourceContext,
description: localize('menus.resourceGroupContext', "The Source Control resource group context menu")
},
{
key: 'scm/resourceFolder/context',
id: MenuId.SCMResourceFolderContext,
description: localize('menus.resourceStateContext', "The Source Control resource state context menu")
},
{
key: 'scm/resourceGroup/context',
id: MenuId.SCMResourceGroupContext,
description: localize('menus.resourceFolderContext', "The Source Control resource folder context menu")
},
{
key: 'scm/change/title',
id: MenuId.SCMChangeContext,
description: localize('menus.changeTitle', "The Source Control inline change menu")
},
{
key: 'statusBar/windowIndicator',
id: MenuId.StatusBarWindowIndicatorMenu,
description: localize('menus.statusBarWindowIndicator', "The window indicator menu in the status bar"),
proposed: true,
supportsSubmenus: false
},
{
key: 'view/title',
id: MenuId.ViewTitle,
description: localize('view.viewTitle', "The contributed view title menu")
},
{
key: 'view/item/context',
id: MenuId.ViewItemContext,
description: localize('view.itemContext', "The contributed view item context menu")
},
{
key: 'comments/commentThread/title',
id: MenuId.CommentThreadTitle,
description: localize('commentThread.title', "The contributed comment thread title menu")
},
{
key: 'comments/commentThread/context',
id: MenuId.CommentThreadActions,
description: localize('commentThread.actions', "The contributed comment thread context menu, rendered as buttons below the comment editor"),
supportsSubmenus: false
},
{
key: 'comments/comment/title',
id: MenuId.CommentTitle,
description: localize('comment.title', "The contributed comment title menu")
},
{
key: 'comments/comment/context',
id: MenuId.CommentActions,
description: localize('comment.actions', "The contributed comment context menu, rendered as buttons below the comment editor"),
supportsSubmenus: false
},
{
key: 'notebook/cell/title',
id: MenuId.NotebookCellTitle,
description: localize('notebook.cell.title', "The contributed notebook cell title menu"),
proposed: true
},
{
key: 'extension/context',
id: MenuId.ExtensionContext,
description: localize('menus.extensionContext', "The extension context menu")
},
{
key: 'timeline/title',
id: MenuId.TimelineTitle,
description: localize('view.timelineTitle', "The Timeline view title menu")
},
{
key: 'timeline/item/context',
id: MenuId.TimelineItemContext,
description: localize('view.timelineContext', "The Timeline view item context menu")
},
];
namespace schema {
// --- menus contribution point
// --- menus, submenus contribution point
export interface IUserFriendlyMenuItem {
command: string;
@@ -26,79 +178,102 @@ namespace schema {
group?: string;
}
export function parseMenuId(value: string): MenuId | undefined {
switch (value) {
case 'commandPalette': return MenuId.CommandPalette;
case 'touchBar': return MenuId.TouchBarContext;
case 'editor/title': return MenuId.EditorTitle;
case 'editor/context': return MenuId.EditorContext;
case 'explorer/context': return MenuId.ExplorerContext;
case 'editor/title/context': return MenuId.EditorTitleContext;
case 'debug/callstack/context': return MenuId.DebugCallStackContext;
case 'debug/toolbar': return MenuId.DebugToolBar;
case 'debug/toolBar': return MenuId.DebugToolBar;
case 'menuBar/webNavigation': return MenuId.MenubarWebNavigationMenu;
case 'scm/title': return MenuId.SCMTitle;
case 'scm/sourceControl': return MenuId.SCMSourceControl;
case 'scm/resourceState/context': return MenuId.SCMResourceContext;//
case 'scm/resourceFolder/context': return MenuId.SCMResourceFolderContext;
case 'scm/resourceGroup/context': return MenuId.SCMResourceGroupContext;
case 'scm/change/title': return MenuId.SCMChangeContext;//
case 'statusBar/windowIndicator': return MenuId.StatusBarWindowIndicatorMenu;
case 'view/title': return MenuId.ViewTitle;
case 'view/item/context': return MenuId.ViewItemContext;
case 'comments/commentThread/title': return MenuId.CommentThreadTitle;
case 'comments/commentThread/context': return MenuId.CommentThreadActions;
case 'comments/comment/title': return MenuId.CommentTitle;
case 'comments/comment/context': return MenuId.CommentActions;
case 'notebook/cell/title': return MenuId.NotebookCellTitle;
case 'extension/context': return MenuId.ExtensionContext;
case 'timeline/title': return MenuId.TimelineTitle;
case 'timeline/item/context': return MenuId.TimelineItemContext;
}
return undefined;
export interface IUserFriendlySubmenuItem {
submenu: string;
when?: string;
group?: string;
}
export function isProposedAPI(menuId: MenuId): boolean {
switch (menuId) {
case MenuId.StatusBarWindowIndicatorMenu:
case MenuId.MenubarWebNavigationMenu:
return true;
}
return false;
export interface IUserFriendlySubmenu {
id: string;
label: string;
icon?: IUserFriendlyIcon;
}
export function isValidMenuItems(menu: IUserFriendlyMenuItem[], collector: ExtensionMessageCollector): boolean {
if (!Array.isArray(menu)) {
collector.error(localize('requirearray', "menu items must be an array"));
export function isMenuItem(item: IUserFriendlyMenuItem | IUserFriendlySubmenuItem): item is IUserFriendlyMenuItem {
return typeof (item as IUserFriendlyMenuItem).command === 'string';
}
export function isValidMenuItem(item: IUserFriendlyMenuItem, collector: ExtensionMessageCollector): boolean {
if (typeof item.command !== 'string') {
collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'command'));
return false;
}
if (item.alt && typeof item.alt !== 'string') {
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'alt'));
return false;
}
if (item.when && typeof item.when !== 'string') {
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when'));
return false;
}
if (item.group && typeof item.group !== 'string') {
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'group'));
return false;
}
for (let item of menu) {
if (typeof item.command !== 'string') {
collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'command'));
return false;
}
if (item.alt && typeof item.alt !== 'string') {
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'alt'));
return false;
}
if (item.when && typeof item.when !== 'string') {
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when'));
return false;
}
if (item.group && typeof item.group !== 'string') {
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'group'));
return false;
return true;
}
export function isValidSubmenuItem(item: IUserFriendlySubmenuItem, collector: ExtensionMessageCollector): boolean {
if (typeof item.submenu !== 'string') {
collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'submenu'));
return false;
}
if (item.when && typeof item.when !== 'string') {
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when'));
return false;
}
if (item.group && typeof item.group !== 'string') {
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'group'));
return false;
}
return true;
}
export function isValidItems(items: (IUserFriendlyMenuItem | IUserFriendlySubmenuItem)[], collector: ExtensionMessageCollector): boolean {
if (!Array.isArray(items)) {
collector.error(localize('requirearray', "submenu items must be an array"));
return false;
}
for (let item of items) {
if (isMenuItem(item)) {
if (!isValidMenuItem(item, collector)) {
return false;
}
} else {
if (!isValidSubmenuItem(item, collector)) {
return false;
}
}
}
return true;
}
export function isValidSubmenu(submenu: IUserFriendlySubmenu, collector: ExtensionMessageCollector): boolean {
if (typeof submenu !== 'object') {
collector.error(localize('require', "submenu items must be an object"));
return false;
}
if (typeof submenu.id !== 'string') {
collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'id'));
return false;
}
if (typeof submenu.label !== 'string') {
collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'label'));
return false;
}
return true;
}
const menuItem: IJSONSchema = {
type: 'object',
required: ['command'],
properties: {
command: {
description: localize('vscode.extension.contributes.menuItem.command', 'Identifier of the command to execute. The command must be declared in the \'commands\'-section'),
@@ -119,138 +294,80 @@ namespace schema {
}
};
const submenuItem: IJSONSchema = {
type: 'object',
required: ['submenu'],
properties: {
submenu: {
description: localize('vscode.extension.contributes.menuItem.submenu', 'Identifier of the submenu to display in this item.'),
type: 'string'
},
when: {
description: localize('vscode.extension.contributes.menuItem.when', 'Condition which must be true to show this item'),
type: 'string'
},
group: {
description: localize('vscode.extension.contributes.menuItem.group', 'Group into which this command belongs'),
type: 'string'
}
}
};
const submenu: IJSONSchema = {
type: 'object',
required: ['id', 'label'],
properties: {
id: {
description: localize('vscode.extension.contributes.submenu.id', 'Identifier of the menu to display as a submenu.'),
type: 'string'
},
label: {
description: localize('vscode.extension.contributes.submenu.label', 'The label of the menu item which leads to this submenu.'),
type: 'string'
},
icon: {
description: localize('vscode.extension.contributes.submenu.icon', '(Optional) Icon which is used to represent the submenu in the UI. Either a file path, an object with file paths for dark and light themes, or a theme icon references, like `\\$(zap)`'),
anyOf: [{
type: 'string'
},
{
type: 'object',
properties: {
light: {
description: localize('vscode.extension.contributes.submenu.icon.light', 'Icon path when a light theme is used'),
type: 'string'
},
dark: {
description: localize('vscode.extension.contributes.submenu.icon.dark', 'Icon path when a dark theme is used'),
type: 'string'
}
}
}]
}
}
};
export const menusContribution: IJSONSchema = {
description: localize('vscode.extension.contributes.menus', "Contributes menu items to the editor"),
type: 'object',
properties: {
'commandPalette': {
description: localize('menus.commandPalette', "The Command Palette"),
type: 'array',
items: menuItem
},
'touchBar': {
description: localize('menus.touchBar', "The touch bar (macOS only)"),
type: 'array',
items: menuItem
},
'editor/title': {
description: localize('menus.editorTitle', "The editor title menu"),
type: 'array',
items: menuItem
},
'editor/context': {
description: localize('menus.editorContext', "The editor context menu"),
type: 'array',
items: menuItem
},
'explorer/context': {
description: localize('menus.explorerContext', "The file explorer context menu"),
type: 'array',
items: menuItem
},
'editor/title/context': {
description: localize('menus.editorTabContext', "The editor tabs context menu"),
type: 'array',
items: menuItem
},
'debug/callstack/context': {
description: localize('menus.debugCallstackContext', "The debug callstack context menu"),
type: 'array',
items: menuItem
},
'debug/toolBar': {
description: localize('menus.debugToolBar', "The debug toolbar menu"),
type: 'array',
items: menuItem
},
'menuBar/webNavigation': {
description: localize('menus.webNavigation', "The top level navigational menu (web only)"),
type: 'array',
items: menuItem
},
'scm/title': {
description: localize('menus.scmTitle', "The Source Control title menu"),
type: 'array',
items: menuItem
},
'scm/sourceControl': {
description: localize('menus.scmSourceControl', "The Source Control menu"),
type: 'array',
items: menuItem
},
'scm/resourceGroup/context': {
description: localize('menus.resourceGroupContext', "The Source Control resource group context menu"),
type: 'array',
items: menuItem
},
'scm/resourceState/context': {
description: localize('menus.resourceStateContext', "The Source Control resource state context menu"),
type: 'array',
items: menuItem
},
'scm/resourceFolder/context': {
description: localize('menus.resourceFolderContext', "The Source Control resource folder context menu"),
type: 'array',
items: menuItem
},
'scm/change/title': {
description: localize('menus.changeTitle', "The Source Control inline change menu"),
type: 'array',
items: menuItem
},
'view/title': {
description: localize('view.viewTitle', "The contributed view title menu"),
type: 'array',
items: menuItem
},
'view/item/context': {
description: localize('view.itemContext', "The contributed view item context menu"),
type: 'array',
items: menuItem
},
'comments/commentThread/title': {
description: localize('commentThread.title', "The contributed comment thread title menu"),
type: 'array',
items: menuItem
},
'comments/commentThread/context': {
description: localize('commentThread.actions', "The contributed comment thread context menu, rendered as buttons below the comment editor"),
type: 'array',
items: menuItem
},
'comments/comment/title': {
description: localize('comment.title', "The contributed comment title menu"),
type: 'array',
items: menuItem
},
'comments/comment/context': {
description: localize('comment.actions', "The contributed comment context menu, rendered as buttons below the comment editor"),
type: 'array',
items: menuItem
},
'notebook/cell/title': {
description: localize('notebook.cell.title', "The contributed notebook cell title menu"),
type: 'array',
items: menuItem
},
'extension/context': {
description: localize('menus.extensionContext', "The extension context menu"),
type: 'array',
items: menuItem
},
'timeline/title': {
description: localize('view.timelineTitle', "The Timeline view title menu"),
type: 'array',
items: menuItem
},
'timeline/item/context': {
description: localize('view.timelineContext', "The Timeline view item context menu"),
type: 'array',
items: menuItem
},
properties: index(apiMenus, menu => menu.key, menu => ({
description: menu.proposed ? `(${localize('proposed', "Proposed API")}) ${menu.description}` : menu.description,
type: 'array',
items: menu.supportsSubmenus === false ? menuItem : { oneOf: [menuItem, submenuItem] }
})),
additionalProperties: {
description: 'Submenu',
type: 'array',
items: { oneOf: [menuItem, submenuItem] }
}
};
export const submenusContribution: IJSONSchema = {
description: localize('vscode.extension.contributes.submenus', "(Proposed API) Contributes submenu items to the editor"),
type: 'array',
items: submenu
};
// --- commands contribution point
export interface IUserFriendlyCommand {
@@ -337,7 +454,7 @@ namespace schema {
type: 'string'
},
icon: {
description: localize('vscode.extension.contributes.commandType.icon', '(Optional) Icon which is used to represent the command in the UI. Either a file path, an object with file paths for dark and light themes, or a theme icon references, like `$(zap)`'),
description: localize('vscode.extension.contributes.commandType.icon', '(Optional) Icon which is used to represent the command in the UI. Either a file path, an object with file paths for dark and light themes, or a theme icon references, like `\\$(zap)`'),
anyOf: [{
type: 'string'
},
@@ -429,74 +546,175 @@ commandsExtensionPoint.setHandler(extensions => {
_commandRegistrations.add(MenuRegistry.addCommands(newCommands));
});
const _menuRegistrations = new DisposableStore();
interface IRegisteredSubmenu {
readonly id: MenuId;
readonly label: string;
readonly icon?: { dark: URI; light?: URI; } | ThemeIcon;
}
ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyMenuItem[] }>({
extensionPoint: 'menus',
jsonSchema: schema.menusContribution
}).setHandler(extensions => {
const _submenus = new Map<string, IRegisteredSubmenu>();
// remove all previous menu registrations
_menuRegistrations.clear();
const submenusExtensionPoint = ExtensionsRegistry.registerExtensionPoint<schema.IUserFriendlySubmenu[]>({
extensionPoint: 'submenus',
jsonSchema: schema.submenusContribution
});
const items: { id: MenuId, item: IMenuItem }[] = [];
submenusExtensionPoint.setHandler(extensions => {
_submenus.clear();
for (let extension of extensions) {
const { value, collector } = extension;
forEach(value, entry => {
if (!schema.isValidMenuItems(entry.value, collector)) {
if (!schema.isValidSubmenu(entry.value, collector)) {
return;
}
const menu = schema.parseMenuId(entry.key);
if (typeof menu === 'undefined') {
if (!entry.value.id) {
collector.warn(localize('submenuId.invalid.id', "`{0}` is not a valid submenu identifier", entry.value.id));
return;
}
if (!entry.value.label) {
collector.warn(localize('submenuId.invalid.label', "`{0}` is not a valid submenu label", entry.value.label));
return;
}
if (!extension.description.enableProposedApi) {
collector.error(localize('submenu.proposedAPI.invalid', "Submenus are proposed API and are only available when running out of dev or with the following command line switch: --enable-proposed-api {0}", extension.description.identifier.value));
return;
}
let absoluteIcon: { dark: URI; light?: URI; } | ThemeIcon | undefined;
if (entry.value.icon) {
if (typeof entry.value.icon === 'string') {
absoluteIcon = ThemeIcon.fromString(entry.value.icon) || { dark: resources.joinPath(extension.description.extensionLocation, entry.value.icon) };
} else {
absoluteIcon = {
dark: resources.joinPath(extension.description.extensionLocation, entry.value.icon.dark),
light: resources.joinPath(extension.description.extensionLocation, entry.value.icon.light)
};
}
}
const item: IRegisteredSubmenu = {
id: new MenuId(`api:${entry.value.id}`),
label: entry.value.label,
icon: absoluteIcon
};
_submenus.set(entry.value.id, item);
});
}
});
const _apiMenusByKey = new Map(Iterable.map(Iterable.from(apiMenus), menu => ([menu.key, menu])));
const _menuRegistrations = new DisposableStore();
const menusExtensionPoint = ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: (schema.IUserFriendlyMenuItem | schema.IUserFriendlySubmenuItem)[] }>({
extensionPoint: 'menus',
jsonSchema: schema.menusContribution,
deps: [submenusExtensionPoint]
});
menusExtensionPoint.setHandler(extensions => {
// remove all previous menu registrations
_menuRegistrations.clear();
const items: { id: MenuId, item: IMenuItem | ISubmenuItem }[] = [];
for (let extension of extensions) {
const { value, collector } = extension;
forEach(value, entry => {
if (!schema.isValidItems(entry.value, collector)) {
return;
}
let menu = _apiMenusByKey.get(entry.key);
let isSubmenu = false;
if (!menu) {
const submenu = _submenus.get(entry.key);
if (submenu) {
menu = {
key: entry.key,
id: submenu.id,
description: ''
};
isSubmenu = true;
}
}
if (!menu) {
collector.warn(localize('menuId.invalid', "`{0}` is not a valid menu identifier", entry.key));
return;
}
if (schema.isProposedAPI(menu) && !extension.description.enableProposedApi) {
if (menu.proposed && !extension.description.enableProposedApi) {
collector.error(localize('proposedAPI.invalid', "{0} is a proposed menu identifier and is only available when running out of dev or with the following command line switch: --enable-proposed-api {1}", entry.key, extension.description.identifier.value));
return;
}
for (let item of entry.value) {
let command = MenuRegistry.getCommand(item.command);
let alt = item.alt && MenuRegistry.getCommand(item.alt) || undefined;
if (isSubmenu && !extension.description.enableProposedApi) {
collector.error(localize('proposedAPI.invalid.submenu', "{0} is a submenu identifier and is only available when running out of dev or with the following command line switch: --enable-proposed-api {1}", entry.key, extension.description.identifier.value));
return;
}
if (!command) {
collector.error(localize('missing.command', "Menu item references a command `{0}` which is not defined in the 'commands' section.", item.command));
continue;
}
if (item.alt && !alt) {
collector.warn(localize('missing.altCommand', "Menu item references an alt-command `{0}` which is not defined in the 'commands' section.", item.alt));
}
if (item.command === item.alt) {
collector.info(localize('dupe.command', "Menu item references the same command as default and alt-command"));
for (const menuItem of entry.value) {
let item: IMenuItem | ISubmenuItem;
if (schema.isMenuItem(menuItem)) {
const command = MenuRegistry.getCommand(menuItem.command);
const alt = menuItem.alt && MenuRegistry.getCommand(menuItem.alt) || undefined;
if (!command) {
collector.error(localize('missing.command', "Menu item references a command `{0}` which is not defined in the 'commands' section.", menuItem.command));
continue;
}
if (menuItem.alt && !alt) {
collector.warn(localize('missing.altCommand', "Menu item references an alt-command `{0}` which is not defined in the 'commands' section.", menuItem.alt));
}
if (menuItem.command === menuItem.alt) {
collector.info(localize('dupe.command', "Menu item references the same command as default and alt-command"));
}
item = { command, alt, group: undefined, order: undefined, when: undefined };
} else {
if (!extension.description.enableProposedApi) {
collector.error(localize('proposedAPI.invalid.submenureference', "Menu item references a submenu which is only available when running out of dev or with the following command line switch: --enable-proposed-api {0}", extension.description.identifier.value));
continue;
}
if (menu.supportsSubmenus === false) {
collector.error(localize('proposedAPI.unsupported.submenureference', "Menu item references a submenu for a menu which doesn't have submenu support."));
continue;
}
const submenu = _submenus.get(menuItem.submenu);
if (!submenu) {
collector.error(localize('missing.submenu', "Menu item references a submenu `{0}` which is not defined in the 'submenus' section.", menuItem.submenu));
continue;
}
item = { submenu: submenu.id, icon: submenu.icon, title: submenu.label, group: undefined, order: undefined, when: undefined };
}
let group: string | undefined;
let order: number | undefined;
if (item.group) {
const idx = item.group.lastIndexOf('@');
if (menuItem.group) {
const idx = menuItem.group.lastIndexOf('@');
if (idx > 0) {
group = item.group.substr(0, idx);
order = Number(item.group.substr(idx + 1)) || undefined;
item.group = menuItem.group.substr(0, idx);
item.order = Number(menuItem.group.substr(idx + 1)) || undefined;
} else {
group = item.group;
item.group = menuItem.group;
}
}
items.push({
id: menu,
item: {
command,
alt,
group,
order,
when: ContextKeyExpr.deserialize(item.when)
}
});
item.when = ContextKeyExpr.deserialize(menuItem.when);
items.push({ id: menu.id, item });
}
});
}

View File

@@ -0,0 +1,138 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'vs/base/common/path';
import { URI, UriComponents } from 'vs/base/common/uri';
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
import * as errors from 'vs/base/common/errors';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder';
import { ISearchService } from 'vs/workbench/services/search/common/search';
import { toWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
const WORKSPACE_CONTAINS_TIMEOUT = 7000;
export interface IExtensionActivationHost {
readonly folders: readonly UriComponents[];
readonly forceUsingSearch: boolean;
exists(path: string): Promise<boolean>;
checkExists(folders: readonly UriComponents[], includes: string[], token: CancellationToken): Promise<boolean>;
}
export interface IExtensionActivationResult {
activationEvent: string;
}
export function checkActivateWorkspaceContainsExtension(host: IExtensionActivationHost, desc: IExtensionDescription): Promise<IExtensionActivationResult | undefined> {
const activationEvents = desc.activationEvents;
if (!activationEvents) {
return Promise.resolve(undefined);
}
const fileNames: string[] = [];
const globPatterns: string[] = [];
for (const activationEvent of activationEvents) {
if (/^workspaceContains:/.test(activationEvent)) {
const fileNameOrGlob = activationEvent.substr('workspaceContains:'.length);
if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0 || host.forceUsingSearch) {
globPatterns.push(fileNameOrGlob);
} else {
fileNames.push(fileNameOrGlob);
}
}
}
if (fileNames.length === 0 && globPatterns.length === 0) {
return Promise.resolve(undefined);
}
let resolveResult: (value: IExtensionActivationResult | undefined) => void;
const result = new Promise<IExtensionActivationResult | undefined>((resolve, reject) => { resolveResult = resolve; });
const activate = (activationEvent: string) => resolveResult({ activationEvent });
const fileNamePromise = Promise.all(fileNames.map((fileName) => _activateIfFileName(host, fileName, activate))).then(() => { });
const globPatternPromise = _activateIfGlobPatterns(host, desc.identifier, globPatterns, activate);
Promise.all([fileNamePromise, globPatternPromise]).then(() => {
// when all are done, resolve with undefined (relevant only if it was not activated so far)
resolveResult(undefined);
});
return result;
}
async function _activateIfFileName(host: IExtensionActivationHost, fileName: string, activate: (activationEvent: string) => void): Promise<void> {
// find exact path
for (const uri of host.folders) {
if (await host.exists(path.join(URI.revive(uri).fsPath, fileName))) {
// the file was found
activate(`workspaceContains:${fileName}`);
return;
}
}
}
async function _activateIfGlobPatterns(host: IExtensionActivationHost, extensionId: ExtensionIdentifier, globPatterns: string[], activate: (activationEvent: string) => void): Promise<void> {
if (globPatterns.length === 0) {
return Promise.resolve(undefined);
}
const tokenSource = new CancellationTokenSource();
const searchP = host.checkExists(host.folders, globPatterns, tokenSource.token);
const timer = setTimeout(async () => {
tokenSource.cancel();
activate(`workspaceContainsTimeout:${globPatterns.join(',')}`);
}, WORKSPACE_CONTAINS_TIMEOUT);
let exists: boolean = false;
try {
exists = await searchP;
} catch (err) {
if (!errors.isPromiseCanceledError(err)) {
errors.onUnexpectedError(err);
}
}
tokenSource.dispose();
clearTimeout(timer);
if (exists) {
// a file was found matching one of the glob patterns
activate(`workspaceContains:${globPatterns.join(',')}`);
}
}
export function checkGlobFileExists(
accessor: ServicesAccessor,
folders: readonly UriComponents[],
includes: string[],
token: CancellationToken,
): Promise<boolean> {
const instantiationService = accessor.get(IInstantiationService);
const searchService = accessor.get(ISearchService);
const queryBuilder = instantiationService.createInstance(QueryBuilder);
const query = queryBuilder.file(folders.map(folder => toWorkspaceFolder(URI.revive(folder))), {
_reason: 'checkExists',
includePattern: includes.join(', '),
expandPatterns: true,
exists: true
});
return searchService.fileSearch(query, token).then(
result => {
return !!result.limitHit;
},
err => {
if (!errors.isPromiseCanceledError(err)) {
return Promise.reject(err);
}
return false;
});
}

View File

@@ -4,46 +4,35 @@
*--------------------------------------------------------------------------------------------*/
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IExtHostOutputService } from 'vs/workbench/api/common/extHostOutput';
import { ExtHostOutputService2 } from 'vs/workbench/api/node/extHostOutputService';
import { IExtHostWorkspace, ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { IExtHostDecorations, ExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations';
import { IExtHostConfiguration, ExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration';
import { IExtHostCommands, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalService';
import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService';
import { IExtHostTask } from 'vs/workbench/api/common/extHostTask';
import { ExtHostTask } from 'vs/workbench/api/node/extHostTask';
import { ExtHostDebugService } from 'vs/workbench/api/node/extHostDebugService';
import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService';
import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch';
import { NativeExtHostSearch } from 'vs/workbench/api/node/extHostSearch';
import { ExtensionStoragePaths } from 'vs/workbench/api/node/extHostStoragePaths';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
import { IExtHostStorage, ExtHostStorage } from 'vs/workbench/api/common/extHostStorage';
import { ILogService } from 'vs/platform/log/common/log';
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
import { ExtHostTunnelService } from 'vs/workbench/api/node/extHostTunnelService';
import { IExtHostApiDeprecationService, ExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService';
import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
import { IExtHostOutputService } from 'vs/workbench/api/common/extHostOutput';
import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch';
import { IExtHostTask } from 'vs/workbench/api/common/extHostTask';
import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService';
import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
import { ILogService } from 'vs/platform/log/common/log';
// #########################################################################
// ### ###
// ### !!! PLEASE ADD COMMON IMPORTS INTO extHost.common.services.ts !!! ###
// ### ###
// #########################################################################
// register singleton services
registerSingleton(ILogService, ExtHostLogService);
registerSingleton(IExtHostApiDeprecationService, ExtHostApiDeprecationService);
registerSingleton(IExtHostOutputService, ExtHostOutputService2);
registerSingleton(IExtHostWorkspace, ExtHostWorkspace);
registerSingleton(IExtHostDecorations, ExtHostDecorations);
registerSingleton(IExtHostConfiguration, ExtHostConfiguration);
registerSingleton(IExtHostCommands, ExtHostCommands);
registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors);
registerSingleton(IExtHostTerminalService, ExtHostTerminalService);
registerSingleton(IExtHostTask, ExtHostTask);
registerSingleton(IExtHostDebugService, ExtHostDebugService);
registerSingleton(IExtHostSearch, NativeExtHostSearch);
registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths);
registerSingleton(IExtHostExtensionService, ExtHostExtensionService);
registerSingleton(IExtHostStorage, ExtHostStorage);
registerSingleton(ILogService, ExtHostLogService);
registerSingleton(IExtHostDebugService, ExtHostDebugService);
registerSingleton(IExtHostOutputService, ExtHostOutputService2);
registerSingleton(IExtHostSearch, NativeExtHostSearch);
registerSingleton(IExtHostTask, ExtHostTask);
registerSingleton(IExtHostTerminalService, ExtHostTerminalService);
registerSingleton(IExtHostTunnelService, ExtHostTunnelService);

View File

@@ -7,10 +7,9 @@ import { generateRandomPipeName } from 'vs/base/parts/ipc/node/ipc.net';
import * as http from 'http';
import * as fs from 'fs';
import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
import { IWindowOpenable, IOpenWindowOptions } from 'vs/platform/windows/common/windows';
import { URI } from 'vs/base/common/uri';
import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
import { INativeOpenWindowOptions } from 'vs/platform/windows/node/window';
import { ILogService } from 'vs/platform/log/common/log';
export interface OpenCommandPipeArgs {
@@ -123,7 +122,7 @@ export class CLIServer {
if (urisToOpen.length) {
const waitMarkerFileURI = waitMarkerFilePath ? URI.file(waitMarkerFilePath) : undefined;
const preferNewWindow = !forceReuseWindow && !waitMarkerFileURI && !addMode;
const windowOpenArgs: INativeOpenWindowOptions = { forceNewWindow, diffMode, addMode, gotoLineMode, forceReuseWindow, preferNewWindow, waitMarkerFileURI };
const windowOpenArgs: IOpenWindowOptions = { forceNewWindow, diffMode, addMode, gotoLineMode, forceReuseWindow, preferNewWindow, waitMarkerFileURI };
this._commands.executeCommand('_files.windowOpen', urisToOpen, windowOpenArgs);
}
res.writeHead(200);

View File

@@ -88,6 +88,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
const configProvider = await this._configurationService.getConfigProvider();
const shell = this._terminalService.getDefaultShell(true, configProvider);
let cwdForPrepareCommand: string | undefined;
if (needNewTerminal || !this._integratedTerminalInstance) {
@@ -97,9 +98,9 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
cwd: args.cwd,
name: args.title || nls.localize('debug.terminal.title', "debuggee"),
};
// @ts-ignore
delete args.cwd;
this._integratedTerminalInstance = this._terminalService.createTerminalFromOptions(options);
} else {
cwdForPrepareCommand = args.cwd;
}
const terminal = this._integratedTerminalInstance;
@@ -107,7 +108,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
terminal.show();
const shellProcessId = await this._integratedTerminalInstance.processId;
const command = prepareCommand(args, shell);
const command = prepareCommand(shell, args.args, cwdForPrepareCommand, args.env);
terminal.sendText(command, true);
return shellProcessId;
@@ -122,5 +123,4 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
protected createVariableResolver(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider): AbstractVariableResolverService {
return new ExtHostVariableResolverService(folders, editorService, configurationService, process.env as env.IProcessEnvironment);
}
}

View File

@@ -13,6 +13,7 @@ import { ExtHostDownloadService } from 'vs/workbench/api/node/extHostDownloadSer
import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
class NodeModuleRequireInterceptor extends RequireInterceptor {
@@ -76,6 +77,10 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
};
}
protected _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined {
return extensionDescription.main;
}
protected _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
if (module.scheme !== Schemas.file) {
throw new Error(`Cannot load URI: '${module}', must be of file-scheme`);

View File

@@ -1,80 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'vs/base/common/path';
import { URI } from 'vs/base/common/uri';
import * as pfs from 'vs/base/node/pfs';
import { IEnvironment, IStaticWorkspaceData } from 'vs/workbench/api/common/extHost.protocol';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { withNullAsUndefined } from 'vs/base/common/types';
import { ILogService } from 'vs/platform/log/common/log';
export class ExtensionStoragePaths implements IExtensionStoragePaths {
readonly _serviceBrand: undefined;
private readonly _workspace?: IStaticWorkspaceData;
private readonly _environment: IEnvironment;
readonly whenReady: Promise<string | undefined>;
private _value?: string;
constructor(
@IExtHostInitDataService initData: IExtHostInitDataService,
@ILogService private readonly _logService: ILogService,
) {
this._workspace = withNullAsUndefined(initData.workspace);
this._environment = initData.environment;
this.whenReady = this._getOrCreateWorkspaceStoragePath().then(value => this._value = value);
}
workspaceValue(extension: IExtensionDescription): string | undefined {
if (this._value) {
return path.join(this._value, extension.identifier.value);
}
return undefined;
}
globalValue(extension: IExtensionDescription): string {
return path.join(this._environment.globalStorageHome.fsPath, extension.identifier.value.toLowerCase());
}
private async _getOrCreateWorkspaceStoragePath(): Promise<string | undefined> {
if (!this._workspace) {
return Promise.resolve(undefined);
}
if (!this._environment.appSettingsHome) {
return undefined;
}
const storageName = this._workspace.id;
const storagePath = path.join(this._environment.appSettingsHome.fsPath, 'workspaceStorage', storageName);
const exists = await pfs.dirExists(storagePath);
if (exists) {
return storagePath;
}
try {
await pfs.mkdirp(storagePath);
await pfs.writeFile(
path.join(storagePath, 'meta.json'),
JSON.stringify({
id: this._workspace.id,
configuration: this._workspace.configuration && URI.revive(this._workspace.configuration).toString(),
name: this._workspace.name
}, undefined, 2)
);
return storagePath;
} catch (e) {
this._logService.error(e);
return undefined;
}
}
}

View File

@@ -47,13 +47,22 @@ export class ExtHostTask extends ExtHostTaskBase {
platform: process.platform
});
}
this._proxy.$registerSupportedExecutions(true, true, true);
}
public async executeTask(extension: IExtensionDescription, task: vscode.Task): Promise<vscode.TaskExecution> {
const tTask = (task as types.Task);
// We have a preserved ID. So the task didn't change.
if (tTask._id !== undefined) {
return this._proxy.$executeTask(TaskHandleDTO.from(tTask)).then(value => this.getTaskExecution(value, task));
// Always get the task execution first to prevent timing issues when retrieving it later
const handleDto = TaskHandleDTO.from(tTask);
const executionDTO = await this._proxy.$getTaskExecution(handleDto);
if (executionDTO.task === undefined) {
throw new Error('Task from execution DTO is undefined');
}
const execution = await this.getTaskExecution(executionDTO, task);
this._proxy.$executeTask(handleDto).catch(() => { /* The error here isn't actionable. */ });
return execution;
} else {
const dto = TaskDTO.from(task, extension);
if (dto === undefined) {
@@ -66,8 +75,10 @@ export class ExtHostTask extends ExtHostTaskBase {
if (CustomExecutionDTO.is(dto.execution)) {
await this.addCustomExecution(dto, task, false);
}
return this._proxy.$executeTask(dto).then(value => this.getTaskExecution(value, task));
// Always get the task execution first to prevent timing issues when retrieving it later
const execution = await this.getTaskExecution(await this._proxy.$getTaskExecution(dto), task);
this._proxy.$executeTask(dto).catch(() => { /* The error here isn't actionable. */ });
return execution;
}
}
@@ -185,4 +196,8 @@ export class ExtHostTask extends ExtHostTaskBase {
public async $jsonTasksSupported(): Promise<boolean> {
return true;
}
public async $findExecutable(command: string, cwd?: string, paths?: string[]): Promise<string> {
return win32.findExecutable(command, cwd, paths);
}
}

View File

@@ -12,7 +12,7 @@ import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/termi
import { IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostConfiguration, ExtHostConfigProvider, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration';
import { ILogService } from 'vs/platform/log/common/log';
import { IShellLaunchConfig, ITerminalEnvironment } from 'vs/workbench/contrib/terminal/common/terminal';
import { IShellLaunchConfig, ITerminalEnvironment, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
import { TerminalProcess } from 'vs/workbench/contrib/terminal/node/terminalProcess';
import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
@@ -20,11 +20,8 @@ import { ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostD
import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { getSystemShell, detectAvailableShells } from 'vs/workbench/contrib/terminal/node/terminal';
import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/terminalEnvironment';
import { BaseExtHostTerminalService, ExtHostTerminal, EnvironmentVariableCollection } from 'vs/workbench/api/common/extHostTerminalService';
import { BaseExtHostTerminalService, ExtHostTerminal } from 'vs/workbench/api/common/extHostTerminalService';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection';
export class ExtHostTerminalService extends BaseExtHostTerminalService {
@@ -32,8 +29,6 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
private _variableResolver: ExtHostVariableResolverService | undefined;
private _lastActiveWorkspace: IWorkspaceFolder | undefined;
private _environmentVariableCollections: Map<string, EnvironmentVariableCollection> = new Map();
// TODO: Pull this from main side
private _isWorkspaceShellAllowed: boolean = false;
@@ -129,7 +124,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
this._variableResolver = new ExtHostVariableResolverService(workspaceFolders || [], this._extHostDocumentsAndEditors, configProvider, process.env as platform.IProcessEnvironment);
}
public async $spawnExtHostProcess(id: number, shellLaunchConfigDto: IShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents | undefined, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise<void> {
public async $spawnExtHostProcess(id: number, shellLaunchConfigDto: IShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents | undefined, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise<ITerminalLaunchError | undefined> {
const shellLaunchConfig: IShellLaunchConfig = {
name: shellLaunchConfigDto.name,
executable: shellLaunchConfigDto.executable,
@@ -205,11 +200,19 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
this._proxy.$sendResolvedLaunchConfig(id, shellLaunchConfig);
// Fork the process and listen for messages
this._logService.debug(`Terminal process launching on ext host`, shellLaunchConfig, initialCwd, cols, rows, env);
this._logService.debug(`Terminal process launching on ext host`, { shellLaunchConfig, initialCwd, cols, rows, env });
// TODO: Support conpty on remote, it doesn't seem to work for some reason?
// TODO: When conpty is enabled, only enable it when accessibilityMode is off
const enableConpty = false; //terminalConfig.get('windowsEnableConpty') as boolean;
this._setupExtHostProcessListeners(id, new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, enableConpty, this._logService));
const terminalProcess = new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, enableConpty, this._logService);
this._setupExtHostProcessListeners(id, terminalProcess);
const error = await terminalProcess.start();
if (error) {
// TODO: Teardown?
return error;
}
return undefined;
}
public $getAvailableShells(): Promise<IShellDefinitionDto[]> {
@@ -227,37 +230,4 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
public $acceptWorkspacePermissionsChanged(isAllowed: boolean): void {
this._isWorkspaceShellAllowed = isAllowed;
}
public getEnvironmentVariableCollection(extension: IExtensionDescription): vscode.EnvironmentVariableCollection {
let collection = this._environmentVariableCollections.get(extension.identifier.value);
if (!collection) {
collection = new EnvironmentVariableCollection();
this._setEnvironmentVariableCollection(extension.identifier.value, collection);
}
return collection;
}
private _syncEnvironmentVariableCollection(extensionIdentifier: string, collection: EnvironmentVariableCollection): void {
const serialized = serializeEnvironmentVariableCollection(collection.map);
this._proxy.$setEnvironmentVariableCollection(extensionIdentifier, collection.persistent, serialized.length === 0 ? undefined : serialized);
}
public $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void {
collections.forEach(entry => {
const extensionIdentifier = entry[0];
const collection = new EnvironmentVariableCollection(entry[1]);
this._setEnvironmentVariableCollection(extensionIdentifier, collection);
});
}
private _setEnvironmentVariableCollection(extensionIdentifier: string, collection: EnvironmentVariableCollection): void {
this._environmentVariableCollections.set(extensionIdentifier, collection);
collection.onDidChangeCollection(() => {
// When any collection value changes send this immediately, this is done to ensure
// following calls to createTerminal will be created with the new environment. It will
// result in more noise by sending multiple updates when called but collections are
// expected to be small.
this._syncEnvironmentVariableCollection(extensionIdentifier, collection!);
});
}
}

View File

@@ -181,7 +181,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
connections.filter((connection => socketMap[connection.socket])).forEach(({ socket, ip, port }) => {
const command = processMap[socketMap[socket].pid].cmd;
if (!command.match('.*\.vscode\-server\-[a-zA-Z]+\/bin.*') && (command.indexOf('out/vs/server/main.js') === -1)) {
if (!command.match(/.*\.vscode-server-[a-zA-Z]+\/bin.*/) && (command.indexOf('out/vs/server/main.js') === -1)) {
ports.push({ host: ip, port, detail: processMap[socketMap[socket].pid].cmd });
}
});

View File

@@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ILogService } from 'vs/platform/log/common/log';
import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
import { ExtHostExtensionService } from 'vs/workbench/api/worker/extHostExtensionService';
import { ExtHostLogService } from 'vs/workbench/api/worker/extHostLogService';
// #########################################################################
// ### ###
// ### !!! PLEASE ADD COMMON IMPORTS INTO extHost.common.services.ts !!! ###
// ### ###
// #########################################################################
registerSingleton(IExtHostExtensionService, ExtHostExtensionService);
registerSingleton(ILogService, ExtHostLogService);

View File

@@ -8,6 +8,7 @@ import { ExtensionActivationTimesBuilder } from 'vs/workbench/api/common/extHost
import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
import { URI } from 'vs/base/common/uri';
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
class WorkerRequireInterceptor extends RequireInterceptor {
@@ -40,6 +41,10 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
await this._fakeModules.install();
}
protected _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined {
return extensionDescription.browser;
}
protected async _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
module = module.with({ path: ensureSuffix(module.path, '.js') });
@@ -50,7 +55,11 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
}
// fetch JS sources as text and create a new function around it
const initFn = new Function('module', 'exports', 'require', 'window', await response.text());
const source = await response.text();
// Here we append #vscode-extension to serve as a marker, such that source maps
// can be adjusted for the extra wrapping function.
const sourceURL = `${module.toString(true)}#vscode-extension`;
const initFn = new Function('module', 'exports', 'require', `${source}\n//# sourceURL=${sourceURL}`);
// define commonjs globals: `module`, `exports`, and `require`
const _exports = {};
@@ -65,7 +74,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
try {
activationTimesBuilder.codeLoadingStart();
initFn(_module, _exports, _require, self);
initFn(_module, _exports, _require);
return <T>(_module.exports !== _exports ? _module.exports : _exports);
} finally {
activationTimesBuilder.codeLoadingStop();

View File

@@ -11,7 +11,7 @@ import { UriComponents } from 'vs/base/common/uri';
export class ExtHostLogService extends AbstractLogService implements ILogService, ExtHostLogServiceShape {
_serviceBrand: undefined;
declare readonly _serviceBrand: undefined;
private readonly _proxy: MainThreadLogShape;
private readonly _logFile: UriComponents;