Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'

This commit is contained in:
Joe Previte
2020-12-15 15:52:33 -07:00
4649 changed files with 1311795 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Action } from 'vs/base/common/actions';
import * as nls from 'vs/nls';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
export class ToggleDevToolsAction extends Action {
static readonly ID = 'workbench.action.toggleDevTools';
static readonly LABEL = nls.localize('toggleDevTools', "Toggle Developer Tools");
constructor(
id: string,
label: string,
@INativeHostService private readonly nativeHostService: INativeHostService
) {
super(id, label);
}
run(): Promise<void> {
return this.nativeHostService.toggleDevTools();
}
}
export class ConfigureRuntimeArgumentsAction extends Action {
static readonly ID = 'workbench.action.configureRuntimeArguments';
static readonly LABEL = nls.localize('configureRuntimeArguments', "Configure Runtime Arguments");
constructor(
id: string,
label: string,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IEditorService private readonly editorService: IEditorService
) {
super(id, label);
}
async run(): Promise<void> {
await this.editorService.openEditor({ resource: this.environmentService.argvResource });
}
}

View File

@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .quick-input-list .quick-input-list-entry.has-actions:hover .quick-input-list-entry-action-bar .action-label.dirty-window::before {
content: "\ea76"; /* Close icon flips between black dot and "X" for dirty windows */
}

View File

@@ -0,0 +1,274 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/actions';
import { URI } from 'vs/base/common/uri';
import { Action } from 'vs/base/common/actions';
import * as nls from 'vs/nls';
import { applyZoom } from 'vs/platform/windows/electron-sandbox/window';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { getZoomLevel } from 'vs/base/browser/browser';
import { FileKind } from 'vs/platform/files/common/files';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IQuickInputService, IQuickInputButton } from 'vs/platform/quickinput/common/quickInput';
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { ICommandHandler } from 'vs/platform/commands/common/commands';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { Codicon } from 'vs/base/common/codicons';
export class CloseCurrentWindowAction extends Action {
static readonly ID = 'workbench.action.closeWindow';
static readonly LABEL = nls.localize('closeWindow', "Close Window");
constructor(
id: string,
label: string,
@INativeHostService private readonly nativeHostService: INativeHostService
) {
super(id, label);
}
async run(): Promise<void> {
this.nativeHostService.closeWindow();
}
}
export abstract class BaseZoomAction extends Action {
private static readonly SETTING_KEY = 'window.zoomLevel';
private static readonly MAX_ZOOM_LEVEL = 9;
private static readonly MIN_ZOOM_LEVEL = -8;
constructor(
id: string,
label: string,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super(id, label);
}
protected async setConfiguredZoomLevel(level: number): Promise<void> {
level = Math.round(level); // when reaching smallest zoom, prevent fractional zoom levels
if (level > BaseZoomAction.MAX_ZOOM_LEVEL || level < BaseZoomAction.MIN_ZOOM_LEVEL) {
return; // https://github.com/microsoft/vscode/issues/48357
}
await this.configurationService.updateValue(BaseZoomAction.SETTING_KEY, level);
applyZoom(level);
}
}
export class ZoomInAction extends BaseZoomAction {
static readonly ID = 'workbench.action.zoomIn';
static readonly LABEL = nls.localize('zoomIn', "Zoom In");
constructor(
id: string,
label: string,
@IConfigurationService configurationService: IConfigurationService
) {
super(id, label, configurationService);
}
async run(): Promise<void> {
this.setConfiguredZoomLevel(getZoomLevel() + 1);
}
}
export class ZoomOutAction extends BaseZoomAction {
static readonly ID = 'workbench.action.zoomOut';
static readonly LABEL = nls.localize('zoomOut', "Zoom Out");
constructor(
id: string,
label: string,
@IConfigurationService configurationService: IConfigurationService
) {
super(id, label, configurationService);
}
async run(): Promise<void> {
this.setConfiguredZoomLevel(getZoomLevel() - 1);
}
}
export class ZoomResetAction extends BaseZoomAction {
static readonly ID = 'workbench.action.zoomReset';
static readonly LABEL = nls.localize('zoomReset', "Reset Zoom");
constructor(
id: string,
label: string,
@IConfigurationService configurationService: IConfigurationService
) {
super(id, label, configurationService);
}
async run(): Promise<void> {
this.setConfiguredZoomLevel(0);
}
}
export class ReloadWindowWithExtensionsDisabledAction extends Action {
static readonly ID = 'workbench.action.reloadWindowWithExtensionsDisabled';
static readonly LABEL = nls.localize('reloadWindowWithExtensionsDisabled', "Reload With Extensions Disabled");
constructor(
id: string,
label: string,
@INativeHostService private readonly nativeHostService: INativeHostService
) {
super(id, label);
}
async run(): Promise<boolean> {
await this.nativeHostService.reload({ disableExtensions: true });
return true;
}
}
export abstract class BaseSwitchWindow extends Action {
private readonly closeWindowAction: IQuickInputButton = {
iconClass: Codicon.removeClose.classNames,
tooltip: nls.localize('close', "Close Window")
};
private readonly closeDirtyWindowAction: IQuickInputButton = {
iconClass: 'dirty-window ' + Codicon.closeDirty,
tooltip: nls.localize('close', "Close Window"),
alwaysVisible: true
};
constructor(
id: string,
label: string,
private readonly quickInputService: IQuickInputService,
private readonly keybindingService: IKeybindingService,
private readonly modelService: IModelService,
private readonly modeService: IModeService,
private readonly nativeHostService: INativeHostService
) {
super(id, label);
}
protected abstract isQuickNavigate(): boolean;
async run(): Promise<void> {
const currentWindowId = this.nativeHostService.windowId;
const windows = await this.nativeHostService.getWindows();
const placeHolder = nls.localize('switchWindowPlaceHolder', "Select a window to switch to");
const picks = windows.map(win => {
const resource = win.filename ? URI.file(win.filename) : win.folderUri ? win.folderUri : win.workspace ? win.workspace.configPath : undefined;
const fileKind = win.filename ? FileKind.FILE : win.workspace ? FileKind.ROOT_FOLDER : win.folderUri ? FileKind.FOLDER : FileKind.FILE;
return {
payload: win.id,
label: win.title,
ariaLabel: win.dirty ? nls.localize('windowDirtyAriaLabel', "{0}, dirty window", win.title) : win.title,
iconClasses: getIconClasses(this.modelService, this.modeService, resource, fileKind),
description: (currentWindowId === win.id) ? nls.localize('current', "Current Window") : undefined,
buttons: currentWindowId !== win.id ? win.dirty ? [this.closeDirtyWindowAction] : [this.closeWindowAction] : undefined
};
});
const autoFocusIndex = (picks.indexOf(picks.filter(pick => pick.payload === currentWindowId)[0]) + 1) % picks.length;
const pick = await this.quickInputService.pick(picks, {
contextKey: 'inWindowsPicker',
activeItem: picks[autoFocusIndex],
placeHolder,
quickNavigate: this.isQuickNavigate() ? { keybindings: this.keybindingService.lookupKeybindings(this.id) } : undefined,
onDidTriggerItemButton: async context => {
await this.nativeHostService.closeWindowById(context.item.payload);
context.removeItem();
}
});
if (pick) {
this.nativeHostService.focusWindow({ windowId: pick.payload });
}
}
}
export class SwitchWindow extends BaseSwitchWindow {
static readonly ID = 'workbench.action.switchWindow';
static readonly LABEL = nls.localize('switchWindow', "Switch Window...");
constructor(
id: string,
label: string,
@IQuickInputService quickInputService: IQuickInputService,
@IKeybindingService keybindingService: IKeybindingService,
@IModelService modelService: IModelService,
@IModeService modeService: IModeService,
@INativeHostService nativeHostService: INativeHostService
) {
super(id, label, quickInputService, keybindingService, modelService, modeService, nativeHostService);
}
protected isQuickNavigate(): boolean {
return false;
}
}
export class QuickSwitchWindow extends BaseSwitchWindow {
static readonly ID = 'workbench.action.quickSwitchWindow';
static readonly LABEL = nls.localize('quickSwitchWindow', "Quick Switch Window...");
constructor(
id: string,
label: string,
@IQuickInputService quickInputService: IQuickInputService,
@IKeybindingService keybindingService: IKeybindingService,
@IModelService modelService: IModelService,
@IModeService modeService: IModeService,
@INativeHostService nativeHostService: INativeHostService
) {
super(id, label, quickInputService, keybindingService, modelService, modeService, nativeHostService);
}
protected isQuickNavigate(): boolean {
return true;
}
}
export const NewWindowTabHandler: ICommandHandler = function (accessor: ServicesAccessor) {
return accessor.get(INativeHostService).newWindowTab();
};
export const ShowPreviousWindowTabHandler: ICommandHandler = function (accessor: ServicesAccessor) {
return accessor.get(INativeHostService).showPreviousWindowTab();
};
export const ShowNextWindowTabHandler: ICommandHandler = function (accessor: ServicesAccessor) {
return accessor.get(INativeHostService).showNextWindowTab();
};
export const MoveWindowTabToNewWindowHandler: ICommandHandler = function (accessor: ServicesAccessor) {
return accessor.get(INativeHostService).moveWindowTabToNewWindow();
};
export const MergeWindowTabsHandlerHandler: ICommandHandler = function (accessor: ServicesAccessor) {
return accessor.get(INativeHostService).mergeAllWindowTabs();
};
export const ToggleWindowTabsBarHandler: ICommandHandler = function (accessor: ServicesAccessor) {
return accessor.get(INativeHostService).toggleWindowTabsBar();
};

View File

