Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'
This commit is contained in:
337
lib/vscode/src/vs/workbench/browser/actions/developerActions.ts
Normal file
337
lib/vscode/src/vs/workbench/browser/actions/developerActions.ts
Normal 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.")
|
||||
},
|
||||
}
|
||||
});
|
||||
383
lib/vscode/src/vs/workbench/browser/actions/helpActions.ts
Normal file
383
lib/vscode/src/vs/workbench/browser/actions/helpActions.ts
Normal 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
|
||||
});
|
||||
}
|
||||
922
lib/vscode/src/vs/workbench/browser/actions/layoutActions.ts
Normal file
922
lib/vscode/src/vs/workbench/browser/actions/layoutActions.ts
Normal 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);
|
||||
796
lib/vscode/src/vs/workbench/browser/actions/listCommands.ts
Normal file
796
lib/vscode/src/vs/workbench/browser/actions/listCommands.ts
Normal 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;
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
313
lib/vscode/src/vs/workbench/browser/actions/navigationActions.ts
Normal file
313
lib/vscode/src/vs/workbench/browser/actions/navigationActions.ts
Normal 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);
|
||||
@@ -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
|
||||
@@ -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);
|
||||
467
lib/vscode/src/vs/workbench/browser/actions/windowActions.ts
Normal file
467
lib/vscode/src/vs/workbench/browser/actions/windowActions.ts
Normal 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()
|
||||
});
|
||||
316
lib/vscode/src/vs/workbench/browser/actions/workspaceActions.ts
Normal file
316
lib/vscode/src/vs/workbench/browser/actions/workspaceActions.ts
Normal 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)
|
||||
});
|
||||
118
lib/vscode/src/vs/workbench/browser/actions/workspaceCommands.ts
Normal file
118
lib/vscode/src/vs/workbench/browser/actions/workspaceCommands.ts
Normal 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;
|
||||
});
|
||||
Reference in New Issue
Block a user