Update to VS Code 1.52.1
This commit is contained in:
@ -33,7 +33,14 @@
|
||||
self.require = {
|
||||
baseUrl: `${window.location.origin}/static/out`,
|
||||
recordStats: true,
|
||||
createTrustedScriptURL: value => value,
|
||||
trustedTypesPolicy: window.trustedTypes?.createPolicy('amdLoader', {
|
||||
createScriptURL(value) {
|
||||
if(value.startsWith(window.location.origin)) {
|
||||
return value;
|
||||
}
|
||||
throw new Error(`Invalid script url: ${value}`)
|
||||
}
|
||||
}),
|
||||
paths: {
|
||||
'vscode-textmate': `${window.location.origin}/static/remote/web/node_modules/vscode-textmate/release/main`,
|
||||
'vscode-oniguruma': `${window.location.origin}/static/remote/web/node_modules/vscode-oniguruma/release/main`,
|
||||
|
@ -32,7 +32,14 @@
|
||||
self.require = {
|
||||
baseUrl: `${window.location.origin}/static/out`,
|
||||
recordStats: true,
|
||||
createTrustedScriptURL: value => value,
|
||||
trustedTypesPolicy: window.trustedTypes?.createPolicy('amdLoader', {
|
||||
createScriptURL(value) {
|
||||
if(value.startsWith(window.location.origin)) {
|
||||
return value;
|
||||
}
|
||||
throw new Error(`Invalid script url: ${value}`)
|
||||
}
|
||||
}),
|
||||
paths: {
|
||||
'vscode-textmate': `${window.location.origin}/static/node_modules/vscode-textmate/release/main`,
|
||||
'vscode-oniguruma': `${window.location.origin}/static/node_modules/vscode-oniguruma/release/main`,
|
||||
|
@ -17,7 +17,11 @@ import { isStandalone } from 'vs/base/browser/browser';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
<<<<<<< HEAD
|
||||
import { encodePath } from 'vs/server/node/util';
|
||||
=======
|
||||
import { parseLogLevel } from 'vs/platform/log/common/log';
|
||||
>>>>>>> e4a830e9b7ca039c7c70697786d29f5b6679d775
|
||||
|
||||
function doCreateUri(path: string, queryValues: Map<string, string>): URI {
|
||||
let query: string | undefined = undefined;
|
||||
@ -438,7 +442,50 @@ class WindowIndicator implements IWindowIndicator {
|
||||
// Find workspace to open and payload
|
||||
let foundWorkspace = false;
|
||||
let workspace: IWorkspace;
|
||||
<<<<<<< HEAD
|
||||
let payload = config.workspaceProvider?.payload || Object.create(null);
|
||||
=======
|
||||
let payload = Object.create(null);
|
||||
let logLevel: string | undefined = undefined;
|
||||
|
||||
const query = new URL(document.location.href).searchParams;
|
||||
query.forEach((value, key) => {
|
||||
switch (key) {
|
||||
|
||||
// Folder
|
||||
case WorkspaceProvider.QUERY_PARAM_FOLDER:
|
||||
workspace = { folderUri: URI.parse(value) };
|
||||
foundWorkspace = true;
|
||||
break;
|
||||
|
||||
// Workspace
|
||||
case WorkspaceProvider.QUERY_PARAM_WORKSPACE:
|
||||
workspace = { workspaceUri: URI.parse(value) };
|
||||
foundWorkspace = true;
|
||||
break;
|
||||
|
||||
// Empty
|
||||
case WorkspaceProvider.QUERY_PARAM_EMPTY_WINDOW:
|
||||
workspace = undefined;
|
||||
foundWorkspace = true;
|
||||
break;
|
||||
|
||||
// Payload
|
||||
case WorkspaceProvider.QUERY_PARAM_PAYLOAD:
|
||||
try {
|
||||
payload = JSON.parse(value);
|
||||
} catch (error) {
|
||||
console.error(error); // possible invalid JSON
|
||||
}
|
||||
break;
|
||||
|
||||
// Log level
|
||||
case 'logLevel':
|
||||
logLevel = value;
|
||||
break;
|
||||
}
|
||||
});
|
||||
>>>>>>> e4a830e9b7ca039c7c70697786d29f5b6679d775
|
||||
|
||||
// If no workspace is provided through the URL, check for config attribute from server
|
||||
if (!foundWorkspace) {
|
||||
@ -496,6 +543,7 @@ class WindowIndicator implements IWindowIndicator {
|
||||
// Finally create workbench
|
||||
create(document.body, {
|
||||
...config,
|
||||
logLevel: logLevel ? parseLogLevel(logLevel) : undefined,
|
||||
settingsSyncOptions,
|
||||
windowIndicator,
|
||||
productQualityChangeHandler,
|
||||
|
@ -51,7 +51,7 @@ import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService, IUserDataSyncStoreManagementService, IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
|
||||
import { UserDataSyncStoreService, UserDataSyncStoreManagementService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
|
||||
import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, UserDataSyncMachinesServiceChannel, UserDataSyncAccountServiceChannel, UserDataSyncStoreManagementServiceChannel, StorageKeysSyncRegistryChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
|
||||
import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, UserDataSyncMachinesServiceChannel, UserDataSyncAccountServiceChannel, UserDataSyncStoreManagementServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
|
||||
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
||||
import { LoggerService } from 'vs/platform/log/node/loggerService';
|
||||
import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog';
|
||||
@ -63,7 +63,6 @@ import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagemen
|
||||
import { UserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSyncResourceEnablementService';
|
||||
import { IUserDataSyncAccountService, UserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount';
|
||||
import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService';
|
||||
import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { ExtensionTipsService } from 'vs/platform/extensionManagement/electron-sandbox/extensionTipsService';
|
||||
import { UserDataSyncMachinesService, IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
|
||||
import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
|
||||
@ -72,6 +71,7 @@ import { ActiveWindowManager } from 'vs/platform/windows/common/windowTracker';
|
||||
import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogAppender';
|
||||
import { UserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataAutoSyncService';
|
||||
import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions';
|
||||
import { ExtensionsStorageSyncService, IExtensionsStorageSyncService } from 'vs/platform/userDataSync/common/extensionsStorageSync';
|
||||
|
||||
export interface ISharedProcessConfiguration {
|
||||
readonly machineId: string;
|
||||
@ -151,7 +151,6 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
|
||||
services.set(IStorageService, storageService);
|
||||
disposables.add(toDisposable(() => storageService.flush()));
|
||||
|
||||
services.set(IStorageKeysSyncRegistryService, new SyncDescriptor(StorageKeysSyncRegistryService));
|
||||
services.set(IEnvironmentService, environmentService);
|
||||
services.set(INativeEnvironmentService, environmentService);
|
||||
|
||||
@ -188,7 +187,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
|
||||
appender: telemetryAppender,
|
||||
commonProperties: resolveCommonProperties(product.commit, product.version, configuration.machineId, product.msftInternalDomains, installSourcePath),
|
||||
sendErrorTelemetry: true,
|
||||
piiPaths: extensionsPath ? [appRoot, extensionsPath] : [appRoot]
|
||||
piiPaths: [appRoot, extensionsPath]
|
||||
};
|
||||
|
||||
telemetryService = new TelemetryService(config, configurationService);
|
||||
@ -199,10 +198,6 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
|
||||
}
|
||||
server.registerChannel('telemetryAppender', new TelemetryAppenderChannel(telemetryAppender));
|
||||
|
||||
const storageKeysSyncRegistryService = accessor.get(IStorageKeysSyncRegistryService);
|
||||
const storageKeysSyncChannel = new StorageKeysSyncRegistryChannel(storageKeysSyncRegistryService);
|
||||
server.registerChannel('storageKeysSyncRegistryService', storageKeysSyncChannel);
|
||||
|
||||
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
|
||||
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
|
||||
services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService));
|
||||
@ -214,6 +209,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
|
||||
services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', client => client.ctx !== 'main')));
|
||||
services.set(IGlobalExtensionEnablementService, new SyncDescriptor(GlobalExtensionEnablementService));
|
||||
services.set(IIgnoredExtensionsManagementService, new SyncDescriptor(IgnoredExtensionsManagementService));
|
||||
services.set(IExtensionsStorageSyncService, new SyncDescriptor(ExtensionsStorageSyncService));
|
||||
services.set(IUserDataSyncStoreManagementService, new SyncDescriptor(UserDataSyncStoreManagementService));
|
||||
services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService));
|
||||
services.set(IUserDataSyncMachinesService, new SyncDescriptor(UserDataSyncMachinesService));
|
||||
|
@ -9,15 +9,12 @@
|
||||
'use strict';
|
||||
|
||||
(function () {
|
||||
const bootstrapWindow = bootstrapWindowLib();
|
||||
|
||||
// Add a perf entry right from the top
|
||||
const perf = perfLib();
|
||||
const perf = bootstrapWindow.perfLib();
|
||||
perf.mark('renderer/started');
|
||||
|
||||
// Load environment in parallel to workbench loading to avoid waterfall
|
||||
const bootstrapWindow = bootstrapWindowLib();
|
||||
const whenEnvResolved = bootstrapWindow.globals().process.whenEnvResolved();
|
||||
|
||||
// Load workbench main JS, CSS and NLS all in parallel. This is an
|
||||
// optimization to prevent a waterfall of loading to happen, because
|
||||
// we know for a fact that workbench.desktop.main will depend on
|
||||
@ -31,12 +28,6 @@
|
||||
|
||||
// Mark start of workbench
|
||||
perf.mark('didLoadWorkbenchMain');
|
||||
performance.mark('workbench-start');
|
||||
|
||||
// Wait for process environment being fully resolved
|
||||
await whenEnvResolved;
|
||||
|
||||
perf.mark('main/startup');
|
||||
|
||||
// @ts-ignore
|
||||
return require('vs/workbench/electron-browser/desktop.main').main(configuration);
|
||||
@ -58,23 +49,11 @@
|
||||
|
||||
//region Helpers
|
||||
|
||||
function perfLib() {
|
||||
globalThis.MonacoPerformanceMarks = globalThis.MonacoPerformanceMarks || [];
|
||||
|
||||
return {
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
mark(name) {
|
||||
globalThis.MonacoPerformanceMarks.push(name, Date.now());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {{
|
||||
* load: (modules: string[], resultCallback: (result, configuration: object) => any, options: object) => unknown,
|
||||
* globals: () => typeof import('../../../base/parts/sandbox/electron-sandbox/globals')
|
||||
* globals: () => typeof import('../../../base/parts/sandbox/electron-sandbox/globals'),
|
||||
* perfLib: () => { mark: (name: string) => void }
|
||||
* }}
|
||||
*/
|
||||
function bootstrapWindowLib() {
|
||||
@ -187,5 +166,5 @@
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
}());
|
||||
|
@ -3,13 +3,13 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { app, ipcMain as ipc, systemPreferences, contentTracing, protocol, IpcMainEvent, BrowserWindow, dialog, session } from 'electron';
|
||||
import { IProcessEnvironment, isWindows, isMacintosh } from 'vs/base/common/platform';
|
||||
import { app, ipcMain, systemPreferences, contentTracing, protocol, BrowserWindow, dialog, session } from 'electron';
|
||||
import { IProcessEnvironment, isWindows, isMacintosh, isLinux } from 'vs/base/common/platform';
|
||||
import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService';
|
||||
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
|
||||
import { OpenContext } from 'vs/platform/windows/node/window';
|
||||
import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
import { getShellEnvironment } from 'vs/code/node/shellEnv';
|
||||
import { resolveShellEnv } from 'vs/code/node/shellEnv';
|
||||
import { IUpdateService } from 'vs/platform/update/common/update';
|
||||
import { UpdateChannel } from 'vs/platform/update/electron-main/updateIpc';
|
||||
import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron-main';
|
||||
@ -24,7 +24,7 @@ import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IStateService } from 'vs/platform/state/node/state';
|
||||
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IURLService } from 'vs/platform/url/common/url';
|
||||
import { IOpenURLOptions, IURLService } from 'vs/platform/url/common/url';
|
||||
import { URLHandlerChannelClient, URLHandlerRouter } from 'vs/platform/url/common/urlIpc';
|
||||
import { ITelemetryService, machineIdKey } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
@ -35,6 +35,7 @@ import { getDelayedChannel, StaticRouter, createChannelReceiver, createChannelSe
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { ProxyAuthHandler } from 'vs/code/electron-main/auth';
|
||||
import { ProxyAuthHandler2 } from 'vs/code/electron-main/auth2';
|
||||
import { FileProtocolHandler } from 'vs/code/electron-main/protocol';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
@ -52,7 +53,7 @@ import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver';
|
||||
import { IMenubarMainService, MenubarMainService } from 'vs/platform/menubar/electron-main/menubarMainService';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { registerContextMenuListener } from 'vs/base/parts/contextmenu/electron-main/contextmenu';
|
||||
import { sep, posix } from 'vs/base/common/path';
|
||||
import { sep, posix, join, isAbsolute } from 'vs/base/common/path';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
@ -72,7 +73,6 @@ import { INativeHostMainService, NativeHostMainService } from 'vs/platform/nativ
|
||||
import { ISharedProcessMainService, SharedProcessMainService } from 'vs/platform/ipc/electron-main/sharedProcessMainService';
|
||||
import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/electron-main/dialogs';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { mnemonicButtonLabel, getPathLabel } from 'vs/base/common/labels';
|
||||
import { WebviewMainService } from 'vs/platform/webview/electron-main/webviewMainService';
|
||||
import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService';
|
||||
@ -82,6 +82,14 @@ import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { EncryptionMainService, IEncryptionMainService } from 'vs/platform/encryption/electron-main/encryptionMainService';
|
||||
import { ActiveWindowManager } from 'vs/platform/windows/common/windowTracker';
|
||||
import { IKeyboardLayoutMainService, KeyboardLayoutMainService } from 'vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService';
|
||||
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
|
||||
import { DisplayMainService, IDisplayMainService } from 'vs/platform/display/electron-main/displayMainService';
|
||||
import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper';
|
||||
import { isEqualOrParent } from 'vs/base/common/extpath';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust';
|
||||
import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/extensionUrlTrustService';
|
||||
|
||||
export class CodeApplication extends Disposable {
|
||||
private windowsMainService: IWindowsMainService | undefined;
|
||||
@ -96,7 +104,8 @@ export class CodeApplication extends Disposable {
|
||||
@IEnvironmentMainService private readonly environmentService: IEnvironmentMainService,
|
||||
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IStateService private readonly stateService: IStateService
|
||||
@IStateService private readonly stateService: IStateService,
|
||||
@IFileService private readonly fileService: IFileService
|
||||
) {
|
||||
super();
|
||||
|
||||
@ -118,9 +127,7 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
// Accessibility change event
|
||||
app.on('accessibility-support-changed', (event, accessibilitySupportEnabled) => {
|
||||
if (this.windowsMainService) {
|
||||
this.windowsMainService.sendToAll('vscode:accessibilitySupportChanged', accessibilitySupportEnabled);
|
||||
}
|
||||
this.windowsMainService?.sendToAll('vscode:accessibilitySupportChanged', accessibilitySupportEnabled);
|
||||
});
|
||||
|
||||
// macOS dock activate
|
||||
@ -128,8 +135,8 @@ export class CodeApplication extends Disposable {
|
||||
this.logService.trace('app#activate');
|
||||
|
||||
// Mac only event: open new window when we get activated
|
||||
if (!hasVisibleWindows && this.windowsMainService) {
|
||||
this.windowsMainService.openEmptyWindow({ context: OpenContext.DOCK });
|
||||
if (!hasVisibleWindows) {
|
||||
this.windowsMainService?.openEmptyWindow({ context: OpenContext.DOCK });
|
||||
}
|
||||
});
|
||||
|
||||
@ -214,9 +221,7 @@ export class CodeApplication extends Disposable {
|
||||
contents.on('new-window', (event, url) => {
|
||||
event.preventDefault(); // prevent code that wants to open links
|
||||
|
||||
if (this.nativeHostMainService) {
|
||||
this.nativeHostMainService.openExternal(undefined, url);
|
||||
}
|
||||
this.nativeHostMainService?.openExternal(undefined, url);
|
||||
});
|
||||
|
||||
session.defaultSession.setPermissionRequestHandler((webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback) => {
|
||||
@ -247,62 +252,119 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
// Handle paths delayed in case more are coming!
|
||||
runningTimeout = setTimeout(() => {
|
||||
if (this.windowsMainService) {
|
||||
this.windowsMainService.open({
|
||||
context: OpenContext.DOCK /* can also be opening from finder while app is running */,
|
||||
cli: this.environmentService.args,
|
||||
urisToOpen: macOpenFileURIs,
|
||||
gotoLineMode: false,
|
||||
preferNewWindow: true /* dropping on the dock or opening from finder prefers to open in a new window */
|
||||
});
|
||||
this.windowsMainService?.open({
|
||||
context: OpenContext.DOCK /* can also be opening from finder while app is running */,
|
||||
cli: this.environmentService.args,
|
||||
urisToOpen: macOpenFileURIs,
|
||||
gotoLineMode: false,
|
||||
preferNewWindow: true /* dropping on the dock or opening from finder prefers to open in a new window */
|
||||
});
|
||||
|
||||
macOpenFileURIs = [];
|
||||
runningTimeout = null;
|
||||
}
|
||||
macOpenFileURIs = [];
|
||||
runningTimeout = null;
|
||||
}, 100);
|
||||
});
|
||||
|
||||
app.on('new-window-for-tab', () => {
|
||||
if (this.windowsMainService) {
|
||||
this.windowsMainService.openEmptyWindow({ context: OpenContext.DESKTOP }); //macOS native tab "+" button
|
||||
}
|
||||
this.windowsMainService?.openEmptyWindow({ context: OpenContext.DESKTOP }); //macOS native tab "+" button
|
||||
});
|
||||
|
||||
ipc.on('vscode:fetchShellEnv', async (event: IpcMainEvent) => {
|
||||
//#region Bootstrap IPC Handlers
|
||||
|
||||
ipcMain.on('vscode:fetchShellEnv', async event => {
|
||||
const webContents = event.sender;
|
||||
const window = this.windowsMainService?.getWindowByWebContents(event.sender);
|
||||
|
||||
try {
|
||||
const shellEnv = await getShellEnvironment(this.logService, this.environmentService);
|
||||
let replied = false;
|
||||
|
||||
if (!webContents.isDestroyed()) {
|
||||
webContents.send('vscode:acceptShellEnv', shellEnv);
|
||||
function acceptShellEnv(env: NodeJS.ProcessEnv): void {
|
||||
clearTimeout(shellEnvSlowWarningHandle);
|
||||
clearTimeout(shellEnvTimeoutErrorHandle);
|
||||
|
||||
if (!replied) {
|
||||
replied = true;
|
||||
|
||||
if (!webContents.isDestroyed()) {
|
||||
webContents.send('vscode:acceptShellEnv', env);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (!webContents.isDestroyed()) {
|
||||
webContents.send('vscode:acceptShellEnv', {});
|
||||
}
|
||||
|
||||
this.logService.error('Error fetching shell env', error);
|
||||
}
|
||||
|
||||
// Handle slow shell environment resolve calls:
|
||||
// - a warning after 3s but continue to resolve
|
||||
// - an error after 10s and stop trying to resolve
|
||||
const cts = new CancellationTokenSource();
|
||||
const shellEnvSlowWarningHandle = setTimeout(() => window?.sendWhenReady('vscode:showShellEnvSlowWarning', cts.token), 3000);
|
||||
const shellEnvTimeoutErrorHandle = setTimeout(function () {
|
||||
cts.dispose(true);
|
||||
window?.sendWhenReady('vscode:showShellEnvTimeoutError', CancellationToken.None);
|
||||
acceptShellEnv({});
|
||||
}, 10000);
|
||||
|
||||
// Prefer to use the args and env from the target window
|
||||
// when resolving the shell env. It is possible that
|
||||
// a first window was opened from the UI but a second
|
||||
// from the CLI and that has implications for wether to
|
||||
// resolve the shell environment or not.
|
||||
let args: NativeParsedArgs;
|
||||
let env: NodeJS.ProcessEnv;
|
||||
if (window?.config) {
|
||||
args = window.config;
|
||||
env = { ...process.env, ...window.config.userEnv };
|
||||
} else {
|
||||
args = this.environmentService.args;
|
||||
env = process.env;
|
||||
}
|
||||
|
||||
// Resolve shell env
|
||||
const shellEnv = await resolveShellEnv(this.logService, args, env);
|
||||
acceptShellEnv(shellEnv);
|
||||
});
|
||||
|
||||
ipc.on('vscode:toggleDevTools', (event: IpcMainEvent) => event.sender.toggleDevTools());
|
||||
ipc.on('vscode:openDevTools', (event: IpcMainEvent) => event.sender.openDevTools());
|
||||
ipcMain.handle('vscode:writeNlsFile', (event, path: unknown, data: unknown) => {
|
||||
const uri = this.validateNlsPath([path]);
|
||||
if (!uri || typeof data !== 'string') {
|
||||
return Promise.reject('Invalid operation (vscode:writeNlsFile)');
|
||||
}
|
||||
|
||||
ipc.on('vscode:reloadWindow', (event: IpcMainEvent) => event.sender.reload());
|
||||
return this.fileService.writeFile(uri, VSBuffer.fromString(data));
|
||||
});
|
||||
|
||||
// Some listeners after window opened
|
||||
(async () => {
|
||||
await this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen);
|
||||
ipcMain.handle('vscode:readNlsFile', async (event, ...paths: unknown[]) => {
|
||||
const uri = this.validateNlsPath(paths);
|
||||
if (!uri) {
|
||||
return Promise.reject('Invalid operation (vscode:readNlsFile)');
|
||||
}
|
||||
|
||||
// Keyboard layout changes (after window opened)
|
||||
const nativeKeymap = await import('native-keymap');
|
||||
nativeKeymap.onDidChangeKeyboardLayout(() => {
|
||||
if (this.windowsMainService) {
|
||||
this.windowsMainService.sendToAll('vscode:keyboardLayoutChanged');
|
||||
return (await this.fileService.readFile(uri)).value.toString();
|
||||
});
|
||||
|
||||
ipcMain.on('vscode:toggleDevTools', event => event.sender.toggleDevTools());
|
||||
ipcMain.on('vscode:openDevTools', event => event.sender.openDevTools());
|
||||
|
||||
ipcMain.on('vscode:reloadWindow', event => event.sender.reload());
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
private validateNlsPath(pathSegments: unknown[]): URI | undefined {
|
||||
let path: string | undefined = undefined;
|
||||
|
||||
for (const pathSegment of pathSegments) {
|
||||
if (typeof pathSegment === 'string') {
|
||||
if (typeof path !== 'string') {
|
||||
path = pathSegment;
|
||||
} else {
|
||||
path = join(path, pathSegment);
|
||||
}
|
||||
});
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof path !== 'string' || !isAbsolute(path) || !isEqualOrParent(path, this.environmentService.cachedLanguagesPath, !isLinux)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return URI.file(path);
|
||||
}
|
||||
|
||||
private onUnexpectedError(err: Error): void {
|
||||
@ -315,9 +377,7 @@ export class CodeApplication extends Disposable {
|
||||
};
|
||||
|
||||
// handle on client side
|
||||
if (this.windowsMainService) {
|
||||
this.windowsMainService.sendToFocused('vscode:reportError', JSON.stringify(friendlyError));
|
||||
}
|
||||
this.windowsMainService?.sendToFocused('vscode:reportError', JSON.stringify(friendlyError));
|
||||
}
|
||||
|
||||
this.logService.error(`[uncaught exception in main]: ${err}`);
|
||||
@ -354,6 +414,9 @@ export class CodeApplication extends Disposable {
|
||||
this.logService.error(error);
|
||||
}
|
||||
|
||||
// Setup Protocol Handler
|
||||
const fileProtocolHandler = this._register(this.instantiationService.createInstance(FileProtocolHandler));
|
||||
|
||||
// Create Electron IPC Server
|
||||
const electronIpcServer = new ElectronIPCServer();
|
||||
|
||||
@ -376,7 +439,7 @@ export class CodeApplication extends Disposable {
|
||||
});
|
||||
this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => {
|
||||
this._register(new RunOnceScheduler(async () => {
|
||||
sharedProcess.spawn(await getShellEnvironment(this.logService, this.environmentService));
|
||||
sharedProcess.spawn(await resolveShellEnv(this.logService, this.environmentService.args, process.env));
|
||||
}, 3000)).schedule();
|
||||
});
|
||||
|
||||
@ -391,15 +454,15 @@ export class CodeApplication extends Disposable {
|
||||
this._register(server);
|
||||
}
|
||||
|
||||
// Setup Auth Handler
|
||||
if (this.configurationService.getValue('window.enableExperimentalProxyLoginDialog') !== true) {
|
||||
// Setup Auth Handler (TODO@ben remove old auth handler eventually)
|
||||
if (this.configurationService.getValue('window.enableExperimentalProxyLoginDialog') === false) {
|
||||
this._register(new ProxyAuthHandler());
|
||||
} else {
|
||||
this._register(appInstantiationService.createInstance(ProxyAuthHandler2));
|
||||
}
|
||||
|
||||
// Open Windows
|
||||
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient));
|
||||
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient, fileProtocolHandler));
|
||||
|
||||
// Post Open Windows Tasks
|
||||
appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor));
|
||||
@ -453,10 +516,13 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
services.set(IIssueMainService, new SyncDescriptor(IssueMainService, [machineId, this.userEnv]));
|
||||
services.set(IEncryptionMainService, new SyncDescriptor(EncryptionMainService, [machineId]));
|
||||
services.set(IKeyboardLayoutMainService, new SyncDescriptor(KeyboardLayoutMainService));
|
||||
services.set(IDisplayMainService, new SyncDescriptor(DisplayMainService));
|
||||
services.set(INativeHostMainService, new SyncDescriptor(NativeHostMainService));
|
||||
services.set(IWebviewManagerService, new SyncDescriptor(WebviewMainService));
|
||||
services.set(IWorkspacesService, new SyncDescriptor(WorkspacesService));
|
||||
services.set(IMenubarMainService, new SyncDescriptor(MenubarMainService));
|
||||
services.set(IExtensionUrlTrustService, new SyncDescriptor(ExtensionUrlTrustService));
|
||||
|
||||
const storageMainService = new StorageMainService(this.logService, this.environmentService);
|
||||
services.set(IStorageMainService, storageMainService);
|
||||
@ -474,7 +540,7 @@ export class CodeApplication extends Disposable {
|
||||
const channel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('telemetryAppender')));
|
||||
const appender = new TelemetryAppenderClient(channel);
|
||||
const commonProperties = resolveCommonProperties(product.commit, product.version, machineId, product.msftInternalDomains, this.environmentService.installSourcePath);
|
||||
const piiPaths = this.environmentService.extensionsPath ? [this.environmentService.appRoot, this.environmentService.extensionsPath] : [this.environmentService.appRoot];
|
||||
const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath];
|
||||
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, sendErrorTelemetry: true };
|
||||
|
||||
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
|
||||
@ -502,14 +568,12 @@ export class CodeApplication extends Disposable {
|
||||
const path = await contentTracing.stopRecording(joinPath(this.environmentService.userHome, `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`).fsPath);
|
||||
|
||||
if (!timeout) {
|
||||
if (this.dialogMainService) {
|
||||
this.dialogMainService.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")]
|
||||
}, withNullAsUndefined(BrowserWindow.getFocusedWindow()));
|
||||
}
|
||||
this.dialogMainService?.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")]
|
||||
}, withNullAsUndefined(BrowserWindow.getFocusedWindow()));
|
||||
} else {
|
||||
this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`);
|
||||
}
|
||||
@ -525,7 +589,7 @@ export class CodeApplication extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise<Client<string>>): ICodeWindow[] {
|
||||
private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise<Client<string>>, fileProtocolHandler: FileProtocolHandler): ICodeWindow[] {
|
||||
|
||||
// Register more Main IPC services
|
||||
const launchMainService = accessor.get(ILaunchMainService);
|
||||
@ -545,6 +609,14 @@ export class CodeApplication extends Disposable {
|
||||
const encryptionChannel = createChannelReceiver(encryptionMainService);
|
||||
electronIpcServer.registerChannel('encryption', encryptionChannel);
|
||||
|
||||
const keyboardLayoutMainService = accessor.get(IKeyboardLayoutMainService);
|
||||
const keyboardLayoutChannel = createChannelReceiver(keyboardLayoutMainService);
|
||||
electronIpcServer.registerChannel('keyboardLayout', keyboardLayoutChannel);
|
||||
|
||||
const displayMainService = accessor.get(IDisplayMainService);
|
||||
const displayChannel = createChannelReceiver(displayMainService);
|
||||
electronIpcServer.registerChannel('display', displayChannel);
|
||||
|
||||
const nativeHostMainService = this.nativeHostMainService = accessor.get(INativeHostMainService);
|
||||
const nativeHostChannel = createChannelReceiver(this.nativeHostMainService);
|
||||
electronIpcServer.registerChannel('nativeHost', nativeHostChannel);
|
||||
@ -566,6 +638,10 @@ export class CodeApplication extends Disposable {
|
||||
const urlChannel = createChannelReceiver(urlService);
|
||||
electronIpcServer.registerChannel('url', urlChannel);
|
||||
|
||||
const extensionUrlTrustService = accessor.get(IExtensionUrlTrustService);
|
||||
const extensionUrlTrustChannel = createChannelReceiver(extensionUrlTrustService);
|
||||
electronIpcServer.registerChannel('extensionUrlTrust', extensionUrlTrustChannel);
|
||||
|
||||
const webviewManagerService = accessor.get(IWebviewManagerService);
|
||||
const webviewChannel = createChannelReceiver(webviewManagerService);
|
||||
electronIpcServer.registerChannel('webview', webviewChannel);
|
||||
@ -579,8 +655,10 @@ export class CodeApplication extends Disposable {
|
||||
electronIpcServer.registerChannel('logger', loggerChannel);
|
||||
sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel));
|
||||
|
||||
// ExtensionHost Debug broadcast service
|
||||
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
|
||||
fileProtocolHandler.injectWindowsMainService(windowsMainService);
|
||||
|
||||
// ExtensionHost Debug broadcast service
|
||||
electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ElectronExtensionHostDebugBroadcastChannel(windowsMainService));
|
||||
|
||||
// Signal phase: ready (services set)
|
||||
@ -591,29 +669,32 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
// Check for initial URLs to handle from protocol link invocations
|
||||
const pendingWindowOpenablesFromProtocolLinks: IWindowOpenable[] = [];
|
||||
const pendingProtocolLinksToHandle = coalesce([
|
||||
|
||||
const pendingProtocolLinksToHandle = [
|
||||
// Windows/Linux: protocol handler invokes CLI with --open-url
|
||||
...this.environmentService.args['open-url'] ? this.environmentService.args._urls || [] : [],
|
||||
|
||||
// macOS: open-url events
|
||||
...((<any>global).getOpenUrls() || []) as string[]
|
||||
].map(pendingUrlToHandle => {
|
||||
].map(url => {
|
||||
try {
|
||||
return URI.parse(pendingUrlToHandle);
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
return { uri: URI.parse(url), url };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})).filter(pendingUriToHandle => {
|
||||
// if URI should be blocked, filter it out
|
||||
if (this.shouldBlockURI(pendingUriToHandle)) {
|
||||
}).filter((obj): obj is { uri: URI, url: string } => {
|
||||
if (!obj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// filter out any protocol link that wants to open as window so that
|
||||
// If URI should be blocked, filter it out
|
||||
if (this.shouldBlockURI(obj.uri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filter out any protocol link that wants to open as window so that
|
||||
// we open the right set of windows on startup and not restore the
|
||||
// previous workspace too.
|
||||
const windowOpenable = this.getWindowOpenableFromProtocolLink(pendingUriToHandle);
|
||||
const windowOpenable = this.getWindowOpenableFromProtocolLink(obj.uri);
|
||||
if (windowOpenable) {
|
||||
pendingWindowOpenablesFromProtocolLinks.push(windowOpenable);
|
||||
|
||||
@ -627,8 +708,9 @@ export class CodeApplication extends Disposable {
|
||||
const app = this;
|
||||
const environmentService = this.environmentService;
|
||||
urlService.registerHandler({
|
||||
async handleURL(uri: URI): Promise<boolean> {
|
||||
// if URI should be blocked, behave as if it's handled
|
||||
async handleURL(uri: URI, options?: IOpenURLOptions): Promise<boolean> {
|
||||
|
||||
// If URI should be blocked, behave as if it's handled
|
||||
if (app.shouldBlockURI(uri)) {
|
||||
return true;
|
||||
}
|
||||
@ -658,7 +740,7 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
await window.ready();
|
||||
|
||||
return urlService.open(uri);
|
||||
return urlService.open(uri, options);
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -666,11 +748,11 @@ export class CodeApplication extends Disposable {
|
||||
});
|
||||
|
||||
// Create a URL handler which forwards to the last active window
|
||||
const activeWindowManager = new ActiveWindowManager({
|
||||
const activeWindowManager = this._register(new ActiveWindowManager({
|
||||
onDidOpenWindow: nativeHostMainService.onDidOpenWindow,
|
||||
onDidFocusWindow: nativeHostMainService.onDidFocusWindow,
|
||||
getActiveWindowId: () => nativeHostMainService.getActiveWindowId(-1)
|
||||
});
|
||||
}));
|
||||
const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
|
||||
const urlHandlerRouter = new URLHandlerRouter(activeWindowRouter);
|
||||
const urlHandlerChannel = electronIpcServer.getChannel('urlHandler', urlHandlerRouter);
|
||||
@ -682,7 +764,7 @@ export class CodeApplication extends Disposable {
|
||||
// Open our first window
|
||||
const args = this.environmentService.args;
|
||||
const macOpenFiles: string[] = (<any>global).macOpenFiles;
|
||||
const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
|
||||
const context = isLaunchedFromCli(process.env) ? OpenContext.CLI : OpenContext.DESKTOP;
|
||||
const hasCliArgs = args._.length;
|
||||
const hasFolderURIs = !!args['folder-uri'];
|
||||
const hasFileURIs = !!args['file-uri'];
|
||||
@ -828,12 +910,14 @@ export class CodeApplication extends Disposable {
|
||||
updateService.initialize();
|
||||
}
|
||||
|
||||
// Start to fetch shell environment (if needed) after window has opened
|
||||
resolveShellEnv(this.logService, this.environmentService.args, process.env);
|
||||
|
||||
// If enable-crash-reporter argv is undefined then this is a fresh start,
|
||||
// based on telemetry.enableCrashreporter settings, generate a UUID which
|
||||
// will be used as crash reporter id and also update the json file.
|
||||
try {
|
||||
const fileService = accessor.get(IFileService);
|
||||
const argvContent = await fileService.readFile(this.environmentService.argvResource);
|
||||
const argvContent = await this.fileService.readFile(this.environmentService.argvResource);
|
||||
const argvString = argvContent.value.toString();
|
||||
const argvJSON = JSON.parse(stripComments(argvString));
|
||||
if (argvJSON['enable-crash-reporter'] === undefined) {
|
||||
@ -850,14 +934,11 @@ export class CodeApplication extends Disposable {
|
||||
'}'
|
||||
];
|
||||
const newArgvString = argvString.substring(0, argvString.length - 2).concat(',\n', additionalArgvContent.join('\n'));
|
||||
await fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(newArgvString));
|
||||
await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(newArgvString));
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
|
||||
// Start to fetch shell environment after window has opened
|
||||
getShellEnvironment(this.logService, this.environmentService);
|
||||
}
|
||||
|
||||
private handleRemoteAuthorities(): void {
|
||||
|
@ -13,6 +13,7 @@ import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeH
|
||||
import { IEncryptionMainService } from 'vs/platform/encryption/electron-main/encryptionMainService';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
interface ElectronAuthenticationResponseDetails extends AuthenticationResponseDetails {
|
||||
firstAuthAttempt?: boolean; // https://github.com/electron/electron/blob/84a42a050e7d45225e69df5bd2d2bf9f1037ea41/shell/browser/login_handler.cc#L70
|
||||
@ -192,7 +193,7 @@ export class ProxyAuthHandler2 extends Disposable {
|
||||
password: this.sessionCredentials?.password ?? storedPassword, // prefer to show already used password (if any) over stored
|
||||
replyChannel: `vscode:proxyAuthResponse:${generateUuid()}`
|
||||
};
|
||||
window.sendWhenReady('vscode:openProxyAuthenticationDialog', payload);
|
||||
window.sendWhenReady('vscode:openProxyAuthenticationDialog', CancellationToken.None, payload);
|
||||
this.state = ProxyAuthState.LoginDialogShown;
|
||||
|
||||
// Handle reply
|
||||
|
@ -200,7 +200,7 @@ class CodeMain {
|
||||
VSCODE_IPC_HOOK: environmentService.mainIPCHandle
|
||||
};
|
||||
|
||||
['VSCODE_NLS_CONFIG', 'VSCODE_LOGS', 'VSCODE_PORTABLE'].forEach(key => {
|
||||
['VSCODE_NLS_CONFIG', 'VSCODE_PORTABLE'].forEach(key => {
|
||||
const value = process.env[key];
|
||||
if (typeof value === 'string') {
|
||||
instanceEnvironment[key] = value;
|
||||
@ -343,15 +343,7 @@ class CodeMain {
|
||||
|
||||
private handleStartupDataDirError(environmentService: IEnvironmentMainService, error: NodeJS.ErrnoException): void {
|
||||
if (error.code === 'EACCES' || error.code === 'EPERM') {
|
||||
const directories = [environmentService.userDataPath];
|
||||
|
||||
if (environmentService.extensionsPath) {
|
||||
directories.push(environmentService.extensionsPath);
|
||||
}
|
||||
|
||||
if (XDG_RUNTIME_DIR) {
|
||||
directories.push(XDG_RUNTIME_DIR);
|
||||
}
|
||||
const directories = coalesce([environmentService.userDataPath, environmentService.extensionsPath, XDG_RUNTIME_DIR]);
|
||||
|
||||
this.showStartupWarningDialog(
|
||||
localize('startupDataDirError', "Unable to write program user data."),
|
||||
|
108
lib/vscode/src/vs/code/electron-main/protocol.ts
Normal file
108
lib/vscode/src/vs/code/electron-main/protocol.ts
Normal file
@ -0,0 +1,108 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { FileAccess, Schemas } from 'vs/base/common/network';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { session } from 'electron';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
|
||||
|
||||
type ProtocolCallback = { (result: string | Electron.FilePathWithHeaders | { error: number }): void };
|
||||
|
||||
export class FileProtocolHandler extends Disposable {
|
||||
|
||||
private readonly validRoots = TernarySearchTree.forUris<boolean>(() => !isLinux);
|
||||
|
||||
constructor(
|
||||
@INativeEnvironmentService environmentService: INativeEnvironmentService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
) {
|
||||
super();
|
||||
|
||||
const { defaultSession } = session;
|
||||
|
||||
// Define an initial set of roots we allow loading from
|
||||
// - appRoot : all files installed as part of the app
|
||||
// - extensions : all files shipped from extensions
|
||||
this.validRoots.set(URI.file(environmentService.appRoot), true);
|
||||
this.validRoots.set(URI.file(environmentService.extensionsPath), true);
|
||||
|
||||
// Register vscode-file:// handler
|
||||
defaultSession.protocol.registerFileProtocol(Schemas.vscodeFileResource, (request, callback) => this.handleResourceRequest(request, callback as unknown as ProtocolCallback));
|
||||
|
||||
// Block any file:// access (sandbox only)
|
||||
if (environmentService.args.__sandbox) {
|
||||
defaultSession.protocol.interceptFileProtocol(Schemas.file, (request, callback) => this.handleFileRequest(request, callback as unknown as ProtocolCallback));
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
this._register(toDisposable(() => {
|
||||
defaultSession.protocol.unregisterProtocol(Schemas.vscodeFileResource);
|
||||
if (environmentService.args.__sandbox) {
|
||||
defaultSession.protocol.uninterceptProtocol(Schemas.file);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
injectWindowsMainService(windowsMainService: IWindowsMainService): void {
|
||||
this._register(windowsMainService.onWindowReady(window => {
|
||||
if (window.config?.extensionDevelopmentPath || window.config?.extensionTestsPath) {
|
||||
const disposables = new DisposableStore();
|
||||
disposables.add(Event.any(window.onClose, window.onDestroy)(() => disposables.dispose()));
|
||||
|
||||
// Allow access to extension development path
|
||||
if (window.config.extensionDevelopmentPath) {
|
||||
for (const extensionDevelopmentPath of window.config.extensionDevelopmentPath) {
|
||||
disposables.add(this.addValidRoot(URI.file(extensionDevelopmentPath)));
|
||||
}
|
||||
}
|
||||
|
||||
// Allow access to extension tests path
|
||||
if (window.config.extensionTestsPath) {
|
||||
disposables.add(this.addValidRoot(URI.file(window.config.extensionTestsPath)));
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private addValidRoot(root: URI): IDisposable {
|
||||
if (!this.validRoots.get(root)) {
|
||||
this.validRoots.set(root, true);
|
||||
|
||||
return toDisposable(() => this.validRoots.delete(root));
|
||||
}
|
||||
|
||||
return Disposable.None;
|
||||
}
|
||||
|
||||
private async handleFileRequest(request: Electron.ProtocolRequest, callback: ProtocolCallback) {
|
||||
const uri = URI.parse(request.url);
|
||||
|
||||
this.logService.error(`Refused to load resource ${uri.fsPath} from ${Schemas.file}: protocol`);
|
||||
callback({ error: -3 /* ABORTED */ });
|
||||
}
|
||||
|
||||
private async handleResourceRequest(request: Electron.ProtocolRequest, callback: ProtocolCallback) {
|
||||
const uri = URI.parse(request.url);
|
||||
|
||||
// Restore the `vscode-file` URI to a `file` URI so that we can
|
||||
// ensure the root is valid and properly tell Chrome where the
|
||||
// resource is at.
|
||||
const fileUri = FileAccess.asFileUri(uri);
|
||||
if (this.validRoots.findSubstr(fileUri)) {
|
||||
return callback({
|
||||
path: fileUri.fsPath
|
||||
});
|
||||
}
|
||||
|
||||
this.logService.error(`${Schemas.vscodeFileResource}: Refused to load resource ${fileUri.fsPath}}`);
|
||||
callback({ error: -3 /* ABORTED */ });
|
||||
}
|
||||
}
|
@ -60,8 +60,9 @@ export class SharedProcess implements ISharedProcess {
|
||||
windowId: this.window.id
|
||||
};
|
||||
|
||||
const windowUrl = FileAccess
|
||||
.asBrowserUri('vs/code/electron-browser/sharedProcess/sharedProcess.html', require)
|
||||
const windowUrl = (this.environmentService.sandbox ?
|
||||
FileAccess._asCodeFileUri('vs/code/electron-browser/sharedProcess/sharedProcess.html', require) :
|
||||
FileAccess.asBrowserUri('vs/code/electron-browser/sharedProcess/sharedProcess.html', require))
|
||||
.with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` });
|
||||
this.window.loadURL(windowUrl.toString(true));
|
||||
|
||||
|
@ -3,9 +3,10 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as os from 'os';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as perf from 'vs/base/common/performance';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme, Event, Details } from 'electron';
|
||||
@ -23,7 +24,6 @@ import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
|
||||
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
|
||||
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
|
||||
import * as perf from 'vs/base/common/performance';
|
||||
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/common/extensionGalleryService';
|
||||
import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
@ -33,8 +33,10 @@ import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
import { IStorageMainService } from 'vs/platform/storage/node/storageMainService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { ByteSize, IFileService } from 'vs/platform/files/common/files';
|
||||
import { FileAccess, Schemas } from 'vs/base/common/network';
|
||||
import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
export interface IWindowCreationOptions {
|
||||
state: IWindowState;
|
||||
@ -84,7 +86,7 @@ const enum ReadyState {
|
||||
|
||||
export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
|
||||
private static readonly MAX_URL_LENGTH = 2 * 1024 * 1024; // https://cs.chromium.org/chromium/src/url/url_constants.cc?l=32
|
||||
private static readonly MAX_URL_LENGTH = 2 * ByteSize.MB; // https://cs.chromium.org/chromium/src/url/url_constants.cc?l=32
|
||||
|
||||
private readonly _onLoad = this._register(new Emitter<void>());
|
||||
readonly onLoad = this._onLoad.event;
|
||||
@ -110,8 +112,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
|
||||
private readonly whenReadyCallbacks: { (window: ICodeWindow): void }[];
|
||||
|
||||
private pendingLoadConfig?: INativeWindowConfiguration;
|
||||
|
||||
private marketplaceHeadersPromise: Promise<object>;
|
||||
|
||||
private readonly touchBarGroups: TouchBarSegmentedControl[];
|
||||
@ -150,7 +150,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
// 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);
|
||||
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings | undefined>('window');
|
||||
|
||||
const options: BrowserWindowConstructorOptions = {
|
||||
width: this.windowState.width,
|
||||
@ -211,7 +211,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
options.tabbingIdentifier = product.nameShort; // this opts in to sierra tabs
|
||||
}
|
||||
|
||||
const useCustomTitleStyle = getTitleBarStyle(this.configurationService, this.environmentService, !!config.extensionDevelopmentPath) === 'custom';
|
||||
const useCustomTitleStyle = getTitleBarStyle(this.configurationService) === 'custom';
|
||||
if (useCustomTitleStyle) {
|
||||
options.titleBarStyle = 'hidden';
|
||||
this.hiddenTitleBarStyle = true;
|
||||
@ -285,6 +285,8 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private pendingLoadConfig: INativeWindowConfiguration | undefined;
|
||||
|
||||
private currentConfig: INativeWindowConfiguration | undefined;
|
||||
get config(): INativeWindowConfiguration | undefined { return this.currentConfig; }
|
||||
|
||||
@ -296,11 +298,11 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
|
||||
get hasHiddenTitleBarStyle(): boolean { return !!this.hiddenTitleBarStyle; }
|
||||
|
||||
get isExtensionDevelopmentHost(): boolean { return !!(this.config && this.config.extensionDevelopmentPath); }
|
||||
get isExtensionDevelopmentHost(): boolean { return !!(this.currentConfig?.extensionDevelopmentPath); }
|
||||
|
||||
get isExtensionTestHost(): boolean { return !!(this.config && this.config.extensionTestsPath); }
|
||||
get isExtensionTestHost(): boolean { return !!(this.currentConfig?.extensionTestsPath); }
|
||||
|
||||
get isExtensionDevelopmentTestFromCli(): boolean { return this.isExtensionDevelopmentHost && this.isExtensionTestHost && !this.config?.debugId; }
|
||||
get isExtensionDevelopmentTestFromCli(): boolean { return this.isExtensionDevelopmentHost && this.isExtensionTestHost && !this.currentConfig?.debugId; }
|
||||
|
||||
setRepresentedFilename(filename: string): void {
|
||||
if (isMacintosh) {
|
||||
@ -468,9 +470,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
return; // disposed
|
||||
}
|
||||
|
||||
// Notify renderers about displays changed
|
||||
this.sendWhenReady('vscode:displayChanged');
|
||||
|
||||
// Simple fullscreen doesn't resize automatically when the resolution changes so as a workaround
|
||||
// we need to detect when display metrics change or displays are added/removed and toggle the
|
||||
// fullscreen manually.
|
||||
@ -520,11 +519,11 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
|
||||
// Window Fullscreen
|
||||
this._win.on('enter-full-screen', () => {
|
||||
this.sendWhenReady('vscode:enterFullScreen');
|
||||
this.sendWhenReady('vscode:enterFullScreen', CancellationToken.None);
|
||||
});
|
||||
|
||||
this._win.on('leave-full-screen', () => {
|
||||
this.sendWhenReady('vscode:leaveFullScreen');
|
||||
this.sendWhenReady('vscode:leaveFullScreen', CancellationToken.None);
|
||||
});
|
||||
|
||||
// Window Failed to load
|
||||
@ -680,6 +679,16 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
|
||||
load(config: INativeWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void {
|
||||
|
||||
// If this window was loaded before from the command line
|
||||
// (as indicated by VSCODE_CLI environment), make sure to
|
||||
// preserve that user environment in subsequent loads,
|
||||
// unless the new configuration context was also a CLI
|
||||
// (for https://github.com/microsoft/vscode/issues/108571)
|
||||
const currentUserEnv = (this.currentConfig ?? this.pendingLoadConfig)?.userEnv;
|
||||
if (currentUserEnv && isLaunchedFromCli(currentUserEnv) && !isLaunchedFromCli(config.userEnv)) {
|
||||
config.userEnv = { ...currentUserEnv, ...config.userEnv }; // still allow to override certain environment as passed in
|
||||
}
|
||||
|
||||
// If this is the first time the window is loaded, we associate the paths
|
||||
// directly with the window because we assume the loading will just work
|
||||
if (this._readyState === ReadyState.NONE) {
|
||||
@ -738,10 +747,10 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
this._onLoad.fire();
|
||||
}
|
||||
|
||||
reload(configurationIn?: INativeWindowConfiguration, cli?: NativeParsedArgs): void {
|
||||
reload(cli?: NativeParsedArgs): void {
|
||||
|
||||
// If config is not provided, copy our current one
|
||||
const configuration = configurationIn ? configurationIn : objects.mixin({}, this.currentConfig);
|
||||
// Copy our current config for reuse
|
||||
const configuration = Object.assign({}, this.currentConfig);
|
||||
|
||||
// Delete some properties we do not want during reload
|
||||
delete configuration.filesToOpenOrCreate;
|
||||
@ -773,7 +782,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
windowConfiguration.logLevel = this.logService.getLevel();
|
||||
|
||||
// Set zoomlevel
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings | undefined>('window');
|
||||
const zoomLevel = windowConfig?.zoomLevel;
|
||||
if (typeof zoomLevel === 'number') {
|
||||
windowConfiguration.zoomLevel = zoomLevel;
|
||||
@ -799,6 +808,11 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
// Parts splash
|
||||
windowConfiguration.partsSplashPath = path.join(this.environmentService.userDataPath, 'rapid_render.json');
|
||||
|
||||
// OS Info
|
||||
windowConfiguration.os = {
|
||||
release: os.release()
|
||||
};
|
||||
|
||||
// Config (combination of process.argv and window configuration)
|
||||
const environment = parseArgs(process.argv, OPTIONS);
|
||||
const config = Object.assign(environment, windowConfiguration) as unknown as { [key: string]: unknown };
|
||||
@ -814,10 +828,9 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
// large depending on user configuration, so we can only remove it in that case.
|
||||
let configUrl = this.doGetUrl(config);
|
||||
if (configUrl.length > CodeWindow.MAX_URL_LENGTH) {
|
||||
delete config.userEnv;
|
||||
this.logService.warn('Application URL exceeds maximum of 2MB and was shortened.');
|
||||
|
||||
configUrl = this.doGetUrl(config);
|
||||
configUrl = this.doGetUrl({ ...config, userEnv: undefined });
|
||||
|
||||
if (configUrl.length > CodeWindow.MAX_URL_LENGTH) {
|
||||
this.logService.error('Application URL exceeds maximum of 2MB and cannot be loaded.');
|
||||
@ -835,10 +848,10 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
workbench = 'vs/code/electron-browser/workbench/workbench.html';
|
||||
}
|
||||
|
||||
return FileAccess
|
||||
.asBrowserUri(workbench, require)
|
||||
.with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` })
|
||||
.toString(true);
|
||||
return (this.environmentService.sandbox ?
|
||||
FileAccess._asCodeFileUri(workbench, require) :
|
||||
FileAccess.asBrowserUri(workbench, require))
|
||||
.with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` }).toString(true);
|
||||
}
|
||||
|
||||
serializeWindowState(): IWindowState {
|
||||
@ -1088,7 +1101,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
}
|
||||
|
||||
// Events
|
||||
this.sendWhenReady(fullscreen ? 'vscode:enterFullScreen' : 'vscode:leaveFullScreen');
|
||||
this.sendWhenReady(fullscreen ? 'vscode:enterFullScreen' : 'vscode:leaveFullScreen', CancellationToken.None);
|
||||
|
||||
// Respect configured menu bar visibility or default to toggle if not set
|
||||
if (this.currentMenuBarVisibility) {
|
||||
@ -1116,7 +1129,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
}
|
||||
|
||||
private useNativeFullScreen(): boolean {
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings | undefined>('window');
|
||||
if (!windowConfig || typeof windowConfig.nativeFullScreen !== 'boolean') {
|
||||
return true; // default
|
||||
}
|
||||
@ -1133,7 +1146,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
}
|
||||
|
||||
private getMenuBarVisibility(): MenuBarVisibility {
|
||||
let menuBarVisibility = getMenuBarVisibility(this.configurationService, this.environmentService, !!this.config?.extensionDevelopmentPath);
|
||||
let menuBarVisibility = getMenuBarVisibility(this.configurationService);
|
||||
if (['visible', 'toggle', 'hidden'].indexOf(menuBarVisibility) < 0) {
|
||||
menuBarVisibility = 'default';
|
||||
}
|
||||
@ -1229,11 +1242,15 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
}
|
||||
}
|
||||
|
||||
sendWhenReady(channel: string, ...args: any[]): void {
|
||||
sendWhenReady(channel: string, token: CancellationToken, ...args: any[]): void {
|
||||
if (this.isReady) {
|
||||
this.send(channel, ...args);
|
||||
} else {
|
||||
this.ready().then(() => this.send(channel, ...args));
|
||||
this.ready().then(() => {
|
||||
if (!token.isCancellationRequested) {
|
||||
this.send(channel, ...args);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1283,7 +1300,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
mode: 'buttons',
|
||||
segmentStyle: 'automatic',
|
||||
change: (selectedIndex) => {
|
||||
this.sendWhenReady('vscode:runAction', { id: (control.segments[selectedIndex] as ITouchBarSegment).id, from: 'touchbar' });
|
||||
this.sendWhenReady('vscode:runAction', CancellationToken.None, { id: (control.segments[selectedIndex] as ITouchBarSegment).id, from: 'touchbar' });
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -150,6 +150,7 @@ export class IssueReporter extends Disposable {
|
||||
applyZoom(configuration.data.zoomLevel);
|
||||
this.applyStyles(configuration.data.styles);
|
||||
this.handleExtensionData(configuration.data.enabledExtensions);
|
||||
this.updateExperimentsInfo(configuration.data.experiments);
|
||||
}
|
||||
|
||||
render(): void {
|
||||
@ -285,7 +286,7 @@ export class IssueReporter extends Disposable {
|
||||
this.render();
|
||||
});
|
||||
|
||||
(['includeSystemInfo', 'includeProcessInfo', 'includeWorkspaceInfo', 'includeExtensions', 'includeSearchedExtensions', 'includeSettingsSearchDetails'] as const).forEach(elementId => {
|
||||
(['includeSystemInfo', 'includeProcessInfo', 'includeWorkspaceInfo', 'includeExtensions', 'includeExperiments'] as const).forEach(elementId => {
|
||||
this.addEventListener(elementId, 'click', (event: Event) => {
|
||||
event.stopPropagation();
|
||||
this.issueReporterModel.update({ [elementId]: !this.issueReporterModel.getData()[elementId] });
|
||||
@ -693,8 +694,7 @@ export class IssueReporter extends Disposable {
|
||||
const processBlock = document.querySelector('.block-process');
|
||||
const workspaceBlock = document.querySelector('.block-workspace');
|
||||
const extensionsBlock = document.querySelector('.block-extensions');
|
||||
const searchedExtensionsBlock = document.querySelector('.block-searchedExtensions');
|
||||
const settingsSearchResultsBlock = document.querySelector('.block-settingsSearchResults');
|
||||
const experimentsBlock = document.querySelector('.block-experiments');
|
||||
|
||||
const problemSource = this.getElementById('problem-source')!;
|
||||
const descriptionTitle = this.getElementById('issue-description-label')!;
|
||||
@ -707,8 +707,7 @@ export class IssueReporter extends Disposable {
|
||||
hide(processBlock);
|
||||
hide(workspaceBlock);
|
||||
hide(extensionsBlock);
|
||||
hide(searchedExtensionsBlock);
|
||||
hide(settingsSearchResultsBlock);
|
||||
hide(experimentsBlock);
|
||||
hide(problemSource);
|
||||
hide(extensionSelector);
|
||||
|
||||
@ -716,6 +715,7 @@ export class IssueReporter extends Disposable {
|
||||
show(blockContainer);
|
||||
show(systemBlock);
|
||||
show(problemSource);
|
||||
show(experimentsBlock);
|
||||
|
||||
if (fileOnExtension) {
|
||||
show(extensionSelector);
|
||||
@ -730,6 +730,7 @@ export class IssueReporter extends Disposable {
|
||||
show(processBlock);
|
||||
show(workspaceBlock);
|
||||
show(problemSource);
|
||||
show(experimentsBlock);
|
||||
|
||||
if (fileOnExtension) {
|
||||
show(extensionSelector);
|
||||
@ -1084,6 +1085,14 @@ export class IssueReporter extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private updateExperimentsInfo(experimentInfo: string | undefined) {
|
||||
this.issueReporterModel.update({ experimentInfo });
|
||||
const target = document.querySelector<HTMLElement>('.block-experiments .block-info');
|
||||
if (target) {
|
||||
target.textContent = experimentInfo ? experimentInfo : localize('noCurrentExperiments', "No current experiments.");
|
||||
}
|
||||
}
|
||||
|
||||
private getExtensionTableHtml(extensions: IssueReporterExtensionData[]): HTMLTableElement {
|
||||
return $('table', undefined,
|
||||
$('tr', undefined,
|
||||
|
@ -19,8 +19,7 @@ export interface IssueReporterData {
|
||||
includeWorkspaceInfo: boolean;
|
||||
includeProcessInfo: boolean;
|
||||
includeExtensions: boolean;
|
||||
includeSearchedExtensions: boolean;
|
||||
includeSettingsSearchDetails: boolean;
|
||||
includeExperiments: boolean;
|
||||
|
||||
numberOfThemeExtesions?: number;
|
||||
allExtensions: IssueReporterExtensionData[];
|
||||
@ -31,6 +30,7 @@ export interface IssueReporterData {
|
||||
actualSearchResults?: ISettingSearchResult[];
|
||||
query?: string;
|
||||
filterResultCount?: number;
|
||||
experimentInfo?: string;
|
||||
}
|
||||
|
||||
export class IssueReporterModel {
|
||||
@ -43,8 +43,7 @@ export class IssueReporterModel {
|
||||
includeWorkspaceInfo: true,
|
||||
includeProcessInfo: true,
|
||||
includeExtensions: true,
|
||||
includeSearchedExtensions: true,
|
||||
includeSettingsSearchDetails: true,
|
||||
includeExperiments: true,
|
||||
allExtensions: []
|
||||
};
|
||||
|
||||
@ -133,6 +132,12 @@ ${this.getInfos()}
|
||||
}
|
||||
}
|
||||
|
||||
if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) {
|
||||
if (this._data.includeExperiments && this._data.experimentInfo) {
|
||||
info += this.generateExperimentsInfoMd();
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
@ -150,7 +155,7 @@ ${this.getInfos()}
|
||||
|GPU Status|${Object.keys(this._data.systemInfo.gpuStatus).map(key => `${key}: ${this._data.systemInfo!.gpuStatus[key]}`).join('<br>')}|
|
||||
|Load (avg)|${this._data.systemInfo.load}|
|
||||
|Memory (System)|${this._data.systemInfo.memory}|
|
||||
|Process Argv|${this._data.systemInfo.processArgs}|
|
||||
|Process Argv|${this._data.systemInfo.processArgs.replace(/\\/g, '\\\\')}|
|
||||
|Screen Reader|${this._data.systemInfo.screenReader}|
|
||||
|VM|${this._data.systemInfo.vmHint}|`;
|
||||
|
||||
@ -203,6 +208,18 @@ ${this._data.processInfo}
|
||||
${this._data.workspaceInfo};
|
||||
\`\`\`
|
||||
|
||||
</details>
|
||||
`;
|
||||
}
|
||||
|
||||
private generateExperimentsInfoMd(): string {
|
||||
return `<details>
|
||||
<summary>A/B Experiments</summary>
|
||||
|
||||
\`\`\`
|
||||
${this._data.experimentInfo}
|
||||
\`\`\`
|
||||
|
||||
</details>
|
||||
`;
|
||||
}
|
||||
|
@ -111,25 +111,15 @@ export default (): string => `
|
||||
<!-- To be dynamically filled -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="block block-searchedExtensions">
|
||||
<input class="sendData" type="checkbox" id="includeSearchedExtensions" checked/>
|
||||
<label class="caption" for="includeSearchedExtensions">${escape(localize({
|
||||
key: 'sendSearchedExtensions',
|
||||
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibility of the searched extensions']
|
||||
}, "Send searched extensions ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
|
||||
<div class="block-info hidden">
|
||||
<div class="block block-experiments">
|
||||
<input class="sendData" type="checkbox" id="includeExperiments" checked/>
|
||||
<label class="caption" for="includeExperiments">${escape(localize({
|
||||
key: 'sendExperiments',
|
||||
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibility of the current experiment information']
|
||||
}, "Include A/B experiment info ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
|
||||
<pre class="block-info hidden">
|
||||
<!-- To be dynamically filled -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="block block-settingsSearchResults">
|
||||
<input class="sendData" type="checkbox" id="includeSettingsSearchDetails" checked/>
|
||||
<label class="caption" for="includeSettingsSearchDetails">${escape(localize({
|
||||
key: 'sendSettingsSearchDetails',
|
||||
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibility of the search details']
|
||||
}, "Send settings search details ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
|
||||
<div class="block-info hidden">
|
||||
<!-- To be dynamically filled -->
|
||||
</div>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
@ -18,8 +18,7 @@ suite('IssueReporter', () => {
|
||||
includeWorkspaceInfo: true,
|
||||
includeProcessInfo: true,
|
||||
includeExtensions: true,
|
||||
includeSearchedExtensions: true,
|
||||
includeSettingsSearchDetails: true,
|
||||
includeExperiments: true,
|
||||
issueType: 0
|
||||
});
|
||||
});
|
||||
@ -78,6 +77,58 @@ OS version: undefined
|
||||
|Screen Reader|no|
|
||||
|VM|0%|
|
||||
</details>Extensions: none
|
||||
<!-- generated by issue reporter -->`);
|
||||
});
|
||||
|
||||
test('serializes experiment info when data is provided', () => {
|
||||
const issueReporterModel = new IssueReporterModel({
|
||||
issueType: 0,
|
||||
systemInfo: {
|
||||
os: 'Darwin',
|
||||
cpus: 'Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)',
|
||||
memory: '16.00GB',
|
||||
vmHint: '0%',
|
||||
processArgs: '',
|
||||
screenReader: 'no',
|
||||
remoteData: [],
|
||||
gpuStatus: {
|
||||
'2d_canvas': 'enabled',
|
||||
'checker_imaging': 'disabled_off'
|
||||
}
|
||||
},
|
||||
experimentInfo: 'vsliv695:30137379\nvsins829:30139715'
|
||||
});
|
||||
assert.equal(issueReporterModel.serialize(),
|
||||
`
|
||||
Issue Type: <b>Bug</b>
|
||||
|
||||
undefined
|
||||
|
||||
VS Code version: undefined
|
||||
OS version: undefined
|
||||
|
||||
<details>
|
||||
<summary>System Info</summary>
|
||||
|
||||
|Item|Value|
|
||||
|---|---|
|
||||
|CPUs|Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)|
|
||||
|GPU Status|2d_canvas: enabled<br>checker_imaging: disabled_off|
|
||||
|Load (avg)|undefined|
|
||||
|Memory (System)|16.00GB|
|
||||
|Process Argv||
|
||||
|Screen Reader|no|
|
||||
|VM|0%|
|
||||
</details>Extensions: none<details>
|
||||
<summary>A/B Experiments</summary>
|
||||
|
||||
\`\`\`
|
||||
vsliv695:30137379
|
||||
vsins829:30139715
|
||||
\`\`\`
|
||||
|
||||
</details>
|
||||
|
||||
<!-- generated by issue reporter -->`);
|
||||
});
|
||||
|
||||
@ -191,6 +242,45 @@ Remote OS version: Linux x64 4.18.0
|
||||
<!-- generated by issue reporter -->`);
|
||||
});
|
||||
|
||||
test('escapes backslashes in processArgs', () => {
|
||||
const issueReporterModel = new IssueReporterModel({
|
||||
issueType: 0,
|
||||
systemInfo: {
|
||||
os: 'Darwin',
|
||||
cpus: 'Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)',
|
||||
memory: '16.00GB',
|
||||
vmHint: '0%',
|
||||
processArgs: '\\\\HOST\\path',
|
||||
screenReader: 'no',
|
||||
remoteData: [],
|
||||
gpuStatus: {}
|
||||
}
|
||||
});
|
||||
assert.equal(issueReporterModel.serialize(),
|
||||
`
|
||||
Issue Type: <b>Bug</b>
|
||||
|
||||
undefined
|
||||
|
||||
VS Code version: undefined
|
||||
OS version: undefined
|
||||
|
||||
<details>
|
||||
<summary>System Info</summary>
|
||||
|
||||
|Item|Value|
|
||||
|---|---|
|
||||
|CPUs|Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)|
|
||||
|GPU Status||
|
||||
|Load (avg)|undefined|
|
||||
|Memory (System)|16.00GB|
|
||||
|Process Argv|\\\\\\\\HOST\\\\path|
|
||||
|Screen Reader|no|
|
||||
|VM|0%|
|
||||
</details>Extensions: none
|
||||
<!-- generated by issue reporter -->`);
|
||||
});
|
||||
|
||||
test('should normalize GitHub urls', () => {
|
||||
[
|
||||
'https://github.com/repo',
|
||||
|
@ -19,6 +19,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics';
|
||||
import { MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
|
||||
import { CodiconLabel } from 'vs/base/browser/ui/codicons/codiconLabel';
|
||||
import { ByteSize } from 'vs/platform/files/common/files';
|
||||
|
||||
const DEBUG_FLAGS_PATTERN = /\s--(inspect|debug)(-brk|port)?=(\d+)?/;
|
||||
const DEBUG_PORT_PATTERN = /\s--(inspect|debug)-port=(\d+)/;
|
||||
@ -67,18 +68,19 @@ class ProcessExplorer {
|
||||
|
||||
private getProcessList(rootProcess: ProcessItem, isLocal: boolean, totalMem: number): FormattedProcessItem[] {
|
||||
const processes: FormattedProcessItem[] = [];
|
||||
const handledProcesses = new Set<number>();
|
||||
|
||||
if (rootProcess) {
|
||||
this.getProcessItem(processes, rootProcess, 0, isLocal, totalMem);
|
||||
this.getProcessItem(processes, rootProcess, 0, isLocal, totalMem, handledProcesses);
|
||||
}
|
||||
|
||||
return processes;
|
||||
}
|
||||
|
||||
private getProcessItem(processes: FormattedProcessItem[], item: ProcessItem, indent: number, isLocal: boolean, totalMem: number): void {
|
||||
private getProcessItem(processes: FormattedProcessItem[], item: ProcessItem, indent: number, isLocal: boolean, totalMem: number, handledProcesses: Set<number>): void {
|
||||
const isRoot = (indent === 0);
|
||||
|
||||
const MB = 1024 * 1024;
|
||||
handledProcesses.add(item.pid);
|
||||
|
||||
let name = item.name;
|
||||
if (isRoot) {
|
||||
@ -95,7 +97,7 @@ class ProcessExplorer {
|
||||
const memory = this.data.platform === 'win32' ? item.mem : (totalMem * (item.mem / 100));
|
||||
processes.push({
|
||||
cpu: item.load,
|
||||
memory: (memory / MB),
|
||||
memory: (memory / ByteSize.MB),
|
||||
pid: item.pid.toFixed(0),
|
||||
name,
|
||||
formattedName,
|
||||
@ -105,9 +107,11 @@ class ProcessExplorer {
|
||||
// Recurse into children if any
|
||||
if (Array.isArray(item.children)) {
|
||||
item.children.forEach(child => {
|
||||
if (child) {
|
||||
this.getProcessItem(processes, child, indent + 1, isLocal, totalMem);
|
||||
if (!child || handledProcesses.has(child.pid)) {
|
||||
return; // prevent loops
|
||||
}
|
||||
|
||||
this.getProcessItem(processes, child, indent + 1, isLocal, totalMem, handledProcesses);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -9,15 +9,12 @@
|
||||
'use strict';
|
||||
|
||||
(function () {
|
||||
const bootstrapWindow = bootstrapWindowLib();
|
||||
|
||||
// Add a perf entry right from the top
|
||||
const perf = perfLib();
|
||||
const perf = bootstrapWindow.perfLib();
|
||||
perf.mark('renderer/started');
|
||||
|
||||
// Load environment in parallel to workbench loading to avoid waterfall
|
||||
const bootstrapWindow = bootstrapWindowLib();
|
||||
const whenEnvResolved = bootstrapWindow.globals().process.whenEnvResolved();
|
||||
|
||||
// Load workbench main JS, CSS and NLS all in parallel. This is an
|
||||
// optimization to prevent a waterfall of loading to happen, because
|
||||
// we know for a fact that workbench.desktop.sandbox.main will depend on
|
||||
@ -31,12 +28,6 @@
|
||||
|
||||
// Mark start of workbench
|
||||
perf.mark('didLoadWorkbenchMain');
|
||||
performance.mark('workbench-start');
|
||||
|
||||
// Wait for process environment being fully resolved
|
||||
await whenEnvResolved;
|
||||
|
||||
perf.mark('main/startup');
|
||||
|
||||
// @ts-ignore
|
||||
return require('vs/workbench/electron-sandbox/desktop.main').main(configuration);
|
||||
@ -58,23 +49,11 @@
|
||||
|
||||
//region Helpers
|
||||
|
||||
function perfLib() {
|
||||
globalThis.MonacoPerformanceMarks = globalThis.MonacoPerformanceMarks || [];
|
||||
|
||||
return {
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
mark(name) {
|
||||
globalThis.MonacoPerformanceMarks.push(name, Date.now());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {{
|
||||
* load: (modules: string[], resultCallback: (result, configuration: object) => any, options: object) => unknown,
|
||||
* globals: () => typeof import('../../../base/parts/sandbox/electron-sandbox/globals')
|
||||
* globals: () => typeof import('../../../base/parts/sandbox/electron-sandbox/globals'),
|
||||
* perfLib: () => { mark: (name: string) => void }
|
||||
* }}
|
||||
*/
|
||||
function bootstrapWindowLib() {
|
||||
|
@ -123,10 +123,6 @@ export async function main(argv: string[]): Promise<any> {
|
||||
'ELECTRON_NO_ATTACH_CONSOLE': '1'
|
||||
};
|
||||
|
||||
if (args['force-user-env']) {
|
||||
env['VSCODE_FORCE_USER_ENV'] = '1';
|
||||
}
|
||||
|
||||
delete env['ELECTRON_RUN_AS_NODE'];
|
||||
|
||||
const processCallbacks: ((child: ChildProcess) => Promise<void>)[] = [];
|
||||
@ -157,41 +153,39 @@ export async function main(argv: string[]): Promise<any> {
|
||||
// stdin. We do this because there is no reliable way to find out if data is piped to stdin. Just
|
||||
// checking for stdin being connected to a TTY is not enough (https://github.com/microsoft/vscode/issues/40351)
|
||||
|
||||
if (args._.length === 0) {
|
||||
if (hasReadStdinArg) {
|
||||
stdinFilePath = getStdinFilePath();
|
||||
if (hasReadStdinArg) {
|
||||
stdinFilePath = getStdinFilePath();
|
||||
|
||||
// returns a file path where stdin input is written into (write in progress).
|
||||
try {
|
||||
readFromStdin(stdinFilePath, !!verbose); // throws error if file can not be written
|
||||
// returns a file path where stdin input is written into (write in progress).
|
||||
try {
|
||||
readFromStdin(stdinFilePath, !!verbose); // throws error if file can not be written
|
||||
|
||||
// Make sure to open tmp file
|
||||
addArg(argv, stdinFilePath);
|
||||
// Make sure to open tmp file
|
||||
addArg(argv, stdinFilePath);
|
||||
|
||||
// Enable --wait to get all data and ignore adding this to history
|
||||
addArg(argv, '--wait');
|
||||
addArg(argv, '--skip-add-to-recently-opened');
|
||||
args.wait = true;
|
||||
// Enable --wait to get all data and ignore adding this to history
|
||||
addArg(argv, '--wait');
|
||||
addArg(argv, '--skip-add-to-recently-opened');
|
||||
args.wait = true;
|
||||
|
||||
console.log(`Reading from stdin via: ${stdinFilePath}`);
|
||||
} catch (e) {
|
||||
console.log(`Failed to create file to read via stdin: ${e.toString()}`);
|
||||
stdinFilePath = undefined;
|
||||
}
|
||||
} else {
|
||||
|
||||
// If the user pipes data via stdin but forgot to add the "-" argument, help by printing a message
|
||||
// if we detect that data flows into via stdin after a certain timeout.
|
||||
processCallbacks.push(_ => stdinDataListener(1000).then(dataReceived => {
|
||||
if (dataReceived) {
|
||||
if (isWindows) {
|
||||
console.log(`Run with '${product.applicationName} -' to read output from another program (e.g. 'echo Hello World | ${product.applicationName} -').`);
|
||||
} else {
|
||||
console.log(`Run with '${product.applicationName} -' to read from stdin (e.g. 'ps aux | grep code | ${product.applicationName} -').`);
|
||||
}
|
||||
}
|
||||
}));
|
||||
console.log(`Reading from stdin via: ${stdinFilePath}`);
|
||||
} catch (e) {
|
||||
console.log(`Failed to create file to read via stdin: ${e.toString()}`);
|
||||
stdinFilePath = undefined;
|
||||
}
|
||||
} else {
|
||||
|
||||
// If the user pipes data via stdin but forgot to add the "-" argument, help by printing a message
|
||||
// if we detect that data flows into via stdin after a certain timeout.
|
||||
processCallbacks.push(_ => stdinDataListener(1000).then(dataReceived => {
|
||||
if (dataReceived) {
|
||||
if (isWindows) {
|
||||
console.log(`Run with '${product.applicationName} -' to read output from another program (e.g. 'echo Hello World | ${product.applicationName} -').`);
|
||||
} else {
|
||||
console.log(`Run with '${product.applicationName} -' to read from stdin (e.g. 'ps aux | grep code | ${product.applicationName} -').`);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ import { mkdirp, writeFile } from 'vs/base/node/pfs';
|
||||
import { getBaseLabel } from 'vs/base/common/labels';
|
||||
import { IStateService } from 'vs/platform/state/node/state';
|
||||
import { StateService } from 'vs/platform/state/node/stateService';
|
||||
import { ILogService, getLogLevel } from 'vs/platform/log/common/log';
|
||||
import { ILogService, getLogLevel, LogLevel, ConsoleLogService, MultiplexLogService } from 'vs/platform/log/common/log';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { areSameExtensions, adoptToGalleryExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
@ -78,7 +78,7 @@ export class Main {
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@INativeEnvironmentService private readonly environmentService: INativeEnvironmentService,
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService
|
||||
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
|
||||
) { }
|
||||
|
||||
async run(argv: NativeParsedArgs): Promise<void> {
|
||||
@ -93,7 +93,7 @@ export class Main {
|
||||
} else if (argv['locate-extension']) {
|
||||
await this.locateExtension(argv['locate-extension']);
|
||||
} else if (argv['telemetry']) {
|
||||
console.log(buildTelemetryMessage(this.environmentService.appRoot, this.environmentService.extensionsPath ? this.environmentService.extensionsPath : undefined));
|
||||
console.log(buildTelemetryMessage(this.environmentService.appRoot, this.environmentService.extensionsPath));
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,13 +126,28 @@ export class Main {
|
||||
extensions.forEach(e => console.log(getId(e.manifest, showVersions)));
|
||||
}
|
||||
|
||||
private async installExtensions(extensions: string[], builtinExtensionIds: string[], isMachineScoped: boolean, force: boolean): Promise<void> {
|
||||
async installExtensions(extensions: string[], builtinExtensionIds: string[], isMachineScoped: boolean, force: boolean): Promise<void> {
|
||||
const failed: string[] = [];
|
||||
const installedExtensionsManifests: IExtensionManifest[] = [];
|
||||
if (extensions.length) {
|
||||
console.log(localize('installingExtensions', "Installing extensions..."));
|
||||
}
|
||||
|
||||
const installed = await this.extensionManagementService.getInstalled(ExtensionType.User);
|
||||
const checkIfNotInstalled = (id: string, version?: string): boolean => {
|
||||
const installedExtension = installed.find(i => areSameExtensions(i.identifier, { id }));
|
||||
if (installedExtension) {
|
||||
if (!version && !force) {
|
||||
console.log(localize('alreadyInstalled-checkAndUpdate', "Extension '{0}' v{1} is already installed. Use '--force' option to update to latest version or provide '@<version>' to install a specific version, for example: '{2}@1.2.3'.", id, installedExtension.manifest.version, id));
|
||||
return false;
|
||||
}
|
||||
if (version && installedExtension.manifest.version === version) {
|
||||
console.log(localize('alreadyInstalled', "Extension '{0}' is already installed.", `${id}@${version}`));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const vsixs: string[] = [];
|
||||
const installExtensionInfos: InstallExtensionInfo[] = [];
|
||||
for (const extension of extensions) {
|
||||
@ -140,12 +155,16 @@ export class Main {
|
||||
vsixs.push(extension);
|
||||
} else {
|
||||
const [id, version] = getIdAndVersion(extension);
|
||||
installExtensionInfos.push({ id, version, installOptions: { isBuiltin: false, isMachineScoped } });
|
||||
if (checkIfNotInstalled(id, version)) {
|
||||
installExtensionInfos.push({ id, version, installOptions: { isBuiltin: false, isMachineScoped } });
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const extension of builtinExtensionIds) {
|
||||
const [id, version] = getIdAndVersion(extension);
|
||||
installExtensionInfos.push({ id, version, installOptions: { isBuiltin: true, isMachineScoped: false } });
|
||||
if (checkIfNotInstalled(id, version)) {
|
||||
installExtensionInfos.push({ id, version, installOptions: { isBuiltin: true, isMachineScoped: false } });
|
||||
}
|
||||
}
|
||||
|
||||
if (vsixs.length) {
|
||||
@ -162,28 +181,29 @@ export class Main {
|
||||
}));
|
||||
}
|
||||
|
||||
const [galleryExtensions, installed] = await Promise.all([
|
||||
this.getGalleryExtensions(installExtensionInfos),
|
||||
this.extensionManagementService.getInstalled(ExtensionType.User)
|
||||
]);
|
||||
if (installExtensionInfos.length) {
|
||||
|
||||
await Promise.all(installExtensionInfos.map(async extensionInfo => {
|
||||
const gallery = galleryExtensions.get(extensionInfo.id.toLowerCase());
|
||||
if (gallery) {
|
||||
try {
|
||||
const manifest = await this.installFromGallery(extensionInfo, gallery, installed, force);
|
||||
if (manifest) {
|
||||
installedExtensionsManifests.push(manifest);
|
||||
const galleryExtensions = await this.getGalleryExtensions(installExtensionInfos);
|
||||
|
||||
await Promise.all(installExtensionInfos.map(async extensionInfo => {
|
||||
const gallery = galleryExtensions.get(extensionInfo.id.toLowerCase());
|
||||
if (gallery) {
|
||||
try {
|
||||
const manifest = await this.installFromGallery(extensionInfo, gallery, installed, force);
|
||||
if (manifest) {
|
||||
installedExtensionsManifests.push(manifest);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err.message || err.stack || err);
|
||||
failed.push(extensionInfo.id);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err.message || err.stack || err);
|
||||
} else {
|
||||
console.error(`${notFound(extensionInfo.version ? `${extensionInfo.id}@${extensionInfo.version}` : extensionInfo.id)}\n${useId}`);
|
||||
failed.push(extensionInfo.id);
|
||||
}
|
||||
} else {
|
||||
console.error(`${notFound(extensionInfo.version ? `${extensionInfo.id}@${extensionInfo.version}` : extensionInfo.id)}\n${useId}`);
|
||||
failed.push(extensionInfo.id);
|
||||
}
|
||||
}));
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
if (installedExtensionsManifests.some(manifest => isLanguagePackExtension(manifest))) {
|
||||
await this.updateLocalizationsCache();
|
||||
@ -244,10 +264,6 @@ export class Main {
|
||||
console.log(localize('alreadyInstalled', "Extension '{0}' is already installed.", version ? `${id}@${version}` : id));
|
||||
return null;
|
||||
}
|
||||
if (!version && !force) {
|
||||
console.log(localize('forceUpdate', "Extension '{0}' v{1} is already installed, but a newer version {2} is available in the marketplace. Use '--force' option to update to newer version.", id, installedExtension.manifest.version, galleryExtension.version));
|
||||
return null;
|
||||
}
|
||||
console.log(localize('updateMessage', "Updating the extension '{0}' to the version {1}", id, galleryExtension.version));
|
||||
}
|
||||
|
||||
@ -315,7 +331,7 @@ export class Main {
|
||||
return;
|
||||
}
|
||||
console.log(localize('uninstalling', "Uninstalling {0}...", id));
|
||||
await this.extensionManagementService.uninstall(extensionToUninstall, true);
|
||||
await this.extensionManagementService.uninstall(extensionToUninstall);
|
||||
uninstalledExtensions.push(extensionToUninstall);
|
||||
console.log(localize('successUninstall', "Extension '{0}' was successfully uninstalled!", id));
|
||||
}
|
||||
@ -353,7 +369,13 @@ export async function main(argv: NativeParsedArgs): Promise<void> {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
const environmentService = new NativeEnvironmentService(argv);
|
||||
const logService: ILogService = new SpdLogService('cli', environmentService.logsPath, getLogLevel(environmentService));
|
||||
const logLevel = getLogLevel(environmentService);
|
||||
const loggers: ILogService[] = [];
|
||||
loggers.push(new SpdLogService('cli', environmentService.logsPath, logLevel));
|
||||
if (logLevel === LogLevel.Trace) {
|
||||
loggers.push(new ConsoleLogService(logLevel));
|
||||
}
|
||||
const logService = new MultiplexLogService(loggers);
|
||||
process.once('exit', () => logService.dispose());
|
||||
logService.info('main', argv);
|
||||
|
||||
@ -403,7 +425,7 @@ export async function main(argv: NativeParsedArgs): Promise<void> {
|
||||
appender: combinedAppender(...appenders),
|
||||
sendErrorTelemetry: false,
|
||||
commonProperties: resolveCommonProperties(product.commit, product.version, stateService.getItem('telemetry.machineId'), product.msftInternalDomains, installSourcePath),
|
||||
piiPaths: extensionsPath ? [appRoot, extensionsPath] : [appRoot]
|
||||
piiPaths: [appRoot, extensionsPath]
|
||||
};
|
||||
|
||||
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
|
||||
|
@ -7,9 +7,57 @@ import { spawn } from 'child_process';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
|
||||
import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
|
||||
function getUnixShellEnvironment(logService: ILogService): Promise<typeof process.env> {
|
||||
/**
|
||||
* We need to get the environment from a user's shell.
|
||||
* This should only be done when Code itself is not launched
|
||||
* from within a shell.
|
||||
*/
|
||||
export async function resolveShellEnv(logService: ILogService, args: NativeParsedArgs, env: NodeJS.ProcessEnv): Promise<typeof process.env> {
|
||||
|
||||
// Skip if --force-disable-user-env
|
||||
if (args['force-disable-user-env']) {
|
||||
logService.trace('resolveShellEnv(): skipped (--force-disable-user-env)');
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// Skip on windows
|
||||
else if (isWindows) {
|
||||
logService.trace('resolveShellEnv(): skipped (Windows)');
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// Skip if running from CLI already
|
||||
else if (isLaunchedFromCli(env) && !args['force-user-env']) {
|
||||
logService.trace('resolveShellEnv(): skipped (VSCODE_CLI is set)');
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// Otherwise resolve (macOS, Linux)
|
||||
else {
|
||||
if (isLaunchedFromCli(env)) {
|
||||
logService.trace('resolveShellEnv(): running (--force-user-env)');
|
||||
} else {
|
||||
logService.trace('resolveShellEnv(): running (macOS/Linux)');
|
||||
}
|
||||
|
||||
if (!unixShellEnvPromise) {
|
||||
unixShellEnvPromise = doResolveUnixShellEnv(logService);
|
||||
}
|
||||
|
||||
return unixShellEnvPromise;
|
||||
}
|
||||
}
|
||||
|
||||
let unixShellEnvPromise: Promise<typeof process.env> | undefined = undefined;
|
||||
|
||||
async function doResolveUnixShellEnv(logService: ILogService): Promise<typeof process.env> {
|
||||
const promise = new Promise<typeof process.env>((resolve, reject) => {
|
||||
const runAsNode = process.env['ELECTRON_RUN_AS_NODE'];
|
||||
logService.trace('getUnixShellEnvironment#runAsNode', runAsNode);
|
||||
@ -78,33 +126,11 @@ function getUnixShellEnvironment(logService: ILogService): Promise<typeof proces
|
||||
});
|
||||
});
|
||||
|
||||
// swallow errors
|
||||
return promise.catch(() => ({}));
|
||||
}
|
||||
try {
|
||||
return await promise;
|
||||
} catch (error) {
|
||||
logService.error('getUnixShellEnvironment#error', toErrorMessage(error));
|
||||
|
||||
let shellEnvPromise: Promise<typeof process.env> | undefined = undefined;
|
||||
|
||||
/**
|
||||
* We need to get the environment from a user's shell.
|
||||
* This should only be done when Code itself is not launched
|
||||
* from within a shell.
|
||||
*/
|
||||
export function getShellEnvironment(logService: ILogService, environmentService: INativeEnvironmentService): Promise<typeof process.env> {
|
||||
if (!shellEnvPromise) {
|
||||
if (environmentService.args['disable-user-env-probe']) {
|
||||
logService.trace('getShellEnvironment: disable-user-env-probe set, skipping');
|
||||
shellEnvPromise = Promise.resolve({});
|
||||
} else if (isWindows) {
|
||||
logService.trace('getShellEnvironment: running on Windows, skipping');
|
||||
shellEnvPromise = Promise.resolve({});
|
||||
} else if (process.env['VSCODE_CLI'] === '1' && process.env['VSCODE_FORCE_USER_ENV'] !== '1') {
|
||||
logService.trace('getShellEnvironment: running on CLI, skipping');
|
||||
shellEnvPromise = Promise.resolve({});
|
||||
} else {
|
||||
logService.trace('getShellEnvironment: running on Unix');
|
||||
shellEnvPromise = getUnixShellEnvironment(logService);
|
||||
}
|
||||
return {}; // ignore any errors
|
||||
}
|
||||
|
||||
return shellEnvPromise;
|
||||
}
|
||||
|
@ -1,40 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
|
||||
suite('Windows Native Helpers', () => {
|
||||
if (!isWindows) {
|
||||
return;
|
||||
}
|
||||
|
||||
test('windows-mutex', async () => {
|
||||
const mutex = await import('windows-mutex');
|
||||
assert.ok(mutex && typeof mutex.isActive === 'function', 'Unable to load windows-mutex dependency.');
|
||||
assert.ok(typeof mutex.isActive === 'function', 'Unable to load windows-mutex dependency.');
|
||||
});
|
||||
|
||||
test('windows-foreground-love', async () => {
|
||||
const foregroundLove = await import('windows-foreground-love');
|
||||
assert.ok(foregroundLove && typeof foregroundLove.allowSetForegroundWindow === 'function', 'Unable to load windows-foreground-love dependency.');
|
||||
});
|
||||
|
||||
test('windows-process-tree', async () => {
|
||||
const processTree = await import('windows-process-tree');
|
||||
assert.ok(processTree && typeof processTree.getProcessTree === 'function', 'Unable to load windows-process-tree dependency.');
|
||||
});
|
||||
|
||||
test('vscode-windows-ca-certs', async () => {
|
||||
// @ts-ignore Windows only
|
||||
const windowsCerts = await import('vscode-windows-ca-certs');
|
||||
assert.ok(windowsCerts, 'Unable to load vscode-windows-ca-certs dependency.');
|
||||
});
|
||||
|
||||
test('vscode-windows-registry', async () => {
|
||||
const windowsRegistry = await import('vscode-windows-registry');
|
||||
assert.ok(windowsRegistry && typeof windowsRegistry.GetStringRegKey === 'function', 'Unable to load vscode-windows-registry dependency.');
|
||||
});
|
||||
});
|
@ -1,292 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as os from 'os';
|
||||
import * as path from 'vs/base/common/path';
|
||||
|
||||
import { restoreWindowsState, getWindowsStateStoreData } from 'vs/platform/windows/electron-main/windowsStateStorage';
|
||||
import { IWindowState as IWindowUIState, WindowMode } from 'vs/platform/windows/electron-main/windows';
|
||||
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IWindowsState, IWindowState } from 'vs/platform/windows/electron-main/windowsMainService';
|
||||
|
||||
function getUIState(): IWindowUIState {
|
||||
return {
|
||||
x: 0,
|
||||
y: 10,
|
||||
width: 100,
|
||||
height: 200,
|
||||
mode: 0
|
||||
};
|
||||
}
|
||||
|
||||
function toWorkspace(uri: URI): IWorkspaceIdentifier {
|
||||
return {
|
||||
id: '1234',
|
||||
configPath: uri
|
||||
};
|
||||
}
|
||||
function assertEqualURI(u1: URI | undefined, u2: URI | undefined, message?: string): void {
|
||||
assert.equal(u1 && u1.toString(), u2 && u2.toString(), message);
|
||||
}
|
||||
|
||||
function assertEqualWorkspace(w1: IWorkspaceIdentifier | undefined, w2: IWorkspaceIdentifier | undefined, message?: string): void {
|
||||
if (!w1 || !w2) {
|
||||
assert.equal(w1, w2, message);
|
||||
return;
|
||||
}
|
||||
assert.equal(w1.id, w2.id, message);
|
||||
assertEqualURI(w1.configPath, w2.configPath, message);
|
||||
}
|
||||
|
||||
function assertEqualWindowState(expected: IWindowState | undefined, actual: IWindowState | undefined, message?: string) {
|
||||
if (!expected || !actual) {
|
||||
assert.deepEqual(expected, actual, message);
|
||||
return;
|
||||
}
|
||||
assert.equal(expected.backupPath, actual.backupPath, message);
|
||||
assertEqualURI(expected.folderUri, actual.folderUri, message);
|
||||
assert.equal(expected.remoteAuthority, actual.remoteAuthority, message);
|
||||
assertEqualWorkspace(expected.workspace, actual.workspace, message);
|
||||
assert.deepEqual(expected.uiState, actual.uiState, message);
|
||||
}
|
||||
|
||||
function assertEqualWindowsState(expected: IWindowsState, actual: IWindowsState, message?: string) {
|
||||
assertEqualWindowState(expected.lastPluginDevelopmentHostWindow, actual.lastPluginDevelopmentHostWindow, message);
|
||||
assertEqualWindowState(expected.lastActiveWindow, actual.lastActiveWindow, message);
|
||||
assert.equal(expected.openedWindows.length, actual.openedWindows.length, message);
|
||||
for (let i = 0; i < expected.openedWindows.length; i++) {
|
||||
assertEqualWindowState(expected.openedWindows[i], actual.openedWindows[i], message);
|
||||
}
|
||||
}
|
||||
|
||||
function assertRestoring(state: IWindowsState, message?: string) {
|
||||
const stored = getWindowsStateStoreData(state);
|
||||
const restored = restoreWindowsState(stored);
|
||||
assertEqualWindowsState(state, restored, message);
|
||||
}
|
||||
|
||||
const testBackupPath1 = path.join(os.tmpdir(), 'windowStateTest', 'backupFolder1');
|
||||
const testBackupPath2 = path.join(os.tmpdir(), 'windowStateTest', 'backupFolder2');
|
||||
|
||||
const testWSPath = URI.file(path.join(os.tmpdir(), 'windowStateTest', 'test.code-workspace'));
|
||||
const testFolderURI = URI.file(path.join(os.tmpdir(), 'windowStateTest', 'testFolder'));
|
||||
|
||||
const testRemoteFolderURI = URI.parse('foo://bar/c/d');
|
||||
|
||||
suite('Windows State Storing', () => {
|
||||
test('storing and restoring', () => {
|
||||
let windowState: IWindowsState;
|
||||
windowState = {
|
||||
openedWindows: []
|
||||
};
|
||||
assertRestoring(windowState, 'no windows');
|
||||
windowState = {
|
||||
openedWindows: [{ backupPath: testBackupPath1, uiState: getUIState() }]
|
||||
};
|
||||
assertRestoring(windowState, 'empty workspace');
|
||||
|
||||
windowState = {
|
||||
openedWindows: [{ backupPath: testBackupPath1, uiState: getUIState(), workspace: toWorkspace(testWSPath) }]
|
||||
};
|
||||
assertRestoring(windowState, 'workspace');
|
||||
|
||||
windowState = {
|
||||
openedWindows: [{ backupPath: testBackupPath2, uiState: getUIState(), folderUri: testFolderURI }]
|
||||
};
|
||||
assertRestoring(windowState, 'folder');
|
||||
|
||||
windowState = {
|
||||
openedWindows: [{ backupPath: testBackupPath1, uiState: getUIState(), folderUri: testFolderURI }, { backupPath: testBackupPath1, uiState: getUIState(), folderUri: testRemoteFolderURI, remoteAuthority: 'bar' }]
|
||||
};
|
||||
assertRestoring(windowState, 'multiple windows');
|
||||
|
||||
windowState = {
|
||||
lastActiveWindow: { backupPath: testBackupPath2, uiState: getUIState(), folderUri: testFolderURI },
|
||||
openedWindows: []
|
||||
};
|
||||
assertRestoring(windowState, 'lastActiveWindow');
|
||||
|
||||
windowState = {
|
||||
lastPluginDevelopmentHostWindow: { backupPath: testBackupPath2, uiState: getUIState(), folderUri: testFolderURI },
|
||||
openedWindows: []
|
||||
};
|
||||
assertRestoring(windowState, 'lastPluginDevelopmentHostWindow');
|
||||
});
|
||||
|
||||
test('open 1_31', () => {
|
||||
const v1_31_workspace = `{
|
||||
"openedWindows": [],
|
||||
"lastActiveWindow": {
|
||||
"workspace": {
|
||||
"id": "a41787288b5e9cc1a61ba2dd84cd0d80",
|
||||
"configPath": "/home/user/workspaces/code-and-docs.code-workspace"
|
||||
},
|
||||
"backupPath": "/home/user/.config/Code - Insiders/Backups/a41787288b5e9cc1a61ba2dd84cd0d80",
|
||||
"uiState": {
|
||||
"mode": 0,
|
||||
"x": 0,
|
||||
"y": 27,
|
||||
"width": 2560,
|
||||
"height": 1364
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
let windowsState = restoreWindowsState(JSON.parse(v1_31_workspace));
|
||||
let expected: IWindowsState = {
|
||||
openedWindows: [],
|
||||
lastActiveWindow: {
|
||||
backupPath: '/home/user/.config/Code - Insiders/Backups/a41787288b5e9cc1a61ba2dd84cd0d80',
|
||||
uiState: { mode: WindowMode.Maximized, x: 0, y: 27, width: 2560, height: 1364 },
|
||||
workspace: { id: 'a41787288b5e9cc1a61ba2dd84cd0d80', configPath: URI.file('/home/user/workspaces/code-and-docs.code-workspace') }
|
||||
}
|
||||
};
|
||||
|
||||
assertEqualWindowsState(expected, windowsState, 'v1_31_workspace');
|
||||
|
||||
const v1_31_folder = `{
|
||||
"openedWindows": [],
|
||||
"lastPluginDevelopmentHostWindow": {
|
||||
"folderUri": {
|
||||
"$mid": 1,
|
||||
"fsPath": "/home/user/workspaces/testing/customdata",
|
||||
"external": "file:///home/user/workspaces/testing/customdata",
|
||||
"path": "/home/user/workspaces/testing/customdata",
|
||||
"scheme": "file"
|
||||
},
|
||||
"uiState": {
|
||||
"mode": 1,
|
||||
"x": 593,
|
||||
"y": 617,
|
||||
"width": 1625,
|
||||
"height": 595
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
windowsState = restoreWindowsState(JSON.parse(v1_31_folder));
|
||||
expected = {
|
||||
openedWindows: [],
|
||||
lastPluginDevelopmentHostWindow: {
|
||||
uiState: { mode: WindowMode.Normal, x: 593, y: 617, width: 1625, height: 595 },
|
||||
folderUri: URI.parse('file:///home/user/workspaces/testing/customdata')
|
||||
}
|
||||
};
|
||||
assertEqualWindowsState(expected, windowsState, 'v1_31_folder');
|
||||
|
||||
const v1_31_empty_window = ` {
|
||||
"openedWindows": [
|
||||
],
|
||||
"lastActiveWindow": {
|
||||
"backupPath": "C:\\\\Users\\\\Mike\\\\AppData\\\\Roaming\\\\Code\\\\Backups\\\\1549538599815",
|
||||
"uiState": {
|
||||
"mode": 0,
|
||||
"x": -8,
|
||||
"y": -8,
|
||||
"width": 2576,
|
||||
"height": 1344
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
windowsState = restoreWindowsState(JSON.parse(v1_31_empty_window));
|
||||
expected = {
|
||||
openedWindows: [],
|
||||
lastActiveWindow: {
|
||||
backupPath: 'C:\\Users\\Mike\\AppData\\Roaming\\Code\\Backups\\1549538599815',
|
||||
uiState: { mode: WindowMode.Maximized, x: -8, y: -8, width: 2576, height: 1344 }
|
||||
}
|
||||
};
|
||||
assertEqualWindowsState(expected, windowsState, 'v1_31_empty_window');
|
||||
|
||||
});
|
||||
|
||||
test('open 1_32', () => {
|
||||
const v1_32_workspace = `{
|
||||
"openedWindows": [],
|
||||
"lastActiveWindow": {
|
||||
"workspaceIdentifier": {
|
||||
"id": "53b714b46ef1a2d4346568b4f591028c",
|
||||
"configURIPath": "file:///home/user/workspaces/testing/custom.code-workspace"
|
||||
},
|
||||
"backupPath": "/home/user/.config/code-oss-dev/Backups/53b714b46ef1a2d4346568b4f591028c",
|
||||
"uiState": {
|
||||
"mode": 0,
|
||||
"x": 0,
|
||||
"y": 27,
|
||||
"width": 2560,
|
||||
"height": 1364
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
let windowsState = restoreWindowsState(JSON.parse(v1_32_workspace));
|
||||
let expected: IWindowsState = {
|
||||
openedWindows: [],
|
||||
lastActiveWindow: {
|
||||
backupPath: '/home/user/.config/code-oss-dev/Backups/53b714b46ef1a2d4346568b4f591028c',
|
||||
uiState: { mode: WindowMode.Maximized, x: 0, y: 27, width: 2560, height: 1364 },
|
||||
workspace: { id: '53b714b46ef1a2d4346568b4f591028c', configPath: URI.parse('file:///home/user/workspaces/testing/custom.code-workspace') }
|
||||
}
|
||||
};
|
||||
|
||||
assertEqualWindowsState(expected, windowsState, 'v1_32_workspace');
|
||||
|
||||
const v1_32_folder = `{
|
||||
"openedWindows": [],
|
||||
"lastActiveWindow": {
|
||||
"folder": "file:///home/user/workspaces/testing/folding",
|
||||
"backupPath": "/home/user/.config/code-oss-dev/Backups/1daac1621c6c06f9e916ac8062e5a1b5",
|
||||
"uiState": {
|
||||
"mode": 1,
|
||||
"x": 625,
|
||||
"y": 263,
|
||||
"width": 1718,
|
||||
"height": 953
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
windowsState = restoreWindowsState(JSON.parse(v1_32_folder));
|
||||
expected = {
|
||||
openedWindows: [],
|
||||
lastActiveWindow: {
|
||||
backupPath: '/home/user/.config/code-oss-dev/Backups/1daac1621c6c06f9e916ac8062e5a1b5',
|
||||
uiState: { mode: WindowMode.Normal, x: 625, y: 263, width: 1718, height: 953 },
|
||||
folderUri: URI.parse('file:///home/user/workspaces/testing/folding')
|
||||
}
|
||||
};
|
||||
assertEqualWindowsState(expected, windowsState, 'v1_32_folder');
|
||||
|
||||
const v1_32_empty_window = ` {
|
||||
"openedWindows": [
|
||||
],
|
||||
"lastActiveWindow": {
|
||||
"backupPath": "/home/user/.config/code-oss-dev/Backups/1549539668998",
|
||||
"uiState": {
|
||||
"mode": 1,
|
||||
"x": 768,
|
||||
"y": 336,
|
||||
"width": 1024,
|
||||
"height": 768
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
windowsState = restoreWindowsState(JSON.parse(v1_32_empty_window));
|
||||
expected = {
|
||||
openedWindows: [],
|
||||
lastActiveWindow: {
|
||||
backupPath: '/home/user/.config/code-oss-dev/Backups/1549539668998',
|
||||
uiState: { mode: WindowMode.Normal, x: 768, y: 336, width: 1024, height: 768 }
|
||||
}
|
||||
};
|
||||
assertEqualWindowsState(expected, windowsState, 'v1_32_empty_window');
|
||||
|
||||
});
|
||||
|
||||
});
|
@ -1,67 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as assert from 'assert';
|
||||
import { formatOptions, Option } from 'vs/platform/environment/node/argv';
|
||||
import { addArg } from 'vs/platform/environment/node/argvHelper';
|
||||
|
||||
suite('formatOptions', () => {
|
||||
|
||||
function o(description: string): Option<any> {
|
||||
return {
|
||||
description, type: 'string'
|
||||
};
|
||||
}
|
||||
|
||||
test('Text should display small columns correctly', () => {
|
||||
assert.deepEqual(
|
||||
formatOptions({
|
||||
'add': o('bar')
|
||||
}, 80),
|
||||
[' --add bar']
|
||||
);
|
||||
assert.deepEqual(
|
||||
formatOptions({
|
||||
'add': o('bar'),
|
||||
'wait': o('ba'),
|
||||
'trace': o('b')
|
||||
}, 80),
|
||||
[
|
||||
' --add bar',
|
||||
' --wait ba',
|
||||
' --trace b'
|
||||
]);
|
||||
});
|
||||
|
||||
test('Text should wrap', () => {
|
||||
assert.deepEqual(
|
||||
formatOptions({
|
||||
'add': o((<any>'bar ').repeat(9))
|
||||
}, 40),
|
||||
[
|
||||
' --add bar bar bar bar bar bar bar bar',
|
||||
' bar'
|
||||
]);
|
||||
});
|
||||
|
||||
test('Text should revert to the condensed view when the terminal is too narrow', () => {
|
||||
assert.deepEqual(
|
||||
formatOptions({
|
||||
'add': o((<any>'bar ').repeat(9))
|
||||
}, 30),
|
||||
[
|
||||
' --add',
|
||||
' bar bar bar bar bar bar bar bar bar '
|
||||
]);
|
||||
});
|
||||
|
||||
test('addArg', () => {
|
||||
assert.deepEqual(addArg([], 'foo'), ['foo']);
|
||||
assert.deepEqual(addArg([], 'foo', 'bar'), ['foo', 'bar']);
|
||||
assert.deepEqual(addArg(['foo'], 'bar'), ['foo', 'bar']);
|
||||
assert.deepEqual(addArg(['--wait'], 'bar'), ['--wait', 'bar']);
|
||||
assert.deepEqual(addArg(['--wait', '--', '--foo'], 'bar'), ['--wait', 'bar', '--', '--foo']);
|
||||
assert.deepEqual(addArg(['--', '--foo'], 'bar'), ['bar', '--', '--foo']);
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user