@@ -0,0 +1,400 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Registry } from 'vs/platform/registry/common/platform';
import * as nls from 'vs/nls';
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IWorkbenchActionRegistry, Extensions, CATEGORIES } from 'vs/workbench/common/actions';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { isLinux, isMacintosh } from 'vs/base/common/platform';
import { ToggleDevToolsAction, ConfigureRuntimeArgumentsAction } from 'vs/workbench/electron-sandbox/actions/developerActions';
import { ZoomResetAction, ZoomOutAction, ZoomInAction, CloseCurrentWindowAction, SwitchWindow, QuickSwitchWindow, ReloadWindowWithExtensionsDisabledAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-sandbox/actions/windowActions';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IsDevelopmentContext, IsMacContext } from 'vs/platform/contextkey/common/contextkeys';
import { EditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import product from 'vs/platform/product/common/product';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
// Actions
(function registerActions(): void {
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
// Actions: Zoom
(function registerZoomActions(): void {
registry.registerWorkbenchAction(SyncActionDescriptor.from(ZoomInAction, { primary: KeyMod.CtrlCmd | KeyCode.US_EQUAL, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_EQUAL, KeyMod.CtrlCmd | KeyCode.NUMPAD_ADD] }), 'View: Zoom In', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(ZoomOutAction, { primary: KeyMod.CtrlCmd | KeyCode.US_MINUS, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS, KeyMod.CtrlCmd | KeyCode.NUMPAD_SUBTRACT], linux: { primary: KeyMod.CtrlCmd | KeyCode.US_MINUS, secondary: [KeyMod.CtrlCmd | KeyCode.NUMPAD_SUBTRACT] } }), 'View: Zoom Out', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(ZoomResetAction, { primary: KeyMod.CtrlCmd | KeyCode.NUMPAD_0 }), 'View: Reset Zoom', CATEGORIES.View.value);
})();
// Actions: Window
(function registerWindowActions(): void {
registry.registerWorkbenchAction(SyncActionDescriptor.from(CloseCurrentWindowAction, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W }), 'Close Window');
registry.registerWorkbenchAction(SyncActionDescriptor.from(SwitchWindow, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_W } }), 'Switch Window...');
registry.registerWorkbenchAction(SyncActionDescriptor.from(QuickSwitchWindow), 'Quick Switch Window...');
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: CloseCurrentWindowAction.ID, // close the window when the last editor is closed by reusing the same keybinding
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(EditorsVisibleContext.toNegated(), SingleEditorGroupsContext),
primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
handler: accessor => {
const nativeHostService = accessor.get(INativeHostService);
nativeHostService.closeWindow();
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.quit',
weight: KeybindingWeight.WorkbenchContrib,
handler(accessor: ServicesAccessor) {
const nativeHostService = accessor.get(INativeHostService);
nativeHostService.quit();
},
when: undefined,
mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_Q },
linux: { primary: KeyMod.CtrlCmd | KeyCode.KEY_Q }
});
})();
// Actions: macOS Native Tabs
(function registerMacOSNativeTabsActions(): void {
if (isMacintosh) {
[
{ handler: NewWindowTabHandler, id: 'workbench.action.newWindowTab', title: { value: nls.localize('newTab', "New Window Tab"), original: 'New Window Tab' } },
{ handler: ShowPreviousWindowTabHandler, id: 'workbench.action.showPreviousWindowTab', title: { value: nls.localize('showPreviousTab', "Show Previous Window Tab"), original: 'Show Previous Window Tab' } },
{ handler: ShowNextWindowTabHandler, id: 'workbench.action.showNextWindowTab', title: { value: nls.localize('showNextWindowTab', "Show Next Window Tab"), original: 'Show Next Window Tab' } },
{ handler: MoveWindowTabToNewWindowHandler, id: 'workbench.action.moveWindowTabToNewWindow', title: { value: nls.localize('moveWindowTabToNewWindow', "Move Window Tab to New Window"), original: 'Move Window Tab to New Window' } },
{ handler: MergeWindowTabsHandlerHandler, id: 'workbench.action.mergeAllWindowTabs', title: { value: nls.localize('mergeAllWindowTabs', "Merge All Windows"), original: 'Merge All Windows' } },
{ handler: ToggleWindowTabsBarHandler, id: 'workbench.action.toggleWindowTabsBar', title: { value: nls.localize('toggleWindowTabsBar', "Toggle Window Tabs Bar"), original: 'Toggle Window Tabs Bar' } }
].forEach(command => {
CommandsRegistry.registerCommand(command.id, command.handler);
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command,
when: ContextKeyExpr.equals('config.window.nativeTabs', true)
});
});
}
})();
// Actions: Developer
(function registerDeveloperActions(): void {
registry.registerWorkbenchAction(SyncActionDescriptor.from(ReloadWindowWithExtensionsDisabledAction), 'Developer: Reload With Extensions Disabled', CATEGORIES.Developer.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleDevToolsAction), 'Developer: Toggle Developer Tools', CATEGORIES.Developer.value);
KeybindingsRegistry.registerKeybindingRule({
id: ToggleDevToolsAction.ID,
weight: KeybindingWeight.WorkbenchContrib + 50,
when: IsDevelopmentContext,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_I,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_I }
});
})();
// Actions: Runtime Arguments
(function registerRuntimeArgumentsAction(): void {
const preferencesCategory = nls.localize('preferences', "Preferences");
registry.registerWorkbenchAction(SyncActionDescriptor.from(ConfigureRuntimeArgumentsAction), 'Preferences: Configure Runtime Arguments', preferencesCategory);
})();
})();
// Menu
(function registerMenu(): void {
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
group: '6_close',
command: {
id: CloseCurrentWindowAction.ID,
title: nls.localize({ key: 'miCloseWindow', comment: ['&& denotes a mnemonic'] }, "Clos&&e Window")
},
order: 4
});
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
group: 'z_Exit',
command: {
id: 'workbench.action.quit',
title: nls.localize({ key: 'miExit', comment: ['&& denotes a mnemonic'] }, "E&&xit")
},
order: 1,
when: IsMacContext.toNegated()
});
// Zoom
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '3_zoom',
command: {
id: ZoomInAction.ID,
title: nls.localize({ key: 'miZoomIn', comment: ['&& denotes a mnemonic'] }, "&&Zoom In")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '3_zoom',
command: {
id: ZoomOutAction.ID,
title: nls.localize({ key: 'miZoomOut', comment: ['&& denotes a mnemonic'] }, "&&Zoom Out")
},
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '3_zoom',
command: {
id: ZoomResetAction.ID,
title: nls.localize({ key: 'miZoomReset', comment: ['&& denotes a mnemonic'] }, "&&Reset Zoom")
},
order: 3
});
if (!!product.reportIssueUrl) {
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '3_feedback',
command: {
id: 'workbench.action.openIssueReporter',
title: nls.localize({ key: 'miReportIssue', comment: ['&& denotes a mnemonic', 'Translate this to "Report Issue in English" in all languages please!'] }, "Report &&Issue")
},
order: 3
});
}
// Tools
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '5_tools',
command: {
id: ToggleDevToolsAction.ID,
title: nls.localize({ key: 'miToggleDevTools', comment: ['&& denotes a mnemonic'] }, "&&Toggle Developer Tools")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '5_tools',
command: {
id: 'workbench.action.openProcessExplorer',
title: nls.localize({ key: 'miOpenProcessExplorerer', comment: ['&& denotes a mnemonic'] }, "Open &&Process Explorer")
},
order: 2
});
})();
// Configuration
(function registerConfiguration(): void {
const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
// Window
registry.registerConfiguration({
'id': 'window',
'order': 8,
'title': nls.localize('windowConfigurationTitle', "Window"),
'type': 'object',
'properties': {
'window.openWithoutArgumentsInNewWindow': {
'type': 'string',
'enum': ['on', 'off'],
'enumDescriptions': [
nls.localize('window.openWithoutArgumentsInNewWindow.on', "Open a new empty window."),
nls.localize('window.openWithoutArgumentsInNewWindow.off', "Focus the last active running instance.")
],
'default': isMacintosh ? 'off' : 'on',
'scope': ConfigurationScope.APPLICATION,
'markdownDescription': nls.localize('openWithoutArgumentsInNewWindow', "Controls whether a new empty window should open when starting a second instance without arguments or if the last running instance should get focus.\nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).")
},
'window.restoreWindows': {
'type': 'string',
'enum': ['all', 'folders', 'one', 'none'],
'enumDescriptions': [
nls.localize('window.reopenFolders.all', "Reopen all windows."),
nls.localize('window.reopenFolders.folders', "Reopen all folders. Empty workspaces will not be restored."),
nls.localize('window.reopenFolders.one', "Reopen the last active window."),
nls.localize('window.reopenFolders.none', "Never reopen a window. Always start with an empty one.")
],
'default': 'all',
'scope': ConfigurationScope.APPLICATION,
'description': nls.localize('restoreWindows', "Controls how windows are being reopened after a restart.")
},
'window.restoreFullscreen': {
'type': 'boolean',
'default': false,
'scope': ConfigurationScope.APPLICATION,
'description': nls.localize('restoreFullscreen', "Controls whether a window should restore to full screen mode if it was exited in full screen mode.")
},
'window.zoomLevel': {
'type': 'number',
'default': 0,
'description': nls.localize('zoomLevel', "Adjust the zoom level of the window. The original size is 0 and each increment above (e.g. 1) or below (e.g. -1) represents zooming 20% larger or smaller. You can also enter decimals to adjust the zoom level with a finer granularity.")
},
'window.newWindowDimensions': {
'type': 'string',
'enum': ['default', 'inherit', 'offset', 'maximized', 'fullscreen'],
'enumDescriptions': [
nls.localize('window.newWindowDimensions.default', "Open new windows in the center of the screen."),
nls.localize('window.newWindowDimensions.inherit', "Open new windows with same dimension as last active one."),
nls.localize('window.newWindowDimensions.offset', "Open new windows with same dimension as last active one with an offset position."),
nls.localize('window.newWindowDimensions.maximized', "Open new windows maximized."),
nls.localize('window.newWindowDimensions.fullscreen', "Open new windows in full screen mode.")
],
'default': 'default',
'scope': ConfigurationScope.APPLICATION,
'description': nls.localize('newWindowDimensions', "Controls the dimensions of opening a new window when at least one window is already opened. Note that this setting does not have an impact on the first window that is opened. The first window will always restore the size and location as you left it before closing.")
},
'window.closeWhenEmpty': {
'type': 'boolean',
'default': false,
'description': nls.localize('closeWhenEmpty', "Controls whether closing the last editor should also close the window. This setting only applies for windows that do not show folders.")
},
'window.doubleClickIconToClose': {
'type': 'boolean',
'default': false,
'scope': ConfigurationScope.APPLICATION,
'markdownDescription': nls.localize('window.doubleClickIconToClose', "If enabled, double clicking the application icon in the title bar will close the window and the window cannot be dragged by the icon. This setting only has an effect when `#window.titleBarStyle#` is set to `custom`.")
},
'window.titleBarStyle': {
'type': 'string',
'enum': ['native', 'custom'],
'default': isLinux ? 'native' : 'custom',
'scope': ConfigurationScope.APPLICATION,
'description': nls.localize('titleBarStyle', "Adjust the appearance of the window title bar. On Linux and Windows, this setting also affects the application and context menu appearances. Changes require a full restart to apply.")
},
'window.dialogStyle': {
'type': 'string',
'enum': ['native', 'custom'],
'default': 'native',
'scope': ConfigurationScope.APPLICATION,
'description': nls.localize('dialogStyle', "Adjust the appearance of dialog windows.")
},
'window.nativeTabs': {
'type': 'boolean',
'default': false,
'scope': ConfigurationScope.APPLICATION,
'description': nls.localize('window.nativeTabs', "Enables macOS Sierra window tabs. Note that changes require a full restart to apply and that native tabs will disable a custom title bar style if configured."),
'included': isMacintosh
},
'window.nativeFullScreen': {
'type': 'boolean',
'default': true,
'description': nls.localize('window.nativeFullScreen', "Controls if native full-screen should be used on macOS. Disable this option to prevent macOS from creating a new space when going full-screen."),
'scope': ConfigurationScope.APPLICATION,
'included': isMacintosh
},
'window.clickThroughInactive': {
'type': 'boolean',
'default': true,
'scope': ConfigurationScope.APPLICATION,
'description': nls.localize('window.clickThroughInactive', "If enabled, clicking on an inactive window will both activate the window and trigger the element under the mouse if it is clickable. If disabled, clicking anywhere on an inactive window will activate it only and a second click is required on the element."),
'included': isMacintosh
},
'window.enableExperimentalProxyLoginDialog': {
'type': 'boolean',
'default': false,
'scope': ConfigurationScope.APPLICATION,
'description': nls.localize('window.enableExperimentalProxyLoginDialog', "Enables a new login dialog for proxy authentication. Requires a restart to take effect."),
}
}
});
// Telemetry
registry.registerConfiguration({
'id': 'telemetry',
'order': 110,
title: nls.localize('telemetryConfigurationTitle', "Telemetry"),
'type': 'object',
'properties': {
'telemetry.enableCrashReporter': {
'type': 'boolean',
'description': nls.localize('telemetry.enableCrashReporting', "Enable crash reports to be sent to a Microsoft online service. \nThis option requires restart to take effect."),
'default': true,
'tags': ['usesOnlineServices']
}
}
});
// Keybinding
registry.registerConfiguration({
'id': 'keyboard',
'order': 15,
'type': 'object',
'title': nls.localize('keyboardConfigurationTitle', "Keyboard"),
'properties': {
'keyboard.touchbar.enabled': {
'type': 'boolean',
'default': true,
'description': nls.localize('touchbar.enabled', "Enables the macOS touchbar buttons on the keyboard if available."),
'included': isMacintosh
},
'keyboard.touchbar.ignored': {
'type': 'array',
'items': {
'type': 'string'
},
'default': [],
'markdownDescription': nls.localize('touchbar.ignored', 'A set of identifiers for entries in the touchbar that should not show up (for example `workbench.action.navigateBack`.'),
'included': isMacintosh
}
}
});
})();
// JSON Schemas
(function registerJSONSchemas(): void {
const argvDefinitionFileSchemaId = 'vscode://schemas/argv';
const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
const schema: IJSONSchema = {
id: argvDefinitionFileSchemaId,
allowComments: true,
allowTrailingCommas: true,
description: 'VSCode static command line definition file',
type: 'object',
additionalProperties: false,
properties: {
locale: {
type: 'string',
description: nls.localize('argv.locale', 'The display Language to use. Picking a different language requires the associated language pack to be installed.')
},
'disable-hardware-acceleration': {
type: 'boolean',
description: nls.localize('argv.disableHardwareAcceleration', 'Disables hardware acceleration. ONLY change this option if you encounter graphic issues.')
},
'disable-color-correct-rendering': {
type: 'boolean',
description: nls.localize('argv.disableColorCorrectRendering', 'Resolves issues around color profile selection. ONLY change this option if you encounter graphic issues.')
},
'force-color-profile': {
type: 'string',
markdownDescription: nls.localize('argv.forceColorProfile', 'Allows to override the color profile to use. If you experience colors appear badly, try to set this to `srgb` and restart.')
},
'enable-crash-reporter': {
type: 'boolean',
markdownDescription: nls.localize('argv.enableCrashReporter', 'Allows to disable crash reporting, should restart the app if the value is changed.')
},
'crash-reporter-id': {
type: 'string',
markdownDescription: nls.localize('argv.crashReporterId', 'Unique id used for correlating crash reports sent from this app instance.')
},
'enable-proposed-api': {
type: 'array',
description: nls.localize('argv.enebleProposedApi', "Enable proposed APIs for a list of extension ids (such as \`vscode.git\`). Proposed APIs are unstable and subject to breaking without warning at any time. This should only be set for extension development and testing purposes."),
items: {
type: 'string'
}
}
}
};
if (isLinux) {
schema.properties!['force-renderer-accessibility'] = {
type: 'boolean',
description: nls.localize('argv.force-renderer-accessibility', 'Forces the renderer to be accessible. ONLY change this if you are using a screen reader on Linux. On other platforms the renderer will automatically be accessible. This flag is automatically set if you have editor.accessibilitySupport: on.'),
};
}
jsonRegistry.registerSchema(argvDefinitionFileSchemaId, schema);
})();

