Merge from vscode 817eb6b0c720a4ecbc13c020afbbebfed667aa09 (#7356)

This commit is contained in:
Anthony Dresser
2019-09-24 21:36:17 -07:00
committed by GitHub
parent a29ae4d3b9
commit 6a6048d40f
541 changed files with 7045 additions and 7287 deletions

View File

@@ -10,7 +10,6 @@ import { IProcessEnvironment, isMacintosh, isLinux, isWeb } from 'vs/base/common
import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history';
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import { ExportData } from 'vs/base/common/performance';
import { LogLevel } from 'vs/platform/log/common/log';
import { DisposableStore, Disposable } from 'vs/base/common/lifecycle';
@@ -21,7 +20,6 @@ import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async
export const IWindowsService = createDecorator<IWindowsService>('windowsService');
export interface INativeOpenDialogOptions {
windowId?: number;
forceNewWindow?: boolean;
defaultPath?: string;
@@ -35,16 +33,6 @@ export interface IEnterWorkspaceResult {
backupPath?: string;
}
export interface CrashReporterStartOptions {
companyName?: string;
submitURL: string;
productName?: string;
uploadToServer?: boolean;
ignoreSystemCrashHandler?: boolean;
extra?: any;
crashesDirectory?: string;
}
export interface OpenDialogOptions {
title?: string;
defaultPath?: string;
@@ -83,15 +71,6 @@ export interface SaveDialogOptions {
showsTagField?: boolean;
}
export interface INewWindowOptions {
remoteAuthority?: string;
reuseWindow?: boolean;
}
export interface IDevToolsOptions {
mode: 'right' | 'bottom' | 'undocked' | 'detach';
}
export interface IWindowsService {
_serviceBrand: undefined;
@@ -103,77 +82,21 @@ export interface IWindowsService {
readonly onWindowUnmaximize: Event<number>;
readonly onRecentlyOpenedChange: Event<void>;
// Dialogs
pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise<void>;
pickFileAndOpen(options: INativeOpenDialogOptions): Promise<void>;
pickFolderAndOpen(options: INativeOpenDialogOptions): Promise<void>;
pickWorkspaceAndOpen(options: INativeOpenDialogOptions): Promise<void>;
showMessageBox(windowId: number, options: MessageBoxOptions): Promise<IMessageBoxResult>;
showSaveDialog(windowId: number, options: SaveDialogOptions): Promise<string>;
showOpenDialog(windowId: number, options: OpenDialogOptions): Promise<string[]>;
reloadWindow(windowId: number, args?: ParsedArgs): Promise<void>;
openDevTools(windowId: number, options?: IDevToolsOptions): Promise<void>;
toggleDevTools(windowId: number): Promise<void>;
closeWorkspace(windowId: number): Promise<void>;
enterWorkspace(windowId: number, path: URI): Promise<IEnterWorkspaceResult | undefined>;
toggleFullScreen(windowId: number): Promise<void>;
setRepresentedFilename(windowId: number, fileName: string): Promise<void>;
addRecentlyOpened(recents: IRecent[]): Promise<void>;
removeFromRecentlyOpened(paths: URI[]): Promise<void>;
clearRecentlyOpened(): Promise<void>;
getRecentlyOpened(windowId: number): Promise<IRecentlyOpened>;
focusWindow(windowId: number): Promise<void>;
closeWindow(windowId: number): Promise<void>;
isFocused(windowId: number): Promise<boolean>;
isMaximized(windowId: number): Promise<boolean>;
maximizeWindow(windowId: number): Promise<void>;
unmaximizeWindow(windowId: number): Promise<void>;
minimizeWindow(windowId: number): Promise<void>;
onWindowTitleDoubleClick(windowId: number): Promise<void>;
setDocumentEdited(windowId: number, flag: boolean): Promise<void>;
quit(): Promise<void>;
relaunch(options: { addArgs?: string[], removeArgs?: string[] }): Promise<void>;
// macOS Native Tabs
newWindowTab(): Promise<void>;
showPreviousWindowTab(): Promise<void>;
showNextWindowTab(): Promise<void>;
moveWindowTabToNewWindow(): Promise<void>;
mergeAllWindowTabs(): Promise<void>;
toggleWindowTabsBar(): Promise<void>;
// macOS TouchBar
updateTouchBar(windowId: number, items: ISerializableCommandAction[][]): Promise<void>;
// Shared process
whenSharedProcessReady(): Promise<void>;
toggleSharedProcess(): Promise<void>;
// Global methods
openWindow(windowId: number, uris: IURIToOpen[], options: IOpenSettings): Promise<void>;
openNewWindow(options?: INewWindowOptions): Promise<void>;
openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise<void>;
getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]>;
getActiveWindowId(): Promise<number | undefined>;
// This needs to be handled from browser process to prevent
// foreground ordering issues on Windows
openExternal(url: string): Promise<boolean>;
// TODO: this is a bit backwards
startCrashReporter(config: CrashReporterStartOptions): Promise<void>;
resolveProxy(windowId: number, url: string): Promise<string | undefined>;
}
export const IWindowService = createDecorator<IWindowService>('windowService');
export interface IMessageBoxResult {
button: number;
checkboxChecked?: boolean;
}
export interface IOpenSettings {
forceNewWindow?: boolean;
forceReuseWindow?: boolean;
@@ -226,37 +149,12 @@ export interface IWindowService {
readonly windowId: number;
pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise<void>;
pickFileAndOpen(options: INativeOpenDialogOptions): Promise<void>;
pickFolderAndOpen(options: INativeOpenDialogOptions): Promise<void>;
pickWorkspaceAndOpen(options: INativeOpenDialogOptions): Promise<void>;
reloadWindow(args?: ParsedArgs): Promise<void>;
openDevTools(options?: IDevToolsOptions): Promise<void>;
toggleDevTools(): Promise<void>;
closeWorkspace(): Promise<void>;
updateTouchBar(items: ISerializableCommandAction[][]): Promise<void>;
enterWorkspace(path: URI): Promise<IEnterWorkspaceResult | undefined>;
// rationale: will eventually move to electron-browser
// tslint:disable-next-line: no-dom-globals
toggleFullScreen(target?: HTMLElement): Promise<void>;
setRepresentedFilename(fileName: string): Promise<void>;
getRecentlyOpened(): Promise<IRecentlyOpened>;
addRecentlyOpened(recents: IRecent[]): Promise<void>;
removeFromRecentlyOpened(paths: URI[]): Promise<void>;
focusWindow(): Promise<void>;
closeWindow(): Promise<void>;
openWindow(uris: IURIToOpen[], options?: IOpenSettings): Promise<void>;
isFocused(): Promise<boolean>;
setDocumentEdited(flag: boolean): Promise<void>;
isMaximized(): Promise<boolean>;
maximizeWindow(): Promise<void>;
unmaximizeWindow(): Promise<void>;
minimizeWindow(): Promise<void>;
onWindowTitleDoubleClick(): Promise<void>;
showMessageBox(options: MessageBoxOptions): Promise<IMessageBoxResult>;
showSaveDialog(options: SaveDialogOptions): Promise<string>;
showOpenDialog(options: OpenDialogOptions): Promise<string[]>;
resolveProxy(url: string): Promise<string | undefined>;
}
export type MenuBarVisibility = 'default' | 'visible' | 'toggle' | 'hidden';

View File

@@ -43,20 +43,6 @@ export class WindowsChannel implements IServerChannel {
call(_: unknown, command: string, arg?: any): Promise<any> {
switch (command) {
case 'pickFileFolderAndOpen': return this.service.pickFileFolderAndOpen(arg);
case 'pickFileAndOpen': return this.service.pickFileAndOpen(arg);
case 'pickFolderAndOpen': return this.service.pickFolderAndOpen(arg);
case 'pickWorkspaceAndOpen': return this.service.pickWorkspaceAndOpen(arg);
case 'showMessageBox': return this.service.showMessageBox(arg[0], arg[1]);
case 'showSaveDialog': return this.service.showSaveDialog(arg[0], arg[1]);
case 'showOpenDialog': return this.service.showOpenDialog(arg[0], arg[1]);
case 'reloadWindow': return this.service.reloadWindow(arg[0], arg[1]);
case 'openDevTools': return this.service.openDevTools(arg[0], arg[1]);
case 'toggleDevTools': return this.service.toggleDevTools(arg);
case 'closeWorkspace': return this.service.closeWorkspace(arg);
case 'enterWorkspace': return this.service.enterWorkspace(arg[0], URI.revive(arg[1]));
case 'toggleFullScreen': return this.service.toggleFullScreen(arg);
case 'setRepresentedFilename': return this.service.setRepresentedFilename(arg[0], arg[1]);
case 'addRecentlyOpened': return this.service.addRecentlyOpened(arg.map((recent: IRecent) => {
if (isRecentFile(recent)) {
recent.fileUri = URI.revive(recent.fileUri);
@@ -69,23 +55,9 @@ export class WindowsChannel implements IServerChannel {
}));
case 'removeFromRecentlyOpened': return this.service.removeFromRecentlyOpened(arg.map(URI.revive));
case 'clearRecentlyOpened': return this.service.clearRecentlyOpened();
case 'newWindowTab': return this.service.newWindowTab();
case 'showPreviousWindowTab': return this.service.showPreviousWindowTab();
case 'showNextWindowTab': return this.service.showNextWindowTab();
case 'moveWindowTabToNewWindow': return this.service.moveWindowTabToNewWindow();
case 'mergeAllWindowTabs': return this.service.mergeAllWindowTabs();
case 'toggleWindowTabsBar': return this.service.toggleWindowTabsBar();
case 'updateTouchBar': return this.service.updateTouchBar(arg[0], arg[1]);
case 'getRecentlyOpened': return this.service.getRecentlyOpened(arg);
case 'focusWindow': return this.service.focusWindow(arg);
case 'closeWindow': return this.service.closeWindow(arg);
case 'isFocused': return this.service.isFocused(arg);
case 'isMaximized': return this.service.isMaximized(arg);
case 'maximizeWindow': return this.service.maximizeWindow(arg);
case 'unmaximizeWindow': return this.service.unmaximizeWindow(arg);
case 'minimizeWindow': return this.service.minimizeWindow(arg);
case 'onWindowTitleDoubleClick': return this.service.onWindowTitleDoubleClick(arg);
case 'setDocumentEdited': return this.service.setDocumentEdited(arg[0], arg[1]);
case 'openWindow': {
const urisToOpen: IURIToOpen[] = arg[1];
const options: IOpenSettings = arg[2];
@@ -101,17 +73,9 @@ export class WindowsChannel implements IServerChannel {
options.waitMarkerFileURI = options.waitMarkerFileURI && URI.revive(options.waitMarkerFileURI);
return this.service.openWindow(arg[0], urisToOpen, options);
}
case 'openNewWindow': return this.service.openNewWindow(arg);
case 'openExtensionDevelopmentHostWindow': return this.service.openExtensionDevelopmentHostWindow(arg[0], arg[1]);
case 'openExtensionDevelopmentHostWindow': return (this.service as any).openExtensionDevelopmentHostWindow(arg[0], arg[1]); // TODO@Isidor move
case 'getWindows': return this.service.getWindows();
case 'relaunch': return this.service.relaunch(arg[0]);
case 'whenSharedProcessReady': return this.service.whenSharedProcessReady();
case 'toggleSharedProcess': return this.service.toggleSharedProcess();
case 'quit': return this.service.quit();
case 'getActiveWindowId': return this.service.getActiveWindowId();
case 'openExternal': return this.service.openExternal(arg);
case 'startCrashReporter': return this.service.startCrashReporter(arg);
case 'resolveProxy': return this.service.resolveProxy(arg[0], arg[1]);
}
throw new Error(`Call not found: ${command}`);

View File

@@ -5,10 +5,9 @@
import { Event } from 'vs/base/common/event';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult, CrashReporterStartOptions, IMessageBoxResult, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions, IDevToolsOptions, INewWindowOptions, IURIToOpen, IOpenSettings } from 'vs/platform/windows/common/windows';
import { IWindowsService, IURIToOpen, IOpenSettings } from 'vs/platform/windows/common/windows';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IRecentlyOpened, IRecent, isRecentWorkspace } from 'vs/platform/history/common/history';
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import { URI } from 'vs/base/common/uri';
import { ParsedArgs } from 'vs/platform/environment/common/environment';
import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
@@ -31,67 +30,6 @@ export class WindowsService implements IWindowsService {
this.channel = mainProcessService.getChannel('windows');
}
pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise<void> {
return this.channel.call('pickFileFolderAndOpen', options);
}
pickFileAndOpen(options: INativeOpenDialogOptions): Promise<void> {
return this.channel.call('pickFileAndOpen', options);
}
pickFolderAndOpen(options: INativeOpenDialogOptions): Promise<void> {
return this.channel.call('pickFolderAndOpen', options);
}
pickWorkspaceAndOpen(options: INativeOpenDialogOptions): Promise<void> {
return this.channel.call('pickWorkspaceAndOpen', options);
}
showMessageBox(windowId: number, options: MessageBoxOptions): Promise<IMessageBoxResult> {
return this.channel.call('showMessageBox', [windowId, options]);
}
showSaveDialog(windowId: number, options: SaveDialogOptions): Promise<string> {
return this.channel.call('showSaveDialog', [windowId, options]);
}
showOpenDialog(windowId: number, options: OpenDialogOptions): Promise<string[]> {
return this.channel.call('showOpenDialog', [windowId, options]);
}
reloadWindow(windowId: number, args?: ParsedArgs): Promise<void> {
return this.channel.call('reloadWindow', [windowId, args]);
}
openDevTools(windowId: number, options?: IDevToolsOptions): Promise<void> {
return this.channel.call('openDevTools', [windowId, options]);
}
toggleDevTools(windowId: number): Promise<void> {
return this.channel.call('toggleDevTools', windowId);
}
closeWorkspace(windowId: number): Promise<void> {
return this.channel.call('closeWorkspace', windowId);
}
async enterWorkspace(windowId: number, path: URI): Promise<IEnterWorkspaceResult | undefined> {
const result: IEnterWorkspaceResult = await this.channel.call('enterWorkspace', [windowId, path]);
if (result) {
result.workspace = reviveWorkspaceIdentifier(result.workspace);
}
return result;
}
toggleFullScreen(windowId: number): Promise<void> {
return this.channel.call('toggleFullScreen', windowId);
}
setRepresentedFilename(windowId: number, fileName: string): Promise<void> {
return this.channel.call('setRepresentedFilename', [windowId, fileName]);
}
addRecentlyOpened(recent: IRecent[]): Promise<void> {
return this.channel.call('addRecentlyOpened', recent);
}
@@ -112,90 +50,18 @@ export class WindowsService implements IWindowsService {
return recentlyOpened;
}
newWindowTab(): Promise<void> {
return this.channel.call('newWindowTab');
}
showPreviousWindowTab(): Promise<void> {
return this.channel.call('showPreviousWindowTab');
}
showNextWindowTab(): Promise<void> {
return this.channel.call('showNextWindowTab');
}
moveWindowTabToNewWindow(): Promise<void> {
return this.channel.call('moveWindowTabToNewWindow');
}
mergeAllWindowTabs(): Promise<void> {
return this.channel.call('mergeAllWindowTabs');
}
toggleWindowTabsBar(): Promise<void> {
return this.channel.call('toggleWindowTabsBar');
}
focusWindow(windowId: number): Promise<void> {
return this.channel.call('focusWindow', windowId);
}
closeWindow(windowId: number): Promise<void> {
return this.channel.call('closeWindow', windowId);
}
isFocused(windowId: number): Promise<boolean> {
return this.channel.call('isFocused', windowId);
}
isMaximized(windowId: number): Promise<boolean> {
return this.channel.call('isMaximized', windowId);
}
maximizeWindow(windowId: number): Promise<void> {
return this.channel.call('maximizeWindow', windowId);
}
unmaximizeWindow(windowId: number): Promise<void> {
return this.channel.call('unmaximizeWindow', windowId);
}
minimizeWindow(windowId: number): Promise<void> {
return this.channel.call('minimizeWindow', windowId);
}
onWindowTitleDoubleClick(windowId: number): Promise<void> {
return this.channel.call('onWindowTitleDoubleClick', windowId);
}
setDocumentEdited(windowId: number, flag: boolean): Promise<void> {
return this.channel.call('setDocumentEdited', [windowId, flag]);
}
quit(): Promise<void> {
return this.channel.call('quit');
}
relaunch(options: { addArgs?: string[], removeArgs?: string[] }): Promise<void> {
return this.channel.call('relaunch', [options]);
}
whenSharedProcessReady(): Promise<void> {
return this.channel.call('whenSharedProcessReady');
}
toggleSharedProcess(): Promise<void> {
return this.channel.call('toggleSharedProcess');
}
openWindow(windowId: number, uris: IURIToOpen[], options: IOpenSettings): Promise<void> {
return this.channel.call('openWindow', [windowId, uris, options]);
}
openNewWindow(options?: INewWindowOptions): Promise<void> {
return this.channel.call('openNewWindow', options);
}
openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise<void> {
return this.channel.call('openExtensionDevelopmentHostWindow', [args, env]);
}
@@ -225,20 +91,4 @@ export class WindowsService implements IWindowsService {
getActiveWindowId(): Promise<number | undefined> {
return this.channel.call('getActiveWindowId');
}
openExternal(url: string): Promise<boolean> {
return this.channel.call('openExternal', url);
}
startCrashReporter(config: CrashReporterStartOptions): Promise<void> {
return this.channel.call('startCrashReporter', config);
}
updateTouchBar(windowId: number, items: ISerializableCommandAction[][]): Promise<void> {
return this.channel.call('updateTouchBar', [windowId, items]);
}
resolveProxy(windowId: number, url: string): Promise<string | undefined> {
return Promise.resolve(this.channel.call('resolveProxy', [windowId, url]));
}
}

View File

@@ -0,0 +1,203 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { assign } from 'vs/base/common/objects';
import { URI } from 'vs/base/common/uri';
import { IWindowsService, OpenContext, IOpenSettings, IURIToOpen } from 'vs/platform/windows/common/windows';
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
import { app, MessageBoxReturnValue, SaveDialogReturnValue, OpenDialogReturnValue, BrowserWindow, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions } from 'electron';
import { Event } from 'vs/base/common/event';
import { IURLService, IURLHandler } from 'vs/platform/url/common/url';
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history';
import { IHistoryMainService } from 'vs/platform/history/electron-main/historyMainService';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { Schemas } from 'vs/base/common/network';
import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform';
import { ILogService } from 'vs/platform/log/common/log';
// @deprecated this should eventually go away and be implemented by host & electron service
export class LegacyWindowsMainService extends Disposable implements IWindowsService, IURLHandler {
_serviceBrand: undefined;
private readonly disposables = this._register(new DisposableStore());
private _activeWindowId: number | undefined;
readonly onWindowOpen: Event<number> = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-created', (_, w: BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id));
readonly onWindowBlur: Event<number> = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-blur', (_, w: BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id));
readonly onWindowMaximize: Event<number> = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-maximize', (_, w: BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id));
readonly onWindowUnmaximize: Event<number> = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-unmaximize', (_, w: BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id));
readonly onWindowFocus: Event<number> = Event.any(
Event.map(Event.filter(Event.map(this.windowsMainService.onWindowsCountChanged, () => this.windowsMainService.getLastActiveWindow()), w => !!w), w => w!.id),
Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-focus', (_, w: BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id))
);
readonly onRecentlyOpenedChange: Event<void> = this.historyMainService.onRecentlyOpenedChange;
constructor(
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IURLService urlService: IURLService,
@IHistoryMainService private readonly historyMainService: IHistoryMainService,
@ILogService private readonly logService: ILogService
) {
super();
urlService.registerHandler(this);
// remember last active window id
Event.latch(Event.any(this.onWindowOpen, this.onWindowFocus))
(id => this._activeWindowId = id, null, this.disposables);
}
async showMessageBox(windowId: number, options: MessageBoxOptions): Promise<MessageBoxReturnValue> {
this.logService.trace('windowsService#showMessageBox', windowId);
return this.withWindow(windowId, codeWindow => this.windowsMainService.showMessageBox(options, codeWindow), () => this.windowsMainService.showMessageBox(options))!;
}
async showSaveDialog(windowId: number, options: SaveDialogOptions): Promise<SaveDialogReturnValue> {
this.logService.trace('windowsService#showSaveDialog', windowId);
return this.withWindow(windowId, codeWindow => this.windowsMainService.showSaveDialog(options, codeWindow), () => this.windowsMainService.showSaveDialog(options))!;
}
async showOpenDialog(windowId: number, options: OpenDialogOptions): Promise<OpenDialogReturnValue> {
this.logService.trace('windowsService#showOpenDialog', windowId);
return this.withWindow(windowId, codeWindow => this.windowsMainService.showOpenDialog(options, codeWindow), () => this.windowsMainService.showOpenDialog(options))!;
}
async addRecentlyOpened(recents: IRecent[]): Promise<void> {
this.logService.trace('windowsService#addRecentlyOpened');
this.historyMainService.addRecentlyOpened(recents);
}
async removeFromRecentlyOpened(paths: URI[]): Promise<void> {
this.logService.trace('windowsService#removeFromRecentlyOpened');
this.historyMainService.removeFromRecentlyOpened(paths);
}
async clearRecentlyOpened(): Promise<void> {
this.logService.trace('windowsService#clearRecentlyOpened');
this.historyMainService.clearRecentlyOpened();
}
async getRecentlyOpened(windowId: number): Promise<IRecentlyOpened> {
this.logService.trace('windowsService#getRecentlyOpened', windowId);
return this.withWindow(windowId, codeWindow => this.historyMainService.getRecentlyOpened(codeWindow.config.workspace, codeWindow.config.folderUri, codeWindow.config.filesToOpenOrCreate), () => this.historyMainService.getRecentlyOpened())!;
}
async focusWindow(windowId: number): Promise<void> {
this.logService.trace('windowsService#focusWindow', windowId);
if (isMacintosh) {
return this.withWindow(windowId, codeWindow => codeWindow.win.show());
} else {
return this.withWindow(windowId, codeWindow => codeWindow.win.focus());
}
}
async isFocused(windowId: number): Promise<boolean> {
this.logService.trace('windowsService#isFocused', windowId);
return this.withWindow(windowId, codeWindow => codeWindow.win.isFocused(), () => false)!;
}
async openWindow(windowId: number, urisToOpen: IURIToOpen[], options: IOpenSettings): Promise<void> {
this.logService.trace('windowsService#openWindow');
if (!urisToOpen || !urisToOpen.length) {
return undefined;
}
this.windowsMainService.open({
context: OpenContext.API,
contextWindowId: windowId,
urisToOpen: urisToOpen,
cli: options.args ? { ...this.environmentService.args, ...options.args } : this.environmentService.args,
forceNewWindow: options.forceNewWindow,
forceReuseWindow: options.forceReuseWindow,
diffMode: options.diffMode,
addMode: options.addMode,
gotoLineMode: options.gotoLineMode,
noRecentEntry: options.noRecentEntry,
waitMarkerFileURI: options.waitMarkerFileURI
});
}
async openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise<void> {
this.logService.trace('windowsService#openExtensionDevelopmentHostWindow ' + JSON.stringify(args));
const extDevPaths = args.extensionDevelopmentPath;
if (extDevPaths) {
this.windowsMainService.openExtensionDevelopmentHostWindow(extDevPaths, {
context: OpenContext.API,
cli: args,
userEnv: Object.keys(env).length > 0 ? env : undefined
});
}
}
async getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]> {
this.logService.trace('windowsService#getWindows');
const windows = this.windowsMainService.getWindows();
return windows.map(window => ({
id: window.id,
workspace: window.openedWorkspace,
folderUri: window.openedFolderUri,
title: window.win.getTitle(),
filename: window.getRepresentedFilename()
}));
}
async getWindowCount(): Promise<number> {
this.logService.trace('windowsService#getWindowCount');
return this.windowsMainService.getWindows().length;
}
async getActiveWindowId(): Promise<number | undefined> {
return this._activeWindowId;
}
async handleURL(uri: URI): Promise<boolean> {
// Catch file URLs
if (uri.authority === Schemas.file && !!uri.path) {
this.openFileForURI({ fileUri: URI.file(uri.fsPath) }); // using fsPath on a non-file URI...
return true;
}
return false;
}
private openFileForURI(uri: IURIToOpen): void {
const cli = assign(Object.create(null), this.environmentService.args);
const urisToOpen = [uri];
this.windowsMainService.open({ context: OpenContext.API, cli, urisToOpen, gotoLineMode: true });
}
private withWindow<T>(windowId: number, fn: (window: ICodeWindow) => T, fallback?: () => T): T | undefined {
const codeWindow = this.windowsMainService.getWindowById(windowId);
if (codeWindow) {
return fn(codeWindow);
}
if (fallback) {
return fallback();
}
return undefined;
}
}

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { OpenContext, IWindowConfiguration, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, INewWindowOptions, IURIToOpen } from 'vs/platform/windows/common/windows';
import { OpenContext, IWindowConfiguration, INativeOpenDialogOptions, IEnterWorkspaceResult, IURIToOpen } from 'vs/platform/windows/common/windows';
import { ParsedArgs } from 'vs/platform/environment/common/environment';
import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
@@ -11,6 +11,7 @@ import { IProcessEnvironment } from 'vs/base/common/platform';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import { URI } from 'vs/base/common/uri';
import { MessageBoxReturnValue, SaveDialogReturnValue, OpenDialogReturnValue, Rectangle, BrowserWindow, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions } from 'electron';
export interface IWindowState {
width?: number;
@@ -30,7 +31,7 @@ export const enum WindowMode {
export interface ICodeWindow {
readonly id: number;
readonly win: Electron.BrowserWindow;
readonly win: BrowserWindow;
readonly config: IWindowConfiguration;
readonly openedFolderUri?: URI;
@@ -49,13 +50,13 @@ export interface ICodeWindow {
addTabbedWindow(window: ICodeWindow): void;
load(config: IWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void;
load(config: IWindowConfiguration, isReload?: boolean): void;
reload(configuration?: IWindowConfiguration, cli?: ParsedArgs): void;
focus(): void;
close(): void;
getBounds(): Electron.Rectangle;
getBounds(): Rectangle;
send(channel: string, ...args: any[]): void;
sendWhenReady(channel: string, ...args: any[]): void;
@@ -66,7 +67,7 @@ export interface ICodeWindow {
hasHiddenTitleBarStyle(): boolean;
setRepresentedFilename(name: string): void;
getRepresentedFilename(): string;
onWindowTitleDoubleClick(): void;
handleTitleDoubleClick(): void;
updateTouchBar(items: ISerializableCommandAction[][]): void;
@@ -97,18 +98,19 @@ export interface IWindowsMainService {
closeWorkspace(win: ICodeWindow): void;
open(openConfig: IOpenConfiguration): ICodeWindow[];
openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string[], openConfig: IOpenConfiguration): void;
pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise<void>;
pickFolderAndOpen(options: INativeOpenDialogOptions): Promise<void>;
pickFileAndOpen(options: INativeOpenDialogOptions): Promise<void>;
pickWorkspaceAndOpen(options: INativeOpenDialogOptions): Promise<void>;
showMessageBox(options: Electron.MessageBoxOptions, win?: ICodeWindow): Promise<IMessageBoxResult>;
showSaveDialog(options: Electron.SaveDialogOptions, win?: ICodeWindow): Promise<string>;
showOpenDialog(options: Electron.OpenDialogOptions, win?: ICodeWindow): Promise<string[]>;
pickFileFolderAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise<void>;
pickFolderAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise<void>;
pickFileAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise<void>;
pickWorkspaceAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise<void>;
showMessageBox(options: MessageBoxOptions, win?: ICodeWindow): Promise<MessageBoxReturnValue>;
showSaveDialog(options: SaveDialogOptions, win?: ICodeWindow): Promise<SaveDialogReturnValue>;
showOpenDialog(options: OpenDialogOptions, win?: ICodeWindow): Promise<OpenDialogReturnValue>;
focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow;
getLastActiveWindow(): ICodeWindow | undefined;
waitForWindowCloseOrLoad(windowId: number): Promise<void>;
openNewWindow(context: OpenContext, options?: INewWindowOptions): ICodeWindow[];
openEmptyWindow(context: OpenContext, options?: { reuse?: boolean, remoteAuthority?: string }): ICodeWindow[];
openNewTabbedWindow(context: OpenContext): ICodeWindow[];
openExternal(url: string): Promise<boolean>;
sendToFocused(channel: string, ...args: any[]): void;
sendToAll(channel: string, payload: any, windowIdsToIgnore?: number[]): void;
getFocusedWindow(): ICodeWindow | undefined;

View File

@@ -1,424 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { assign } from 'vs/base/common/objects';
import { URI } from 'vs/base/common/uri';
import { IWindowsService, OpenContext, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IDevToolsOptions, INewWindowOptions, IOpenSettings, IURIToOpen } from 'vs/platform/windows/common/windows';
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
import { shell, crashReporter, app, Menu } from 'electron';
import { Event } from 'vs/base/common/event';
import { IURLService, IURLHandler } from 'vs/platform/url/common/url';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { IWindowsMainService, ISharedProcess, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history';
import { IHistoryMainService } from 'vs/platform/history/electron-main/historyMainService';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import { Schemas } from 'vs/base/common/network';
import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform';
import { ILogService } from 'vs/platform/log/common/log';
export class WindowsService extends Disposable implements IWindowsService, IURLHandler {
_serviceBrand: undefined;
private readonly disposables = this._register(new DisposableStore());
private _activeWindowId: number | undefined;
readonly onWindowOpen: Event<number> = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-created', (_, w: Electron.BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id));
readonly onWindowBlur: Event<number> = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-blur', (_, w: Electron.BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id));
readonly onWindowMaximize: Event<number> = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-maximize', (_, w: Electron.BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id));
readonly onWindowUnmaximize: Event<number> = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-unmaximize', (_, w: Electron.BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id));
readonly onWindowFocus: Event<number> = Event.any(
Event.map(Event.filter(Event.map(this.windowsMainService.onWindowsCountChanged, () => this.windowsMainService.getLastActiveWindow()), w => !!w), w => w!.id),
Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-focus', (_, w: Electron.BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id))
);
readonly onRecentlyOpenedChange: Event<void> = this.historyMainService.onRecentlyOpenedChange;
constructor(
private sharedProcess: ISharedProcess,
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IURLService urlService: IURLService,
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
@IHistoryMainService private readonly historyMainService: IHistoryMainService,
@ILogService private readonly logService: ILogService
) {
super();
urlService.registerHandler(this);
// remember last active window id
Event.latch(Event.any(this.onWindowOpen, this.onWindowFocus))
(id => this._activeWindowId = id, null, this.disposables);
}
async pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise<void> {
this.logService.trace('windowsService#pickFileFolderAndOpen');
this.windowsMainService.pickFileFolderAndOpen(options);
}
async pickFileAndOpen(options: INativeOpenDialogOptions): Promise<void> {
this.logService.trace('windowsService#pickFileAndOpen');
this.windowsMainService.pickFileAndOpen(options);
}
async pickFolderAndOpen(options: INativeOpenDialogOptions): Promise<void> {
this.logService.trace('windowsService#pickFolderAndOpen');
this.windowsMainService.pickFolderAndOpen(options);
}
async pickWorkspaceAndOpen(options: INativeOpenDialogOptions): Promise<void> {
this.logService.trace('windowsService#pickWorkspaceAndOpen');
this.windowsMainService.pickWorkspaceAndOpen(options);
}
async showMessageBox(windowId: number, options: Electron.MessageBoxOptions): Promise<IMessageBoxResult> {
this.logService.trace('windowsService#showMessageBox', windowId);
return this.withWindow(windowId, codeWindow => this.windowsMainService.showMessageBox(options, codeWindow), () => this.windowsMainService.showMessageBox(options))!;
}
async showSaveDialog(windowId: number, options: Electron.SaveDialogOptions): Promise<string> {
this.logService.trace('windowsService#showSaveDialog', windowId);
return this.withWindow(windowId, codeWindow => this.windowsMainService.showSaveDialog(options, codeWindow), () => this.windowsMainService.showSaveDialog(options))!;
}
async showOpenDialog(windowId: number, options: Electron.OpenDialogOptions): Promise<string[]> {
this.logService.trace('windowsService#showOpenDialog', windowId);
return this.withWindow(windowId, codeWindow => this.windowsMainService.showOpenDialog(options, codeWindow), () => this.windowsMainService.showOpenDialog(options))!;
}
async reloadWindow(windowId: number, args: ParsedArgs): Promise<void> {
this.logService.trace('windowsService#reloadWindow', windowId);
return this.withWindow(windowId, codeWindow => this.windowsMainService.reload(codeWindow, args));
}
async openDevTools(windowId: number, options?: IDevToolsOptions): Promise<void> {
this.logService.trace('windowsService#openDevTools', windowId);
return this.withWindow(windowId, codeWindow => codeWindow.win.webContents.openDevTools(options));
}
async toggleDevTools(windowId: number): Promise<void> {
this.logService.trace('windowsService#toggleDevTools', windowId);
return this.withWindow(windowId, codeWindow => {
const contents = codeWindow.win.webContents;
if (isMacintosh && codeWindow.hasHiddenTitleBarStyle() && !codeWindow.isFullScreen() && !contents.isDevToolsOpened()) {
contents.openDevTools({ mode: 'undocked' }); // due to https://github.com/electron/electron/issues/3647
} else {
contents.toggleDevTools();
}
});
}
async updateTouchBar(windowId: number, items: ISerializableCommandAction[][]): Promise<void> {
this.logService.trace('windowsService#updateTouchBar', windowId);
return this.withWindow(windowId, codeWindow => codeWindow.updateTouchBar(items));
}
async closeWorkspace(windowId: number): Promise<void> {
this.logService.trace('windowsService#closeWorkspace', windowId);
return this.withWindow(windowId, codeWindow => this.windowsMainService.closeWorkspace(codeWindow));
}
async enterWorkspace(windowId: number, path: URI): Promise<IEnterWorkspaceResult | undefined> {
this.logService.trace('windowsService#enterWorkspace', windowId);
return this.withWindow(windowId, codeWindow => this.windowsMainService.enterWorkspace(codeWindow, path));
}
async toggleFullScreen(windowId: number): Promise<void> {
this.logService.trace('windowsService#toggleFullScreen', windowId);
return this.withWindow(windowId, codeWindow => codeWindow.toggleFullScreen());
}
async setRepresentedFilename(windowId: number, fileName: string): Promise<void> {
this.logService.trace('windowsService#setRepresentedFilename', windowId);
return this.withWindow(windowId, codeWindow => codeWindow.setRepresentedFilename(fileName));
}
async addRecentlyOpened(recents: IRecent[]): Promise<void> {
this.logService.trace('windowsService#addRecentlyOpened');
this.historyMainService.addRecentlyOpened(recents);
}
async removeFromRecentlyOpened(paths: URI[]): Promise<void> {
this.logService.trace('windowsService#removeFromRecentlyOpened');
this.historyMainService.removeFromRecentlyOpened(paths);
}
async clearRecentlyOpened(): Promise<void> {
this.logService.trace('windowsService#clearRecentlyOpened');
this.historyMainService.clearRecentlyOpened();
}
async getRecentlyOpened(windowId: number): Promise<IRecentlyOpened> {
this.logService.trace('windowsService#getRecentlyOpened', windowId);
return this.withWindow(windowId, codeWindow => this.historyMainService.getRecentlyOpened(codeWindow.config.workspace, codeWindow.config.folderUri, codeWindow.config.filesToOpenOrCreate), () => this.historyMainService.getRecentlyOpened())!;
}
async newWindowTab(): Promise<void> {
this.logService.trace('windowsService#newWindowTab');
this.windowsMainService.openNewTabbedWindow(OpenContext.API);
}
async showPreviousWindowTab(): Promise<void> {
this.logService.trace('windowsService#showPreviousWindowTab');
Menu.sendActionToFirstResponder('selectPreviousTab:');
}
async showNextWindowTab(): Promise<void> {
this.logService.trace('windowsService#showNextWindowTab');
Menu.sendActionToFirstResponder('selectNextTab:');
}
async moveWindowTabToNewWindow(): Promise<void> {
this.logService.trace('windowsService#moveWindowTabToNewWindow');
Menu.sendActionToFirstResponder('moveTabToNewWindow:');
}
async mergeAllWindowTabs(): Promise<void> {
this.logService.trace('windowsService#mergeAllWindowTabs');
Menu.sendActionToFirstResponder('mergeAllWindows:');
}
async toggleWindowTabsBar(): Promise<void> {
this.logService.trace('windowsService#toggleWindowTabsBar');
Menu.sendActionToFirstResponder('toggleTabBar:');
}
async focusWindow(windowId: number): Promise<void> {
this.logService.trace('windowsService#focusWindow', windowId);
if (isMacintosh) {
return this.withWindow(windowId, codeWindow => codeWindow.win.show());
} else {
return this.withWindow(windowId, codeWindow => codeWindow.win.focus());
}
}
async closeWindow(windowId: number): Promise<void> {
this.logService.trace('windowsService#closeWindow', windowId);
return this.withWindow(windowId, codeWindow => codeWindow.win.close());
}
async isFocused(windowId: number): Promise<boolean> {
this.logService.trace('windowsService#isFocused', windowId);
return this.withWindow(windowId, codeWindow => codeWindow.win.isFocused(), () => false)!;
}
async isMaximized(windowId: number): Promise<boolean> {
this.logService.trace('windowsService#isMaximized', windowId);
return this.withWindow(windowId, codeWindow => codeWindow.win.isMaximized(), () => false)!;
}
async maximizeWindow(windowId: number): Promise<void> {
this.logService.trace('windowsService#maximizeWindow', windowId);
return this.withWindow(windowId, codeWindow => codeWindow.win.maximize());
}
async unmaximizeWindow(windowId: number): Promise<void> {
this.logService.trace('windowsService#unmaximizeWindow', windowId);
return this.withWindow(windowId, codeWindow => codeWindow.win.unmaximize());
}
async minimizeWindow(windowId: number): Promise<void> {
this.logService.trace('windowsService#minimizeWindow', windowId);
return this.withWindow(windowId, codeWindow => codeWindow.win.minimize());
}
async onWindowTitleDoubleClick(windowId: number): Promise<void> {
this.logService.trace('windowsService#onWindowTitleDoubleClick', windowId);
return this.withWindow(windowId, codeWindow => codeWindow.onWindowTitleDoubleClick());
}
async setDocumentEdited(windowId: number, flag: boolean): Promise<void> {
this.logService.trace('windowsService#setDocumentEdited', windowId);
return this.withWindow(windowId, codeWindow => {
if (codeWindow.win.isDocumentEdited() !== flag) {
codeWindow.win.setDocumentEdited(flag);
}
});
}
async openWindow(windowId: number, urisToOpen: IURIToOpen[], options: IOpenSettings): Promise<void> {
this.logService.trace('windowsService#openWindow');
if (!urisToOpen || !urisToOpen.length) {
return undefined;
}
this.windowsMainService.open({
context: OpenContext.API,
contextWindowId: windowId,
urisToOpen: urisToOpen,
cli: options.args ? { ...this.environmentService.args, ...options.args } : this.environmentService.args,
forceNewWindow: options.forceNewWindow,
forceReuseWindow: options.forceReuseWindow,
diffMode: options.diffMode,
addMode: options.addMode,
gotoLineMode: options.gotoLineMode,
noRecentEntry: options.noRecentEntry,
waitMarkerFileURI: options.waitMarkerFileURI
});
}
async openNewWindow(options?: INewWindowOptions): Promise<void> {
this.logService.trace('windowsService#openNewWindow ' + JSON.stringify(options));
this.windowsMainService.openNewWindow(OpenContext.API, options);
}
async openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise<void> {
this.logService.trace('windowsService#openExtensionDevelopmentHostWindow ' + JSON.stringify(args));
const extDevPaths = args.extensionDevelopmentPath;
if (extDevPaths) {
this.windowsMainService.openExtensionDevelopmentHostWindow(extDevPaths, {
context: OpenContext.API,
cli: args,
userEnv: Object.keys(env).length > 0 ? env : undefined
});
}
}
async getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]> {
this.logService.trace('windowsService#getWindows');
const windows = this.windowsMainService.getWindows();
return windows.map(window => ({
id: window.id,
workspace: window.openedWorkspace,
folderUri: window.openedFolderUri,
title: window.win.getTitle(),
filename: window.getRepresentedFilename()
}));
}
async getWindowCount(): Promise<number> {
this.logService.trace('windowsService#getWindowCount');
return this.windowsMainService.getWindows().length;
}
async getActiveWindowId(): Promise<number | undefined> {
return this._activeWindowId;
}
async openExternal(url: string): Promise<boolean> {
this.logService.trace('windowsService#openExternal');
shell.openExternal(url);
return true;
}
async startCrashReporter(config: Electron.CrashReporterStartOptions): Promise<void> {
this.logService.trace('windowsService#startCrashReporter');
crashReporter.start(config);
}
async quit(): Promise<void> {
this.logService.trace('windowsService#quit');
this.windowsMainService.quit();
}
async relaunch(options: { addArgs?: string[], removeArgs?: string[] }): Promise<void> {
this.logService.trace('windowsService#relaunch');
this.lifecycleMainService.relaunch(options);
}
async whenSharedProcessReady(): Promise<void> {
this.logService.trace('windowsService#whenSharedProcessReady');
return this.sharedProcess.whenReady();
}
async toggleSharedProcess(): Promise<void> {
this.logService.trace('windowsService#toggleSharedProcess');
this.sharedProcess.toggle();
}
async handleURL(uri: URI): Promise<boolean> {
// Catch file URLs
if (uri.authority === Schemas.file && !!uri.path) {
this.openFileForURI({ fileUri: URI.file(uri.fsPath) }); // using fsPath on a non-file URI...
return true;
}
return false;
}
private openFileForURI(uri: IURIToOpen): void {
const cli = assign(Object.create(null), this.environmentService.args);
const urisToOpen = [uri];
this.windowsMainService.open({ context: OpenContext.API, cli, urisToOpen, gotoLineMode: true });
}
async resolveProxy(windowId: number, url: string): Promise<string | undefined> {
return new Promise(resolve => {
const codeWindow = this.windowsMainService.getWindowById(windowId);
if (codeWindow) {
codeWindow.win.webContents.session.resolveProxy(url, proxy => {
resolve(proxy);
});
} else {
resolve();
}
});
}
private withWindow<T>(windowId: number, fn: (window: ICodeWindow) => T, fallback?: () => T): T | undefined {
const codeWindow = this.windowsMainService.getWindowById(windowId);
if (codeWindow) {
return fn(codeWindow);
}
if (fallback) {
return fallback();
}
return undefined;
}
}