mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-28 01:25:39 -05:00
Merge from vscode 79a1f5a5ca0c6c53db617aa1fa5a2396d2caebe2
This commit is contained in:
@@ -7,7 +7,7 @@ import { Disposable, IDisposable } 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 } 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';
|
||||
@@ -18,43 +18,56 @@ 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';
|
||||
|
||||
interface AllowedExtension {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const accountUsages = new Map<string, { [accountName: string]: string[] }>();
|
||||
import { fromNow } from 'vs/base/common/date';
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
accountUsages.set(providerId, providerAccountUsage);
|
||||
}
|
||||
interface IAccountUsage {
|
||||
extensionId: string;
|
||||
extensionName: string;
|
||||
lastUsed: number;
|
||||
}
|
||||
|
||||
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);
|
||||
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
|
||||
}
|
||||
} catch (err) { }
|
||||
}
|
||||
|
||||
return trustedExtensions;
|
||||
return usages;
|
||||
}
|
||||
|
||||
function removeAccountUsage(storageService: IStorageService, providerId: string, accountName: string): void {
|
||||
const accountKey = `${providerId}-${accountName}-usages`;
|
||||
storageService.remove(accountKey, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
function addAccountUsage(storageService: IStorageService, providerId: string, accountName: string, extensionId: string, extensionName: string) {
|
||||
const accountKey = `${providerId}-${accountName}-usages`;
|
||||
const usages = readAccountUsages(storageService, providerId, accountName);
|
||||
|
||||
const existingUsageIndex = usages.findIndex(usage => usage.extensionId === extensionId);
|
||||
if (existingUsageIndex > -1) {
|
||||
usages.splice(existingUsageIndex, 1, {
|
||||
extensionId,
|
||||
extensionName,
|
||||
lastUsed: Date.now()
|
||||
});
|
||||
} else {
|
||||
usages.push({
|
||||
extensionId,
|
||||
extensionName,
|
||||
lastUsed: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
storageService.store(accountKey, JSON.stringify(usages), StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
export class MainThreadAuthenticationProvider extends Disposable {
|
||||
@@ -66,8 +79,10 @@ export class MainThreadAuthenticationProvider extends Disposable {
|
||||
private readonly _proxy: ExtHostAuthenticationShape,
|
||||
public readonly id: string,
|
||||
public readonly displayName: string,
|
||||
public readonly supportsMultipleAccounts: boolean,
|
||||
private readonly notificationService: INotificationService,
|
||||
private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService
|
||||
private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
|
||||
private readonly storageService: IStorageService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -81,12 +96,17 @@ export class MainThreadAuthenticationProvider extends Disposable {
|
||||
}
|
||||
|
||||
private manageTrustedExtensions(quickInputService: IQuickInputService, storageService: IStorageService, accountName: string) {
|
||||
const quickPick = quickInputService.createQuickPick<{ label: string, extension: AllowedExtension }>();
|
||||
const quickPick = quickInputService.createQuickPick<{ label: string, description: string, extension: AllowedExtension }>();
|
||||
quickPick.canSelectMany = true;
|
||||
const allowedExtensions = readAllowedExtensions(storageService, this.id, accountName);
|
||||
const usages = readAccountUsages(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('accountLastUsedDate', "Last used this account {0}", fromNow(usage.lastUsed, true))
|
||||
: nls.localize('notUsed', "Has not used this account"),
|
||||
extension
|
||||
};
|
||||
});
|
||||
@@ -110,24 +130,6 @@ 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));
|
||||
@@ -163,10 +165,9 @@ export class MainThreadAuthenticationProvider extends Disposable {
|
||||
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 }]);
|
||||
const items = ([{ label: manage }, { label: signOut }]);
|
||||
|
||||
quickPick.items = items;
|
||||
|
||||
@@ -180,10 +181,6 @@ export class MainThreadAuthenticationProvider extends Disposable {
|
||||
this.manageTrustedExtensions(quickInputService, storageService, session.account.displayName);
|
||||
}
|
||||
|
||||
if (selected.label === showUsage) {
|
||||
this.showUsage(quickInputService, session.account.displayName);
|
||||
}
|
||||
|
||||
quickPick.dispose();
|
||||
});
|
||||
|
||||
@@ -199,39 +196,24 @@ export class MainThreadAuthenticationProvider extends Disposable {
|
||||
}
|
||||
|
||||
async signOut(dialogService: IDialogService, session: modes.AuthenticationSession): Promise<void> {
|
||||
const providerUsage = accountUsages.get(this.id);
|
||||
const accountUsage = (providerUsage || {})[session.account.displayName] || [];
|
||||
const accountUsages = readAccountUsages(this.storageService, this.id, session.account.displayName);
|
||||
const sessionsForAccount = this._accounts.get(session.account.displayName);
|
||||
|
||||
// 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'))
|
||||
message: accountUsages.length
|
||||
? nls.localize('signOutMessagve', "The account {0} has been used by: \n\n{1}\n\n Sign out of these features?", session.account.displayName, accountUsages.map(usage => usage.extensionName).join('\n'))
|
||||
: nls.localize('signOutMessageSimple', "Sign out of {0}?", session.account.displayName)
|
||||
});
|
||||
|
||||
if (result.confirmed) {
|
||||
accountUsages.set(this.id, { [session.account.displayName]: [] });
|
||||
sessionsForAccount?.forEach(sessionId => this.logout(sessionId));
|
||||
removeAccountUsage(this.storageService, this.id, session.account.displayName);
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
@@ -262,13 +244,7 @@ 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> {
|
||||
@@ -294,14 +270,31 @@ 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
|
||||
) {
|
||||
super();
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostAuthentication);
|
||||
|
||||
this._register(this.authenticationService.onDidChangeSessions(e => {
|
||||
this._proxy.$onDidChangeAuthenticationSessions(e.providerId, e.event);
|
||||
}));
|
||||
|
||||
this._register(this.authenticationService.onDidRegisterAuthenticationProvider(providerId => {
|
||||
this._proxy.$onDidChangeAuthenticationProviders([providerId], []);
|
||||
}));
|
||||
|
||||
this._register(this.authenticationService.onDidUnregisterAuthenticationProvider(providerId => {
|
||||
this._proxy.$onDidChangeAuthenticationProviders([], [providerId]);
|
||||
}));
|
||||
}
|
||||
|
||||
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, displayName: string, supportsMultipleAccounts: boolean): Promise<void> {
|
||||
const provider = new MainThreadAuthenticationProvider(this._proxy, id, displayName, supportsMultipleAccounts, this.notificationService, this.storageKeysSyncRegistryService, this.storageService);
|
||||
await provider.initialize();
|
||||
this.authenticationService.registerAuthenticationProvider(id, provider);
|
||||
}
|
||||
@@ -310,21 +303,142 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
|
||||
this.authenticationService.unregisterAuthenticationProvider(id);
|
||||
}
|
||||
|
||||
$onDidChangeSessions(id: string, event: modes.AuthenticationSessionsChangeEvent): void {
|
||||
$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.sort().join(' ') === orderedScopes);
|
||||
const displayName = this.authenticationService.getDisplayName(providerId);
|
||||
|
||||
if (sessions.length) {
|
||||
if (!this.authenticationService.supportsMultipleAccounts(providerId)) {
|
||||
const session = sessions[0];
|
||||
const allowed = await this.$getSessionsPrompt(providerId, session.account.displayName, displayName, 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, displayName, extensionId, extensionName, sessions, scopes, !!options.clearSessionPreference);
|
||||
return sessions.find(session => session.id === selected.id);
|
||||
} else {
|
||||
if (options.createIfNone) {
|
||||
const isAllowed = await this.$loginPrompt(displayName, extensionName);
|
||||
if (!isAllowed) {
|
||||
throw new Error('User did not consent to login.');
|
||||
}
|
||||
|
||||
const session = await this.authenticationService.login(providerId, scopes);
|
||||
await this.$setTrustedExtension(providerId, session.account.displayName, extensionId, extensionName);
|
||||
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.displayName, 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.displayName,
|
||||
session
|
||||
};
|
||||
});
|
||||
|
||||
items.push({
|
||||
label: nls.localize('useOtherAccount', "Sign in to another account")
|
||||
});
|
||||
|
||||
quickPick.items = items;
|
||||
quickPick.title = nls.localize('selectAccount', "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.displayName;
|
||||
|
||||
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)) {
|
||||
addAccountUsage(this.storageService, providerId, accountName, extensionId, extensionName);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -339,6 +453,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);
|
||||
}
|
||||
|
||||
@@ -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,7 +127,10 @@ 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> {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -10,17 +10,19 @@ 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, isEqualOrParent, extUri } from 'vs/base/common/resources';
|
||||
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
|
||||
|
||||
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(
|
||||
@@ -34,10 +36,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 (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 +58,7 @@ export class BoundModelReferenceCollection {
|
||||
}
|
||||
};
|
||||
handle = setTimeout(dispose, this._maxAge);
|
||||
entry = { length, dispose };
|
||||
entry = { uri, length, dispose };
|
||||
|
||||
this._data.push(entry);
|
||||
this._length += length;
|
||||
@@ -69,12 +79,13 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
|
||||
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 readonly _proxy: ExtHostDocumentsShape;
|
||||
private readonly _modelIsSynced = new Set<string>();
|
||||
private _modelReferenceCollection = new BoundModelReferenceCollection();
|
||||
private readonly _modelReferenceCollection = new BoundModelReferenceCollection();
|
||||
|
||||
constructor(
|
||||
documentsAndEditors: MainThreadDocumentsAndEditors,
|
||||
@@ -83,13 +94,16 @@ 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
|
||||
) {
|
||||
this._modelService = modelService;
|
||||
this._textModelResolverService = textModelResolverService;
|
||||
this._textFileService = textFileService;
|
||||
this._fileService = fileService;
|
||||
this._environmentService = environmentService;
|
||||
this._uriIdentityService = uriIdentityService;
|
||||
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocuments);
|
||||
|
||||
@@ -109,6 +123,12 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
|
||||
}
|
||||
}));
|
||||
|
||||
this._toDispose.add(workingCopyFileService.onDidRunWorkingCopyFileOperation(e => {
|
||||
if (e.source && (e.operation === FileOperation.MOVE || e.operation === FileOperation.DELETE)) {
|
||||
this._modelReferenceCollection.remove(e.source);
|
||||
}
|
||||
}));
|
||||
|
||||
this._modelToDisposeMap = Object.create(null);
|
||||
}
|
||||
|
||||
@@ -163,33 +183,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 +221,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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ 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';
|
||||
|
||||
namespace delta {
|
||||
|
||||
@@ -326,11 +328,13 @@ 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,
|
||||
) {
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentsAndEditors);
|
||||
|
||||
const mainThreadDocuments = this._toDispose.add(new MainThreadDocuments(this, extHostContext, this._modelService, this._textFileService, fileService, textModelResolverService, environmentService));
|
||||
const mainThreadDocuments = this._toDispose.add(new MainThreadDocuments(this, extHostContext, this._modelService, this._textFileService, fileService, textModelResolverService, environmentService, uriIdentityService, workingCopyFileService));
|
||||
extHostContext.set(MainContext.MainThreadDocuments, mainThreadDocuments);
|
||||
|
||||
const mainThreadTextEditors = this._toDispose.add(new MainThreadTextEditors(this, extHostContext, codeEditorService, bulkEditService, this._editorService, this._editorGroupService));
|
||||
|
||||
@@ -8,12 +8,9 @@ import { FileChangeType, IFileService, FileOperation } from 'vs/platform/files/c
|
||||
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
|
||||
@@ -25,9 +22,6 @@ export class MainThreadFileSystemEventService {
|
||||
extHostContext: IExtHostContext,
|
||||
@IFileService fileService: IFileService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@IProgressService progressService: IProgressService,
|
||||
@IConfigurationService configService: IConfigurationService,
|
||||
@ILogService logService: ILogService,
|
||||
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService
|
||||
) {
|
||||
|
||||
|
||||
@@ -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))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,19 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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, INotebookModelAddedData } from '../common/extHost.protocol';
|
||||
import { Disposable, IDisposable, combinedDisposable } 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 { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, INotebookKernelInfo, INotebookKernelInfoDto, INotebookTextModelBackup, IEditor, INotebookRendererInfo, IOutputRenderRequest, IOutputRenderResponse } 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';
|
||||
|
||||
export class MainThreadNotebookDocument extends Disposable {
|
||||
private _textModel: NotebookTextModel;
|
||||
@@ -27,37 +29,103 @@ export class MainThreadNotebookDocument extends Disposable {
|
||||
private readonly _proxy: ExtHostNotebookShape,
|
||||
public handle: number,
|
||||
public viewType: string,
|
||||
public uri: URI
|
||||
public uri: URI,
|
||||
readonly notebookService: INotebookService
|
||||
) {
|
||||
super();
|
||||
this._textModel = new NotebookTextModel(handle, viewType, uri);
|
||||
this._register(this._textModel.onDidModelChange(e => {
|
||||
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);
|
||||
async applyEdit(modelVersionId: number, edits: ICellEditOperation[]): Promise<boolean> {
|
||||
await this.notebookService.transformEditsOutputs(this.textModel, edits);
|
||||
return this._textModel.$applyEdit(modelVersionId, edits);
|
||||
}
|
||||
|
||||
updateRenderers(renderers: number[]) {
|
||||
this._textModel.updateRenderers(renderers);
|
||||
async spliceNotebookCellOutputs(cellHandle: number, splices: NotebookCellOutputsSplice[]) {
|
||||
await this.notebookService.transformSpliceOutputs(this.textModel, splices);
|
||||
this._textModel.$spliceNotebookCellOutputs(cellHandle, splices);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._textModel.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentAndEditorState {
|
||||
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
|
||||
};
|
||||
}
|
||||
// const documentDelta = delta.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;
|
||||
|
||||
return {
|
||||
addedEditors: addedAPIEditors,
|
||||
removedEditors: removedAPIEditors,
|
||||
newActiveEditor: newActiveEditor
|
||||
};
|
||||
}
|
||||
|
||||
constructor(
|
||||
readonly documents: Set<URI>,
|
||||
readonly textEditors: Map<string, IEditor>,
|
||||
readonly activeEditor: string | null | undefined,
|
||||
) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadNotebook)
|
||||
export class MainThreadNotebooks extends Disposable implements MainThreadNotebookShape {
|
||||
private readonly _notebookProviders = new Map<string, MainThreadNotebookController>();
|
||||
private readonly _notebookKernels = new Map<string, MainThreadNotebookKernel>();
|
||||
private readonly _notebookRenderers = new Map<string, MainThreadNotebookRenderer>();
|
||||
private readonly _proxy: ExtHostNotebookShape;
|
||||
private _toDisposeOnEditorRemove = new Map<string, IDisposable>();
|
||||
private _currentState?: DocumentAndEditorState;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@@ -83,12 +151,30 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
}
|
||||
|
||||
registerListeners() {
|
||||
this._notebookService.listNotebookEditors().forEach((e) => {
|
||||
this._addNotebookEditor(e);
|
||||
});
|
||||
|
||||
this._register(this._notebookService.onDidChangeActiveEditor(e => {
|
||||
this._proxy.$acceptDocumentAndEditorsDelta({
|
||||
newActiveEditor: e.uri
|
||||
newActiveEditor: e
|
||||
});
|
||||
}));
|
||||
|
||||
this._register(this._notebookService.onDidChangeVisibleEditors(e => {
|
||||
this._proxy.$acceptDocumentAndEditorsDelta({
|
||||
visibleEditors: e
|
||||
});
|
||||
}));
|
||||
|
||||
this._register(this._notebookService.onNotebookEditorAdd(editor => {
|
||||
this._addNotebookEditor(editor);
|
||||
}));
|
||||
|
||||
this._register(this._notebookService.onNotebookEditorRemove(editor => {
|
||||
this._removeNotebookEditor(editor);
|
||||
}));
|
||||
|
||||
const updateOrder = () => {
|
||||
let userOrder = this.configurationService.getValue<string[]>('notebook.displayOrder');
|
||||
this._proxy.$acceptDisplayOrder({
|
||||
@@ -108,29 +194,120 @@ 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)));
|
||||
async addNotebookDocument(data: INotebookModelAddedData) {
|
||||
await this._proxy.$acceptDocumentAndEditorsDelta({
|
||||
addedDocuments: [data]
|
||||
});
|
||||
}
|
||||
|
||||
async $unregisterNotebookRenderer(handle: number): Promise<void> {
|
||||
this._notebookService.unregisterNotebookRenderer(handle);
|
||||
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 $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise<void> {
|
||||
let controller = new MainThreadNotebookController(this._proxy, this, viewType);
|
||||
private _removeNotebookEditor(e: IEditor) {
|
||||
const sub = this._toDisposeOnEditorRemove.get(e.getId());
|
||||
if (sub) {
|
||||
this._toDisposeOnEditorRemove.delete(e.getId());
|
||||
sub.dispose();
|
||||
this._updateState();
|
||||
}
|
||||
}
|
||||
|
||||
private async _updateState(focusedNotebookEditor?: IEditor) {
|
||||
const documents = new Set<URI>();
|
||||
this._notebookService.listNotebookDocuments().forEach(document => {
|
||||
documents.add(document.uri);
|
||||
});
|
||||
|
||||
const editors = new Map<string, IEditor>();
|
||||
let activeEditor: string | null = null;
|
||||
|
||||
for (const editor of this._notebookService.listNotebookEditors()) {
|
||||
if (editor.hasModel()) {
|
||||
editors.set(editor.getId(), editor);
|
||||
if (editor.hasFocus()) {
|
||||
activeEditor = editor.getId();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!activeEditor && focusedNotebookEditor) {
|
||||
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);
|
||||
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._proxy.$acceptDocumentAndEditorsDelta(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, kernel: INotebookKernelInfoDto | undefined): Promise<void> {
|
||||
let controller = new MainThreadNotebookController(this._proxy, this, viewType, kernel, this._notebookService);
|
||||
this._notebookProviders.set(viewType, controller);
|
||||
this._notebookService.registerNotebookController(viewType, extension, controller);
|
||||
return;
|
||||
}
|
||||
|
||||
async $onNotebookChange(viewType: string, uri: UriComponents): Promise<void> {
|
||||
let controller = this._notebookProviders.get(viewType);
|
||||
if (controller) {
|
||||
controller.handleNotebookChange(uri);
|
||||
}
|
||||
}
|
||||
|
||||
async $unregisterNotebookProvider(viewType: string): Promise<void> {
|
||||
this._notebookProviders.delete(viewType);
|
||||
this._notebookService.unregisterNotebookProvider(viewType);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
async $unregisterNotebookKernel(id: string): Promise<void> {
|
||||
this._notebookKernels.delete(id);
|
||||
this._notebookService.unregisterNotebookKernel(id);
|
||||
return;
|
||||
}
|
||||
|
||||
async $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise<void> {
|
||||
let controller = this._notebookProviders.get(viewType);
|
||||
|
||||
@@ -157,18 +334,18 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
|
||||
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);
|
||||
await controller?.spliceNotebookCellOutputs(resource, cellHandle, splices, renderers);
|
||||
}
|
||||
|
||||
async executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise<void> {
|
||||
return this._proxy.$executeNotebook(viewType, uri, undefined, token);
|
||||
async executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise<void> {
|
||||
return this._proxy.$executeNotebook(viewType, uri, undefined, useAttachedKernel, token);
|
||||
}
|
||||
|
||||
async $postMessage(handle: number, value: any): Promise<boolean> {
|
||||
|
||||
const activeEditorPane = this.editorService.activeEditorPane as any | undefined;
|
||||
if (activeEditorPane?.isNotebookEditor) {
|
||||
const notebookEditor = (activeEditorPane as INotebookEditor);
|
||||
const notebookEditor = (activeEditorPane.getControl() as INotebookEditor);
|
||||
|
||||
if (notebookEditor.viewModel?.handle === handle) {
|
||||
notebookEditor.postMessage(value);
|
||||
@@ -187,11 +364,14 @@ export class MainThreadNotebookController implements IMainNotebookController {
|
||||
constructor(
|
||||
private readonly _proxy: ExtHostNotebookShape,
|
||||
private _mainThreadNotebook: MainThreadNotebooks,
|
||||
private _viewType: string
|
||||
private _viewType: string,
|
||||
readonly kernel: INotebookKernelInfoDto | undefined,
|
||||
readonly notebookService: INotebookService,
|
||||
|
||||
) {
|
||||
}
|
||||
|
||||
async createNotebook(viewType: string, uri: URI, forBackup: boolean, forceReload: boolean): Promise<NotebookTextModel | undefined> {
|
||||
async createNotebook(viewType: string, uri: URI, backup: INotebookTextModelBackup | undefined, forceReload: boolean, editorId?: string): Promise<NotebookTextModel | undefined> {
|
||||
let mainthreadNotebook = this._mapping.get(URI.from(uri).toString());
|
||||
|
||||
if (mainthreadNotebook) {
|
||||
@@ -203,7 +383,7 @@ export class MainThreadNotebookController implements IMainNotebookController {
|
||||
|
||||
mainthreadNotebook.textModel.languages = data.languages;
|
||||
mainthreadNotebook.textModel.metadata = data.metadata;
|
||||
mainthreadNotebook.textModel.applyEdit(mainthreadNotebook.textModel.versionId, [
|
||||
mainthreadNotebook.textModel.$applyEdit(mainthreadNotebook.textModel.versionId, [
|
||||
{ editType: CellEditType.Delete, count: mainthreadNotebook.textModel.cells.length, index: 0 },
|
||||
{ editType: CellEditType.Insert, index: 0, cells: data.cells }
|
||||
]);
|
||||
@@ -211,10 +391,43 @@ export class MainThreadNotebookController implements IMainNotebookController {
|
||||
return mainthreadNotebook.textModel;
|
||||
}
|
||||
|
||||
let document = new MainThreadNotebookDocument(this._proxy, MainThreadNotebookController.documentHandle++, viewType, uri);
|
||||
await this.createNotebookDocument(document);
|
||||
let document = new MainThreadNotebookDocument(this._proxy, MainThreadNotebookController.documentHandle++, viewType, uri, this.notebookService);
|
||||
this._mapping.set(document.uri.toString(), document);
|
||||
|
||||
if (backup) {
|
||||
// trigger events
|
||||
document.textModel.metadata = backup.metadata;
|
||||
document.textModel.languages = backup.languages;
|
||||
|
||||
document.textModel.$applyEdit(document.textModel.versionId, [
|
||||
{
|
||||
editType: CellEditType.Insert,
|
||||
index: 0,
|
||||
cells: backup.cells || []
|
||||
}
|
||||
]);
|
||||
|
||||
await this._mainThreadNotebook.addNotebookDocument({
|
||||
viewType: document.viewType,
|
||||
handle: document.handle,
|
||||
uri: document.uri,
|
||||
metadata: document.textModel.metadata,
|
||||
versionId: document.textModel.versionId,
|
||||
cells: document.textModel.cells.map(cell => ({
|
||||
handle: cell.handle,
|
||||
uri: cell.uri,
|
||||
source: cell.textBuffer.getLinesContent(),
|
||||
language: cell.language,
|
||||
cellKind: cell.cellKind,
|
||||
outputs: cell.outputs,
|
||||
metadata: cell.metadata
|
||||
})),
|
||||
attachedEditor: editorId ? {
|
||||
id: editorId,
|
||||
selections: document.textModel.selections
|
||||
} : undefined
|
||||
});
|
||||
|
||||
if (forBackup) {
|
||||
return document.textModel;
|
||||
}
|
||||
|
||||
@@ -226,7 +439,36 @@ export class MainThreadNotebookController implements IMainNotebookController {
|
||||
|
||||
document.textModel.languages = data.languages;
|
||||
document.textModel.metadata = data.metadata;
|
||||
document.textModel.initialize(data!.cells);
|
||||
|
||||
if (data.cells.length) {
|
||||
document.textModel.initialize(data!.cells);
|
||||
} else {
|
||||
const mainCell = document.textModel.createCellTextModel([''], document.textModel.languages.length ? document.textModel.languages[0] : '', CellKind.Code, [], undefined);
|
||||
document.textModel.insertTemplateCell(mainCell);
|
||||
}
|
||||
|
||||
await this._mainThreadNotebook.addNotebookDocument({
|
||||
viewType: document.viewType,
|
||||
handle: document.handle,
|
||||
uri: document.uri,
|
||||
metadata: document.textModel.metadata,
|
||||
versionId: document.textModel.versionId,
|
||||
cells: document.textModel.cells.map(cell => ({
|
||||
handle: cell.handle,
|
||||
uri: cell.uri,
|
||||
source: cell.textBuffer.getLinesContent(),
|
||||
language: cell.language,
|
||||
cellKind: cell.cellKind,
|
||||
outputs: cell.outputs,
|
||||
metadata: cell.metadata
|
||||
})),
|
||||
attachedEditor: editorId ? {
|
||||
id: editorId,
|
||||
selections: document.textModel.selections
|
||||
} : undefined
|
||||
});
|
||||
|
||||
this._proxy.$acceptEditorPropertiesChanged(uri, { selections: null, metadata: document.textModel.metadata });
|
||||
|
||||
return document.textModel;
|
||||
}
|
||||
@@ -235,37 +477,23 @@ export class MainThreadNotebookController implements IMainNotebookController {
|
||||
let mainthreadNotebook = this._mapping.get(URI.from(resource).toString());
|
||||
|
||||
if (mainthreadNotebook) {
|
||||
mainthreadNotebook.updateRenderers(renderers);
|
||||
return mainthreadNotebook.applyEdit(modelVersionId, edits);
|
||||
return await mainthreadNotebook.applyEdit(modelVersionId, edits);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
spliceNotebookCellOutputs(resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): void {
|
||||
async spliceNotebookCellOutputs(resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise<void> {
|
||||
let mainthreadNotebook = this._mapping.get(URI.from(resource).toString());
|
||||
mainthreadNotebook?.textModel.updateRenderers(renderers);
|
||||
mainthreadNotebook?.textModel.$spliceNotebookCellOutputs(cellHandle, splices);
|
||||
await mainthreadNotebook?.spliceNotebookCellOutputs(cellHandle, splices);
|
||||
}
|
||||
|
||||
async executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise<void> {
|
||||
this._mainThreadNotebook.executeNotebook(viewType, uri, token);
|
||||
async executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise<void> {
|
||||
return this._mainThreadNotebook.executeNotebook(viewType, uri, useAttachedKernel, 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
|
||||
}]
|
||||
});
|
||||
onDidReceiveMessage(editorId: string, message: any): void {
|
||||
this._proxy.$onDidReceiveMessage(editorId, message);
|
||||
}
|
||||
|
||||
async removeNotebookDocument(notebook: INotebookTextModel): Promise<void> {
|
||||
@@ -282,6 +510,11 @@ export class MainThreadNotebookController implements IMainNotebookController {
|
||||
|
||||
// Methods for ExtHost
|
||||
|
||||
handleNotebookChange(resource: UriComponents) {
|
||||
let document = this._mapping.get(URI.from(resource).toString());
|
||||
document?.textModel.handleUnknownChange();
|
||||
}
|
||||
|
||||
updateLanguages(resource: UriComponents, languages: string[]) {
|
||||
let document = this._mapping.get(URI.from(resource).toString());
|
||||
document?.textModel.updateLanguages(languages);
|
||||
@@ -297,13 +530,8 @@ export class MainThreadNotebookController implements IMainNotebookController {
|
||||
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 executeNotebookCell(uri: URI, handle: number, useAttachedKernel: boolean, token: CancellationToken): Promise<void> {
|
||||
return this._proxy.$executeNotebook(this._viewType, uri, handle, useAttachedKernel, token);
|
||||
}
|
||||
|
||||
async save(uri: URI, token: CancellationToken): Promise<boolean> {
|
||||
@@ -315,3 +543,41 @@ export class MainThreadNotebookController implements IMainNotebookController {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class MainThreadNotebookKernel implements INotebookKernelInfo {
|
||||
constructor(
|
||||
private readonly _proxy: ExtHostNotebookShape,
|
||||
readonly id: string,
|
||||
readonly label: string,
|
||||
readonly selectors: (string | IRelativePattern)[],
|
||||
readonly extension: ExtensionIdentifier,
|
||||
readonly extensionLocation: URI,
|
||||
readonly preloads: URI[]
|
||||
) {
|
||||
}
|
||||
|
||||
async executeNotebook(viewType: string, uri: URI, handle: number | undefined, token: CancellationToken): Promise<void> {
|
||||
return this._proxy.$executeNotebook2(this.id, viewType, uri, handle, token);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ 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 {
|
||||
@@ -25,9 +26,14 @@ 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;
|
||||
let ariaLabel: string;
|
||||
if (accessibilityInformation) {
|
||||
ariaLabel = accessibilityInformation.label;
|
||||
} else {
|
||||
ariaLabel = text.indexOf('$(') === -1 ? text : tooltip || text;
|
||||
}
|
||||
const entry: IStatusbarEntry = { text, tooltip, command, color, ariaLabel };
|
||||
|
||||
if (typeof priority === 'undefined') {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -325,13 +325,14 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||
throw new Error(`Provider for ${viewType} already registered`);
|
||||
}
|
||||
|
||||
this._customEditorService.registerCustomEditorCapabilities(viewType, {
|
||||
supportsMultipleEditorsPerDocument
|
||||
});
|
||||
|
||||
const extension = reviveWebviewExtension(extensionData);
|
||||
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
disposables.add(this._customEditorService.registerCustomEditorCapabilities(viewType, {
|
||||
supportsMultipleEditorsPerDocument
|
||||
}));
|
||||
|
||||
disposables.add(this._webviewWorkbenchService.registerResolver({
|
||||
canResolve: (webviewInput) => {
|
||||
return webviewInput instanceof CustomEditorInput && webviewInput.viewType === viewType;
|
||||
@@ -360,6 +361,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();
|
||||
});
|
||||
|
||||
@@ -649,10 +661,11 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
|
||||
) {
|
||||
super();
|
||||
|
||||
this._fromBackup = fromBackup;
|
||||
|
||||
if (_editable) {
|
||||
this._register(workingCopyService.registerWorkingCopy(this));
|
||||
}
|
||||
this._fromBackup = fromBackup;
|
||||
}
|
||||
|
||||
get editorResource() {
|
||||
@@ -710,7 +723,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
|
||||
//#endregion
|
||||
|
||||
public isReadonly() {
|
||||
return this._editable;
|
||||
return !this._editable;
|
||||
}
|
||||
|
||||
public get viewType() {
|
||||
|
||||
@@ -80,6 +80,9 @@ interface IUserFriendlyViewDescriptor {
|
||||
name: string;
|
||||
when?: string;
|
||||
|
||||
icon?: string;
|
||||
contextualTitle?: string;
|
||||
|
||||
// From 'remoteViewDescriptor' type
|
||||
group?: string;
|
||||
remoteName?: string | string[];
|
||||
@@ -100,6 +103,14 @@ 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'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -406,12 +417,14 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
? container.viewOrderDelegate.getOrder(item.group)
|
||||
: undefined;
|
||||
|
||||
const icon = item.icon ? resources.joinPath(extension.description.extensionLocation, item.icon) : undefined;
|
||||
const viewDescriptor = <ICustomViewDescriptor>{
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
ctorDescriptor: new SyncDescriptor(TreeViewPane),
|
||||
when: ContextKeyExpr.deserialize(item.when),
|
||||
containerIcon: viewContainer?.icon,
|
||||
containerIcon: icon || viewContainer?.icon,
|
||||
containerTitle: item.contextualTitle || viewContainer?.name,
|
||||
canToggleVisibility: true,
|
||||
canMoveView: true,
|
||||
treeView: this.instantiationService.createInstance(CustomTreeView, item.id, item.name),
|
||||
@@ -468,6 +481,14 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -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, IDefaultConfigurationExtension, 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);
|
||||
|
||||
@@ -116,7 +117,13 @@ defaultConfigurationExtPoint.setHandler((extensions, { added, removed }) => {
|
||||
const addedDefaultConfigurations = added.map(extension => {
|
||||
const id = extension.description.identifier;
|
||||
const name = extension.description.name;
|
||||
const defaults = objects.deepClone(extension.value);
|
||||
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 <IDefaultConfigurationExtension>{
|
||||
id, name, defaults
|
||||
};
|
||||
|
||||
@@ -135,7 +135,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
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, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, initData.environment));
|
||||
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));
|
||||
@@ -196,9 +196,18 @@ 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;
|
||||
},
|
||||
hasSessions(providerId: string, scopes: string[]): Thenable<boolean> {
|
||||
return extHostAuthentication.hasSessions(providerId, scopes);
|
||||
},
|
||||
getSession(providerId: string, scopes: string[], options: vscode.AuthenticationGetSessionOptions) {
|
||||
return extHostAuthentication.getSession(extension, providerId, scopes, options as any);
|
||||
},
|
||||
getSessions(providerId: string, scopes: string[]): Thenable<readonly vscode.AuthenticationSession[]> {
|
||||
return extHostAuthentication.getSessions(extension, providerId, scopes);
|
||||
},
|
||||
@@ -265,7 +274,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 ?? '<UNKNOWN_APP_ROOT>'; },
|
||||
get uriScheme() { return initData.environment.appUriScheme; },
|
||||
get logLevel() {
|
||||
checkProposedApiEnabled(extension);
|
||||
@@ -428,6 +437,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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -528,12 +541,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);
|
||||
@@ -541,7 +556,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);
|
||||
@@ -703,8 +718,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;
|
||||
});
|
||||
});
|
||||
},
|
||||
@@ -931,10 +946,20 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.onDidCloseNotebookDocument;
|
||||
},
|
||||
get visibleNotebookEditors() {
|
||||
return extHostNotebook.visibleNotebookEditors;
|
||||
},
|
||||
get onDidChangeVisibleNotebookEditors() {
|
||||
return extHostNotebook.onDidChangeVisibleNotebookEditors;
|
||||
},
|
||||
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);
|
||||
},
|
||||
registerNotebookOutputRenderer: (type: string, outputFilter: vscode.NotebookOutputSelector, renderer: vscode.NotebookOutputRenderer) => {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.registerNotebookOutputRenderer(type, extension, outputFilter, renderer);
|
||||
@@ -947,9 +972,21 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
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);
|
||||
},
|
||||
createConcatTextDocument(notebook, selector) {
|
||||
checkProposedApiEnabled(extension);
|
||||
@@ -1050,6 +1087,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,
|
||||
@@ -1086,7 +1124,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
TimelineItem: extHostTypes.TimelineItem,
|
||||
CellKind: extHostTypes.CellKind,
|
||||
CellOutputKind: extHostTypes.CellOutputKind,
|
||||
NotebookCellRunState: extHostTypes.NotebookCellRunState
|
||||
NotebookCellRunState: extHostTypes.NotebookCellRunState,
|
||||
AuthenticationSession2: extHostTypes.AuthenticationSession
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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, IRawOutput } 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';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views';
|
||||
@@ -161,12 +162,20 @@ export interface MainThreadCommentsShape extends IDisposable {
|
||||
}
|
||||
|
||||
export interface MainThreadAuthenticationShape extends IDisposable {
|
||||
$registerAuthenticationProvider(id: string, displayName: string): void;
|
||||
$registerAuthenticationProvider(id: string, displayName: string, supportsMultipleAccounts: boolean): void;
|
||||
$unregisterAuthenticationProvider(id: string): void;
|
||||
$onDidChangeSessions(providerId: string, event: modes.AuthenticationSessionsChangeEvent): 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>;
|
||||
$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 {
|
||||
@@ -215,7 +224,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>;
|
||||
}
|
||||
|
||||
@@ -394,6 +403,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 {
|
||||
@@ -547,7 +557,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;
|
||||
}
|
||||
|
||||
@@ -674,7 +684,7 @@ export interface ICellDto {
|
||||
source: string[];
|
||||
language: string;
|
||||
cellKind: CellKind;
|
||||
outputs: IOutput[];
|
||||
outputs: IProcessedOutput[];
|
||||
metadata?: NotebookCellMetadata;
|
||||
}
|
||||
|
||||
@@ -687,14 +697,17 @@ export type NotebookCellsSplice = [
|
||||
export type NotebookCellOutputsSplice = [
|
||||
number /* start */,
|
||||
number /* delete count */,
|
||||
IOutput[]
|
||||
IRawOutput[]
|
||||
];
|
||||
|
||||
export interface MainThreadNotebookShape extends IDisposable {
|
||||
$registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise<void>;
|
||||
$registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, 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>;
|
||||
$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>;
|
||||
@@ -1006,6 +1019,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(providerId: string, event: modes.AuthenticationSessionsChangeEvent): Promise<void>;
|
||||
$onDidChangeAuthenticationProviders(added: string[], removed: string[]): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ExtHostSearchShape {
|
||||
@@ -1539,30 +1554,44 @@ 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>;
|
||||
$executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, useAttachedKernel: boolean, token: CancellationToken): Promise<void>;
|
||||
$executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise<void>;
|
||||
$saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise<boolean>;
|
||||
$saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise<boolean>;
|
||||
$acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void;
|
||||
$onDidReceiveMessage(uri: UriComponents, message: any): 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, message: any): void;
|
||||
$acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void;
|
||||
$acceptEditorPropertiesChanged(uriComponents: UriComponents, data: INotebookEditorPropertiesChangeData): void;
|
||||
$acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): Promise<void>;
|
||||
|
||||
@@ -24,6 +24,10 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadAuthentication);
|
||||
}
|
||||
|
||||
getProviderIds(): Promise<ReadonlyArray<string>> {
|
||||
return this._proxy.$getProviderIds();
|
||||
}
|
||||
|
||||
get providerIds(): string[] {
|
||||
const ids: string[] = [];
|
||||
this._authenticationProviders.forEach(provider => {
|
||||
@@ -33,16 +37,74 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
return ids;
|
||||
}
|
||||
|
||||
async getSessions(requestingExtension: IExtensionDescription, providerId: string, scopes: string[]): Promise<readonly vscode.AuthenticationSession[]> {
|
||||
private async resolveSessions(providerId: string): Promise<ReadonlyArray<modes.AuthenticationSession>> {
|
||||
const provider = this._authenticationProviders.get(providerId);
|
||||
|
||||
let sessions;
|
||||
if (!provider) {
|
||||
throw new Error(`No authentication provider with id '${providerId}' is currently registered.`);
|
||||
sessions = await this._proxy.$getSessions(providerId);
|
||||
} else {
|
||||
sessions = await provider.getSessions();
|
||||
}
|
||||
|
||||
return sessions;
|
||||
}
|
||||
|
||||
async hasSessions(providerId: string, scopes: string[]): Promise<boolean> {
|
||||
const orderedScopes = scopes.sort().join(' ');
|
||||
const sessions = await this.resolveSessions(providerId);
|
||||
return !!(sessions.filter(session => session.scopes.sort().join(' ') === orderedScopes).length);
|
||||
}
|
||||
|
||||
async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: string[], options: vscode.AuthenticationGetSessionOptions & { createIfNone: true }): Promise<vscode.AuthenticationSession2>;
|
||||
async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: string[], options: vscode.AuthenticationGetSessionOptions): Promise<vscode.AuthenticationSession2 | undefined> {
|
||||
const provider = this._authenticationProviders.get(providerId);
|
||||
const extensionName = requestingExtension.displayName || requestingExtension.name;
|
||||
const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier);
|
||||
|
||||
if (!provider) {
|
||||
return this._proxy.$getSession(providerId, scopes, extensionId, extensionName, options);
|
||||
}
|
||||
|
||||
const orderedScopes = scopes.sort().join(' ');
|
||||
const sessions = (await provider.getSessions()).filter(session => session.scopes.sort().join(' ') === orderedScopes);
|
||||
|
||||
if (sessions.length) {
|
||||
if (!provider.supportsMultipleAccounts) {
|
||||
const session = sessions[0];
|
||||
const allowed = await this._proxy.$getSessionsPrompt(providerId, session.account.displayName, provider.displayName, 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.displayName, 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.displayName, extensionName);
|
||||
if (!isAllowed) {
|
||||
throw new Error('User did not consent to login.');
|
||||
}
|
||||
|
||||
const session = await provider.login(scopes);
|
||||
await this._proxy.$setTrustedExtension(providerId, session.account.displayName, extensionId, extensionName);
|
||||
return session;
|
||||
} else {
|
||||
await this._proxy.$requestNewSession(providerId, scopes, extensionId, extensionName);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getSessions(requestingExtension: IExtensionDescription, providerId: string, scopes: string[]): Promise<readonly vscode.AuthenticationSession[]> {
|
||||
const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier);
|
||||
const orderedScopes = scopes.sort().join(' ');
|
||||
|
||||
return (await provider.getSessions())
|
||||
const sessions = await this.resolveSessions(providerId);
|
||||
return sessions
|
||||
.filter(session => session.scopes.sort().join(' ') === orderedScopes)
|
||||
.map(session => {
|
||||
return {
|
||||
@@ -51,9 +113,10 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
scopes: session.scopes,
|
||||
getAccessToken: async () => {
|
||||
const isAllowed = await this._proxy.$getSessionsPrompt(
|
||||
provider.id,
|
||||
providerId,
|
||||
session.account.displayName,
|
||||
provider.displayName,
|
||||
'', // TODO
|
||||
// provider.displayName,
|
||||
extensionId,
|
||||
requestingExtension.displayName || requestingExtension.name);
|
||||
|
||||
@@ -61,7 +124,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
throw new Error('User did not consent to token access.');
|
||||
}
|
||||
|
||||
return session.getAccessToken();
|
||||
return session.accessToken;
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -97,7 +160,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
throw new Error('User did not consent to token access.');
|
||||
}
|
||||
|
||||
return session.getAccessToken();
|
||||
return session.accessToken;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -105,7 +168,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
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);
|
||||
@@ -119,18 +182,15 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
this._authenticationProviders.set(provider.id, provider);
|
||||
|
||||
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.displayName, provider.supportsMultipleAccounts);
|
||||
|
||||
return new Disposable(() => {
|
||||
listener.dispose();
|
||||
this._authenticationProviders.delete(provider.id);
|
||||
this._proxy.$unregisterAuthenticationProvider(provider.id);
|
||||
this._onDidChangeAuthenticationProviders.fire({ added: [], removed: [provider.id] });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -167,7 +227,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 +235,14 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
|
||||
throw new Error(`Unable to find authentication provider with handle: ${providerId}`);
|
||||
}
|
||||
|
||||
$onDidChangeAuthenticationSessions(providerId: string, event: modes.AuthenticationSessionsChangeEvent) {
|
||||
this._onDidChangeSessions.fire({ [providerId]: event });
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
$onDidChangeAuthenticationProviders(added: string[], removed: string[]) {
|
||||
this._onDidChangeAuthenticationProviders.fire({ added, removed });
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
@@ -1392,7 +1319,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 +1337,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 +1705,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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,14 +10,16 @@ import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecyc
|
||||
import { ISplice } from 'vs/base/common/sequence';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { CellKind, CellOutputKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, MainThreadDocumentsShape, INotebookEditorPropertiesChangeData, INotebookDocumentsAndEditorsDelta } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { CellKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, MainThreadDocumentsShape, INotebookEditorPropertiesChangeData, INotebookDocumentsAndEditorsDelta } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||
import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, IErrorOutput, INotebookDisplayOrder, INotebookEditData, IOrderedMimeType, IStreamOutput, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellsChangedEvent, NotebookCellsSplice2, sortMimeTypes, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType, NotebookDataDto } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { Disposable as VSCodeDisposable } from './extHostTypes';
|
||||
import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, INotebookDisplayOrder, INotebookEditData, NotebookCellsChangedEvent, NotebookCellsSplice2, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType, NotebookDataDto, IOutputRenderRequest, IOutputRenderResponse, IOutputRenderResponseOutputInfo, IOutputRenderResponseCellInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData';
|
||||
import { NotImplementedProxy } from 'vs/base/common/types';
|
||||
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview';
|
||||
|
||||
interface IObservable<T> {
|
||||
proxy: T;
|
||||
@@ -40,6 +42,12 @@ function getObservable<T extends Object>(obj: T): IObservable<T> {
|
||||
};
|
||||
}
|
||||
|
||||
interface INotebookEventEmitter {
|
||||
emitModelChange(events: vscode.NotebookCellsChangeEvent): void;
|
||||
emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void;
|
||||
emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void;
|
||||
}
|
||||
|
||||
export class ExtHostCell extends Disposable implements vscode.NotebookCell {
|
||||
|
||||
// private originalSource: string[];
|
||||
@@ -59,14 +67,17 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell {
|
||||
return this._documentData.document;
|
||||
}
|
||||
|
||||
get notebook(): vscode.NotebookDocument {
|
||||
return this._notebook;
|
||||
}
|
||||
|
||||
get source() {
|
||||
// todo@jrieken remove this
|
||||
return this._documentData.getText();
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly viewType: string,
|
||||
private readonly documentUri: URI,
|
||||
private readonly _notebook: ExtHostNotebookDocument,
|
||||
readonly handle: number,
|
||||
readonly uri: URI,
|
||||
content: string,
|
||||
@@ -133,7 +144,7 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell {
|
||||
}
|
||||
|
||||
private updateMetadata(): Promise<void> {
|
||||
return this._proxy.$updateNotebookCellMetadata(this.viewType, this.documentUri, this.handle, this._metadata);
|
||||
return this._proxy.$updateNotebookCellMetadata(this._notebook.viewType, this._notebook.uri, this.handle, this._metadata);
|
||||
}
|
||||
|
||||
attachTextDocument(document: ExtHostDocumentData) {
|
||||
@@ -217,9 +228,12 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
|
||||
return this._versionId;
|
||||
}
|
||||
|
||||
private _disposed = false;
|
||||
|
||||
constructor(
|
||||
private readonly _proxy: MainThreadNotebookShape,
|
||||
private _documentsAndEditors: ExtHostDocumentsAndEditors,
|
||||
private _emitter: INotebookEventEmitter,
|
||||
public viewType: string,
|
||||
public uri: URI,
|
||||
public renderingHandler: ExtHostNotebookOutputRenderingHandler
|
||||
@@ -238,6 +252,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._disposed = true;
|
||||
super.dispose();
|
||||
this._cellDisposableMapping.forEach(cell => cell.dispose());
|
||||
}
|
||||
@@ -246,7 +261,8 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
|
||||
|
||||
get isDirty() { return false; }
|
||||
|
||||
accpetModelChanged(event: NotebookCellsChangedEvent) {
|
||||
accpetModelChanged(event: NotebookCellsChangedEvent): void {
|
||||
this._versionId = event.versionId;
|
||||
if (event.kind === NotebookCellsChangeType.ModelChange) {
|
||||
this.$spliceNotebookCells(event.changes);
|
||||
} else if (event.kind === NotebookCellsChangeType.Move) {
|
||||
@@ -258,19 +274,19 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
|
||||
} else if (event.kind === NotebookCellsChangeType.ChangeLanguage) {
|
||||
this.$changeCellLanguage(event.index, event.language);
|
||||
}
|
||||
|
||||
this._versionId = event.versionId;
|
||||
}
|
||||
|
||||
private $spliceNotebookCells(splices: NotebookCellsSplice2[]): void {
|
||||
if (!splices.length) {
|
||||
if (this._disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
let contentChangeEvents: vscode.NotebookCellsChangeData[] = [];
|
||||
|
||||
splices.reverse().forEach(splice => {
|
||||
let cellDtos = splice[2];
|
||||
let newCells = cellDtos.map(cell => {
|
||||
const extCell = new ExtHostCell(this.viewType, this.uri, cell.handle, URI.revive(cell.uri), cell.source.join('\n'), cell.cellKind, cell.language, cell.outputs, cell.metadata, this._proxy);
|
||||
const extCell = new ExtHostCell(this, cell.handle, URI.revive(cell.uri), cell.source.join('\n'), cell.cellKind, cell.language, cell.outputs, cell.metadata, this._proxy);
|
||||
const documentData = this._documentsAndEditors.getDocument(URI.revive(cell.uri));
|
||||
|
||||
if (documentData) {
|
||||
@@ -297,107 +313,88 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
|
||||
}
|
||||
|
||||
this.cells.splice(splice[0], splice[1], ...newCells);
|
||||
|
||||
const event: vscode.NotebookCellsChangeData = {
|
||||
start: splice[0],
|
||||
deletedCount: splice[1],
|
||||
items: newCells
|
||||
};
|
||||
|
||||
contentChangeEvents.push(event);
|
||||
});
|
||||
|
||||
this._emitter.emitModelChange({
|
||||
document: this,
|
||||
changes: contentChangeEvents
|
||||
});
|
||||
}
|
||||
|
||||
private $moveCell(index: number, newIdx: number) {
|
||||
private $moveCell(index: number, newIdx: number): void {
|
||||
const cells = this.cells.splice(index, 1);
|
||||
this.cells.splice(newIdx, 0, ...cells);
|
||||
const changes: vscode.NotebookCellsChangeData[] = [{
|
||||
start: index,
|
||||
deletedCount: 1,
|
||||
items: []
|
||||
}, {
|
||||
start: newIdx,
|
||||
deletedCount: 0,
|
||||
items: cells
|
||||
}];
|
||||
this._emitter.emitModelChange({
|
||||
document: this,
|
||||
changes
|
||||
});
|
||||
}
|
||||
|
||||
private $clearCellOutputs(index: number) {
|
||||
private $clearCellOutputs(index: number): void {
|
||||
const cell = this.cells[index];
|
||||
cell.outputs = [];
|
||||
const event: vscode.NotebookCellOutputsChangeEvent = { document: this, cells: [cell] };
|
||||
this._emitter.emitCellOutputsChange(event);
|
||||
}
|
||||
|
||||
private $clearAllCellOutputs() {
|
||||
this.cells.forEach(cell => cell.outputs = []);
|
||||
private $clearAllCellOutputs(): void {
|
||||
const modifedCells: vscode.NotebookCell[] = [];
|
||||
this.cells.forEach(cell => {
|
||||
if (cell.outputs.length !== 0) {
|
||||
cell.outputs = [];
|
||||
modifedCells.push(cell);
|
||||
}
|
||||
});
|
||||
const event: vscode.NotebookCellOutputsChangeEvent = { document: this, cells: modifedCells };
|
||||
this._emitter.emitCellOutputsChange(event);
|
||||
}
|
||||
|
||||
private $changeCellLanguage(index: number, language: string) {
|
||||
private $changeCellLanguage(index: number, language: string): void {
|
||||
const cell = this.cells[index];
|
||||
cell.language = language;
|
||||
const event: vscode.NotebookCellLanguageChangeEvent = { document: this, cell, language };
|
||||
this._emitter.emitCellLanguageChange(event);
|
||||
}
|
||||
|
||||
eventuallyUpdateCellOutputs(cell: ExtHostCell, diffs: ISplice<vscode.CellOutput>[]) {
|
||||
async eventuallyUpdateCellOutputs(cell: ExtHostCell, diffs: ISplice<vscode.CellOutput>[]) {
|
||||
let renderers = new Set<number>();
|
||||
let outputDtos: NotebookCellOutputsSplice[] = diffs.map(diff => {
|
||||
let outputs = diff.toInsert;
|
||||
|
||||
let transformedOutputs = outputs.map(output => {
|
||||
if (output.outputKind === CellOutputKind.Rich) {
|
||||
const ret = this.transformMimeTypes(output);
|
||||
|
||||
if (ret.orderedMimeTypes[ret.pickedMimeTypeIndex].isResolved) {
|
||||
renderers.add(ret.orderedMimeTypes[ret.pickedMimeTypeIndex].rendererId!);
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
return output as IStreamOutput | IErrorOutput;
|
||||
}
|
||||
});
|
||||
|
||||
return [diff.start, diff.deleteCount, transformedOutputs];
|
||||
return [diff.start, diff.deleteCount, outputs];
|
||||
});
|
||||
|
||||
this._proxy.$spliceNotebookCellOutputs(this.viewType, this.uri, cell.handle, outputDtos, Array.from(renderers));
|
||||
}
|
||||
|
||||
transformMimeTypes(output: vscode.CellDisplayOutput): ITransformedDisplayOutputDto {
|
||||
let mimeTypes = Object.keys(output.data);
|
||||
let coreDisplayOrder = this.renderingHandler.outputDisplayOrder;
|
||||
const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], this._displayOrder, coreDisplayOrder?.defaultOrder || []);
|
||||
|
||||
let orderMimeTypes: IOrderedMimeType[] = [];
|
||||
|
||||
sorted.forEach(mimeType => {
|
||||
let handlers = this.renderingHandler.findBestMatchedRenderer(mimeType);
|
||||
|
||||
if (handlers.length) {
|
||||
let renderedOutput = handlers[0].render(this, output, mimeType);
|
||||
|
||||
orderMimeTypes.push({
|
||||
mimeType: mimeType,
|
||||
isResolved: true,
|
||||
rendererId: handlers[0].handle,
|
||||
output: renderedOutput
|
||||
});
|
||||
|
||||
for (let i = 1; i < handlers.length; i++) {
|
||||
orderMimeTypes.push({
|
||||
mimeType: mimeType,
|
||||
isResolved: false,
|
||||
rendererId: handlers[i].handle
|
||||
});
|
||||
}
|
||||
|
||||
if (mimeTypeSupportedByCore(mimeType)) {
|
||||
orderMimeTypes.push({
|
||||
mimeType: mimeType,
|
||||
isResolved: false,
|
||||
rendererId: -1
|
||||
});
|
||||
}
|
||||
} else {
|
||||
orderMimeTypes.push({
|
||||
mimeType: mimeType,
|
||||
isResolved: false
|
||||
});
|
||||
}
|
||||
await this._proxy.$spliceNotebookCellOutputs(this.viewType, this.uri, cell.handle, outputDtos, Array.from(renderers));
|
||||
this._emitter.emitCellOutputsChange({
|
||||
document: this,
|
||||
cells: [cell]
|
||||
});
|
||||
|
||||
return {
|
||||
outputKind: output.outputKind,
|
||||
data: output.data,
|
||||
orderedMimeTypes: orderMimeTypes,
|
||||
pickedMimeTypeIndex: 0
|
||||
};
|
||||
}
|
||||
|
||||
getCell(cellHandle: number) {
|
||||
return this.cells.find(cell => cell.handle === cellHandle);
|
||||
}
|
||||
|
||||
getCell2(cellUri: UriComponents) {
|
||||
return this.cells.find(cell => cell.uri.fragment === cellUri.fragment);
|
||||
}
|
||||
|
||||
attachCellTextDocument(textDocument: ExtHostDocumentData) {
|
||||
let cell = this.cells.find(cell => cell.uri.toString() === textDocument.document.uri.toString());
|
||||
if (cell) {
|
||||
@@ -448,25 +445,10 @@ export class NotebookEditorCellEditBuilder implements vscode.NotebookEditorCellE
|
||||
source: sourceArr,
|
||||
language,
|
||||
cellKind: type,
|
||||
outputs: (outputs as any[]), // TODO@rebornix
|
||||
outputs: outputs,
|
||||
metadata
|
||||
};
|
||||
|
||||
const transformedOutputs = outputs.map(output => {
|
||||
if (output.outputKind === CellOutputKind.Rich) {
|
||||
const ret = this.editor.document.transformMimeTypes(output);
|
||||
|
||||
if (ret.orderedMimeTypes[ret.pickedMimeTypeIndex].isResolved) {
|
||||
this._renderers.add(ret.orderedMimeTypes[ret.pickedMimeTypeIndex].rendererId!);
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
return output as IStreamOutput | IErrorOutput;
|
||||
}
|
||||
});
|
||||
|
||||
cell.outputs = transformedOutputs;
|
||||
|
||||
this._collectedEdits.push({
|
||||
editType: CellEditType.Insert,
|
||||
index,
|
||||
@@ -489,6 +471,35 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook
|
||||
private _viewColumn: vscode.ViewColumn | undefined;
|
||||
|
||||
selection?: ExtHostCell = undefined;
|
||||
|
||||
private _active: boolean = false;
|
||||
get active(): boolean {
|
||||
return this._active;
|
||||
}
|
||||
|
||||
set active(_state: boolean) {
|
||||
throw readonly('active');
|
||||
}
|
||||
|
||||
private _visible: boolean = false;
|
||||
get visible(): boolean {
|
||||
return this._visible;
|
||||
}
|
||||
|
||||
set visible(_state: boolean) {
|
||||
throw readonly('visible');
|
||||
}
|
||||
|
||||
_acceptVisibility(value: boolean) {
|
||||
this._visible = value;
|
||||
}
|
||||
|
||||
_acceptActive(value: boolean) {
|
||||
this._active = value;
|
||||
}
|
||||
|
||||
private _onDidDispose = new Emitter<void>();
|
||||
readonly onDidDispose: Event<void> = this._onDidDispose.event;
|
||||
onDidReceiveMessage: vscode.Event<any> = this._onDidReceiveMessage.event;
|
||||
|
||||
constructor(
|
||||
@@ -497,6 +508,7 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook
|
||||
public uri: URI,
|
||||
private _proxy: MainThreadNotebookShape,
|
||||
private _onDidReceiveMessage: Emitter<any>,
|
||||
private _webviewInitData: WebviewInitData,
|
||||
public document: ExtHostNotebookDocument,
|
||||
private _documentsAndEditors: ExtHostDocumentsAndEditors
|
||||
) {
|
||||
@@ -584,6 +596,13 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook
|
||||
return this._proxy.$postMessage(this.document.handle, message);
|
||||
}
|
||||
|
||||
asWebviewUri(localResource: vscode.Uri): vscode.Uri {
|
||||
return asWebviewUri(this._webviewInitData, this.id, localResource);
|
||||
}
|
||||
dispose() {
|
||||
this._onDidDispose.fire();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostNotebookOutputRenderer {
|
||||
@@ -620,16 +639,21 @@ export interface ExtHostNotebookOutputRenderingHandler {
|
||||
}
|
||||
|
||||
export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostNotebookOutputRenderingHandler {
|
||||
private static _handlePool: number = 0;
|
||||
|
||||
private readonly _proxy: MainThreadNotebookShape;
|
||||
private readonly _notebookContentProviders = new Map<string, { readonly provider: vscode.NotebookContentProvider, readonly extension: IExtensionDescription; }>();
|
||||
private readonly _notebookKernels = new Map<string, { readonly kernel: vscode.NotebookKernel, readonly extension: IExtensionDescription; }>();
|
||||
private readonly _documents = new Map<string, ExtHostNotebookDocument>();
|
||||
private readonly _unInitializedDocuments = new Map<string, ExtHostNotebookDocument>();
|
||||
private readonly _editors = new Map<string, { editor: ExtHostNotebookEditor, onDidReceiveMessage: Emitter<any>; }>();
|
||||
private readonly _notebookOutputRenderers = new Map<number, ExtHostNotebookOutputRenderer>();
|
||||
|
||||
private readonly _onDidChangeNotebookDocument = new Emitter<{ document: ExtHostNotebookDocument, changes: NotebookCellsChangedEvent[]; }>();
|
||||
readonly onDidChangeNotebookDocument: Event<{ document: ExtHostNotebookDocument, changes: NotebookCellsChangedEvent[]; }> = this._onDidChangeNotebookDocument.event;
|
||||
private readonly _notebookOutputRenderers = new Map<string, ExtHostNotebookOutputRenderer>();
|
||||
private readonly _onDidChangeNotebookCells = new Emitter<vscode.NotebookCellsChangeEvent>();
|
||||
readonly onDidChangeNotebookCells = this._onDidChangeNotebookCells.event;
|
||||
private readonly _onDidChangeCellOutputs = new Emitter<vscode.NotebookCellOutputsChangeEvent>();
|
||||
readonly onDidChangeCellOutputs = this._onDidChangeCellOutputs.event;
|
||||
private readonly _onDidChangeCellLanguage = new Emitter<vscode.NotebookCellLanguageChangeEvent>();
|
||||
readonly onDidChangeCellLanguage = this._onDidChangeCellLanguage.event;
|
||||
private readonly _onDidChangeActiveNotebookEditor = new Emitter<vscode.NotebookEditor | undefined>();
|
||||
readonly onDidChangeActiveNotebookEditor = this._onDidChangeActiveNotebookEditor.event;
|
||||
|
||||
private _outputDisplayOrder: INotebookDisplayOrder | undefined;
|
||||
|
||||
@@ -653,8 +677,11 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
onDidOpenNotebookDocument: Event<vscode.NotebookDocument> = this._onDidOpenNotebookDocument.event;
|
||||
private _onDidCloseNotebookDocument = new Emitter<vscode.NotebookDocument>();
|
||||
onDidCloseNotebookDocument: Event<vscode.NotebookDocument> = this._onDidCloseNotebookDocument.event;
|
||||
visibleNotebookEditors: ExtHostNotebookEditor[] = [];
|
||||
private _onDidChangeVisibleNotebookEditors = new Emitter<vscode.NotebookEditor[]>();
|
||||
onDidChangeVisibleNotebookEditors = this._onDidChangeVisibleNotebookEditors.event;
|
||||
|
||||
constructor(mainContext: IMainContext, commands: ExtHostCommands, private _documentsAndEditors: ExtHostDocumentsAndEditors) {
|
||||
constructor(mainContext: IMainContext, commands: ExtHostCommands, private _documentsAndEditors: ExtHostDocumentsAndEditors, private readonly _webviewInitData: WebviewInitData) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadNotebook);
|
||||
|
||||
commands.registerArgumentProcessor({
|
||||
@@ -683,15 +710,85 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
filter: vscode.NotebookOutputSelector,
|
||||
renderer: vscode.NotebookOutputRenderer
|
||||
): vscode.Disposable {
|
||||
if (this._notebookKernels.has(type)) {
|
||||
throw new Error(`Notebook renderer for '${type}' already registered`);
|
||||
}
|
||||
|
||||
let extHostRenderer = new ExtHostNotebookOutputRenderer(type, filter, renderer);
|
||||
this._notebookOutputRenderers.set(extHostRenderer.handle, extHostRenderer);
|
||||
this._proxy.$registerNotebookRenderer({ id: extension.identifier, location: extension.extensionLocation }, type, filter, extHostRenderer.handle, renderer.preloads || []);
|
||||
return new VSCodeDisposable(() => {
|
||||
this._notebookOutputRenderers.delete(extHostRenderer.handle);
|
||||
this._proxy.$unregisterNotebookRenderer(extHostRenderer.handle);
|
||||
this._notebookOutputRenderers.set(extHostRenderer.type, extHostRenderer);
|
||||
this._proxy.$registerNotebookRenderer({ id: extension.identifier, location: extension.extensionLocation }, type, filter, renderer.preloads || []);
|
||||
return new extHostTypes.Disposable(() => {
|
||||
this._notebookOutputRenderers.delete(extHostRenderer.type);
|
||||
this._proxy.$unregisterNotebookRenderer(extHostRenderer.type);
|
||||
});
|
||||
}
|
||||
|
||||
async $renderOutputs(uriComponents: UriComponents, id: string, request: IOutputRenderRequest<UriComponents>): Promise<IOutputRenderResponse<UriComponents> | undefined> {
|
||||
if (!this._notebookOutputRenderers.has(id)) {
|
||||
throw new Error(`Notebook renderer for '${id}' is not registered`);
|
||||
}
|
||||
|
||||
const document = this._documents.get(URI.revive(uriComponents).toString());
|
||||
|
||||
if (!document) {
|
||||
return undefined; // {{SQL CARBON EDIT}} strict-null-checks
|
||||
}
|
||||
|
||||
const renderer = this._notebookOutputRenderers.get(id)!;
|
||||
const cellsResponse: IOutputRenderResponseCellInfo<UriComponents>[] = request.items.map(cellInfo => {
|
||||
const cell = document.getCell2(cellInfo.key);
|
||||
const outputResponse: IOutputRenderResponseOutputInfo[] = cellInfo.outputs.map(output => {
|
||||
return {
|
||||
index: output.index,
|
||||
mimeType: output.mimeType,
|
||||
handlerId: id,
|
||||
transformedOutput: renderer.render(document, cell!.outputs[output.index] as vscode.CellDisplayOutput, output.mimeType)
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
key: cellInfo.key,
|
||||
outputs: outputResponse
|
||||
};
|
||||
});
|
||||
|
||||
return { items: cellsResponse };
|
||||
}
|
||||
|
||||
/**
|
||||
* The request carry the raw data for outputs so we don't look up in the existing document
|
||||
*/
|
||||
async $renderOutputs2<T>(uriComponents: UriComponents, id: string, request: IOutputRenderRequest<T>): Promise<IOutputRenderResponse<T> | undefined> {
|
||||
if (!this._notebookOutputRenderers.has(id)) {
|
||||
throw new Error(`Notebook renderer for '${id}' is not registered`);
|
||||
}
|
||||
|
||||
const document = this._documents.get(URI.revive(uriComponents).toString());
|
||||
|
||||
if (!document) {
|
||||
return undefined; // {{SQL CARBON EDIT}} strict-null-checks
|
||||
}
|
||||
|
||||
const renderer = this._notebookOutputRenderers.get(id)!;
|
||||
const cellsResponse: IOutputRenderResponseCellInfo<T>[] = request.items.map(cellInfo => {
|
||||
const outputResponse: IOutputRenderResponseOutputInfo[] = cellInfo.outputs.map(output => {
|
||||
return {
|
||||
index: output.index,
|
||||
mimeType: output.mimeType,
|
||||
handlerId: id,
|
||||
transformedOutput: renderer.render(document, output.output! as vscode.CellDisplayOutput, output.mimeType)
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
key: cellInfo.key,
|
||||
outputs: outputResponse
|
||||
};
|
||||
});
|
||||
|
||||
return { items: cellsResponse };
|
||||
}
|
||||
|
||||
findBestMatchedRenderer(mimeType: string): ExtHostNotebookOutputRenderer[] {
|
||||
let matches: ExtHostNotebookOutputRenderer[] = [];
|
||||
for (let renderer of this._notebookOutputRenderers) {
|
||||
@@ -713,50 +810,70 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
throw new Error(`Notebook provider for '${viewType}' already registered`);
|
||||
}
|
||||
|
||||
// if ((<any>provider).executeCell) {
|
||||
// throw new Error('NotebookContentKernel.executeCell is removed, please use vscode.notebook.registerNotebookKernel instead.');
|
||||
// }
|
||||
|
||||
this._notebookContentProviders.set(viewType, { extension, provider });
|
||||
this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType);
|
||||
return new VSCodeDisposable(() => {
|
||||
|
||||
const listener = provider.onDidChangeNotebook
|
||||
? provider.onDidChangeNotebook(e => this._proxy.$onNotebookChange(viewType, e.document.uri))
|
||||
: Disposable.None;
|
||||
|
||||
this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, provider.kernel ? { id: viewType, label: provider.kernel.label, extensionLocation: extension.extensionLocation, preloads: provider.kernel.preloads } : undefined);
|
||||
return new extHostTypes.Disposable(() => {
|
||||
listener.dispose();
|
||||
this._notebookContentProviders.delete(viewType);
|
||||
this._proxy.$unregisterNotebookProvider(viewType);
|
||||
});
|
||||
}
|
||||
|
||||
async $resolveNotebookData(viewType: string, uri: UriComponents): Promise<NotebookDataDto | undefined> {
|
||||
let provider = this._notebookContentProviders.get(viewType);
|
||||
let document = this._documents.get(URI.revive(uri).toString());
|
||||
registerNotebookKernel(extension: IExtensionDescription, id: string, selectors: vscode.GlobPattern[], kernel: vscode.NotebookKernel): vscode.Disposable {
|
||||
if (this._notebookKernels.has(id)) {
|
||||
throw new Error(`Notebook kernel for '${id}' already registered`);
|
||||
}
|
||||
|
||||
this._notebookKernels.set(id, { kernel, extension });
|
||||
const transformedSelectors = selectors.map(selector => typeConverters.GlobPattern.from(selector));
|
||||
|
||||
this._proxy.$registerNotebookKernel({ id: extension.identifier, location: extension.extensionLocation }, id, kernel.label, transformedSelectors, kernel.preloads || []);
|
||||
return new extHostTypes.Disposable(() => {
|
||||
this._notebookKernels.delete(id);
|
||||
this._proxy.$unregisterNotebookKernel(id);
|
||||
});
|
||||
}
|
||||
|
||||
async $resolveNotebookData(viewType: string, uri: UriComponents): Promise<NotebookDataDto | undefined> {
|
||||
const provider = this._notebookContentProviders.get(viewType);
|
||||
const revivedUri = URI.revive(uri);
|
||||
|
||||
if (provider) {
|
||||
let document = this._documents.get(URI.revive(uri).toString());
|
||||
|
||||
if (!document) {
|
||||
const that = this;
|
||||
document = this._unInitializedDocuments.get(revivedUri.toString()) ?? new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, {
|
||||
emitModelChange(event: vscode.NotebookCellsChangeEvent): void {
|
||||
that._onDidChangeNotebookCells.fire(event);
|
||||
},
|
||||
emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void {
|
||||
that._onDidChangeCellOutputs.fire(event);
|
||||
},
|
||||
emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void {
|
||||
that._onDidChangeCellLanguage.fire(event);
|
||||
}
|
||||
}, viewType, revivedUri, this);
|
||||
this._unInitializedDocuments.set(revivedUri.toString(), document);
|
||||
}
|
||||
|
||||
if (provider && document) {
|
||||
const rawCells = await provider.provider.openNotebook(URI.revive(uri));
|
||||
const renderers = new Set<number>();
|
||||
const dto = {
|
||||
metadata: {
|
||||
...notebookDocumentMetadataDefaults,
|
||||
...rawCells.metadata
|
||||
},
|
||||
languages: rawCells.languages,
|
||||
cells: rawCells.cells.map(cell => {
|
||||
let transformedOutputs = cell.outputs.map(output => {
|
||||
if (output.outputKind === CellOutputKind.Rich) {
|
||||
// TODO display string[]
|
||||
const ret = this._transformMimeTypes(document!, (rawCells.metadata.displayOrder as string[]) || [], output);
|
||||
|
||||
if (ret.orderedMimeTypes[ret.pickedMimeTypeIndex].isResolved) {
|
||||
renderers.add(ret.orderedMimeTypes[ret.pickedMimeTypeIndex].rendererId!);
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
return output as IStreamOutput | IErrorOutput;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
language: cell.language,
|
||||
cellKind: cell.cellKind,
|
||||
metadata: cell.metadata,
|
||||
source: cell.source,
|
||||
outputs: transformedOutputs
|
||||
};
|
||||
})
|
||||
cells: rawCells.cells,
|
||||
};
|
||||
|
||||
return dto;
|
||||
@@ -765,58 +882,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
return undefined; // {{SQL CARBON EDIT}}
|
||||
}
|
||||
|
||||
private _transformMimeTypes(document: ExtHostNotebookDocument, displayOrder: string[], output: vscode.CellDisplayOutput): ITransformedDisplayOutputDto {
|
||||
let mimeTypes = Object.keys(output.data);
|
||||
let coreDisplayOrder = this.outputDisplayOrder;
|
||||
const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], displayOrder, coreDisplayOrder?.defaultOrder || []);
|
||||
|
||||
let orderMimeTypes: IOrderedMimeType[] = [];
|
||||
|
||||
sorted.forEach(mimeType => {
|
||||
let handlers = this.findBestMatchedRenderer(mimeType);
|
||||
|
||||
if (handlers.length) {
|
||||
let renderedOutput = handlers[0].render(document, output, mimeType);
|
||||
|
||||
orderMimeTypes.push({
|
||||
mimeType: mimeType,
|
||||
isResolved: true,
|
||||
rendererId: handlers[0].handle,
|
||||
output: renderedOutput
|
||||
});
|
||||
|
||||
for (let i = 1; i < handlers.length; i++) {
|
||||
orderMimeTypes.push({
|
||||
mimeType: mimeType,
|
||||
isResolved: false,
|
||||
rendererId: handlers[i].handle
|
||||
});
|
||||
}
|
||||
|
||||
if (mimeTypeSupportedByCore(mimeType)) {
|
||||
orderMimeTypes.push({
|
||||
mimeType: mimeType,
|
||||
isResolved: false,
|
||||
rendererId: -1
|
||||
});
|
||||
}
|
||||
} else {
|
||||
orderMimeTypes.push({
|
||||
mimeType: mimeType,
|
||||
isResolved: false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
outputKind: output.outputKind,
|
||||
data: output.data,
|
||||
orderedMimeTypes: orderMimeTypes,
|
||||
pickedMimeTypeIndex: 0
|
||||
};
|
||||
}
|
||||
|
||||
async $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise<void> {
|
||||
async $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, useAttachedKernel: boolean, token: CancellationToken): Promise<void> {
|
||||
let document = this._documents.get(URI.revive(uri).toString());
|
||||
|
||||
if (!document) {
|
||||
@@ -824,9 +890,38 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
}
|
||||
|
||||
if (this._notebookContentProviders.has(viewType)) {
|
||||
let cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined;
|
||||
const cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined;
|
||||
const provider = this._notebookContentProviders.get(viewType)!.provider;
|
||||
|
||||
return this._notebookContentProviders.get(viewType)!.provider.executeCell(document, cell, token);
|
||||
if (provider.kernel && useAttachedKernel) {
|
||||
if (cell) {
|
||||
return provider.kernel.executeCell(document, cell, token);
|
||||
} else {
|
||||
return provider.kernel.executeAllCells(document, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async $executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise<void> {
|
||||
let document = this._documents.get(URI.revive(uri).toString());
|
||||
|
||||
if (!document || document.viewType !== viewType) {
|
||||
return;
|
||||
}
|
||||
|
||||
let kernelInfo = this._notebookKernels.get(kernelId);
|
||||
|
||||
if (!kernelInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
let cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined;
|
||||
|
||||
if (cell) {
|
||||
return kernelInfo.kernel.executeCell(document, cell, token);
|
||||
} else {
|
||||
return kernelInfo.kernel.executeAllCells(document, token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -872,8 +967,21 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
this._outputDisplayOrder = displayOrder;
|
||||
}
|
||||
|
||||
$onDidReceiveMessage(uri: UriComponents, message: any): void {
|
||||
let editor = this._editors.get(URI.revive(uri).toString());
|
||||
// TODO: remove document - editor one on one mapping
|
||||
private _getEditorFromURI(uriComponents: UriComponents) {
|
||||
const uriStr = URI.revive(uriComponents).toString();
|
||||
let editor: { editor: ExtHostNotebookEditor, onDidReceiveMessage: Emitter<any>; } | undefined;
|
||||
this._editors.forEach(e => {
|
||||
if (e.editor.uri.toString() === uriStr) {
|
||||
editor = e;
|
||||
}
|
||||
});
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
$onDidReceiveMessage(editorId: string, message: any): void {
|
||||
let editor = this._editors.get(editorId);
|
||||
|
||||
if (editor) {
|
||||
editor.onDidReceiveMessage.fire(message);
|
||||
@@ -881,20 +989,15 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
}
|
||||
|
||||
$acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void {
|
||||
let editor = this._editors.get(URI.revive(uriComponents).toString());
|
||||
const document = this._documents.get(URI.revive(uriComponents).toString());
|
||||
|
||||
if (editor) {
|
||||
editor.editor.document.accpetModelChanged(event);
|
||||
this._onDidChangeNotebookDocument.fire({
|
||||
document: editor.editor.document,
|
||||
changes: [event]
|
||||
});
|
||||
if (document) {
|
||||
document.accpetModelChanged(event);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$acceptEditorPropertiesChanged(uriComponents: UriComponents, data: INotebookEditorPropertiesChangeData): void {
|
||||
let editor = this._editors.get(URI.revive(uriComponents).toString());
|
||||
let editor = this._getEditorFromURI(uriComponents);
|
||||
|
||||
if (!editor) {
|
||||
return;
|
||||
@@ -910,61 +1013,196 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
editor.editor.selection = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.metadata) {
|
||||
editor.editor.document.metadata = {
|
||||
...notebookDocumentMetadataDefaults,
|
||||
...data.metadata
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private _createExtHostEditor(document: ExtHostNotebookDocument, editorId: string, selections: number[]) {
|
||||
const onDidReceiveMessage = new Emitter<any>();
|
||||
const revivedUri = document.uri;
|
||||
|
||||
let editor = new ExtHostNotebookEditor(
|
||||
document.viewType,
|
||||
editorId,
|
||||
revivedUri,
|
||||
this._proxy,
|
||||
onDidReceiveMessage,
|
||||
this._webviewInitData,
|
||||
document,
|
||||
this._documentsAndEditors
|
||||
);
|
||||
|
||||
const cells = editor.document.cells;
|
||||
|
||||
if (selections.length) {
|
||||
const firstCell = selections[0];
|
||||
editor.selection = cells.find(cell => cell.handle === firstCell);
|
||||
} else {
|
||||
editor.selection = undefined;
|
||||
}
|
||||
|
||||
this._editors.get(editorId)?.editor.dispose();
|
||||
|
||||
this._editors.set(editorId, { editor, onDidReceiveMessage });
|
||||
}
|
||||
|
||||
async $acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta) {
|
||||
let editorChanged = false;
|
||||
|
||||
if (delta.removedDocuments) {
|
||||
delta.removedDocuments.forEach((uri) => {
|
||||
let document = this._documents.get(URI.revive(uri).toString());
|
||||
const revivedUri = URI.revive(uri);
|
||||
const revivedUriStr = revivedUri.toString();
|
||||
let document = this._documents.get(revivedUriStr);
|
||||
|
||||
if (document) {
|
||||
document.dispose();
|
||||
this._documents.delete(URI.revive(uri).toString());
|
||||
this._documents.delete(revivedUriStr);
|
||||
this._onDidCloseNotebookDocument.fire(document);
|
||||
}
|
||||
|
||||
let editor = this._editors.get(URI.revive(uri).toString());
|
||||
|
||||
if (editor) {
|
||||
editor.editor.dispose();
|
||||
editor.onDidReceiveMessage.dispose();
|
||||
this._editors.delete(URI.revive(uri).toString());
|
||||
}
|
||||
[...this._editors.values()].forEach((e) => {
|
||||
if (e.editor.uri.toString() === revivedUriStr) {
|
||||
e.editor.dispose();
|
||||
e.onDidReceiveMessage.dispose();
|
||||
this._editors.delete(e.editor.id);
|
||||
editorChanged = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (delta.addedDocuments) {
|
||||
delta.addedDocuments.forEach(modelData => {
|
||||
const revivedUri = URI.revive(modelData.uri);
|
||||
const revivedUriStr = revivedUri.toString();
|
||||
const viewType = modelData.viewType;
|
||||
if (!this._documents.has(revivedUri.toString())) {
|
||||
let document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, revivedUri, this);
|
||||
this._documents.set(revivedUri.toString(), document);
|
||||
if (!this._documents.has(revivedUriStr)) {
|
||||
const that = this;
|
||||
let document = this._unInitializedDocuments.get(revivedUriStr) ?? new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, {
|
||||
emitModelChange(event: vscode.NotebookCellsChangeEvent): void {
|
||||
that._onDidChangeNotebookCells.fire(event);
|
||||
},
|
||||
emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void {
|
||||
that._onDidChangeCellOutputs.fire(event);
|
||||
},
|
||||
emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void {
|
||||
that._onDidChangeCellLanguage.fire(event);
|
||||
}
|
||||
}, viewType, revivedUri, this);
|
||||
|
||||
this._unInitializedDocuments.delete(revivedUriStr);
|
||||
if (modelData.metadata) {
|
||||
document.metadata = {
|
||||
...notebookDocumentMetadataDefaults,
|
||||
...modelData.metadata
|
||||
};
|
||||
}
|
||||
|
||||
document.accpetModelChanged({
|
||||
kind: NotebookCellsChangeType.ModelChange,
|
||||
versionId: modelData.versionId,
|
||||
changes: [[
|
||||
0,
|
||||
0,
|
||||
modelData.cells
|
||||
]]
|
||||
});
|
||||
|
||||
this._documents.get(revivedUriStr)?.dispose();
|
||||
this._documents.set(revivedUriStr, document);
|
||||
|
||||
// create editor if populated
|
||||
if (modelData.attachedEditor) {
|
||||
this._createExtHostEditor(document, modelData.attachedEditor.id, modelData.attachedEditor.selections);
|
||||
editorChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
const onDidReceiveMessage = new Emitter<any>();
|
||||
const document = this._documents.get(revivedUri.toString())!;
|
||||
|
||||
let editor = new ExtHostNotebookEditor(
|
||||
viewType,
|
||||
`${ExtHostNotebookController._handlePool++}`,
|
||||
revivedUri,
|
||||
this._proxy,
|
||||
onDidReceiveMessage,
|
||||
document,
|
||||
this._documentsAndEditors
|
||||
);
|
||||
|
||||
const document = this._documents.get(revivedUriStr)!;
|
||||
this._onDidOpenNotebookDocument.fire(document);
|
||||
|
||||
// TODO, does it already exist?
|
||||
this._editors.set(revivedUri.toString(), { editor, onDidReceiveMessage });
|
||||
});
|
||||
}
|
||||
|
||||
if (delta.newActiveEditor) {
|
||||
this._activeNotebookDocument = this._documents.get(URI.revive(delta.newActiveEditor).toString());
|
||||
this._activeNotebookEditor = this._editors.get(URI.revive(delta.newActiveEditor).toString())?.editor;
|
||||
if (delta.addedEditors) {
|
||||
delta.addedEditors.forEach(editorModelData => {
|
||||
if (this._editors.has(editorModelData.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const revivedUri = URI.revive(editorModelData.documentUri);
|
||||
const document = this._documents.get(revivedUri.toString());
|
||||
|
||||
if (document) {
|
||||
this._createExtHostEditor(document, editorModelData.id, editorModelData.selections);
|
||||
editorChanged = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const removedEditors: { editor: ExtHostNotebookEditor, onDidReceiveMessage: Emitter<any>; }[] = [];
|
||||
|
||||
if (delta.removedEditors) {
|
||||
delta.removedEditors.forEach(editorid => {
|
||||
const editor = this._editors.get(editorid);
|
||||
|
||||
if (editor) {
|
||||
editorChanged = true;
|
||||
this._editors.delete(editorid);
|
||||
|
||||
if (this.activeNotebookEditor?.id === editor.editor.id) {
|
||||
this._activeNotebookEditor = undefined;
|
||||
this._activeNotebookDocument = undefined;
|
||||
}
|
||||
|
||||
removedEditors.push(editor);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (editorChanged) {
|
||||
removedEditors.forEach(e => {
|
||||
e.editor.dispose();
|
||||
e.onDidReceiveMessage.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
if (delta.visibleEditors) {
|
||||
this.visibleNotebookEditors = delta.visibleEditors.map(id => this._editors.get(id)?.editor).filter(editor => !!editor) as ExtHostNotebookEditor[];
|
||||
const visibleEditorsSet = new Set<string>();
|
||||
this.visibleNotebookEditors.forEach(editor => visibleEditorsSet.add(editor.id));
|
||||
|
||||
[...this._editors.values()].forEach((e) => {
|
||||
const newValue = visibleEditorsSet.has(e.editor.id);
|
||||
e.editor._acceptVisibility(newValue);
|
||||
});
|
||||
|
||||
this.visibleNotebookEditors = [...this._editors.values()].map(e => e.editor).filter(e => e.visible);
|
||||
this._onDidChangeVisibleNotebookEditors.fire(this.visibleNotebookEditors);
|
||||
}
|
||||
|
||||
if (delta.newActiveEditor !== undefined) {
|
||||
if (delta.newActiveEditor) {
|
||||
this._activeNotebookEditor = this._editors.get(delta.newActiveEditor)?.editor;
|
||||
this._activeNotebookEditor?._acceptActive(true);
|
||||
this._activeNotebookDocument = this._activeNotebookEditor ? this._documents.get(this._activeNotebookEditor!.uri.toString()) : undefined;
|
||||
} else {
|
||||
this._activeNotebookEditor = undefined;
|
||||
this._activeNotebookDocument = undefined;
|
||||
}
|
||||
|
||||
this._onDidChangeActiveNotebookEditor.fire(this._activeNotebookEditor);
|
||||
}
|
||||
|
||||
[...this._editors.values()].forEach((e) => {
|
||||
if (e.editor !== this.activeNotebookEditor) {
|
||||
e.editor._acceptActive(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,13 +44,17 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD
|
||||
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.onDidChangeCellOutputs(e => documentChange(e.document)));
|
||||
this._disposables.add(extHostNotebooks.onDidChangeNotebookCells(e => documentChange(e.document)));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
|
||||
@@ -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();
|
||||
@@ -136,7 +142,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 +202,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 {
|
||||
|
||||
@@ -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,6 +413,7 @@ 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>();
|
||||
@@ -496,6 +498,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({
|
||||
@@ -626,17 +629,24 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape {
|
||||
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(result => {
|
||||
this._taskExecutions.set(execution.id, result);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
protected checkDeprecation(task: vscode.Task, handler: HandlerData) {
|
||||
|
||||
@@ -152,7 +152,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 +189,3 @@ export class ExtHostTimeline implements IExtHostTimeline {
|
||||
function getUriKey(uri: URI | undefined): string | undefined {
|
||||
return uri?.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -498,7 +498,7 @@ export class ExtHostTreeView<T> extends Disposable {
|
||||
return node;
|
||||
}
|
||||
|
||||
protected createTreeNode(element: T, extensionTreeItem: azdata.TreeItem, parent: TreeNode | Root): TreeNode { // {{SQL CARBON EDIT}} change to protected, change to azdata.TreeItem
|
||||
protected createTreeNode(element: T, extensionTreeItem: azdata.TreeItem2, parent: TreeNode | Root): TreeNode { // {{SQL CARBON EDIT}} change to protected, change to azdata.TreeItem
|
||||
const disposable = new DisposableStore();
|
||||
const handle = this.createHandle(element, extensionTreeItem, parent);
|
||||
const icon = this.getLightIconPath(extensionTreeItem);
|
||||
@@ -515,10 +515,10 @@ export class ExtHostTreeView<T> extends Disposable {
|
||||
iconDark: this.getDarkIconPath(extensionTreeItem) || icon,
|
||||
themeIcon: extensionTreeItem.iconPath instanceof ThemeIcon ? { id: extensionTreeItem.iconPath.id } : undefined,
|
||||
collapsibleState: isUndefinedOrNull(extensionTreeItem.collapsibleState) ? TreeItemCollapsibleState.None : extensionTreeItem.collapsibleState,
|
||||
// {{SQL CARBON EDIT}}
|
||||
payload: extensionTreeItem.payload,
|
||||
childProvider: extensionTreeItem.childProvider,
|
||||
type: extensionTreeItem.type
|
||||
accessibilityInformation: extensionTreeItem.accessibilityInformation,
|
||||
payload: extensionTreeItem.payload, // {{SQL CARBON EDIT}}
|
||||
childProvider: extensionTreeItem.childProvider, // {{SQL CARBON EDIT}}
|
||||
type: extensionTreeItem.type // {{SQL CARBON EDIT}}
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2766,3 +2766,17 @@ export enum ExtensionMode {
|
||||
}
|
||||
|
||||
//#endregion ExtensionContext
|
||||
|
||||
|
||||
//#region Authentication
|
||||
export class AuthenticationSession implements vscode.AuthenticationSession2 {
|
||||
constructor(public id: string, public accessToken: string, public account: { displayName: string, id: string }, public scopes: string[]) { }
|
||||
}
|
||||
|
||||
//#endregion Authentication
|
||||
export enum StandardTokenType {
|
||||
Other = 0,
|
||||
Comment = 1,
|
||||
String = 2,
|
||||
RegEx = 4
|
||||
}
|
||||
|
||||
@@ -573,7 +573,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);
|
||||
@@ -628,7 +628,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 +761,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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -50,7 +50,8 @@ 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();
|
||||
const initFn = new Function('module', 'exports', 'require', 'window', `${source}\n//# sourceURL=${module.toString(true)}`);
|
||||
|
||||
// define commonjs globals: `module`, `exports`, and `require`
|
||||
const _exports = {};
|
||||
|
||||
Reference in New Issue
Block a user