View File

@@ -0,0 +1,280 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { zoomLevelToZoomFactor } from 'vs/platform/windows/common/windows';
import { importEntries, mark } from 'vs/base/common/performance';
import { Workbench } from 'vs/workbench/browser/workbench';
import { NativeWindow } from 'vs/workbench/electron-sandbox/window';
import { setZoomLevel, setZoomFactor, setFullscreen } from 'vs/base/browser/browser';
import { domContentLoaded, addDisposableListener, EventType, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
import { URI } from 'vs/base/common/uri';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ILogService } from 'vs/platform/log/common/log';
import { Schemas } from 'vs/base/common/network';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { Disposable } from 'vs/base/common/lifecycle';
import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { FileService } from 'vs/platform/files/common/fileService';
import { IFileService } from 'vs/platform/files/common/files';
import { RemoteFileSystemProvider } from 'vs/workbench/services/remote/common/remoteAgentFileSystemChannel';
import { ISignService } from 'vs/platform/sign/common/sign';
import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider';
import { IProductService } from 'vs/platform/product/common/productService';
import product from 'vs/platform/product/common/product';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHostService';
import { SimpleConfigurationService, simpleFileSystemProvider, SimpleLogService, SimpleRemoteAgentService, SimpleSignService, SimpleStorageService, SimpleNativeWorkbenchEnvironmentService, SimpleWorkspaceService } from 'vs/workbench/electron-sandbox/sandbox.simpleservices';
import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-sandbox/remoteAuthorityResolverService';
class DesktopMain extends Disposable {
private readonly environmentService = new SimpleNativeWorkbenchEnvironmentService(this.configuration);
constructor(private configuration: INativeWorkbenchConfiguration) {
super();
this.init();
}
private init(): void {
// Massage configuration file URIs
this.reviveUris();
// Setup perf
importEntries(this.configuration.perfEntries);
// Browser config
const zoomLevel = this.configuration.zoomLevel || 0;
setZoomFactor(zoomLevelToZoomFactor(zoomLevel));
setZoomLevel(zoomLevel, true /* isTrusted */);
setFullscreen(!!this.configuration.fullscreen);
}
private reviveUris() {
if (this.configuration.folderUri) {
this.configuration.folderUri = URI.revive(this.configuration.folderUri);
}
if (this.configuration.workspace) {
this.configuration.workspace = reviveWorkspaceIdentifier(this.configuration.workspace);
}
const filesToWait = this.configuration.filesToWait;
const filesToWaitPaths = filesToWait?.paths;
[filesToWaitPaths, this.configuration.filesToOpenOrCreate, this.configuration.filesToDiff].forEach(paths => {
if (Array.isArray(paths)) {
paths.forEach(path => {
if (path.fileUri) {
path.fileUri = URI.revive(path.fileUri);
}
});
}
});
if (filesToWait) {
filesToWait.waitMarkerFileUri = URI.revive(filesToWait.waitMarkerFileUri);
}
}
async open(): Promise<void> {
const services = await this.initServices();
await domContentLoaded();
mark('willStartWorkbench');
// Create Workbench
const workbench = new Workbench(document.body, services.serviceCollection, services.logService);
// Listeners
this.registerListeners(workbench, services.storageService);
// Startup
const instantiationService = workbench.startup();
// Window
this._register(instantiationService.createInstance(NativeWindow));
// Logging
services.logService.trace('workbench configuration', JSON.stringify(this.configuration));
}
private registerListeners(workbench: Workbench, storageService: SimpleStorageService): void {
// Layout
this._register(addDisposableListener(window, EventType.RESIZE, e => this.onWindowResize(e, true, workbench)));
// Workbench Lifecycle
this._register(workbench.onShutdown(() => this.dispose()));
this._register(workbench.onWillShutdown(event => event.join(storageService.close())));
}
private onWindowResize(e: Event, retry: boolean, workbench: Workbench): void {
if (e.target === window) {
if (window.document && window.document.body && window.document.body.clientWidth === 0) {
// TODO@Ben this is an electron issue on macOS when simple fullscreen is enabled
// where for some reason the window clientWidth is reported as 0 when switching
// between simple fullscreen and normal screen. In that case we schedule the layout
// call at the next animation frame once, in the hope that the dimensions are
// proper then.
if (retry) {
scheduleAtNextAnimationFrame(() => this.onWindowResize(e, false, workbench));
}
return;
}
workbench.layout();
}
}
private async initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService, storageService: SimpleStorageService }> {
const serviceCollection = new ServiceCollection();
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// NOTE: Please do NOT register services here. Use `registerSingleton()`
// from `workbench.common.main.ts` if the service is shared between
// desktop and web or `workbench.sandbox.main.ts` if the service
// is desktop only.
//
// DO NOT add services to `workbench.desktop.main.ts`, always add
// to `workbench.sandbox.main.ts` to support our Electron sandbox
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// Main Process
const mainProcessService = this._register(new MainProcessService(this.configuration.windowId));
serviceCollection.set(IMainProcessService, mainProcessService);
// Environment
serviceCollection.set(IWorkbenchEnvironmentService, this.environmentService);
serviceCollection.set(INativeWorkbenchEnvironmentService, this.environmentService);
// Product
const productService: IProductService = { _serviceBrand: undefined, ...product };
serviceCollection.set(IProductService, productService);
// Log
const logService = new SimpleLogService();
serviceCollection.set(ILogService, logService);
// Remote
const remoteAuthorityResolverService = new RemoteAuthorityResolverService();
serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService);
// Sign
const signService = new SimpleSignService();
serviceCollection.set(ISignService, signService);
// Remote Agent
const remoteAgentService = new SimpleRemoteAgentService();
serviceCollection.set(IRemoteAgentService, remoteAgentService);
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// NOTE: Please do NOT register services here. Use `registerSingleton()`
// from `workbench.common.main.ts` if the service is shared between
// desktop and web or `workbench.sandbox.main.ts` if the service
// is desktop only.
//
// DO NOT add services to `workbench.desktop.main.ts`, always add
// to `workbench.sandbox.main.ts` to support our Electron sandbox
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// Native Host
const nativeHostService = new NativeHostService(this.configuration.windowId, mainProcessService) as INativeHostService;
serviceCollection.set(INativeHostService, nativeHostService);
// Files
const fileService = this._register(new FileService(logService));
serviceCollection.set(IFileService, fileService);
fileService.registerProvider(Schemas.file, simpleFileSystemProvider);
// User Data Provider
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(URI.file('user-home'), undefined, simpleFileSystemProvider, this.environmentService, logService));
const connection = remoteAgentService.getConnection();
if (connection) {
const remoteFileSystemProvider = this._register(new RemoteFileSystemProvider(remoteAgentService));
fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider);
}
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// NOTE: Please do NOT register services here. Use `registerSingleton()`
// from `workbench.common.main.ts` if the service is shared between
// desktop and web or `workbench.sandbox.main.ts` if the service
// is desktop only.
//
// DO NOT add services to `workbench.desktop.main.ts`, always add
// to `workbench.sandbox.main.ts` to support our Electron sandbox
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
const services = await Promise.all([
this.createWorkspaceService().then(service => {
// Workspace
serviceCollection.set(IWorkspaceContextService, service);
// Configuration
serviceCollection.set(IConfigurationService, new SimpleConfigurationService());
return service;
}),
this.createStorageService().then(service => {
// Storage
serviceCollection.set(IStorageService, service);
return service;
})
]);
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// NOTE: Please do NOT register services here. Use `registerSingleton()`
// from `workbench.common.main.ts` if the service is shared between
// desktop and web or `workbench.sandbox.main.ts` if the service
// is desktop only.
//
// DO NOT add services to `workbench.desktop.main.ts`, always add
// to `workbench.sandbox.main.ts` to support our Electron sandbox
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
return { serviceCollection, logService, storageService: services[1] };
}
private async createWorkspaceService(): Promise<IWorkspaceContextService> {
return new SimpleWorkspaceService();
}
private async createStorageService(): Promise<SimpleStorageService> {
return new SimpleStorageService();
}
}
export function main(configuration: INativeWorkbenchConfiguration): Promise<void> {
const workbench = new DesktopMain(configuration);
return workbench.open();
}

View File

