Merge from vscode 2e5312cd61ff99c570299ecc122c52584265eda2

This commit is contained in:
ADS Merger
2020-04-23 02:50:35 +00:00
committed by Anthony Dresser
parent 3603f55d97
commit 7f1d8fc32f
659 changed files with 22709 additions and 12497 deletions

View File

@@ -12,31 +12,35 @@ import { ExtHostAuthenticationShape, ExtHostContext, IExtHostContext, MainContex
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import Severity from 'vs/base/common/severity';
import { MenuRegistry, MenuId, IMenuItem } from 'vs/platform/actions/common/actions';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
interface AuthDependent {
providerId: string;
label: string;
scopes: string[];
scopeDescriptions?: string;
}
const BUILT_IN_AUTH_DEPENDENTS: AuthDependent[] = [
{
providerId: 'microsoft',
label: 'Settings sync',
scopes: ['https://management.core.windows.net/.default', 'offline_access'],
scopeDescriptions: 'Read user email'
}
];
import { INotificationService } from 'vs/platform/notification/common/notification';
interface AllowedExtension {
id: string;
name: string;
}
const accountUsages = new Map<string, { [accountName: string]: string[] }>();
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);
}
}
function readAllowedExtensions(storageService: IStorageService, providerId: string, accountName: string): AllowedExtension[] {
let trustedExtensions: AllowedExtension[] = [];
try {
@@ -53,17 +57,22 @@ export class MainThreadAuthenticationProvider extends Disposable {
private _sessionMenuItems = new Map<string, IDisposable[]>();
private _accounts = new Map<string, string[]>(); // Map account name to session ids
private _sessions = new Map<string, string>(); // Map account id to name
private _signInMenuItem: IMenuItem | undefined;
constructor(
private readonly _proxy: ExtHostAuthenticationShape,
public readonly id: string,
public readonly displayName: string,
public readonly dependents: AuthDependent[]
private readonly notificationService: INotificationService
) {
super();
}
this.registerCommandsAndContextMenuItems();
public async initialize(): Promise<void> {
return this.registerCommandsAndContextMenuItems();
}
public hasSessions(): boolean {
return !!this._sessions.size;
}
private manageTrustedExtensions(quickInputService: IQuickInputService, storageService: IStorageService, accountName: string) {
@@ -96,50 +105,45 @@ 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);
if (this.dependents.length) {
this._register(CommandsRegistry.registerCommand({
id: `signIn${this.id}`,
handler: (accessor, args) => {
this.login(this.dependents.reduce((previous: string[], current) => previous.concat(current.scopes), []));
},
}));
this._signInMenuItem = {
group: '2_providers',
command: {
id: `signIn${this.id}`,
title: sessions.length
? nls.localize('addAnotherAccount', "Sign in to another {0} account", this.displayName)
: nls.localize('addAccount', "Sign in to {0}", this.displayName)
},
order: 3
};
this._register(MenuRegistry.appendMenuItem(MenuId.AccountsContext, this._signInMenuItem));
}
sessions.forEach(session => this.registerSession(session));
}
private registerSession(session: modes.AuthenticationSession) {
this._sessions.set(session.id, session.accountName);
this._sessions.set(session.id, session.account.displayName);
const existingSessionsForAccount = this._accounts.get(session.accountName);
const existingSessionsForAccount = this._accounts.get(session.account.displayName);
if (existingSessionsForAccount) {
this._accounts.set(session.accountName, existingSessionsForAccount.concat(session.id));
this._accounts.set(session.account.displayName, existingSessionsForAccount.concat(session.id));
return;
} else {
this._accounts.set(session.accountName, [session.id]);
this._accounts.set(session.account.displayName, [session.id]);
}
const menuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, {
group: '1_accounts',
command: {
id: `configureSessions${session.id}`,
title: session.accountName
title: `${session.account.displayName} (${this.displayName})`
},
order: 3
});
@@ -149,23 +153,28 @@ export class MainThreadAuthenticationProvider extends Disposable {
handler: (accessor, args) => {
const quickInputService = accessor.get(IQuickInputService);
const storageService = accessor.get(IStorageService);
const dialogService = accessor.get(IDialogService);
const quickPick = quickInputService.createQuickPick();
const showUsage = nls.localize('showUsage', "Show Extensions and Features Using This Account");
const manage = nls.localize('manageTrustedExtensions', "Manage Trusted Extensions");
const signOut = nls.localize('signOut', "Sign Out");
const items = ([{ label: manage }, { label: signOut }]);
const items = ([{ label: showUsage }, { label: manage }, { label: signOut }]);
quickPick.items = items;
quickPick.onDidAccept(e => {
const selected = quickPick.selectedItems[0];
if (selected.label === signOut) {
const sessionsForAccount = this._accounts.get(session.accountName);
sessionsForAccount?.forEach(sessionId => this.logout(sessionId));
this.signOut(dialogService, session);
}
if (selected.label === manage) {
this.manageTrustedExtensions(quickInputService, storageService, session.accountName);
this.manageTrustedExtensions(quickInputService, storageService, session.account.displayName);
}
if (selected.label === showUsage) {
this.showUsage(quickInputService, session.account.displayName);
}
quickPick.dispose();
@@ -179,15 +188,41 @@ export class MainThreadAuthenticationProvider extends Disposable {
},
});
this._sessionMenuItems.set(session.accountName, [menuItem, manageCommand]);
this._sessionMenuItems.set(session.account.displayName, [menuItem, manageCommand]);
}
async signOut(dialogService: IDialogService, session: modes.AuthenticationSession): Promise<void> {
const providerUsage = accountUsages.get(this.id);
const accountUsage = (providerUsage || {})[session.account.displayName] || [];
const sessionsForAccount = this._accounts.get(session.account.displayName);
// 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'))
});
if (result.confirmed) {
accountUsages.set(this.id, { [session.account.displayName]: [] });
sessionsForAccount?.forEach(sessionId => this.logout(sessionId));
}
}
async getSessions(): Promise<ReadonlyArray<modes.AuthenticationSession>> {
return (await this._proxy.$getSessions(this.id)).map(session => {
return {
id: session.id,
accountName: session.accountName,
getAccessToken: () => this._proxy.$getSessionAccessToken(this.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);
}
};
});
}
@@ -200,6 +235,7 @@ export class MainThreadAuthenticationProvider extends Disposable {
removed.forEach(sessionId => {
const accountName = this._sessions.get(sessionId);
if (accountName) {
this._sessions.delete(sessionId);
let sessionsForAccount = this._accounts.get(accountName) || [];
const sessionIndex = sessionsForAccount.indexOf(sessionId);
sessionsForAccount.splice(sessionIndex);
@@ -211,33 +247,26 @@ export class MainThreadAuthenticationProvider extends Disposable {
this._sessionMenuItems.delete(accountName);
}
this._accounts.delete(accountName);
if (this._signInMenuItem) {
this._signInMenuItem.command.title = nls.localize('addAccount', "Sign in to {0}", this.displayName);
}
}
}
});
addedSessions.forEach(session => this.registerSession(session));
if (addedSessions.length && this._signInMenuItem) {
this._signInMenuItem.command.title = nls.localize('addAnotherAccount', "Sign in to another {0} account", this.displayName);
}
}
login(scopes: string[]): Promise<modes.AuthenticationSession> {
return this._proxy.$login(this.id, scopes).then(session => {
return {
id: session.id,
accountName: session.accountName,
account: session.account,
getAccessToken: () => this._proxy.$getSessionAccessToken(this.id, session.id)
};
});
}
logout(sessionId: string): Promise<void> {
return this._proxy.$logout(this.id, sessionId);
async logout(sessionId: string): Promise<void> {
await this._proxy.$logout(this.id, sessionId);
this.notificationService.info(nls.localize('signedOut', "Successfully signed out."));
}
dispose(): void {
@@ -255,16 +284,16 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
extHostContext: IExtHostContext,
@IAuthenticationService private readonly authenticationService: IAuthenticationService,
@IDialogService private readonly dialogService: IDialogService,
@IStorageService private readonly storageService: IStorageService
@IStorageService private readonly storageService: IStorageService,
@INotificationService private readonly notificationService: INotificationService
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostAuthentication);
}
async $registerAuthenticationProvider(id: string, displayName: string): Promise<void> {
const dependentBuiltIns = BUILT_IN_AUTH_DEPENDENTS.filter(dependency => dependency.providerId === id);
const provider = new MainThreadAuthenticationProvider(this._proxy, id, displayName, dependentBuiltIns);
const provider = new MainThreadAuthenticationProvider(this._proxy, id, displayName, this.notificationService);
await provider.initialize();
this.authenticationService.registerAuthenticationProvider(id, provider);
}
@@ -277,6 +306,8 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
}
async $getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean> {
addAccountUsage(providerId, accountName, extensionName);
const allowList = readAllowedExtensions(this.storageService, providerId, accountName);
const extensionData = allowList.find(extension => extension.id === extensionId);
if (extensionData) {
@@ -313,4 +344,12 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
return choice === 1;
}
async $setTrustedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise<void> {
const allowList = readAllowedExtensions(this.storageService, providerId, accountName);
if (!allowList.find(allowed => allowed.id === extensionId)) {
allowList.push({ id: extensionId, name: extensionName });
this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL);
}
}
}

View File

@@ -378,7 +378,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments
this._commentService.registerCommentController(providerId, provider);
this._commentControllers.set(handle, provider);
const commentsPanelAlreadyConstructed = !!this._viewDescriptorService.getViewDescriptor(COMMENTS_VIEW_ID);
const commentsPanelAlreadyConstructed = !!this._viewDescriptorService.getViewDescriptorById(COMMENTS_VIEW_ID);
if (!commentsPanelAlreadyConstructed) {
this.registerView(commentsPanelAlreadyConstructed);
this.registerViewOpenedListener(commentsPanelAlreadyConstructed);
@@ -451,7 +451,8 @@ export class MainThreadComments extends Disposable implements MainThreadComments
const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry).registerViewContainer({
id: COMMENTS_VIEW_ID,
name: COMMENTS_VIEW_TITLE,
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [COMMENTS_VIEW_ID, COMMENTS_VIEW_TITLE, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]),
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [COMMENTS_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]),
storageId: COMMENTS_VIEW_TITLE,
hideIfEmpty: true,
order: 10,
}, ViewContainerLocation.Panel);

View File

