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,337 @@
/*---------------------------------------------------------------------------------------------
* 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 * as nls from 'vs/nls';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { domEvent } from 'vs/base/browser/event';
import { Color } from 'vs/base/common/color';
import { Event } from 'vs/base/common/event';
import { IDisposable, toDisposable, dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { getDomNodePagePosition, createStyleSheet, createCSSRule, append, $ } from 'vs/base/browser/dom';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { Context } from 'vs/platform/contextkey/browser/contextKeyService';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { timeout } from 'vs/base/common/async';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { Registry } from 'vs/platform/registry/common/platform';
import { registerAction2, Action2 } from 'vs/platform/actions/common/actions';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { clamp } from 'vs/base/common/numbers';
import { KeyCode } from 'vs/base/common/keyCodes';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { ILogService } from 'vs/platform/log/common/log';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { CATEGORIES } from 'vs/workbench/common/actions';
class InspectContextKeysAction extends Action2 {
constructor() {
super({
id: 'workbench.action.inspectContextKeys',
title: { value: nls.localize('inspect context keys', "Inspect Context Keys"), original: 'Inspect Context Keys' },
category: CATEGORIES.Developer,
f1: true
});
}
run(accessor: ServicesAccessor): void {
const contextKeyService = accessor.get(IContextKeyService);
const disposables = new DisposableStore();
const stylesheet = createStyleSheet();
disposables.add(toDisposable(() => {
if (stylesheet.parentNode) {
stylesheet.parentNode.removeChild(stylesheet);
}
}));
createCSSRule('*', 'cursor: crosshair !important;', stylesheet);
const hoverFeedback = document.createElement('div');
document.body.appendChild(hoverFeedback);
disposables.add(toDisposable(() => document.body.removeChild(hoverFeedback)));
hoverFeedback.style.position = 'absolute';
hoverFeedback.style.pointerEvents = 'none';
hoverFeedback.style.backgroundColor = 'rgba(255, 0, 0, 0.5)';
hoverFeedback.style.zIndex = '1000';
const onMouseMove = domEvent(document.body, 'mousemove', true);
disposables.add(onMouseMove(e => {
const target = e.target as HTMLElement;
const position = getDomNodePagePosition(target);
hoverFeedback.style.top = `${position.top}px`;
hoverFeedback.style.left = `${position.left}px`;
hoverFeedback.style.width = `${position.width}px`;
hoverFeedback.style.height = `${position.height}px`;
}));
const onMouseDown = Event.once(domEvent(document.body, 'mousedown', true));
onMouseDown(e => { e.preventDefault(); e.stopPropagation(); }, null, disposables);
const onMouseUp = Event.once(domEvent(document.body, 'mouseup', true));
onMouseUp(e => {
e.preventDefault();
e.stopPropagation();
const context = contextKeyService.getContext(e.target as HTMLElement) as Context;
console.log(context.collectAllValues());
dispose(disposables);
}, null, disposables);
}
}
class ToggleScreencastModeAction extends Action2 {
static disposable: IDisposable | undefined;
constructor() {
super({
id: 'workbench.action.toggleScreencastMode',
title: { value: nls.localize('toggle screencast mode', "Toggle Screencast Mode"), original: 'Toggle Screencast Mode' },
category: CATEGORIES.Developer,
f1: true
});
}
run(accessor: ServicesAccessor): void {
if (ToggleScreencastModeAction.disposable) {
ToggleScreencastModeAction.disposable.dispose();
ToggleScreencastModeAction.disposable = undefined;
return;
}
const layoutService = accessor.get(ILayoutService);
const configurationService = accessor.get(IConfigurationService);
const keybindingService = accessor.get(IKeybindingService);
const disposables = new DisposableStore();
const container = layoutService.container;
const mouseMarker = append(container, $('.screencast-mouse'));
disposables.add(toDisposable(() => mouseMarker.remove()));
const onMouseDown = domEvent(container, 'mousedown', true);
const onMouseUp = domEvent(container, 'mouseup', true);
const onMouseMove = domEvent(container, 'mousemove', true);
const updateMouseIndicatorColor = () => {
mouseMarker.style.borderColor = Color.fromHex(configurationService.getValue<string>('screencastMode.mouseIndicatorColor')).toString();
};
let mouseIndicatorSize: number;
const updateMouseIndicatorSize = () => {
mouseIndicatorSize = clamp(configurationService.getValue<number>('screencastMode.mouseIndicatorSize') || 20, 20, 100);
mouseMarker.style.height = `${mouseIndicatorSize}px`;
mouseMarker.style.width = `${mouseIndicatorSize}px`;
};
updateMouseIndicatorColor();
updateMouseIndicatorSize();
disposables.add(onMouseDown(e => {
mouseMarker.style.top = `${e.clientY - mouseIndicatorSize / 2}px`;
mouseMarker.style.left = `${e.clientX - mouseIndicatorSize / 2}px`;
mouseMarker.style.display = 'block';
const mouseMoveListener = onMouseMove(e => {
mouseMarker.style.top = `${e.clientY - mouseIndicatorSize / 2}px`;
mouseMarker.style.left = `${e.clientX - mouseIndicatorSize / 2}px`;
});
Event.once(onMouseUp)(() => {
mouseMarker.style.display = 'none';
mouseMoveListener.dispose();
});
}));
const keyboardMarker = append(container, $('.screencast-keyboard'));
disposables.add(toDisposable(() => keyboardMarker.remove()));
const updateKeyboardFontSize = () => {
keyboardMarker.style.fontSize = `${clamp(configurationService.getValue<number>('screencastMode.fontSize') || 56, 20, 100)}px`;
};
const updateKeyboardMarker = () => {
keyboardMarker.style.bottom = `${clamp(configurationService.getValue<number>('screencastMode.verticalOffset') || 0, 0, 90)}%`;
};
let keyboardMarkerTimeout: number;
const updateKeyboardMarkerTimeout = () => {
keyboardMarkerTimeout = clamp(configurationService.getValue<number>('screencastMode.keyboardOverlayTimeout') || 800, 500, 5000);
};
updateKeyboardFontSize();
updateKeyboardMarker();
updateKeyboardMarkerTimeout();
disposables.add(configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('screencastMode.verticalOffset')) {
updateKeyboardMarker();
}
if (e.affectsConfiguration('screencastMode.fontSize')) {
updateKeyboardFontSize();
}
if (e.affectsConfiguration('screencastMode.keyboardOverlayTimeout')) {
updateKeyboardMarkerTimeout();
}
if (e.affectsConfiguration('screencastMode.mouseIndicatorColor')) {
updateMouseIndicatorColor();
}
if (e.affectsConfiguration('screencastMode.mouseIndicatorSize')) {
updateMouseIndicatorSize();
}
}));
const onKeyDown = domEvent(window, 'keydown', true);
let keyboardTimeout: IDisposable = Disposable.None;
let length = 0;
disposables.add(onKeyDown(e => {
keyboardTimeout.dispose();
const event = new StandardKeyboardEvent(e);
const shortcut = keybindingService.softDispatch(event, event.target);
if (shortcut || !configurationService.getValue<boolean>('screencastMode.onlyKeyboardShortcuts')) {
if (
event.ctrlKey || event.altKey || event.metaKey || event.shiftKey
|| length > 20
|| event.keyCode === KeyCode.Backspace || event.keyCode === KeyCode.Escape
) {
keyboardMarker.innerText = '';
length = 0;
}
const keybinding = keybindingService.resolveKeyboardEvent(event);
const label = keybinding.getLabel();
const key = $('span.key', {}, label || '');
length++;
append(keyboardMarker, key);
}
const promise = timeout(keyboardMarkerTimeout);
keyboardTimeout = toDisposable(() => promise.cancel());
promise.then(() => {
keyboardMarker.textContent = '';
length = 0;
});
}));
ToggleScreencastModeAction.disposable = disposables;
}
}
class LogStorageAction extends Action2 {
constructor() {
super({
id: 'workbench.action.logStorage',
title: { value: nls.localize({ key: 'logStorage', comment: ['A developer only action to log the contents of the storage for the current window.'] }, "Log Storage Database Contents"), original: 'Log Storage Database Contents' },
category: CATEGORIES.Developer,
f1: true
});
}
run(accessor: ServicesAccessor): void {
accessor.get(IStorageService).logStorage();
}
}
class LogWorkingCopiesAction extends Action2 {
constructor() {
super({
id: 'workbench.action.logWorkingCopies',
title: { value: nls.localize({ key: 'logWorkingCopies', comment: ['A developer only action to log the working copies that exist.'] }, "Log Working Copies"), original: 'Log Working Copies' },
category: CATEGORIES.Developer,
f1: true
});
}
run(accessor: ServicesAccessor): void {
const workingCopyService = accessor.get(IWorkingCopyService);
const logService = accessor.get(ILogService);
const msg = [
`Dirty Working Copies:`,
...workingCopyService.dirtyWorkingCopies.map(workingCopy => workingCopy.resource.toString(true)),
``,
`All Working Copies:`,
...workingCopyService.workingCopies.map(workingCopy => workingCopy.resource.toString(true)),
];
logService.info(msg.join('\n'));
}
}
// --- Actions Registration
registerAction2(InspectContextKeysAction);
registerAction2(ToggleScreencastModeAction);
registerAction2(LogStorageAction);
registerAction2(LogWorkingCopiesAction);
// --- Configuration
// Screen Cast Mode
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
id: 'screencastMode',
order: 9,
title: nls.localize('screencastModeConfigurationTitle', "Screencast Mode"),
type: 'object',
properties: {
'screencastMode.verticalOffset': {
type: 'number',
default: 20,
minimum: 0,
maximum: 90,
description: nls.localize('screencastMode.location.verticalPosition', "Controls the vertical offset of the screencast mode overlay from the bottom as a percentage of the workbench height.")
},
'screencastMode.fontSize': {
type: 'number',
default: 56,
minimum: 20,
maximum: 100,
description: nls.localize('screencastMode.fontSize', "Controls the font size (in pixels) of the screencast mode keyboard.")
},
'screencastMode.onlyKeyboardShortcuts': {
type: 'boolean',
description: nls.localize('screencastMode.onlyKeyboardShortcuts', "Only show keyboard shortcuts in screencast mode."),
default: false
},
'screencastMode.keyboardOverlayTimeout': {
type: 'number',
default: 800,
minimum: 500,
maximum: 5000,
description: nls.localize('screencastMode.keyboardOverlayTimeout', "Controls how long (in milliseconds) the keyboard overlay is shown in screencast mode.")
},
'screencastMode.mouseIndicatorColor': {
type: 'string',
format: 'color-hex',
default: '#FF0000',
description: nls.localize('screencastMode.mouseIndicatorColor', "Controls the color in hex (#RGB, #RGBA, #RRGGBB or #RRGGBBAA) of the mouse indicator in screencast mode.")
},
'screencastMode.mouseIndicatorSize': {
type: 'number',
default: 20,
minimum: 20,
maximum: 100,
description: nls.localize('screencastMode.mouseIndicatorSize', "Controls the size (in pixels) of the mouse indicator in screencast mode.")
},
}
});

View File

@@ -0,0 +1,383 @@
/*---------------------------------------------------------------------------------------------
* 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 product from 'vs/platform/product/common/product';
import { isMacintosh, isLinux, language } from 'vs/base/common/platform';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { URI } from 'vs/base/common/uri';
import { MenuId, Action2, registerAction2, MenuRegistry } from 'vs/platform/actions/common/actions';
import { KeyChord, KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { IProductService } from 'vs/platform/product/common/productService';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { CATEGORIES } from 'vs/workbench/common/actions';
class KeybindingsReferenceAction extends Action2 {
static readonly ID = 'workbench.action.keybindingsReference';
static readonly AVAILABLE = !!(isLinux ? product.keyboardShortcutsUrlLinux : isMacintosh ? product.keyboardShortcutsUrlMac : product.keyboardShortcutsUrlWin);
constructor() {
super({
id: KeybindingsReferenceAction.ID,
title: { value: nls.localize('keybindingsReference', "Keyboard Shortcuts Reference"), original: 'Keyboard Shortcuts Reference' },
category: CATEGORIES.Help,
f1: true,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
when: null,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_R)
}
});
}
run(accessor: ServicesAccessor): void {
const productService = accessor.get(IProductService);
const openerService = accessor.get(IOpenerService);
const url = isLinux ? productService.keyboardShortcutsUrlLinux : isMacintosh ? productService.keyboardShortcutsUrlMac : productService.keyboardShortcutsUrlWin;
if (url) {
openerService.open(URI.parse(url));
}
}
}
class OpenDocumentationUrlAction extends Action2 {
static readonly ID = 'workbench.action.openDocumentationUrl';
static readonly AVAILABLE = !!product.documentationUrl;
constructor() {
super({
id: OpenDocumentationUrlAction.ID,
title: { value: nls.localize('openDocumentationUrl', "Documentation"), original: 'Documentation' },
category: CATEGORIES.Help,
f1: true
});
}
run(accessor: ServicesAccessor): void {
const productService = accessor.get(IProductService);
const openerService = accessor.get(IOpenerService);
if (productService.documentationUrl) {
openerService.open(URI.parse(productService.documentationUrl));
}
}
}
class OpenIntroductoryVideosUrlAction extends Action2 {
static readonly ID = 'workbench.action.openIntroductoryVideosUrl';
static readonly AVAILABLE = !!product.introductoryVideosUrl;
constructor() {
super({
id: OpenIntroductoryVideosUrlAction.ID,
title: { value: nls.localize('openIntroductoryVideosUrl', "Introductory Videos"), original: 'Introductory Videos' },
category: CATEGORIES.Help,
f1: true
});
}
run(accessor: ServicesAccessor): void {
const productService = accessor.get(IProductService);
const openerService = accessor.get(IOpenerService);
if (productService.introductoryVideosUrl) {
openerService.open(URI.parse(productService.introductoryVideosUrl));
}
}
}
class OpenTipsAndTricksUrlAction extends Action2 {
static readonly ID = 'workbench.action.openTipsAndTricksUrl';
static readonly AVAILABLE = !!product.tipsAndTricksUrl;
constructor() {
super({
id: OpenTipsAndTricksUrlAction.ID,
title: { value: nls.localize('openTipsAndTricksUrl', "Tips and Tricks"), original: 'Tips and Tricks' },
category: CATEGORIES.Help,
f1: true
});
}
run(accessor: ServicesAccessor): void {
const productService = accessor.get(IProductService);
const openerService = accessor.get(IOpenerService);
if (productService.tipsAndTricksUrl) {
openerService.open(URI.parse(productService.tipsAndTricksUrl));
}
}
}
class OpenNewsletterSignupUrlAction extends Action2 {
static readonly ID = 'workbench.action.openNewsletterSignupUrl';
static readonly AVAILABLE = !!product.newsletterSignupUrl;
constructor() {
super({
id: OpenNewsletterSignupUrlAction.ID,
title: { value: nls.localize('newsletterSignup', "Signup for the VS Code Newsletter"), original: 'Signup for the VS Code Newsletter' },
category: CATEGORIES.Help,
f1: true
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const productService = accessor.get(IProductService);
const openerService = accessor.get(IOpenerService);
const telemetryService = accessor.get(ITelemetryService);
const info = await telemetryService.getTelemetryInfo();
openerService.open(URI.parse(`${productService.newsletterSignupUrl}?machineId=${encodeURIComponent(info.machineId)}`));
}
}
class OpenTwitterUrlAction extends Action2 {
static readonly ID = 'workbench.action.openTwitterUrl';
static readonly AVAILABLE = !!product.twitterUrl;
constructor() {
super({
id: OpenTwitterUrlAction.ID,
title: { value: nls.localize('openTwitterUrl', "Join Us on Twitter"), original: 'Join Us on Twitter' },
category: CATEGORIES.Help,
f1: true
});
}
run(accessor: ServicesAccessor): void {
const productService = accessor.get(IProductService);
const openerService = accessor.get(IOpenerService);
if (productService.twitterUrl) {
openerService.open(URI.parse(productService.twitterUrl));
}
}
}
class OpenRequestFeatureUrlAction extends Action2 {
static readonly ID = 'workbench.action.openRequestFeatureUrl';
static readonly AVAILABLE = !!product.requestFeatureUrl;
constructor() {
super({
id: OpenRequestFeatureUrlAction.ID,
title: { value: nls.localize('openUserVoiceUrl', "Search Feature Requests"), original: 'Search Feature Requests' },
category: CATEGORIES.Help,
f1: true
});
}
run(accessor: ServicesAccessor): void {
const productService = accessor.get(IProductService);
const openerService = accessor.get(IOpenerService);
if (productService.requestFeatureUrl) {
openerService.open(URI.parse(productService.requestFeatureUrl));
}
}
}
class OpenLicenseUrlAction extends Action2 {
static readonly ID = 'workbench.action.openLicenseUrl';
static readonly AVAILABLE = !!product.licenseUrl;
constructor() {
super({
id: OpenLicenseUrlAction.ID,
title: { value: nls.localize('openLicenseUrl', "View License"), original: 'View License' },
category: CATEGORIES.Help,
f1: true
});
}
run(accessor: ServicesAccessor): void {
const productService = accessor.get(IProductService);
const openerService = accessor.get(IOpenerService);
if (productService.licenseUrl) {
if (language) {
const queryArgChar = productService.licenseUrl.indexOf('?') > 0 ? '&' : '?';
openerService.open(URI.parse(`${productService.licenseUrl}${queryArgChar}lang=${language}`));
} else {
openerService.open(URI.parse(productService.licenseUrl));
}
}
}
}
class OpenPrivacyStatementUrlAction extends Action2 {
static readonly ID = 'workbench.action.openPrivacyStatementUrl';
static readonly AVAILABE = !!product.privacyStatementUrl;
constructor() {
super({
id: OpenPrivacyStatementUrlAction.ID,
title: { value: nls.localize('openPrivacyStatement', "Privacy Statement"), original: 'Privacy Statement' },
category: CATEGORIES.Help,
f1: true
});
}
run(accessor: ServicesAccessor): void {
const productService = accessor.get(IProductService);
const openerService = accessor.get(IOpenerService);
if (productService.privacyStatementUrl) {
if (language) {
const queryArgChar = productService.privacyStatementUrl.indexOf('?') > 0 ? '&' : '?';
openerService.open(URI.parse(`${productService.privacyStatementUrl}${queryArgChar}lang=${language}`));
} else {
openerService.open(URI.parse(productService.privacyStatementUrl));
}
}
}
}
// --- Actions Registration
if (KeybindingsReferenceAction.AVAILABLE) {
registerAction2(KeybindingsReferenceAction);
}
if (OpenDocumentationUrlAction.AVAILABLE) {
registerAction2(OpenDocumentationUrlAction);
}
if (OpenIntroductoryVideosUrlAction.AVAILABLE) {
registerAction2(OpenIntroductoryVideosUrlAction);
}
if (OpenTipsAndTricksUrlAction.AVAILABLE) {
registerAction2(OpenTipsAndTricksUrlAction);
}
if (OpenNewsletterSignupUrlAction.AVAILABLE) {
registerAction2(OpenNewsletterSignupUrlAction);
}
if (OpenTwitterUrlAction.AVAILABLE) {
registerAction2(OpenTwitterUrlAction);
}
if (OpenRequestFeatureUrlAction.AVAILABLE) {
registerAction2(OpenRequestFeatureUrlAction);
}
if (OpenLicenseUrlAction.AVAILABLE) {
registerAction2(OpenLicenseUrlAction);
}
if (OpenPrivacyStatementUrlAction.AVAILABE) {
registerAction2(OpenPrivacyStatementUrlAction);
}
// --- Menu Registration
// Help
if (OpenDocumentationUrlAction.AVAILABLE) {
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '1_welcome',
command: {
id: OpenDocumentationUrlAction.ID,
title: nls.localize({ key: 'miDocumentation', comment: ['&& denotes a mnemonic'] }, "&&Documentation")
},
order: 3
});
}
// Reference
if (KeybindingsReferenceAction.AVAILABLE) {
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '2_reference',
command: {
id: KeybindingsReferenceAction.ID,
title: nls.localize({ key: 'miKeyboardShortcuts', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts Reference")
},
order: 1
});
}
if (OpenIntroductoryVideosUrlAction.AVAILABLE) {
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '2_reference',
command: {
id: OpenIntroductoryVideosUrlAction.ID,
title: nls.localize({ key: 'miIntroductoryVideos', comment: ['&& denotes a mnemonic'] }, "Introductory &&Videos")
},
order: 2
});
}
if (OpenTipsAndTricksUrlAction.AVAILABLE) {
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '2_reference',
command: {
id: OpenTipsAndTricksUrlAction.ID,
title: nls.localize({ key: 'miTipsAndTricks', comment: ['&& denotes a mnemonic'] }, "Tips and Tri&&cks")
},
order: 3
});
}
// Feedback
if (OpenTwitterUrlAction.AVAILABLE) {
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '3_feedback',
command: {
id: OpenTwitterUrlAction.ID,
title: nls.localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join Us on Twitter")
},
order: 1
});
}
if (OpenRequestFeatureUrlAction.AVAILABLE) {
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '3_feedback',
command: {
id: OpenRequestFeatureUrlAction.ID,
title: nls.localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Search Feature Requests")
},
order: 2
});
}
// Legal
if (OpenLicenseUrlAction.AVAILABLE) {
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '4_legal',
command: {
id: OpenLicenseUrlAction.ID,
title: nls.localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "View &&License")
},
order: 1
});
}
if (OpenPrivacyStatementUrlAction.AVAILABE) {
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '4_legal',
command: {
id: OpenPrivacyStatementUrlAction.ID,
title: nls.localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "Privac&&y Statement")
},
order: 2
});
}

View File

@@ -0,0 +1,922 @@
/*---------------------------------------------------------------------------------------------
* 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 { Registry } from 'vs/platform/registry/common/platform';
import { Action } from 'vs/base/common/actions';
import { SyncActionDescriptor, MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions, CATEGORIES } from 'vs/workbench/common/actions';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService';
import { IEditorGroupsService, GroupOrientation } from 'vs/workbench/services/editor/common/editorGroupsService';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { getMenuBarVisibility } from 'vs/platform/windows/common/windows';
import { isWindows, isLinux, isWeb } from 'vs/base/common/platform';
import { IsMacNativeContext } from 'vs/platform/contextkey/common/contextkeys';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { InEditorZenModeContext, IsCenteredLayoutContext, EditorAreaVisibleContext } from 'vs/workbench/common/editor';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { SideBarVisibleContext } from 'vs/workbench/common/viewlet';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IViewDescriptorService, IViewsService, FocusedViewContext, ViewContainerLocation, IViewDescriptor } from 'vs/workbench/common/views';
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { Codicon } from 'vs/base/common/codicons';
const registry = Registry.as<IWorkbenchActionRegistry>(WorkbenchExtensions.WorkbenchActions);
// --- Close Side Bar
class CloseSidebarAction extends Action2 {
constructor() {
super({
id: 'workbench.action.closeSidebar',
title: { value: nls.localize('closeSidebar', "Close Side Bar"), original: 'Close Side Bar' },
category: CATEGORIES.View,
f1: true
});
}
run(accessor: ServicesAccessor): void {
accessor.get(IWorkbenchLayoutService).setSideBarHidden(true);
}
}
registerAction2(CloseSidebarAction);
// --- Toggle Activity Bar
export class ToggleActivityBarVisibilityAction extends Action2 {
static readonly ID = 'workbench.action.toggleActivityBarVisibility';
static readonly LABEL = nls.localize('toggleActivityBar', "Toggle Activity Bar Visibility");
private static readonly activityBarVisibleKey = 'workbench.activityBar.visible';
constructor() {
super({
id: ToggleActivityBarVisibilityAction.ID,
title: { value: ToggleActivityBarVisibilityAction.LABEL, original: 'Toggle Activity Bar Visibility' },
category: CATEGORIES.View,
f1: true
});
}
run(accessor: ServicesAccessor): void {
const layoutService = accessor.get(IWorkbenchLayoutService);
const configurationService = accessor.get(IConfigurationService);
const visibility = layoutService.isVisible(Parts.ACTIVITYBAR_PART);
const newVisibilityValue = !visibility;
configurationService.updateValue(ToggleActivityBarVisibilityAction.activityBarVisibleKey, newVisibilityValue, ConfigurationTarget.USER);
}
}
registerAction2(ToggleActivityBarVisibilityAction);
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '2_workbench_layout',
command: {
id: ToggleActivityBarVisibilityAction.ID,
title: nls.localize({ key: 'miShowActivityBar', comment: ['&& denotes a mnemonic'] }, "Show &&Activity Bar"),
toggled: ContextKeyExpr.equals('config.workbench.activityBar.visible', true)
},
order: 4
});
// --- Toggle Centered Layout
class ToggleCenteredLayout extends Action2 {
static readonly ID = 'workbench.action.toggleCenteredLayout';
constructor() {
super({
id: ToggleCenteredLayout.ID,
title: { value: nls.localize('toggleCenteredLayout', "Toggle Centered Layout"), original: 'Toggle Centered Layout' },
category: CATEGORIES.View,
f1: true
});
}
run(accessor: ServicesAccessor): void {
const layoutService = accessor.get(IWorkbenchLayoutService);
layoutService.centerEditorLayout(!layoutService.isEditorLayoutCentered());
}
}
registerAction2(ToggleCenteredLayout);
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '1_toggle_view',
command: {
id: ToggleCenteredLayout.ID,
title: nls.localize({ key: 'miToggleCenteredLayout', comment: ['&& denotes a mnemonic'] }, "&&Centered Layout"),
toggled: IsCenteredLayoutContext
},
order: 3
});
// --- Toggle Editor Layout
export class ToggleEditorLayoutAction extends Action {
static readonly ID = 'workbench.action.toggleEditorGroupLayout';
static readonly LABEL = nls.localize('flipLayout', "Toggle Vertical/Horizontal Editor Layout");
private readonly toDispose = this._register(new DisposableStore());
constructor(
id: string,
label: string,
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService
) {
super(id, label);
this.class = Codicon.editorLayout.classNames;
this.updateEnablement();
this.registerListeners();
}
private registerListeners(): void {
this.toDispose.add(this.editorGroupService.onDidAddGroup(() => this.updateEnablement()));
this.toDispose.add(this.editorGroupService.onDidRemoveGroup(() => this.updateEnablement()));
}
private updateEnablement(): void {
this.enabled = this.editorGroupService.count > 1;
}
async run(): Promise<void> {
const newOrientation = (this.editorGroupService.orientation === GroupOrientation.VERTICAL) ? GroupOrientation.HORIZONTAL : GroupOrientation.VERTICAL;
this.editorGroupService.setGroupOrientation(newOrientation);
}
}
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleEditorLayoutAction, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_0 } }), 'View: Toggle Vertical/Horizontal Editor Layout', CATEGORIES.View.value);
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
group: 'z_flip',
command: {
id: ToggleEditorLayoutAction.ID,
title: nls.localize({ key: 'miToggleEditorLayout', comment: ['&& denotes a mnemonic'] }, "Flip &&Layout")
},
order: 1
});
// --- Toggle Sidebar Position
export class ToggleSidebarPositionAction extends Action {
static readonly ID = 'workbench.action.toggleSidebarPosition';
static readonly LABEL = nls.localize('toggleSidebarPosition', "Toggle Side Bar Position");
private static readonly sidebarPositionConfigurationKey = 'workbench.sideBar.location';
constructor(
id: string,
label: string,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super(id, label);
}
run(): Promise<void> {
const position = this.layoutService.getSideBarPosition();
const newPositionValue = (position === Position.LEFT) ? 'right' : 'left';
return this.configurationService.updateValue(ToggleSidebarPositionAction.sidebarPositionConfigurationKey, newPositionValue, ConfigurationTarget.USER);
}
static getLabel(layoutService: IWorkbenchLayoutService): string {
return layoutService.getSideBarPosition() === Position.LEFT ? nls.localize('moveSidebarRight', "Move Side Bar Right") : nls.localize('moveSidebarLeft', "Move Side Bar Left");
}
}
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleSidebarPositionAction), 'View: Toggle Side Bar Position', CATEGORIES.View.value);
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '3_workbench_layout_move',
command: {
id: ToggleSidebarPositionAction.ID,
title: nls.localize({ key: 'miMoveSidebarRight', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Right")
},
when: ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'),
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '3_workbench_layout_move',
command: {
id: ToggleSidebarPositionAction.ID,
title: nls.localize({ key: 'miMoveSidebarLeft', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Left")
},
when: ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'),
order: 2
});
// --- Toggle Sidebar Visibility
export class ToggleEditorVisibilityAction extends Action {
static readonly ID = 'workbench.action.toggleEditorVisibility';
static readonly LABEL = nls.localize('toggleEditor', "Toggle Editor Area Visibility");
constructor(
id: string,
label: string,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService
) {
super(id, label);
}
async run(): Promise<void> {
this.layoutService.toggleMaximizedPanel();
}
}
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleEditorVisibilityAction), 'View: Toggle Editor Area Visibility', CATEGORIES.View.value);
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '2_workbench_layout',
command: {
id: ToggleEditorVisibilityAction.ID,
title: nls.localize({ key: 'miShowEditorArea', comment: ['&& denotes a mnemonic'] }, "Show &&Editor Area"),
toggled: EditorAreaVisibleContext
},
order: 5
});
export class ToggleSidebarVisibilityAction extends Action {
static readonly ID = 'workbench.action.toggleSidebarVisibility';
static readonly LABEL = nls.localize('toggleSidebar', "Toggle Side Bar Visibility");
constructor(
id: string,
label: string,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService
) {
super(id, label);
}
async run(): Promise<void> {
const hideSidebar = this.layoutService.isVisible(Parts.SIDEBAR_PART);
this.layoutService.setSideBarHidden(hideSidebar);
}
}
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleSidebarVisibilityAction, { primary: KeyMod.CtrlCmd | KeyCode.KEY_B }), 'View: Toggle Side Bar Visibility', CATEGORIES.View.value);
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
group: '2_appearance',
title: nls.localize({ key: 'miAppearance', comment: ['&& denotes a mnemonic'] }, "&&Appearance"),
submenu: MenuId.MenubarAppearanceMenu,
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '2_workbench_layout',
command: {
id: ToggleSidebarVisibilityAction.ID,
title: nls.localize({ key: 'miShowSidebar', comment: ['&& denotes a mnemonic'] }, "Show &&Side Bar"),
toggled: SideBarVisibleContext
},
order: 1
});
// --- Toggle Statusbar Visibility
export class ToggleStatusbarVisibilityAction extends Action {
static readonly ID = 'workbench.action.toggleStatusbarVisibility';
static readonly LABEL = nls.localize('toggleStatusbar', "Toggle Status Bar Visibility");
private static readonly statusbarVisibleKey = 'workbench.statusBar.visible';
constructor(
id: string,
label: string,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super(id, label);
}
run(): Promise<void> {
const visibility = this.layoutService.isVisible(Parts.STATUSBAR_PART);
const newVisibilityValue = !visibility;
return this.configurationService.updateValue(ToggleStatusbarVisibilityAction.statusbarVisibleKey, newVisibilityValue, ConfigurationTarget.USER);
}
}
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleStatusbarVisibilityAction), 'View: Toggle Status Bar Visibility', CATEGORIES.View.value);
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '2_workbench_layout',
command: {
id: ToggleStatusbarVisibilityAction.ID,
title: nls.localize({ key: 'miShowStatusbar', comment: ['&& denotes a mnemonic'] }, "Show S&&tatus Bar"),
toggled: ContextKeyExpr.equals('config.workbench.statusBar.visible', true)
},
order: 3
});
// --- Toggle Tabs Visibility
class ToggleTabsVisibilityAction extends Action {
static readonly ID = 'workbench.action.toggleTabsVisibility';
static readonly LABEL = nls.localize('toggleTabs', "Toggle Tab Visibility");
private static readonly tabsVisibleKey = 'workbench.editor.showTabs';
constructor(
id: string,
label: string,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super(id, label);
}
run(): Promise<void> {
const visibility = this.configurationService.getValue<string>(ToggleTabsVisibilityAction.tabsVisibleKey);
const newVisibilityValue = !visibility;
return this.configurationService.updateValue(ToggleTabsVisibilityAction.tabsVisibleKey, newVisibilityValue);
}
}
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleTabsVisibilityAction, {
primary: undefined,
mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_W, },
linux: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_W, }
}), 'View: Toggle Tab Visibility', CATEGORIES.View.value);
// --- Toggle Zen Mode
class ToggleZenMode extends Action {
static readonly ID = 'workbench.action.toggleZenMode';
static readonly LABEL = nls.localize('toggleZenMode', "Toggle Zen Mode");
constructor(
id: string,
label: string,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService
) {
super(id, label);
}
async run(): Promise<void> {
this.layoutService.toggleZenMode();
}
}
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleZenMode, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_Z) }), 'View: Toggle Zen Mode', CATEGORIES.View.value);
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '1_toggle_view',
command: {
id: ToggleZenMode.ID,
title: nls.localize('miToggleZenMode', "Zen Mode"),
toggled: InEditorZenModeContext
},
order: 2
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.exitZenMode',
weight: KeybindingWeight.EditorContrib - 1000,
handler(accessor: ServicesAccessor) {
const layoutService = accessor.get(IWorkbenchLayoutService);
layoutService.toggleZenMode();
},
when: InEditorZenModeContext,
primary: KeyChord(KeyCode.Escape, KeyCode.Escape)
});
// --- Toggle Menu Bar
export class ToggleMenuBarAction extends Action {
static readonly ID = 'workbench.action.toggleMenuBar';
static readonly LABEL = nls.localize('toggleMenuBar', "Toggle Menu Bar");
private static readonly menuBarVisibilityKey = 'window.menuBarVisibility';
constructor(
id: string,
label: string,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IEnvironmentService private readonly environmentService: IEnvironmentService
) {
super(id, label);
}
run(): Promise<void> {
let currentVisibilityValue = getMenuBarVisibility(this.configurationService, this.environmentService);
if (typeof currentVisibilityValue !== 'string') {
currentVisibilityValue = 'default';
}
let newVisibilityValue: string;
if (currentVisibilityValue === 'visible' || currentVisibilityValue === 'default') {
newVisibilityValue = 'toggle';
} else if (currentVisibilityValue === 'compact') {
newVisibilityValue = 'hidden';
} else {
newVisibilityValue = (isWeb && currentVisibilityValue === 'hidden') ? 'compact' : 'default';
}
return this.configurationService.updateValue(ToggleMenuBarAction.menuBarVisibilityKey, newVisibilityValue, ConfigurationTarget.USER);
}
}
if (isWindows || isLinux || isWeb) {
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleMenuBarAction), 'View: Toggle Menu Bar', CATEGORIES.View.value);
}
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '2_workbench_layout',
command: {
id: ToggleMenuBarAction.ID,
title: nls.localize({ key: 'miShowMenuBar', comment: ['&& denotes a mnemonic'] }, "Show Menu &&Bar"),
toggled: ContextKeyExpr.and(IsMacNativeContext.toNegated(), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'hidden'), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'toggle'))
},
when: IsMacNativeContext.toNegated(),
order: 0
});
// --- Reset View Positions
export class ResetViewLocationsAction extends Action {
static readonly ID = 'workbench.action.resetViewLocations';
static readonly LABEL = nls.localize('resetViewLocations', "Reset View Locations");
constructor(
id: string,
label: string,
@IViewDescriptorService private viewDescriptorService: IViewDescriptorService
) {
super(id, label);
}
async run(): Promise<void> {
this.viewDescriptorService.reset();
}
}
registry.registerWorkbenchAction(SyncActionDescriptor.from(ResetViewLocationsAction), 'View: Reset View Locations', CATEGORIES.View.value);
// --- Toggle View with Command
export abstract class ToggleViewAction extends Action {
constructor(
id: string,
label: string,
private readonly viewId: string,
protected viewsService: IViewsService,
protected viewDescriptorService: IViewDescriptorService,
protected contextKeyService: IContextKeyService,
private layoutService: IWorkbenchLayoutService,
cssClass?: string
) {
super(id, label, cssClass);
}
async run(): Promise<void> {
const focusedViewId = FocusedViewContext.getValue(this.contextKeyService);
if (focusedViewId === this.viewId) {
if (this.viewDescriptorService.getViewLocationById(this.viewId) === ViewContainerLocation.Sidebar) {
this.layoutService.setSideBarHidden(true);
} else {
this.layoutService.setPanelHidden(true);
}
} else {
this.viewsService.openView(this.viewId, true);
}
}
}
// --- Move View with Command
export class MoveViewAction extends Action {
static readonly ID = 'workbench.action.moveView';
static readonly LABEL = nls.localize('moveView', "Move View");
constructor(
id: string,
label: string,
@IViewDescriptorService private viewDescriptorService: IViewDescriptorService,
@IInstantiationService private instantiationService: IInstantiationService,
@IQuickInputService private quickInputService: IQuickInputService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IActivityBarService private activityBarService: IActivityBarService,
@IPanelService private panelService: IPanelService
) {
super(id, label);
}
private getViewItems(): Array<IQuickPickItem | IQuickPickSeparator> {
const results: Array<IQuickPickItem | IQuickPickSeparator> = [];
const viewlets = this.activityBarService.getVisibleViewContainerIds();
viewlets.forEach(viewletId => {
const container = this.viewDescriptorService.getViewContainerById(viewletId)!;
const containerModel = this.viewDescriptorService.getViewContainerModel(container);
let hasAddedView = false;
containerModel.visibleViewDescriptors.forEach(viewDescriptor => {
if (viewDescriptor.canMoveView) {
if (!hasAddedView) {
results.push({
type: 'separator',
label: nls.localize('sidebarContainer', "Side Bar / {0}", containerModel.title)
});
hasAddedView = true;
}
results.push({
id: viewDescriptor.id,
label: viewDescriptor.name
});
}
});
});
const panels = this.panelService.getPinnedPanels();
panels.forEach(panel => {
const container = this.viewDescriptorService.getViewContainerById(panel.id)!;
const containerModel = this.viewDescriptorService.getViewContainerModel(container);
let hasAddedView = false;
containerModel.visibleViewDescriptors.forEach(viewDescriptor => {
if (viewDescriptor.canMoveView) {
if (!hasAddedView) {
results.push({
type: 'separator',
label: nls.localize('panelContainer', "Panel / {0}", containerModel.title)
});
hasAddedView = true;
}
results.push({
id: viewDescriptor.id,
label: viewDescriptor.name
});
}
});
});
return results;
}
private async getView(viewId?: string): Promise<string> {
const quickPick = this.quickInputService.createQuickPick();
quickPick.placeholder = nls.localize('moveFocusedView.selectView', "Select a View to Move");
quickPick.items = this.getViewItems();
quickPick.selectedItems = quickPick.items.filter(item => (item as IQuickPickItem).id === viewId) as IQuickPickItem[];
return new Promise((resolve, reject) => {
quickPick.onDidAccept(() => {
const viewId = quickPick.selectedItems[0];
if (viewId.id) {
resolve(viewId.id);
} else {
reject();
}
quickPick.hide();
});
quickPick.onDidHide(() => reject());
quickPick.show();
});
}
async run(): Promise<void> {
const focusedViewId = FocusedViewContext.getValue(this.contextKeyService);
let viewId: string;
if (focusedViewId && this.viewDescriptorService.getViewDescriptorById(focusedViewId)?.canMoveView) {
viewId = focusedViewId;
}
viewId = await this.getView(viewId!);
if (!viewId) {
return;
}
this.instantiationService.createInstance(MoveFocusedViewAction, MoveFocusedViewAction.ID, MoveFocusedViewAction.LABEL).run(viewId);
}
}
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveViewAction), 'View: Move View', CATEGORIES.View.value);
// --- Move Focused View with Command
export class MoveFocusedViewAction extends Action {
static readonly ID = 'workbench.action.moveFocusedView';
static readonly LABEL = nls.localize('moveFocusedView', "Move Focused View");
constructor(
id: string,
label: string,
@IViewDescriptorService private viewDescriptorService: IViewDescriptorService,
@IViewsService private viewsService: IViewsService,
@IQuickInputService private quickInputService: IQuickInputService,
@IContextKeyService private contextKeyService: IContextKeyService,
@INotificationService private notificationService: INotificationService,
@IActivityBarService private activityBarService: IActivityBarService,
@IPanelService private panelService: IPanelService
) {
super(id, label);
}
async run(viewId: string): Promise<void> {
const focusedViewId = viewId || FocusedViewContext.getValue(this.contextKeyService);
if (focusedViewId === undefined || focusedViewId.trim() === '') {
this.notificationService.error(nls.localize('moveFocusedView.error.noFocusedView', "There is no view currently focused."));
return;
}
const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(focusedViewId);
if (!viewDescriptor || !viewDescriptor.canMoveView) {
this.notificationService.error(nls.localize('moveFocusedView.error.nonMovableView', "The currently focused view is not movable."));
return;
}
const quickPick = this.quickInputService.createQuickPick();
quickPick.placeholder = nls.localize('moveFocusedView.selectDestination', "Select a Destination for the View");
quickPick.title = nls.localize({ key: 'moveFocusedView.title', comment: ['{0} indicates the title of the view the user has selected to move.'] }, "View: Move {0}", viewDescriptor.name);
const items: Array<IQuickPickItem | IQuickPickSeparator> = [];
const currentContainer = this.viewDescriptorService.getViewContainerByViewId(focusedViewId)!;
const currentLocation = this.viewDescriptorService.getViewLocationById(focusedViewId)!;
const isViewSolo = this.viewDescriptorService.getViewContainerModel(currentContainer).allViewDescriptors.length === 1;
if (!(isViewSolo && currentLocation === ViewContainerLocation.Panel)) {
items.push({
id: '_.panel.newcontainer',
label: nls.localize({ key: 'moveFocusedView.newContainerInPanel', comment: ['Creates a new top-level tab in the panel.'] }, "New Panel Entry"),
});
}
if (!(isViewSolo && currentLocation === ViewContainerLocation.Sidebar)) {
items.push({
id: '_.sidebar.newcontainer',
label: nls.localize('moveFocusedView.newContainerInSidebar', "New Side Bar Entry")
});
}
items.push({
type: 'separator',
label: nls.localize('sidebar', "Side Bar")
});
const pinnedViewlets = this.activityBarService.getVisibleViewContainerIds();
items.push(...pinnedViewlets
.filter(viewletId => {
if (viewletId === this.viewDescriptorService.getViewContainerByViewId(focusedViewId)!.id) {
return false;
}
return !this.viewDescriptorService.getViewContainerById(viewletId)!.rejectAddedViews;
})
.map(viewletId => {
return {
id: viewletId,
label: this.viewDescriptorService.getViewContainerById(viewletId)!.name
};
}));
items.push({
type: 'separator',
label: nls.localize('panel', "Panel")
});
const pinnedPanels = this.panelService.getPinnedPanels();
items.push(...pinnedPanels
.filter(panel => {
if (panel.id === this.viewDescriptorService.getViewContainerByViewId(focusedViewId)!.id) {
return false;
}
return !this.viewDescriptorService.getViewContainerById(panel.id)!.rejectAddedViews;
})
.map(panel => {
return {
id: panel.id,
label: this.viewDescriptorService.getViewContainerById(panel.id)!.name
};
}));
quickPick.items = items;
quickPick.onDidAccept(() => {
const destination = quickPick.selectedItems[0];
if (destination.id === '_.panel.newcontainer') {
this.viewDescriptorService.moveViewToLocation(viewDescriptor!, ViewContainerLocation.Panel);
this.viewsService.openView(focusedViewId, true);
} else if (destination.id === '_.sidebar.newcontainer') {
this.viewDescriptorService.moveViewToLocation(viewDescriptor!, ViewContainerLocation.Sidebar);
this.viewsService.openView(focusedViewId, true);
} else if (destination.id) {
this.viewDescriptorService.moveViewsToContainer([viewDescriptor], this.viewDescriptorService.getViewContainerById(destination.id)!);
this.viewsService.openView(focusedViewId, true);
}
quickPick.hide();
});
quickPick.show();
}
}
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveFocusedViewAction), 'View: Move Focused View', CATEGORIES.View.value, FocusedViewContext.notEqualsTo(''));
// --- Reset View Location with Command
export class ResetFocusedViewLocationAction extends Action {
static readonly ID = 'workbench.action.resetFocusedViewLocation';
static readonly LABEL = nls.localize('resetFocusedViewLocation', "Reset Focused View Location");
constructor(
id: string,
label: string,
@IViewDescriptorService private viewDescriptorService: IViewDescriptorService,
@IContextKeyService private contextKeyService: IContextKeyService,
@INotificationService private notificationService: INotificationService,
@IViewsService private viewsService: IViewsService
) {
super(id, label);
}
async run(): Promise<void> {
const focusedViewId = FocusedViewContext.getValue(this.contextKeyService);
let viewDescriptor: IViewDescriptor | null = null;
if (focusedViewId !== undefined && focusedViewId.trim() !== '') {
viewDescriptor = this.viewDescriptorService.getViewDescriptorById(focusedViewId);
}
if (!viewDescriptor) {
this.notificationService.error(nls.localize('resetFocusedView.error.noFocusedView', "There is no view currently focused."));
return;
}
const defaultContainer = this.viewDescriptorService.getDefaultContainerById(viewDescriptor.id);
if (!defaultContainer || defaultContainer === this.viewDescriptorService.getViewContainerByViewId(viewDescriptor.id)) {
return;
}
this.viewDescriptorService.moveViewsToContainer([viewDescriptor], defaultContainer);
this.viewsService.openView(viewDescriptor.id, true);
}
}
registry.registerWorkbenchAction(SyncActionDescriptor.from(ResetFocusedViewLocationAction), 'View: Reset Focused View Location', CATEGORIES.View.value, FocusedViewContext.notEqualsTo(''));
// --- Resize View
export abstract class BaseResizeViewAction extends Action2 {
protected static readonly RESIZE_INCREMENT = 6.5; // This is a media-size percentage
protected resizePart(widthChange: number, heightChange: number, layoutService: IWorkbenchLayoutService, partToResize?: Parts): void {
let part: Parts | undefined;
if (partToResize === undefined) {
const isEditorFocus = layoutService.hasFocus(Parts.EDITOR_PART);
const isSidebarFocus = layoutService.hasFocus(Parts.SIDEBAR_PART);
const isPanelFocus = layoutService.hasFocus(Parts.PANEL_PART);
if (isSidebarFocus) {
part = Parts.SIDEBAR_PART;
} else if (isPanelFocus) {
part = Parts.PANEL_PART;
} else if (isEditorFocus) {
part = Parts.EDITOR_PART;
}
} else {
part = partToResize;
}
if (part) {
layoutService.resizePart(part, widthChange, heightChange);
}
}
}
export class IncreaseViewSizeAction extends BaseResizeViewAction {
constructor() {
super({
id: 'workbench.action.increaseViewSize',
title: { value: nls.localize('increaseViewSize', "Increase Current View Size"), original: 'Increase Current View Size' },
f1: true
});
}
async run(accessor: ServicesAccessor): Promise<void> {
this.resizePart(BaseResizeViewAction.RESIZE_INCREMENT, BaseResizeViewAction.RESIZE_INCREMENT, accessor.get(IWorkbenchLayoutService));
}
}
export class IncreaseViewWidthAction extends BaseResizeViewAction {
constructor() {
super({
id: 'workbench.action.increaseViewWidth',
title: { value: nls.localize('increaseEditorWidth', "Increase Editor Width"), original: 'Increase Editor Width' },
f1: true
});
}
async run(accessor: ServicesAccessor): Promise<void> {
this.resizePart(BaseResizeViewAction.RESIZE_INCREMENT, 0, accessor.get(IWorkbenchLayoutService), Parts.EDITOR_PART);
}
}
export class IncreaseViewHeightAction extends BaseResizeViewAction {
constructor() {
super({
id: 'workbench.action.increaseViewHeight',
title: { value: nls.localize('increaseEditorHeight', "Increase Editor Height"), original: 'Increase Editor Height' },
f1: true
});
}
async run(accessor: ServicesAccessor): Promise<void> {
this.resizePart(0, BaseResizeViewAction.RESIZE_INCREMENT, accessor.get(IWorkbenchLayoutService), Parts.EDITOR_PART);
}
}
export class DecreaseViewSizeAction extends BaseResizeViewAction {
constructor() {
super({
id: 'workbench.action.decreaseViewSize',
title: { value: nls.localize('decreaseViewSize', "Decrease Current View Size"), original: 'Decrease Current View Size' },
f1: true
});
}
async run(accessor: ServicesAccessor): Promise<void> {
this.resizePart(-BaseResizeViewAction.RESIZE_INCREMENT, -BaseResizeViewAction.RESIZE_INCREMENT, accessor.get(IWorkbenchLayoutService));
}
}
export class DecreaseViewWidthAction extends BaseResizeViewAction {
constructor() {
super({
id: 'workbench.action.decreaseViewWidth',
title: { value: nls.localize('decreaseEditorWidth', "Decrease Editor Width"), original: 'Decrease Editor Width' },
f1: true
});
}
async run(accessor: ServicesAccessor): Promise<void> {
this.resizePart(-BaseResizeViewAction.RESIZE_INCREMENT, 0, accessor.get(IWorkbenchLayoutService), Parts.EDITOR_PART);
}
}
export class DecreaseViewHeightAction extends BaseResizeViewAction {
constructor() {
super({
id: 'workbench.action.decreaseViewHeight',
title: { value: nls.localize('decreaseEditorHeight', "Decrease Editor Height"), original: 'Decrease Editor Height' },
f1: true
});
}
async run(accessor: ServicesAccessor): Promise<void> {
this.resizePart(0, -BaseResizeViewAction.RESIZE_INCREMENT, accessor.get(IWorkbenchLayoutService), Parts.EDITOR_PART);
}
}
registerAction2(IncreaseViewSizeAction);
registerAction2(IncreaseViewWidthAction);
registerAction2(IncreaseViewHeightAction);
registerAction2(DecreaseViewSizeAction);
registerAction2(DecreaseViewWidthAction);
registerAction2(DecreaseViewHeightAction);

View File

@@ -0,0 +1,796 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { List } from 'vs/base/browser/ui/list/listWidget';
import { WorkbenchListFocusContextKey, IListService, WorkbenchListSupportsMultiSelectContextKey, ListWidget, WorkbenchListHasSelectionOrFocus, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService';
import { PagedList } from 'vs/base/browser/ui/list/listPaging';
import { range } from 'vs/base/common/arrays';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree';
import { DataTree } from 'vs/base/browser/ui/tree/dataTree';
import { ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
function ensureDOMFocus(widget: ListWidget | undefined): void {
// it can happen that one of the commands is executed while
// DOM focus is within another focusable control within the
// list/tree item. therefor we should ensure that the
// list/tree has DOM focus again after the command ran.
if (widget && widget.getHTMLElement() !== document.activeElement) {
widget.domFocus();
}
}
function focusDown(accessor: ServicesAccessor, arg2?: number, loop: boolean = false): void {
const focused = accessor.get(IListService).lastFocusedList;
const count = typeof arg2 === 'number' ? arg2 : 1;
// List
if (focused instanceof List || focused instanceof PagedList) {
const list = focused;
list.focusNext(count);
const listFocus = list.getFocus();
if (listFocus.length) {
list.reveal(listFocus[0]);
}
}
// Tree
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
const tree = focused;
const fakeKeyboardEvent = new KeyboardEvent('keydown');
tree.focusNext(count, loop, fakeKeyboardEvent);
const listFocus = tree.getFocus();
if (listFocus.length) {
tree.reveal(listFocus[0]);
}
}
// Ensure DOM Focus
ensureDOMFocus(focused);
}
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.focusDown',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
primary: KeyCode.DownArrow,
mac: {
primary: KeyCode.DownArrow,
secondary: [KeyMod.WinCtrl | KeyCode.KEY_N]
},
handler: (accessor, arg2) => focusDown(accessor, arg2)
});
function expandMultiSelection(focused: List<unknown> | PagedList<unknown> | ObjectTree<unknown, unknown> | DataTree<unknown, unknown, unknown> | AsyncDataTree<unknown, unknown, unknown>, previousFocus: unknown): void {
// List
if (focused instanceof List || focused instanceof PagedList) {
const list = focused;
const focus = list.getFocus() ? list.getFocus()[0] : undefined;
const selection = list.getSelection();
if (selection && typeof focus === 'number' && selection.indexOf(focus) >= 0) {
list.setSelection(selection.filter(s => s !== previousFocus));
} else {
if (typeof focus === 'number') {
list.setSelection(selection.concat(focus));
}
}
}
// Tree
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
const list = focused;
const focus = list.getFocus() ? list.getFocus()[0] : undefined;
if (previousFocus === focus) {
return;
}
const selection = list.getSelection();
const fakeKeyboardEvent = new KeyboardEvent('keydown', { shiftKey: true });
if (selection && selection.indexOf(focus) >= 0) {
list.setSelection(selection.filter(s => s !== previousFocus), fakeKeyboardEvent);
} else {
list.setSelection(selection.concat(focus), fakeKeyboardEvent);
}
}
}
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.expandSelectionDown',
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(WorkbenchListFocusContextKey, WorkbenchListSupportsMultiSelectContextKey),
primary: KeyMod.Shift | KeyCode.DownArrow,
handler: (accessor, arg2) => {
const focused = accessor.get(IListService).lastFocusedList;
// List / Tree
if (focused instanceof List || focused instanceof PagedList || focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
const list = focused;
// Focus down first
const previousFocus = list.getFocus() ? list.getFocus()[0] : undefined;
focusDown(accessor, arg2, false);
// Then adjust selection
expandMultiSelection(focused, previousFocus);
}
}
});
function focusUp(accessor: ServicesAccessor, arg2?: number, loop: boolean = false): void {
const focused = accessor.get(IListService).lastFocusedList;
const count = typeof arg2 === 'number' ? arg2 : 1;
// List
if (focused instanceof List || focused instanceof PagedList) {
const list = focused;
list.focusPrevious(count);
const listFocus = list.getFocus();
if (listFocus.length) {
list.reveal(listFocus[0]);
}
}
// Tree
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
const tree = focused;
const fakeKeyboardEvent = new KeyboardEvent('keydown');
tree.focusPrevious(count, loop, fakeKeyboardEvent);
const listFocus = tree.getFocus();
if (listFocus.length) {
tree.reveal(listFocus[0]);
}
}
// Ensure DOM Focus
ensureDOMFocus(focused);
}
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.focusUp',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
primary: KeyCode.UpArrow,
mac: {
primary: KeyCode.UpArrow,
secondary: [KeyMod.WinCtrl | KeyCode.KEY_P]
},
handler: (accessor, arg2) => focusUp(accessor, arg2)
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.expandSelectionUp',
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(WorkbenchListFocusContextKey, WorkbenchListSupportsMultiSelectContextKey),
primary: KeyMod.Shift | KeyCode.UpArrow,
handler: (accessor, arg2) => {
const focused = accessor.get(IListService).lastFocusedList;
// List / Tree
if (focused instanceof List || focused instanceof PagedList || focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
const list = focused;
// Focus up first
const previousFocus = list.getFocus() ? list.getFocus()[0] : undefined;
focusUp(accessor, arg2, false);
// Then adjust selection
expandMultiSelection(focused, previousFocus);
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.collapse',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
primary: KeyCode.LeftArrow,
mac: {
primary: KeyCode.LeftArrow,
secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow]
},
handler: (accessor) => {
const focused = accessor.get(IListService).lastFocusedList;
// Tree only
if (focused && !(focused instanceof List || focused instanceof PagedList)) {
if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
const tree = focused;
const focusedElements = tree.getFocus();
if (focusedElements.length === 0) {
return;
}
const focus = focusedElements[0];
if (!tree.collapse(focus)) {
const parent = tree.getParentElement(focus);
if (parent) {
const fakeKeyboardEvent = new KeyboardEvent('keydown');
tree.setFocus([parent], fakeKeyboardEvent);
tree.reveal(parent);
}
}
}
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.collapseAll',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
primary: KeyMod.CtrlCmd | KeyCode.LeftArrow,
mac: {
primary: KeyMod.CtrlCmd | KeyCode.LeftArrow,
secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow]
},
handler: (accessor) => {
const focusedTree = accessor.get(IListService).lastFocusedList;
if (focusedTree && !(focusedTree instanceof List || focusedTree instanceof PagedList)) {
focusedTree.collapseAll();
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.focusParent',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
handler: (accessor) => {
const focused = accessor.get(IListService).lastFocusedList;
if (!focused || focused instanceof List || focused instanceof PagedList) {
return;
}
if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
const tree = focused;
const focusedElements = tree.getFocus();
if (focusedElements.length === 0) {
return;
}
const focus = focusedElements[0];
const parent = tree.getParentElement(focus);
if (parent) {
const fakeKeyboardEvent = new KeyboardEvent('keydown');
tree.setFocus([parent], fakeKeyboardEvent);
tree.reveal(parent);
}
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.expand',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
primary: KeyCode.RightArrow,
handler: (accessor) => {
const focused = accessor.get(IListService).lastFocusedList;
// Tree only
if (focused && !(focused instanceof List || focused instanceof PagedList)) {
if (focused instanceof ObjectTree || focused instanceof DataTree) {
// TODO@Joao: instead of doing this here, just delegate to a tree method
const tree = focused;
const focusedElements = tree.getFocus();
if (focusedElements.length === 0) {
return;
}
const focus = focusedElements[0];
if (!tree.expand(focus)) {
const child = tree.getFirstElementChild(focus);
if (child) {
const node = tree.getNode(child);
if (node.visible) {
const fakeKeyboardEvent = new KeyboardEvent('keydown');
tree.setFocus([child], fakeKeyboardEvent);
tree.reveal(child);
}
}
}
} else if (focused instanceof AsyncDataTree) {
// TODO@Joao: instead of doing this here, just delegate to a tree method
const tree = focused;
const focusedElements = tree.getFocus();
if (focusedElements.length === 0) {
return;
}
const focus = focusedElements[0];
tree.expand(focus).then(didExpand => {
if (focus && !didExpand) {
const child = tree.getFirstElementChild(focus);
if (child) {
const node = tree.getNode(child);
if (node.visible) {
const fakeKeyboardEvent = new KeyboardEvent('keydown');
tree.setFocus([child], fakeKeyboardEvent);
tree.reveal(child);
}
}
}
});
}
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.focusPageUp',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
primary: KeyCode.PageUp,
handler: (accessor) => {
const focused = accessor.get(IListService).lastFocusedList;
// List
if (focused instanceof List || focused instanceof PagedList) {
const list = focused;
list.focusPreviousPage();
list.reveal(list.getFocus()[0]);
}
// Tree
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
const list = focused;
const fakeKeyboardEvent = new KeyboardEvent('keydown');
list.focusPreviousPage(fakeKeyboardEvent);
list.reveal(list.getFocus()[0]);
}
// Ensure DOM Focus
ensureDOMFocus(focused);
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.focusPageDown',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
primary: KeyCode.PageDown,
handler: (accessor) => {
const focused = accessor.get(IListService).lastFocusedList;
// List
if (focused instanceof List || focused instanceof PagedList) {
const list = focused;
list.focusNextPage();
list.reveal(list.getFocus()[0]);
}
// Tree
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
const list = focused;
const fakeKeyboardEvent = new KeyboardEvent('keydown');
list.focusNextPage(fakeKeyboardEvent);
list.reveal(list.getFocus()[0]);
}
// Ensure DOM Focus
ensureDOMFocus(focused);
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.focusFirst',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
primary: KeyCode.Home,
handler: accessor => listFocusFirst(accessor)
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.focusFirstChild',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
primary: 0,
handler: accessor => listFocusFirst(accessor, { fromFocused: true })
});
function listFocusFirst(accessor: ServicesAccessor, options?: { fromFocused: boolean }): void {
const focused = accessor.get(IListService).lastFocusedList;
// List
if (focused instanceof List || focused instanceof PagedList) {
const list = focused;
list.setFocus([0]);
list.reveal(0);
}
// Tree
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
const tree = focused;
const fakeKeyboardEvent = new KeyboardEvent('keydown');
tree.focusFirst(fakeKeyboardEvent);
const focus = tree.getFocus();
if (focus.length > 0) {
tree.reveal(focus[0]);
}
}
// Ensure DOM Focus
ensureDOMFocus(focused);
}
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.focusLast',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
primary: KeyCode.End,
handler: accessor => listFocusLast(accessor)
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.focusLastChild',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
primary: 0,
handler: accessor => listFocusLast(accessor, { fromFocused: true })
});
function listFocusLast(accessor: ServicesAccessor, options?: { fromFocused: boolean }): void {
const focused = accessor.get(IListService).lastFocusedList;
// List
if (focused instanceof List || focused instanceof PagedList) {
const list = focused;
list.setFocus([list.length - 1]);
list.reveal(list.length - 1);
}
// Tree
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
const tree = focused;
const fakeKeyboardEvent = new KeyboardEvent('keydown');
tree.focusLast(fakeKeyboardEvent);
const focus = tree.getFocus();
if (focus.length > 0) {
tree.reveal(focus[0]);
}
}
// Ensure DOM Focus
ensureDOMFocus(focused);
}
function focusElement(accessor: ServicesAccessor, retainCurrentFocus: boolean): void {
const focused = accessor.get(IListService).lastFocusedList;
const fakeKeyboardEvent = getSelectionKeyboardEvent('keydown', retainCurrentFocus);
// List
if (focused instanceof List || focused instanceof PagedList) {
const list = focused;
list.setSelection(list.getFocus(), fakeKeyboardEvent);
}
// Trees
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
const tree = focused;
const focus = tree.getFocus();
if (focus.length > 0) {
let toggleCollapsed = true;
if (tree.expandOnlyOnTwistieClick === true) {
toggleCollapsed = false;
} else if (typeof tree.expandOnlyOnTwistieClick !== 'boolean' && tree.expandOnlyOnTwistieClick(focus[0])) {
toggleCollapsed = false;
}
if (toggleCollapsed) {
tree.toggleCollapsed(focus[0]);
}
}
tree.setSelection(focus, fakeKeyboardEvent);
}
}
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.select',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
primary: KeyCode.Enter,
mac: {
primary: KeyCode.Enter,
secondary: [KeyMod.CtrlCmd | KeyCode.DownArrow]
},
handler: (accessor) => {
focusElement(accessor, false);
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.selectAndPreserveFocus',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
handler: accessor => {
focusElement(accessor, true);
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.selectAll',
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(WorkbenchListFocusContextKey, WorkbenchListSupportsMultiSelectContextKey),
primary: KeyMod.CtrlCmd | KeyCode.KEY_A,
handler: (accessor) => {
const focused = accessor.get(IListService).lastFocusedList;
// List
if (focused instanceof List || focused instanceof PagedList) {
const list = focused;
const fakeKeyboardEvent = new KeyboardEvent('keydown');
list.setSelection(range(list.length), fakeKeyboardEvent);
}
// Trees
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
const tree = focused;
const focus = tree.getFocus();
const selection = tree.getSelection();
// Which element should be considered to start selecting all?
let start: unknown | undefined = undefined;
if (focus.length > 0 && (selection.length === 0 || !selection.includes(focus[0]))) {
start = focus[0];
}
if (!start && selection.length > 0) {
start = selection[0];
}
// What is the scope of select all?
let scope: unknown | undefined = undefined;
if (!start) {
scope = undefined;
} else {
scope = tree.getParentElement(start);
}
const newSelection: unknown[] = [];
const visit = (node: ITreeNode<unknown, unknown>) => {
for (const child of node.children) {
if (child.visible) {
newSelection.push(child.element);
if (!child.collapsed) {
visit(child);
}
}
}
};
// Add the whole scope subtree to the new selection
visit(tree.getNode(scope));
// If the scope isn't the tree root, it should be part of the new selection
if (scope && selection.length === newSelection.length) {
newSelection.unshift(scope);
}
const fakeKeyboardEvent = new KeyboardEvent('keydown');
tree.setSelection(newSelection, fakeKeyboardEvent);
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.toggleSelection',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter,
handler: (accessor) => {
const widget = accessor.get(IListService).lastFocusedList;
if (!widget) {
return;
}
const focus = widget.getFocus();
if (focus.length === 0) {
return;
}
const selection = widget.getSelection();
const index = selection.indexOf(focus[0]);
if (index > -1) {
widget.setSelection([...selection.slice(0, index), ...selection.slice(index + 1)]);
} else {
widget.setSelection([...selection, focus[0]]);
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.toggleExpand',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
primary: KeyCode.Space,
handler: (accessor) => {
const focused = accessor.get(IListService).lastFocusedList;
// Tree only
if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
const tree = focused;
const focus = tree.getFocus();
if (focus.length > 0 && tree.isCollapsible(focus[0])) {
tree.toggleCollapsed(focus[0]);
return;
}
}
focusElement(accessor, true);
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.clear',
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(WorkbenchListFocusContextKey, WorkbenchListHasSelectionOrFocus),
primary: KeyCode.Escape,
handler: (accessor) => {
const focused = accessor.get(IListService).lastFocusedList;
// List
if (focused instanceof List || focused instanceof PagedList) {
const list = focused;
list.setSelection([]);
list.setFocus([]);
}
// Tree
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
const list = focused;
const fakeKeyboardEvent = new KeyboardEvent('keydown');
list.setSelection([], fakeKeyboardEvent);
list.setFocus([], fakeKeyboardEvent);
}
}
});
CommandsRegistry.registerCommand({
id: 'list.toggleKeyboardNavigation',
handler: (accessor) => {
const focused = accessor.get(IListService).lastFocusedList;
// List
if (focused instanceof List || focused instanceof PagedList) {
const list = focused;
list.toggleKeyboardNavigation();
}
// Tree
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
const tree = focused;
tree.toggleKeyboardNavigation();
}
}
});
CommandsRegistry.registerCommand({
id: 'list.toggleFilterOnType',
handler: (accessor) => {
const focused = accessor.get(IListService).lastFocusedList;
// List
if (focused instanceof List || focused instanceof PagedList) {
// TODO@joao
}
// Tree
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
const tree = focused;
tree.updateOptions({ filterOnType: !tree.filterOnType });
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.scrollUp',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
primary: KeyMod.CtrlCmd | KeyCode.UpArrow,
handler: accessor => {
const focused = accessor.get(IListService).lastFocusedList;
if (!focused) {
return;
}
focused.scrollTop -= 10;
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.scrollDown',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
primary: KeyMod.CtrlCmd | KeyCode.DownArrow,
handler: accessor => {
const focused = accessor.get(IListService).lastFocusedList;
if (!focused) {
return;
}
focused.scrollTop += 10;
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.scrollLeft',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
handler: accessor => {
const focused = accessor.get(IListService).lastFocusedList;
if (!focused) {
return;
}
focused.scrollLeft -= 10;
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.scrollRight',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
handler: accessor => {
const focused = accessor.get(IListService).lastFocusedList;
if (!focused) {
return;
}
focused.scrollLeft += 10;
}
});

View File

@@ -0,0 +1,48 @@
/*---------------------------------------------------------------------------------------------
* 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-workspace::before {
content: "\ea76"; /* Close icon flips between black dot and "X" for dirty workspaces */
}
.monaco-workbench .screencast-mouse {
position: absolute;
border-width: 2px;
border-style: solid;
border-radius: 50%;
z-index: 100000;
content: ' ';
pointer-events: none;
display: none;
}
.monaco-workbench .screencast-keyboard {
position: absolute;
background-color: rgba(0, 0, 0 ,0.5);
width: 100%;
left: 0;
z-index: 100000;
pointer-events: none;
color: #eee;
line-height: 1.75em;
text-align: center;
transition: opacity 0.3s ease-out;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.monaco-workbench .screencast-keyboard:empty {
opacity: 0;
}
.monaco-workbench .screencast-keyboard > .key {
padding: 0 8px;
box-shadow: inset 0 -3px 0 hsla(0,0%,73%,.4);
margin-right: 6px;
border: 1px solid hsla(0,0%,80%,.4);
border-radius: 5px;
background-color: rgba(255, 255, 255, 0.05);
}

View File

@@ -0,0 +1,313 @@
/*---------------------------------------------------------------------------------------------
* 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 { Registry } from 'vs/platform/registry/common/platform';
import { Action } from 'vs/base/common/actions';
import { IEditorGroupsService, GroupDirection, GroupLocation, IFindGroupScope } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IViewlet } from 'vs/workbench/common/viewlet';
import { IPanel } from 'vs/workbench/common/panel';
import { Action2, MenuId, registerAction2, SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { IWorkbenchActionRegistry, Extensions, CATEGORIES } from 'vs/workbench/common/actions';
import { Direction } from 'vs/base/browser/ui/grid/grid';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { isAncestor } from 'vs/base/browser/dom';
abstract class BaseNavigationAction extends Action {
constructor(
id: string,
label: string,
protected direction: Direction,
@IEditorGroupsService protected editorGroupService: IEditorGroupsService,
@IPanelService protected panelService: IPanelService,
@IWorkbenchLayoutService protected layoutService: IWorkbenchLayoutService,
@IViewletService protected viewletService: IViewletService
) {
super(id, label);
}
async run(): Promise<boolean | IViewlet | IPanel> {
const isEditorFocus = this.layoutService.hasFocus(Parts.EDITOR_PART);
const isPanelFocus = this.layoutService.hasFocus(Parts.PANEL_PART);
const isSidebarFocus = this.layoutService.hasFocus(Parts.SIDEBAR_PART);
let neighborPart: Parts | undefined;
if (isEditorFocus) {
const didNavigate = this.navigateAcrossEditorGroup(this.toGroupDirection(this.direction));
if (didNavigate) {
return true;
}
neighborPart = this.layoutService.getVisibleNeighborPart(Parts.EDITOR_PART, this.direction);
}
if (isPanelFocus) {
neighborPart = this.layoutService.getVisibleNeighborPart(Parts.PANEL_PART, this.direction);
}
if (isSidebarFocus) {
neighborPart = this.layoutService.getVisibleNeighborPart(Parts.SIDEBAR_PART, this.direction);
}
if (neighborPart === Parts.EDITOR_PART) {
return this.navigateToEditorGroup(this.direction === Direction.Right ? GroupLocation.FIRST : GroupLocation.LAST);
}
if (neighborPart === Parts.SIDEBAR_PART) {
return this.navigateToSidebar();
}
if (neighborPart === Parts.PANEL_PART) {
return this.navigateToPanel();
}
return false;
}
private async navigateToPanel(): Promise<IPanel | boolean> {
if (!this.layoutService.isVisible(Parts.PANEL_PART)) {
return false;
}
const activePanel = this.panelService.getActivePanel();
if (!activePanel) {
return false;
}
const activePanelId = activePanel.getId();
const res = await this.panelService.openPanel(activePanelId, true);
if (!res) {
return false;
}
return res;
}
private async navigateToSidebar(): Promise<IViewlet | boolean> {
if (!this.layoutService.isVisible(Parts.SIDEBAR_PART)) {
return false;
}
const activeViewlet = this.viewletService.getActiveViewlet();
if (!activeViewlet) {
return false;
}
const activeViewletId = activeViewlet.getId();
const viewlet = await this.viewletService.openViewlet(activeViewletId, true);
return !!viewlet;
}
private navigateAcrossEditorGroup(direction: GroupDirection): boolean {
return this.doNavigateToEditorGroup({ direction });
}
private navigateToEditorGroup(location: GroupLocation): boolean {
return this.doNavigateToEditorGroup({ location });
}
private toGroupDirection(direction: Direction): GroupDirection {
switch (direction) {
case Direction.Down: return GroupDirection.DOWN;
case Direction.Left: return GroupDirection.LEFT;
case Direction.Right: return GroupDirection.RIGHT;
case Direction.Up: return GroupDirection.UP;
}
}
private doNavigateToEditorGroup(scope: IFindGroupScope): boolean {
const targetGroup = this.editorGroupService.findGroup(scope, this.editorGroupService.activeGroup);
if (targetGroup) {
targetGroup.focus();
return true;
}
return false;
}
}
class NavigateLeftAction extends BaseNavigationAction {
static readonly ID = 'workbench.action.navigateLeft';
static readonly LABEL = nls.localize('navigateLeft', "Navigate to the View on the Left");
constructor(
id: string,
label: string,
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IPanelService panelService: IPanelService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@IViewletService viewletService: IViewletService
) {
super(id, label, Direction.Left, editorGroupService, panelService, layoutService, viewletService);
}
}
class NavigateRightAction extends BaseNavigationAction {
static readonly ID = 'workbench.action.navigateRight';
static readonly LABEL = nls.localize('navigateRight', "Navigate to the View on the Right");
constructor(
id: string,
label: string,
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IPanelService panelService: IPanelService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@IViewletService viewletService: IViewletService
) {
super(id, label, Direction.Right, editorGroupService, panelService, layoutService, viewletService);
}
}
class NavigateUpAction extends BaseNavigationAction {
static readonly ID = 'workbench.action.navigateUp';
static readonly LABEL = nls.localize('navigateUp', "Navigate to the View Above");
constructor(
id: string,
label: string,
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IPanelService panelService: IPanelService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@IViewletService viewletService: IViewletService
) {
super(id, label, Direction.Up, editorGroupService, panelService, layoutService, viewletService);
}
}
class NavigateDownAction extends BaseNavigationAction {
static readonly ID = 'workbench.action.navigateDown';
static readonly LABEL = nls.localize('navigateDown', "Navigate to the View Below");
constructor(
id: string,
label: string,
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IPanelService panelService: IPanelService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@IViewletService viewletService: IViewletService
) {
super(id, label, Direction.Down, editorGroupService, panelService, layoutService, viewletService);
}
}
function findVisibleNeighbour(layoutService: IWorkbenchLayoutService, part: Parts, next: boolean): Parts {
const neighbour = part === Parts.EDITOR_PART ? (next ? Parts.PANEL_PART : Parts.SIDEBAR_PART) : part === Parts.PANEL_PART ? (next ? Parts.STATUSBAR_PART : Parts.EDITOR_PART) :
part === Parts.STATUSBAR_PART ? (next ? Parts.ACTIVITYBAR_PART : Parts.PANEL_PART) : part === Parts.ACTIVITYBAR_PART ? (next ? Parts.SIDEBAR_PART : Parts.STATUSBAR_PART) :
part === Parts.SIDEBAR_PART ? (next ? Parts.EDITOR_PART : Parts.ACTIVITYBAR_PART) : Parts.EDITOR_PART;
if (layoutService.isVisible(neighbour) || neighbour === Parts.EDITOR_PART) {
return neighbour;
}
return findVisibleNeighbour(layoutService, neighbour, next);
}
function focusNextOrPreviousPart(layoutService: IWorkbenchLayoutService, editorService: IEditorService, next: boolean): void {
const currentlyFocusedPart = isActiveElementInNotebookEditor(editorService) ? Parts.EDITOR_PART : layoutService.hasFocus(Parts.EDITOR_PART) ? Parts.EDITOR_PART : layoutService.hasFocus(Parts.ACTIVITYBAR_PART) ? Parts.ACTIVITYBAR_PART :
layoutService.hasFocus(Parts.STATUSBAR_PART) ? Parts.STATUSBAR_PART : layoutService.hasFocus(Parts.SIDEBAR_PART) ? Parts.SIDEBAR_PART : layoutService.hasFocus(Parts.PANEL_PART) ? Parts.PANEL_PART : undefined;
let partToFocus = Parts.EDITOR_PART;
if (currentlyFocusedPart) {
partToFocus = findVisibleNeighbour(layoutService, currentlyFocusedPart, next);
}
layoutService.focusPart(partToFocus);
}
function isActiveElementInNotebookEditor(editorService: IEditorService): boolean {
const activeEditorPane = editorService.activeEditorPane as unknown as { isNotebookEditor?: boolean } | undefined;
if (activeEditorPane?.isNotebookEditor) {
const control = editorService.activeEditorPane?.getControl() as { getDomNode(): HTMLElement; getOverflowContainerDomNode(): HTMLElement; };
const activeElement = document.activeElement;
return isAncestor(activeElement, control.getDomNode()) || isAncestor(activeElement, control.getOverflowContainerDomNode());
}
return false;
}
export class FocusNextPart extends Action {
static readonly ID = 'workbench.action.focusNextPart';
static readonly LABEL = nls.localize('focusNextPart', "Focus Next Part");
constructor(
id: string,
label: string,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@IEditorService private readonly editorService: IEditorService
) {
super(id, label);
}
async run(): Promise<void> {
focusNextOrPreviousPart(this.layoutService, this.editorService, true);
}
}
export class FocusPreviousPart extends Action {
static readonly ID = 'workbench.action.focusPreviousPart';
static readonly LABEL = nls.localize('focusPreviousPart', "Focus Previous Part");
constructor(
id: string,
label: string,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@IEditorService private readonly editorService: IEditorService
) {
super(id, label);
}
async run(): Promise<void> {
focusNextOrPreviousPart(this.layoutService, this.editorService, false);
}
}
class GoHomeContributor implements IWorkbenchContribution {
constructor(
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
) {
const homeIndicator = environmentService.options?.homeIndicator;
if (homeIndicator) {
registerAction2(class extends Action2 {
constructor() {
super({
id: `workbench.actions.goHome`,
title: nls.localize('goHome', "Go Home"),
menu: { id: MenuId.MenubarWebNavigationMenu }
});
}
async run(): Promise<void> {
window.location.href = homeIndicator.href;
}
});
}
}
}
// --- Actions Registration
const actionsRegistry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateUpAction, undefined), 'View: Navigate to the View Above', CATEGORIES.View.value);
actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateDownAction, undefined), 'View: Navigate to the View Below', CATEGORIES.View.value);
actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateLeftAction, undefined), 'View: Navigate to the View on the Left', CATEGORIES.View.value);
actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateRightAction, undefined), 'View: Navigate to the View on the Right', CATEGORIES.View.value);
actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(FocusNextPart, { primary: KeyCode.F6 }), 'View: Focus Next Part', CATEGORIES.View.value);
actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(FocusPreviousPart, { primary: KeyMod.Shift | KeyCode.F6 }), 'View: Focus Previous Part', CATEGORIES.View.value);
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(GoHomeContributor, LifecyclePhase.Ready);

View File

@@ -0,0 +1,239 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { MenuRegistry, MenuId, Action2, registerAction2, ILocalizedString } from 'vs/platform/actions/common/actions';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { KeybindingsRegistry, KeybindingWeight, IKeybindingRule } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IQuickInputService, ItemActivation } from 'vs/platform/quickinput/common/quickInput';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { inQuickPickContext, defaultQuickAccessContext, getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess';
//#region Quick access management commands and keys
const globalQuickAccessKeybinding = {
primary: KeyMod.CtrlCmd | KeyCode.KEY_P,
secondary: [KeyMod.CtrlCmd | KeyCode.KEY_E],
mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_P, secondary: undefined }
};
const QUICKACCESS_ACTION_ID = 'workbench.action.quickOpen';
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: { id: QUICKACCESS_ACTION_ID, title: { value: localize('quickOpen', "Go to File..."), original: 'Go to File...' } }
});
KeybindingsRegistry.registerKeybindingRule({
id: QUICKACCESS_ACTION_ID,
weight: KeybindingWeight.WorkbenchContrib,
when: undefined,
primary: globalQuickAccessKeybinding.primary,
secondary: globalQuickAccessKeybinding.secondary,
mac: globalQuickAccessKeybinding.mac
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.closeQuickOpen',
weight: KeybindingWeight.WorkbenchContrib,
when: inQuickPickContext,
primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape],
handler: accessor => {
const quickInputService = accessor.get(IQuickInputService);
return quickInputService.cancel();
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.acceptSelectedQuickOpenItem',
weight: KeybindingWeight.WorkbenchContrib,
when: inQuickPickContext,
primary: 0,
handler: accessor => {
const quickInputService = accessor.get(IQuickInputService);
return quickInputService.accept();
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.alternativeAcceptSelectedQuickOpenItem',
weight: KeybindingWeight.WorkbenchContrib,
when: inQuickPickContext,
primary: 0,
handler: accessor => {
const quickInputService = accessor.get(IQuickInputService);
return quickInputService.accept({ ctrlCmd: true, alt: false });
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.focusQuickOpen',
weight: KeybindingWeight.WorkbenchContrib,
when: inQuickPickContext,
primary: 0,
handler: accessor => {
const quickInputService = accessor.get(IQuickInputService);
quickInputService.focus();
}
});
const quickAccessNavigateNextInFilePickerId = 'workbench.action.quickOpenNavigateNextInFilePicker';
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: quickAccessNavigateNextInFilePickerId,
weight: KeybindingWeight.WorkbenchContrib + 50,
handler: getQuickNavigateHandler(quickAccessNavigateNextInFilePickerId, true),
when: defaultQuickAccessContext,
primary: globalQuickAccessKeybinding.primary,
secondary: globalQuickAccessKeybinding.secondary,
mac: globalQuickAccessKeybinding.mac
});
const quickAccessNavigatePreviousInFilePickerId = 'workbench.action.quickOpenNavigatePreviousInFilePicker';
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: quickAccessNavigatePreviousInFilePickerId,
weight: KeybindingWeight.WorkbenchContrib + 50,
handler: getQuickNavigateHandler(quickAccessNavigatePreviousInFilePickerId, false),
when: defaultQuickAccessContext,
primary: globalQuickAccessKeybinding.primary | KeyMod.Shift,
secondary: [globalQuickAccessKeybinding.secondary[0] | KeyMod.Shift],
mac: {
primary: globalQuickAccessKeybinding.mac.primary | KeyMod.Shift,
secondary: undefined
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.quickPickManyToggle',
weight: KeybindingWeight.WorkbenchContrib,
when: inQuickPickContext,
primary: 0,
handler: accessor => {
const quickInputService = accessor.get(IQuickInputService);
quickInputService.toggle();
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.quickInputBack',
weight: KeybindingWeight.WorkbenchContrib + 50,
when: inQuickPickContext,
primary: 0,
win: { primary: KeyMod.Alt | KeyCode.LeftArrow },
mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS },
linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS },
handler: accessor => {
const quickInputService = accessor.get(IQuickInputService);
quickInputService.back();
}
});
CommandsRegistry.registerCommand({
id: QUICKACCESS_ACTION_ID,
handler: async function (accessor: ServicesAccessor, prefix: unknown) {
const quickInputService = accessor.get(IQuickInputService);
quickInputService.quickAccess.show(typeof prefix === 'string' ? prefix : undefined, { preserveValue: typeof prefix === 'string' /* preserve as is if provided */ });
},
description: {
description: `Quick access`,
args: [{
name: 'prefix',
schema: {
'type': 'string'
}
}]
}
});
CommandsRegistry.registerCommand('workbench.action.quickOpenPreviousEditor', async accessor => {
const quickInputService = accessor.get(IQuickInputService);
quickInputService.quickAccess.show('', { itemActivation: ItemActivation.SECOND });
});
//#endregion
//#region Workbench actions
class BaseQuickAccessNavigateAction extends Action2 {
constructor(
private id: string,
title: ILocalizedString,
private next: boolean,
private quickNavigate: boolean,
keybinding?: Omit<IKeybindingRule, 'id'>
) {
super({ id, title, f1: true, keybinding });
}
async run(accessor: ServicesAccessor): Promise<void> {
const keybindingService = accessor.get(IKeybindingService);
const quickInputService = accessor.get(IQuickInputService);
const keys = keybindingService.lookupKeybindings(this.id);
const quickNavigate = this.quickNavigate ? { keybindings: keys } : undefined;
quickInputService.navigate(this.next, quickNavigate);
}
}
class QuickAccessNavigateNextAction extends BaseQuickAccessNavigateAction {
constructor() {
super('workbench.action.quickOpenNavigateNext', { value: localize('quickNavigateNext', "Navigate Next in Quick Open"), original: 'Navigate Next in Quick Open' }, true, true);
}
}
class QuickAccessNavigatePreviousAction extends BaseQuickAccessNavigateAction {
constructor() {
super('workbench.action.quickOpenNavigatePrevious', { value: localize('quickNavigatePrevious', "Navigate Previous in Quick Open"), original: 'Navigate Previous in Quick Open' }, false, true);
}
}
class QuickAccessSelectNextAction extends BaseQuickAccessNavigateAction {
constructor() {
super(
'workbench.action.quickOpenSelectNext',
{ value: localize('quickSelectNext', "Select Next in Quick Open"), original: 'Select Next in Quick Open' },
true,
false,
{
weight: KeybindingWeight.WorkbenchContrib + 50,
when: inQuickPickContext,
primary: 0,
mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_N }
}
);
}
}
class QuickAccessSelectPreviousAction extends BaseQuickAccessNavigateAction {
constructor() {
super(
'workbench.action.quickOpenSelectPrevious',
{ value: localize('quickSelectPrevious', "Select Previous in Quick Open"), original: 'Select Previous in Quick Open' },
false,
false,
{
weight: KeybindingWeight.WorkbenchContrib + 50,
when: inQuickPickContext,
primary: 0,
mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_P }
}
);
}
}
registerAction2(QuickAccessSelectNextAction);
registerAction2(QuickAccessSelectPreviousAction);
registerAction2(QuickAccessNavigateNextAction);
registerAction2(QuickAccessNavigatePreviousAction);
//#endregion