@@ -0,0 +1,243 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getZoomFactor } from 'vs/base/browser/browser';
import * as DOM from 'vs/base/browser/dom';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { ILabelService } from 'vs/platform/label/common/label';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { isMacintosh, isWindows, isLinux } from 'vs/base/common/platform';
import { IMenuService } from 'vs/platform/actions/common/actions';
import { TitlebarPart as BrowserTitleBarPart } from 'vs/workbench/browser/parts/titlebar/titlebarPart';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { IProductService } from 'vs/platform/product/common/productService';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { getTitleBarStyle } from 'vs/platform/windows/common/windows';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Codicon } from 'vs/base/common/codicons';
export class TitlebarPart extends BrowserTitleBarPart {
private appIcon: HTMLElement | undefined;
private windowControls: HTMLElement | undefined;
private maxRestoreControl: HTMLElement | undefined;
private dragRegion: HTMLElement | undefined;
private resizer: HTMLElement | undefined;
constructor(
@IContextMenuService contextMenuService: IContextMenuService,
@IConfigurationService protected readonly configurationService: IConfigurationService,
@IEditorService editorService: IEditorService,
@IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@ILabelService labelService: ILabelService,
@IStorageService storageService: IStorageService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@IMenuService menuService: IMenuService,
@IContextKeyService contextKeyService: IContextKeyService,
@IHostService hostService: IHostService,
@IProductService productService: IProductService,
@INativeHostService private readonly nativeHostService: INativeHostService
) {
super(contextMenuService, configurationService, editorService, environmentService, contextService, instantiationService, themeService, labelService, storageService, layoutService, menuService, contextKeyService, hostService, productService);
}
private onUpdateAppIconDragBehavior() {
const setting = this.configurationService.getValue('window.doubleClickIconToClose');
if (setting && this.appIcon) {
(this.appIcon.style as any)['-webkit-app-region'] = 'no-drag';
} else if (this.appIcon) {
(this.appIcon.style as any)['-webkit-app-region'] = 'drag';
}
}
private onDidChangeMaximized(maximized: boolean) {
if (this.maxRestoreControl) {
if (maximized) {
this.maxRestoreControl.classList.remove(...Codicon.chromeMaximize.classNamesArray);
this.maxRestoreControl.classList.add(...Codicon.chromeRestore.classNamesArray);
} else {
this.maxRestoreControl.classList.remove(...Codicon.chromeRestore.classNamesArray);
this.maxRestoreControl.classList.add(...Codicon.chromeMaximize.classNamesArray);
}
}
if (this.resizer) {
if (maximized) {
DOM.hide(this.resizer);
} else {
DOM.show(this.resizer);
}
}
this.adjustTitleMarginToCenter();
}
private onMenubarFocusChanged(focused: boolean) {
if ((isWindows || isLinux) && this.currentMenubarVisibility !== 'compact' && this.dragRegion) {
if (focused) {
DOM.hide(this.dragRegion);
} else {
DOM.show(this.dragRegion);
}
}
}
protected onMenubarVisibilityChanged(visible: boolean) {
// Hide title when toggling menu bar
if ((isWindows || isLinux) && this.currentMenubarVisibility === 'toggle' && visible) {
// Hack to fix issue #52522 with layered webkit-app-region elements appearing under cursor
if (this.dragRegion) {
DOM.hide(this.dragRegion);
setTimeout(() => DOM.show(this.dragRegion!), 50);
}
}
super.onMenubarVisibilityChanged(visible);
}
protected onConfigurationChanged(event: IConfigurationChangeEvent): void {
super.onConfigurationChanged(event);
if (event.affectsConfiguration('window.doubleClickIconToClose')) {
if (this.appIcon) {
this.onUpdateAppIconDragBehavior();
}
}
}
protected adjustTitleMarginToCenter(): void {
if (this.customMenubar && this.menubar) {
const leftMarker = (this.appIcon ? this.appIcon.clientWidth : 0) + this.menubar.clientWidth + 10;
const rightMarker = this.element.clientWidth - (this.windowControls ? this.windowControls.clientWidth : 0) - 10;
// Not enough space to center the titlebar within window,
// Center between menu and window controls
if (leftMarker > (this.element.clientWidth - this.title.clientWidth) / 2 ||
rightMarker < (this.element.clientWidth + this.title.clientWidth) / 2) {
this.title.style.position = '';
this.title.style.left = '';
this.title.style.transform = '';
return;
}
}
this.title.style.position = 'absolute';
this.title.style.left = '50%';
this.title.style.transform = 'translate(-50%, 0)';
}
protected installMenubar(): void {
super.installMenubar();
if (this.menubar) {
return;
}
if (this.customMenubar) {
this._register(this.customMenubar.onFocusStateChange(e => this.onMenubarFocusChanged(e)));
}
}
createContentArea(parent: HTMLElement): HTMLElement {
const ret = super.createContentArea(parent);
// App Icon (Native Windows/Linux)
if (!isMacintosh) {
this.appIcon = DOM.prepend(this.element, DOM.$('div.window-appicon'));
this.onUpdateAppIconDragBehavior();
this._register(DOM.addDisposableListener(this.appIcon, DOM.EventType.DBLCLICK, (e => {
this.nativeHostService.closeWindow();
})));
}
// Draggable region that we can manipulate for #52522
this.dragRegion = DOM.prepend(this.element, DOM.$('div.titlebar-drag-region'));
// Window Controls (Native Windows/Linux)
if (!isMacintosh) {
this.windowControls = DOM.append(this.element, DOM.$('div.window-controls-container'));
// Minimize
const minimizeIcon = DOM.append(this.windowControls, DOM.$('div.window-icon.window-minimize' + Codicon.chromeMinimize.cssSelector));
this._register(DOM.addDisposableListener(minimizeIcon, DOM.EventType.CLICK, e => {
this.nativeHostService.minimizeWindow();
}));
// Restore
this.maxRestoreControl = DOM.append(this.windowControls, DOM.$('div.window-icon.window-max-restore'));
this._register(DOM.addDisposableListener(this.maxRestoreControl, DOM.EventType.CLICK, async e => {
const maximized = await this.nativeHostService.isMaximized();
if (maximized) {
return this.nativeHostService.unmaximizeWindow();
}
return this.nativeHostService.maximizeWindow();
}));
// Close
const closeIcon = DOM.append(this.windowControls, DOM.$('div.window-icon.window-close' + Codicon.chromeClose.cssSelector));
this._register(DOM.addDisposableListener(closeIcon, DOM.EventType.CLICK, e => {
this.nativeHostService.closeWindow();
}));
// Resizer
this.resizer = DOM.append(this.element, DOM.$('div.resizer'));
this._register(this.layoutService.onMaximizeChange(maximized => this.onDidChangeMaximized(maximized)));
this.onDidChangeMaximized(this.layoutService.isWindowMaximized());
}
return ret;
}
updateLayout(dimension: DOM.Dimension): void {
this.lastLayoutDimensions = dimension;
if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') {
// Only prevent zooming behavior on macOS or when the menubar is not visible
if (isMacintosh || this.currentMenubarVisibility === 'hidden') {
this.title.style.zoom = `${1 / getZoomFactor()}`;
if (isWindows || isLinux) {
if (this.appIcon) {
this.appIcon.style.zoom = `${1 / getZoomFactor()}`;
}
if (this.windowControls) {
this.windowControls.style.zoom = `${1 / getZoomFactor()}`;
}
}
} else {
this.title.style.zoom = '';
if (isWindows || isLinux) {
if (this.appIcon) {
this.appIcon.style.zoom = '';
}
if (this.windowControls) {
this.windowControls.style.zoom = '';
}
}
}
DOM.runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter());
if (this.customMenubar) {
const menubarDimension = new DOM.Dimension(0, dimension.height);
this.customMenubar.layout(menubarDimension);
}
}
}
}

View File

