mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-25 17:23:10 -05:00
Merge from vscode 8e0f348413f4f616c23a88ae30030efa85811973 (#6381)
* Merge from vscode 8e0f348413f4f616c23a88ae30030efa85811973 * disable strict null check
This commit is contained in:
@@ -9,7 +9,7 @@ import { WindowsManager } from 'vs/code/electron-main/windows';
|
||||
import { IWindowsService, OpenContext, ActiveWindowManager, IURIToOpen } from 'vs/platform/windows/common/windows';
|
||||
import { WindowsChannel } from 'vs/platform/windows/node/windowsIpc';
|
||||
import { WindowsService } from 'vs/platform/windows/electron-main/windowsService';
|
||||
import { ILifecycleService, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
|
||||
import { ILifecycleService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
|
||||
import { getShellEnvironment } from 'vs/code/node/shellEnv';
|
||||
import { IUpdateService } from 'vs/platform/update/common/update';
|
||||
import { UpdateChannel } from 'vs/platform/update/node/updateIpc';
|
||||
@@ -36,12 +36,10 @@ import { getDelayedChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc';
|
||||
import product from 'vs/platform/product/node/product';
|
||||
import pkg from 'vs/platform/product/node/package';
|
||||
import { ProxyAuthHandler } from 'vs/code/electron-main/auth';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
|
||||
import { IHistoryMainService } from 'vs/platform/history/common/history';
|
||||
import { isUndefinedOrNull, withUndefinedAsNull } from 'vs/base/common/types';
|
||||
import { KeyboardLayoutMonitor } from 'vs/code/electron-main/keyboard';
|
||||
import { withUndefinedAsNull } from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { WorkspacesChannel } from 'vs/platform/workspaces/node/workspacesIpc';
|
||||
import { IWorkspacesMainService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
|
||||
@@ -52,7 +50,7 @@ import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateServ
|
||||
import { IIssueService } from 'vs/platform/issue/common/issue';
|
||||
import { IssueChannel } from 'vs/platform/issue/node/issueIpc';
|
||||
import { IssueService } from 'vs/platform/issue/electron-main/issueService';
|
||||
import { LogLevelSetterChannel } from 'vs/platform/log/node/logIpc';
|
||||
import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc';
|
||||
import { setUnexpectedErrorHandler, onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener';
|
||||
import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver';
|
||||
@@ -63,7 +61,6 @@ import { MenubarChannel } from 'vs/platform/menubar/node/menubarIpc';
|
||||
import { hasArgs } from 'vs/platform/environment/node/argv';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { registerContextMenuListener } from 'vs/base/parts/contextmenu/electron-main/contextmenu';
|
||||
import { storeBackgroundColor } from 'vs/code/electron-main/theme';
|
||||
import { homedir } from 'os';
|
||||
import { join, sep } from 'vs/base/common/path';
|
||||
import { localize } from 'vs/nls';
|
||||
@@ -83,17 +80,19 @@ import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAg
|
||||
import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { statSync } from 'fs';
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
import { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnosticsService';
|
||||
import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsIpc';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
|
||||
|
||||
export class CodeApplication extends Disposable {
|
||||
|
||||
private static readonly MACHINE_ID_KEY = 'telemetry.machineId';
|
||||
private static readonly TRUE_MACHINE_ID_KEY = 'telemetry.trueMachineId';
|
||||
|
||||
private windowsMainService: IWindowsMainService;
|
||||
|
||||
private electronIpcServer: ElectronIPCServer;
|
||||
|
||||
private sharedProcess: SharedProcess;
|
||||
private sharedProcessClient: Promise<Client>;
|
||||
private windowsMainService: IWindowsMainService | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly mainIpcServer: Server,
|
||||
@@ -102,14 +101,12 @@ export class CodeApplication extends Disposable {
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@ILifecycleService private readonly lifecycleService: ILifecycleService,
|
||||
@IConfigurationService private readonly configurationService: ConfigurationService,
|
||||
@IStateService private readonly stateService: IStateService
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IStateService private readonly stateService: IStateService,
|
||||
@ISignService private readonly signService: ISignService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(mainIpcServer);
|
||||
this._register(configurationService);
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
@@ -120,12 +117,12 @@ export class CodeApplication extends Disposable {
|
||||
process.on('uncaughtException', err => this.onUnexpectedError(err));
|
||||
process.on('unhandledRejection', (reason: unknown) => onUnexpectedError(reason));
|
||||
|
||||
// Contextmenu via IPC support
|
||||
registerContextMenuListener();
|
||||
|
||||
// Dispose on shutdown
|
||||
this.lifecycleService.onWillShutdown(() => this.dispose());
|
||||
|
||||
// Contextmenu via IPC support
|
||||
registerContextMenuListener();
|
||||
|
||||
app.on('accessibility-support-changed', (event: Event, accessibilitySupportEnabled: boolean) => {
|
||||
if (this.windowsMainService) {
|
||||
this.windowsMainService.sendToAll('vscode:accessibilitySupportChanged', accessibilitySupportEnabled);
|
||||
@@ -142,8 +139,38 @@ export class CodeApplication extends Disposable {
|
||||
});
|
||||
|
||||
// Security related measures (https://electronjs.org/docs/tutorial/security)
|
||||
// DO NOT CHANGE without consulting the documentation
|
||||
app.on('web-contents-created', (event: Electron.Event, contents) => {
|
||||
//
|
||||
// !!! DO NOT CHANGE without consulting the documentation !!!
|
||||
//
|
||||
// app.on('remote-get-guest-web-contents', event => event.preventDefault()); // TODO@Ben TODO@Matt revisit this need for <webview>
|
||||
app.on('remote-require', (event, sender, module) => {
|
||||
this.logService.trace('App#on(remote-require): prevented');
|
||||
|
||||
event.preventDefault();
|
||||
});
|
||||
app.on('remote-get-global', (event, sender, module) => {
|
||||
this.logService.trace(`App#on(remote-get-global): prevented on ${module}`);
|
||||
|
||||
event.preventDefault();
|
||||
});
|
||||
app.on('remote-get-builtin', (event, sender, module) => {
|
||||
this.logService.trace(`App#on(remote-get-builtin): prevented on ${module}`);
|
||||
|
||||
if (module !== 'clipboard') {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
app.on('remote-get-current-window', event => {
|
||||
this.logService.trace(`App#on(remote-get-current-window): prevented`);
|
||||
|
||||
event.preventDefault();
|
||||
});
|
||||
app.on('remote-get-current-web-contents', event => {
|
||||
this.logService.trace(`App#on(remote-get-current-web-contents): prevented`);
|
||||
|
||||
event.preventDefault();
|
||||
});
|
||||
app.on('web-contents-created', (_event: Electron.Event, contents) => {
|
||||
contents.on('will-attach-webview', (event: Electron.Event, webPreferences, params) => {
|
||||
|
||||
const isValidWebviewSource = (source: string): boolean => {
|
||||
@@ -198,7 +225,7 @@ export class CodeApplication extends Disposable {
|
||||
event.preventDefault();
|
||||
|
||||
// Keep in array because more might come!
|
||||
macOpenFileURIs.push(getURIToOpenFromPathSync(path));
|
||||
macOpenFileURIs.push(this.getURIToOpenFromPathSync(path));
|
||||
|
||||
// Clear previous handler if any
|
||||
if (runningTimeout !== null) {
|
||||
@@ -215,6 +242,7 @@ export class CodeApplication extends Disposable {
|
||||
urisToOpen: macOpenFileURIs,
|
||||
preferNewWindow: true /* dropping on the dock or opening from finder prefers to open in a new window */
|
||||
});
|
||||
|
||||
macOpenFileURIs = [];
|
||||
runningTimeout = null;
|
||||
}
|
||||
@@ -222,7 +250,9 @@ export class CodeApplication extends Disposable {
|
||||
});
|
||||
|
||||
app.on('new-window-for-tab', () => {
|
||||
this.windowsMainService.openNewWindow(OpenContext.DESKTOP); //macOS native tab "+" button
|
||||
if (this.windowsMainService) {
|
||||
this.windowsMainService.openNewWindow(OpenContext.DESKTOP); //macOS native tab "+" button
|
||||
}
|
||||
});
|
||||
|
||||
ipc.on('vscode:exit', (event: Event, code: number) => {
|
||||
@@ -232,37 +262,26 @@ export class CodeApplication extends Disposable {
|
||||
this.lifecycleService.kill(code);
|
||||
});
|
||||
|
||||
ipc.on('vscode:fetchShellEnv', (event: Event) => {
|
||||
ipc.on('vscode:fetchShellEnv', async (event: Event) => {
|
||||
const webContents = event.sender;
|
||||
getShellEnvironment(this.logService).then(shellEnv => {
|
||||
|
||||
try {
|
||||
const shellEnv = await getShellEnvironment(this.logService, this.environmentService);
|
||||
if (!webContents.isDestroyed()) {
|
||||
webContents.send('vscode:acceptShellEnv', shellEnv);
|
||||
}
|
||||
}, err => {
|
||||
} catch (error) {
|
||||
if (!webContents.isDestroyed()) {
|
||||
webContents.send('vscode:acceptShellEnv', {});
|
||||
}
|
||||
|
||||
this.logService.error('Error fetching shell env', err);
|
||||
});
|
||||
});
|
||||
|
||||
ipc.on('vscode:broadcast', (event: Event, windowId: number, broadcast: { channel: string; payload: object; }) => {
|
||||
if (this.windowsMainService && broadcast.channel && !isUndefinedOrNull(broadcast.payload)) {
|
||||
this.logService.trace('IPC#vscode:broadcast', broadcast.channel, broadcast.payload);
|
||||
|
||||
// Handle specific events on main side
|
||||
this.onBroadcast(broadcast.channel, broadcast.payload);
|
||||
|
||||
// Send to all windows (except sender window)
|
||||
this.windowsMainService.sendToAll('vscode:broadcast', broadcast, [windowId]);
|
||||
this.logService.error('Error fetching shell env', error);
|
||||
}
|
||||
});
|
||||
|
||||
ipc.on('vscode:extensionHostDebug', (_: Event, windowId: number, broadcast: any) => {
|
||||
if (this.windowsMainService) {
|
||||
// Send to all windows (except sender window)
|
||||
this.windowsMainService.sendToAll('vscode:extensionHostDebug', broadcast, [windowId]);
|
||||
this.windowsMainService.sendToAll('vscode:extensionHostDebug', broadcast, [windowId]); // Send to all windows (except sender window)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -271,11 +290,25 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
ipc.on('vscode:reloadWindow', (event: Event) => event.sender.reload());
|
||||
|
||||
powerMonitor.on('resume', () => { // After waking up from sleep
|
||||
if (this.windowsMainService) {
|
||||
this.windowsMainService.sendToAll('vscode:osResume', undefined);
|
||||
}
|
||||
});
|
||||
// Some listeners after window opened
|
||||
(async () => {
|
||||
await this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen);
|
||||
|
||||
// After waking up from sleep (after window opened)
|
||||
powerMonitor.on('resume', () => {
|
||||
if (this.windowsMainService) {
|
||||
this.windowsMainService.sendToAll('vscode:osResume', undefined);
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard layout changes (after window opened)
|
||||
const nativeKeymap = await import('native-keymap');
|
||||
nativeKeymap.onDidChangeKeyboardLayout(() => {
|
||||
if (this.windowsMainService) {
|
||||
this.windowsMainService.sendToAll('vscode:keyboardLayoutChanged', false);
|
||||
}
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
private onUnexpectedError(err: Error): void {
|
||||
@@ -299,15 +332,7 @@ export class CodeApplication extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private onBroadcast(event: string, payload: object): void {
|
||||
|
||||
// Theme changes
|
||||
if (event === 'vscode:changeColorTheme' && typeof payload === 'string') {
|
||||
storeBackgroundColor(this.stateService, JSON.parse(payload));
|
||||
}
|
||||
}
|
||||
|
||||
startup(): Promise<void> {
|
||||
async startup(): Promise<void> {
|
||||
this.logService.debug('Starting VS Code');
|
||||
this.logService.debug(`from: ${this.environmentService.appRoot}`);
|
||||
this.logService.debug('args:', this.environmentService.args);
|
||||
@@ -335,64 +360,142 @@ export class CodeApplication extends Disposable {
|
||||
}
|
||||
|
||||
// Create Electron IPC Server
|
||||
this.electronIpcServer = new ElectronIPCServer();
|
||||
|
||||
const startupWithMachineId = (machineId: string) => {
|
||||
this.logService.trace(`Resolved machine identifier: ${machineId}`);
|
||||
|
||||
// Spawn shared process
|
||||
this.sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv);
|
||||
this.sharedProcessClient = this.sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main'));
|
||||
|
||||
// Services
|
||||
return this.initServices(machineId).then(appInstantiationService => {
|
||||
|
||||
// Create driver
|
||||
if (this.environmentService.driverHandle) {
|
||||
serveDriver(this.electronIpcServer, this.environmentService.driverHandle, this.environmentService, appInstantiationService).then(server => {
|
||||
this.logService.info('Driver started at:', this.environmentService.driverHandle);
|
||||
this._register(server);
|
||||
});
|
||||
}
|
||||
|
||||
// Setup Auth Handler
|
||||
const authHandler = appInstantiationService.createInstance(ProxyAuthHandler);
|
||||
this._register(authHandler);
|
||||
|
||||
// Open Windows
|
||||
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor));
|
||||
|
||||
// Post Open Windows Tasks
|
||||
appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor));
|
||||
|
||||
// Tracing: Stop tracing after windows are ready if enabled
|
||||
if (this.environmentService.args.trace) {
|
||||
this.stopTracingEventually(windows);
|
||||
}
|
||||
});
|
||||
};
|
||||
const electronIpcServer = new ElectronIPCServer();
|
||||
|
||||
// Resolve unique machine ID
|
||||
this.logService.trace('Resolving machine identifier...');
|
||||
const resolvedMachineId = this.resolveMachineId();
|
||||
if (typeof resolvedMachineId === 'string') {
|
||||
return startupWithMachineId(resolvedMachineId);
|
||||
} else {
|
||||
return resolvedMachineId.then(machineId => startupWithMachineId(machineId));
|
||||
const { machineId, trueMachineId } = await this.resolveMachineId();
|
||||
this.logService.trace(`Resolved machine identifier: ${machineId} (trueMachineId: ${trueMachineId})`);
|
||||
|
||||
// Spawn shared process after the first window has opened and 3s have passed
|
||||
const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv);
|
||||
const sharedProcessClient = sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main'));
|
||||
this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen).then(() => {
|
||||
this._register(new RunOnceScheduler(async () => {
|
||||
const userEnv = await getShellEnvironment(this.logService, this.environmentService);
|
||||
|
||||
sharedProcess.spawn(userEnv);
|
||||
}, 3000)).schedule();
|
||||
});
|
||||
|
||||
// Services
|
||||
const appInstantiationService = await this.createServices(machineId, trueMachineId, sharedProcess, sharedProcessClient);
|
||||
|
||||
// Create driver
|
||||
if (this.environmentService.driverHandle) {
|
||||
const server = await serveDriver(electronIpcServer, this.environmentService.driverHandle!, this.environmentService, appInstantiationService);
|
||||
|
||||
this.logService.info('Driver started at:', this.environmentService.driverHandle);
|
||||
this._register(server);
|
||||
}
|
||||
|
||||
// Setup Auth Handler
|
||||
const authHandler = appInstantiationService.createInstance(ProxyAuthHandler);
|
||||
this._register(authHandler);
|
||||
|
||||
// Open Windows
|
||||
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient));
|
||||
|
||||
// Post Open Windows Tasks
|
||||
this.afterWindowOpen();
|
||||
|
||||
// Tracing: Stop tracing after windows are ready if enabled
|
||||
if (this.environmentService.args.trace) {
|
||||
this.stopTracingEventually(windows);
|
||||
}
|
||||
}
|
||||
|
||||
private resolveMachineId(): string | Promise<string> {
|
||||
const machineId = this.stateService.getItem<string>(CodeApplication.MACHINE_ID_KEY);
|
||||
if (machineId) {
|
||||
return machineId;
|
||||
private async resolveMachineId(): Promise<{ machineId: string, trueMachineId?: string }> {
|
||||
|
||||
// We cache the machineId for faster lookups on startup
|
||||
// and resolve it only once initially if not cached
|
||||
let machineId = this.stateService.getItem<string>(CodeApplication.MACHINE_ID_KEY);
|
||||
if (!machineId) {
|
||||
machineId = await getMachineId();
|
||||
|
||||
this.stateService.setItem(CodeApplication.MACHINE_ID_KEY, machineId);
|
||||
}
|
||||
|
||||
return getMachineId().then(machineId => {
|
||||
this.stateService.setItem(CodeApplication.MACHINE_ID_KEY, machineId);
|
||||
// Check if machineId is hashed iBridge Device
|
||||
let trueMachineId: string | undefined;
|
||||
if (isMacintosh && machineId === '6c9d2bc8f91b89624add29c0abeae7fb42bf539fa1cdb2e3e57cd668fa9bcead') {
|
||||
trueMachineId = this.stateService.getItem<string>(CodeApplication.TRUE_MACHINE_ID_KEY);
|
||||
if (!trueMachineId) {
|
||||
trueMachineId = await getMachineId();
|
||||
|
||||
return machineId;
|
||||
});
|
||||
this.stateService.setItem(CodeApplication.TRUE_MACHINE_ID_KEY, trueMachineId);
|
||||
}
|
||||
}
|
||||
|
||||
return { machineId, trueMachineId };
|
||||
}
|
||||
|
||||
private async createServices(machineId: string, trueMachineId: string | undefined, sharedProcess: SharedProcess, sharedProcessClient: Promise<Client<string>>): Promise<IInstantiationService> {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
// Files
|
||||
const fileService = this._register(new FileService(this.logService));
|
||||
services.set(IFileService, fileService);
|
||||
|
||||
const diskFileSystemProvider = this._register(new DiskFileSystemProvider(this.logService));
|
||||
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
|
||||
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
services.set(IUpdateService, new SyncDescriptor(Win32UpdateService));
|
||||
break;
|
||||
|
||||
case 'linux':
|
||||
if (process.env.SNAP && process.env.SNAP_REVISION) {
|
||||
services.set(IUpdateService, new SyncDescriptor(SnapUpdateService, [process.env.SNAP, process.env.SNAP_REVISION]));
|
||||
} else {
|
||||
services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'darwin':
|
||||
services.set(IUpdateService, new SyncDescriptor(DarwinUpdateService));
|
||||
break;
|
||||
}
|
||||
|
||||
services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, [machineId, this.userEnv]));
|
||||
services.set(IWindowsService, new SyncDescriptor(WindowsService, [sharedProcess]));
|
||||
services.set(ILaunchService, new SyncDescriptor(LaunchService));
|
||||
|
||||
const diagnosticsChannel = getDelayedChannel(sharedProcessClient.then(client => client.getChannel('diagnostics')));
|
||||
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService, [diagnosticsChannel]));
|
||||
|
||||
services.set(IIssueService, new SyncDescriptor(IssueService, [machineId, this.userEnv]));
|
||||
services.set(IMenubarService, new SyncDescriptor(MenubarService));
|
||||
|
||||
const storageMainService = new StorageMainService(this.logService, this.environmentService);
|
||||
services.set(IStorageMainService, storageMainService);
|
||||
this.lifecycleService.onWillShutdown(e => e.join(storageMainService.close()));
|
||||
|
||||
const backupMainService = new BackupMainService(this.environmentService, this.configurationService, this.logService);
|
||||
services.set(IBackupMainService, backupMainService);
|
||||
|
||||
services.set(IHistoryMainService, new SyncDescriptor(HistoryMainService));
|
||||
services.set(IURLService, new SyncDescriptor(URLService));
|
||||
services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService));
|
||||
|
||||
// Telemetry
|
||||
if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
|
||||
const channel = getDelayedChannel(sharedProcessClient.then(client => client.getChannel('telemetryAppender')));
|
||||
const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(this.logService));
|
||||
const commonProperties = resolveCommonProperties(product.commit, pkg.version, machineId, this.environmentService.installSourcePath);
|
||||
const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath];
|
||||
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, trueMachineId };
|
||||
|
||||
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
|
||||
} else {
|
||||
services.set(ITelemetryService, NullTelemetryService);
|
||||
}
|
||||
|
||||
// Init services that require it
|
||||
await backupMainService.initialize();
|
||||
|
||||
return this.instantiationService.createChild(services);
|
||||
}
|
||||
|
||||
private stopTracingEventually(windows: ICodeWindow[]): void {
|
||||
@@ -408,12 +511,14 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`), path => {
|
||||
if (!timeout) {
|
||||
this.windowsMainService.showMessageBox({
|
||||
type: 'info',
|
||||
message: localize('trace.message', "Successfully created trace."),
|
||||
detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path),
|
||||
buttons: [localize('trace.ok', "Ok")]
|
||||
}, this.windowsMainService.getLastActiveWindow());
|
||||
if (this.windowsMainService) {
|
||||
this.windowsMainService.showMessageBox({
|
||||
type: 'info',
|
||||
message: localize('trace.message', "Successfully created trace."),
|
||||
detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path),
|
||||
buttons: [localize('trace.ok', "Ok")]
|
||||
}, this.windowsMainService.getLastActiveWindow());
|
||||
}
|
||||
} else {
|
||||
this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`);
|
||||
}
|
||||
@@ -430,72 +535,7 @@ export class CodeApplication extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
private initServices(machineId: string): Promise<IInstantiationService> {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
services.set(IUpdateService, new SyncDescriptor(Win32UpdateService));
|
||||
} else if (process.platform === 'linux') {
|
||||
if (process.env.SNAP && process.env.SNAP_REVISION) {
|
||||
services.set(IUpdateService, new SyncDescriptor(SnapUpdateService, [process.env.SNAP, process.env.SNAP_REVISION]));
|
||||
} else {
|
||||
services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService));
|
||||
}
|
||||
} else if (process.platform === 'darwin') {
|
||||
services.set(IUpdateService, new SyncDescriptor(DarwinUpdateService));
|
||||
}
|
||||
|
||||
services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, [machineId]));
|
||||
services.set(IWindowsService, new SyncDescriptor(WindowsService, [this.sharedProcess]));
|
||||
services.set(ILaunchService, new SyncDescriptor(LaunchService));
|
||||
services.set(IIssueService, new SyncDescriptor(IssueService, [machineId, this.userEnv]));
|
||||
services.set(IMenubarService, new SyncDescriptor(MenubarService));
|
||||
services.set(IStorageMainService, new SyncDescriptor(StorageMainService));
|
||||
services.set(IBackupMainService, new SyncDescriptor(BackupMainService));
|
||||
services.set(IHistoryMainService, new SyncDescriptor(HistoryMainService));
|
||||
services.set(IURLService, new SyncDescriptor(URLService));
|
||||
services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService));
|
||||
|
||||
// Telemetry
|
||||
if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
|
||||
const channel = getDelayedChannel(this.sharedProcessClient.then(c => c.getChannel('telemetryAppender')));
|
||||
const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(this.logService));
|
||||
const commonProperties = resolveCommonProperties(product.commit, pkg.version, machineId, this.environmentService.installSourcePath);
|
||||
const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath];
|
||||
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths };
|
||||
|
||||
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
|
||||
} else {
|
||||
services.set(ITelemetryService, NullTelemetryService);
|
||||
}
|
||||
|
||||
const appInstantiationService = this.instantiationService.createChild(services);
|
||||
|
||||
// Init services that require it
|
||||
return appInstantiationService.invokeFunction(accessor => Promise.all([
|
||||
this.initStorageService(accessor),
|
||||
this.initBackupService(accessor)
|
||||
])).then(() => appInstantiationService);
|
||||
}
|
||||
|
||||
private initStorageService(accessor: ServicesAccessor): Promise<void> {
|
||||
const storageMainService = accessor.get(IStorageMainService) as StorageMainService;
|
||||
|
||||
// Ensure to close storage on shutdown
|
||||
this.lifecycleService.onWillShutdown(e => e.join(storageMainService.close()));
|
||||
|
||||
return Promise.resolve();
|
||||
|
||||
}
|
||||
|
||||
private initBackupService(accessor: ServicesAccessor): Promise<void> {
|
||||
const backupMainService = accessor.get(IBackupMainService) as BackupMainService;
|
||||
|
||||
return backupMainService.initialize();
|
||||
}
|
||||
|
||||
private openFirstWindow(accessor: ServicesAccessor): ICodeWindow[] {
|
||||
const appInstantiationService = accessor.get(IInstantiationService);
|
||||
private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise<Client<string>>): ICodeWindow[] {
|
||||
|
||||
// Register more Main IPC services
|
||||
const launchService = accessor.get(ILaunchService);
|
||||
@@ -505,48 +545,48 @@ export class CodeApplication extends Disposable {
|
||||
// Register more Electron IPC services
|
||||
const updateService = accessor.get(IUpdateService);
|
||||
const updateChannel = new UpdateChannel(updateService);
|
||||
this.electronIpcServer.registerChannel('update', updateChannel);
|
||||
electronIpcServer.registerChannel('update', updateChannel);
|
||||
|
||||
const issueService = accessor.get(IIssueService);
|
||||
const issueChannel = new IssueChannel(issueService);
|
||||
this.electronIpcServer.registerChannel('issue', issueChannel);
|
||||
electronIpcServer.registerChannel('issue', issueChannel);
|
||||
|
||||
const workspacesService = accessor.get(IWorkspacesMainService);
|
||||
const workspacesChannel = appInstantiationService.createInstance(WorkspacesChannel, workspacesService);
|
||||
this.electronIpcServer.registerChannel('workspaces', workspacesChannel);
|
||||
const workspacesChannel = new WorkspacesChannel(workspacesService);
|
||||
electronIpcServer.registerChannel('workspaces', workspacesChannel);
|
||||
|
||||
const windowsService = accessor.get(IWindowsService);
|
||||
const windowsChannel = new WindowsChannel(windowsService);
|
||||
this.electronIpcServer.registerChannel('windows', windowsChannel);
|
||||
this.sharedProcessClient.then(client => client.registerChannel('windows', windowsChannel));
|
||||
electronIpcServer.registerChannel('windows', windowsChannel);
|
||||
sharedProcessClient.then(client => client.registerChannel('windows', windowsChannel));
|
||||
|
||||
const menubarService = accessor.get(IMenubarService);
|
||||
const menubarChannel = new MenubarChannel(menubarService);
|
||||
this.electronIpcServer.registerChannel('menubar', menubarChannel);
|
||||
electronIpcServer.registerChannel('menubar', menubarChannel);
|
||||
|
||||
const urlService = accessor.get(IURLService);
|
||||
const urlChannel = new URLServiceChannel(urlService);
|
||||
this.electronIpcServer.registerChannel('url', urlChannel);
|
||||
electronIpcServer.registerChannel('url', urlChannel);
|
||||
|
||||
const storageMainService = accessor.get(IStorageMainService);
|
||||
const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService as StorageMainService));
|
||||
this.electronIpcServer.registerChannel('storage', storageChannel);
|
||||
electronIpcServer.registerChannel('storage', storageChannel);
|
||||
|
||||
// Log level management
|
||||
const logLevelChannel = new LogLevelSetterChannel(accessor.get(ILogService));
|
||||
this.electronIpcServer.registerChannel('loglevel', logLevelChannel);
|
||||
this.sharedProcessClient.then(client => client.registerChannel('loglevel', logLevelChannel));
|
||||
electronIpcServer.registerChannel('loglevel', logLevelChannel);
|
||||
sharedProcessClient.then(client => client.registerChannel('loglevel', logLevelChannel));
|
||||
|
||||
// Lifecycle
|
||||
(this.lifecycleService as LifecycleService).ready();
|
||||
// Signal phase: ready (services set)
|
||||
this.lifecycleService.phase = LifecycleMainPhase.Ready;
|
||||
|
||||
// Propagate to clients
|
||||
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); // TODO@Joao: unfold this
|
||||
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
|
||||
|
||||
// Create a URL handler which forwards to the last active window
|
||||
const activeWindowManager = new ActiveWindowManager(windowsService);
|
||||
const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
|
||||
const urlHandlerChannel = this.electronIpcServer.getChannel('urlHandler', activeWindowRouter);
|
||||
const urlHandlerChannel = electronIpcServer.getChannel('urlHandler', activeWindowRouter);
|
||||
const multiplexURLHandler = new URLHandlerChannelClient(urlHandlerChannel);
|
||||
|
||||
// On Mac, Code can be running without any open windows, so we must create a window to handle urls,
|
||||
@@ -555,15 +595,17 @@ export class CodeApplication extends Disposable {
|
||||
const environmentService = accessor.get(IEnvironmentService);
|
||||
|
||||
urlService.registerHandler({
|
||||
handleURL(uri: URI): Promise<boolean> {
|
||||
async handleURL(uri: URI): Promise<boolean> {
|
||||
if (windowsMainService.getWindowCount() === 0) {
|
||||
const cli = { ...environmentService.args, goto: true };
|
||||
const [window] = windowsMainService.open({ context: OpenContext.API, cli, forceEmpty: true });
|
||||
|
||||
return window.ready().then(() => urlService.open(uri));
|
||||
await window.ready();
|
||||
|
||||
return urlService.open(uri);
|
||||
}
|
||||
|
||||
return Promise.resolve(false);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -574,11 +616,9 @@ export class CodeApplication extends Disposable {
|
||||
// Watch Electron URLs and forward them to the UrlService
|
||||
const args = this.environmentService.args;
|
||||
const urls = args['open-url'] ? args._urls : [];
|
||||
const urlListener = new ElectronURLListener(urls || [], urlService, this.windowsMainService);
|
||||
const urlListener = new ElectronURLListener(urls || [], urlService, windowsMainService);
|
||||
this._register(urlListener);
|
||||
|
||||
this.windowsMainService.ready(this.userEnv);
|
||||
|
||||
// Open our first window
|
||||
const macOpenFiles: string[] = (<any>global).macOpenFiles;
|
||||
const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
|
||||
@@ -588,9 +628,9 @@ export class CodeApplication extends Disposable {
|
||||
const noRecentEntry = args['skip-add-to-recently-opened'] === true;
|
||||
const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined;
|
||||
|
||||
// new window if "-n" was used without paths
|
||||
if (args['new-window'] && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
|
||||
// new window if "-n" was used without paths
|
||||
return this.windowsMainService.open({
|
||||
return windowsMainService.open({
|
||||
context,
|
||||
cli: args,
|
||||
forceNewWindow: true,
|
||||
@@ -601,12 +641,12 @@ export class CodeApplication extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
// mac: open-file event received on startup
|
||||
if (macOpenFiles && macOpenFiles.length && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
|
||||
// mac: open-file event received on startup
|
||||
return this.windowsMainService.open({
|
||||
return windowsMainService.open({
|
||||
context: OpenContext.DOCK,
|
||||
cli: args,
|
||||
urisToOpen: macOpenFiles.map(getURIToOpenFromPathSync),
|
||||
urisToOpen: macOpenFiles.map(file => this.getURIToOpenFromPathSync(file)),
|
||||
noRecentEntry,
|
||||
waitMarkerFileURI,
|
||||
initialStartup: true
|
||||
@@ -614,7 +654,7 @@ export class CodeApplication extends Disposable {
|
||||
}
|
||||
|
||||
// default: read paths from cli
|
||||
return this.windowsMainService.open({
|
||||
return windowsMainService.open({
|
||||
context,
|
||||
cli: args,
|
||||
forceNewWindow: args['new-window'] || (!hasCliArgs && args['unity-launch']),
|
||||
@@ -625,61 +665,30 @@ export class CodeApplication extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
private afterWindowOpen(accessor: ServicesAccessor): void {
|
||||
const windowsMainService = accessor.get(IWindowsMainService);
|
||||
const historyMainService = accessor.get(IHistoryMainService);
|
||||
|
||||
if (isWindows) {
|
||||
|
||||
// Setup Windows mutex
|
||||
try {
|
||||
const Mutex = (require.__$__nodeRequire('windows-mutex') as any).Mutex;
|
||||
const windowsMutex = new Mutex(product.win32MutexName);
|
||||
this._register(toDisposable(() => windowsMutex.release()));
|
||||
} catch (e) {
|
||||
if (!this.environmentService.isBuilt) {
|
||||
windowsMainService.showMessageBox({
|
||||
title: product.nameLong,
|
||||
type: 'warning',
|
||||
message: 'Failed to load windows-mutex!',
|
||||
detail: e.toString(),
|
||||
noLink: true
|
||||
});
|
||||
}
|
||||
private getURIToOpenFromPathSync(path: string): IURIToOpen {
|
||||
try {
|
||||
const fileStat = statSync(path);
|
||||
if (fileStat.isDirectory()) {
|
||||
return { folderUri: URI.file(path) };
|
||||
}
|
||||
|
||||
// Ensure Windows foreground love module
|
||||
try {
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
require.__$__nodeRequire('windows-foreground-love');
|
||||
} catch (e) {
|
||||
if (!this.environmentService.isBuilt) {
|
||||
windowsMainService.showMessageBox({
|
||||
title: product.nameLong,
|
||||
type: 'warning',
|
||||
message: 'Failed to load windows-foreground-love!',
|
||||
detail: e.toString(),
|
||||
noLink: true
|
||||
});
|
||||
}
|
||||
if (hasWorkspaceFileExtension(path)) {
|
||||
return { workspaceUri: URI.file(path) };
|
||||
}
|
||||
} catch (error) {
|
||||
// ignore errors
|
||||
}
|
||||
|
||||
return { fileUri: URI.file(path) };
|
||||
}
|
||||
|
||||
private afterWindowOpen(): void {
|
||||
|
||||
// Signal phase: after window open
|
||||
this.lifecycleService.phase = LifecycleMainPhase.AfterWindowOpen;
|
||||
|
||||
// Remote Authorities
|
||||
this.handleRemoteAuthorities();
|
||||
|
||||
// Keyboard layout changes
|
||||
KeyboardLayoutMonitor.INSTANCE.onDidChangeKeyboardLayout(() => {
|
||||
this.windowsMainService.sendToAll('vscode:keyboardLayoutChanged', false);
|
||||
});
|
||||
|
||||
// Jump List
|
||||
historyMainService.updateWindowsJumpList();
|
||||
historyMainService.onRecentlyOpenedChange(() => historyMainService.updateWindowsJumpList());
|
||||
|
||||
// Start shared process after a while
|
||||
const sharedProcessSpawn = this._register(new RunOnceScheduler(() => getShellEnvironment(this.logService).then(userEnv => this.sharedProcess.spawn(userEnv)), 3000));
|
||||
sharedProcessSpawn.schedule();
|
||||
}
|
||||
|
||||
private handleRemoteAuthorities(): void {
|
||||
@@ -692,18 +701,21 @@ export class CodeApplication extends Disposable {
|
||||
private readonly _connection: Promise<ManagementPersistentConnection>;
|
||||
private readonly _disposeRunner: RunOnceScheduler;
|
||||
|
||||
constructor(authority: string, host: string, port: number) {
|
||||
constructor(authority: string, host: string, port: number, signService: ISignService) {
|
||||
this._authority = authority;
|
||||
|
||||
const options: IConnectionOptions = {
|
||||
isBuilt: isBuilt,
|
||||
isBuilt,
|
||||
commit: product.commit,
|
||||
webSocketFactory: nodeWebSocketFactory,
|
||||
addressProvider: {
|
||||
getAddress: () => {
|
||||
return Promise.resolve({ host, port });
|
||||
}
|
||||
}
|
||||
},
|
||||
signService
|
||||
};
|
||||
|
||||
this._connection = connectRemoteAgentManagement(options, authority, `main`);
|
||||
this._disposeRunner = new RunOnceScheduler(() => this.dispose(), 5000);
|
||||
}
|
||||
@@ -711,14 +723,13 @@ export class CodeApplication extends Disposable {
|
||||
dispose(): void {
|
||||
this._disposeRunner.dispose();
|
||||
connectionPool.delete(this._authority);
|
||||
this._connection.then((connection) => {
|
||||
connection.dispose();
|
||||
});
|
||||
this._connection.then(connection => connection.dispose());
|
||||
}
|
||||
|
||||
async getClient(): Promise<Client<RemoteAgentConnectionContext>> {
|
||||
this._disposeRunner.schedule();
|
||||
const connection = await this._connection;
|
||||
|
||||
return connection.client;
|
||||
}
|
||||
}
|
||||
@@ -726,7 +737,9 @@ export class CodeApplication extends Disposable {
|
||||
const resolvedAuthorities = new Map<string, ResolvedAuthority>();
|
||||
ipc.on('vscode:remoteAuthorityResolved', (event: Electron.Event, data: ResolvedAuthority) => {
|
||||
this.logService.info('Received resolved authority', data.authority);
|
||||
|
||||
resolvedAuthorities.set(data.authority, data);
|
||||
|
||||
// Make sure to close and remove any existing connections
|
||||
if (connectionPool.has(data.authority)) {
|
||||
connectionPool.get(data.authority)!.dispose();
|
||||
@@ -735,15 +748,19 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
const resolveAuthority = (authority: string): ResolvedAuthority | null => {
|
||||
this.logService.info('Resolving authority', authority);
|
||||
|
||||
if (authority.indexOf('+') >= 0) {
|
||||
if (resolvedAuthorities.has(authority)) {
|
||||
return withUndefinedAsNull(resolvedAuthorities.get(authority));
|
||||
}
|
||||
|
||||
this.logService.info('Didnot find resolved authority for', authority);
|
||||
|
||||
return null;
|
||||
} else {
|
||||
const [host, strPort] = authority.split(':');
|
||||
const port = parseInt(strPort, 10);
|
||||
|
||||
return { authority, host, port };
|
||||
}
|
||||
};
|
||||
@@ -752,6 +769,7 @@ export class CodeApplication extends Disposable {
|
||||
if (request.method !== 'GET') {
|
||||
return callback(undefined);
|
||||
}
|
||||
|
||||
const uri = URI.parse(request.url);
|
||||
|
||||
let activeConnection: ActiveConnection | undefined;
|
||||
@@ -763,9 +781,11 @@ export class CodeApplication extends Disposable {
|
||||
callback(undefined);
|
||||
return;
|
||||
}
|
||||
activeConnection = new ActiveConnection(uri.authority, resolvedAuthority.host, resolvedAuthority.port);
|
||||
|
||||
activeConnection = new ActiveConnection(uri.authority, resolvedAuthority.host, resolvedAuthority.port, this.signService);
|
||||
connectionPool.set(uri.authority, activeConnection);
|
||||
}
|
||||
|
||||
try {
|
||||
const rawClient = await activeConnection!.getClient();
|
||||
if (connectionPool.has(uri.authority)) { // not disposed in the meantime
|
||||
@@ -784,16 +804,3 @@ export class CodeApplication extends Disposable {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getURIToOpenFromPathSync(path: string): IURIToOpen {
|
||||
try {
|
||||
const fileStat = statSync(path);
|
||||
if (fileStat.isDirectory()) {
|
||||
return { folderUri: URI.file(path) };
|
||||
} else if (hasWorkspaceFileExtension(path)) {
|
||||
return { workspaceUri: URI.file(path) };
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
return { fileUri: URI.file(path) };
|
||||
}
|
||||
|
||||
@@ -54,7 +54,11 @@ export class ProxyAuthHandler {
|
||||
width: 450,
|
||||
height: 220,
|
||||
show: true,
|
||||
title: 'VS Code'
|
||||
title: 'VS Code',
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
webviewTag: true
|
||||
}
|
||||
};
|
||||
|
||||
const focusedWindow = this.windowsMainService.getFocusedWindow();
|
||||
|
||||
@@ -1,32 +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 * as nativeKeymap from 'native-keymap';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export class KeyboardLayoutMonitor {
|
||||
|
||||
public static readonly INSTANCE = new KeyboardLayoutMonitor();
|
||||
|
||||
private readonly _emitter: Emitter<void>;
|
||||
private _registered: boolean;
|
||||
|
||||
private constructor() {
|
||||
this._emitter = new Emitter<void>();
|
||||
this._registered = false;
|
||||
}
|
||||
|
||||
public onDidChangeKeyboardLayout(callback: () => void): IDisposable {
|
||||
if (!this._registered) {
|
||||
this._registered = true;
|
||||
|
||||
nativeKeymap.onDidChangeKeyboardLayout(() => {
|
||||
this._emitter.fire();
|
||||
});
|
||||
}
|
||||
return this._emitter.event(callback);
|
||||
}
|
||||
}
|
||||
@@ -1,153 +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 * as os from 'os';
|
||||
import * as cp from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
|
||||
import * as path from 'vs/base/common/path';
|
||||
import { localize } from 'vs/nls';
|
||||
import product from 'vs/platform/product/node/product';
|
||||
import { IRequestService } from 'vs/platform/request/node/request';
|
||||
import { IRequestContext } from 'vs/base/node/request';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ILaunchService } from 'vs/platform/launch/electron-main/launchService';
|
||||
|
||||
interface PostResult {
|
||||
readonly blob_id: string;
|
||||
}
|
||||
|
||||
class Endpoint {
|
||||
private constructor(
|
||||
public readonly url: string
|
||||
) { }
|
||||
|
||||
public static getFromProduct(): Endpoint | undefined {
|
||||
const logUploaderUrl = product.logUploaderUrl;
|
||||
return logUploaderUrl ? new Endpoint(logUploaderUrl) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function uploadLogs(
|
||||
launchService: ILaunchService,
|
||||
requestService: IRequestService,
|
||||
environmentService: IEnvironmentService
|
||||
): Promise<any> {
|
||||
const endpoint = Endpoint.getFromProduct();
|
||||
if (!endpoint) {
|
||||
console.error(localize('invalidEndpoint', 'Invalid log uploader endpoint'));
|
||||
return;
|
||||
}
|
||||
|
||||
const logsPath = await launchService.getLogsPath();
|
||||
|
||||
if (await promptUserToConfirmLogUpload(logsPath, environmentService)) {
|
||||
console.log(localize('beginUploading', 'Uploading...'));
|
||||
const outZip = await zipLogs(logsPath);
|
||||
const result = await postLogs(endpoint, outZip, requestService);
|
||||
console.log(localize('didUploadLogs', 'Upload successful! Log file ID: {0}', result.blob_id));
|
||||
}
|
||||
}
|
||||
|
||||
function promptUserToConfirmLogUpload(
|
||||
logsPath: string,
|
||||
environmentService: IEnvironmentService
|
||||
): boolean {
|
||||
const confirmKey = 'iConfirmLogsUpload';
|
||||
if ((environmentService.args['upload-logs'] || '').toLowerCase() === confirmKey.toLowerCase()) {
|
||||
return true;
|
||||
} else {
|
||||
const message = localize('logUploadPromptHeader', 'You are about to upload your session logs to a secure Microsoft endpoint that only Microsoft\'s members of the VS Code team can access.')
|
||||
+ '\n\n' + localize('logUploadPromptBody', 'Session logs may contain personal information such as full paths or file contents. Please review and redact your session log files here: \'{0}\'', logsPath)
|
||||
+ '\n\n' + localize('logUploadPromptBodyDetails', 'By continuing you confirm that you have reviewed and redacted your session log files and that you agree to Microsoft using them to debug VS Code.')
|
||||
+ '\n\n' + localize('logUploadPromptAcceptInstructions', 'Please run code with \'--upload-logs={0}\' to proceed with upload', confirmKey);
|
||||
console.log(message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function postLogs(
|
||||
endpoint: Endpoint,
|
||||
outZip: string,
|
||||
requestService: IRequestService
|
||||
): Promise<PostResult> {
|
||||
const dotter = setInterval(() => console.log('.'), 5000);
|
||||
let result: IRequestContext;
|
||||
try {
|
||||
result = await requestService.request({
|
||||
url: endpoint.url,
|
||||
type: 'POST',
|
||||
data: Buffer.from(fs.readFileSync(outZip)).toString('base64'),
|
||||
headers: {
|
||||
'Content-Type': 'application/zip'
|
||||
}
|
||||
}, CancellationToken.None);
|
||||
} catch (e) {
|
||||
clearInterval(dotter);
|
||||
console.log(localize('postError', 'Error posting logs: {0}', e));
|
||||
throw e;
|
||||
}
|
||||
|
||||
return new Promise<PostResult>((resolve, reject) => {
|
||||
const parts: Buffer[] = [];
|
||||
result.stream.on('data', data => {
|
||||
parts.push(data);
|
||||
});
|
||||
|
||||
result.stream.on('end', () => {
|
||||
clearInterval(dotter);
|
||||
try {
|
||||
const response = Buffer.concat(parts).toString('utf-8');
|
||||
if (result.res.statusCode === 200) {
|
||||
resolve(JSON.parse(response));
|
||||
} else {
|
||||
const errorMessage = localize('responseError', 'Error posting logs. Got {0} — {1}', result.res.statusCode, response);
|
||||
console.log(errorMessage);
|
||||
reject(new Error(errorMessage));
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(localize('parseError', 'Error parsing response'));
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function zipLogs(
|
||||
logsPath: string
|
||||
): Promise<string> {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vscode-log-upload'));
|
||||
const outZip = path.join(tempDir, 'logs.zip');
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
doZip(logsPath, outZip, tempDir, (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
console.error(localize('zipError', 'Error zipping logs: {0}', err.message));
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(outZip);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function doZip(
|
||||
logsPath: string,
|
||||
outZip: string,
|
||||
tempDir: string,
|
||||
callback: (error: Error, stdout: string, stderr: string) => void
|
||||
) {
|
||||
switch (os.platform()) {
|
||||
case 'win32':
|
||||
// Copy directory first to avoid file locking issues
|
||||
const sub = path.join(tempDir, 'sub');
|
||||
return cp.execFile('powershell', ['-Command',
|
||||
`[System.IO.Directory]::CreateDirectory("${sub}"); Copy-Item -recurse "${logsPath}" "${sub}"; Compress-Archive -Path "${sub}" -DestinationPath "${outZip}"`],
|
||||
{ cwd: logsPath },
|
||||
callback);
|
||||
default:
|
||||
return cp.execFile('zip', ['-r', outZip, '.'], { cwd: logsPath }, callback);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/code/code.main';
|
||||
import 'vs/platform/update/node/update.config.contribution';
|
||||
import { app, dialog } from 'electron';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
@@ -26,82 +26,191 @@ import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/
|
||||
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
|
||||
import { IRequestService } from 'vs/platform/request/node/request';
|
||||
import { IRequestService } from 'vs/platform/request/common/request';
|
||||
import { RequestService } from 'vs/platform/request/electron-main/requestService';
|
||||
import * as fs from 'fs';
|
||||
import { CodeApplication } from 'vs/code/electron-main/app';
|
||||
import { localize } from 'vs/nls';
|
||||
import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { IDiagnosticsService, DiagnosticsService } from 'vs/platform/diagnostics/electron-main/diagnosticsService';
|
||||
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { BufferLogService } from 'vs/platform/log/common/bufferLog';
|
||||
import { uploadLogs } from 'vs/code/electron-main/logUploader';
|
||||
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
|
||||
import { Client } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
import { SignService } from 'vs/platform/sign/node/signService';
|
||||
import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsIpc';
|
||||
|
||||
class ExpectedError extends Error {
|
||||
readonly isExpected = true;
|
||||
}
|
||||
|
||||
function setupIPC(accessor: ServicesAccessor): Promise<Server> {
|
||||
const logService = accessor.get(ILogService);
|
||||
const environmentService = accessor.get(IEnvironmentService);
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
class CodeMain {
|
||||
|
||||
function allowSetForegroundWindow(service: LaunchChannelClient): Promise<void> {
|
||||
let promise: Promise<void> = Promise.resolve();
|
||||
if (platform.isWindows) {
|
||||
promise = service.getMainProcessId()
|
||||
.then(processId => {
|
||||
logService.trace('Sending some foreground love to the running instance:', processId);
|
||||
main(): void {
|
||||
|
||||
try {
|
||||
const { allowSetForegroundWindow } = require.__$__nodeRequire('windows-foreground-love');
|
||||
allowSetForegroundWindow(processId);
|
||||
} catch (e) {
|
||||
// noop
|
||||
}
|
||||
});
|
||||
// Set the error handler early enough so that we are not getting the
|
||||
// default electron error dialog popping up
|
||||
setUnexpectedErrorHandler(err => console.error(err));
|
||||
|
||||
// Parse arguments
|
||||
let args: ParsedArgs;
|
||||
try {
|
||||
args = parseMainProcessArgv(process.argv);
|
||||
args = validatePaths(args);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
app.exit(1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return promise;
|
||||
// If we are started with --wait create a random temporary file
|
||||
// and pass it over to the starting instance. We can use this file
|
||||
// to wait for it to be deleted to monitor that the edited file
|
||||
// is closed and then exit the waiting process.
|
||||
//
|
||||
// Note: we are not doing this if the wait marker has been already
|
||||
// added as argument. This can happen if Code was started from CLI.
|
||||
if (args.wait && !args.waitMarkerFilePath) {
|
||||
const waitMarkerFilePath = createWaitMarkerFile(args.verbose);
|
||||
if (waitMarkerFilePath) {
|
||||
addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath);
|
||||
args.waitMarkerFilePath = waitMarkerFilePath;
|
||||
}
|
||||
}
|
||||
|
||||
// Launch
|
||||
this.startup(args);
|
||||
}
|
||||
|
||||
function setup(retry: boolean): Promise<Server> {
|
||||
return serve(environmentService.mainIPCHandle).then(server => {
|
||||
private async startup(args: ParsedArgs): Promise<void> {
|
||||
|
||||
// Print --status usage info
|
||||
if (environmentService.args.status) {
|
||||
logService.warn('Warning: The --status argument can only be used if Code is already running. Please run it again after Code has started.');
|
||||
throw new ExpectedError('Terminating...');
|
||||
}
|
||||
// We need to buffer the spdlog logs until we are sure
|
||||
// we are the only instance running, otherwise we'll have concurrent
|
||||
// log file access on Windows (https://github.com/Microsoft/vscode/issues/41218)
|
||||
const bufferLogService = new BufferLogService();
|
||||
|
||||
// Log uploader usage info
|
||||
if (typeof environmentService.args['upload-logs'] !== 'undefined') {
|
||||
logService.warn('Warning: The --upload-logs argument can only be used if Code is already running. Please run it again after Code has started.');
|
||||
throw new ExpectedError('Terminating...');
|
||||
}
|
||||
const [instantiationService, instanceEnvironment] = this.createServices(args, bufferLogService);
|
||||
try {
|
||||
|
||||
// dock might be hidden at this case due to a retry
|
||||
if (platform.isMacintosh) {
|
||||
app.dock.show();
|
||||
}
|
||||
// Init services
|
||||
await instantiationService.invokeFunction(async accessor => {
|
||||
const environmentService = accessor.get(IEnvironmentService);
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
const stateService = accessor.get(IStateService);
|
||||
|
||||
// Set the VSCODE_PID variable here when we are sure we are the first
|
||||
// instance to startup. Otherwise we would wrongly overwrite the PID
|
||||
process.env['VSCODE_PID'] = String(process.pid);
|
||||
try {
|
||||
await this.initServices(environmentService, configurationService as ConfigurationService, stateService as StateService);
|
||||
} catch (error) {
|
||||
|
||||
return server;
|
||||
}, err => {
|
||||
// Show a dialog for errors that can be resolved by the user
|
||||
this.handleStartupDataDirError(environmentService, error);
|
||||
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
// Startup
|
||||
await instantiationService.invokeFunction(async accessor => {
|
||||
const environmentService = accessor.get(IEnvironmentService);
|
||||
const logService = accessor.get(ILogService);
|
||||
const lifecycleService = accessor.get(ILifecycleService);
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
|
||||
const mainIpcServer = await this.doStartup(logService, environmentService, lifecycleService, instantiationService, true);
|
||||
|
||||
bufferLogService.logger = new SpdLogService('main', environmentService.logsPath, bufferLogService.getLevel());
|
||||
once(lifecycleService.onWillShutdown)(() => (configurationService as ConfigurationService).dispose());
|
||||
|
||||
return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup();
|
||||
});
|
||||
} catch (error) {
|
||||
instantiationService.invokeFunction(this.quit, error);
|
||||
}
|
||||
}
|
||||
|
||||
private createServices(args: ParsedArgs, bufferLogService: BufferLogService): [IInstantiationService, typeof process.env] {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
const environmentService = new EnvironmentService(args, process.execPath);
|
||||
const instanceEnvironment = this.patchEnvironment(environmentService); // Patch `process.env` with the instance's environment
|
||||
services.set(IEnvironmentService, environmentService);
|
||||
|
||||
const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]);
|
||||
process.once('exit', () => logService.dispose());
|
||||
services.set(ILogService, logService);
|
||||
|
||||
services.set(IConfigurationService, new ConfigurationService(environmentService.settingsResource));
|
||||
services.set(ILifecycleService, new SyncDescriptor(LifecycleService));
|
||||
services.set(IStateService, new SyncDescriptor(StateService));
|
||||
services.set(IRequestService, new SyncDescriptor(RequestService));
|
||||
services.set(IThemeMainService, new SyncDescriptor(ThemeMainService));
|
||||
services.set(ISignService, new SyncDescriptor(SignService));
|
||||
|
||||
return [new InstantiationService(services, true), instanceEnvironment];
|
||||
}
|
||||
|
||||
private initServices(environmentService: IEnvironmentService, configurationService: ConfigurationService, stateService: StateService): Promise<unknown> {
|
||||
|
||||
// Environment service (paths)
|
||||
const environmentServiceInitialization = Promise.all<void | undefined>([
|
||||
environmentService.extensionsPath,
|
||||
environmentService.nodeCachedDataDir,
|
||||
environmentService.logsPath,
|
||||
environmentService.globalStorageHome,
|
||||
environmentService.workspaceStorageHome,
|
||||
environmentService.backupHome.fsPath
|
||||
].map((path): undefined | Promise<void> => path ? mkdirp(path) : undefined));
|
||||
|
||||
// Configuration service
|
||||
const configurationServiceInitialization = configurationService.initialize();
|
||||
|
||||
// State service
|
||||
const stateServiceInitialization = stateService.init();
|
||||
|
||||
return Promise.all([environmentServiceInitialization, configurationServiceInitialization, stateServiceInitialization]);
|
||||
}
|
||||
|
||||
private patchEnvironment(environmentService: IEnvironmentService): typeof process.env {
|
||||
const instanceEnvironment: typeof process.env = {
|
||||
VSCODE_IPC_HOOK: environmentService.mainIPCHandle,
|
||||
VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG'],
|
||||
VSCODE_LOGS: process.env['VSCODE_LOGS'],
|
||||
// {{SQL CARBON EDIT}} We keep VSCODE_LOGS to not break functionality for merged code
|
||||
ADS_LOGS: process.env['ADS_LOGS']
|
||||
};
|
||||
|
||||
if (process.env['VSCODE_PORTABLE']) {
|
||||
instanceEnvironment['VSCODE_PORTABLE'] = process.env['VSCODE_PORTABLE'];
|
||||
}
|
||||
|
||||
assign(process.env, instanceEnvironment);
|
||||
|
||||
return instanceEnvironment;
|
||||
}
|
||||
|
||||
private async doStartup(logService: ILogService, environmentService: IEnvironmentService, lifecycleService: ILifecycleService, instantiationService: IInstantiationService, retry: boolean): Promise<Server> {
|
||||
|
||||
// Try to setup a server for running. If that succeeds it means
|
||||
// we are the first instance to startup. Otherwise it is likely
|
||||
// that another instance is already running.
|
||||
let server: Server;
|
||||
try {
|
||||
server = await serve(environmentService.mainIPCHandle);
|
||||
once(lifecycleService.onWillShutdown)(() => server.dispose());
|
||||
} catch (error) {
|
||||
|
||||
// Handle unexpected errors (the only expected error is EADDRINUSE that
|
||||
// indicates a second instance of Code is running)
|
||||
if (err.code !== 'EADDRINUSE') {
|
||||
if (error.code !== 'EADDRINUSE') {
|
||||
|
||||
// Show a dialog for errors that can be resolved by the user
|
||||
handleStartupDataDirError(environmentService, err);
|
||||
this.handleStartupDataDirError(environmentService, error);
|
||||
|
||||
// Any other runtime error is just printed to the console
|
||||
return Promise.reject<Server>(err);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Since we are the second instance, we do not want to show the dock
|
||||
@@ -110,263 +219,177 @@ function setupIPC(accessor: ServicesAccessor): Promise<Server> {
|
||||
}
|
||||
|
||||
// there's a running instance, let's connect to it
|
||||
return connect(environmentService.mainIPCHandle, 'main').then(
|
||||
client => {
|
||||
let client: Client<string>;
|
||||
try {
|
||||
client = await connect(environmentService.mainIPCHandle, 'main');
|
||||
} catch (error) {
|
||||
|
||||
// Tests from CLI require to be the only instance currently
|
||||
if (environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break) {
|
||||
const msg = 'Running extension tests from the command line is currently only supported if no other instance of Code is running.';
|
||||
logService.error(msg);
|
||||
client.dispose();
|
||||
|
||||
return Promise.reject(new Error(msg));
|
||||
// Handle unexpected connection errors by showing a dialog to the user
|
||||
if (!retry || platform.isWindows || error.code !== 'ECONNREFUSED') {
|
||||
if (error.code === 'EPERM') {
|
||||
this.showStartupWarningDialog(
|
||||
localize('secondInstanceAdmin', "A second instance of {0} is already running as administrator.", product.nameShort),
|
||||
localize('secondInstanceAdminDetail', "Please close the other instance and try again.")
|
||||
);
|
||||
}
|
||||
|
||||
// Show a warning dialog after some timeout if it takes long to talk to the other instance
|
||||
// Skip this if we are running with --wait where it is expected that we wait for a while.
|
||||
// Also skip when gathering diagnostics (--status) which can take a longer time.
|
||||
let startupWarningDialogHandle: NodeJS.Timeout;
|
||||
if (!environmentService.wait && !environmentService.status && !environmentService.args['upload-logs']) {
|
||||
startupWarningDialogHandle = setTimeout(() => {
|
||||
showStartupWarningDialog(
|
||||
localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", product.nameShort),
|
||||
localize('secondInstanceNoResponseDetail', "Please close all other instances and try again.")
|
||||
);
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
const channel = client.getChannel('launch');
|
||||
const service = new LaunchChannelClient(channel);
|
||||
|
||||
// Process Info
|
||||
if (environmentService.args.status) {
|
||||
return instantiationService.invokeFunction(accessor => {
|
||||
return accessor.get(IDiagnosticsService).getDiagnostics(service).then(diagnostics => {
|
||||
console.log(diagnostics);
|
||||
return Promise.reject(new ExpectedError());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Log uploader
|
||||
if (typeof environmentService.args['upload-logs'] !== 'undefined') {
|
||||
return instantiationService.invokeFunction(accessor => {
|
||||
return uploadLogs(service, accessor.get(IRequestService), environmentService)
|
||||
.then(() => Promise.reject(new ExpectedError()));
|
||||
});
|
||||
}
|
||||
|
||||
logService.trace('Sending env to running instance...');
|
||||
|
||||
return allowSetForegroundWindow(service)
|
||||
.then(() => service.start(environmentService.args, process.env as platform.IProcessEnvironment))
|
||||
.then(() => client.dispose())
|
||||
.then(() => {
|
||||
|
||||
// Now that we started, make sure the warning dialog is prevented
|
||||
if (startupWarningDialogHandle) {
|
||||
clearTimeout(startupWarningDialogHandle);
|
||||
}
|
||||
|
||||
return Promise.reject(new ExpectedError('Sent env to running instance. Terminating...'));
|
||||
});
|
||||
},
|
||||
err => {
|
||||
if (!retry || platform.isWindows || err.code !== 'ECONNREFUSED') {
|
||||
if (err.code === 'EPERM') {
|
||||
showStartupWarningDialog(
|
||||
localize('secondInstanceAdmin', "A second instance of {0} is already running as administrator.", product.nameShort),
|
||||
localize('secondInstanceAdminDetail', "Please close the other instance and try again.")
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.reject<Server>(err);
|
||||
}
|
||||
|
||||
// it happens on Linux and OS X that the pipe is left behind
|
||||
// let's delete it, since we can't connect to it
|
||||
// and then retry the whole thing
|
||||
try {
|
||||
fs.unlinkSync(environmentService.mainIPCHandle);
|
||||
} catch (e) {
|
||||
logService.warn('Could not delete obsolete instance handle', e);
|
||||
return Promise.reject<Server>(e);
|
||||
}
|
||||
|
||||
return setup(false);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// it happens on Linux and OS X that the pipe is left behind
|
||||
// let's delete it, since we can't connect to it and then
|
||||
// retry the whole thing
|
||||
try {
|
||||
fs.unlinkSync(environmentService.mainIPCHandle);
|
||||
} catch (error) {
|
||||
logService.warn('Could not delete obsolete instance handle', error);
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
return this.doStartup(logService, environmentService, lifecycleService, instantiationService, false);
|
||||
}
|
||||
|
||||
// Tests from CLI require to be the only instance currently
|
||||
if (environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break) {
|
||||
const msg = 'Running extension tests from the command line is currently only supported if no other instance of Code is running.';
|
||||
logService.error(msg);
|
||||
client.dispose();
|
||||
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
// Show a warning dialog after some timeout if it takes long to talk to the other instance
|
||||
// Skip this if we are running with --wait where it is expected that we wait for a while.
|
||||
// Also skip when gathering diagnostics (--status) which can take a longer time.
|
||||
let startupWarningDialogHandle: NodeJS.Timeout | undefined = undefined;
|
||||
if (!environmentService.wait && !environmentService.status) {
|
||||
startupWarningDialogHandle = setTimeout(() => {
|
||||
this.showStartupWarningDialog(
|
||||
localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", product.nameShort),
|
||||
localize('secondInstanceNoResponseDetail', "Please close all other instances and try again.")
|
||||
);
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
const channel = client.getChannel('launch');
|
||||
const launchClient = new LaunchChannelClient(channel);
|
||||
|
||||
// Process Info
|
||||
if (environmentService.args.status) {
|
||||
return instantiationService.invokeFunction(async accessor => {
|
||||
// Create a diagnostic service connected to the existing shared process
|
||||
const sharedProcessClient = await connect(environmentService.sharedIPCHandle, 'main');
|
||||
const diagnosticsChannel = sharedProcessClient.getChannel('diagnostics');
|
||||
const diagnosticsService = new DiagnosticsService(diagnosticsChannel);
|
||||
const mainProcessInfo = await launchClient.getMainProcessInfo();
|
||||
const remoteDiagnostics = await launchClient.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true });
|
||||
const diagnostics = await diagnosticsService.getDiagnostics(mainProcessInfo, remoteDiagnostics);
|
||||
console.log(diagnostics);
|
||||
|
||||
throw new ExpectedError();
|
||||
});
|
||||
}
|
||||
|
||||
// Windows: allow to set foreground
|
||||
if (platform.isWindows) {
|
||||
await this.windowsAllowSetForegroundWindow(launchClient, logService);
|
||||
}
|
||||
|
||||
// Send environment over...
|
||||
logService.trace('Sending env to running instance...');
|
||||
await launchClient.start(environmentService.args, process.env as platform.IProcessEnvironment);
|
||||
|
||||
// Cleanup
|
||||
await client.dispose();
|
||||
|
||||
// Now that we started, make sure the warning dialog is prevented
|
||||
if (startupWarningDialogHandle) {
|
||||
clearTimeout(startupWarningDialogHandle);
|
||||
}
|
||||
|
||||
throw new ExpectedError('Sent env to running instance. Terminating...');
|
||||
}
|
||||
|
||||
// Print --status usage info
|
||||
if (environmentService.args.status) {
|
||||
logService.warn('Warning: The --status argument can only be used if Code is already running. Please run it again after Code has started.');
|
||||
|
||||
throw new ExpectedError('Terminating...');
|
||||
}
|
||||
|
||||
// dock might be hidden at this case due to a retry
|
||||
if (platform.isMacintosh) {
|
||||
app.dock.show();
|
||||
}
|
||||
|
||||
// Set the VSCODE_PID variable here when we are sure we are the first
|
||||
// instance to startup. Otherwise we would wrongly overwrite the PID
|
||||
process.env['VSCODE_PID'] = String(process.pid);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
private handleStartupDataDirError(environmentService: IEnvironmentService, error: NodeJS.ErrnoException): void {
|
||||
if (error.code === 'EACCES' || error.code === 'EPERM') {
|
||||
this.showStartupWarningDialog(
|
||||
localize('startupDataDirError', "Unable to write program user data."),
|
||||
localize('startupDataDirErrorDetail', "Please make sure the directories {0} and {1} are writeable.", environmentService.userDataPath, environmentService.extensionsPath)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private showStartupWarningDialog(message: string, detail: string): void {
|
||||
dialog.showMessageBox({
|
||||
title: product.nameLong,
|
||||
type: 'warning',
|
||||
buttons: [mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
|
||||
message,
|
||||
detail,
|
||||
noLink: true
|
||||
});
|
||||
}
|
||||
|
||||
return setup(true);
|
||||
}
|
||||
private async windowsAllowSetForegroundWindow(client: LaunchChannelClient, logService: ILogService): Promise<void> {
|
||||
if (platform.isWindows) {
|
||||
const processId = await client.getMainProcessId();
|
||||
|
||||
function showStartupWarningDialog(message: string, detail: string): void {
|
||||
dialog.showMessageBox({
|
||||
title: product.nameLong,
|
||||
type: 'warning',
|
||||
buttons: [mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
|
||||
message,
|
||||
detail,
|
||||
noLink: true
|
||||
});
|
||||
}
|
||||
logService.trace('Sending some foreground love to the running instance:', processId);
|
||||
|
||||
function handleStartupDataDirError(environmentService: IEnvironmentService, error: NodeJS.ErrnoException): void {
|
||||
if (error.code === 'EACCES' || error.code === 'EPERM') {
|
||||
showStartupWarningDialog(
|
||||
localize('startupDataDirError', "Unable to write program user data."),
|
||||
localize('startupDataDirErrorDetail', "Please make sure the directories {0} and {1} are writeable.", environmentService.userDataPath, environmentService.extensionsPath)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function quit(accessor: ServicesAccessor, reason?: ExpectedError | Error): void {
|
||||
const logService = accessor.get(ILogService);
|
||||
const lifecycleService = accessor.get(ILifecycleService);
|
||||
|
||||
let exitCode = 0;
|
||||
|
||||
if (reason) {
|
||||
if ((reason as ExpectedError).isExpected) {
|
||||
if (reason.message) {
|
||||
logService.trace(reason.message);
|
||||
try {
|
||||
(await import('windows-foreground-love')).allowSetForegroundWindow(processId);
|
||||
} catch (error) {
|
||||
logService.error(error);
|
||||
}
|
||||
} else {
|
||||
exitCode = 1; // signal error to the outside
|
||||
}
|
||||
}
|
||||
|
||||
if (reason.stack) {
|
||||
logService.error(reason.stack);
|
||||
private quit(accessor: ServicesAccessor, reason?: ExpectedError | Error): void {
|
||||
const logService = accessor.get(ILogService);
|
||||
const lifecycleService = accessor.get(ILifecycleService);
|
||||
|
||||
let exitCode = 0;
|
||||
|
||||
if (reason) {
|
||||
if ((reason as ExpectedError).isExpected) {
|
||||
if (reason.message) {
|
||||
logService.trace(reason.message);
|
||||
}
|
||||
} else {
|
||||
logService.error(`Startup error: ${reason.toString()}`);
|
||||
exitCode = 1; // signal error to the outside
|
||||
|
||||
if (reason.stack) {
|
||||
logService.error(reason.stack);
|
||||
} else {
|
||||
logService.error(`Startup error: ${reason.toString()}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleService.kill(exitCode);
|
||||
}
|
||||
|
||||
lifecycleService.kill(exitCode);
|
||||
}
|
||||
|
||||
function patchEnvironment(environmentService: IEnvironmentService): typeof process.env {
|
||||
const instanceEnvironment: typeof process.env = {
|
||||
VSCODE_IPC_HOOK: environmentService.mainIPCHandle,
|
||||
VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG'],
|
||||
VSCODE_LOGS: process.env['VSCODE_LOGS'],
|
||||
// {{SQL CARBON EDIT}} We keep VSCODE_LOGS to not break functionality for merged code
|
||||
ADS_LOGS: process.env['ADS_LOGS']
|
||||
};
|
||||
|
||||
if (process.env['VSCODE_PORTABLE']) {
|
||||
instanceEnvironment['VSCODE_PORTABLE'] = process.env['VSCODE_PORTABLE'];
|
||||
}
|
||||
|
||||
assign(process.env, instanceEnvironment);
|
||||
|
||||
return instanceEnvironment;
|
||||
}
|
||||
|
||||
function startup(args: ParsedArgs): void {
|
||||
|
||||
// We need to buffer the spdlog logs until we are sure
|
||||
// we are the only instance running, otherwise we'll have concurrent
|
||||
// log file access on Windows (https://github.com/Microsoft/vscode/issues/41218)
|
||||
const bufferLogService = new BufferLogService();
|
||||
|
||||
const instantiationService = createServices(args, bufferLogService);
|
||||
instantiationService.invokeFunction(accessor => {
|
||||
const environmentService = accessor.get(IEnvironmentService);
|
||||
const stateService = accessor.get(IStateService);
|
||||
|
||||
// Patch `process.env` with the instance's environment
|
||||
const instanceEnvironment = patchEnvironment(environmentService);
|
||||
|
||||
// Startup
|
||||
return initServices(environmentService, stateService as StateService)
|
||||
.then(() => instantiationService.invokeFunction(setupIPC), error => {
|
||||
|
||||
// Show a dialog for errors that can be resolved by the user
|
||||
handleStartupDataDirError(environmentService, error);
|
||||
|
||||
return Promise.reject(error);
|
||||
})
|
||||
.then(mainIpcServer => {
|
||||
createSpdLogService('main', bufferLogService.getLevel(), environmentService.logsPath).then(logger => bufferLogService.logger = logger);
|
||||
|
||||
return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup();
|
||||
});
|
||||
}).then(null, err => instantiationService.invokeFunction(quit, err));
|
||||
}
|
||||
|
||||
function createServices(args: ParsedArgs, bufferLogService: BufferLogService): IInstantiationService {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
const environmentService = new EnvironmentService(args, process.execPath);
|
||||
|
||||
const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]);
|
||||
process.once('exit', () => logService.dispose());
|
||||
|
||||
services.set(IEnvironmentService, environmentService);
|
||||
services.set(ILogService, logService);
|
||||
services.set(ILifecycleService, new SyncDescriptor(LifecycleService));
|
||||
services.set(IStateService, new SyncDescriptor(StateService));
|
||||
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService, [environmentService.appSettingsPath]));
|
||||
services.set(IRequestService, new SyncDescriptor(RequestService));
|
||||
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService));
|
||||
|
||||
return new InstantiationService(services, true);
|
||||
}
|
||||
|
||||
function initServices(environmentService: IEnvironmentService, stateService: StateService): Promise<unknown> {
|
||||
|
||||
// Ensure paths for environment service exist
|
||||
const environmentServiceInitialization = Promise.all<void | undefined>([
|
||||
environmentService.extensionsPath,
|
||||
environmentService.nodeCachedDataDir,
|
||||
environmentService.logsPath,
|
||||
environmentService.globalStorageHome,
|
||||
environmentService.workspaceStorageHome,
|
||||
environmentService.backupHome
|
||||
].map((path): undefined | Promise<void> => path ? mkdirp(path) : undefined));
|
||||
|
||||
// State service
|
||||
const stateServiceInitialization = stateService.init();
|
||||
|
||||
return Promise.all([environmentServiceInitialization, stateServiceInitialization]);
|
||||
}
|
||||
|
||||
function main(): void {
|
||||
|
||||
// Set the error handler early enough so that we are not getting the
|
||||
// default electron error dialog popping up
|
||||
setUnexpectedErrorHandler(err => console.error(err));
|
||||
|
||||
// Parse arguments
|
||||
let args: ParsedArgs;
|
||||
try {
|
||||
args = parseMainProcessArgv(process.argv);
|
||||
args = validatePaths(args);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
app.exit(1);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// If we are started with --wait create a random temporary file
|
||||
// and pass it over to the starting instance. We can use this file
|
||||
// to wait for it to be deleted to monitor that the edited file
|
||||
// is closed and then exit the waiting process.
|
||||
//
|
||||
// Note: we are not doing this if the wait marker has been already
|
||||
// added as argument. This can happen if Code was started from CLI.
|
||||
if (args.wait && !args.waitMarkerFilePath) {
|
||||
const waitMarkerFilePath = createWaitMarkerFile(args.verbose);
|
||||
if (waitMarkerFilePath) {
|
||||
addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath);
|
||||
args.waitMarkerFilePath = waitMarkerFilePath;
|
||||
}
|
||||
}
|
||||
startup(args);
|
||||
}
|
||||
|
||||
main();
|
||||
// Main Startup
|
||||
const code = new CodeMain();
|
||||
code.main();
|
||||
@@ -11,9 +11,8 @@ import { ISharedProcess } from 'vs/platform/windows/electron-main/windows';
|
||||
import { Barrier } from 'vs/base/common/async';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
|
||||
import { IStateService } from 'vs/platform/state/common/state';
|
||||
import { getBackgroundColor } from 'vs/code/electron-main/theme';
|
||||
import { dispose, toDisposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
|
||||
import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
|
||||
export class SharedProcess implements ISharedProcess {
|
||||
|
||||
@@ -26,19 +25,20 @@ export class SharedProcess implements ISharedProcess {
|
||||
private userEnv: NodeJS.ProcessEnv,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@ILifecycleService private readonly lifecycleService: ILifecycleService,
|
||||
@IStateService private readonly stateService: IStateService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IThemeMainService private readonly themeMainService: IThemeMainService
|
||||
) { }
|
||||
|
||||
@memoize
|
||||
private get _whenReady(): Promise<void> {
|
||||
this.window = new BrowserWindow({
|
||||
show: false,
|
||||
backgroundColor: getBackgroundColor(this.stateService),
|
||||
backgroundColor: this.themeMainService.getBackgroundColor(),
|
||||
webPreferences: {
|
||||
images: false,
|
||||
webaudio: false,
|
||||
webgl: false,
|
||||
nodeIntegration: true,
|
||||
disableBlinkFeatures: 'Auxclick' // do NOT change, allows us to identify this window as shared-process in the process explorer
|
||||
}
|
||||
});
|
||||
@@ -67,10 +67,10 @@ export class SharedProcess implements ISharedProcess {
|
||||
|
||||
this.window.on('close', onClose);
|
||||
|
||||
const disposables: IDisposable[] = [];
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
this.lifecycleService.onWillShutdown(() => {
|
||||
dispose(disposables);
|
||||
disposables.dispose();
|
||||
|
||||
// Shut the shared process down when we are quitting
|
||||
//
|
||||
@@ -104,7 +104,7 @@ export class SharedProcess implements ISharedProcess {
|
||||
logLevel: this.logService.getLevel()
|
||||
});
|
||||
|
||||
disposables.push(toDisposable(() => sender.send('handshake:goodbye')));
|
||||
disposables.add(toDisposable(() => sender.send('handshake:goodbye')));
|
||||
ipcMain.once('handshake:im ready', () => c(undefined));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,44 +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 { isWindows, isMacintosh } from 'vs/base/common/platform';
|
||||
import { systemPreferences } from 'electron';
|
||||
import { IStateService } from 'vs/platform/state/common/state';
|
||||
|
||||
const DEFAULT_BG_LIGHT = '#FFFFFF';
|
||||
const DEFAULT_BG_DARK = '#1E1E1E';
|
||||
const DEFAULT_BG_HC_BLACK = '#000000';
|
||||
|
||||
const THEME_STORAGE_KEY = 'theme';
|
||||
const THEME_BG_STORAGE_KEY = 'themeBackground';
|
||||
|
||||
export function storeBackgroundColor(stateService: IStateService, data: { baseTheme: string, background: string }): void {
|
||||
stateService.setItem(THEME_STORAGE_KEY, data.baseTheme);
|
||||
stateService.setItem(THEME_BG_STORAGE_KEY, data.background);
|
||||
}
|
||||
|
||||
export function getBackgroundColor(stateService: IStateService): string {
|
||||
if (isWindows && systemPreferences.isInvertedColorScheme()) {
|
||||
return DEFAULT_BG_HC_BLACK;
|
||||
}
|
||||
|
||||
let background = stateService.getItem<string | null>(THEME_BG_STORAGE_KEY, null);
|
||||
if (!background) {
|
||||
let baseTheme: string;
|
||||
if (isWindows && systemPreferences.isInvertedColorScheme()) {
|
||||
baseTheme = 'hc-black';
|
||||
} else {
|
||||
baseTheme = stateService.getItem<string>(THEME_STORAGE_KEY, 'vs-dark').split(' ')[0];
|
||||
}
|
||||
|
||||
background = (baseTheme === 'hc-black') ? DEFAULT_BG_HC_BLACK : (baseTheme === 'vs' ? DEFAULT_BG_LIGHT : DEFAULT_BG_DARK);
|
||||
}
|
||||
|
||||
if (isMacintosh && background.toUpperCase() === DEFAULT_BG_DARK) {
|
||||
background = '#171717'; // https://github.com/electron/electron/issues/5150
|
||||
}
|
||||
|
||||
return background;
|
||||
}
|
||||
@@ -7,14 +7,13 @@ import * as path from 'vs/base/common/path';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import * as nls from 'vs/nls';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IStateService } from 'vs/platform/state/common/state';
|
||||
import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display } from 'electron';
|
||||
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { parseArgs } from 'vs/platform/environment/node/argv';
|
||||
import product from 'vs/platform/product/node/product';
|
||||
import { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState, IRunActionInWindowRequest, getTitleBarStyle } from 'vs/platform/windows/common/windows';
|
||||
import { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState, getTitleBarStyle } from 'vs/platform/windows/common/windows';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
|
||||
import { ICodeWindow, IWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows';
|
||||
@@ -22,10 +21,14 @@ import { IWorkspaceIdentifier, IWorkspacesMainService } from 'vs/platform/worksp
|
||||
import { IBackupMainService } from 'vs/platform/backup/common/backup';
|
||||
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
|
||||
import * as perf from 'vs/base/common/performance';
|
||||
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/node/extensionGalleryService';
|
||||
import { getBackgroundColor } from 'vs/code/electron-main/theme';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/common/extensionGalleryService';
|
||||
import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
|
||||
import { endsWith } from 'vs/base/common/strings';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import pkg from 'vs/platform/product/node/package';
|
||||
|
||||
const RUN_TEXTMATE_IN_WORKER = false;
|
||||
|
||||
export interface IWindowCreationOptions {
|
||||
state: IWindowState;
|
||||
@@ -41,14 +44,6 @@ export const defaultWindowState = function (mode = WindowMode.Normal): IWindowSt
|
||||
};
|
||||
};
|
||||
|
||||
interface IWorkbenchEditorConfiguration {
|
||||
workbench: {
|
||||
editor: {
|
||||
swipeToNavigate: boolean
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
interface ITouchBarSegment extends Electron.SegmentedControlSegment {
|
||||
id: string;
|
||||
}
|
||||
@@ -83,8 +78,9 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
config: IWindowCreationOptions,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IStateService private readonly stateService: IStateService,
|
||||
@IThemeMainService private readonly themeMainService: IThemeMainService,
|
||||
@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
|
||||
@IBackupMainService private readonly backupMainService: IBackupMainService,
|
||||
) {
|
||||
@@ -114,7 +110,8 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
private createBrowserWindow(config: IWindowCreationOptions): void {
|
||||
|
||||
// Load window state
|
||||
this.windowState = this.restoreWindowState(config.state);
|
||||
const [state, hasMultipleDisplays] = this.restoreWindowState(config.state);
|
||||
this.windowState = state;
|
||||
|
||||
// in case we are maximized or fullscreen, only show later after the call to maximize/fullscreen (see below)
|
||||
const isFullscreenOrMaximized = (this.windowState.mode === WindowMode.Maximized || this.windowState.mode === WindowMode.Fullscreen);
|
||||
@@ -124,7 +121,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
height: this.windowState.height,
|
||||
x: this.windowState.x,
|
||||
y: this.windowState.y,
|
||||
backgroundColor: getBackgroundColor(this.stateService),
|
||||
backgroundColor: this.themeMainService.getBackgroundColor(),
|
||||
minWidth: CodeWindow.MIN_WIDTH,
|
||||
minHeight: CodeWindow.MIN_HEIGHT,
|
||||
show: !isFullscreenOrMaximized,
|
||||
@@ -134,7 +131,10 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
// want to enforce that Code stays in the foreground. This triggers a disable_hidden_
|
||||
// flag that Electron provides via patch:
|
||||
// https://github.com/electron/libchromiumcontent/blob/master/patches/common/chromium/disable_hidden.patch
|
||||
backgroundThrottling: false
|
||||
backgroundThrottling: false,
|
||||
nodeIntegration: true,
|
||||
nodeIntegrationInWorker: RUN_TEXTMATE_IN_WORKER,
|
||||
webviewTag: true
|
||||
}
|
||||
};
|
||||
|
||||
@@ -156,7 +156,8 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
}
|
||||
}
|
||||
|
||||
if (isMacintosh && windowConfig && windowConfig.nativeTabs === true) {
|
||||
const useNativeTabs = isMacintosh && windowConfig && windowConfig.nativeTabs === true;
|
||||
if (useNativeTabs) {
|
||||
options.tabbingIdentifier = product.nameShort; // this opts in to sierra tabs
|
||||
}
|
||||
|
||||
@@ -177,6 +178,24 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
this._win.setSheetOffset(22); // offset dialogs by the height of the custom title bar if we have any
|
||||
}
|
||||
|
||||
// TODO@Ben (Electron 4 regression): when running on multiple displays where the target display
|
||||
// to open the window has a larger resolution than the primary display, the window will not size
|
||||
// correctly unless we set the bounds again (https://github.com/microsoft/vscode/issues/74872)
|
||||
//
|
||||
// However, when running with native tabs with multiple windows we cannot use this workaround
|
||||
// because there is a potential that the new window will be added as native tab instead of being
|
||||
// a window on its own. In that case calling setBounds() would cause https://github.com/microsoft/vscode/issues/75830
|
||||
if (isMacintosh && hasMultipleDisplays && (!useNativeTabs || BrowserWindow.getAllWindows().length === 1)) {
|
||||
if ([this.windowState.width, this.windowState.height, this.windowState.x, this.windowState.y].every(value => typeof value === 'number')) {
|
||||
this._win.setBounds({
|
||||
width: this.windowState.width!,
|
||||
height: this.windowState.height!,
|
||||
x: this.windowState.x!,
|
||||
y: this.windowState.y!
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (isFullscreenOrMaximized) {
|
||||
this._win.maximize();
|
||||
|
||||
@@ -204,12 +223,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
return !!this.config.extensionTestsPath;
|
||||
}
|
||||
|
||||
/*
|
||||
get extensionDevelopmentPaths(): string | string[] | undefined {
|
||||
return this.config.extensionDevelopmentPath;
|
||||
}
|
||||
*/
|
||||
|
||||
get config(): IWindowConfiguration {
|
||||
return this.currentConfig;
|
||||
}
|
||||
@@ -297,13 +310,13 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
private handleMarketplaceRequests(): void {
|
||||
|
||||
// Resolve marketplace headers
|
||||
this.marketplaceHeadersPromise = resolveMarketplaceHeaders(this.environmentService);
|
||||
this.marketplaceHeadersPromise = resolveMarketplaceHeaders(pkg.version, this.environmentService, this.fileService);
|
||||
|
||||
// Inject headers when requests are incoming
|
||||
const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*'];
|
||||
this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => {
|
||||
this.marketplaceHeadersPromise.then(headers => {
|
||||
const requestHeaders = objects.assign(details.requestHeaders, headers);
|
||||
const requestHeaders = objects.assign(details.requestHeaders, headers) as { [key: string]: string | undefined };
|
||||
if (!this.configurationService.getValue('extensions.disableExperimentalAzureSearch')) {
|
||||
requestHeaders['Cookie'] = `${requestHeaders['Cookie'] ? requestHeaders['Cookie'] + ';' : ''}EnableExternalSearchForVSCode=true`;
|
||||
}
|
||||
@@ -327,12 +340,14 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
});
|
||||
|
||||
this._win.webContents.session.webRequest.onHeadersReceived(null!, (details, callback) => {
|
||||
const contentType: string[] = (details.responseHeaders['content-type'] || details.responseHeaders['Content-Type']);
|
||||
const responseHeaders = details.responseHeaders as { [key: string]: string[] };
|
||||
|
||||
const contentType: string[] = (responseHeaders['content-type'] || responseHeaders['Content-Type']);
|
||||
if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) {
|
||||
return callback({ cancel: true });
|
||||
}
|
||||
|
||||
return callback({ cancel: false, responseHeaders: details.responseHeaders });
|
||||
return callback({ cancel: false, responseHeaders });
|
||||
});
|
||||
|
||||
// Remember that we loaded
|
||||
@@ -358,9 +373,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
}
|
||||
});
|
||||
|
||||
// App commands support
|
||||
this.registerNavigationListenerOn('app-command', 'browser-backward', 'browser-forward', false);
|
||||
|
||||
// Window Focus
|
||||
this._win.on('focus', () => {
|
||||
this._lastFocusTime = Date.now();
|
||||
@@ -446,30 +458,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
this.currentMenuBarVisibility = newMenuBarVisibility;
|
||||
this.setMenuBarVisibility(newMenuBarVisibility);
|
||||
}
|
||||
|
||||
// Swipe command support (macOS)
|
||||
if (isMacintosh) {
|
||||
const config = this.configurationService.getValue<IWorkbenchEditorConfiguration>();
|
||||
if (config && config.workbench && config.workbench.editor && config.workbench.editor.swipeToNavigate) {
|
||||
this.registerNavigationListenerOn('swipe', 'left', 'right', true);
|
||||
} else {
|
||||
this._win.removeAllListeners('swipe');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private registerNavigationListenerOn(command: 'swipe' | 'app-command', back: 'left' | 'browser-backward', forward: 'right' | 'browser-forward', acrossEditors: boolean) {
|
||||
this._win.on(command as 'swipe' /* | 'app-command' */, (e: Electron.Event, cmd: string) => {
|
||||
if (!this.isReady) {
|
||||
return; // window must be ready
|
||||
}
|
||||
|
||||
if (cmd === back) {
|
||||
this.send('vscode:runAction', { id: acrossEditors ? 'workbench.action.openPreviousRecentlyUsedEditor' : 'workbench.action.navigateBack', from: 'mouse' } as IRunActionInWindowRequest);
|
||||
} else if (cmd === forward) {
|
||||
this.send('vscode:runAction', { id: acrossEditors ? 'workbench.action.openNextRecentlyUsedEditor' : 'workbench.action.navigateForward', from: 'mouse' } as IRunActionInWindowRequest);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addTabbedWindow(window: ICodeWindow): void {
|
||||
@@ -599,9 +587,10 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
// Config (combination of process.argv and window configuration)
|
||||
const environment = parseArgs(process.argv);
|
||||
const config = objects.assign(environment, windowConfiguration);
|
||||
for (let key in config) {
|
||||
if (config[key] === undefined || config[key] === null || config[key] === '' || config[key] === false) {
|
||||
delete config[key]; // only send over properties that have a true value
|
||||
for (const key in config) {
|
||||
const configValue = (config as any)[key];
|
||||
if (configValue === undefined || configValue === null || configValue === '' || configValue === false) {
|
||||
delete (config as any)[key]; // only send over properties that have a true value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -675,7 +664,12 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
|
||||
// only consider non-minimized window states
|
||||
if (mode === WindowMode.Normal || mode === WindowMode.Maximized) {
|
||||
const bounds = this.getBounds();
|
||||
let bounds: Electron.Rectangle;
|
||||
if (mode === WindowMode.Normal) {
|
||||
bounds = this.getBounds();
|
||||
} else {
|
||||
bounds = this._win.getNormalBounds(); // make sure to persist the normal bounds when maximized to be able to restore them
|
||||
}
|
||||
|
||||
state.x = bounds.x;
|
||||
state.y = bounds.y;
|
||||
@@ -686,18 +680,23 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
return state;
|
||||
}
|
||||
|
||||
private restoreWindowState(state?: IWindowState): IWindowState {
|
||||
private restoreWindowState(state?: IWindowState): [IWindowState, boolean? /* has multiple displays */] {
|
||||
let hasMultipleDisplays = false;
|
||||
if (state) {
|
||||
try {
|
||||
state = this.validateWindowState(state);
|
||||
const displays = screen.getAllDisplays();
|
||||
hasMultipleDisplays = displays.length > 1;
|
||||
|
||||
state = this.validateWindowState(state, displays);
|
||||
} catch (err) {
|
||||
this.logService.warn(`Unexpected error validating window state: ${err}\n${err.stack}`); // somehow display API can be picky about the state to validate
|
||||
}
|
||||
}
|
||||
return state || defaultWindowState();
|
||||
|
||||
return [state || defaultWindowState(), hasMultipleDisplays];
|
||||
}
|
||||
|
||||
private validateWindowState(state: IWindowState): IWindowState | undefined {
|
||||
private validateWindowState(state: IWindowState, displays: Display[]): IWindowState | undefined {
|
||||
if (typeof state.x !== 'number'
|
||||
|| typeof state.y !== 'number'
|
||||
|| typeof state.width !== 'number'
|
||||
@@ -710,12 +709,10 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const displays = screen.getAllDisplays();
|
||||
|
||||
// Single Monitor: be strict about x/y positioning
|
||||
if (displays.length === 1) {
|
||||
const displayWorkingArea = this.getWorkingArea(displays[0]);
|
||||
if (state.mode !== WindowMode.Maximized && displayWorkingArea) {
|
||||
if (displayWorkingArea) {
|
||||
if (state.x < displayWorkingArea.x) {
|
||||
state.x = displayWorkingArea.x; // prevent window from falling out of the screen to the left
|
||||
}
|
||||
@@ -741,10 +738,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
}
|
||||
}
|
||||
|
||||
if (state.mode === WindowMode.Maximized) {
|
||||
return defaultWindowState(WindowMode.Maximized); // when maximized, make sure we have good values when the user restores the window
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -772,14 +765,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
bounds.x + bounds.width > displayWorkingArea.x && // prevent window from falling out of the screen to the left
|
||||
bounds.y + bounds.height > displayWorkingArea.y // prevent window from falling out of the scree nto the top
|
||||
) {
|
||||
if (state.mode === WindowMode.Maximized) {
|
||||
const defaults = defaultWindowState(WindowMode.Maximized); // when maximized, make sure we have good values when the user restores the window
|
||||
defaults.x = state.x; // carefull to keep x/y position so that the window ends up on the correct monitor
|
||||
defaults.y = state.y;
|
||||
|
||||
return defaults;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -853,16 +838,17 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
}
|
||||
|
||||
private useNativeFullScreen(): boolean {
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
|
||||
if (!windowConfig || typeof windowConfig.nativeFullScreen !== 'boolean') {
|
||||
return true; // default
|
||||
}
|
||||
return true; // TODO@ben enable simple fullscreen again (https://github.com/microsoft/vscode/issues/75054)
|
||||
// const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
|
||||
// if (!windowConfig || typeof windowConfig.nativeFullScreen !== 'boolean') {
|
||||
// return true; // default
|
||||
// }
|
||||
|
||||
if (windowConfig.nativeTabs) {
|
||||
return true; // https://github.com/electron/electron/issues/16142
|
||||
}
|
||||
// if (windowConfig.nativeTabs) {
|
||||
// return true; // https://github.com/electron/electron/issues/16142
|
||||
// }
|
||||
|
||||
return windowConfig.nativeFullScreen !== false;
|
||||
// return windowConfig.nativeFullScreen !== false;
|
||||
}
|
||||
|
||||
isMinimized(): boolean {
|
||||
|
||||
@@ -15,7 +15,7 @@ import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window';
|
||||
import { hasArgs, asArray } from 'vs/platform/environment/node/argv';
|
||||
import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences, FileFilter } from 'electron';
|
||||
import { parseLineAndColumnAware } from 'vs/code/node/paths';
|
||||
import { ILifecycleService, UnloadReason, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
|
||||
import { ILifecycleService, UnloadReason, LifecycleService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, IPathsToWaitFor, IEnterWorkspaceResult, IMessageBoxResult, INewWindowOptions, IURIToOpen, isFileToOpen, isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows';
|
||||
@@ -38,6 +38,8 @@ import { getComparisonKey, isEqual, normalizePath, basename as resourcesBasename
|
||||
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { restoreWindowsState, WindowsStateStorageData, getWindowsStateStoreData } from 'vs/code/electron-main/windowsStateStorage';
|
||||
import { getWorkspaceIdentifier } from 'vs/platform/workspaces/electron-main/workspacesMainService';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
const enum WindowError {
|
||||
UNRESPONSIVE = 1,
|
||||
@@ -153,15 +155,13 @@ interface IWorkspacePathToOpen {
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export class WindowsManager implements IWindowsMainService {
|
||||
export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private static readonly windowsStateStorageKey = 'windowsState';
|
||||
|
||||
private static WINDOWS: ICodeWindow[] = [];
|
||||
|
||||
private initialUserEnv: IProcessEnvironment;
|
||||
private static readonly WINDOWS: ICodeWindow[] = [];
|
||||
|
||||
private readonly windowsState: IWindowsState;
|
||||
private lastClosedWindowState?: IWindowState;
|
||||
@@ -169,20 +169,21 @@ export class WindowsManager implements IWindowsMainService {
|
||||
private readonly dialogs: Dialogs;
|
||||
private readonly workspacesManager: WorkspacesManager;
|
||||
|
||||
private _onWindowReady = new Emitter<ICodeWindow>();
|
||||
onWindowReady: CommonEvent<ICodeWindow> = this._onWindowReady.event;
|
||||
private readonly _onWindowReady = this._register(new Emitter<ICodeWindow>());
|
||||
readonly onWindowReady: CommonEvent<ICodeWindow> = this._onWindowReady.event;
|
||||
|
||||
private _onWindowClose = new Emitter<number>();
|
||||
onWindowClose: CommonEvent<number> = this._onWindowClose.event;
|
||||
private readonly _onWindowClose = this._register(new Emitter<number>());
|
||||
readonly onWindowClose: CommonEvent<number> = this._onWindowClose.event;
|
||||
|
||||
private _onWindowLoad = new Emitter<number>();
|
||||
onWindowLoad: CommonEvent<number> = this._onWindowLoad.event;
|
||||
private readonly _onWindowLoad = this._register(new Emitter<number>());
|
||||
readonly onWindowLoad: CommonEvent<number> = this._onWindowLoad.event;
|
||||
|
||||
private _onWindowsCountChanged = new Emitter<IWindowsCountChangedEvent>();
|
||||
onWindowsCountChanged: CommonEvent<IWindowsCountChangedEvent> = this._onWindowsCountChanged.event;
|
||||
private readonly _onWindowsCountChanged = this._register(new Emitter<IWindowsCountChangedEvent>());
|
||||
readonly onWindowsCountChanged: CommonEvent<IWindowsCountChangedEvent> = this._onWindowsCountChanged.event;
|
||||
|
||||
constructor(
|
||||
private readonly machineId: string,
|
||||
private readonly initialUserEnv: IProcessEnvironment,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IStateService private readonly stateService: IStateService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@@ -194,6 +195,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
super();
|
||||
const windowsStateStoreData = this.stateService.getItem<WindowsStateStorageData>(WindowsManager.windowsStateStorageKey);
|
||||
|
||||
this.windowsState = restoreWindowsState(windowsStateStoreData);
|
||||
@@ -203,12 +205,21 @@ export class WindowsManager implements IWindowsMainService {
|
||||
|
||||
this.dialogs = new Dialogs(stateService, this);
|
||||
this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, this);
|
||||
|
||||
this.lifecycleService.when(LifecycleMainPhase.Ready).then(() => this.registerListeners());
|
||||
this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.installWindowsMutex());
|
||||
}
|
||||
|
||||
ready(initialUserEnv: IProcessEnvironment): void {
|
||||
this.initialUserEnv = initialUserEnv;
|
||||
|
||||
this.registerListeners();
|
||||
private installWindowsMutex(): void {
|
||||
if (isWindows) {
|
||||
try {
|
||||
const WindowsMutex = (require.__$__nodeRequire('windows-mutex') as typeof import('windows-mutex')).Mutex;
|
||||
const mutex = new WindowsMutex(product.win32MutexName);
|
||||
once(this.lifecycleService.onWillShutdown)(() => mutex.release());
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
@@ -543,6 +554,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
|
||||
// Find suitable window or folder path to open files in
|
||||
const fileToCheck = fileInputs.filesToOpenOrCreate[0] || fileInputs.filesToDiff[0];
|
||||
|
||||
// only look at the windows with correct authority
|
||||
const windows = WindowsManager.WINDOWS.filter(w => w.remoteAuthority === fileInputs!.remoteAuthority);
|
||||
|
||||
@@ -639,7 +651,6 @@ export class WindowsManager implements IWindowsMainService {
|
||||
|
||||
// Handle folders to open (instructed and to restore)
|
||||
const allFoldersToOpen = arrays.distinct(foldersToOpen, folder => getComparisonKey(folder.folderUri)); // prevent duplicates
|
||||
|
||||
if (allFoldersToOpen.length > 0) {
|
||||
|
||||
// Check for existing instances
|
||||
@@ -713,7 +724,9 @@ export class WindowsManager implements IWindowsMainService {
|
||||
if (fileInputs && !emptyToOpen) {
|
||||
emptyToOpen++;
|
||||
}
|
||||
|
||||
const remoteAuthority = fileInputs ? fileInputs.remoteAuthority : (openConfig.cli && openConfig.cli.remote || undefined);
|
||||
|
||||
for (let i = 0; i < emptyToOpen; i++) {
|
||||
usedWindows.push(this.openInBrowserWindow({
|
||||
userEnv: openConfig.userEnv,
|
||||
@@ -1289,7 +1302,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
// For all other cases we first call into registerEmptyWindowBackupSync() to set it before
|
||||
// loading the window.
|
||||
if (options.emptyWindowBackupInfo) {
|
||||
configuration.backupPath = join(this.environmentService.backupHome, options.emptyWindowBackupInfo.backupFolder);
|
||||
configuration.backupPath = join(this.environmentService.backupHome.fsPath, options.emptyWindowBackupInfo.backupFolder);
|
||||
}
|
||||
|
||||
let window: ICodeWindow | undefined;
|
||||
@@ -1536,14 +1549,13 @@ export class WindowsManager implements IWindowsMainService {
|
||||
return state;
|
||||
}
|
||||
|
||||
reload(win: ICodeWindow, cli?: ParsedArgs): void {
|
||||
async reload(win: ICodeWindow, cli?: ParsedArgs): Promise<void> {
|
||||
|
||||
// Only reload when the window has not vetoed this
|
||||
this.lifecycleService.unload(win, UnloadReason.RELOAD).then(veto => {
|
||||
if (!veto) {
|
||||
win.reload(undefined, cli);
|
||||
}
|
||||
});
|
||||
const veto = await this.lifecycleService.unload(win, UnloadReason.RELOAD);
|
||||
if (!veto) {
|
||||
win.reload(undefined, cli);
|
||||
}
|
||||
}
|
||||
|
||||
closeWorkspace(win: ICodeWindow): void {
|
||||
@@ -1554,8 +1566,10 @@ export class WindowsManager implements IWindowsMainService {
|
||||
});
|
||||
}
|
||||
|
||||
enterWorkspace(win: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | undefined> {
|
||||
return this.workspacesManager.enterWorkspace(win, path).then(result => result ? this.doEnterWorkspace(win, result) : undefined);
|
||||
async enterWorkspace(win: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | undefined> {
|
||||
const result = await this.workspacesManager.enterWorkspace(win, path);
|
||||
|
||||
return result ? this.doEnterWorkspace(win, result) : undefined;
|
||||
}
|
||||
|
||||
private doEnterWorkspace(win: ICodeWindow, result: IEnterWorkspaceResult): IEnterWorkspaceResult {
|
||||
@@ -1666,14 +1680,13 @@ export class WindowsManager implements IWindowsMainService {
|
||||
|
||||
private onWindowError(window: ICodeWindow, error: WindowError): void {
|
||||
this.logService.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive');
|
||||
|
||||
/* __GDPR__
|
||||
"windowerror" : {
|
||||
"type" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('windowerror', { type: error });
|
||||
|
||||
type WindowErrorClassification = {
|
||||
type: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
|
||||
};
|
||||
type WindowErrorEvent = {
|
||||
type: WindowError;
|
||||
};
|
||||
this.telemetryService.publicLog2<WindowErrorEvent, WindowErrorClassification>('windowerror', { type: error });
|
||||
// Unresponsive
|
||||
if (error === WindowError.UNRESPONSIVE) {
|
||||
if (window.isExtensionDevelopmentHost || window.isExtensionTestHost || (window.win && window.win.webContents && window.win.webContents.isDevToolsOpened())) {
|
||||
@@ -1751,8 +1764,10 @@ export class WindowsManager implements IWindowsMainService {
|
||||
const paths = await this.dialogs.pick({ ...options, pickFolders: true, pickFiles: true, title });
|
||||
if (paths) {
|
||||
this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFileFolder', options.telemetryExtraData);
|
||||
const urisToOpen = await Promise.all(paths.map(path => {
|
||||
return dirExists(path).then(isDir => isDir ? { folderUri: URI.file(path) } : { fileUri: URI.file(path) });
|
||||
const urisToOpen = await Promise.all(paths.map(async path => {
|
||||
const isDir = await dirExists(path);
|
||||
|
||||
return isDir ? { folderUri: URI.file(path) } : { fileUri: URI.file(path) };
|
||||
}));
|
||||
this.open({
|
||||
context: OpenContext.DIALOG,
|
||||
@@ -1880,7 +1895,7 @@ class Dialogs {
|
||||
this.noWindowDialogQueue = new Queue<void>();
|
||||
}
|
||||
|
||||
pick(options: IInternalNativeOpenDialogOptions): Promise<string[] | undefined> {
|
||||
async pick(options: IInternalNativeOpenDialogOptions): Promise<string[] | undefined> {
|
||||
|
||||
// Ensure dialog options
|
||||
const dialogOptions: Electron.OpenDialogOptions = {
|
||||
@@ -1913,16 +1928,16 @@ class Dialogs {
|
||||
// Show Dialog
|
||||
const focusedWindow = (typeof options.windowId === 'number' ? this.windowsMainService.getWindowById(options.windowId) : undefined) || this.windowsMainService.getFocusedWindow();
|
||||
|
||||
return this.showOpenDialog(dialogOptions, focusedWindow).then(paths => {
|
||||
if (paths && paths.length > 0) {
|
||||
const paths = await this.showOpenDialog(dialogOptions, focusedWindow);
|
||||
if (paths && paths.length > 0) {
|
||||
|
||||
// Remember path in storage for next time
|
||||
this.stateService.setItem(Dialogs.workingDirPickerStorageKey, dirname(paths[0]));
|
||||
return paths;
|
||||
}
|
||||
// Remember path in storage for next time
|
||||
this.stateService.setItem(Dialogs.workingDirPickerStorageKey, dirname(paths[0]));
|
||||
|
||||
return undefined;
|
||||
});
|
||||
return paths;
|
||||
}
|
||||
|
||||
return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check
|
||||
}
|
||||
|
||||
private getDialogQueue(window?: ICodeWindow): Queue<any> {
|
||||
@@ -2028,28 +2043,26 @@ class WorkspacesManager {
|
||||
private readonly windowsMainService: IWindowsMainService,
|
||||
) { }
|
||||
|
||||
enterWorkspace(window: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | null> {
|
||||
async enterWorkspace(window: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | null> {
|
||||
if (!window || !window.win || !window.isReady) {
|
||||
return Promise.resolve(null); // return early if the window is not ready or disposed
|
||||
return null; // return early if the window is not ready or disposed
|
||||
}
|
||||
|
||||
return this.isValidTargetWorkspacePath(window, path).then(isValid => {
|
||||
if (!isValid) {
|
||||
return null; // return early if the workspace is not valid
|
||||
}
|
||||
const workspaceIdentifier = getWorkspaceIdentifier(path);
|
||||
return this.doOpenWorkspace(window, workspaceIdentifier);
|
||||
});
|
||||
const isValid = await this.isValidTargetWorkspacePath(window, path);
|
||||
if (!isValid) {
|
||||
return null; // return early if the workspace is not valid
|
||||
}
|
||||
|
||||
return this.doOpenWorkspace(window, getWorkspaceIdentifier(path));
|
||||
}
|
||||
|
||||
private isValidTargetWorkspacePath(window: ICodeWindow, path?: URI): Promise<boolean> {
|
||||
private async isValidTargetWorkspacePath(window: ICodeWindow, path?: URI): Promise<boolean> {
|
||||
if (!path) {
|
||||
return Promise.resolve(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (window.openedWorkspace && isEqual(window.openedWorkspace.configPath, path)) {
|
||||
return Promise.resolve(false); // window is already opened on a workspace with that path
|
||||
return false; // window is already opened on a workspace with that path
|
||||
}
|
||||
|
||||
// Prevent overwriting a workspace that is currently opened in another window
|
||||
@@ -2063,10 +2076,12 @@ class WorkspacesManager {
|
||||
noLink: true
|
||||
};
|
||||
|
||||
return this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow()).then(() => false);
|
||||
await this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return Promise.resolve(true); // OK
|
||||
return true; // OK
|
||||
}
|
||||
|
||||
private doOpenWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult {
|
||||
@@ -2090,14 +2105,16 @@ class WorkspacesManager {
|
||||
|
||||
return { workspace, backupPath };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function resourceFromURIToOpen(u: IURIToOpen) {
|
||||
function resourceFromURIToOpen(u: IURIToOpen): URI {
|
||||
if (isWorkspaceToOpen(u)) {
|
||||
return u.workspaceUri;
|
||||
} else if (isFolderToOpen(u)) {
|
||||
}
|
||||
|
||||
if (isFolderToOpen(u)) {
|
||||
return u.folderUri;
|
||||
}
|
||||
|
||||
return u.fileUri;
|
||||
}
|
||||
Reference in New Issue
Block a user