View File

@@ -0,0 +1,99 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IAction, Action, Separator } from 'vs/base/common/actions';
import { localize } from 'vs/nls';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { Disposable } from 'vs/base/common/lifecycle';
import { EventHelper } from 'vs/base/browser/dom';
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { Registry } from 'vs/platform/registry/common/platform';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { isNative } from 'vs/base/common/platform';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
export class TextInputActionsProvider extends Disposable implements IWorkbenchContribution {
private textInputActions: IAction[] = [];
constructor(
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IClipboardService private readonly clipboardService: IClipboardService
) {
super();
this.createActions();
this.registerListeners();
}
private createActions(): void {
this.textInputActions.push(
// Undo/Redo
new Action('undo', localize('undo', "Undo"), undefined, true, async () => document.execCommand('undo')),
new Action('redo', localize('redo', "Redo"), undefined, true, async () => document.execCommand('redo')),
new Separator(),
// Cut / Copy / Paste
new Action('editor.action.clipboardCutAction', localize('cut', "Cut"), undefined, true, async () => document.execCommand('cut')),
new Action('editor.action.clipboardCopyAction', localize('copy', "Copy"), undefined, true, async () => document.execCommand('copy')),
new Action('editor.action.clipboardPasteAction', localize('paste', "Paste"), undefined, true, async (element: HTMLInputElement | HTMLTextAreaElement) => {
// Native: paste is supported
if (isNative) {
document.execCommand('paste');
}
// Web: paste is not supported due to security reasons
else {
const clipboardText = await this.clipboardService.readText();
if (
element instanceof HTMLTextAreaElement ||
element instanceof HTMLInputElement
) {
const selectionStart = element.selectionStart || 0;
const selectionEnd = element.selectionEnd || 0;
element.value = `${element.value.substring(0, selectionStart)}${clipboardText}${element.value.substring(selectionEnd, element.value.length)}`;
element.selectionStart = selectionStart + clipboardText.length;
element.selectionEnd = element.selectionStart;
}
}
}),
new Separator(),
// Select All
new Action('editor.action.selectAll', localize('selectAll', "Select All"), undefined, true, async () => document.execCommand('selectAll'))
);
}
private registerListeners(): void {
// Context menu support in input/textarea
this.layoutService.container.addEventListener('contextmenu', e => this.onContextMenu(e));
}
private onContextMenu(e: MouseEvent): void {
if (e.target instanceof HTMLElement) {
const target = <HTMLElement>e.target;
if (target.nodeName && (target.nodeName.toLowerCase() === 'input' || target.nodeName.toLowerCase() === 'textarea')) {
EventHelper.stop(e, true);
this.contextMenuService.showContextMenu({
getAnchor: () => e,
getActions: () => this.textInputActions,
getActionsContext: () => target,
onHide: () => target.focus() // fixes https://github.com/microsoft/vscode/issues/52948
});
}
}
}
}
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TextInputActionsProvider, LifecyclePhase.Ready);

