feature: vor und nachteile

This commit is contained in:
Hikyu
2026-05-31 13:54:31 +02:00
parent 552a947a1c
commit e3dad17446
23270 changed files with 3024794 additions and 200 deletions
@@ -0,0 +1,109 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { addDisposableListener } from '../../../base/browser/dom.js';
import { mainWindow } from '../../../base/browser/window.js';
import { Emitter } from '../../../base/common/event.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from '../common/accessibility.js';
import { IConfigurationService } from '../../configuration/common/configuration.js';
import { IContextKeyService } from '../../contextkey/common/contextkey.js';
import { ILayoutService } from '../../layout/browser/layoutService.js';
let AccessibilityService = class AccessibilityService extends Disposable {
constructor(_contextKeyService, _layoutService, _configurationService) {
super();
this._contextKeyService = _contextKeyService;
this._layoutService = _layoutService;
this._configurationService = _configurationService;
this._accessibilitySupport = 0 /* AccessibilitySupport.Unknown */;
this._onDidChangeScreenReaderOptimized = new Emitter();
this._onDidChangeReducedMotion = new Emitter();
this._onDidChangeLinkUnderline = new Emitter();
this._accessibilityModeEnabledContext = CONTEXT_ACCESSIBILITY_MODE_ENABLED.bindTo(this._contextKeyService);
const updateContextKey = () => this._accessibilityModeEnabledContext.set(this.isScreenReaderOptimized());
this._register(this._configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('editor.accessibilitySupport')) {
updateContextKey();
this._onDidChangeScreenReaderOptimized.fire();
}
if (e.affectsConfiguration('workbench.reduceMotion')) {
this._configMotionReduced = this._configurationService.getValue('workbench.reduceMotion');
this._onDidChangeReducedMotion.fire();
}
}));
updateContextKey();
this._register(this.onDidChangeScreenReaderOptimized(() => updateContextKey()));
const reduceMotionMatcher = mainWindow.matchMedia(`(prefers-reduced-motion: reduce)`);
this._systemMotionReduced = reduceMotionMatcher.matches;
this._configMotionReduced = this._configurationService.getValue('workbench.reduceMotion');
this._linkUnderlinesEnabled = this._configurationService.getValue('accessibility.underlineLinks');
this.initReducedMotionListeners(reduceMotionMatcher);
this.initLinkUnderlineListeners();
}
initReducedMotionListeners(reduceMotionMatcher) {
this._register(addDisposableListener(reduceMotionMatcher, 'change', () => {
this._systemMotionReduced = reduceMotionMatcher.matches;
if (this._configMotionReduced === 'auto') {
this._onDidChangeReducedMotion.fire();
}
}));
const updateRootClasses = () => {
const reduce = this.isMotionReduced();
this._layoutService.mainContainer.classList.toggle('reduce-motion', reduce);
this._layoutService.mainContainer.classList.toggle('enable-motion', !reduce);
};
updateRootClasses();
this._register(this.onDidChangeReducedMotion(() => updateRootClasses()));
}
initLinkUnderlineListeners() {
this._register(this._configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('accessibility.underlineLinks')) {
const linkUnderlinesEnabled = this._configurationService.getValue('accessibility.underlineLinks');
this._linkUnderlinesEnabled = linkUnderlinesEnabled;
this._onDidChangeLinkUnderline.fire();
}
}));
const updateLinkUnderlineClasses = () => {
const underlineLinks = this._linkUnderlinesEnabled;
this._layoutService.mainContainer.classList.toggle('underline-links', underlineLinks);
};
updateLinkUnderlineClasses();
this._register(this.onDidChangeLinkUnderlines(() => updateLinkUnderlineClasses()));
}
onDidChangeLinkUnderlines(listener) {
return this._onDidChangeLinkUnderline.event(listener);
}
get onDidChangeScreenReaderOptimized() {
return this._onDidChangeScreenReaderOptimized.event;
}
isScreenReaderOptimized() {
const config = this._configurationService.getValue('editor.accessibilitySupport');
return config === 'on' || (config === 'auto' && this._accessibilitySupport === 2 /* AccessibilitySupport.Enabled */);
}
get onDidChangeReducedMotion() {
return this._onDidChangeReducedMotion.event;
}
isMotionReduced() {
const config = this._configMotionReduced;
return config === 'on' || (config === 'auto' && this._systemMotionReduced);
}
getAccessibilitySupport() {
return this._accessibilitySupport;
}
};
AccessibilityService = __decorate([
__param(0, IContextKeyService),
__param(1, ILayoutService),
__param(2, IConfigurationService)
], AccessibilityService);
export { AccessibilityService };
@@ -0,0 +1,23 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export const AccessibleViewRegistry = new class AccessibleViewRegistry {
constructor() {
this._implementations = [];
}
register(implementation) {
this._implementations.push(implementation);
return {
dispose: () => {
const idx = this._implementations.indexOf(implementation);
if (idx !== -1) {
this._implementations.splice(idx, 1);
}
}
};
}
getImplementations() {
return this._implementations;
}
};
@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { RawContextKey } from '../../contextkey/common/contextkey.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
export const IAccessibilityService = createDecorator('accessibilityService');
export const CONTEXT_ACCESSIBILITY_MODE_ENABLED = new RawContextKey('accessibilityModeEnabled', false);
@@ -0,0 +1,275 @@
import { localize } from '../../../nls.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
export const IAccessibilitySignalService = createDecorator('accessibilitySignalService');
/** Make sure you understand the doc comments of the method you want to call when using this token! */
export const AcknowledgeDocCommentsToken = Symbol('AcknowledgeDocCommentsToken');
/**
* Corresponds to the audio files in ./media.
*/
export class Sound {
static register(options) {
const sound = new Sound(options.fileName);
return sound;
}
static { this.error = Sound.register({ fileName: 'error.mp3' }); }
static { this.warning = Sound.register({ fileName: 'warning.mp3' }); }
static { this.success = Sound.register({ fileName: 'success.mp3' }); }
static { this.foldedArea = Sound.register({ fileName: 'foldedAreas.mp3' }); }
static { this.break = Sound.register({ fileName: 'break.mp3' }); }
static { this.quickFixes = Sound.register({ fileName: 'quickFixes.mp3' }); }
static { this.taskCompleted = Sound.register({ fileName: 'taskCompleted.mp3' }); }
static { this.taskFailed = Sound.register({ fileName: 'taskFailed.mp3' }); }
static { this.terminalBell = Sound.register({ fileName: 'terminalBell.mp3' }); }
static { this.diffLineInserted = Sound.register({ fileName: 'diffLineInserted.mp3' }); }
static { this.diffLineDeleted = Sound.register({ fileName: 'diffLineDeleted.mp3' }); }
static { this.diffLineModified = Sound.register({ fileName: 'diffLineModified.mp3' }); }
static { this.chatRequestSent = Sound.register({ fileName: 'chatRequestSent.mp3' }); }
static { this.chatResponseReceived1 = Sound.register({ fileName: 'chatResponseReceived1.mp3' }); }
static { this.chatResponseReceived2 = Sound.register({ fileName: 'chatResponseReceived2.mp3' }); }
static { this.chatResponseReceived3 = Sound.register({ fileName: 'chatResponseReceived3.mp3' }); }
static { this.chatResponseReceived4 = Sound.register({ fileName: 'chatResponseReceived4.mp3' }); }
static { this.clear = Sound.register({ fileName: 'clear.mp3' }); }
static { this.save = Sound.register({ fileName: 'save.mp3' }); }
static { this.format = Sound.register({ fileName: 'format.mp3' }); }
static { this.voiceRecordingStarted = Sound.register({ fileName: 'voiceRecordingStarted.mp3' }); }
static { this.voiceRecordingStopped = Sound.register({ fileName: 'voiceRecordingStopped.mp3' }); }
static { this.progress = Sound.register({ fileName: 'progress.mp3' }); }
constructor(fileName) {
this.fileName = fileName;
}
}
export class SoundSource {
constructor(randomOneOf) {
this.randomOneOf = randomOneOf;
}
}
export class AccessibilitySignal {
constructor(sound, name, legacySoundSettingsKey, settingsKey, legacyAnnouncementSettingsKey, announcementMessage) {
this.sound = sound;
this.name = name;
this.legacySoundSettingsKey = legacySoundSettingsKey;
this.settingsKey = settingsKey;
this.legacyAnnouncementSettingsKey = legacyAnnouncementSettingsKey;
this.announcementMessage = announcementMessage;
}
static { this._signals = new Set(); }
static register(options) {
const soundSource = new SoundSource('randomOneOf' in options.sound ? options.sound.randomOneOf : [options.sound]);
const signal = new AccessibilitySignal(soundSource, options.name, options.legacySoundSettingsKey, options.settingsKey, options.legacyAnnouncementSettingsKey, options.announcementMessage);
AccessibilitySignal._signals.add(signal);
return signal;
}
static { this.errorAtPosition = AccessibilitySignal.register({
name: localize('accessibilitySignals.positionHasError.name', 'Error at Position'),
sound: Sound.error,
announcementMessage: localize('accessibility.signals.positionHasError', 'Error'),
settingsKey: 'accessibility.signals.positionHasError',
delaySettingsKey: 'accessibility.signalOptions.delays.errorAtPosition'
}); }
static { this.warningAtPosition = AccessibilitySignal.register({
name: localize('accessibilitySignals.positionHasWarning.name', 'Warning at Position'),
sound: Sound.warning,
announcementMessage: localize('accessibility.signals.positionHasWarning', 'Warning'),
settingsKey: 'accessibility.signals.positionHasWarning',
delaySettingsKey: 'accessibility.signalOptions.delays.warningAtPosition'
}); }
static { this.errorOnLine = AccessibilitySignal.register({
name: localize('accessibilitySignals.lineHasError.name', 'Error on Line'),
sound: Sound.error,
legacySoundSettingsKey: 'audioCues.lineHasError',
legacyAnnouncementSettingsKey: 'accessibility.alert.error',
announcementMessage: localize('accessibility.signals.lineHasError', 'Error on Line'),
settingsKey: 'accessibility.signals.lineHasError',
}); }
static { this.warningOnLine = AccessibilitySignal.register({
name: localize('accessibilitySignals.lineHasWarning.name', 'Warning on Line'),
sound: Sound.warning,
legacySoundSettingsKey: 'audioCues.lineHasWarning',
legacyAnnouncementSettingsKey: 'accessibility.alert.warning',
announcementMessage: localize('accessibility.signals.lineHasWarning', 'Warning on Line'),
settingsKey: 'accessibility.signals.lineHasWarning',
}); }
static { this.foldedArea = AccessibilitySignal.register({
name: localize('accessibilitySignals.lineHasFoldedArea.name', 'Folded Area on Line'),
sound: Sound.foldedArea,
legacySoundSettingsKey: 'audioCues.lineHasFoldedArea',
legacyAnnouncementSettingsKey: 'accessibility.alert.foldedArea',
announcementMessage: localize('accessibility.signals.lineHasFoldedArea', 'Folded'),
settingsKey: 'accessibility.signals.lineHasFoldedArea',
}); }
static { this.break = AccessibilitySignal.register({
name: localize('accessibilitySignals.lineHasBreakpoint.name', 'Breakpoint on Line'),
sound: Sound.break,
legacySoundSettingsKey: 'audioCues.lineHasBreakpoint',
legacyAnnouncementSettingsKey: 'accessibility.alert.breakpoint',
announcementMessage: localize('accessibility.signals.lineHasBreakpoint', 'Breakpoint'),
settingsKey: 'accessibility.signals.lineHasBreakpoint',
}); }
static { this.inlineSuggestion = AccessibilitySignal.register({
name: localize('accessibilitySignals.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'),
sound: Sound.quickFixes,
legacySoundSettingsKey: 'audioCues.lineHasInlineSuggestion',
settingsKey: 'accessibility.signals.lineHasInlineSuggestion',
}); }
static { this.terminalQuickFix = AccessibilitySignal.register({
name: localize('accessibilitySignals.terminalQuickFix.name', 'Terminal Quick Fix'),
sound: Sound.quickFixes,
legacySoundSettingsKey: 'audioCues.terminalQuickFix',
legacyAnnouncementSettingsKey: 'accessibility.alert.terminalQuickFix',
announcementMessage: localize('accessibility.signals.terminalQuickFix', 'Quick Fix'),
settingsKey: 'accessibility.signals.terminalQuickFix',
}); }
static { this.onDebugBreak = AccessibilitySignal.register({
name: localize('accessibilitySignals.onDebugBreak.name', 'Debugger Stopped on Breakpoint'),
sound: Sound.break,
legacySoundSettingsKey: 'audioCues.onDebugBreak',
legacyAnnouncementSettingsKey: 'accessibility.alert.onDebugBreak',
announcementMessage: localize('accessibility.signals.onDebugBreak', 'Breakpoint'),
settingsKey: 'accessibility.signals.onDebugBreak',
}); }
static { this.noInlayHints = AccessibilitySignal.register({
name: localize('accessibilitySignals.noInlayHints', 'No Inlay Hints on Line'),
sound: Sound.error,
legacySoundSettingsKey: 'audioCues.noInlayHints',
legacyAnnouncementSettingsKey: 'accessibility.alert.noInlayHints',
announcementMessage: localize('accessibility.signals.noInlayHints', 'No Inlay Hints'),
settingsKey: 'accessibility.signals.noInlayHints',
}); }
static { this.taskCompleted = AccessibilitySignal.register({
name: localize('accessibilitySignals.taskCompleted', 'Task Completed'),
sound: Sound.taskCompleted,
legacySoundSettingsKey: 'audioCues.taskCompleted',
legacyAnnouncementSettingsKey: 'accessibility.alert.taskCompleted',
announcementMessage: localize('accessibility.signals.taskCompleted', 'Task Completed'),
settingsKey: 'accessibility.signals.taskCompleted',
}); }
static { this.taskFailed = AccessibilitySignal.register({
name: localize('accessibilitySignals.taskFailed', 'Task Failed'),
sound: Sound.taskFailed,
legacySoundSettingsKey: 'audioCues.taskFailed',
legacyAnnouncementSettingsKey: 'accessibility.alert.taskFailed',
announcementMessage: localize('accessibility.signals.taskFailed', 'Task Failed'),
settingsKey: 'accessibility.signals.taskFailed',
}); }
static { this.terminalCommandFailed = AccessibilitySignal.register({
name: localize('accessibilitySignals.terminalCommandFailed', 'Terminal Command Failed'),
sound: Sound.error,
legacySoundSettingsKey: 'audioCues.terminalCommandFailed',
legacyAnnouncementSettingsKey: 'accessibility.alert.terminalCommandFailed',
announcementMessage: localize('accessibility.signals.terminalCommandFailed', 'Command Failed'),
settingsKey: 'accessibility.signals.terminalCommandFailed',
}); }
static { this.terminalCommandSucceeded = AccessibilitySignal.register({
name: localize('accessibilitySignals.terminalCommandSucceeded', 'Terminal Command Succeeded'),
sound: Sound.success,
announcementMessage: localize('accessibility.signals.terminalCommandSucceeded', 'Command Succeeded'),
settingsKey: 'accessibility.signals.terminalCommandSucceeded',
}); }
static { this.terminalBell = AccessibilitySignal.register({
name: localize('accessibilitySignals.terminalBell', 'Terminal Bell'),
sound: Sound.terminalBell,
legacySoundSettingsKey: 'audioCues.terminalBell',
legacyAnnouncementSettingsKey: 'accessibility.alert.terminalBell',
announcementMessage: localize('accessibility.signals.terminalBell', 'Terminal Bell'),
settingsKey: 'accessibility.signals.terminalBell',
}); }
static { this.notebookCellCompleted = AccessibilitySignal.register({
name: localize('accessibilitySignals.notebookCellCompleted', 'Notebook Cell Completed'),
sound: Sound.taskCompleted,
legacySoundSettingsKey: 'audioCues.notebookCellCompleted',
legacyAnnouncementSettingsKey: 'accessibility.alert.notebookCellCompleted',
announcementMessage: localize('accessibility.signals.notebookCellCompleted', 'Notebook Cell Completed'),
settingsKey: 'accessibility.signals.notebookCellCompleted',
}); }
static { this.notebookCellFailed = AccessibilitySignal.register({
name: localize('accessibilitySignals.notebookCellFailed', 'Notebook Cell Failed'),
sound: Sound.taskFailed,
legacySoundSettingsKey: 'audioCues.notebookCellFailed',
legacyAnnouncementSettingsKey: 'accessibility.alert.notebookCellFailed',
announcementMessage: localize('accessibility.signals.notebookCellFailed', 'Notebook Cell Failed'),
settingsKey: 'accessibility.signals.notebookCellFailed',
}); }
static { this.diffLineInserted = AccessibilitySignal.register({
name: localize('accessibilitySignals.diffLineInserted', 'Diff Line Inserted'),
sound: Sound.diffLineInserted,
legacySoundSettingsKey: 'audioCues.diffLineInserted',
settingsKey: 'accessibility.signals.diffLineInserted',
}); }
static { this.diffLineDeleted = AccessibilitySignal.register({
name: localize('accessibilitySignals.diffLineDeleted', 'Diff Line Deleted'),
sound: Sound.diffLineDeleted,
legacySoundSettingsKey: 'audioCues.diffLineDeleted',
settingsKey: 'accessibility.signals.diffLineDeleted',
}); }
static { this.diffLineModified = AccessibilitySignal.register({
name: localize('accessibilitySignals.diffLineModified', 'Diff Line Modified'),
sound: Sound.diffLineModified,
legacySoundSettingsKey: 'audioCues.diffLineModified',
settingsKey: 'accessibility.signals.diffLineModified',
}); }
static { this.chatRequestSent = AccessibilitySignal.register({
name: localize('accessibilitySignals.chatRequestSent', 'Chat Request Sent'),
sound: Sound.chatRequestSent,
legacySoundSettingsKey: 'audioCues.chatRequestSent',
legacyAnnouncementSettingsKey: 'accessibility.alert.chatRequestSent',
announcementMessage: localize('accessibility.signals.chatRequestSent', 'Chat Request Sent'),
settingsKey: 'accessibility.signals.chatRequestSent',
}); }
static { this.chatResponseReceived = AccessibilitySignal.register({
name: localize('accessibilitySignals.chatResponseReceived', 'Chat Response Received'),
legacySoundSettingsKey: 'audioCues.chatResponseReceived',
sound: {
randomOneOf: [
Sound.chatResponseReceived1,
Sound.chatResponseReceived2,
Sound.chatResponseReceived3,
Sound.chatResponseReceived4
]
},
settingsKey: 'accessibility.signals.chatResponseReceived'
}); }
static { this.progress = AccessibilitySignal.register({
name: localize('accessibilitySignals.progress', 'Progress'),
sound: Sound.progress,
legacySoundSettingsKey: 'audioCues.chatResponsePending',
legacyAnnouncementSettingsKey: 'accessibility.alert.progress',
announcementMessage: localize('accessibility.signals.progress', 'Progress'),
settingsKey: 'accessibility.signals.progress'
}); }
static { this.clear = AccessibilitySignal.register({
name: localize('accessibilitySignals.clear', 'Clear'),
sound: Sound.clear,
legacySoundSettingsKey: 'audioCues.clear',
legacyAnnouncementSettingsKey: 'accessibility.alert.clear',
announcementMessage: localize('accessibility.signals.clear', 'Clear'),
settingsKey: 'accessibility.signals.clear'
}); }
static { this.save = AccessibilitySignal.register({
name: localize('accessibilitySignals.save', 'Save'),
sound: Sound.save,
legacySoundSettingsKey: 'audioCues.save',
legacyAnnouncementSettingsKey: 'accessibility.alert.save',
announcementMessage: localize('accessibility.signals.save', 'Save'),
settingsKey: 'accessibility.signals.save'
}); }
static { this.format = AccessibilitySignal.register({
name: localize('accessibilitySignals.format', 'Format'),
sound: Sound.format,
legacySoundSettingsKey: 'audioCues.format',
legacyAnnouncementSettingsKey: 'accessibility.alert.format',
announcementMessage: localize('accessibility.signals.format', 'Format'),
settingsKey: 'accessibility.signals.format'
}); }
static { this.voiceRecordingStarted = AccessibilitySignal.register({
name: localize('accessibilitySignals.voiceRecordingStarted', 'Voice Recording Started'),
sound: Sound.voiceRecordingStarted,
legacySoundSettingsKey: 'audioCues.voiceRecordingStarted',
settingsKey: 'accessibility.signals.voiceRecordingStarted'
}); }
static { this.voiceRecordingStopped = AccessibilitySignal.register({
name: localize('accessibilitySignals.voiceRecordingStopped', 'Voice Recording Stopped'),
sound: Sound.voiceRecordingStopped,
legacySoundSettingsKey: 'audioCues.voiceRecordingStopped',
settingsKey: 'accessibility.signals.voiceRecordingStopped'
}); }
}
@@ -0,0 +1,13 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function isLocalizedString(thing) {
return thing
&& typeof thing === 'object'
&& typeof thing.original === 'string'
&& typeof thing.value === 'string';
}
export function isICommandActionToggleInfo(thing) {
return thing ? thing.condition !== undefined : false;
}
@@ -0,0 +1,13 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize2 } from '../../../nls.js';
export const Categories = Object.freeze({
View: localize2('view', 'View'),
Help: localize2('help', 'Help'),
Test: localize2('test', 'Test'),
File: localize2('file', 'File'),
Preferences: localize2('preferences', 'Preferences'),
Developer: localize2({ key: 'developer', comment: ['A developer on Code itself or someone diagnosing issues in Code'] }, "Developer"),
});
@@ -0,0 +1,272 @@
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as dom from '../../../base/browser/dom.js';
import { KeybindingLabel } from '../../../base/browser/ui/keybindingLabel/keybindingLabel.js';
import { List } from '../../../base/browser/ui/list/listWidget.js';
import { CancellationTokenSource } from '../../../base/common/cancellation.js';
import { Codicon } from '../../../base/common/codicons.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { OS } from '../../../base/common/platform.js';
import { ThemeIcon } from '../../../base/common/themables.js';
import './actionWidget.css';
import { localize } from '../../../nls.js';
import { IContextViewService } from '../../contextview/browser/contextView.js';
import { IKeybindingService } from '../../keybinding/common/keybinding.js';
import { defaultListStyles } from '../../theme/browser/defaultStyles.js';
import { asCssVariable } from '../../theme/common/colorRegistry.js';
export const acceptSelectedActionCommand = 'acceptSelectedCodeAction';
export const previewSelectedActionCommand = 'previewSelectedCodeAction';
class HeaderRenderer {
get templateId() { return "header" /* ActionListItemKind.Header */; }
renderTemplate(container) {
container.classList.add('group-header');
const text = document.createElement('span');
container.append(text);
return { container, text };
}
renderElement(element, _index, templateData) {
templateData.text.textContent = element.group?.title ?? '';
}
disposeTemplate(_templateData) {
// noop
}
}
let ActionItemRenderer = class ActionItemRenderer {
get templateId() { return "action" /* ActionListItemKind.Action */; }
constructor(_supportsPreview, _keybindingService) {
this._supportsPreview = _supportsPreview;
this._keybindingService = _keybindingService;
}
renderTemplate(container) {
container.classList.add(this.templateId);
const icon = document.createElement('div');
icon.className = 'icon';
container.append(icon);
const text = document.createElement('span');
text.className = 'title';
container.append(text);
const keybinding = new KeybindingLabel(container, OS);
return { container, icon, text, keybinding };
}
renderElement(element, _index, data) {
if (element.group?.icon) {
data.icon.className = ThemeIcon.asClassName(element.group.icon);
if (element.group.icon.color) {
data.icon.style.color = asCssVariable(element.group.icon.color.id);
}
}
else {
data.icon.className = ThemeIcon.asClassName(Codicon.lightBulb);
data.icon.style.color = 'var(--vscode-editorLightBulb-foreground)';
}
if (!element.item || !element.label) {
return;
}
data.text.textContent = stripNewlines(element.label);
data.keybinding.set(element.keybinding);
dom.setVisibility(!!element.keybinding, data.keybinding.element);
const actionTitle = this._keybindingService.lookupKeybinding(acceptSelectedActionCommand)?.getLabel();
const previewTitle = this._keybindingService.lookupKeybinding(previewSelectedActionCommand)?.getLabel();
data.container.classList.toggle('option-disabled', element.disabled);
if (element.disabled) {
data.container.title = element.label;
}
else if (actionTitle && previewTitle) {
if (this._supportsPreview && element.canPreview) {
data.container.title = localize({ key: 'label-preview', comment: ['placeholders are keybindings, e.g "F2 to Apply, Shift+F2 to Preview"'] }, "{0} to Apply, {1} to Preview", actionTitle, previewTitle);
}
else {
data.container.title = localize({ key: 'label', comment: ['placeholder is a keybinding, e.g "F2 to Apply"'] }, "{0} to Apply", actionTitle);
}
}
else {
data.container.title = '';
}
}
disposeTemplate(_templateData) {
_templateData.keybinding.dispose();
}
};
ActionItemRenderer = __decorate([
__param(1, IKeybindingService)
], ActionItemRenderer);
class AcceptSelectedEvent extends UIEvent {
constructor() { super('acceptSelectedAction'); }
}
class PreviewSelectedEvent extends UIEvent {
constructor() { super('previewSelectedAction'); }
}
function getKeyboardNavigationLabel(item) {
// Filter out header vs. action
if (item.kind === 'action') {
return item.label;
}
return undefined;
}
let ActionList = class ActionList extends Disposable {
constructor(user, preview, items, _delegate, _contextViewService, _keybindingService) {
super();
this._delegate = _delegate;
this._contextViewService = _contextViewService;
this._keybindingService = _keybindingService;
this._actionLineHeight = 24;
this._headerLineHeight = 26;
this.cts = this._register(new CancellationTokenSource());
this.domNode = document.createElement('div');
this.domNode.classList.add('actionList');
const virtualDelegate = {
getHeight: element => element.kind === "header" /* ActionListItemKind.Header */ ? this._headerLineHeight : this._actionLineHeight,
getTemplateId: element => element.kind
};
this._list = this._register(new List(user, this.domNode, virtualDelegate, [
new ActionItemRenderer(preview, this._keybindingService),
new HeaderRenderer(),
], {
keyboardSupport: false,
typeNavigationEnabled: true,
keyboardNavigationLabelProvider: { getKeyboardNavigationLabel },
accessibilityProvider: {
getAriaLabel: element => {
if (element.kind === "action" /* ActionListItemKind.Action */) {
let label = element.label ? stripNewlines(element?.label) : '';
if (element.disabled) {
label = localize({ key: 'customQuickFixWidget.labels', comment: [`Action widget labels for accessibility.`] }, "{0}, Disabled Reason: {1}", label, element.disabled);
}
return label;
}
return null;
},
getWidgetAriaLabel: () => localize({ key: 'customQuickFixWidget', comment: [`An action widget option`] }, "Action Widget"),
getRole: (e) => e.kind === "action" /* ActionListItemKind.Action */ ? 'option' : 'separator',
getWidgetRole: () => 'listbox',
},
}));
this._list.style(defaultListStyles);
this._register(this._list.onMouseClick(e => this.onListClick(e)));
this._register(this._list.onMouseOver(e => this.onListHover(e)));
this._register(this._list.onDidChangeFocus(() => this.onFocus()));
this._register(this._list.onDidChangeSelection(e => this.onListSelection(e)));
this._allMenuItems = items;
this._list.splice(0, this._list.length, this._allMenuItems);
if (this._list.length) {
this.focusNext();
}
}
focusCondition(element) {
return !element.disabled && element.kind === "action" /* ActionListItemKind.Action */;
}
hide(didCancel) {
this._delegate.onHide(didCancel);
this.cts.cancel();
this._contextViewService.hideContextView();
}
layout(minWidth) {
// Updating list height, depending on how many separators and headers there are.
const numHeaders = this._allMenuItems.filter(item => item.kind === 'header').length;
const itemsHeight = this._allMenuItems.length * this._actionLineHeight;
const heightWithHeaders = itemsHeight + numHeaders * this._headerLineHeight - numHeaders * this._actionLineHeight;
this._list.layout(heightWithHeaders);
let maxWidth = minWidth;
if (this._allMenuItems.length >= 50) {
maxWidth = 380;
}
else {
// For finding width dynamically (not using resize observer)
const itemWidths = this._allMenuItems.map((_, index) => {
const element = this.domNode.ownerDocument.getElementById(this._list.getElementID(index));
if (element) {
element.style.width = 'auto';
const width = element.getBoundingClientRect().width;
element.style.width = '';
return width;
}
return 0;
});
// resize observer - can be used in the future since list widget supports dynamic height but not width
maxWidth = Math.max(...itemWidths, minWidth);
}
const maxVhPrecentage = 0.7;
const height = Math.min(heightWithHeaders, this.domNode.ownerDocument.body.clientHeight * maxVhPrecentage);
this._list.layout(height, maxWidth);
this.domNode.style.height = `${height}px`;
this._list.domFocus();
return maxWidth;
}
focusPrevious() {
this._list.focusPrevious(1, true, undefined, this.focusCondition);
}
focusNext() {
this._list.focusNext(1, true, undefined, this.focusCondition);
}
acceptSelected(preview) {
const focused = this._list.getFocus();
if (focused.length === 0) {
return;
}
const focusIndex = focused[0];
const element = this._list.element(focusIndex);
if (!this.focusCondition(element)) {
return;
}
const event = preview ? new PreviewSelectedEvent() : new AcceptSelectedEvent();
this._list.setSelection([focusIndex], event);
}
onListSelection(e) {
if (!e.elements.length) {
return;
}
const element = e.elements[0];
if (element.item && this.focusCondition(element)) {
this._delegate.onSelect(element.item, e.browserEvent instanceof PreviewSelectedEvent);
}
else {
this._list.setSelection([]);
}
}
onFocus() {
const focused = this._list.getFocus();
if (focused.length === 0) {
return;
}
const focusIndex = focused[0];
const element = this._list.element(focusIndex);
this._delegate.onFocus?.(element.item);
}
async onListHover(e) {
const element = e.element;
if (element && element.item && this.focusCondition(element)) {
if (this._delegate.onHover && !element.disabled && element.kind === "action" /* ActionListItemKind.Action */) {
const result = await this._delegate.onHover(element.item, this.cts.token);
element.canPreview = result ? result.canPreview : undefined;
}
if (e.index) {
this._list.splice(e.index, 1, [element]);
}
}
this._list.setFocus(typeof e.index === 'number' ? [e.index] : []);
}
onListClick(e) {
if (e.element && this.focusCondition(e.element)) {
this._list.setFocus([]);
}
}
};
ActionList = __decorate([
__param(4, IContextViewService),
__param(5, IKeybindingService)
], ActionList);
export { ActionList };
function stripNewlines(str) {
return str.replace(/\r\n|\r|\n/g, ' ');
}
@@ -0,0 +1,161 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.action-widget {
font-size: 13px;
border-radius: 0;
min-width: 160px;
max-width: 80vw;
z-index: 40;
display: block;
width: 100%;
border: 1px solid var(--vscode-editorWidget-border) !important;
border-radius: 2px;
background-color: var(--vscode-editorWidget-background);
color: var(--vscode-editorWidget-foreground);
}
.context-view-block {
position: fixed;
cursor: initial;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: -1;
}
.context-view-pointerBlock {
position: fixed;
cursor: initial;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 2;
}
.action-widget .monaco-list {
user-select: none;
-webkit-user-select: none;
border: none !important;
border-width: 0 !important;
}
.action-widget .monaco-list:focus:before {
outline: 0 !important;
}
.action-widget .monaco-list .monaco-scrollable-element {
overflow: visible;
}
/** Styles for each row in the list element **/
.action-widget .monaco-list .monaco-list-row {
padding: 0 10px;
white-space: nowrap;
cursor: pointer;
touch-action: none;
width: 100%;
}
.action-widget .monaco-list .monaco-list-row.action.focused:not(.option-disabled) {
background-color: var(--vscode-quickInputList-focusBackground) !important;
color: var(--vscode-quickInputList-focusForeground);
outline: 1px solid var(--vscode-menu-selectionBorder, transparent);
outline-offset: -1px;
}
.action-widget .monaco-list-row.group-header {
color: var(--vscode-descriptionForeground) !important;
font-weight: 600;
}
.action-widget .monaco-list .group-header,
.action-widget .monaco-list .option-disabled,
.action-widget .monaco-list .option-disabled:before,
.action-widget .monaco-list .option-disabled .focused,
.action-widget .monaco-list .option-disabled .focused:before {
cursor: default !important;
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
background-color: transparent !important;
outline: 0 solid !important;
}
.action-widget .monaco-list-row.action {
display: flex;
gap: 6px;
align-items: center;
}
.action-widget .monaco-list-row.action.option-disabled,
.action-widget .monaco-list:focus .monaco-list-row.focused.action.option-disabled,
.action-widget .monaco-list-row.action.option-disabled .codicon,
.action-widget .monaco-list:not(.drop-target):not(.dragging) .monaco-list-row:hover:not(.selected):not(.focused).option-disabled {
color: var(--vscode-disabledForeground);
}
.action-widget .monaco-list-row.action:not(.option-disabled) .codicon {
color: inherit;
}
.action-widget .monaco-list-row.action .title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
}
.action-widget .monaco-list-row.action .monaco-keybinding > .monaco-keybinding-key {
background-color: var(--vscode-keybindingLabel-background);
color: var(--vscode-keybindingLabel-foreground);
border-style: solid;
border-width: 1px;
border-radius: 3px;
border-color: var(--vscode-keybindingLabel-border);
border-bottom-color: var(--vscode-keybindingLabel-bottomBorder);
box-shadow: inset 0 -1px 0 var(--vscode-widget-shadow);
}
/* Action bar */
.action-widget .action-widget-action-bar {
background-color: var(--vscode-editorHoverWidget-statusBarBackground);
border-top: 1px solid var(--vscode-editorHoverWidget-border);
}
.action-widget .action-widget-action-bar::before {
display: block;
content: "";
width: 100%;
}
.action-widget .action-widget-action-bar .actions-container {
padding: 0 8px;
}
.action-widget-action-bar .action-label {
color: var(--vscode-textLink-activeForeground);
font-size: 12px;
line-height: 22px;
padding: 0;
pointer-events: all;
}
.action-widget-action-bar .action-item {
margin-right: 16px;
pointer-events: none;
}
.action-widget-action-bar .action-label:hover {
background-color: transparent !important;
}
.monaco-action-bar .actions-container.highlight-toggled .action-label.checked {
/* The important gives this rule precedence over the hover rule. */
background: var(--vscode-actionBar-toggledBackground) !important;
}
@@ -0,0 +1,227 @@
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as dom from '../../../base/browser/dom.js';
import { ActionBar } from '../../../base/browser/ui/actionbar/actionbar.js';
import { Disposable, DisposableStore, MutableDisposable } from '../../../base/common/lifecycle.js';
import './actionWidget.css';
import { localize, localize2 } from '../../../nls.js';
import { acceptSelectedActionCommand, ActionList, previewSelectedActionCommand } from './actionList.js';
import { Action2, registerAction2 } from '../../actions/common/actions.js';
import { IContextKeyService, RawContextKey } from '../../contextkey/common/contextkey.js';
import { IContextViewService } from '../../contextview/browser/contextView.js';
import { registerSingleton } from '../../instantiation/common/extensions.js';
import { createDecorator, IInstantiationService } from '../../instantiation/common/instantiation.js';
import { inputActiveOptionBackground, registerColor } from '../../theme/common/colorRegistry.js';
registerColor('actionBar.toggledBackground', inputActiveOptionBackground, localize('actionBar.toggledBackground', 'Background color for toggled action items in action bar.'));
const ActionWidgetContextKeys = {
Visible: new RawContextKey('codeActionMenuVisible', false, localize('codeActionMenuVisible', "Whether the action widget list is visible"))
};
export const IActionWidgetService = createDecorator('actionWidgetService');
let ActionWidgetService = class ActionWidgetService extends Disposable {
get isVisible() {
return ActionWidgetContextKeys.Visible.getValue(this._contextKeyService) || false;
}
constructor(_contextViewService, _contextKeyService, _instantiationService) {
super();
this._contextViewService = _contextViewService;
this._contextKeyService = _contextKeyService;
this._instantiationService = _instantiationService;
this._list = this._register(new MutableDisposable());
}
show(user, supportsPreview, items, delegate, anchor, container, actionBarActions) {
const visibleContext = ActionWidgetContextKeys.Visible.bindTo(this._contextKeyService);
const list = this._instantiationService.createInstance(ActionList, user, supportsPreview, items, delegate);
this._contextViewService.showContextView({
getAnchor: () => anchor,
render: (container) => {
visibleContext.set(true);
return this._renderWidget(container, list, actionBarActions ?? []);
},
onHide: (didCancel) => {
visibleContext.reset();
this._onWidgetClosed(didCancel);
},
}, container, false);
}
acceptSelected(preview) {
this._list.value?.acceptSelected(preview);
}
focusPrevious() {
this._list?.value?.focusPrevious();
}
focusNext() {
this._list?.value?.focusNext();
}
hide(didCancel) {
this._list.value?.hide(didCancel);
this._list.clear();
}
_renderWidget(element, list, actionBarActions) {
const widget = document.createElement('div');
widget.classList.add('action-widget');
element.appendChild(widget);
this._list.value = list;
if (this._list.value) {
widget.appendChild(this._list.value.domNode);
}
else {
throw new Error('List has no value');
}
const renderDisposables = new DisposableStore();
// Invisible div to block mouse interaction in the rest of the UI
const menuBlock = document.createElement('div');
const block = element.appendChild(menuBlock);
block.classList.add('context-view-block');
renderDisposables.add(dom.addDisposableListener(block, dom.EventType.MOUSE_DOWN, e => e.stopPropagation()));
// Invisible div to block mouse interaction with the menu
const pointerBlockDiv = document.createElement('div');
const pointerBlock = element.appendChild(pointerBlockDiv);
pointerBlock.classList.add('context-view-pointerBlock');
// Removes block on click INSIDE widget or ANY mouse movement
renderDisposables.add(dom.addDisposableListener(pointerBlock, dom.EventType.POINTER_MOVE, () => pointerBlock.remove()));
renderDisposables.add(dom.addDisposableListener(pointerBlock, dom.EventType.MOUSE_DOWN, () => pointerBlock.remove()));
// Action bar
let actionBarWidth = 0;
if (actionBarActions.length) {
const actionBar = this._createActionBar('.action-widget-action-bar', actionBarActions);
if (actionBar) {
widget.appendChild(actionBar.getContainer().parentElement);
renderDisposables.add(actionBar);
actionBarWidth = actionBar.getContainer().offsetWidth;
}
}
const width = this._list.value?.layout(actionBarWidth);
widget.style.width = `${width}px`;
const focusTracker = renderDisposables.add(dom.trackFocus(element));
renderDisposables.add(focusTracker.onDidBlur(() => this.hide(true)));
return renderDisposables;
}
_createActionBar(className, actions) {
if (!actions.length) {
return undefined;
}
const container = dom.$(className);
const actionBar = new ActionBar(container);
actionBar.push(actions, { icon: false, label: true });
return actionBar;
}
_onWidgetClosed(didCancel) {
this._list.value?.hide(didCancel);
}
};
ActionWidgetService = __decorate([
__param(0, IContextViewService),
__param(1, IContextKeyService),
__param(2, IInstantiationService)
], ActionWidgetService);
registerSingleton(IActionWidgetService, ActionWidgetService, 1 /* InstantiationType.Delayed */);
const weight = 100 /* KeybindingWeight.EditorContrib */ + 1000;
registerAction2(class extends Action2 {
constructor() {
super({
id: 'hideCodeActionWidget',
title: localize2('hideCodeActionWidget.title', "Hide action widget"),
precondition: ActionWidgetContextKeys.Visible,
keybinding: {
weight,
primary: 9 /* KeyCode.Escape */,
secondary: [1024 /* KeyMod.Shift */ | 9 /* KeyCode.Escape */]
},
});
}
run(accessor) {
accessor.get(IActionWidgetService).hide(true);
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: 'selectPrevCodeAction',
title: localize2('selectPrevCodeAction.title', "Select previous action"),
precondition: ActionWidgetContextKeys.Visible,
keybinding: {
weight,
primary: 16 /* KeyCode.UpArrow */,
secondary: [2048 /* KeyMod.CtrlCmd */ | 16 /* KeyCode.UpArrow */],
mac: { primary: 16 /* KeyCode.UpArrow */, secondary: [2048 /* KeyMod.CtrlCmd */ | 16 /* KeyCode.UpArrow */, 256 /* KeyMod.WinCtrl */ | 46 /* KeyCode.KeyP */] },
}
});
}
run(accessor) {
const widgetService = accessor.get(IActionWidgetService);
if (widgetService instanceof ActionWidgetService) {
widgetService.focusPrevious();
}
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: 'selectNextCodeAction',
title: localize2('selectNextCodeAction.title', "Select next action"),
precondition: ActionWidgetContextKeys.Visible,
keybinding: {
weight,
primary: 18 /* KeyCode.DownArrow */,
secondary: [2048 /* KeyMod.CtrlCmd */ | 18 /* KeyCode.DownArrow */],
mac: { primary: 18 /* KeyCode.DownArrow */, secondary: [2048 /* KeyMod.CtrlCmd */ | 18 /* KeyCode.DownArrow */, 256 /* KeyMod.WinCtrl */ | 44 /* KeyCode.KeyN */] }
}
});
}
run(accessor) {
const widgetService = accessor.get(IActionWidgetService);
if (widgetService instanceof ActionWidgetService) {
widgetService.focusNext();
}
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: acceptSelectedActionCommand,
title: localize2('acceptSelected.title', "Accept selected action"),
precondition: ActionWidgetContextKeys.Visible,
keybinding: {
weight,
primary: 3 /* KeyCode.Enter */,
secondary: [2048 /* KeyMod.CtrlCmd */ | 89 /* KeyCode.Period */],
}
});
}
run(accessor) {
const widgetService = accessor.get(IActionWidgetService);
if (widgetService instanceof ActionWidgetService) {
widgetService.acceptSelected();
}
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: previewSelectedActionCommand,
title: localize2('previewSelected.title', "Preview selected action"),
precondition: ActionWidgetContextKeys.Visible,
keybinding: {
weight,
primary: 2048 /* KeyMod.CtrlCmd */ | 3 /* KeyCode.Enter */,
}
});
}
run(accessor) {
const widgetService = accessor.get(IActionWidgetService);
if (widgetService instanceof ActionWidgetService) {
widgetService.acceptSelected(true);
}
}
});
@@ -0,0 +1,5 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export {};
@@ -0,0 +1,63 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-action-bar .action-item.menu-entry .action-label.icon {
width: 16px;
height: 16px;
background-repeat: no-repeat;
background-position: 50%;
background-size: 16px;
}
.monaco-action-bar .action-item.menu-entry.text-only .action-label {
color: var(--vscode-descriptionForeground);
overflow: hidden;
border-radius: 2px;
}
.monaco-action-bar .action-item.menu-entry.text-only.use-comma:not(:last-of-type) .action-label::after {
content: ', ';
}
.monaco-action-bar .action-item.menu-entry.text-only + .action-item:not(.text-only) > .monaco-dropdown .action-label {
color: var(--vscode-descriptionForeground);
}
.monaco-dropdown-with-default {
display: flex !important;
flex-direction: row;
border-radius: 5px;
}
.monaco-dropdown-with-default > .action-container > .action-label {
margin-right: 0;
}
.monaco-dropdown-with-default > .action-container.menu-entry > .action-label.icon {
width: 16px;
height: 16px;
background-repeat: no-repeat;
background-position: 50%;
background-size: 16px;
}
.monaco-dropdown-with-default:hover {
background-color: var(--vscode-toolbar-hoverBackground);
}
.monaco-dropdown-with-default > .dropdown-action-container > .monaco-dropdown > .dropdown-label .codicon[class*='codicon-'] {
font-size: 12px;
padding-left: 0px;
padding-right: 0px;
line-height: 16px;
margin-left: -3px;
}
.monaco-dropdown-with-default > .dropdown-action-container > .monaco-dropdown > .dropdown-label > .action-label {
display: block;
background-size: 16px;
background-position: center center;
background-repeat: no-repeat;
}
@@ -0,0 +1,517 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { $, addDisposableListener, append, asCSSUrl, EventType, ModifierKeyEmitter, prepend } from '../../../base/browser/dom.js';
import { StandardKeyboardEvent } from '../../../base/browser/keyboardEvent.js';
import { ActionViewItem, BaseActionViewItem, SelectActionViewItem } from '../../../base/browser/ui/actionbar/actionViewItems.js';
import { DropdownMenuActionViewItem } from '../../../base/browser/ui/dropdown/dropdownActionViewItem.js';
import { ActionRunner, Separator, SubmenuAction } from '../../../base/common/actions.js';
import { UILabelProvider } from '../../../base/common/keybindingLabels.js';
import { combinedDisposable, MutableDisposable, toDisposable } from '../../../base/common/lifecycle.js';
import { isLinux, isWindows, OS } from '../../../base/common/platform.js';
import './menuEntryActionViewItem.css';
import { localize } from '../../../nls.js';
import { IMenuService, MenuItemAction, SubmenuItemAction } from '../common/actions.js';
import { isICommandActionToggleInfo } from '../../action/common/action.js';
import { IContextKeyService } from '../../contextkey/common/contextkey.js';
import { IContextMenuService, IContextViewService } from '../../contextview/browser/contextView.js';
import { IInstantiationService } from '../../instantiation/common/instantiation.js';
import { IKeybindingService } from '../../keybinding/common/keybinding.js';
import { INotificationService } from '../../notification/common/notification.js';
import { IStorageService } from '../../storage/common/storage.js';
import { IThemeService } from '../../theme/common/themeService.js';
import { ThemeIcon } from '../../../base/common/themables.js';
import { isDark } from '../../theme/common/theme.js';
import { assertType } from '../../../base/common/types.js';
import { asCssVariable, selectBorder } from '../../theme/common/colorRegistry.js';
import { defaultSelectBoxStyles } from '../../theme/browser/defaultStyles.js';
import { IAccessibilityService } from '../../accessibility/common/accessibility.js';
export function createAndFillInContextMenuActions(menu, optionsOrTarget, targetOrPrimaryGroup, primaryGroupOrUndefined) {
let target;
let primaryGroup;
let groups;
if (Array.isArray(menu)) {
groups = menu;
target = optionsOrTarget;
primaryGroup = targetOrPrimaryGroup;
}
else {
const options = optionsOrTarget;
groups = menu.getActions(options);
target = targetOrPrimaryGroup;
primaryGroup = primaryGroupOrUndefined;
}
const modifierKeyEmitter = ModifierKeyEmitter.getInstance();
const useAlternativeActions = modifierKeyEmitter.keyStatus.altKey || ((isWindows || isLinux) && modifierKeyEmitter.keyStatus.shiftKey);
fillInActions(groups, target, useAlternativeActions, primaryGroup ? actionGroup => actionGroup === primaryGroup : actionGroup => actionGroup === 'navigation');
}
export function createAndFillInActionBarActions(menu, optionsOrTarget, targetOrPrimaryGroup, primaryGroupOrShouldInlineSubmenu, shouldInlineSubmenuOrUseSeparatorsInPrimaryActions, useSeparatorsInPrimaryActionsOrUndefined) {
let target;
let primaryGroup;
let shouldInlineSubmenu;
let useSeparatorsInPrimaryActions;
let groups;
if (Array.isArray(menu)) {
groups = menu;
target = optionsOrTarget;
primaryGroup = targetOrPrimaryGroup;
shouldInlineSubmenu = primaryGroupOrShouldInlineSubmenu;
useSeparatorsInPrimaryActions = shouldInlineSubmenuOrUseSeparatorsInPrimaryActions;
}
else {
const options = optionsOrTarget;
groups = menu.getActions(options);
target = targetOrPrimaryGroup;
primaryGroup = primaryGroupOrShouldInlineSubmenu;
shouldInlineSubmenu = shouldInlineSubmenuOrUseSeparatorsInPrimaryActions;
useSeparatorsInPrimaryActions = useSeparatorsInPrimaryActionsOrUndefined;
}
const isPrimaryAction = typeof primaryGroup === 'string' ? (actionGroup) => actionGroup === primaryGroup : primaryGroup;
// Action bars handle alternative actions on their own so the alternative actions should be ignored
fillInActions(groups, target, false, isPrimaryAction, shouldInlineSubmenu, useSeparatorsInPrimaryActions);
}
function fillInActions(groups, target, useAlternativeActions, isPrimaryAction = actionGroup => actionGroup === 'navigation', shouldInlineSubmenu = () => false, useSeparatorsInPrimaryActions = false) {
let primaryBucket;
let secondaryBucket;
if (Array.isArray(target)) {
primaryBucket = target;
secondaryBucket = target;
}
else {
primaryBucket = target.primary;
secondaryBucket = target.secondary;
}
const submenuInfo = new Set();
for (const [group, actions] of groups) {
let target;
if (isPrimaryAction(group)) {
target = primaryBucket;
if (target.length > 0 && useSeparatorsInPrimaryActions) {
target.push(new Separator());
}
}
else {
target = secondaryBucket;
if (target.length > 0) {
target.push(new Separator());
}
}
for (let action of actions) {
if (useAlternativeActions) {
action = action instanceof MenuItemAction && action.alt ? action.alt : action;
}
const newLen = target.push(action);
// keep submenu info for later inlining
if (action instanceof SubmenuAction) {
submenuInfo.add({ group, action, index: newLen - 1 });
}
}
}
// ask the outside if submenu should be inlined or not. only ask when
// there would be enough space
for (const { group, action, index } of submenuInfo) {
const target = isPrimaryAction(group) ? primaryBucket : secondaryBucket;
// inlining submenus with length 0 or 1 is easy,
// larger submenus need to be checked with the overall limit
const submenuActions = action.actions;
if (shouldInlineSubmenu(action, group, target.length)) {
target.splice(index, 1, ...submenuActions);
}
}
}
let MenuEntryActionViewItem = class MenuEntryActionViewItem extends ActionViewItem {
constructor(action, _options, _keybindingService, _notificationService, _contextKeyService, _themeService, _contextMenuService, _accessibilityService) {
super(undefined, action, { icon: !!(action.class || action.item.icon), label: !action.class && !action.item.icon, draggable: _options?.draggable, keybinding: _options?.keybinding, hoverDelegate: _options?.hoverDelegate });
this._options = _options;
this._keybindingService = _keybindingService;
this._notificationService = _notificationService;
this._contextKeyService = _contextKeyService;
this._themeService = _themeService;
this._contextMenuService = _contextMenuService;
this._accessibilityService = _accessibilityService;
this._wantsAltCommand = false;
this._itemClassDispose = this._register(new MutableDisposable());
this._altKey = ModifierKeyEmitter.getInstance();
}
get _menuItemAction() {
return this._action;
}
get _commandAction() {
return this._wantsAltCommand && this._menuItemAction.alt || this._menuItemAction;
}
async onClick(event) {
event.preventDefault();
event.stopPropagation();
try {
await this.actionRunner.run(this._commandAction, this._context);
}
catch (err) {
this._notificationService.error(err);
}
}
render(container) {
super.render(container);
container.classList.add('menu-entry');
if (this.options.icon) {
this._updateItemClass(this._menuItemAction.item);
}
if (this._menuItemAction.alt) {
let isMouseOver = false;
const updateAltState = () => {
const wantsAltCommand = !!this._menuItemAction.alt?.enabled &&
(!this._accessibilityService.isMotionReduced() || isMouseOver) && (this._altKey.keyStatus.altKey ||
(this._altKey.keyStatus.shiftKey && isMouseOver));
if (wantsAltCommand !== this._wantsAltCommand) {
this._wantsAltCommand = wantsAltCommand;
this.updateLabel();
this.updateTooltip();
this.updateClass();
}
};
this._register(this._altKey.event(updateAltState));
this._register(addDisposableListener(container, 'mouseleave', _ => {
isMouseOver = false;
updateAltState();
}));
this._register(addDisposableListener(container, 'mouseenter', _ => {
isMouseOver = true;
updateAltState();
}));
updateAltState();
}
}
updateLabel() {
if (this.options.label && this.label) {
this.label.textContent = this._commandAction.label;
}
}
getTooltip() {
const keybinding = this._keybindingService.lookupKeybinding(this._commandAction.id, this._contextKeyService);
const keybindingLabel = keybinding && keybinding.getLabel();
const tooltip = this._commandAction.tooltip || this._commandAction.label;
let title = keybindingLabel
? localize('titleAndKb', "{0} ({1})", tooltip, keybindingLabel)
: tooltip;
if (!this._wantsAltCommand && this._menuItemAction.alt?.enabled) {
const altTooltip = this._menuItemAction.alt.tooltip || this._menuItemAction.alt.label;
const altKeybinding = this._keybindingService.lookupKeybinding(this._menuItemAction.alt.id, this._contextKeyService);
const altKeybindingLabel = altKeybinding && altKeybinding.getLabel();
const altTitleSection = altKeybindingLabel
? localize('titleAndKb', "{0} ({1})", altTooltip, altKeybindingLabel)
: altTooltip;
title = localize('titleAndKbAndAlt', "{0}\n[{1}] {2}", title, UILabelProvider.modifierLabels[OS].altKey, altTitleSection);
}
return title;
}
updateClass() {
if (this.options.icon) {
if (this._commandAction !== this._menuItemAction) {
if (this._menuItemAction.alt) {
this._updateItemClass(this._menuItemAction.alt.item);
}
}
else {
this._updateItemClass(this._menuItemAction.item);
}
}
}
_updateItemClass(item) {
this._itemClassDispose.value = undefined;
const { element, label } = this;
if (!element || !label) {
return;
}
const icon = this._commandAction.checked && isICommandActionToggleInfo(item.toggled) && item.toggled.icon ? item.toggled.icon : item.icon;
if (!icon) {
return;
}
if (ThemeIcon.isThemeIcon(icon)) {
// theme icons
const iconClasses = ThemeIcon.asClassNameArray(icon);
label.classList.add(...iconClasses);
this._itemClassDispose.value = toDisposable(() => {
label.classList.remove(...iconClasses);
});
}
else {
// icon path/url
label.style.backgroundImage = (isDark(this._themeService.getColorTheme().type)
? asCSSUrl(icon.dark)
: asCSSUrl(icon.light));
label.classList.add('icon');
this._itemClassDispose.value = combinedDisposable(toDisposable(() => {
label.style.backgroundImage = '';
label.classList.remove('icon');
}), this._themeService.onDidColorThemeChange(() => {
// refresh when the theme changes in case we go between dark <-> light
this.updateClass();
}));
}
}
};
MenuEntryActionViewItem = __decorate([
__param(2, IKeybindingService),
__param(3, INotificationService),
__param(4, IContextKeyService),
__param(5, IThemeService),
__param(6, IContextMenuService),
__param(7, IAccessibilityService)
], MenuEntryActionViewItem);
export { MenuEntryActionViewItem };
export class TextOnlyMenuEntryActionViewItem extends MenuEntryActionViewItem {
render(container) {
this.options.label = true;
this.options.icon = false;
super.render(container);
container.classList.add('text-only');
container.classList.toggle('use-comma', this._options?.useComma ?? false);
}
updateLabel() {
const kb = this._keybindingService.lookupKeybinding(this._action.id, this._contextKeyService);
if (!kb) {
return super.updateLabel();
}
if (this.label) {
const kb2 = TextOnlyMenuEntryActionViewItem._symbolPrintEnter(kb);
if (this._options?.conversational) {
this.label.textContent = localize({ key: 'content2', comment: ['A label with keybindg like "ESC to dismiss"'] }, '{1} to {0}', this._action.label, kb2);
}
else {
this.label.textContent = localize({ key: 'content', comment: ['A label', 'A keybinding'] }, '{0} ({1})', this._action.label, kb2);
}
}
}
static _symbolPrintEnter(kb) {
return kb.getLabel()
?.replace(/\benter\b/gi, '\u23CE')
.replace(/\bEscape\b/gi, 'Esc');
}
}
let SubmenuEntryActionViewItem = class SubmenuEntryActionViewItem extends DropdownMenuActionViewItem {
constructor(action, options, _keybindingService, _contextMenuService, _themeService) {
const dropdownOptions = {
...options,
menuAsChild: options?.menuAsChild ?? false,
classNames: options?.classNames ?? (ThemeIcon.isThemeIcon(action.item.icon) ? ThemeIcon.asClassName(action.item.icon) : undefined),
keybindingProvider: options?.keybindingProvider ?? (action => _keybindingService.lookupKeybinding(action.id))
};
super(action, { getActions: () => action.actions }, _contextMenuService, dropdownOptions);
this._keybindingService = _keybindingService;
this._contextMenuService = _contextMenuService;
this._themeService = _themeService;
}
render(container) {
super.render(container);
assertType(this.element);
container.classList.add('menu-entry');
const action = this._action;
const { icon } = action.item;
if (icon && !ThemeIcon.isThemeIcon(icon)) {
this.element.classList.add('icon');
const setBackgroundImage = () => {
if (this.element) {
this.element.style.backgroundImage = (isDark(this._themeService.getColorTheme().type)
? asCSSUrl(icon.dark)
: asCSSUrl(icon.light));
}
};
setBackgroundImage();
this._register(this._themeService.onDidColorThemeChange(() => {
// refresh when the theme changes in case we go between dark <-> light
setBackgroundImage();
}));
}
}
};
SubmenuEntryActionViewItem = __decorate([
__param(2, IKeybindingService),
__param(3, IContextMenuService),
__param(4, IThemeService)
], SubmenuEntryActionViewItem);
export { SubmenuEntryActionViewItem };
let DropdownWithDefaultActionViewItem = class DropdownWithDefaultActionViewItem extends BaseActionViewItem {
constructor(submenuAction, options, _keybindingService, _notificationService, _contextMenuService, _menuService, _instaService, _storageService) {
super(null, submenuAction);
this._keybindingService = _keybindingService;
this._notificationService = _notificationService;
this._contextMenuService = _contextMenuService;
this._menuService = _menuService;
this._instaService = _instaService;
this._storageService = _storageService;
this._container = null;
this._options = options;
this._storageKey = `${submenuAction.item.submenu.id}_lastActionId`;
// determine default action
let defaultAction;
const defaultActionId = options?.persistLastActionId ? _storageService.get(this._storageKey, 1 /* StorageScope.WORKSPACE */) : undefined;
if (defaultActionId) {
defaultAction = submenuAction.actions.find(a => defaultActionId === a.id);
}
if (!defaultAction) {
defaultAction = submenuAction.actions[0];
}
this._defaultAction = this._instaService.createInstance(MenuEntryActionViewItem, defaultAction, { keybinding: this._getDefaultActionKeybindingLabel(defaultAction) });
const dropdownOptions = {
keybindingProvider: action => this._keybindingService.lookupKeybinding(action.id),
...options,
menuAsChild: options?.menuAsChild ?? true,
classNames: options?.classNames ?? ['codicon', 'codicon-chevron-down'],
actionRunner: options?.actionRunner ?? new ActionRunner(),
};
this._dropdown = new DropdownMenuActionViewItem(submenuAction, submenuAction.actions, this._contextMenuService, dropdownOptions);
this._register(this._dropdown.actionRunner.onDidRun((e) => {
if (e.action instanceof MenuItemAction) {
this.update(e.action);
}
}));
}
update(lastAction) {
if (this._options?.persistLastActionId) {
this._storageService.store(this._storageKey, lastAction.id, 1 /* StorageScope.WORKSPACE */, 1 /* StorageTarget.MACHINE */);
}
this._defaultAction.dispose();
this._defaultAction = this._instaService.createInstance(MenuEntryActionViewItem, lastAction, { keybinding: this._getDefaultActionKeybindingLabel(lastAction) });
this._defaultAction.actionRunner = new class extends ActionRunner {
async runAction(action, context) {
await action.run(undefined);
}
}();
if (this._container) {
this._defaultAction.render(prepend(this._container, $('.action-container')));
}
}
_getDefaultActionKeybindingLabel(defaultAction) {
let defaultActionKeybinding;
if (this._options?.renderKeybindingWithDefaultActionLabel) {
const kb = this._keybindingService.lookupKeybinding(defaultAction.id);
if (kb) {
defaultActionKeybinding = `(${kb.getLabel()})`;
}
}
return defaultActionKeybinding;
}
setActionContext(newContext) {
super.setActionContext(newContext);
this._defaultAction.setActionContext(newContext);
this._dropdown.setActionContext(newContext);
}
render(container) {
this._container = container;
super.render(this._container);
this._container.classList.add('monaco-dropdown-with-default');
const primaryContainer = $('.action-container');
this._defaultAction.render(append(this._container, primaryContainer));
this._register(addDisposableListener(primaryContainer, EventType.KEY_DOWN, (e) => {
const event = new StandardKeyboardEvent(e);
if (event.equals(17 /* KeyCode.RightArrow */)) {
this._defaultAction.element.tabIndex = -1;
this._dropdown.focus();
event.stopPropagation();
}
}));
const dropdownContainer = $('.dropdown-action-container');
this._dropdown.render(append(this._container, dropdownContainer));
this._register(addDisposableListener(dropdownContainer, EventType.KEY_DOWN, (e) => {
const event = new StandardKeyboardEvent(e);
if (event.equals(15 /* KeyCode.LeftArrow */)) {
this._defaultAction.element.tabIndex = 0;
this._dropdown.setFocusable(false);
this._defaultAction.element?.focus();
event.stopPropagation();
}
}));
}
focus(fromRight) {
if (fromRight) {
this._dropdown.focus();
}
else {
this._defaultAction.element.tabIndex = 0;
this._defaultAction.element.focus();
}
}
blur() {
this._defaultAction.element.tabIndex = -1;
this._dropdown.blur();
this._container.blur();
}
setFocusable(focusable) {
if (focusable) {
this._defaultAction.element.tabIndex = 0;
}
else {
this._defaultAction.element.tabIndex = -1;
this._dropdown.setFocusable(false);
}
}
dispose() {
this._defaultAction.dispose();
this._dropdown.dispose();
super.dispose();
}
};
DropdownWithDefaultActionViewItem = __decorate([
__param(2, IKeybindingService),
__param(3, INotificationService),
__param(4, IContextMenuService),
__param(5, IMenuService),
__param(6, IInstantiationService),
__param(7, IStorageService)
], DropdownWithDefaultActionViewItem);
export { DropdownWithDefaultActionViewItem };
let SubmenuEntrySelectActionViewItem = class SubmenuEntrySelectActionViewItem extends SelectActionViewItem {
constructor(action, contextViewService) {
super(null, action, action.actions.map(a => ({
text: a.id === Separator.ID ? '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' : a.label,
isDisabled: !a.enabled,
})), 0, contextViewService, defaultSelectBoxStyles, { ariaLabel: action.tooltip, optionsAsChildren: true });
this.select(Math.max(0, action.actions.findIndex(a => a.checked)));
}
render(container) {
super.render(container);
container.style.borderColor = asCssVariable(selectBorder);
}
runAction(option, index) {
const action = this.action.actions[index];
if (action) {
this.actionRunner.run(action);
}
}
};
SubmenuEntrySelectActionViewItem = __decorate([
__param(1, IContextViewService)
], SubmenuEntrySelectActionViewItem);
/**
* Creates action view items for menu actions or submenu actions.
*/
export function createActionViewItem(instaService, action, options) {
if (action instanceof MenuItemAction) {
return instaService.createInstance(MenuEntryActionViewItem, action, options);
}
else if (action instanceof SubmenuItemAction) {
if (action.item.isSelection) {
return instaService.createInstance(SubmenuEntrySelectActionViewItem, action);
}
else {
if (action.item.rememberDefaultAction) {
return instaService.createInstance(DropdownWithDefaultActionViewItem, action, { ...options, persistLastActionId: true });
}
else {
return instaService.createInstance(SubmenuEntryActionViewItem, action, options);
}
}
}
else {
return undefined;
}
}
@@ -0,0 +1,258 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { addDisposableListener, getWindow } from '../../../base/browser/dom.js';
import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js';
import { ToggleMenuAction, ToolBar } from '../../../base/browser/ui/toolbar/toolbar.js';
import { Separator, toAction } from '../../../base/common/actions.js';
import { coalesceInPlace } from '../../../base/common/arrays.js';
import { intersection } from '../../../base/common/collections.js';
import { BugIndicatingError } from '../../../base/common/errors.js';
import { Emitter } from '../../../base/common/event.js';
import { Iterable } from '../../../base/common/iterator.js';
import { DisposableStore } from '../../../base/common/lifecycle.js';
import { localize } from '../../../nls.js';
import { createAndFillInActionBarActions } from './menuEntryActionViewItem.js';
import { IMenuService, MenuItemAction, SubmenuItemAction } from '../common/actions.js';
import { createConfigureKeybindingAction } from '../common/menuService.js';
import { ICommandService } from '../../commands/common/commands.js';
import { IContextKeyService } from '../../contextkey/common/contextkey.js';
import { IContextMenuService } from '../../contextview/browser/contextView.js';
import { IKeybindingService } from '../../keybinding/common/keybinding.js';
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
/**
* The `WorkbenchToolBar` does
* - support hiding of menu items
* - lookup keybindings for each actions automatically
* - send `workbenchActionExecuted`-events for each action
*
* See {@link MenuWorkbenchToolBar} for a toolbar that is backed by a menu.
*/
let WorkbenchToolBar = class WorkbenchToolBar extends ToolBar {
constructor(container, _options, _menuService, _contextKeyService, _contextMenuService, _keybindingService, _commandService, telemetryService) {
super(container, _contextMenuService, {
// defaults
getKeyBinding: (action) => _keybindingService.lookupKeybinding(action.id) ?? undefined,
// options (override defaults)
..._options,
// mandatory (overide options)
allowContextMenu: true,
skipTelemetry: typeof _options?.telemetrySource === 'string',
});
this._options = _options;
this._menuService = _menuService;
this._contextKeyService = _contextKeyService;
this._contextMenuService = _contextMenuService;
this._keybindingService = _keybindingService;
this._commandService = _commandService;
this._sessionDisposables = this._store.add(new DisposableStore());
// telemetry logic
const telemetrySource = _options?.telemetrySource;
if (telemetrySource) {
this._store.add(this.actionBar.onDidRun(e => telemetryService.publicLog2('workbenchActionExecuted', { id: e.action.id, from: telemetrySource })));
}
}
setActions(_primary, _secondary = [], menuIds) {
this._sessionDisposables.clear();
const primary = _primary.slice(); // for hiding and overflow we set some items to undefined
const secondary = _secondary.slice();
const toggleActions = [];
let toggleActionsCheckedCount = 0;
const extraSecondary = [];
let someAreHidden = false;
// unless disabled, move all hidden items to secondary group or ignore them
if (this._options?.hiddenItemStrategy !== -1 /* HiddenItemStrategy.NoHide */) {
for (let i = 0; i < primary.length; i++) {
const action = primary[i];
if (!(action instanceof MenuItemAction) && !(action instanceof SubmenuItemAction)) {
// console.warn(`Action ${action.id}/${action.label} is not a MenuItemAction`);
continue;
}
if (!action.hideActions) {
continue;
}
// collect all toggle actions
toggleActions.push(action.hideActions.toggle);
if (action.hideActions.toggle.checked) {
toggleActionsCheckedCount++;
}
// hidden items move into overflow or ignore
if (action.hideActions.isHidden) {
someAreHidden = true;
primary[i] = undefined;
if (this._options?.hiddenItemStrategy !== 0 /* HiddenItemStrategy.Ignore */) {
extraSecondary[i] = action;
}
}
}
}
// count for max
if (this._options?.overflowBehavior !== undefined) {
const exemptedIds = intersection(new Set(this._options.overflowBehavior.exempted), Iterable.map(primary, a => a?.id));
const maxItems = this._options.overflowBehavior.maxItems - exemptedIds.size;
let count = 0;
for (let i = 0; i < primary.length; i++) {
const action = primary[i];
if (!action) {
continue;
}
count++;
if (exemptedIds.has(action.id)) {
continue;
}
if (count >= maxItems) {
primary[i] = undefined;
extraSecondary[i] = action;
}
}
}
// coalesce turns Array<IAction|undefined> into IAction[]
coalesceInPlace(primary);
coalesceInPlace(extraSecondary);
super.setActions(primary, Separator.join(extraSecondary, secondary));
// add context menu for toggle and configure keybinding actions
if (toggleActions.length > 0 || primary.length > 0) {
this._sessionDisposables.add(addDisposableListener(this.getElement(), 'contextmenu', e => {
const event = new StandardMouseEvent(getWindow(this.getElement()), e);
const action = this.getItemAction(event.target);
if (!(action)) {
return;
}
event.preventDefault();
event.stopPropagation();
const primaryActions = [];
// -- Configure Keybinding Action --
if (action instanceof MenuItemAction && action.menuKeybinding) {
primaryActions.push(action.menuKeybinding);
}
else if (!(action instanceof SubmenuItemAction || action instanceof ToggleMenuAction)) {
// only enable the configure keybinding action for actions that support keybindings
const supportsKeybindings = !!this._keybindingService.lookupKeybinding(action.id);
primaryActions.push(createConfigureKeybindingAction(this._commandService, this._keybindingService, action.id, undefined, supportsKeybindings));
}
// -- Hide Actions --
if (toggleActions.length > 0) {
let noHide = false;
// last item cannot be hidden when using ignore strategy
if (toggleActionsCheckedCount === 1 && this._options?.hiddenItemStrategy === 0 /* HiddenItemStrategy.Ignore */) {
noHide = true;
for (let i = 0; i < toggleActions.length; i++) {
if (toggleActions[i].checked) {
toggleActions[i] = toAction({
id: action.id,
label: action.label,
checked: true,
enabled: false,
run() { }
});
break; // there is only one
}
}
}
// add "hide foo" actions
if (!noHide && (action instanceof MenuItemAction || action instanceof SubmenuItemAction)) {
if (!action.hideActions) {
// no context menu for MenuItemAction instances that support no hiding
// those are fake actions and need to be cleaned up
return;
}
primaryActions.push(action.hideActions.hide);
}
else {
primaryActions.push(toAction({
id: 'label',
label: localize('hide', "Hide"),
enabled: false,
run() { }
}));
}
}
const actions = Separator.join(primaryActions, toggleActions);
// add "Reset Menu" action
if (this._options?.resetMenu && !menuIds) {
menuIds = [this._options.resetMenu];
}
if (someAreHidden && menuIds) {
actions.push(new Separator());
actions.push(toAction({
id: 'resetThisMenu',
label: localize('resetThisMenu', "Reset Menu"),
run: () => this._menuService.resetHiddenStates(menuIds)
}));
}
if (actions.length === 0) {
return;
}
this._contextMenuService.showContextMenu({
getAnchor: () => event,
getActions: () => actions,
// add context menu actions (iff appicable)
menuId: this._options?.contextMenu,
menuActionOptions: { renderShortTitle: true, ...this._options?.menuOptions },
skipTelemetry: typeof this._options?.telemetrySource === 'string',
contextKeyService: this._contextKeyService,
});
}));
}
}
};
WorkbenchToolBar = __decorate([
__param(2, IMenuService),
__param(3, IContextKeyService),
__param(4, IContextMenuService),
__param(5, IKeybindingService),
__param(6, ICommandService),
__param(7, ITelemetryService)
], WorkbenchToolBar);
export { WorkbenchToolBar };
/**
* A {@link WorkbenchToolBar workbench toolbar} that is purely driven from a {@link MenuId menu}-identifier.
*
* *Note* that Manual updates via `setActions` are NOT supported.
*/
let MenuWorkbenchToolBar = class MenuWorkbenchToolBar extends WorkbenchToolBar {
constructor(container, menuId, options, menuService, contextKeyService, contextMenuService, keybindingService, commandService, telemetryService) {
super(container, { resetMenu: menuId, ...options }, menuService, contextKeyService, contextMenuService, keybindingService, commandService, telemetryService);
this._onDidChangeMenuItems = this._store.add(new Emitter());
this.onDidChangeMenuItems = this._onDidChangeMenuItems.event;
// update logic
const menu = this._store.add(menuService.createMenu(menuId, contextKeyService, { emitEventsForSubmenuChanges: true }));
const updateToolbar = () => {
const primary = [];
const secondary = [];
createAndFillInActionBarActions(menu, options?.menuOptions, { primary, secondary }, options?.toolbarOptions?.primaryGroup, options?.toolbarOptions?.shouldInlineSubmenu, options?.toolbarOptions?.useSeparatorsInPrimaryActions);
container.classList.toggle('has-no-actions', primary.length === 0 && secondary.length === 0);
super.setActions(primary, secondary);
};
this._store.add(menu.onDidChange(() => {
updateToolbar();
this._onDidChangeMenuItems.fire(this);
}));
updateToolbar();
}
/**
* @deprecated The WorkbenchToolBar does not support this method because it works with menus.
*/
setActions() {
throw new BugIndicatingError('This toolbar is populated from a menu.');
}
};
MenuWorkbenchToolBar = __decorate([
__param(3, IMenuService),
__param(4, IContextKeyService),
__param(5, IContextMenuService),
__param(6, IKeybindingService),
__param(7, ICommandService),
__param(8, ITelemetryService)
], MenuWorkbenchToolBar);
export { MenuWorkbenchToolBar };
@@ -0,0 +1,449 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var MenuItemAction_1;
import { SubmenuAction } from '../../../base/common/actions.js';
import { ThemeIcon } from '../../../base/common/themables.js';
import { MicrotaskEmitter } from '../../../base/common/event.js';
import { DisposableStore, dispose, toDisposable } from '../../../base/common/lifecycle.js';
import { LinkedList } from '../../../base/common/linkedList.js';
import { CommandsRegistry, ICommandService } from '../../commands/common/commands.js';
import { ContextKeyExpr, IContextKeyService } from '../../contextkey/common/contextkey.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
import { KeybindingsRegistry } from '../../keybinding/common/keybindingsRegistry.js';
export function isIMenuItem(item) {
return item.command !== undefined;
}
export function isISubmenuItem(item) {
return item.submenu !== undefined;
}
export class MenuId {
static { this._instances = new Map(); }
static { this.CommandPalette = new MenuId('CommandPalette'); }
static { this.DebugBreakpointsContext = new MenuId('DebugBreakpointsContext'); }
static { this.DebugCallStackContext = new MenuId('DebugCallStackContext'); }
static { this.DebugConsoleContext = new MenuId('DebugConsoleContext'); }
static { this.DebugVariablesContext = new MenuId('DebugVariablesContext'); }
static { this.NotebookVariablesContext = new MenuId('NotebookVariablesContext'); }
static { this.DebugHoverContext = new MenuId('DebugHoverContext'); }
static { this.DebugWatchContext = new MenuId('DebugWatchContext'); }
static { this.DebugToolBar = new MenuId('DebugToolBar'); }
static { this.DebugToolBarStop = new MenuId('DebugToolBarStop'); }
static { this.DebugCallStackToolbar = new MenuId('DebugCallStackToolbar'); }
static { this.EditorContext = new MenuId('EditorContext'); }
static { this.SimpleEditorContext = new MenuId('SimpleEditorContext'); }
static { this.EditorContent = new MenuId('EditorContent'); }
static { this.EditorLineNumberContext = new MenuId('EditorLineNumberContext'); }
static { this.EditorContextCopy = new MenuId('EditorContextCopy'); }
static { this.EditorContextPeek = new MenuId('EditorContextPeek'); }
static { this.EditorContextShare = new MenuId('EditorContextShare'); }
static { this.EditorTitle = new MenuId('EditorTitle'); }
static { this.EditorTitleRun = new MenuId('EditorTitleRun'); }
static { this.EditorTitleContext = new MenuId('EditorTitleContext'); }
static { this.EditorTitleContextShare = new MenuId('EditorTitleContextShare'); }
static { this.EmptyEditorGroup = new MenuId('EmptyEditorGroup'); }
static { this.EmptyEditorGroupContext = new MenuId('EmptyEditorGroupContext'); }
static { this.EditorTabsBarContext = new MenuId('EditorTabsBarContext'); }
static { this.EditorTabsBarShowTabsSubmenu = new MenuId('EditorTabsBarShowTabsSubmenu'); }
static { this.EditorTabsBarShowTabsZenModeSubmenu = new MenuId('EditorTabsBarShowTabsZenModeSubmenu'); }
static { this.EditorActionsPositionSubmenu = new MenuId('EditorActionsPositionSubmenu'); }
static { this.ExplorerContext = new MenuId('ExplorerContext'); }
static { this.ExplorerContextShare = new MenuId('ExplorerContextShare'); }
static { this.ExtensionContext = new MenuId('ExtensionContext'); }
static { this.GlobalActivity = new MenuId('GlobalActivity'); }
static { this.CommandCenter = new MenuId('CommandCenter'); }
static { this.CommandCenterCenter = new MenuId('CommandCenterCenter'); }
static { this.LayoutControlMenuSubmenu = new MenuId('LayoutControlMenuSubmenu'); }
static { this.LayoutControlMenu = new MenuId('LayoutControlMenu'); }
static { this.MenubarMainMenu = new MenuId('MenubarMainMenu'); }
static { this.MenubarAppearanceMenu = new MenuId('MenubarAppearanceMenu'); }
static { this.MenubarDebugMenu = new MenuId('MenubarDebugMenu'); }
static { this.MenubarEditMenu = new MenuId('MenubarEditMenu'); }
static { this.MenubarCopy = new MenuId('MenubarCopy'); }
static { this.MenubarFileMenu = new MenuId('MenubarFileMenu'); }
static { this.MenubarGoMenu = new MenuId('MenubarGoMenu'); }
static { this.MenubarHelpMenu = new MenuId('MenubarHelpMenu'); }
static { this.MenubarLayoutMenu = new MenuId('MenubarLayoutMenu'); }
static { this.MenubarNewBreakpointMenu = new MenuId('MenubarNewBreakpointMenu'); }
static { this.PanelAlignmentMenu = new MenuId('PanelAlignmentMenu'); }
static { this.PanelPositionMenu = new MenuId('PanelPositionMenu'); }
static { this.ActivityBarPositionMenu = new MenuId('ActivityBarPositionMenu'); }
static { this.MenubarPreferencesMenu = new MenuId('MenubarPreferencesMenu'); }
static { this.MenubarRecentMenu = new MenuId('MenubarRecentMenu'); }
static { this.MenubarSelectionMenu = new MenuId('MenubarSelectionMenu'); }
static { this.MenubarShare = new MenuId('MenubarShare'); }
static { this.MenubarSwitchEditorMenu = new MenuId('MenubarSwitchEditorMenu'); }
static { this.MenubarSwitchGroupMenu = new MenuId('MenubarSwitchGroupMenu'); }
static { this.MenubarTerminalMenu = new MenuId('MenubarTerminalMenu'); }
static { this.MenubarViewMenu = new MenuId('MenubarViewMenu'); }
static { this.MenubarHomeMenu = new MenuId('MenubarHomeMenu'); }
static { this.OpenEditorsContext = new MenuId('OpenEditorsContext'); }
static { this.OpenEditorsContextShare = new MenuId('OpenEditorsContextShare'); }
static { this.ProblemsPanelContext = new MenuId('ProblemsPanelContext'); }
static { this.SCMInputBox = new MenuId('SCMInputBox'); }
static { this.SCMChangesSeparator = new MenuId('SCMChangesSeparator'); }
static { this.SCMChangesContext = new MenuId('SCMChangesContext'); }
static { this.SCMIncomingChanges = new MenuId('SCMIncomingChanges'); }
static { this.SCMIncomingChangesContext = new MenuId('SCMIncomingChangesContext'); }
static { this.SCMIncomingChangesSetting = new MenuId('SCMIncomingChangesSetting'); }
static { this.SCMOutgoingChanges = new MenuId('SCMOutgoingChanges'); }
static { this.SCMOutgoingChangesContext = new MenuId('SCMOutgoingChangesContext'); }
static { this.SCMOutgoingChangesSetting = new MenuId('SCMOutgoingChangesSetting'); }
static { this.SCMIncomingChangesAllChangesContext = new MenuId('SCMIncomingChangesAllChangesContext'); }
static { this.SCMIncomingChangesHistoryItemContext = new MenuId('SCMIncomingChangesHistoryItemContext'); }
static { this.SCMOutgoingChangesAllChangesContext = new MenuId('SCMOutgoingChangesAllChangesContext'); }
static { this.SCMOutgoingChangesHistoryItemContext = new MenuId('SCMOutgoingChangesHistoryItemContext'); }
static { this.SCMChangeContext = new MenuId('SCMChangeContext'); }
static { this.SCMResourceContext = new MenuId('SCMResourceContext'); }
static { this.SCMResourceContextShare = new MenuId('SCMResourceContextShare'); }
static { this.SCMResourceFolderContext = new MenuId('SCMResourceFolderContext'); }
static { this.SCMResourceGroupContext = new MenuId('SCMResourceGroupContext'); }
static { this.SCMSourceControl = new MenuId('SCMSourceControl'); }
static { this.SCMSourceControlInline = new MenuId('SCMSourceControlInline'); }
static { this.SCMSourceControlTitle = new MenuId('SCMSourceControlTitle'); }
static { this.SCMTitle = new MenuId('SCMTitle'); }
static { this.SearchContext = new MenuId('SearchContext'); }
static { this.SearchActionMenu = new MenuId('SearchActionContext'); }
static { this.StatusBarWindowIndicatorMenu = new MenuId('StatusBarWindowIndicatorMenu'); }
static { this.StatusBarRemoteIndicatorMenu = new MenuId('StatusBarRemoteIndicatorMenu'); }
static { this.StickyScrollContext = new MenuId('StickyScrollContext'); }
static { this.TestItem = new MenuId('TestItem'); }
static { this.TestItemGutter = new MenuId('TestItemGutter'); }
static { this.TestProfilesContext = new MenuId('TestProfilesContext'); }
static { this.TestMessageContext = new MenuId('TestMessageContext'); }
static { this.TestMessageContent = new MenuId('TestMessageContent'); }
static { this.TestPeekElement = new MenuId('TestPeekElement'); }
static { this.TestPeekTitle = new MenuId('TestPeekTitle'); }
static { this.TestCallStack = new MenuId('TestCallStack'); }
static { this.TouchBarContext = new MenuId('TouchBarContext'); }
static { this.TitleBarContext = new MenuId('TitleBarContext'); }
static { this.TitleBarTitleContext = new MenuId('TitleBarTitleContext'); }
static { this.TunnelContext = new MenuId('TunnelContext'); }
static { this.TunnelPrivacy = new MenuId('TunnelPrivacy'); }
static { this.TunnelProtocol = new MenuId('TunnelProtocol'); }
static { this.TunnelPortInline = new MenuId('TunnelInline'); }
static { this.TunnelTitle = new MenuId('TunnelTitle'); }
static { this.TunnelLocalAddressInline = new MenuId('TunnelLocalAddressInline'); }
static { this.TunnelOriginInline = new MenuId('TunnelOriginInline'); }
static { this.ViewItemContext = new MenuId('ViewItemContext'); }
static { this.ViewContainerTitle = new MenuId('ViewContainerTitle'); }
static { this.ViewContainerTitleContext = new MenuId('ViewContainerTitleContext'); }
static { this.ViewTitle = new MenuId('ViewTitle'); }
static { this.ViewTitleContext = new MenuId('ViewTitleContext'); }
static { this.CommentEditorActions = new MenuId('CommentEditorActions'); }
static { this.CommentThreadTitle = new MenuId('CommentThreadTitle'); }
static { this.CommentThreadActions = new MenuId('CommentThreadActions'); }
static { this.CommentThreadAdditionalActions = new MenuId('CommentThreadAdditionalActions'); }
static { this.CommentThreadTitleContext = new MenuId('CommentThreadTitleContext'); }
static { this.CommentThreadCommentContext = new MenuId('CommentThreadCommentContext'); }
static { this.CommentTitle = new MenuId('CommentTitle'); }
static { this.CommentActions = new MenuId('CommentActions'); }
static { this.CommentsViewThreadActions = new MenuId('CommentsViewThreadActions'); }
static { this.InteractiveToolbar = new MenuId('InteractiveToolbar'); }
static { this.InteractiveCellTitle = new MenuId('InteractiveCellTitle'); }
static { this.InteractiveCellDelete = new MenuId('InteractiveCellDelete'); }
static { this.InteractiveCellExecute = new MenuId('InteractiveCellExecute'); }
static { this.InteractiveInputExecute = new MenuId('InteractiveInputExecute'); }
static { this.InteractiveInputConfig = new MenuId('InteractiveInputConfig'); }
static { this.ReplInputExecute = new MenuId('ReplInputExecute'); }
static { this.IssueReporter = new MenuId('IssueReporter'); }
static { this.NotebookToolbar = new MenuId('NotebookToolbar'); }
static { this.NotebookStickyScrollContext = new MenuId('NotebookStickyScrollContext'); }
static { this.NotebookCellTitle = new MenuId('NotebookCellTitle'); }
static { this.NotebookCellDelete = new MenuId('NotebookCellDelete'); }
static { this.NotebookCellInsert = new MenuId('NotebookCellInsert'); }
static { this.NotebookCellBetween = new MenuId('NotebookCellBetween'); }
static { this.NotebookCellListTop = new MenuId('NotebookCellTop'); }
static { this.NotebookCellExecute = new MenuId('NotebookCellExecute'); }
static { this.NotebookCellExecuteGoTo = new MenuId('NotebookCellExecuteGoTo'); }
static { this.NotebookCellExecutePrimary = new MenuId('NotebookCellExecutePrimary'); }
static { this.NotebookDiffCellInputTitle = new MenuId('NotebookDiffCellInputTitle'); }
static { this.NotebookDiffCellMetadataTitle = new MenuId('NotebookDiffCellMetadataTitle'); }
static { this.NotebookDiffCellOutputsTitle = new MenuId('NotebookDiffCellOutputsTitle'); }
static { this.NotebookOutputToolbar = new MenuId('NotebookOutputToolbar'); }
static { this.NotebookOutlineFilter = new MenuId('NotebookOutlineFilter'); }
static { this.NotebookOutlineActionMenu = new MenuId('NotebookOutlineActionMenu'); }
static { this.NotebookEditorLayoutConfigure = new MenuId('NotebookEditorLayoutConfigure'); }
static { this.NotebookKernelSource = new MenuId('NotebookKernelSource'); }
static { this.BulkEditTitle = new MenuId('BulkEditTitle'); }
static { this.BulkEditContext = new MenuId('BulkEditContext'); }
static { this.TimelineItemContext = new MenuId('TimelineItemContext'); }
static { this.TimelineTitle = new MenuId('TimelineTitle'); }
static { this.TimelineTitleContext = new MenuId('TimelineTitleContext'); }
static { this.TimelineFilterSubMenu = new MenuId('TimelineFilterSubMenu'); }
static { this.AccountsContext = new MenuId('AccountsContext'); }
static { this.SidebarTitle = new MenuId('SidebarTitle'); }
static { this.PanelTitle = new MenuId('PanelTitle'); }
static { this.AuxiliaryBarTitle = new MenuId('AuxiliaryBarTitle'); }
static { this.AuxiliaryBarHeader = new MenuId('AuxiliaryBarHeader'); }
static { this.TerminalInstanceContext = new MenuId('TerminalInstanceContext'); }
static { this.TerminalEditorInstanceContext = new MenuId('TerminalEditorInstanceContext'); }
static { this.TerminalNewDropdownContext = new MenuId('TerminalNewDropdownContext'); }
static { this.TerminalTabContext = new MenuId('TerminalTabContext'); }
static { this.TerminalTabEmptyAreaContext = new MenuId('TerminalTabEmptyAreaContext'); }
static { this.TerminalStickyScrollContext = new MenuId('TerminalStickyScrollContext'); }
static { this.WebviewContext = new MenuId('WebviewContext'); }
static { this.InlineCompletionsActions = new MenuId('InlineCompletionsActions'); }
static { this.InlineEditsActions = new MenuId('InlineEditsActions'); }
static { this.InlineEditActions = new MenuId('InlineEditActions'); }
static { this.NewFile = new MenuId('NewFile'); }
static { this.MergeInput1Toolbar = new MenuId('MergeToolbar1Toolbar'); }
static { this.MergeInput2Toolbar = new MenuId('MergeToolbar2Toolbar'); }
static { this.MergeBaseToolbar = new MenuId('MergeBaseToolbar'); }
static { this.MergeInputResultToolbar = new MenuId('MergeToolbarResultToolbar'); }
static { this.InlineSuggestionToolbar = new MenuId('InlineSuggestionToolbar'); }
static { this.InlineEditToolbar = new MenuId('InlineEditToolbar'); }
static { this.ChatContext = new MenuId('ChatContext'); }
static { this.ChatCodeBlock = new MenuId('ChatCodeblock'); }
static { this.ChatCompareBlock = new MenuId('ChatCompareBlock'); }
static { this.ChatMessageTitle = new MenuId('ChatMessageTitle'); }
static { this.ChatExecute = new MenuId('ChatExecute'); }
static { this.ChatExecuteSecondary = new MenuId('ChatExecuteSecondary'); }
static { this.ChatInputSide = new MenuId('ChatInputSide'); }
static { this.AccessibleView = new MenuId('AccessibleView'); }
static { this.MultiDiffEditorFileToolbar = new MenuId('MultiDiffEditorFileToolbar'); }
static { this.DiffEditorHunkToolbar = new MenuId('DiffEditorHunkToolbar'); }
static { this.DiffEditorSelectionToolbar = new MenuId('DiffEditorSelectionToolbar'); }
/**
* Create a new `MenuId` with the unique identifier. Will throw if a menu
* with the identifier already exists, use `MenuId.for(ident)` or a unique
* identifier
*/
constructor(identifier) {
if (MenuId._instances.has(identifier)) {
throw new TypeError(`MenuId with identifier '${identifier}' already exists. Use MenuId.for(ident) or a unique identifier`);
}
MenuId._instances.set(identifier, this);
this.id = identifier;
}
}
export const IMenuService = createDecorator('menuService');
class MenuRegistryChangeEvent {
static { this._all = new Map(); }
static for(id) {
let value = this._all.get(id);
if (!value) {
value = new MenuRegistryChangeEvent(id);
this._all.set(id, value);
}
return value;
}
static merge(events) {
const ids = new Set();
for (const item of events) {
if (item instanceof MenuRegistryChangeEvent) {
ids.add(item.id);
}
}
return ids;
}
constructor(id) {
this.id = id;
this.has = candidate => candidate === id;
}
}
export const MenuRegistry = new class {
constructor() {
this._commands = new Map();
this._menuItems = new Map();
this._onDidChangeMenu = new MicrotaskEmitter({
merge: MenuRegistryChangeEvent.merge
});
this.onDidChangeMenu = this._onDidChangeMenu.event;
}
addCommand(command) {
this._commands.set(command.id, command);
this._onDidChangeMenu.fire(MenuRegistryChangeEvent.for(MenuId.CommandPalette));
return toDisposable(() => {
if (this._commands.delete(command.id)) {
this._onDidChangeMenu.fire(MenuRegistryChangeEvent.for(MenuId.CommandPalette));
}
});
}
getCommand(id) {
return this._commands.get(id);
}
getCommands() {
const map = new Map();
this._commands.forEach((value, key) => map.set(key, value));
return map;
}
appendMenuItem(id, item) {
let list = this._menuItems.get(id);
if (!list) {
list = new LinkedList();
this._menuItems.set(id, list);
}
const rm = list.push(item);
this._onDidChangeMenu.fire(MenuRegistryChangeEvent.for(id));
return toDisposable(() => {
rm();
this._onDidChangeMenu.fire(MenuRegistryChangeEvent.for(id));
});
}
appendMenuItems(items) {
const result = new DisposableStore();
for (const { id, item } of items) {
result.add(this.appendMenuItem(id, item));
}
return result;
}
getMenuItems(id) {
let result;
if (this._menuItems.has(id)) {
result = [...this._menuItems.get(id)];
}
else {
result = [];
}
if (id === MenuId.CommandPalette) {
// CommandPalette is special because it shows
// all commands by default
this._appendImplicitItems(result);
}
return result;
}
_appendImplicitItems(result) {
const set = new Set();
for (const item of result) {
if (isIMenuItem(item)) {
set.add(item.command.id);
if (item.alt) {
set.add(item.alt.id);
}
}
}
this._commands.forEach((command, id) => {
if (!set.has(id)) {
result.push({ command });
}
});
}
};
export class SubmenuItemAction extends SubmenuAction {
constructor(item, hideActions, actions) {
super(`submenuitem.${item.submenu.id}`, typeof item.title === 'string' ? item.title : item.title.value, actions, 'submenu');
this.item = item;
this.hideActions = hideActions;
}
}
// implements IAction, does NOT extend Action, so that no one
// subscribes to events of Action or modified properties
let MenuItemAction = MenuItemAction_1 = class MenuItemAction {
static label(action, options) {
return options?.renderShortTitle && action.shortTitle
? (typeof action.shortTitle === 'string' ? action.shortTitle : action.shortTitle.value)
: (typeof action.title === 'string' ? action.title : action.title.value);
}
constructor(item, alt, options, hideActions, menuKeybinding, contextKeyService, _commandService) {
this.hideActions = hideActions;
this.menuKeybinding = menuKeybinding;
this._commandService = _commandService;
this.id = item.id;
this.label = MenuItemAction_1.label(item, options);
this.tooltip = (typeof item.tooltip === 'string' ? item.tooltip : item.tooltip?.value) ?? '';
this.enabled = !item.precondition || contextKeyService.contextMatchesRules(item.precondition);
this.checked = undefined;
let icon;
if (item.toggled) {
const toggled = (item.toggled.condition ? item.toggled : { condition: item.toggled });
this.checked = contextKeyService.contextMatchesRules(toggled.condition);
if (this.checked && toggled.tooltip) {
this.tooltip = typeof toggled.tooltip === 'string' ? toggled.tooltip : toggled.tooltip.value;
}
if (this.checked && ThemeIcon.isThemeIcon(toggled.icon)) {
icon = toggled.icon;
}
if (this.checked && toggled.title) {
this.label = typeof toggled.title === 'string' ? toggled.title : toggled.title.value;
}
}
if (!icon) {
icon = ThemeIcon.isThemeIcon(item.icon) ? item.icon : undefined;
}
this.item = item;
this.alt = alt ? new MenuItemAction_1(alt, undefined, options, hideActions, undefined, contextKeyService, _commandService) : undefined;
this._options = options;
this.class = icon && ThemeIcon.asClassName(icon);
}
run(...args) {
let runArgs = [];
if (this._options?.arg) {
runArgs = [...runArgs, this._options.arg];
}
if (this._options?.shouldForwardArgs) {
runArgs = [...runArgs, ...args];
}
return this._commandService.executeCommand(this.id, ...runArgs);
}
};
MenuItemAction = MenuItemAction_1 = __decorate([
__param(5, IContextKeyService),
__param(6, ICommandService)
], MenuItemAction);
export { MenuItemAction };
export class Action2 {
constructor(desc) {
this.desc = desc;
}
}
export function registerAction2(ctor) {
const disposables = []; // not using `DisposableStore` to reduce startup perf cost
const action = new ctor();
const { f1, menu, keybinding, ...command } = action.desc;
if (CommandsRegistry.getCommand(command.id)) {
throw new Error(`Cannot register two commands with the same id: ${command.id}`);
}
// command
disposables.push(CommandsRegistry.registerCommand({
id: command.id,
handler: (accessor, ...args) => action.run(accessor, ...args),
metadata: command.metadata,
}));
// menu
if (Array.isArray(menu)) {
for (const item of menu) {
disposables.push(MenuRegistry.appendMenuItem(item.id, { command: { ...command, precondition: item.precondition === null ? undefined : command.precondition }, ...item }));
}
}
else if (menu) {
disposables.push(MenuRegistry.appendMenuItem(menu.id, { command: { ...command, precondition: menu.precondition === null ? undefined : command.precondition }, ...menu }));
}
if (f1) {
disposables.push(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command, when: command.precondition }));
disposables.push(MenuRegistry.addCommand(command));
}
// keybinding
if (Array.isArray(keybinding)) {
for (const item of keybinding) {
disposables.push(KeybindingsRegistry.registerKeybindingRule({
...item,
id: command.id,
when: command.precondition ? ContextKeyExpr.and(command.precondition, item.when) : item.when
}));
}
}
else if (keybinding) {
disposables.push(KeybindingsRegistry.registerKeybindingRule({
...keybinding,
id: command.id,
when: command.precondition ? ContextKeyExpr.and(command.precondition, keybinding.when) : keybinding.when
}));
}
return {
dispose() {
dispose(disposables);
}
};
}
//#endregion
@@ -0,0 +1,424 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var PersistedMenuHideState_1, MenuInfo_1;
import { RunOnceScheduler } from '../../../base/common/async.js';
import { DebounceEmitter, Emitter } from '../../../base/common/event.js';
import { DisposableStore } from '../../../base/common/lifecycle.js';
import { isIMenuItem, isISubmenuItem, MenuItemAction, MenuRegistry, SubmenuItemAction } from './actions.js';
import { ICommandService } from '../../commands/common/commands.js';
import { IContextKeyService } from '../../contextkey/common/contextkey.js';
import { Separator, toAction } from '../../../base/common/actions.js';
import { IStorageService } from '../../storage/common/storage.js';
import { removeFastWithoutKeepingOrder } from '../../../base/common/arrays.js';
import { localize } from '../../../nls.js';
import { IKeybindingService } from '../../keybinding/common/keybinding.js';
let MenuService = class MenuService {
constructor(_commandService, _keybindingService, storageService) {
this._commandService = _commandService;
this._keybindingService = _keybindingService;
this._hiddenStates = new PersistedMenuHideState(storageService);
}
createMenu(id, contextKeyService, options) {
return new MenuImpl(id, this._hiddenStates, { emitEventsForSubmenuChanges: false, eventDebounceDelay: 50, ...options }, this._commandService, this._keybindingService, contextKeyService);
}
getMenuActions(id, contextKeyService, options) {
const menu = new MenuImpl(id, this._hiddenStates, { emitEventsForSubmenuChanges: false, eventDebounceDelay: 50, ...options }, this._commandService, this._keybindingService, contextKeyService);
const actions = menu.getActions(options);
menu.dispose();
return actions;
}
resetHiddenStates(ids) {
this._hiddenStates.reset(ids);
}
};
MenuService = __decorate([
__param(0, ICommandService),
__param(1, IKeybindingService),
__param(2, IStorageService)
], MenuService);
export { MenuService };
let PersistedMenuHideState = class PersistedMenuHideState {
static { PersistedMenuHideState_1 = this; }
static { this._key = 'menu.hiddenCommands'; }
constructor(_storageService) {
this._storageService = _storageService;
this._disposables = new DisposableStore();
this._onDidChange = new Emitter();
this.onDidChange = this._onDidChange.event;
this._ignoreChangeEvent = false;
this._hiddenByDefaultCache = new Map();
try {
const raw = _storageService.get(PersistedMenuHideState_1._key, 0 /* StorageScope.PROFILE */, '{}');
this._data = JSON.parse(raw);
}
catch (err) {
this._data = Object.create(null);
}
this._disposables.add(_storageService.onDidChangeValue(0 /* StorageScope.PROFILE */, PersistedMenuHideState_1._key, this._disposables)(() => {
if (!this._ignoreChangeEvent) {
try {
const raw = _storageService.get(PersistedMenuHideState_1._key, 0 /* StorageScope.PROFILE */, '{}');
this._data = JSON.parse(raw);
}
catch (err) {
console.log('FAILED to read storage after UPDATE', err);
}
}
this._onDidChange.fire();
}));
}
dispose() {
this._onDidChange.dispose();
this._disposables.dispose();
}
_isHiddenByDefault(menu, commandId) {
return this._hiddenByDefaultCache.get(`${menu.id}/${commandId}`) ?? false;
}
setDefaultState(menu, commandId, hidden) {
this._hiddenByDefaultCache.set(`${menu.id}/${commandId}`, hidden);
}
isHidden(menu, commandId) {
const hiddenByDefault = this._isHiddenByDefault(menu, commandId);
const state = this._data[menu.id]?.includes(commandId) ?? false;
return hiddenByDefault ? !state : state;
}
updateHidden(menu, commandId, hidden) {
const hiddenByDefault = this._isHiddenByDefault(menu, commandId);
if (hiddenByDefault) {
hidden = !hidden;
}
const entries = this._data[menu.id];
if (!hidden) {
// remove and cleanup
if (entries) {
const idx = entries.indexOf(commandId);
if (idx >= 0) {
removeFastWithoutKeepingOrder(entries, idx);
}
if (entries.length === 0) {
delete this._data[menu.id];
}
}
}
else {
// add unless already added
if (!entries) {
this._data[menu.id] = [commandId];
}
else {
const idx = entries.indexOf(commandId);
if (idx < 0) {
entries.push(commandId);
}
}
}
this._persist();
}
reset(menus) {
if (menus === undefined) {
// reset all
this._data = Object.create(null);
this._persist();
}
else {
// reset only for a specific menu
for (const { id } of menus) {
if (this._data[id]) {
delete this._data[id];
}
}
this._persist();
}
}
_persist() {
try {
this._ignoreChangeEvent = true;
const raw = JSON.stringify(this._data);
this._storageService.store(PersistedMenuHideState_1._key, raw, 0 /* StorageScope.PROFILE */, 0 /* StorageTarget.USER */);
}
finally {
this._ignoreChangeEvent = false;
}
}
};
PersistedMenuHideState = PersistedMenuHideState_1 = __decorate([
__param(0, IStorageService)
], PersistedMenuHideState);
class MenuInfoSnapshot {
constructor(_id, _collectContextKeysForSubmenus) {
this._id = _id;
this._collectContextKeysForSubmenus = _collectContextKeysForSubmenus;
this._menuGroups = [];
this._structureContextKeys = new Set();
this._preconditionContextKeys = new Set();
this._toggledContextKeys = new Set();
this.refresh();
}
get structureContextKeys() {
return this._structureContextKeys;
}
get preconditionContextKeys() {
return this._preconditionContextKeys;
}
get toggledContextKeys() {
return this._toggledContextKeys;
}
refresh() {
// reset
this._menuGroups.length = 0;
this._structureContextKeys.clear();
this._preconditionContextKeys.clear();
this._toggledContextKeys.clear();
const menuItems = this._sort(MenuRegistry.getMenuItems(this._id));
let group;
for (const item of menuItems) {
// group by groupId
const groupName = item.group || '';
if (!group || group[0] !== groupName) {
group = [groupName, []];
this._menuGroups.push(group);
}
group[1].push(item);
// keep keys for eventing
this._collectContextKeys(item);
}
}
_sort(menuItems) {
// no sorting needed in snapshot
return menuItems;
}
_collectContextKeys(item) {
MenuInfoSnapshot._fillInKbExprKeys(item.when, this._structureContextKeys);
if (isIMenuItem(item)) {
// keep precondition keys for event if applicable
if (item.command.precondition) {
MenuInfoSnapshot._fillInKbExprKeys(item.command.precondition, this._preconditionContextKeys);
}
// keep toggled keys for event if applicable
if (item.command.toggled) {
const toggledExpression = item.command.toggled.condition || item.command.toggled;
MenuInfoSnapshot._fillInKbExprKeys(toggledExpression, this._toggledContextKeys);
}
}
else if (this._collectContextKeysForSubmenus) {
// recursively collect context keys from submenus so that this
// menu fires events when context key changes affect submenus
MenuRegistry.getMenuItems(item.submenu).forEach(this._collectContextKeys, this);
}
}
static _fillInKbExprKeys(exp, set) {
if (exp) {
for (const key of exp.keys()) {
set.add(key);
}
}
}
}
let MenuInfo = MenuInfo_1 = class MenuInfo extends MenuInfoSnapshot {
constructor(_id, _hiddenStates, _collectContextKeysForSubmenus, _commandService, _keybindingService, _contextKeyService) {
super(_id, _collectContextKeysForSubmenus);
this._hiddenStates = _hiddenStates;
this._commandService = _commandService;
this._keybindingService = _keybindingService;
this._contextKeyService = _contextKeyService;
this.refresh();
}
createActionGroups(options) {
const result = [];
for (const group of this._menuGroups) {
const [id, items] = group;
let activeActions;
for (const item of items) {
if (this._contextKeyService.contextMatchesRules(item.when)) {
const isMenuItem = isIMenuItem(item);
if (isMenuItem) {
this._hiddenStates.setDefaultState(this._id, item.command.id, !!item.isHiddenByDefault);
}
const menuHide = createMenuHide(this._id, isMenuItem ? item.command : item, this._hiddenStates);
if (isMenuItem) {
// MenuItemAction
const menuKeybinding = createConfigureKeybindingAction(this._commandService, this._keybindingService, item.command.id, item.when);
(activeActions ??= []).push(new MenuItemAction(item.command, item.alt, options, menuHide, menuKeybinding, this._contextKeyService, this._commandService));
}
else {
// SubmenuItemAction
const groups = new MenuInfo_1(item.submenu, this._hiddenStates, this._collectContextKeysForSubmenus, this._commandService, this._keybindingService, this._contextKeyService).createActionGroups(options);
const submenuActions = Separator.join(...groups.map(g => g[1]));
if (submenuActions.length > 0) {
(activeActions ??= []).push(new SubmenuItemAction(item, menuHide, submenuActions));
}
}
}
}
if (activeActions && activeActions.length > 0) {
result.push([id, activeActions]);
}
}
return result;
}
_sort(menuItems) {
return menuItems.sort(MenuInfo_1._compareMenuItems);
}
static _compareMenuItems(a, b) {
const aGroup = a.group;
const bGroup = b.group;
if (aGroup !== bGroup) {
// Falsy groups come last
if (!aGroup) {
return 1;
}
else if (!bGroup) {
return -1;
}
// 'navigation' group comes first
if (aGroup === 'navigation') {
return -1;
}
else if (bGroup === 'navigation') {
return 1;
}
// lexical sort for groups
const value = aGroup.localeCompare(bGroup);
if (value !== 0) {
return value;
}
}
// sort on priority - default is 0
const aPrio = a.order || 0;
const bPrio = b.order || 0;
if (aPrio < bPrio) {
return -1;
}
else if (aPrio > bPrio) {
return 1;
}
// sort on titles
return MenuInfo_1._compareTitles(isIMenuItem(a) ? a.command.title : a.title, isIMenuItem(b) ? b.command.title : b.title);
}
static _compareTitles(a, b) {
const aStr = typeof a === 'string' ? a : a.original;
const bStr = typeof b === 'string' ? b : b.original;
return aStr.localeCompare(bStr);
}
};
MenuInfo = MenuInfo_1 = __decorate([
__param(3, ICommandService),
__param(4, IKeybindingService),
__param(5, IContextKeyService)
], MenuInfo);
let MenuImpl = class MenuImpl {
constructor(id, hiddenStates, options, commandService, keybindingService, contextKeyService) {
this._disposables = new DisposableStore();
this._menuInfo = new MenuInfo(id, hiddenStates, options.emitEventsForSubmenuChanges, commandService, keybindingService, contextKeyService);
// Rebuild this menu whenever the menu registry reports an event for this MenuId.
// This usually happen while code and extensions are loaded and affects the over
// structure of the menu
const rebuildMenuSoon = new RunOnceScheduler(() => {
this._menuInfo.refresh();
this._onDidChange.fire({ menu: this, isStructuralChange: true, isEnablementChange: true, isToggleChange: true });
}, options.eventDebounceDelay);
this._disposables.add(rebuildMenuSoon);
this._disposables.add(MenuRegistry.onDidChangeMenu(e => {
if (e.has(id)) {
rebuildMenuSoon.schedule();
}
}));
// When context keys or storage state changes we need to check if the menu also has changed. However,
// we only do that when someone listens on this menu because (1) these events are
// firing often and (2) menu are often leaked
const lazyListener = this._disposables.add(new DisposableStore());
const merge = (events) => {
let isStructuralChange = false;
let isEnablementChange = false;
let isToggleChange = false;
for (const item of events) {
isStructuralChange = isStructuralChange || item.isStructuralChange;
isEnablementChange = isEnablementChange || item.isEnablementChange;
isToggleChange = isToggleChange || item.isToggleChange;
if (isStructuralChange && isEnablementChange && isToggleChange) {
// everything is TRUE, no need to continue iterating
break;
}
}
return { menu: this, isStructuralChange, isEnablementChange, isToggleChange };
};
const startLazyListener = () => {
lazyListener.add(contextKeyService.onDidChangeContext(e => {
const isStructuralChange = e.affectsSome(this._menuInfo.structureContextKeys);
const isEnablementChange = e.affectsSome(this._menuInfo.preconditionContextKeys);
const isToggleChange = e.affectsSome(this._menuInfo.toggledContextKeys);
if (isStructuralChange || isEnablementChange || isToggleChange) {
this._onDidChange.fire({ menu: this, isStructuralChange, isEnablementChange, isToggleChange });
}
}));
lazyListener.add(hiddenStates.onDidChange(e => {
this._onDidChange.fire({ menu: this, isStructuralChange: true, isEnablementChange: false, isToggleChange: false });
}));
};
this._onDidChange = new DebounceEmitter({
// start/stop context key listener
onWillAddFirstListener: startLazyListener,
onDidRemoveLastListener: lazyListener.clear.bind(lazyListener),
delay: options.eventDebounceDelay,
merge
});
this.onDidChange = this._onDidChange.event;
}
getActions(options) {
return this._menuInfo.createActionGroups(options);
}
dispose() {
this._disposables.dispose();
this._onDidChange.dispose();
}
};
MenuImpl = __decorate([
__param(3, ICommandService),
__param(4, IKeybindingService),
__param(5, IContextKeyService)
], MenuImpl);
function createMenuHide(menu, command, states) {
const id = isISubmenuItem(command) ? command.submenu.id : command.id;
const title = typeof command.title === 'string' ? command.title : command.title.value;
const hide = toAction({
id: `hide/${menu.id}/${id}`,
label: localize('hide.label', 'Hide \'{0}\'', title),
run() { states.updateHidden(menu, id, true); }
});
const toggle = toAction({
id: `toggle/${menu.id}/${id}`,
label: title,
get checked() { return !states.isHidden(menu, id); },
run() { states.updateHidden(menu, id, !!this.checked); }
});
return {
hide,
toggle,
get isHidden() { return !toggle.checked; },
};
}
export function createConfigureKeybindingAction(commandService, keybindingService, commandId, when = undefined, enabled = true) {
return toAction({
id: `configureKeybinding/${commandId}`,
label: localize('configure keybinding', "Configure Keybinding"),
enabled,
run() {
// Only set the when clause when there is no keybinding
// It is possible that the action and the keybinding have different when clauses
const hasKeybinding = !!keybindingService.lookupKeybinding(commandId); // This may only be called inside the `run()` method as it can be expensive on startup. #210529
const whenValue = !hasKeybinding && when ? when.serialize() : undefined;
commandService.executeCommand('workbench.action.openGlobalKeybindings', `@command:${commandId}` + (whenValue ? ` +when:${whenValue}` : ''));
}
});
}
@@ -0,0 +1,183 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var BrowserClipboardService_1;
import { isSafari, isWebkitWebView } from '../../../base/browser/browser.js';
import { $, addDisposableListener, getActiveDocument, getActiveWindow, isHTMLElement, onDidRegisterWindow } from '../../../base/browser/dom.js';
import { mainWindow } from '../../../base/browser/window.js';
import { DeferredPromise } from '../../../base/common/async.js';
import { Event } from '../../../base/common/event.js';
import { hash } from '../../../base/common/hash.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { ILayoutService } from '../../layout/browser/layoutService.js';
import { ILogService } from '../../log/common/log.js';
let BrowserClipboardService = class BrowserClipboardService extends Disposable {
static { BrowserClipboardService_1 = this; }
constructor(layoutService, logService) {
super();
this.layoutService = layoutService;
this.logService = logService;
this.mapTextToType = new Map(); // unsupported in web (only in-memory)
this.findText = ''; // unsupported in web (only in-memory)
this.resources = []; // unsupported in web (only in-memory)
this.resourcesStateHash = undefined;
if (isSafari || isWebkitWebView) {
this.installWebKitWriteTextWorkaround();
}
// Keep track of copy operations to reset our set of
// copied resources: since we keep resources in memory
// and not in the clipboard, we have to invalidate
// that state when the user copies other data.
this._register(Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => {
disposables.add(addDisposableListener(window.document, 'copy', () => this.clearResources()));
}, { window: mainWindow, disposables: this._store }));
}
// In Safari, it has the following note:
//
// "The request to write to the clipboard must be triggered during a user gesture.
// A call to clipboard.write or clipboard.writeText outside the scope of a user
// gesture(such as "click" or "touch" event handlers) will result in the immediate
// rejection of the promise returned by the API call."
// From: https://webkit.org/blog/10855/async-clipboard-api/
//
// Since extensions run in a web worker, and handle gestures in an asynchronous way,
// they are not classified by Safari as "in response to a user gesture" and will reject.
//
// This function sets up some handlers to work around that behavior.
installWebKitWriteTextWorkaround() {
const handler = () => {
const currentWritePromise = new DeferredPromise();
// Cancel the previous promise since we just created a new one in response to this new event
if (this.webKitPendingClipboardWritePromise && !this.webKitPendingClipboardWritePromise.isSettled) {
this.webKitPendingClipboardWritePromise.cancel();
}
this.webKitPendingClipboardWritePromise = currentWritePromise;
// The ctor of ClipboardItem allows you to pass in a promise that will resolve to a string.
// This allows us to pass in a Promise that will either be cancelled by another event or
// resolved with the contents of the first call to this.writeText.
// see https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem/ClipboardItem#parameters
getActiveWindow().navigator.clipboard.write([new ClipboardItem({
'text/plain': currentWritePromise.p,
})]).catch(async (err) => {
if (!(err instanceof Error) || err.name !== 'NotAllowedError' || !currentWritePromise.isRejected) {
this.logService.error(err);
}
});
};
this._register(Event.runAndSubscribe(this.layoutService.onDidAddContainer, ({ container, disposables }) => {
disposables.add(addDisposableListener(container, 'click', handler));
disposables.add(addDisposableListener(container, 'keydown', handler));
}, { container: this.layoutService.mainContainer, disposables: this._store }));
}
async writeText(text, type) {
// Clear resources given we are writing text
this.writeResources([]);
// With type: only in-memory is supported
if (type) {
this.mapTextToType.set(type, text);
return;
}
if (this.webKitPendingClipboardWritePromise) {
// For Safari, we complete this Promise which allows the call to `navigator.clipboard.write()`
// above to resolve and successfully copy to the clipboard. If we let this continue, Safari
// would throw an error because this call stack doesn't appear to originate from a user gesture.
return this.webKitPendingClipboardWritePromise.complete(text);
}
// Guard access to navigator.clipboard with try/catch
// as we have seen DOMExceptions in certain browsers
// due to security policies.
try {
return await getActiveWindow().navigator.clipboard.writeText(text);
}
catch (error) {
console.error(error);
}
// Fallback to textarea and execCommand solution
this.fallbackWriteText(text);
}
fallbackWriteText(text) {
const activeDocument = getActiveDocument();
const activeElement = activeDocument.activeElement;
const textArea = activeDocument.body.appendChild($('textarea', { 'aria-hidden': true }));
textArea.style.height = '1px';
textArea.style.width = '1px';
textArea.style.position = 'absolute';
textArea.value = text;
textArea.focus();
textArea.select();
activeDocument.execCommand('copy');
if (isHTMLElement(activeElement)) {
activeElement.focus();
}
textArea.remove();
}
async readText(type) {
// With type: only in-memory is supported
if (type) {
return this.mapTextToType.get(type) || '';
}
// Guard access to navigator.clipboard with try/catch
// as we have seen DOMExceptions in certain browsers
// due to security policies.
try {
return await getActiveWindow().navigator.clipboard.readText();
}
catch (error) {
console.error(error);
}
return '';
}
async readFindText() {
return this.findText;
}
async writeFindText(text) {
this.findText = text;
}
static { this.MAX_RESOURCE_STATE_SOURCE_LENGTH = 1000; }
async writeResources(resources) {
if (resources.length === 0) {
this.clearResources();
}
else {
this.resources = resources;
this.resourcesStateHash = await this.computeResourcesStateHash();
}
}
async readResources() {
const resourcesStateHash = await this.computeResourcesStateHash();
if (this.resourcesStateHash !== resourcesStateHash) {
this.clearResources(); // state mismatch, resources no longer valid
}
return this.resources;
}
async computeResourcesStateHash() {
if (this.resources.length === 0) {
return undefined; // no resources, no hash needed
}
// Resources clipboard is managed in-memory only and thus
// fails to invalidate when clipboard data is changing.
// As such, we compute the hash of the current clipboard
// and use that to later validate the resources clipboard.
const clipboardText = await this.readText();
return hash(clipboardText.substring(0, BrowserClipboardService_1.MAX_RESOURCE_STATE_SOURCE_LENGTH));
}
clearResources() {
this.resources = [];
this.resourcesStateHash = undefined;
}
};
BrowserClipboardService = BrowserClipboardService_1 = __decorate([
__param(0, ILayoutService),
__param(1, ILogService)
], BrowserClipboardService);
export { BrowserClipboardService };
@@ -0,0 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from '../../instantiation/common/instantiation.js';
export const IClipboardService = createDecorator('clipboardService');
@@ -0,0 +1,80 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter } from '../../../base/common/event.js';
import { Iterable } from '../../../base/common/iterator.js';
import { toDisposable } from '../../../base/common/lifecycle.js';
import { LinkedList } from '../../../base/common/linkedList.js';
import { validateConstraints } from '../../../base/common/types.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
export const ICommandService = createDecorator('commandService');
export const CommandsRegistry = new class {
constructor() {
this._commands = new Map();
this._onDidRegisterCommand = new Emitter();
this.onDidRegisterCommand = this._onDidRegisterCommand.event;
}
registerCommand(idOrCommand, handler) {
if (!idOrCommand) {
throw new Error(`invalid command`);
}
if (typeof idOrCommand === 'string') {
if (!handler) {
throw new Error(`invalid command`);
}
return this.registerCommand({ id: idOrCommand, handler });
}
// add argument validation if rich command metadata is provided
if (idOrCommand.metadata && Array.isArray(idOrCommand.metadata.args)) {
const constraints = [];
for (const arg of idOrCommand.metadata.args) {
constraints.push(arg.constraint);
}
const actualHandler = idOrCommand.handler;
idOrCommand.handler = function (accessor, ...args) {
validateConstraints(args, constraints);
return actualHandler(accessor, ...args);
};
}
// find a place to store the command
const { id } = idOrCommand;
let commands = this._commands.get(id);
if (!commands) {
commands = new LinkedList();
this._commands.set(id, commands);
}
const removeFn = commands.unshift(idOrCommand);
const ret = toDisposable(() => {
removeFn();
const command = this._commands.get(id);
if (command?.isEmpty()) {
this._commands.delete(id);
}
});
// tell the world about this command
this._onDidRegisterCommand.fire(id);
return ret;
}
registerCommandAlias(oldId, newId) {
return CommandsRegistry.registerCommand(oldId, (accessor, ...args) => accessor.get(ICommandService).executeCommand(newId, ...args));
}
getCommand(id) {
const list = this._commands.get(id);
if (!list || list.isEmpty()) {
return undefined;
}
return Iterable.first(list);
}
getCommands() {
const result = new Map();
for (const key of this._commands.keys()) {
const command = this.getCommand(key);
if (command) {
result.set(key, command);
}
}
return result;
}
};
CommandsRegistry.registerCommand('noop', () => { });
@@ -0,0 +1,90 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from '../../instantiation/common/instantiation.js';
export const IConfigurationService = createDecorator('configurationService');
export function toValuesTree(properties, conflictReporter) {
const root = Object.create(null);
for (const key in properties) {
addToValueTree(root, key, properties[key], conflictReporter);
}
return root;
}
export function addToValueTree(settingsTreeRoot, key, value, conflictReporter) {
const segments = key.split('.');
const last = segments.pop();
let curr = settingsTreeRoot;
for (let i = 0; i < segments.length; i++) {
const s = segments[i];
let obj = curr[s];
switch (typeof obj) {
case 'undefined':
obj = curr[s] = Object.create(null);
break;
case 'object':
if (obj === null) {
conflictReporter(`Ignoring ${key} as ${segments.slice(0, i + 1).join('.')} is null`);
return;
}
break;
default:
conflictReporter(`Ignoring ${key} as ${segments.slice(0, i + 1).join('.')} is ${JSON.stringify(obj)}`);
return;
}
curr = obj;
}
if (typeof curr === 'object' && curr !== null) {
try {
curr[last] = value; // workaround https://github.com/microsoft/vscode/issues/13606
}
catch (e) {
conflictReporter(`Ignoring ${key} as ${segments.join('.')} is ${JSON.stringify(curr)}`);
}
}
else {
conflictReporter(`Ignoring ${key} as ${segments.join('.')} is ${JSON.stringify(curr)}`);
}
}
export function removeFromValueTree(valueTree, key) {
const segments = key.split('.');
doRemoveFromValueTree(valueTree, segments);
}
function doRemoveFromValueTree(valueTree, segments) {
const first = segments.shift();
if (segments.length === 0) {
// Reached last segment
delete valueTree[first];
return;
}
if (Object.keys(valueTree).indexOf(first) !== -1) {
const value = valueTree[first];
if (typeof value === 'object' && !Array.isArray(value)) {
doRemoveFromValueTree(value, segments);
if (Object.keys(value).length === 0) {
delete valueTree[first];
}
}
}
}
/**
* A helper function to get the configuration value with a specific settings path (e.g. config.some.setting)
*/
export function getConfigurationValue(config, settingPath, defaultValue) {
function accessSetting(config, path) {
let current = config;
for (const component of path) {
if (typeof current !== 'object' || current === null) {
return undefined;
}
current = current[component];
}
return current;
}
const path = settingPath.split('.');
const result = accessSetting(config, path);
return typeof result === 'undefined' ? defaultValue : result;
}
export function getLanguageTagSettingPlainKey(settingKey) {
return settingKey.replace(/[\[\]]/g, '');
}
@@ -0,0 +1,574 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as arrays from '../../../base/common/arrays.js';
import { ResourceMap } from '../../../base/common/map.js';
import * as objects from '../../../base/common/objects.js';
import * as types from '../../../base/common/types.js';
import { URI } from '../../../base/common/uri.js';
import { addToValueTree, getConfigurationValue, removeFromValueTree, toValuesTree } from './configuration.js';
import { Extensions, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX } from './configurationRegistry.js';
import { Registry } from '../../registry/common/platform.js';
function freeze(data) {
return Object.isFrozen(data) ? data : objects.deepFreeze(data);
}
export class ConfigurationModel {
static createEmptyModel(logService) {
return new ConfigurationModel({}, [], [], undefined, logService);
}
constructor(_contents, _keys, _overrides, raw, logService) {
this._contents = _contents;
this._keys = _keys;
this._overrides = _overrides;
this.raw = raw;
this.logService = logService;
this.overrideConfigurations = new Map();
}
get rawConfiguration() {
if (!this._rawConfiguration) {
if (this.raw?.length) {
const rawConfigurationModels = this.raw.map(raw => {
if (raw instanceof ConfigurationModel) {
return raw;
}
const parser = new ConfigurationModelParser('', this.logService);
parser.parseRaw(raw);
return parser.configurationModel;
});
this._rawConfiguration = rawConfigurationModels.reduce((previous, current) => current === previous ? current : previous.merge(current), rawConfigurationModels[0]);
}
else {
// raw is same as current
this._rawConfiguration = this;
}
}
return this._rawConfiguration;
}
get contents() {
return this._contents;
}
get overrides() {
return this._overrides;
}
get keys() {
return this._keys;
}
isEmpty() {
return this._keys.length === 0 && Object.keys(this._contents).length === 0 && this._overrides.length === 0;
}
getValue(section) {
return section ? getConfigurationValue(this.contents, section) : this.contents;
}
inspect(section, overrideIdentifier) {
const that = this;
return {
get value() {
return freeze(that.rawConfiguration.getValue(section));
},
get override() {
return overrideIdentifier ? freeze(that.rawConfiguration.getOverrideValue(section, overrideIdentifier)) : undefined;
},
get merged() {
return freeze(overrideIdentifier ? that.rawConfiguration.override(overrideIdentifier).getValue(section) : that.rawConfiguration.getValue(section));
},
get overrides() {
const overrides = [];
for (const { contents, identifiers, keys } of that.rawConfiguration.overrides) {
const value = new ConfigurationModel(contents, keys, [], undefined, that.logService).getValue(section);
if (value !== undefined) {
overrides.push({ identifiers, value });
}
}
return overrides.length ? freeze(overrides) : undefined;
}
};
}
getOverrideValue(section, overrideIdentifier) {
const overrideContents = this.getContentsForOverrideIdentifer(overrideIdentifier);
return overrideContents
? section ? getConfigurationValue(overrideContents, section) : overrideContents
: undefined;
}
override(identifier) {
let overrideConfigurationModel = this.overrideConfigurations.get(identifier);
if (!overrideConfigurationModel) {
overrideConfigurationModel = this.createOverrideConfigurationModel(identifier);
this.overrideConfigurations.set(identifier, overrideConfigurationModel);
}
return overrideConfigurationModel;
}
merge(...others) {
const contents = objects.deepClone(this.contents);
const overrides = objects.deepClone(this.overrides);
const keys = [...this.keys];
const raws = this.raw?.length ? [...this.raw] : [this];
for (const other of others) {
raws.push(...(other.raw?.length ? other.raw : [other]));
if (other.isEmpty()) {
continue;
}
this.mergeContents(contents, other.contents);
for (const otherOverride of other.overrides) {
const [override] = overrides.filter(o => arrays.equals(o.identifiers, otherOverride.identifiers));
if (override) {
this.mergeContents(override.contents, otherOverride.contents);
override.keys.push(...otherOverride.keys);
override.keys = arrays.distinct(override.keys);
}
else {
overrides.push(objects.deepClone(otherOverride));
}
}
for (const key of other.keys) {
if (keys.indexOf(key) === -1) {
keys.push(key);
}
}
}
return new ConfigurationModel(contents, keys, overrides, raws.every(raw => raw instanceof ConfigurationModel) ? undefined : raws, this.logService);
}
createOverrideConfigurationModel(identifier) {
const overrideContents = this.getContentsForOverrideIdentifer(identifier);
if (!overrideContents || typeof overrideContents !== 'object' || !Object.keys(overrideContents).length) {
// If there are no valid overrides, return self
return this;
}
const contents = {};
for (const key of arrays.distinct([...Object.keys(this.contents), ...Object.keys(overrideContents)])) {
let contentsForKey = this.contents[key];
const overrideContentsForKey = overrideContents[key];
// If there are override contents for the key, clone and merge otherwise use base contents
if (overrideContentsForKey) {
// Clone and merge only if base contents and override contents are of type object otherwise just override
if (typeof contentsForKey === 'object' && typeof overrideContentsForKey === 'object') {
contentsForKey = objects.deepClone(contentsForKey);
this.mergeContents(contentsForKey, overrideContentsForKey);
}
else {
contentsForKey = overrideContentsForKey;
}
}
contents[key] = contentsForKey;
}
return new ConfigurationModel(contents, this.keys, this.overrides, undefined, this.logService);
}
mergeContents(source, target) {
for (const key of Object.keys(target)) {
if (key in source) {
if (types.isObject(source[key]) && types.isObject(target[key])) {
this.mergeContents(source[key], target[key]);
continue;
}
}
source[key] = objects.deepClone(target[key]);
}
}
getContentsForOverrideIdentifer(identifier) {
let contentsForIdentifierOnly = null;
let contents = null;
const mergeContents = (contentsToMerge) => {
if (contentsToMerge) {
if (contents) {
this.mergeContents(contents, contentsToMerge);
}
else {
contents = objects.deepClone(contentsToMerge);
}
}
};
for (const override of this.overrides) {
if (override.identifiers.length === 1 && override.identifiers[0] === identifier) {
contentsForIdentifierOnly = override.contents;
}
else if (override.identifiers.includes(identifier)) {
mergeContents(override.contents);
}
}
// Merge contents of the identifier only at the end to take precedence.
mergeContents(contentsForIdentifierOnly);
return contents;
}
toJSON() {
return {
contents: this.contents,
overrides: this.overrides,
keys: this.keys
};
}
setValue(key, value) {
this.updateValue(key, value, false);
}
removeValue(key) {
const index = this.keys.indexOf(key);
if (index === -1) {
return;
}
this.keys.splice(index, 1);
removeFromValueTree(this.contents, key);
if (OVERRIDE_PROPERTY_REGEX.test(key)) {
this.overrides.splice(this.overrides.findIndex(o => arrays.equals(o.identifiers, overrideIdentifiersFromKey(key))), 1);
}
}
updateValue(key, value, add) {
addToValueTree(this.contents, key, value, e => this.logService.error(e));
add = add || this.keys.indexOf(key) === -1;
if (add) {
this.keys.push(key);
}
if (OVERRIDE_PROPERTY_REGEX.test(key)) {
const identifiers = overrideIdentifiersFromKey(key);
const override = {
identifiers,
keys: Object.keys(this.contents[key]),
contents: toValuesTree(this.contents[key], message => this.logService.error(message)),
};
const index = this.overrides.findIndex(o => arrays.equals(o.identifiers, identifiers));
if (index !== -1) {
this.overrides[index] = override;
}
else {
this.overrides.push(override);
}
}
}
}
export class ConfigurationModelParser {
constructor(_name, logService) {
this._name = _name;
this.logService = logService;
this._raw = null;
this._configurationModel = null;
this._restrictedConfigurations = [];
}
get configurationModel() {
return this._configurationModel || ConfigurationModel.createEmptyModel(this.logService);
}
parseRaw(raw, options) {
this._raw = raw;
const { contents, keys, overrides, restricted, hasExcludedProperties } = this.doParseRaw(raw, options);
this._configurationModel = new ConfigurationModel(contents, keys, overrides, hasExcludedProperties ? [raw] : undefined /* raw has not changed */, this.logService);
this._restrictedConfigurations = restricted || [];
}
doParseRaw(raw, options) {
const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties();
const filtered = this.filter(raw, configurationProperties, true, options);
raw = filtered.raw;
const contents = toValuesTree(raw, message => this.logService.error(`Conflict in settings file ${this._name}: ${message}`));
const keys = Object.keys(raw);
const overrides = this.toOverrides(raw, message => this.logService.error(`Conflict in settings file ${this._name}: ${message}`));
return { contents, keys, overrides, restricted: filtered.restricted, hasExcludedProperties: filtered.hasExcludedProperties };
}
filter(properties, configurationProperties, filterOverriddenProperties, options) {
let hasExcludedProperties = false;
if (!options?.scopes && !options?.skipRestricted && !options?.exclude?.length) {
return { raw: properties, restricted: [], hasExcludedProperties };
}
const raw = {};
const restricted = [];
for (const key in properties) {
if (OVERRIDE_PROPERTY_REGEX.test(key) && filterOverriddenProperties) {
const result = this.filter(properties[key], configurationProperties, false, options);
raw[key] = result.raw;
hasExcludedProperties = hasExcludedProperties || result.hasExcludedProperties;
restricted.push(...result.restricted);
}
else {
const propertySchema = configurationProperties[key];
const scope = propertySchema ? typeof propertySchema.scope !== 'undefined' ? propertySchema.scope : 3 /* ConfigurationScope.WINDOW */ : undefined;
if (propertySchema?.restricted) {
restricted.push(key);
}
if (!options.exclude?.includes(key) /* Check exclude */
&& (options.include?.includes(key) /* Check include */
|| ((scope === undefined || options.scopes === undefined || options.scopes.includes(scope)) /* Check scopes */
&& !(options.skipRestricted && propertySchema?.restricted)))) /* Check restricted */ {
raw[key] = properties[key];
}
else {
hasExcludedProperties = true;
}
}
}
return { raw, restricted, hasExcludedProperties };
}
toOverrides(raw, conflictReporter) {
const overrides = [];
for (const key of Object.keys(raw)) {
if (OVERRIDE_PROPERTY_REGEX.test(key)) {
const overrideRaw = {};
for (const keyInOverrideRaw in raw[key]) {
overrideRaw[keyInOverrideRaw] = raw[key][keyInOverrideRaw];
}
overrides.push({
identifiers: overrideIdentifiersFromKey(key),
keys: Object.keys(overrideRaw),
contents: toValuesTree(overrideRaw, conflictReporter)
});
}
}
return overrides;
}
}
class ConfigurationInspectValue {
constructor(key, overrides, _value, overrideIdentifiers, defaultConfiguration, policyConfiguration, applicationConfiguration, userConfiguration, localUserConfiguration, remoteUserConfiguration, workspaceConfiguration, folderConfigurationModel, memoryConfigurationModel) {
this.key = key;
this.overrides = overrides;
this._value = _value;
this.overrideIdentifiers = overrideIdentifiers;
this.defaultConfiguration = defaultConfiguration;
this.policyConfiguration = policyConfiguration;
this.applicationConfiguration = applicationConfiguration;
this.userConfiguration = userConfiguration;
this.localUserConfiguration = localUserConfiguration;
this.remoteUserConfiguration = remoteUserConfiguration;
this.workspaceConfiguration = workspaceConfiguration;
this.folderConfigurationModel = folderConfigurationModel;
this.memoryConfigurationModel = memoryConfigurationModel;
}
toInspectValue(inspectValue) {
return inspectValue?.value !== undefined || inspectValue?.override !== undefined || inspectValue?.overrides !== undefined ? inspectValue : undefined;
}
get userInspectValue() {
if (!this._userInspectValue) {
this._userInspectValue = this.userConfiguration.inspect(this.key, this.overrides.overrideIdentifier);
}
return this._userInspectValue;
}
get user() {
return this.toInspectValue(this.userInspectValue);
}
}
export class Configuration {
constructor(_defaultConfiguration, _policyConfiguration, _applicationConfiguration, _localUserConfiguration, _remoteUserConfiguration, _workspaceConfiguration, _folderConfigurations, _memoryConfiguration, _memoryConfigurationByResource, logService) {
this._defaultConfiguration = _defaultConfiguration;
this._policyConfiguration = _policyConfiguration;
this._applicationConfiguration = _applicationConfiguration;
this._localUserConfiguration = _localUserConfiguration;
this._remoteUserConfiguration = _remoteUserConfiguration;
this._workspaceConfiguration = _workspaceConfiguration;
this._folderConfigurations = _folderConfigurations;
this._memoryConfiguration = _memoryConfiguration;
this._memoryConfigurationByResource = _memoryConfigurationByResource;
this.logService = logService;
this._workspaceConsolidatedConfiguration = null;
this._foldersConsolidatedConfigurations = new ResourceMap();
this._userConfiguration = null;
}
getValue(section, overrides, workspace) {
const consolidateConfigurationModel = this.getConsolidatedConfigurationModel(section, overrides, workspace);
return consolidateConfigurationModel.getValue(section);
}
updateValue(key, value, overrides = {}) {
let memoryConfiguration;
if (overrides.resource) {
memoryConfiguration = this._memoryConfigurationByResource.get(overrides.resource);
if (!memoryConfiguration) {
memoryConfiguration = ConfigurationModel.createEmptyModel(this.logService);
this._memoryConfigurationByResource.set(overrides.resource, memoryConfiguration);
}
}
else {
memoryConfiguration = this._memoryConfiguration;
}
if (value === undefined) {
memoryConfiguration.removeValue(key);
}
else {
memoryConfiguration.setValue(key, value);
}
if (!overrides.resource) {
this._workspaceConsolidatedConfiguration = null;
}
}
inspect(key, overrides, workspace) {
const consolidateConfigurationModel = this.getConsolidatedConfigurationModel(key, overrides, workspace);
const folderConfigurationModel = this.getFolderConfigurationModelForResource(overrides.resource, workspace);
const memoryConfigurationModel = overrides.resource ? this._memoryConfigurationByResource.get(overrides.resource) || this._memoryConfiguration : this._memoryConfiguration;
const overrideIdentifiers = new Set();
for (const override of consolidateConfigurationModel.overrides) {
for (const overrideIdentifier of override.identifiers) {
if (consolidateConfigurationModel.getOverrideValue(key, overrideIdentifier) !== undefined) {
overrideIdentifiers.add(overrideIdentifier);
}
}
}
return new ConfigurationInspectValue(key, overrides, consolidateConfigurationModel.getValue(key), overrideIdentifiers.size ? [...overrideIdentifiers] : undefined, this._defaultConfiguration, this._policyConfiguration.isEmpty() ? undefined : this._policyConfiguration, this.applicationConfiguration.isEmpty() ? undefined : this.applicationConfiguration, this.userConfiguration, this.localUserConfiguration, this.remoteUserConfiguration, workspace ? this._workspaceConfiguration : undefined, folderConfigurationModel ? folderConfigurationModel : undefined, memoryConfigurationModel);
}
get applicationConfiguration() {
return this._applicationConfiguration;
}
get userConfiguration() {
if (!this._userConfiguration) {
this._userConfiguration = this._remoteUserConfiguration.isEmpty() ? this._localUserConfiguration : this._localUserConfiguration.merge(this._remoteUserConfiguration);
}
return this._userConfiguration;
}
get localUserConfiguration() {
return this._localUserConfiguration;
}
get remoteUserConfiguration() {
return this._remoteUserConfiguration;
}
getConsolidatedConfigurationModel(section, overrides, workspace) {
let configurationModel = this.getConsolidatedConfigurationModelForResource(overrides, workspace);
if (overrides.overrideIdentifier) {
configurationModel = configurationModel.override(overrides.overrideIdentifier);
}
if (!this._policyConfiguration.isEmpty() && this._policyConfiguration.getValue(section) !== undefined) {
configurationModel = configurationModel.merge(this._policyConfiguration);
}
return configurationModel;
}
getConsolidatedConfigurationModelForResource({ resource }, workspace) {
let consolidateConfiguration = this.getWorkspaceConsolidatedConfiguration();
if (workspace && resource) {
const root = workspace.getFolder(resource);
if (root) {
consolidateConfiguration = this.getFolderConsolidatedConfiguration(root.uri) || consolidateConfiguration;
}
const memoryConfigurationForResource = this._memoryConfigurationByResource.get(resource);
if (memoryConfigurationForResource) {
consolidateConfiguration = consolidateConfiguration.merge(memoryConfigurationForResource);
}
}
return consolidateConfiguration;
}
getWorkspaceConsolidatedConfiguration() {
if (!this._workspaceConsolidatedConfiguration) {
this._workspaceConsolidatedConfiguration = this._defaultConfiguration.merge(this.applicationConfiguration, this.userConfiguration, this._workspaceConfiguration, this._memoryConfiguration);
}
return this._workspaceConsolidatedConfiguration;
}
getFolderConsolidatedConfiguration(folder) {
let folderConsolidatedConfiguration = this._foldersConsolidatedConfigurations.get(folder);
if (!folderConsolidatedConfiguration) {
const workspaceConsolidateConfiguration = this.getWorkspaceConsolidatedConfiguration();
const folderConfiguration = this._folderConfigurations.get(folder);
if (folderConfiguration) {
folderConsolidatedConfiguration = workspaceConsolidateConfiguration.merge(folderConfiguration);
this._foldersConsolidatedConfigurations.set(folder, folderConsolidatedConfiguration);
}
else {
folderConsolidatedConfiguration = workspaceConsolidateConfiguration;
}
}
return folderConsolidatedConfiguration;
}
getFolderConfigurationModelForResource(resource, workspace) {
if (workspace && resource) {
const root = workspace.getFolder(resource);
if (root) {
return this._folderConfigurations.get(root.uri);
}
}
return undefined;
}
toData() {
return {
defaults: {
contents: this._defaultConfiguration.contents,
overrides: this._defaultConfiguration.overrides,
keys: this._defaultConfiguration.keys
},
policy: {
contents: this._policyConfiguration.contents,
overrides: this._policyConfiguration.overrides,
keys: this._policyConfiguration.keys
},
application: {
contents: this.applicationConfiguration.contents,
overrides: this.applicationConfiguration.overrides,
keys: this.applicationConfiguration.keys
},
user: {
contents: this.userConfiguration.contents,
overrides: this.userConfiguration.overrides,
keys: this.userConfiguration.keys
},
workspace: {
contents: this._workspaceConfiguration.contents,
overrides: this._workspaceConfiguration.overrides,
keys: this._workspaceConfiguration.keys
},
folders: [...this._folderConfigurations.keys()].reduce((result, folder) => {
const { contents, overrides, keys } = this._folderConfigurations.get(folder);
result.push([folder, { contents, overrides, keys }]);
return result;
}, [])
};
}
static parse(data, logService) {
const defaultConfiguration = this.parseConfigurationModel(data.defaults, logService);
const policyConfiguration = this.parseConfigurationModel(data.policy, logService);
const applicationConfiguration = this.parseConfigurationModel(data.application, logService);
const userConfiguration = this.parseConfigurationModel(data.user, logService);
const workspaceConfiguration = this.parseConfigurationModel(data.workspace, logService);
const folders = data.folders.reduce((result, value) => {
result.set(URI.revive(value[0]), this.parseConfigurationModel(value[1], logService));
return result;
}, new ResourceMap());
return new Configuration(defaultConfiguration, policyConfiguration, applicationConfiguration, userConfiguration, ConfigurationModel.createEmptyModel(logService), workspaceConfiguration, folders, ConfigurationModel.createEmptyModel(logService), new ResourceMap(), logService);
}
static parseConfigurationModel(model, logService) {
return new ConfigurationModel(model.contents, model.keys, model.overrides, undefined, logService);
}
}
export class ConfigurationChangeEvent {
constructor(change, previous, currentConfiguraiton, currentWorkspace, logService) {
this.change = change;
this.previous = previous;
this.currentConfiguraiton = currentConfiguraiton;
this.currentWorkspace = currentWorkspace;
this.logService = logService;
this._marker = '\n';
this._markerCode1 = this._marker.charCodeAt(0);
this._markerCode2 = '.'.charCodeAt(0);
this.affectedKeys = new Set();
this._previousConfiguration = undefined;
for (const key of change.keys) {
this.affectedKeys.add(key);
}
for (const [, keys] of change.overrides) {
for (const key of keys) {
this.affectedKeys.add(key);
}
}
// Example: '\nfoo.bar\nabc.def\n'
this._affectsConfigStr = this._marker;
for (const key of this.affectedKeys) {
this._affectsConfigStr += key + this._marker;
}
}
get previousConfiguration() {
if (!this._previousConfiguration && this.previous) {
this._previousConfiguration = Configuration.parse(this.previous.data, this.logService);
}
return this._previousConfiguration;
}
affectsConfiguration(section, overrides) {
// we have one large string with all keys that have changed. we pad (marker) the section
// and check that either find it padded or before a segment character
const needle = this._marker + section;
const idx = this._affectsConfigStr.indexOf(needle);
if (idx < 0) {
// NOT: (marker + section)
return false;
}
const pos = idx + needle.length;
if (pos >= this._affectsConfigStr.length) {
return false;
}
const code = this._affectsConfigStr.charCodeAt(pos);
if (code !== this._markerCode1 && code !== this._markerCode2) {
// NOT: section + (marker | segment)
return false;
}
if (overrides) {
const value1 = this.previousConfiguration ? this.previousConfiguration.getValue(section, overrides, this.previous?.workspace) : undefined;
const value2 = this.currentConfiguraiton.getValue(section, overrides, this.currentWorkspace);
return !objects.equals(value1, value2);
}
return true;
}
}
@@ -0,0 +1,389 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { distinct } from '../../../base/common/arrays.js';
import { Emitter } from '../../../base/common/event.js';
import * as types from '../../../base/common/types.js';
import * as nls from '../../../nls.js';
import { getLanguageTagSettingPlainKey } from './configuration.js';
import { Extensions as JSONExtensions } from '../../jsonschemas/common/jsonContributionRegistry.js';
import { Registry } from '../../registry/common/platform.js';
export const Extensions = {
Configuration: 'base.contributions.configuration'
};
export const allSettings = { properties: {}, patternProperties: {} };
export const applicationSettings = { properties: {}, patternProperties: {} };
export const machineSettings = { properties: {}, patternProperties: {} };
export const machineOverridableSettings = { properties: {}, patternProperties: {} };
export const windowSettings = { properties: {}, patternProperties: {} };
export const resourceSettings = { properties: {}, patternProperties: {} };
export const resourceLanguageSettingsSchemaId = 'vscode://schemas/settings/resourceLanguage';
const contributionRegistry = Registry.as(JSONExtensions.JSONContribution);
class ConfigurationRegistry {
constructor() {
this.registeredConfigurationDefaults = [];
this.overrideIdentifiers = new Set();
this._onDidSchemaChange = new Emitter();
this._onDidUpdateConfiguration = new Emitter();
this.configurationDefaultsOverrides = new Map();
this.defaultLanguageConfigurationOverridesNode = {
id: 'defaultOverrides',
title: nls.localize('defaultLanguageConfigurationOverrides.title', "Default Language Configuration Overrides"),
properties: {}
};
this.configurationContributors = [this.defaultLanguageConfigurationOverridesNode];
this.resourceLanguageSettingsSchema = {
properties: {},
patternProperties: {},
additionalProperties: true,
allowTrailingCommas: true,
allowComments: true
};
this.configurationProperties = {};
this.policyConfigurations = new Map();
this.excludedConfigurationProperties = {};
contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema);
this.registerOverridePropertyPatternKey();
}
registerConfiguration(configuration, validate = true) {
this.registerConfigurations([configuration], validate);
}
registerConfigurations(configurations, validate = true) {
const properties = new Set();
this.doRegisterConfigurations(configurations, validate, properties);
contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema);
this._onDidSchemaChange.fire();
this._onDidUpdateConfiguration.fire({ properties });
}
registerDefaultConfigurations(configurationDefaults) {
const properties = new Set();
this.doRegisterDefaultConfigurations(configurationDefaults, properties);
this._onDidSchemaChange.fire();
this._onDidUpdateConfiguration.fire({ properties, defaultsOverrides: true });
}
doRegisterDefaultConfigurations(configurationDefaults, bucket) {
this.registeredConfigurationDefaults.push(...configurationDefaults);
const overrideIdentifiers = [];
for (const { overrides, source } of configurationDefaults) {
for (const key in overrides) {
bucket.add(key);
const configurationDefaultOverridesForKey = this.configurationDefaultsOverrides.get(key)
?? this.configurationDefaultsOverrides.set(key, { configurationDefaultOverrides: [] }).get(key);
const value = overrides[key];
configurationDefaultOverridesForKey.configurationDefaultOverrides.push({ value, source });
// Configuration defaults for Override Identifiers
if (OVERRIDE_PROPERTY_REGEX.test(key)) {
const newDefaultOverride = this.mergeDefaultConfigurationsForOverrideIdentifier(key, value, source, configurationDefaultOverridesForKey.configurationDefaultOverrideValue);
if (!newDefaultOverride) {
continue;
}
configurationDefaultOverridesForKey.configurationDefaultOverrideValue = newDefaultOverride;
this.updateDefaultOverrideProperty(key, newDefaultOverride, source);
overrideIdentifiers.push(...overrideIdentifiersFromKey(key));
}
// Configuration defaults for Configuration Properties
else {
const newDefaultOverride = this.mergeDefaultConfigurationsForConfigurationProperty(key, value, source, configurationDefaultOverridesForKey.configurationDefaultOverrideValue);
if (!newDefaultOverride) {
continue;
}
configurationDefaultOverridesForKey.configurationDefaultOverrideValue = newDefaultOverride;
const property = this.configurationProperties[key];
if (property) {
this.updatePropertyDefaultValue(key, property);
this.updateSchema(key, property);
}
}
}
}
this.doRegisterOverrideIdentifiers(overrideIdentifiers);
}
updateDefaultOverrideProperty(key, newDefaultOverride, source) {
const property = {
type: 'object',
default: newDefaultOverride.value,
description: nls.localize('defaultLanguageConfiguration.description', "Configure settings to be overridden for the {0} language.", getLanguageTagSettingPlainKey(key)),
$ref: resourceLanguageSettingsSchemaId,
defaultDefaultValue: newDefaultOverride.value,
source,
defaultValueSource: source
};
this.configurationProperties[key] = property;
this.defaultLanguageConfigurationOverridesNode.properties[key] = property;
}
mergeDefaultConfigurationsForOverrideIdentifier(overrideIdentifier, configurationValueObject, valueSource, existingDefaultOverride) {
const defaultValue = existingDefaultOverride?.value || {};
const source = existingDefaultOverride?.source ?? new Map();
// This should not happen
if (!(source instanceof Map)) {
console.error('objectConfigurationSources is not a Map');
return undefined;
}
for (const propertyKey of Object.keys(configurationValueObject)) {
const propertyDefaultValue = configurationValueObject[propertyKey];
const isObjectSetting = types.isObject(propertyDefaultValue) &&
(types.isUndefined(defaultValue[propertyKey]) || types.isObject(defaultValue[propertyKey]));
// If the default value is an object, merge the objects and store the source of each keys
if (isObjectSetting) {
defaultValue[propertyKey] = { ...(defaultValue[propertyKey] ?? {}), ...propertyDefaultValue };
// Track the source of each value in the object
if (valueSource) {
for (const objectKey in propertyDefaultValue) {
source.set(`${propertyKey}.${objectKey}`, valueSource);
}
}
}
// Primitive values are overridden
else {
defaultValue[propertyKey] = propertyDefaultValue;
if (valueSource) {
source.set(propertyKey, valueSource);
}
else {
source.delete(propertyKey);
}
}
}
return { value: defaultValue, source };
}
mergeDefaultConfigurationsForConfigurationProperty(propertyKey, value, valuesSource, existingDefaultOverride) {
const property = this.configurationProperties[propertyKey];
const existingDefaultValue = existingDefaultOverride?.value ?? property?.defaultDefaultValue;
let source = valuesSource;
const isObjectSetting = types.isObject(value) &&
(property !== undefined && property.type === 'object' ||
property === undefined && (types.isUndefined(existingDefaultValue) || types.isObject(existingDefaultValue)));
// If the default value is an object, merge the objects and store the source of each keys
if (isObjectSetting) {
source = existingDefaultOverride?.source ?? new Map();
// This should not happen
if (!(source instanceof Map)) {
console.error('defaultValueSource is not a Map');
return undefined;
}
for (const objectKey in value) {
if (valuesSource) {
source.set(`${propertyKey}.${objectKey}`, valuesSource);
}
}
value = { ...(types.isObject(existingDefaultValue) ? existingDefaultValue : {}), ...value };
}
return { value, source };
}
registerOverrideIdentifiers(overrideIdentifiers) {
this.doRegisterOverrideIdentifiers(overrideIdentifiers);
this._onDidSchemaChange.fire();
}
doRegisterOverrideIdentifiers(overrideIdentifiers) {
for (const overrideIdentifier of overrideIdentifiers) {
this.overrideIdentifiers.add(overrideIdentifier);
}
this.updateOverridePropertyPatternKey();
}
doRegisterConfigurations(configurations, validate, bucket) {
configurations.forEach(configuration => {
this.validateAndRegisterProperties(configuration, validate, configuration.extensionInfo, configuration.restrictedProperties, undefined, bucket);
this.configurationContributors.push(configuration);
this.registerJSONConfiguration(configuration);
});
}
validateAndRegisterProperties(configuration, validate = true, extensionInfo, restrictedProperties, scope = 3 /* ConfigurationScope.WINDOW */, bucket) {
scope = types.isUndefinedOrNull(configuration.scope) ? scope : configuration.scope;
const properties = configuration.properties;
if (properties) {
for (const key in properties) {
const property = properties[key];
if (validate && validateProperty(key, property)) {
delete properties[key];
continue;
}
property.source = extensionInfo;
// update default value
property.defaultDefaultValue = properties[key].default;
this.updatePropertyDefaultValue(key, property);
// update scope
if (OVERRIDE_PROPERTY_REGEX.test(key)) {
property.scope = undefined; // No scope for overridable properties `[${identifier}]`
}
else {
property.scope = types.isUndefinedOrNull(property.scope) ? scope : property.scope;
property.restricted = types.isUndefinedOrNull(property.restricted) ? !!restrictedProperties?.includes(key) : property.restricted;
}
// Add to properties maps
// Property is included by default if 'included' is unspecified
if (properties[key].hasOwnProperty('included') && !properties[key].included) {
this.excludedConfigurationProperties[key] = properties[key];
delete properties[key];
continue;
}
else {
this.configurationProperties[key] = properties[key];
if (properties[key].policy?.name) {
this.policyConfigurations.set(properties[key].policy.name, key);
}
}
if (!properties[key].deprecationMessage && properties[key].markdownDeprecationMessage) {
// If not set, default deprecationMessage to the markdown source
properties[key].deprecationMessage = properties[key].markdownDeprecationMessage;
}
bucket.add(key);
}
}
const subNodes = configuration.allOf;
if (subNodes) {
for (const node of subNodes) {
this.validateAndRegisterProperties(node, validate, extensionInfo, restrictedProperties, scope, bucket);
}
}
}
getConfigurationProperties() {
return this.configurationProperties;
}
getPolicyConfigurations() {
return this.policyConfigurations;
}
registerJSONConfiguration(configuration) {
const register = (configuration) => {
const properties = configuration.properties;
if (properties) {
for (const key in properties) {
this.updateSchema(key, properties[key]);
}
}
const subNodes = configuration.allOf;
subNodes?.forEach(register);
};
register(configuration);
}
updateSchema(key, property) {
allSettings.properties[key] = property;
switch (property.scope) {
case 1 /* ConfigurationScope.APPLICATION */:
applicationSettings.properties[key] = property;
break;
case 2 /* ConfigurationScope.MACHINE */:
machineSettings.properties[key] = property;
break;
case 6 /* ConfigurationScope.MACHINE_OVERRIDABLE */:
machineOverridableSettings.properties[key] = property;
break;
case 3 /* ConfigurationScope.WINDOW */:
windowSettings.properties[key] = property;
break;
case 4 /* ConfigurationScope.RESOURCE */:
resourceSettings.properties[key] = property;
break;
case 5 /* ConfigurationScope.LANGUAGE_OVERRIDABLE */:
resourceSettings.properties[key] = property;
this.resourceLanguageSettingsSchema.properties[key] = property;
break;
}
}
updateOverridePropertyPatternKey() {
for (const overrideIdentifier of this.overrideIdentifiers.values()) {
const overrideIdentifierProperty = `[${overrideIdentifier}]`;
const resourceLanguagePropertiesSchema = {
type: 'object',
description: nls.localize('overrideSettings.defaultDescription', "Configure editor settings to be overridden for a language."),
errorMessage: nls.localize('overrideSettings.errorMessage', "This setting does not support per-language configuration."),
$ref: resourceLanguageSettingsSchemaId,
};
this.updatePropertyDefaultValue(overrideIdentifierProperty, resourceLanguagePropertiesSchema);
allSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;
applicationSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;
machineSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;
machineOverridableSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;
windowSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;
resourceSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;
}
}
registerOverridePropertyPatternKey() {
const resourceLanguagePropertiesSchema = {
type: 'object',
description: nls.localize('overrideSettings.defaultDescription', "Configure editor settings to be overridden for a language."),
errorMessage: nls.localize('overrideSettings.errorMessage', "This setting does not support per-language configuration."),
$ref: resourceLanguageSettingsSchemaId,
};
allSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema;
applicationSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema;
machineSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema;
machineOverridableSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema;
windowSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema;
resourceSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema;
this._onDidSchemaChange.fire();
}
updatePropertyDefaultValue(key, property) {
const configurationdefaultOverride = this.configurationDefaultsOverrides.get(key)?.configurationDefaultOverrideValue;
let defaultValue = undefined;
let defaultSource = undefined;
if (configurationdefaultOverride
&& (!property.disallowConfigurationDefault || !configurationdefaultOverride.source) // Prevent overriding the default value if the property is disallowed to be overridden by configuration defaults from extensions
) {
defaultValue = configurationdefaultOverride.value;
defaultSource = configurationdefaultOverride.source;
}
if (types.isUndefined(defaultValue)) {
defaultValue = property.defaultDefaultValue;
defaultSource = undefined;
}
if (types.isUndefined(defaultValue)) {
defaultValue = getDefaultValue(property.type);
}
property.default = defaultValue;
property.defaultValueSource = defaultSource;
}
}
const OVERRIDE_IDENTIFIER_PATTERN = `\\[([^\\]]+)\\]`;
const OVERRIDE_IDENTIFIER_REGEX = new RegExp(OVERRIDE_IDENTIFIER_PATTERN, 'g');
export const OVERRIDE_PROPERTY_PATTERN = `^(${OVERRIDE_IDENTIFIER_PATTERN})+$`;
export const OVERRIDE_PROPERTY_REGEX = new RegExp(OVERRIDE_PROPERTY_PATTERN);
export function overrideIdentifiersFromKey(key) {
const identifiers = [];
if (OVERRIDE_PROPERTY_REGEX.test(key)) {
let matches = OVERRIDE_IDENTIFIER_REGEX.exec(key);
while (matches?.length) {
const identifier = matches[1].trim();
if (identifier) {
identifiers.push(identifier);
}
matches = OVERRIDE_IDENTIFIER_REGEX.exec(key);
}
}
return distinct(identifiers);
}
export function getDefaultValue(type) {
const t = Array.isArray(type) ? type[0] : type;
switch (t) {
case 'boolean':
return false;
case 'integer':
case 'number':
return 0;
case 'string':
return '';
case 'array':
return [];
case 'object':
return {};
default:
return null;
}
}
const configurationRegistry = new ConfigurationRegistry();
Registry.add(Extensions.Configuration, configurationRegistry);
export function validateProperty(property, schema) {
if (!property.trim()) {
return nls.localize('config.property.empty', "Cannot register an empty property");
}
if (OVERRIDE_PROPERTY_REGEX.test(property)) {
return nls.localize('config.property.languageDefault', "Cannot register '{0}'. This matches property pattern '\\\\[.*\\\\]$' for describing language specific editor settings. Use 'configurationDefaults' contribution.", property);
}
if (configurationRegistry.getConfigurationProperties()[property] !== undefined) {
return nls.localize('config.property.duplicate', "Cannot register '{0}'. This property is already registered.", property);
}
if (schema.policy?.name && configurationRegistry.getPolicyConfigurations().get(schema.policy?.name) !== undefined) {
return nls.localize('config.policy.duplicate', "Cannot register '{0}'. The associated policy {1} is already registered with {2}.", property, schema.policy?.name, configurationRegistry.getPolicyConfigurations().get(schema.policy?.name));
}
return null;
}
@@ -0,0 +1,42 @@
import { Disposable } from '../../../base/common/lifecycle.js';
import { ConfigurationModel } from './configurationModels.js';
import { Extensions } from './configurationRegistry.js';
import { Registry } from '../../registry/common/platform.js';
export class DefaultConfiguration extends Disposable {
get configurationModel() {
return this._configurationModel;
}
constructor(logService) {
super();
this.logService = logService;
this._configurationModel = ConfigurationModel.createEmptyModel(this.logService);
}
reload() {
this.resetConfigurationModel();
return this.configurationModel;
}
getConfigurationDefaultOverrides() {
return {};
}
resetConfigurationModel() {
this._configurationModel = ConfigurationModel.createEmptyModel(this.logService);
const properties = Registry.as(Extensions.Configuration).getConfigurationProperties();
this.updateConfigurationModel(Object.keys(properties), properties);
}
updateConfigurationModel(properties, configurationProperties) {
const configurationDefaultsOverrides = this.getConfigurationDefaultOverrides();
for (const key of properties) {
const defaultOverrideValue = configurationDefaultsOverrides[key];
const propertySchema = configurationProperties[key];
if (defaultOverrideValue !== undefined) {
this._configurationModel.setValue(key, defaultOverrideValue);
}
else if (propertySchema) {
this._configurationModel.setValue(key, propertySchema.default);
}
else {
this._configurationModel.removeValue(key);
}
}
}
}
@@ -0,0 +1,432 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { PauseableEmitter } from '../../../base/common/event.js';
import { Iterable } from '../../../base/common/iterator.js';
import { Disposable, MutableDisposable } from '../../../base/common/lifecycle.js';
import { cloneAndChange } from '../../../base/common/objects.js';
import { TernarySearchTree } from '../../../base/common/ternarySearchTree.js';
import { URI } from '../../../base/common/uri.js';
import { localize } from '../../../nls.js';
import { CommandsRegistry } from '../../commands/common/commands.js';
import { IConfigurationService } from '../../configuration/common/configuration.js';
import { IContextKeyService, RawContextKey } from '../common/contextkey.js';
const KEYBINDING_CONTEXT_ATTR = 'data-keybinding-context';
export class Context {
constructor(id, parent) {
this._id = id;
this._parent = parent;
this._value = Object.create(null);
this._value['_contextId'] = id;
}
get value() {
return { ...this._value };
}
setValue(key, value) {
// console.log('SET ' + key + ' = ' + value + ' ON ' + this._id);
if (this._value[key] !== value) {
this._value[key] = value;
return true;
}
return false;
}
removeValue(key) {
// console.log('REMOVE ' + key + ' FROM ' + this._id);
if (key in this._value) {
delete this._value[key];
return true;
}
return false;
}
getValue(key) {
const ret = this._value[key];
if (typeof ret === 'undefined' && this._parent) {
return this._parent.getValue(key);
}
return ret;
}
}
class NullContext extends Context {
static { this.INSTANCE = new NullContext(); }
constructor() {
super(-1, null);
}
setValue(key, value) {
return false;
}
removeValue(key) {
return false;
}
getValue(key) {
return undefined;
}
}
class ConfigAwareContextValuesContainer extends Context {
static { this._keyPrefix = 'config.'; }
constructor(id, _configurationService, emitter) {
super(id, null);
this._configurationService = _configurationService;
this._values = TernarySearchTree.forConfigKeys();
this._listener = this._configurationService.onDidChangeConfiguration(event => {
if (event.source === 7 /* ConfigurationTarget.DEFAULT */) {
// new setting, reset everything
const allKeys = Array.from(this._values, ([k]) => k);
this._values.clear();
emitter.fire(new ArrayContextKeyChangeEvent(allKeys));
}
else {
const changedKeys = [];
for (const configKey of event.affectedKeys) {
const contextKey = `config.${configKey}`;
const cachedItems = this._values.findSuperstr(contextKey);
if (cachedItems !== undefined) {
changedKeys.push(...Iterable.map(cachedItems, ([key]) => key));
this._values.deleteSuperstr(contextKey);
}
if (this._values.has(contextKey)) {
changedKeys.push(contextKey);
this._values.delete(contextKey);
}
}
emitter.fire(new ArrayContextKeyChangeEvent(changedKeys));
}
});
}
dispose() {
this._listener.dispose();
}
getValue(key) {
if (key.indexOf(ConfigAwareContextValuesContainer._keyPrefix) !== 0) {
return super.getValue(key);
}
if (this._values.has(key)) {
return this._values.get(key);
}
const configKey = key.substr(ConfigAwareContextValuesContainer._keyPrefix.length);
const configValue = this._configurationService.getValue(configKey);
let value = undefined;
switch (typeof configValue) {
case 'number':
case 'boolean':
case 'string':
value = configValue;
break;
default:
if (Array.isArray(configValue)) {
value = JSON.stringify(configValue);
}
else {
value = configValue;
}
}
this._values.set(key, value);
return value;
}
setValue(key, value) {
return super.setValue(key, value);
}
removeValue(key) {
return super.removeValue(key);
}
}
class ContextKey {
constructor(service, key, defaultValue) {
this._service = service;
this._key = key;
this._defaultValue = defaultValue;
this.reset();
}
set(value) {
this._service.setContext(this._key, value);
}
reset() {
if (typeof this._defaultValue === 'undefined') {
this._service.removeContext(this._key);
}
else {
this._service.setContext(this._key, this._defaultValue);
}
}
get() {
return this._service.getContextKeyValue(this._key);
}
}
class SimpleContextKeyChangeEvent {
constructor(key) {
this.key = key;
}
affectsSome(keys) {
return keys.has(this.key);
}
allKeysContainedIn(keys) {
return this.affectsSome(keys);
}
}
class ArrayContextKeyChangeEvent {
constructor(keys) {
this.keys = keys;
}
affectsSome(keys) {
for (const key of this.keys) {
if (keys.has(key)) {
return true;
}
}
return false;
}
allKeysContainedIn(keys) {
return this.keys.every(key => keys.has(key));
}
}
class CompositeContextKeyChangeEvent {
constructor(events) {
this.events = events;
}
affectsSome(keys) {
for (const e of this.events) {
if (e.affectsSome(keys)) {
return true;
}
}
return false;
}
allKeysContainedIn(keys) {
return this.events.every(evt => evt.allKeysContainedIn(keys));
}
}
function allEventKeysInContext(event, context) {
return event.allKeysContainedIn(new Set(Object.keys(context)));
}
export class AbstractContextKeyService extends Disposable {
constructor(myContextId) {
super();
this._onDidChangeContext = this._register(new PauseableEmitter({ merge: input => new CompositeContextKeyChangeEvent(input) }));
this.onDidChangeContext = this._onDidChangeContext.event;
this._isDisposed = false;
this._myContextId = myContextId;
}
createKey(key, defaultValue) {
if (this._isDisposed) {
throw new Error(`AbstractContextKeyService has been disposed`);
}
return new ContextKey(this, key, defaultValue);
}
bufferChangeEvents(callback) {
this._onDidChangeContext.pause();
try {
callback();
}
finally {
this._onDidChangeContext.resume();
}
}
createScoped(domNode) {
if (this._isDisposed) {
throw new Error(`AbstractContextKeyService has been disposed`);
}
return new ScopedContextKeyService(this, domNode);
}
contextMatchesRules(rules) {
if (this._isDisposed) {
throw new Error(`AbstractContextKeyService has been disposed`);
}
const context = this.getContextValuesContainer(this._myContextId);
const result = (rules ? rules.evaluate(context) : true);
// console.group(rules.serialize() + ' -> ' + result);
// rules.keys().forEach(key => { console.log(key, ctx[key]); });
// console.groupEnd();
return result;
}
getContextKeyValue(key) {
if (this._isDisposed) {
return undefined;
}
return this.getContextValuesContainer(this._myContextId).getValue(key);
}
setContext(key, value) {
if (this._isDisposed) {
return;
}
const myContext = this.getContextValuesContainer(this._myContextId);
if (!myContext) {
return;
}
if (myContext.setValue(key, value)) {
this._onDidChangeContext.fire(new SimpleContextKeyChangeEvent(key));
}
}
removeContext(key) {
if (this._isDisposed) {
return;
}
if (this.getContextValuesContainer(this._myContextId).removeValue(key)) {
this._onDidChangeContext.fire(new SimpleContextKeyChangeEvent(key));
}
}
getContext(target) {
if (this._isDisposed) {
return NullContext.INSTANCE;
}
return this.getContextValuesContainer(findContextAttr(target));
}
dispose() {
super.dispose();
this._isDisposed = true;
}
}
let ContextKeyService = class ContextKeyService extends AbstractContextKeyService {
constructor(configurationService) {
super(0);
this._contexts = new Map();
this._lastContextId = 0;
const myContext = this._register(new ConfigAwareContextValuesContainer(this._myContextId, configurationService, this._onDidChangeContext));
this._contexts.set(this._myContextId, myContext);
// Uncomment this to see the contexts continuously logged
// let lastLoggedValue: string | null = null;
// setInterval(() => {
// let values = Object.keys(this._contexts).map((key) => this._contexts[key]);
// let logValue = values.map(v => JSON.stringify(v._value, null, '\t')).join('\n');
// if (lastLoggedValue !== logValue) {
// lastLoggedValue = logValue;
// console.log(lastLoggedValue);
// }
// }, 2000);
}
getContextValuesContainer(contextId) {
if (this._isDisposed) {
return NullContext.INSTANCE;
}
return this._contexts.get(contextId) || NullContext.INSTANCE;
}
createChildContext(parentContextId = this._myContextId) {
if (this._isDisposed) {
throw new Error(`ContextKeyService has been disposed`);
}
const id = (++this._lastContextId);
this._contexts.set(id, new Context(id, this.getContextValuesContainer(parentContextId)));
return id;
}
disposeContext(contextId) {
if (!this._isDisposed) {
this._contexts.delete(contextId);
}
}
};
ContextKeyService = __decorate([
__param(0, IConfigurationService)
], ContextKeyService);
export { ContextKeyService };
class ScopedContextKeyService extends AbstractContextKeyService {
constructor(parent, domNode) {
super(parent.createChildContext());
this._parentChangeListener = this._register(new MutableDisposable());
this._parent = parent;
this._updateParentChangeListener();
this._domNode = domNode;
if (this._domNode.hasAttribute(KEYBINDING_CONTEXT_ATTR)) {
let extraInfo = '';
if (this._domNode.classList) {
extraInfo = Array.from(this._domNode.classList.values()).join(', ');
}
console.error(`Element already has context attribute${extraInfo ? ': ' + extraInfo : ''}`);
}
this._domNode.setAttribute(KEYBINDING_CONTEXT_ATTR, String(this._myContextId));
}
_updateParentChangeListener() {
// Forward parent events to this listener. Parent will change.
this._parentChangeListener.value = this._parent.onDidChangeContext(e => {
const thisContainer = this._parent.getContextValuesContainer(this._myContextId);
const thisContextValues = thisContainer.value;
if (!allEventKeysInContext(e, thisContextValues)) {
this._onDidChangeContext.fire(e);
}
});
}
dispose() {
if (this._isDisposed) {
return;
}
this._parent.disposeContext(this._myContextId);
this._domNode.removeAttribute(KEYBINDING_CONTEXT_ATTR);
super.dispose();
}
getContextValuesContainer(contextId) {
if (this._isDisposed) {
return NullContext.INSTANCE;
}
return this._parent.getContextValuesContainer(contextId);
}
createChildContext(parentContextId = this._myContextId) {
if (this._isDisposed) {
throw new Error(`ScopedContextKeyService has been disposed`);
}
return this._parent.createChildContext(parentContextId);
}
disposeContext(contextId) {
if (this._isDisposed) {
return;
}
this._parent.disposeContext(contextId);
}
}
function findContextAttr(domNode) {
while (domNode) {
if (domNode.hasAttribute(KEYBINDING_CONTEXT_ATTR)) {
const attr = domNode.getAttribute(KEYBINDING_CONTEXT_ATTR);
if (attr) {
return parseInt(attr, 10);
}
return NaN;
}
domNode = domNode.parentElement;
}
return 0;
}
export function setContext(accessor, contextKey, contextValue) {
const contextKeyService = accessor.get(IContextKeyService);
contextKeyService.createKey(String(contextKey), stringifyURIs(contextValue));
}
function stringifyURIs(contextValue) {
return cloneAndChange(contextValue, (obj) => {
if (typeof obj === 'object' && obj.$mid === 1 /* MarshalledId.Uri */) {
return URI.revive(obj).toString();
}
if (obj instanceof URI) {
return obj.toString();
}
return undefined;
});
}
CommandsRegistry.registerCommand('_setContext', setContext);
CommandsRegistry.registerCommand({
id: 'getContextKeyInfo',
handler() {
return [...RawContextKey.all()].sort((a, b) => a.key.localeCompare(b.key));
},
metadata: {
description: localize('getContextKeyInfo', "A command that returns information about context keys"),
args: []
}
});
CommandsRegistry.registerCommand('_generateContextKeyInfo', function () {
const result = [];
const seen = new Set();
for (const info of RawContextKey.all()) {
if (!seen.has(info.key)) {
seen.add(info.key);
result.push(info);
}
}
result.sort((a, b) => a.key.localeCompare(b.key));
console.log(JSON.stringify(result, undefined, 2));
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isIOS, isLinux, isMacintosh, isMobile, isWeb, isWindows } from '../../../base/common/platform.js';
import { localize } from '../../../nls.js';
import { RawContextKey } from './contextkey.js';
export const IsMacContext = new RawContextKey('isMac', isMacintosh, localize('isMac', "Whether the operating system is macOS"));
export const IsLinuxContext = new RawContextKey('isLinux', isLinux, localize('isLinux', "Whether the operating system is Linux"));
export const IsWindowsContext = new RawContextKey('isWindows', isWindows, localize('isWindows', "Whether the operating system is Windows"));
export const IsWebContext = new RawContextKey('isWeb', isWeb, localize('isWeb', "Whether the platform is a web browser"));
export const IsMacNativeContext = new RawContextKey('isMacNative', isMacintosh && !isWeb, localize('isMacNative', "Whether the operating system is macOS on a non-browser platform"));
export const IsIOSContext = new RawContextKey('isIOS', isIOS, localize('isIOS', "Whether the operating system is iOS"));
export const IsMobileContext = new RawContextKey('isMobile', isMobile, localize('isMobile', "Whether the platform is a mobile web browser"));
export const IsDevelopmentContext = new RawContextKey('isDevelopment', false, true);
export const ProductQualityContext = new RawContextKey('productQualityType', '', localize('productQualityType', "Quality type of VS Code"));
export const InputFocusedContextKey = 'inputFocus';
export const InputFocusedContext = new RawContextKey(InputFocusedContextKey, false, localize('inputFocus', "Whether keyboard focus is inside an input box"));
@@ -0,0 +1,284 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { illegalState } from '../../../base/common/errors.js';
import { localize } from '../../../nls.js';
function hintDidYouMean(...meant) {
switch (meant.length) {
case 1:
return localize('contextkey.scanner.hint.didYouMean1', "Did you mean {0}?", meant[0]);
case 2:
return localize('contextkey.scanner.hint.didYouMean2', "Did you mean {0} or {1}?", meant[0], meant[1]);
case 3:
return localize('contextkey.scanner.hint.didYouMean3', "Did you mean {0}, {1} or {2}?", meant[0], meant[1], meant[2]);
default: // we just don't expect that many
return undefined;
}
}
const hintDidYouForgetToOpenOrCloseQuote = localize('contextkey.scanner.hint.didYouForgetToOpenOrCloseQuote', "Did you forget to open or close the quote?");
const hintDidYouForgetToEscapeSlash = localize('contextkey.scanner.hint.didYouForgetToEscapeSlash', "Did you forget to escape the '/' (slash) character? Put two backslashes before it to escape, e.g., '\\\\/\'.");
/**
* A simple scanner for context keys.
*
* Example:
*
* ```ts
* const scanner = new Scanner().reset('resourceFileName =~ /docker/ && !config.docker.enabled');
* const tokens = [...scanner];
* if (scanner.errorTokens.length > 0) {
* scanner.errorTokens.forEach(err => console.error(`Unexpected token at ${err.offset}: ${err.lexeme}\nHint: ${err.additional}`));
* } else {
* // process tokens
* }
* ```
*/
export class Scanner {
constructor() {
this._input = '';
this._start = 0;
this._current = 0;
this._tokens = [];
this._errors = [];
// u - unicode, y - sticky // TODO@ulugbekna: we accept double quotes as part of the string rather than as a delimiter (to preserve old parser's behavior)
this.stringRe = /[a-zA-Z0-9_<>\-\./\\:\*\?\+\[\]\^,#@;"%\$\p{L}-]+/uy;
}
static getLexeme(token) {
switch (token.type) {
case 0 /* TokenType.LParen */:
return '(';
case 1 /* TokenType.RParen */:
return ')';
case 2 /* TokenType.Neg */:
return '!';
case 3 /* TokenType.Eq */:
return token.isTripleEq ? '===' : '==';
case 4 /* TokenType.NotEq */:
return token.isTripleEq ? '!==' : '!=';
case 5 /* TokenType.Lt */:
return '<';
case 6 /* TokenType.LtEq */:
return '<=';
case 7 /* TokenType.Gt */:
return '>=';
case 8 /* TokenType.GtEq */:
return '>=';
case 9 /* TokenType.RegexOp */:
return '=~';
case 10 /* TokenType.RegexStr */:
return token.lexeme;
case 11 /* TokenType.True */:
return 'true';
case 12 /* TokenType.False */:
return 'false';
case 13 /* TokenType.In */:
return 'in';
case 14 /* TokenType.Not */:
return 'not';
case 15 /* TokenType.And */:
return '&&';
case 16 /* TokenType.Or */:
return '||';
case 17 /* TokenType.Str */:
return token.lexeme;
case 18 /* TokenType.QuotedStr */:
return token.lexeme;
case 19 /* TokenType.Error */:
return token.lexeme;
case 20 /* TokenType.EOF */:
return 'EOF';
default:
throw illegalState(`unhandled token type: ${JSON.stringify(token)}; have you forgotten to add a case?`);
}
}
static { this._regexFlags = new Set(['i', 'g', 's', 'm', 'y', 'u'].map(ch => ch.charCodeAt(0))); }
static { this._keywords = new Map([
['not', 14 /* TokenType.Not */],
['in', 13 /* TokenType.In */],
['false', 12 /* TokenType.False */],
['true', 11 /* TokenType.True */],
]); }
reset(value) {
this._input = value;
this._start = 0;
this._current = 0;
this._tokens = [];
this._errors = [];
return this;
}
scan() {
while (!this._isAtEnd()) {
this._start = this._current;
const ch = this._advance();
switch (ch) {
case 40 /* CharCode.OpenParen */:
this._addToken(0 /* TokenType.LParen */);
break;
case 41 /* CharCode.CloseParen */:
this._addToken(1 /* TokenType.RParen */);
break;
case 33 /* CharCode.ExclamationMark */:
if (this._match(61 /* CharCode.Equals */)) {
const isTripleEq = this._match(61 /* CharCode.Equals */); // eat last `=` if `!==`
this._tokens.push({ type: 4 /* TokenType.NotEq */, offset: this._start, isTripleEq });
}
else {
this._addToken(2 /* TokenType.Neg */);
}
break;
case 39 /* CharCode.SingleQuote */:
this._quotedString();
break;
case 47 /* CharCode.Slash */:
this._regex();
break;
case 61 /* CharCode.Equals */:
if (this._match(61 /* CharCode.Equals */)) { // support `==`
const isTripleEq = this._match(61 /* CharCode.Equals */); // eat last `=` if `===`
this._tokens.push({ type: 3 /* TokenType.Eq */, offset: this._start, isTripleEq });
}
else if (this._match(126 /* CharCode.Tilde */)) {
this._addToken(9 /* TokenType.RegexOp */);
}
else {
this._error(hintDidYouMean('==', '=~'));
}
break;
case 60 /* CharCode.LessThan */:
this._addToken(this._match(61 /* CharCode.Equals */) ? 6 /* TokenType.LtEq */ : 5 /* TokenType.Lt */);
break;
case 62 /* CharCode.GreaterThan */:
this._addToken(this._match(61 /* CharCode.Equals */) ? 8 /* TokenType.GtEq */ : 7 /* TokenType.Gt */);
break;
case 38 /* CharCode.Ampersand */:
if (this._match(38 /* CharCode.Ampersand */)) {
this._addToken(15 /* TokenType.And */);
}
else {
this._error(hintDidYouMean('&&'));
}
break;
case 124 /* CharCode.Pipe */:
if (this._match(124 /* CharCode.Pipe */)) {
this._addToken(16 /* TokenType.Or */);
}
else {
this._error(hintDidYouMean('||'));
}
break;
// TODO@ulugbekna: 1) rewrite using a regex 2) reconsider what characters are considered whitespace, including unicode, nbsp, etc.
case 32 /* CharCode.Space */:
case 13 /* CharCode.CarriageReturn */:
case 9 /* CharCode.Tab */:
case 10 /* CharCode.LineFeed */:
case 160 /* CharCode.NoBreakSpace */: // &nbsp
break;
default:
this._string();
}
}
this._start = this._current;
this._addToken(20 /* TokenType.EOF */);
return Array.from(this._tokens);
}
_match(expected) {
if (this._isAtEnd()) {
return false;
}
if (this._input.charCodeAt(this._current) !== expected) {
return false;
}
this._current++;
return true;
}
_advance() {
return this._input.charCodeAt(this._current++);
}
_peek() {
return this._isAtEnd() ? 0 /* CharCode.Null */ : this._input.charCodeAt(this._current);
}
_addToken(type) {
this._tokens.push({ type, offset: this._start });
}
_error(additional) {
const offset = this._start;
const lexeme = this._input.substring(this._start, this._current);
const errToken = { type: 19 /* TokenType.Error */, offset: this._start, lexeme };
this._errors.push({ offset, lexeme, additionalInfo: additional });
this._tokens.push(errToken);
}
_string() {
this.stringRe.lastIndex = this._start;
const match = this.stringRe.exec(this._input);
if (match) {
this._current = this._start + match[0].length;
const lexeme = this._input.substring(this._start, this._current);
const keyword = Scanner._keywords.get(lexeme);
if (keyword) {
this._addToken(keyword);
}
else {
this._tokens.push({ type: 17 /* TokenType.Str */, lexeme, offset: this._start });
}
}
}
// captures the lexeme without the leading and trailing '
_quotedString() {
while (this._peek() !== 39 /* CharCode.SingleQuote */ && !this._isAtEnd()) { // TODO@ulugbekna: add support for escaping ' ?
this._advance();
}
if (this._isAtEnd()) {
this._error(hintDidYouForgetToOpenOrCloseQuote);
return;
}
// consume the closing '
this._advance();
this._tokens.push({ type: 18 /* TokenType.QuotedStr */, lexeme: this._input.substring(this._start + 1, this._current - 1), offset: this._start + 1 });
}
/*
* Lexing a regex expression: /.../[igsmyu]*
* Based on https://github.com/microsoft/TypeScript/blob/9247ef115e617805983740ba795d7a8164babf89/src/compiler/scanner.ts#L2129-L2181
*
* Note that we want slashes within a regex to be escaped, e.g., /file:\\/\\/\\// should match `file:///`
*/
_regex() {
let p = this._current;
let inEscape = false;
let inCharacterClass = false;
while (true) {
if (p >= this._input.length) {
this._current = p;
this._error(hintDidYouForgetToEscapeSlash);
return;
}
const ch = this._input.charCodeAt(p);
if (inEscape) { // parsing an escape character
inEscape = false;
}
else if (ch === 47 /* CharCode.Slash */ && !inCharacterClass) { // end of regex
p++;
break;
}
else if (ch === 91 /* CharCode.OpenSquareBracket */) {
inCharacterClass = true;
}
else if (ch === 92 /* CharCode.Backslash */) {
inEscape = true;
}
else if (ch === 93 /* CharCode.CloseSquareBracket */) {
inCharacterClass = false;
}
p++;
}
// Consume flags // TODO@ulugbekna: use regex instead
while (p < this._input.length && Scanner._regexFlags.has(this._input.charCodeAt(p))) {
p++;
}
this._current = p;
const lexeme = this._input.substring(this._start, this._current);
this._tokens.push({ type: 10 /* TokenType.RegexStr */, lexeme, offset: this._start });
}
_isAtEnd() {
return this._current >= this._input.length;
}
}
@@ -0,0 +1,122 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { $, addDisposableListener, EventType, getActiveElement, getWindow, isAncestor, isHTMLElement } from '../../../base/browser/dom.js';
import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js';
import { Menu } from '../../../base/browser/ui/menu/menu.js';
import { ActionRunner } from '../../../base/common/actions.js';
import { isCancellationError } from '../../../base/common/errors.js';
import { combinedDisposable, DisposableStore } from '../../../base/common/lifecycle.js';
import { defaultMenuStyles } from '../../theme/browser/defaultStyles.js';
export class ContextMenuHandler {
constructor(contextViewService, telemetryService, notificationService, keybindingService) {
this.contextViewService = contextViewService;
this.telemetryService = telemetryService;
this.notificationService = notificationService;
this.keybindingService = keybindingService;
this.focusToReturn = null;
this.lastContainer = null;
this.block = null;
this.blockDisposable = null;
this.options = { blockMouse: true };
}
configure(options) {
this.options = options;
}
showContextMenu(delegate) {
const actions = delegate.getActions();
if (!actions.length) {
return; // Don't render an empty context menu
}
this.focusToReturn = getActiveElement();
let menu;
const shadowRootElement = isHTMLElement(delegate.domForShadowRoot) ? delegate.domForShadowRoot : undefined;
this.contextViewService.showContextView({
getAnchor: () => delegate.getAnchor(),
canRelayout: false,
anchorAlignment: delegate.anchorAlignment,
anchorAxisAlignment: delegate.anchorAxisAlignment,
render: (container) => {
this.lastContainer = container;
const className = delegate.getMenuClassName ? delegate.getMenuClassName() : '';
if (className) {
container.className += ' ' + className;
}
// Render invisible div to block mouse interaction in the rest of the UI
if (this.options.blockMouse) {
this.block = container.appendChild($('.context-view-block'));
this.block.style.position = 'fixed';
this.block.style.cursor = 'initial';
this.block.style.left = '0';
this.block.style.top = '0';
this.block.style.width = '100%';
this.block.style.height = '100%';
this.block.style.zIndex = '-1';
this.blockDisposable?.dispose();
this.blockDisposable = addDisposableListener(this.block, EventType.MOUSE_DOWN, e => e.stopPropagation());
}
const menuDisposables = new DisposableStore();
const actionRunner = delegate.actionRunner || new ActionRunner();
actionRunner.onWillRun(evt => this.onActionRun(evt, !delegate.skipTelemetry), this, menuDisposables);
actionRunner.onDidRun(this.onDidActionRun, this, menuDisposables);
menu = new Menu(container, actions, {
actionViewItemProvider: delegate.getActionViewItem,
context: delegate.getActionsContext ? delegate.getActionsContext() : null,
actionRunner,
getKeyBinding: delegate.getKeyBinding ? delegate.getKeyBinding : action => this.keybindingService.lookupKeybinding(action.id)
}, defaultMenuStyles);
menu.onDidCancel(() => this.contextViewService.hideContextView(true), null, menuDisposables);
menu.onDidBlur(() => this.contextViewService.hideContextView(true), null, menuDisposables);
const targetWindow = getWindow(container);
menuDisposables.add(addDisposableListener(targetWindow, EventType.BLUR, () => this.contextViewService.hideContextView(true)));
menuDisposables.add(addDisposableListener(targetWindow, EventType.MOUSE_DOWN, (e) => {
if (e.defaultPrevented) {
return;
}
const event = new StandardMouseEvent(targetWindow, e);
let element = event.target;
// Don't do anything as we are likely creating a context menu
if (event.rightButton) {
return;
}
while (element) {
if (element === container) {
return;
}
element = element.parentElement;
}
this.contextViewService.hideContextView(true);
}));
return combinedDisposable(menuDisposables, menu);
},
focus: () => {
menu?.focus(!!delegate.autoSelectFirstItem);
},
onHide: (didCancel) => {
delegate.onHide?.(!!didCancel);
if (this.block) {
this.block.remove();
this.block = null;
}
this.blockDisposable?.dispose();
this.blockDisposable = null;
if (!!this.lastContainer && (getActiveElement() === this.lastContainer || isAncestor(getActiveElement(), this.lastContainer))) {
this.focusToReturn?.focus();
}
this.lastContainer = null;
}
}, shadowRootElement, !!shadowRootElement);
}
onActionRun(e, logTelemetry) {
if (logTelemetry) {
this.telemetryService.publicLog2('workbenchActionExecuted', { id: e.action.id, from: 'contextMenu' });
}
this.contextViewService.hideContextView(false);
}
onDidActionRun(e) {
if (e.error && !isCancellationError(e.error)) {
this.notificationService.error(e.error);
}
}
}
@@ -0,0 +1,100 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { ModifierKeyEmitter } from '../../../base/browser/dom.js';
import { Separator } from '../../../base/common/actions.js';
import { Emitter } from '../../../base/common/event.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { createAndFillInContextMenuActions } from '../../actions/browser/menuEntryActionViewItem.js';
import { IMenuService, MenuId } from '../../actions/common/actions.js';
import { IContextKeyService } from '../../contextkey/common/contextkey.js';
import { IKeybindingService } from '../../keybinding/common/keybinding.js';
import { INotificationService } from '../../notification/common/notification.js';
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
import { ContextMenuHandler } from './contextMenuHandler.js';
import { IContextViewService } from './contextView.js';
let ContextMenuService = class ContextMenuService extends Disposable {
get contextMenuHandler() {
if (!this._contextMenuHandler) {
this._contextMenuHandler = new ContextMenuHandler(this.contextViewService, this.telemetryService, this.notificationService, this.keybindingService);
}
return this._contextMenuHandler;
}
constructor(telemetryService, notificationService, contextViewService, keybindingService, menuService, contextKeyService) {
super();
this.telemetryService = telemetryService;
this.notificationService = notificationService;
this.contextViewService = contextViewService;
this.keybindingService = keybindingService;
this.menuService = menuService;
this.contextKeyService = contextKeyService;
this._contextMenuHandler = undefined;
this._onDidShowContextMenu = this._store.add(new Emitter());
this.onDidShowContextMenu = this._onDidShowContextMenu.event;
this._onDidHideContextMenu = this._store.add(new Emitter());
}
configure(options) {
this.contextMenuHandler.configure(options);
}
// ContextMenu
showContextMenu(delegate) {
delegate = ContextMenuMenuDelegate.transform(delegate, this.menuService, this.contextKeyService);
this.contextMenuHandler.showContextMenu({
...delegate,
onHide: (didCancel) => {
delegate.onHide?.(didCancel);
this._onDidHideContextMenu.fire();
}
});
ModifierKeyEmitter.getInstance().resetKeyStatus();
this._onDidShowContextMenu.fire();
}
};
ContextMenuService = __decorate([
__param(0, ITelemetryService),
__param(1, INotificationService),
__param(2, IContextViewService),
__param(3, IKeybindingService),
__param(4, IMenuService),
__param(5, IContextKeyService)
], ContextMenuService);
export { ContextMenuService };
export var ContextMenuMenuDelegate;
(function (ContextMenuMenuDelegate) {
function is(thing) {
return thing && thing.menuId instanceof MenuId;
}
function transform(delegate, menuService, globalContextKeyService) {
if (!is(delegate)) {
return delegate;
}
const { menuId, menuActionOptions, contextKeyService } = delegate;
return {
...delegate,
getActions: () => {
const target = [];
if (menuId) {
const menu = menuService.getMenuActions(menuId, contextKeyService ?? globalContextKeyService, menuActionOptions);
createAndFillInContextMenuActions(menu, target);
}
if (!delegate.getActions) {
return target;
}
else {
return Separator.join(delegate.getActions(), target);
}
}
};
}
ContextMenuMenuDelegate.transform = transform;
})(ContextMenuMenuDelegate || (ContextMenuMenuDelegate = {}));
@@ -0,0 +1,7 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from '../../instantiation/common/instantiation.js';
export const IContextViewService = createDecorator('contextViewService');
export const IContextMenuService = createDecorator('contextMenuService');
@@ -0,0 +1,71 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { ContextView } from '../../../base/browser/ui/contextview/contextview.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { ILayoutService } from '../../layout/browser/layoutService.js';
import { getWindow } from '../../../base/browser/dom.js';
let ContextViewHandler = class ContextViewHandler extends Disposable {
constructor(layoutService) {
super();
this.layoutService = layoutService;
this.contextView = this._register(new ContextView(this.layoutService.mainContainer, 1 /* ContextViewDOMPosition.ABSOLUTE */));
this.layout();
this._register(layoutService.onDidLayoutContainer(() => this.layout()));
}
// ContextView
showContextView(delegate, container, shadowRoot) {
let domPosition;
if (container) {
if (container === this.layoutService.getContainer(getWindow(container))) {
domPosition = 1 /* ContextViewDOMPosition.ABSOLUTE */;
}
else if (shadowRoot) {
domPosition = 3 /* ContextViewDOMPosition.FIXED_SHADOW */;
}
else {
domPosition = 2 /* ContextViewDOMPosition.FIXED */;
}
}
else {
domPosition = 1 /* ContextViewDOMPosition.ABSOLUTE */;
}
this.contextView.setContainer(container ?? this.layoutService.activeContainer, domPosition);
this.contextView.show(delegate);
const openContextView = {
close: () => {
if (this.openContextView === openContextView) {
this.hideContextView();
}
}
};
this.openContextView = openContextView;
return openContextView;
}
layout() {
this.contextView.layout();
}
hideContextView(data) {
this.contextView.hide(data);
this.openContextView = undefined;
}
};
ContextViewHandler = __decorate([
__param(0, ILayoutService)
], ContextViewHandler);
export { ContextViewHandler };
export class ContextViewService extends ContextViewHandler {
getContextViewElement() {
return this.contextView.getViewElement();
}
}
@@ -0,0 +1,2 @@
import { createDecorator } from '../../instantiation/common/instantiation.js';
export const IDialogService = createDecorator('dialogService');
@@ -0,0 +1,36 @@
import { Registry } from '../../registry/common/platform.js';
//#region Editor / Resources DND
export const CodeDataTransfers = {
EDITORS: 'CodeEditors',
FILES: 'CodeFiles'
};
class DragAndDropContributionRegistry {
}
export const Extensions = {
DragAndDropContribution: 'workbench.contributions.dragAndDrop'
};
Registry.add(Extensions.DragAndDropContribution, new DragAndDropContributionRegistry());
//#endregion
//#region DND Utilities
/**
* A singleton to store transfer data during drag & drop operations that are only valid within the application.
*/
export class LocalSelectionTransfer {
static { this.INSTANCE = new LocalSelectionTransfer(); }
constructor() {
// protect against external instantiation
}
static getInstance() {
return LocalSelectionTransfer.INSTANCE;
}
hasData(proto) {
return proto && proto === this.proto;
}
getData(proto) {
if (this.hasData(proto)) {
return this.data;
}
return undefined;
}
}
//#endregion
@@ -0,0 +1,17 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export var EditorOpenSource;
(function (EditorOpenSource) {
/**
* Default: the editor is opening via a programmatic call
* to the editor service API.
*/
EditorOpenSource[EditorOpenSource["API"] = 0] = "API";
/**
* Indicates that a user action triggered the opening, e.g.
* via mouse or keyboard use.
*/
EditorOpenSource[EditorOpenSource["USER"] = 1] = "USER";
})(EditorOpenSource || (EditorOpenSource = {}));
@@ -0,0 +1,2 @@
import { createDecorator } from '../../instantiation/common/instantiation.js';
export const IEnvironmentService = createDecorator('environmentService');
@@ -0,0 +1,47 @@
/**
* **!Do not construct directly!**
*
* **!Only static methods because it gets serialized!**
*
* This represents the "canonical" version for an extension identifier. Extension ids
* have to be case-insensitive (due to the marketplace), but we must ensure case
* preservation because the extension API is already public at this time.
*
* For example, given an extension with the publisher `"Hello"` and the name `"World"`,
* its canonical extension identifier is `"Hello.World"`. This extension could be
* referenced in some other extension's dependencies using the string `"hello.world"`.
*
* To make matters more complicated, an extension can optionally have an UUID. When two
* extensions have the same UUID, they are considered equal even if their identifier is different.
*/
export class ExtensionIdentifier {
constructor(value) {
this.value = value;
this._lower = value.toLowerCase();
}
/**
* Gives the value by which to index (for equality).
*/
static toKey(id) {
if (typeof id === 'string') {
return id.toLowerCase();
}
return id._lower;
}
}
export class ExtensionIdentifierSet {
constructor(iterable) {
this._set = new Set();
if (iterable) {
for (const value of iterable) {
this.add(value);
}
}
}
add(id) {
this._set.add(ExtensionIdentifier.toKey(id));
}
has(id) {
return this._set.has(ExtensionIdentifier.toKey(id));
}
}
@@ -0,0 +1,9 @@
//#endregion
//#region Utilities
export var FileKind;
(function (FileKind) {
FileKind[FileKind["FILE"] = 0] = "FILE";
FileKind[FileKind["FOLDER"] = 1] = "FOLDER";
FileKind[FileKind["ROOT_FOLDER"] = 2] = "ROOT_FOLDER";
})(FileKind || (FileKind = {}));
//#endregion
@@ -0,0 +1,105 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { FindInput } from '../../../base/browser/ui/findinput/findInput.js';
import { ReplaceInput } from '../../../base/browser/ui/findinput/replaceInput.js';
import { ContextKeyExpr, IContextKeyService, RawContextKey } from '../../contextkey/common/contextkey.js';
import { KeybindingsRegistry } from '../../keybinding/common/keybindingsRegistry.js';
import { localize } from '../../../nls.js';
import { DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';
import { isActiveElement } from '../../../base/browser/dom.js';
export const historyNavigationVisible = new RawContextKey('suggestWidgetVisible', false, localize('suggestWidgetVisible', "Whether suggestion are visible"));
const HistoryNavigationWidgetFocusContext = 'historyNavigationWidgetFocus';
const HistoryNavigationForwardsEnablementContext = 'historyNavigationForwardsEnabled';
const HistoryNavigationBackwardsEnablementContext = 'historyNavigationBackwardsEnabled';
let lastFocusedWidget = undefined;
const widgets = [];
export function registerAndCreateHistoryNavigationContext(scopedContextKeyService, widget) {
if (widgets.includes(widget)) {
throw new Error('Cannot register the same widget multiple times');
}
widgets.push(widget);
const disposableStore = new DisposableStore();
const historyNavigationWidgetFocus = new RawContextKey(HistoryNavigationWidgetFocusContext, false).bindTo(scopedContextKeyService);
const historyNavigationForwardsEnablement = new RawContextKey(HistoryNavigationForwardsEnablementContext, true).bindTo(scopedContextKeyService);
const historyNavigationBackwardsEnablement = new RawContextKey(HistoryNavigationBackwardsEnablementContext, true).bindTo(scopedContextKeyService);
const onDidFocus = () => {
historyNavigationWidgetFocus.set(true);
lastFocusedWidget = widget;
};
const onDidBlur = () => {
historyNavigationWidgetFocus.set(false);
if (lastFocusedWidget === widget) {
lastFocusedWidget = undefined;
}
};
// Check for currently being focused
if (isActiveElement(widget.element)) {
onDidFocus();
}
disposableStore.add(widget.onDidFocus(() => onDidFocus()));
disposableStore.add(widget.onDidBlur(() => onDidBlur()));
disposableStore.add(toDisposable(() => {
widgets.splice(widgets.indexOf(widget), 1);
onDidBlur();
}));
return {
historyNavigationForwardsEnablement,
historyNavigationBackwardsEnablement,
dispose() {
disposableStore.dispose();
}
};
}
let ContextScopedFindInput = class ContextScopedFindInput extends FindInput {
constructor(container, contextViewProvider, options, contextKeyService) {
super(container, contextViewProvider, options);
const scopedContextKeyService = this._register(contextKeyService.createScoped(this.inputBox.element));
this._register(registerAndCreateHistoryNavigationContext(scopedContextKeyService, this.inputBox));
}
};
ContextScopedFindInput = __decorate([
__param(3, IContextKeyService)
], ContextScopedFindInput);
export { ContextScopedFindInput };
let ContextScopedReplaceInput = class ContextScopedReplaceInput extends ReplaceInput {
constructor(container, contextViewProvider, options, contextKeyService, showReplaceOptions = false) {
super(container, contextViewProvider, showReplaceOptions, options);
const scopedContextKeyService = this._register(contextKeyService.createScoped(this.inputBox.element));
this._register(registerAndCreateHistoryNavigationContext(scopedContextKeyService, this.inputBox));
}
};
ContextScopedReplaceInput = __decorate([
__param(3, IContextKeyService)
], ContextScopedReplaceInput);
export { ContextScopedReplaceInput };
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'history.showPrevious',
weight: 200 /* KeybindingWeight.WorkbenchContrib */,
when: ContextKeyExpr.and(ContextKeyExpr.has(HistoryNavigationWidgetFocusContext), ContextKeyExpr.equals(HistoryNavigationBackwardsEnablementContext, true), ContextKeyExpr.not('isComposing'), historyNavigationVisible.isEqualTo(false)),
primary: 16 /* KeyCode.UpArrow */,
secondary: [512 /* KeyMod.Alt */ | 16 /* KeyCode.UpArrow */],
handler: (accessor) => {
lastFocusedWidget?.showPreviousValue();
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'history.showNext',
weight: 200 /* KeybindingWeight.WorkbenchContrib */,
when: ContextKeyExpr.and(ContextKeyExpr.has(HistoryNavigationWidgetFocusContext), ContextKeyExpr.equals(HistoryNavigationForwardsEnablementContext, true), ContextKeyExpr.not('isComposing'), historyNavigationVisible.isEqualTo(false)),
primary: 18 /* KeyCode.DownArrow */,
secondary: [512 /* KeyMod.Alt */ | 18 /* KeyCode.DownArrow */],
handler: (accessor) => {
lastFocusedWidget?.showNextValue();
}
});
@@ -0,0 +1,7 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function showHistoryKeybindingHint(keybindingService) {
return keybindingService.lookupKeybinding('history.showPrevious')?.getElectronAccelerator() === 'Up' && keybindingService.lookupKeybinding('history.showNext')?.getElectronAccelerator() === 'Down';
}
@@ -0,0 +1,94 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { createDecorator } from '../../instantiation/common/instantiation.js';
import { Disposable, DisposableStore } from '../../../base/common/lifecycle.js';
import { IConfigurationService } from '../../configuration/common/configuration.js';
import { addStandardDisposableListener, isHTMLElement } from '../../../base/browser/dom.js';
export const IHoverService = createDecorator('hoverService');
let WorkbenchHoverDelegate = class WorkbenchHoverDelegate extends Disposable {
get delay() {
if (this.isInstantlyHovering()) {
return 0; // show instantly when a hover was recently shown
}
return this._delay;
}
constructor(placement, instantHover, overrideOptions = {}, configurationService, hoverService) {
super();
this.placement = placement;
this.instantHover = instantHover;
this.overrideOptions = overrideOptions;
this.configurationService = configurationService;
this.hoverService = hoverService;
this.lastHoverHideTime = 0;
this.timeLimit = 200;
this.hoverDisposables = this._register(new DisposableStore());
this._delay = this.configurationService.getValue('workbench.hover.delay');
this._register(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('workbench.hover.delay')) {
this._delay = this.configurationService.getValue('workbench.hover.delay');
}
}));
}
showHover(options, focus) {
const overrideOptions = typeof this.overrideOptions === 'function' ? this.overrideOptions(options, focus) : this.overrideOptions;
// close hover on escape
this.hoverDisposables.clear();
const targets = isHTMLElement(options.target) ? [options.target] : options.target.targetElements;
for (const target of targets) {
this.hoverDisposables.add(addStandardDisposableListener(target, 'keydown', (e) => {
if (e.equals(9 /* KeyCode.Escape */)) {
this.hoverService.hideHover();
}
}));
}
const id = isHTMLElement(options.content) ? undefined : options.content.toString();
return this.hoverService.showHover({
...options,
...overrideOptions,
persistence: {
hideOnKeyDown: true,
...overrideOptions.persistence
},
id,
appearance: {
...options.appearance,
compact: true,
skipFadeInAnimation: this.isInstantlyHovering(),
...overrideOptions.appearance
}
}, focus);
}
isInstantlyHovering() {
return this.instantHover && Date.now() - this.lastHoverHideTime < this.timeLimit;
}
onDidHideHover() {
this.hoverDisposables.clear();
if (this.instantHover) {
this.lastHoverHideTime = Date.now();
}
}
};
WorkbenchHoverDelegate = __decorate([
__param(3, IConfigurationService),
__param(4, IHoverService)
], WorkbenchHoverDelegate);
export { WorkbenchHoverDelegate };
// TODO@benibenj remove this, only temp fix for contextviews
export const nativeHoverDelegate = {
showHover: function () {
throw new Error('Native hover function not implemented.');
},
delay: 0,
showNativeHover: true
};
@@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export class SyncDescriptor {
constructor(ctor, staticArguments = [], supportsDelayedInstantiation = false) {
this.ctor = ctor;
this.staticArguments = staticArguments;
this.supportsDelayedInstantiation = supportsDelayedInstantiation;
}
}
@@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SyncDescriptor } from './descriptors.js';
const _registry = [];
export function registerSingleton(id, ctorOrDescriptor, supportsDelayedInstantiation) {
if (!(ctorOrDescriptor instanceof SyncDescriptor)) {
ctorOrDescriptor = new SyncDescriptor(ctorOrDescriptor, [], Boolean(supportsDelayedInstantiation));
}
_registry.push([id, ctorOrDescriptor]);
}
export function getSingletonServiceDescriptors() {
return _registry;
}
@@ -0,0 +1,89 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export class Node {
constructor(key, data) {
this.key = key;
this.data = data;
this.incoming = new Map();
this.outgoing = new Map();
}
}
export class Graph {
constructor(_hashFn) {
this._hashFn = _hashFn;
this._nodes = new Map();
// empty
}
roots() {
const ret = [];
for (const node of this._nodes.values()) {
if (node.outgoing.size === 0) {
ret.push(node);
}
}
return ret;
}
insertEdge(from, to) {
const fromNode = this.lookupOrInsertNode(from);
const toNode = this.lookupOrInsertNode(to);
fromNode.outgoing.set(toNode.key, toNode);
toNode.incoming.set(fromNode.key, fromNode);
}
removeNode(data) {
const key = this._hashFn(data);
this._nodes.delete(key);
for (const node of this._nodes.values()) {
node.outgoing.delete(key);
node.incoming.delete(key);
}
}
lookupOrInsertNode(data) {
const key = this._hashFn(data);
let node = this._nodes.get(key);
if (!node) {
node = new Node(key, data);
this._nodes.set(key, node);
}
return node;
}
isEmpty() {
return this._nodes.size === 0;
}
toString() {
const data = [];
for (const [key, value] of this._nodes) {
data.push(`${key}\n\t(-> incoming)[${[...value.incoming.keys()].join(', ')}]\n\t(outgoing ->)[${[...value.outgoing.keys()].join(',')}]\n`);
}
return data.join('\n');
}
/**
* This is brute force and slow and **only** be used
* to trouble shoot.
*/
findCycleSlow() {
for (const [id, node] of this._nodes) {
const seen = new Set([id]);
const res = this._findCycle(node, seen);
if (res) {
return res;
}
}
return undefined;
}
_findCycle(node, seen) {
for (const [id, outgoing] of node.outgoing) {
if (seen.has(id)) {
return [...seen, id].join(' -> ');
}
seen.add(id);
const value = this._findCycle(outgoing, seen);
if (value) {
return value;
}
seen.delete(id);
}
return undefined;
}
}
@@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// ------ internal util
export var _util;
(function (_util) {
_util.serviceIds = new Map();
_util.DI_TARGET = '$di$target';
_util.DI_DEPENDENCIES = '$di$dependencies';
function getServiceDependencies(ctor) {
return ctor[_util.DI_DEPENDENCIES] || [];
}
_util.getServiceDependencies = getServiceDependencies;
})(_util || (_util = {}));
export const IInstantiationService = createDecorator('instantiationService');
function storeServiceDependency(id, target, index) {
if (target[_util.DI_TARGET] === target) {
target[_util.DI_DEPENDENCIES].push({ id, index });
}
else {
target[_util.DI_DEPENDENCIES] = [{ id, index }];
target[_util.DI_TARGET] = target;
}
}
/**
* The *only* valid way to create a {{ServiceIdentifier}}.
*/
export function createDecorator(serviceId) {
if (_util.serviceIds.has(serviceId)) {
return _util.serviceIds.get(serviceId);
}
const id = function (target, key, index) {
if (arguments.length !== 3) {
throw new Error('@IServiceName-decorator can only be used to decorate a parameter');
}
storeServiceDependency(id, target, index);
};
id.toString = () => serviceId;
_util.serviceIds.set(serviceId, id);
return id;
}
@@ -0,0 +1,392 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { GlobalIdleValue } from '../../../base/common/async.js';
import { illegalState } from '../../../base/common/errors.js';
import { dispose, isDisposable, toDisposable } from '../../../base/common/lifecycle.js';
import { SyncDescriptor } from './descriptors.js';
import { Graph } from './graph.js';
import { IInstantiationService, _util } from './instantiation.js';
import { ServiceCollection } from './serviceCollection.js';
import { LinkedList } from '../../../base/common/linkedList.js';
// TRACING
const _enableAllTracing = false;
class CyclicDependencyError extends Error {
constructor(graph) {
super('cyclic dependency between services');
this.message = graph.findCycleSlow() ?? `UNABLE to detect cycle, dumping graph: \n${graph.toString()}`;
}
}
export class InstantiationService {
constructor(_services = new ServiceCollection(), _strict = false, _parent, _enableTracing = _enableAllTracing) {
this._services = _services;
this._strict = _strict;
this._parent = _parent;
this._enableTracing = _enableTracing;
this._isDisposed = false;
this._servicesToMaybeDispose = new Set();
this._children = new Set();
this._activeInstantiations = new Set();
this._services.set(IInstantiationService, this);
this._globalGraph = _enableTracing ? _parent?._globalGraph ?? new Graph(e => e) : undefined;
}
dispose() {
if (!this._isDisposed) {
this._isDisposed = true;
// dispose all child services
dispose(this._children);
this._children.clear();
// dispose all services created by this service
for (const candidate of this._servicesToMaybeDispose) {
if (isDisposable(candidate)) {
candidate.dispose();
}
}
this._servicesToMaybeDispose.clear();
}
}
_throwIfDisposed() {
if (this._isDisposed) {
throw new Error('InstantiationService has been disposed');
}
}
createChild(services, store) {
this._throwIfDisposed();
const that = this;
const result = new class extends InstantiationService {
dispose() {
that._children.delete(result);
super.dispose();
}
}(services, this._strict, this, this._enableTracing);
this._children.add(result);
store?.add(result);
return result;
}
invokeFunction(fn, ...args) {
this._throwIfDisposed();
const _trace = Trace.traceInvocation(this._enableTracing, fn);
let _done = false;
try {
const accessor = {
get: (id) => {
if (_done) {
throw illegalState('service accessor is only valid during the invocation of its target method');
}
const result = this._getOrCreateServiceInstance(id, _trace);
if (!result) {
throw new Error(`[invokeFunction] unknown service '${id}'`);
}
return result;
}
};
return fn(accessor, ...args);
}
finally {
_done = true;
_trace.stop();
}
}
createInstance(ctorOrDescriptor, ...rest) {
this._throwIfDisposed();
let _trace;
let result;
if (ctorOrDescriptor instanceof SyncDescriptor) {
_trace = Trace.traceCreation(this._enableTracing, ctorOrDescriptor.ctor);
result = this._createInstance(ctorOrDescriptor.ctor, ctorOrDescriptor.staticArguments.concat(rest), _trace);
}
else {
_trace = Trace.traceCreation(this._enableTracing, ctorOrDescriptor);
result = this._createInstance(ctorOrDescriptor, rest, _trace);
}
_trace.stop();
return result;
}
_createInstance(ctor, args = [], _trace) {
// arguments defined by service decorators
const serviceDependencies = _util.getServiceDependencies(ctor).sort((a, b) => a.index - b.index);
const serviceArgs = [];
for (const dependency of serviceDependencies) {
const service = this._getOrCreateServiceInstance(dependency.id, _trace);
if (!service) {
this._throwIfStrict(`[createInstance] ${ctor.name} depends on UNKNOWN service ${dependency.id}.`, false);
}
serviceArgs.push(service);
}
const firstServiceArgPos = serviceDependencies.length > 0 ? serviceDependencies[0].index : args.length;
// check for argument mismatches, adjust static args if needed
if (args.length !== firstServiceArgPos) {
console.trace(`[createInstance] First service dependency of ${ctor.name} at position ${firstServiceArgPos + 1} conflicts with ${args.length} static arguments`);
const delta = firstServiceArgPos - args.length;
if (delta > 0) {
args = args.concat(new Array(delta));
}
else {
args = args.slice(0, firstServiceArgPos);
}
}
// now create the instance
return Reflect.construct(ctor, args.concat(serviceArgs));
}
_setCreatedServiceInstance(id, instance) {
if (this._services.get(id) instanceof SyncDescriptor) {
this._services.set(id, instance);
}
else if (this._parent) {
this._parent._setCreatedServiceInstance(id, instance);
}
else {
throw new Error('illegalState - setting UNKNOWN service instance');
}
}
_getServiceInstanceOrDescriptor(id) {
const instanceOrDesc = this._services.get(id);
if (!instanceOrDesc && this._parent) {
return this._parent._getServiceInstanceOrDescriptor(id);
}
else {
return instanceOrDesc;
}
}
_getOrCreateServiceInstance(id, _trace) {
if (this._globalGraph && this._globalGraphImplicitDependency) {
this._globalGraph.insertEdge(this._globalGraphImplicitDependency, String(id));
}
const thing = this._getServiceInstanceOrDescriptor(id);
if (thing instanceof SyncDescriptor) {
return this._safeCreateAndCacheServiceInstance(id, thing, _trace.branch(id, true));
}
else {
_trace.branch(id, false);
return thing;
}
}
_safeCreateAndCacheServiceInstance(id, desc, _trace) {
if (this._activeInstantiations.has(id)) {
throw new Error(`illegal state - RECURSIVELY instantiating service '${id}'`);
}
this._activeInstantiations.add(id);
try {
return this._createAndCacheServiceInstance(id, desc, _trace);
}
finally {
this._activeInstantiations.delete(id);
}
}
_createAndCacheServiceInstance(id, desc, _trace) {
const graph = new Graph(data => data.id.toString());
let cycleCount = 0;
const stack = [{ id, desc, _trace }];
const seen = new Set();
while (stack.length) {
const item = stack.pop();
if (seen.has(String(item.id))) {
continue;
}
seen.add(String(item.id));
graph.lookupOrInsertNode(item);
// a weak but working heuristic for cycle checks
if (cycleCount++ > 1000) {
throw new CyclicDependencyError(graph);
}
// check all dependencies for existence and if they need to be created first
for (const dependency of _util.getServiceDependencies(item.desc.ctor)) {
const instanceOrDesc = this._getServiceInstanceOrDescriptor(dependency.id);
if (!instanceOrDesc) {
this._throwIfStrict(`[createInstance] ${id} depends on ${dependency.id} which is NOT registered.`, true);
}
// take note of all service dependencies
this._globalGraph?.insertEdge(String(item.id), String(dependency.id));
if (instanceOrDesc instanceof SyncDescriptor) {
const d = { id: dependency.id, desc: instanceOrDesc, _trace: item._trace.branch(dependency.id, true) };
graph.insertEdge(item, d);
stack.push(d);
}
}
}
while (true) {
const roots = graph.roots();
// if there is no more roots but still
// nodes in the graph we have a cycle
if (roots.length === 0) {
if (!graph.isEmpty()) {
throw new CyclicDependencyError(graph);
}
break;
}
for (const { data } of roots) {
// Repeat the check for this still being a service sync descriptor. That's because
// instantiating a dependency might have side-effect and recursively trigger instantiation
// so that some dependencies are now fullfilled already.
const instanceOrDesc = this._getServiceInstanceOrDescriptor(data.id);
if (instanceOrDesc instanceof SyncDescriptor) {
// create instance and overwrite the service collections
const instance = this._createServiceInstanceWithOwner(data.id, data.desc.ctor, data.desc.staticArguments, data.desc.supportsDelayedInstantiation, data._trace);
this._setCreatedServiceInstance(data.id, instance);
}
graph.removeNode(data);
}
}
return this._getServiceInstanceOrDescriptor(id);
}
_createServiceInstanceWithOwner(id, ctor, args = [], supportsDelayedInstantiation, _trace) {
if (this._services.get(id) instanceof SyncDescriptor) {
return this._createServiceInstance(id, ctor, args, supportsDelayedInstantiation, _trace, this._servicesToMaybeDispose);
}
else if (this._parent) {
return this._parent._createServiceInstanceWithOwner(id, ctor, args, supportsDelayedInstantiation, _trace);
}
else {
throw new Error(`illegalState - creating UNKNOWN service instance ${ctor.name}`);
}
}
_createServiceInstance(id, ctor, args = [], supportsDelayedInstantiation, _trace, disposeBucket) {
if (!supportsDelayedInstantiation) {
// eager instantiation
const result = this._createInstance(ctor, args, _trace);
disposeBucket.add(result);
return result;
}
else {
const child = new InstantiationService(undefined, this._strict, this, this._enableTracing);
child._globalGraphImplicitDependency = String(id);
// Return a proxy object that's backed by an idle value. That
// strategy is to instantiate services in our idle time or when actually
// needed but not when injected into a consumer
// return "empty events" when the service isn't instantiated yet
const earlyListeners = new Map();
const idle = new GlobalIdleValue(() => {
const result = child._createInstance(ctor, args, _trace);
// early listeners that we kept are now being subscribed to
// the real service
for (const [key, values] of earlyListeners) {
const candidate = result[key];
if (typeof candidate === 'function') {
for (const value of values) {
value.disposable = candidate.apply(result, value.listener);
}
}
}
earlyListeners.clear();
disposeBucket.add(result);
return result;
});
return new Proxy(Object.create(null), {
get(target, key) {
if (!idle.isInitialized) {
// looks like an event
if (typeof key === 'string' && (key.startsWith('onDid') || key.startsWith('onWill'))) {
let list = earlyListeners.get(key);
if (!list) {
list = new LinkedList();
earlyListeners.set(key, list);
}
const event = (callback, thisArg, disposables) => {
if (idle.isInitialized) {
return idle.value[key](callback, thisArg, disposables);
}
else {
const entry = { listener: [callback, thisArg, disposables], disposable: undefined };
const rm = list.push(entry);
const result = toDisposable(() => {
rm();
entry.disposable?.dispose();
});
return result;
}
};
return event;
}
}
// value already exists
if (key in target) {
return target[key];
}
// create value
const obj = idle.value;
let prop = obj[key];
if (typeof prop !== 'function') {
return prop;
}
prop = prop.bind(obj);
target[key] = prop;
return prop;
},
set(_target, p, value) {
idle.value[p] = value;
return true;
},
getPrototypeOf(_target) {
return ctor.prototype;
}
});
}
}
_throwIfStrict(msg, printWarning) {
if (printWarning) {
console.warn(msg);
}
if (this._strict) {
throw new Error(msg);
}
}
}
export class Trace {
static { this.all = new Set(); }
static { this._None = new class extends Trace {
constructor() { super(0 /* TraceType.None */, null); }
stop() { }
branch() { return this; }
}; }
static traceInvocation(_enableTracing, ctor) {
return !_enableTracing ? Trace._None : new Trace(2 /* TraceType.Invocation */, ctor.name || new Error().stack.split('\n').slice(3, 4).join('\n'));
}
static traceCreation(_enableTracing, ctor) {
return !_enableTracing ? Trace._None : new Trace(1 /* TraceType.Creation */, ctor.name);
}
static { this._totals = 0; }
constructor(type, name) {
this.type = type;
this.name = name;
this._start = Date.now();
this._dep = [];
}
branch(id, first) {
const child = new Trace(3 /* TraceType.Branch */, id.toString());
this._dep.push([id, first, child]);
return child;
}
stop() {
const dur = Date.now() - this._start;
Trace._totals += dur;
let causedCreation = false;
function printChild(n, trace) {
const res = [];
const prefix = new Array(n + 1).join('\t');
for (const [id, first, child] of trace._dep) {
if (first && child) {
causedCreation = true;
res.push(`${prefix}CREATES -> ${id}`);
const nested = printChild(n + 1, child);
if (nested) {
res.push(nested);
}
}
else {
res.push(`${prefix}uses -> ${id}`);
}
}
return res.join('\n');
}
const lines = [
`${this.type === 1 /* TraceType.Creation */ ? 'CREATE' : 'CALL'} ${this.name}`,
`${printChild(1, this)}`,
`DONE, took ${dur.toFixed(2)}ms (grand total ${Trace._totals.toFixed(2)}ms)`
];
if (dur > 2 || causedCreation) {
Trace.all.add(lines.join('\n'));
}
}
}
//#endregion
@@ -0,0 +1,20 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export class ServiceCollection {
constructor(...entries) {
this._entries = new Map();
for (const [id, service] of entries) {
this.set(id, service);
}
}
set(id, instanceOrDescriptor) {
const result = this._entries.get(id);
this._entries.set(id, instanceOrDescriptor);
return result;
}
get(id) {
return this._entries.get(id);
}
}
@@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter } from '../../../base/common/event.js';
import * as platform from '../../registry/common/platform.js';
export const Extensions = {
JSONContribution: 'base.contributions.json'
};
function normalizeId(id) {
if (id.length > 0 && id.charAt(id.length - 1) === '#') {
return id.substring(0, id.length - 1);
}
return id;
}
class JSONContributionRegistry {
constructor() {
this._onDidChangeSchema = new Emitter();
this.schemasById = {};
}
registerSchema(uri, unresolvedSchemaContent) {
this.schemasById[normalizeId(uri)] = unresolvedSchemaContent;
this._onDidChangeSchema.fire(uri);
}
notifySchemaChanged(uri) {
this._onDidChangeSchema.fire(uri);
}
}
const jsonContributionRegistry = new JSONContributionRegistry();
platform.Registry.add(Extensions.JSONContribution, jsonContributionRegistry);
@@ -0,0 +1,283 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IntervalTimer, TimeoutTimer } from '../../../base/common/async.js';
import { illegalState } from '../../../base/common/errors.js';
import { Emitter, Event } from '../../../base/common/event.js';
import { IME } from '../../../base/common/ime.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import * as nls from '../../../nls.js';
import { NoMatchingKb } from './keybindingResolver.js';
const HIGH_FREQ_COMMANDS = /^(cursor|delete|undo|redo|tab|editor\.action\.clipboard)/;
export class AbstractKeybindingService extends Disposable {
get onDidUpdateKeybindings() {
return this._onDidUpdateKeybindings ? this._onDidUpdateKeybindings.event : Event.None; // Sinon stubbing walks properties on prototype
}
get inChordMode() {
return this._currentChords.length > 0;
}
constructor(_contextKeyService, _commandService, _telemetryService, _notificationService, _logService) {
super();
this._contextKeyService = _contextKeyService;
this._commandService = _commandService;
this._telemetryService = _telemetryService;
this._notificationService = _notificationService;
this._logService = _logService;
this._onDidUpdateKeybindings = this._register(new Emitter());
this._currentChords = [];
this._currentChordChecker = new IntervalTimer();
this._currentChordStatusMessage = null;
this._ignoreSingleModifiers = KeybindingModifierSet.EMPTY;
this._currentSingleModifier = null;
this._currentSingleModifierClearTimeout = new TimeoutTimer();
this._currentlyDispatchingCommandId = null;
this._logging = false;
}
dispose() {
super.dispose();
}
_log(str) {
if (this._logging) {
this._logService.info(`[KeybindingService]: ${str}`);
}
}
getKeybindings() {
return this._getResolver().getKeybindings();
}
lookupKeybinding(commandId, context) {
const result = this._getResolver().lookupPrimaryKeybinding(commandId, context || this._contextKeyService);
if (!result) {
return undefined;
}
return result.resolvedKeybinding;
}
dispatchEvent(e, target) {
return this._dispatch(e, target);
}
// TODO@ulugbekna: update namings to align with `_doDispatch`
// TODO@ulugbekna: this fn doesn't seem to take into account single-modifier keybindings, eg `shift shift`
softDispatch(e, target) {
this._log(`/ Soft dispatching keyboard event`);
const keybinding = this.resolveKeyboardEvent(e);
if (keybinding.hasMultipleChords()) {
console.warn('keyboard event should not be mapped to multiple chords');
return NoMatchingKb;
}
const [firstChord,] = keybinding.getDispatchChords();
if (firstChord === null) {
// cannot be dispatched, probably only modifier keys
this._log(`\\ Keyboard event cannot be dispatched`);
return NoMatchingKb;
}
const contextValue = this._contextKeyService.getContext(target);
const currentChords = this._currentChords.map((({ keypress }) => keypress));
return this._getResolver().resolve(contextValue, currentChords, firstChord);
}
_scheduleLeaveChordMode() {
const chordLastInteractedTime = Date.now();
this._currentChordChecker.cancelAndSet(() => {
if (!this._documentHasFocus()) {
// Focus has been lost => leave chord mode
this._leaveChordMode();
return;
}
if (Date.now() - chordLastInteractedTime > 5000) {
// 5 seconds elapsed => leave chord mode
this._leaveChordMode();
}
}, 500);
}
_expectAnotherChord(firstChord, keypressLabel) {
this._currentChords.push({ keypress: firstChord, label: keypressLabel });
switch (this._currentChords.length) {
case 0:
throw illegalState('impossible');
case 1:
// TODO@ulugbekna: revise this message and the one below (at least, fix terminology)
this._currentChordStatusMessage = this._notificationService.status(nls.localize('first.chord', "({0}) was pressed. Waiting for second key of chord...", keypressLabel));
break;
default: {
const fullKeypressLabel = this._currentChords.map(({ label }) => label).join(', ');
this._currentChordStatusMessage = this._notificationService.status(nls.localize('next.chord', "({0}) was pressed. Waiting for next key of chord...", fullKeypressLabel));
}
}
this._scheduleLeaveChordMode();
if (IME.enabled) {
IME.disable();
}
}
_leaveChordMode() {
if (this._currentChordStatusMessage) {
this._currentChordStatusMessage.dispose();
this._currentChordStatusMessage = null;
}
this._currentChordChecker.cancel();
this._currentChords = [];
IME.enable();
}
_dispatch(e, target) {
return this._doDispatch(this.resolveKeyboardEvent(e), target, /*isSingleModiferChord*/ false);
}
_singleModifierDispatch(e, target) {
const keybinding = this.resolveKeyboardEvent(e);
const [singleModifier,] = keybinding.getSingleModifierDispatchChords();
if (singleModifier) {
if (this._ignoreSingleModifiers.has(singleModifier)) {
this._log(`+ Ignoring single modifier ${singleModifier} due to it being pressed together with other keys.`);
this._ignoreSingleModifiers = KeybindingModifierSet.EMPTY;
this._currentSingleModifierClearTimeout.cancel();
this._currentSingleModifier = null;
return false;
}
this._ignoreSingleModifiers = KeybindingModifierSet.EMPTY;
if (this._currentSingleModifier === null) {
// we have a valid `singleModifier`, store it for the next keyup, but clear it in 300ms
this._log(`+ Storing single modifier for possible chord ${singleModifier}.`);
this._currentSingleModifier = singleModifier;
this._currentSingleModifierClearTimeout.cancelAndSet(() => {
this._log(`+ Clearing single modifier due to 300ms elapsed.`);
this._currentSingleModifier = null;
}, 300);
return false;
}
if (singleModifier === this._currentSingleModifier) {
// bingo!
this._log(`/ Dispatching single modifier chord ${singleModifier} ${singleModifier}`);
this._currentSingleModifierClearTimeout.cancel();
this._currentSingleModifier = null;
return this._doDispatch(keybinding, target, /*isSingleModiferChord*/ true);
}
this._log(`+ Clearing single modifier due to modifier mismatch: ${this._currentSingleModifier} ${singleModifier}`);
this._currentSingleModifierClearTimeout.cancel();
this._currentSingleModifier = null;
return false;
}
// When pressing a modifier and holding it pressed with any other modifier or key combination,
// the pressed modifiers should no longer be considered for single modifier dispatch.
const [firstChord,] = keybinding.getChords();
this._ignoreSingleModifiers = new KeybindingModifierSet(firstChord);
if (this._currentSingleModifier !== null) {
this._log(`+ Clearing single modifier due to other key up.`);
}
this._currentSingleModifierClearTimeout.cancel();
this._currentSingleModifier = null;
return false;
}
_doDispatch(userKeypress, target, isSingleModiferChord = false) {
let shouldPreventDefault = false;
if (userKeypress.hasMultipleChords()) { // warn - because user can press a single chord at a time
console.warn('Unexpected keyboard event mapped to multiple chords');
return false;
}
let userPressedChord = null;
let currentChords = null;
if (isSingleModiferChord) {
// The keybinding is the second keypress of a single modifier chord, e.g. "shift shift".
// A single modifier can only occur when the same modifier is pressed in short sequence,
// hence we disregard `_currentChord` and use the same modifier instead.
const [dispatchKeyname,] = userKeypress.getSingleModifierDispatchChords();
userPressedChord = dispatchKeyname;
currentChords = dispatchKeyname ? [dispatchKeyname] : []; // TODO@ulugbekna: in the `else` case we assign an empty array - make sure `resolve` can handle an empty array well
}
else {
[userPressedChord,] = userKeypress.getDispatchChords();
currentChords = this._currentChords.map(({ keypress }) => keypress);
}
if (userPressedChord === null) {
this._log(`\\ Keyboard event cannot be dispatched in keydown phase.`);
// cannot be dispatched, probably only modifier keys
return shouldPreventDefault;
}
const contextValue = this._contextKeyService.getContext(target);
const keypressLabel = userKeypress.getLabel();
const resolveResult = this._getResolver().resolve(contextValue, currentChords, userPressedChord);
switch (resolveResult.kind) {
case 0 /* ResultKind.NoMatchingKb */: {
this._logService.trace('KeybindingService#dispatch', keypressLabel, `[ No matching keybinding ]`);
if (this.inChordMode) {
const currentChordsLabel = this._currentChords.map(({ label }) => label).join(', ');
this._log(`+ Leaving multi-chord mode: Nothing bound to "${currentChordsLabel}, ${keypressLabel}".`);
this._notificationService.status(nls.localize('missing.chord', "The key combination ({0}, {1}) is not a command.", currentChordsLabel, keypressLabel), { hideAfter: 10 * 1000 /* 10s */ });
this._leaveChordMode();
shouldPreventDefault = true;
}
return shouldPreventDefault;
}
case 1 /* ResultKind.MoreChordsNeeded */: {
this._logService.trace('KeybindingService#dispatch', keypressLabel, `[ Several keybindings match - more chords needed ]`);
shouldPreventDefault = true;
this._expectAnotherChord(userPressedChord, keypressLabel);
this._log(this._currentChords.length === 1 ? `+ Entering multi-chord mode...` : `+ Continuing multi-chord mode...`);
return shouldPreventDefault;
}
case 2 /* ResultKind.KbFound */: {
this._logService.trace('KeybindingService#dispatch', keypressLabel, `[ Will dispatch command ${resolveResult.commandId} ]`);
if (resolveResult.commandId === null || resolveResult.commandId === '') {
if (this.inChordMode) {
const currentChordsLabel = this._currentChords.map(({ label }) => label).join(', ');
this._log(`+ Leaving chord mode: Nothing bound to "${currentChordsLabel}, ${keypressLabel}".`);
this._notificationService.status(nls.localize('missing.chord', "The key combination ({0}, {1}) is not a command.", currentChordsLabel, keypressLabel), { hideAfter: 10 * 1000 /* 10s */ });
this._leaveChordMode();
shouldPreventDefault = true;
}
}
else {
if (this.inChordMode) {
this._leaveChordMode();
}
if (!resolveResult.isBubble) {
shouldPreventDefault = true;
}
this._log(`+ Invoking command ${resolveResult.commandId}.`);
this._currentlyDispatchingCommandId = resolveResult.commandId;
try {
if (typeof resolveResult.commandArgs === 'undefined') {
this._commandService.executeCommand(resolveResult.commandId).then(undefined, err => this._notificationService.warn(err));
}
else {
this._commandService.executeCommand(resolveResult.commandId, resolveResult.commandArgs).then(undefined, err => this._notificationService.warn(err));
}
}
finally {
this._currentlyDispatchingCommandId = null;
}
if (!HIGH_FREQ_COMMANDS.test(resolveResult.commandId)) {
this._telemetryService.publicLog2('workbenchActionExecuted', { id: resolveResult.commandId, from: 'keybinding', detail: userKeypress.getUserSettingsLabel() ?? undefined });
}
}
return shouldPreventDefault;
}
}
}
mightProducePrintableCharacter(event) {
if (event.ctrlKey || event.metaKey) {
// ignore ctrl/cmd-combination but not shift/alt-combinatios
return false;
}
// weak check for certain ranges. this is properly implemented in a subclass
// with access to the KeyboardMapperFactory.
if ((event.keyCode >= 31 /* KeyCode.KeyA */ && event.keyCode <= 56 /* KeyCode.KeyZ */)
|| (event.keyCode >= 21 /* KeyCode.Digit0 */ && event.keyCode <= 30 /* KeyCode.Digit9 */)) {
return true;
}
return false;
}
}
class KeybindingModifierSet {
static { this.EMPTY = new KeybindingModifierSet(null); }
constructor(source) {
this._ctrlKey = source ? source.ctrlKey : false;
this._shiftKey = source ? source.shiftKey : false;
this._altKey = source ? source.altKey : false;
this._metaKey = source ? source.metaKey : false;
}
has(modifier) {
switch (modifier) {
case 'ctrl': return this._ctrlKey;
case 'shift': return this._shiftKey;
case 'alt': return this._altKey;
case 'meta': return this._metaKey;
}
}
}
@@ -0,0 +1,53 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { illegalArgument } from '../../../base/common/errors.js';
import { AriaLabelProvider, ElectronAcceleratorLabelProvider, UILabelProvider, UserSettingsLabelProvider } from '../../../base/common/keybindingLabels.js';
import { ResolvedKeybinding, ResolvedChord } from '../../../base/common/keybindings.js';
export class BaseResolvedKeybinding extends ResolvedKeybinding {
constructor(os, chords) {
super();
if (chords.length === 0) {
throw illegalArgument(`chords`);
}
this._os = os;
this._chords = chords;
}
getLabel() {
return UILabelProvider.toLabel(this._os, this._chords, (keybinding) => this._getLabel(keybinding));
}
getAriaLabel() {
return AriaLabelProvider.toLabel(this._os, this._chords, (keybinding) => this._getAriaLabel(keybinding));
}
getElectronAccelerator() {
if (this._chords.length > 1) {
// [Electron Accelerators] Electron cannot handle chords
return null;
}
if (this._chords[0].isDuplicateModifierCase()) {
// [Electron Accelerators] Electron cannot handle modifier only keybindings
// e.g. "shift shift"
return null;
}
return ElectronAcceleratorLabelProvider.toLabel(this._os, this._chords, (keybinding) => this._getElectronAccelerator(keybinding));
}
getUserSettingsLabel() {
return UserSettingsLabelProvider.toLabel(this._os, this._chords, (keybinding) => this._getUserSettingsLabel(keybinding));
}
hasMultipleChords() {
return (this._chords.length > 1);
}
getChords() {
return this._chords.map((keybinding) => this._getChord(keybinding));
}
_getChord(keybinding) {
return new ResolvedChord(keybinding.ctrlKey, keybinding.shiftKey, keybinding.altKey, keybinding.metaKey, this._getLabel(keybinding), this._getAriaLabel(keybinding));
}
getDispatchChords() {
return this._chords.map((keybinding) => this._getChordDispatch(keybinding));
}
getSingleModifierDispatchChords() {
return this._chords.map((keybinding) => this._getSingleModifierChordDispatch(keybinding));
}
}
@@ -0,0 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from '../../instantiation/common/instantiation.js';
export const IKeybindingService = createDecorator('keybindingService');
@@ -0,0 +1,298 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { implies, expressionsAreEqualWithConstantSubstitution } from '../../contextkey/common/contextkey.js';
// util definitions to make working with the above types easier within this module:
export const NoMatchingKb = { kind: 0 /* ResultKind.NoMatchingKb */ };
const MoreChordsNeeded = { kind: 1 /* ResultKind.MoreChordsNeeded */ };
function KbFound(commandId, commandArgs, isBubble) {
return { kind: 2 /* ResultKind.KbFound */, commandId, commandArgs, isBubble };
}
//#endregion
/**
* Stores mappings from keybindings to commands and from commands to keybindings.
* Given a sequence of chords, `resolve`s which keybinding it matches
*/
export class KeybindingResolver {
constructor(
/** built-in and extension-provided keybindings */
defaultKeybindings,
/** user's keybindings */
overrides, log) {
this._log = log;
this._defaultKeybindings = defaultKeybindings;
this._defaultBoundCommands = new Map();
for (const defaultKeybinding of defaultKeybindings) {
const command = defaultKeybinding.command;
if (command && command.charAt(0) !== '-') {
this._defaultBoundCommands.set(command, true);
}
}
this._map = new Map();
this._lookupMap = new Map();
this._keybindings = KeybindingResolver.handleRemovals([].concat(defaultKeybindings).concat(overrides));
for (let i = 0, len = this._keybindings.length; i < len; i++) {
const k = this._keybindings[i];
if (k.chords.length === 0) {
// unbound
continue;
}
// substitute with constants that are registered after startup - https://github.com/microsoft/vscode/issues/174218#issuecomment-1437972127
const when = k.when?.substituteConstants();
if (when && when.type === 0 /* ContextKeyExprType.False */) {
// when condition is false
continue;
}
this._addKeyPress(k.chords[0], k);
}
}
static _isTargetedForRemoval(defaultKb, keypress, when) {
if (keypress) {
for (let i = 0; i < keypress.length; i++) {
if (keypress[i] !== defaultKb.chords[i]) {
return false;
}
}
}
// `true` means always, as does `undefined`
// so we will treat `true` === `undefined`
if (when && when.type !== 1 /* ContextKeyExprType.True */) {
if (!defaultKb.when) {
return false;
}
if (!expressionsAreEqualWithConstantSubstitution(when, defaultKb.when)) {
return false;
}
}
return true;
}
/**
* Looks for rules containing "-commandId" and removes them.
*/
static handleRemovals(rules) {
// Do a first pass and construct a hash-map for removals
const removals = new Map();
for (let i = 0, len = rules.length; i < len; i++) {
const rule = rules[i];
if (rule.command && rule.command.charAt(0) === '-') {
const command = rule.command.substring(1);
if (!removals.has(command)) {
removals.set(command, [rule]);
}
else {
removals.get(command).push(rule);
}
}
}
if (removals.size === 0) {
// There are no removals
return rules;
}
// Do a second pass and keep only non-removed keybindings
const result = [];
for (let i = 0, len = rules.length; i < len; i++) {
const rule = rules[i];
if (!rule.command || rule.command.length === 0) {
result.push(rule);
continue;
}
if (rule.command.charAt(0) === '-') {
continue;
}
const commandRemovals = removals.get(rule.command);
if (!commandRemovals || !rule.isDefault) {
result.push(rule);
continue;
}
let isRemoved = false;
for (const commandRemoval of commandRemovals) {
const when = commandRemoval.when;
if (this._isTargetedForRemoval(rule, commandRemoval.chords, when)) {
isRemoved = true;
break;
}
}
if (!isRemoved) {
result.push(rule);
continue;
}
}
return result;
}
_addKeyPress(keypress, item) {
const conflicts = this._map.get(keypress);
if (typeof conflicts === 'undefined') {
// There is no conflict so far
this._map.set(keypress, [item]);
this._addToLookupMap(item);
return;
}
for (let i = conflicts.length - 1; i >= 0; i--) {
const conflict = conflicts[i];
if (conflict.command === item.command) {
continue;
}
// Test if the shorter keybinding is a prefix of the longer one.
// If the shorter keybinding is a prefix, it effectively will shadow the longer one and is considered a conflict.
let isShorterKbPrefix = true;
for (let i = 1; i < conflict.chords.length && i < item.chords.length; i++) {
if (conflict.chords[i] !== item.chords[i]) {
// The ith step does not conflict
isShorterKbPrefix = false;
break;
}
}
if (!isShorterKbPrefix) {
continue;
}
if (KeybindingResolver.whenIsEntirelyIncluded(conflict.when, item.when)) {
// `item` completely overwrites `conflict`
// Remove conflict from the lookupMap
this._removeFromLookupMap(conflict);
}
}
conflicts.push(item);
this._addToLookupMap(item);
}
_addToLookupMap(item) {
if (!item.command) {
return;
}
let arr = this._lookupMap.get(item.command);
if (typeof arr === 'undefined') {
arr = [item];
this._lookupMap.set(item.command, arr);
}
else {
arr.push(item);
}
}
_removeFromLookupMap(item) {
if (!item.command) {
return;
}
const arr = this._lookupMap.get(item.command);
if (typeof arr === 'undefined') {
return;
}
for (let i = 0, len = arr.length; i < len; i++) {
if (arr[i] === item) {
arr.splice(i, 1);
return;
}
}
}
/**
* Returns true if it is provable `a` implies `b`.
*/
static whenIsEntirelyIncluded(a, b) {
if (!b || b.type === 1 /* ContextKeyExprType.True */) {
return true;
}
if (!a || a.type === 1 /* ContextKeyExprType.True */) {
return false;
}
return implies(a, b);
}
getKeybindings() {
return this._keybindings;
}
lookupPrimaryKeybinding(commandId, context) {
const items = this._lookupMap.get(commandId);
if (typeof items === 'undefined' || items.length === 0) {
return null;
}
if (items.length === 1) {
return items[0];
}
for (let i = items.length - 1; i >= 0; i--) {
const item = items[i];
if (context.contextMatchesRules(item.when)) {
return item;
}
}
return items[items.length - 1];
}
/**
* Looks up a keybinding trigged as a result of pressing a sequence of chords - `[...currentChords, keypress]`
*
* Example: resolving 3 chords pressed sequentially - `cmd+k cmd+p cmd+i`:
* `currentChords = [ 'cmd+k' , 'cmd+p' ]` and `keypress = `cmd+i` - last pressed chord
*/
resolve(context, currentChords, keypress) {
const pressedChords = [...currentChords, keypress];
this._log(`| Resolving ${pressedChords}`);
const kbCandidates = this._map.get(pressedChords[0]);
if (kbCandidates === undefined) {
// No bindings with such 0-th chord
this._log(`\\ No keybinding entries.`);
return NoMatchingKb;
}
let lookupMap = null;
if (pressedChords.length < 2) {
lookupMap = kbCandidates;
}
else {
// Fetch all chord bindings for `currentChords`
lookupMap = [];
for (let i = 0, len = kbCandidates.length; i < len; i++) {
const candidate = kbCandidates[i];
if (pressedChords.length > candidate.chords.length) { // # of pressed chords can't be less than # of chords in a keybinding to invoke
continue;
}
let prefixMatches = true;
for (let i = 1; i < pressedChords.length; i++) {
if (candidate.chords[i] !== pressedChords[i]) {
prefixMatches = false;
break;
}
}
if (prefixMatches) {
lookupMap.push(candidate);
}
}
}
// check there's a keybinding with a matching when clause
const result = this._findCommand(context, lookupMap);
if (!result) {
this._log(`\\ From ${lookupMap.length} keybinding entries, no when clauses matched the context.`);
return NoMatchingKb;
}
// check we got all chords necessary to be sure a particular keybinding needs to be invoked
if (pressedChords.length < result.chords.length) {
// The chord sequence is not complete
this._log(`\\ From ${lookupMap.length} keybinding entries, awaiting ${result.chords.length - pressedChords.length} more chord(s), when: ${printWhenExplanation(result.when)}, source: ${printSourceExplanation(result)}.`);
return MoreChordsNeeded;
}
this._log(`\\ From ${lookupMap.length} keybinding entries, matched ${result.command}, when: ${printWhenExplanation(result.when)}, source: ${printSourceExplanation(result)}.`);
return KbFound(result.command, result.commandArgs, result.bubble);
}
_findCommand(context, matches) {
for (let i = matches.length - 1; i >= 0; i--) {
const k = matches[i];
if (!KeybindingResolver._contextMatchesRules(context, k.when)) {
continue;
}
return k;
}
return null;
}
static _contextMatchesRules(context, rules) {
if (!rules) {
return true;
}
return rules.evaluate(context);
}
}
function printWhenExplanation(when) {
if (!when) {
return `no when condition`;
}
return `${when.serialize()}`;
}
function printSourceExplanation(kb) {
return (kb.extensionId
? (kb.isBuiltinExtension ? `built-in extension ${kb.extensionId}` : `user extension ${kb.extensionId}`)
: (kb.isDefault ? `built-in` : `user`));
}
@@ -0,0 +1,108 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { decodeKeybinding } from '../../../base/common/keybindings.js';
import { OS } from '../../../base/common/platform.js';
import { CommandsRegistry } from '../../commands/common/commands.js';
import { Registry } from '../../registry/common/platform.js';
import { combinedDisposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';
import { LinkedList } from '../../../base/common/linkedList.js';
/**
* Stores all built-in and extension-provided keybindings (but not ones that user defines themselves)
*/
class KeybindingsRegistryImpl {
constructor() {
this._coreKeybindings = new LinkedList();
this._extensionKeybindings = [];
this._cachedMergedKeybindings = null;
}
/**
* Take current platform into account and reduce to primary & secondary.
*/
static bindToCurrentPlatform(kb) {
if (OS === 1 /* OperatingSystem.Windows */) {
if (kb && kb.win) {
return kb.win;
}
}
else if (OS === 2 /* OperatingSystem.Macintosh */) {
if (kb && kb.mac) {
return kb.mac;
}
}
else {
if (kb && kb.linux) {
return kb.linux;
}
}
return kb;
}
registerKeybindingRule(rule) {
const actualKb = KeybindingsRegistryImpl.bindToCurrentPlatform(rule);
const result = new DisposableStore();
if (actualKb && actualKb.primary) {
const kk = decodeKeybinding(actualKb.primary, OS);
if (kk) {
result.add(this._registerDefaultKeybinding(kk, rule.id, rule.args, rule.weight, 0, rule.when));
}
}
if (actualKb && Array.isArray(actualKb.secondary)) {
for (let i = 0, len = actualKb.secondary.length; i < len; i++) {
const k = actualKb.secondary[i];
const kk = decodeKeybinding(k, OS);
if (kk) {
result.add(this._registerDefaultKeybinding(kk, rule.id, rule.args, rule.weight, -i - 1, rule.when));
}
}
}
return result;
}
registerCommandAndKeybindingRule(desc) {
return combinedDisposable(this.registerKeybindingRule(desc), CommandsRegistry.registerCommand(desc));
}
_registerDefaultKeybinding(keybinding, commandId, commandArgs, weight1, weight2, when) {
const remove = this._coreKeybindings.push({
keybinding: keybinding,
command: commandId,
commandArgs: commandArgs,
when: when,
weight1: weight1,
weight2: weight2,
extensionId: null,
isBuiltinExtension: false
});
this._cachedMergedKeybindings = null;
return toDisposable(() => {
remove();
this._cachedMergedKeybindings = null;
});
}
getDefaultKeybindings() {
if (!this._cachedMergedKeybindings) {
this._cachedMergedKeybindings = Array.from(this._coreKeybindings).concat(this._extensionKeybindings);
this._cachedMergedKeybindings.sort(sorter);
}
return this._cachedMergedKeybindings.slice(0);
}
}
export const KeybindingsRegistry = new KeybindingsRegistryImpl();
// Define extension point ids
export const Extensions = {
EditorModes: 'platform.keybindingsRegistry'
};
Registry.add(Extensions.EditorModes, KeybindingsRegistry);
function sorter(a, b) {
if (a.weight1 !== b.weight1) {
return a.weight1 - b.weight1;
}
if (a.command && b.command) {
if (a.command < b.command) {
return -1;
}
if (a.command > b.command) {
return 1;
}
}
return a.weight2 - b.weight2;
}
@@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export class ResolvedKeybindingItem {
constructor(resolvedKeybinding, command, commandArgs, when, isDefault, extensionId, isBuiltinExtension) {
this._resolvedKeybindingItemBrand = undefined;
this.resolvedKeybinding = resolvedKeybinding;
this.chords = resolvedKeybinding ? toEmptyArrayIfContainsNull(resolvedKeybinding.getDispatchChords()) : [];
if (resolvedKeybinding && this.chords.length === 0) {
// handle possible single modifier chord keybindings
this.chords = toEmptyArrayIfContainsNull(resolvedKeybinding.getSingleModifierDispatchChords());
}
this.bubble = (command ? command.charCodeAt(0) === 94 /* CharCode.Caret */ : false);
this.command = this.bubble ? command.substr(1) : command;
this.commandArgs = commandArgs;
this.when = when;
this.isDefault = isDefault;
this.extensionId = extensionId;
this.isBuiltinExtension = isBuiltinExtension;
}
}
export function toEmptyArrayIfContainsNull(arr) {
const result = [];
for (let i = 0, len = arr.length; i < len; i++) {
const element = arr[i];
if (!element) {
return [];
}
result.push(element);
}
return result;
}
@@ -0,0 +1,172 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { KeyCodeUtils, IMMUTABLE_CODE_TO_KEY_CODE } from '../../../base/common/keyCodes.js';
import { KeyCodeChord } from '../../../base/common/keybindings.js';
import { BaseResolvedKeybinding } from './baseResolvedKeybinding.js';
import { toEmptyArrayIfContainsNull } from './resolvedKeybindingItem.js';
/**
* Do not instantiate. Use KeybindingService to get a ResolvedKeybinding seeded with information about the current kb layout.
*/
export class USLayoutResolvedKeybinding extends BaseResolvedKeybinding {
constructor(chords, os) {
super(os, chords);
}
_keyCodeToUILabel(keyCode) {
if (this._os === 2 /* OperatingSystem.Macintosh */) {
switch (keyCode) {
case 15 /* KeyCode.LeftArrow */:
return '←';
case 16 /* KeyCode.UpArrow */:
return '↑';
case 17 /* KeyCode.RightArrow */:
return '→';
case 18 /* KeyCode.DownArrow */:
return '↓';
}
}
return KeyCodeUtils.toString(keyCode);
}
_getLabel(chord) {
if (chord.isDuplicateModifierCase()) {
return '';
}
return this._keyCodeToUILabel(chord.keyCode);
}
_getAriaLabel(chord) {
if (chord.isDuplicateModifierCase()) {
return '';
}
return KeyCodeUtils.toString(chord.keyCode);
}
_getElectronAccelerator(chord) {
return KeyCodeUtils.toElectronAccelerator(chord.keyCode);
}
_getUserSettingsLabel(chord) {
if (chord.isDuplicateModifierCase()) {
return '';
}
const result = KeyCodeUtils.toUserSettingsUS(chord.keyCode);
return (result ? result.toLowerCase() : result);
}
_getChordDispatch(chord) {
return USLayoutResolvedKeybinding.getDispatchStr(chord);
}
static getDispatchStr(chord) {
if (chord.isModifierKey()) {
return null;
}
let result = '';
if (chord.ctrlKey) {
result += 'ctrl+';
}
if (chord.shiftKey) {
result += 'shift+';
}
if (chord.altKey) {
result += 'alt+';
}
if (chord.metaKey) {
result += 'meta+';
}
result += KeyCodeUtils.toString(chord.keyCode);
return result;
}
_getSingleModifierChordDispatch(keybinding) {
if (keybinding.keyCode === 5 /* KeyCode.Ctrl */ && !keybinding.shiftKey && !keybinding.altKey && !keybinding.metaKey) {
return 'ctrl';
}
if (keybinding.keyCode === 4 /* KeyCode.Shift */ && !keybinding.ctrlKey && !keybinding.altKey && !keybinding.metaKey) {
return 'shift';
}
if (keybinding.keyCode === 6 /* KeyCode.Alt */ && !keybinding.ctrlKey && !keybinding.shiftKey && !keybinding.metaKey) {
return 'alt';
}
if (keybinding.keyCode === 57 /* KeyCode.Meta */ && !keybinding.ctrlKey && !keybinding.shiftKey && !keybinding.altKey) {
return 'meta';
}
return null;
}
/**
* *NOTE*: Check return value for `KeyCode.Unknown`.
*/
static _scanCodeToKeyCode(scanCode) {
const immutableKeyCode = IMMUTABLE_CODE_TO_KEY_CODE[scanCode];
if (immutableKeyCode !== -1 /* KeyCode.DependsOnKbLayout */) {
return immutableKeyCode;
}
switch (scanCode) {
case 10 /* ScanCode.KeyA */: return 31 /* KeyCode.KeyA */;
case 11 /* ScanCode.KeyB */: return 32 /* KeyCode.KeyB */;
case 12 /* ScanCode.KeyC */: return 33 /* KeyCode.KeyC */;
case 13 /* ScanCode.KeyD */: return 34 /* KeyCode.KeyD */;
case 14 /* ScanCode.KeyE */: return 35 /* KeyCode.KeyE */;
case 15 /* ScanCode.KeyF */: return 36 /* KeyCode.KeyF */;
case 16 /* ScanCode.KeyG */: return 37 /* KeyCode.KeyG */;
case 17 /* ScanCode.KeyH */: return 38 /* KeyCode.KeyH */;
case 18 /* ScanCode.KeyI */: return 39 /* KeyCode.KeyI */;
case 19 /* ScanCode.KeyJ */: return 40 /* KeyCode.KeyJ */;
case 20 /* ScanCode.KeyK */: return 41 /* KeyCode.KeyK */;
case 21 /* ScanCode.KeyL */: return 42 /* KeyCode.KeyL */;
case 22 /* ScanCode.KeyM */: return 43 /* KeyCode.KeyM */;
case 23 /* ScanCode.KeyN */: return 44 /* KeyCode.KeyN */;
case 24 /* ScanCode.KeyO */: return 45 /* KeyCode.KeyO */;
case 25 /* ScanCode.KeyP */: return 46 /* KeyCode.KeyP */;
case 26 /* ScanCode.KeyQ */: return 47 /* KeyCode.KeyQ */;
case 27 /* ScanCode.KeyR */: return 48 /* KeyCode.KeyR */;
case 28 /* ScanCode.KeyS */: return 49 /* KeyCode.KeyS */;
case 29 /* ScanCode.KeyT */: return 50 /* KeyCode.KeyT */;
case 30 /* ScanCode.KeyU */: return 51 /* KeyCode.KeyU */;
case 31 /* ScanCode.KeyV */: return 52 /* KeyCode.KeyV */;
case 32 /* ScanCode.KeyW */: return 53 /* KeyCode.KeyW */;
case 33 /* ScanCode.KeyX */: return 54 /* KeyCode.KeyX */;
case 34 /* ScanCode.KeyY */: return 55 /* KeyCode.KeyY */;
case 35 /* ScanCode.KeyZ */: return 56 /* KeyCode.KeyZ */;
case 36 /* ScanCode.Digit1 */: return 22 /* KeyCode.Digit1 */;
case 37 /* ScanCode.Digit2 */: return 23 /* KeyCode.Digit2 */;
case 38 /* ScanCode.Digit3 */: return 24 /* KeyCode.Digit3 */;
case 39 /* ScanCode.Digit4 */: return 25 /* KeyCode.Digit4 */;
case 40 /* ScanCode.Digit5 */: return 26 /* KeyCode.Digit5 */;
case 41 /* ScanCode.Digit6 */: return 27 /* KeyCode.Digit6 */;
case 42 /* ScanCode.Digit7 */: return 28 /* KeyCode.Digit7 */;
case 43 /* ScanCode.Digit8 */: return 29 /* KeyCode.Digit8 */;
case 44 /* ScanCode.Digit9 */: return 30 /* KeyCode.Digit9 */;
case 45 /* ScanCode.Digit0 */: return 21 /* KeyCode.Digit0 */;
case 51 /* ScanCode.Minus */: return 88 /* KeyCode.Minus */;
case 52 /* ScanCode.Equal */: return 86 /* KeyCode.Equal */;
case 53 /* ScanCode.BracketLeft */: return 92 /* KeyCode.BracketLeft */;
case 54 /* ScanCode.BracketRight */: return 94 /* KeyCode.BracketRight */;
case 55 /* ScanCode.Backslash */: return 93 /* KeyCode.Backslash */;
case 56 /* ScanCode.IntlHash */: return 0 /* KeyCode.Unknown */; // missing
case 57 /* ScanCode.Semicolon */: return 85 /* KeyCode.Semicolon */;
case 58 /* ScanCode.Quote */: return 95 /* KeyCode.Quote */;
case 59 /* ScanCode.Backquote */: return 91 /* KeyCode.Backquote */;
case 60 /* ScanCode.Comma */: return 87 /* KeyCode.Comma */;
case 61 /* ScanCode.Period */: return 89 /* KeyCode.Period */;
case 62 /* ScanCode.Slash */: return 90 /* KeyCode.Slash */;
case 106 /* ScanCode.IntlBackslash */: return 97 /* KeyCode.IntlBackslash */;
}
return 0 /* KeyCode.Unknown */;
}
static _toKeyCodeChord(chord) {
if (!chord) {
return null;
}
if (chord instanceof KeyCodeChord) {
return chord;
}
const keyCode = this._scanCodeToKeyCode(chord.scanCode);
if (keyCode === 0 /* KeyCode.Unknown */) {
return null;
}
return new KeyCodeChord(chord.ctrlKey, chord.shiftKey, chord.altKey, chord.metaKey, keyCode);
}
static resolveKeybinding(keybinding, os) {
const chords = toEmptyArrayIfContainsNull(keybinding.chords.map(chord => this._toKeyCodeChord(chord)));
if (chords.length > 0) {
return [new USLayoutResolvedKeybinding(chords, os)];
}
return [];
}
}
@@ -0,0 +1,2 @@
import { createDecorator } from '../../instantiation/common/instantiation.js';
export const ILabelService = createDecorator('labelService');
@@ -0,0 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from '../../instantiation/common/instantiation.js';
export const ILayoutService = createDecorator('layoutService');
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,150 @@
import { Emitter } from '../../../base/common/event.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { RawContextKey } from '../../contextkey/common/contextkey.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
export const ILogService = createDecorator('logService');
export var LogLevel;
(function (LogLevel) {
LogLevel[LogLevel["Off"] = 0] = "Off";
LogLevel[LogLevel["Trace"] = 1] = "Trace";
LogLevel[LogLevel["Debug"] = 2] = "Debug";
LogLevel[LogLevel["Info"] = 3] = "Info";
LogLevel[LogLevel["Warning"] = 4] = "Warning";
LogLevel[LogLevel["Error"] = 5] = "Error";
})(LogLevel || (LogLevel = {}));
export const DEFAULT_LOG_LEVEL = LogLevel.Info;
export class AbstractLogger extends Disposable {
constructor() {
super(...arguments);
this.level = DEFAULT_LOG_LEVEL;
this._onDidChangeLogLevel = this._register(new Emitter());
this.onDidChangeLogLevel = this._onDidChangeLogLevel.event;
}
setLevel(level) {
if (this.level !== level) {
this.level = level;
this._onDidChangeLogLevel.fire(this.level);
}
}
getLevel() {
return this.level;
}
checkLogLevel(level) {
return this.level !== LogLevel.Off && this.level <= level;
}
}
export class ConsoleLogger extends AbstractLogger {
constructor(logLevel = DEFAULT_LOG_LEVEL, useColors = true) {
super();
this.useColors = useColors;
this.setLevel(logLevel);
}
trace(message, ...args) {
if (this.checkLogLevel(LogLevel.Trace)) {
if (this.useColors) {
console.log('%cTRACE', 'color: #888', message, ...args);
}
else {
console.log(message, ...args);
}
}
}
debug(message, ...args) {
if (this.checkLogLevel(LogLevel.Debug)) {
if (this.useColors) {
console.log('%cDEBUG', 'background: #eee; color: #888', message, ...args);
}
else {
console.log(message, ...args);
}
}
}
info(message, ...args) {
if (this.checkLogLevel(LogLevel.Info)) {
if (this.useColors) {
console.log('%c INFO', 'color: #33f', message, ...args);
}
else {
console.log(message, ...args);
}
}
}
warn(message, ...args) {
if (this.checkLogLevel(LogLevel.Warning)) {
if (this.useColors) {
console.log('%c WARN', 'color: #993', message, ...args);
}
else {
console.log(message, ...args);
}
}
}
error(message, ...args) {
if (this.checkLogLevel(LogLevel.Error)) {
if (this.useColors) {
console.log('%c ERR', 'color: #f33', message, ...args);
}
else {
console.error(message, ...args);
}
}
}
}
export class MultiplexLogger extends AbstractLogger {
constructor(loggers) {
super();
this.loggers = loggers;
if (loggers.length) {
this.setLevel(loggers[0].getLevel());
}
}
setLevel(level) {
for (const logger of this.loggers) {
logger.setLevel(level);
}
super.setLevel(level);
}
trace(message, ...args) {
for (const logger of this.loggers) {
logger.trace(message, ...args);
}
}
debug(message, ...args) {
for (const logger of this.loggers) {
logger.debug(message, ...args);
}
}
info(message, ...args) {
for (const logger of this.loggers) {
logger.info(message, ...args);
}
}
warn(message, ...args) {
for (const logger of this.loggers) {
logger.warn(message, ...args);
}
}
error(message, ...args) {
for (const logger of this.loggers) {
logger.error(message, ...args);
}
}
dispose() {
for (const logger of this.loggers) {
logger.dispose();
}
super.dispose();
}
}
export function LogLevelToString(logLevel) {
switch (logLevel) {
case LogLevel.Trace: return 'trace';
case LogLevel.Debug: return 'debug';
case LogLevel.Info: return 'info';
case LogLevel.Warning: return 'warn';
case LogLevel.Error: return 'error';
case LogLevel.Off: return 'off';
}
}
// Contexts
export const CONTEXT_LOG_LEVEL = new RawContextKey('logLevel', LogLevelToString(LogLevel.Info));
@@ -0,0 +1,37 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from '../../../base/common/lifecycle.js';
import { MultiplexLogger } from './log.js';
export class LogService extends Disposable {
constructor(primaryLogger, otherLoggers = []) {
super();
this.logger = new MultiplexLogger([primaryLogger, ...otherLoggers]);
this._register(primaryLogger.onDidChangeLogLevel(level => this.setLevel(level)));
}
get onDidChangeLogLevel() {
return this.logger.onDidChangeLogLevel;
}
setLevel(level) {
this.logger.setLevel(level);
}
getLevel() {
return this.logger.getLevel();
}
trace(message, ...args) {
this.logger.trace(message, ...args);
}
debug(message, ...args) {
this.logger.debug(message, ...args);
}
info(message, ...args) {
this.logger.info(message, ...args);
}
warn(message, ...args) {
this.logger.warn(message, ...args);
}
error(message, ...args) {
this.logger.error(message, ...args);
}
}
@@ -0,0 +1,303 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isFalsyOrEmpty, isNonEmptyArray } from '../../../base/common/arrays.js';
import { DebounceEmitter } from '../../../base/common/event.js';
import { Iterable } from '../../../base/common/iterator.js';
import { ResourceMap } from '../../../base/common/map.js';
import { Schemas } from '../../../base/common/network.js';
import { URI } from '../../../base/common/uri.js';
import { MarkerSeverity } from './markers.js';
export const unsupportedSchemas = new Set([
Schemas.inMemory,
Schemas.vscodeSourceControl,
Schemas.walkThrough,
Schemas.walkThroughSnippet,
Schemas.vscodeChatCodeBlock,
Schemas.vscodeCopilotBackingChatCodeBlock,
]);
class DoubleResourceMap {
constructor() {
this._byResource = new ResourceMap();
this._byOwner = new Map();
}
set(resource, owner, value) {
let ownerMap = this._byResource.get(resource);
if (!ownerMap) {
ownerMap = new Map();
this._byResource.set(resource, ownerMap);
}
ownerMap.set(owner, value);
let resourceMap = this._byOwner.get(owner);
if (!resourceMap) {
resourceMap = new ResourceMap();
this._byOwner.set(owner, resourceMap);
}
resourceMap.set(resource, value);
}
get(resource, owner) {
const ownerMap = this._byResource.get(resource);
return ownerMap?.get(owner);
}
delete(resource, owner) {
let removedA = false;
let removedB = false;
const ownerMap = this._byResource.get(resource);
if (ownerMap) {
removedA = ownerMap.delete(owner);
}
const resourceMap = this._byOwner.get(owner);
if (resourceMap) {
removedB = resourceMap.delete(resource);
}
if (removedA !== removedB) {
throw new Error('illegal state');
}
return removedA && removedB;
}
values(key) {
if (typeof key === 'string') {
return this._byOwner.get(key)?.values() ?? Iterable.empty();
}
if (URI.isUri(key)) {
return this._byResource.get(key)?.values() ?? Iterable.empty();
}
return Iterable.map(Iterable.concat(...this._byOwner.values()), map => map[1]);
}
}
class MarkerStats {
constructor(service) {
this.errors = 0;
this.infos = 0;
this.warnings = 0;
this.unknowns = 0;
this._data = new ResourceMap();
this._service = service;
this._subscription = service.onMarkerChanged(this._update, this);
}
dispose() {
this._subscription.dispose();
}
_update(resources) {
for (const resource of resources) {
const oldStats = this._data.get(resource);
if (oldStats) {
this._substract(oldStats);
}
const newStats = this._resourceStats(resource);
this._add(newStats);
this._data.set(resource, newStats);
}
}
_resourceStats(resource) {
const result = { errors: 0, warnings: 0, infos: 0, unknowns: 0 };
// TODO this is a hack
if (unsupportedSchemas.has(resource.scheme)) {
return result;
}
for (const { severity } of this._service.read({ resource })) {
if (severity === MarkerSeverity.Error) {
result.errors += 1;
}
else if (severity === MarkerSeverity.Warning) {
result.warnings += 1;
}
else if (severity === MarkerSeverity.Info) {
result.infos += 1;
}
else {
result.unknowns += 1;
}
}
return result;
}
_substract(op) {
this.errors -= op.errors;
this.warnings -= op.warnings;
this.infos -= op.infos;
this.unknowns -= op.unknowns;
}
_add(op) {
this.errors += op.errors;
this.warnings += op.warnings;
this.infos += op.infos;
this.unknowns += op.unknowns;
}
}
export class MarkerService {
constructor() {
this._onMarkerChanged = new DebounceEmitter({
delay: 0,
merge: MarkerService._merge
});
this.onMarkerChanged = this._onMarkerChanged.event;
this._data = new DoubleResourceMap();
this._stats = new MarkerStats(this);
}
dispose() {
this._stats.dispose();
this._onMarkerChanged.dispose();
}
remove(owner, resources) {
for (const resource of resources || []) {
this.changeOne(owner, resource, []);
}
}
changeOne(owner, resource, markerData) {
if (isFalsyOrEmpty(markerData)) {
// remove marker for this (owner,resource)-tuple
const removed = this._data.delete(resource, owner);
if (removed) {
this._onMarkerChanged.fire([resource]);
}
}
else {
// insert marker for this (owner,resource)-tuple
const markers = [];
for (const data of markerData) {
const marker = MarkerService._toMarker(owner, resource, data);
if (marker) {
markers.push(marker);
}
}
this._data.set(resource, owner, markers);
this._onMarkerChanged.fire([resource]);
}
}
static _toMarker(owner, resource, data) {
let { code, severity, message, source, startLineNumber, startColumn, endLineNumber, endColumn, relatedInformation, tags, } = data;
if (!message) {
return undefined;
}
// santize data
startLineNumber = startLineNumber > 0 ? startLineNumber : 1;
startColumn = startColumn > 0 ? startColumn : 1;
endLineNumber = endLineNumber >= startLineNumber ? endLineNumber : startLineNumber;
endColumn = endColumn > 0 ? endColumn : startColumn;
return {
resource,
owner,
code,
severity,
message,
source,
startLineNumber,
startColumn,
endLineNumber,
endColumn,
relatedInformation,
tags,
};
}
changeAll(owner, data) {
const changes = [];
// remove old marker
const existing = this._data.values(owner);
if (existing) {
for (const data of existing) {
const first = Iterable.first(data);
if (first) {
changes.push(first.resource);
this._data.delete(first.resource, owner);
}
}
}
// add new markers
if (isNonEmptyArray(data)) {
// group by resource
const groups = new ResourceMap();
for (const { resource, marker: markerData } of data) {
const marker = MarkerService._toMarker(owner, resource, markerData);
if (!marker) {
// filter bad markers
continue;
}
const array = groups.get(resource);
if (!array) {
groups.set(resource, [marker]);
changes.push(resource);
}
else {
array.push(marker);
}
}
// insert all
for (const [resource, value] of groups) {
this._data.set(resource, owner, value);
}
}
if (changes.length > 0) {
this._onMarkerChanged.fire(changes);
}
}
read(filter = Object.create(null)) {
let { owner, resource, severities, take } = filter;
if (!take || take < 0) {
take = -1;
}
if (owner && resource) {
// exactly one owner AND resource
const data = this._data.get(resource, owner);
if (!data) {
return [];
}
else {
const result = [];
for (const marker of data) {
if (MarkerService._accept(marker, severities)) {
const newLen = result.push(marker);
if (take > 0 && newLen === take) {
break;
}
}
}
return result;
}
}
else if (!owner && !resource) {
// all
const result = [];
for (const markers of this._data.values()) {
for (const data of markers) {
if (MarkerService._accept(data, severities)) {
const newLen = result.push(data);
if (take > 0 && newLen === take) {
return result;
}
}
}
}
return result;
}
else {
// of one resource OR owner
const iterable = this._data.values(resource ?? owner);
const result = [];
for (const markers of iterable) {
for (const data of markers) {
if (MarkerService._accept(data, severities)) {
const newLen = result.push(data);
if (take > 0 && newLen === take) {
return result;
}
}
}
}
return result;
}
}
static _accept(marker, severities) {
return severities === undefined || (severities & marker.severity) === marker.severity;
}
// --- event debounce logic
static _merge(all) {
const set = new ResourceMap();
for (const array of all) {
for (const item of array) {
set.set(item, true);
}
}
return Array.from(set.keys());
}
}
@@ -0,0 +1,116 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Severity from '../../../base/common/severity.js';
import { localize } from '../../../nls.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
export var MarkerSeverity;
(function (MarkerSeverity) {
MarkerSeverity[MarkerSeverity["Hint"] = 1] = "Hint";
MarkerSeverity[MarkerSeverity["Info"] = 2] = "Info";
MarkerSeverity[MarkerSeverity["Warning"] = 4] = "Warning";
MarkerSeverity[MarkerSeverity["Error"] = 8] = "Error";
})(MarkerSeverity || (MarkerSeverity = {}));
(function (MarkerSeverity) {
function compare(a, b) {
return b - a;
}
MarkerSeverity.compare = compare;
const _displayStrings = Object.create(null);
_displayStrings[MarkerSeverity.Error] = localize('sev.error', "Error");
_displayStrings[MarkerSeverity.Warning] = localize('sev.warning', "Warning");
_displayStrings[MarkerSeverity.Info] = localize('sev.info', "Info");
function toString(a) {
return _displayStrings[a] || '';
}
MarkerSeverity.toString = toString;
function fromSeverity(severity) {
switch (severity) {
case Severity.Error: return MarkerSeverity.Error;
case Severity.Warning: return MarkerSeverity.Warning;
case Severity.Info: return MarkerSeverity.Info;
case Severity.Ignore: return MarkerSeverity.Hint;
}
}
MarkerSeverity.fromSeverity = fromSeverity;
function toSeverity(severity) {
switch (severity) {
case MarkerSeverity.Error: return Severity.Error;
case MarkerSeverity.Warning: return Severity.Warning;
case MarkerSeverity.Info: return Severity.Info;
case MarkerSeverity.Hint: return Severity.Ignore;
}
}
MarkerSeverity.toSeverity = toSeverity;
})(MarkerSeverity || (MarkerSeverity = {}));
export var IMarkerData;
(function (IMarkerData) {
const emptyString = '';
function makeKey(markerData) {
return makeKeyOptionalMessage(markerData, true);
}
IMarkerData.makeKey = makeKey;
function makeKeyOptionalMessage(markerData, useMessage) {
const result = [emptyString];
if (markerData.source) {
result.push(markerData.source.replace('¦', '\\¦'));
}
else {
result.push(emptyString);
}
if (markerData.code) {
if (typeof markerData.code === 'string') {
result.push(markerData.code.replace('¦', '\\¦'));
}
else {
result.push(markerData.code.value.replace('¦', '\\¦'));
}
}
else {
result.push(emptyString);
}
if (markerData.severity !== undefined && markerData.severity !== null) {
result.push(MarkerSeverity.toString(markerData.severity));
}
else {
result.push(emptyString);
}
// Modifed to not include the message as part of the marker key to work around
// https://github.com/microsoft/vscode/issues/77475
if (markerData.message && useMessage) {
result.push(markerData.message.replace('¦', '\\¦'));
}
else {
result.push(emptyString);
}
if (markerData.startLineNumber !== undefined && markerData.startLineNumber !== null) {
result.push(markerData.startLineNumber.toString());
}
else {
result.push(emptyString);
}
if (markerData.startColumn !== undefined && markerData.startColumn !== null) {
result.push(markerData.startColumn.toString());
}
else {
result.push(emptyString);
}
if (markerData.endLineNumber !== undefined && markerData.endLineNumber !== null) {
result.push(markerData.endLineNumber.toString());
}
else {
result.push(emptyString);
}
if (markerData.endColumn !== undefined && markerData.endColumn !== null) {
result.push(markerData.endColumn.toString());
}
else {
result.push(emptyString);
}
result.push(emptyString);
return result.join('¦');
}
IMarkerData.makeKeyOptionalMessage = makeKeyOptionalMessage;
})(IMarkerData || (IMarkerData = {}));
export const IMarkerService = createDecorator('markerService');
@@ -0,0 +1,6 @@
import BaseSeverity from '../../../base/common/severity.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
export var Severity = BaseSeverity;
export const INotificationService = createDecorator('notificationService');
export class NoOpNotification {
}
@@ -0,0 +1,21 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { autorunOpts } from '../../../base/common/observable.js';
import { observableFromEventOpts } from '../../../base/common/observableInternal/utils.js';
/** Creates an observable update when a configuration key updates. */
export function observableConfigValue(key, defaultValue, configurationService) {
return observableFromEventOpts({ debugName: () => `Configuration Key "${key}"`, }, (handleChange) => configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(key)) {
handleChange(e);
}
}), () => configurationService.getValue(key) ?? defaultValue);
}
/** Update the configuration key with a value derived from observables. */
export function bindContextKey(key, service, computeValue) {
const boundKey = key.bindTo(service);
return autorunOpts({ debugName: () => `Set Context Key "${key.key}"` }, reader => {
boundKey.set(computeValue(reader));
});
}
@@ -0,0 +1,54 @@
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isHotReloadEnabled } from '../../../base/common/hotReload.js';
import { readHotReloadableExport } from '../../../base/common/hotReloadHelpers.js';
import { autorunWithStore } from '../../../base/common/observable.js';
import { IInstantiationService } from '../../instantiation/common/instantiation.js';
class BaseClass {
constructor(instantiationService) {
this.instantiationService = instantiationService;
}
init(...params) { }
}
function createWrapper(getClass, B) {
return (class ReloadableWrapper extends B {
constructor() {
super(...arguments);
this._autorun = undefined;
}
init(...params) {
this._autorun = autorunWithStore((reader, store) => {
const clazz = readHotReloadableExport(getClass(), reader);
store.add(this.instantiationService.createInstance(clazz, ...params));
});
}
dispose() {
this._autorun?.dispose();
}
});
}
/**
* Wrap a class in a reloadable wrapper.
* When the wrapper is created, the original class is created.
* When the original class changes, the instance is re-created.
*/
export function wrapInReloadableClass1(getClass) {
return !isHotReloadEnabled() ? getClass() : createWrapper(getClass, BaseClass1);
}
let BaseClass1 = class BaseClass1 extends BaseClass {
constructor(param1, i) { super(i); this.init(param1); }
};
BaseClass1 = __decorate([
__param(1, IInstantiationService)
], BaseClass1);
@@ -0,0 +1,13 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-link {
color: var(--vscode-textLink-foreground);
}
.monaco-link:hover {
color: var(--vscode-textLink-activeForeground);
}
@@ -0,0 +1,96 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { $, append, EventHelper } from '../../../base/browser/dom.js';
import { DomEmitter } from '../../../base/browser/event.js';
import { StandardKeyboardEvent } from '../../../base/browser/keyboardEvent.js';
import { EventType as TouchEventType, Gesture } from '../../../base/browser/touch.js';
import { Event } from '../../../base/common/event.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { IOpenerService } from '../common/opener.js';
import './link.css';
import { getDefaultHoverDelegate } from '../../../base/browser/ui/hover/hoverDelegateFactory.js';
import { IHoverService } from '../../hover/browser/hover.js';
let Link = class Link extends Disposable {
get enabled() {
return this._enabled;
}
set enabled(enabled) {
if (enabled) {
this.el.setAttribute('aria-disabled', 'false');
this.el.tabIndex = 0;
this.el.style.pointerEvents = 'auto';
this.el.style.opacity = '1';
this.el.style.cursor = 'pointer';
this._enabled = false;
}
else {
this.el.setAttribute('aria-disabled', 'true');
this.el.tabIndex = -1;
this.el.style.pointerEvents = 'none';
this.el.style.opacity = '0.4';
this.el.style.cursor = 'default';
this._enabled = true;
}
this._enabled = enabled;
}
constructor(container, _link, options = {}, _hoverService, openerService) {
super();
this._link = _link;
this._hoverService = _hoverService;
this._enabled = true;
this.el = append(container, $('a.monaco-link', {
tabIndex: _link.tabIndex ?? 0,
href: _link.href,
}, _link.label));
this.hoverDelegate = options.hoverDelegate ?? getDefaultHoverDelegate('mouse');
this.setTooltip(_link.title);
this.el.setAttribute('role', 'button');
const onClickEmitter = this._register(new DomEmitter(this.el, 'click'));
const onKeyPress = this._register(new DomEmitter(this.el, 'keypress'));
const onEnterPress = Event.chain(onKeyPress.event, $ => $.map(e => new StandardKeyboardEvent(e))
.filter(e => e.keyCode === 3 /* KeyCode.Enter */));
const onTap = this._register(new DomEmitter(this.el, TouchEventType.Tap)).event;
this._register(Gesture.addTarget(this.el));
const onOpen = Event.any(onClickEmitter.event, onEnterPress, onTap);
this._register(onOpen(e => {
if (!this.enabled) {
return;
}
EventHelper.stop(e, true);
if (options?.opener) {
options.opener(this._link.href);
}
else {
openerService.open(this._link.href, { allowCommands: true });
}
}));
this.enabled = true;
}
setTooltip(title) {
if (this.hoverDelegate.showNativeHover) {
this.el.title = title ?? '';
}
else if (!this.hover && title) {
this.hover = this._register(this._hoverService.setupManagedHover(this.hoverDelegate, this.el, title));
}
else if (this.hover) {
this.hover.update(title);
}
}
};
Link = __decorate([
__param(3, IHoverService),
__param(4, IOpenerService)
], Link);
export { Link };
@@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from '../../instantiation/common/instantiation.js';
export const IOpenerService = createDecorator('openerService');
/**
* file:///some/file.js#73
* file:///some/file.js#L73
* file:///some/file.js#73,84
* file:///some/file.js#L73,84
* file:///some/file.js#73-83
* file:///some/file.js#L73-L83
* file:///some/file.js#73,84-83,52
* file:///some/file.js#L73,84-L83,52
*/
export function extractSelection(uri) {
let selection = undefined;
const match = /^L?(\d+)(?:,(\d+))?(-L?(\d+)(?:,(\d+))?)?/.exec(uri.fragment);
if (match) {
selection = {
startLineNumber: parseInt(match[1]),
startColumn: match[2] ? parseInt(match[2]) : 1,
endLineNumber: match[4] ? parseInt(match[4]) : undefined,
endColumn: match[4] ? (match[5] ? parseInt(match[5]) : 1) : undefined
};
uri = uri.with({ fragment: '' });
}
return { selection, uri };
}
@@ -0,0 +1 @@
export {};
@@ -0,0 +1,18 @@
import { createDecorator } from '../../instantiation/common/instantiation.js';
export const IProgressService = createDecorator('progressService');
export const emptyProgressRunner = Object.freeze({
total() { },
worked() { },
done() { }
});
export class Progress {
static { this.None = Object.freeze({ report() { } }); }
constructor(callback) {
this.callback = callback;
}
report(item) {
this._value = item;
this.callback(this._value);
}
}
export const IEditorProgressService = createDecorator('editorProgressService');
@@ -0,0 +1,354 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var AbstractCommandsQuickAccessProvider_1, CommandsHistory_1;
import { toErrorMessage } from '../../../base/common/errorMessage.js';
import { isCancellationError } from '../../../base/common/errors.js';
import { matchesContiguousSubString, matchesPrefix, matchesWords, or } from '../../../base/common/filters.js';
import { createSingleCallFunction } from '../../../base/common/functional.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { LRUCache } from '../../../base/common/map.js';
import { TfIdfCalculator, normalizeTfIdfScores } from '../../../base/common/tfIdf.js';
import { localize } from '../../../nls.js';
import { ICommandService } from '../../commands/common/commands.js';
import { IConfigurationService } from '../../configuration/common/configuration.js';
import { IDialogService } from '../../dialogs/common/dialogs.js';
import { IInstantiationService } from '../../instantiation/common/instantiation.js';
import { IKeybindingService } from '../../keybinding/common/keybinding.js';
import { ILogService } from '../../log/common/log.js';
import { PickerQuickAccessProvider } from './pickerQuickAccess.js';
import { IStorageService, WillSaveStateReason } from '../../storage/common/storage.js';
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
let AbstractCommandsQuickAccessProvider = class AbstractCommandsQuickAccessProvider extends PickerQuickAccessProvider {
static { AbstractCommandsQuickAccessProvider_1 = this; }
static { this.PREFIX = '>'; }
static { this.TFIDF_THRESHOLD = 0.5; }
static { this.TFIDF_MAX_RESULTS = 5; }
static { this.WORD_FILTER = or(matchesPrefix, matchesWords, matchesContiguousSubString); }
constructor(options, instantiationService, keybindingService, commandService, telemetryService, dialogService) {
super(AbstractCommandsQuickAccessProvider_1.PREFIX, options);
this.instantiationService = instantiationService;
this.keybindingService = keybindingService;
this.commandService = commandService;
this.telemetryService = telemetryService;
this.dialogService = dialogService;
this.commandsHistory = this._register(this.instantiationService.createInstance(CommandsHistory));
this.options = options;
}
async _getPicks(filter, _disposables, token, runOptions) {
// Ask subclass for all command picks
const allCommandPicks = await this.getCommandPicks(token);
if (token.isCancellationRequested) {
return [];
}
const runTfidf = createSingleCallFunction(() => {
const tfidf = new TfIdfCalculator();
tfidf.updateDocuments(allCommandPicks.map(commandPick => ({
key: commandPick.commandId,
textChunks: [this.getTfIdfChunk(commandPick)]
})));
const result = tfidf.calculateScores(filter, token);
return normalizeTfIdfScores(result)
.filter(score => score.score > AbstractCommandsQuickAccessProvider_1.TFIDF_THRESHOLD)
.slice(0, AbstractCommandsQuickAccessProvider_1.TFIDF_MAX_RESULTS);
});
// Filter
const filteredCommandPicks = [];
for (const commandPick of allCommandPicks) {
const labelHighlights = AbstractCommandsQuickAccessProvider_1.WORD_FILTER(filter, commandPick.label) ?? undefined;
const aliasHighlights = commandPick.commandAlias ? AbstractCommandsQuickAccessProvider_1.WORD_FILTER(filter, commandPick.commandAlias) ?? undefined : undefined;
// Add if matching in label or alias
if (labelHighlights || aliasHighlights) {
commandPick.highlights = {
label: labelHighlights,
detail: this.options.showAlias ? aliasHighlights : undefined
};
filteredCommandPicks.push(commandPick);
}
// Also add if we have a 100% command ID match
else if (filter === commandPick.commandId) {
filteredCommandPicks.push(commandPick);
}
// Handle tf-idf scoring for the rest if there's a filter
else if (filter.length >= 3) {
const tfidf = runTfidf();
if (token.isCancellationRequested) {
return [];
}
// Add if we have a tf-idf score
const tfidfScore = tfidf.find(score => score.key === commandPick.commandId);
if (tfidfScore) {
commandPick.tfIdfScore = tfidfScore.score;
filteredCommandPicks.push(commandPick);
}
}
}
// Add description to commands that have duplicate labels
const mapLabelToCommand = new Map();
for (const commandPick of filteredCommandPicks) {
const existingCommandForLabel = mapLabelToCommand.get(commandPick.label);
if (existingCommandForLabel) {
commandPick.description = commandPick.commandId;
existingCommandForLabel.description = existingCommandForLabel.commandId;
}
else {
mapLabelToCommand.set(commandPick.label, commandPick);
}
}
// Sort by MRU order and fallback to name otherwise
filteredCommandPicks.sort((commandPickA, commandPickB) => {
// If a result came from tf-idf, we want to put that towards the bottom
if (commandPickA.tfIdfScore && commandPickB.tfIdfScore) {
if (commandPickA.tfIdfScore === commandPickB.tfIdfScore) {
return commandPickA.label.localeCompare(commandPickB.label); // prefer lexicographically smaller command
}
return commandPickB.tfIdfScore - commandPickA.tfIdfScore; // prefer higher tf-idf score
}
else if (commandPickA.tfIdfScore) {
return 1; // first command has a score but other doesn't so other wins
}
else if (commandPickB.tfIdfScore) {
return -1; // other command has a score but first doesn't so first wins
}
const commandACounter = this.commandsHistory.peek(commandPickA.commandId);
const commandBCounter = this.commandsHistory.peek(commandPickB.commandId);
if (commandACounter && commandBCounter) {
return commandACounter > commandBCounter ? -1 : 1; // use more recently used command before older
}
if (commandACounter) {
return -1; // first command was used, so it wins over the non used one
}
if (commandBCounter) {
return 1; // other command was used so it wins over the command
}
if (this.options.suggestedCommandIds) {
const commandASuggestion = this.options.suggestedCommandIds.has(commandPickA.commandId);
const commandBSuggestion = this.options.suggestedCommandIds.has(commandPickB.commandId);
if (commandASuggestion && commandBSuggestion) {
return 0; // honor the order of the array
}
if (commandASuggestion) {
return -1; // first command was suggested, so it wins over the non suggested one
}
if (commandBSuggestion) {
return 1; // other command was suggested so it wins over the command
}
}
// both commands were never used, so we sort by name
return commandPickA.label.localeCompare(commandPickB.label);
});
const commandPicks = [];
let addOtherSeparator = false;
let addSuggestedSeparator = true;
let addCommonlyUsedSeparator = !!this.options.suggestedCommandIds;
for (let i = 0; i < filteredCommandPicks.length; i++) {
const commandPick = filteredCommandPicks[i];
// Separator: recently used
if (i === 0 && this.commandsHistory.peek(commandPick.commandId)) {
commandPicks.push({ type: 'separator', label: localize('recentlyUsed', "recently used") });
addOtherSeparator = true;
}
if (addSuggestedSeparator && commandPick.tfIdfScore !== undefined) {
commandPicks.push({ type: 'separator', label: localize('suggested', "similar commands") });
addSuggestedSeparator = false;
}
// Separator: commonly used
if (addCommonlyUsedSeparator && commandPick.tfIdfScore === undefined && !this.commandsHistory.peek(commandPick.commandId) && this.options.suggestedCommandIds?.has(commandPick.commandId)) {
commandPicks.push({ type: 'separator', label: localize('commonlyUsed', "commonly used") });
addOtherSeparator = true;
addCommonlyUsedSeparator = false;
}
// Separator: other commands
if (addOtherSeparator && commandPick.tfIdfScore === undefined && !this.commandsHistory.peek(commandPick.commandId) && !this.options.suggestedCommandIds?.has(commandPick.commandId)) {
commandPicks.push({ type: 'separator', label: localize('morecCommands', "other commands") });
addOtherSeparator = false;
}
// Command
commandPicks.push(this.toCommandPick(commandPick, runOptions));
}
if (!this.hasAdditionalCommandPicks(filter, token)) {
return commandPicks;
}
return {
picks: commandPicks,
additionalPicks: (async () => {
const additionalCommandPicks = await this.getAdditionalCommandPicks(allCommandPicks, filteredCommandPicks, filter, token);
if (token.isCancellationRequested) {
return [];
}
const commandPicks = additionalCommandPicks.map(commandPick => this.toCommandPick(commandPick, runOptions));
// Basically, if we haven't already added a separator, we add one before the additional picks so long
// as one hasn't been added to the start of the array.
if (addSuggestedSeparator && commandPicks[0]?.type !== 'separator') {
commandPicks.unshift({ type: 'separator', label: localize('suggested', "similar commands") });
}
return commandPicks;
})()
};
}
toCommandPick(commandPick, runOptions) {
if (commandPick.type === 'separator') {
return commandPick;
}
const keybinding = this.keybindingService.lookupKeybinding(commandPick.commandId);
const ariaLabel = keybinding ?
localize('commandPickAriaLabelWithKeybinding', "{0}, {1}", commandPick.label, keybinding.getAriaLabel()) :
commandPick.label;
return {
...commandPick,
ariaLabel,
detail: this.options.showAlias && commandPick.commandAlias !== commandPick.label ? commandPick.commandAlias : undefined,
keybinding,
accept: async () => {
// Add to history
this.commandsHistory.push(commandPick.commandId);
// Telementry
this.telemetryService.publicLog2('workbenchActionExecuted', {
id: commandPick.commandId,
from: runOptions?.from ?? 'quick open'
});
// Run
try {
commandPick.args?.length
? await this.commandService.executeCommand(commandPick.commandId, ...commandPick.args)
: await this.commandService.executeCommand(commandPick.commandId);
}
catch (error) {
if (!isCancellationError(error)) {
this.dialogService.error(localize('canNotRun', "Command '{0}' resulted in an error", commandPick.label), toErrorMessage(error));
}
}
}
};
}
// TF-IDF string to be indexed
getTfIdfChunk({ label, commandAlias, commandDescription }) {
let chunk = label;
if (commandAlias && commandAlias !== label) {
chunk += ` - ${commandAlias}`;
}
if (commandDescription && commandDescription.value !== label) {
// If the original is the same as the value, don't add it
chunk += ` - ${commandDescription.value === commandDescription.original ? commandDescription.value : `${commandDescription.value} (${commandDescription.original})`}`;
}
return chunk;
}
};
AbstractCommandsQuickAccessProvider = AbstractCommandsQuickAccessProvider_1 = __decorate([
__param(1, IInstantiationService),
__param(2, IKeybindingService),
__param(3, ICommandService),
__param(4, ITelemetryService),
__param(5, IDialogService)
], AbstractCommandsQuickAccessProvider);
export { AbstractCommandsQuickAccessProvider };
let CommandsHistory = class CommandsHistory extends Disposable {
static { CommandsHistory_1 = this; }
static { this.DEFAULT_COMMANDS_HISTORY_LENGTH = 50; }
static { this.PREF_KEY_CACHE = 'commandPalette.mru.cache'; }
static { this.PREF_KEY_COUNTER = 'commandPalette.mru.counter'; }
static { this.counter = 1; }
static { this.hasChanges = false; }
constructor(storageService, configurationService, logService) {
super();
this.storageService = storageService;
this.configurationService = configurationService;
this.logService = logService;
this.configuredCommandsHistoryLength = 0;
this.updateConfiguration();
this.load();
this.registerListeners();
}
registerListeners() {
this._register(this.configurationService.onDidChangeConfiguration(e => this.updateConfiguration(e)));
this._register(this.storageService.onWillSaveState(e => {
if (e.reason === WillSaveStateReason.SHUTDOWN) {
// Commands history is very dynamic and so we limit impact
// on storage to only save on shutdown. This helps reduce
// the overhead of syncing this data across machines.
this.saveState();
}
}));
}
updateConfiguration(e) {
if (e && !e.affectsConfiguration('workbench.commandPalette.history')) {
return;
}
this.configuredCommandsHistoryLength = CommandsHistory_1.getConfiguredCommandHistoryLength(this.configurationService);
if (CommandsHistory_1.cache && CommandsHistory_1.cache.limit !== this.configuredCommandsHistoryLength) {
CommandsHistory_1.cache.limit = this.configuredCommandsHistoryLength;
CommandsHistory_1.hasChanges = true;
}
}
load() {
const raw = this.storageService.get(CommandsHistory_1.PREF_KEY_CACHE, 0 /* StorageScope.PROFILE */);
let serializedCache;
if (raw) {
try {
serializedCache = JSON.parse(raw);
}
catch (error) {
this.logService.error(`[CommandsHistory] invalid data: ${error}`);
}
}
const cache = CommandsHistory_1.cache = new LRUCache(this.configuredCommandsHistoryLength, 1);
if (serializedCache) {
let entries;
if (serializedCache.usesLRU) {
entries = serializedCache.entries;
}
else {
entries = serializedCache.entries.sort((a, b) => a.value - b.value);
}
entries.forEach(entry => cache.set(entry.key, entry.value));
}
CommandsHistory_1.counter = this.storageService.getNumber(CommandsHistory_1.PREF_KEY_COUNTER, 0 /* StorageScope.PROFILE */, CommandsHistory_1.counter);
}
push(commandId) {
if (!CommandsHistory_1.cache) {
return;
}
CommandsHistory_1.cache.set(commandId, CommandsHistory_1.counter++); // set counter to command
CommandsHistory_1.hasChanges = true;
}
peek(commandId) {
return CommandsHistory_1.cache?.peek(commandId);
}
saveState() {
if (!CommandsHistory_1.cache) {
return;
}
if (!CommandsHistory_1.hasChanges) {
return;
}
const serializedCache = { usesLRU: true, entries: [] };
CommandsHistory_1.cache.forEach((value, key) => serializedCache.entries.push({ key, value }));
this.storageService.store(CommandsHistory_1.PREF_KEY_CACHE, JSON.stringify(serializedCache), 0 /* StorageScope.PROFILE */, 0 /* StorageTarget.USER */);
this.storageService.store(CommandsHistory_1.PREF_KEY_COUNTER, CommandsHistory_1.counter, 0 /* StorageScope.PROFILE */, 0 /* StorageTarget.USER */);
CommandsHistory_1.hasChanges = false;
}
static getConfiguredCommandHistoryLength(configurationService) {
const config = configurationService.getValue();
const configuredCommandHistoryLength = config.workbench?.commandPalette?.history;
if (typeof configuredCommandHistoryLength === 'number') {
return configuredCommandHistoryLength;
}
return CommandsHistory_1.DEFAULT_COMMANDS_HISTORY_LENGTH;
}
};
CommandsHistory = CommandsHistory_1 = __decorate([
__param(0, IStorageService),
__param(1, IConfigurationService),
__param(2, ILogService)
], CommandsHistory);
export { CommandsHistory };
@@ -0,0 +1,75 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var HelpQuickAccessProvider_1;
import { localize } from '../../../nls.js';
import { Registry } from '../../registry/common/platform.js';
import { DisposableStore } from '../../../base/common/lifecycle.js';
import { IKeybindingService } from '../../keybinding/common/keybinding.js';
import { Extensions } from '../common/quickAccess.js';
import { IQuickInputService } from '../common/quickInput.js';
let HelpQuickAccessProvider = class HelpQuickAccessProvider {
static { HelpQuickAccessProvider_1 = this; }
static { this.PREFIX = '?'; }
constructor(quickInputService, keybindingService) {
this.quickInputService = quickInputService;
this.keybindingService = keybindingService;
this.registry = Registry.as(Extensions.Quickaccess);
}
provide(picker) {
const disposables = new DisposableStore();
// Open a picker with the selected value if picked
disposables.add(picker.onDidAccept(() => {
const [item] = picker.selectedItems;
if (item) {
this.quickInputService.quickAccess.show(item.prefix, { preserveValue: true });
}
}));
// Also open a picker when we detect the user typed the exact
// name of a provider (e.g. `?term` for terminals)
disposables.add(picker.onDidChangeValue(value => {
const providerDescriptor = this.registry.getQuickAccessProvider(value.substr(HelpQuickAccessProvider_1.PREFIX.length));
if (providerDescriptor && providerDescriptor.prefix && providerDescriptor.prefix !== HelpQuickAccessProvider_1.PREFIX) {
this.quickInputService.quickAccess.show(providerDescriptor.prefix, { preserveValue: true });
}
}));
// Fill in all providers
picker.items = this.getQuickAccessProviders().filter(p => p.prefix !== HelpQuickAccessProvider_1.PREFIX);
return disposables;
}
getQuickAccessProviders() {
const providers = this.registry
.getQuickAccessProviders()
.sort((providerA, providerB) => providerA.prefix.localeCompare(providerB.prefix))
.flatMap(provider => this.createPicks(provider));
return providers;
}
createPicks(provider) {
return provider.helpEntries.map(helpEntry => {
const prefix = helpEntry.prefix || provider.prefix;
const label = prefix || '\u2026' /* ... */;
return {
prefix,
label,
keybinding: helpEntry.commandId ? this.keybindingService.lookupKeybinding(helpEntry.commandId) : undefined,
ariaLabel: localize('helpPickAriaLabel', "{0}, {1}", label, helpEntry.description),
description: helpEntry.description
};
});
}
};
HelpQuickAccessProvider = HelpQuickAccessProvider_1 = __decorate([
__param(0, IQuickInputService),
__param(1, IKeybindingService)
], HelpQuickAccessProvider);
export { HelpQuickAccessProvider };
@@ -0,0 +1,358 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.quick-input-widget {
position: absolute;
width: 600px;
z-index: 2550;
left: 50%;
margin-left: -300px;
-webkit-app-region: no-drag;
border-radius: 6px;
}
.quick-input-titlebar {
display: flex;
align-items: center;
border-top-right-radius: 5px;
border-top-left-radius: 5px;
}
.quick-input-left-action-bar {
display: flex;
margin-left: 4px;
flex: 1;
}
.quick-input-inline-action-bar {
margin: 2px 0 0 5px;
}
.quick-input-title {
padding: 3px 0px;
text-align: center;
text-overflow: ellipsis;
overflow: hidden;
}
.quick-input-right-action-bar {
display: flex;
margin-right: 4px;
flex: 1;
}
.quick-input-right-action-bar > .actions-container {
justify-content: flex-end;
}
.quick-input-titlebar .monaco-action-bar .action-label.codicon {
background-position: center;
background-repeat: no-repeat;
padding: 2px;
}
.quick-input-description {
margin: 6px 6px 6px 11px;
}
.quick-input-header .quick-input-description {
margin: 4px 2px;
flex: 1;
}
.quick-input-header {
display: flex;
padding: 8px 6px 2px 6px;
}
.quick-input-widget.hidden-input .quick-input-header {
/* reduce margins and paddings when input box hidden */
padding: 0;
margin-bottom: 0;
}
.quick-input-and-message {
display: flex;
flex-direction: column;
flex-grow: 1;
min-width: 0;
position: relative;
}
.quick-input-check-all {
align-self: center;
margin: 0;
}
.quick-input-filter {
flex-grow: 1;
display: flex;
position: relative;
}
.quick-input-box {
flex-grow: 1;
}
.quick-input-widget.show-checkboxes .quick-input-box,
.quick-input-widget.show-checkboxes .quick-input-message {
margin-left: 5px;
}
.quick-input-visible-count {
position: absolute;
left: -10000px;
}
.quick-input-count {
align-self: center;
position: absolute;
right: 4px;
display: flex;
align-items: center;
}
.quick-input-count .monaco-count-badge {
vertical-align: middle;
padding: 2px 4px;
border-radius: 2px;
min-height: auto;
line-height: normal;
}
.quick-input-action {
margin-left: 6px;
}
.quick-input-action .monaco-text-button {
font-size: 11px;
padding: 0 6px;
display: flex;
height: 25px;
align-items: center;
}
.quick-input-message {
margin-top: -1px;
padding: 5px;
overflow-wrap: break-word;
}
.quick-input-message > .codicon {
margin: 0 0.2em;
vertical-align: text-bottom;
}
/* Links in descriptions & validations */
.quick-input-message a {
color: inherit;
}
.quick-input-progress.monaco-progress-container {
position: relative;
}
.quick-input-list {
line-height: 22px;
}
.quick-input-widget.hidden-input .quick-input-list {
margin-top: 4px; /* reduce margins when input box hidden */
padding-bottom: 4px;
}
.quick-input-list .monaco-list {
overflow: hidden;
max-height: calc(20 * 22px);
padding-bottom: 5px;
}
.quick-input-list .monaco-scrollable-element {
padding: 0px 5px;
}
.quick-input-list .quick-input-list-entry {
box-sizing: border-box;
overflow: hidden;
display: flex;
padding: 0 6px;
}
.quick-input-list .quick-input-list-entry.quick-input-list-separator-border {
border-top-width: 1px;
border-top-style: solid;
}
.quick-input-list .monaco-list-row {
border-radius: 3px;
}
.quick-input-list .monaco-list-row[data-index="0"] .quick-input-list-entry.quick-input-list-separator-border {
border-top-style: none;
}
.quick-input-list .quick-input-list-label {
overflow: hidden;
display: flex;
height: 100%;
flex: 1;
}
.quick-input-list .quick-input-list-checkbox {
align-self: center;
margin: 0;
}
.quick-input-list .quick-input-list-icon {
background-size: 16px;
background-position: left center;
background-repeat: no-repeat;
padding-right: 6px;
width: 16px;
height: 22px;
display: flex;
align-items: center;
justify-content: center;
}
.quick-input-list .quick-input-list-rows {
overflow: hidden;
text-overflow: ellipsis;
display: flex;
flex-direction: column;
height: 100%;
flex: 1;
margin-left: 5px;
}
.quick-input-widget.show-checkboxes .quick-input-list .quick-input-list-rows {
margin-left: 10px;
}
.quick-input-widget .quick-input-list .quick-input-list-checkbox {
display: none;
}
.quick-input-widget.show-checkboxes .quick-input-list .quick-input-list-checkbox {
display: inline;
}
.quick-input-list .quick-input-list-rows > .quick-input-list-row {
display: flex;
align-items: center;
}
.quick-input-list .quick-input-list-rows > .quick-input-list-row .monaco-icon-label,
.quick-input-list .quick-input-list-rows > .quick-input-list-row .monaco-icon-label .monaco-icon-label-container > .monaco-icon-name-container {
flex: 1; /* make sure the icon label grows within the row */
}
.quick-input-list .quick-input-list-rows > .quick-input-list-row .codicon[class*='codicon-'] {
vertical-align: text-bottom;
}
.quick-input-list .quick-input-list-rows .monaco-highlighted-label > span {
opacity: 1;
}
.quick-input-list .quick-input-list-entry .quick-input-list-entry-keybinding {
margin-right: 8px; /* separate from the separator label or scrollbar if any */
}
.quick-input-list .quick-input-list-label-meta {
opacity: 0.7;
line-height: normal;
text-overflow: ellipsis;
overflow: hidden;
}
/* preserve list-like styling instead of tree-like styling */
.quick-input-list .monaco-list .monaco-list-row .monaco-highlighted-label .highlight {
font-weight: bold;
background-color: unset;
color: var(--vscode-list-highlightForeground) !important;
}
/* preserve list-like styling instead of tree-like styling */
.quick-input-list .monaco-list .monaco-list-row.focused .monaco-highlighted-label .highlight {
color: var(--vscode-list-focusHighlightForeground) !important;
}
.quick-input-list .quick-input-list-entry .quick-input-list-separator {
margin-right: 4px; /* separate from keybindings or actions */
}
.quick-input-list .quick-input-list-entry-action-bar {
display: flex;
flex: 0;
overflow: visible;
}
.quick-input-list .quick-input-list-entry-action-bar .action-label {
/*
* By default, actions in the quick input action bar are hidden
* until hovered over them or selected.
*/
display: none;
}
.quick-input-list .quick-input-list-entry-action-bar .action-label.codicon {
margin-right: 4px;
padding: 2px;
}
.quick-input-list .quick-input-list-entry-action-bar {
margin-top: 1px;
}
.quick-input-list .quick-input-list-entry-action-bar {
margin-right: 4px; /* separate from scrollbar */
}
.quick-input-list .quick-input-list-entry .quick-input-list-entry-action-bar .action-label.always-visible,
.quick-input-list .quick-input-list-entry:hover .quick-input-list-entry-action-bar .action-label,
.quick-input-list .quick-input-list-entry.focus-inside .quick-input-list-entry-action-bar .action-label,
.quick-input-list .monaco-list-row.focused .quick-input-list-entry-action-bar .action-label,
.quick-input-list .monaco-list-row.passive-focused .quick-input-list-entry-action-bar .action-label {
display: flex;
}
/* focused items in quick pick */
.quick-input-list .monaco-list-row.focused .monaco-keybinding-key,
.quick-input-list .monaco-list-row.focused .quick-input-list-entry .quick-input-list-separator {
color: inherit
}
.quick-input-list .monaco-list-row.focused .monaco-keybinding-key {
background: none;
}
.quick-input-list .quick-input-list-separator-as-item {
padding: 4px 6px;
font-size: 12px;
}
/* Quick input separators as full-row item */
.quick-input-list .quick-input-list-separator-as-item .label-name {
font-weight: 600;
}
.quick-input-list .quick-input-list-separator-as-item .label-description {
/* Override default description opacity so we don't have a contrast ratio issue. */
opacity: 1 !important;
}
/* Hide border when the item becomes the sticky one */
.quick-input-list .monaco-tree-sticky-row .quick-input-list-entry.quick-input-list-separator-as-item.quick-input-list-separator-border {
border-top-style: none;
}
/* Give sticky row the same padding as the scrollable list */
.quick-input-list .monaco-tree-sticky-row {
padding: 0 5px;
}
/* Hide the twistie containers so that there isn't blank indent */
.quick-input-list .monaco-tl-twistie {
display: none !important;
}
@@ -0,0 +1,269 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { timeout } from '../../../base/common/async.js';
import { CancellationTokenSource } from '../../../base/common/cancellation.js';
import { Disposable, DisposableStore, MutableDisposable } from '../../../base/common/lifecycle.js';
import { isFunction } from '../../../base/common/types.js';
export var TriggerAction;
(function (TriggerAction) {
/**
* Do nothing after the button was clicked.
*/
TriggerAction[TriggerAction["NO_ACTION"] = 0] = "NO_ACTION";
/**
* Close the picker.
*/
TriggerAction[TriggerAction["CLOSE_PICKER"] = 1] = "CLOSE_PICKER";
/**
* Update the results of the picker.
*/
TriggerAction[TriggerAction["REFRESH_PICKER"] = 2] = "REFRESH_PICKER";
/**
* Remove the item from the picker.
*/
TriggerAction[TriggerAction["REMOVE_ITEM"] = 3] = "REMOVE_ITEM";
})(TriggerAction || (TriggerAction = {}));
function isPicksWithActive(obj) {
const candidate = obj;
return Array.isArray(candidate.items);
}
function isFastAndSlowPicks(obj) {
const candidate = obj;
return !!candidate.picks && candidate.additionalPicks instanceof Promise;
}
export class PickerQuickAccessProvider extends Disposable {
constructor(prefix, options) {
super();
this.prefix = prefix;
this.options = options;
}
provide(picker, token, runOptions) {
const disposables = new DisposableStore();
// Apply options if any
picker.canAcceptInBackground = !!this.options?.canAcceptInBackground;
// Disable filtering & sorting, we control the results
picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false;
// Set initial picks and update on type
let picksCts = undefined;
const picksDisposable = disposables.add(new MutableDisposable());
const updatePickerItems = async () => {
const picksDisposables = picksDisposable.value = new DisposableStore();
// Cancel any previous ask for picks and busy
picksCts?.dispose(true);
picker.busy = false;
// Create new cancellation source for this run
picksCts = new CancellationTokenSource(token);
// Collect picks and support both long running and short or combined
const picksToken = picksCts.token;
let picksFilter = picker.value.substring(this.prefix.length);
if (!this.options?.shouldSkipTrimPickFilter) {
picksFilter = picksFilter.trim();
}
const providedPicks = this._getPicks(picksFilter, picksDisposables, picksToken, runOptions);
const applyPicks = (picks, skipEmpty) => {
let items;
let activeItem = undefined;
if (isPicksWithActive(picks)) {
items = picks.items;
activeItem = picks.active;
}
else {
items = picks;
}
if (items.length === 0) {
if (skipEmpty) {
return false;
}
// We show the no results pick if we have no input to prevent completely empty pickers #172613
if ((picksFilter.length > 0 || picker.hideInput) && this.options?.noResultsPick) {
if (isFunction(this.options.noResultsPick)) {
items = [this.options.noResultsPick(picksFilter)];
}
else {
items = [this.options.noResultsPick];
}
}
}
picker.items = items;
if (activeItem) {
picker.activeItems = [activeItem];
}
return true;
};
const applyFastAndSlowPicks = async (fastAndSlowPicks) => {
let fastPicksApplied = false;
let slowPicksApplied = false;
await Promise.all([
// Fast Picks: if `mergeDelay` is configured, in order to reduce
// amount of flicker, we race against the slow picks over some delay
// and then set the fast picks.
// If the slow picks are faster, we reduce the flicker by only
// setting the items once.
(async () => {
if (typeof fastAndSlowPicks.mergeDelay === 'number') {
await timeout(fastAndSlowPicks.mergeDelay);
if (picksToken.isCancellationRequested) {
return;
}
}
if (!slowPicksApplied) {
fastPicksApplied = applyPicks(fastAndSlowPicks.picks, true /* skip over empty to reduce flicker */);
}
})(),
// Slow Picks: we await the slow picks and then set them at
// once together with the fast picks, but only if we actually
// have additional results.
(async () => {
picker.busy = true;
try {
const awaitedAdditionalPicks = await fastAndSlowPicks.additionalPicks;
if (picksToken.isCancellationRequested) {
return;
}
let picks;
let activePick = undefined;
if (isPicksWithActive(fastAndSlowPicks.picks)) {
picks = fastAndSlowPicks.picks.items;
activePick = fastAndSlowPicks.picks.active;
}
else {
picks = fastAndSlowPicks.picks;
}
let additionalPicks;
let additionalActivePick = undefined;
if (isPicksWithActive(awaitedAdditionalPicks)) {
additionalPicks = awaitedAdditionalPicks.items;
additionalActivePick = awaitedAdditionalPicks.active;
}
else {
additionalPicks = awaitedAdditionalPicks;
}
if (additionalPicks.length > 0 || !fastPicksApplied) {
// If we do not have any activePick or additionalActivePick
// we try to preserve the currently active pick from the
// fast results. This fixes an issue where the user might
// have made a pick active before the additional results
// kick in.
// See https://github.com/microsoft/vscode/issues/102480
let fallbackActivePick = undefined;
if (!activePick && !additionalActivePick) {
const fallbackActivePickCandidate = picker.activeItems[0];
if (fallbackActivePickCandidate && picks.indexOf(fallbackActivePickCandidate) !== -1) {
fallbackActivePick = fallbackActivePickCandidate;
}
}
applyPicks({
items: [...picks, ...additionalPicks],
active: activePick || additionalActivePick || fallbackActivePick
});
}
}
finally {
if (!picksToken.isCancellationRequested) {
picker.busy = false;
}
slowPicksApplied = true;
}
})()
]);
};
// No Picks
if (providedPicks === null) {
// Ignore
}
// Fast and Slow Picks
else if (isFastAndSlowPicks(providedPicks)) {
await applyFastAndSlowPicks(providedPicks);
}
// Fast Picks
else if (!(providedPicks instanceof Promise)) {
applyPicks(providedPicks);
}
// Slow Picks
else {
picker.busy = true;
try {
const awaitedPicks = await providedPicks;
if (picksToken.isCancellationRequested) {
return;
}
if (isFastAndSlowPicks(awaitedPicks)) {
await applyFastAndSlowPicks(awaitedPicks);
}
else {
applyPicks(awaitedPicks);
}
}
finally {
if (!picksToken.isCancellationRequested) {
picker.busy = false;
}
}
}
};
disposables.add(picker.onDidChangeValue(() => updatePickerItems()));
updatePickerItems();
// Accept the pick on accept and hide picker
disposables.add(picker.onDidAccept(event => {
if (runOptions?.handleAccept) {
if (!event.inBackground) {
picker.hide(); // hide picker unless we accept in background
}
runOptions.handleAccept?.(picker.activeItems[0]);
return;
}
const [item] = picker.selectedItems;
if (typeof item?.accept === 'function') {
if (!event.inBackground) {
picker.hide(); // hide picker unless we accept in background
}
item.accept(picker.keyMods, event);
}
}));
const buttonTrigger = async (button, item) => {
if (typeof item.trigger !== 'function') {
return;
}
const buttonIndex = item.buttons?.indexOf(button) ?? -1;
if (buttonIndex >= 0) {
const result = item.trigger(buttonIndex, picker.keyMods);
const action = (typeof result === 'number') ? result : await result;
if (token.isCancellationRequested) {
return;
}
switch (action) {
case TriggerAction.NO_ACTION:
break;
case TriggerAction.CLOSE_PICKER:
picker.hide();
break;
case TriggerAction.REFRESH_PICKER:
updatePickerItems();
break;
case TriggerAction.REMOVE_ITEM: {
const index = picker.items.indexOf(item);
if (index !== -1) {
const items = picker.items.slice();
const removed = items.splice(index, 1);
const activeItems = picker.activeItems.filter(activeItem => activeItem !== removed[0]);
const keepScrollPositionBefore = picker.keepScrollPosition;
picker.keepScrollPosition = true;
picker.items = items;
if (activeItems) {
picker.activeItems = activeItems;
}
picker.keepScrollPosition = keepScrollPositionBefore;
}
break;
}
}
}
};
// Trigger the pick with button index if button triggered
disposables.add(picker.onDidTriggerItemButton(({ button, item }) => buttonTrigger(button, item)));
disposables.add(picker.onDidTriggerSeparatorButton(({ button, separator }) => buttonTrigger(button, separator)));
return disposables;
}
}
@@ -0,0 +1,199 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { DeferredPromise } from '../../../base/common/async.js';
import { CancellationTokenSource } from '../../../base/common/cancellation.js';
import { Event } from '../../../base/common/event.js';
import { Disposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';
import { IInstantiationService } from '../../instantiation/common/instantiation.js';
import { DefaultQuickAccessFilterValue, Extensions } from '../common/quickAccess.js';
import { IQuickInputService, ItemActivation } from '../common/quickInput.js';
import { Registry } from '../../registry/common/platform.js';
let QuickAccessController = class QuickAccessController extends Disposable {
constructor(quickInputService, instantiationService) {
super();
this.quickInputService = quickInputService;
this.instantiationService = instantiationService;
this.registry = Registry.as(Extensions.Quickaccess);
this.mapProviderToDescriptor = new Map();
this.lastAcceptedPickerValues = new Map();
this.visibleQuickAccess = undefined;
}
show(value = '', options) {
this.doShowOrPick(value, false, options);
}
doShowOrPick(value, pick, options) {
// Find provider for the value to show
const [provider, descriptor] = this.getOrInstantiateProvider(value, options?.enabledProviderPrefixes);
// Return early if quick access is already showing on that same prefix
const visibleQuickAccess = this.visibleQuickAccess;
const visibleDescriptor = visibleQuickAccess?.descriptor;
if (visibleQuickAccess && descriptor && visibleDescriptor === descriptor) {
// Apply value only if it is more specific than the prefix
// from the provider and we are not instructed to preserve
if (value !== descriptor.prefix && !options?.preserveValue) {
visibleQuickAccess.picker.value = value;
}
// Always adjust selection
this.adjustValueSelection(visibleQuickAccess.picker, descriptor, options);
return;
}
// Rewrite the filter value based on certain rules unless disabled
if (descriptor && !options?.preserveValue) {
let newValue = undefined;
// If we have a visible provider with a value, take it's filter value but
// rewrite to new provider prefix in case they differ
if (visibleQuickAccess && visibleDescriptor && visibleDescriptor !== descriptor) {
const newValueCandidateWithoutPrefix = visibleQuickAccess.value.substr(visibleDescriptor.prefix.length);
if (newValueCandidateWithoutPrefix) {
newValue = `${descriptor.prefix}${newValueCandidateWithoutPrefix}`;
}
}
// Otherwise, take a default value as instructed
if (!newValue) {
const defaultFilterValue = provider?.defaultFilterValue;
if (defaultFilterValue === DefaultQuickAccessFilterValue.LAST) {
newValue = this.lastAcceptedPickerValues.get(descriptor);
}
else if (typeof defaultFilterValue === 'string') {
newValue = `${descriptor.prefix}${defaultFilterValue}`;
}
}
if (typeof newValue === 'string') {
value = newValue;
}
}
// Store the existing selection if there was one.
const visibleSelection = visibleQuickAccess?.picker?.valueSelection;
const visibleValue = visibleQuickAccess?.picker?.value;
// Create a picker for the provider to use with the initial value
// and adjust the filtering to exclude the prefix from filtering
const disposables = new DisposableStore();
const picker = disposables.add(this.quickInputService.createQuickPick());
picker.value = value;
this.adjustValueSelection(picker, descriptor, options);
picker.placeholder = options?.placeholder ?? descriptor?.placeholder;
picker.quickNavigate = options?.quickNavigateConfiguration;
picker.hideInput = !!picker.quickNavigate && !visibleQuickAccess; // only hide input if there was no picker opened already
if (typeof options?.itemActivation === 'number' || options?.quickNavigateConfiguration) {
picker.itemActivation = options?.itemActivation ?? ItemActivation.SECOND /* quick nav is always second */;
}
picker.contextKey = descriptor?.contextKey;
picker.filterValue = (value) => value.substring(descriptor ? descriptor.prefix.length : 0);
// Pick mode: setup a promise that can be resolved
// with the selected items and prevent execution
let pickPromise = undefined;
if (pick) {
pickPromise = new DeferredPromise();
disposables.add(Event.once(picker.onWillAccept)(e => {
e.veto();
picker.hide();
}));
}
// Register listeners
disposables.add(this.registerPickerListeners(picker, provider, descriptor, value, options));
// Ask provider to fill the picker as needed if we have one
// and pass over a cancellation token that will indicate when
// the picker is hiding without a pick being made.
const cts = disposables.add(new CancellationTokenSource());
if (provider) {
disposables.add(provider.provide(picker, cts.token, options?.providerOptions));
}
// Finally, trigger disposal and cancellation when the picker
// hides depending on items selected or not.
Event.once(picker.onDidHide)(() => {
if (picker.selectedItems.length === 0) {
cts.cancel();
}
// Start to dispose once picker hides
disposables.dispose();
// Resolve pick promise with selected items
pickPromise?.complete(picker.selectedItems.slice(0));
});
// Finally, show the picker. This is important because a provider
// may not call this and then our disposables would leak that rely
// on the onDidHide event.
picker.show();
// If the previous picker had a selection and the value is unchanged, we should set that in the new picker.
if (visibleSelection && visibleValue === value) {
picker.valueSelection = visibleSelection;
}
// Pick mode: return with promise
if (pick) {
return pickPromise?.p;
}
}
adjustValueSelection(picker, descriptor, options) {
let valueSelection;
// Preserve: just always put the cursor at the end
if (options?.preserveValue) {
valueSelection = [picker.value.length, picker.value.length];
}
// Otherwise: select the value up until the prefix
else {
valueSelection = [descriptor?.prefix.length ?? 0, picker.value.length];
}
picker.valueSelection = valueSelection;
}
registerPickerListeners(picker, provider, descriptor, value, options) {
const disposables = new DisposableStore();
// Remember as last visible picker and clean up once picker get's disposed
const visibleQuickAccess = this.visibleQuickAccess = { picker, descriptor, value };
disposables.add(toDisposable(() => {
if (visibleQuickAccess === this.visibleQuickAccess) {
this.visibleQuickAccess = undefined;
}
}));
// Whenever the value changes, check if the provider has
// changed and if so - re-create the picker from the beginning
disposables.add(picker.onDidChangeValue(value => {
const [providerForValue] = this.getOrInstantiateProvider(value, options?.enabledProviderPrefixes);
if (providerForValue !== provider) {
this.show(value, {
enabledProviderPrefixes: options?.enabledProviderPrefixes,
// do not rewrite value from user typing!
preserveValue: true,
// persist the value of the providerOptions from the original showing
providerOptions: options?.providerOptions
});
}
else {
visibleQuickAccess.value = value; // remember the value in our visible one
}
}));
// Remember picker input for future use when accepting
if (descriptor) {
disposables.add(picker.onDidAccept(() => {
this.lastAcceptedPickerValues.set(descriptor, picker.value);
}));
}
return disposables;
}
getOrInstantiateProvider(value, enabledProviderPrefixes) {
const providerDescriptor = this.registry.getQuickAccessProvider(value);
if (!providerDescriptor || enabledProviderPrefixes && !enabledProviderPrefixes?.includes(providerDescriptor.prefix)) {
return [undefined, undefined];
}
let provider = this.mapProviderToDescriptor.get(providerDescriptor);
if (!provider) {
provider = this.instantiationService.createInstance(providerDescriptor.ctor);
this.mapProviderToDescriptor.set(providerDescriptor, provider);
}
return [provider, providerDescriptor];
}
};
QuickAccessController = __decorate([
__param(0, IQuickInputService),
__param(1, IInstantiationService)
], QuickAccessController);
export { QuickAccessController };
@@ -0,0 +1,942 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import * as dom from '../../../base/browser/dom.js';
import { StandardKeyboardEvent } from '../../../base/browser/keyboardEvent.js';
import { Toggle } from '../../../base/browser/ui/toggle/toggle.js';
import { equals } from '../../../base/common/arrays.js';
import { TimeoutTimer } from '../../../base/common/async.js';
import { Codicon } from '../../../base/common/codicons.js';
import { Emitter, EventBufferer } from '../../../base/common/event.js';
import { Disposable, DisposableStore } from '../../../base/common/lifecycle.js';
import { isIOS } from '../../../base/common/platform.js';
import Severity from '../../../base/common/severity.js';
import { ThemeIcon } from '../../../base/common/themables.js';
import './media/quickInput.css';
import { localize } from '../../../nls.js';
import { ItemActivation, NO_KEY_MODS, QuickInputButtonLocation, QuickInputHideReason } from '../common/quickInput.js';
import { quickInputButtonToAction, renderQuickInputDescription } from './quickInputUtils.js';
import { IConfigurationService } from '../../configuration/common/configuration.js';
import { IHoverService, WorkbenchHoverDelegate } from '../../hover/browser/hover.js';
import { QuickPickFocus } from '../common/quickInput.js';
import { ContextKeyExpr, RawContextKey } from '../../contextkey/common/contextkey.js';
export const inQuickInputContextKeyValue = 'inQuickInput';
export const InQuickInputContextKey = new RawContextKey(inQuickInputContextKeyValue, false, localize('inQuickInput', "Whether keyboard focus is inside the quick input control"));
export const inQuickInputContext = ContextKeyExpr.has(inQuickInputContextKeyValue);
export const quickInputTypeContextKeyValue = 'quickInputType';
export const QuickInputTypeContextKey = new RawContextKey(quickInputTypeContextKeyValue, undefined, localize('quickInputType', "The type of the currently visible quick input"));
export const endOfQuickInputBoxContextKeyValue = 'cursorAtEndOfQuickInputBox';
export const EndOfQuickInputBoxContextKey = new RawContextKey(endOfQuickInputBoxContextKeyValue, false, localize('cursorAtEndOfQuickInputBox', "Whether the cursor in the quick input is at the end of the input box"));
export const endOfQuickInputBoxContext = ContextKeyExpr.has(endOfQuickInputBoxContextKeyValue);
export const backButton = {
iconClass: ThemeIcon.asClassName(Codicon.quickInputBack),
tooltip: localize('quickInput.back', "Back"),
handle: -1 // TODO
};
class QuickInput extends Disposable {
static { this.noPromptMessage = localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel"); }
constructor(ui) {
super();
this.ui = ui;
this._widgetUpdated = false;
this.visible = false;
this._enabled = true;
this._busy = false;
this._ignoreFocusOut = false;
this._leftButtons = [];
this._rightButtons = [];
this._inlineButtons = [];
this.buttonsUpdated = false;
this._toggles = [];
this.togglesUpdated = false;
this.noValidationMessage = QuickInput.noPromptMessage;
this._severity = Severity.Ignore;
this.onDidTriggerButtonEmitter = this._register(new Emitter());
this.onDidHideEmitter = this._register(new Emitter());
this.onWillHideEmitter = this._register(new Emitter());
this.onDisposeEmitter = this._register(new Emitter());
this.visibleDisposables = this._register(new DisposableStore());
this.onDidHide = this.onDidHideEmitter.event;
}
get title() {
return this._title;
}
set title(title) {
this._title = title;
this.update();
}
get description() {
return this._description;
}
set description(description) {
this._description = description;
this.update();
}
get step() {
return this._steps;
}
set step(step) {
this._steps = step;
this.update();
}
get totalSteps() {
return this._totalSteps;
}
set totalSteps(totalSteps) {
this._totalSteps = totalSteps;
this.update();
}
get enabled() {
return this._enabled;
}
set enabled(enabled) {
this._enabled = enabled;
this.update();
}
get contextKey() {
return this._contextKey;
}
set contextKey(contextKey) {
this._contextKey = contextKey;
this.update();
}
get busy() {
return this._busy;
}
set busy(busy) {
this._busy = busy;
this.update();
}
get ignoreFocusOut() {
return this._ignoreFocusOut;
}
set ignoreFocusOut(ignoreFocusOut) {
const shouldUpdate = this._ignoreFocusOut !== ignoreFocusOut && !isIOS;
this._ignoreFocusOut = ignoreFocusOut && !isIOS;
if (shouldUpdate) {
this.update();
}
}
get titleButtons() {
return this._leftButtons.length
? [...this._leftButtons, this._rightButtons]
: this._rightButtons;
}
get buttons() {
return [
...this._leftButtons,
...this._rightButtons,
...this._inlineButtons
];
}
set buttons(buttons) {
this._leftButtons = buttons.filter(b => b === backButton);
this._rightButtons = buttons.filter(b => b !== backButton && b.location !== QuickInputButtonLocation.Inline);
this._inlineButtons = buttons.filter(b => b.location === QuickInputButtonLocation.Inline);
this.buttonsUpdated = true;
this.update();
}
get toggles() {
return this._toggles;
}
set toggles(toggles) {
this._toggles = toggles ?? [];
this.togglesUpdated = true;
this.update();
}
get validationMessage() {
return this._validationMessage;
}
set validationMessage(validationMessage) {
this._validationMessage = validationMessage;
this.update();
}
get severity() {
return this._severity;
}
set severity(severity) {
this._severity = severity;
this.update();
}
show() {
if (this.visible) {
return;
}
this.visibleDisposables.add(this.ui.onDidTriggerButton(button => {
if (this.buttons.indexOf(button) !== -1) {
this.onDidTriggerButtonEmitter.fire(button);
}
}));
this.ui.show(this);
// update properties in the controller that get reset in the ui.show() call
this.visible = true;
// This ensures the message/prompt gets rendered
this._lastValidationMessage = undefined;
// This ensures the input box has the right severity applied
this._lastSeverity = undefined;
if (this.buttons.length) {
// if there are buttons, the ui.show() clears them out of the UI so we should
// rerender them.
this.buttonsUpdated = true;
}
if (this.toggles.length) {
// if there are toggles, the ui.show() clears them out of the UI so we should
// rerender them.
this.togglesUpdated = true;
}
this.update();
}
hide() {
if (!this.visible) {
return;
}
this.ui.hide();
}
didHide(reason = QuickInputHideReason.Other) {
this.visible = false;
this.visibleDisposables.clear();
this.onDidHideEmitter.fire({ reason });
}
willHide(reason = QuickInputHideReason.Other) {
this.onWillHideEmitter.fire({ reason });
}
update() {
if (!this.visible) {
return;
}
const title = this.getTitle();
if (title && this.ui.title.textContent !== title) {
this.ui.title.textContent = title;
}
else if (!title && this.ui.title.innerHTML !== '&nbsp;') {
this.ui.title.innerText = '\u00a0';
}
const description = this.getDescription();
if (this.ui.description1.textContent !== description) {
this.ui.description1.textContent = description;
}
if (this.ui.description2.textContent !== description) {
this.ui.description2.textContent = description;
}
if (this._widgetUpdated) {
this._widgetUpdated = false;
if (this._widget) {
dom.reset(this.ui.widget, this._widget);
}
else {
dom.reset(this.ui.widget);
}
}
if (this.busy && !this.busyDelay) {
this.busyDelay = new TimeoutTimer();
this.busyDelay.setIfNotSet(() => {
if (this.visible) {
this.ui.progressBar.infinite();
}
}, 800);
}
if (!this.busy && this.busyDelay) {
this.ui.progressBar.stop();
this.busyDelay.cancel();
this.busyDelay = undefined;
}
if (this.buttonsUpdated) {
this.buttonsUpdated = false;
this.ui.leftActionBar.clear();
const leftButtons = this._leftButtons
.map((button, index) => quickInputButtonToAction(button, `id-${index}`, async () => this.onDidTriggerButtonEmitter.fire(button)));
this.ui.leftActionBar.push(leftButtons, { icon: true, label: false });
this.ui.rightActionBar.clear();
const rightButtons = this._rightButtons
.map((button, index) => quickInputButtonToAction(button, `id-${index}`, async () => this.onDidTriggerButtonEmitter.fire(button)));
this.ui.rightActionBar.push(rightButtons, { icon: true, label: false });
this.ui.inlineActionBar.clear();
const inlineButtons = this._inlineButtons
.map((button, index) => quickInputButtonToAction(button, `id-${index}`, async () => this.onDidTriggerButtonEmitter.fire(button)));
this.ui.inlineActionBar.push(inlineButtons, { icon: true, label: false });
}
if (this.togglesUpdated) {
this.togglesUpdated = false;
// HACK: Filter out toggles here that are not concrete Toggle objects. This is to workaround
// a layering issue as quick input's interface is in common but Toggle is in browser and
// it requires a HTMLElement on its interface
const concreteToggles = this.toggles?.filter(opts => opts instanceof Toggle) ?? [];
this.ui.inputBox.toggles = concreteToggles;
}
this.ui.ignoreFocusOut = this.ignoreFocusOut;
this.ui.setEnabled(this.enabled);
this.ui.setContextKey(this.contextKey);
const validationMessage = this.validationMessage || this.noValidationMessage;
if (this._lastValidationMessage !== validationMessage) {
this._lastValidationMessage = validationMessage;
dom.reset(this.ui.message);
renderQuickInputDescription(validationMessage, this.ui.message, {
callback: (content) => {
this.ui.linkOpenerDelegate(content);
},
disposables: this.visibleDisposables,
});
}
if (this._lastSeverity !== this.severity) {
this._lastSeverity = this.severity;
this.showMessageDecoration(this.severity);
}
}
getTitle() {
if (this.title && this.step) {
return `${this.title} (${this.getSteps()})`;
}
if (this.title) {
return this.title;
}
if (this.step) {
return this.getSteps();
}
return '';
}
getDescription() {
return this.description || '';
}
getSteps() {
if (this.step && this.totalSteps) {
return localize('quickInput.steps', "{0}/{1}", this.step, this.totalSteps);
}
if (this.step) {
return String(this.step);
}
return '';
}
showMessageDecoration(severity) {
this.ui.inputBox.showDecoration(severity);
if (severity !== Severity.Ignore) {
const styles = this.ui.inputBox.stylesForType(severity);
this.ui.message.style.color = styles.foreground ? `${styles.foreground}` : '';
this.ui.message.style.backgroundColor = styles.background ? `${styles.background}` : '';
this.ui.message.style.border = styles.border ? `1px solid ${styles.border}` : '';
this.ui.message.style.marginBottom = '-2px';
}
else {
this.ui.message.style.color = '';
this.ui.message.style.backgroundColor = '';
this.ui.message.style.border = '';
this.ui.message.style.marginBottom = '';
}
}
dispose() {
this.hide();
this.onDisposeEmitter.fire();
super.dispose();
}
}
export class QuickPick extends QuickInput {
constructor() {
super(...arguments);
this._value = '';
this.onDidChangeValueEmitter = this._register(new Emitter());
this.onWillAcceptEmitter = this._register(new Emitter());
this.onDidAcceptEmitter = this._register(new Emitter());
this.onDidCustomEmitter = this._register(new Emitter());
this._items = [];
this.itemsUpdated = false;
this._canSelectMany = false;
this._canAcceptInBackground = false;
this._matchOnDescription = false;
this._matchOnDetail = false;
this._matchOnLabel = true;
this._matchOnLabelMode = 'fuzzy';
this._sortByLabel = true;
this._keepScrollPosition = false;
this._itemActivation = ItemActivation.FIRST;
this._activeItems = [];
this.activeItemsUpdated = false;
this.activeItemsToConfirm = [];
this.onDidChangeActiveEmitter = this._register(new Emitter());
this._selectedItems = [];
this.selectedItemsUpdated = false;
this.selectedItemsToConfirm = [];
this.onDidChangeSelectionEmitter = this._register(new Emitter());
this.onDidTriggerItemButtonEmitter = this._register(new Emitter());
this.onDidTriggerSeparatorButtonEmitter = this._register(new Emitter());
this.valueSelectionUpdated = true;
this._ok = 'default';
this._customButton = false;
this._focusEventBufferer = new EventBufferer();
this.type = "quickPick" /* QuickInputType.QuickPick */;
this.filterValue = (value) => value;
this.onDidChangeValue = this.onDidChangeValueEmitter.event;
this.onWillAccept = this.onWillAcceptEmitter.event;
this.onDidAccept = this.onDidAcceptEmitter.event;
this.onDidChangeActive = this.onDidChangeActiveEmitter.event;
this.onDidChangeSelection = this.onDidChangeSelectionEmitter.event;
this.onDidTriggerItemButton = this.onDidTriggerItemButtonEmitter.event;
this.onDidTriggerSeparatorButton = this.onDidTriggerSeparatorButtonEmitter.event;
}
static { this.DEFAULT_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results."); }
get quickNavigate() {
return this._quickNavigate;
}
set quickNavigate(quickNavigate) {
this._quickNavigate = quickNavigate;
this.update();
}
get value() {
return this._value;
}
set value(value) {
this.doSetValue(value);
}
doSetValue(value, skipUpdate) {
if (this._value !== value) {
this._value = value;
if (!skipUpdate) {
this.update();
}
if (this.visible) {
const didFilter = this.ui.list.filter(this.filterValue(this._value));
if (didFilter) {
this.trySelectFirst();
}
}
this.onDidChangeValueEmitter.fire(this._value);
}
}
set ariaLabel(ariaLabel) {
this._ariaLabel = ariaLabel;
this.update();
}
get ariaLabel() {
return this._ariaLabel;
}
get placeholder() {
return this._placeholder;
}
set placeholder(placeholder) {
this._placeholder = placeholder;
this.update();
}
get items() {
return this._items;
}
get scrollTop() {
return this.ui.list.scrollTop;
}
set scrollTop(scrollTop) {
this.ui.list.scrollTop = scrollTop;
}
set items(items) {
this._items = items;
this.itemsUpdated = true;
this.update();
}
get canSelectMany() {
return this._canSelectMany;
}
set canSelectMany(canSelectMany) {
this._canSelectMany = canSelectMany;
this.update();
}
get canAcceptInBackground() {
return this._canAcceptInBackground;
}
set canAcceptInBackground(canAcceptInBackground) {
this._canAcceptInBackground = canAcceptInBackground;
}
get matchOnDescription() {
return this._matchOnDescription;
}
set matchOnDescription(matchOnDescription) {
this._matchOnDescription = matchOnDescription;
this.update();
}
get matchOnDetail() {
return this._matchOnDetail;
}
set matchOnDetail(matchOnDetail) {
this._matchOnDetail = matchOnDetail;
this.update();
}
get matchOnLabel() {
return this._matchOnLabel;
}
set matchOnLabel(matchOnLabel) {
this._matchOnLabel = matchOnLabel;
this.update();
}
get matchOnLabelMode() {
return this._matchOnLabelMode;
}
set matchOnLabelMode(matchOnLabelMode) {
this._matchOnLabelMode = matchOnLabelMode;
this.update();
}
get sortByLabel() {
return this._sortByLabel;
}
set sortByLabel(sortByLabel) {
this._sortByLabel = sortByLabel;
this.update();
}
get keepScrollPosition() {
return this._keepScrollPosition;
}
set keepScrollPosition(keepScrollPosition) {
this._keepScrollPosition = keepScrollPosition;
}
get itemActivation() {
return this._itemActivation;
}
set itemActivation(itemActivation) {
this._itemActivation = itemActivation;
}
get activeItems() {
return this._activeItems;
}
set activeItems(activeItems) {
this._activeItems = activeItems;
this.activeItemsUpdated = true;
this.update();
}
get selectedItems() {
return this._selectedItems;
}
set selectedItems(selectedItems) {
this._selectedItems = selectedItems;
this.selectedItemsUpdated = true;
this.update();
}
get keyMods() {
if (this._quickNavigate) {
// Disable keyMods when quick navigate is enabled
// because in this model the interaction is purely
// keyboard driven and Ctrl/Alt are typically
// pressed and hold during this interaction.
return NO_KEY_MODS;
}
return this.ui.keyMods;
}
get valueSelection() {
const selection = this.ui.inputBox.getSelection();
if (!selection) {
return undefined;
}
return [selection.start, selection.end];
}
set valueSelection(valueSelection) {
this._valueSelection = valueSelection;
this.valueSelectionUpdated = true;
this.update();
}
get customButton() {
return this._customButton;
}
set customButton(showCustomButton) {
this._customButton = showCustomButton;
this.update();
}
get customLabel() {
return this._customButtonLabel;
}
set customLabel(label) {
this._customButtonLabel = label;
this.update();
}
get customHover() {
return this._customButtonHover;
}
set customHover(hover) {
this._customButtonHover = hover;
this.update();
}
get ok() {
return this._ok;
}
set ok(showOkButton) {
this._ok = showOkButton;
this.update();
}
get hideInput() {
return !!this._hideInput;
}
set hideInput(hideInput) {
this._hideInput = hideInput;
this.update();
}
trySelectFirst() {
if (!this.canSelectMany) {
this.ui.list.focus(QuickPickFocus.First);
}
}
show() {
if (!this.visible) {
this.visibleDisposables.add(this.ui.inputBox.onDidChange(value => {
this.doSetValue(value, true /* skip update since this originates from the UI */);
}));
this.visibleDisposables.add(this.ui.onDidAccept(() => {
if (this.canSelectMany) {
// if there are no checked elements, it means that an onDidChangeSelection never fired to overwrite
// `_selectedItems`. In that case, we should emit one with an empty array to ensure that
// `.selectedItems` is up to date.
if (!this.ui.list.getCheckedElements().length) {
this._selectedItems = [];
this.onDidChangeSelectionEmitter.fire(this.selectedItems);
}
}
else if (this.activeItems[0]) {
// For single-select, we set `selectedItems` to the item that was accepted.
this._selectedItems = [this.activeItems[0]];
this.onDidChangeSelectionEmitter.fire(this.selectedItems);
}
this.handleAccept(false);
}));
this.visibleDisposables.add(this.ui.onDidCustom(() => {
this.onDidCustomEmitter.fire();
}));
this.visibleDisposables.add(this._focusEventBufferer.wrapEvent(this.ui.list.onDidChangeFocus,
// Only fire the last event
(_, e) => e)(focusedItems => {
if (this.activeItemsUpdated) {
return; // Expect another event.
}
if (this.activeItemsToConfirm !== this._activeItems && equals(focusedItems, this._activeItems, (a, b) => a === b)) {
return;
}
this._activeItems = focusedItems;
this.onDidChangeActiveEmitter.fire(focusedItems);
}));
this.visibleDisposables.add(this.ui.list.onDidChangeSelection(({ items: selectedItems, event }) => {
if (this.canSelectMany) {
if (selectedItems.length) {
this.ui.list.setSelectedElements([]);
}
return;
}
if (this.selectedItemsToConfirm !== this._selectedItems && equals(selectedItems, this._selectedItems, (a, b) => a === b)) {
return;
}
this._selectedItems = selectedItems;
this.onDidChangeSelectionEmitter.fire(selectedItems);
if (selectedItems.length) {
this.handleAccept(dom.isMouseEvent(event) && event.button === 1 /* mouse middle click */);
}
}));
this.visibleDisposables.add(this.ui.list.onChangedCheckedElements(checkedItems => {
if (!this.canSelectMany || !this.visible) {
return;
}
if (this.selectedItemsToConfirm !== this._selectedItems && equals(checkedItems, this._selectedItems, (a, b) => a === b)) {
return;
}
this._selectedItems = checkedItems;
this.onDidChangeSelectionEmitter.fire(checkedItems);
}));
this.visibleDisposables.add(this.ui.list.onButtonTriggered(event => this.onDidTriggerItemButtonEmitter.fire(event)));
this.visibleDisposables.add(this.ui.list.onSeparatorButtonTriggered(event => this.onDidTriggerSeparatorButtonEmitter.fire(event)));
this.visibleDisposables.add(this.registerQuickNavigation());
this.valueSelectionUpdated = true;
}
super.show(); // TODO: Why have show() bubble up while update() trickles down?
}
handleAccept(inBackground) {
// Figure out veto via `onWillAccept` event
let veto = false;
this.onWillAcceptEmitter.fire({ veto: () => veto = true });
// Continue with `onDidAccept` if no veto
if (!veto) {
this.onDidAcceptEmitter.fire({ inBackground });
}
}
registerQuickNavigation() {
return dom.addDisposableListener(this.ui.container, dom.EventType.KEY_UP, e => {
if (this.canSelectMany || !this._quickNavigate) {
return;
}
const keyboardEvent = new StandardKeyboardEvent(e);
const keyCode = keyboardEvent.keyCode;
// Select element when keys are pressed that signal it
const quickNavKeys = this._quickNavigate.keybindings;
const wasTriggerKeyPressed = quickNavKeys.some(k => {
const chords = k.getChords();
if (chords.length > 1) {
return false;
}
if (chords[0].shiftKey && keyCode === 4 /* KeyCode.Shift */) {
if (keyboardEvent.ctrlKey || keyboardEvent.altKey || keyboardEvent.metaKey) {
return false; // this is an optimistic check for the shift key being used to navigate back in quick input
}
return true;
}
if (chords[0].altKey && keyCode === 6 /* KeyCode.Alt */) {
return true;
}
if (chords[0].ctrlKey && keyCode === 5 /* KeyCode.Ctrl */) {
return true;
}
if (chords[0].metaKey && keyCode === 57 /* KeyCode.Meta */) {
return true;
}
return false;
});
if (wasTriggerKeyPressed) {
if (this.activeItems[0]) {
this._selectedItems = [this.activeItems[0]];
this.onDidChangeSelectionEmitter.fire(this.selectedItems);
this.handleAccept(false);
}
// Unset quick navigate after press. It is only valid once
// and should not result in any behaviour change afterwards
// if the picker remains open because there was no active item
this._quickNavigate = undefined;
}
});
}
update() {
if (!this.visible) {
return;
}
// store the scrollTop before it is reset
const scrollTopBefore = this.keepScrollPosition ? this.scrollTop : 0;
const hasDescription = !!this.description;
const visibilities = {
title: !!this.title || !!this.step || !!this.titleButtons.length,
description: hasDescription,
checkAll: this.canSelectMany && !this._hideCheckAll,
checkBox: this.canSelectMany,
inputBox: !this._hideInput,
progressBar: !this._hideInput || hasDescription,
visibleCount: true,
count: this.canSelectMany && !this._hideCountBadge,
ok: this.ok === 'default' ? this.canSelectMany : this.ok,
list: true,
message: !!this.validationMessage,
customButton: this.customButton
};
this.ui.setVisibilities(visibilities);
super.update();
if (this.ui.inputBox.value !== this.value) {
this.ui.inputBox.value = this.value;
}
if (this.valueSelectionUpdated) {
this.valueSelectionUpdated = false;
this.ui.inputBox.select(this._valueSelection && { start: this._valueSelection[0], end: this._valueSelection[1] });
}
if (this.ui.inputBox.placeholder !== (this.placeholder || '')) {
this.ui.inputBox.placeholder = (this.placeholder || '');
}
let ariaLabel = this.ariaLabel;
// Only set aria label to the input box placeholder if we actually have an input box.
if (!ariaLabel && visibilities.inputBox) {
ariaLabel = this.placeholder || QuickPick.DEFAULT_ARIA_LABEL;
// If we have a title, include it in the aria label.
if (this.title) {
ariaLabel += ` - ${this.title}`;
}
}
if (this.ui.list.ariaLabel !== ariaLabel) {
this.ui.list.ariaLabel = ariaLabel ?? null;
}
this.ui.list.matchOnDescription = this.matchOnDescription;
this.ui.list.matchOnDetail = this.matchOnDetail;
this.ui.list.matchOnLabel = this.matchOnLabel;
this.ui.list.matchOnLabelMode = this.matchOnLabelMode;
this.ui.list.sortByLabel = this.sortByLabel;
if (this.itemsUpdated) {
this.itemsUpdated = false;
this._focusEventBufferer.bufferEvents(() => {
this.ui.list.setElements(this.items);
// We want focus to exist in the list if there are items so that space can be used to toggle
this.ui.list.shouldLoop = !this.canSelectMany;
this.ui.list.filter(this.filterValue(this.ui.inputBox.value));
switch (this._itemActivation) {
case ItemActivation.NONE:
this._itemActivation = ItemActivation.FIRST; // only valid once, then unset
break;
case ItemActivation.SECOND:
this.ui.list.focus(QuickPickFocus.Second);
this._itemActivation = ItemActivation.FIRST; // only valid once, then unset
break;
case ItemActivation.LAST:
this.ui.list.focus(QuickPickFocus.Last);
this._itemActivation = ItemActivation.FIRST; // only valid once, then unset
break;
default:
this.trySelectFirst();
break;
}
});
}
if (this.ui.container.classList.contains('show-checkboxes') !== !!this.canSelectMany) {
if (this.canSelectMany) {
this.ui.list.clearFocus();
}
else {
this.trySelectFirst();
}
}
if (this.activeItemsUpdated) {
this.activeItemsUpdated = false;
this.activeItemsToConfirm = this._activeItems;
this.ui.list.setFocusedElements(this.activeItems);
if (this.activeItemsToConfirm === this._activeItems) {
this.activeItemsToConfirm = null;
}
}
if (this.selectedItemsUpdated) {
this.selectedItemsUpdated = false;
this.selectedItemsToConfirm = this._selectedItems;
if (this.canSelectMany) {
this.ui.list.setCheckedElements(this.selectedItems);
}
else {
this.ui.list.setSelectedElements(this.selectedItems);
}
if (this.selectedItemsToConfirm === this._selectedItems) {
this.selectedItemsToConfirm = null;
}
}
this.ui.customButton.label = this.customLabel || '';
this.ui.customButton.element.title = this.customHover || '';
if (!visibilities.inputBox) {
// we need to move focus into the tree to detect keybindings
// properly when the input box is not visible (quick nav)
this.ui.list.domFocus();
// Focus the first element in the list if multiselect is enabled
if (this.canSelectMany) {
this.ui.list.focus(QuickPickFocus.First);
}
}
// Set the scroll position to what it was before updating the items
if (this.keepScrollPosition) {
this.scrollTop = scrollTopBefore;
}
}
focus(focus) {
this.ui.list.focus(focus);
// To allow things like space to check/uncheck items
if (this.canSelectMany) {
this.ui.list.domFocus();
}
}
accept(inBackground) {
if (inBackground && !this._canAcceptInBackground) {
return; // needs to be enabled
}
if (this.activeItems[0]) {
this._selectedItems = [this.activeItems[0]];
this.onDidChangeSelectionEmitter.fire(this.selectedItems);
this.handleAccept(inBackground ?? false);
}
}
}
export class InputBox extends QuickInput {
constructor() {
super(...arguments);
this._value = '';
this.valueSelectionUpdated = true;
this._password = false;
this.onDidValueChangeEmitter = this._register(new Emitter());
this.onDidAcceptEmitter = this._register(new Emitter());
this.type = "inputBox" /* QuickInputType.InputBox */;
this.onDidChangeValue = this.onDidValueChangeEmitter.event;
this.onDidAccept = this.onDidAcceptEmitter.event;
}
get value() {
return this._value;
}
set value(value) {
this._value = value || '';
this.update();
}
get placeholder() {
return this._placeholder;
}
set placeholder(placeholder) {
this._placeholder = placeholder;
this.update();
}
get password() {
return this._password;
}
set password(password) {
this._password = password;
this.update();
}
show() {
if (!this.visible) {
this.visibleDisposables.add(this.ui.inputBox.onDidChange(value => {
if (value === this.value) {
return;
}
this._value = value;
this.onDidValueChangeEmitter.fire(value);
}));
this.visibleDisposables.add(this.ui.onDidAccept(() => this.onDidAcceptEmitter.fire()));
this.valueSelectionUpdated = true;
}
super.show();
}
update() {
if (!this.visible) {
return;
}
this.ui.container.classList.remove('hidden-input');
const visibilities = {
title: !!this.title || !!this.step || !!this.titleButtons.length,
description: !!this.description || !!this.step,
inputBox: true,
message: true,
progressBar: true
};
this.ui.setVisibilities(visibilities);
super.update();
if (this.ui.inputBox.value !== this.value) {
this.ui.inputBox.value = this.value;
}
if (this.valueSelectionUpdated) {
this.valueSelectionUpdated = false;
this.ui.inputBox.select(this._valueSelection && { start: this._valueSelection[0], end: this._valueSelection[1] });
}
if (this.ui.inputBox.placeholder !== (this.placeholder || '')) {
this.ui.inputBox.placeholder = (this.placeholder || '');
}
if (this.ui.inputBox.password !== this.password) {
this.ui.inputBox.password = this.password;
}
}
}
let QuickInputHoverDelegate = class QuickInputHoverDelegate extends WorkbenchHoverDelegate {
constructor(configurationService, hoverService) {
super('element', false, (options) => this.getOverrideOptions(options), configurationService, hoverService);
}
getOverrideOptions(options) {
// Only show the hover hint if the content is of a decent size
const showHoverHint = (dom.isHTMLElement(options.content)
? options.content.textContent ?? ''
: typeof options.content === 'string'
? options.content
: options.content.value).includes('\n');
return {
persistence: {
hideOnKeyDown: false,
},
appearance: {
showHoverHint,
skipFadeInAnimation: true,
},
};
}
};
QuickInputHoverDelegate = __decorate([
__param(0, IConfigurationService),
__param(1, IHoverService)
], QuickInputHoverDelegate);
export { QuickInputHoverDelegate };
@@ -0,0 +1,144 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isMacintosh } from '../../../base/common/platform.js';
import { localize } from '../../../nls.js';
import { ContextKeyExpr } from '../../contextkey/common/contextkey.js';
import { InputFocusedContext } from '../../contextkey/common/contextkeys.js';
import { KeybindingsRegistry } from '../../keybinding/common/keybindingsRegistry.js';
import { endOfQuickInputBoxContext, inQuickInputContext, quickInputTypeContextKeyValue } from './quickInput.js';
import { IQuickInputService, QuickPickFocus } from '../common/quickInput.js';
const defaultCommandAndKeybindingRule = {
weight: 200 /* KeybindingWeight.WorkbenchContrib */,
when: ContextKeyExpr.and(ContextKeyExpr.equals(quickInputTypeContextKeyValue, "quickPick" /* QuickInputType.QuickPick */), inQuickInputContext),
metadata: { description: localize('quickPick', "Used while in the context of the quick pick. If you change one keybinding for this command, you should change all of the other keybindings (modifier variants) of this command as well.") }
};
function registerQuickPickCommandAndKeybindingRule(rule, options = {}) {
KeybindingsRegistry.registerCommandAndKeybindingRule({
...defaultCommandAndKeybindingRule,
...rule,
secondary: getSecondary(rule.primary, rule.secondary ?? [], options)
});
}
const ctrlKeyMod = isMacintosh ? 256 /* KeyMod.WinCtrl */ : 2048 /* KeyMod.CtrlCmd */;
// This function will generate all the combinations of keybindings for the given primary keybinding
function getSecondary(primary, secondary, options = {}) {
if (options.withAltMod) {
secondary.push(512 /* KeyMod.Alt */ + primary);
}
if (options.withCtrlMod) {
secondary.push(ctrlKeyMod + primary);
if (options.withAltMod) {
secondary.push(512 /* KeyMod.Alt */ + ctrlKeyMod + primary);
}
}
if (options.withCmdMod && isMacintosh) {
secondary.push(2048 /* KeyMod.CtrlCmd */ + primary);
if (options.withCtrlMod) {
secondary.push(2048 /* KeyMod.CtrlCmd */ + 256 /* KeyMod.WinCtrl */ + primary);
}
if (options.withAltMod) {
secondary.push(2048 /* KeyMod.CtrlCmd */ + 512 /* KeyMod.Alt */ + primary);
if (options.withCtrlMod) {
secondary.push(2048 /* KeyMod.CtrlCmd */ + 512 /* KeyMod.Alt */ + 256 /* KeyMod.WinCtrl */ + primary);
}
}
}
return secondary;
}
//#region Navigation
function focusHandler(focus, focusOnQuickNatigate) {
return accessor => {
// Assuming this is a quick pick due to above when clause
const currentQuickPick = accessor.get(IQuickInputService).currentQuickInput;
if (!currentQuickPick) {
return;
}
if (focusOnQuickNatigate && currentQuickPick.quickNavigate) {
return currentQuickPick.focus(focusOnQuickNatigate);
}
return currentQuickPick.focus(focus);
};
}
registerQuickPickCommandAndKeybindingRule({ id: 'quickInput.pageNext', primary: 12 /* KeyCode.PageDown */, handler: focusHandler(QuickPickFocus.NextPage) }, { withAltMod: true, withCtrlMod: true, withCmdMod: true });
registerQuickPickCommandAndKeybindingRule({ id: 'quickInput.pagePrevious', primary: 11 /* KeyCode.PageUp */, handler: focusHandler(QuickPickFocus.PreviousPage) }, { withAltMod: true, withCtrlMod: true, withCmdMod: true });
registerQuickPickCommandAndKeybindingRule({ id: 'quickInput.first', primary: ctrlKeyMod + 14 /* KeyCode.Home */, handler: focusHandler(QuickPickFocus.First) }, { withAltMod: true, withCmdMod: true });
registerQuickPickCommandAndKeybindingRule({ id: 'quickInput.last', primary: ctrlKeyMod + 13 /* KeyCode.End */, handler: focusHandler(QuickPickFocus.Last) }, { withAltMod: true, withCmdMod: true });
registerQuickPickCommandAndKeybindingRule({ id: 'quickInput.next', primary: 18 /* KeyCode.DownArrow */, handler: focusHandler(QuickPickFocus.Next) }, { withCtrlMod: true });
registerQuickPickCommandAndKeybindingRule({ id: 'quickInput.previous', primary: 16 /* KeyCode.UpArrow */, handler: focusHandler(QuickPickFocus.Previous) }, { withCtrlMod: true });
// The next & previous separator commands are interesting because if we are in quick access mode, we are already holding a modifier key down.
// In this case, we want that modifier key+up/down to navigate to the next/previous item, not the next/previous separator.
// To handle this, we have a separate command for navigating to the next/previous separator when we are not in quick access mode.
// If, however, we are in quick access mode, and you hold down an additional modifier key, we will navigate to the next/previous separator.
const nextSeparatorFallbackDesc = localize('quickInput.nextSeparatorWithQuickAccessFallback', "If we're in quick access mode, this will navigate to the next item. If we are not in quick access mode, this will navigate to the next separator.");
const prevSeparatorFallbackDesc = localize('quickInput.previousSeparatorWithQuickAccessFallback', "If we're in quick access mode, this will navigate to the previous item. If we are not in quick access mode, this will navigate to the previous separator.");
if (isMacintosh) {
registerQuickPickCommandAndKeybindingRule({
id: 'quickInput.nextSeparatorWithQuickAccessFallback',
primary: 2048 /* KeyMod.CtrlCmd */ + 18 /* KeyCode.DownArrow */,
handler: focusHandler(QuickPickFocus.NextSeparator, QuickPickFocus.Next),
metadata: { description: nextSeparatorFallbackDesc }
});
registerQuickPickCommandAndKeybindingRule({
id: 'quickInput.nextSeparator',
primary: 2048 /* KeyMod.CtrlCmd */ + 512 /* KeyMod.Alt */ + 18 /* KeyCode.DownArrow */,
// Since macOS has the cmd key as the primary modifier, we need to add this additional
// keybinding to capture cmd+ctrl+upArrow
secondary: [2048 /* KeyMod.CtrlCmd */ + 256 /* KeyMod.WinCtrl */ + 18 /* KeyCode.DownArrow */],
handler: focusHandler(QuickPickFocus.NextSeparator)
}, { withCtrlMod: true });
registerQuickPickCommandAndKeybindingRule({
id: 'quickInput.previousSeparatorWithQuickAccessFallback',
primary: 2048 /* KeyMod.CtrlCmd */ + 16 /* KeyCode.UpArrow */,
handler: focusHandler(QuickPickFocus.PreviousSeparator, QuickPickFocus.Previous),
metadata: { description: prevSeparatorFallbackDesc }
});
registerQuickPickCommandAndKeybindingRule({
id: 'quickInput.previousSeparator',
primary: 2048 /* KeyMod.CtrlCmd */ + 512 /* KeyMod.Alt */ + 16 /* KeyCode.UpArrow */,
// Since macOS has the cmd key as the primary modifier, we need to add this additional
// keybinding to capture cmd+ctrl+upArrow
secondary: [2048 /* KeyMod.CtrlCmd */ + 256 /* KeyMod.WinCtrl */ + 16 /* KeyCode.UpArrow */],
handler: focusHandler(QuickPickFocus.PreviousSeparator)
}, { withCtrlMod: true });
}
else {
registerQuickPickCommandAndKeybindingRule({
id: 'quickInput.nextSeparatorWithQuickAccessFallback',
primary: 512 /* KeyMod.Alt */ + 18 /* KeyCode.DownArrow */,
handler: focusHandler(QuickPickFocus.NextSeparator, QuickPickFocus.Next),
metadata: { description: nextSeparatorFallbackDesc }
});
registerQuickPickCommandAndKeybindingRule({
id: 'quickInput.nextSeparator',
primary: 2048 /* KeyMod.CtrlCmd */ + 512 /* KeyMod.Alt */ + 18 /* KeyCode.DownArrow */,
handler: focusHandler(QuickPickFocus.NextSeparator)
});
registerQuickPickCommandAndKeybindingRule({
id: 'quickInput.previousSeparatorWithQuickAccessFallback',
primary: 512 /* KeyMod.Alt */ + 16 /* KeyCode.UpArrow */,
handler: focusHandler(QuickPickFocus.PreviousSeparator, QuickPickFocus.Previous),
metadata: { description: prevSeparatorFallbackDesc }
});
registerQuickPickCommandAndKeybindingRule({
id: 'quickInput.previousSeparator',
primary: 2048 /* KeyMod.CtrlCmd */ + 512 /* KeyMod.Alt */ + 16 /* KeyCode.UpArrow */,
handler: focusHandler(QuickPickFocus.PreviousSeparator)
});
}
//#endregion
//#region Accept
registerQuickPickCommandAndKeybindingRule({
id: 'quickInput.acceptInBackground',
// If we are in the quick pick but the input box is not focused or our cursor is at the end of the input box
when: ContextKeyExpr.and(defaultCommandAndKeybindingRule.when, ContextKeyExpr.or(InputFocusedContext.negate(), endOfQuickInputBoxContext)),
primary: 17 /* KeyCode.RightArrow */,
// Need a little extra weight to ensure this keybinding is preferred over the default cmd+alt+right arrow keybinding
// https://github.com/microsoft/vscode/blob/1451e4fbbbf074a4355cc537c35b547b80ce1c52/src/vs/workbench/browser/parts/editor/editorActions.ts#L1178-L1195
weight: 200 /* KeybindingWeight.WorkbenchContrib */ + 50,
handler: (accessor) => {
const currentQuickPick = accessor.get(IQuickInputService).currentQuickInput;
currentQuickPick?.accept(true);
},
}, { withAltMod: true, withCtrlMod: true, withCmdMod: true });
@@ -0,0 +1,90 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as dom from '../../../base/browser/dom.js';
import { FindInput } from '../../../base/browser/ui/findinput/findInput.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import Severity from '../../../base/common/severity.js';
import './media/quickInput.css';
const $ = dom.$;
export class QuickInputBox extends Disposable {
constructor(parent, inputBoxStyles, toggleStyles) {
super();
this.parent = parent;
this.onKeyDown = (handler) => {
return dom.addStandardDisposableListener(this.findInput.inputBox.inputElement, dom.EventType.KEY_DOWN, handler);
};
this.onDidChange = (handler) => {
return this.findInput.onDidChange(handler);
};
this.container = dom.append(this.parent, $('.quick-input-box'));
this.findInput = this._register(new FindInput(this.container, undefined, { label: '', inputBoxStyles, toggleStyles }));
const input = this.findInput.inputBox.inputElement;
input.role = 'combobox';
input.ariaHasPopup = 'menu';
input.ariaAutoComplete = 'list';
input.ariaExpanded = 'true';
}
get value() {
return this.findInput.getValue();
}
set value(value) {
this.findInput.setValue(value);
}
select(range = null) {
this.findInput.inputBox.select(range);
}
getSelection() {
return this.findInput.inputBox.getSelection();
}
isSelectionAtEnd() {
return this.findInput.inputBox.isSelectionAtEnd();
}
get placeholder() {
return this.findInput.inputBox.inputElement.getAttribute('placeholder') || '';
}
set placeholder(placeholder) {
this.findInput.inputBox.setPlaceHolder(placeholder);
}
get password() {
return this.findInput.inputBox.inputElement.type === 'password';
}
set password(password) {
this.findInput.inputBox.inputElement.type = password ? 'password' : 'text';
}
set enabled(enabled) {
// We can't disable the input box because it is still used for
// navigating the list. Instead, we disable the list and the OK
// so that nothing can be selected.
// TODO: should this be what we do for all find inputs? Or maybe some _other_ API
// on findInput to change it to readonly?
this.findInput.inputBox.inputElement.toggleAttribute('readonly', !enabled);
// TODO: styles of the quick pick need to be moved to the CSS instead of being in line
// so things like this can be done in CSS
// this.findInput.inputBox.inputElement.classList.toggle('disabled', !enabled);
}
set toggles(toggles) {
this.findInput.setAdditionalToggles(toggles);
}
setAttribute(name, value) {
this.findInput.inputBox.inputElement.setAttribute(name, value);
}
showDecoration(decoration) {
if (decoration === Severity.Ignore) {
this.findInput.clearMessage();
}
else {
this.findInput.showMessage({ type: decoration === Severity.Info ? 1 /* MessageType.INFO */ : decoration === Severity.Warning ? 2 /* MessageType.WARNING */ : 3 /* MessageType.ERROR */, content: '' });
}
}
stylesForType(decoration) {
return this.findInput.inputBox.stylesForType(decoration === Severity.Info ? 1 /* MessageType.INFO */ : decoration === Severity.Warning ? 2 /* MessageType.WARNING */ : 3 /* MessageType.ERROR */);
}
setFocus() {
this.findInput.focus();
}
layout() {
this.findInput.inputBox.layout();
}
}
@@ -0,0 +1,615 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var QuickInputController_1;
import * as dom from '../../../base/browser/dom.js';
import { ActionBar } from '../../../base/browser/ui/actionbar/actionbar.js';
import { Button } from '../../../base/browser/ui/button/button.js';
import { CountBadge } from '../../../base/browser/ui/countBadge/countBadge.js';
import { ProgressBar } from '../../../base/browser/ui/progressbar/progressbar.js';
import { CancellationToken } from '../../../base/common/cancellation.js';
import { Emitter, Event } from '../../../base/common/event.js';
import { Disposable, dispose } from '../../../base/common/lifecycle.js';
import Severity from '../../../base/common/severity.js';
import { localize } from '../../../nls.js';
import { QuickInputHideReason } from '../common/quickInput.js';
import { QuickInputBox } from './quickInputBox.js';
import { QuickPick, backButton, InputBox, InQuickInputContextKey, QuickInputTypeContextKey, EndOfQuickInputBoxContextKey } from './quickInput.js';
import { ILayoutService } from '../../layout/browser/layoutService.js';
import { mainWindow } from '../../../base/browser/window.js';
import { IInstantiationService } from '../../instantiation/common/instantiation.js';
import { QuickInputTree } from './quickInputTree.js';
import { IContextKeyService } from '../../contextkey/common/contextkey.js';
import './quickInputActions.js';
const $ = dom.$;
let QuickInputController = class QuickInputController extends Disposable {
static { QuickInputController_1 = this; }
static { this.MAX_WIDTH = 600; } // Max total width of quick input widget
get currentQuickInput() { return this.controller ?? undefined; }
get container() { return this._container; }
constructor(options, layoutService, instantiationService, contextKeyService) {
super();
this.options = options;
this.layoutService = layoutService;
this.instantiationService = instantiationService;
this.contextKeyService = contextKeyService;
this.enabled = true;
this.onDidAcceptEmitter = this._register(new Emitter());
this.onDidCustomEmitter = this._register(new Emitter());
this.onDidTriggerButtonEmitter = this._register(new Emitter());
this.keyMods = { ctrlCmd: false, alt: false };
this.controller = null;
this.onShowEmitter = this._register(new Emitter());
this.onShow = this.onShowEmitter.event;
this.onHideEmitter = this._register(new Emitter());
this.onHide = this.onHideEmitter.event;
this.inQuickInputContext = InQuickInputContextKey.bindTo(this.contextKeyService);
this.quickInputTypeContext = QuickInputTypeContextKey.bindTo(this.contextKeyService);
this.endOfQuickInputBoxContext = EndOfQuickInputBoxContextKey.bindTo(this.contextKeyService);
this.idPrefix = options.idPrefix;
this._container = options.container;
this.styles = options.styles;
this._register(Event.runAndSubscribe(dom.onDidRegisterWindow, ({ window, disposables }) => this.registerKeyModsListeners(window, disposables), { window: mainWindow, disposables: this._store }));
this._register(dom.onWillUnregisterWindow(window => {
if (this.ui && dom.getWindow(this.ui.container) === window) {
// The window this quick input is contained in is about to
// close, so we have to make sure to reparent it back to an
// existing parent to not loose functionality.
// (https://github.com/microsoft/vscode/issues/195870)
this.reparentUI(this.layoutService.mainContainer);
this.layout(this.layoutService.mainContainerDimension, this.layoutService.mainContainerOffset.quickPickTop);
}
}));
}
registerKeyModsListeners(window, disposables) {
const listener = (e) => {
this.keyMods.ctrlCmd = e.ctrlKey || e.metaKey;
this.keyMods.alt = e.altKey;
};
for (const event of [dom.EventType.KEY_DOWN, dom.EventType.KEY_UP, dom.EventType.MOUSE_DOWN]) {
disposables.add(dom.addDisposableListener(window, event, listener, true));
}
}
getUI(showInActiveContainer) {
if (this.ui) {
// In order to support aux windows, re-parent the controller
// if the original event is from a different document
if (showInActiveContainer) {
if (dom.getWindow(this._container) !== dom.getWindow(this.layoutService.activeContainer)) {
this.reparentUI(this.layoutService.activeContainer);
this.layout(this.layoutService.activeContainerDimension, this.layoutService.activeContainerOffset.quickPickTop);
}
}
return this.ui;
}
const container = dom.append(this._container, $('.quick-input-widget.show-file-icons'));
container.tabIndex = -1;
container.style.display = 'none';
const styleSheet = dom.createStyleSheet(container);
const titleBar = dom.append(container, $('.quick-input-titlebar'));
const leftActionBar = this._register(new ActionBar(titleBar, { hoverDelegate: this.options.hoverDelegate }));
leftActionBar.domNode.classList.add('quick-input-left-action-bar');
const title = dom.append(titleBar, $('.quick-input-title'));
const rightActionBar = this._register(new ActionBar(titleBar, { hoverDelegate: this.options.hoverDelegate }));
rightActionBar.domNode.classList.add('quick-input-right-action-bar');
const headerContainer = dom.append(container, $('.quick-input-header'));
const checkAll = dom.append(headerContainer, $('input.quick-input-check-all'));
checkAll.type = 'checkbox';
checkAll.setAttribute('aria-label', localize('quickInput.checkAll', "Toggle all checkboxes"));
this._register(dom.addStandardDisposableListener(checkAll, dom.EventType.CHANGE, e => {
const checked = checkAll.checked;
list.setAllVisibleChecked(checked);
}));
this._register(dom.addDisposableListener(checkAll, dom.EventType.CLICK, e => {
if (e.x || e.y) { // Avoid 'click' triggered by 'space'...
inputBox.setFocus();
}
}));
const description2 = dom.append(headerContainer, $('.quick-input-description'));
const inputContainer = dom.append(headerContainer, $('.quick-input-and-message'));
const filterContainer = dom.append(inputContainer, $('.quick-input-filter'));
const inputBox = this._register(new QuickInputBox(filterContainer, this.styles.inputBox, this.styles.toggle));
inputBox.setAttribute('aria-describedby', `${this.idPrefix}message`);
const visibleCountContainer = dom.append(filterContainer, $('.quick-input-visible-count'));
visibleCountContainer.setAttribute('aria-live', 'polite');
visibleCountContainer.setAttribute('aria-atomic', 'true');
const visibleCount = new CountBadge(visibleCountContainer, { countFormat: localize({ key: 'quickInput.visibleCount', comment: ['This tells the user how many items are shown in a list of items to select from. The items can be anything. Currently not visible, but read by screen readers.'] }, "{0} Results") }, this.styles.countBadge);
const countContainer = dom.append(filterContainer, $('.quick-input-count'));
countContainer.setAttribute('aria-live', 'polite');
const count = new CountBadge(countContainer, { countFormat: localize({ key: 'quickInput.countSelected', comment: ['This tells the user how many items are selected in a list of items to select from. The items can be anything.'] }, "{0} Selected") }, this.styles.countBadge);
const inlineActionBar = this._register(new ActionBar(headerContainer, { hoverDelegate: this.options.hoverDelegate }));
inlineActionBar.domNode.classList.add('quick-input-inline-action-bar');
const okContainer = dom.append(headerContainer, $('.quick-input-action'));
const ok = this._register(new Button(okContainer, this.styles.button));
ok.label = localize('ok', "OK");
this._register(ok.onDidClick(e => {
this.onDidAcceptEmitter.fire();
}));
const customButtonContainer = dom.append(headerContainer, $('.quick-input-action'));
const customButton = this._register(new Button(customButtonContainer, { ...this.styles.button, supportIcons: true }));
customButton.label = localize('custom', "Custom");
this._register(customButton.onDidClick(e => {
this.onDidCustomEmitter.fire();
}));
const message = dom.append(inputContainer, $(`#${this.idPrefix}message.quick-input-message`));
const progressBar = this._register(new ProgressBar(container, this.styles.progressBar));
progressBar.getContainer().classList.add('quick-input-progress');
const widget = dom.append(container, $('.quick-input-html-widget'));
widget.tabIndex = -1;
const description1 = dom.append(container, $('.quick-input-description'));
const listId = this.idPrefix + 'list';
const list = this._register(this.instantiationService.createInstance(QuickInputTree, container, this.options.hoverDelegate, this.options.linkOpenerDelegate, listId));
inputBox.setAttribute('aria-controls', listId);
this._register(list.onDidChangeFocus(() => {
inputBox.setAttribute('aria-activedescendant', list.getActiveDescendant() ?? '');
}));
this._register(list.onChangedAllVisibleChecked(checked => {
checkAll.checked = checked;
}));
this._register(list.onChangedVisibleCount(c => {
visibleCount.setCount(c);
}));
this._register(list.onChangedCheckedCount(c => {
count.setCount(c);
}));
this._register(list.onLeave(() => {
// Defer to avoid the input field reacting to the triggering key.
// TODO@TylerLeonhardt https://github.com/microsoft/vscode/issues/203675
setTimeout(() => {
if (!this.controller) {
return;
}
inputBox.setFocus();
if (this.controller instanceof QuickPick && this.controller.canSelectMany) {
list.clearFocus();
}
}, 0);
}));
const focusTracker = dom.trackFocus(container);
this._register(focusTracker);
this._register(dom.addDisposableListener(container, dom.EventType.FOCUS, e => {
const ui = this.getUI();
if (dom.isAncestor(e.relatedTarget, ui.inputContainer)) {
const value = ui.inputBox.isSelectionAtEnd();
if (this.endOfQuickInputBoxContext.get() !== value) {
this.endOfQuickInputBoxContext.set(value);
}
}
// Ignore focus events within container
if (dom.isAncestor(e.relatedTarget, ui.container)) {
return;
}
this.inQuickInputContext.set(true);
this.previousFocusElement = dom.isHTMLElement(e.relatedTarget) ? e.relatedTarget : undefined;
}, true));
this._register(focusTracker.onDidBlur(() => {
if (!this.getUI().ignoreFocusOut && !this.options.ignoreFocusOut()) {
this.hide(QuickInputHideReason.Blur);
}
this.inQuickInputContext.set(false);
this.endOfQuickInputBoxContext.set(false);
this.previousFocusElement = undefined;
}));
this._register(inputBox.onKeyDown(_ => {
const value = this.getUI().inputBox.isSelectionAtEnd();
if (this.endOfQuickInputBoxContext.get() !== value) {
this.endOfQuickInputBoxContext.set(value);
}
}));
this._register(dom.addDisposableListener(container, dom.EventType.FOCUS, (e) => {
inputBox.setFocus();
}));
// TODO: Turn into commands instead of handling KEY_DOWN
// Keybindings for the quickinput widget as a whole
this._register(dom.addStandardDisposableListener(container, dom.EventType.KEY_DOWN, (event) => {
if (dom.isAncestor(event.target, widget)) {
return; // Ignore event if target is inside widget to allow the widget to handle the event.
}
switch (event.keyCode) {
case 3 /* KeyCode.Enter */:
dom.EventHelper.stop(event, true);
if (this.enabled) {
this.onDidAcceptEmitter.fire();
}
break;
case 9 /* KeyCode.Escape */:
dom.EventHelper.stop(event, true);
this.hide(QuickInputHideReason.Gesture);
break;
case 2 /* KeyCode.Tab */:
if (!event.altKey && !event.ctrlKey && !event.metaKey) {
// detect only visible actions
const selectors = [
'.quick-input-list .monaco-action-bar .always-visible',
'.quick-input-list-entry:hover .monaco-action-bar',
'.monaco-list-row.focused .monaco-action-bar'
];
if (container.classList.contains('show-checkboxes')) {
selectors.push('input');
}
else {
selectors.push('input[type=text]');
}
if (this.getUI().list.displayed) {
selectors.push('.monaco-list');
}
// focus links if there are any
if (this.getUI().message) {
selectors.push('.quick-input-message a');
}
if (this.getUI().widget) {
if (dom.isAncestor(event.target, this.getUI().widget)) {
// let the widget control tab
break;
}
selectors.push('.quick-input-html-widget');
}
const stops = container.querySelectorAll(selectors.join(', '));
if (event.shiftKey && event.target === stops[0]) {
// Clear the focus from the list in order to allow
// screen readers to read operations in the input box.
dom.EventHelper.stop(event, true);
list.clearFocus();
}
else if (!event.shiftKey && dom.isAncestor(event.target, stops[stops.length - 1])) {
dom.EventHelper.stop(event, true);
stops[0].focus();
}
}
break;
case 10 /* KeyCode.Space */:
if (event.ctrlKey) {
dom.EventHelper.stop(event, true);
this.getUI().list.toggleHover();
}
break;
}
}));
this.ui = {
container,
styleSheet,
leftActionBar,
titleBar,
title,
description1,
description2,
widget,
rightActionBar,
inlineActionBar,
checkAll,
inputContainer,
filterContainer,
inputBox,
visibleCountContainer,
visibleCount,
countContainer,
count,
okContainer,
ok,
message,
customButtonContainer,
customButton,
list,
progressBar,
onDidAccept: this.onDidAcceptEmitter.event,
onDidCustom: this.onDidCustomEmitter.event,
onDidTriggerButton: this.onDidTriggerButtonEmitter.event,
ignoreFocusOut: false,
keyMods: this.keyMods,
show: controller => this.show(controller),
hide: () => this.hide(),
setVisibilities: visibilities => this.setVisibilities(visibilities),
setEnabled: enabled => this.setEnabled(enabled),
setContextKey: contextKey => this.options.setContextKey(contextKey),
linkOpenerDelegate: content => this.options.linkOpenerDelegate(content)
};
this.updateStyles();
return this.ui;
}
reparentUI(container) {
if (this.ui) {
this._container = container;
dom.append(this._container, this.ui.container);
}
}
pick(picks, options = {}, token = CancellationToken.None) {
return new Promise((doResolve, reject) => {
let resolve = (result) => {
resolve = doResolve;
options.onKeyMods?.(input.keyMods);
doResolve(result);
};
if (token.isCancellationRequested) {
resolve(undefined);
return;
}
const input = this.createQuickPick();
let activeItem;
const disposables = [
input,
input.onDidAccept(() => {
if (input.canSelectMany) {
resolve(input.selectedItems.slice());
input.hide();
}
else {
const result = input.activeItems[0];
if (result) {
resolve(result);
input.hide();
}
}
}),
input.onDidChangeActive(items => {
const focused = items[0];
if (focused && options.onDidFocus) {
options.onDidFocus(focused);
}
}),
input.onDidChangeSelection(items => {
if (!input.canSelectMany) {
const result = items[0];
if (result) {
resolve(result);
input.hide();
}
}
}),
input.onDidTriggerItemButton(event => options.onDidTriggerItemButton && options.onDidTriggerItemButton({
...event,
removeItem: () => {
const index = input.items.indexOf(event.item);
if (index !== -1) {
const items = input.items.slice();
const removed = items.splice(index, 1);
const activeItems = input.activeItems.filter(activeItem => activeItem !== removed[0]);
const keepScrollPositionBefore = input.keepScrollPosition;
input.keepScrollPosition = true;
input.items = items;
if (activeItems) {
input.activeItems = activeItems;
}
input.keepScrollPosition = keepScrollPositionBefore;
}
}
})),
input.onDidTriggerSeparatorButton(event => options.onDidTriggerSeparatorButton?.(event)),
input.onDidChangeValue(value => {
if (activeItem && !value && (input.activeItems.length !== 1 || input.activeItems[0] !== activeItem)) {
input.activeItems = [activeItem];
}
}),
token.onCancellationRequested(() => {
input.hide();
}),
input.onDidHide(() => {
dispose(disposables);
resolve(undefined);
}),
];
input.title = options.title;
input.canSelectMany = !!options.canPickMany;
input.placeholder = options.placeHolder;
input.ignoreFocusOut = !!options.ignoreFocusLost;
input.matchOnDescription = !!options.matchOnDescription;
input.matchOnDetail = !!options.matchOnDetail;
input.matchOnLabel = (options.matchOnLabel === undefined) || options.matchOnLabel; // default to true
input.quickNavigate = options.quickNavigate;
input.hideInput = !!options.hideInput;
input.contextKey = options.contextKey;
input.busy = true;
Promise.all([picks, options.activeItem])
.then(([items, _activeItem]) => {
activeItem = _activeItem;
input.busy = false;
input.items = items;
if (input.canSelectMany) {
input.selectedItems = items.filter(item => item.type !== 'separator' && item.picked);
}
if (activeItem) {
input.activeItems = [activeItem];
}
});
input.show();
Promise.resolve(picks).then(undefined, err => {
reject(err);
input.hide();
});
});
}
createQuickPick() {
const ui = this.getUI(true);
return new QuickPick(ui);
}
createInputBox() {
const ui = this.getUI(true);
return new InputBox(ui);
}
show(controller) {
const ui = this.getUI(true);
this.onShowEmitter.fire();
const oldController = this.controller;
this.controller = controller;
oldController?.didHide();
this.setEnabled(true);
ui.leftActionBar.clear();
ui.title.textContent = '';
ui.description1.textContent = '';
ui.description2.textContent = '';
dom.reset(ui.widget);
ui.rightActionBar.clear();
ui.inlineActionBar.clear();
ui.checkAll.checked = false;
// ui.inputBox.value = ''; Avoid triggering an event.
ui.inputBox.placeholder = '';
ui.inputBox.password = false;
ui.inputBox.showDecoration(Severity.Ignore);
ui.visibleCount.setCount(0);
ui.count.setCount(0);
dom.reset(ui.message);
ui.progressBar.stop();
ui.list.setElements([]);
ui.list.matchOnDescription = false;
ui.list.matchOnDetail = false;
ui.list.matchOnLabel = true;
ui.list.sortByLabel = true;
ui.ignoreFocusOut = false;
ui.inputBox.toggles = undefined;
const backKeybindingLabel = this.options.backKeybindingLabel();
backButton.tooltip = backKeybindingLabel ? localize('quickInput.backWithKeybinding', "Back ({0})", backKeybindingLabel) : localize('quickInput.back', "Back");
ui.container.style.display = '';
this.updateLayout();
ui.inputBox.setFocus();
this.quickInputTypeContext.set(controller.type);
}
isVisible() {
return !!this.ui && this.ui.container.style.display !== 'none';
}
setVisibilities(visibilities) {
const ui = this.getUI();
ui.title.style.display = visibilities.title ? '' : 'none';
ui.description1.style.display = visibilities.description && (visibilities.inputBox || visibilities.checkAll) ? '' : 'none';
ui.description2.style.display = visibilities.description && !(visibilities.inputBox || visibilities.checkAll) ? '' : 'none';
ui.checkAll.style.display = visibilities.checkAll ? '' : 'none';
ui.inputContainer.style.display = visibilities.inputBox ? '' : 'none';
ui.filterContainer.style.display = visibilities.inputBox ? '' : 'none';
ui.visibleCountContainer.style.display = visibilities.visibleCount ? '' : 'none';
ui.countContainer.style.display = visibilities.count ? '' : 'none';
ui.okContainer.style.display = visibilities.ok ? '' : 'none';
ui.customButtonContainer.style.display = visibilities.customButton ? '' : 'none';
ui.message.style.display = visibilities.message ? '' : 'none';
ui.progressBar.getContainer().style.display = visibilities.progressBar ? '' : 'none';
ui.list.displayed = !!visibilities.list;
ui.container.classList.toggle('show-checkboxes', !!visibilities.checkBox);
ui.container.classList.toggle('hidden-input', !visibilities.inputBox && !visibilities.description);
this.updateLayout(); // TODO
}
setEnabled(enabled) {
if (enabled !== this.enabled) {
this.enabled = enabled;
for (const item of this.getUI().leftActionBar.viewItems) {
item.action.enabled = enabled;
}
for (const item of this.getUI().rightActionBar.viewItems) {
item.action.enabled = enabled;
}
this.getUI().checkAll.disabled = !enabled;
this.getUI().inputBox.enabled = enabled;
this.getUI().ok.enabled = enabled;
this.getUI().list.enabled = enabled;
}
}
hide(reason) {
const controller = this.controller;
if (!controller) {
return;
}
controller.willHide(reason);
const container = this.ui?.container;
const focusChanged = container && !dom.isAncestorOfActiveElement(container);
this.controller = null;
this.onHideEmitter.fire();
if (container) {
container.style.display = 'none';
}
if (!focusChanged) {
let currentElement = this.previousFocusElement;
while (currentElement && !currentElement.offsetParent) {
currentElement = currentElement.parentElement ?? undefined;
}
if (currentElement?.offsetParent) {
currentElement.focus();
this.previousFocusElement = undefined;
}
else {
this.options.returnFocus();
}
}
controller.didHide(reason);
}
layout(dimension, titleBarOffset) {
this.dimension = dimension;
this.titleBarOffset = titleBarOffset;
this.updateLayout();
}
updateLayout() {
if (this.ui && this.isVisible()) {
this.ui.container.style.top = `${this.titleBarOffset}px`;
const style = this.ui.container.style;
const width = Math.min(this.dimension.width * 0.62 /* golden cut */, QuickInputController_1.MAX_WIDTH);
style.width = width + 'px';
style.marginLeft = '-' + (width / 2) + 'px';
this.ui.inputBox.layout();
this.ui.list.layout(this.dimension && this.dimension.height * 0.4);
}
}
applyStyles(styles) {
this.styles = styles;
this.updateStyles();
}
updateStyles() {
if (this.ui) {
const { quickInputTitleBackground, quickInputBackground, quickInputForeground, widgetBorder, widgetShadow, } = this.styles.widget;
this.ui.titleBar.style.backgroundColor = quickInputTitleBackground ?? '';
this.ui.container.style.backgroundColor = quickInputBackground ?? '';
this.ui.container.style.color = quickInputForeground ?? '';
this.ui.container.style.border = widgetBorder ? `1px solid ${widgetBorder}` : '';
this.ui.container.style.boxShadow = widgetShadow ? `0 0 8px 2px ${widgetShadow}` : '';
this.ui.list.style(this.styles.list);
const content = [];
if (this.styles.pickerGroup.pickerGroupBorder) {
content.push(`.quick-input-list .quick-input-list-entry { border-top-color: ${this.styles.pickerGroup.pickerGroupBorder}; }`);
}
if (this.styles.pickerGroup.pickerGroupForeground) {
content.push(`.quick-input-list .quick-input-list-separator { color: ${this.styles.pickerGroup.pickerGroupForeground}; }`);
}
if (this.styles.pickerGroup.pickerGroupForeground) {
content.push(`.quick-input-list .quick-input-list-separator-as-item { color: var(--vscode-descriptionForeground); }`);
}
if (this.styles.keybindingLabel.keybindingLabelBackground ||
this.styles.keybindingLabel.keybindingLabelBorder ||
this.styles.keybindingLabel.keybindingLabelBottomBorder ||
this.styles.keybindingLabel.keybindingLabelShadow ||
this.styles.keybindingLabel.keybindingLabelForeground) {
content.push('.quick-input-list .monaco-keybinding > .monaco-keybinding-key {');
if (this.styles.keybindingLabel.keybindingLabelBackground) {
content.push(`background-color: ${this.styles.keybindingLabel.keybindingLabelBackground};`);
}
if (this.styles.keybindingLabel.keybindingLabelBorder) {
// Order matters here. `border-color` must come before `border-bottom-color`.
content.push(`border-color: ${this.styles.keybindingLabel.keybindingLabelBorder};`);
}
if (this.styles.keybindingLabel.keybindingLabelBottomBorder) {
content.push(`border-bottom-color: ${this.styles.keybindingLabel.keybindingLabelBottomBorder};`);
}
if (this.styles.keybindingLabel.keybindingLabelShadow) {
content.push(`box-shadow: inset 0 -1px 0 ${this.styles.keybindingLabel.keybindingLabelShadow};`);
}
if (this.styles.keybindingLabel.keybindingLabelForeground) {
content.push(`color: ${this.styles.keybindingLabel.keybindingLabelForeground};`);
}
content.push('}');
}
const newStyles = content.join('\n');
if (newStyles !== this.ui.styleSheet.textContent) {
this.ui.styleSheet.textContent = newStyles;
}
}
}
};
QuickInputController = QuickInputController_1 = __decorate([
__param(1, ILayoutService),
__param(2, IInstantiationService),
__param(3, IContextKeyService)
], QuickInputController);
export { QuickInputController };
@@ -0,0 +1,176 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { CancellationToken } from '../../../base/common/cancellation.js';
import { Emitter } from '../../../base/common/event.js';
import { IContextKeyService, RawContextKey } from '../../contextkey/common/contextkey.js';
import { IInstantiationService } from '../../instantiation/common/instantiation.js';
import { ILayoutService } from '../../layout/browser/layoutService.js';
import { IOpenerService } from '../../opener/common/opener.js';
import { QuickAccessController } from './quickAccess.js';
import { defaultButtonStyles, defaultCountBadgeStyles, defaultInputBoxStyles, defaultKeybindingLabelStyles, defaultProgressBarStyles, defaultToggleStyles, getListStyles } from '../../theme/browser/defaultStyles.js';
import { activeContrastBorder, asCssVariable, pickerGroupBorder, pickerGroupForeground, quickInputBackground, quickInputForeground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, quickInputTitleBackground, widgetBorder, widgetShadow } from '../../theme/common/colorRegistry.js';
import { IThemeService, Themable } from '../../theme/common/themeService.js';
import { QuickInputHoverDelegate } from './quickInput.js';
import { QuickInputController } from './quickInputController.js';
import { IConfigurationService } from '../../configuration/common/configuration.js';
import { getWindow } from '../../../base/browser/dom.js';
let QuickInputService = class QuickInputService extends Themable {
get controller() {
if (!this._controller) {
this._controller = this._register(this.createController());
}
return this._controller;
}
get hasController() { return !!this._controller; }
get currentQuickInput() { return this.controller.currentQuickInput; }
get quickAccess() {
if (!this._quickAccess) {
this._quickAccess = this._register(this.instantiationService.createInstance(QuickAccessController));
}
return this._quickAccess;
}
constructor(instantiationService, contextKeyService, themeService, layoutService, configurationService) {
super(themeService);
this.instantiationService = instantiationService;
this.contextKeyService = contextKeyService;
this.layoutService = layoutService;
this.configurationService = configurationService;
this._onShow = this._register(new Emitter());
this._onHide = this._register(new Emitter());
this.contexts = new Map();
}
createController(host = this.layoutService, options) {
const defaultOptions = {
idPrefix: 'quickInput_',
container: host.activeContainer,
ignoreFocusOut: () => false,
backKeybindingLabel: () => undefined,
setContextKey: (id) => this.setContextKey(id),
linkOpenerDelegate: (content) => {
// HACK: https://github.com/microsoft/vscode/issues/173691
this.instantiationService.invokeFunction(accessor => {
const openerService = accessor.get(IOpenerService);
openerService.open(content, { allowCommands: true, fromUserGesture: true });
});
},
returnFocus: () => host.focus(),
styles: this.computeStyles(),
hoverDelegate: this._register(this.instantiationService.createInstance(QuickInputHoverDelegate))
};
const controller = this._register(this.instantiationService.createInstance(QuickInputController, {
...defaultOptions,
...options
}));
controller.layout(host.activeContainerDimension, host.activeContainerOffset.quickPickTop);
// Layout changes
this._register(host.onDidLayoutActiveContainer(dimension => {
if (getWindow(host.activeContainer) === getWindow(controller.container)) {
controller.layout(dimension, host.activeContainerOffset.quickPickTop);
}
}));
this._register(host.onDidChangeActiveContainer(() => {
if (controller.isVisible()) {
return;
}
controller.layout(host.activeContainerDimension, host.activeContainerOffset.quickPickTop);
}));
// Context keys
this._register(controller.onShow(() => {
this.resetContextKeys();
this._onShow.fire();
}));
this._register(controller.onHide(() => {
this.resetContextKeys();
this._onHide.fire();
}));
return controller;
}
setContextKey(id) {
let key;
if (id) {
key = this.contexts.get(id);
if (!key) {
key = new RawContextKey(id, false)
.bindTo(this.contextKeyService);
this.contexts.set(id, key);
}
}
if (key && key.get()) {
return; // already active context
}
this.resetContextKeys();
key?.set(true);
}
resetContextKeys() {
this.contexts.forEach(context => {
if (context.get()) {
context.reset();
}
});
}
pick(picks, options = {}, token = CancellationToken.None) {
return this.controller.pick(picks, options, token);
}
createQuickPick() {
return this.controller.createQuickPick();
}
createInputBox() {
return this.controller.createInputBox();
}
updateStyles() {
if (this.hasController) {
this.controller.applyStyles(this.computeStyles());
}
}
computeStyles() {
return {
widget: {
quickInputBackground: asCssVariable(quickInputBackground),
quickInputForeground: asCssVariable(quickInputForeground),
quickInputTitleBackground: asCssVariable(quickInputTitleBackground),
widgetBorder: asCssVariable(widgetBorder),
widgetShadow: asCssVariable(widgetShadow),
},
inputBox: defaultInputBoxStyles,
toggle: defaultToggleStyles,
countBadge: defaultCountBadgeStyles,
button: defaultButtonStyles,
progressBar: defaultProgressBarStyles,
keybindingLabel: defaultKeybindingLabelStyles,
list: getListStyles({
listBackground: quickInputBackground,
listFocusBackground: quickInputListFocusBackground,
listFocusForeground: quickInputListFocusForeground,
// Look like focused when inactive.
listInactiveFocusForeground: quickInputListFocusForeground,
listInactiveSelectionIconForeground: quickInputListFocusIconForeground,
listInactiveFocusBackground: quickInputListFocusBackground,
listFocusOutline: activeContrastBorder,
listInactiveFocusOutline: activeContrastBorder,
}),
pickerGroup: {
pickerGroupBorder: asCssVariable(pickerGroupBorder),
pickerGroupForeground: asCssVariable(pickerGroupForeground),
}
};
}
};
QuickInputService = __decorate([
__param(0, IInstantiationService),
__param(1, IContextKeyService),
__param(2, IThemeService),
__param(3, ILayoutService),
__param(4, IConfigurationService)
], QuickInputService);
export { QuickInputService };
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,84 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as dom from '../../../base/browser/dom.js';
import { DomEmitter } from '../../../base/browser/event.js';
import { Event } from '../../../base/common/event.js';
import { StandardKeyboardEvent } from '../../../base/browser/keyboardEvent.js';
import { Gesture, EventType as GestureEventType } from '../../../base/browser/touch.js';
import { renderLabelWithIcons } from '../../../base/browser/ui/iconLabel/iconLabels.js';
import { IdGenerator } from '../../../base/common/idGenerator.js';
import { parseLinkedText } from '../../../base/common/linkedText.js';
import './media/quickInput.css';
import { localize } from '../../../nls.js';
const iconPathToClass = {};
const iconClassGenerator = new IdGenerator('quick-input-button-icon-');
function getIconClass(iconPath) {
if (!iconPath) {
return undefined;
}
let iconClass;
const key = iconPath.dark.toString();
if (iconPathToClass[key]) {
iconClass = iconPathToClass[key];
}
else {
iconClass = iconClassGenerator.nextId();
dom.createCSSRule(`.${iconClass}, .hc-light .${iconClass}`, `background-image: ${dom.asCSSUrl(iconPath.light || iconPath.dark)}`);
dom.createCSSRule(`.vs-dark .${iconClass}, .hc-black .${iconClass}`, `background-image: ${dom.asCSSUrl(iconPath.dark)}`);
iconPathToClass[key] = iconClass;
}
return iconClass;
}
export function quickInputButtonToAction(button, id, run) {
let cssClasses = button.iconClass || getIconClass(button.iconPath);
if (button.alwaysVisible) {
cssClasses = cssClasses ? `${cssClasses} always-visible` : 'always-visible';
}
return {
id,
label: '',
tooltip: button.tooltip || '',
class: cssClasses,
enabled: true,
run
};
}
export function renderQuickInputDescription(description, container, actionHandler) {
dom.reset(container);
const parsed = parseLinkedText(description);
let tabIndex = 0;
for (const node of parsed.nodes) {
if (typeof node === 'string') {
container.append(...renderLabelWithIcons(node));
}
else {
let title = node.title;
if (!title && node.href.startsWith('command:')) {
title = localize('executeCommand', "Click to execute command '{0}'", node.href.substring('command:'.length));
}
else if (!title) {
title = node.href;
}
const anchor = dom.$('a', { href: node.href, title, tabIndex: tabIndex++ }, node.label);
anchor.style.textDecoration = 'underline';
const handleOpen = (e) => {
if (dom.isEventLike(e)) {
dom.EventHelper.stop(e, true);
}
actionHandler.callback(node.href);
};
const onClick = actionHandler.disposables.add(new DomEmitter(anchor, dom.EventType.CLICK)).event;
const onKeydown = actionHandler.disposables.add(new DomEmitter(anchor, dom.EventType.KEY_DOWN)).event;
const onSpaceOrEnter = Event.chain(onKeydown, $ => $.filter(e => {
const event = new StandardKeyboardEvent(e);
return event.equals(10 /* KeyCode.Space */) || event.equals(3 /* KeyCode.Enter */);
}));
actionHandler.disposables.add(Gesture.addTarget(anchor));
const onTap = actionHandler.disposables.add(new DomEmitter(anchor, GestureEventType.Tap)).event;
Event.any(onClick, onTap, onSpaceOrEnter)(handleOpen, null, actionHandler.disposables);
container.appendChild(anchor);
}
}
}
@@ -0,0 +1,53 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { coalesce } from '../../../base/common/arrays.js';
import { toDisposable } from '../../../base/common/lifecycle.js';
import { Registry } from '../../registry/common/platform.js';
export var DefaultQuickAccessFilterValue;
(function (DefaultQuickAccessFilterValue) {
/**
* Keep the value as it is given to quick access.
*/
DefaultQuickAccessFilterValue[DefaultQuickAccessFilterValue["PRESERVE"] = 0] = "PRESERVE";
/**
* Use the value that was used last time something was accepted from the picker.
*/
DefaultQuickAccessFilterValue[DefaultQuickAccessFilterValue["LAST"] = 1] = "LAST";
})(DefaultQuickAccessFilterValue || (DefaultQuickAccessFilterValue = {}));
export const Extensions = {
Quickaccess: 'workbench.contributions.quickaccess'
};
export class QuickAccessRegistry {
constructor() {
this.providers = [];
this.defaultProvider = undefined;
}
registerQuickAccessProvider(provider) {
// Extract the default provider when no prefix is present
if (provider.prefix.length === 0) {
this.defaultProvider = provider;
}
else {
this.providers.push(provider);
}
// sort the providers by decreasing prefix length, such that longer
// prefixes take priority: 'ext' vs 'ext install' - the latter should win
this.providers.sort((providerA, providerB) => providerB.prefix.length - providerA.prefix.length);
return toDisposable(() => {
this.providers.splice(this.providers.indexOf(provider), 1);
if (this.defaultProvider === provider) {
this.defaultProvider = undefined;
}
});
}
getQuickAccessProviders() {
return coalesce([this.defaultProvider, ...this.providers]);
}
getQuickAccessProvider(prefix) {
const result = prefix ? (this.providers.find(provider => prefix.startsWith(provider.prefix)) || undefined) : undefined;
return result || this.defaultProvider;
}
}
Registry.add(Extensions.Quickaccess, new QuickAccessRegistry());
@@ -0,0 +1,105 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from '../../instantiation/common/instantiation.js';
export const NO_KEY_MODS = { ctrlCmd: false, alt: false };
export var QuickInputHideReason;
(function (QuickInputHideReason) {
/**
* Focus moved away from the quick input.
*/
QuickInputHideReason[QuickInputHideReason["Blur"] = 1] = "Blur";
/**
* An explicit user gesture, e.g. pressing Escape key.
*/
QuickInputHideReason[QuickInputHideReason["Gesture"] = 2] = "Gesture";
/**
* Anything else.
*/
QuickInputHideReason[QuickInputHideReason["Other"] = 3] = "Other";
})(QuickInputHideReason || (QuickInputHideReason = {}));
/**
* Represents the activation behavior for items in a quick input. This means which item will be
* "active" (aka focused).
*/
export var ItemActivation;
(function (ItemActivation) {
/**
* No item will be active.
*/
ItemActivation[ItemActivation["NONE"] = 0] = "NONE";
/**
* First item will be active.
*/
ItemActivation[ItemActivation["FIRST"] = 1] = "FIRST";
/**
* Second item will be active.
*/
ItemActivation[ItemActivation["SECOND"] = 2] = "SECOND";
/**
* Last item will be active.
*/
ItemActivation[ItemActivation["LAST"] = 3] = "LAST";
})(ItemActivation || (ItemActivation = {}));
/**
* Represents the focus options for a quick pick.
*/
export var QuickPickFocus;
(function (QuickPickFocus) {
/**
* Focus the first item in the list.
*/
QuickPickFocus[QuickPickFocus["First"] = 1] = "First";
/**
* Focus the second item in the list.
*/
QuickPickFocus[QuickPickFocus["Second"] = 2] = "Second";
/**
* Focus the last item in the list.
*/
QuickPickFocus[QuickPickFocus["Last"] = 3] = "Last";
/**
* Focus the next item in the list.
*/
QuickPickFocus[QuickPickFocus["Next"] = 4] = "Next";
/**
* Focus the previous item in the list.
*/
QuickPickFocus[QuickPickFocus["Previous"] = 5] = "Previous";
/**
* Focus the next page in the list.
*/
QuickPickFocus[QuickPickFocus["NextPage"] = 6] = "NextPage";
/**
* Focus the previous page in the list.
*/
QuickPickFocus[QuickPickFocus["PreviousPage"] = 7] = "PreviousPage";
/**
* Focus the first item under the next separator.
*/
QuickPickFocus[QuickPickFocus["NextSeparator"] = 8] = "NextSeparator";
/**
* Focus the first item under the current separator.
*/
QuickPickFocus[QuickPickFocus["PreviousSeparator"] = 9] = "PreviousSeparator";
})(QuickPickFocus || (QuickPickFocus = {}));
export var QuickInputButtonLocation;
(function (QuickInputButtonLocation) {
/**
* In the title bar.
*/
QuickInputButtonLocation[QuickInputButtonLocation["Title"] = 1] = "Title";
/**
* To the right of the input box.
*/
QuickInputButtonLocation[QuickInputButtonLocation["Inline"] = 2] = "Inline";
})(QuickInputButtonLocation || (QuickInputButtonLocation = {}));
export class QuickPickItemScorerAccessor {
constructor(options) {
this.options = options;
}
}
export const quickPickItemScorerAccessor = new QuickPickItemScorerAccessor();
//#endregion
export const IQuickInputService = createDecorator('quickInputService');
@@ -0,0 +1,21 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as Assert from '../../../base/common/assert.js';
import * as Types from '../../../base/common/types.js';
class RegistryImpl {
constructor() {
this.data = new Map();
}
add(id, data) {
Assert.ok(Types.isString(id));
Assert.ok(Types.isObject(data));
Assert.ok(!this.data.has(id), 'There is already an extension with this id');
this.data.set(id, data);
}
as(id) {
return this.data.get(id) || null;
}
}
export const Registry = new RegistryImpl();
@@ -0,0 +1,31 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .zone-widget .codicon.codicon-error,
.markers-panel .marker-icon.error, .markers-panel .marker-icon .codicon.codicon-error,
.text-search-provider-messages .providerMessage .codicon.codicon-error,
.extensions-viewlet > .extensions .codicon.codicon-error,
.extension-editor .codicon.codicon-error,
.preferences-editor .codicon.codicon-error {
color: var(--vscode-problemsErrorIcon-foreground);
}
.monaco-editor .zone-widget .codicon.codicon-warning,
.markers-panel .marker-icon.warning, .markers-panel .marker-icon .codicon.codicon-warning,
.text-search-provider-messages .providerMessage .codicon.codicon-warning,
.extensions-viewlet > .extensions .codicon.codicon-warning,
.extension-editor .codicon.codicon-warning,
.preferences-editor .codicon.codicon-warning {
color: var(--vscode-problemsWarningIcon-foreground);
}
.monaco-editor .zone-widget .codicon.codicon-info,
.markers-panel .marker-icon.info, .markers-panel .marker-icon .codicon.codicon-info,
.text-search-provider-messages .providerMessage .codicon.codicon-info,
.extensions-viewlet > .extensions .codicon.codicon-info,
.extension-editor .codicon.codicon-info,
.preferences-editor .codicon.codicon-info {
color: var(--vscode-problemsInfoIcon-foreground);
}
@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './media/severityIcon.css';
import { Codicon } from '../../../base/common/codicons.js';
import { ThemeIcon } from '../../../base/common/themables.js';
import Severity from '../../../base/common/severity.js';
export var SeverityIcon;
(function (SeverityIcon) {
function className(severity) {
switch (severity) {
case Severity.Ignore:
return 'severity-ignore ' + ThemeIcon.asClassName(Codicon.info);
case Severity.Info:
return ThemeIcon.asClassName(Codicon.info);
case Severity.Warning:
return ThemeIcon.asClassName(Codicon.warning);
case Severity.Error:
return ThemeIcon.asClassName(Codicon.error);
default:
return '';
}
}
SeverityIcon.className = className;
})(SeverityIcon || (SeverityIcon = {}));
@@ -0,0 +1,186 @@
import { Emitter, Event, PauseableEmitter } from '../../../base/common/event.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { isUndefinedOrNull } from '../../../base/common/types.js';
import { InMemoryStorageDatabase, Storage, StorageHint } from '../../../base/parts/storage/common/storage.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
export const TARGET_KEY = '__$__targetStorageMarker';
export const IStorageService = createDecorator('storageService');
export var WillSaveStateReason;
(function (WillSaveStateReason) {
/**
* No specific reason to save state.
*/
WillSaveStateReason[WillSaveStateReason["NONE"] = 0] = "NONE";
/**
* A hint that the workbench is about to shutdown.
*/
WillSaveStateReason[WillSaveStateReason["SHUTDOWN"] = 1] = "SHUTDOWN";
})(WillSaveStateReason || (WillSaveStateReason = {}));
export function loadKeyTargets(storage) {
const keysRaw = storage.get(TARGET_KEY);
if (keysRaw) {
try {
return JSON.parse(keysRaw);
}
catch (error) {
// Fail gracefully
}
}
return Object.create(null);
}
export class AbstractStorageService extends Disposable {
static { this.DEFAULT_FLUSH_INTERVAL = 60 * 1000; } // every minute
constructor(options = { flushInterval: AbstractStorageService.DEFAULT_FLUSH_INTERVAL }) {
super();
this.options = options;
this._onDidChangeValue = this._register(new PauseableEmitter());
this._onDidChangeTarget = this._register(new PauseableEmitter());
this._onWillSaveState = this._register(new Emitter());
this.onWillSaveState = this._onWillSaveState.event;
this._workspaceKeyTargets = undefined;
this._profileKeyTargets = undefined;
this._applicationKeyTargets = undefined;
}
onDidChangeValue(scope, key, disposable) {
return Event.filter(this._onDidChangeValue.event, e => e.scope === scope && (key === undefined || e.key === key), disposable);
}
emitDidChangeValue(scope, event) {
const { key, external } = event;
// Specially handle `TARGET_KEY`
if (key === TARGET_KEY) {
// Clear our cached version which is now out of date
switch (scope) {
case -1 /* StorageScope.APPLICATION */:
this._applicationKeyTargets = undefined;
break;
case 0 /* StorageScope.PROFILE */:
this._profileKeyTargets = undefined;
break;
case 1 /* StorageScope.WORKSPACE */:
this._workspaceKeyTargets = undefined;
break;
}
// Emit as `didChangeTarget` event
this._onDidChangeTarget.fire({ scope });
}
// Emit any other key to outside
else {
this._onDidChangeValue.fire({ scope, key, target: this.getKeyTargets(scope)[key], external });
}
}
get(key, scope, fallbackValue) {
return this.getStorage(scope)?.get(key, fallbackValue);
}
getBoolean(key, scope, fallbackValue) {
return this.getStorage(scope)?.getBoolean(key, fallbackValue);
}
getNumber(key, scope, fallbackValue) {
return this.getStorage(scope)?.getNumber(key, fallbackValue);
}
store(key, value, scope, target, external = false) {
// We remove the key for undefined/null values
if (isUndefinedOrNull(value)) {
this.remove(key, scope, external);
return;
}
// Update our datastructures but send events only after
this.withPausedEmitters(() => {
// Update key-target map
this.updateKeyTarget(key, scope, target);
// Store actual value
this.getStorage(scope)?.set(key, value, external);
});
}
remove(key, scope, external = false) {
// Update our datastructures but send events only after
this.withPausedEmitters(() => {
// Update key-target map
this.updateKeyTarget(key, scope, undefined);
// Remove actual key
this.getStorage(scope)?.delete(key, external);
});
}
withPausedEmitters(fn) {
// Pause emitters
this._onDidChangeValue.pause();
this._onDidChangeTarget.pause();
try {
fn();
}
finally {
// Resume emitters
this._onDidChangeValue.resume();
this._onDidChangeTarget.resume();
}
}
updateKeyTarget(key, scope, target, external = false) {
// Add
const keyTargets = this.getKeyTargets(scope);
if (typeof target === 'number') {
if (keyTargets[key] !== target) {
keyTargets[key] = target;
this.getStorage(scope)?.set(TARGET_KEY, JSON.stringify(keyTargets), external);
}
}
// Remove
else {
if (typeof keyTargets[key] === 'number') {
delete keyTargets[key];
this.getStorage(scope)?.set(TARGET_KEY, JSON.stringify(keyTargets), external);
}
}
}
get workspaceKeyTargets() {
if (!this._workspaceKeyTargets) {
this._workspaceKeyTargets = this.loadKeyTargets(1 /* StorageScope.WORKSPACE */);
}
return this._workspaceKeyTargets;
}
get profileKeyTargets() {
if (!this._profileKeyTargets) {
this._profileKeyTargets = this.loadKeyTargets(0 /* StorageScope.PROFILE */);
}
return this._profileKeyTargets;
}
get applicationKeyTargets() {
if (!this._applicationKeyTargets) {
this._applicationKeyTargets = this.loadKeyTargets(-1 /* StorageScope.APPLICATION */);
}
return this._applicationKeyTargets;
}
getKeyTargets(scope) {
switch (scope) {
case -1 /* StorageScope.APPLICATION */:
return this.applicationKeyTargets;
case 0 /* StorageScope.PROFILE */:
return this.profileKeyTargets;
default:
return this.workspaceKeyTargets;
}
}
loadKeyTargets(scope) {
const storage = this.getStorage(scope);
return storage ? loadKeyTargets(storage) : Object.create(null);
}
}
export class InMemoryStorageService extends AbstractStorageService {
constructor() {
super();
this.applicationStorage = this._register(new Storage(new InMemoryStorageDatabase(), { hint: StorageHint.STORAGE_IN_MEMORY }));
this.profileStorage = this._register(new Storage(new InMemoryStorageDatabase(), { hint: StorageHint.STORAGE_IN_MEMORY }));
this.workspaceStorage = this._register(new Storage(new InMemoryStorageDatabase(), { hint: StorageHint.STORAGE_IN_MEMORY }));
this._register(this.workspaceStorage.onDidChangeStorage(e => this.emitDidChangeValue(1 /* StorageScope.WORKSPACE */, e)));
this._register(this.profileStorage.onDidChangeStorage(e => this.emitDidChangeValue(0 /* StorageScope.PROFILE */, e)));
this._register(this.applicationStorage.onDidChangeStorage(e => this.emitDidChangeValue(-1 /* StorageScope.APPLICATION */, e)));
}
getStorage(scope) {
switch (scope) {
case -1 /* StorageScope.APPLICATION */:
return this.applicationStorage;
case 0 /* StorageScope.PROFILE */:
return this.profileStorage;
default:
return this.workspaceStorage;
}
}
}
@@ -0,0 +1 @@
export {};
@@ -0,0 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from '../../instantiation/common/instantiation.js';
export const ITelemetryService = createDecorator('telemetryService');
@@ -0,0 +1,179 @@
import { keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, asCssVariable, widgetShadow, buttonForeground, buttonSeparator, buttonBackground, buttonHoverBackground, buttonSecondaryForeground, buttonSecondaryBackground, buttonSecondaryHoverBackground, buttonBorder, progressBarBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputActiveOptionBackground, editorWidgetBackground, editorWidgetForeground, contrastBorder, checkboxBorder, checkboxBackground, checkboxForeground, problemsErrorIconForeground, problemsWarningIconForeground, problemsInfoIconForeground, inputBackground, inputForeground, inputBorder, textLinkForeground, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationErrorBorder, inputValidationErrorBackground, inputValidationErrorForeground, listFilterWidgetBackground, listFilterWidgetNoMatchesOutline, listFilterWidgetOutline, listFilterWidgetShadow, badgeBackground, badgeForeground, breadcrumbsBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, activeContrastBorder, listActiveSelectionBackground, listActiveSelectionForeground, listActiveSelectionIconForeground, listDropOverBackground, listFocusAndSelectionOutline, listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, listInactiveFocusBackground, listInactiveFocusOutline, listInactiveSelectionBackground, listInactiveSelectionForeground, listInactiveSelectionIconForeground, tableColumnsBorder, tableOddRowsBackgroundColor, treeIndentGuidesStroke, asCssVariableWithDefault, editorWidgetBorder, focusBorder, pickerGroupForeground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, selectBackground, selectBorder, selectForeground, selectListBackground, treeInactiveIndentGuidesStroke, menuBorder, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuSeparatorBackground, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listDropBetweenBackground, radioActiveBackground, radioActiveForeground, radioInactiveBackground, radioInactiveForeground, radioInactiveBorder, radioInactiveHoverBackground, radioActiveBorder } from '../common/colorRegistry.js';
import { Color } from '../../../base/common/color.js';
function overrideStyles(override, styles) {
const result = { ...styles };
for (const key in override) {
const val = override[key];
result[key] = val !== undefined ? asCssVariable(val) : undefined;
}
return result;
}
export const defaultKeybindingLabelStyles = {
keybindingLabelBackground: asCssVariable(keybindingLabelBackground),
keybindingLabelForeground: asCssVariable(keybindingLabelForeground),
keybindingLabelBorder: asCssVariable(keybindingLabelBorder),
keybindingLabelBottomBorder: asCssVariable(keybindingLabelBottomBorder),
keybindingLabelShadow: asCssVariable(widgetShadow)
};
export const defaultButtonStyles = {
buttonForeground: asCssVariable(buttonForeground),
buttonSeparator: asCssVariable(buttonSeparator),
buttonBackground: asCssVariable(buttonBackground),
buttonHoverBackground: asCssVariable(buttonHoverBackground),
buttonSecondaryForeground: asCssVariable(buttonSecondaryForeground),
buttonSecondaryBackground: asCssVariable(buttonSecondaryBackground),
buttonSecondaryHoverBackground: asCssVariable(buttonSecondaryHoverBackground),
buttonBorder: asCssVariable(buttonBorder),
};
export const defaultProgressBarStyles = {
progressBarBackground: asCssVariable(progressBarBackground)
};
export const defaultToggleStyles = {
inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder),
inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground),
inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground)
};
export const defaultRadioStyles = {
activeForeground: asCssVariable(radioActiveForeground),
activeBackground: asCssVariable(radioActiveBackground),
activeBorder: asCssVariable(radioActiveBorder),
inactiveForeground: asCssVariable(radioInactiveForeground),
inactiveBackground: asCssVariable(radioInactiveBackground),
inactiveBorder: asCssVariable(radioInactiveBorder),
inactiveHoverBackground: asCssVariable(radioInactiveHoverBackground),
};
export const defaultCheckboxStyles = {
checkboxBackground: asCssVariable(checkboxBackground),
checkboxBorder: asCssVariable(checkboxBorder),
checkboxForeground: asCssVariable(checkboxForeground)
};
export const defaultDialogStyles = {
dialogBackground: asCssVariable(editorWidgetBackground),
dialogForeground: asCssVariable(editorWidgetForeground),
dialogShadow: asCssVariable(widgetShadow),
dialogBorder: asCssVariable(contrastBorder),
errorIconForeground: asCssVariable(problemsErrorIconForeground),
warningIconForeground: asCssVariable(problemsWarningIconForeground),
infoIconForeground: asCssVariable(problemsInfoIconForeground),
textLinkForeground: asCssVariable(textLinkForeground)
};
export const defaultInputBoxStyles = {
inputBackground: asCssVariable(inputBackground),
inputForeground: asCssVariable(inputForeground),
inputBorder: asCssVariable(inputBorder),
inputValidationInfoBorder: asCssVariable(inputValidationInfoBorder),
inputValidationInfoBackground: asCssVariable(inputValidationInfoBackground),
inputValidationInfoForeground: asCssVariable(inputValidationInfoForeground),
inputValidationWarningBorder: asCssVariable(inputValidationWarningBorder),
inputValidationWarningBackground: asCssVariable(inputValidationWarningBackground),
inputValidationWarningForeground: asCssVariable(inputValidationWarningForeground),
inputValidationErrorBorder: asCssVariable(inputValidationErrorBorder),
inputValidationErrorBackground: asCssVariable(inputValidationErrorBackground),
inputValidationErrorForeground: asCssVariable(inputValidationErrorForeground)
};
export const defaultFindWidgetStyles = {
listFilterWidgetBackground: asCssVariable(listFilterWidgetBackground),
listFilterWidgetOutline: asCssVariable(listFilterWidgetOutline),
listFilterWidgetNoMatchesOutline: asCssVariable(listFilterWidgetNoMatchesOutline),
listFilterWidgetShadow: asCssVariable(listFilterWidgetShadow),
inputBoxStyles: defaultInputBoxStyles,
toggleStyles: defaultToggleStyles
};
export const defaultCountBadgeStyles = {
badgeBackground: asCssVariable(badgeBackground),
badgeForeground: asCssVariable(badgeForeground),
badgeBorder: asCssVariable(contrastBorder)
};
export const defaultBreadcrumbsWidgetStyles = {
breadcrumbsBackground: asCssVariable(breadcrumbsBackground),
breadcrumbsForeground: asCssVariable(breadcrumbsForeground),
breadcrumbsHoverForeground: asCssVariable(breadcrumbsFocusForeground),
breadcrumbsFocusForeground: asCssVariable(breadcrumbsFocusForeground),
breadcrumbsFocusAndSelectionForeground: asCssVariable(breadcrumbsActiveSelectionForeground)
};
export const defaultListStyles = {
listBackground: undefined,
listInactiveFocusForeground: undefined,
listFocusBackground: asCssVariable(listFocusBackground),
listFocusForeground: asCssVariable(listFocusForeground),
listFocusOutline: asCssVariable(listFocusOutline),
listActiveSelectionBackground: asCssVariable(listActiveSelectionBackground),
listActiveSelectionForeground: asCssVariable(listActiveSelectionForeground),
listActiveSelectionIconForeground: asCssVariable(listActiveSelectionIconForeground),
listFocusAndSelectionOutline: asCssVariable(listFocusAndSelectionOutline),
listFocusAndSelectionBackground: asCssVariable(listActiveSelectionBackground),
listFocusAndSelectionForeground: asCssVariable(listActiveSelectionForeground),
listInactiveSelectionBackground: asCssVariable(listInactiveSelectionBackground),
listInactiveSelectionIconForeground: asCssVariable(listInactiveSelectionIconForeground),
listInactiveSelectionForeground: asCssVariable(listInactiveSelectionForeground),
listInactiveFocusBackground: asCssVariable(listInactiveFocusBackground),
listInactiveFocusOutline: asCssVariable(listInactiveFocusOutline),
listHoverBackground: asCssVariable(listHoverBackground),
listHoverForeground: asCssVariable(listHoverForeground),
listDropOverBackground: asCssVariable(listDropOverBackground),
listDropBetweenBackground: asCssVariable(listDropBetweenBackground),
listSelectionOutline: asCssVariable(activeContrastBorder),
listHoverOutline: asCssVariable(activeContrastBorder),
treeIndentGuidesStroke: asCssVariable(treeIndentGuidesStroke),
treeInactiveIndentGuidesStroke: asCssVariable(treeInactiveIndentGuidesStroke),
treeStickyScrollBackground: undefined,
treeStickyScrollBorder: undefined,
treeStickyScrollShadow: asCssVariable(scrollbarShadow),
tableColumnsBorder: asCssVariable(tableColumnsBorder),
tableOddRowsBackgroundColor: asCssVariable(tableOddRowsBackgroundColor),
};
export function getListStyles(override) {
return overrideStyles(override, defaultListStyles);
}
export const defaultSelectBoxStyles = {
selectBackground: asCssVariable(selectBackground),
selectListBackground: asCssVariable(selectListBackground),
selectForeground: asCssVariable(selectForeground),
decoratorRightForeground: asCssVariable(pickerGroupForeground),
selectBorder: asCssVariable(selectBorder),
focusBorder: asCssVariable(focusBorder),
listFocusBackground: asCssVariable(quickInputListFocusBackground),
listInactiveSelectionIconForeground: asCssVariable(quickInputListFocusIconForeground),
listFocusForeground: asCssVariable(quickInputListFocusForeground),
listFocusOutline: asCssVariableWithDefault(activeContrastBorder, Color.transparent.toString()),
listHoverBackground: asCssVariable(listHoverBackground),
listHoverForeground: asCssVariable(listHoverForeground),
listHoverOutline: asCssVariable(activeContrastBorder),
selectListBorder: asCssVariable(editorWidgetBorder),
listBackground: undefined,
listActiveSelectionBackground: undefined,
listActiveSelectionForeground: undefined,
listActiveSelectionIconForeground: undefined,
listFocusAndSelectionBackground: undefined,
listDropOverBackground: undefined,
listDropBetweenBackground: undefined,
listInactiveSelectionBackground: undefined,
listInactiveSelectionForeground: undefined,
listInactiveFocusBackground: undefined,
listInactiveFocusOutline: undefined,
listSelectionOutline: undefined,
listFocusAndSelectionForeground: undefined,
listFocusAndSelectionOutline: undefined,
listInactiveFocusForeground: undefined,
tableColumnsBorder: undefined,
tableOddRowsBackgroundColor: undefined,
treeIndentGuidesStroke: undefined,
treeInactiveIndentGuidesStroke: undefined,
treeStickyScrollBackground: undefined,
treeStickyScrollBorder: undefined,
treeStickyScrollShadow: undefined
};
export const defaultMenuStyles = {
shadowColor: asCssVariable(widgetShadow),
borderColor: asCssVariable(menuBorder),
foregroundColor: asCssVariable(menuForeground),
backgroundColor: asCssVariable(menuBackground),
selectionForegroundColor: asCssVariable(menuSelectionForeground),
selectionBackgroundColor: asCssVariable(menuSelectionBackground),
selectionBorderColor: asCssVariable(menuSelectionBorder),
separatorColor: asCssVariable(menuSeparatorBackground),
scrollbarShadow: asCssVariable(scrollbarShadow),
scrollbarSliderBackground: asCssVariable(scrollbarSliderBackground),
scrollbarSliderHoverBackground: asCssVariable(scrollbarSliderHoverBackground),
scrollbarSliderActiveBackground: asCssVariable(scrollbarSliderActiveBackground)
};
@@ -0,0 +1,69 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { asCSSPropertyValue, asCSSUrl } from '../../../base/browser/dom.js';
import { Emitter } from '../../../base/common/event.js';
import { DisposableStore } from '../../../base/common/lifecycle.js';
import { ThemeIcon } from '../../../base/common/themables.js';
import { getIconRegistry } from '../common/iconRegistry.js';
export function getIconsStyleSheet(themeService) {
const disposable = new DisposableStore();
const onDidChangeEmmiter = disposable.add(new Emitter());
const iconRegistry = getIconRegistry();
disposable.add(iconRegistry.onDidChange(() => onDidChangeEmmiter.fire()));
if (themeService) {
disposable.add(themeService.onDidProductIconThemeChange(() => onDidChangeEmmiter.fire()));
}
return {
dispose: () => disposable.dispose(),
onDidChange: onDidChangeEmmiter.event,
getCSS() {
const productIconTheme = themeService ? themeService.getProductIconTheme() : new UnthemedProductIconTheme();
const usedFontIds = {};
const rules = [];
const rootAttribs = [];
for (const contribution of iconRegistry.getIcons()) {
const definition = productIconTheme.getIcon(contribution);
if (!definition) {
continue;
}
const fontContribution = definition.font;
const fontFamilyVar = `--vscode-icon-${contribution.id}-font-family`;
const contentVar = `--vscode-icon-${contribution.id}-content`;
if (fontContribution) {
usedFontIds[fontContribution.id] = fontContribution.definition;
rootAttribs.push(`${fontFamilyVar}: ${asCSSPropertyValue(fontContribution.id)};`, `${contentVar}: '${definition.fontCharacter}';`);
rules.push(`.codicon-${contribution.id}:before { content: '${definition.fontCharacter}'; font-family: ${asCSSPropertyValue(fontContribution.id)}; }`);
}
else {
rootAttribs.push(`${contentVar}: '${definition.fontCharacter}'; ${fontFamilyVar}: 'codicon';`);
rules.push(`.codicon-${contribution.id}:before { content: '${definition.fontCharacter}'; }`);
}
}
for (const id in usedFontIds) {
const definition = usedFontIds[id];
const fontWeight = definition.weight ? `font-weight: ${definition.weight};` : '';
const fontStyle = definition.style ? `font-style: ${definition.style};` : '';
const src = definition.src.map(l => `${asCSSUrl(l.location)} format('${l.format}')`).join(', ');
rules.push(`@font-face { src: ${src}; font-family: ${asCSSPropertyValue(id)};${fontWeight}${fontStyle} font-display: block; }`);
}
rules.push(`:root { ${rootAttribs.join(' ')} }`);
return rules.join('\n');
}
};
}
export class UnthemedProductIconTheme {
getIcon(contribution) {
const iconRegistry = getIconRegistry();
let definition = contribution.defaults;
while (ThemeIcon.isThemeIcon(definition)) {
const c = iconRegistry.getIcon(definition.id);
if (!c) {
return undefined;
}
definition = c.defaults;
}
return definition;
}
}
@@ -0,0 +1,16 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export * from './colorUtils.js';
// Make sure all color files are exported
export * from './colors/baseColors.js';
export * from './colors/chartsColors.js';
export * from './colors/editorColors.js';
export * from './colors/inputColors.js';
export * from './colors/listColors.js';
export * from './colors/menuColors.js';
export * from './colors/minimapColors.js';
export * from './colors/miscColors.js';
export * from './colors/quickpickColors.js';
export * from './colors/searchColors.js';
@@ -0,0 +1,188 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { assertNever } from '../../../base/common/assert.js';
import { RunOnceScheduler } from '../../../base/common/async.js';
import { Color } from '../../../base/common/color.js';
import { Emitter } from '../../../base/common/event.js';
import { Extensions as JSONExtensions } from '../../jsonschemas/common/jsonContributionRegistry.js';
import * as platform from '../../registry/common/platform.js';
import * as nls from '../../../nls.js';
/**
* Returns the css variable name for the given color identifier. Dots (`.`) are replaced with hyphens (`-`) and
* everything is prefixed with `--vscode-`.
*
* @sample `editorSuggestWidget.background` is `--vscode-editorSuggestWidget-background`.
*/
export function asCssVariableName(colorIdent) {
return `--vscode-${colorIdent.replace(/\./g, '-')}`;
}
export function asCssVariable(color) {
return `var(${asCssVariableName(color)})`;
}
export function asCssVariableWithDefault(color, defaultCssValue) {
return `var(${asCssVariableName(color)}, ${defaultCssValue})`;
}
export function isColorDefaults(value) {
return value !== null && typeof value === 'object' && 'light' in value && 'dark' in value;
}
// color registry
export const Extensions = {
ColorContribution: 'base.contributions.colors'
};
export const DEFAULT_COLOR_CONFIG_VALUE = 'default';
class ColorRegistry {
constructor() {
this._onDidChangeSchema = new Emitter();
this.onDidChangeSchema = this._onDidChangeSchema.event;
this.colorSchema = { type: 'object', properties: {} };
this.colorReferenceSchema = { type: 'string', enum: [], enumDescriptions: [] };
this.colorsById = {};
}
registerColor(id, defaults, description, needsTransparency = false, deprecationMessage) {
const colorContribution = { id, description, defaults, needsTransparency, deprecationMessage };
this.colorsById[id] = colorContribution;
const propertySchema = { type: 'string', description, format: 'color-hex', defaultSnippets: [{ body: '${1:#ff0000}' }] };
if (deprecationMessage) {
propertySchema.deprecationMessage = deprecationMessage;
}
if (needsTransparency) {
propertySchema.pattern = '^#(?:(?<rgba>[0-9a-fA-f]{3}[0-9a-eA-E])|(?:[0-9a-fA-F]{6}(?:(?![fF]{2})(?:[0-9a-fA-F]{2}))))?$';
propertySchema.patternErrorMessage = nls.localize('transparecyRequired', 'This color must be transparent or it will obscure content');
}
this.colorSchema.properties[id] = {
oneOf: [
propertySchema,
{ type: 'string', const: DEFAULT_COLOR_CONFIG_VALUE, description: nls.localize('useDefault', 'Use the default color.') }
]
};
this.colorReferenceSchema.enum.push(id);
this.colorReferenceSchema.enumDescriptions.push(description);
this._onDidChangeSchema.fire();
return id;
}
getColors() {
return Object.keys(this.colorsById).map(id => this.colorsById[id]);
}
resolveDefaultColor(id, theme) {
const colorDesc = this.colorsById[id];
if (colorDesc?.defaults) {
const colorValue = isColorDefaults(colorDesc.defaults) ? colorDesc.defaults[theme.type] : colorDesc.defaults;
return resolveColorValue(colorValue, theme);
}
return undefined;
}
getColorSchema() {
return this.colorSchema;
}
toString() {
const sorter = (a, b) => {
const cat1 = a.indexOf('.') === -1 ? 0 : 1;
const cat2 = b.indexOf('.') === -1 ? 0 : 1;
if (cat1 !== cat2) {
return cat1 - cat2;
}
return a.localeCompare(b);
};
return Object.keys(this.colorsById).sort(sorter).map(k => `- \`${k}\`: ${this.colorsById[k].description}`).join('\n');
}
}
const colorRegistry = new ColorRegistry();
platform.Registry.add(Extensions.ColorContribution, colorRegistry);
export function registerColor(id, defaults, description, needsTransparency, deprecationMessage) {
return colorRegistry.registerColor(id, defaults, description, needsTransparency, deprecationMessage);
}
// ----- color functions
export function executeTransform(transform, theme) {
switch (transform.op) {
case 0 /* ColorTransformType.Darken */:
return resolveColorValue(transform.value, theme)?.darken(transform.factor);
case 1 /* ColorTransformType.Lighten */:
return resolveColorValue(transform.value, theme)?.lighten(transform.factor);
case 2 /* ColorTransformType.Transparent */:
return resolveColorValue(transform.value, theme)?.transparent(transform.factor);
case 3 /* ColorTransformType.Opaque */: {
const backgroundColor = resolveColorValue(transform.background, theme);
if (!backgroundColor) {
return resolveColorValue(transform.value, theme);
}
return resolveColorValue(transform.value, theme)?.makeOpaque(backgroundColor);
}
case 4 /* ColorTransformType.OneOf */:
for (const candidate of transform.values) {
const color = resolveColorValue(candidate, theme);
if (color) {
return color;
}
}
return undefined;
case 6 /* ColorTransformType.IfDefinedThenElse */:
return resolveColorValue(theme.defines(transform.if) ? transform.then : transform.else, theme);
case 5 /* ColorTransformType.LessProminent */: {
const from = resolveColorValue(transform.value, theme);
if (!from) {
return undefined;
}
const backgroundColor = resolveColorValue(transform.background, theme);
if (!backgroundColor) {
return from.transparent(transform.factor * transform.transparency);
}
return from.isDarkerThan(backgroundColor)
? Color.getLighterColor(from, backgroundColor, transform.factor).transparent(transform.transparency)
: Color.getDarkerColor(from, backgroundColor, transform.factor).transparent(transform.transparency);
}
default:
throw assertNever(transform);
}
}
export function darken(colorValue, factor) {
return { op: 0 /* ColorTransformType.Darken */, value: colorValue, factor };
}
export function lighten(colorValue, factor) {
return { op: 1 /* ColorTransformType.Lighten */, value: colorValue, factor };
}
export function transparent(colorValue, factor) {
return { op: 2 /* ColorTransformType.Transparent */, value: colorValue, factor };
}
export function oneOf(...colorValues) {
return { op: 4 /* ColorTransformType.OneOf */, values: colorValues };
}
export function ifDefinedThenElse(ifArg, thenArg, elseArg) {
return { op: 6 /* ColorTransformType.IfDefinedThenElse */, if: ifArg, then: thenArg, else: elseArg };
}
export function lessProminent(colorValue, backgroundColorValue, factor, transparency) {
return { op: 5 /* ColorTransformType.LessProminent */, value: colorValue, background: backgroundColorValue, factor, transparency };
}
// ----- implementation
/**
* @param colorValue Resolve a color value in the context of a theme
*/
export function resolveColorValue(colorValue, theme) {
if (colorValue === null) {
return undefined;
}
else if (typeof colorValue === 'string') {
if (colorValue[0] === '#') {
return Color.fromHex(colorValue);
}
return theme.getColor(colorValue);
}
else if (colorValue instanceof Color) {
return colorValue;
}
else if (typeof colorValue === 'object') {
return executeTransform(colorValue, theme);
}
return undefined;
}
export const workbenchColorsSchemaId = 'vscode://schemas/workbench-colors';
const schemaRegistry = platform.Registry.as(JSONExtensions.JSONContribution);
schemaRegistry.registerSchema(workbenchColorsSchemaId, colorRegistry.getColorSchema());
const delayer = new RunOnceScheduler(() => schemaRegistry.notifySchemaChanged(workbenchColorsSchemaId), 200);
colorRegistry.onDidChangeSchema(() => {
if (!delayer.isScheduled()) {
delayer.schedule();
}
});
// setTimeout(_ => console.log(colorRegistry.toString()), 5000);
@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* 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 '../../../../nls.js';
// Import the effects we need
import { Color } from '../../../../base/common/color.js';
import { registerColor, transparent } from '../colorUtils.js';
export const foreground = registerColor('foreground', { dark: '#CCCCCC', light: '#616161', hcDark: '#FFFFFF', hcLight: '#292929' }, nls.localize('foreground', "Overall foreground color. This color is only used if not overridden by a component."));
export const disabledForeground = registerColor('disabledForeground', { dark: '#CCCCCC80', light: '#61616180', hcDark: '#A5A5A5', hcLight: '#7F7F7F' }, nls.localize('disabledForeground', "Overall foreground for disabled elements. This color is only used if not overridden by a component."));
export const errorForeground = registerColor('errorForeground', { dark: '#F48771', light: '#A1260D', hcDark: '#F48771', hcLight: '#B5200D' }, nls.localize('errorForeground', "Overall foreground color for error messages. This color is only used if not overridden by a component."));
export const descriptionForeground = registerColor('descriptionForeground', { light: '#717171', dark: transparent(foreground, 0.7), hcDark: transparent(foreground, 0.7), hcLight: transparent(foreground, 0.7) }, nls.localize('descriptionForeground', "Foreground color for description text providing additional information, for example for a label."));
export const iconForeground = registerColor('icon.foreground', { dark: '#C5C5C5', light: '#424242', hcDark: '#FFFFFF', hcLight: '#292929' }, nls.localize('iconForeground', "The default color for icons in the workbench."));
export const focusBorder = registerColor('focusBorder', { dark: '#007FD4', light: '#0090F1', hcDark: '#F38518', hcLight: '#006BBD' }, nls.localize('focusBorder', "Overall border color for focused elements. This color is only used if not overridden by a component."));
export const contrastBorder = registerColor('contrastBorder', { light: null, dark: null, hcDark: '#6FC3DF', hcLight: '#0F4A85' }, nls.localize('contrastBorder', "An extra border around elements to separate them from others for greater contrast."));
export const activeContrastBorder = registerColor('contrastActiveBorder', { light: null, dark: null, hcDark: focusBorder, hcLight: focusBorder }, nls.localize('activeContrastBorder', "An extra border around active elements to separate them from others for greater contrast."));
export const selectionBackground = registerColor('selection.background', null, nls.localize('selectionBackground', "The background color of text selections in the workbench (e.g. for input fields or text areas). Note that this does not apply to selections within the editor."));
// ------ text link
export const textLinkForeground = registerColor('textLink.foreground', { light: '#006AB1', dark: '#3794FF', hcDark: '#21A6FF', hcLight: '#0F4A85' }, nls.localize('textLinkForeground', "Foreground color for links in text."));
export const textLinkActiveForeground = registerColor('textLink.activeForeground', { light: '#006AB1', dark: '#3794FF', hcDark: '#21A6FF', hcLight: '#0F4A85' }, nls.localize('textLinkActiveForeground', "Foreground color for links in text when clicked on and on mouse hover."));
export const textSeparatorForeground = registerColor('textSeparator.foreground', { light: '#0000002e', dark: '#ffffff2e', hcDark: Color.black, hcLight: '#292929' }, nls.localize('textSeparatorForeground', "Color for text separators."));
// ------ text preformat
export const textPreformatForeground = registerColor('textPreformat.foreground', { light: '#A31515', dark: '#D7BA7D', hcDark: '#000000', hcLight: '#FFFFFF' }, nls.localize('textPreformatForeground', "Foreground color for preformatted text segments."));
export const textPreformatBackground = registerColor('textPreformat.background', { light: '#0000001A', dark: '#FFFFFF1A', hcDark: '#FFFFFF', hcLight: '#09345f' }, nls.localize('textPreformatBackground', "Background color for preformatted text segments."));
// ------ text block quote
export const textBlockQuoteBackground = registerColor('textBlockQuote.background', { light: '#f2f2f2', dark: '#222222', hcDark: null, hcLight: '#F2F2F2' }, nls.localize('textBlockQuoteBackground', "Background color for block quotes in text."));
export const textBlockQuoteBorder = registerColor('textBlockQuote.border', { light: '#007acc80', dark: '#007acc80', hcDark: Color.white, hcLight: '#292929' }, nls.localize('textBlockQuoteBorder', "Border color for block quotes in text."));
// ------ text code block
export const textCodeBlockBackground = registerColor('textCodeBlock.background', { light: '#dcdcdc66', dark: '#0a0a0a66', hcDark: Color.black, hcLight: '#F2F2F2' }, nls.localize('textCodeBlockBackground', "Background color for code blocks in text."));
@@ -0,0 +1,17 @@
/*---------------------------------------------------------------------------------------------
* 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 '../../../../nls.js';
import { registerColor, transparent } from '../colorUtils.js';
import { foreground } from './baseColors.js';
import { editorErrorForeground, editorInfoForeground, editorWarningForeground } from './editorColors.js';
import { minimapFindMatch } from './minimapColors.js';
export const chartsForeground = registerColor('charts.foreground', foreground, nls.localize('chartsForeground', "The foreground color used in charts."));
export const chartsLines = registerColor('charts.lines', transparent(foreground, .5), nls.localize('chartsLines', "The color used for horizontal lines in charts."));
export const chartsRed = registerColor('charts.red', editorErrorForeground, nls.localize('chartsRed', "The red color used in chart visualizations."));
export const chartsBlue = registerColor('charts.blue', editorInfoForeground, nls.localize('chartsBlue', "The blue color used in chart visualizations."));
export const chartsYellow = registerColor('charts.yellow', editorWarningForeground, nls.localize('chartsYellow', "The yellow color used in chart visualizations."));
export const chartsOrange = registerColor('charts.orange', minimapFindMatch, nls.localize('chartsOrange', "The orange color used in chart visualizations."));
export const chartsGreen = registerColor('charts.green', { dark: '#89D185', light: '#388A34', hcDark: '#89D185', hcLight: '#374e06' }, nls.localize('chartsGreen', "The green color used in chart visualizations."));
export const chartsPurple = registerColor('charts.purple', { dark: '#B180D7', light: '#652D90', hcDark: '#B180D7', hcLight: '#652D90' }, nls.localize('chartsPurple', "The purple color used in chart visualizations."));
@@ -0,0 +1,125 @@
/*---------------------------------------------------------------------------------------------
* 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 '../../../../nls.js';
// Import the effects we need
import { Color, RGBA } from '../../../../base/common/color.js';
import { registerColor, transparent, lessProminent, darken, lighten } from '../colorUtils.js';
// Import the colors we need
import { foreground, contrastBorder, activeContrastBorder } from './baseColors.js';
import { scrollbarShadow, badgeBackground } from './miscColors.js';
// ----- editor
export const editorBackground = registerColor('editor.background', { light: '#ffffff', dark: '#1E1E1E', hcDark: Color.black, hcLight: Color.white }, nls.localize('editorBackground', "Editor background color."));
export const editorForeground = registerColor('editor.foreground', { light: '#333333', dark: '#BBBBBB', hcDark: Color.white, hcLight: foreground }, nls.localize('editorForeground', "Editor default foreground color."));
export const editorStickyScrollBackground = registerColor('editorStickyScroll.background', editorBackground, nls.localize('editorStickyScrollBackground', "Background color of sticky scroll in the editor"));
export const editorStickyScrollHoverBackground = registerColor('editorStickyScrollHover.background', { dark: '#2A2D2E', light: '#F0F0F0', hcDark: null, hcLight: Color.fromHex('#0F4A85').transparent(0.1) }, nls.localize('editorStickyScrollHoverBackground', "Background color of sticky scroll on hover in the editor"));
export const editorStickyScrollBorder = registerColor('editorStickyScroll.border', { dark: null, light: null, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('editorStickyScrollBorder', "Border color of sticky scroll in the editor"));
export const editorStickyScrollShadow = registerColor('editorStickyScroll.shadow', scrollbarShadow, nls.localize('editorStickyScrollShadow', " Shadow color of sticky scroll in the editor"));
export const editorWidgetBackground = registerColor('editorWidget.background', { dark: '#252526', light: '#F3F3F3', hcDark: '#0C141F', hcLight: Color.white }, nls.localize('editorWidgetBackground', 'Background color of editor widgets, such as find/replace.'));
export const editorWidgetForeground = registerColor('editorWidget.foreground', foreground, nls.localize('editorWidgetForeground', 'Foreground color of editor widgets, such as find/replace.'));
export const editorWidgetBorder = registerColor('editorWidget.border', { dark: '#454545', light: '#C8C8C8', hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('editorWidgetBorder', 'Border color of editor widgets. The color is only used if the widget chooses to have a border and if the color is not overridden by a widget.'));
export const editorWidgetResizeBorder = registerColor('editorWidget.resizeBorder', null, nls.localize('editorWidgetResizeBorder', "Border color of the resize bar of editor widgets. The color is only used if the widget chooses to have a resize border and if the color is not overridden by a widget."));
export const editorErrorBackground = registerColor('editorError.background', null, nls.localize('editorError.background', 'Background color of error text in the editor. The color must not be opaque so as not to hide underlying decorations.'), true);
export const editorErrorForeground = registerColor('editorError.foreground', { dark: '#F14C4C', light: '#E51400', hcDark: '#F48771', hcLight: '#B5200D' }, nls.localize('editorError.foreground', 'Foreground color of error squigglies in the editor.'));
export const editorErrorBorder = registerColor('editorError.border', { dark: null, light: null, hcDark: Color.fromHex('#E47777').transparent(0.8), hcLight: '#B5200D' }, nls.localize('errorBorder', 'If set, color of double underlines for errors in the editor.'));
export const editorWarningBackground = registerColor('editorWarning.background', null, nls.localize('editorWarning.background', 'Background color of warning text in the editor. The color must not be opaque so as not to hide underlying decorations.'), true);
export const editorWarningForeground = registerColor('editorWarning.foreground', { dark: '#CCA700', light: '#BF8803', hcDark: '#FFD370', hcLight: '#895503' }, nls.localize('editorWarning.foreground', 'Foreground color of warning squigglies in the editor.'));
export const editorWarningBorder = registerColor('editorWarning.border', { dark: null, light: null, hcDark: Color.fromHex('#FFCC00').transparent(0.8), hcLight: Color.fromHex('#FFCC00').transparent(0.8) }, nls.localize('warningBorder', 'If set, color of double underlines for warnings in the editor.'));
export const editorInfoBackground = registerColor('editorInfo.background', null, nls.localize('editorInfo.background', 'Background color of info text in the editor. The color must not be opaque so as not to hide underlying decorations.'), true);
export const editorInfoForeground = registerColor('editorInfo.foreground', { dark: '#3794FF', light: '#1a85ff', hcDark: '#3794FF', hcLight: '#1a85ff' }, nls.localize('editorInfo.foreground', 'Foreground color of info squigglies in the editor.'));
export const editorInfoBorder = registerColor('editorInfo.border', { dark: null, light: null, hcDark: Color.fromHex('#3794FF').transparent(0.8), hcLight: '#292929' }, nls.localize('infoBorder', 'If set, color of double underlines for infos in the editor.'));
export const editorHintForeground = registerColor('editorHint.foreground', { dark: Color.fromHex('#eeeeee').transparent(0.7), light: '#6c6c6c', hcDark: null, hcLight: null }, nls.localize('editorHint.foreground', 'Foreground color of hint squigglies in the editor.'));
export const editorHintBorder = registerColor('editorHint.border', { dark: null, light: null, hcDark: Color.fromHex('#eeeeee').transparent(0.8), hcLight: '#292929' }, nls.localize('hintBorder', 'If set, color of double underlines for hints in the editor.'));
export const editorActiveLinkForeground = registerColor('editorLink.activeForeground', { dark: '#4E94CE', light: Color.blue, hcDark: Color.cyan, hcLight: '#292929' }, nls.localize('activeLinkForeground', 'Color of active links.'));
// ----- editor selection
export const editorSelectionBackground = registerColor('editor.selectionBackground', { light: '#ADD6FF', dark: '#264F78', hcDark: '#f3f518', hcLight: '#0F4A85' }, nls.localize('editorSelectionBackground', "Color of the editor selection."));
export const editorSelectionForeground = registerColor('editor.selectionForeground', { light: null, dark: null, hcDark: '#000000', hcLight: Color.white }, nls.localize('editorSelectionForeground', "Color of the selected text for high contrast."));
export const editorInactiveSelection = registerColor('editor.inactiveSelectionBackground', { light: transparent(editorSelectionBackground, 0.5), dark: transparent(editorSelectionBackground, 0.5), hcDark: transparent(editorSelectionBackground, 0.7), hcLight: transparent(editorSelectionBackground, 0.5) }, nls.localize('editorInactiveSelection', "Color of the selection in an inactive editor. The color must not be opaque so as not to hide underlying decorations."), true);
export const editorSelectionHighlight = registerColor('editor.selectionHighlightBackground', { light: lessProminent(editorSelectionBackground, editorBackground, 0.3, 0.6), dark: lessProminent(editorSelectionBackground, editorBackground, 0.3, 0.6), hcDark: null, hcLight: null }, nls.localize('editorSelectionHighlight', 'Color for regions with the same content as the selection. The color must not be opaque so as not to hide underlying decorations.'), true);
export const editorSelectionHighlightBorder = registerColor('editor.selectionHighlightBorder', { light: null, dark: null, hcDark: activeContrastBorder, hcLight: activeContrastBorder }, nls.localize('editorSelectionHighlightBorder', "Border color for regions with the same content as the selection."));
// ----- editor find
export const editorFindMatch = registerColor('editor.findMatchBackground', { light: '#A8AC94', dark: '#515C6A', hcDark: null, hcLight: null }, nls.localize('editorFindMatch', "Color of the current search match."));
export const editorFindMatchForeground = registerColor('editor.findMatchForeground', null, nls.localize('editorFindMatchForeground', "Text color of the current search match."));
export const editorFindMatchHighlight = registerColor('editor.findMatchHighlightBackground', { light: '#EA5C0055', dark: '#EA5C0055', hcDark: null, hcLight: null }, nls.localize('findMatchHighlight', "Color of the other search matches. The color must not be opaque so as not to hide underlying decorations."), true);
export const editorFindMatchHighlightForeground = registerColor('editor.findMatchHighlightForeground', null, nls.localize('findMatchHighlightForeground', "Foreground color of the other search matches."), true);
export const editorFindRangeHighlight = registerColor('editor.findRangeHighlightBackground', { dark: '#3a3d4166', light: '#b4b4b44d', hcDark: null, hcLight: null }, nls.localize('findRangeHighlight', "Color of the range limiting the search. The color must not be opaque so as not to hide underlying decorations."), true);
export const editorFindMatchBorder = registerColor('editor.findMatchBorder', { light: null, dark: null, hcDark: activeContrastBorder, hcLight: activeContrastBorder }, nls.localize('editorFindMatchBorder', "Border color of the current search match."));
export const editorFindMatchHighlightBorder = registerColor('editor.findMatchHighlightBorder', { light: null, dark: null, hcDark: activeContrastBorder, hcLight: activeContrastBorder }, nls.localize('findMatchHighlightBorder', "Border color of the other search matches."));
export const editorFindRangeHighlightBorder = registerColor('editor.findRangeHighlightBorder', { dark: null, light: null, hcDark: transparent(activeContrastBorder, 0.4), hcLight: transparent(activeContrastBorder, 0.4) }, nls.localize('findRangeHighlightBorder', "Border color of the range limiting the search. The color must not be opaque so as not to hide underlying decorations."), true);
// ----- editor hover
export const editorHoverHighlight = registerColor('editor.hoverHighlightBackground', { light: '#ADD6FF26', dark: '#264f7840', hcDark: '#ADD6FF26', hcLight: null }, nls.localize('hoverHighlight', 'Highlight below the word for which a hover is shown. The color must not be opaque so as not to hide underlying decorations.'), true);
export const editorHoverBackground = registerColor('editorHoverWidget.background', editorWidgetBackground, nls.localize('hoverBackground', 'Background color of the editor hover.'));
export const editorHoverForeground = registerColor('editorHoverWidget.foreground', editorWidgetForeground, nls.localize('hoverForeground', 'Foreground color of the editor hover.'));
export const editorHoverBorder = registerColor('editorHoverWidget.border', editorWidgetBorder, nls.localize('hoverBorder', 'Border color of the editor hover.'));
export const editorHoverStatusBarBackground = registerColor('editorHoverWidget.statusBarBackground', { dark: lighten(editorHoverBackground, 0.2), light: darken(editorHoverBackground, 0.05), hcDark: editorWidgetBackground, hcLight: editorWidgetBackground }, nls.localize('statusBarBackground', "Background color of the editor hover status bar."));
// ----- editor inlay hint
export const editorInlayHintForeground = registerColor('editorInlayHint.foreground', { dark: '#969696', light: '#969696', hcDark: Color.white, hcLight: Color.black }, nls.localize('editorInlayHintForeground', 'Foreground color of inline hints'));
export const editorInlayHintBackground = registerColor('editorInlayHint.background', { dark: transparent(badgeBackground, .10), light: transparent(badgeBackground, .10), hcDark: transparent(Color.white, .10), hcLight: transparent(badgeBackground, .10) }, nls.localize('editorInlayHintBackground', 'Background color of inline hints'));
export const editorInlayHintTypeForeground = registerColor('editorInlayHint.typeForeground', editorInlayHintForeground, nls.localize('editorInlayHintForegroundTypes', 'Foreground color of inline hints for types'));
export const editorInlayHintTypeBackground = registerColor('editorInlayHint.typeBackground', editorInlayHintBackground, nls.localize('editorInlayHintBackgroundTypes', 'Background color of inline hints for types'));
export const editorInlayHintParameterForeground = registerColor('editorInlayHint.parameterForeground', editorInlayHintForeground, nls.localize('editorInlayHintForegroundParameter', 'Foreground color of inline hints for parameters'));
export const editorInlayHintParameterBackground = registerColor('editorInlayHint.parameterBackground', editorInlayHintBackground, nls.localize('editorInlayHintBackgroundParameter', 'Background color of inline hints for parameters'));
// ----- editor lightbulb
export const editorLightBulbForeground = registerColor('editorLightBulb.foreground', { dark: '#FFCC00', light: '#DDB100', hcDark: '#FFCC00', hcLight: '#007ACC' }, nls.localize('editorLightBulbForeground', "The color used for the lightbulb actions icon."));
export const editorLightBulbAutoFixForeground = registerColor('editorLightBulbAutoFix.foreground', { dark: '#75BEFF', light: '#007ACC', hcDark: '#75BEFF', hcLight: '#007ACC' }, nls.localize('editorLightBulbAutoFixForeground', "The color used for the lightbulb auto fix actions icon."));
export const editorLightBulbAiForeground = registerColor('editorLightBulbAi.foreground', editorLightBulbForeground, nls.localize('editorLightBulbAiForeground', "The color used for the lightbulb AI icon."));
// ----- editor snippet
export const snippetTabstopHighlightBackground = registerColor('editor.snippetTabstopHighlightBackground', { dark: new Color(new RGBA(124, 124, 124, 0.3)), light: new Color(new RGBA(10, 50, 100, 0.2)), hcDark: new Color(new RGBA(124, 124, 124, 0.3)), hcLight: new Color(new RGBA(10, 50, 100, 0.2)) }, nls.localize('snippetTabstopHighlightBackground', "Highlight background color of a snippet tabstop."));
export const snippetTabstopHighlightBorder = registerColor('editor.snippetTabstopHighlightBorder', null, nls.localize('snippetTabstopHighlightBorder', "Highlight border color of a snippet tabstop."));
export const snippetFinalTabstopHighlightBackground = registerColor('editor.snippetFinalTabstopHighlightBackground', null, nls.localize('snippetFinalTabstopHighlightBackground', "Highlight background color of the final tabstop of a snippet."));
export const snippetFinalTabstopHighlightBorder = registerColor('editor.snippetFinalTabstopHighlightBorder', { dark: '#525252', light: new Color(new RGBA(10, 50, 100, 0.5)), hcDark: '#525252', hcLight: '#292929' }, nls.localize('snippetFinalTabstopHighlightBorder', "Highlight border color of the final tabstop of a snippet."));
// ----- diff editor
export const defaultInsertColor = new Color(new RGBA(155, 185, 85, .2));
export const defaultRemoveColor = new Color(new RGBA(255, 0, 0, .2));
export const diffInserted = registerColor('diffEditor.insertedTextBackground', { dark: '#9ccc2c33', light: '#9ccc2c40', hcDark: null, hcLight: null }, nls.localize('diffEditorInserted', 'Background color for text that got inserted. The color must not be opaque so as not to hide underlying decorations.'), true);
export const diffRemoved = registerColor('diffEditor.removedTextBackground', { dark: '#ff000033', light: '#ff000033', hcDark: null, hcLight: null }, nls.localize('diffEditorRemoved', 'Background color for text that got removed. The color must not be opaque so as not to hide underlying decorations.'), true);
export const diffInsertedLine = registerColor('diffEditor.insertedLineBackground', { dark: defaultInsertColor, light: defaultInsertColor, hcDark: null, hcLight: null }, nls.localize('diffEditorInsertedLines', 'Background color for lines that got inserted. The color must not be opaque so as not to hide underlying decorations.'), true);
export const diffRemovedLine = registerColor('diffEditor.removedLineBackground', { dark: defaultRemoveColor, light: defaultRemoveColor, hcDark: null, hcLight: null }, nls.localize('diffEditorRemovedLines', 'Background color for lines that got removed. The color must not be opaque so as not to hide underlying decorations.'), true);
export const diffInsertedLineGutter = registerColor('diffEditorGutter.insertedLineBackground', null, nls.localize('diffEditorInsertedLineGutter', 'Background color for the margin where lines got inserted.'));
export const diffRemovedLineGutter = registerColor('diffEditorGutter.removedLineBackground', null, nls.localize('diffEditorRemovedLineGutter', 'Background color for the margin where lines got removed.'));
export const diffOverviewRulerInserted = registerColor('diffEditorOverview.insertedForeground', null, nls.localize('diffEditorOverviewInserted', 'Diff overview ruler foreground for inserted content.'));
export const diffOverviewRulerRemoved = registerColor('diffEditorOverview.removedForeground', null, nls.localize('diffEditorOverviewRemoved', 'Diff overview ruler foreground for removed content.'));
export const diffInsertedOutline = registerColor('diffEditor.insertedTextBorder', { dark: null, light: null, hcDark: '#33ff2eff', hcLight: '#374E06' }, nls.localize('diffEditorInsertedOutline', 'Outline color for the text that got inserted.'));
export const diffRemovedOutline = registerColor('diffEditor.removedTextBorder', { dark: null, light: null, hcDark: '#FF008F', hcLight: '#AD0707' }, nls.localize('diffEditorRemovedOutline', 'Outline color for text that got removed.'));
export const diffBorder = registerColor('diffEditor.border', { dark: null, light: null, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('diffEditorBorder', 'Border color between the two text editors.'));
export const diffDiagonalFill = registerColor('diffEditor.diagonalFill', { dark: '#cccccc33', light: '#22222233', hcDark: null, hcLight: null }, nls.localize('diffDiagonalFill', "Color of the diff editor's diagonal fill. The diagonal fill is used in side-by-side diff views."));
export const diffUnchangedRegionBackground = registerColor('diffEditor.unchangedRegionBackground', 'sideBar.background', nls.localize('diffEditor.unchangedRegionBackground', "The background color of unchanged blocks in the diff editor."));
export const diffUnchangedRegionForeground = registerColor('diffEditor.unchangedRegionForeground', 'foreground', nls.localize('diffEditor.unchangedRegionForeground', "The foreground color of unchanged blocks in the diff editor."));
export const diffUnchangedTextBackground = registerColor('diffEditor.unchangedCodeBackground', { dark: '#74747429', light: '#b8b8b829', hcDark: null, hcLight: null }, nls.localize('diffEditor.unchangedCodeBackground', "The background color of unchanged code in the diff editor."));
// ----- widget
export const widgetShadow = registerColor('widget.shadow', { dark: transparent(Color.black, .36), light: transparent(Color.black, .16), hcDark: null, hcLight: null }, nls.localize('widgetShadow', 'Shadow color of widgets such as find/replace inside the editor.'));
export const widgetBorder = registerColor('widget.border', { dark: null, light: null, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('widgetBorder', 'Border color of widgets such as find/replace inside the editor.'));
// ----- toolbar
export const toolbarHoverBackground = registerColor('toolbar.hoverBackground', { dark: '#5a5d5e50', light: '#b8b8b850', hcDark: null, hcLight: null }, nls.localize('toolbarHoverBackground', "Toolbar background when hovering over actions using the mouse"));
export const toolbarHoverOutline = registerColor('toolbar.hoverOutline', { dark: null, light: null, hcDark: activeContrastBorder, hcLight: activeContrastBorder }, nls.localize('toolbarHoverOutline', "Toolbar outline when hovering over actions using the mouse"));
export const toolbarActiveBackground = registerColor('toolbar.activeBackground', { dark: lighten(toolbarHoverBackground, 0.1), light: darken(toolbarHoverBackground, 0.1), hcDark: null, hcLight: null }, nls.localize('toolbarActiveBackground', "Toolbar background when holding the mouse over actions"));
// ----- breadcumbs
export const breadcrumbsForeground = registerColor('breadcrumb.foreground', transparent(foreground, 0.8), nls.localize('breadcrumbsFocusForeground', "Color of focused breadcrumb items."));
export const breadcrumbsBackground = registerColor('breadcrumb.background', editorBackground, nls.localize('breadcrumbsBackground', "Background color of breadcrumb items."));
export const breadcrumbsFocusForeground = registerColor('breadcrumb.focusForeground', { light: darken(foreground, 0.2), dark: lighten(foreground, 0.1), hcDark: lighten(foreground, 0.1), hcLight: lighten(foreground, 0.1) }, nls.localize('breadcrumbsFocusForeground', "Color of focused breadcrumb items."));
export const breadcrumbsActiveSelectionForeground = registerColor('breadcrumb.activeSelectionForeground', { light: darken(foreground, 0.2), dark: lighten(foreground, 0.1), hcDark: lighten(foreground, 0.1), hcLight: lighten(foreground, 0.1) }, nls.localize('breadcrumbsSelectedForeground', "Color of selected breadcrumb items."));
export const breadcrumbsPickerBackground = registerColor('breadcrumbPicker.background', editorWidgetBackground, nls.localize('breadcrumbsSelectedBackground', "Background color of breadcrumb item picker."));
// ----- merge
const headerTransparency = 0.5;
const currentBaseColor = Color.fromHex('#40C8AE').transparent(headerTransparency);
const incomingBaseColor = Color.fromHex('#40A6FF').transparent(headerTransparency);
const commonBaseColor = Color.fromHex('#606060').transparent(0.4);
const contentTransparency = 0.4;
const rulerTransparency = 1;
export const mergeCurrentHeaderBackground = registerColor('merge.currentHeaderBackground', { dark: currentBaseColor, light: currentBaseColor, hcDark: null, hcLight: null }, nls.localize('mergeCurrentHeaderBackground', 'Current header background in inline merge-conflicts. The color must not be opaque so as not to hide underlying decorations.'), true);
export const mergeCurrentContentBackground = registerColor('merge.currentContentBackground', transparent(mergeCurrentHeaderBackground, contentTransparency), nls.localize('mergeCurrentContentBackground', 'Current content background in inline merge-conflicts. The color must not be opaque so as not to hide underlying decorations.'), true);
export const mergeIncomingHeaderBackground = registerColor('merge.incomingHeaderBackground', { dark: incomingBaseColor, light: incomingBaseColor, hcDark: null, hcLight: null }, nls.localize('mergeIncomingHeaderBackground', 'Incoming header background in inline merge-conflicts. The color must not be opaque so as not to hide underlying decorations.'), true);
export const mergeIncomingContentBackground = registerColor('merge.incomingContentBackground', transparent(mergeIncomingHeaderBackground, contentTransparency), nls.localize('mergeIncomingContentBackground', 'Incoming content background in inline merge-conflicts. The color must not be opaque so as not to hide underlying decorations.'), true);
export const mergeCommonHeaderBackground = registerColor('merge.commonHeaderBackground', { dark: commonBaseColor, light: commonBaseColor, hcDark: null, hcLight: null }, nls.localize('mergeCommonHeaderBackground', 'Common ancestor header background in inline merge-conflicts. The color must not be opaque so as not to hide underlying decorations.'), true);
export const mergeCommonContentBackground = registerColor('merge.commonContentBackground', transparent(mergeCommonHeaderBackground, contentTransparency), nls.localize('mergeCommonContentBackground', 'Common ancestor content background in inline merge-conflicts. The color must not be opaque so as not to hide underlying decorations.'), true);
export const mergeBorder = registerColor('merge.border', { dark: null, light: null, hcDark: '#C3DF6F', hcLight: '#007ACC' }, nls.localize('mergeBorder', 'Border color on headers and the splitter in inline merge-conflicts.'));
export const overviewRulerCurrentContentForeground = registerColor('editorOverviewRuler.currentContentForeground', { dark: transparent(mergeCurrentHeaderBackground, rulerTransparency), light: transparent(mergeCurrentHeaderBackground, rulerTransparency), hcDark: mergeBorder, hcLight: mergeBorder }, nls.localize('overviewRulerCurrentContentForeground', 'Current overview ruler foreground for inline merge-conflicts.'));
export const overviewRulerIncomingContentForeground = registerColor('editorOverviewRuler.incomingContentForeground', { dark: transparent(mergeIncomingHeaderBackground, rulerTransparency), light: transparent(mergeIncomingHeaderBackground, rulerTransparency), hcDark: mergeBorder, hcLight: mergeBorder }, nls.localize('overviewRulerIncomingContentForeground', 'Incoming overview ruler foreground for inline merge-conflicts.'));
export const overviewRulerCommonContentForeground = registerColor('editorOverviewRuler.commonContentForeground', { dark: transparent(mergeCommonHeaderBackground, rulerTransparency), light: transparent(mergeCommonHeaderBackground, rulerTransparency), hcDark: mergeBorder, hcLight: mergeBorder }, nls.localize('overviewRulerCommonContentForeground', 'Common ancestor overview ruler foreground for inline merge-conflicts.'));
export const overviewRulerFindMatchForeground = registerColor('editorOverviewRuler.findMatchForeground', { dark: '#d186167e', light: '#d186167e', hcDark: '#AB5A00', hcLight: '' }, nls.localize('overviewRulerFindMatchForeground', 'Overview ruler marker color for find matches. The color must not be opaque so as not to hide underlying decorations.'), true);
export const overviewRulerSelectionHighlightForeground = registerColor('editorOverviewRuler.selectionHighlightForeground', '#A0A0A0CC', nls.localize('overviewRulerSelectionHighlightForeground', 'Overview ruler marker color for selection highlights. The color must not be opaque so as not to hide underlying decorations.'), true);
// ----- problems
export const problemsErrorIconForeground = registerColor('problemsErrorIcon.foreground', editorErrorForeground, nls.localize('problemsErrorIconForeground', "The color used for the problems error icon."));
export const problemsWarningIconForeground = registerColor('problemsWarningIcon.foreground', editorWarningForeground, nls.localize('problemsWarningIconForeground', "The color used for the problems warning icon."));
export const problemsInfoIconForeground = registerColor('problemsInfoIcon.foreground', editorInfoForeground, nls.localize('problemsInfoIconForeground', "The color used for the problems info icon."));
@@ -0,0 +1,63 @@
/*---------------------------------------------------------------------------------------------
* 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 '../../../../nls.js';
// Import the effects we need
import { Color, RGBA } from '../../../../base/common/color.js';
import { registerColor, transparent, lighten, darken } from '../colorUtils.js';
// Import the colors we need
import { foreground, contrastBorder, focusBorder, iconForeground } from './baseColors.js';
import { editorWidgetBackground } from './editorColors.js';
// ----- input
export const inputBackground = registerColor('input.background', { dark: '#3C3C3C', light: Color.white, hcDark: Color.black, hcLight: Color.white }, nls.localize('inputBoxBackground', "Input box background."));
export const inputForeground = registerColor('input.foreground', foreground, nls.localize('inputBoxForeground', "Input box foreground."));
export const inputBorder = registerColor('input.border', { dark: null, light: null, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('inputBoxBorder', "Input box border."));
export const inputActiveOptionBorder = registerColor('inputOption.activeBorder', { dark: '#007ACC', light: '#007ACC', hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('inputBoxActiveOptionBorder', "Border color of activated options in input fields."));
export const inputActiveOptionHoverBackground = registerColor('inputOption.hoverBackground', { dark: '#5a5d5e80', light: '#b8b8b850', hcDark: null, hcLight: null }, nls.localize('inputOption.hoverBackground', "Background color of activated options in input fields."));
export const inputActiveOptionBackground = registerColor('inputOption.activeBackground', { dark: transparent(focusBorder, 0.4), light: transparent(focusBorder, 0.2), hcDark: Color.transparent, hcLight: Color.transparent }, nls.localize('inputOption.activeBackground', "Background hover color of options in input fields."));
export const inputActiveOptionForeground = registerColor('inputOption.activeForeground', { dark: Color.white, light: Color.black, hcDark: foreground, hcLight: foreground }, nls.localize('inputOption.activeForeground', "Foreground color of activated options in input fields."));
export const inputPlaceholderForeground = registerColor('input.placeholderForeground', { light: transparent(foreground, 0.5), dark: transparent(foreground, 0.5), hcDark: transparent(foreground, 0.7), hcLight: transparent(foreground, 0.7) }, nls.localize('inputPlaceholderForeground', "Input box foreground color for placeholder text."));
// ----- input validation
export const inputValidationInfoBackground = registerColor('inputValidation.infoBackground', { dark: '#063B49', light: '#D6ECF2', hcDark: Color.black, hcLight: Color.white }, nls.localize('inputValidationInfoBackground', "Input validation background color for information severity."));
export const inputValidationInfoForeground = registerColor('inputValidation.infoForeground', { dark: null, light: null, hcDark: null, hcLight: foreground }, nls.localize('inputValidationInfoForeground', "Input validation foreground color for information severity."));
export const inputValidationInfoBorder = registerColor('inputValidation.infoBorder', { dark: '#007acc', light: '#007acc', hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('inputValidationInfoBorder', "Input validation border color for information severity."));
export const inputValidationWarningBackground = registerColor('inputValidation.warningBackground', { dark: '#352A05', light: '#F6F5D2', hcDark: Color.black, hcLight: Color.white }, nls.localize('inputValidationWarningBackground', "Input validation background color for warning severity."));
export const inputValidationWarningForeground = registerColor('inputValidation.warningForeground', { dark: null, light: null, hcDark: null, hcLight: foreground }, nls.localize('inputValidationWarningForeground', "Input validation foreground color for warning severity."));
export const inputValidationWarningBorder = registerColor('inputValidation.warningBorder', { dark: '#B89500', light: '#B89500', hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('inputValidationWarningBorder', "Input validation border color for warning severity."));
export const inputValidationErrorBackground = registerColor('inputValidation.errorBackground', { dark: '#5A1D1D', light: '#F2DEDE', hcDark: Color.black, hcLight: Color.white }, nls.localize('inputValidationErrorBackground', "Input validation background color for error severity."));
export const inputValidationErrorForeground = registerColor('inputValidation.errorForeground', { dark: null, light: null, hcDark: null, hcLight: foreground }, nls.localize('inputValidationErrorForeground', "Input validation foreground color for error severity."));
export const inputValidationErrorBorder = registerColor('inputValidation.errorBorder', { dark: '#BE1100', light: '#BE1100', hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('inputValidationErrorBorder', "Input validation border color for error severity."));
// ----- select
export const selectBackground = registerColor('dropdown.background', { dark: '#3C3C3C', light: Color.white, hcDark: Color.black, hcLight: Color.white }, nls.localize('dropdownBackground', "Dropdown background."));
export const selectListBackground = registerColor('dropdown.listBackground', { dark: null, light: null, hcDark: Color.black, hcLight: Color.white }, nls.localize('dropdownListBackground', "Dropdown list background."));
export const selectForeground = registerColor('dropdown.foreground', { dark: '#F0F0F0', light: foreground, hcDark: Color.white, hcLight: foreground }, nls.localize('dropdownForeground', "Dropdown foreground."));
export const selectBorder = registerColor('dropdown.border', { dark: selectBackground, light: '#CECECE', hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('dropdownBorder', "Dropdown border."));
// ------ button
export const buttonForeground = registerColor('button.foreground', Color.white, nls.localize('buttonForeground', "Button foreground color."));
export const buttonSeparator = registerColor('button.separator', transparent(buttonForeground, .4), nls.localize('buttonSeparator', "Button separator color."));
export const buttonBackground = registerColor('button.background', { dark: '#0E639C', light: '#007ACC', hcDark: null, hcLight: '#0F4A85' }, nls.localize('buttonBackground', "Button background color."));
export const buttonHoverBackground = registerColor('button.hoverBackground', { dark: lighten(buttonBackground, 0.2), light: darken(buttonBackground, 0.2), hcDark: buttonBackground, hcLight: buttonBackground }, nls.localize('buttonHoverBackground', "Button background color when hovering."));
export const buttonBorder = registerColor('button.border', contrastBorder, nls.localize('buttonBorder', "Button border color."));
export const buttonSecondaryForeground = registerColor('button.secondaryForeground', { dark: Color.white, light: Color.white, hcDark: Color.white, hcLight: foreground }, nls.localize('buttonSecondaryForeground', "Secondary button foreground color."));
export const buttonSecondaryBackground = registerColor('button.secondaryBackground', { dark: '#3A3D41', light: '#5F6A79', hcDark: null, hcLight: Color.white }, nls.localize('buttonSecondaryBackground', "Secondary button background color."));
export const buttonSecondaryHoverBackground = registerColor('button.secondaryHoverBackground', { dark: lighten(buttonSecondaryBackground, 0.2), light: darken(buttonSecondaryBackground, 0.2), hcDark: null, hcLight: null }, nls.localize('buttonSecondaryHoverBackground', "Secondary button background color when hovering."));
// ------ radio
export const radioActiveForeground = registerColor('radio.activeForeground', inputActiveOptionForeground, nls.localize('radioActiveForeground', "Foreground color of active radio option."));
export const radioActiveBackground = registerColor('radio.activeBackground', inputActiveOptionBackground, nls.localize('radioBackground', "Background color of active radio option."));
export const radioActiveBorder = registerColor('radio.activeBorder', inputActiveOptionBorder, nls.localize('radioActiveBorder', "Border color of the active radio option."));
export const radioInactiveForeground = registerColor('radio.inactiveForeground', null, nls.localize('radioInactiveForeground', "Foreground color of inactive radio option."));
export const radioInactiveBackground = registerColor('radio.inactiveBackground', null, nls.localize('radioInactiveBackground', "Background color of inactive radio option."));
export const radioInactiveBorder = registerColor('radio.inactiveBorder', { light: transparent(radioActiveForeground, .2), dark: transparent(radioActiveForeground, .2), hcDark: transparent(radioActiveForeground, .4), hcLight: transparent(radioActiveForeground, .2) }, nls.localize('radioInactiveBorder', "Border color of the inactive radio option."));
export const radioInactiveHoverBackground = registerColor('radio.inactiveHoverBackground', inputActiveOptionHoverBackground, nls.localize('radioHoverBackground', "Background color of inactive active radio option when hovering."));
// ------ checkbox
export const checkboxBackground = registerColor('checkbox.background', selectBackground, nls.localize('checkbox.background', "Background color of checkbox widget."));
export const checkboxSelectBackground = registerColor('checkbox.selectBackground', editorWidgetBackground, nls.localize('checkbox.select.background', "Background color of checkbox widget when the element it's in is selected."));
export const checkboxForeground = registerColor('checkbox.foreground', selectForeground, nls.localize('checkbox.foreground', "Foreground color of checkbox widget."));
export const checkboxBorder = registerColor('checkbox.border', selectBorder, nls.localize('checkbox.border', "Border color of checkbox widget."));
export const checkboxSelectBorder = registerColor('checkbox.selectBorder', iconForeground, nls.localize('checkbox.select.border', "Border color of checkbox widget when the element it's in is selected."));
// ------ keybinding label
export const keybindingLabelBackground = registerColor('keybindingLabel.background', { dark: new Color(new RGBA(128, 128, 128, 0.17)), light: new Color(new RGBA(221, 221, 221, 0.4)), hcDark: Color.transparent, hcLight: Color.transparent }, nls.localize('keybindingLabelBackground', "Keybinding label background color. The keybinding label is used to represent a keyboard shortcut."));
export const keybindingLabelForeground = registerColor('keybindingLabel.foreground', { dark: Color.fromHex('#CCCCCC'), light: Color.fromHex('#555555'), hcDark: Color.white, hcLight: foreground }, nls.localize('keybindingLabelForeground', "Keybinding label foreground color. The keybinding label is used to represent a keyboard shortcut."));
export const keybindingLabelBorder = registerColor('keybindingLabel.border', { dark: new Color(new RGBA(51, 51, 51, 0.6)), light: new Color(new RGBA(204, 204, 204, 0.4)), hcDark: new Color(new RGBA(111, 195, 223)), hcLight: contrastBorder }, nls.localize('keybindingLabelBorder', "Keybinding label border color. The keybinding label is used to represent a keyboard shortcut."));
export const keybindingLabelBottomBorder = registerColor('keybindingLabel.bottomBorder', { dark: new Color(new RGBA(68, 68, 68, 0.6)), light: new Color(new RGBA(187, 187, 187, 0.4)), hcDark: new Color(new RGBA(111, 195, 223)), hcLight: foreground }, nls.localize('keybindingLabelBottomBorder', "Keybinding label border bottom color. The keybinding label is used to represent a keyboard shortcut."));
@@ -0,0 +1,45 @@
/*---------------------------------------------------------------------------------------------
* 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 '../../../../nls.js';
// Import the effects we need
import { Color } from '../../../../base/common/color.js';
import { registerColor, darken, lighten, transparent, ifDefinedThenElse } from '../colorUtils.js';
// Import the colors we need
import { foreground, contrastBorder, activeContrastBorder, focusBorder, iconForeground } from './baseColors.js';
import { editorWidgetBackground, editorFindMatchHighlightBorder, editorFindMatchHighlight, widgetShadow } from './editorColors.js';
export const listFocusBackground = registerColor('list.focusBackground', null, nls.localize('listFocusBackground', "List/Tree background color for the focused item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not."));
export const listFocusForeground = registerColor('list.focusForeground', null, nls.localize('listFocusForeground', "List/Tree foreground color for the focused item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not."));
export const listFocusOutline = registerColor('list.focusOutline', { dark: focusBorder, light: focusBorder, hcDark: activeContrastBorder, hcLight: activeContrastBorder }, nls.localize('listFocusOutline', "List/Tree outline color for the focused item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not."));
export const listFocusAndSelectionOutline = registerColor('list.focusAndSelectionOutline', null, nls.localize('listFocusAndSelectionOutline', "List/Tree outline color for the focused item when the list/tree is active and selected. An active list/tree has keyboard focus, an inactive does not."));
export const listActiveSelectionBackground = registerColor('list.activeSelectionBackground', { dark: '#04395E', light: '#0060C0', hcDark: null, hcLight: Color.fromHex('#0F4A85').transparent(0.1) }, nls.localize('listActiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not."));
export const listActiveSelectionForeground = registerColor('list.activeSelectionForeground', { dark: Color.white, light: Color.white, hcDark: null, hcLight: null }, nls.localize('listActiveSelectionForeground', "List/Tree foreground color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not."));
export const listActiveSelectionIconForeground = registerColor('list.activeSelectionIconForeground', null, nls.localize('listActiveSelectionIconForeground', "List/Tree icon foreground color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not."));
export const listInactiveSelectionBackground = registerColor('list.inactiveSelectionBackground', { dark: '#37373D', light: '#E4E6F1', hcDark: null, hcLight: Color.fromHex('#0F4A85').transparent(0.1) }, nls.localize('listInactiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not."));
export const listInactiveSelectionForeground = registerColor('list.inactiveSelectionForeground', null, nls.localize('listInactiveSelectionForeground', "List/Tree foreground color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not."));
export const listInactiveSelectionIconForeground = registerColor('list.inactiveSelectionIconForeground', null, nls.localize('listInactiveSelectionIconForeground', "List/Tree icon foreground color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not."));
export const listInactiveFocusBackground = registerColor('list.inactiveFocusBackground', null, nls.localize('listInactiveFocusBackground', "List/Tree background color for the focused item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not."));
export const listInactiveFocusOutline = registerColor('list.inactiveFocusOutline', null, nls.localize('listInactiveFocusOutline', "List/Tree outline color for the focused item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not."));
export const listHoverBackground = registerColor('list.hoverBackground', { dark: '#2A2D2E', light: '#F0F0F0', hcDark: Color.white.transparent(0.1), hcLight: Color.fromHex('#0F4A85').transparent(0.1) }, nls.localize('listHoverBackground', "List/Tree background when hovering over items using the mouse."));
export const listHoverForeground = registerColor('list.hoverForeground', null, nls.localize('listHoverForeground', "List/Tree foreground when hovering over items using the mouse."));
export const listDropOverBackground = registerColor('list.dropBackground', { dark: '#062F4A', light: '#D6EBFF', hcDark: null, hcLight: null }, nls.localize('listDropBackground', "List/Tree drag and drop background when moving items over other items when using the mouse."));
export const listDropBetweenBackground = registerColor('list.dropBetweenBackground', { dark: iconForeground, light: iconForeground, hcDark: null, hcLight: null }, nls.localize('listDropBetweenBackground', "List/Tree drag and drop border color when moving items between items when using the mouse."));
export const listHighlightForeground = registerColor('list.highlightForeground', { dark: '#2AAAFF', light: '#0066BF', hcDark: focusBorder, hcLight: focusBorder }, nls.localize('highlight', 'List/Tree foreground color of the match highlights when searching inside the list/tree.'));
export const listFocusHighlightForeground = registerColor('list.focusHighlightForeground', { dark: listHighlightForeground, light: ifDefinedThenElse(listActiveSelectionBackground, listHighlightForeground, '#BBE7FF'), hcDark: listHighlightForeground, hcLight: listHighlightForeground }, nls.localize('listFocusHighlightForeground', 'List/Tree foreground color of the match highlights on actively focused items when searching inside the list/tree.'));
export const listInvalidItemForeground = registerColor('list.invalidItemForeground', { dark: '#B89500', light: '#B89500', hcDark: '#B89500', hcLight: '#B5200D' }, nls.localize('invalidItemForeground', 'List/Tree foreground color for invalid items, for example an unresolved root in explorer.'));
export const listErrorForeground = registerColor('list.errorForeground', { dark: '#F88070', light: '#B01011', hcDark: null, hcLight: null }, nls.localize('listErrorForeground', 'Foreground color of list items containing errors.'));
export const listWarningForeground = registerColor('list.warningForeground', { dark: '#CCA700', light: '#855F00', hcDark: null, hcLight: null }, nls.localize('listWarningForeground', 'Foreground color of list items containing warnings.'));
export const listFilterWidgetBackground = registerColor('listFilterWidget.background', { light: darken(editorWidgetBackground, 0), dark: lighten(editorWidgetBackground, 0), hcDark: editorWidgetBackground, hcLight: editorWidgetBackground }, nls.localize('listFilterWidgetBackground', 'Background color of the type filter widget in lists and trees.'));
export const listFilterWidgetOutline = registerColor('listFilterWidget.outline', { dark: Color.transparent, light: Color.transparent, hcDark: '#f38518', hcLight: '#007ACC' }, nls.localize('listFilterWidgetOutline', 'Outline color of the type filter widget in lists and trees.'));
export const listFilterWidgetNoMatchesOutline = registerColor('listFilterWidget.noMatchesOutline', { dark: '#BE1100', light: '#BE1100', hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('listFilterWidgetNoMatchesOutline', 'Outline color of the type filter widget in lists and trees, when there are no matches.'));
export const listFilterWidgetShadow = registerColor('listFilterWidget.shadow', widgetShadow, nls.localize('listFilterWidgetShadow', 'Shadow color of the type filter widget in lists and trees.'));
export const listFilterMatchHighlight = registerColor('list.filterMatchBackground', { dark: editorFindMatchHighlight, light: editorFindMatchHighlight, hcDark: null, hcLight: null }, nls.localize('listFilterMatchHighlight', 'Background color of the filtered match.'));
export const listFilterMatchHighlightBorder = registerColor('list.filterMatchBorder', { dark: editorFindMatchHighlightBorder, light: editorFindMatchHighlightBorder, hcDark: contrastBorder, hcLight: activeContrastBorder }, nls.localize('listFilterMatchHighlightBorder', 'Border color of the filtered match.'));
export const listDeemphasizedForeground = registerColor('list.deemphasizedForeground', { dark: '#8C8C8C', light: '#8E8E90', hcDark: '#A7A8A9', hcLight: '#666666' }, nls.localize('listDeemphasizedForeground', "List/Tree foreground color for items that are deemphasized."));
// ------ tree
export const treeIndentGuidesStroke = registerColor('tree.indentGuidesStroke', { dark: '#585858', light: '#a9a9a9', hcDark: '#a9a9a9', hcLight: '#a5a5a5' }, nls.localize('treeIndentGuidesStroke', "Tree stroke color for the indentation guides."));
export const treeInactiveIndentGuidesStroke = registerColor('tree.inactiveIndentGuidesStroke', transparent(treeIndentGuidesStroke, 0.4), nls.localize('treeInactiveIndentGuidesStroke', "Tree stroke color for the indentation guides that are not active."));
// ------ table
export const tableColumnsBorder = registerColor('tree.tableColumnsBorder', { dark: '#CCCCCC20', light: '#61616120', hcDark: null, hcLight: null }, nls.localize('tableColumnsBorder', "Table border color between columns."));
export const tableOddRowsBackgroundColor = registerColor('tree.tableOddRowsBackground', { dark: transparent(foreground, 0.04), light: transparent(foreground, 0.04), hcDark: null, hcLight: null }, nls.localize('tableOddRowsBackgroundColor', "Background color for odd table rows."));
@@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* 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 '../../../../nls.js';
// Import the effects we need
import { registerColor } from '../colorUtils.js';
// Import the colors we need
import { contrastBorder, activeContrastBorder } from './baseColors.js';
import { selectForeground, selectBackground } from './inputColors.js';
import { listActiveSelectionBackground, listActiveSelectionForeground } from './listColors.js';
export const menuBorder = registerColor('menu.border', { dark: null, light: null, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('menuBorder', "Border color of menus."));
export const menuForeground = registerColor('menu.foreground', selectForeground, nls.localize('menuForeground', "Foreground color of menu items."));
export const menuBackground = registerColor('menu.background', selectBackground, nls.localize('menuBackground', "Background color of menu items."));
export const menuSelectionForeground = registerColor('menu.selectionForeground', listActiveSelectionForeground, nls.localize('menuSelectionForeground', "Foreground color of the selected menu item in menus."));
export const menuSelectionBackground = registerColor('menu.selectionBackground', listActiveSelectionBackground, nls.localize('menuSelectionBackground', "Background color of the selected menu item in menus."));
export const menuSelectionBorder = registerColor('menu.selectionBorder', { dark: null, light: null, hcDark: activeContrastBorder, hcLight: activeContrastBorder }, nls.localize('menuSelectionBorder', "Border color of the selected menu item in menus."));
export const menuSeparatorBackground = registerColor('menu.separatorBackground', { dark: '#606060', light: '#D4D4D4', hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('menuSeparatorBackground', "Color of a separator menu item in menus."));
@@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* 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 '../../../../nls.js';
// Import the effects we need
import { Color, RGBA } from '../../../../base/common/color.js';
import { registerColor, transparent } from '../colorUtils.js';
// Import the colors we need
import { editorInfoForeground, editorWarningForeground, editorWarningBorder, editorInfoBorder } from './editorColors.js';
import { scrollbarSliderBackground, scrollbarSliderHoverBackground, scrollbarSliderActiveBackground } from './miscColors.js';
export const minimapFindMatch = registerColor('minimap.findMatchHighlight', { light: '#d18616', dark: '#d18616', hcDark: '#AB5A00', hcLight: '#0F4A85' }, nls.localize('minimapFindMatchHighlight', 'Minimap marker color for find matches.'), true);
export const minimapSelectionOccurrenceHighlight = registerColor('minimap.selectionOccurrenceHighlight', { light: '#c9c9c9', dark: '#676767', hcDark: '#ffffff', hcLight: '#0F4A85' }, nls.localize('minimapSelectionOccurrenceHighlight', 'Minimap marker color for repeating editor selections.'), true);
export const minimapSelection = registerColor('minimap.selectionHighlight', { light: '#ADD6FF', dark: '#264F78', hcDark: '#ffffff', hcLight: '#0F4A85' }, nls.localize('minimapSelectionHighlight', 'Minimap marker color for the editor selection.'), true);
export const minimapInfo = registerColor('minimap.infoHighlight', { dark: editorInfoForeground, light: editorInfoForeground, hcDark: editorInfoBorder, hcLight: editorInfoBorder }, nls.localize('minimapInfo', 'Minimap marker color for infos.'));
export const minimapWarning = registerColor('minimap.warningHighlight', { dark: editorWarningForeground, light: editorWarningForeground, hcDark: editorWarningBorder, hcLight: editorWarningBorder }, nls.localize('overviewRuleWarning', 'Minimap marker color for warnings.'));
export const minimapError = registerColor('minimap.errorHighlight', { dark: new Color(new RGBA(255, 18, 18, 0.7)), light: new Color(new RGBA(255, 18, 18, 0.7)), hcDark: new Color(new RGBA(255, 50, 50, 1)), hcLight: '#B5200D' }, nls.localize('minimapError', 'Minimap marker color for errors.'));
export const minimapBackground = registerColor('minimap.background', null, nls.localize('minimapBackground', "Minimap background color."));
export const minimapForegroundOpacity = registerColor('minimap.foregroundOpacity', Color.fromHex('#000f'), nls.localize('minimapForegroundOpacity', 'Opacity of foreground elements rendered in the minimap. For example, "#000000c0" will render the elements with 75% opacity.'));
export const minimapSliderBackground = registerColor('minimapSlider.background', transparent(scrollbarSliderBackground, 0.5), nls.localize('minimapSliderBackground', "Minimap slider background color."));
export const minimapSliderHoverBackground = registerColor('minimapSlider.hoverBackground', transparent(scrollbarSliderHoverBackground, 0.5), nls.localize('minimapSliderHoverBackground', "Minimap slider background color when hovering."));
export const minimapSliderActiveBackground = registerColor('minimapSlider.activeBackground', transparent(scrollbarSliderActiveBackground, 0.5), nls.localize('minimapSliderActiveBackground', "Minimap slider background color when clicked on."));
@@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* 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 '../../../../nls.js';
// Import the effects we need
import { Color } from '../../../../base/common/color.js';
import { registerColor, transparent } from '../colorUtils.js';
// Import the colors we need
import { contrastBorder, focusBorder } from './baseColors.js';
// ----- sash
export const sashHoverBorder = registerColor('sash.hoverBorder', focusBorder, nls.localize('sashActiveBorder', "Border color of active sashes."));
// ----- badge
export const badgeBackground = registerColor('badge.background', { dark: '#4D4D4D', light: '#C4C4C4', hcDark: Color.black, hcLight: '#0F4A85' }, nls.localize('badgeBackground', "Badge background color. Badges are small information labels, e.g. for search results count."));
export const badgeForeground = registerColor('badge.foreground', { dark: Color.white, light: '#333', hcDark: Color.white, hcLight: Color.white }, nls.localize('badgeForeground', "Badge foreground color. Badges are small information labels, e.g. for search results count."));
// ----- scrollbar
export const scrollbarShadow = registerColor('scrollbar.shadow', { dark: '#000000', light: '#DDDDDD', hcDark: null, hcLight: null }, nls.localize('scrollbarShadow', "Scrollbar shadow to indicate that the view is scrolled."));
export const scrollbarSliderBackground = registerColor('scrollbarSlider.background', { dark: Color.fromHex('#797979').transparent(0.4), light: Color.fromHex('#646464').transparent(0.4), hcDark: transparent(contrastBorder, 0.6), hcLight: transparent(contrastBorder, 0.4) }, nls.localize('scrollbarSliderBackground', "Scrollbar slider background color."));
export const scrollbarSliderHoverBackground = registerColor('scrollbarSlider.hoverBackground', { dark: Color.fromHex('#646464').transparent(0.7), light: Color.fromHex('#646464').transparent(0.7), hcDark: transparent(contrastBorder, 0.8), hcLight: transparent(contrastBorder, 0.8) }, nls.localize('scrollbarSliderHoverBackground', "Scrollbar slider background color when hovering."));
export const scrollbarSliderActiveBackground = registerColor('scrollbarSlider.activeBackground', { dark: Color.fromHex('#BFBFBF').transparent(0.4), light: Color.fromHex('#000000').transparent(0.6), hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('scrollbarSliderActiveBackground', "Scrollbar slider background color when clicked on."));
// ----- progress bar
export const progressBarBackground = registerColor('progressBar.background', { dark: Color.fromHex('#0E70C0'), light: Color.fromHex('#0E70C0'), hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('progressBarBackground', "Background color of the progress bar that can show for long running operations."));

Some files were not shown because too many files have changed in this diff Show More