mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-23 01:58:53 +01:00
User data sync: Implement starting flow
- user identity - user login provider - user data provider - activation event - status bar UI
This commit is contained in:
@@ -18,6 +18,7 @@ import './mainThreadCodeInsets';
|
||||
import './mainThreadClipboard';
|
||||
import './mainThreadCommands';
|
||||
import './mainThreadConfiguration';
|
||||
import './mainThreadUserData';
|
||||
import './mainThreadConsole';
|
||||
import './mainThreadDebugService';
|
||||
import './mainThreadDecorations';
|
||||
|
||||
79
src/vs/workbench/api/browser/mainThreadUserData.ts
Normal file
79
src/vs/workbench/api/browser/mainThreadUserData.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { MainContext, ExtHostContext, IExtHostContext, MainThreadUserDataShape, ExtHostUserDataShape } from '../common/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { IUserDataProviderService, IUserIdentityService, IUserDataProvider, IUserLoginProvider } from 'vs/workbench/services/userData/common/userData';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadUserData)
|
||||
export class MainThreadUserData extends Disposable implements MainThreadUserDataShape {
|
||||
|
||||
private readonly proxy: ExtHostUserDataShape;
|
||||
private readonly loginProviders: Map<string, UserLoginProvider> = new Map<string, UserLoginProvider>();
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IUserIdentityService private readonly userIdentityService: IUserIdentityService,
|
||||
@IUserDataProviderService private readonly userDataProviderService: IUserDataProviderService,
|
||||
) {
|
||||
super();
|
||||
this.proxy = extHostContext.getProxy(ExtHostContext.ExtHostUserData);
|
||||
this._register(toDisposable(() => {
|
||||
this.userDataProviderService.deregisterAll();
|
||||
this.loginProviders.forEach((loginProvider, identity) => this.userIdentityService.deregisterUserLoginProvider(identity));
|
||||
this.loginProviders.clear();
|
||||
}));
|
||||
}
|
||||
|
||||
$registerUserLoginProvider(identity: string, loggedIn: boolean): void {
|
||||
const userLoginProvider = new UserLoginProvider(identity, loggedIn, this.proxy);
|
||||
this.loginProviders.set(identity, userLoginProvider);
|
||||
this.userIdentityService.registerUserLoginProvider(identity, userLoginProvider);
|
||||
}
|
||||
|
||||
$registerUserDataProvider(identity: string, userDataProvider: IUserDataProvider): void {
|
||||
this.userDataProviderService.registerUserDataProvider(identity, userDataProvider);
|
||||
}
|
||||
|
||||
$updateLoggedIn(identity: string, loggedIn: boolean): void {
|
||||
const loginProvider = this.loginProviders.get(identity);
|
||||
if (loginProvider) {
|
||||
loginProvider.loggedIn = loggedIn;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class UserLoginProvider extends Disposable implements IUserLoginProvider {
|
||||
|
||||
private _onDidChange: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onDidChange: Event<void> = this._onDidChange.event;
|
||||
|
||||
private _loggedIn: boolean;
|
||||
get loggedIn(): boolean { return this._loggedIn; }
|
||||
set loggedIn(loggedIn: boolean) {
|
||||
if (this._loggedIn !== loggedIn) {
|
||||
this._loggedIn = loggedIn;
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private readonly identity: string, loggedIn: boolean, private readonly proxy: ExtHostUserDataShape) {
|
||||
super();
|
||||
this._loggedIn = loggedIn;
|
||||
}
|
||||
|
||||
login(): Promise<void> {
|
||||
return this.proxy.$logIn(this.identity);
|
||||
}
|
||||
|
||||
logout(): Promise<void> {
|
||||
return this.proxy.$logOut(this.identity);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -68,6 +68,7 @@ import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
|
||||
import { ExtHostUserData } from 'vs/workbench/api/common/extHostUserData';
|
||||
|
||||
export interface IExtensionApiFactory {
|
||||
(extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode;
|
||||
@@ -132,6 +133,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol));
|
||||
const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress)));
|
||||
const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHosLabelService, new ExtHostLabelService(rpcProtocol));
|
||||
const extHostUserData = rpcProtocol.set(ExtHostContext.ExtHostUserData, new ExtHostUserData(rpcProtocol.getProxy(MainContext.MainThreadUserData), extHostFileSystem));
|
||||
|
||||
// Check that no named customers are missing
|
||||
const expected: ProxyIdentifier<any>[] = values(ExtHostContext);
|
||||
@@ -541,7 +543,13 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
},
|
||||
createInputBox(): vscode.InputBox {
|
||||
return extHostQuickOpen.createInputBox(extension.identifier);
|
||||
}
|
||||
},
|
||||
registerUserDataProvider: proposedApiFunction(extension, (identity: string, userDataProvider: vscode.UserDataProvider): vscode.Disposable => {
|
||||
return extHostUserData.registerUserDataProvider(identity, userDataProvider);
|
||||
}),
|
||||
registerUserLoginProvider: proposedApiFunction(extension, (identity: string, userLoginProvider: vscode.UserLoginProvider): vscode.Disposable => {
|
||||
return extHostUserData.registerUserLoginProvider(identity, userLoginProvider);
|
||||
})
|
||||
};
|
||||
|
||||
// namespace: workspace
|
||||
|
||||
@@ -47,6 +47,7 @@ import { ExtensionActivationError } from 'vs/workbench/services/extensions/commo
|
||||
import { createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId, IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier';
|
||||
import * as search from 'vs/workbench/services/search/common/search';
|
||||
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IUserDataProvider } from 'vs/workbench/services/userData/common/userData';
|
||||
|
||||
export interface IEnvironment {
|
||||
isExtensionDevelopmentDebug: boolean;
|
||||
@@ -140,6 +141,12 @@ export interface MainThreadConfigurationShape extends IDisposable {
|
||||
$removeConfigurationOption(target: ConfigurationTarget | null, key: string, resource: UriComponents | undefined): Promise<void>;
|
||||
}
|
||||
|
||||
export interface MainThreadUserDataShape extends IDisposable {
|
||||
$registerUserLoginProvider(identitiy: string, loggedIn: boolean): void;
|
||||
$updateLoggedIn(identitiy: string, loggedIn: boolean): void;
|
||||
$registerUserDataProvider(identitiy: string, userDataProvider: IUserDataProvider): void;
|
||||
}
|
||||
|
||||
export interface MainThreadDiagnosticsShape extends IDisposable {
|
||||
$changeMany(owner: string, entries: [UriComponents, IMarkerData[] | undefined][]): void;
|
||||
$clear(owner: string): void;
|
||||
@@ -743,6 +750,11 @@ export interface ExtHostConfigurationShape {
|
||||
$acceptConfigurationChanged(data: IConfigurationInitData, eventData: IWorkspaceConfigurationChangeEventData): void;
|
||||
}
|
||||
|
||||
export interface ExtHostUserDataShape {
|
||||
$logIn(identity: string): Promise<void>;
|
||||
$logOut(identity: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ExtHostDiagnosticsShape {
|
||||
$acceptMarkersChange(data: [UriComponents, IMarkerData[]][]): void;
|
||||
}
|
||||
@@ -1316,6 +1328,7 @@ export const MainContext = {
|
||||
MainThreadCommands: createMainId<MainThreadCommandsShape>('MainThreadCommands'),
|
||||
MainThreadComments: createMainId<MainThreadCommentsShape>('MainThreadComments'),
|
||||
MainThreadConfiguration: createMainId<MainThreadConfigurationShape>('MainThreadConfiguration'),
|
||||
MainThreadUserData: createMainId<MainThreadUserDataShape>('MainThreadUserData'),
|
||||
MainThreadConsole: createMainId<MainThreadConsoleShape>('MainThreadConsole'),
|
||||
MainThreadDebugService: createMainId<MainThreadDebugServiceShape>('MainThreadDebugService'),
|
||||
MainThreadDecorations: createMainId<MainThreadDecorationsShape>('MainThreadDecorations'),
|
||||
@@ -1355,6 +1368,7 @@ export const MainContext = {
|
||||
export const ExtHostContext = {
|
||||
ExtHostCommands: createExtId<ExtHostCommandsShape>('ExtHostCommands'),
|
||||
ExtHostConfiguration: createExtId<ExtHostConfigurationShape>('ExtHostConfiguration'),
|
||||
ExtHostUserData: createExtId<ExtHostUserDataShape>('ExtHostUserData'),
|
||||
ExtHostDiagnostics: createExtId<ExtHostDiagnosticsShape>('ExtHostDiagnostics'),
|
||||
ExtHostDebugService: createExtId<ExtHostDebugServiceShape>('ExtHostDebugService'),
|
||||
ExtHostDecorations: createExtId<ExtHostDecorationsShape>('ExtHostDecorations'),
|
||||
|
||||
53
src/vs/workbench/api/common/extHostUserData.ts
Normal file
53
src/vs/workbench/api/common/extHostUserData.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExtHostUserDataShape, MainThreadUserDataShape } from './extHost.protocol';
|
||||
import { ExtHostFileSystem } from 'vs/workbench/api/common/extHostFileSystem';
|
||||
import * as vscode from 'vscode';
|
||||
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export class ExtHostUserData implements ExtHostUserDataShape {
|
||||
|
||||
private readonly loginProviders: Map<string, vscode.UserLoginProvider> = new Map<string, vscode.UserLoginProvider>();
|
||||
|
||||
constructor(
|
||||
private readonly proxy: MainThreadUserDataShape,
|
||||
private readonly extHostFileSystem: ExtHostFileSystem
|
||||
) {
|
||||
}
|
||||
|
||||
registerUserDataProvider(identity: string, userDataProvider: vscode.UserDataProvider): vscode.Disposable {
|
||||
const userDataScheme = `vscode-userdata-${identity}`;
|
||||
const disposable = this.extHostFileSystem.registerFileSystemProvider(userDataScheme, userDataProvider.dataProvider);
|
||||
this.proxy.$registerUserDataProvider(identity, { userDataScheme });
|
||||
return disposable;
|
||||
}
|
||||
|
||||
registerUserLoginProvider(identity: string, loginProvider: vscode.UserLoginProvider): vscode.Disposable {
|
||||
this.loginProviders.set(identity, loginProvider);
|
||||
this.proxy.$registerUserLoginProvider(identity, loginProvider.isLoggedin());
|
||||
const disposable = new DisposableStore();
|
||||
disposable.add(loginProvider.onDidChange(() => this.proxy.$updateLoggedIn(identity, loginProvider.isLoggedin())));
|
||||
disposable.add(toDisposable(() => this.loginProviders.delete(identity)));
|
||||
return disposable;
|
||||
}
|
||||
|
||||
async $logIn(identity: string): Promise<void> {
|
||||
const loginProvider = this.loginProviders.get(identity);
|
||||
if (!loginProvider) {
|
||||
return Promise.reject(new Error(`No login provider found for ${identity}`));
|
||||
}
|
||||
await loginProvider.login();
|
||||
}
|
||||
|
||||
$logOut(identity: string): Promise<void> {
|
||||
const loginProvider = this.loginProviders.get(identity);
|
||||
if (!loginProvider) {
|
||||
return Promise.reject(new Error(`No login provider found for ${identity}`));
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
}
|
||||
113
src/vs/workbench/api/common/userDataExtensionPoint.ts
Normal file
113
src/vs/workbench/api/common/userDataExtensionPoint.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IUserIdentityService, IUserIdentity } from 'vs/workbench/services/userData/common/userData';
|
||||
|
||||
export interface IUserFriendlyUserIdentityDescriptor {
|
||||
id: string;
|
||||
title: string;
|
||||
iconText: string;
|
||||
}
|
||||
|
||||
export const userIdentityContribution: IJSONSchema = {
|
||||
description: localize('vscode.extension.contributes.userIdentity', 'Contributes user identity to the editor'),
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
description: localize({ key: 'vscode.extension.contributes.user.identity.id', comment: ['Contribution refers to those that an extension contributes to VS Code through an extension/contribution point. '] }, "Unique id to identify the user user identity"),
|
||||
type: 'string',
|
||||
pattern: '^[a-zA-Z0-9_-]+$'
|
||||
},
|
||||
title: {
|
||||
description: localize('vscode.extension.contributes.views.containers.title', 'Human readable string used to render the user identity'),
|
||||
type: 'string'
|
||||
},
|
||||
iconText: {
|
||||
description: localize('vscode.extension.contributes.views.containers.icon', "Path to the user identity icon."),
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const viewsContainersExtensionPoint: IExtensionPoint<IUserFriendlyUserIdentityDescriptor> = ExtensionsRegistry.registerExtensionPoint<IUserFriendlyUserIdentityDescriptor>({
|
||||
extensionPoint: 'userIdentity',
|
||||
jsonSchema: userIdentityContribution
|
||||
});
|
||||
|
||||
class UserIdentityExtensionHandler implements IWorkbenchContribution {
|
||||
|
||||
constructor(
|
||||
@IUserIdentityService private readonly userIdentityService: IUserIdentityService
|
||||
) {
|
||||
this.handleAndRegisterUserIdentities();
|
||||
}
|
||||
|
||||
private handleAndRegisterUserIdentities() {
|
||||
viewsContainersExtensionPoint.setHandler((extensions, { added, removed }) => {
|
||||
if (removed.length) {
|
||||
this.removeUserIdentities(removed);
|
||||
}
|
||||
if (added.length) {
|
||||
this.addUserIdentities(added);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private addUserIdentities(extensionPoints: readonly IExtensionPointUser<IUserFriendlyUserIdentityDescriptor>[]) {
|
||||
const userIdentities: IUserIdentity[] = [];
|
||||
for (let { value, collector } of extensionPoints) {
|
||||
if (!this.isValidUserIdentity(value, collector)) {
|
||||
return;
|
||||
}
|
||||
userIdentities.push({
|
||||
identity: value.id,
|
||||
title: value.title,
|
||||
iconText: `$(${value.iconText})`
|
||||
});
|
||||
}
|
||||
if (userIdentities.length) {
|
||||
this.userIdentityService.registerUserIdentities(userIdentities);
|
||||
}
|
||||
}
|
||||
|
||||
private removeUserIdentities(extensionPoints: readonly IExtensionPointUser<IUserFriendlyUserIdentityDescriptor>[]) {
|
||||
const identities = extensionPoints.map(({ value }) => value.id);
|
||||
if (identities.length) {
|
||||
this.userIdentityService.deregisterUserIdentities(identities);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private isValidUserIdentity(userIdentityDescriptor: IUserFriendlyUserIdentityDescriptor, collector: ExtensionMessageCollector): boolean {
|
||||
if (typeof userIdentityDescriptor.id !== 'string') {
|
||||
collector.error(localize('requireidstring', "property `{0}` is mandatory and must be of type `string`. Only alphanumeric characters, '_', and '-' are allowed.", 'id'));
|
||||
return false;
|
||||
}
|
||||
if (!(/^[a-z0-9_-]+$/i.test(userIdentityDescriptor.id))) {
|
||||
collector.error(localize('requireidstring', "property `{0}` is mandatory and must be of type `string`. Only alphanumeric characters, '_', and '-' are allowed.", 'id'));
|
||||
return false;
|
||||
}
|
||||
if (typeof userIdentityDescriptor.title !== 'string') {
|
||||
collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'title'));
|
||||
return false;
|
||||
}
|
||||
if (typeof userIdentityDescriptor.iconText !== 'string') {
|
||||
collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'icon'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
|
||||
workbenchRegistry.registerWorkbenchContribution(UserIdentityExtensionHandler, LifecyclePhase.Starting);
|
||||
Reference in New Issue
Block a user