View File

@@ -0,0 +1,467 @@
/*---------------------------------------------------------------------------------------------
* 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 { Action } from 'vs/base/common/actions';
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { SyncActionDescriptor, MenuRegistry, MenuId, Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { Registry } from 'vs/platform/registry/common/platform';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { IsFullscreenContext } from 'vs/workbench/browser/contextkeys';
import { IsMacNativeContext, IsDevelopmentContext, IsWebContext } from 'vs/platform/contextkey/common/contextkeys';
import { IWorkbenchActionRegistry, Extensions, CATEGORIES } from 'vs/workbench/common/actions';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IQuickInputButton, IQuickInputService, IQuickPickSeparator, IKeyMods, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ILabelService } from 'vs/platform/label/common/label';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IRecent, isRecentFolder, isRecentWorkspace, IWorkspacesService, IWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { URI } from 'vs/base/common/uri';
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { FileKind } from 'vs/platform/files/common/files';
import { splitName } from 'vs/base/common/labels';
import { isMacintosh } from 'vs/base/common/platform';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { inQuickPickContext, getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { ResourceMap } from 'vs/base/common/map';
import { Codicon } from 'vs/base/common/codicons';
import { isHTMLElement } from 'vs/base/browser/dom';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
export const inRecentFilesPickerContextKey = 'inRecentFilesPicker';
interface IRecentlyOpenedPick extends IQuickPickItem {
resource: URI,
openable: IWindowOpenable;
}
abstract class BaseOpenRecentAction extends Action {
private readonly removeFromRecentlyOpened: IQuickInputButton = {
iconClass: Codicon.removeClose.classNames,
tooltip: nls.localize('remove', "Remove from Recently Opened")
};
private readonly dirtyRecentlyOpened: IQuickInputButton = {
iconClass: 'dirty-workspace ' + Codicon.closeDirty.classNames,
tooltip: nls.localize('dirtyRecentlyOpened', "Workspace With Dirty Files"),
alwaysVisible: true
};
constructor(
id: string,
label: string,
private workspacesService: IWorkspacesService,
private quickInputService: IQuickInputService,
private contextService: IWorkspaceContextService,
private labelService: ILabelService,
private keybindingService: IKeybindingService,
private modelService: IModelService,
private modeService: IModeService,
private hostService: IHostService,
private dialogService: IDialogService
) {
super(id, label);
}
protected abstract isQuickNavigate(): boolean;
async run(): Promise<void> {
const recentlyOpened = await this.workspacesService.getRecentlyOpened();
const dirtyWorkspacesAndFolders = await this.workspacesService.getDirtyWorkspaces();
// Identify all folders and workspaces with dirty files
const dirtyFolders = new ResourceMap<boolean>();
const dirtyWorkspaces = new ResourceMap<IWorkspaceIdentifier>();
for (const dirtyWorkspace of dirtyWorkspacesAndFolders) {
if (URI.isUri(dirtyWorkspace)) {
dirtyFolders.set(dirtyWorkspace, true);
} else {
dirtyWorkspaces.set(dirtyWorkspace.configPath, dirtyWorkspace);
}
}
// Identify all recently opened folders and workspaces
const recentFolders = new ResourceMap<boolean>();
const recentWorkspaces = new ResourceMap<IWorkspaceIdentifier>();
for (const recent of recentlyOpened.workspaces) {
if (isRecentFolder(recent)) {
recentFolders.set(recent.folderUri, true);
} else {
recentWorkspaces.set(recent.workspace.configPath, recent.workspace);
}
}
// Fill in all known recently opened workspaces
const workspacePicks: IRecentlyOpenedPick[] = [];
for (const recent of recentlyOpened.workspaces) {
const isDirty = isRecentFolder(recent) ? dirtyFolders.has(recent.folderUri) : dirtyWorkspaces.has(recent.workspace.configPath);
workspacePicks.push(this.toQuickPick(recent, isDirty));
}
// Fill any backup workspace that is not yet shown at the end
for (const dirtyWorkspaceOrFolder of dirtyWorkspacesAndFolders) {
if (URI.isUri(dirtyWorkspaceOrFolder) && !recentFolders.has(dirtyWorkspaceOrFolder)) {
workspacePicks.push(this.toQuickPick({ folderUri: dirtyWorkspaceOrFolder }, true));
} else if (isWorkspaceIdentifier(dirtyWorkspaceOrFolder) && !recentWorkspaces.has(dirtyWorkspaceOrFolder.configPath)) {
workspacePicks.push(this.toQuickPick({ workspace: dirtyWorkspaceOrFolder }, true));
}
}
const filePicks = recentlyOpened.files.map(p => this.toQuickPick(p, false));
// focus second entry if the first recent workspace is the current workspace
const firstEntry = recentlyOpened.workspaces[0];
const autoFocusSecondEntry: boolean = firstEntry && this.contextService.isCurrentWorkspace(isRecentWorkspace(firstEntry) ? firstEntry.workspace : firstEntry.folderUri);
let keyMods: IKeyMods | undefined;
const workspaceSeparator: IQuickPickSeparator = { type: 'separator', label: nls.localize('workspaces', "workspaces") };
const fileSeparator: IQuickPickSeparator = { type: 'separator', label: nls.localize('files', "files") };
const picks = [workspaceSeparator, ...workspacePicks, fileSeparator, ...filePicks];
const pick = await this.quickInputService.pick(picks, {
contextKey: inRecentFilesPickerContextKey,
activeItem: [...workspacePicks, ...filePicks][autoFocusSecondEntry ? 1 : 0],
placeHolder: isMacintosh ? nls.localize('openRecentPlaceholderMac', "Select to open (hold Cmd-key to force new window or Alt-key for same window)") : nls.localize('openRecentPlaceholder', "Select to open (hold Ctrl-key to force new window or Alt-key for same window)"),
matchOnDescription: true,
onKeyMods: mods => keyMods = mods,
quickNavigate: this.isQuickNavigate() ? { keybindings: this.keybindingService.lookupKeybindings(this.id) } : undefined,
onDidTriggerItemButton: async context => {
// Remove
if (context.button === this.removeFromRecentlyOpened) {
await this.workspacesService.removeRecentlyOpened([context.item.resource]);
context.removeItem();
}
// Dirty Workspace
else if (context.button === this.dirtyRecentlyOpened) {
const result = await this.dialogService.confirm({
type: 'question',
title: nls.localize('dirtyWorkspace', "Workspace with Dirty Files"),
message: nls.localize('dirtyWorkspaceConfirm', "Do you want to open the workspace to review the dirty files?"),
detail: nls.localize('dirtyWorkspaceConfirmDetail', "Workspaces with dirty files cannot be removed until all dirty files have been saved or reverted.")
});
if (result.confirmed) {
this.hostService.openWindow([context.item.openable]);
this.quickInputService.cancel();
}
}
}
});
if (pick) {
return this.hostService.openWindow([pick.openable], { forceNewWindow: keyMods?.ctrlCmd, forceReuseWindow: keyMods?.alt });
}
}
private toQuickPick(recent: IRecent, isDirty: boolean): IRecentlyOpenedPick {
let openable: IWindowOpenable | undefined;
let iconClasses: string[];
let fullLabel: string | undefined;
let resource: URI | undefined;
// Folder
if (isRecentFolder(recent)) {
resource = recent.folderUri;
iconClasses = getIconClasses(this.modelService, this.modeService, resource, FileKind.FOLDER);
openable = { folderUri: resource };
fullLabel = recent.label || this.labelService.getWorkspaceLabel(resource, { verbose: true });
}
// Workspace
else if (isRecentWorkspace(recent)) {
resource = recent.workspace.configPath;
iconClasses = getIconClasses(this.modelService, this.modeService, resource, FileKind.ROOT_FOLDER);
openable = { workspaceUri: resource };
fullLabel = recent.label || this.labelService.getWorkspaceLabel(recent.workspace, { verbose: true });
}
// File
else {
resource = recent.fileUri;
iconClasses = getIconClasses(this.modelService, this.modeService, resource, FileKind.FILE);
openable = { fileUri: resource };
fullLabel = recent.label || this.labelService.getUriLabel(resource);
}
const { name, parentPath } = splitName(fullLabel);
return {
iconClasses,
label: name,
ariaLabel: isDirty ? nls.localize('recentDirtyAriaLabel', "{0}, dirty workspace", name) : name,
description: parentPath,
buttons: isDirty ? [this.dirtyRecentlyOpened] : [this.removeFromRecentlyOpened],
openable,
resource
};
}
}
export class OpenRecentAction extends BaseOpenRecentAction {
static readonly ID = 'workbench.action.openRecent';
static readonly LABEL = nls.localize('openRecent', "Open Recent...");
constructor(
id: string,
label: string,
@IWorkspacesService workspacesService: IWorkspacesService,
@IQuickInputService quickInputService: IQuickInputService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IKeybindingService keybindingService: IKeybindingService,
@IModelService modelService: IModelService,
@IModeService modeService: IModeService,
@ILabelService labelService: ILabelService,
@IHostService hostService: IHostService,
@IDialogService dialogService: IDialogService
) {
super(id, label, workspacesService, quickInputService, contextService, labelService, keybindingService, modelService, modeService, hostService, dialogService);
}
protected isQuickNavigate(): boolean {
return false;
}
}
class QuickPickRecentAction extends BaseOpenRecentAction {
static readonly ID = 'workbench.action.quickOpenRecent';
static readonly LABEL = nls.localize('quickOpenRecent', "Quick Open Recent...");
constructor(
id: string,
label: string,
@IWorkspacesService workspacesService: IWorkspacesService,
@IQuickInputService quickInputService: IQuickInputService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IKeybindingService keybindingService: IKeybindingService,
@IModelService modelService: IModelService,
@IModeService modeService: IModeService,
@ILabelService labelService: ILabelService,
@IHostService hostService: IHostService,
@IDialogService dialogService: IDialogService
) {
super(id, label, workspacesService, quickInputService, contextService, labelService, keybindingService, modelService, modeService, hostService, dialogService);
}
protected isQuickNavigate(): boolean {
return true;
}
}
class ToggleFullScreenAction extends Action {
static readonly ID = 'workbench.action.toggleFullScreen';
static readonly LABEL = nls.localize('toggleFullScreen', "Toggle Full Screen");
constructor(
id: string,
label: string,
@IHostService private readonly hostService: IHostService
) {
super(id, label);
}
run(): Promise<void> {
return this.hostService.toggleFullScreen();
}
}
export class ReloadWindowAction extends Action {
static readonly ID = 'workbench.action.reloadWindow';
static readonly LABEL = nls.localize('reloadWindow', "Reload Window");
constructor(
id: string,
label: string,
@IHostService private readonly hostService: IHostService
) {
super(id, label);
}
async run(): Promise<boolean> {
await this.hostService.reload();
return true;
}
}
class ShowAboutDialogAction extends Action {
static readonly ID = 'workbench.action.showAboutDialog';
static readonly LABEL = nls.localize('about', "About");
constructor(
id: string,
label: string,
@IDialogService private readonly dialogService: IDialogService
) {
super(id, label);
}
run(): Promise<void> {
return this.dialogService.about();
}
}
export class NewWindowAction extends Action {
static readonly ID = 'workbench.action.newWindow';
static readonly LABEL = nls.localize('newWindow', "New Window");
constructor(
id: string,
label: string,
@IHostService private readonly hostService: IHostService
) {
super(id, label);
}
run(): Promise<void> {
return this.hostService.openWindow();
}
}
class BlurAction extends Action2 {
constructor() {
super({
id: 'workbench.action.blur',
title: nls.localize('blur', "Remove keyboard focus from focused element")
});
}
run(): void {
const el = document.activeElement;
if (isHTMLElement(el)) {
el.blur();
}
}
}
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
// --- Actions Registration
const fileCategory = nls.localize('file', "File");
registry.registerWorkbenchAction(SyncActionDescriptor.from(NewWindowAction, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_N }), 'New Window');
registry.registerWorkbenchAction(SyncActionDescriptor.from(QuickPickRecentAction), 'File: Quick Open Recent...', fileCategory);
registry.registerWorkbenchAction(SyncActionDescriptor.from(OpenRecentAction, { primary: KeyMod.CtrlCmd | KeyCode.KEY_R, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_R } }), 'File: Open Recent...', fileCategory);
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleFullScreenAction, { primary: KeyCode.F11, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_F } }), 'View: Toggle Full Screen', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(ReloadWindowAction), 'Developer: Reload Window', CATEGORIES.Developer.value, IsWebContext.toNegated());
registry.registerWorkbenchAction(SyncActionDescriptor.from(ShowAboutDialogAction), `Help: About`, CATEGORIES.Help.value);
registerAction2(BlurAction);
// --- Commands/Keybindings Registration
const recentFilesPickerContext = ContextKeyExpr.and(inQuickPickContext, ContextKeyExpr.has(inRecentFilesPickerContextKey));
const quickPickNavigateNextInRecentFilesPickerId = 'workbench.action.quickOpenNavigateNextInRecentFilesPicker';
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: quickPickNavigateNextInRecentFilesPickerId,
weight: KeybindingWeight.WorkbenchContrib + 50,
handler: getQuickNavigateHandler(quickPickNavigateNextInRecentFilesPickerId, true),
when: recentFilesPickerContext,
primary: KeyMod.CtrlCmd | KeyCode.KEY_R,
mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_R }
});
const quickPickNavigatePreviousInRecentFilesPicker = 'workbench.action.quickOpenNavigatePreviousInRecentFilesPicker';
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: quickPickNavigatePreviousInRecentFilesPicker,
weight: KeybindingWeight.WorkbenchContrib + 50,
handler: getQuickNavigateHandler(quickPickNavigatePreviousInRecentFilesPicker, false),
when: recentFilesPickerContext,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_R,
mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_R }
});
KeybindingsRegistry.registerKeybindingRule({
id: ReloadWindowAction.ID,
weight: KeybindingWeight.WorkbenchContrib + 50,
when: IsDevelopmentContext,
primary: KeyMod.CtrlCmd | KeyCode.KEY_R
});
CommandsRegistry.registerCommand('workbench.action.toggleConfirmBeforeClose', accessor => {
const configurationService = accessor.get(IConfigurationService);
const setting = configurationService.inspect<'always' | 'keyboardOnly' | 'never'>('window.confirmBeforeClose').userValue;
return configurationService.updateValue('window.confirmBeforeClose', setting === 'never' ? 'keyboardOnly' : 'never', ConfigurationTarget.USER);
});
// --- Menu Registration
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
group: 'z_ConfirmClose',
command: {
id: 'workbench.action.toggleConfirmBeforeClose',
title: nls.localize('miConfirmClose', "Confirm Before Close"),
toggled: ContextKeyExpr.notEquals('config.window.confirmBeforeClose', 'never')
},
order: 1,
when: IsWebContext
});
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
group: '1_new',
command: {
id: NewWindowAction.ID,
title: nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window")
},
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
title: nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent"),
submenu: MenuId.MenubarRecentMenu,
group: '2_open',
order: 4
});
MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, {
group: 'y_more',
command: {
id: OpenRecentAction.ID,
title: nls.localize({ key: 'miMore', comment: ['&& denotes a mnemonic'] }, "&&More...")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '1_toggle_view',
command: {
id: ToggleFullScreenAction.ID,
title: nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "&&Full Screen"),
toggled: IsFullscreenContext
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: 'z_about',
command: {
id: ShowAboutDialogAction.ID,
title: nls.localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About")
},
order: 1,
when: IsMacNativeContext.toNegated()
});

View File

@@ -0,0 +1,316 @@
/*---------------------------------------------------------------------------------------------
* 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 { ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL, PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands';
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { MenuRegistry, MenuId, SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { EmptyWorkspaceSupportContext, WorkbenchStateContext, WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWorkspacesService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
export class OpenFileAction extends Action {
static readonly ID = 'workbench.action.files.openFile';
static readonly LABEL = nls.localize('openFile', "Open File...");
constructor(
id: string,
label: string,
@IFileDialogService private readonly dialogService: IFileDialogService
) {
super(id, label);
}
run(event?: unknown, data?: ITelemetryData): Promise<void> {
return this.dialogService.pickFileAndOpen({ forceNewWindow: false, telemetryExtraData: data });
}
}
export class OpenFolderAction extends Action {
static readonly ID = 'workbench.action.files.openFolder';
static readonly LABEL = nls.localize('openFolder', "Open Folder...");
constructor(
id: string,
label: string,
@IFileDialogService private readonly dialogService: IFileDialogService
) {
super(id, label);
}
run(event?: unknown, data?: ITelemetryData): Promise<void> {
return this.dialogService.pickFolderAndOpen({ forceNewWindow: false, telemetryExtraData: data });
}
}
export class OpenFileFolderAction extends Action {
static readonly ID = 'workbench.action.files.openFileFolder';
static readonly LABEL = nls.localize('openFileFolder', "Open...");
constructor(
id: string,
label: string,
@IFileDialogService private readonly dialogService: IFileDialogService
) {
super(id, label);
}
run(event?: unknown, data?: ITelemetryData): Promise<void> {
return this.dialogService.pickFileFolderAndOpen({ forceNewWindow: false, telemetryExtraData: data });
}
}
export class OpenWorkspaceAction extends Action {
static readonly ID = 'workbench.action.openWorkspace';
static readonly LABEL = nls.localize('openWorkspaceAction', "Open Workspace...");
constructor(
id: string,
label: string,
@IFileDialogService private readonly dialogService: IFileDialogService
) {
super(id, label);
}
run(event?: unknown, data?: ITelemetryData): Promise<void> {
return this.dialogService.pickWorkspaceAndOpen({ telemetryExtraData: data });
}
}
export class CloseWorkspaceAction extends Action {
static readonly ID = 'workbench.action.closeFolder';
static readonly LABEL = nls.localize('closeWorkspace', "Close Workspace");
constructor(
id: string,
label: string,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@INotificationService private readonly notificationService: INotificationService,
@IHostService private readonly hostService: IHostService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
) {
super(id, label);
}
async run(): Promise<void> {
if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
this.notificationService.info(nls.localize('noWorkspaceOpened', "There is currently no workspace opened in this instance to close."));
return;
}
return this.hostService.openWindow({ forceReuseWindow: true, remoteAuthority: this.environmentService.remoteAuthority });
}
}
export class OpenWorkspaceConfigFileAction extends Action {
static readonly ID = 'workbench.action.openWorkspaceConfigFile';
static readonly LABEL = nls.localize('openWorkspaceConfigFile', "Open Workspace Configuration File");
constructor(
id: string,
label: string,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IEditorService private readonly editorService: IEditorService
) {
super(id, label);
this.enabled = !!this.workspaceContextService.getWorkspace().configuration;
}
async run(): Promise<void> {
const configuration = this.workspaceContextService.getWorkspace().configuration;
if (configuration) {
await this.editorService.openEditor({ resource: configuration });
}
}
}
export class AddRootFolderAction extends Action {
static readonly ID = 'workbench.action.addRootFolder';
static readonly LABEL = ADD_ROOT_FOLDER_LABEL;
constructor(
id: string,
label: string,
@ICommandService private readonly commandService: ICommandService
) {
super(id, label);
}
run(): Promise<void> {
return this.commandService.executeCommand(ADD_ROOT_FOLDER_COMMAND_ID);
}
}
export class GlobalRemoveRootFolderAction extends Action {
static readonly ID = 'workbench.action.removeRootFolder';
static readonly LABEL = nls.localize('globalRemoveFolderFromWorkspace', "Remove Folder from Workspace...");
constructor(
id: string,
label: string,
@IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@ICommandService private readonly commandService: ICommandService
) {
super(id, label);
}
async run(): Promise<void> {
const state = this.contextService.getWorkbenchState();
// Workspace / Folder
if (state === WorkbenchState.WORKSPACE || state === WorkbenchState.FOLDER) {
const folder = await this.commandService.executeCommand<IWorkspaceFolder>(PICK_WORKSPACE_FOLDER_COMMAND_ID);
if (folder) {
await this.workspaceEditingService.removeFolders([folder.uri]);
}
}
}
}
export class SaveWorkspaceAsAction extends Action {
static readonly ID = 'workbench.action.saveWorkspaceAs';
static readonly LABEL = nls.localize('saveWorkspaceAsAction', "Save Workspace As...");
constructor(
id: string,
label: string,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService
) {
super(id, label);
}
async run(): Promise<void> {
const configPathUri = await this.workspaceEditingService.pickNewWorkspacePath();
if (configPathUri && hasWorkspaceFileExtension(configPathUri)) {
switch (this.contextService.getWorkbenchState()) {
case WorkbenchState.EMPTY:
case WorkbenchState.FOLDER:
const folders = this.contextService.getWorkspace().folders.map(folder => ({ uri: folder.uri }));
return this.workspaceEditingService.createAndEnterWorkspace(folders, configPathUri);
case WorkbenchState.WORKSPACE:
return this.workspaceEditingService.saveAndEnterWorkspace(configPathUri);
}
}
}
}
export class DuplicateWorkspaceInNewWindowAction extends Action {
static readonly ID = 'workbench.action.duplicateWorkspaceInNewWindow';
static readonly LABEL = nls.localize('duplicateWorkspaceInNewWindow', "Duplicate Workspace in New Window");
constructor(
id: string,
label: string,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService,
@IHostService private readonly hostService: IHostService,
@IWorkspacesService private readonly workspacesService: IWorkspacesService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
) {
super(id, label);
}
async run(): Promise<void> {
const folders = this.workspaceContextService.getWorkspace().folders;
const remoteAuthority = this.environmentService.remoteAuthority;
const newWorkspace = await this.workspacesService.createUntitledWorkspace(folders, remoteAuthority);
await this.workspaceEditingService.copyWorkspaceSettings(newWorkspace);
return this.hostService.openWindow([{ workspaceUri: newWorkspace.configPath }], { forceNewWindow: true });
}
}
// --- Actions Registration
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
const workspacesCategory = nls.localize('workspaces', "Workspaces");
registry.registerWorkbenchAction(SyncActionDescriptor.from(AddRootFolderAction), 'Workspaces: Add Folder to Workspace...', workspacesCategory);
registry.registerWorkbenchAction(SyncActionDescriptor.from(GlobalRemoveRootFolderAction), 'Workspaces: Remove Folder from Workspace...', workspacesCategory);
registry.registerWorkbenchAction(SyncActionDescriptor.from(CloseWorkspaceAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'Workspaces: Close Workspace', workspacesCategory, EmptyWorkspaceSupportContext);
registry.registerWorkbenchAction(SyncActionDescriptor.from(SaveWorkspaceAsAction), 'Workspaces: Save Workspace As...', workspacesCategory, EmptyWorkspaceSupportContext);
registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateWorkspaceInNewWindowAction), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory);
// --- Menu Registration
CommandsRegistry.registerCommand(OpenWorkspaceConfigFileAction.ID, serviceAccessor => {
serviceAccessor.get(IInstantiationService).createInstance(OpenWorkspaceConfigFileAction, OpenWorkspaceConfigFileAction.ID, OpenWorkspaceConfigFileAction.LABEL).run();
});
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
group: '3_workspace',
command: {
id: ADD_ROOT_FOLDER_COMMAND_ID,
title: nls.localize({ key: 'miAddFolderToWorkspace', comment: ['&& denotes a mnemonic'] }, "A&&dd Folder to Workspace...")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
group: '3_workspace',
command: {
id: SaveWorkspaceAsAction.ID,
title: nls.localize('miSaveWorkspaceAs', "Save Workspace As...")
},
order: 2,
when: EmptyWorkspaceSupportContext
});
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: OpenWorkspaceConfigFileAction.ID,
title: { value: `${workspacesCategory}: ${OpenWorkspaceConfigFileAction.LABEL}`, original: 'Workspaces: Open Workspace Configuration File' },
},
when: WorkbenchStateContext.isEqualTo('workspace')
});
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
group: '6_close',
command: {
id: CloseWorkspaceAction.ID,
title: nls.localize({ key: 'miCloseFolder', comment: ['&& denotes a mnemonic'] }, "Close &&Folder"),
precondition: WorkspaceFolderCountContext.notEqualsTo('0')
},
order: 3,
when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), EmptyWorkspaceSupportContext)
});
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
group: '6_close',
command: {
id: CloseWorkspaceAction.ID,
title: nls.localize({ key: 'miCloseWorkspace', comment: ['&& denotes a mnemonic'] }, "Close &&Workspace")
},
order: 3,
when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace'), EmptyWorkspaceSupportContext)
});

View File

@@ -0,0 +1,118 @@
/*---------------------------------------------------------------------------------------------
* 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 { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
import * as resources from 'vs/base/common/resources';
import { CancellationToken } from 'vs/base/common/cancellation';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { FileKind } from 'vs/platform/files/common/files';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
import { IQuickInputService, IPickOptions, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
export const ADD_ROOT_FOLDER_COMMAND_ID = 'addRootFolder';
export const ADD_ROOT_FOLDER_LABEL = nls.localize('addFolderToWorkspace', "Add Folder to Workspace...");
export const PICK_WORKSPACE_FOLDER_COMMAND_ID = '_workbench.pickWorkspaceFolder';
// Command registration
CommandsRegistry.registerCommand({
id: 'workbench.action.files.openFileFolderInNewWindow',
handler: (accessor: ServicesAccessor) => accessor.get(IFileDialogService).pickFileFolderAndOpen({ forceNewWindow: true })
});
CommandsRegistry.registerCommand({
id: '_files.pickFolderAndOpen',
handler: (accessor: ServicesAccessor, options: { forceNewWindow: boolean }) => accessor.get(IFileDialogService).pickFolderAndOpen(options)
});
CommandsRegistry.registerCommand({
id: 'workbench.action.files.openFolderInNewWindow',
handler: (accessor: ServicesAccessor) => accessor.get(IFileDialogService).pickFolderAndOpen({ forceNewWindow: true })
});
CommandsRegistry.registerCommand({
id: 'workbench.action.files.openFileInNewWindow',
handler: (accessor: ServicesAccessor) => accessor.get(IFileDialogService).pickFileAndOpen({ forceNewWindow: true })
});
CommandsRegistry.registerCommand({
id: 'workbench.action.openWorkspaceInNewWindow',
handler: (accessor: ServicesAccessor) => accessor.get(IFileDialogService).pickWorkspaceAndOpen({ forceNewWindow: true })
});
CommandsRegistry.registerCommand({
id: ADD_ROOT_FOLDER_COMMAND_ID,
handler: async (accessor) => {
const workspaceEditingService = accessor.get(IWorkspaceEditingService);
const dialogsService = accessor.get(IFileDialogService);
const folders = await dialogsService.showOpenDialog({
openLabel: mnemonicButtonLabel(nls.localize({ key: 'add', comment: ['&& denotes a mnemonic'] }, "&&Add")),
title: nls.localize('addFolderToWorkspaceTitle', "Add Folder to Workspace"),
canSelectFolders: true,
canSelectMany: true,
defaultUri: dialogsService.defaultFolderPath()
});
if (!folders || !folders.length) {
return;
}
await workspaceEditingService.addFolders(folders.map(folder => ({ uri: resources.removeTrailingPathSeparator(folder) })));
}
});
CommandsRegistry.registerCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, async function (accessor, args?: [IPickOptions<IQuickPickItem>, CancellationToken]) {
const quickInputService = accessor.get(IQuickInputService);
const labelService = accessor.get(ILabelService);
const contextService = accessor.get(IWorkspaceContextService);
const modelService = accessor.get(IModelService);
const modeService = accessor.get(IModeService);
const folders = contextService.getWorkspace().folders;
if (!folders.length) {
return;
}
const folderPicks: IQuickPickItem[] = folders.map(folder => {
return {
label: folder.name,
description: labelService.getUriLabel(resources.dirname(folder.uri), { relative: true }),
folder,
iconClasses: getIconClasses(modelService, modeService, folder.uri, FileKind.ROOT_FOLDER)
};
});
const options: IPickOptions<IQuickPickItem> = (args ? args[0] : undefined) || Object.create(null);
if (!options.activeItem) {
options.activeItem = folderPicks[0];
}
if (!options.placeHolder) {
options.placeHolder = nls.localize('workspaceFolderPickerPlaceholder', "Select workspace folder");
}
if (typeof options.matchOnDescription !== 'boolean') {
options.matchOnDescription = true;
}
const token: CancellationToken = (args ? args[1] : undefined) || CancellationToken.None;
const pick = await quickInputService.pick(folderPicks, options, token);
if (pick) {
return folders[folderPicks.indexOf(pick)];
}
return;
});