shell.ts 19.7 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7 8 9
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

import 'vs/css!./media/shell';

10
import * as nls from 'vs/nls';
11
import {TPromise} from 'vs/base/common/winjs.base';
J
Joao Moreno 已提交
12
import * as platform from 'vs/base/common/platform';
E
Erich Gamma 已提交
13 14
import {Dimension, Builder, $} from 'vs/base/browser/builder';
import dom = require('vs/base/browser/dom');
15
import aria = require('vs/base/browser/ui/aria/aria');
16
import {dispose, IDisposable, Disposables} from 'vs/base/common/lifecycle';
E
Erich Gamma 已提交
17
import errors = require('vs/base/common/errors');
18
import {toErrorMessage} from 'vs/base/common/errorMessage';
19 20
import product from 'vs/platform/product';
import pkg from 'vs/platform/package';
E
Erich Gamma 已提交
21 22
import {ContextViewService} from 'vs/platform/contextview/browser/contextViewService';
import timer = require('vs/base/common/timer');
23
import {Workbench} from 'vs/workbench/electron-browser/workbench';
24
import {Storage, inMemoryLocalStorageInstance} from 'vs/workbench/common/storage';
25
import {ITelemetryService, NullTelemetryService, loadExperiments} from 'vs/platform/telemetry/common/telemetry';
26
import {ITelemetryAppenderChannel, TelemetryAppenderClient} from 'vs/platform/telemetry/common/telemetryIpc';
27
import {TelemetryService, ITelemetryServiceConfig} from  'vs/platform/telemetry/common/telemetryService';
28
import {IdleMonitor, UserStatus} from  'vs/platform/telemetry/browser/idleMonitor';
J
Joao Moreno 已提交
29
import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry';
30
import {resolveWorkbenchCommonProperties} from 'vs/platform/telemetry/node/workbenchCommonProperties';
E
Erich Gamma 已提交
31 32
import {ElectronIntegration} from 'vs/workbench/electron-browser/integration';
import {Update} from 'vs/workbench/electron-browser/update';
A
Alex Dima 已提交
33
import {WorkspaceStats} from 'vs/workbench/services/telemetry/common/workspaceStats';
E
Erich Gamma 已提交
34 35
import {IWindowService, WindowService} from 'vs/workbench/services/window/electron-browser/windowService';
import {MessageService} from 'vs/workbench/services/message/electron-browser/messageService';
J
Joao Moreno 已提交
36 37
import {IRequestService} from 'vs/platform/request/common/request';
import {RequestService} from 'vs/platform/request/node/requestService';
E
Erich Gamma 已提交
38 39 40 41
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
import {SearchService} from 'vs/workbench/services/search/node/searchService';
import {LifecycleService} from 'vs/workbench/services/lifecycle/electron-browser/lifecycleService';
import {MainThreadService} from 'vs/workbench/services/thread/electron-browser/threadService';
42
import {MarkerService} from 'vs/platform/markers/common/markerService';
E
Erich Gamma 已提交
43 44 45 46
import {IModelService} from 'vs/editor/common/services/modelService';
import {ModelServiceImpl} from 'vs/editor/common/services/modelServiceImpl';
import {CodeEditorServiceImpl} from 'vs/editor/browser/services/codeEditorServiceImpl';
import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService';
A
Alex Dima 已提交
47 48
import {IntegrityServiceImpl} from 'vs/platform/integrity/node/integrityServiceImpl';
import {IIntegrityService} from 'vs/platform/integrity/common/integrity';
49 50
import {EditorWorkerServiceImpl} from 'vs/editor/common/services/editorWorkerServiceImpl';
import {IEditorWorkerService} from 'vs/editor/common/services/editorWorkerService';
51
import {MainProcessExtensionService} from 'vs/workbench/api/node/mainThreadExtensionService';
E
Erich Gamma 已提交
52
import {IOptions} from 'vs/workbench/common/options';
M
Martin Aeschlimann 已提交
53
import {IStorageService} from 'vs/platform/storage/common/storage';
54
import {ServiceCollection} from 'vs/platform/instantiation/common/serviceCollection';
55
import {InstantiationService} from 'vs/platform/instantiation/common/instantiationService';
56
import {IContextViewService} from 'vs/platform/contextview/browser/contextView';
E
Erich Gamma 已提交
57 58
import {IEventService} from 'vs/platform/event/common/event';
import {ILifecycleService} from 'vs/platform/lifecycle/common/lifecycle';
B
Benjamin Pasero 已提交
59
import {IMarkerService} from 'vs/platform/markers/common/markers';
60
import {IEnvironmentService} from 'vs/platform/environment/common/environment';
J
Joao Moreno 已提交
61
import {IMessageService, IChoiceService, Severity} from 'vs/platform/message/common/message';
S
Sandeep Somavarapu 已提交
62
import {ChoiceChannel} from 'vs/platform/message/common/messageIpc';
E
Erich Gamma 已提交
63
import {ISearchService} from 'vs/platform/search/common/search';
64
import {IThreadService} from 'vs/workbench/services/thread/common/threadService';
65 66
import {ICommandService} from 'vs/platform/commands/common/commands';
import {CommandService} from 'vs/platform/commands/common/commandService';
67
import {IWorkspaceContextService, IWorkspace} from 'vs/platform/workspace/common/workspace';
A
Alex Dima 已提交
68
import {IExtensionService} from 'vs/platform/extensions/common/extensions';
E
Erich Gamma 已提交
69 70
import {MainThreadModeServiceImpl} from 'vs/editor/common/services/modeServiceImpl';
import {IModeService} from 'vs/editor/common/services/modeService';
71
import {IUntitledEditorService, UntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService';
E
Erich Gamma 已提交
72
import {CrashReporter} from 'vs/workbench/electron-browser/crashReporter';
M
Martin Aeschlimann 已提交
73 74
import {IThemeService} from 'vs/workbench/services/themes/common/themeService';
import {ThemeService} from 'vs/workbench/services/themes/electron-browser/themeService';
J
Joao Moreno 已提交
75
import {getDelayedChannel} from 'vs/base/parts/ipc/common/ipc';
J
Joao Moreno 已提交
76 77 78
import {connect as connectNet} from 'vs/base/parts/ipc/node/ipc.net';
import {Client as ElectronIPCClient} from 'vs/base/parts/ipc/common/ipc.electron';
import {ipcRenderer} from 'electron';
J
Joao Moreno 已提交
79 80
import {IExtensionManagementChannel, ExtensionManagementChannelClient} from 'vs/platform/extensionManagement/common/extensionManagementIpc';
import {IExtensionManagementService} from 'vs/platform/extensionManagement/common/extensionManagement';
J
Joao Moreno 已提交
81 82
import {URLChannelClient} from 'vs/platform/url/common/urlIpc';
import {IURLService} from 'vs/platform/url/common/url';
83
import {ReloadWindowAction} from 'vs/workbench/electron-browser/actions';
84
import {WorkspaceConfigurationService} from 'vs/workbench/services/configuration/node/configurationService';
85 86

// self registering services
87
import 'vs/platform/opener/browser/opener.contribution';
88

89 90 91 92 93 94 95
/**
 * Services that we require for the Shell
 */
export interface ICoreServices {
	contextService: IWorkspaceContextService;
	eventService: IEventService;
	configurationService: IConfigurationService;
96
	environmentService: IEnvironmentService;
97 98
}

E
Erich Gamma 已提交
99
/**
B
Benjamin Pasero 已提交
100
 * The workbench shell contains the workbench with a rich header containing navigation and the activity bar.
E
Erich Gamma 已提交
101 102 103
 * With the Shell being the top level element in the page, it is also responsible for driving the layouting.
 */
export class WorkbenchShell {
B
Benjamin Pasero 已提交
104
	private storageService: IStorageService;
105
	private messageService: MessageService;
106
	private eventService: IEventService;
107
	private environmentService:IEnvironmentService;
B
Benjamin Pasero 已提交
108 109 110
	private contextViewService: ContextViewService;
	private windowService: IWindowService;
	private threadService: MainThreadService;
111
	private configurationService: IConfigurationService;
M
Martin Aeschlimann 已提交
112
	private themeService: ThemeService;
113
	private contextService: IWorkspaceContextService;
114
	private telemetryService: ITelemetryService;
E
Erich Gamma 已提交
115 116

	private container: HTMLElement;
117
	private toUnbind: IDisposable[];
E
Erich Gamma 已提交
118 119 120 121 122 123 124 125 126
	private previousErrorValue: string;
	private previousErrorTime: number;
	private content: HTMLElement;
	private contentsContainer: Builder;

	private workspace: IWorkspace;
	private options: IOptions;
	private workbench: Workbench;

127
	constructor(container: HTMLElement, workspace: IWorkspace, services: ICoreServices, options: IOptions) {
E
Erich Gamma 已提交
128 129 130
		this.container = container;

		this.workspace = workspace;
131 132 133 134 135
		this.options = options;

		this.contextService = services.contextService;
		this.eventService = services.eventService;
		this.configurationService = services.configurationService;
136
		this.environmentService = services.environmentService;
E
Erich Gamma 已提交
137 138 139 140 141 142 143

		this.toUnbind = [];
		this.previousErrorTime = 0;
	}

	private createContents(parent: Builder): Builder {

144
		// ARIA
B
Benjamin Pasero 已提交
145
		aria.setARIAContainer(document.body);
146

E
Erich Gamma 已提交
147
		// Workbench Container
148
		const workbenchContainer = $(parent).div();
E
Erich Gamma 已提交
149 150

		// Instantiation service with services
151
		const [instantiationService, serviceCollection] = this.initServiceCollection(parent.getHTMLElement());
E
Erich Gamma 已提交
152 153

		//crash reporting
154 155 156
		if (!!product.crashReporter) {
			const crashReporter = instantiationService.createInstance(CrashReporter, pkg.version, product.commit);
			crashReporter.start(product.crashReporter);
E
Erich Gamma 已提交
157 158 159
		}

		// Workbench
160
		this.workbench = instantiationService.createInstance(Workbench, workbenchContainer.getHTMLElement(), this.workspace, this.options, serviceCollection);
E
Erich Gamma 已提交
161
		this.workbench.startup({
162 163
			onWorkbenchStarted: (customKeybindingsCount) => {
				this.onWorkbenchStarted(customKeybindingsCount);
E
Erich Gamma 已提交
164 165 166 167 168 169 170 171 172 173
			}
		});

		// Electron integration
		this.workbench.getInstantiationService().createInstance(ElectronIntegration).integrate(this.container);

		// Update
		this.workbench.getInstantiationService().createInstance(Update);

		// Handle case where workbench is not starting up properly
174
		const timeoutHandle = setTimeout(() => {
E
Erich Gamma 已提交
175 176 177 178 179 180 181 182 183 184
			console.warn('Workbench did not finish loading in 10 seconds, that might be a problem that should be reported.');
		}, 10000);

		this.workbench.joinCreation().then(() => {
			clearTimeout(timeoutHandle);
		});

		return workbenchContainer;
	}

185
	private onWorkbenchStarted(customKeybindingsCount: number): void {
B
Benjamin Pasero 已提交
186 187

		// Log to telemetry service
188
		const windowSize = {
B
Benjamin Pasero 已提交
189 190 191 192 193 194 195 196 197 198 199
			innerHeight: window.innerHeight,
			innerWidth: window.innerWidth,
			outerHeight: window.outerHeight,
			outerWidth: window.outerWidth
		};

		this.telemetryService.publicLog('workspaceLoad',
			{
				userAgent: navigator.userAgent,
				windowSize: windowSize,
				emptyWorkbench: !this.contextService.getWorkspace(),
200
				customKeybindingsCount,
M
Martin Aeschlimann 已提交
201
				theme: this.themeService.getColorTheme(),
202 203
				language: platform.language,
				experiments: this.telemetryService.getExperiments()
B
Benjamin Pasero 已提交
204 205
			});

206
		const workspaceStats: WorkspaceStats = <WorkspaceStats>this.workbench.getInstantiationService().createInstance(WorkspaceStats);
B
Benjamin Pasero 已提交
207
		workspaceStats.reportWorkspaceTags();
J
Joao Moreno 已提交
208 209 210 211

		if ((platform.isLinux || platform.isMacintosh) && process.getuid() === 0) {
			this.messageService.show(Severity.Warning, nls.localize('runningAsRoot', "It is recommended not to run Code as 'root'."));
		}
B
Benjamin Pasero 已提交
212 213
	}

214
	private initServiceCollection(container: HTMLElement): [InstantiationService, ServiceCollection] {
J
Joao Moreno 已提交
215 216
		const disposables = new Disposables();

J
Joao Moreno 已提交
217
		const mainProcessClient = new ElectronIPCClient(ipcRenderer);
J
Joao Moreno 已提交
218
		disposables.add(mainProcessClient);
J
Joao Moreno 已提交
219

220 221 222 223
		const serviceCollection = new ServiceCollection();
		serviceCollection.set(IEventService, this.eventService);
		serviceCollection.set(IWorkspaceContextService, this.contextService);
		serviceCollection.set(IConfigurationService, this.configurationService);
224
		serviceCollection.set(IEnvironmentService, this.environmentService);
225

226
		const instantiationService = new InstantiationService(serviceCollection, true);
227

228 229 230
		this.windowService = instantiationService.createInstance(WindowService);
		serviceCollection.set(IWindowService, this.windowService);

231 232
		const sharedProcess = connectNet(this.environmentService.sharedIPCHandle, `window:${ this.windowService.getWindowId() }`);
		sharedProcess.done(client => {
S
Sandeep Somavarapu 已提交
233 234 235

			client.registerChannel('choice', new ChoiceChannel(this.messageService));

236 237 238 239 240 241 242 243
			client.onClose(() => {
				this.messageService.show(Severity.Error, {
					message: nls.localize('sharedProcessCrashed', "The shared process terminated unexpectedly. Please reload the window to recover."),
					actions: [instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, ReloadWindowAction.LABEL)]
				});
			});
		}, errors.onUnexpectedError);

244
		// Storage
245
		const disableWorkspaceStorage = this.environmentService.extensionTestsPath || (!this.workspace && !this.environmentService.extensionDevelopmentPath); // without workspace or in any extension test, we use inMemory storage unless we develop an extension where we want to preserve state
246 247
		this.storageService = instantiationService.createInstance(Storage, window.localStorage, disableWorkspaceStorage ? inMemoryLocalStorageInstance : window.localStorage);
		serviceCollection.set(IStorageService, this.storageService);
E
Erich Gamma 已提交
248

249
		// Telemetry
250
		if (this.environmentService.isBuilt && !this.environmentService.extensionDevelopmentPath && !!product.enableTelemetry) {
251
			const channel = getDelayedChannel<ITelemetryAppenderChannel>(sharedProcess.then(c => c.getChannel('telemetryAppender')));
252 253
			const commit = product.commit;
			const version = pkg.version;
254

255
			const config: ITelemetryServiceConfig = {
256
				appender: new TelemetryAppenderClient(channel),
257
				commonProperties: resolveWorkbenchCommonProperties(this.storageService, commit, version),
258
				piiPaths: [this.environmentService.appRoot, this.environmentService.extensionsPath],
259
				experiments: loadExperiments(this.storageService, this.configurationService)
260
			};
261

J
Joao Moreno 已提交
262
			const telemetryService = instantiationService.createInstance(TelemetryService, config);
263 264
			this.telemetryService = telemetryService;

J
Joao Moreno 已提交
265
			const errorTelemetry = new ErrorTelemetry(telemetryService);
266
			const idleMonitor = new IdleMonitor(2 * 60 * 1000); // 2 minutes
267

268 269 270 271
			const listener = idleMonitor.onStatusChange(status =>
				this.telemetryService.publicLog(status === UserStatus.Active
					? TelemetryService.IDLE_STOP_EVENT_NAME
					: TelemetryService.IDLE_START_EVENT_NAME
272
				));
273

274
			disposables.add(telemetryService, errorTelemetry, listener, idleMonitor);
275
		} else {
276
			NullTelemetryService._experiments = loadExperiments(this.storageService, this.configurationService);
277
			this.telemetryService = NullTelemetryService;
278
		}
E
Erich Gamma 已提交
279

280
		serviceCollection.set(ITelemetryService, this.telemetryService);
281 282 283
		if (this.configurationService instanceof WorkspaceConfigurationService) {
			this.configurationService.telemetryService = this.telemetryService;
		}
E
Erich Gamma 已提交
284

285
		this.messageService = instantiationService.createInstance(MessageService, container);
286
		serviceCollection.set(IMessageService, this.messageService);
J
Joao Moreno 已提交
287
		serviceCollection.set(IChoiceService, this.messageService);
E
Erich Gamma 已提交
288

289
		const lifecycleService = instantiationService.createInstance(LifecycleService);
290 291
		this.toUnbind.push(lifecycleService.onShutdown(() => disposables.dispose()));
		serviceCollection.set(ILifecycleService, lifecycleService);
292

293 294
		this.threadService = instantiationService.createInstance(MainThreadService);
		serviceCollection.set(IThreadService, this.threadService);
295

296
		const extensionService = instantiationService.createInstance(MainProcessExtensionService);
297
		serviceCollection.set(IExtensionService, extensionService);
E
Erich Gamma 已提交
298

299 300
		serviceCollection.set(ICommandService, new CommandService(instantiationService, extensionService));

301 302
		this.contextViewService = instantiationService.createInstance(ContextViewService, this.container);
		serviceCollection.set(IContextViewService, this.contextViewService);
E
Erich Gamma 已提交
303

J
Joao Moreno 已提交
304
		const requestService = instantiationService.createInstance(RequestService);
305
		serviceCollection.set(IRequestService, requestService);
E
Erich Gamma 已提交
306

307
		const markerService = instantiationService.createInstance(MarkerService);
308
		serviceCollection.set(IMarkerService, markerService);
E
Erich Gamma 已提交
309

310
		const modeService = instantiationService.createInstance(MainThreadModeServiceImpl);
311
		serviceCollection.set(IModeService, modeService);
312

313
		const modelService = instantiationService.createInstance(ModelServiceImpl);
314
		serviceCollection.set(IModelService, modelService);
315

316
		const editorWorkerService = instantiationService.createInstance(EditorWorkerServiceImpl);
317
		serviceCollection.set(IEditorWorkerService, editorWorkerService);
318

319
		const untitledEditorService = instantiationService.createInstance(UntitledEditorService);
320 321 322
		serviceCollection.set(IUntitledEditorService, untitledEditorService);

		this.themeService = instantiationService.createInstance(ThemeService);
323
		serviceCollection.set(IThemeService, this.themeService);
324

325
		const searchService = instantiationService.createInstance(SearchService);
326 327
		serviceCollection.set(ISearchService, searchService);

328
		const codeEditorService = instantiationService.createInstance(CodeEditorServiceImpl);
329 330
		serviceCollection.set(ICodeEditorService, codeEditorService);

A
Alex Dima 已提交
331 332 333
		const integrityService = instantiationService.createInstance(IntegrityServiceImpl);
		serviceCollection.set(IIntegrityService, integrityService);

334
		const extensionManagementChannel = getDelayedChannel<IExtensionManagementChannel>(sharedProcess.then(c => c.getChannel('extensions')));
J
Joao Moreno 已提交
335
		const extensionManagementChannelClient = new ExtensionManagementChannelClient(extensionManagementChannel);
336
		serviceCollection.set(IExtensionManagementService, extensionManagementChannelClient);
337

J
Joao Moreno 已提交
338
		const urlChannel = mainProcessClient.getChannel('url');
J
Joao Moreno 已提交
339
		const urlChannelClient = new URLChannelClient(urlChannel, this.windowService.getWindowId());
J
Joao Moreno 已提交
340 341
		serviceCollection.set(IURLService, urlChannelClient);

342
		return [instantiationService, serviceCollection];
E
Erich Gamma 已提交
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
	}

	public open(): void {

		// Listen on unexpected errors
		errors.setUnexpectedErrorHandler((error: any) => {
			this.onUnexpectedError(error);
		});

		// Shell Class for CSS Scoping
		$(this.container).addClass('monaco-shell');

		// Controls
		this.content = $('.monaco-shell-content').appendTo(this.container).getHTMLElement();

		// Handle Load Performance Timers
		this.writeTimers();

		// Create Contents
		this.contentsContainer = this.createContents($(this.content));

		// Layout
		this.layout();

		// Listeners
		this.registerListeners();

		// Enable theme support
M
Martin Aeschlimann 已提交
371
		this.themeService.initialize(this.container).then(null, error => {
372 373
			errors.onUnexpectedError(error);
		});
E
Erich Gamma 已提交
374 375 376 377 378 379 380 381 382
	}

	private registerListeners(): void {

		// Resize
		$(window).on(dom.EventType.RESIZE, () => this.layout(), this.toUnbind);
	}

	private writeTimers(): void {
383
		const timers = (<any>window).MonacoEnvironment.timers;
E
Erich Gamma 已提交
384
		if (timers) {
385
			const events: timer.IExistingTimerEvent[] = [];
E
Erich Gamma 已提交
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421

			// Window
			if (timers.vscodeStart) {
				events.push({
					startTime: timers.vscodeStart,
					stopTime: timers.beforeLoad,
					topic: 'Startup',
					name: 'VSCode Startup',
					description: 'Time it takes to create a window and startup VSCode'
				});
			}

			// Load
			events.push({
				startTime: timers.beforeLoad,
				stopTime: timers.afterLoad,
				topic: 'Startup',
				name: 'Load Modules',
				description: 'Time it takes to load VSCodes main modules'
			});

			// Ready
			events.push({
				startTime: timers.beforeReady,
				stopTime: timers.afterReady,
				topic: 'Startup',
				name: 'Event DOMContentLoaded',
				description: 'Time it takes for the DOM to emit DOMContentLoaded event'
			});

			// Write to Timer
			timer.getTimeKeeper().setInitialCollectedEvents(events, timers.start);
		}
	}

	public onUnexpectedError(error: any): void {
422
		const errorMsg = toErrorMessage(error, true);
E
Erich Gamma 已提交
423 424 425 426
		if (!errorMsg) {
			return;
		}

427
		const now = Date.now();
E
Erich Gamma 已提交
428 429 430 431 432 433 434 435 436 437 438
		if (errorMsg === this.previousErrorValue && now - this.previousErrorTime <= 1000) {
			return; // Return if error message identical to previous and shorter than 1 second
		}

		this.previousErrorTime = now;
		this.previousErrorValue = errorMsg;

		// Log to console
		console.error(errorMsg);

		// Show to user if friendly message provided
439
		if (error && error.friendlyMessage && this.messageService) {
B
Benjamin Pasero 已提交
440
			this.messageService.show(Severity.Error, error.friendlyMessage);
E
Erich Gamma 已提交
441 442 443
		}
	}

B
Benjamin Pasero 已提交
444
	private layout(): void {
445
		const clArea = $(this.container).getClientArea();
E
Erich Gamma 已提交
446

447
		const contentsSize = new Dimension(clArea.width, clArea.height);
E
Erich Gamma 已提交
448 449
		this.contentsContainer.size(contentsSize.width, contentsSize.height);

B
Benjamin Pasero 已提交
450
		this.contextViewService.layout();
E
Erich Gamma 已提交
451 452 453 454 455 456 457
		this.workbench.layout();
	}

	public joinCreation(): TPromise<boolean> {
		return this.workbench.joinCreation();
	}

B
Benjamin Pasero 已提交
458
	public dispose(): void {
E
Erich Gamma 已提交
459 460 461

		// Workbench
		if (this.workbench) {
B
Benjamin Pasero 已提交
462
			this.workbench.dispose();
E
Erich Gamma 已提交
463 464
		}

B
Benjamin Pasero 已提交
465
		this.contextViewService.dispose();
E
Erich Gamma 已提交
466 467

		// Listeners
J
Joao Moreno 已提交
468
		this.toUnbind = dispose(this.toUnbind);
E
Erich Gamma 已提交
469 470 471 472 473

		// Container
		$(this.container).empty();
	}
}