@@ -15,6 +15,7 @@ import severity from 'vs/base/common/severity';
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { convertToVSCPaths, convertToDAPaths } from 'vs/workbench/contrib/debug/common/debugUtils';
import { DebugConfigurationProviderScope } from 'vs/workbench/api/common/extHostTypes';
@extHostNamedCustomer(MainContext.MainThreadDebugService)
export class MainThreadDebugService implements MainThreadDebugServiceShape, IDebugAdapterFactory {
@@ -154,10 +155,11 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
return Promise.resolve();
}
public $registerDebugConfigurationProvider(debugType: string, hasProvide: boolean, hasResolve: boolean, hasResolve2: boolean, hasProvideDebugAdapter: boolean, handle: number): Promise<void> {
public $registerDebugConfigurationProvider(debugType: string, providerScope: DebugConfigurationProviderScope, hasProvide: boolean, hasResolve: boolean, hasResolve2: boolean, hasProvideDebugAdapter: boolean, handle: number): Promise<void> {
const provider = <IDebugConfigurationProvider>{
type: debugType
type: debugType,
scope: providerScope
};
if (hasProvide) {
provider.provideDebugConfigurations = (folder, token) => {
@@ -271,7 +273,6 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
this.getDebugAdapter(handle).acceptMessage(convertToVSCPaths(message, false));
}
public $acceptDAError(handle: number, name: string, message: string, stack: string) {
this.getDebugAdapter(handle).fireError(handle, new Error(`${name}: ${message}\n${stack}`));
}

View File

@@ -24,6 +24,7 @@ import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, IExtHostContex
import { EditorViewColumn, editorGroupToViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { DEFAULT_EDITOR_ID } from 'vs/workbench/contrib/files/common/files';
export class MainThreadTextEditors implements MainThreadTextEditorsShape {
@@ -293,6 +294,30 @@ CommandsRegistry.registerCommand('_workbench.open', function (accessor: Services
return openerService.open(resource).then(_ => undefined);
});
CommandsRegistry.registerCommand('_workbench.openWith', (accessor: ServicesAccessor, args: [URI, string, ITextEditorOptions | undefined, EditorViewColumn | undefined]) => {
const editorService = accessor.get(IEditorService);
const editorGroupService = accessor.get(IEditorGroupsService);
const [resource, id, options, position] = args;
const group = editorGroupService.getGroup(viewColumnToEditorGroup(editorGroupService, position)) ?? editorGroupService.activeGroup;
const textOptions = options ? { ...options, ignoreOverrides: true } : { ignoreOverrides: true };
const fileEditorInput = editorService.createEditorInput({ resource, forceFile: true });
if (id === DEFAULT_EDITOR_ID) {
return editorService.openEditor(fileEditorInput, textOptions, position);
}
const editors = editorService.getEditorOverrides(fileEditorInput, undefined, group);
for (const [handler, data] of editors) {
if (data.id === id) {
return handler.open(fileEditorInput, options, group, id);
}
}
return undefined;
});
CommandsRegistry.registerCommand('_workbench.diff', function (accessor: ServicesAccessor, args: [URI, URI, string, string, IEditorOptions, EditorViewColumn]) {
const editorService = accessor.get(IEditorService);

View File

@@ -405,8 +405,8 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
private static _inflateSuggestDto(defaultRange: IRange | { insert: IRange, replace: IRange }, data: ISuggestDataDto): modes.CompletionItem {
return {
label: data[ISuggestDataDtoField.label2] || data[ISuggestDataDtoField.label],
kind: data[ISuggestDataDtoField.kind],
label: data[ISuggestDataDtoField.label2] ?? data[ISuggestDataDtoField.label],
kind: data[ISuggestDataDtoField.kind] ?? modes.CompletionItemKind.Property,
tags: data[ISuggestDataDtoField.kindModifier],
detail: data[ISuggestDataDtoField.detail],
documentation: data[ISuggestDataDtoField.documentation],
@@ -414,7 +414,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
filterText: data[ISuggestDataDtoField.filterText],
preselect: data[ISuggestDataDtoField.preselect],
insertText: typeof data.h === 'undefined' ? data[ISuggestDataDtoField.label] : data.h,
range: data[ISuggestDataDtoField.range] || defaultRange,
range: data[ISuggestDataDtoField.range] ?? defaultRange,
insertTextRules: data[ISuggestDataDtoField.insertTextRules],
commitCharacters: data[ISuggestDataDtoField.commitCharacters],
additionalTextEdits: data[ISuggestDataDtoField.additionalTextEdits],

View File

@@ -33,6 +33,10 @@ export class MainThreadNotebookDocument extends Disposable {
this._register(this._textModel.onDidModelChange(e => {
this._proxy.$acceptModelChanged(this.uri, e);
}));
this._register(this._textModel.onDidSelectionChange(e => {
const selectionsChange = e ? { selections: e } : null;
this._proxy.$acceptEditorPropertiesChanged(uri, { selections: selectionsChange });
}));
}
applyEdit(modelVersionId: number, edits: ICellEditOperation[]): boolean {

View File

@@ -96,7 +96,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
// ---- input
$input(options: IInputBoxOptions | undefined, validateInput: boolean, token: CancellationToken): Promise<string> {
$input(options: IInputBoxOptions | undefined, validateInput: boolean, token: CancellationToken): Promise<string | undefined> {
const inputOptions: IInputOptions = Object.create(null);
if (options) {

View File

@@ -71,8 +71,8 @@ class MainThreadSCMResource implements ISCMResource {
public decorations: ISCMResourceDecorations
) { }
open(): Promise<void> {
return this.proxy.$executeResourceCommand(this.sourceControlHandle, this.groupHandle, this.handle);
open(preserveFocus: boolean): Promise<void> {
return this.proxy.$executeResourceCommand(this.sourceControlHandle, this.groupHandle, this.handle, preserveFocus);
}
toJSON(): any {

View File

@@ -26,7 +26,9 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape {
}
$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 {
const entry: IStatusbarEntry = { text, tooltip, command, color };
// if there are icons in the text use the tooltip for the aria label
const ariaLabel = text.indexOf('$(') === -1 ? text : tooltip || text;
const entry: IStatusbarEntry = { text, tooltip, command, color, ariaLabel };
if (typeof priority === 'undefined') {
priority = 0;

View File

@@ -133,6 +133,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IBackupFileService private readonly _backupService: IBackupFileService,
) {
super();
@@ -151,7 +152,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
this.updateWebviewViewStates(this._editorService.activeEditor);
}));
// This reviver's only job is to activate webview panel extensions
// This reviver's only job is to activate extensions.
// This should trigger the real reviver to be registered from the extension host side.
this._register(_webviewWorkbenchService.registerResolver({
canResolve: (webview: WebviewInput) => {
@@ -305,11 +306,11 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
}
public $registerTextEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: extHostProtocol.CustomTextEditorCapabilities): void {
this.registerEditorProvider(ModelType.Text, extensionData, viewType, options, capabilities);
this.registerEditorProvider(ModelType.Text, extensionData, viewType, options, capabilities, true);
}
public $registerCustomEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void {
this.registerEditorProvider(ModelType.Custom, extensionData, viewType, options, {});
public $registerCustomEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, supportsMultipleEditorsPerResource: boolean): void {
this.registerEditorProvider(ModelType.Custom, extensionData, viewType, options, {}, supportsMultipleEditorsPerResource);
}
private registerEditorProvider(
@@ -318,11 +319,16 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
viewType: string,
options: modes.IWebviewPanelOptions,
capabilities: extHostProtocol.CustomTextEditorCapabilities,
supportsMultipleEditorsPerResource: boolean,
): DisposableStore {
if (this._editorProviders.has(viewType)) {
throw new Error(`Provider for ${viewType} already registered`);
}
this._customEditorService.registerCustomEditorCapabilities(viewType, {
supportsMultipleEditorsPerResource
});
const extension = reviveWebviewExtension(extensionData);
const disposables = new DisposableStore();
@@ -341,7 +347,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
let modelRef: IReference<ICustomEditorModel>;
try {
modelRef = await this.getOrCreateCustomEditorModel(modelType, resource, viewType, cancellation);
modelRef = await this.getOrCreateCustomEditorModel(modelType, resource, viewType, { backupId: webviewInput.backupId }, cancellation);
} catch (error) {
onUnexpectedError(error);
webviewInput.webview.html = MainThreadWebviews.getWebviewResolvedFailedContent(viewType);
@@ -360,7 +366,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
if (capabilities.supportsMove) {
webviewInput.onMove(async (newResource: URI) => {
const oldModel = modelRef;
modelRef = await this.getOrCreateCustomEditorModel(modelType, newResource, viewType, CancellationToken.None);
modelRef = await this.getOrCreateCustomEditorModel(modelType, newResource, viewType, {}, CancellationToken.None);
this._proxy.$onMoveCustomEditor(handle, newResource, viewType);
oldModel.dispose();
});
@@ -398,6 +404,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
modelType: ModelType,
resource: URI,
viewType: string,
options: { backupId?: string },
cancellation: CancellationToken,
): Promise<IReference<ICustomEditorModel>> {
const existingModel = this._customEditorService.models.tryRetain(resource, viewType);
@@ -405,26 +412,33 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
return existingModel;
}
const model = modelType === ModelType.Text
? CustomTextEditorModel.create(this._instantiationService, viewType, resource)
: MainThreadCustomEditorModel.create(this._instantiationService, this._proxy, viewType, resource, () => {
return Array.from(this._webviewInputs)
.filter(editor => editor instanceof CustomEditorInput && isEqual(editor.resource, resource)) as CustomEditorInput[];
}, cancellation);
return this._customEditorService.models.add(resource, viewType, model);
switch (modelType) {
case ModelType.Text:
{
const model = CustomTextEditorModel.create(this._instantiationService, viewType, resource);
return this._customEditorService.models.add(resource, viewType, model);
}
case ModelType.Custom:
{
const model = MainThreadCustomEditorModel.create(this._instantiationService, this._proxy, viewType, resource, options, () => {
return Array.from(this._webviewInputs)
.filter(editor => editor instanceof CustomEditorInput && isEqual(editor.resource, resource)) as CustomEditorInput[];
}, cancellation, this._backupService);
return this._customEditorService.models.add(resource, viewType, model);
}
}
}
public async $onDidEdit(resourceComponents: UriComponents, viewType: string, editId: number, label: string | undefined): Promise<void> {
const resource = URI.revive(resourceComponents);
const model = await this._customEditorService.models.get(resource, viewType);
if (!model || !(model instanceof MainThreadCustomEditorModel)) {
throw new Error('Could not find model for webview editor');
}
const model = await this.getCustomEditorModel(resourceComponents, viewType);
model.pushEdit(editId, label);
}
public async $onContentChange(resourceComponents: UriComponents, viewType: string): Promise<void> {
const model = await this.getCustomEditorModel(resourceComponents, viewType);
model.changeContent();
}
private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, input: WebviewInput) {
const disposables = new DisposableStore();
@@ -531,6 +545,15 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
return this._webviewInputs.getInputForHandle(handle);
}
private async getCustomEditorModel(resourceComponents: UriComponents, viewType: string) {
const resource = URI.revive(resourceComponents);
const model = await this._customEditorService.models.get(resource, viewType);
if (!model || !(model instanceof MainThreadCustomEditorModel)) {
throw new Error('Could not find model for webview editor');
}
return model;
}
private static getWebviewResolvedFailedContent(viewType: string) {
return `<!DOCTYPE html>
<html>
@@ -577,7 +600,7 @@ namespace HotExitState {
readonly type = Type.Pending;
constructor(
public readonly operation: CancelablePromise<void>,
public readonly operation: CancelablePromise<string>,
) { }
}
@@ -588,58 +611,58 @@ namespace HotExitState {
class MainThreadCustomEditorModel extends Disposable implements ICustomEditorModel, IWorkingCopy {
private _hotExitState: HotExitState.State = HotExitState.Allowed;
private readonly _fromBackup: boolean = false;
private _currentEditIndex: number = -1;
private _savePoint: number = -1;
private readonly _edits: Array<number> = [];
private _fromBackup: boolean = false;
private _isDirtyFromContentChange = false;
private _ongoingSave?: CancelablePromise<void>;
public static async create(
instantiationService: IInstantiationService,
proxy: extHostProtocol.ExtHostWebviewsShape,
viewType: string,
resource: URI,
options: { backupId?: string },
getEditors: () => CustomEditorInput[],
cancellation: CancellationToken,
_backupFileService: IBackupFileService,
) {
const { editable } = await proxy.$createWebviewCustomEditorDocument(resource, viewType, cancellation);
const model = instantiationService.createInstance(MainThreadCustomEditorModel, proxy, viewType, resource, editable, getEditors);
await model.init();
return model;
const { editable } = await proxy.$createCustomDocument(resource, viewType, options.backupId, cancellation);
return instantiationService.createInstance(MainThreadCustomEditorModel, proxy, viewType, resource, !!options.backupId, editable, getEditors);
}
constructor(
private readonly _proxy: extHostProtocol.ExtHostWebviewsShape,
private readonly _viewType: string,
private readonly _editorResource: URI,
fromBackup: boolean,
private readonly _editable: boolean,
private readonly _getEditors: () => CustomEditorInput[],
@IWorkingCopyService workingCopyService: IWorkingCopyService,
@ILabelService private readonly _labelService: ILabelService,
@IFileService private readonly _fileService: IFileService,
@IUndoRedoService private readonly _undoService: IUndoRedoService,
@IBackupFileService private readonly _backupFileService: IBackupFileService,
) {
super();
if (_editable) {
this._register(workingCopyService.registerWorkingCopy(this));
}
this._fromBackup = fromBackup;
}
get editorResource() {
return this._editorResource;
}
async init(): Promise<void> {
const backup = await this._backupFileService.resolve<CustomDocumentBackupData>(this.resource);
this._fromBackup = !!backup;
}
dispose() {
if (this._editable) {
this._undoService.removeElements(this._editorResource);
}
this._proxy.$disposeWebviewCustomEditorDocument(this._editorResource, this._viewType);
this._proxy.$disposeCustomDocument(this._editorResource, this._viewType);
super.dispose();
}
@@ -647,11 +670,15 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
public get resource() {
// Make sure each custom editor has a unique resource for backup and edits
return MainThreadCustomEditorModel.toWorkingCopyResource(this._viewType, this._editorResource);
}
private static toWorkingCopyResource(viewType: string, resource: URI) {
return URI.from({
scheme: Schemas.vscodeCustomEditor,
authority: this._viewType,
path: this._editorResource.path,
query: JSON.stringify(this._editorResource.toJSON()),
authority: viewType,
path: resource.path,
query: JSON.stringify(resource.toJSON()),
});
}
@@ -664,6 +691,9 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
}
public isDirty(): boolean {
if (this._isDirtyFromContentChange) {
return true;
}
if (this._edits.length > 0) {
return this._savePoint !== this._currentEditIndex;
}
@@ -705,6 +735,12 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
});
}
public changeContent() {
this.change(() => {
this._isDirtyFromContentChange = true;
});
}
private async undo(): Promise<void> {
if (!this._editable) {
return;
@@ -719,15 +755,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
this.change(() => {
--this._currentEditIndex;
});
await this._proxy.$undo(this._editorResource, this.viewType, undoneEdit, this.getEditState());
}
private getEditState(): extHostProtocol.CustomDocumentEditState {
return {
allEdits: this._edits,
currentIndex: this._currentEditIndex,
saveIndex: this._savePoint,
};
await this._proxy.$undo(this._editorResource, this.viewType, undoneEdit, this.isDirty());
}
private async redo(): Promise<void> {
@@ -744,7 +772,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
this.change(() => {
++this._currentEditIndex;
});
await this._proxy.$redo(this._editorResource, this.viewType, redoneEdit, this.getEditState());
await this._proxy.$redo(this._editorResource, this.viewType, redoneEdit, this.isDirty());
}
private spliceEdits(editToInsert?: number) {
@@ -775,21 +803,13 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
return;
}
if (this._currentEditIndex === this._savePoint) {
if (this._currentEditIndex === this._savePoint && !this._isDirtyFromContentChange) {
return;
}
let editsToUndo: number[] = [];
let editsToRedo: number[] = [];
if (this._currentEditIndex >= this._savePoint) {
editsToUndo = this._edits.slice(this._savePoint, this._currentEditIndex).reverse();
} else if (this._currentEditIndex < this._savePoint) {
editsToRedo = this._edits.slice(this._currentEditIndex, this._savePoint);
}
this._proxy.$revert(this._editorResource, this.viewType, { undoneEdits: editsToUndo, redoneEdits: editsToRedo }, this.getEditState());
this._proxy.$revert(this._editorResource, this.viewType, CancellationToken.None);
this.change(() => {
this._isDirtyFromContentChange = false;
this._currentEditIndex = this._savePoint;
this.spliceEdits();
});
@@ -804,11 +824,23 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
return undefined;
}
// TODO: handle save untitled case
// TODO: handle cancellation
await createCancelablePromise(token => this._proxy.$onSave(this._editorResource, this.viewType, token));
const savePromise = createCancelablePromise(token => this._proxy.$onSave(this._editorResource, this.viewType, token));
this._ongoingSave?.cancel();
this._ongoingSave = savePromise;
this.change(() => {
this._isDirtyFromContentChange = false;
this._savePoint = this._currentEditIndex;
});
try {
await savePromise;
} finally {
if (this._ongoingSave === savePromise) {
this._ongoingSave = undefined;
}
}
return this._editorResource;
}
@@ -838,6 +870,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
meta: {
viewType: this.viewType,
editorResource: this._editorResource,
backupId: '',
extension: primaryEditor.extension ? {
id: primaryEditor.extension.id.value,
location: primaryEditor.extension.location,
@@ -864,10 +897,11 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
this._hotExitState = pendingState;
try {
await pendingState.operation;
const backupId = await pendingState.operation;
// Make sure state has not changed in the meantime
if (this._hotExitState === pendingState) {
this._hotExitState = HotExitState.Allowed;
backupData.meta!.backupId = backupId;
}
} catch (e) {
// Make sure state has not changed in the meantime

View File

@@ -320,7 +320,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
name: title, extensionId,
ctorDescriptor: new SyncDescriptor(
ViewPaneContainer,
[id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }]
[id, { mergeViewWithContainerWhenSingleView: true }]
),
hideIfEmpty: true,
order,

View File

@@ -53,7 +53,7 @@ import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyId
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import * as vscode from 'vscode';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { originalFSPath, joinPath } from 'vs/base/common/resources';
import { originalFSPath } from 'vs/base/common/resources';
import { values } from 'vs/base/common/collections';
import { ExtHostEditorInsets } from 'vs/workbench/api/common/extHostCodeInsets';
import { ExtHostLabelService } from 'vs/workbench/api/common/extHostLabelService';
@@ -74,6 +74,7 @@ import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelServ
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication';
import { ExtHostTimeline } from 'vs/workbench/api/common/extHostTimeline';
import { ExtHostNotebookConcatDocument } from 'vs/workbench/api/common/extHostNotebookConcatDocument';
export interface IExtensionApiFactory {
(extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode;
@@ -585,12 +586,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
registerWebviewPanelSerializer: (viewType: string, serializer: vscode.WebviewPanelSerializer) => {
return extHostWebviews.registerWebviewPanelSerializer(extension, viewType, serializer);
},
registerCustomEditorProvider: (viewType: string, provider: vscode.CustomTextEditorProvider, options?: { webviewOptions?: vscode.WebviewPanelOptions }) => {
return extHostWebviews.registerCustomEditorProvider(extension, viewType, provider, options?.webviewOptions);
registerCustomEditorProvider: (viewType: string, provider: vscode.CustomTextEditorProvider, options: { webviewOptions?: vscode.WebviewPanelOptions } = {}) => {
return extHostWebviews.registerCustomEditorProvider(extension, viewType, provider, options);
},
registerCustomEditorProvider2: (viewType: string, provider: vscode.CustomEditorProvider, options?: { webviewOptions?: vscode.WebviewPanelOptions }) => {
registerCustomEditorProvider2: (viewType: string, provider: vscode.CustomEditorProvider, options: { webviewOptions?: vscode.WebviewPanelOptions, supportsMultipleEditorsPerResource?: boolean } = {}) => {
checkProposedApiEnabled(extension);
return extHostWebviews.registerCustomEditorProvider(extension, viewType, provider, options?.webviewOptions);
return extHostWebviews.registerCustomEditorProvider(extension, viewType, provider, options);
},
registerDecorationProvider(provider: vscode.DecorationProvider) {
checkProposedApiEnabled(extension);
@@ -606,11 +607,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
return extHostQuickOpen.createInputBox(extension.identifier);
},
get activeColorTheme(): vscode.ColorTheme {
checkProposedApiEnabled(extension);
return extHostTheming.activeColorTheme;
},
onDidChangeActiveColorTheme(listener, thisArg?, disposables?) {
checkProposedApiEnabled(extension);
return extHostTheming.onDidChangeActiveColorTheme(listener, thisArg, disposables);
}
};
@@ -856,7 +855,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
extHostLogService.warn('Debug API is disabled in Azure Data Studio');
return undefined!;
},
registerDebugConfigurationProvider(debugType: string, provider: vscode.DebugConfigurationProvider) {
registerDebugConfigurationProvider(debugType: string, provider: vscode.DebugConfigurationProvider, scope?: vscode.DebugConfigurationProviderScope) {
extHostLogService.warn('Debug API is disabled in Azure Data Studio');
return undefined!;
},
@@ -924,13 +923,24 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
// namespace: notebook
const notebook: typeof vscode.notebook = {
registerNotebookProvider: (viewType: string, provider: vscode.NotebookProvider) => {
checkProposedApiEnabled(extension);
return extHostNotebook.registerNotebookProvider(extension, viewType, provider);
},
registerNotebookOutputRenderer: (type: string, outputFilter: vscode.NotebookOutputSelector, renderer: vscode.NotebookOutputRenderer) => {
checkProposedApiEnabled(extension);
return extHostNotebook.registerNotebookOutputRenderer(type, extension, outputFilter, renderer);
},
get activeNotebookDocument(): vscode.NotebookDocument | undefined {
checkProposedApiEnabled(extension);
return extHostNotebook.activeNotebookDocument;
},
get activeNotebookEditor(): vscode.NotebookEditor | undefined {
checkProposedApiEnabled(extension);
return extHostNotebook.activeNotebookEditor;
},
createConcatTextDocument(notebook, selector) {
checkProposedApiEnabled(extension);
return new ExtHostNotebookConcatDocument(extHostNotebook, extHostDocuments, notebook, selector);
}
};
@@ -1055,13 +1065,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
CallHierarchyIncomingCall: extHostTypes.CallHierarchyIncomingCall,
CallHierarchyItem: extHostTypes.CallHierarchyItem,
DebugConsoleMode: extHostTypes.DebugConsoleMode,
DebugConfigurationProviderScope: extHostTypes.DebugConfigurationProviderScope,
Decoration: extHostTypes.Decoration,
UIKind: UIKind,
ColorThemeKind: extHostTypes.ColorThemeKind,
TimelineItem: extHostTypes.TimelineItem,
CellKind: extHostTypes.CellKind,
CellOutputKind: extHostTypes.CellOutputKind,
CustomDocument: extHostTypes.CustomDocument,
NotebookCellRunState: extHostTypes.NotebookCellRunState
};
};
}
@@ -1089,11 +1100,6 @@ class Extension<T> implements vscode.Extension<T> {
this.extensionKind = kind;
}
asExtensionUri(relativePath: string): URI {
checkProposedApiEnabled(this.packageJSON);
return joinPath(this.packageJSON.extensionLocation, relativePath);
}
get isActive(): boolean {
return this._extensionService.isActivated(this._identifier);
}

View File

@@ -55,6 +55,7 @@ import { INotebookMimeTypeSelector, IOutput, INotebookDisplayOrder, NotebookCell
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 { DebugConfigurationProviderScope } from 'vs/workbench/api/common/extHostTypes';
// {{SQL CARBON EDIT}}
import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views';
@@ -164,6 +165,7 @@ export interface MainThreadAuthenticationShape extends IDisposable {
$onDidChangeSessions(providerId: string, event: modes.AuthenticationSessionsChangeEvent): void;
$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>;
}
export interface MainThreadConfigurationShape extends IDisposable {
@@ -538,7 +540,7 @@ export interface MainThreadQuickOpenShape extends IDisposable {
$show(instance: number, options: quickInput.IPickOptions<TransferQuickPickItems>, token: CancellationToken): Promise<number | number[] | undefined>;
$setItems(instance: number, items: TransferQuickPickItems[]): Promise<void>;
$setError(instance: number, error: Error): Promise<void>;
$input(options: IInputBoxOptions | undefined, validateInput: boolean, token: CancellationToken): Promise<string>;
$input(options: IInputBoxOptions | undefined, validateInput: boolean, token: CancellationToken): Promise<string | undefined>;
$createOrUpdate(params: TransferQuickInput): Promise<void>;
$dispose(id: number): Promise<void>;
}
@@ -614,10 +616,11 @@ export interface MainThreadWebviewsShape extends IDisposable {
$unregisterSerializer(viewType: string): void;
$registerTextEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: CustomTextEditorCapabilities): void;
$registerCustomEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void;
$registerCustomEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, supportsMultipleEditorsPerResource: boolean): void;
$unregisterEditorProvider(viewType: string): void;
$onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void;
$onContentChange(resource: UriComponents, viewType: string): void;
}
export interface WebviewPanelViewStateData {
@@ -628,12 +631,6 @@ export interface WebviewPanelViewStateData {
};
}
export interface CustomDocumentEditState {
readonly allEdits: readonly number[];
readonly currentIndex: number;
readonly saveIndex: number;
}
export interface ExtHostWebviewsShape {
$onMessage(handle: WebviewPanelHandle, message: any): void;
$onMissingCsp(handle: WebviewPanelHandle, extensionId: string): void;
@@ -643,18 +640,18 @@ export interface ExtHostWebviewsShape {
$deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise<void>;
$resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions, cancellation: CancellationToken): Promise<void>;
$createWebviewCustomEditorDocument(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise<{ editable: boolean }>;
$disposeWebviewCustomEditorDocument(resource: UriComponents, viewType: string): Promise<void>;
$createCustomDocument(resource: UriComponents, viewType: string, backupId: string | undefined, cancellation: CancellationToken): Promise<{ editable: boolean }>;
$disposeCustomDocument(resource: UriComponents, viewType: string): Promise<void>;
$undo(resource: UriComponents, viewType: string, editId: number, state: CustomDocumentEditState): Promise<void>;
$redo(resource: UriComponents, viewType: string, editId: number, state: CustomDocumentEditState): Promise<void>;
$revert(resource: UriComponents, viewType: string, changes: { undoneEdits: number[], redoneEdits: number[] }, state: CustomDocumentEditState): Promise<void>;
$undo(resource: UriComponents, viewType: string, editId: number, isDirty: boolean): Promise<void>;
$redo(resource: UriComponents, viewType: string, editId: number, isDirty: boolean): Promise<void>;
$revert(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise<void>;
$disposeEdits(resourceComponents: UriComponents, viewType: string, editIds: number[]): void;
$onSave(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise<void>;
$onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents, cancellation: CancellationToken): Promise<void>;
$backup(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise<void>;
$backup(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise<string>;
$onMoveCustomEditor(handle: WebviewPanelHandle, newResource: UriComponents, viewType: string): Promise<void>;
}
@@ -853,7 +850,7 @@ export interface MainThreadDebugServiceShape extends IDisposable {
$acceptDAMessage(handle: number, message: DebugProtocol.ProtocolMessage): void;
$acceptDAError(handle: number, name: string, message: string, stack: string | undefined): void;
$acceptDAExit(handle: number, code: number | undefined, signal: string | undefined): void;
$registerDebugConfigurationProvider(type: string, hasProvideMethod: boolean, hasResolveMethod: boolean, hasResolve2Method: boolean, hasProvideDaMethod: boolean, handle: number): Promise<void>;
$registerDebugConfigurationProvider(type: string, scope: DebugConfigurationProviderScope, hasProvideMethod: boolean, hasResolveMethod: boolean, hasResolve2Method: boolean, hasProvideDaMethod: boolean, handle: number): Promise<void>;
$registerDebugAdapterDescriptorFactory(type: string, handle: number): Promise<void>;
$unregisterDebugConfigurationProvider(handle: number): void;
$unregisterDebugAdapterDescriptorFactory(handle: number): void;
@@ -1114,7 +1111,7 @@ export const enum ISuggestDataDtoField {
export interface ISuggestDataDto {
[ISuggestDataDtoField.label]: string;
[ISuggestDataDtoField.label2]?: string | modes.CompletionItemLabel;
[ISuggestDataDtoField.kind]: modes.CompletionItemKind;
[ISuggestDataDtoField.kind]?: modes.CompletionItemKind;
[ISuggestDataDtoField.detail]?: string;
[ISuggestDataDtoField.documentation]?: string | IMarkdownString;
[ISuggestDataDtoField.sortText]?: string;
@@ -1399,7 +1396,7 @@ export interface ExtHostTerminalServiceShape {
export interface ExtHostSCMShape {
$provideOriginalResource(sourceControlHandle: number, uri: UriComponents, token: CancellationToken): Promise<UriComponents | null>;
$onInputBoxValueChange(sourceControlHandle: number, value: string): void;
$executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number): Promise<void>;
$executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number, preserveFocus: boolean): Promise<void>;
$validateInput(sourceControlHandle: number, value: string, cursorPosition: number): Promise<[string, number] | undefined>;
$setSelectedSourceControls(selectedSourceControlHandles: number[]): Promise<void>;
}
@@ -1535,6 +1532,15 @@ export interface ExtHostCommentsShape {
$toggleReaction(commentControllerHandle: number, threadHandle: number, uri: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise<void>;
}
export interface INotebookSelectionChangeEvent {
// handles
selections: number[];
}
export interface INotebookEditorPropertiesChangeData {
selections: INotebookSelectionChangeEvent | null;
}
export interface ExtHostNotebookShape {
$resolveNotebook(viewType: string, uri: UriComponents): Promise<number | undefined>;
$executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise<void>;
@@ -1544,6 +1550,7 @@ export interface ExtHostNotebookShape {
$acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void;
$onDidReceiveMessage(uri: UriComponents, message: any): void;
$acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void;
$acceptEditorPropertiesChanged(uriComponents: UriComponents, data: INotebookEditorPropertiesChangeData): void;
}
export interface ExtHostStorageShape {

View File

@@ -143,22 +143,22 @@ const newCommands: ApiCommand[] = [
new ApiCommand(
'vscode.executeDefinitionProvider', '_executeDefinitionProvider', 'Execute all definition providers.',
[ApiCommandArgument.Uri, ApiCommandArgument.Position],
new ApiCommandResult<modes.Location[], types.Location[] | undefined>('A promise that resolves to an array of Location-instances.', tryMapWith(typeConverters.location.to))
new ApiCommandResult<(modes.Location | modes.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location-instances.', mapLocationOrLocationLink)
),
new ApiCommand(
'vscode.executeTypeDefinitionProvider', '_executeTypeDefinitionProvider', 'Execute all type definition providers.',
[ApiCommandArgument.Uri, ApiCommandArgument.Position],
new ApiCommandResult<modes.Location[], types.Location[] | undefined>('A promise that resolves to an array of Location-instances.', tryMapWith(typeConverters.location.to))
new ApiCommandResult<(modes.Location | modes.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location-instances.', mapLocationOrLocationLink)
),
new ApiCommand(
'vscode.executeDeclarationProvider', '_executeDeclarationProvider', 'Execute all declaration providers.',
[ApiCommandArgument.Uri, ApiCommandArgument.Position],
new ApiCommandResult<modes.Location[], types.Location[] | undefined>('A promise that resolves to an array of Location-instances.', tryMapWith(typeConverters.location.to))
new ApiCommandResult<(modes.Location | modes.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location-instances.', mapLocationOrLocationLink)
),
new ApiCommand(
'vscode.executeImplementationProvider', '_executeImplementationProvider', 'Execute all implementation providers.',
[ApiCommandArgument.Uri, ApiCommandArgument.Position],
new ApiCommandResult<modes.Location[], types.Location[] | undefined>('A promise that resolves to an array of Location-instances.', tryMapWith(typeConverters.location.to))
new ApiCommandResult<(modes.Location | modes.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location-instances.', mapLocationOrLocationLink)
),
new ApiCommand(
'vscode.executeReferenceProvider', '_executeReferenceProvider', 'Execute all reference providers.',
@@ -507,3 +507,18 @@ function tryMapWith<T, R>(f: (x: T) => R) {
return undefined;
};
}
function mapLocationOrLocationLink(values: (modes.Location | modes.LocationLink)[]): (types.Location | vscode.LocationLink)[] | undefined {
if (!Array.isArray(values)) {
return undefined;
}
const result: (types.Location | vscode.LocationLink)[] = [];
for (const item of values) {
if (modes.isLocationLink(item)) {
result.push(typeConverters.DefinitionLink.to(item));
} else {
result.push(typeConverters.location.to(item));
}
}
return result;
}

View File

@@ -47,12 +47,12 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
.map(session => {
return {
id: session.id,
accountName: session.accountName,
account: session.account,
scopes: session.scopes,
getAccessToken: async () => {
const isAllowed = await this._proxy.$getSessionsPrompt(
provider.id,
session.accountName,
session.account.displayName,
provider.displayName,
extensionId,
requestingExtension.displayName || requestingExtension.name);
@@ -80,14 +80,15 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
}
const session = await provider.login(scopes);
await this._proxy.$setTrustedExtension(provider.id, session.account.displayName, ExtensionIdentifier.toKey(requestingExtension.identifier), extensionName);
return {
id: session.id,
accountName: session.accountName,
account: session.account,
scopes: session.scopes,
getAccessToken: async () => {
const isAllowed = await this._proxy.$getSessionsPrompt(
provider.id,
session.accountName,
session.account.displayName,
provider.displayName,
ExtensionIdentifier.toKey(requestingExtension.identifier),
requestingExtension.displayName || requestingExtension.name);

View File

@@ -51,7 +51,7 @@ export interface IExtHostDebugService extends ExtHostDebugServiceShape {
addBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise<void>;
removeBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise<void>;
startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, options: vscode.DebugSessionOptions): Promise<boolean>;
registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable;
registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider, scope: vscode.DebugConfigurationProviderScope): vscode.Disposable;
registerDebugAdapterDescriptorFactory(extension: IExtensionDescription, type: string, factory: vscode.DebugAdapterDescriptorFactory): vscode.Disposable;
registerDebugAdapterTrackerFactory(type: string, factory: vscode.DebugAdapterTrackerFactory): vscode.Disposable;
asDebugSourceUri(source: vscode.DebugProtocolSource, session?: vscode.DebugSession): vscode.Uri;
@@ -299,7 +299,7 @@ export class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDeb
});
}
public registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable {
public registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider, scope: vscode.DebugConfigurationProviderScope): vscode.Disposable {
if (!provider) {
return new Disposable(() => { });
@@ -312,7 +312,7 @@ export class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDeb
const handle = this._configProviderHandleCounter++;
this._configProviders.push({ type, handle, provider });
this._debugServiceProxy.$registerDebugConfigurationProvider(type,
this._debugServiceProxy.$registerDebugConfigurationProvider(type, scope,
!!provider.provideDebugConfigurations,
!!provider.resolveDebugConfiguration,
!!provider.resolveDebugConfigurationWithSubstitutedVariables,
@@ -1072,11 +1072,9 @@ class DirectDebugAdapter extends AbstractDebugAdapter {
constructor(private implementation: vscode.DebugAdapter) {
super();
if (this.implementation.onDidSendMessage) {
implementation.onDidSendMessage((message: vscode.DebugProtocolMessage) => {
this.acceptMessage(message as DebugProtocol.ProtocolMessage);
});
}
implementation.onDidSendMessage((message: vscode.DebugProtocolMessage) => {
this.acceptMessage(message as DebugProtocol.ProtocolMessage);
});
}
startSession(): Promise<void> {
@@ -1084,15 +1082,11 @@ class DirectDebugAdapter extends AbstractDebugAdapter {
}
sendMessage(message: DebugProtocol.ProtocolMessage): void {
if (this.implementation.handleMessage) {
this.implementation.handleMessage(message);
}
this.implementation.handleMessage(message);
}
stopSession(): Promise<void> {
if (this.implementation.dispose) {
this.implementation.dispose();
}
this.implementation.dispose();
return Promise.resolve(undefined);
}
}

View File

@@ -181,6 +181,10 @@ export class ExtHostDocumentData extends MirrorTextModel {
throw new Error('Invalid argument');
}
if (this._lines.length === 0) {
return position.with(0, 0);
}
let { line, character } = position;
let hasChanged = false;

View File

@@ -371,7 +371,6 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
get storagePath() { return that._storagePath.workspaceValue(extensionDescription); },
get globalStoragePath() { return that._storagePath.globalValue(extensionDescription); },
asAbsolutePath(relativePath: string) { return path.join(extensionDescription.extensionLocation.fsPath, relativePath); },
asExtensionUri(relativePath: string) { return joinPath(extensionDescription.extensionLocation, relativePath); },
get logPath() { return path.join(that._initData.logsLocation.fsPath, extensionDescription.identifier.value); }
});
});

View File

@@ -834,7 +834,7 @@ class SuggestAdapter {
private readonly _extension: IExtensionDescription,
) { }
provideCompletionItems(resource: URI, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise<extHostProtocol.ISuggestResultDto | undefined> {
async provideCompletionItems(resource: URI, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise<extHostProtocol.ISuggestResultDto | undefined> {
const doc = this._documents.getDocument(resource);
const pos = typeConvert.Position.to(position);
@@ -845,48 +845,47 @@ class SuggestAdapter {
const replaceRange = doc.getWordRangeAtPosition(pos) || new Range(pos, pos);
const insertRange = replaceRange.with({ end: pos });
return asPromise(() => this._provider.provideCompletionItems(doc, pos, token, typeConvert.CompletionContext.to(context))).then(value => {
const itemsOrList = await asPromise(() => this._provider.provideCompletionItems(doc, pos, token, typeConvert.CompletionContext.to(context)));
if (!value) {
// undefined and null are valid results
return undefined;
if (!itemsOrList) {
// undefined and null are valid results
return undefined;
}
if (token.isCancellationRequested) {
// cancelled -> return without further ado, esp no caching
// of results as they will leak
return undefined;
}
const list = Array.isArray(itemsOrList) ? new CompletionList(itemsOrList) : itemsOrList;
// keep result for providers that support resolving
const pid: number = SuggestAdapter.supportsResolving(this._provider) ? this._cache.add(list.items) : this._cache.add([]);
const disposables = new DisposableStore();
this._disposables.set(pid, disposables);
const completions: extHostProtocol.ISuggestDataDto[] = [];
const result: extHostProtocol.ISuggestResultDto = {
x: pid,
[extHostProtocol.ISuggestResultDtoField.completions]: completions,
[extHostProtocol.ISuggestResultDtoField.defaultRanges]: { replace: typeConvert.Range.from(replaceRange), insert: typeConvert.Range.from(insertRange) },
[extHostProtocol.ISuggestResultDtoField.isIncomplete]: list.isIncomplete || undefined
};
for (let i = 0; i < list.items.length; i++) {
const item = list.items[i];
// check for bad completion item first
if (this._validateCompletionItem(item, pos)) {
const dto = this._convertCompletionItem(item, [pid, i], insertRange, replaceRange);
completions.push(dto);
}
}
if (token.isCancellationRequested) {
// cancelled -> return without further ado, esp no caching
// of results as they will leak
return undefined;
}
const list = Array.isArray(value) ? new CompletionList(value) : value;
// keep result for providers that support resolving
const pid: number = SuggestAdapter.supportsResolving(this._provider) ? this._cache.add(list.items) : this._cache.add([]);
const disposables = new DisposableStore();
this._disposables.set(pid, disposables);
const completions: extHostProtocol.ISuggestDataDto[] = [];
const result: extHostProtocol.ISuggestResultDto = {
x: pid,
[extHostProtocol.ISuggestResultDtoField.completions]: completions,
[extHostProtocol.ISuggestResultDtoField.defaultRanges]: { replace: typeConvert.Range.from(replaceRange), insert: typeConvert.Range.from(insertRange) },
[extHostProtocol.ISuggestResultDtoField.isIncomplete]: list.isIncomplete || undefined
};
for (let i = 0; i < list.items.length; i++) {
const item = list.items[i];
// check for bad completion item first
if (this._validateCompletionItem(item, pos)) {
const dto = this._convertCompletionItem(item, [pid, i], insertRange, replaceRange);
completions.push(dto);
}
}
return result;
});
return result;
}
resolveCompletionItem(_resource: URI, position: IPosition, id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise<extHostProtocol.ISuggestDataDto | undefined> {
async resolveCompletionItem(_resource: URI, position: IPosition, id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise<extHostProtocol.ISuggestDataDto | undefined> {
if (typeof this._provider.resolveCompletionItem !== 'function') {
return Promise.resolve(undefined);
@@ -901,40 +900,39 @@ class SuggestAdapter {
const _mustNotChange = SuggestAdapter._mustNotChangeHash(item);
const _mayNotChange = SuggestAdapter._mayNotChangeHash(item);
return asPromise(() => this._provider.resolveCompletionItem!(item, token)).then(resolvedItem => {
const resolvedItem = await asPromise(() => this._provider.resolveCompletionItem!(item, token));
if (!resolvedItem || !this._validateCompletionItem(resolvedItem, pos)) {
return undefined;
}
if (!resolvedItem || !this._validateCompletionItem(resolvedItem, pos)) {
return undefined;
}
type BlameExtension = {
extensionId: string;
kind: string;
index: string;
};
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' };
};
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 _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;
}
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);
});
return this._convertCompletionItem(resolvedItem, id);
}
releaseCompletionItems(id: number): any {
@@ -956,7 +954,7 @@ class SuggestAdapter {
//
[extHostProtocol.ISuggestDataDtoField.label]: item.label,
[extHostProtocol.ISuggestDataDtoField.label2]: item.label2,
[extHostProtocol.ISuggestDataDtoField.kind]: typeConvert.CompletionItemKind.from(item.kind),
[extHostProtocol.ISuggestDataDtoField.kind]: item.kind !== undefined ? typeConvert.CompletionItemKind.from(item.kind) : undefined,
[extHostProtocol.ISuggestDataDtoField.kindModifier]: item.tags && item.tags.map(typeConvert.CompletionItemTag.from),
[extHostProtocol.ISuggestDataDtoField.detail]: item.detail,
[extHostProtocol.ISuggestDataDtoField.documentation]: typeof item.documentation === 'undefined' ? undefined : typeConvert.MarkdownString.fromStrict(item.documentation),
@@ -994,7 +992,7 @@ class SuggestAdapter {
// "old" range
result[extHostProtocol.ISuggestDataDtoField.range] = typeConvert.Range.from(range);
} else if (range && !defaultInsertRange?.isEqual(range.inserting) && !defaultReplaceRange?.isEqual(range.replacing)) {
} else if (range && (!defaultInsertRange?.isEqual(range.inserting) || !defaultReplaceRange?.isEqual(range.replacing))) {
// ONLY send range when it's different from the default ranges (safe bandwidth)
result[extHostProtocol.ISuggestDataDtoField.range] = {
insert: typeConvert.Range.from(range.inserting),

View File

@@ -10,12 +10,14 @@ 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 } from 'vs/workbench/api/common/extHost.protocol';
import { CellKind, CellOutputKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, MainThreadDocumentsShape, INotebookEditorPropertiesChangeData } 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 } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { Disposable as VSCodeDisposable } from './extHostTypes';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData';
import { NotImplementedProxy } from 'vs/base/common/types';
interface IObservable<T> {
proxy: T;
@@ -40,39 +42,48 @@ function getObservable<T extends Object>(obj: T): IObservable<T> {
export class ExtHostCell extends Disposable implements vscode.NotebookCell {
private originalSource: string[];
// private originalSource: string[];
private _outputs: any[];
private _onDidChangeOutputs = new Emitter<ISplice<vscode.CellOutput>[]>();
onDidChangeOutputs: Event<ISplice<vscode.CellOutput>[]> = this._onDidChangeOutputs.event;
private _textDocument: vscode.TextDocument | undefined;
private _initalVersion: number = -1;
// private _textDocument: vscode.TextDocument | undefined;
// private _initalVersion: number = -1;
private _outputMapping = new Set<vscode.CellOutput>();
private _metadata: vscode.NotebookCellMetadata;
private _metadataChangeListener: IDisposable;
private _documentData: ExtHostDocumentData;
get document(): vscode.TextDocument {
return this._documentData.document;
}
get source() {
if (this._textDocument && this._initalVersion !== this._textDocument?.version) {
return this._textDocument.getText();
} else {
return this.originalSource.join('\n');
}
// todo@jrieken remove this
return this._documentData.getText();
}
constructor(
private viewType: string,
private documentUri: URI,
private readonly viewType: string,
private readonly documentUri: URI,
readonly handle: number,
readonly uri: URI,
private _content: string,
content: string,
public readonly cellKind: CellKind,
public language: string,
outputs: any[],
_metadata: vscode.NotebookCellMetadata | undefined,
private _proxy: MainThreadNotebookShape
private _proxy: MainThreadNotebookShape,
) {
super();
this.originalSource = this._content.split(/\r|\n|\r\n/g);
this._documentData = new ExtHostDocumentData(
new class extends NotImplementedProxy<MainThreadDocumentsShape>('document') { },
uri,
content.split(/\r|\n|\r\n/g), '\n',
language, 0, false
);
this._outputs = outputs;
const observableMetadata = getObservable(_metadata || {});
@@ -125,26 +136,19 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell {
return this._proxy.$updateNotebookCellMetadata(this.viewType, this.documentUri, this.handle, this._metadata);
}
getContent(): string {
if (this._textDocument && this._initalVersion !== this._textDocument?.version) {
return this._textDocument.getText();
} else {
return this.originalSource.join('\n');
}
}
attachTextDocument(document: vscode.TextDocument) {
this._textDocument = document;
this._initalVersion = this._textDocument.version;
attachTextDocument(document: ExtHostDocumentData) {
this._documentData = document;
// this._initalVersion = this._documentData.version;
}
detachTextDocument() {
if (this._textDocument && this._textDocument.version !== this._initalVersion) {
this.originalSource = this._textDocument.getText().split(/\r|\n|\r\n/g);
}
// no-op? keep stale document until new comes along?
this._textDocument = undefined;
this._initalVersion = -1;
// if (this._textDocument && this._textDocument.version !== this._initalVersion) {
// this.originalSource = this._textDocument.getText().split(/\r|\n|\r\n/g);
// }
// this._textDocument = undefined;
// this._initalVersion = -1;
}
}
@@ -256,10 +260,10 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
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 document = this._documentsAndEditors.getDocument(URI.revive(cell.uri));
const documentData = this._documentsAndEditors.getDocument(URI.revive(cell.uri));
if (document) {
extCell.attachTextDocument(document.document);
if (documentData) {
extCell.attachTextDocument(documentData);
}
if (!this._cellDisposableMapping.has(extCell.handle)) {
@@ -366,15 +370,15 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
return this.cells.find(cell => cell.handle === cellHandle);
}
attachCellTextDocument(textDocument: vscode.TextDocument) {
let cell = this.cells.find(cell => cell.uri.toString() === textDocument.uri.toString());
attachCellTextDocument(textDocument: ExtHostDocumentData) {
let cell = this.cells.find(cell => cell.uri.toString() === textDocument.document.uri.toString());
if (cell) {
cell.attachTextDocument(textDocument);
}
}
detachCellTextDocument(textDocument: vscode.TextDocument) {
let cell = this.cells.find(cell => cell.uri.toString() === textDocument.uri.toString());
detachCellTextDocument(textDocument: ExtHostDocumentData) {
let cell = this.cells.find(cell => cell.uri.toString() === textDocument.document.uri.toString());
if (cell) {
cell.detachTextDocument();
}
@@ -455,6 +459,8 @@ export class NotebookEditorCellEdit {
export class ExtHostNotebookEditor extends Disposable implements vscode.NotebookEditor {
private _viewColumn: vscode.ViewColumn | undefined;
selection?: ExtHostCell = undefined;
onDidReceiveMessage: vscode.Event<any> = this._onDidReceiveMessage.event;
constructor(
@@ -468,22 +474,22 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook
) {
super();
this._register(this._documentsAndEditors.onDidAddDocuments(documents => {
for (const { document: textDocument } of documents) {
let data = CellUri.parse(textDocument.uri);
for (const documentData of documents) {
let data = CellUri.parse(documentData.document.uri);
if (data) {
if (this.document.uri.toString() === data.notebook.toString()) {
document.attachCellTextDocument(textDocument);
document.attachCellTextDocument(documentData);
}
}
}
}));
this._register(this._documentsAndEditors.onDidRemoveDocuments(documents => {
for (const { document: textDocument } of documents) {
let data = CellUri.parse(textDocument.uri);
for (const documentData of documents) {
let data = CellUri.parse(documentData.document.uri);
if (data) {
if (this.document.uri.toString() === data.notebook.toString()) {
document.detachCellTextDocument(textDocument);
document.detachCellTextDocument(documentData);
}
}
}
@@ -593,6 +599,10 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
private readonly _documents = 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: NotebookCellsSplice2[] }>();
readonly onDidChangeNotebookDocument: Event<{ document: ExtHostNotebookDocument, changes: NotebookCellsSplice2[] }> = this._onDidChangeNotebookDocument.event;
private _outputDisplayOrder: INotebookDisplayOrder | undefined;
get outputDisplayOrder(): INotebookDisplayOrder | undefined {
@@ -605,6 +615,12 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
return this._activeNotebookDocument;
}
private _activeNotebookEditor: ExtHostNotebookEditor | undefined;
get activeNotebookEditor() {
return this._activeNotebookEditor;
}
constructor(mainContext: IMainContext, commands: ExtHostCommands, private _documentsAndEditors: ExtHostDocumentsAndEditors) {
this._proxy = mainContext.getProxy(MainContext.MainThreadNotebook);
@@ -738,6 +754,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
async $updateActiveEditor(viewType: string, uri: UriComponents): Promise<void> {
this._activeNotebookDocument = this._documents.get(URI.revive(uri).toString());
this._activeNotebookEditor = this._editors.get(URI.revive(uri).toString())?.editor;
}
async $destoryNotebookDocument(viewType: string, uri: UriComponents): Promise<boolean> {
@@ -782,7 +799,30 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
if (editor) {
editor.editor.document.accpetModelChanged(event);
this._onDidChangeNotebookDocument.fire({
document: editor.editor.document,
changes: event.changes
});
}
}
$acceptEditorPropertiesChanged(uriComponents: UriComponents, data: INotebookEditorPropertiesChangeData): void {
let editor = this._editors.get(URI.revive(uriComponents).toString());
if (!editor) {
return;
}
if (data.selections) {
const cells = editor.editor.document.cells;
if (data.selections.selections.length) {
const firstCell = data.selections.selections[0];
editor.editor.selection = cells.find(cell => cell.handle === firstCell);
} else {
editor.editor.selection = undefined;
}
}
}
}

View File

@@ -0,0 +1,158 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as types from 'vs/workbench/api/common/extHostTypes';
import * as vscode from 'vscode';
import { Event, Emitter } from 'vs/base/common/event';
import { ExtHostNotebookController, ExtHostCell } from 'vs/workbench/api/common/extHostNotebook';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { score } from 'vs/editor/common/modes/languageSelector';
import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { isEqual } from 'vs/base/common/resources';
export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextDocument {
private _disposables = new DisposableStore();
private _isClosed = false;
private _cells!: ExtHostCell[];
private _cellLengths!: PrefixSumComputer;
private _cellLines!: PrefixSumComputer;
private _versionId = 0;
private readonly _onDidChange = new Emitter<void>();
readonly onDidChange: Event<void> = this._onDidChange.event;
constructor(
extHostNotebooks: ExtHostNotebookController,
extHostDocuments: ExtHostDocuments,
private readonly _notebook: vscode.NotebookDocument,
private readonly _selector: vscode.DocumentSelector | undefined,
) {
this._init();
this._disposables.add(extHostDocuments.onDidChangeDocument(e => {
let cellIdx = this._cells.findIndex(cell => isEqual(cell.uri, e.document.uri));
if (cellIdx >= 0) {
this._cellLengths.changeValue(cellIdx, this._cells[cellIdx].document.getText().length + 1);
this._cellLines.changeValue(cellIdx, this._cells[cellIdx].document.lineCount);
this._versionId += 1;
this._onDidChange.fire(undefined);
}
}));
this._disposables.add(extHostNotebooks.onDidChangeNotebookDocument(e => {
if (e.document === this._notebook) {
this._init();
this._versionId += 1;
this._onDidChange.fire(undefined);
}
}));
}
dispose(): void {
this._disposables.dispose();
this._isClosed = true;
}
get isClosed() {
return this._isClosed;
}
private _init() {
this._cells = [];
const cellLengths: number[] = [];
const cellLineCounts: number[] = [];
for (let cell of this._notebook.cells) {
if (cell.cellKind === CellKind.Code && (!this._selector || score(this._selector, cell.uri, cell.language, true))) {
this._cells.push(<ExtHostCell>cell);
cellLengths.push(cell.document.getText().length + 1);
cellLineCounts.push(cell.document.lineCount);
}
}
this._cellLengths = new PrefixSumComputer(new Uint32Array(cellLengths));
this._cellLines = new PrefixSumComputer(new Uint32Array(cellLineCounts));
}
get version(): number {
return this._versionId;
}
getText(range?: vscode.Range): string {
if (!range) {
let result = '';
for (let cell of this._cells) {
result += cell.document.getText() + '\n';
}
// remove last newline again
result = result.slice(0, -1);
return result;
}
if (range.isEmpty) {
return '';
}
// get start and end locations and create substrings
const start = this.locationAt(range.start);
const end = this.locationAt(range.end);
const startCell = this._cells.find(cell => isEqual(cell.uri, start.uri));
const endCell = this._cells.find(cell => isEqual(cell.uri, end.uri));
if (!startCell || !endCell) {
return '';
} else if (startCell === endCell) {
return startCell.document.getText(new types.Range(start.range.start, end.range.end));
} else {
let a = startCell.document.getText(new types.Range(start.range.start, new types.Position(startCell.document.lineCount, 0)));
let b = endCell.document.getText(new types.Range(new types.Position(0, 0), end.range.end));
return a + '\n' + b;
}
}
offsetAt(position: vscode.Position): number {
const idx = this._cellLines.getIndexOf(position.line);
const offset1 = this._cellLengths.getAccumulatedValue(idx.index - 1);
const offset2 = this._cells[idx.index].document.offsetAt(position.with(idx.remainder));
return offset1 + offset2;
}
positionAt(locationOrOffset: vscode.Location | number): vscode.Position {
if (typeof locationOrOffset === 'number') {
const idx = this._cellLengths.getIndexOf(locationOrOffset);
const lineCount = this._cellLines.getAccumulatedValue(idx.index - 1);
return this._cells[idx.index].document.positionAt(idx.remainder).translate(lineCount);
}
const idx = this._cells.findIndex(cell => isEqual(cell.uri, locationOrOffset.uri));
if (idx >= 0) {
let line = this._cellLines.getAccumulatedValue(idx - 1);
return new types.Position(line + locationOrOffset.range.start.line, locationOrOffset.range.start.character);
}
// do better?
// return undefined;
return new types.Position(0, 0);
}
locationAt(positionOrRange: vscode.Range | vscode.Position): types.Location {
if (!types.Range.isRange(positionOrRange)) {
positionOrRange = new types.Range(<types.Position>positionOrRange, <types.Position>positionOrRange);
}
const startIdx = this._cellLines.getIndexOf(positionOrRange.start.line);
let endIdx = startIdx;
if (!positionOrRange.isEmpty) {
endIdx = this._cellLines.getIndexOf(positionOrRange.end.line);
}
let startPos = new types.Position(startIdx.remainder, positionOrRange.start.character);
let endPos = new types.Position(endIdx.remainder, positionOrRange.end.character);
let range = new types.Range(startPos, endPos);
const startCell = this._cells[startIdx.index];
return new types.Location(startCell.uri, <types.Range>startCell.document.validateRange(range));
}
}

View File

@@ -9,7 +9,7 @@ import { debounce } from 'vs/base/common/decorators';
import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
import { asPromise } from 'vs/base/common/async';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto } from './extHost.protocol';
import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto, MainThreadTelemetryShape } from './extHost.protocol';
import { sortedDiff, equals } from 'vs/base/common/arrays';
import { comparePaths } from 'vs/base/common/comparers';
import type * as vscode from 'vscode';
@@ -194,6 +194,11 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox {
set visible(visible: boolean) {
visible = !!visible;
if (this._visible === visible) {
return;
}
this._visible = visible;
this._proxy.$setInputBoxVisibility(this._sourceControlHandle, visible);
}
@@ -266,14 +271,14 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG
return this._resourceStatesMap.get(handle);
}
$executeResourceCommand(handle: number): Promise<void> {
$executeResourceCommand(handle: number, preserveFocus: boolean): Promise<void> {
const command = this._resourceStatesCommandsMap.get(handle);
if (!command) {
return Promise.resolve(undefined);
}
return asPromise(() => this._commands.executeCommand(command.command, ...(command.arguments || [])));
return asPromise(() => this._commands.executeCommand(command.command, ...(command.arguments || []), preserveFocus));
}
_takeResourceStateSnapshot(): SCMRawResourceSplice[] {
@@ -524,6 +529,7 @@ export class ExtHostSCM implements ExtHostSCMShape {
private static _handlePool: number = 0;
private _proxy: MainThreadSCMShape;
private readonly _telemetry: MainThreadTelemetryShape;
private _sourceControls: Map<ProviderHandle, ExtHostSourceControl> = new Map<ProviderHandle, ExtHostSourceControl>();
private _sourceControlsByExtension: Map<string, ExtHostSourceControl[]> = new Map<string, ExtHostSourceControl[]>();
@@ -538,6 +544,7 @@ export class ExtHostSCM implements ExtHostSCMShape {
@ILogService private readonly logService: ILogService
) {
this._proxy = mainContext.getProxy(MainContext.MainThreadSCM);
this._telemetry = mainContext.getProxy(MainContext.MainThreadTelemetry);
_commands.registerArgumentProcessor({
processArgument: arg => {
@@ -581,6 +588,12 @@ export class ExtHostSCM implements ExtHostSCMShape {
createSourceControl(extension: IExtensionDescription, id: string, label: string, rootUri: vscode.Uri | undefined): vscode.SourceControl {
this.logService.trace('ExtHostSCM#createSourceControl', extension.identifier.value, id, label, rootUri);
type TEvent = { extensionId: string; };
type TMeta = { extensionId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; };
this._telemetry.$publicLog2<TEvent, TMeta>('api/scm/createSourceControl', {
extensionId: extension.identifier.value,
});
const handle = ExtHostSCM._handlePool++;
const sourceControl = new ExtHostSourceControl(extension, this._proxy, this._commands, id, label, rootUri);
this._sourceControls.set(handle, sourceControl);
@@ -628,7 +641,7 @@ export class ExtHostSCM implements ExtHostSCMShape {
return Promise.resolve(undefined);
}
$executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number): Promise<void> {
$executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number, preserveFocus: boolean): Promise<void> {
this.logService.trace('ExtHostSCM#$executeResourceCommand', sourceControlHandle, groupHandle, handle);
const sourceControl = this._sourceControls.get(sourceControlHandle);
@@ -643,7 +656,7 @@ export class ExtHostSCM implements ExtHostSCMShape {
return Promise.resolve(undefined);
}
return group.$executeResourceCommand(handle);
return group.$executeResourceCommand(handle, preserveFocus);
}
$validateInput(sourceControlHandle: number, value: string, cursorPosition: number): Promise<[string, number] | undefined> {

View File

@@ -692,7 +692,7 @@ export class EnvironmentVariableCollection implements vscode.EnvironmentVariable
forEach(callback: (variable: string, mutator: vscode.EnvironmentVariableMutator, collection: vscode.EnvironmentVariableCollection) => any, thisArg?: any): void {
this._checkDisposed();
this.map.forEach((value, key) => callback(key, value, this));
this.map.forEach((value, key) => callback.call(thisArg, key, value, this));
}
delete(variable: string): void {

View File

@@ -728,6 +728,18 @@ export namespace DefinitionLink {
: undefined,
};
}
export function to(value: modes.LocationLink): vscode.LocationLink {
return {
targetUri: value.uri,
targetRange: Range.to(value.range),
targetSelectionRange: value.targetSelectionRange
? Range.to(value.targetSelectionRange)
: undefined,
originSelectionRange: value.originSelectionRange
? Range.to(value.originSelectionRange)
: undefined
};
}
}
export namespace Hover {
@@ -808,70 +820,72 @@ export namespace CompletionItemTag {
export namespace CompletionItemKind {
export function from(kind: types.CompletionItemKind | undefined): modes.CompletionItemKind {
switch (kind) {
case types.CompletionItemKind.Method: return modes.CompletionItemKind.Method;
case types.CompletionItemKind.Function: return modes.CompletionItemKind.Function;
case types.CompletionItemKind.Constructor: return modes.CompletionItemKind.Constructor;
case types.CompletionItemKind.Field: return modes.CompletionItemKind.Field;
case types.CompletionItemKind.Variable: return modes.CompletionItemKind.Variable;
case types.CompletionItemKind.Class: return modes.CompletionItemKind.Class;
case types.CompletionItemKind.Interface: return modes.CompletionItemKind.Interface;
case types.CompletionItemKind.Struct: return modes.CompletionItemKind.Struct;
case types.CompletionItemKind.Module: return modes.CompletionItemKind.Module;
case types.CompletionItemKind.Property: return modes.CompletionItemKind.Property;
case types.CompletionItemKind.Unit: return modes.CompletionItemKind.Unit;
case types.CompletionItemKind.Value: return modes.CompletionItemKind.Value;
case types.CompletionItemKind.Constant: return modes.CompletionItemKind.Constant;
case types.CompletionItemKind.Enum: return modes.CompletionItemKind.Enum;
case types.CompletionItemKind.EnumMember: return modes.CompletionItemKind.EnumMember;
case types.CompletionItemKind.Keyword: return modes.CompletionItemKind.Keyword;
case types.CompletionItemKind.Snippet: return modes.CompletionItemKind.Snippet;
case types.CompletionItemKind.Text: return modes.CompletionItemKind.Text;
case types.CompletionItemKind.Color: return modes.CompletionItemKind.Color;
case types.CompletionItemKind.File: return modes.CompletionItemKind.File;
case types.CompletionItemKind.Reference: return modes.CompletionItemKind.Reference;
case types.CompletionItemKind.Folder: return modes.CompletionItemKind.Folder;
case types.CompletionItemKind.Event: return modes.CompletionItemKind.Event;
case types.CompletionItemKind.Operator: return modes.CompletionItemKind.Operator;
case types.CompletionItemKind.TypeParameter: return modes.CompletionItemKind.TypeParameter;
case types.CompletionItemKind.Issue: return modes.CompletionItemKind.Issue;
case types.CompletionItemKind.User: return modes.CompletionItemKind.User;
}
return modes.CompletionItemKind.Property;
const _from = new Map<types.CompletionItemKind, modes.CompletionItemKind>([
[types.CompletionItemKind.Method, modes.CompletionItemKind.Method],
[types.CompletionItemKind.Function, modes.CompletionItemKind.Function],
[types.CompletionItemKind.Constructor, modes.CompletionItemKind.Constructor],
[types.CompletionItemKind.Field, modes.CompletionItemKind.Field],
[types.CompletionItemKind.Variable, modes.CompletionItemKind.Variable],
[types.CompletionItemKind.Class, modes.CompletionItemKind.Class],
[types.CompletionItemKind.Interface, modes.CompletionItemKind.Interface],
[types.CompletionItemKind.Struct, modes.CompletionItemKind.Struct],
[types.CompletionItemKind.Module, modes.CompletionItemKind.Module],
[types.CompletionItemKind.Property, modes.CompletionItemKind.Property],
[types.CompletionItemKind.Unit, modes.CompletionItemKind.Unit],
[types.CompletionItemKind.Value, modes.CompletionItemKind.Value],
[types.CompletionItemKind.Constant, modes.CompletionItemKind.Constant],
[types.CompletionItemKind.Enum, modes.CompletionItemKind.Enum],
[types.CompletionItemKind.EnumMember, modes.CompletionItemKind.EnumMember],
[types.CompletionItemKind.Keyword, modes.CompletionItemKind.Keyword],
[types.CompletionItemKind.Snippet, modes.CompletionItemKind.Snippet],
[types.CompletionItemKind.Text, modes.CompletionItemKind.Text],
[types.CompletionItemKind.Color, modes.CompletionItemKind.Color],
[types.CompletionItemKind.File, modes.CompletionItemKind.File],
[types.CompletionItemKind.Reference, modes.CompletionItemKind.Reference],
[types.CompletionItemKind.Folder, modes.CompletionItemKind.Folder],
[types.CompletionItemKind.Event, modes.CompletionItemKind.Event],
[types.CompletionItemKind.Operator, modes.CompletionItemKind.Operator],
[types.CompletionItemKind.TypeParameter, modes.CompletionItemKind.TypeParameter],
[types.CompletionItemKind.Issue, modes.CompletionItemKind.Issue],
[types.CompletionItemKind.User, modes.CompletionItemKind.User],
]);
export function from(kind: types.CompletionItemKind): modes.CompletionItemKind {
return _from.get(kind) ?? modes.CompletionItemKind.Property;
}
const _to = new Map<modes.CompletionItemKind, types.CompletionItemKind>([
[modes.CompletionItemKind.Method, types.CompletionItemKind.Method],
[modes.CompletionItemKind.Function, types.CompletionItemKind.Function],
[modes.CompletionItemKind.Constructor, types.CompletionItemKind.Constructor],
[modes.CompletionItemKind.Field, types.CompletionItemKind.Field],
[modes.CompletionItemKind.Variable, types.CompletionItemKind.Variable],
[modes.CompletionItemKind.Class, types.CompletionItemKind.Class],
[modes.CompletionItemKind.Interface, types.CompletionItemKind.Interface],
[modes.CompletionItemKind.Struct, types.CompletionItemKind.Struct],
[modes.CompletionItemKind.Module, types.CompletionItemKind.Module],
[modes.CompletionItemKind.Property, types.CompletionItemKind.Property],
[modes.CompletionItemKind.Unit, types.CompletionItemKind.Unit],
[modes.CompletionItemKind.Value, types.CompletionItemKind.Value],
[modes.CompletionItemKind.Constant, types.CompletionItemKind.Constant],
[modes.CompletionItemKind.Enum, types.CompletionItemKind.Enum],
[modes.CompletionItemKind.EnumMember, types.CompletionItemKind.EnumMember],
[modes.CompletionItemKind.Keyword, types.CompletionItemKind.Keyword],
[modes.CompletionItemKind.Snippet, types.CompletionItemKind.Snippet],
[modes.CompletionItemKind.Text, types.CompletionItemKind.Text],
[modes.CompletionItemKind.Color, types.CompletionItemKind.Color],
[modes.CompletionItemKind.File, types.CompletionItemKind.File],
[modes.CompletionItemKind.Reference, types.CompletionItemKind.Reference],
[modes.CompletionItemKind.Folder, types.CompletionItemKind.Folder],
[modes.CompletionItemKind.Event, types.CompletionItemKind.Event],
[modes.CompletionItemKind.Operator, types.CompletionItemKind.Operator],
[modes.CompletionItemKind.TypeParameter, types.CompletionItemKind.TypeParameter],
[modes.CompletionItemKind.User, types.CompletionItemKind.User],
[modes.CompletionItemKind.Issue, types.CompletionItemKind.Issue],
]);
export function to(kind: modes.CompletionItemKind): types.CompletionItemKind {
switch (kind) {
case modes.CompletionItemKind.Method: return types.CompletionItemKind.Method;
case modes.CompletionItemKind.Function: return types.CompletionItemKind.Function;
case modes.CompletionItemKind.Constructor: return types.CompletionItemKind.Constructor;
case modes.CompletionItemKind.Field: return types.CompletionItemKind.Field;
case modes.CompletionItemKind.Variable: return types.CompletionItemKind.Variable;
case modes.CompletionItemKind.Class: return types.CompletionItemKind.Class;
case modes.CompletionItemKind.Interface: return types.CompletionItemKind.Interface;
case modes.CompletionItemKind.Struct: return types.CompletionItemKind.Struct;
case modes.CompletionItemKind.Module: return types.CompletionItemKind.Module;
case modes.CompletionItemKind.Property: return types.CompletionItemKind.Property;
case modes.CompletionItemKind.Unit: return types.CompletionItemKind.Unit;
case modes.CompletionItemKind.Value: return types.CompletionItemKind.Value;
case modes.CompletionItemKind.Constant: return types.CompletionItemKind.Constant;
case modes.CompletionItemKind.Enum: return types.CompletionItemKind.Enum;
case modes.CompletionItemKind.EnumMember: return types.CompletionItemKind.EnumMember;
case modes.CompletionItemKind.Keyword: return types.CompletionItemKind.Keyword;
case modes.CompletionItemKind.Snippet: return types.CompletionItemKind.Snippet;
case modes.CompletionItemKind.Text: return types.CompletionItemKind.Text;
case modes.CompletionItemKind.Color: return types.CompletionItemKind.Color;
case modes.CompletionItemKind.File: return types.CompletionItemKind.File;
case modes.CompletionItemKind.Reference: return types.CompletionItemKind.Reference;
case modes.CompletionItemKind.Folder: return types.CompletionItemKind.Folder;
case modes.CompletionItemKind.Event: return types.CompletionItemKind.Event;
case modes.CompletionItemKind.Operator: return types.CompletionItemKind.Operator;
case modes.CompletionItemKind.TypeParameter: return types.CompletionItemKind.TypeParameter;
case modes.CompletionItemKind.User: return types.CompletionItemKind.User;
case modes.CompletionItemKind.Issue: return types.CompletionItemKind.Issue;
}
return types.CompletionItemKind.Property;
return _to.get(kind) ?? types.CompletionItemKind.Property;
}
}
@@ -939,7 +953,8 @@ export namespace SignatureInformation {
return {
label: info.label,
documentation: info.documentation ? MarkdownString.fromStrict(info.documentation) : undefined,
parameters: Array.isArray(info.parameters) ? info.parameters.map(ParameterInformation.from) : []
parameters: Array.isArray(info.parameters) ? info.parameters.map(ParameterInformation.from) : [],
activeParameter: info.activeParameter,
};
}
@@ -947,7 +962,8 @@ export namespace SignatureInformation {
return {
label: info.label,
documentation: htmlContent.isMarkdownString(info.documentation) ? MarkdownString.to(info.documentation) : info.documentation,
parameters: Array.isArray(info.parameters) ? info.parameters.map(ParameterInformation.to) : []
parameters: Array.isArray(info.parameters) ? info.parameters.map(ParameterInformation.to) : [],
activeParameter: info.activeParameter,
};
}
}

View File

@@ -6,18 +6,15 @@
import { coalesce, equals } from 'vs/base/common/arrays';
import { escapeCodicons } from 'vs/base/common/codicons';
import { illegalArgument } from 'vs/base/common/errors';
import { Emitter } from 'vs/base/common/event';
import { IRelativePattern } from 'vs/base/common/glob';
import { isMarkdownString } from 'vs/base/common/htmlContent';
import { startsWith } from 'vs/base/common/strings';
import { isStringArray } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files';
import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver';
import type * as vscode from 'vscode';
import { Cache } from './cache';
import { assertIsDefined, isStringArray } from 'vs/base/common/types';
import { Schemas } from 'vs/base/common/network';
function es5ClassCompat(target: Function): any {
///@ts-expect-error
@@ -1294,6 +1291,7 @@ export class SignatureInformation {
label: string;
documentation?: string | MarkdownString;
parameters: ParameterInformation[];
activeParameter?: number;
constructor(label: string, documentation?: string | MarkdownString) {
this.label = label;
@@ -2654,6 +2652,22 @@ export enum DebugConsoleMode {
MergeWithParent = 1
}
/**
* VS Code can call the `provideDebugConfigurations` method of a `DebugConfigurationProvider` in two situations (aka 'scopes'):
* to provide the initial debug configurations for a newly create launch.json or to provide debug configurations dynamically based on context.
* A scope value is used when registering a `DebugConfigurationProvider` with `debug.registerDebugConfigurationProvider`.
*/
export enum DebugConfigurationProviderScope {
/**
* The 'initial' scope denotes a context where all debug configurations for a newly created launch.json are needed.
*/
Initial = 1,
/**
* The 'dynamic' scope denotes a context where all debug configurations for the current context are needed.
*/
Dynamic = 2
}
//#endregion
@es5ClassCompat
@@ -2716,6 +2730,13 @@ export enum CellOutputKind {
Rich = 3
}
export enum NotebookCellRunState {
Running = 1,
Idle = 2,
Success = 3,
Error = 4
}
//#endregion
//#region Timeline
@@ -2726,94 +2747,3 @@ export class TimelineItem implements vscode.TimelineItem {
}
//#endregion Timeline
//#region Custom Editors
interface EditState {
readonly allEdits: readonly number[];
readonly currentIndex: number;
readonly saveIndex: number;
}
export class CustomDocument<EditType = unknown> implements vscode.CustomDocument<EditType> {
readonly #edits = new Cache<EditType>('edits');
readonly #uri: vscode.Uri;
#editState: EditState = {
allEdits: [],
currentIndex: -1,
saveIndex: -1,
};
#isDisposed = false;
#version = 1;
constructor(uri: vscode.Uri) {
this.#uri = uri;
}
//#region Public API
public get uri(): vscode.Uri { return this.#uri; }
public get fileName(): string { return this.uri.fsPath; }
public get isUntitled() { return this.uri.scheme === Schemas.untitled; }
#onDidDispose = new Emitter<void>();
public readonly onDidDispose = this.#onDidDispose.event;
public get isClosed() { return this.#isDisposed; }
public get version() { return this.#version; }
public get isDirty() {
return this.#editState.currentIndex !== this.#editState.saveIndex;
}
public get appliedEdits() {
return this.#editState.allEdits.slice(0, this.#editState.currentIndex + 1)
.map(id => this._getEdit(id));
}
public get savedEdits() {
return this.#editState.allEdits.slice(0, this.#editState.saveIndex + 1)
.map(id => this._getEdit(id));
}
//#endregion
/** @internal */ _dispose(): void {
this.#isDisposed = true;
this.#onDidDispose.fire();
this.#onDidDispose.dispose();
}
/** @internal */ _updateEditState(state: EditState) {
++this.#version;
this.#editState = state;
}
/** @internal*/ _getEdit(editId: number): EditType {
return assertIsDefined(this.#edits.get(editId, 0));
}
/** @internal*/ _disposeEdits(editIds: number[]) {
for (const editId of editIds) {
this.#edits.delete(editId);
}
}
/** @internal*/ _addEdit(edit: EditType): number {
const id = this.#edits.add([edit]);
this._updateEditState({
allEdits: [...this.#editState.allEdits.slice(0, this.#editState.currentIndex + 1), id],
currentIndex: this.#editState.currentIndex + 1,
saveIndex: this.#editState.saveIndex,
});
return id;
}
}
// #endregion

View File

@@ -18,6 +18,7 @@ import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor';
import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview';
import type * as vscode from 'vscode';
import { Cache } from './cache';
import * as extHostProtocol from './extHost.protocol';
import * as extHostTypes from './extHostTypes';
@@ -262,22 +263,78 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa
}
}
class WebviewDocumentStore {
private readonly _documents = new Map<string, extHostTypes.CustomDocument>();
class CustomDocumentStoreEntry {
public get(viewType: string, resource: vscode.Uri): extHostTypes.CustomDocument | undefined {
constructor(
public readonly document: vscode.CustomDocument,
) { }
private readonly _edits = new Cache<vscode.CustomDocumentEditEvent>('custom documents');
private _backup?: vscode.CustomDocumentBackup;
addEdit(item: vscode.CustomDocumentEditEvent): number {
return this._edits.add([item]);
}
async undo(editId: number, isDirty: boolean): Promise<void> {
await this.getEdit(editId).undo();
if (!isDirty) {
this.disposeBackup();
}
}
async redo(editId: number, isDirty: boolean): Promise<void> {
await this.getEdit(editId).redo();
if (!isDirty) {
this.disposeBackup();
}
}
disposeEdits(editIds: number[]): void {
for (const id of editIds) {
this._edits.delete(id);
}
}
updateBackup(backup: vscode.CustomDocumentBackup): void {
this._backup?.dispose();
this._backup = backup;
}
disposeBackup(): void {
this._backup?.dispose();
this._backup = undefined;
}
private getEdit(editId: number): vscode.CustomDocumentEditEvent {
const edit = this._edits.get(editId, 0);
if (!edit) {
throw new Error('No edit found');
}
return edit;
}
}
class CustomDocumentStore {
private readonly _documents = new Map<string, CustomDocumentStoreEntry>();
public get(viewType: string, resource: vscode.Uri): CustomDocumentStoreEntry | undefined {
return this._documents.get(this.key(viewType, resource));
}
public add(viewType: string, document: extHostTypes.CustomDocument) {
public add(viewType: string, document: vscode.CustomDocument): CustomDocumentStoreEntry {
const key = this.key(viewType, document.uri);
if (this._documents.has(key)) {
throw new Error(`Document already exists for viewType:${viewType} resource:${document.uri}`);
}
this._documents.set(key, document);
const entry = new CustomDocumentStoreEntry(document);
this._documents.set(key, entry);
return entry;
}
public delete(viewType: string, document: extHostTypes.CustomDocument) {
public delete(viewType: string, document: vscode.CustomDocument) {
const key = this.key(viewType, document.uri);
this._documents.delete(key);
}
@@ -285,6 +342,7 @@ class WebviewDocumentStore {
private key(viewType: string, resource: vscode.Uri): string {
return `${viewType}@@@${resource}`;
}
}
const enum WebviewEditorType {
@@ -342,7 +400,7 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
private readonly _editorProviders = new EditorProviderStore();
private readonly _documents = new WebviewDocumentStore();
private readonly _documents = new CustomDocumentStore();
constructor(
mainContext: extHostProtocol.IMainContext,
@@ -399,24 +457,17 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
extension: IExtensionDescription,
viewType: string,
provider: vscode.CustomEditorProvider | vscode.CustomTextEditorProvider,
options: vscode.WebviewPanelOptions | undefined = {}
options: { webviewOptions?: vscode.WebviewPanelOptions, supportsMultipleEditorsPerResource?: boolean },
): vscode.Disposable {
const disposables = new DisposableStore();
if ('resolveCustomTextEditor' in provider) {
disposables.add(this._editorProviders.addTextProvider(viewType, extension, provider));
this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options, {
this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options.webviewOptions || {}, {
supportsMove: !!provider.moveCustomTextEditor,
});
} else {
disposables.add(this._editorProviders.addCustomProvider(viewType, extension, provider));
this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options);
if (provider.editingDelegate) {
disposables.add(provider.editingDelegate.onDidEdit(e => {
const document = e.document;
const editId = (document as extHostTypes.CustomDocument)._addEdit(e.edit);
this._proxy.$onDidEdit(document.uri, viewType, editId, e.label);
}));
}
this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options.webviewOptions || {}, !!options.supportsMultipleEditorsPerResource);
}
return extHostTypes.Disposable.from(
@@ -504,7 +555,7 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
await serializer.deserializeWebviewPanel(revivedPanel, state);
}
async $createWebviewCustomEditorDocument(resource: UriComponents, viewType: string, cancellation: CancellationToken) {
async $createCustomDocument(resource: UriComponents, viewType: string, backupId: string | undefined, cancellation: CancellationToken) {
const entry = this._editorProviders.get(viewType);
if (!entry) {
throw new Error(`No provider found for '${viewType}'`);
@@ -515,14 +566,24 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
}
const revivedResource = URI.revive(resource);
const document = await entry.provider.openCustomDocument(revivedResource, cancellation);
this._documents.add(viewType, document as extHostTypes.CustomDocument);
return {
editable: !!entry.provider.editingDelegate,
};
const document = await entry.provider.openCustomDocument(revivedResource, { backupId }, cancellation);
const documentEntry = this._documents.add(viewType, document);
if (this.isEditable(document)) {
document.onDidChange(e => {
if (isEditEvent(e)) {
const editId = documentEntry.addEdit(e);
this._proxy.$onDidEdit(document.uri, viewType, editId, e.label);
} else {
this._proxy.$onContentChange(document.uri, viewType);
}
});
}
return { editable: this.isEditable(document) };
}
async $disposeWebviewCustomEditorDocument(resource: UriComponents, viewType: string): Promise<void> {
async $disposeCustomDocument(resource: UriComponents, viewType: string): Promise<void> {
const entry = this._editorProviders.get(viewType);
if (!entry) {
throw new Error(`No provider found for '${viewType}'`);
@@ -533,9 +594,9 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
}
const revivedResource = URI.revive(resource);
const document = this.getCustomDocument(viewType, revivedResource);
const { document } = this.getCustomDocumentEntry(viewType, revivedResource);
this._documents.delete(viewType, document);
document._dispose();
document.dispose();
}
async $resolveWebviewEditor(
@@ -561,7 +622,7 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
switch (entry.type) {
case WebviewEditorType.Custom:
{
const document = this.getCustomDocument(viewType, revivedResource);
const { document } = this.getCustomDocumentEntry(viewType, revivedResource);
return entry.provider.resolveCustomEditor(document, revivedPanel, cancellation);
}
case WebviewEditorType.Text:
@@ -577,8 +638,8 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
}
$disposeEdits(resourceComponents: UriComponents, viewType: string, editIds: number[]): void {
const document = this.getCustomDocument(viewType, resourceComponents);
document._disposeEdits(editIds);
const document = this.getCustomDocumentEntry(viewType, resourceComponents);
document.disposeEdits(editIds);
}
async $onMoveCustomEditor(handle: string, newResourceComponents: UriComponents, viewType: string): Promise<void> {
@@ -601,69 +662,65 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
await (entry.provider as vscode.CustomTextEditorProvider).moveCustomTextEditor!(document, webview, CancellationToken.None);
}
async $undo(resourceComponents: UriComponents, viewType: string, editId: number, state: extHostProtocol.CustomDocumentEditState): Promise<void> {
const delegate = this.getEditingDelegate(viewType);
const document = this.getCustomDocument(viewType, resourceComponents);
document._updateEditState(state);
return delegate.undoEdits(document, [document._getEdit(editId)]);
async $undo(resourceComponents: UriComponents, viewType: string, editId: number, isDirty: boolean): Promise<void> {
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
return entry.undo(editId, isDirty);
}
async $redo(resourceComponents: UriComponents, viewType: string, editId: number, state: extHostProtocol.CustomDocumentEditState): Promise<void> {
const delegate = this.getEditingDelegate(viewType);
const document = this.getCustomDocument(viewType, resourceComponents);
document._updateEditState(state);
return delegate.applyEdits(document, [document._getEdit(editId)]);
async $redo(resourceComponents: UriComponents, viewType: string, editId: number, isDirty: boolean): Promise<void> {
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
return entry.redo(editId, isDirty);
}
async $revert(resourceComponents: UriComponents, viewType: string, changes: { undoneEdits: number[], redoneEdits: number[] }, state: extHostProtocol.CustomDocumentEditState): Promise<void> {
const delegate = this.getEditingDelegate(viewType);
const document = this.getCustomDocument(viewType, resourceComponents);
const undoneEdits = changes.undoneEdits.map(id => document._getEdit(id));
const appliedEdits = changes.redoneEdits.map(id => document._getEdit(id));
document._updateEditState(state);
return delegate.revert(document, { undoneEdits, appliedEdits });
async $revert(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise<void> {
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
const document = this.getEditableCustomDocument(viewType, resourceComponents);
await document.revert(cancellation);
entry.disposeBackup();
}
async $onSave(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise<void> {
const delegate = this.getEditingDelegate(viewType);
const document = this.getCustomDocument(viewType, resourceComponents);
return delegate.save(document, cancellation);
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
const document = this.getEditableCustomDocument(viewType, resourceComponents);
await document.save(cancellation);
entry.disposeBackup();
}
async $onSaveAs(resourceComponents: UriComponents, viewType: string, targetResource: UriComponents, cancellation: CancellationToken): Promise<void> {
const delegate = this.getEditingDelegate(viewType);
const document = this.getCustomDocument(viewType, resourceComponents);
return delegate.saveAs(document, URI.revive(targetResource), cancellation);
const document = this.getEditableCustomDocument(viewType, resourceComponents);
return document.saveAs(URI.revive(targetResource), cancellation);
}
async $backup(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise<void> {
const delegate = this.getEditingDelegate(viewType);
const document = this.getCustomDocument(viewType, resourceComponents);
return delegate.backup(document, cancellation);
async $backup(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise<string> {
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
const document = this.getEditableCustomDocument(viewType, resourceComponents);
const backup = await document.backup(cancellation);
entry.updateBackup(backup);
return backup.backupId;
}
private getWebviewPanel(handle: extHostProtocol.WebviewPanelHandle): ExtHostWebviewEditor | undefined {
return this._webviewPanels.get(handle);
}
private getCustomDocument(viewType: string, resource: UriComponents): extHostTypes.CustomDocument {
const document = this._documents.get(viewType, URI.revive(resource));
if (!document) {
throw new Error('No webview editor custom document found');
private getCustomDocumentEntry(viewType: string, resource: UriComponents): CustomDocumentStoreEntry {
const entry = this._documents.get(viewType, URI.revive(resource));
if (!entry) {
throw new Error('No custom document found');
}
return document;
return entry;
}
private getEditingDelegate(viewType: string): vscode.CustomEditorEditingDelegate {
const entry = this._editorProviders.get(viewType);
if (!entry) {
throw new Error(`No provider found for '${viewType}'`);
private isEditable(document: vscode.CustomDocument): document is vscode.EditableCustomDocument {
return !!(document as vscode.EditableCustomDocument).onDidChange;
}
private getEditableCustomDocument(viewType: string, resource: UriComponents): vscode.EditableCustomDocument {
const { document } = this.getCustomDocumentEntry(viewType, resource);
if (!this.isEditable(document)) {
throw new Error('Custom document is not editable');
}
const delegate = (entry.provider as vscode.CustomEditorProvider).editingDelegate;
if (!delegate) {
throw new Error(`Provider for ${viewType}' does not support editing`);
}
return delegate;
return document;
}
}
@@ -691,3 +748,8 @@ function getDefaultLocalResourceRoots(
extension.extensionLocation,
];
}
function isEditEvent(e: vscode.CustomDocumentContentChangeEvent | vscode.CustomDocumentEditEvent): e is vscode.CustomDocumentEditEvent {
return typeof (e as vscode.CustomDocumentEditEvent).undo === 'function'
&& typeof (e as vscode.CustomDocumentEditEvent).redo === 'function';
}

View File

@@ -116,7 +116,7 @@ class ExtHostWorkspaceImpl extends Workspace {
}
private readonly _workspaceFolders: vscode.WorkspaceFolder[] = [];
private readonly _structure = TernarySearchTree.forPaths<vscode.WorkspaceFolder>();
private readonly _structure = TernarySearchTree.forUris<vscode.WorkspaceFolder>();
constructor(id: string, private _name: string, folders: vscode.WorkspaceFolder[], configuration: URI | null, private _isUntitled: boolean) {
super(id, folders.map(f => new WorkspaceFolder(f)), configuration);
@@ -124,7 +124,7 @@ class ExtHostWorkspaceImpl extends Workspace {
// setup the workspace folder data structure
folders.forEach(folder => {
this._workspaceFolders.push(folder);
this._structure.set(folder.uri.toString(), folder);
this._structure.set(folder.uri, folder);
});
}
@@ -141,15 +141,15 @@ class ExtHostWorkspaceImpl extends Workspace {
}
getWorkspaceFolder(uri: URI, resolveParent?: boolean): vscode.WorkspaceFolder | undefined {
if (resolveParent && this._structure.get(uri.toString())) {
if (resolveParent && this._structure.get(uri)) {
// `uri` is a workspace folder so we check for its parent
uri = dirname(uri);
}
return this._structure.findSubstr(uri.toString());
return this._structure.findSubstr(uri);
}
resolveWorkspaceFolder(uri: URI): vscode.WorkspaceFolder | undefined {
return this._structure.get(uri.toString());
return this._structure.get(uri);
}
}