Merge from vscode 2cfc8172e533e50c90e6a3152f6bfb1f82f963f3 (#6516)

* Merge from vscode 2cfc8172e533e50c90e6a3152f6bfb1f82f963f3

* fix tests
This commit is contained in:
Anthony Dresser
2019-07-28 15:15:24 -07:00
committed by GitHub
parent aacf1e7f1c
commit 1d56a17f32
292 changed files with 19784 additions and 1873 deletions

View File

@@ -44,6 +44,7 @@ import { ExtensionDependencyChecker } from 'vs/workbench/contrib/extensions/brow
import { CancellationToken } from 'vs/base/common/cancellation';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { RemoteExtensionsInstaller } from 'vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller';
// Singletons
registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService);
@@ -188,12 +189,16 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
description: localize('extensionsCloseExtensionDetailsOnViewChange', "When enabled, editors with extension details will be automatically closed upon navigating away from the Extensions View."),
default: false
},
// {{SQL CARBON EDIT}}
'extensions.extensionsPolicy': {
'extensions.extensionsPolicy': { // {{SQL CARBON EDIT}}
type: 'string',
description: localize('extensionsPolicy', "Sets the security policy for downloading extensions."),
scope: ConfigurationScope.APPLICATION,
default: ExtensionsPolicy.allowAll
},
'extensions.confirmedUriHandlerExtensionIds': {
type: 'array',
description: localize('handleUriConfirmedExtensions', "When an extension is listed here, a confirmation prompt will not be shown when that extension handles a URI."),
default: []
}
}
});
@@ -379,3 +384,4 @@ workbenchRegistry.registerWorkbenchContribution(KeymapExtensions, LifecyclePhase
workbenchRegistry.registerWorkbenchContribution(ExtensionsViewletViewsContribution, LifecyclePhase.Starting);
workbenchRegistry.registerWorkbenchContribution(ExtensionActivationProgress, LifecyclePhase.Eventually);
workbenchRegistry.registerWorkbenchContribution(ExtensionDependencyChecker, LifecyclePhase.Eventually);
workbenchRegistry.registerWorkbenchContribution(RemoteExtensionsInstaller, LifecyclePhase.Eventually);

View File

@@ -62,6 +62,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile
import { IProductService } from 'vs/platform/product/common/product';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; // {{SQL CARBON EDIT}}
@@ -287,7 +288,7 @@ export class InstallAction extends ExtensionAction {
}
}
export class InstallInOtherServerAction extends ExtensionAction {
export abstract class InstallInOtherServerAction extends ExtensionAction {
protected static INSTALL_LABEL = localize('install', "Install");
protected static INSTALLING_LABEL = localize('installing', "Installing");
@@ -296,7 +297,6 @@ export class InstallInOtherServerAction extends ExtensionAction {
private static readonly InstallingClass = 'extension-action install installing';
updateWhenCounterExtensionChanges: boolean = true;
protected installing: boolean = false;
constructor(
id: string,
@@ -304,70 +304,63 @@ export class InstallInOtherServerAction extends ExtensionAction {
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
) {
super(id, InstallInOtherServerAction.INSTALL_LABEL, InstallInOtherServerAction.Class, false);
this.updateLabel();
this.update();
}
private updateLabel(): void {
this.label = this.getLabel();
this.tooltip = this.label;
}
protected getLabel(): string {
return this.installing ? InstallInOtherServerAction.INSTALLING_LABEL :
this.server ? `${InstallInOtherServerAction.INSTALL_LABEL} on ${this.server.label}`
: InstallInOtherServerAction.INSTALL_LABEL;
}
update(): void {
this.enabled = false;
this.class = InstallInOtherServerAction.Class;
if (this.installing) {
this.enabled = true;
this.class = InstallInOtherServerAction.InstallingClass;
this.updateLabel();
return;
}
if (
this.extension && this.extension.local && this.server && this.extension.state === ExtensionState.Installed
this.extension && this.extension.local && this.server && this.extension.state === ExtensionState.Installed && this.extension.type === ExtensionType.User
// disabled by extension kind or it is a language pack extension
&& (this.extension.enablementState === EnablementState.DisabledByExtensionKind || isLanguagePackExtension(this.extension.local.manifest))
// Not installed in other server and can install in other server
&& !this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.server)
&& this.extensionsWorkbenchService.canInstall(this.extension)
) {
this.enabled = true;
this.updateLabel();
return;
const extensionInOtherServer = this.extensionsWorkbenchService.installed.filter(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.server)[0];
if (extensionInOtherServer) {
// Getting installed in other server
if (extensionInOtherServer.state === ExtensionState.Installing && !extensionInOtherServer.local) {
this.enabled = true;
this.label = InstallInOtherServerAction.INSTALLING_LABEL;
this.class = InstallInOtherServerAction.InstallingClass;
}
} else {
// Not installed in other server
this.enabled = true;
this.label = this.getInstallLabel();
}
}
}
async run(): Promise<void> {
if (this.server && !this.installing) {
this.installing = true;
this.update();
if (this.server) {
this.extensionsWorkbenchService.open(this.extension);
alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName));
if (this.extension.gallery) {
await this.server.extensionManagementService.installFromGallery(this.extension.gallery);
this.installing = false;
this.update();
} else {
const vsix = await this.extension.server!.extensionManagementService.zip(this.extension.local!);
await this.server.extensionManagementService.install(vsix);
}
}
}
protected abstract getInstallLabel(): string;
}
export class RemoteInstallAction extends InstallInOtherServerAction {
constructor(
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService
) {
super(`extensions.remoteinstall`, extensionManagementServerService.remoteExtensionManagementServer, extensionsWorkbenchService);
}
protected getInstallLabel(): string {
return this.extensionManagementServerService.remoteExtensionManagementServer ? localize('Install on Server', "Install on {0}", this.extensionManagementServerService.remoteExtensionManagementServer.label) : InstallInOtherServerAction.INSTALL_LABEL;
}
}
export class LocalInstallAction extends InstallInOtherServerAction {
@@ -379,8 +372,8 @@ export class LocalInstallAction extends InstallInOtherServerAction {
super(`extensions.localinstall`, extensionManagementServerService.localExtensionManagementServer, extensionsWorkbenchService);
}
protected getLabel(): string {
return this.installing ? InstallInOtherServerAction.INSTALLING_LABEL : localize('install locally', "Install Locally");
protected getInstallLabel(): string {
return localize('install locally', "Install Locally");
}
}
@@ -1220,7 +1213,7 @@ export class ReloadAction extends ExtensionAction {
const isSameExtensionRunning = runningExtension && this.extension.server === this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation);
if (isUninstalled) {
if (isSameExtensionRunning) {
if (isSameExtensionRunning && !this.extensionService.canRemoveExtension(runningExtension)) {
this.enabled = true;
this.label = localize('reloadRequired', "Reload Required");
// {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio
@@ -1725,7 +1718,7 @@ export class InstallRecommendedExtensionAction extends Action {
return this.viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => {
viewlet.search('@recommended ');
viewlet.search(`@id:${this.extensionId}`);
viewlet.focus();
return this.extensionWorkbenchService.queryGallery({ names: [this.extensionId], source: 'install-recommendation', pageSize: 1 }, CancellationToken.None)
.then(pager => {
@@ -3092,6 +3085,119 @@ export class InstallSpecificVersionOfExtensionAction extends Action {
}
}
interface IExtensionPickItem extends IQuickPickItem {
extension?: IExtension;
}
export class InstallLocalExtensionsOnRemoteAction extends Action {
constructor(
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@INotificationService private readonly notificationService: INotificationService,
@IWindowService private readonly windowService: IWindowService,
@IProgressService private readonly progressService: IProgressService,
@IInstantiationService private readonly instantiationService: IInstantiationService
) {
super('workbench.extensions.actions.installLocalExtensionsOnRemote');
this.update();
this._register(this.extensionsWorkbenchService.onChange(() => this.update()));
}
get label(): string {
return this.extensionManagementServerService.remoteExtensionManagementServer ?
localize('install local extensions', "Install Local Extensions on {0}", this.extensionManagementServerService.remoteExtensionManagementServer.label) : '';
}
private update(): void {
this.enabled = this.getLocalExtensionsToInstall().length > 0;
}
private getLocalExtensionsToInstall(): IExtension[] {
return this.extensionsWorkbenchService.local.filter(extension => {
const action = this.instantiationService.createInstance(RemoteInstallAction);
action.extension = extension;
return action.enabled;
});
}
async run(): Promise<void> {
this.selectAndInstallLocalExtensions();
return Promise.resolve();
}
private selectAndInstallLocalExtensions(): void {
const quickPick = this.quickInputService.createQuickPick<IExtensionPickItem>();
quickPick.busy = true;
const disposable = quickPick.onDidAccept(() => {
disposable.dispose();
quickPick.hide();
quickPick.dispose();
this.onDidAccept(quickPick.selectedItems);
});
quickPick.show();
const localExtensionsToInstall = this.getLocalExtensionsToInstall();
quickPick.busy = false;
if (localExtensionsToInstall.length) {
quickPick.placeholder = localize('select extensions to install', "Select extensions to install");
quickPick.canSelectMany = true;
localExtensionsToInstall.sort((e1, e2) => e1.displayName.localeCompare(e2.displayName));
quickPick.items = localExtensionsToInstall.map<IExtensionPickItem>(extension => ({ extension, label: extension.displayName, description: extension.version }));
} else {
quickPick.hide();
quickPick.dispose();
this.notificationService.notify({
severity: Severity.Info,
message: localize('no local extensions', "There are no extensions to install.")
});
}
}
private onDidAccept(selectedItems: ReadonlyArray<IExtensionPickItem>): void {
if (selectedItems.length) {
const localExtensionsToInstall = selectedItems.filter(r => !!r.extension).map(r => r.extension!);
if (localExtensionsToInstall.length) {
this.progressService.withProgress(
{
location: ProgressLocation.Notification,
title: localize('installing extensions', "Installing Extensions...")
},
() => this.installLocalExtensions(localExtensionsToInstall));
}
}
}
private async installLocalExtensions(localExtensionsToInstall: IExtension[]): Promise<void> {
const galleryExtensions: IGalleryExtension[] = [];
const vsixs: URI[] = [];
await Promise.all(localExtensionsToInstall.map(async extension => {
if (this.extensionGalleryService.isEnabled()) {
const gallery = await this.extensionGalleryService.getCompatibleExtension(extension.identifier, extension.version);
if (gallery) {
galleryExtensions.push(gallery);
return;
}
}
const vsix = await this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.zip(extension.local!);
vsixs.push(vsix);
}));
await Promise.all(galleryExtensions.map(gallery => this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.installFromGallery(gallery)));
await Promise.all(vsixs.map(vsix => this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.install(vsix)));
this.notificationService.notify({
severity: Severity.Info,
message: localize('finished installing', "Completed installing the extensions. Please reload the window now."),
actions: {
primary: [new Action('realod', localize('reload', "Realod Window"), '', true,
() => this.windowService.reloadWindow())]
}
});
}
}
CommandsRegistry.registerCommand('workbench.extensions.action.showExtensionsForLanguage', function (accessor: ServicesAccessor, fileExtension: string) {
const viewletService = accessor.get(IViewletService);

View File

@@ -18,11 +18,11 @@ import { append, $, addClass, toggleClass, Dimension } from 'vs/base/browser/dom
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, ExtensionState, AutoUpdateConfigurationKey, ShowRecommendationsOnlyOnDemandKey, CloseExtensionDetailsOnViewChangeKey, VIEW_CONTAINER } from '../common/extensions';
import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, AutoUpdateConfigurationKey, ShowRecommendationsOnlyOnDemandKey, CloseExtensionDetailsOnViewChangeKey, VIEW_CONTAINER } from '../common/extensions';
import {
ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowDisabledExtensionsAction,
ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction,
EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction
EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction, InstallLocalExtensionsOnRemoteAction
} from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
@@ -32,7 +32,7 @@ import { OpenGlobalSettingsAction } from 'vs/workbench/contrib/preferences/brows
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import Severity from 'vs/base/common/severity';
import { IActivityService, ProgressBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity';
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IViewsRegistry, IViewDescriptor, Extensions } from 'vs/workbench/common/views';
@@ -55,7 +55,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { Registry } from 'vs/platform/registry/common/platform';
import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { RemoteAuthorityContext } from 'vs/workbench/browser/contextkeys';
import { RemoteNameContext } from 'vs/workbench/browser/contextkeys';
import { ILabelService } from 'vs/platform/label/common/label';
import { MementoObject } from 'vs/workbench/common/memento';
@@ -146,7 +146,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
id,
name: viewIdNameMappings[id],
ctorDescriptor: { ctor: EnabledExtensionsView },
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteAuthorityContext.isEqualTo('')),
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteNameContext.isEqualTo('')),
weight: 40,
canToggleVisibility: true,
order: 1
@@ -161,7 +161,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
id,
name: viewIdNameMappings[id],
ctorDescriptor: { ctor: DisabledExtensionsView },
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteAuthorityContext.isEqualTo('')),
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteNameContext.isEqualTo('')),
weight: 10,
canToggleVisibility: true,
order: 3,
@@ -212,7 +212,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
id: `extensions.${server.authority}.default`,
get name() { return getInstalledViewName(); },
ctorDescriptor: { ctor: ServerExtensionsView, arguments: [server, EventOf.map<void, string>(onDidChangeServerLabel, () => getInstalledViewName())] },
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteAuthorityContext.notEqualsTo('')),
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteNameContext.notEqualsTo('')),
weight: 40,
order: 1
}];
@@ -352,6 +352,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
@IInstantiationService instantiationService: IInstantiationService,
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
@INotificationService private readonly notificationService: INotificationService,
@IViewletService private readonly viewletService: IViewletService,
@IThemeService themeService: IThemeService,
@@ -483,6 +484,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
this.instantiationService.createInstance(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL),
...(this.configurationService.getValue(AutoUpdateConfigurationKey) ? [this.instantiationService.createInstance(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL)] : [this.instantiationService.createInstance(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL), this.instantiationService.createInstance(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL)]),
this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL),
...(this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer ? [this.instantiationService.createInstance(InstallLocalExtensionsOnRemoteAction)] : []),
new Separator(),
this.instantiationService.createInstance(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL),
this.instantiationService.createInstance(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL)
@@ -635,11 +637,6 @@ export class StatusUpdater extends Disposable implements IWorkbenchContribution
private onServiceChange(): void {
this.badgeHandle.clear();
if (this.extensionsWorkbenchService.local.some(e => e.state === ExtensionState.Installing)) {
this.badgeHandle.value = this.activityService.showActivity(VIEWLET_ID, new ProgressBadge(() => localize('extensions', "Extensions")), 'extensions-badge progress-badge');
return;
}
const outdated = this.extensionsWorkbenchService.outdated.reduce((r, e) => r + (this.extensionEnablementService.isEnabled(e.local!) ? 1 : 0), 0);
if (outdated > 0) {
const badge = new NumberBadge(outdated, n => localize('outdatedExtensions', '{0} Outdated Extensions', n));

View File

@@ -555,6 +555,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
this.resetIgnoreAutoUpdateExtensions();
this.eventuallySyncWithGallery(true);
});
this._register(this.onChange(() => this.updateActivity()));
}
get local(): IExtension[] {
@@ -1082,6 +1084,21 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
return this._extensionAllowedBadgeProviders;
}
private _activityCallBack: (() => void) | null = null;
private updateActivity(): void {
if ((this.localExtensions && this.localExtensions.local.some(e => e.state === ExtensionState.Installing || e.state === ExtensionState.Uninstalling))
|| (this.remoteExtensions && this.remoteExtensions.local.some(e => e.state === ExtensionState.Installing || e.state === ExtensionState.Uninstalling))) {
if (!this._activityCallBack) {
this.progressService.withProgress({ location: ProgressLocation.Extensions }, () => new Promise(c => this._activityCallBack = c));
}
} else {
if (this._activityCallBack) {
this._activityCallBack();
}
this._activityCallBack = null;
}
}
private onError(err: any): void {
if (isPromiseCanceledError(err)) {
return;

View File

@@ -0,0 +1,44 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { localize } from 'vs/nls';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ILabelService } from 'vs/platform/label/common/label';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { InstallLocalExtensionsOnRemoteAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
export class RemoteExtensionsInstaller extends Disposable implements IWorkbenchContribution {
constructor(
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
@ILabelService labelService: ILabelService,
@IInstantiationService instantiationService: IInstantiationService
) {
super();
if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) {
const installLocalExtensionsOnRemoteAction = instantiationService.createInstance(InstallLocalExtensionsOnRemoteAction);
CommandsRegistry.registerCommand('workbench.extensions.installLocalExtensions', () => installLocalExtensionsOnRemoteAction.run());
let disposable = Disposable.None;
const appendMenuItem = () => {
disposable.dispose();
disposable = MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: 'workbench.extensions.installLocalExtensions',
category: localize('extensions', "Extensions"),
title: installLocalExtensionsOnRemoteAction.label
}
});
};
appendMenuItem();
this._register(labelService.onDidChangeFormatters(e => appendMenuItem()));
this._register(toDisposable(() => disposable.dispose()));
}
}
}