@@ -0,0 +1,834 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* eslint-disable code-no-standalone-editor */
/* eslint-disable code-import-patterns */
import { ConsoleLogService } from 'vs/platform/log/common/log';
import { ISignService } from 'vs/platform/sign/common/sign';
import { URI } from 'vs/base/common/uri';
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
import { Event } from 'vs/base/common/event';
import { IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics';
import { IAddressProvider, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { ITelemetryData, ITelemetryInfo, ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { BrowserSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory';
import { ExtensionIdentifier, IExtension, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { SimpleConfigurationService as BaseSimpleConfigurationService } from 'vs/editor/standalone/browser/simpleServices';
import { InMemoryStorageService } from 'vs/platform/storage/common/storage';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backup/common/backup';
import { ITextSnapshot } from 'vs/editor/common/model';
import { IExtensionService, NullExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ClassifiedEvent, GDPRClassification, StrictPropertyChecker } from 'vs/platform/telemetry/common/gdprTypings';
import { IKeyboardLayoutInfo, IKeymapService, ILinuxKeyboardLayoutInfo, ILinuxKeyboardMapping, IMacKeyboardLayoutInfo, IMacKeyboardMapping, IWindowsKeyboardLayoutInfo, IWindowsKeyboardMapping } from 'vs/workbench/services/keybinding/common/keymapInfo';
import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding';
import { DispatchConfig } from 'vs/workbench/services/keybinding/common/dispatchConfig';
import { IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper';
import { ChordKeybinding, ResolvedKeybinding, SimpleKeybinding } from 'vs/base/common/keyCodes';
import { ScanCodeBinding } from 'vs/base/common/scanCode';
import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding';
import { isWindows, OS } from 'vs/base/common/platform';
import { IWebviewService, WebviewContentOptions, WebviewElement, WebviewExtensionDescription, WebviewIcons, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { AbstractTextFileService } from 'vs/workbench/services/textfile/browser/textFileService';
import { IExtensionManagementServer, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ITunnelProvider, ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { IManualSyncTask, IResourcePreview, ISyncResourceHandle, ISyncTask, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, IUserDataSyncStoreManagementService, SyncResource, SyncStatus, UserDataSyncStoreType } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncAccount, IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount';
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ITaskProvider, ITaskService, ITaskSummary, ProblemMatcherRunOptions, Task, TaskFilter, TaskTerminateResponse, WorkspaceFolderTaskResult } from 'vs/workbench/contrib/tasks/common/taskService';
import { Action } from 'vs/base/common/actions';
import { LinkedMap } from 'vs/base/common/map';
import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder, WorkbenchState, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { CustomTask, ContributedTask, InMemoryTask, TaskRunSource, ConfiguringTask, TaskIdentifier, TaskSorter } from 'vs/workbench/contrib/tasks/common/tasks';
import { TaskSystemInfo } from 'vs/workbench/contrib/tasks/common/taskSystem';
import { IExtensionTipsService, IConfigBasedExtensionTip, IExecutableBasedExtensionTip, IWorkspaceTips } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkspaceTagsService, Tags } from 'vs/workbench/contrib/tags/common/workspaceTags';
import { AsbtractOutputChannelModelService, IOutputChannelModelService } from 'vs/workbench/services/output/common/outputChannelModel';
import { joinPath } from 'vs/base/common/resources';
import { VSBuffer } from 'vs/base/common/buffer';
import { IIntegrityService, IntegrityTestResult } from 'vs/workbench/services/integrity/common/integrity';
import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { IExtensionHostDebugParams } from 'vs/platform/environment/common/environment';
import type { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api';
import { Schemas } from 'vs/base/common/network';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
//#region Environment
export class SimpleNativeWorkbenchEnvironmentService implements INativeWorkbenchEnvironmentService {
declare readonly _serviceBrand: undefined;
constructor(
readonly configuration: INativeWorkbenchConfiguration
) { }
get userRoamingDataHome(): URI { return URI.file('/sandbox-user-data-dir').with({ scheme: Schemas.userData }); }
get settingsResource(): URI { return joinPath(this.userRoamingDataHome, 'settings.json'); }
get argvResource(): URI { return joinPath(this.userRoamingDataHome, 'argv.json'); }
get snippetsHome(): URI { return joinPath(this.userRoamingDataHome, 'snippets'); }
get globalStorageHome(): URI { return URI.joinPath(this.userRoamingDataHome, 'globalStorage'); }
get workspaceStorageHome(): URI { return URI.joinPath(this.userRoamingDataHome, 'workspaceStorage'); }
get keybindingsResource(): URI { return joinPath(this.userRoamingDataHome, 'keybindings.json'); }
get logFile(): URI { return joinPath(this.userRoamingDataHome, 'window.log'); }
get untitledWorkspacesHome(): URI { return joinPath(this.userRoamingDataHome, 'Workspaces'); }
get serviceMachineIdResource(): URI { return joinPath(this.userRoamingDataHome, 'machineid'); }
get userDataSyncLogResource(): URI { return joinPath(this.userRoamingDataHome, 'syncLog'); }
get userDataSyncHome(): URI { return joinPath(this.userRoamingDataHome, 'syncHome'); }
get tmpDir(): URI { return joinPath(this.userRoamingDataHome, 'tmp'); }
get logsPath(): string { return joinPath(this.userRoamingDataHome, 'logs').path; }
get backupWorkspaceHome(): URI { return joinPath(this.userRoamingDataHome, 'Backups', 'workspace'); }
updateBackupPath(newPath: string | undefined): void { }
sessionId = this.configuration.sessionId;
machineId = this.configuration.machineId;
remoteAuthority = this.configuration.remoteAuthority;
options?: IWorkbenchConstructionOptions | undefined;
logExtensionHostCommunication?: boolean | undefined;
extensionEnabledProposedApi?: string[] | undefined;
webviewExternalEndpoint: string = undefined!;
webviewResourceRoot: string = undefined!;
webviewCspSource: string = undefined!;
skipReleaseNotes: boolean = undefined!;
keyboardLayoutResource: URI = undefined!;
sync: 'on' | 'off' | undefined;
debugExtensionHost: IExtensionHostDebugParams = undefined!;
debugRenderer = false;
isExtensionDevelopment: boolean = false;
disableExtensions: boolean | string[] = [];
extensionDevelopmentLocationURI?: URI[] | undefined;
extensionTestsLocationURI?: URI | undefined;
logLevel?: string | undefined;
args: NativeParsedArgs = Object.create(null);
execPath: string = undefined!;
appRoot: string = undefined!;
userHome: URI = undefined!;
appSettingsHome: URI = undefined!;
userDataPath: string = undefined!;
machineSettingsResource: URI = undefined!;
log?: string | undefined;
extHostLogsPath: URI = undefined!;
installSourcePath: string = undefined!;
sharedIPCHandle: string = undefined!;
extensionsPath?: string | undefined;
extensionsDownloadPath: string = undefined!;
builtinExtensionsPath: string = undefined!;
driverHandle?: string | undefined;
crashReporterDirectory?: string | undefined;
crashReporterId?: string | undefined;
nodeCachedDataDir?: string | undefined;
verbose = false;
isBuilt = false;
get telemetryLogResource(): URI { return joinPath(this.userRoamingDataHome, 'telemetry.log'); }
disableTelemetry = false;
}
//#endregion
//#region Workspace
export const workspaceResource = URI.file(isWindows ? '\\simpleWorkspace' : '/simpleWorkspace');
export class SimpleWorkspaceService implements IWorkspaceContextService {
declare readonly _serviceBrand: undefined;
readonly onDidChangeWorkspaceName = Event.None;
readonly onDidChangeWorkspaceFolders = Event.None;
readonly onDidChangeWorkbenchState = Event.None;
private readonly workspace: IWorkspace;
constructor() {
this.workspace = { id: '4064f6ec-cb38-4ad0-af64-ee6467e63c82', folders: [new WorkspaceFolder({ uri: workspaceResource, name: '', index: 0 })] };
}
async getCompleteWorkspace(): Promise<IWorkspace> { return this.getWorkspace(); }
getWorkspace(): IWorkspace { return this.workspace; }
getWorkbenchState(): WorkbenchState {
if (this.workspace) {
if (this.workspace.configuration) {
return WorkbenchState.WORKSPACE;
}
return WorkbenchState.FOLDER;
}
return WorkbenchState.EMPTY;
}
getWorkspaceFolder(resource: URI): IWorkspaceFolder | null { return resource && resource.scheme === workspaceResource.scheme ? this.workspace.folders[0] : null; }
isInsideWorkspace(resource: URI): boolean { return resource && resource.scheme === workspaceResource.scheme; }
isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean { return true; }
}
//#endregion
//#region Configuration
export class SimpleStorageService extends InMemoryStorageService { }
//#endregion
//#region Configuration
export class SimpleConfigurationService extends BaseSimpleConfigurationService { }
//#endregion
//#region Logger
export class SimpleLogService extends ConsoleLogService { }
export class SimpleSignService implements ISignService {
declare readonly _serviceBrand: undefined;
async sign(value: string): Promise<string> { return value; }
}
//#endregion
//#region Files
class SimpleFileSystemProvider extends InMemoryFileSystemProvider { }
export const simpleFileSystemProvider = new SimpleFileSystemProvider();
function createFile(parent: string, name: string, content: string = ''): void {
simpleFileSystemProvider.writeFile(joinPath(workspaceResource, parent, name), VSBuffer.fromString(content).buffer, { create: true, overwrite: true });
}
function createFolder(name: string): void {
simpleFileSystemProvider.mkdir(joinPath(workspaceResource, name));
}
createFolder('');
createFolder('src');
createFolder('test');
createFile('', '.gitignore', `out
node_modules
.vscode-test/
*.vsix
`);
createFile('', '.vscodeignore', `.vscode/**
.vscode-test/**
out/test/**
src/**
.gitignore
vsc-extension-quickstart.md
**/tsconfig.json
**/tslint.json
**/*.map
**/*.ts`);
createFile('', 'CHANGELOG.md', `# Change Log
All notable changes to the "test-ts" extension will be documented in this file.
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
## [Unreleased]
- Initial release`);
createFile('', 'package.json', `{
"name": "test-ts",
"displayName": "test-ts",
"description": "",
"version": "0.0.1",
"engines": {
"vscode": "^1.31.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onCommand:extension.helloWorld"
],
"main": "./out/extension.js",
"contributes": {
"commands": [
{
"command": "extension.helloWorld",
"title": "Hello World"
}
]
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"postinstall": "node ./node_modules/vscode/bin/install",
"test": "npm run compile && node ./node_modules/vscode/bin/test"
},
"devDependencies": {
"typescript": "^3.3.1",
"vscode": "^1.1.28",
"tslint": "^5.12.1",
"@types/node": "^8.10.25",
"@types/mocha": "^2.2.42"
}
}
`);
createFile('', 'tsconfig.json', `{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "out",
"lib": [
"es6"
],
"sourceMap": true,
"rootDir": "src",
"strict": true /* enable all strict type-checking options */
/* Additional Checks */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
},
"exclude": [
"node_modules",
".vscode-test"
]
}
`);
createFile('', 'tslint.json', `{
"rules": {
"no-string-throw": true,
"no-unused-expression": true,
"no-duplicate-variable": true,
"curly": true,
"class-name": true,
"semicolon": [
true,
"always"
],
"triple-equals": true
},
"defaultSeverity": "warning"
}
`);
createFile('src', 'extension.ts', `// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
// Use the console to output diagnostic information (console.log) and errors (console.error)
// This line of code will only be executed once when your extension is activated
console.log('Congratulations, your extension "test-ts" is now active!');
// The command has been defined in the package.json file
// Now provide the implementation of the command with registerCommand
// The commandId parameter must match the command field in package.json
let disposable = vscode.commands.registerCommand('extension.helloWorld', () => {
// The code you place here will be executed every time your command is executed
// Display a message box to the user
vscode.window.showInformationMessage('Hello World!');
});
context.subscriptions.push(disposable);
}
// this method is called when your extension is deactivated
export function deactivate() {}
`);
createFile('test', 'extension.test.ts', `//
// Note: This example test is leveraging the Mocha test framework.
// Please refer to their documentation on https://mochajs.org/ for help.
//
// The module 'assert' provides assertion methods from node
import * as assert from 'assert';
// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
// import * as vscode from 'vscode';
// import * as myExtension from '../extension';
// Defines a Mocha test suite to group tests of similar kind together
suite("Extension Tests", function () {
// Defines a Mocha unit test
test("Something 1", function() {
assert.equal(-1, [1, 2, 3].indexOf(5));
assert.equal(-1, [1, 2, 3].indexOf(0));
});
});`);
createFile('test', 'index.ts', `//
// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING
//
// This file is providing the test runner to use when running extension tests.
// By default the test runner in use is Mocha based.
//
// You can provide your own test runner if you want to override it by exporting
// a function run(testRoot: string, clb: (error:Error) => void) that the extension
// host can call to run the tests. The test runner is expected to use console.log
// to report the results back to the caller. When the tests are finished, return
// a possible error to the callback or null if none.
import * as testRunner from 'vscode/lib/testrunner';
// You can directly control Mocha options by configuring the test runner below
// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options
// for more info
testRunner.configure({
ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.)
useColors: true // colored output from test results
});
module.exports = testRunner;`);
//#endregion
//#region Remote
export class SimpleRemoteAgentService implements IRemoteAgentService {
declare readonly _serviceBrand: undefined;
socketFactory: ISocketFactory = new BrowserSocketFactory(null);
getConnection(): IRemoteAgentConnection | null { return null; }
async getEnvironment(bail?: boolean): Promise<IRemoteAgentEnvironment | null> { return null; }
async getDiagnosticInfo(options: IDiagnosticInfoOptions): Promise<IDiagnosticInfo | undefined> { return undefined; }
async disableTelemetry(): Promise<void> { }
async logTelemetry(eventName: string, data?: ITelemetryData): Promise<void> { }
async flushTelemetry(): Promise<void> { }
async getRawEnvironment(): Promise<IRemoteAgentEnvironment | null> { return null; }
async scanExtensions(skipExtensions?: ExtensionIdentifier[]): Promise<IExtensionDescription[]> { return []; }
async scanSingleExtension(extensionLocation: URI, isBuiltin: boolean): Promise<IExtensionDescription | null> { return null; }
}
//#endregion
//#region Backup File
class SimpleBackupFileService implements IBackupFileService {
declare readonly _serviceBrand: undefined;
async hasBackups(): Promise<boolean> { return false; }
async discardResourceBackup(resource: URI): Promise<void> { }
async discardAllWorkspaceBackups(): Promise<void> { }
toBackupResource(resource: URI): URI { return resource; }
hasBackupSync(resource: URI, versionId?: number): boolean { return false; }
async getBackups(): Promise<URI[]> { return []; }
async resolve<T extends object>(resource: URI): Promise<IResolvedBackup<T> | undefined> { return undefined; }
async backup<T extends object>(resource: URI, content?: ITextSnapshot, versionId?: number, meta?: T): Promise<void> { }
async discardBackup(resource: URI): Promise<void> { }
async discardBackups(): Promise<void> { }
}
registerSingleton(IBackupFileService, SimpleBackupFileService);
//#endregion
//#region Extensions
class SimpleExtensionService extends NullExtensionService { }
registerSingleton(IExtensionService, SimpleExtensionService);
//#endregion
//#region Telemetry
class SimpleTelemetryService implements ITelemetryService {
declare readonly _serviceBrand: undefined;
readonly sendErrorTelemetry = false;
readonly isOptedIn = false;
async publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise<void> { }
async publicLog2<E extends ClassifiedEvent<T> = never, T extends GDPRClassification<T> = never>(eventName: string, data?: StrictPropertyChecker<E, ClassifiedEvent<T>, 'Type of classified event does not match event properties'>, anonymizeFilePaths?: boolean): Promise<void> { }
async publicLogError(errorEventName: string, data?: ITelemetryData): Promise<void> { }
async publicLogError2<E extends ClassifiedEvent<T> = never, T extends GDPRClassification<T> = never>(eventName: string, data?: StrictPropertyChecker<E, ClassifiedEvent<T>, 'Type of classified event does not match event properties'>): Promise<void> { }
setEnabled(value: boolean): void { }
setExperimentProperty(name: string, value: string): void { }
async getTelemetryInfo(): Promise<ITelemetryInfo> {
return {
instanceId: 'someValue.instanceId',
sessionId: 'someValue.sessionId',
machineId: 'someValue.machineId'
};
}
}
registerSingleton(ITelemetryService, SimpleTelemetryService);
//#endregion
//#region Keymap Service
class SimpleKeyboardMapper implements IKeyboardMapper {
dumpDebugInfo(): string { return ''; }
resolveKeybinding(keybinding: ChordKeybinding): ResolvedKeybinding[] { return []; }
resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding {
let keybinding = new SimpleKeybinding(
keyboardEvent.ctrlKey,
keyboardEvent.shiftKey,
keyboardEvent.altKey,
keyboardEvent.metaKey,
keyboardEvent.keyCode
).toChord();
return new USLayoutResolvedKeybinding(keybinding, OS);
}
resolveUserBinding(firstPart: (SimpleKeybinding | ScanCodeBinding)[]): ResolvedKeybinding[] { return []; }
}
class SimpleKeymapService implements IKeymapService {
declare readonly _serviceBrand: undefined;
onDidChangeKeyboardMapper = Event.None;
getKeyboardMapper(dispatchConfig: DispatchConfig): IKeyboardMapper { return new SimpleKeyboardMapper(); }
getCurrentKeyboardLayout(): (IWindowsKeyboardLayoutInfo & { isUserKeyboardLayout?: boolean | undefined; isUSStandard?: true | undefined; }) | (ILinuxKeyboardLayoutInfo & { isUserKeyboardLayout?: boolean | undefined; isUSStandard?: true | undefined; }) | (IMacKeyboardLayoutInfo & { isUserKeyboardLayout?: boolean | undefined; isUSStandard?: true | undefined; }) | null { return null; }
getAllKeyboardLayouts(): IKeyboardLayoutInfo[] { return []; }
getRawKeyboardMapping(): IWindowsKeyboardMapping | ILinuxKeyboardMapping | IMacKeyboardMapping | null { return null; }
validateCurrentKeyboardMapping(keyboardEvent: IKeyboardEvent): void { }
}
registerSingleton(IKeymapService, SimpleKeymapService);
//#endregion
//#region Webview
class SimpleWebviewService implements IWebviewService {
declare readonly _serviceBrand: undefined;
readonly activeWebview = undefined;
createWebviewElement(id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, extension: WebviewExtensionDescription | undefined): WebviewElement { throw new Error('Method not implemented.'); }
createWebviewOverlay(id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, extension: WebviewExtensionDescription | undefined): WebviewOverlay { throw new Error('Method not implemented.'); }
setIcons(id: string, value: WebviewIcons | undefined): void { }
}
registerSingleton(IWebviewService, SimpleWebviewService);
//#endregion
//#region Textfiles
class SimpleTextFileService extends AbstractTextFileService {
declare readonly _serviceBrand: undefined;
}
registerSingleton(ITextFileService, SimpleTextFileService);
//#endregion
//#region extensions management
class SimpleExtensionManagementServerService implements IExtensionManagementServerService {
declare readonly _serviceBrand: undefined;
readonly localExtensionManagementServer = null;
readonly remoteExtensionManagementServer = null;
readonly webExtensionManagementServer = null;
getExtensionManagementServer(extension: IExtension): IExtensionManagementServer | null { return null; }
}
registerSingleton(IExtensionManagementServerService, SimpleExtensionManagementServerService);
//#endregion
//#region Tunnel
class SimpleTunnelService implements ITunnelService {
declare readonly _serviceBrand: undefined;
tunnels: Promise<readonly RemoteTunnel[]> = Promise.resolve([]);
onTunnelOpened = Event.None;
onTunnelClosed = Event.None;
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined { return undefined; }
async closeTunnel(remoteHost: string, remotePort: number): Promise<void> { }
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable { return Disposable.None; }
}
registerSingleton(ITunnelService, SimpleTunnelService);
//#endregion
//#region User Data Sync
class SimpleUserDataSyncService implements IUserDataSyncService {
declare readonly _serviceBrand: undefined;
onDidChangeStatus = Event.None;
onDidChangeConflicts = Event.None;
onDidChangeLocal = Event.None;
onSyncErrors = Event.None;
onDidChangeLastSyncTime = Event.None;
onDidResetRemote = Event.None;
onDidResetLocal = Event.None;
status: SyncStatus = SyncStatus.Idle;
conflicts: [SyncResource, IResourcePreview[]][] = [];
lastSyncTime = undefined;
createSyncTask(): Promise<ISyncTask> { throw new Error('Method not implemented.'); }
createManualSyncTask(): Promise<IManualSyncTask> { throw new Error('Method not implemented.'); }
async replace(uri: URI): Promise<void> { }
async reset(): Promise<void> { }
async resetRemote(): Promise<void> { }
async resetLocal(): Promise<void> { }
async hasLocalData(): Promise<boolean> { return false; }
async hasPreviouslySynced(): Promise<boolean> { return false; }
async resolveContent(resource: URI): Promise<string | null> { return null; }
async accept(resource: SyncResource, conflictResource: URI, content: string | null | undefined, apply: boolean): Promise<void> { }
async getLocalSyncResourceHandles(resource: SyncResource): Promise<ISyncResourceHandle[]> { return []; }
async getRemoteSyncResourceHandles(resource: SyncResource): Promise<ISyncResourceHandle[]> { return []; }
async getAssociatedResources(resource: SyncResource, syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI; comparableResource: URI; }[]> { return []; }
async getMachineId(resource: SyncResource, syncResourceHandle: ISyncResourceHandle): Promise<string | undefined> { return undefined; }
}
registerSingleton(IUserDataSyncService, SimpleUserDataSyncService);
//#endregion
//#region User Data Sync Account
class SimpleUserDataSyncAccountService implements IUserDataSyncAccountService {
declare readonly _serviceBrand: undefined;
onTokenFailed = Event.None;
onDidChangeAccount = Event.None;
account: IUserDataSyncAccount | undefined = undefined;
async updateAccount(account: IUserDataSyncAccount | undefined): Promise<void> { }
}
registerSingleton(IUserDataSyncAccountService, SimpleUserDataSyncAccountService);
//#endregion
//#region User Data Auto Sync Account
class SimpleUserDataAutoSyncAccountService implements IUserDataAutoSyncService {
declare readonly _serviceBrand: undefined;
onError = Event.None;
onDidChangeEnablement = Event.None;
isEnabled(): boolean { return false; }
canToggleEnablement(): boolean { return false; }
async turnOn(): Promise<void> { }
async turnOff(everywhere: boolean): Promise<void> { }
async triggerSync(sources: string[], hasToLimitSync: boolean, disableCache: boolean): Promise<void> { }
}
registerSingleton(IUserDataAutoSyncService, SimpleUserDataAutoSyncAccountService);
//#endregion
//#region User Data Sync Store Management
class SimpleIUserDataSyncStoreManagementService implements IUserDataSyncStoreManagementService {
declare readonly _serviceBrand: undefined;
onDidChangeUserDataSyncStore = Event.None;
userDataSyncStore: IUserDataSyncStore | undefined = undefined;
async switch(type: UserDataSyncStoreType): Promise<void> { }
async getPreviousUserDataSyncStore(): Promise<IUserDataSyncStore | undefined> { return undefined; }
}
registerSingleton(IUserDataSyncStoreManagementService, SimpleIUserDataSyncStoreManagementService);
//#endregion
//#region IStorageKeysSyncRegistryService
class SimpleIStorageKeysSyncRegistryService implements IStorageKeysSyncRegistryService {
declare readonly _serviceBrand: undefined;
onDidChangeStorageKeys = Event.None;
storageKeys = [];
registerStorageKey(): void { }
onDidChangeExtensionStorageKeys = Event.None;
extensionsStorageKeys = [];
getExtensioStorageKeys() { return undefined; }
registerExtensionStorageKeys(): void { }
}
registerSingleton(IStorageKeysSyncRegistryService, SimpleIStorageKeysSyncRegistryService);
//#endregion
//#region Task
class SimpleTaskService implements ITaskService {
declare readonly _serviceBrand: undefined;
onDidStateChange = Event.None;
supportsMultipleTaskExecutions = false;
configureAction(): Action { throw new Error('Method not implemented.'); }
build(): Promise<ITaskSummary> { throw new Error('Method not implemented.'); }
runTest(): Promise<ITaskSummary> { throw new Error('Method not implemented.'); }
run(task: CustomTask | ContributedTask | InMemoryTask | undefined, options?: ProblemMatcherRunOptions): Promise<ITaskSummary | undefined> { throw new Error('Method not implemented.'); }
inTerminal(): boolean { throw new Error('Method not implemented.'); }
isActive(): Promise<boolean> { throw new Error('Method not implemented.'); }
getActiveTasks(): Promise<Task[]> { throw new Error('Method not implemented.'); }
getBusyTasks(): Promise<Task[]> { throw new Error('Method not implemented.'); }
restart(task: Task): void { throw new Error('Method not implemented.'); }
terminate(task: Task): Promise<TaskTerminateResponse> { throw new Error('Method not implemented.'); }
terminateAll(): Promise<TaskTerminateResponse[]> { throw new Error('Method not implemented.'); }
tasks(filter?: TaskFilter): Promise<Task[]> { throw new Error('Method not implemented.'); }
taskTypes(): string[] { throw new Error('Method not implemented.'); }
getWorkspaceTasks(runSource?: TaskRunSource): Promise<Map<string, WorkspaceFolderTaskResult>> { throw new Error('Method not implemented.'); }
readRecentTasks(): Promise<(CustomTask | ContributedTask | InMemoryTask | ConfiguringTask)[]> { throw new Error('Method not implemented.'); }
getTask(workspaceFolder: string | IWorkspace | IWorkspaceFolder, alias: string | TaskIdentifier, compareId?: boolean): Promise<CustomTask | ContributedTask | InMemoryTask | undefined> { throw new Error('Method not implemented.'); }
tryResolveTask(configuringTask: ConfiguringTask): Promise<CustomTask | ContributedTask | InMemoryTask | undefined> { throw new Error('Method not implemented.'); }
getTasksForGroup(group: string): Promise<Task[]> { throw new Error('Method not implemented.'); }
getRecentlyUsedTasks(): LinkedMap<string, string> { throw new Error('Method not implemented.'); }
migrateRecentTasks(tasks: Task[]): Promise<void> { throw new Error('Method not implemented.'); }
createSorter(): TaskSorter { throw new Error('Method not implemented.'); }
getTaskDescription(task: CustomTask | ContributedTask | InMemoryTask | ConfiguringTask): string | undefined { throw new Error('Method not implemented.'); }
canCustomize(task: CustomTask | ContributedTask): boolean { throw new Error('Method not implemented.'); }
customize(task: CustomTask | ContributedTask | ConfiguringTask, properties?: {}, openConfig?: boolean): Promise<void> { throw new Error('Method not implemented.'); }
openConfig(task: CustomTask | ConfiguringTask | undefined): Promise<boolean> { throw new Error('Method not implemented.'); }
registerTaskProvider(taskProvider: ITaskProvider, type: string): IDisposable { throw new Error('Method not implemented.'); }
registerTaskSystem(scheme: string, taskSystemInfo: TaskSystemInfo): void { throw new Error('Method not implemented.'); }
registerSupportedExecutions(custom?: boolean, shell?: boolean, process?: boolean): void { throw new Error('Method not implemented.'); }
setJsonTasksSupported(areSuppored: Promise<boolean>): void { throw new Error('Method not implemented.'); }
extensionCallbackTaskComplete(task: Task, result: number | undefined): Promise<void> { throw new Error('Method not implemented.'); }
}
registerSingleton(ITaskService, SimpleTaskService);
//#endregion
//#region Extension Tips
class SimpleExtensionTipsService implements IExtensionTipsService {
declare readonly _serviceBrand: undefined;
onRecommendationChange = Event.None;
async getConfigBasedTips(folder: URI): Promise<IConfigBasedExtensionTip[]> { return []; }
async getImportantExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]> { return []; }
async getOtherExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]> { return []; }
async getAllWorkspacesTips(): Promise<IWorkspaceTips[]> { return []; }
}
registerSingleton(IExtensionTipsService, SimpleExtensionTipsService);
//#endregion
//#region Workspace Tags
class SimpleWorkspaceTagsService implements IWorkspaceTagsService {
declare readonly _serviceBrand: undefined;
async getTags(): Promise<Tags> { return Object.create(null); }
getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): string | undefined { return undefined; }
async getHashedRemotesFromUri(workspaceUri: URI, stripEndingDotGit?: boolean): Promise<string[]> { return []; }
}
registerSingleton(IWorkspaceTagsService, SimpleWorkspaceTagsService);
//#endregion
//#region Output Channel
class SimpleOutputChannelModelService extends AsbtractOutputChannelModelService {
declare readonly _serviceBrand: undefined;
}
registerSingleton(IOutputChannelModelService, SimpleOutputChannelModelService);
//#endregion
//#region Integrity
class SimpleIntegrityService implements IIntegrityService {
declare readonly _serviceBrand: undefined;
async isPure(): Promise<IntegrityTestResult> {
return { isPure: true, proof: [] };
}
}
registerSingleton(IIntegrityService, SimpleIntegrityService);
//#endregion

View File

@@ -0,0 +1,851 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { URI } from 'vs/base/common/uri';
import * as errors from 'vs/base/common/errors';
import { equals } from 'vs/base/common/objects';
import * as DOM from 'vs/base/browser/dom';
import { IAction, Separator } from 'vs/base/common/actions';
import { IFileService } from 'vs/platform/files/common/files';
import { EditorResourceAccessor, IUntitledTextResourceEditorInput, SideBySideEditor, pathsToEditors } from 'vs/workbench/common/editor';
import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { WindowMinimumSize, IOpenFileRequest, IWindowsConfiguration, getTitleBarStyle, IAddFoldersRequest, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, INativeOpenFileRequest } from 'vs/platform/windows/common/windows';
import { ITitleService } from 'vs/workbench/services/title/common/titleService';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { applyZoom } from 'vs/platform/windows/electron-sandbox/window';
import { setFullscreen, getZoomLevel } from 'vs/base/browser/browser';
import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { RunOnceScheduler } from 'vs/base/common/async';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { LifecyclePhase, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IWorkspaceFolderCreationData, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity';
import { isWindows, isMacintosh } from 'vs/base/common/platform';
import { IProductService } from 'vs/platform/product/common/productService';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { coalesce } from 'vs/base/common/arrays';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { MenubarControl } from '../browser/parts/titlebar/menubarControl';
import { ILabelService } from 'vs/platform/label/common/label';
import { IUpdateService } from 'vs/platform/update/common/update';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IPreferencesService } from '../services/preferences/common/preferences';
import { IMenubarData, IMenubarMenu, IMenubarKeybinding, IMenubarMenuItemSubmenu, IMenubarMenuItemAction, MenubarMenuItem } from 'vs/platform/menubar/common/menubar';
import { IMenubarService } from 'vs/platform/menubar/electron-sandbox/menubar';
import { withNullAsUndefined, assertIsDefined } from 'vs/base/common/types';
import { IOpenerService, OpenOptions } from 'vs/platform/opener/common/opener';
import { Schemas } from 'vs/base/common/network';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { posix, dirname } from 'vs/base/common/path';
import { getBaseLabel } from 'vs/base/common/labels';
import { ITunnelService, extractLocalHostUriMetaDataForPortMapping } from 'vs/platform/remote/common/tunnel';
import { IWorkbenchLayoutService, Parts, positionFromString, Position } from 'vs/workbench/services/layout/browser/layoutService';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { Event } from 'vs/base/common/event';
import { clearAllFontInfos } from 'vs/editor/browser/config/configuration';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IAddressProvider, IAddress } from 'vs/platform/remote/common/remoteAgentConnection';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { AuthInfo } from 'vs/base/parts/sandbox/electron-sandbox/electronTypes';
export class NativeWindow extends Disposable {
private static REMEMBER_PROXY_CREDENTIALS_KEY = 'window.rememberProxyCredentials';
private touchBarMenu: IMenu | undefined;
private readonly touchBarDisposables = this._register(new DisposableStore());
private lastInstalledTouchedBar: ICommandAction[][] | undefined;
private readonly customTitleContextMenuDisposable = this._register(new DisposableStore());
private previousConfiguredZoomLevel: number | undefined;
private readonly addFoldersScheduler = this._register(new RunOnceScheduler(() => this.doAddFolders(), 100));
private pendingFoldersToAdd: URI[] = [];
private readonly closeEmptyWindowScheduler = this._register(new RunOnceScheduler(() => this.onAllEditorsClosed(), 50));
private isDocumentedEdited = false;
constructor(
@IEditorService private readonly editorService: IEditorService,
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@ITitleService private readonly titleService: ITitleService,
@IWorkbenchThemeService protected themeService: IWorkbenchThemeService,
@INotificationService private readonly notificationService: INotificationService,
@ICommandService private readonly commandService: ICommandService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService,
@IFileService private readonly fileService: IFileService,
@IMenuService private readonly menuService: IMenuService,
@ILifecycleService private readonly lifecycleService: ILifecycleService,
@IIntegrityService private readonly integrityService: IIntegrityService,
@INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService,
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IOpenerService private readonly openerService: IOpenerService,
@INativeHostService private readonly nativeHostService: INativeHostService,
@ITunnelService private readonly tunnelService: ITunnelService,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@IWorkingCopyService private readonly workingCopyService: IWorkingCopyService,
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService,
@IProductService private readonly productService: IProductService,
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@IDialogService private readonly dialogService: IDialogService,
@IStorageService private readonly storageService: IStorageService
) {
super();
this.registerListeners();
this.create();
}
private registerListeners(): void {
// React to editor input changes
this._register(this.editorService.onDidActiveEditorChange(() => this.updateTouchbarMenu()));
// prevent opening a real URL inside the shell
[DOM.EventType.DRAG_OVER, DOM.EventType.DROP].forEach(event => {
window.document.body.addEventListener(event, (e: DragEvent) => {
DOM.EventHelper.stop(e);
});
});
// Support runAction event
ipcRenderer.on('vscode:runAction', async (event: unknown, request: INativeRunActionInWindowRequest) => {
const args: unknown[] = request.args || [];
// If we run an action from the touchbar, we fill in the currently active resource
// as payload because the touch bar items are context aware depending on the editor
if (request.from === 'touchbar') {
const activeEditor = this.editorService.activeEditor;
if (activeEditor) {
const resource = EditorResourceAccessor.getOriginalUri(activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY });
if (resource) {
args.push(resource);
}
}
} else {
args.push({ from: request.from });
}
try {
await this.commandService.executeCommand(request.id, ...args);
type CommandExecutedClassifcation = {
id: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
from: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
};
this.telemetryService.publicLog2<{ id: String, from: String }, CommandExecutedClassifcation>('commandExecuted', { id: request.id, from: request.from });
} catch (error) {
this.notificationService.error(error);
}
});
// Support runKeybinding event
ipcRenderer.on('vscode:runKeybinding', (event: unknown, request: INativeRunKeybindingInWindowRequest) => {
if (document.activeElement) {
this.keybindingService.dispatchByUserSettingsLabel(request.userSettingsLabel, document.activeElement);
}
});
// Error reporting from main
ipcRenderer.on('vscode:reportError', (event: unknown, error: string) => {
if (error) {
errors.onUnexpectedError(JSON.parse(error));
}
});
// Support openFiles event for existing and new files
ipcRenderer.on('vscode:openFiles', (event: unknown, request: IOpenFileRequest) => this.onOpenFiles(request));
// Support addFolders event if we have a workspace opened
ipcRenderer.on('vscode:addFolders', (event: unknown, request: IAddFoldersRequest) => this.onAddFoldersRequest(request));
// Message support
ipcRenderer.on('vscode:showInfoMessage', (event: unknown, message: string) => {
this.notificationService.info(message);
});
// Display change events
ipcRenderer.on('vscode:displayChanged', () => {
clearAllFontInfos();
});
// Fullscreen Events
ipcRenderer.on('vscode:enterFullScreen', async () => {
await this.lifecycleService.when(LifecyclePhase.Ready);
setFullscreen(true);
});
ipcRenderer.on('vscode:leaveFullScreen', async () => {
await this.lifecycleService.when(LifecyclePhase.Ready);
setFullscreen(false);
});
// Proxy Login Dialog
ipcRenderer.on('vscode:openProxyAuthenticationDialog', async (event: unknown, payload: { authInfo: AuthInfo, username?: string, password?: string, replyChannel: string }) => {
const rememberCredentials = this.storageService.getBoolean(NativeWindow.REMEMBER_PROXY_CREDENTIALS_KEY, StorageScope.GLOBAL);
const result = await this.dialogService.input(Severity.Warning, nls.localize('proxyAuthRequired', "Proxy Authentication Required"),
[
nls.localize({ key: 'loginButton', comment: ['&& denotes a mnemonic'] }, "&&Log In"),
nls.localize({ key: 'cancelButton', comment: ['&& denotes a mnemonic'] }, "&&Cancel")
],
[
{ placeholder: nls.localize('username', "Username"), value: payload.username },
{ placeholder: nls.localize('password', "Password"), type: 'password', value: payload.password }
],
{
cancelId: 1,
detail: nls.localize('proxyDetail', "The proxy {0} requires a username and password.", `${payload.authInfo.host}:${payload.authInfo.port}`),
checkbox: {
label: nls.localize('rememberCredentials', "Remember my credentials"),
checked: rememberCredentials
}
});
// Reply back to the channel without result to indicate
// that the login dialog was cancelled
if (result.choice !== 0 || !result.values) {
ipcRenderer.send(payload.replyChannel);
}
// Other reply back with the picked credentials
else {
// Update state based on checkbox
if (result.checkboxChecked) {
this.storageService.store(NativeWindow.REMEMBER_PROXY_CREDENTIALS_KEY, true, StorageScope.GLOBAL);
} else {
this.storageService.remove(NativeWindow.REMEMBER_PROXY_CREDENTIALS_KEY, StorageScope.GLOBAL);
}
// Reply back to main side with credentials
const [username, password] = result.values;
ipcRenderer.send(payload.replyChannel, { username, password, remember: !!result.checkboxChecked });
}
});
// Accessibility support changed event
ipcRenderer.on('vscode:accessibilitySupportChanged', (event: unknown, accessibilitySupportEnabled: boolean) => {
this.accessibilityService.setAccessibilitySupport(accessibilitySupportEnabled ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled);
});
// Zoom level changes
this.updateWindowZoomLevel();
this._register(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('window.zoomLevel')) {
this.updateWindowZoomLevel();
} else if (e.affectsConfiguration('keyboard.touchbar.enabled') || e.affectsConfiguration('keyboard.touchbar.ignored')) {
this.updateTouchbarMenu();
}
}));
// Listen to visible editor changes
this._register(this.editorService.onDidVisibleEditorsChange(() => this.onDidVisibleEditorsChange()));
// Listen to editor closing (if we run with --wait)
const filesToWait = this.environmentService.configuration.filesToWait;
if (filesToWait) {
this.trackClosedWaitFiles(filesToWait.waitMarkerFileUri, coalesce(filesToWait.paths.map(path => path.fileUri)));
}
// macOS OS integration
if (isMacintosh) {
this._register(this.editorService.onDidActiveEditorChange(() => {
const file = EditorResourceAccessor.getOriginalUri(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: Schemas.file });
// Represented Filename
this.updateRepresentedFilename(file?.fsPath);
// Custom title menu
this.provideCustomTitleContextMenu(file?.fsPath);
}));
}
// Maximize/Restore on doubleclick (for macOS custom title)
if (isMacintosh && getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') {
const titlePart = assertIsDefined(this.layoutService.getContainer(Parts.TITLEBAR_PART));
this._register(DOM.addDisposableListener(titlePart, DOM.EventType.DBLCLICK, e => {
DOM.EventHelper.stop(e);
this.nativeHostService.handleTitleDoubleClick();
}));
}
// Document edited: indicate for dirty working copies
this._register(this.workingCopyService.onDidChangeDirty(workingCopy => {
const gotDirty = workingCopy.isDirty();
if (gotDirty && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled) && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) {
return; // do not indicate dirty of working copies that are auto saved after short delay
}
this.updateDocumentEdited(gotDirty);
}));
this.updateDocumentEdited();
// Detect minimize / maximize
this._register(Event.any(
Event.map(Event.filter(this.nativeHostService.onDidMaximizeWindow, id => id === this.nativeHostService.windowId), () => true),
Event.map(Event.filter(this.nativeHostService.onDidUnmaximizeWindow, id => id === this.nativeHostService.windowId), () => false)
)(e => this.onDidChangeMaximized(e)));
this.onDidChangeMaximized(this.environmentService.configuration.maximized ?? false);
// Detect panel position to determine minimum width
this._register(this.layoutService.onPanelPositionChange(pos => {
this.onDidPanelPositionChange(positionFromString(pos));
}));
this.onDidPanelPositionChange(this.layoutService.getPanelPosition());
}
private updateDocumentEdited(isDirty = this.workingCopyService.hasDirty): void {
if ((!this.isDocumentedEdited && isDirty) || (this.isDocumentedEdited && !isDirty)) {
this.isDocumentedEdited = isDirty;
this.nativeHostService.setDocumentEdited(isDirty);
}
}
private onDidChangeMaximized(maximized: boolean): void {
this.layoutService.updateWindowMaximizedState(maximized);
}
private getWindowMinimumWidth(panelPosition: Position = this.layoutService.getPanelPosition()): number {
// if panel is on the side, then return the larger minwidth
const panelOnSide = panelPosition === Position.LEFT || panelPosition === Position.RIGHT;
if (panelOnSide) {
return WindowMinimumSize.WIDTH_WITH_VERTICAL_PANEL;
}
else {
return WindowMinimumSize.WIDTH;
}
}
private onDidPanelPositionChange(pos: Position): void {
const minWidth = this.getWindowMinimumWidth(pos);
this.nativeHostService.setMinimumSize(minWidth, undefined);
}
private onDidVisibleEditorsChange(): void {
// Close when empty: check if we should close the window based on the setting
// Overruled by: window has a workspace opened or this window is for extension development
// or setting is disabled. Also enabled when running with --wait from the command line.
const visibleEditorPanes = this.editorService.visibleEditorPanes;
if (visibleEditorPanes.length === 0 && this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && !this.environmentService.isExtensionDevelopment) {
const closeWhenEmpty = this.configurationService.getValue<boolean>('window.closeWhenEmpty');
if (closeWhenEmpty || this.environmentService.args.wait) {
this.closeEmptyWindowScheduler.schedule();
}
}
}
private onAllEditorsClosed(): void {
const visibleEditorPanes = this.editorService.visibleEditorPanes.length;
if (visibleEditorPanes === 0) {
this.nativeHostService.closeWindow();
}
}
private updateWindowZoomLevel(): void {
const windowConfig = this.configurationService.getValue<IWindowsConfiguration>();
let configuredZoomLevel = 0;
if (windowConfig.window && typeof windowConfig.window.zoomLevel === 'number') {
configuredZoomLevel = windowConfig.window.zoomLevel;
// Leave early if the configured zoom level did not change (https://github.com/microsoft/vscode/issues/1536)
if (this.previousConfiguredZoomLevel === configuredZoomLevel) {
return;
}
this.previousConfiguredZoomLevel = configuredZoomLevel;
}
if (getZoomLevel() !== configuredZoomLevel) {
applyZoom(configuredZoomLevel);
}
}
private updateRepresentedFilename(filePath: string | undefined): void {
this.nativeHostService.setRepresentedFilename(filePath ? filePath : '');
}
private provideCustomTitleContextMenu(filePath: string | undefined): void {
// Clear old menu
this.customTitleContextMenuDisposable.clear();
// Provide new menu if a file is opened and we are on a custom title
if (!filePath || getTitleBarStyle(this.configurationService, this.environmentService) !== 'custom') {
return;
}
// Split up filepath into segments
const segments = filePath.split(posix.sep);
for (let i = segments.length; i > 0; i--) {
const isFile = (i === segments.length);
let pathOffset = i;
if (!isFile) {
pathOffset++; // for segments which are not the file name we want to open the folder
}
const path = segments.slice(0, pathOffset).join(posix.sep);
let label: string;
if (!isFile) {
label = getBaseLabel(dirname(path));
} else {
label = getBaseLabel(path);
}
const commandId = `workbench.action.revealPathInFinder${i}`;
this.customTitleContextMenuDisposable.add(CommandsRegistry.registerCommand(commandId, () => this.nativeHostService.showItemInFolder(path)));
this.customTitleContextMenuDisposable.add(MenuRegistry.appendMenuItem(MenuId.TitleBarContext, { command: { id: commandId, title: label || posix.sep }, order: -i }));
}
}
private create(): void {
// Native menu controller
if (isMacintosh || getTitleBarStyle(this.configurationService, this.environmentService) === 'native') {
this._register(this.instantiationService.createInstance(NativeMenubarControl));
}
// Handle open calls
this.setupOpenHandlers();
// Notify main side when window ready
this.lifecycleService.when(LifecyclePhase.Ready).then(() => this.nativeHostService.notifyReady());
// Integrity warning
this.integrityService.isPure().then(res => this.titleService.updateProperties({ isPure: res.isPure }));
// Root warning
this.lifecycleService.when(LifecyclePhase.Restored).then(async () => {
const isAdmin = await this.nativeHostService.isAdmin();
// Update title
this.titleService.updateProperties({ isAdmin });
// Show warning message (unix only)
if (isAdmin && !isWindows) {
this.notificationService.warn(nls.localize('runningAsRoot', "It is not recommended to run {0} as root user.", this.productService.nameShort));
}
});
// Touchbar menu (if enabled)
this.updateTouchbarMenu();
}
private setupOpenHandlers(): void {
// Block window.open() calls
window.open = function (): Window | null {
throw new Error('Prevented call to window.open(). Use IOpenerService instead!');
};
// Handle external open() calls
this.openerService.setExternalOpener({
openExternal: async (href: string) => {
const success = await this.nativeHostService.openExternal(href);
if (!success) {
const fileCandidate = URI.parse(href);
if (fileCandidate.scheme === Schemas.file) {
// if opening failed, and this is a file, we can still try to reveal it
await this.nativeHostService.showItemInFolder(fileCandidate.fsPath);
}
}
return true;
}
});
// Register external URI resolver
this.openerService.registerExternalUriResolver({
resolveExternalUri: async (uri: URI, options?: OpenOptions) => {
if (options?.allowTunneling) {
const portMappingRequest = extractLocalHostUriMetaDataForPortMapping(uri);
if (portMappingRequest) {
const remoteAuthority = this.environmentService.remoteAuthority;
const addressProvider: IAddressProvider | undefined = remoteAuthority ? {
getAddress: async (): Promise<IAddress> => {
return (await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority)).authority;
}
} : undefined;
const tunnel = await this.tunnelService.openTunnel(addressProvider, portMappingRequest.address, portMappingRequest.port);
if (tunnel) {
return {
resolved: uri.with({ authority: tunnel.localAddress }),
dispose: () => tunnel.dispose(),
};
}
}
}
return undefined;
}
});
}
private updateTouchbarMenu(): void {
if (!isMacintosh) {
return; // macOS only
}
// Dispose old
this.touchBarDisposables.clear();
this.touchBarMenu = undefined;
// Create new (delayed)
const scheduler: RunOnceScheduler = this.touchBarDisposables.add(new RunOnceScheduler(() => this.doUpdateTouchbarMenu(scheduler), 300));
scheduler.schedule();
}
private doUpdateTouchbarMenu(scheduler: RunOnceScheduler): void {
if (!this.touchBarMenu) {
const scopedContextKeyService = this.editorService.activeEditorPane?.scopedContextKeyService || this.editorGroupService.activeGroup.scopedContextKeyService;
this.touchBarMenu = this.menuService.createMenu(MenuId.TouchBarContext, scopedContextKeyService);
this.touchBarDisposables.add(this.touchBarMenu);
this.touchBarDisposables.add(this.touchBarMenu.onDidChange(() => scheduler.schedule()));
}
const actions: Array<MenuItemAction | Separator> = [];
const disabled = this.configurationService.getValue<boolean>('keyboard.touchbar.enabled') === false;
const ignoredItems = this.configurationService.getValue<string[]>('keyboard.touchbar.ignored') || [];
// Fill actions into groups respecting order
this.touchBarDisposables.add(createAndFillInActionBarActions(this.touchBarMenu, undefined, actions));
// Convert into command action multi array
const items: ICommandAction[][] = [];
let group: ICommandAction[] = [];
if (!disabled) {
for (const action of actions) {
// Command
if (action instanceof MenuItemAction) {
if (ignoredItems.indexOf(action.item.id) >= 0) {
continue; // ignored
}
group.push(action.item);
}
// Separator
else if (action instanceof Separator) {
if (group.length) {
items.push(group);
}
group = [];
}
}
if (group.length) {
items.push(group);
}
}
// Only update if the actions have changed
if (!equals(this.lastInstalledTouchedBar, items)) {
this.lastInstalledTouchedBar = items;
this.nativeHostService.updateTouchBar(items);
}
}
private onAddFoldersRequest(request: IAddFoldersRequest): void {
// Buffer all pending requests
this.pendingFoldersToAdd.push(...request.foldersToAdd.map(folder => URI.revive(folder)));
// Delay the adding of folders a bit to buffer in case more requests are coming
if (!this.addFoldersScheduler.isScheduled()) {
this.addFoldersScheduler.schedule();
}
}
private doAddFolders(): void {
const foldersToAdd: IWorkspaceFolderCreationData[] = [];
this.pendingFoldersToAdd.forEach(folder => {
foldersToAdd.push(({ uri: folder }));
});
this.pendingFoldersToAdd = [];
this.workspaceEditingService.addFolders(foldersToAdd);
}
private async onOpenFiles(request: INativeOpenFileRequest): Promise<void> {
const inputs: IResourceEditorInputType[] = [];
const diffMode = !!(request.filesToDiff && (request.filesToDiff.length === 2));
if (!diffMode && request.filesToOpenOrCreate) {
inputs.push(...(await pathsToEditors(request.filesToOpenOrCreate, this.fileService)));
}
if (diffMode && request.filesToDiff) {
inputs.push(...(await pathsToEditors(request.filesToDiff, this.fileService)));
}
if (inputs.length) {
this.openResources(inputs, diffMode);
}
if (request.filesToWait && inputs.length) {
// In wait mode, listen to changes to the editors and wait until the files
// are closed that the user wants to wait for. When this happens we delete
// the wait marker file to signal to the outside that editing is done.
this.trackClosedWaitFiles(URI.revive(request.filesToWait.waitMarkerFileUri), coalesce(request.filesToWait.paths.map(p => URI.revive(p.fileUri))));
}
}
private async trackClosedWaitFiles(waitMarkerFile: URI, resourcesToWaitFor: URI[]): Promise<void> {
// Wait for the resources to be closed in the editor...
await this.editorService.whenClosed(resourcesToWaitFor.map(resource => ({ resource })), { waitForSaved: true });
// ...before deleting the wait marker file
await this.fileService.del(waitMarkerFile);
}
private async openResources(resources: Array<IResourceEditorInput | IUntitledTextResourceEditorInput>, diffMode: boolean): Promise<unknown> {
await this.lifecycleService.when(LifecyclePhase.Ready);
// In diffMode we open 2 resources as diff
if (diffMode && resources.length === 2 && resources[0].resource && resources[1].resource) {
return this.editorService.openEditor({ leftResource: resources[0].resource, rightResource: resources[1].resource, options: { pinned: true } });
}
// For one file, just put it into the current active editor
if (resources.length === 1) {
return this.editorService.openEditor(resources[0]);
}
// Otherwise open all
return this.editorService.openEditors(resources);
}
}
class NativeMenubarControl extends MenubarControl {
constructor(
@IMenuService menuService: IMenuService,
@IWorkspacesService workspacesService: IWorkspacesService,
@IContextKeyService contextKeyService: IContextKeyService,
@IKeybindingService keybindingService: IKeybindingService,
@IConfigurationService configurationService: IConfigurationService,
@ILabelService labelService: ILabelService,
@IUpdateService updateService: IUpdateService,
@IStorageService storageService: IStorageService,
@INotificationService notificationService: INotificationService,
@IPreferencesService preferencesService: IPreferencesService,
@INativeWorkbenchEnvironmentService protected readonly environmentService: INativeWorkbenchEnvironmentService,
@IAccessibilityService accessibilityService: IAccessibilityService,
@IMenubarService private readonly menubarService: IMenubarService,
@IHostService hostService: IHostService,
@INativeHostService private readonly nativeHostService: INativeHostService
) {
super(
menuService,
workspacesService,
contextKeyService,
keybindingService,
configurationService,
labelService,
updateService,
storageService,
notificationService,
preferencesService,
environmentService,
accessibilityService,
hostService
);
if (isMacintosh) {
this.menus['Preferences'] = this._register(this.menuService.createMenu(MenuId.MenubarPreferencesMenu, this.contextKeyService));
this.topLevelTitles['Preferences'] = nls.localize('mPreferences', "Preferences");
}
for (const topLevelMenuName of Object.keys(this.topLevelTitles)) {
const menu = this.menus[topLevelMenuName];
if (menu) {
this._register(menu.onDidChange(() => this.updateMenubar()));
}
}
(async () => {
this.recentlyOpened = await this.workspacesService.getRecentlyOpened();
this.doUpdateMenubar();
})();
this.registerListeners();
}
protected doUpdateMenubar(): void {
// Since the native menubar is shared between windows (main process)
// only allow the focused window to update the menubar
if (!this.hostService.hasFocus) {
return;
}
// Send menus to main process to be rendered by Electron
const menubarData = { menus: {}, keybindings: {} };
if (this.getMenubarMenus(menubarData)) {
this.menubarService.updateMenubar(this.nativeHostService.windowId, menubarData);
}
}
private getMenubarMenus(menubarData: IMenubarData): boolean {
if (!menubarData) {
return false;
}
menubarData.keybindings = this.getAdditionalKeybindings();
for (const topLevelMenuName of Object.keys(this.topLevelTitles)) {
const menu = this.menus[topLevelMenuName];
if (menu) {
const menubarMenu: IMenubarMenu = { items: [] };
this.populateMenuItems(menu, menubarMenu, menubarData.keybindings);
if (menubarMenu.items.length === 0) {
return false; // Menus are incomplete
}
menubarData.menus[topLevelMenuName] = menubarMenu;
}
}
return true;
}
private populateMenuItems(menu: IMenu, menuToPopulate: IMenubarMenu, keybindings: { [id: string]: IMenubarKeybinding | undefined }) {
let groups = menu.getActions();
for (let group of groups) {
const [, actions] = group;
actions.forEach(menuItem => {
if (menuItem instanceof SubmenuItemAction) {
const submenu = { items: [] };
if (!this.menus[menuItem.item.submenu.id]) {
const menu = this.menus[menuItem.item.submenu.id] = this.menuService.createMenu(menuItem.item.submenu, this.contextKeyService);
this._register(menu.onDidChange(() => this.updateMenubar()));
}
const menuToDispose = this.menuService.createMenu(menuItem.item.submenu, this.contextKeyService);
this.populateMenuItems(menuToDispose, submenu, keybindings);
let menubarSubmenuItem: IMenubarMenuItemSubmenu = {
id: menuItem.id,
label: menuItem.label,
submenu: submenu
};
menuToPopulate.items.push(menubarSubmenuItem);
menuToDispose.dispose();
} else {
if (menuItem.id === 'workbench.action.openRecent') {
const actions = this.getOpenRecentActions().map(this.transformOpenRecentAction);
menuToPopulate.items.push(...actions);
}
let menubarMenuItem: IMenubarMenuItemAction = {
id: menuItem.id,
label: menuItem.label
};
if (menuItem.checked) {
menubarMenuItem.checked = true;
}
if (!menuItem.enabled) {
menubarMenuItem.enabled = false;
}
menubarMenuItem.label = this.calculateActionLabel(menubarMenuItem);
keybindings[menuItem.id] = this.getMenubarKeybinding(menuItem.id);
menuToPopulate.items.push(menubarMenuItem);
}
});
menuToPopulate.items.push({ id: 'vscode.menubar.separator' });
}
if (menuToPopulate.items.length > 0) {
menuToPopulate.items.pop();
}
}
private transformOpenRecentAction(action: Separator | (IAction & { uri: URI })): MenubarMenuItem {
if (action instanceof Separator) {
return { id: 'vscode.menubar.separator' };
}
return {
id: action.id,
uri: action.uri,
enabled: action.enabled,
label: action.label
};
}
private getAdditionalKeybindings(): { [id: string]: IMenubarKeybinding } {
const keybindings: { [id: string]: IMenubarKeybinding } = {};
if (isMacintosh) {
const keybinding = this.getMenubarKeybinding('workbench.action.quit');
if (keybinding) {
keybindings['workbench.action.quit'] = keybinding;
}
}
return keybindings;
}
private getMenubarKeybinding(id: string): IMenubarKeybinding | undefined {
const binding = this.keybindingService.lookupKeybinding(id);
if (!binding) {
return undefined;
}
// first try to resolve a native accelerator
const electronAccelerator = binding.getElectronAccelerator();
if (electronAccelerator) {
return { label: electronAccelerator, userSettingsLabel: withNullAsUndefined(binding.getUserSettingsLabel()) };
}
// we need this fallback to support keybindings that cannot show in electron menus (e.g. chords)
const acceleratorLabel = binding.getLabel();
if (acceleratorLabel) {
return { label: acceleratorLabel, isNative: false, userSettingsLabel: withNullAsUndefined(binding.getUserSettingsLabel()) };
}
return undefined;
}
}