Version 1.0

This commit is contained in:
Alexander R.
2026-07-21 22:24:25 +00:00
parent f3645fbfbc
commit 9edbfd3f77
4134 changed files with 1448752 additions and 1 deletions
@@ -0,0 +1,111 @@
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';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
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('monaco-reduce-motion', reduce);
this._layoutService.mainContainer.classList.toggle('monaco-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,25 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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;
}
};
export { AccessibleViewRegistry };
@@ -0,0 +1,11 @@
import { RawContextKey } from '../../contextkey/common/contextkey.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const IAccessibilityService = createDecorator('accessibilityService');
const CONTEXT_ACCESSIBILITY_MODE_ENABLED = new RawContextKey('accessibilityModeEnabled', false);
export { CONTEXT_ACCESSIBILITY_MODE_ENABLED, IAccessibilityService };
@@ -0,0 +1,331 @@
import { localize } from '../../../nls.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
const IAccessibilitySignalService = createDecorator('accessibilitySignalService');
/**
* Corresponds to the audio files in ./media.
*/
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.requestSent = Sound.register({ fileName: 'requestSent.mp3' }); }
static { this.responseReceived1 = Sound.register({ fileName: 'responseReceived1.mp3' }); }
static { this.responseReceived2 = Sound.register({ fileName: 'responseReceived2.mp3' }); }
static { this.responseReceived3 = Sound.register({ fileName: 'responseReceived3.mp3' }); }
static { this.responseReceived4 = Sound.register({ fileName: 'responseReceived4.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' }); }
static { this.chatEditModifiedFile = Sound.register({ fileName: 'chatEditModifiedFile.mp3' }); }
static { this.editsKept = Sound.register({ fileName: 'editsKept.mp3' }); }
static { this.editsUndone = Sound.register({ fileName: 'editsUndone.mp3' }); }
static { this.nextEditSuggestion = Sound.register({ fileName: 'nextEditSuggestion.mp3' }); }
static { this.terminalCommandSucceeded = Sound.register({ fileName: 'terminalCommandSucceeded.mp3' }); }
static { this.chatUserActionRequired = Sound.register({ fileName: 'chatUserActionRequired.mp3' }); }
static { this.codeActionTriggered = Sound.register({ fileName: 'codeActionTriggered.mp3' }); }
static { this.codeActionApplied = Sound.register({ fileName: 'codeActionApplied.mp3' }); }
constructor(fileName) {
this.fileName = fileName;
}
}
class SoundSource {
constructor(randomOneOf) {
this.randomOneOf = randomOneOf;
}
}
class AccessibilitySignal {
constructor(sound, name, legacySoundSettingsKey, settingsKey, legacyAnnouncementSettingsKey, announcementMessage, managesOwnEnablement = false) {
this.sound = sound;
this.name = name;
this.legacySoundSettingsKey = legacySoundSettingsKey;
this.settingsKey = settingsKey;
this.legacyAnnouncementSettingsKey = legacyAnnouncementSettingsKey;
this.announcementMessage = announcementMessage;
this.managesOwnEnablement = managesOwnEnablement;
}
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, options.managesOwnEnablement);
AccessibilitySignal._signals.add(signal);
return signal;
}
static { this.errorAtPosition = AccessibilitySignal.register({
name: localize(1576, 'Error at Position'),
sound: Sound.error,
announcementMessage: localize(1577, 'Error'),
settingsKey: 'accessibility.signals.positionHasError',
delaySettingsKey: 'accessibility.signalOptions.delays.errorAtPosition'
}); }
static { this.warningAtPosition = AccessibilitySignal.register({
name: localize(1578, 'Warning at Position'),
sound: Sound.warning,
announcementMessage: localize(1579, 'Warning'),
settingsKey: 'accessibility.signals.positionHasWarning',
delaySettingsKey: 'accessibility.signalOptions.delays.warningAtPosition'
}); }
static { this.errorOnLine = AccessibilitySignal.register({
name: localize(1580, 'Error on Line'),
sound: Sound.error,
legacySoundSettingsKey: 'audioCues.lineHasError',
legacyAnnouncementSettingsKey: 'accessibility.alert.error',
announcementMessage: localize(1581, 'Error on Line'),
settingsKey: 'accessibility.signals.lineHasError',
}); }
static { this.warningOnLine = AccessibilitySignal.register({
name: localize(1582, 'Warning on Line'),
sound: Sound.warning,
legacySoundSettingsKey: 'audioCues.lineHasWarning',
legacyAnnouncementSettingsKey: 'accessibility.alert.warning',
announcementMessage: localize(1583, 'Warning on Line'),
settingsKey: 'accessibility.signals.lineHasWarning',
}); }
static { this.foldedArea = AccessibilitySignal.register({
name: localize(1584, 'Folded Area on Line'),
sound: Sound.foldedArea,
legacySoundSettingsKey: 'audioCues.lineHasFoldedArea',
legacyAnnouncementSettingsKey: 'accessibility.alert.foldedArea',
announcementMessage: localize(1585, 'Folded'),
settingsKey: 'accessibility.signals.lineHasFoldedArea',
}); }
static { this.break = AccessibilitySignal.register({
name: localize(1586, 'Breakpoint on Line'),
sound: Sound.break,
legacySoundSettingsKey: 'audioCues.lineHasBreakpoint',
legacyAnnouncementSettingsKey: 'accessibility.alert.breakpoint',
announcementMessage: localize(1587, 'Breakpoint'),
settingsKey: 'accessibility.signals.lineHasBreakpoint',
}); }
static { this.inlineSuggestion = AccessibilitySignal.register({
name: localize(1588, 'Inline Suggestion on Line'),
sound: Sound.quickFixes,
legacySoundSettingsKey: 'audioCues.lineHasInlineSuggestion',
settingsKey: 'accessibility.signals.lineHasInlineSuggestion',
}); }
static { this.nextEditSuggestion = AccessibilitySignal.register({
name: localize(1589, 'Next Edit Suggestion on Line'),
sound: Sound.nextEditSuggestion,
legacySoundSettingsKey: 'audioCues.nextEditSuggestion',
settingsKey: 'accessibility.signals.nextEditSuggestion',
announcementMessage: localize(1590, 'Next Edit Suggestion'),
}); }
static { this.terminalQuickFix = AccessibilitySignal.register({
name: localize(1591, 'Terminal Quick Fix'),
sound: Sound.quickFixes,
legacySoundSettingsKey: 'audioCues.terminalQuickFix',
legacyAnnouncementSettingsKey: 'accessibility.alert.terminalQuickFix',
announcementMessage: localize(1592, 'Quick Fix'),
settingsKey: 'accessibility.signals.terminalQuickFix',
}); }
static { this.onDebugBreak = AccessibilitySignal.register({
name: localize(1593, 'Debugger Stopped on Breakpoint'),
sound: Sound.break,
legacySoundSettingsKey: 'audioCues.onDebugBreak',
legacyAnnouncementSettingsKey: 'accessibility.alert.onDebugBreak',
announcementMessage: localize(1594, 'Breakpoint'),
settingsKey: 'accessibility.signals.onDebugBreak',
}); }
static { this.noInlayHints = AccessibilitySignal.register({
name: localize(1595, 'No Inlay Hints on Line'),
sound: Sound.error,
legacySoundSettingsKey: 'audioCues.noInlayHints',
legacyAnnouncementSettingsKey: 'accessibility.alert.noInlayHints',
announcementMessage: localize(1596, 'No Inlay Hints'),
settingsKey: 'accessibility.signals.noInlayHints',
}); }
static { this.taskCompleted = AccessibilitySignal.register({
name: localize(1597, 'Task Completed'),
sound: Sound.taskCompleted,
legacySoundSettingsKey: 'audioCues.taskCompleted',
legacyAnnouncementSettingsKey: 'accessibility.alert.taskCompleted',
announcementMessage: localize(1598, 'Task Completed'),
settingsKey: 'accessibility.signals.taskCompleted',
}); }
static { this.taskFailed = AccessibilitySignal.register({
name: localize(1599, 'Task Failed'),
sound: Sound.taskFailed,
legacySoundSettingsKey: 'audioCues.taskFailed',
legacyAnnouncementSettingsKey: 'accessibility.alert.taskFailed',
announcementMessage: localize(1600, 'Task Failed'),
settingsKey: 'accessibility.signals.taskFailed',
}); }
static { this.terminalCommandFailed = AccessibilitySignal.register({
name: localize(1601, 'Terminal Command Failed'),
sound: Sound.error,
legacySoundSettingsKey: 'audioCues.terminalCommandFailed',
legacyAnnouncementSettingsKey: 'accessibility.alert.terminalCommandFailed',
announcementMessage: localize(1602, 'Command Failed'),
settingsKey: 'accessibility.signals.terminalCommandFailed',
}); }
static { this.terminalCommandSucceeded = AccessibilitySignal.register({
name: localize(1603, 'Terminal Command Succeeded'),
sound: Sound.terminalCommandSucceeded,
announcementMessage: localize(1604, 'Command Succeeded'),
settingsKey: 'accessibility.signals.terminalCommandSucceeded',
}); }
static { this.terminalBell = AccessibilitySignal.register({
name: localize(1605, 'Terminal Bell'),
sound: Sound.terminalBell,
legacySoundSettingsKey: 'audioCues.terminalBell',
legacyAnnouncementSettingsKey: 'accessibility.alert.terminalBell',
announcementMessage: localize(1606, 'Terminal Bell'),
settingsKey: 'accessibility.signals.terminalBell',
}); }
static { this.notebookCellCompleted = AccessibilitySignal.register({
name: localize(1607, 'Notebook Cell Completed'),
sound: Sound.taskCompleted,
legacySoundSettingsKey: 'audioCues.notebookCellCompleted',
legacyAnnouncementSettingsKey: 'accessibility.alert.notebookCellCompleted',
announcementMessage: localize(1608, 'Notebook Cell Completed'),
settingsKey: 'accessibility.signals.notebookCellCompleted',
}); }
static { this.notebookCellFailed = AccessibilitySignal.register({
name: localize(1609, 'Notebook Cell Failed'),
sound: Sound.taskFailed,
legacySoundSettingsKey: 'audioCues.notebookCellFailed',
legacyAnnouncementSettingsKey: 'accessibility.alert.notebookCellFailed',
announcementMessage: localize(1610, 'Notebook Cell Failed'),
settingsKey: 'accessibility.signals.notebookCellFailed',
}); }
static { this.diffLineInserted = AccessibilitySignal.register({
name: localize(1611, 'Diff Line Inserted'),
sound: Sound.diffLineInserted,
legacySoundSettingsKey: 'audioCues.diffLineInserted',
settingsKey: 'accessibility.signals.diffLineInserted',
}); }
static { this.diffLineDeleted = AccessibilitySignal.register({
name: localize(1612, 'Diff Line Deleted'),
sound: Sound.diffLineDeleted,
legacySoundSettingsKey: 'audioCues.diffLineDeleted',
settingsKey: 'accessibility.signals.diffLineDeleted',
}); }
static { this.diffLineModified = AccessibilitySignal.register({
name: localize(1613, 'Diff Line Modified'),
sound: Sound.diffLineModified,
legacySoundSettingsKey: 'audioCues.diffLineModified',
settingsKey: 'accessibility.signals.diffLineModified',
}); }
static { this.chatEditModifiedFile = AccessibilitySignal.register({
name: localize(1614, 'Chat Edit Modified File'),
sound: Sound.chatEditModifiedFile,
announcementMessage: localize(1615, 'File Modified from Chat Edits'),
settingsKey: 'accessibility.signals.chatEditModifiedFile',
}); }
static { this.chatRequestSent = AccessibilitySignal.register({
name: localize(1616, 'Chat Request Sent'),
sound: Sound.requestSent,
legacySoundSettingsKey: 'audioCues.chatRequestSent',
legacyAnnouncementSettingsKey: 'accessibility.alert.chatRequestSent',
announcementMessage: localize(1617, 'Chat Request Sent'),
settingsKey: 'accessibility.signals.chatRequestSent',
}); }
static { this.chatResponseReceived = AccessibilitySignal.register({
name: localize(1618, 'Chat Response Received'),
legacySoundSettingsKey: 'audioCues.chatResponseReceived',
sound: {
randomOneOf: [
Sound.responseReceived1,
Sound.responseReceived2,
Sound.responseReceived3,
Sound.responseReceived4
]
},
settingsKey: 'accessibility.signals.chatResponseReceived'
}); }
static { this.codeActionTriggered = AccessibilitySignal.register({
name: localize(1619, 'Code Action Request Triggered'),
sound: Sound.codeActionTriggered,
legacySoundSettingsKey: 'audioCues.codeActionRequestTriggered',
legacyAnnouncementSettingsKey: 'accessibility.alert.codeActionRequestTriggered',
announcementMessage: localize(1620, 'Code Action Request Triggered'),
settingsKey: 'accessibility.signals.codeActionTriggered',
}); }
static { this.codeActionApplied = AccessibilitySignal.register({
name: localize(1621, 'Code Action Applied'),
legacySoundSettingsKey: 'audioCues.codeActionApplied',
sound: Sound.codeActionApplied,
settingsKey: 'accessibility.signals.codeActionApplied'
}); }
static { this.progress = AccessibilitySignal.register({
name: localize(1622, 'Progress'),
sound: Sound.progress,
legacySoundSettingsKey: 'audioCues.chatResponsePending',
legacyAnnouncementSettingsKey: 'accessibility.alert.progress',
announcementMessage: localize(1623, 'Progress'),
settingsKey: 'accessibility.signals.progress'
}); }
static { this.clear = AccessibilitySignal.register({
name: localize(1624, 'Clear'),
sound: Sound.clear,
legacySoundSettingsKey: 'audioCues.clear',
legacyAnnouncementSettingsKey: 'accessibility.alert.clear',
announcementMessage: localize(1625, 'Clear'),
settingsKey: 'accessibility.signals.clear'
}); }
static { this.save = AccessibilitySignal.register({
name: localize(1626, 'Save'),
sound: Sound.save,
legacySoundSettingsKey: 'audioCues.save',
legacyAnnouncementSettingsKey: 'accessibility.alert.save',
announcementMessage: localize(1627, 'Save'),
settingsKey: 'accessibility.signals.save'
}); }
static { this.format = AccessibilitySignal.register({
name: localize(1628, 'Format'),
sound: Sound.format,
legacySoundSettingsKey: 'audioCues.format',
legacyAnnouncementSettingsKey: 'accessibility.alert.format',
announcementMessage: localize(1629, 'Format'),
settingsKey: 'accessibility.signals.format'
}); }
static { this.voiceRecordingStarted = AccessibilitySignal.register({
name: localize(1630, 'Voice Recording Started'),
sound: Sound.voiceRecordingStarted,
legacySoundSettingsKey: 'audioCues.voiceRecordingStarted',
settingsKey: 'accessibility.signals.voiceRecordingStarted'
}); }
static { this.voiceRecordingStopped = AccessibilitySignal.register({
name: localize(1631, 'Voice Recording Stopped'),
sound: Sound.voiceRecordingStopped,
legacySoundSettingsKey: 'audioCues.voiceRecordingStopped',
settingsKey: 'accessibility.signals.voiceRecordingStopped'
}); }
static { this.editsKept = AccessibilitySignal.register({
name: localize(1632, 'Edits Kept'),
sound: Sound.editsKept,
announcementMessage: localize(1633, 'Edits Kept'),
settingsKey: 'accessibility.signals.editsKept',
}); }
static { this.editsUndone = AccessibilitySignal.register({
name: localize(1634, 'Undo Edits'),
sound: Sound.editsUndone,
announcementMessage: localize(1635, 'Edits Undone'),
settingsKey: 'accessibility.signals.editsUndone',
}); }
static { this.chatUserActionRequired = AccessibilitySignal.register({
name: localize(1636, 'Chat User Action Required'),
sound: Sound.chatUserActionRequired,
announcementMessage: localize(1637, 'Chat User Action Required'),
settingsKey: 'accessibility.signals.chatUserActionRequired',
managesOwnEnablement: true
}); }
}
export { AccessibilitySignal, IAccessibilitySignalService, Sound, SoundSource };
@@ -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.
*--------------------------------------------------------------------------------------------*/
function isLocalizedString(thing) {
return !!thing
&& typeof thing === 'object'
&& typeof thing.original === 'string'
&& typeof thing.value === 'string';
}
function isICommandActionToggleInfo(thing) {
return thing ? thing.condition !== undefined : false;
}
export { isICommandActionToggleInfo, isLocalizedString };
@@ -0,0 +1,16 @@
import { localize2 } from '../../../nls.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const Categories = Object.freeze({
View: localize2(1638, 'View'),
Help: localize2(1639, 'Help'),
Test: localize2(1640, 'Test'),
File: localize2(1641, 'File'),
Preferences: localize2(1642, 'Preferences'),
Developer: localize2(1643, "Developer"),
});
export { Categories };
@@ -0,0 +1,344 @@
import { setVisibility, getWindow } 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/colorUtils.js';
import '../../theme/common/colors/baseColors.js';
import '../../theme/common/colors/chartsColors.js';
import '../../theme/common/colors/editorColors.js';
import '../../theme/common/colors/inputColors.js';
import '../../theme/common/colors/listColors.js';
import '../../theme/common/colors/menuColors.js';
import '../../theme/common/colors/minimapColors.js';
import '../../theme/common/colors/miscColors.js';
import '../../theme/common/colors/quickpickColors.js';
import '../../theme/common/colors/searchColors.js';
import { ILayoutService } from '../../layout/browser/layoutService.js';
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
const acceptSelectedActionCommand = 'acceptSelectedCodeAction';
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 ?? element.label ?? '';
}
disposeTemplate(_templateData) {
// noop
}
}
class SeparatorRenderer {
get templateId() { return "separator" /* ActionListItemKind.Separator */; }
renderTemplate(container) {
container.classList.add('separator');
const text = document.createElement('span');
container.append(text);
return { container, text };
}
renderElement(element, _index, templateData) {
templateData.text.textContent = element.label ?? '';
}
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 description = document.createElement('span');
description.className = 'description';
container.append(description);
const keybinding = new KeybindingLabel(container, OS);
return { container, icon, text, description, 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;
}
setVisibility(!element.hideIcon, data.icon);
data.text.textContent = stripNewlines(element.label);
// if there is a keybinding, prioritize over description for now
if (element.keybinding) {
data.description.textContent = element.keybinding.getLabel();
data.description.style.display = 'inline';
data.description.style.letterSpacing = '0.5px';
}
else if (element.description) {
data.description.textContent = stripNewlines(element.description);
data.description.style.display = 'inline';
}
else {
data.description.textContent = '';
data.description.style.display = 'none';
}
const actionTitle = this._keybindingService.lookupKeybinding(acceptSelectedActionCommand)?.getLabel();
const previewTitle = this._keybindingService.lookupKeybinding(previewSelectedActionCommand)?.getLabel();
data.container.classList.toggle('option-disabled', element.disabled);
if (element.tooltip) {
data.container.title = element.tooltip;
}
else if (element.disabled) {
data.container.title = element.label;
}
else if (actionTitle && previewTitle) {
if (this._supportsPreview && element.canPreview) {
data.container.title = localize(1653, "{0} to Apply, {1} to Preview", actionTitle, previewTitle);
}
else {
data.container.title = localize(1654, "{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 vs. separator
if (item.kind === 'action') {
return item.label;
}
return undefined;
}
let ActionList = class ActionList extends Disposable {
constructor(user, preview, items, _delegate, accessibilityProvider, _contextViewService, _keybindingService, _layoutService) {
super();
this._delegate = _delegate;
this._contextViewService = _contextViewService;
this._keybindingService = _keybindingService;
this._layoutService = _layoutService;
this._actionLineHeight = 28;
this._headerLineHeight = 28;
this._separatorLineHeight = 8;
this.cts = this._register(new CancellationTokenSource());
this.domNode = document.createElement('div');
this.domNode.classList.add('actionList');
const virtualDelegate = {
getHeight: element => {
switch (element.kind) {
case "header" /* ActionListItemKind.Header */:
return this._headerLineHeight;
case "separator" /* ActionListItemKind.Separator */:
return this._separatorLineHeight;
default:
return this._actionLineHeight;
}
},
getTemplateId: element => element.kind
};
this._list = this._register(new List(user, this.domNode, virtualDelegate, [
new ActionItemRenderer(preview, this._keybindingService),
new HeaderRenderer(),
new SeparatorRenderer(),
], {
keyboardSupport: false,
typeNavigationEnabled: true,
keyboardNavigationLabelProvider: { getKeyboardNavigationLabel },
accessibilityProvider: {
getAriaLabel: element => {
if (element.kind === "action" /* ActionListItemKind.Action */) {
let label = element.label ? stripNewlines(element?.label) : '';
if (element.description) {
label = label + ', ' + stripNewlines(element.description);
}
if (element.disabled) {
label = localize(1655, "{0}, Disabled Reason: {1}", label, element.disabled);
}
return label;
}
return null;
},
getWidgetAriaLabel: () => localize(1656, "Action Widget"),
getRole: (e) => {
switch (e.kind) {
case "action" /* ActionListItemKind.Action */:
return 'option';
case "separator" /* ActionListItemKind.Separator */:
return 'separator';
default:
return 'separator';
}
},
getWidgetRole: () => 'listbox',
...accessibilityProvider
},
}));
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 numSeparators = this._allMenuItems.filter(item => item.kind === 'separator').length;
const itemsHeight = this._allMenuItems.length * this._actionLineHeight;
const heightWithHeaders = itemsHeight + numHeaders * this._headerLineHeight - numHeaders * this._actionLineHeight;
const heightWithSeparators = heightWithHeaders + numSeparators * this._separatorLineHeight - numSeparators * this._actionLineHeight;
this._list.layout(heightWithSeparators);
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) => {
// eslint-disable-next-line no-restricted-syntax
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(heightWithSeparators, this._layoutService.getContainer(getWindow(this.domNode)).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(5, IContextViewService),
__param(6, IKeybindingService),
__param(7, ILayoutService)
], ActionList);
function stripNewlines(str) {
return str.replace(/\r\n|\r|\n/g, ' ');
}
export { ActionList, acceptSelectedActionCommand, previewSelectedActionCommand };
@@ -0,0 +1,197 @@
/*---------------------------------------------------------------------------------------------
* 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: 100px;
max-width: 80vw;
z-index: 40;
display: block;
width: 100%;
border: 1px solid var(--vscode-menu-border) !important;
border-radius: 5px;
background-color: var(--vscode-menu-background);
color: var(--vscode-menu-foreground);
padding: 4px;
box-shadow: 0 2px 8px var(--vscode-widget-shadow);
}
.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 4px 0 4px;
white-space: nowrap;
cursor: pointer;
touch-action: none;
width: 100%;
border-radius: 3px;
}
.action-widget .monaco-list .monaco-list-row.action.focused:not(.option-disabled) {
background-color: var(--vscode-list-activeSelectionBackground) !important;
color: var(--vscode-list-activeSelectionForeground);
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;
font-size: 13px;
}
.action-widget .monaco-list-row.group-header:not(:first-of-type) {
margin-top: 2px;
}
.action-widget .monaco-scrollable-element .monaco-list-rows .monaco-list-row.separator {
border-top: 1px solid var(--vscode-editorHoverWidget-border);
color: var(--vscode-descriptionForeground);
font-size: 12px;
padding: 0;
margin: 4px 0 0 0;
cursor: default;
user-select: none;
border-radius: 0;
}
.action-widget .monaco-scrollable-element .monaco-list-rows .monaco-list-row.separator.focused {
outline: 0 solid;
background-color: transparent;
border-radius: 0;
}
.action-widget .monaco-list-row.separator:first-of-type {
border-top: none;
margin-top: 0;
}
.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: 4px;
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-menu-background);
border-top: 1px solid var(--vscode-menu-border);
margin-top: 2px;
}
.action-widget .action-widget-action-bar::before {
display: block;
content: "";
width: 100%;
}
.action-widget .action-widget-action-bar .actions-container {
padding: 4px 8px 2px 24px;
}
.action-widget-action-bar .action-label {
color: var(--vscode-textLink-activeForeground);
font-size: 13px;
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;
}
.action-widget .monaco-list .monaco-list-row .description {
opacity: 0.7;
margin-left: 0.5em;
}
@@ -0,0 +1,236 @@
import { addDisposableListener, EventType, trackFocus, $ } from '../../../base/browser/dom.js';
import { ActionBar } from '../../../base/browser/ui/actionbar/actionbar.js';
import { Disposable, MutableDisposable, DisposableStore } from '../../../base/common/lifecycle.js';
import './actionWidget.css';
import { localize, localize2 } from '../../../nls.js';
import { acceptSelectedActionCommand, previewSelectedActionCommand, ActionList } from './actionList.js';
import { registerAction2, Action2 } from '../../actions/common/actions.js';
import { RawContextKey, IContextKeyService } 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 { registerColor } from '../../theme/common/colorUtils.js';
import '../../theme/common/colors/baseColors.js';
import '../../theme/common/colors/chartsColors.js';
import '../../theme/common/colors/editorColors.js';
import { inputActiveOptionBackground } from '../../theme/common/colors/inputColors.js';
import '../../theme/common/colors/listColors.js';
import '../../theme/common/colors/menuColors.js';
import '../../theme/common/colors/minimapColors.js';
import '../../theme/common/colors/miscColors.js';
import '../../theme/common/colors/quickpickColors.js';
import '../../theme/common/colors/searchColors.js';
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
registerColor('actionBar.toggledBackground', inputActiveOptionBackground, localize(1657, 'Background color for toggled action items in action bar.'));
const ActionWidgetContextKeys = {
Visible: new RawContextKey('codeActionMenuVisible', false, localize(1658, "Whether the action widget list is visible"))
};
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, accessibilityProvider) {
const visibleContext = ActionWidgetContextKeys.Visible.bindTo(this._contextKeyService);
const list = this._instantiationService.createInstance(ActionList, user, supportsPreview, items, delegate, accessibilityProvider);
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(addDisposableListener(block, 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(addDisposableListener(pointerBlock, EventType.POINTER_MOVE, () => pointerBlock.remove()));
renderDisposables.add(addDisposableListener(pointerBlock, 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(trackFocus(element));
renderDisposables.add(focusTracker.onDidBlur(() => this.hide(true)));
return renderDisposables;
}
_createActionBar(className, actions) {
if (!actions.length) {
return undefined;
}
const container = $(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(1659, "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(1660, "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(1661, "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(1662, "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(1663, "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);
}
}
});
export { IActionWidgetService };
@@ -0,0 +1,29 @@
import { Emitter } from '../../../base/common/event.js';
import { registerSingleton } from '../../instantiation/common/extensions.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
import { MenuId } from '../common/actions.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const IActionViewItemService = createDecorator('IActionViewItemService');
class ActionViewItemService {
constructor() {
this._providers = new Map();
this._onDidChange = new Emitter();
this.onDidChange = this._onDidChange.event;
}
dispose() {
this._onDidChange.dispose();
}
lookUp(menu, commandOrMenuId) {
return this._providers.get(this._makeKey(menu, commandOrMenuId));
}
_makeKey(menu, commandOrMenuId) {
return `${menu.id}/${(commandOrMenuId instanceof MenuId ? commandOrMenuId.id : commandOrMenuId)}`;
}
}
registerSingleton(IActionViewItemService, ActionViewItemService, 1 /* InstantiationType.Delayed */);
export { IActionViewItemService };
@@ -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,519 @@
import { asCSSUrl } from '../../../base/browser/cssValue.js';
import { ModifierKeyEmitter, addDisposableListener, prepend, $, append, EventType } 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 { MutableDisposable, toDisposable, combinedDisposable, DisposableStore } from '../../../base/common/lifecycle.js';
import { OS, isWindows, isLinux } from '../../../base/common/platform.js';
import { ThemeIcon } from '../../../base/common/themables.js';
import { assertType } from '../../../base/common/types.js';
import { localize } from '../../../nls.js';
import { IAccessibilityService } from '../../accessibility/common/accessibility.js';
import { isICommandActionToggleInfo } from '../../action/common/action.js';
import { IConfigurationService } from '../../configuration/common/configuration.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 { defaultSelectBoxStyles } from '../../theme/browser/defaultStyles.js';
import { asCssVariable } from '../../theme/common/colorUtils.js';
import '../../theme/common/colors/baseColors.js';
import '../../theme/common/colors/chartsColors.js';
import '../../theme/common/colors/editorColors.js';
import { selectBorder } from '../../theme/common/colors/inputColors.js';
import '../../theme/common/colors/listColors.js';
import '../../theme/common/colors/menuColors.js';
import '../../theme/common/colors/minimapColors.js';
import '../../theme/common/colors/miscColors.js';
import '../../theme/common/colors/quickpickColors.js';
import '../../theme/common/colors/searchColors.js';
import { isDark } from '../../theme/common/theme.js';
import { IThemeService } from '../../theme/common/themeService.js';
import { hasNativeContextMenu } from '../../window/common/window.js';
import { IMenuService, MenuItemAction, SubmenuItemAction } from '../common/actions.js';
import './menuEntryActionViewItem.css';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
function getFlatContextMenuActions(groups, primaryGroup) {
const target = [];
getContextMenuActionsImpl(groups, target);
return target;
}
function getContextMenuActionsImpl(groups, target, primaryGroup) {
const modifierKeyEmitter = ModifierKeyEmitter.getInstance();
const useAlternativeActions = modifierKeyEmitter.keyStatus.altKey || ((isWindows || isLinux) && modifierKeyEmitter.keyStatus.shiftKey);
fillInActions(groups, target, useAlternativeActions, actionGroup => actionGroup === 'navigation');
}
function getActionBarActions(groups, primaryGroup, shouldInlineSubmenu, useSeparatorsInPrimaryActions) {
const target = { primary: [], secondary: [] };
fillInActionBarActions(groups, target, primaryGroup, shouldInlineSubmenu, useSeparatorsInPrimaryActions);
return target;
}
function getFlatActionBarActions(groups, primaryGroup, shouldInlineSubmenu, useSeparatorsInPrimaryActions) {
const target = [];
fillInActionBarActions(groups, target, primaryGroup, shouldInlineSubmenu, useSeparatorsInPrimaryActions);
return target;
}
function fillInActionBarActions(groups, target, primaryGroup, shouldInlineSubmenu, useSeparatorsInPrimaryActions) {
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, keybindingNotRenderedWithLabel: _options?.keybindingNotRenderedWithLabel });
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(1644, "{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(1645, "{0} ({1})", altTooltip, altKeybindingLabel)
: altTooltip;
title = localize(1646, "{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);
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(1647, '{1} to {0}', this._action.label, kb2);
}
else {
this.label.textContent = localize(1648, '{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);
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._defaultActionDisposables = this._register(new DisposableStore());
this._container = null;
this._options = options;
this._storageKey = `${submenuAction.item.submenu.id}_lastActionId`;
// determine default action
let defaultAction;
const defaultActionId = options?.togglePrimaryAction ? _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._defaultActionDisposables.add(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 ?? this._register(new ActionRunner()),
};
this._dropdown = this._register(new DropdownMenuActionViewItem(submenuAction, submenuAction.actions, this._contextMenuService, dropdownOptions));
if (options?.togglePrimaryAction) {
this._register(this._dropdown.actionRunner.onDidRun((e) => {
if (e.action instanceof MenuItemAction) {
this.update(e.action);
}
}));
}
}
update(lastAction) {
if (this._options?.togglePrimaryAction) {
this._storageService.store(this._storageKey, lastAction.id, 1 /* StorageScope.WORKSPACE */, 1 /* StorageTarget.MACHINE */);
}
this._defaultActionDisposables.clear();
this._defaultAction = this._defaultActionDisposables.add(this._instaService.createInstance(MenuEntryActionViewItem, lastAction, { keybinding: this._getDefaultActionKeybindingLabel(lastAction) }));
this._defaultAction.actionRunner = this._defaultActionDisposables.add(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);
}
set actionRunner(actionRunner) {
super.actionRunner = actionRunner;
this._defaultAction.actionRunner = actionRunner;
this._dropdown.actionRunner = actionRunner;
}
get actionRunner() {
return super.actionRunner;
}
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);
}
}
};
DropdownWithDefaultActionViewItem = __decorate([
__param(2, IKeybindingService),
__param(3, INotificationService),
__param(4, IContextMenuService),
__param(5, IMenuService),
__param(6, IInstantiationService),
__param(7, IStorageService)
], DropdownWithDefaultActionViewItem);
let SubmenuEntrySelectActionViewItem = class SubmenuEntrySelectActionViewItem extends SelectActionViewItem {
constructor(action, contextViewService, configurationService) {
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, useCustomDrawn: !hasNativeContextMenu(configurationService) });
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),
__param(2, IConfigurationService)
], SubmenuEntrySelectActionViewItem);
/**
* Creates action view items for menu actions or submenu actions.
*/
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.isSplitButton) {
return instaService.createInstance(DropdownWithDefaultActionViewItem, action, {
...options,
togglePrimaryAction: typeof action.item.isSplitButton !== 'boolean' ? action.item.isSplitButton.togglePrimaryAction : false,
});
}
else {
return instaService.createInstance(SubmenuEntryActionViewItem, action, options);
}
}
else {
return undefined;
}
}
export { DropdownWithDefaultActionViewItem, MenuEntryActionViewItem, SubmenuEntryActionViewItem, TextOnlyMenuEntryActionViewItem, createActionViewItem, fillInActionBarActions, getActionBarActions, getFlatActionBarActions, getFlatContextMenuActions };
@@ -0,0 +1,280 @@
import { addDisposableListener, getWindow } from '../../../base/browser/dom.js';
import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js';
import { ToolBar, ToggleMenuAction } 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 { createActionViewItem, getActionBarActions } 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';
import { IActionViewItemService } from './actionViewItemService.js';
import { IInstantiationService } from '../../instantiation/common/instantiation.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
/**
* 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(1649, "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(1650, "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);
/**
* 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 {
get onDidChangeMenuItems() { return this._onDidChangeMenuItems.event; }
constructor(container, menuId, options, menuService, contextKeyService, contextMenuService, keybindingService, commandService, telemetryService, actionViewService, instantiationService) {
super(container, {
resetMenu: menuId,
...options,
actionViewItemProvider: (action, opts) => {
let provider = actionViewService.lookUp(menuId, action instanceof SubmenuItemAction ? action.item.submenu.id : action.id);
if (!provider) {
provider = options?.actionViewItemProvider;
}
const viewItem = provider?.(action, opts, instantiationService, getWindow(container).vscodeWindowId);
if (viewItem) {
return viewItem;
}
return createActionViewItem(instantiationService, action, opts);
}
}, menuService, contextKeyService, contextMenuService, keybindingService, commandService, telemetryService);
this._onDidChangeMenuItems = this._store.add(new Emitter());
// update logic
const menu = this._store.add(menuService.createMenu(menuId, contextKeyService, { emitEventsForSubmenuChanges: true, eventDebounceDelay: options?.eventDebounceDelay }));
const updateToolbar = () => {
const { primary, secondary } = getActionBarActions(menu.getActions(options?.menuOptions), 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);
}));
this._store.add(actionViewService.onDidChange(e => {
if (e === menuId) {
updateToolbar();
}
}));
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),
__param(9, IActionViewItemService),
__param(10, IInstantiationService)
], MenuWorkbenchToolBar);
export { MenuWorkbenchToolBar, WorkbenchToolBar };
@@ -0,0 +1,483 @@
import { SubmenuAction } from '../../../base/common/actions.js';
import { MicrotaskEmitter } from '../../../base/common/event.js';
import { markAsSingleton, toDisposable, DisposableStore, dispose } from '../../../base/common/lifecycle.js';
import { LinkedList } from '../../../base/common/linkedList.js';
import { ThemeIcon } from '../../../base/common/themables.js';
import { ICommandService, CommandsRegistry } from '../../commands/common/commands.js';
import { IContextKeyService, ContextKeyExpr } from '../../contextkey/common/contextkey.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
import { KeybindingsRegistry } from '../../keybinding/common/keybindingsRegistry.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var MenuItemAction_1;
function isIMenuItem(item) {
return item.command !== undefined;
}
function isISubmenuItem(item) {
return item.submenu !== undefined;
}
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.DebugDisassemblyContext = new MenuId('DebugDisassemblyContext'); }
static { this.DebugCallStackToolbar = new MenuId('DebugCallStackToolbar'); }
static { this.DebugCreateConfiguration = new MenuId('DebugCreateConfiguration'); }
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.CompactWindowEditorTitle = new MenuId('CompactWindowEditorTitle'); }
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.EditorSplitMoveSubmenu = new MenuId('EditorSplitMoveSubmenu'); }
static { this.ExplorerContext = new MenuId('ExplorerContext'); }
static { this.ExplorerContextShare = new MenuId('ExplorerContextShare'); }
static { this.ExtensionContext = new MenuId('ExtensionContext'); }
static { this.ExtensionEditorContextMenu = new MenuId('ExtensionEditorContextMenu'); }
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.MenubarTerminalSuggestStatusMenu = new MenuId('MenubarTerminalSuggestStatusMenu'); }
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.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.SCMHistoryTitle = new MenuId('SCMHistoryTitle'); }
static { this.SCMHistoryItemContext = new MenuId('SCMHistoryItemContext'); }
static { this.SCMHistoryItemChangeContext = new MenuId('SCMHistoryItemChangeContext'); }
static { this.SCMHistoryItemRefContext = new MenuId('SCMHistoryItemRefContext'); }
static { this.SCMArtifactGroupContext = new MenuId('SCMArtifactGroupContext'); }
static { this.SCMArtifactContext = new MenuId('SCMArtifactContext'); }
static { this.SCMQuickDiffDecorations = new MenuId('SCMQuickDiffDecorations'); }
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.TestCoverageFilterItem = new MenuId('TestCoverageFilterItem'); }
static { this.TouchBarContext = new MenuId('TouchBarContext'); }
static { this.TitleBar = new MenuId('TitleBar'); }
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.NotebookToolbarContext = new MenuId('NotebookToolbarContext'); }
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.NotebookDiffDocumentMetadata = new MenuId('NotebookDiffDocumentMetadata'); }
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.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.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.ChatHistory = new MenuId('ChatHistory'); }
static { this.ChatWelcomeContext = new MenuId('ChatWelcomeContext'); }
static { this.ChatMessageFooter = new MenuId('ChatMessageFooter'); }
static { this.ChatExecute = new MenuId('ChatExecute'); }
static { this.ChatInput = new MenuId('ChatInput'); }
static { this.ChatInputSide = new MenuId('ChatInputSide'); }
static { this.ChatModePicker = new MenuId('ChatModePicker'); }
static { this.ChatEditingWidgetToolbar = new MenuId('ChatEditingWidgetToolbar'); }
static { this.ChatEditingEditorContent = new MenuId('ChatEditingEditorContent'); }
static { this.ChatEditingEditorHunk = new MenuId('ChatEditingEditorHunk'); }
static { this.ChatEditingDeletedNotebookCell = new MenuId('ChatEditingDeletedNotebookCell'); }
static { this.ChatInputAttachmentToolbar = new MenuId('ChatInputAttachmentToolbar'); }
static { this.ChatEditingWidgetModifiedFilesToolbar = new MenuId('ChatEditingWidgetModifiedFilesToolbar'); }
static { this.ChatInputResourceAttachmentContext = new MenuId('ChatInputResourceAttachmentContext'); }
static { this.ChatInputSymbolAttachmentContext = new MenuId('ChatInputSymbolAttachmentContext'); }
static { this.ChatInlineResourceAnchorContext = new MenuId('ChatInlineResourceAnchorContext'); }
static { this.ChatInlineSymbolAnchorContext = new MenuId('ChatInlineSymbolAnchorContext'); }
static { this.ChatMessageCheckpoint = new MenuId('ChatMessageCheckpoint'); }
static { this.ChatMessageRestoreCheckpoint = new MenuId('ChatMessageRestoreCheckpoint'); }
static { this.ChatNewMenu = new MenuId('ChatNewMenu'); }
static { this.ChatEditingCodeBlockContext = new MenuId('ChatEditingCodeBlockContext'); }
static { this.ChatTitleBarMenu = new MenuId('ChatTitleBarMenu'); }
static { this.ChatAttachmentsContext = new MenuId('ChatAttachmentsContext'); }
static { this.ChatToolOutputResourceToolbar = new MenuId('ChatToolOutputResourceToolbar'); }
static { this.ChatTextEditorMenu = new MenuId('ChatTextEditorMenu'); }
static { this.ChatToolOutputResourceContext = new MenuId('ChatToolOutputResourceContext'); }
static { this.ChatMultiDiffContext = new MenuId('ChatMultiDiffContext'); }
static { this.ChatSessionsMenu = new MenuId('ChatSessionsMenu'); }
static { this.ChatSessionsCreateSubMenu = new MenuId('ChatSessionsCreateSubMenu'); }
static { this.ChatConfirmationMenu = new MenuId('ChatConfirmationMenu'); }
static { this.ChatEditorInlineExecute = new MenuId('ChatEditorInputExecute'); }
static { this.ChatEditorInlineInputSide = new MenuId('ChatEditorInputSide'); }
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;
}
}
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;
}
}
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 markAsSingleton(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 markAsSingleton(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 });
}
});
}
};
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);
class Action2 {
constructor(desc) {
this.desc = desc;
}
}
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 ?? { description: action.desc.title }
}));
// 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
export { Action2, IMenuService, MenuId, MenuItemAction, MenuRegistry, SubmenuItemAction, isIMenuItem, isISubmenuItem, registerAction2 };
@@ -0,0 +1,436 @@
import { RunOnceScheduler } from '../../../base/common/async.js';
import { Emitter, DebounceEmitter } from '../../../base/common/event.js';
import { DisposableStore } from '../../../base/common/lifecycle.js';
import { isIMenuItem, MenuItemAction, SubmenuItemAction, MenuRegistry, isISubmenuItem } 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';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var PersistedMenuHideState_1, MenuInfo_1;
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);
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._allMenuIds = new Set();
this._structureContextKeys = new Set();
this._preconditionContextKeys = new Set();
this._toggledContextKeys = new Set();
this.refresh();
}
get allMenuIds() {
return this._allMenuIds;
}
get structureContextKeys() {
return this._structureContextKeys;
}
get preconditionContextKeys() {
return this._preconditionContextKeys;
}
get toggledContextKeys() {
return this._toggledContextKeys;
}
refresh() {
// reset
this._menuGroups.length = 0;
this._allMenuIds.clear();
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 and submenu ids for eventing
this._collectContextKeysAndSubmenuIds(item);
}
this._allMenuIds.add(this._id);
}
_sort(menuItems) {
// no sorting needed in snapshot
return menuItems;
}
_collectContextKeysAndSubmenuIds(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._collectContextKeysAndSubmenuIds, this);
this._allMenuIds.add(item.submenu);
}
}
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 => {
for (const id of this._menuInfo.allMenuIds) {
if (e.has(id)) {
rebuildMenuSoon.schedule();
break;
}
}
}));
// 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(1651, '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; },
};
}
function createConfigureKeybindingAction(commandService, keybindingService, commandId, when = undefined, enabled = true) {
return toAction({
id: `configureKeybinding/${commandId}`,
label: localize(1652, "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}` : ''));
}
});
}
export { MenuService, createConfigureKeybindingAction };
@@ -0,0 +1,215 @@
import { isSafari, isWebkitWebView } from '../../../base/browser/browser.js';
import { onDidRegisterWindow, addDisposableListener, getActiveWindow, getActiveDocument, $, isHTMLElement } 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 { URI } from '../../../base/common/uri.js';
import { ILayoutService } from '../../layout/browser/layoutService.js';
import { ILogService } from '../../log/common/log.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var BrowserClipboardService_1;
/**
* Custom mime type used for storing a list of uris in the clipboard.
*
* Requires support for custom web clipboards https://github.com/w3c/clipboard-apis/pull/175
*/
const vscodeResourcesMime = 'application/vnd.code.resources';
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.clearResourcesState()));
}, { window: mainWindow, disposables: this._store }));
}
triggerPaste() {
this.logService.trace('BrowserClipboardService#triggerPaste');
return undefined;
}
// 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) {
this.logService.trace('BrowserClipboardService#writeText called with type:', type, ' text.length:', text.length);
// Clear resources given we are writing text
this.clearResourcesState();
// With type: only in-memory is supported
if (type) {
this.mapTextToType.set(type, text);
this.logService.trace('BrowserClipboardService#writeText');
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 {
this.logService.trace('before navigator.clipboard.writeText');
return await getActiveWindow().navigator.clipboard.writeText(text);
}
catch (error) {
console.error(error);
}
// Fallback to textarea and execCommand solution
this.fallbackWriteText(text);
}
fallbackWriteText(text) {
this.logService.trace('BrowserClipboardService#fallbackWriteText');
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) {
this.logService.trace('BrowserClipboardService#readText called with type:', type);
// With type: only in-memory is supported
if (type) {
const readText = this.mapTextToType.get(type) || '';
this.logService.trace('BrowserClipboardService#readText text.length:', readText.length);
return readText;
}
// Guard access to navigator.clipboard with try/catch
// as we have seen DOMExceptions in certain browsers
// due to security policies.
try {
const readText = await getActiveWindow().navigator.clipboard.readText();
this.logService.trace('BrowserClipboardService#readText text.length:', readText.length);
return 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 readResources() {
// Guard access to navigator.clipboard with try/catch
// as we have seen DOMExceptions in certain browsers
// due to security policies.
try {
const items = await getActiveWindow().navigator.clipboard.read();
for (const item of items) {
if (item.types.includes(`web ${vscodeResourcesMime}`)) {
const blob = await item.getType(`web ${vscodeResourcesMime}`);
const resources = JSON.parse(await blob.text()).map(x => URI.from(x));
return resources;
}
}
}
catch (error) {
// Noop
}
const resourcesStateHash = await this.computeResourcesStateHash();
if (this.resourcesStateHash !== resourcesStateHash) {
this.clearResourcesState(); // 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));
}
clearInternalState() {
this.clearResourcesState();
}
clearResourcesState() {
this.resources = [];
this.resourcesStateHash = undefined;
}
};
BrowserClipboardService = BrowserClipboardService_1 = __decorate([
__param(0, ILayoutService),
__param(1, ILogService)
], BrowserClipboardService);
export { BrowserClipboardService };
@@ -0,0 +1,9 @@
import { createDecorator } from '../../instantiation/common/instantiation.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const IClipboardService = createDecorator('clipboardService');
export { IClipboardService };
@@ -0,0 +1,83 @@
import { Emitter } from '../../../base/common/event.js';
import { Iterable } from '../../../base/common/iterator.js';
import { markAsSingleton, 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';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const ICommandService = createDecorator('commandService');
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 markAsSingleton(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', () => { });
export { CommandsRegistry, ICommandService };
@@ -0,0 +1,92 @@
import { createDecorator } from '../../instantiation/common/instantiation.js';
const IConfigurationService = createDecorator('configurationService');
function toValuesTree(properties, conflictReporter) {
const root = Object.create(null);
for (const key in properties) {
addToValueTree(root, key, properties[key], conflictReporter);
}
return root;
}
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)}`);
}
}
function removeFromValueTree(valueTree, key) {
const segments = key.split('.');
doRemoveFromValueTree(valueTree, segments);
}
function doRemoveFromValueTree(valueTree, segments) {
if (!valueTree) {
return;
}
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];
}
}
}
}
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;
}
function getLanguageTagSettingPlainKey(settingKey) {
return settingKey
.replace(/^\[/, '')
.replace(/]$/g, '')
.replace(/\]\[/g, ', ');
}
export { IConfigurationService, addToValueTree, getConfigurationValue, getLanguageTagSettingPlainKey, removeFromValueTree, toValuesTree };
@@ -0,0 +1,614 @@
import { equals, distinct } from '../../../base/common/arrays.js';
import { ResourceMap } from '../../../base/common/map.js';
import { deepClone, equals as equals$1, deepFreeze } from '../../../base/common/objects.js';
import { isObject } from '../../../base/common/types.js';
import { URI } from '../../../base/common/uri.js';
import { getConfigurationValue, removeFromValueTree, addToValueTree, toValuesTree } from './configuration.js';
import { OVERRIDE_PROPERTY_REGEX, overrideIdentifiersFromKey, Extensions } from './configurationRegistry.js';
import { Registry } from '../../registry/common/platform.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function freeze(data) {
return Object.isFrozen(data) ? data : deepFreeze(data);
}
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) {
const rawConfigurationModels = (Array.isArray(this.raw) ? this.raw : [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 = deepClone(this.contents);
const overrides = deepClone(this.overrides);
const keys = [...this.keys];
const raws = this.raw ? Array.isArray(this.raw) ? [...this.raw] : [this.raw] : [this];
for (const other of others) {
raws.push(...(other.raw ? Array.isArray(other.raw) ? other.raw : [other.raw] : [other]));
if (other.isEmpty()) {
continue;
}
this.mergeContents(contents, other.contents);
for (const otherOverride of other.overrides) {
const [override] = overrides.filter(o => equals(o.identifiers, otherOverride.identifiers));
if (override) {
this.mergeContents(override.contents, otherOverride.contents);
override.keys.push(...otherOverride.keys);
override.keys = distinct(override.keys);
}
else {
overrides.push(deepClone(otherOverride));
}
}
for (const key of other.keys) {
if (keys.indexOf(key) === -1) {
keys.push(key);
}
}
}
return new ConfigurationModel(contents, keys, overrides, !raws.length || 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 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 = 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 (isObject(source[key]) && isObject(target[key])) {
this.mergeContents(source[key], target[key]);
continue;
}
}
source[key] = deepClone(target[key]);
}
}
getContentsForOverrideIdentifer(identifier) {
let contentsForIdentifierOnly = null;
let contents = null;
const mergeContents = (contentsToMerge) => {
if (contentsToMerge) {
if (contents) {
this.mergeContents(contents, contentsToMerge);
}
else {
contents = 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 => 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 => equals(o.identifiers, identifiers));
if (index !== -1) {
this.overrides[index] = override;
}
else {
this.overrides.push(override);
}
}
}
}
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 registry = Registry.as(Extensions.Configuration);
const configurationProperties = registry.getConfigurationProperties();
const excludedConfigurationProperties = registry.getExcludedConfigurationProperties();
const filtered = this.filter(raw, configurationProperties, excludedConfigurationProperties, 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, excludedConfigurationProperties, filterOverriddenProperties, options) {
let hasExcludedProperties = false;
if (!options?.scopes && !options?.skipRestricted && !options?.skipUnregistered && !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, excludedConfigurationProperties, false, options);
raw[key] = result.raw;
hasExcludedProperties = hasExcludedProperties || result.hasExcludedProperties;
restricted.push(...result.restricted);
}
else {
const propertySchema = configurationProperties[key];
if (propertySchema?.restricted) {
restricted.push(key);
}
if (this.shouldInclude(key, propertySchema, excludedConfigurationProperties, options)) {
raw[key] = properties[key];
}
else {
hasExcludedProperties = true;
}
}
}
return { raw, restricted, hasExcludedProperties };
}
shouldInclude(key, propertySchema, excludedConfigurationProperties, options) {
if (options.exclude?.includes(key)) {
return false;
}
if (options.include?.includes(key)) {
return true;
}
if (options.skipRestricted && propertySchema?.restricted) {
return false;
}
if (options.skipUnregistered && !propertySchema) {
return false;
}
const schema = propertySchema ?? excludedConfigurationProperties[key];
const scope = schema ? typeof schema.scope !== 'undefined' ? schema.scope : 4 /* ConfigurationScope.WINDOW */ : undefined;
if (scope === undefined || options.scopes === undefined) {
return true;
}
return options.scopes.includes(scope);
}
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);
}
}
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) {
if (this._remoteUserConfiguration.isEmpty()) {
this._userConfiguration = this._localUserConfiguration;
}
else {
const merged = this._localUserConfiguration.merge(this._remoteUserConfiguration);
this._userConfiguration = new ConfigurationModel(merged.contents, merged.keys, merged.overrides, undefined, this.logService);
}
}
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) {
// clone by merging
configurationModel = configurationModel.merge();
for (const key of this._policyConfiguration.keys) {
configurationModel.setValue(key, this._policyConfiguration.getValue(key));
}
}
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,
raw: Array.isArray(this.applicationConfiguration.raw) ? undefined : this.applicationConfiguration.raw
},
userLocal: {
contents: this.localUserConfiguration.contents,
overrides: this.localUserConfiguration.overrides,
keys: this.localUserConfiguration.keys,
raw: Array.isArray(this.localUserConfiguration.raw) ? undefined : this.localUserConfiguration.raw
},
userRemote: {
contents: this.remoteUserConfiguration.contents,
overrides: this.remoteUserConfiguration.overrides,
keys: this.remoteUserConfiguration.keys,
raw: Array.isArray(this.remoteUserConfiguration.raw) ? undefined : this.remoteUserConfiguration.raw
},
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 userLocalConfiguration = this.parseConfigurationModel(data.userLocal, logService);
const userRemoteConfiguration = this.parseConfigurationModel(data.userRemote, 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, userLocalConfiguration, userRemoteConfiguration, workspaceConfiguration, folders, ConfigurationModel.createEmptyModel(logService), new ResourceMap(), logService);
}
static parseConfigurationModel(model, logService) {
return new ConfigurationModel(model.contents, model.keys, model.overrides, model.raw, logService);
}
}
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 !equals$1(value1, value2);
}
return true;
}
}
export { Configuration, ConfigurationChangeEvent, ConfigurationModel, ConfigurationModelParser };
@@ -0,0 +1,400 @@
import { distinct } from '../../../base/common/arrays.js';
import { Emitter } from '../../../base/common/event.js';
import { isObject, isUndefined, isUndefinedOrNull } from '../../../base/common/types.js';
import { localize } from '../../../nls.js';
import { getLanguageTagSettingPlainKey } from './configuration.js';
import { Extensions as Extensions$1 } from '../../jsonschemas/common/jsonContributionRegistry.js';
import { Registry } from '../../registry/common/platform.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import product from '../../product/common/product.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const Extensions = {
Configuration: 'base.contributions.configuration'
};
const resourceLanguageSettingsSchemaId = 'vscode://schemas/settings/resourceLanguage';
const contributionRegistry = Registry.as(Extensions$1.JSONContribution);
class ConfigurationRegistry extends Disposable {
constructor() {
super();
this.registeredConfigurationDefaults = [];
this.overrideIdentifiers = new Set();
this._onDidSchemaChange = this._register(new Emitter());
this._onDidUpdateConfiguration = this._register(new Emitter());
this.configurationDefaultsOverrides = new Map();
this.defaultLanguageConfigurationOverridesNode = {
id: 'defaultOverrides',
title: localize(1664, "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);
return configuration;
}
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 = {
section: {
id: this.defaultLanguageConfigurationOverridesNode.id,
title: this.defaultLanguageConfigurationOverridesNode.title,
order: this.defaultLanguageConfigurationOverridesNode.order,
extensionInfo: this.defaultLanguageConfigurationOverridesNode.extensionInfo
},
type: 'object',
default: newDefaultOverride.value,
description: localize(1665, "Configure settings to be overridden for {0}.", 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 = isObject(propertyDefaultValue) &&
(isUndefined(defaultValue[propertyKey]) || 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 = isObject(value) &&
(property !== undefined && property.type === 'object' ||
property === undefined && (isUndefined(existingDefaultValue) || 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 = { ...(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 = 4 /* ConfigurationScope.WINDOW */, bucket) {
scope = isUndefinedOrNull(configuration.scope) ? scope : configuration.scope;
const properties = configuration.properties;
if (properties) {
for (const key in properties) {
const property = properties[key];
property.section = {
id: configuration.id,
title: configuration.title,
order: configuration.order,
extensionInfo: configuration.extensionInfo
};
if (validate && validateProperty(key, property, extensionInfo?.id)) {
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 = isUndefinedOrNull(property.scope) ? scope : property.scope;
property.restricted = isUndefinedOrNull(property.restricted) ? !!restrictedProperties?.includes(key) : property.restricted;
}
if (property.experiment) {
if (!property.tags?.some(tag => tag.toLowerCase() === 'onexp')) {
property.tags = property.tags ?? [];
property.tags.push('onExP');
}
}
else if (property.tags?.some(tag => tag.toLowerCase() === 'onexp')) {
console.error(`Invalid tag 'onExP' found for property '${key}'. Please use 'experiment' property instead.`);
property.experiment = { mode: 'startup' };
}
const excluded = properties[key].hasOwnProperty('included') && !properties[key].included;
const policyName = properties[key].policy?.name;
if (excluded) {
this.excludedConfigurationProperties[key] = properties[key];
if (policyName) {
this.policyConfigurations.set(policyName, key);
bucket.add(key);
}
delete properties[key];
}
else {
bucket.add(key);
if (policyName) {
this.policyConfigurations.set(policyName, key);
}
this.configurationProperties[key] = properties[key];
if (!properties[key].deprecationMessage && properties[key].markdownDeprecationMessage) {
// If not set, default deprecationMessage to the markdown source
properties[key].deprecationMessage = properties[key].markdownDeprecationMessage;
}
}
}
}
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;
}
getExcludedConfigurationProperties() {
return this.excludedConfigurationProperties;
}
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) {
switch (property.scope) {
case 1 /* ConfigurationScope.APPLICATION */:
break;
case 2 /* ConfigurationScope.MACHINE */:
break;
case 3 /* ConfigurationScope.APPLICATION_MACHINE */:
break;
case 7 /* ConfigurationScope.MACHINE_OVERRIDABLE */:
break;
case 4 /* ConfigurationScope.WINDOW */:
break;
case 5 /* ConfigurationScope.RESOURCE */:
break;
case 6 /* ConfigurationScope.LANGUAGE_OVERRIDABLE */:
this.resourceLanguageSettingsSchema.properties[key] = property;
break;
}
}
updateOverridePropertyPatternKey() {
for (const overrideIdentifier of this.overrideIdentifiers.values()) {
const overrideIdentifierProperty = `[${overrideIdentifier}]`;
const resourceLanguagePropertiesSchema = {
type: 'object',
description: localize(1666, "Configure editor settings to be overridden for a language."),
errorMessage: localize(1667, "This setting does not support per-language configuration."),
$ref: resourceLanguageSettingsSchemaId,
};
this.updatePropertyDefaultValue(overrideIdentifierProperty, resourceLanguagePropertiesSchema);
}
}
registerOverridePropertyPatternKey() {
({
description: localize(1668, "Configure editor settings to be overridden for a language."),
errorMessage: localize(1669, "This setting does not support per-language configuration.")});
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 (isUndefined(defaultValue)) {
defaultValue = property.defaultDefaultValue;
defaultSource = undefined;
}
if (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');
const OVERRIDE_PROPERTY_PATTERN = `^(${OVERRIDE_IDENTIFIER_PATTERN})+$`;
const OVERRIDE_PROPERTY_REGEX = new RegExp(OVERRIDE_PROPERTY_PATTERN);
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);
}
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);
function validateProperty(property, schema, extensionId) {
if (!property.trim()) {
return localize(1670, "Cannot register an empty property");
}
if (OVERRIDE_PROPERTY_REGEX.test(property)) {
return localize(1671, "Cannot register '{0}'. This matches property pattern '\\\\[.*\\\\]$' for describing language specific editor settings. Use 'configurationDefaults' contribution.", property);
}
if (configurationRegistry.getConfigurationProperties()[property] !== undefined && (!extensionId || !EXTENSION_UNIFICATION_EXTENSION_IDS.has(extensionId.toLowerCase()))) {
return localize(1672, "Cannot register '{0}'. This property is already registered.", property);
}
if (schema.policy?.name && configurationRegistry.getPolicyConfigurations().get(schema.policy?.name) !== undefined) {
return localize(1673, "Cannot register '{0}'. The associated policy {1} is already registered with {2}.", property, schema.policy?.name, configurationRegistry.getPolicyConfigurations().get(schema.policy?.name));
}
return null;
}
// Used for extension unification. Should be removed when complete.
const EXTENSION_UNIFICATION_EXTENSION_IDS = new Set(product.defaultChatAgent ? [product.defaultChatAgent.extensionId, product.defaultChatAgent.chatExtensionId].map(id => id.toLowerCase()) : []);
export { EXTENSION_UNIFICATION_EXTENSION_IDS, Extensions, OVERRIDE_PROPERTY_PATTERN, OVERRIDE_PROPERTY_REGEX, getDefaultValue, overrideIdentifiersFromKey, resourceLanguageSettingsSchemaId, validateProperty };
@@ -0,0 +1,46 @@
import { Disposable } from '../../../base/common/lifecycle.js';
import { deepClone } from '../../../base/common/objects.js';
import { ConfigurationModel } from './configurationModels.js';
import { Extensions } from './configurationRegistry.js';
import { Registry } from '../../registry/common/platform.js';
class DefaultConfiguration extends Disposable {
get configurationModel() {
return this._configurationModel;
}
constructor(logService) {
super();
this.logService = logService;
this._configurationModel = ConfigurationModel.createEmptyModel(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, deepClone(propertySchema.default));
}
else {
this._configurationModel.removeValue(key);
}
}
}
}
export { DefaultConfiguration };
@@ -0,0 +1,468 @@
import { Event, PauseableEmitter } from '../../../base/common/event.js';
import { Iterable } from '../../../base/common/iterator.js';
import { MutableDisposable, DisposableStore, Disposable } 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 { RawContextKey, IContextKeyService } from '../common/contextkey.js';
import { InputFocusedContext } from '../common/contextkeys.js';
import { mainWindow } from '../../../base/browser/window.js';
import { onDidRegisterWindow, addDisposableListener, EventType, trackFocus, getActiveWindow, isEditableElement } from '../../../base/browser/dom.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
const KEYBINDING_CONTEXT_ATTR = 'data-keybinding-context';
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)));
}
class AbstractContextKeyService extends Disposable {
get onDidChangeContext() { return this._onDidChangeContext.event; }
constructor(myContextId) {
super();
this._onDidChangeContext = this._register(new PauseableEmitter({ merge: input => new CompositeContextKeyChangeEvent(input) }));
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;
this.inputFocusedContext = InputFocusedContext.bindTo(this);
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);
this._register(Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => {
const onFocusDisposables = disposables.add(new MutableDisposable());
disposables.add(addDisposableListener(window, EventType.FOCUS_IN, () => {
onFocusDisposables.value = new DisposableStore();
this.updateInputContextKeys(window.document, onFocusDisposables.value);
}, true));
}, { window: mainWindow, disposables: this._store }));
}
updateInputContextKeys(ownerDocument, disposables) {
function activeElementIsInput() {
return !!ownerDocument.activeElement && isEditableElement(ownerDocument.activeElement);
}
const isInputFocused = activeElementIsInput();
this.inputFocusedContext.set(isInputFocused);
if (isInputFocused) {
const tracker = disposables.add(trackFocus(ownerDocument.activeElement));
Event.once(tracker.onDidBlur)(() => {
// Ensure we are only updating the context key if we are
// still in the same document that we are tracking. This
// fixes a race condition in multi-window setups where
// the blur event arrives in the inactive window overwriting
// the context key of the active window. This is because
// blur events from the focus tracker are emitted with a
// timeout of 0.
if (getActiveWindow().document === ownerDocument) {
this.inputFocusedContext.set(activeElementIsInput());
}
tracker.dispose();
}, undefined, disposables);
}
}
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);
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;
}
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(1674, "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));
});
export { AbstractContextKeyService, Context, ContextKeyService, setContext };
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,21 @@
import { isMacintosh, isLinux, isWindows, isWeb, isIOS, isMobile } from '../../../base/common/platform.js';
import { localize } from '../../../nls.js';
import { RawContextKey } from './contextkey.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
new RawContextKey('isMac', isMacintosh, localize(1684, "Whether the operating system is macOS"));
new RawContextKey('isLinux', isLinux, localize(1685, "Whether the operating system is Linux"));
const IsWindowsContext = new RawContextKey('isWindows', isWindows, localize(1686, "Whether the operating system is Windows"));
const IsWebContext = new RawContextKey('isWeb', isWeb, localize(1687, "Whether the platform is a web browser"));
new RawContextKey('isMacNative', isMacintosh && !isWeb, localize(1688, "Whether the operating system is macOS on a non-browser platform"));
new RawContextKey('isIOS', isIOS, localize(1689, "Whether the operating system is iOS"));
new RawContextKey('isMobile', isMobile, localize(1690, "Whether the platform is a mobile web browser"));
new RawContextKey('isDevelopment', false, true);
new RawContextKey('productQualityType', '', localize(1691, "Quality type of VS Code"));
const InputFocusedContextKey = 'inputFocus';
const InputFocusedContext = new RawContextKey(InputFocusedContextKey, false, localize(1692, "Whether keyboard focus is inside an input box"));
export { InputFocusedContext, InputFocusedContextKey, IsWebContext, IsWindowsContext };
@@ -0,0 +1,287 @@
import { illegalState } from '../../../base/common/errors.js';
import { localize } from '../../../nls.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function hintDidYouMean(...meant) {
switch (meant.length) {
case 1:
return localize(1693, "Did you mean {0}?", meant[0]);
case 2:
return localize(1694, "Did you mean {0} or {1}?", meant[0], meant[1]);
case 3:
return localize(1695, "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(1696, "Did you forget to open or close the quote?");
const hintDidYouForgetToEscapeSlash = localize(1697, "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
* }
* ```
*/
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;
}
}
export { Scanner };
@@ -0,0 +1,126 @@
import { getActiveElement, isHTMLElement, isAncestor, $, addDisposableListener, EventType, getWindow } 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 { DisposableStore, combinedDisposable } from '../../../base/common/lifecycle.js';
import { defaultMenuStyles } from '../../theme/browser/defaultStyles.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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,
layer: delegate.layer,
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 || menuDisposables.add(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);
}
}
}
export { ContextMenuHandler };
@@ -0,0 +1,103 @@
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 { getFlatContextMenuActions } 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';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
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());
this.onDidHideContextMenu = this._onDidHideContextMenu.event;
}
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);
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: () => {
let target = [];
if (menuId) {
const menu = menuService.getMenuActions(menuId, contextKeyService ?? globalContextKeyService, menuActionOptions);
target = getFlatContextMenuActions(menu);
}
if (!delegate.getActions) {
return target;
}
else {
return Separator.join(delegate.getActions(), target);
}
}
};
}
ContextMenuMenuDelegate.transform = transform;
})(ContextMenuMenuDelegate || (ContextMenuMenuDelegate = {}));
export { ContextMenuMenuDelegate, ContextMenuService };
@@ -0,0 +1,10 @@
import { createDecorator } from '../../instantiation/common/instantiation.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const IContextViewService = createDecorator('contextViewService');
const IContextMenuService = createDecorator('contextMenuService');
export { IContextMenuService, IContextViewService };
@@ -0,0 +1,73 @@
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';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
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);
class ContextViewService extends ContextViewHandler {
getContextViewElement() {
return this.contextView.getViewElement();
}
}
export { ContextViewHandler, ContextViewService };
@@ -0,0 +1,60 @@
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
import { IDataChannelService } from '../common/dataChannel.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
class InterceptingTelemetryService {
constructor(_baseService, _intercept) {
this._baseService = _baseService;
this._intercept = _intercept;
}
publicLog2(eventName, data) {
this._intercept(eventName, data);
this._baseService.publicLog2(eventName, data);
}
}
let DataChannelForwardingTelemetryService = class DataChannelForwardingTelemetryService extends InterceptingTelemetryService {
constructor(telemetryService, dataChannelService) {
super(telemetryService, (eventName, data) => {
// filter for extension
let forward = true;
if (data && shouldForwardToChannel in data) {
forward = Boolean(data[shouldForwardToChannel]);
}
if (forward) {
dataChannelService.getDataChannel('editTelemetry').sendData({ eventName, data: data ?? {} });
}
});
}
};
DataChannelForwardingTelemetryService = __decorate([
__param(0, ITelemetryService),
__param(1, IDataChannelService)
], DataChannelForwardingTelemetryService);
const shouldForwardToChannel = Symbol('shouldForwardToChannel');
function forwardToChannelIf(value) {
return {
// This will not be sent via telemetry, it is just a marker
[shouldForwardToChannel]: value
};
}
function isCopilotLikeExtension(extensionId) {
if (!extensionId) {
return false;
}
const extIdLowerCase = extensionId.toLowerCase();
return extIdLowerCase === 'github.copilot' || extIdLowerCase === 'github.copilot-chat';
}
export { DataChannelForwardingTelemetryService, InterceptingTelemetryService, forwardToChannelIf, isCopilotLikeExtension };
@@ -0,0 +1,12 @@
import { createDecorator } from '../../instantiation/common/instantiation.js';
const IDataChannelService = createDecorator('dataChannelService');
class NullDataChannelService {
getDataChannel(_channelId) {
return {
sendData: () => { },
};
}
}
export { IDataChannelService, NullDataChannelService };
@@ -0,0 +1,5 @@
import { createDecorator } from '../../instantiation/common/instantiation.js';
const IDialogService = createDecorator('dialogService');
export { IDialogService };
+49
View File
@@ -0,0 +1,49 @@
import { isNative } from '../../../base/common/platform.js';
import { Registry } from '../../registry/common/platform.js';
//#region Editor / Resources DND
const CodeDataTransfers = {
EDITORS: 'CodeEditors',
FILES: 'CodeFiles'};
class DragAndDropContributionRegistry {
}
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.
*/
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;
}
}
/**
* A helper to get access to Electrons `webUtils.getPathForFile` function
* in a safe way without crashing the application when running in the web.
*/
function getPathForFile(file) {
if (isNative && typeof globalThis.vscode?.webUtils?.getPathForFile === 'function') {
return globalThis.vscode?.webUtils?.getPathForFile(file);
}
return undefined;
}
//#endregion
export { CodeDataTransfers, Extensions, LocalSelectionTransfer, getPathForFile };
@@ -0,0 +1,15 @@
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 = {}));
export { EditorOpenSource };
@@ -0,0 +1,5 @@
import { createDecorator } from '../../instantiation/common/instantiation.js';
const IEnvironmentService = createDecorator('environmentService');
export { IEnvironmentService };
@@ -0,0 +1,49 @@
/**
* **!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.
*/
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;
}
}
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));
}
}
export { ExtensionIdentifier, ExtensionIdentifierSet };
@@ -0,0 +1,19 @@
import { createDecorator } from '../../instantiation/common/instantiation.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//#region file service & providers
const IFileService = createDecorator('fileService');
//#endregion
//#region Utilities
var FileKind;
(function (FileKind) {
FileKind[FileKind["FILE"] = 0] = "FILE";
FileKind[FileKind["FOLDER"] = 1] = "FOLDER";
FileKind[FileKind["ROOT_FOLDER"] = 2] = "ROOT_FOLDER";
})(FileKind || (FileKind = {}));
//#endregion
export { FileKind, IFileService };
@@ -0,0 +1,106 @@
import { FindInput } from '../../../base/browser/ui/findinput/findInput.js';
import { ReplaceInput } from '../../../base/browser/ui/findinput/replaceInput.js';
import { RawContextKey, IContextKeyService, ContextKeyExpr } 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';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
const historyNavigationVisible = new RawContextKey('suggestWidgetVisible', false, localize(1698, "Whether suggestion are visible"));
const HistoryNavigationWidgetFocusContext = 'historyNavigationWidgetFocus';
const HistoryNavigationForwardsEnablementContext = 'historyNavigationForwardsEnabled';
const HistoryNavigationBackwardsEnablementContext = 'historyNavigationBackwardsEnabled';
let lastFocusedWidget = undefined;
const widgets = [];
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);
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);
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();
}
});
export { ContextScopedFindInput, ContextScopedReplaceInput, historyNavigationVisible, registerAndCreateHistoryNavigationContext };
@@ -0,0 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function showHistoryKeybindingHint(keybindingService) {
return keybindingService.lookupKeybinding('history.showPrevious')?.getElectronAccelerator() === 'Up' && keybindingService.lookupKeybinding('history.showNext')?.getElectronAccelerator() === 'Down';
}
export { showHistoryKeybindingHint };
@@ -0,0 +1,143 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* stylelint-disable layer-checker */
.monaco-hover.workbench-hover {
position: relative;
font-size: 13px;
line-height: 19px;
/* Must be higher than sash's z-index and terminal canvases */
z-index: 40;
overflow: hidden;
max-width: 700px;
background: var(--vscode-editorHoverWidget-background);
border: 1px solid var(--vscode-editorHoverWidget-border);
border-radius: 5px;
color: var(--vscode-editorHoverWidget-foreground);
box-shadow: 0 2px 8px var(--vscode-widget-shadow);
}
.monaco-hover.workbench-hover .monaco-action-bar .action-item .codicon {
/* Given our font-size, adjust action icons accordingly */
width: 13px;
height: 13px;
}
.monaco-hover.workbench-hover hr {
border-bottom: none;
}
.monaco-hover.workbench-hover.compact {
font-size: 12px;
}
.monaco-hover.workbench-hover.compact .monaco-action-bar .action-item .codicon {
/* Given our font-size, adjust action icons accordingly */
width: 12px;
height: 12px;
}
.monaco-hover.workbench-hover.compact .hover-contents {
padding: 2px 8px;
}
.workbench-hover-container.locked .monaco-hover.workbench-hover {
outline: 1px solid var(--vscode-editorHoverWidget-border);
}
.workbench-hover-container:focus-within.locked .monaco-hover.workbench-hover {
outline-color: var(--vscode-focusBorder);
}
.workbench-hover-pointer {
position: absolute;
/* Must be higher than workbench hover z-index */
z-index: 41;
pointer-events: none;
}
.workbench-hover-pointer:after {
content: '';
position: absolute;
width: 5px;
height: 5px;
background-color: var(--vscode-editorHoverWidget-background);
border-right: 1px solid var(--vscode-editorHoverWidget-border);
border-bottom: 1px solid var(--vscode-editorHoverWidget-border);
}
.workbench-hover-container:not(:focus-within).locked .workbench-hover-pointer:after {
width: 4px;
height: 4px;
border-right-width: 2px;
border-bottom-width: 2px;
}
.workbench-hover-container:focus-within .workbench-hover-pointer:after {
border-right: 1px solid var(--vscode-focusBorder);
border-bottom: 1px solid var(--vscode-focusBorder);
}
.workbench-hover-pointer.left { left: -3px; }
.workbench-hover-pointer.right { right: 3px; }
.workbench-hover-pointer.top { top: -3px; }
.workbench-hover-pointer.bottom { bottom: 3px; }
.workbench-hover-pointer.left:after {
transform: rotate(135deg);
}
.workbench-hover-pointer.right:after {
transform: rotate(315deg);
}
.workbench-hover-pointer.top:after {
transform: rotate(225deg);
}
.workbench-hover-pointer.bottom:after {
transform: rotate(45deg);
}
.monaco-hover.workbench-hover a {
color: var(--vscode-textLink-foreground);
}
.monaco-hover.workbench-hover a:focus {
outline: 1px solid;
outline-offset: -1px;
text-decoration: underline;
outline-color: var(--vscode-focusBorder);
}
.monaco-hover.workbench-hover a.codicon:focus,
.monaco-hover.workbench-hover a.monaco-button:focus {
text-decoration: none;
}
.monaco-hover.workbench-hover a:hover,
.monaco-hover.workbench-hover a:active {
color: var(--vscode-textLink-activeForeground);
}
.monaco-hover.workbench-hover code {
background: var(--vscode-textCodeBlock-background);
}
.monaco-hover.workbench-hover .hover-row .actions {
background: var(--vscode-editorHoverWidget-statusBarBackground);
}
.monaco-hover.workbench-hover.right-aligned {
/* The context view service wraps strangely when it's right up against the edge without this */
left: 1px;
}
.monaco-hover.workbench-hover.right-aligned .hover-row.status-bar .actions {
flex-direction: row-reverse;
}
.monaco-hover.workbench-hover.right-aligned .hover-row.status-bar .actions .action-container {
margin-right: 0;
margin-left: 16px;
}
@@ -0,0 +1,103 @@
import { createDecorator } from '../../instantiation/common/instantiation.js';
import { Disposable, DisposableStore } from '../../../base/common/lifecycle.js';
import { IConfigurationService } from '../../configuration/common/configuration.js';
import { isHTMLElement, addStandardDisposableListener } from '../../../base/browser/dom.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
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
}
if (this.hoverOptions?.dynamicDelay) {
return content => this.hoverOptions?.dynamicDelay?.(content) ?? this._delay;
}
return this._delay;
}
constructor(placement, hoverOptions, overrideOptions = {}, configurationService, hoverService) {
super();
this.placement = placement;
this.hoverOptions = hoverOptions;
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
: typeof options.content === 'string'
? options.content.toString()
: options.content.value;
return this.hoverService.showInstantHover({
...options,
...overrideOptions,
persistence: {
hideOnKeyDown: true,
...overrideOptions.persistence
},
id,
appearance: {
...options.appearance,
compact: true,
skipFadeInAnimation: this.isInstantlyHovering(),
...overrideOptions.appearance
}
}, focus);
}
isInstantlyHovering() {
return !!this.hoverOptions?.instantHover && Date.now() - this.lastHoverHideTime < this.timeLimit;
}
onDidHideHover() {
this.hoverDisposables.clear();
if (this.hoverOptions?.instantHover) {
this.lastHoverHideTime = Date.now();
}
}
};
WorkbenchHoverDelegate = __decorate([
__param(3, IConfigurationService),
__param(4, IHoverService)
], WorkbenchHoverDelegate);
// TODO@benibenj remove this, only temp fix for contextviews
const nativeHoverDelegate = {
showHover: function () {
throw new Error('Native hover function not implemented.');
},
delay: 0,
showNativeHover: true
};
export { IHoverService, WorkbenchHoverDelegate, nativeHoverDelegate };
@@ -0,0 +1,542 @@
import { registerSingleton } from '../../instantiation/common/extensions.js';
import { registerThemingParticipant } from '../../theme/common/themeService.js';
import '../../theme/common/colorUtils.js';
import '../../theme/common/colors/baseColors.js';
import '../../theme/common/colors/chartsColors.js';
import { editorHoverBorder } from '../../theme/common/colors/editorColors.js';
import '../../theme/common/colors/inputColors.js';
import '../../theme/common/colors/listColors.js';
import '../../theme/common/colors/menuColors.js';
import '../../theme/common/colors/minimapColors.js';
import '../../theme/common/colors/miscColors.js';
import '../../theme/common/colors/quickpickColors.js';
import '../../theme/common/colors/searchColors.js';
import { IHoverService } from './hover.js';
import { IContextMenuService } from '../../contextview/browser/contextView.js';
import { IInstantiationService } from '../../instantiation/common/instantiation.js';
import { HoverWidget } from './hoverWidget.js';
import { Disposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';
import { addDisposableListener, EventType, getActiveElement, isHTMLElement, isAncestorOfActiveElement, getWindow, isAncestor, isEditableElement } from '../../../base/browser/dom.js';
import { IKeybindingService } from '../../keybinding/common/keybinding.js';
import { StandardKeyboardEvent } from '../../../base/browser/keyboardEvent.js';
import { IAccessibilityService } from '../../accessibility/common/accessibility.js';
import { ILayoutService } from '../../layout/browser/layoutService.js';
import { mainWindow } from '../../../base/browser/window.js';
import { ContextViewHandler } from '../../contextview/browser/contextViewService.js';
import { isManagedHoverTooltipMarkdownString } from '../../../base/browser/ui/hover/hover.js';
import { ManagedHoverWidget } from './updatableHoverWidget.js';
import { timeout, TimeoutTimer } from '../../../base/common/async.js';
import { IConfigurationService } from '../../configuration/common/configuration.js';
import { isNumber, isString } from '../../../base/common/types.js';
import { KeyChord } from '../../../base/common/keyCodes.js';
import { KeybindingsRegistry } from '../../keybinding/common/keybindingsRegistry.js';
import { stripIcons } from '../../../base/common/iconLabels.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
let HoverService = class HoverService extends Disposable {
constructor(_instantiationService, _configurationService, contextMenuService, _keybindingService, _layoutService, _accessibilityService) {
super();
this._instantiationService = _instantiationService;
this._configurationService = _configurationService;
this._keybindingService = _keybindingService;
this._layoutService = _layoutService;
this._accessibilityService = _accessibilityService;
this._currentDelayedHoverWasShown = false;
this._delayedHovers = new Map();
this._managedHovers = new Map();
this._register(contextMenuService.onDidShowContextMenu(() => this.hideHover()));
this._contextViewHandler = this._register(new ContextViewHandler(this._layoutService));
this._register(KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.showHover',
weight: 0 /* KeybindingWeight.EditorCore */,
primary: KeyChord(2048 /* KeyMod.CtrlCmd */ | 41 /* KeyCode.KeyK */, 2048 /* KeyMod.CtrlCmd */ | 39 /* KeyCode.KeyI */),
handler: () => { this._showAndFocusHoverForActiveElement(); },
}));
}
showInstantHover(options, focus, skipLastFocusedUpdate, dontShow) {
const hover = this._createHover(options, skipLastFocusedUpdate);
if (!hover) {
return undefined;
}
this._showHover(hover, options, focus);
return hover;
}
showDelayedHover(options, lifecycleOptions) {
// Set `id` to default if it's undefined
if (options.id === undefined) {
options.id = getHoverIdFromContent(options.content);
}
if (!this._currentDelayedHover || this._currentDelayedHoverWasShown) {
// Current hover is locked, reject
if (this._currentHover?.isLocked) {
return undefined;
}
// Identity is the same, return current hover
if (getHoverOptionsIdentity(this._currentHoverOptions) === getHoverOptionsIdentity(options)) {
return this._currentHover;
}
// Check group identity, if it's the same skip the delay and show the hover immediately
if (this._currentHover && !this._currentHover.isDisposed && this._currentDelayedHoverGroupId !== undefined && this._currentDelayedHoverGroupId === lifecycleOptions?.groupId) {
return this.showInstantHover({
...options,
appearance: {
...options.appearance,
skipFadeInAnimation: true
}
});
}
}
else if (this._currentDelayedHover && getHoverOptionsIdentity(this._currentHoverOptions) === getHoverOptionsIdentity(options)) {
// If the hover is the same but timeout is not finished yet, return the current hover
return this._currentDelayedHover;
}
const hover = this._createHover(options, undefined);
if (!hover) {
this._currentDelayedHover = undefined;
this._currentDelayedHoverWasShown = false;
this._currentDelayedHoverGroupId = undefined;
return undefined;
}
this._currentDelayedHover = hover;
this._currentDelayedHoverWasShown = false;
this._currentDelayedHoverGroupId = lifecycleOptions?.groupId;
timeout(this._configurationService.getValue('workbench.hover.delay')).then(() => {
if (hover && !hover.isDisposed) {
this._currentDelayedHoverWasShown = true;
this._showHover(hover, options);
}
});
return hover;
}
setupDelayedHover(target, options, lifecycleOptions) {
const resolveHoverOptions = () => ({
...typeof options === 'function' ? options() : options,
target
});
return this._setupDelayedHover(target, resolveHoverOptions, lifecycleOptions);
}
setupDelayedHoverAtMouse(target, options, lifecycleOptions) {
const resolveHoverOptions = (e) => ({
...typeof options === 'function' ? options() : options,
target: {
targetElements: [target],
x: e !== undefined ? e.x + 10 : undefined,
}
});
return this._setupDelayedHover(target, resolveHoverOptions, lifecycleOptions);
}
_setupDelayedHover(target, resolveHoverOptions, lifecycleOptions) {
const store = new DisposableStore();
store.add(addDisposableListener(target, EventType.MOUSE_OVER, e => {
this.showDelayedHover(resolveHoverOptions(e), {
groupId: lifecycleOptions?.groupId
});
}));
if (lifecycleOptions?.setupKeyboardEvents) {
store.add(addDisposableListener(target, EventType.KEY_DOWN, e => {
const evt = new StandardKeyboardEvent(e);
if (evt.equals(10 /* KeyCode.Space */) || evt.equals(3 /* KeyCode.Enter */)) {
this.showInstantHover(resolveHoverOptions(), true);
}
}));
}
this._delayedHovers.set(target, { show: (focus) => { this.showInstantHover(resolveHoverOptions(), focus); } });
store.add(toDisposable(() => this._delayedHovers.delete(target)));
return store;
}
_createHover(options, skipLastFocusedUpdate) {
this._currentDelayedHover = undefined;
if (options.content === '') {
return undefined;
}
if (this._currentHover?.isLocked) {
return undefined;
}
// Set `id` to default if it's undefined
if (options.id === undefined) {
options.id = getHoverIdFromContent(options.content);
}
if (getHoverOptionsIdentity(this._currentHoverOptions) === getHoverOptionsIdentity(options)) {
return undefined;
}
this._currentHoverOptions = options;
this._lastHoverOptions = options;
const trapFocus = options.trapFocus || this._accessibilityService.isScreenReaderOptimized();
const activeElement = getActiveElement();
// HACK, remove this check when #189076 is fixed
if (!skipLastFocusedUpdate) {
if (trapFocus && activeElement) {
if (!activeElement.classList.contains('monaco-hover')) {
this._lastFocusedElementBeforeOpen = activeElement;
}
}
else {
this._lastFocusedElementBeforeOpen = undefined;
}
}
const hoverDisposables = new DisposableStore();
const hover = this._instantiationService.createInstance(HoverWidget, options);
if (options.persistence?.sticky) {
hover.isLocked = true;
}
// Adjust target position when a mouse event is provided as the hover position
if (options.position?.hoverPosition && !isNumber(options.position.hoverPosition)) {
options.target = {
targetElements: isHTMLElement(options.target) ? [options.target] : options.target.targetElements,
x: options.position.hoverPosition.x + 10
};
}
hover.onDispose(() => {
const hoverWasFocused = this._currentHover?.domNode && isAncestorOfActiveElement(this._currentHover.domNode);
if (hoverWasFocused) {
// Required to handle cases such as closing the hover with the escape key
this._lastFocusedElementBeforeOpen?.focus();
}
// Only clear the current options if it's the current hover, the current options help
// reduce flickering when the same hover is shown multiple times
if (getHoverOptionsIdentity(this._currentHoverOptions) === getHoverOptionsIdentity(options)) {
this.doHideHover();
}
hoverDisposables.dispose();
}, undefined, hoverDisposables);
// Set the container explicitly to enable aux window support
if (!options.container) {
const targetElement = isHTMLElement(options.target) ? options.target : options.target.targetElements[0];
options.container = this._layoutService.getContainer(getWindow(targetElement));
}
hover.onRequestLayout(() => this._contextViewHandler.layout(), undefined, hoverDisposables);
if (options.persistence?.sticky) {
hoverDisposables.add(addDisposableListener(getWindow(options.container).document, EventType.MOUSE_DOWN, e => {
if (!isAncestor(e.target, hover.domNode)) {
this.doHideHover();
}
}));
}
else {
if ('targetElements' in options.target) {
for (const element of options.target.targetElements) {
hoverDisposables.add(addDisposableListener(element, EventType.CLICK, () => this.hideHover()));
}
}
else {
hoverDisposables.add(addDisposableListener(options.target, EventType.CLICK, () => this.hideHover()));
}
const focusedElement = getActiveElement();
if (focusedElement) {
const focusedElementDocument = getWindow(focusedElement).document;
hoverDisposables.add(addDisposableListener(focusedElement, EventType.KEY_DOWN, e => this._keyDown(e, hover, !!options.persistence?.hideOnKeyDown)));
hoverDisposables.add(addDisposableListener(focusedElementDocument, EventType.KEY_DOWN, e => this._keyDown(e, hover, !!options.persistence?.hideOnKeyDown)));
hoverDisposables.add(addDisposableListener(focusedElement, EventType.KEY_UP, e => this._keyUp(e, hover)));
hoverDisposables.add(addDisposableListener(focusedElementDocument, EventType.KEY_UP, e => this._keyUp(e, hover)));
}
}
if ('IntersectionObserver' in mainWindow) {
const observer = new IntersectionObserver(e => this._intersectionChange(e, hover), { threshold: 0 });
const firstTargetElement = 'targetElements' in options.target ? options.target.targetElements[0] : options.target;
observer.observe(firstTargetElement);
hoverDisposables.add(toDisposable(() => observer.disconnect()));
}
this._currentHover = hover;
return hover;
}
_showHover(hover, options, focus) {
this._contextViewHandler.showContextView(new HoverContextViewDelegate(hover, focus), options.container);
}
hideHover(force) {
if ((!force && this._currentHover?.isLocked) || !this._currentHoverOptions) {
return;
}
this.doHideHover();
}
doHideHover() {
this._currentHover = undefined;
this._currentHoverOptions = undefined;
this._contextViewHandler.hideContextView();
}
_intersectionChange(entries, hover) {
const entry = entries[entries.length - 1];
if (!entry.isIntersecting) {
hover.dispose();
}
}
showAndFocusLastHover() {
if (!this._lastHoverOptions) {
return;
}
this.showInstantHover(this._lastHoverOptions, true, true);
}
_showAndFocusHoverForActiveElement() {
// TODO: if hover is visible, focus it to avoid flickering
let activeElement = getActiveElement();
while (activeElement) {
const hover = this._delayedHovers.get(activeElement) ?? this._managedHovers.get(activeElement);
if (hover) {
hover.show(true);
return;
}
activeElement = activeElement.parentElement;
}
}
_keyDown(e, hover, hideOnKeyDown) {
if (e.key === 'Alt') {
hover.isLocked = true;
return;
}
const event = new StandardKeyboardEvent(e);
const keybinding = this._keybindingService.resolveKeyboardEvent(event);
if (keybinding.getSingleModifierDispatchChords().some(value => !!value) || this._keybindingService.softDispatch(event, event.target).kind !== 0 /* ResultKind.NoMatchingKb */) {
return;
}
if (hideOnKeyDown && (!this._currentHoverOptions?.trapFocus || e.key !== 'Tab')) {
this.hideHover();
this._lastFocusedElementBeforeOpen?.focus();
}
}
_keyUp(e, hover) {
if (e.key === 'Alt') {
hover.isLocked = false;
// Hide if alt is released while the mouse is not over hover/target
if (!hover.isMouseIn) {
this.hideHover();
this._lastFocusedElementBeforeOpen?.focus();
}
}
}
// TODO: Investigate performance of this function. There seems to be a lot of content created
// and thrown away on start up
setupManagedHover(hoverDelegate, targetElement, content, options) {
if (hoverDelegate.showNativeHover) {
return setupNativeHover(targetElement, content);
}
targetElement.setAttribute('custom-hover', 'true');
if (targetElement.title !== '') {
console.warn('HTML element already has a title attribute, which will conflict with the custom hover. Please remove the title attribute.');
console.trace('Stack trace:', targetElement.title);
targetElement.title = '';
}
let hoverPreparation;
let hoverWidget;
const hideHover = (disposeWidget, disposePreparation) => {
const hadHover = hoverWidget !== undefined;
if (disposeWidget) {
hoverWidget?.dispose();
hoverWidget = undefined;
}
if (disposePreparation) {
hoverPreparation?.dispose();
hoverPreparation = undefined;
}
if (hadHover) {
hoverDelegate.onDidHideHover?.();
hoverWidget = undefined;
}
};
const triggerShowHover = (delay, focus, target, trapFocus) => {
return new TimeoutTimer(async () => {
if (!hoverWidget || hoverWidget.isDisposed) {
hoverWidget = new ManagedHoverWidget(hoverDelegate, target || targetElement, delay > 0);
await hoverWidget.update(typeof content === 'function' ? content() : content, focus, { ...options, trapFocus });
}
}, delay);
};
const store = new DisposableStore();
let isMouseDown = false;
store.add(addDisposableListener(targetElement, EventType.MOUSE_DOWN, () => {
isMouseDown = true;
hideHover(true, true);
}, true));
store.add(addDisposableListener(targetElement, EventType.MOUSE_UP, () => {
isMouseDown = false;
}, true));
store.add(addDisposableListener(targetElement, EventType.MOUSE_LEAVE, (e) => {
isMouseDown = false;
hideHover(false, e.fromElement === targetElement);
}, true));
store.add(addDisposableListener(targetElement, EventType.MOUSE_OVER, (e) => {
if (hoverPreparation) {
return;
}
const mouseOverStore = new DisposableStore();
const target = {
targetElements: [targetElement],
dispose: () => { }
};
if (hoverDelegate.placement === undefined || hoverDelegate.placement === 'mouse') {
// track the mouse position
const onMouseMove = (e) => {
target.x = e.x + 10;
if (!eventIsRelatedToTarget(e, targetElement)) {
hideHover(true, true);
}
};
mouseOverStore.add(addDisposableListener(targetElement, EventType.MOUSE_MOVE, onMouseMove, true));
}
hoverPreparation = mouseOverStore;
if (!eventIsRelatedToTarget(e, targetElement)) {
return; // Do not show hover when the mouse is over another hover target
}
mouseOverStore.add(triggerShowHover(typeof hoverDelegate.delay === 'function' ? hoverDelegate.delay(content) : hoverDelegate.delay, false, target));
}, true));
const onFocus = (e) => {
if (isMouseDown || hoverPreparation) {
return;
}
if (!eventIsRelatedToTarget(e, targetElement)) {
return; // Do not show hover when the focus is on another hover target
}
const target = {
targetElements: [targetElement],
dispose: () => { }
};
const toDispose = new DisposableStore();
const onBlur = () => hideHover(true, true);
toDispose.add(addDisposableListener(targetElement, EventType.BLUR, onBlur, true));
toDispose.add(triggerShowHover(typeof hoverDelegate.delay === 'function' ? hoverDelegate.delay(content) : hoverDelegate.delay, false, target));
hoverPreparation = toDispose;
};
// Do not show hover when focusing an input or textarea
if (!isEditableElement(targetElement)) {
store.add(addDisposableListener(targetElement, EventType.FOCUS, onFocus, true));
}
const hover = {
show: focus => {
hideHover(false, true); // terminate a ongoing mouse over preparation
triggerShowHover(0, focus, undefined, focus); // show hover immediately
},
hide: () => {
hideHover(true, true);
},
update: async (newContent, hoverOptions) => {
content = newContent;
await hoverWidget?.update(content, undefined, hoverOptions);
},
dispose: () => {
this._managedHovers.delete(targetElement);
store.dispose();
hideHover(true, true);
}
};
this._managedHovers.set(targetElement, hover);
return hover;
}
showManagedHover(target) {
const hover = this._managedHovers.get(target);
if (hover) {
hover.show(true);
}
}
dispose() {
this._managedHovers.forEach(hover => hover.dispose());
super.dispose();
}
};
HoverService = __decorate([
__param(0, IInstantiationService),
__param(1, IConfigurationService),
__param(2, IContextMenuService),
__param(3, IKeybindingService),
__param(4, ILayoutService),
__param(5, IAccessibilityService)
], HoverService);
function getHoverOptionsIdentity(options) {
if (options === undefined) {
return undefined;
}
return options?.id ?? options;
}
function getHoverIdFromContent(content) {
if (isHTMLElement(content)) {
return undefined;
}
if (typeof content === 'string') {
return content.toString();
}
return content.value;
}
function getStringContent(contentOrFactory) {
const content = typeof contentOrFactory === 'function' ? contentOrFactory() : contentOrFactory;
if (isString(content)) {
// Icons don't render in the native hover so we strip them out
return stripIcons(content);
}
if (isManagedHoverTooltipMarkdownString(content)) {
return content.markdownNotSupportedFallback;
}
return undefined;
}
function setupNativeHover(targetElement, content) {
function updateTitle(title) {
if (title) {
targetElement.setAttribute('title', title);
}
else {
targetElement.removeAttribute('title');
}
}
updateTitle(getStringContent(content));
return {
update: (content) => updateTitle(getStringContent(content)),
show: () => { },
hide: () => { },
dispose: () => updateTitle(undefined),
};
}
class HoverContextViewDelegate {
get anchorPosition() {
return this._hover.anchor;
}
constructor(_hover, _focus = false) {
this._hover = _hover;
this._focus = _focus;
// Render over all other context views
this.layer = 1;
}
render(container) {
this._hover.render(container);
if (this._focus) {
this._hover.focus();
}
return this._hover;
}
getAnchor() {
return {
x: this._hover.x,
y: this._hover.y
};
}
layout() {
this._hover.layout();
}
}
function eventIsRelatedToTarget(event, target) {
return isHTMLElement(event.target) && getHoverTargetElement(event.target, target) === target;
}
function getHoverTargetElement(element, stopElement) {
stopElement = stopElement ?? getWindow(element).document.body;
while (!element.hasAttribute('custom-hover') && element !== stopElement) {
element = element.parentElement;
}
return element;
}
registerSingleton(IHoverService, HoverService, 1 /* InstantiationType.Delayed */);
registerThemingParticipant((theme, collector) => {
const hoverBorder = theme.getColor(editorHoverBorder);
if (hoverBorder) {
collector.addRule(`.monaco-hover.workbench-hover .hover-row:not(:first-child):not(:empty) { border-top: 1px solid ${hoverBorder.transparent(0.5)}; }`);
collector.addRule(`.monaco-hover.workbench-hover hr { border-top: 1px solid ${hoverBorder.transparent(0.5)}; }`);
}
});
export { HoverService };
@@ -0,0 +1,605 @@
import './hover.css';
import { DisposableStore, MutableDisposable } from '../../../base/common/lifecycle.js';
import { Emitter } from '../../../base/common/event.js';
import { getWindow, $ as $$1, addDisposableListener, isHTMLElement, prepend, append, getDomNodeZoomLevel } from '../../../base/browser/dom.js';
import { IKeybindingService } from '../../keybinding/common/keybinding.js';
import { IConfigurationService } from '../../configuration/common/configuration.js';
import { HoverWidget as HoverWidget$1, HoverAction, getHoverAccessibleViewHint } from '../../../base/browser/ui/hover/hoverWidget.js';
import { Widget } from '../../../base/browser/ui/widget.js';
import { IMarkdownRendererService } from '../../markdown/browser/markdownRenderer.js';
import { isMarkdownString } from '../../../base/common/htmlContent.js';
import { localize } from '../../../nls.js';
import { isMacintosh } from '../../../base/common/platform.js';
import { IAccessibilityService } from '../../accessibility/common/accessibility.js';
import { status } from '../../../base/browser/ui/aria/aria.js';
import { TimeoutTimer } from '../../../base/common/async.js';
import { isNumber } from '../../../base/common/types.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
const $ = $$1;
let HoverWidget = class HoverWidget extends Widget {
get _targetWindow() {
return getWindow(this._target.targetElements[0]);
}
get _targetDocumentElement() {
return getWindow(this._target.targetElements[0]).document.documentElement;
}
get isDisposed() { return this._isDisposed; }
get isMouseIn() { return this._lockMouseTracker.isMouseIn; }
get domNode() { return this._hover.containerDomNode; }
get onDispose() { return this._onDispose.event; }
get onRequestLayout() { return this._onRequestLayout.event; }
get anchor() { return this._hoverPosition === 2 /* HoverPosition.BELOW */ ? 0 /* AnchorPosition.BELOW */ : 1 /* AnchorPosition.ABOVE */; }
get x() { return this._x; }
get y() { return this._y; }
/**
* Whether the hover is "locked" by holding the alt/option key. When locked, the hover will not
* hide and can be hovered regardless of whether the `hideOnHover` hover option is set.
*/
get isLocked() { return this._isLocked; }
set isLocked(value) {
if (this._isLocked === value) {
return;
}
this._isLocked = value;
this._hoverContainer.classList.toggle('locked', this._isLocked);
}
constructor(options, _keybindingService, _configurationService, _markdownRenderer, _accessibilityService) {
super();
this._keybindingService = _keybindingService;
this._configurationService = _configurationService;
this._markdownRenderer = _markdownRenderer;
this._accessibilityService = _accessibilityService;
this._messageListeners = new DisposableStore();
this._isDisposed = false;
this._forcePosition = false;
this._x = 0;
this._y = 0;
this._isLocked = false;
this._enableFocusTraps = false;
this._addedFocusTrap = false;
this._maxHeightRatioRelativeToWindow = 0.5;
this._onDispose = this._register(new Emitter());
this._onRequestLayout = this._register(new Emitter());
this._linkHandler = options.linkHandler;
this._target = 'targetElements' in options.target ? options.target : new ElementHoverTarget(options.target);
if (options.style) {
switch (options.style) {
case 1 /* HoverStyle.Pointer */: {
options.appearance ??= {};
options.appearance.compact ??= true;
options.appearance.showPointer ??= true;
break;
}
case 2 /* HoverStyle.Mouse */: {
options.appearance ??= {};
options.appearance.compact ??= true;
break;
}
}
}
this._hoverPointer = options.appearance?.showPointer ? $('div.workbench-hover-pointer') : undefined;
this._hover = this._register(new HoverWidget$1(!options.appearance?.skipFadeInAnimation));
this._hover.containerDomNode.classList.add('workbench-hover');
if (options.appearance?.compact) {
this._hover.containerDomNode.classList.add('workbench-hover', 'compact');
}
if (options.additionalClasses) {
this._hover.containerDomNode.classList.add(...options.additionalClasses);
}
if (options.position?.forcePosition) {
this._forcePosition = true;
}
if (options.trapFocus) {
this._enableFocusTraps = true;
}
const maxHeightRatio = options.appearance?.maxHeightRatio;
if (maxHeightRatio !== undefined && maxHeightRatio > 0 && maxHeightRatio <= 1) {
this._maxHeightRatioRelativeToWindow = maxHeightRatio;
}
// Default to position above when the position is unspecified or a mouse event
this._hoverPosition = options.position?.hoverPosition === undefined
? 3 /* HoverPosition.ABOVE */
: isNumber(options.position.hoverPosition)
? options.position.hoverPosition
: 2 /* HoverPosition.BELOW */;
// Don't allow mousedown out of the widget, otherwise preventDefault will call and text will
// not be selected.
this.onmousedown(this._hover.containerDomNode, e => e.stopPropagation());
// Hide hover on escape
this.onkeydown(this._hover.containerDomNode, e => {
if (e.equals(9 /* KeyCode.Escape */)) {
this.dispose();
}
});
// Hide when the window loses focus
this._register(addDisposableListener(this._targetWindow, 'blur', () => this.dispose()));
const rowElement = $('div.hover-row.markdown-hover');
const contentsElement = $('div.hover-contents');
if (typeof options.content === 'string') {
contentsElement.textContent = options.content;
contentsElement.style.whiteSpace = 'pre-wrap';
}
else if (isHTMLElement(options.content)) {
contentsElement.appendChild(options.content);
contentsElement.classList.add('html-hover-contents');
}
else {
const markdown = options.content;
const { element } = this._register(this._markdownRenderer.render(markdown, {
actionHandler: this._linkHandler,
asyncRenderCallback: () => {
contentsElement.classList.add('code-hover-contents');
this.layout();
// This changes the dimensions of the hover so trigger a layout
this._onRequestLayout.fire();
}
}));
contentsElement.appendChild(element);
}
rowElement.appendChild(contentsElement);
this._hover.contentsDomNode.appendChild(rowElement);
if (options.actions && options.actions.length > 0) {
const statusBarElement = $('div.hover-row.status-bar');
const actionsElement = $('div.actions');
options.actions.forEach(action => {
const keybinding = this._keybindingService.lookupKeybinding(action.commandId);
const keybindingLabel = keybinding ? keybinding.getLabel() : null;
this._register(HoverAction.render(actionsElement, {
label: action.label,
commandId: action.commandId,
run: e => {
action.run(e);
this.dispose();
},
iconClass: action.iconClass
}, keybindingLabel));
});
statusBarElement.appendChild(actionsElement);
this._hover.containerDomNode.appendChild(statusBarElement);
}
this._hoverContainer = $('div.workbench-hover-container');
if (this._hoverPointer) {
this._hoverContainer.appendChild(this._hoverPointer);
}
this._hoverContainer.appendChild(this._hover.containerDomNode);
// Determine whether to hide on hover
let hideOnHover;
if (options.actions && options.actions.length > 0) {
// If there are actions, require hover so they can be accessed
hideOnHover = false;
}
else {
if (options.persistence?.hideOnHover === undefined) {
// When unset, will default to true when it's a string or when it's markdown that
// appears to have a link using a naive check for '](' and '</a>'
hideOnHover = typeof options.content === 'string' ||
isMarkdownString(options.content) && !options.content.value.includes('](') && !options.content.value.includes('</a>');
}
else {
// It's set explicitly
hideOnHover = options.persistence.hideOnHover;
}
}
// Show the hover hint if needed
if (options.appearance?.showHoverHint) {
const statusBarElement = $('div.hover-row.status-bar');
const infoElement = $('div.info');
infoElement.textContent = localize(1699, 'Hold {0} key to mouse over', isMacintosh ? 'Option' : 'Alt');
statusBarElement.appendChild(infoElement);
this._hover.containerDomNode.appendChild(statusBarElement);
}
const mouseTrackerTargets = [...this._target.targetElements];
if (!hideOnHover) {
mouseTrackerTargets.push(this._hoverContainer);
}
const mouseTracker = this._register(new CompositeMouseTracker(mouseTrackerTargets));
this._register(mouseTracker.onMouseOut(() => {
if (!this._isLocked) {
this.dispose();
}
}));
// Setup another mouse tracker when hideOnHover is set in order to track the hover as well
// when it is locked. This ensures the hover will hide on mouseout after alt has been
// released to unlock the element.
if (hideOnHover) {
const mouseTracker2Targets = [...this._target.targetElements, this._hoverContainer];
this._lockMouseTracker = this._register(new CompositeMouseTracker(mouseTracker2Targets));
this._register(this._lockMouseTracker.onMouseOut(() => {
if (!this._isLocked) {
this.dispose();
}
}));
}
else {
this._lockMouseTracker = mouseTracker;
}
}
addFocusTrap() {
if (!this._enableFocusTraps || this._addedFocusTrap) {
return;
}
this._addedFocusTrap = true;
// Add a hover tab loop if the hover has at least one element with a valid tabIndex
const firstContainerFocusElement = this._hover.containerDomNode;
const lastContainerFocusElement = this.findLastFocusableChild(this._hover.containerDomNode);
if (lastContainerFocusElement) {
const beforeContainerFocusElement = prepend(this._hoverContainer, $('div'));
const afterContainerFocusElement = append(this._hoverContainer, $('div'));
beforeContainerFocusElement.tabIndex = 0;
afterContainerFocusElement.tabIndex = 0;
this._register(addDisposableListener(afterContainerFocusElement, 'focus', (e) => {
firstContainerFocusElement.focus();
e.preventDefault();
}));
this._register(addDisposableListener(beforeContainerFocusElement, 'focus', (e) => {
lastContainerFocusElement.focus();
e.preventDefault();
}));
}
}
findLastFocusableChild(root) {
if (root.hasChildNodes()) {
for (let i = 0; i < root.childNodes.length; i++) {
const node = root.childNodes.item(root.childNodes.length - i - 1);
if (node.nodeType === node.ELEMENT_NODE) {
const parsedNode = node;
if (typeof parsedNode.tabIndex === 'number' && parsedNode.tabIndex >= 0) {
return parsedNode;
}
}
const recursivelyFoundElement = this.findLastFocusableChild(node);
if (recursivelyFoundElement) {
return recursivelyFoundElement;
}
}
}
return undefined;
}
render(container) {
container.appendChild(this._hoverContainer);
const hoverFocused = this._hoverContainer.contains(this._hoverContainer.ownerDocument.activeElement);
const accessibleViewHint = hoverFocused && getHoverAccessibleViewHint(this._configurationService.getValue('accessibility.verbosity.hover') === true && this._accessibilityService.isScreenReaderOptimized(), this._keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel());
if (accessibleViewHint) {
status(accessibleViewHint);
}
this.layout();
this.addFocusTrap();
}
layout() {
this._hover.containerDomNode.classList.remove('right-aligned');
this._hover.contentsDomNode.style.maxHeight = '';
const getZoomAccountedBoundingClientRect = (e) => {
const zoom = getDomNodeZoomLevel(e);
const boundingRect = e.getBoundingClientRect();
return {
top: boundingRect.top * zoom,
bottom: boundingRect.bottom * zoom,
right: boundingRect.right * zoom,
left: boundingRect.left * zoom,
};
};
const targetBounds = this._target.targetElements.map(e => getZoomAccountedBoundingClientRect(e));
const { top, right, bottom, left } = targetBounds[0];
const width = right - left;
const height = bottom - top;
const targetRect = {
top, right, bottom, left, width, height,
center: {
x: left + (width / 2),
y: top + (height / 2)
}
};
// These calls adjust the position depending on spacing.
this.adjustHorizontalHoverPosition(targetRect);
this.adjustVerticalHoverPosition(targetRect);
// This call limits the maximum height of the hover.
this.adjustHoverMaxHeight(targetRect);
// Offset the hover position if there is a pointer so it aligns with the target element
this._hoverContainer.style.padding = '';
this._hoverContainer.style.margin = '';
if (this._hoverPointer) {
switch (this._hoverPosition) {
case 1 /* HoverPosition.RIGHT */:
targetRect.left += 3 /* Constants.PointerSize */;
targetRect.right += 3 /* Constants.PointerSize */;
this._hoverContainer.style.paddingLeft = `${3 /* Constants.PointerSize */}px`;
this._hoverContainer.style.marginLeft = `${ -3 /* Constants.PointerSize */}px`;
break;
case 0 /* HoverPosition.LEFT */:
targetRect.left -= 3 /* Constants.PointerSize */;
targetRect.right -= 3 /* Constants.PointerSize */;
this._hoverContainer.style.paddingRight = `${3 /* Constants.PointerSize */}px`;
this._hoverContainer.style.marginRight = `${ -3 /* Constants.PointerSize */}px`;
break;
case 2 /* HoverPosition.BELOW */:
targetRect.top += 3 /* Constants.PointerSize */;
targetRect.bottom += 3 /* Constants.PointerSize */;
this._hoverContainer.style.paddingTop = `${3 /* Constants.PointerSize */}px`;
this._hoverContainer.style.marginTop = `${ -3 /* Constants.PointerSize */}px`;
break;
case 3 /* HoverPosition.ABOVE */:
targetRect.top -= 3 /* Constants.PointerSize */;
targetRect.bottom -= 3 /* Constants.PointerSize */;
this._hoverContainer.style.paddingBottom = `${3 /* Constants.PointerSize */}px`;
this._hoverContainer.style.marginBottom = `${ -3 /* Constants.PointerSize */}px`;
break;
}
targetRect.center.x = targetRect.left + (width / 2);
targetRect.center.y = targetRect.top + (height / 2);
}
this.computeXCordinate(targetRect);
this.computeYCordinate(targetRect);
if (this._hoverPointer) {
// reset
this._hoverPointer.classList.remove('top');
this._hoverPointer.classList.remove('left');
this._hoverPointer.classList.remove('right');
this._hoverPointer.classList.remove('bottom');
this.setHoverPointerPosition(targetRect);
}
this._hover.onContentsChanged();
}
computeXCordinate(target) {
const hoverWidth = this._hover.containerDomNode.clientWidth + 2 /* Constants.HoverBorderWidth */;
if (this._target.x !== undefined) {
this._x = this._target.x;
}
else if (this._hoverPosition === 1 /* HoverPosition.RIGHT */) {
this._x = target.right;
}
else if (this._hoverPosition === 0 /* HoverPosition.LEFT */) {
this._x = target.left - hoverWidth;
}
else {
if (this._hoverPointer) {
this._x = target.center.x - (this._hover.containerDomNode.clientWidth / 2);
}
else {
this._x = target.left;
}
// Hover is going beyond window towards right end
if (this._x + hoverWidth >= this._targetDocumentElement.clientWidth) {
this._hover.containerDomNode.classList.add('right-aligned');
this._x = Math.max(this._targetDocumentElement.clientWidth - hoverWidth - 2 /* Constants.HoverWindowEdgeMargin */, this._targetDocumentElement.clientLeft);
}
}
// Hover is going beyond window towards left end
if (this._x < this._targetDocumentElement.clientLeft) {
this._x = target.left + 2 /* Constants.HoverWindowEdgeMargin */;
}
}
computeYCordinate(target) {
if (this._target.y !== undefined) {
this._y = this._target.y;
}
else if (this._hoverPosition === 3 /* HoverPosition.ABOVE */) {
this._y = target.top;
}
else if (this._hoverPosition === 2 /* HoverPosition.BELOW */) {
this._y = target.bottom - 2;
}
else {
if (this._hoverPointer) {
this._y = target.center.y + (this._hover.containerDomNode.clientHeight / 2);
}
else {
this._y = target.bottom;
}
}
// Hover on bottom is going beyond window
if (this._y > this._targetWindow.innerHeight) {
this._y = target.bottom;
}
}
adjustHorizontalHoverPosition(target) {
// Do not adjust horizontal hover position if x cordiante is provided
if (this._target.x !== undefined) {
return;
}
const hoverPointerOffset = (this._hoverPointer ? 3 /* Constants.PointerSize */ : 0);
// When force position is enabled, restrict max width
if (this._forcePosition) {
const padding = hoverPointerOffset + 2 /* Constants.HoverBorderWidth */;
if (this._hoverPosition === 1 /* HoverPosition.RIGHT */) {
this._hover.containerDomNode.style.maxWidth = `${this._targetDocumentElement.clientWidth - target.right - padding}px`;
}
else if (this._hoverPosition === 0 /* HoverPosition.LEFT */) {
this._hover.containerDomNode.style.maxWidth = `${target.left - padding}px`;
}
return;
}
// Position hover on right to target
if (this._hoverPosition === 1 /* HoverPosition.RIGHT */) {
const roomOnRight = this._targetDocumentElement.clientWidth - target.right;
// Hover on the right is going beyond window.
if (roomOnRight < this._hover.containerDomNode.clientWidth + hoverPointerOffset) {
const roomOnLeft = target.left;
// There's enough room on the left, flip the hover position
if (roomOnLeft >= this._hover.containerDomNode.clientWidth + hoverPointerOffset) {
this._hoverPosition = 0 /* HoverPosition.LEFT */;
}
// Hover on the left would go beyond window too
else {
this._hoverPosition = 2 /* HoverPosition.BELOW */;
}
}
}
// Position hover on left to target
else if (this._hoverPosition === 0 /* HoverPosition.LEFT */) {
const roomOnLeft = target.left;
// Hover on the left is going beyond window.
if (roomOnLeft < this._hover.containerDomNode.clientWidth + hoverPointerOffset) {
const roomOnRight = this._targetDocumentElement.clientWidth - target.right;
// There's enough room on the right, flip the hover position
if (roomOnRight >= this._hover.containerDomNode.clientWidth + hoverPointerOffset) {
this._hoverPosition = 1 /* HoverPosition.RIGHT */;
}
// Hover on the right would go beyond window too
else {
this._hoverPosition = 2 /* HoverPosition.BELOW */;
}
}
// Hover on the left is going beyond window.
if (target.left - this._hover.containerDomNode.clientWidth - hoverPointerOffset <= this._targetDocumentElement.clientLeft) {
this._hoverPosition = 1 /* HoverPosition.RIGHT */;
}
}
}
adjustVerticalHoverPosition(target) {
// Do not adjust vertical hover position if the y coordinate is provided
// or the position is forced
if (this._target.y !== undefined || this._forcePosition) {
return;
}
const hoverPointerOffset = (this._hoverPointer ? 3 /* Constants.PointerSize */ : 0);
// Position hover on top of the target
if (this._hoverPosition === 3 /* HoverPosition.ABOVE */) {
// Hover on top is going beyond window
if (target.top - this._hover.containerDomNode.clientHeight - hoverPointerOffset < 0) {
this._hoverPosition = 2 /* HoverPosition.BELOW */;
}
}
// Position hover below the target
else if (this._hoverPosition === 2 /* HoverPosition.BELOW */) {
// Hover on bottom is going beyond window
if (target.bottom + this._hover.containerDomNode.offsetHeight + hoverPointerOffset > this._targetWindow.innerHeight) {
this._hoverPosition = 3 /* HoverPosition.ABOVE */;
}
}
}
adjustHoverMaxHeight(target) {
let maxHeight = this._targetWindow.innerHeight * this._maxHeightRatioRelativeToWindow;
// When force position is enabled, restrict max height
if (this._forcePosition) {
const padding = (this._hoverPointer ? 3 /* Constants.PointerSize */ : 0) + 2 /* Constants.HoverBorderWidth */;
if (this._hoverPosition === 3 /* HoverPosition.ABOVE */) {
maxHeight = Math.min(maxHeight, target.top - padding);
}
else if (this._hoverPosition === 2 /* HoverPosition.BELOW */) {
maxHeight = Math.min(maxHeight, this._targetWindow.innerHeight - target.bottom - padding);
}
}
this._hover.containerDomNode.style.maxHeight = `${maxHeight}px`;
if (this._hover.contentsDomNode.clientHeight < this._hover.contentsDomNode.scrollHeight) {
// Add padding for a vertical scrollbar
const extraRightPadding = `${this._hover.scrollbar.options.verticalScrollbarSize}px`;
if (this._hover.contentsDomNode.style.paddingRight !== extraRightPadding) {
this._hover.contentsDomNode.style.paddingRight = extraRightPadding;
}
}
}
setHoverPointerPosition(target) {
if (!this._hoverPointer) {
return;
}
switch (this._hoverPosition) {
case 0 /* HoverPosition.LEFT */:
case 1 /* HoverPosition.RIGHT */: {
this._hoverPointer.classList.add(this._hoverPosition === 0 /* HoverPosition.LEFT */ ? 'right' : 'left');
const hoverHeight = this._hover.containerDomNode.clientHeight;
// If hover is taller than target, then show the pointer at the center of target
if (hoverHeight > target.height) {
this._hoverPointer.style.top = `${target.center.y - (this._y - hoverHeight) - 3 /* Constants.PointerSize */}px`;
}
// Otherwise show the pointer at the center of hover
else {
this._hoverPointer.style.top = `${Math.round((hoverHeight / 2)) - 3 /* Constants.PointerSize */}px`;
}
break;
}
case 3 /* HoverPosition.ABOVE */:
case 2 /* HoverPosition.BELOW */: {
this._hoverPointer.classList.add(this._hoverPosition === 3 /* HoverPosition.ABOVE */ ? 'bottom' : 'top');
const hoverWidth = this._hover.containerDomNode.clientWidth;
// Position pointer at the center of the hover
let pointerLeftPosition = Math.round((hoverWidth / 2)) - 3 /* Constants.PointerSize */;
// If pointer goes beyond target then position it at the center of the target
const pointerX = this._x + pointerLeftPosition;
if (pointerX < target.left || pointerX > target.right) {
pointerLeftPosition = target.center.x - this._x - 3 /* Constants.PointerSize */;
}
this._hoverPointer.style.left = `${pointerLeftPosition}px`;
break;
}
}
}
focus() {
this._hover.containerDomNode.focus();
}
dispose() {
if (!this._isDisposed) {
this._onDispose.fire();
this._target.dispose?.();
this._hoverContainer.remove();
this._messageListeners.dispose();
super.dispose();
}
this._isDisposed = true;
}
};
HoverWidget = __decorate([
__param(1, IKeybindingService),
__param(2, IConfigurationService),
__param(3, IMarkdownRendererService),
__param(4, IAccessibilityService)
], HoverWidget);
class CompositeMouseTracker extends Widget {
get onMouseOut() { return this._onMouseOut.event; }
get isMouseIn() { return this._isMouseIn; }
/**
* @param _elements The target elements to track mouse in/out events on.
* @param _eventDebounceDelay The delay in ms to debounce the event firing. This is used to
* allow a short period for the mouse to move into the hover or a nearby target element. For
* example hovering a scroll bar will not hide the hover immediately.
*/
constructor(_elements, _eventDebounceDelay = 200) {
super();
this._elements = _elements;
this._eventDebounceDelay = _eventDebounceDelay;
this._isMouseIn = true;
this._mouseTimer = this._register(new MutableDisposable());
this._onMouseOut = this._register(new Emitter());
for (const element of this._elements) {
this.onmouseover(element, () => this._onTargetMouseOver());
this.onmouseleave(element, () => this._onTargetMouseLeave());
}
}
_onTargetMouseOver() {
this._isMouseIn = true;
this._mouseTimer.clear();
}
_onTargetMouseLeave() {
this._isMouseIn = false;
// Evaluate whether the mouse is still outside asynchronously such that other mouse targets
// have the opportunity to first their mouse in event.
this._mouseTimer.value = new TimeoutTimer(() => this._fireIfMouseOutside(), this._eventDebounceDelay);
}
_fireIfMouseOutside() {
if (!this._isMouseIn) {
this._onMouseOut.fire();
}
}
}
class ElementHoverTarget {
constructor(_element) {
this._element = _element;
this.targetElements = [this._element];
}
dispose() {
}
}
export { HoverWidget };
@@ -0,0 +1,107 @@
import { isHTMLElement } from '../../../base/browser/dom.js';
import { isManagedHoverTooltipMarkdownString } from '../../../base/browser/ui/hover/hover.js';
import { CancellationTokenSource } from '../../../base/common/cancellation.js';
import { isMarkdownString } from '../../../base/common/htmlContent.js';
import { isString, isFunction } from '../../../base/common/types.js';
import { localize } from '../../../nls.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
class ManagedHoverWidget {
constructor(hoverDelegate, target, fadeInAnimation) {
this.hoverDelegate = hoverDelegate;
this.target = target;
this.fadeInAnimation = fadeInAnimation;
}
async update(content, focus, options) {
if (this._cancellationTokenSource) {
// there's an computation ongoing, cancel it
this._cancellationTokenSource.dispose(true);
this._cancellationTokenSource = undefined;
}
if (this.isDisposed) {
return;
}
let resolvedContent;
if (isString(content) || isHTMLElement(content) || content === undefined) {
resolvedContent = content;
}
else {
// compute the content, potentially long-running
this._cancellationTokenSource = new CancellationTokenSource();
const token = this._cancellationTokenSource.token;
let managedContent;
if (isManagedHoverTooltipMarkdownString(content)) {
if (isFunction(content.markdown)) {
managedContent = content.markdown(token).then(resolvedContent => resolvedContent ?? content.markdownNotSupportedFallback);
}
else {
managedContent = content.markdown ?? content.markdownNotSupportedFallback;
}
}
else {
managedContent = content.element(token);
}
// compute the content
if (managedContent instanceof Promise) {
// show 'Loading' if no hover is up yet
if (!this._hoverWidget) {
this.show(localize(1700, "Loading..."), focus, options);
}
resolvedContent = await managedContent;
}
else {
resolvedContent = managedContent;
}
if (this.isDisposed || token.isCancellationRequested) {
// either the widget has been closed in the meantime
// or there has been a new call to `update`
return;
}
}
this.show(resolvedContent, focus, options);
}
show(content, focus, options) {
const oldHoverWidget = this._hoverWidget;
if (this.hasContent(content)) {
const hoverOptions = {
content,
target: this.target,
actions: options?.actions,
linkHandler: options?.linkHandler,
trapFocus: options?.trapFocus,
appearance: {
showPointer: this.hoverDelegate.placement === 'element',
skipFadeInAnimation: !this.fadeInAnimation || !!oldHoverWidget, // do not fade in if the hover is already showing
showHoverHint: options?.appearance?.showHoverHint,
},
position: {
hoverPosition: 2 /* HoverPosition.BELOW */,
},
};
this._hoverWidget = this.hoverDelegate.showHover(hoverOptions, focus);
}
oldHoverWidget?.dispose();
}
hasContent(content) {
if (!content) {
return false;
}
if (isMarkdownString(content)) {
return !!content.value;
}
return true;
}
get isDisposed() {
return this._hoverWidget?.isDisposed;
}
dispose() {
this._hoverWidget?.dispose();
this._cancellationTokenSource?.dispose(true);
this._cancellationTokenSource = undefined;
}
}
export { ManagedHoverWidget };
@@ -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.
*--------------------------------------------------------------------------------------------*/
class SyncDescriptor {
constructor(ctor, staticArguments = [], supportsDelayedInstantiation = false) {
this.ctor = ctor;
this.staticArguments = staticArguments;
this.supportsDelayedInstantiation = supportsDelayedInstantiation;
}
}
export { SyncDescriptor };
@@ -0,0 +1,18 @@
import { SyncDescriptor } from './descriptors.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const _registry = [];
function registerSingleton(id, ctorOrDescriptor, supportsDelayedInstantiation) {
if (!(ctorOrDescriptor instanceof SyncDescriptor)) {
ctorOrDescriptor = new SyncDescriptor(ctorOrDescriptor, [], Boolean(supportsDelayedInstantiation));
}
_registry.push([id, ctorOrDescriptor]);
}
function getSingletonServiceDescriptors() {
return _registry;
}
export { getSingletonServiceDescriptors, registerSingleton };
@@ -0,0 +1,91 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
class Node {
constructor(key, data) {
this.key = key;
this.data = data;
this.incoming = new Map();
this.outgoing = new Map();
}
}
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;
}
}
export { Graph, Node };
@@ -0,0 +1,44 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// ------ internal util
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 = {}));
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}}.
*/
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;
}
export { IInstantiationService, _util, createDecorator };
@@ -0,0 +1,403 @@
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';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// 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()}`;
}
}
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;
},
getIfExists: (id) => {
if (_done) {
throw illegalState('service accessor is only valid during the invocation of its target method');
}
const result = this._getOrCreateServiceInstance(id, _trace);
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) {
// eslint-disable-next-line local/code-no-any-casts
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);
}
}
}
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
export { InstantiationService, Trace };
@@ -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.
*--------------------------------------------------------------------------------------------*/
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);
}
}
export { ServiceCollection };
@@ -0,0 +1,42 @@
import { Emitter } from '../../../base/common/event.js';
import { Disposable, toDisposable } from '../../../base/common/lifecycle.js';
import { Registry } from '../../registry/common/platform.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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 extends Disposable {
constructor() {
super(...arguments);
this.schemasById = {};
this._onDidChangeSchema = this._register(new Emitter());
}
registerSchema(uri, unresolvedSchemaContent, store) {
const normalizedUri = normalizeId(uri);
this.schemasById[normalizedUri] = unresolvedSchemaContent;
this._onDidChangeSchema.fire(uri);
if (store) {
store.add(toDisposable(() => {
delete this.schemasById[normalizedUri];
this._onDidChangeSchema.fire(uri);
}));
}
}
notifySchemaChanged(uri) {
this._onDidChangeSchema.fire(uri);
}
}
const jsonContributionRegistry = new JSONContributionRegistry();
Registry.add(Extensions.JSONContribution, jsonContributionRegistry);
export { Extensions };
@@ -0,0 +1,286 @@
import { IntervalTimer, TimeoutTimer } from '../../../base/common/async.js';
import { illegalState } from '../../../base/common/errors.js';
import { Event, Emitter } from '../../../base/common/event.js';
import { IME } from '../../../base/common/ime.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { localize } from '../../../nls.js';
import { NoMatchingKb } from './keybindingResolver.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const HIGH_FREQ_COMMANDS = /^(cursor|delete|undo|redo|tab|editor\.action\.clipboard)/;
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, enforceContextCheck = false) {
const result = this._getResolver().lookupPrimaryKeybinding(commandId, context || this._contextKeyService, enforceContextCheck);
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(localize(1701, "({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(localize(1702, "({0}) was pressed. Waiting for next key of chord...", fullKeypressLabel));
}
}
this._scheduleLeaveChordMode();
if (IME.enabled) {
IME.disable();
}
}
_leaveChordMode() {
if (this._currentChordStatusMessage) {
this._currentChordStatusMessage.close();
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(localize(1703, "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(localize(1704, "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;
}
}
}
export { AbstractKeybindingService };
@@ -0,0 +1,56 @@
import { illegalArgument } from '../../../base/common/errors.js';
import { UILabelProvider, AriaLabelProvider, ElectronAcceleratorLabelProvider, UserSettingsLabelProvider } from '../../../base/common/keybindingLabels.js';
import { ResolvedKeybinding, ResolvedChord } from '../../../base/common/keybindings.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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));
}
}
export { BaseResolvedKeybinding };
@@ -0,0 +1,9 @@
import { createDecorator } from '../../instantiation/common/instantiation.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const IKeybindingService = createDecorator('keybindingService');
export { IKeybindingService };
@@ -0,0 +1,304 @@
import { expressionsAreEqualWithConstantSubstitution, implies } from '../../contextkey/common/contextkey.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// util definitions to make working with the above types easier within this module:
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
*/
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, enforceContextCheck = false) {
const items = this._lookupMap.get(commandId);
if (typeof items === 'undefined' || items.length === 0) {
return null;
}
if (items.length === 1 && !enforceContextCheck) {
return items[0];
}
for (let i = items.length - 1; i >= 0; i--) {
const item = items[i];
if (context.contextMatchesRules(item.when)) {
return item;
}
}
if (enforceContextCheck) {
return null;
}
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`));
}
export { KeybindingResolver, NoMatchingKb };
@@ -0,0 +1,111 @@
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 { DisposableStore, combinedDisposable, toDisposable } from '../../../base/common/lifecycle.js';
import { LinkedList } from '../../../base/common/linkedList.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* 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);
}
}
const KeybindingsRegistry = new KeybindingsRegistryImpl();
// Define extension point ids
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;
}
export { Extensions, KeybindingsRegistry };
@@ -0,0 +1,35 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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;
}
}
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;
}
export { ResolvedKeybindingItem, toEmptyArrayIfContainsNull };
@@ -0,0 +1,175 @@
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';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Do not instantiate. Use KeybindingService to get a ResolvedKeybinding seeded with information about the current kb layout.
*/
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 [];
}
}
export { USLayoutResolvedKeybinding };
@@ -0,0 +1,5 @@
import { createDecorator } from '../../instantiation/common/instantiation.js';
const ILabelService = createDecorator('labelService');
export { ILabelService };
@@ -0,0 +1,9 @@
import { createDecorator } from '../../instantiation/common/instantiation.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const ILayoutService = createDecorator('layoutService');
export { ILayoutService };
File diff suppressed because it is too large Load Diff
+271
View File
@@ -0,0 +1,271 @@
import { Emitter } from '../../../base/common/event.js';
import { hash } from '../../../base/common/hash.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { ResourceMap } from '../../../base/common/map.js';
import { joinPath } from '../../../base/common/resources.js';
import { isString } from '../../../base/common/types.js';
import { URI } from '../../../base/common/uri.js';
import { RawContextKey } from '../../contextkey/common/contextkey.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
const ILogService = createDecorator('logService');
const ILoggerService = createDecorator('loggerService');
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 = {}));
const DEFAULT_LOG_LEVEL = LogLevel.Info;
function canLog(loggerLevel, messageLevel) {
return loggerLevel !== LogLevel.Off && loggerLevel <= messageLevel;
}
class AbstractLogger extends Disposable {
constructor() {
super(...arguments);
this.level = DEFAULT_LOG_LEVEL;
this._onDidChangeLogLevel = this._register(new Emitter());
}
get onDidChangeLogLevel() { return this._onDidChangeLogLevel.event; }
setLevel(level) {
if (this.level !== level) {
this.level = level;
this._onDidChangeLogLevel.fire(this.level);
}
}
getLevel() {
return this.level;
}
checkLogLevel(level) {
return canLog(this.level, level);
}
canLog(level) {
if (this._store.isDisposed) {
return false;
}
return this.checkLogLevel(level);
}
}
class ConsoleLogger extends AbstractLogger {
constructor(logLevel = DEFAULT_LOG_LEVEL, useColors = true) {
super();
this.useColors = useColors;
this.setLevel(logLevel);
}
trace(message, ...args) {
if (this.canLog(LogLevel.Trace)) {
if (this.useColors) {
console.log('%cTRACE', 'color: #888', message, ...args);
}
else {
console.log(message, ...args);
}
}
}
debug(message, ...args) {
if (this.canLog(LogLevel.Debug)) {
if (this.useColors) {
console.log('%cDEBUG', 'background: #eee; color: #888', message, ...args);
}
else {
console.log(message, ...args);
}
}
}
info(message, ...args) {
if (this.canLog(LogLevel.Info)) {
if (this.useColors) {
console.log('%c INFO', 'color: #33f', message, ...args);
}
else {
console.log(message, ...args);
}
}
}
warn(message, ...args) {
if (this.canLog(LogLevel.Warning)) {
if (this.useColors) {
console.warn('%c WARN', 'color: #993', message, ...args);
}
else {
console.log(message, ...args);
}
}
}
error(message, ...args) {
if (this.canLog(LogLevel.Error)) {
if (this.useColors) {
console.error('%c ERR', 'color: #f33', message, ...args);
}
else {
console.error(message, ...args);
}
}
}
}
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();
}
}
class AbstractLoggerService extends Disposable {
constructor(logLevel, logsHome, loggerResources) {
super();
this.logLevel = logLevel;
this.logsHome = logsHome;
this._loggers = new ResourceMap();
this._onDidChangeLoggers = this._register(new Emitter);
this._onDidChangeVisibility = this._register(new Emitter);
if (loggerResources) {
for (const loggerResource of loggerResources) {
this._loggers.set(loggerResource.resource, { logger: undefined, info: loggerResource });
}
}
}
getLoggerEntry(resourceOrId) {
if (isString(resourceOrId)) {
return [...this._loggers.values()].find(logger => logger.info.id === resourceOrId);
}
return this._loggers.get(resourceOrId);
}
createLogger(idOrResource, options) {
const resource = this.toResource(idOrResource);
const id = isString(idOrResource) ? idOrResource : (options?.id ?? hash(resource.toString()).toString(16));
let logger = this._loggers.get(resource)?.logger;
const logLevel = options?.logLevel === 'always' ? LogLevel.Trace : options?.logLevel;
if (!logger) {
logger = this.doCreateLogger(resource, logLevel ?? this.getLogLevel(resource) ?? this.logLevel, { ...options, id });
}
const loggerEntry = {
logger,
info: {
resource,
id,
logLevel,
name: options?.name,
hidden: options?.hidden,
group: options?.group,
extensionId: options?.extensionId,
when: options?.when
}
};
this.registerLogger(loggerEntry.info);
// TODO: @sandy081 Remove this once registerLogger can take ILogger
this._loggers.set(resource, loggerEntry);
return logger;
}
toResource(idOrResource) {
return isString(idOrResource) ? joinPath(this.logsHome, `${idOrResource}.log`) : idOrResource;
}
setVisibility(resourceOrId, visibility) {
const logger = this.getLoggerEntry(resourceOrId);
if (logger && visibility !== !logger.info.hidden) {
logger.info.hidden = !visibility;
this._loggers.set(logger.info.resource, logger);
this._onDidChangeVisibility.fire([logger.info.resource, visibility]);
}
}
getLogLevel(resource) {
let logLevel;
if (resource) {
logLevel = this._loggers.get(resource)?.info.logLevel;
}
return logLevel ?? this.logLevel;
}
registerLogger(resource) {
const existing = this._loggers.get(resource.resource);
if (existing) {
if (existing.info.hidden !== resource.hidden) {
this.setVisibility(resource.resource, !resource.hidden);
}
}
else {
this._loggers.set(resource.resource, { info: resource, logger: undefined });
this._onDidChangeLoggers.fire({ added: [resource], removed: [] });
}
}
dispose() {
this._loggers.forEach(logger => logger.logger?.dispose());
this._loggers.clear();
super.dispose();
}
}
class NullLogger {
constructor() {
this.onDidChangeLogLevel = new Emitter().event;
}
setLevel(level) { }
getLevel() { return LogLevel.Info; }
trace(message, ...args) { }
debug(message, ...args) { }
info(message, ...args) { }
warn(message, ...args) { }
error(message, ...args) { }
dispose() { }
}
class NullLoggerService extends AbstractLoggerService {
constructor() {
super(LogLevel.Off, URI.parse('log:///log'));
}
doCreateLogger(resource, logLevel, options) {
return new NullLogger();
}
}
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
new RawContextKey('logLevel', LogLevelToString(LogLevel.Info));
export { AbstractLogger, AbstractLoggerService, ConsoleLogger, DEFAULT_LOG_LEVEL, ILogService, ILoggerService, LogLevel, LogLevelToString, MultiplexLogger, NullLogger, NullLoggerService, canLog };
@@ -0,0 +1,40 @@
import { Disposable } from '../../../base/common/lifecycle.js';
import { MultiplexLogger } from './log.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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);
}
}
export { LogService };
@@ -0,0 +1,73 @@
import { renderMarkdown } from '../../../base/browser/markdownRenderer.js';
import { onUnexpectedError } from '../../../base/common/errors.js';
import { registerSingleton } from '../../instantiation/common/extensions.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
import { IOpenerService } from '../../opener/common/opener.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
const IMarkdownRendererService = createDecorator('markdownRendererService');
let MarkdownRendererService = class MarkdownRendererService {
constructor(_openerService) {
this._openerService = _openerService;
}
render(markdown, options, outElement) {
const resolvedOptions = { ...options };
if (!resolvedOptions.actionHandler) {
resolvedOptions.actionHandler = (link, mdStr) => {
return openLinkFromMarkdown(this._openerService, link, mdStr.isTrusted);
};
}
if (!resolvedOptions.codeBlockRenderer) {
resolvedOptions.codeBlockRenderer = (alias, value) => {
return this._defaultCodeBlockRenderer?.renderCodeBlock(alias, value, resolvedOptions ?? {}) ?? Promise.resolve(document.createElement('span'));
};
}
const rendered = renderMarkdown(markdown, resolvedOptions, outElement);
rendered.element.classList.add('rendered-markdown');
return rendered;
}
setDefaultCodeBlockRenderer(renderer) {
this._defaultCodeBlockRenderer = renderer;
}
};
MarkdownRendererService = __decorate([
__param(0, IOpenerService)
], MarkdownRendererService);
async function openLinkFromMarkdown(openerService, link, isTrusted, skipValidation) {
try {
return await openerService.open(link, {
fromUserGesture: true,
allowContributedOpeners: true,
allowCommands: toAllowCommandsOption(isTrusted),
skipValidation
});
}
catch (e) {
onUnexpectedError(e);
return false;
}
}
function toAllowCommandsOption(isTrusted) {
if (isTrusted === true) {
return true; // Allow all commands
}
if (isTrusted && Array.isArray(isTrusted.enabledCommands)) {
return isTrusted.enabledCommands; // Allow subset of commands
}
return false; // Block commands
}
registerSingleton(IMarkdownRendererService, MarkdownRendererService, 1 /* InstantiationType.Delayed */);
export { IMarkdownRendererService, MarkdownRendererService, openLinkFromMarkdown };
@@ -0,0 +1,330 @@
import { isFalsyOrEmpty, isNonEmptyArray } from '../../../base/common/arrays.js';
import { DebounceEmitter } from '../../../base/common/event.js';
import { Iterable } from '../../../base/common/iterator.js';
import { ResourceMap, ResourceSet } from '../../../base/common/map.js';
import { Schemas } from '../../../base/common/network.js';
import { URI } from '../../../base/common/uri.js';
import { localize } from '../../../nls.js';
import { MarkerSeverity } from './markers.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const unsupportedSchemas = new Set([
Schemas.inMemory,
Schemas.vscodeSourceControl,
Schemas.walkThrough,
Schemas.walkThroughSnippet,
Schemas.vscodeChatCodeBlock,
Schemas.vscodeTerminal
]);
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;
}
}
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);
this._filteredResources = new ResourceMap();
}
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, origin } = 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,
origin
};
}
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);
}
}
/**
* Creates an information marker for filtered resources
*/
_createFilteredMarker(resource, reasons) {
const message = reasons.length === 1
? localize(1738, "Problems are paused because: \"{0}\"", reasons[0])
: localize(1739, "Problems are paused because: \"{0}\" and {1} more", reasons[0], reasons.length - 1);
return {
owner: 'markersFilter',
resource,
severity: MarkerSeverity.Info,
message,
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: 1,
};
}
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 reasons = !filter.ignoreResourceFilters ? this._filteredResources.get(resource) : undefined;
if (reasons?.length) {
const infoMarker = this._createFilteredMarker(resource, reasons);
return [infoMarker];
}
const data = this._data.get(resource, owner);
if (!data) {
return [];
}
const result = [];
for (const marker of data) {
if (take > 0 && result.length === take) {
break;
}
const reasons = !filter.ignoreResourceFilters ? this._filteredResources.get(resource) : undefined;
if (reasons?.length) {
result.push(this._createFilteredMarker(resource, reasons));
}
else if (MarkerService._accept(marker, severities)) {
result.push(marker);
}
}
return result;
}
else {
// of one resource OR owner
const iterable = !owner && !resource
? this._data.values()
: this._data.values(resource ?? owner);
const result = [];
const filtered = new ResourceSet();
for (const markers of iterable) {
for (const data of markers) {
if (filtered.has(data.resource)) {
continue;
}
if (take > 0 && result.length === take) {
break;
}
const reasons = !filter.ignoreResourceFilters ? this._filteredResources.get(data.resource) : undefined;
if (reasons?.length) {
result.push(this._createFilteredMarker(data.resource, reasons));
filtered.add(data.resource);
}
else if (MarkerService._accept(data, severities)) {
result.push(data);
}
}
}
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());
}
}
export { MarkerService, unsupportedSchemas };
@@ -0,0 +1,127 @@
import Severity from '../../../base/common/severity.js';
import { localize } from '../../../nls.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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(1732, "Error");
_displayStrings[MarkerSeverity.Warning] = localize(1733, "Warning");
_displayStrings[MarkerSeverity.Info] = localize(1734, "Info");
function toString(a) {
return _displayStrings[a] || '';
}
MarkerSeverity.toString = toString;
const _displayStringsPlural = Object.create(null);
_displayStringsPlural[MarkerSeverity.Error] = localize(1735, "Errors");
_displayStringsPlural[MarkerSeverity.Warning] = localize(1736, "Warnings");
_displayStringsPlural[MarkerSeverity.Info] = localize(1737, "Infos");
function toStringPlural(a) {
return _displayStringsPlural[a] || '';
}
MarkerSeverity.toStringPlural = toStringPlural;
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 = {}));
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 = {}));
const IMarkerService = createDecorator('markerService');
export { IMarkerData, IMarkerService, MarkerSeverity };
@@ -0,0 +1,9 @@
import Severity$1 from '../../../base/common/severity.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
var Severity = Severity$1;
const INotificationService = createDecorator('notificationService');
class NoOpNotification {
}
export { INotificationService, NoOpNotification, Severity };
@@ -0,0 +1,31 @@
import { DisposableStore } from '../../../base/common/lifecycle.js';
import '../../../base/common/observableInternal/index.js';
import { DebugLocation } from '../../../base/common/observableInternal/debugLocation.js';
import { observableFromEventOpts } from '../../../base/common/observableInternal/observables/observableFromEvent.js';
import { derivedOpts } from '../../../base/common/observableInternal/observables/derived.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/** Creates an observable update when a configuration key updates. */
function observableConfigValue(key, defaultValue, configurationService, debugLocation = DebugLocation.ofCaller()) {
return observableFromEventOpts({ debugName: () => `Configuration Key "${key}"`, }, (handleChange) => configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(key)) {
handleChange(e);
}
}), () => configurationService.getValue(key) ?? defaultValue, debugLocation);
}
/** Update the configuration key with a value derived from observables. */
function bindContextKey(key, service, computeValue, debugLocation = DebugLocation.ofCaller()) {
const boundKey = key.bindTo(service);
const store = new DisposableStore();
derivedOpts({ debugName: () => `Set Context Key "${key.key}"` }, reader => {
const value = computeValue(reader);
boundKey.set(value);
return value;
}, debugLocation).recomputeInitiallyAndOnChange(store);
return store;
}
export { bindContextKey, observableConfigValue };
@@ -0,0 +1,41 @@
import '../../../base/common/observableInternal/index.js';
import { IInstantiationService } from '../../instantiation/common/instantiation.js';
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
function hotClassGetOriginalInstance(value) {
if (value instanceof BaseClass) {
// eslint-disable-next-line local/code-no-any-casts
return value._instance;
}
return value;
}
class BaseClass {
constructor(instantiationService) {
this.instantiationService = instantiationService;
}
init(...params) { }
}
/**
* 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.
*/
function wrapInHotClass1(clazz) {
return clazz.get() ;
}
let BaseClass1 = class BaseClass1 extends BaseClass {
constructor(param1, i) { super(i); this.init(param1); }
};
BaseClass1 = __decorate([
__param(1, IInstantiationService)
], BaseClass1);
export { hotClassGetOriginalInstance, wrapInHotClass1 };
@@ -0,0 +1,35 @@
import '../../../base/common/observableInternal/index.js';
import { IInstantiationService } from '../../instantiation/common/instantiation.js';
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
class BaseClass {
constructor(instantiationService) {
this.instantiationService = instantiationService;
}
init(...params) { }
}
/**
* 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.
*/
function wrapInReloadableClass1(getClass) {
// eslint-disable-next-line local/code-no-any-casts
return getClass() ;
}
let BaseClass1 = class BaseClass1 extends BaseClass {
constructor(param1, i) { super(i); this.init(param1); }
};
BaseClass1 = __decorate([
__param(1, IInstantiationService)
], BaseClass1);
export { wrapInReloadableClass1 };
@@ -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,95 @@
import { append, $, EventHelper } from '../../../base/browser/dom.js';
import { DomEmitter } from '../../../base/browser/event.js';
import { StandardKeyboardEvent } from '../../../base/browser/keyboardEvent.js';
import { EventType, 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';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
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, EventType.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.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,43 @@
import { createDecorator } from '../../instantiation/common/instantiation.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const IOpenerService = createDecorator('openerService');
/**
* Encodes selection into the `URI`.
*
* IMPORTANT: you MUST use `extractSelection` to separate the selection
* again from the original `URI` before passing the `URI` into any
* component that is not aware of selections.
*/
function withSelection(uri, selection) {
return uri.with({ fragment: `${selection.startLineNumber},${selection.startColumn}${selection.endLineNumber ? `-${selection.endLineNumber}${selection.endColumn ? `,${selection.endColumn}` : ''}` : ''}` });
}
/**
* 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
*/
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 };
}
export { IOpenerService, extractSelection, withSelection };
@@ -0,0 +1,70 @@
import { env } from '../../../base/common/process.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* @deprecated It is preferred that you use `IProductService` if you can. This
* allows web embedders to override our defaults. But for things like `product.quality`,
* the use is fine because that property is not overridable.
*/
let product;
// Native sandbox environment
const vscodeGlobal = globalThis.vscode;
if (typeof vscodeGlobal !== 'undefined' && typeof vscodeGlobal.context !== 'undefined') {
const configuration = vscodeGlobal.context.configuration();
if (configuration) {
product = configuration.product;
}
else {
throw new Error('Sandbox: unable to resolve product configuration from preload script.');
}
}
// _VSCODE environment
else if (globalThis._VSCODE_PRODUCT_JSON && globalThis._VSCODE_PACKAGE_JSON) {
// Obtain values from product.json and package.json-data
product = globalThis._VSCODE_PRODUCT_JSON;
// Running out of sources
if (env['VSCODE_DEV']) {
Object.assign(product, {
nameShort: `${product.nameShort} Dev`,
nameLong: `${product.nameLong} Dev`,
dataFolderName: `${product.dataFolderName}-dev`,
serverDataFolderName: product.serverDataFolderName ? `${product.serverDataFolderName}-dev` : undefined
});
}
// Version is added during built time, but we still
// want to have it running out of sources so we
// read it from package.json only when we need it.
if (!product.version) {
const pkg = globalThis._VSCODE_PACKAGE_JSON;
Object.assign(product, {
version: pkg.version
});
}
}
// Web environment or unknown
else {
// Built time configuration (do NOT modify)
// eslint-disable-next-line local/code-no-dangerous-type-assertions
product = { /*BUILD->INSERT_PRODUCT_CONFIGURATION*/};
// Running out of sources
if (Object.keys(product).length === 0) {
Object.assign(product, {
version: '1.104.0-dev',
nameShort: 'Code - OSS Dev',
nameLong: 'Code - OSS Dev',
applicationName: 'code-oss',
dataFolderName: '.vscode-oss',
urlProtocol: 'code-oss',
reportIssueUrl: 'https://github.com/microsoft/vscode/issues/new',
licenseName: 'MIT',
licenseUrl: 'https://github.com/microsoft/vscode/blob/main/LICENSE.txt',
serverLicenseUrl: 'https://github.com/microsoft/vscode/blob/main/LICENSE.txt'
});
}
}
var product$1 = product;
export { product$1 as default };
@@ -0,0 +1,9 @@
import { createDecorator } from '../../instantiation/common/instantiation.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const IProductService = createDecorator('productService');
export { IProductService };
@@ -0,0 +1,16 @@
import { createDecorator } from '../../instantiation/common/instantiation.js';
const IProgressService = createDecorator('progressService');
class Progress {
static { this.None = Object.freeze({ report() { } }); }
constructor(callback) {
this.callback = callback;
}
report(item) {
this._value = item;
this.callback(this._value);
}
}
const IEditorProgressService = createDecorator('editorProgressService');
export { IEditorProgressService, IProgressService, Progress };
@@ -0,0 +1,388 @@
import { toErrorMessage } from '../../../base/common/errorMessage.js';
import { isCancellationError } from '../../../base/common/errors.js';
import { or, matchesContiguousSubString, matchesWords, matchesPrefix } 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 { WillSaveStateReason, IStorageService } from '../../storage/common/storage.js';
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
import { removeAccents } from '../../../base/common/normalization.js';
import { Categories } from '../../action/common/actionCommonCategories.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var AbstractCommandsQuickAccessProvider_1, CommandsHistory_1;
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.keybindingService = keybindingService;
this.commandService = commandService;
this.telemetryService = telemetryService;
this.dialogService = dialogService;
this.commandsHistory = this._register(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);
});
const noAccentsFilter = this.normalizeForFiltering(filter);
// Filter
const filteredCommandPicks = [];
for (const commandPick of allCommandPicks) {
commandPick.labelNoAccents ??= this.normalizeForFiltering(commandPick.label);
const labelHighlights = AbstractCommandsQuickAccessProvider_1.WORD_FILTER(noAccentsFilter, commandPick.labelNoAccents) ?? undefined;
let aliasHighlights;
if (commandPick.commandAlias) {
commandPick.aliasNoAccents ??= this.normalizeForFiltering(commandPick.commandAlias);
aliasHighlights = AbstractCommandsQuickAccessProvider_1.WORD_FILTER(noAccentsFilter, commandPick.aliasNoAccents) ?? 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
}
}
// if one is Developer and the other isn't, put non-Developer first
const isDeveloperA = commandPickA.commandCategory === Categories.Developer.value;
const isDeveloperB = commandPickB.commandCategory === Categories.Developer.value;
if (isDeveloperA && !isDeveloperB) {
return 1;
}
if (!isDeveloperA && isDeveloperB) {
return -1;
}
// 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(1740, "recently used") });
addOtherSeparator = true;
}
if (addSuggestedSeparator && commandPick.tfIdfScore !== undefined) {
commandPicks.push({ type: 'separator', label: localize(1741, "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(1742, "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(1743, "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(1744, "similar commands") });
}
return commandPicks;
})()
};
}
toCommandPick(commandPick, runOptions) {
if (commandPick.type === 'separator') {
return commandPick;
}
const keybinding = this.keybindingService.lookupKeybinding(commandPick.commandId);
const ariaLabel = keybinding ?
localize(1745, "{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(1746, "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;
}
/**
* Normalizes a string for filtering by removing accents, but only if
* the result has the same length, otherwise returns the original string.
*/
normalizeForFiltering(value) {
const withoutAccents = removeAccents(value);
if (withoutAccents.length !== value.length) {
this.telemetryService.publicLog2('QuickAccess:FilterLengthMismatch', {
originalLength: value.length,
normalizedLength: withoutAccents.length
});
return value;
}
else {
return withoutAccents;
}
}
};
AbstractCommandsQuickAccessProvider = AbstractCommandsQuickAccessProvider_1 = __decorate([
__param(1, IInstantiationService),
__param(2, IKeybindingService),
__param(3, ICommandService),
__param(4, ITelemetryService),
__param(5, IDialogService)
], 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 { AbstractCommandsQuickAccessProvider, CommandsHistory };
@@ -0,0 +1,77 @@
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';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var HelpQuickAccessProvider_1;
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(1747, "{0}, {1}", label, helpEntry.description),
description: helpEntry.description
};
});
}
};
HelpQuickAccessProvider = HelpQuickAccessProvider_1 = __decorate([
__param(0, IQuickInputService),
__param(1, IKeybindingService)
], HelpQuickAccessProvider);
export { HelpQuickAccessProvider };
@@ -0,0 +1,486 @@
/*---------------------------------------------------------------------------------------------
* 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%;
-webkit-app-region: no-drag;
border-radius: 6px;
}
.quick-input-titlebar {
cursor: grab;
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;
}
/* give some space between input and action bar */
.quick-input-inline-action-bar > .actions-container > .action-item:first-child {
margin-left: 5px;
}
/* center horizontally */
.quick-input-inline-action-bar > .actions-container > .action-item {
margin-top: 2px;
}
.quick-input-title {
cursor: grab;
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-right-action-bar > .actions-container > .action-item {
margin-left: 4px;
}
.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 {
cursor: grab;
display: flex;
padding: 6px 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-widget .quick-input-header .monaco-checkbox {
margin-top: 6px;
}
.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 6px;
}
.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-widget .monaco-checkbox {
margin-right: 0;
}
.quick-input-widget .quick-input-list .monaco-checkbox,
.quick-input-widget .quick-input-tree .monaco-checkbox {
margin-top: 4px;
}
.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-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;
}
.quick-input-list > .monaco-list:focus .monaco-list-row.focused {
outline: 1px solid var(--vscode-list-focusOutline) !important;
outline-offset: -1px;
}
.quick-input-list > .monaco-list:focus .monaco-list-row.focused .quick-input-list-entry.quick-input-list-separator-border {
border-color: transparent;
}
/* 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;
}
/* Tree */
.quick-input-tree .monaco-list {
overflow: hidden;
max-height: calc(20 * 22px);
padding-bottom: 5px;
}
.quick-input-tree .quick-input-tree-entry {
box-sizing: border-box;
overflow: hidden;
display: flex;
padding: 0 6px;
}
.quick-input-tree .quick-input-tree-label {
overflow: hidden;
display: flex;
height: 100%;
flex: 1;
}
.quick-input-tree .quick-input-tree-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-tree .quick-input-tree-rows {
overflow: hidden;
text-overflow: ellipsis;
display: flex;
flex-direction: column;
height: 100%;
flex: 1;
margin-left: 5px;
}
.quick-input-tree .quick-input-tree-rows > .quick-input-tree-row {
display: flex;
align-items: center;
}
.quick-input-tree .quick-input-tree-rows > .quick-input-tree-row .monaco-icon-label,
.quick-input-tree .quick-input-tree-rows > .quick-input-tree-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-tree .quick-input-tree-rows > .quick-input-tree-row .codicon[class*='codicon-'] {
vertical-align: text-bottom;
}
.quick-input-tree .quick-input-tree-rows .monaco-highlighted-label > span {
opacity: 1;
}
.quick-input-tree .quick-input-tree-entry-action-bar {
display: flex;
flex: 0;
overflow: visible;
}
.quick-input-tree .quick-input-tree-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-tree .quick-input-tree-entry-action-bar .action-label.codicon {
margin-right: 4px;
padding: 2px;
}
.quick-input-tree .quick-input-tree-entry-action-bar {
margin-top: 1px;
}
.quick-input-tree .quick-input-tree-entry-action-bar {
margin-right: 4px;
/* separate from scrollbar */
}
.quick-input-tree .quick-input-tree-entry .quick-input-tree-entry-action-bar .action-label.always-visible,
.quick-input-tree .quick-input-tree-entry:hover .quick-input-tree-entry-action-bar .action-label,
.quick-input-tree .quick-input-tree-entry.focus-inside .quick-input-tree-entry-action-bar .action-label,
.quick-input-tree .monaco-list-row.focused .quick-input-tree-entry-action-bar .action-label,
.quick-input-tree .monaco-list-row.passive-focused .quick-input-tree-entry-action-bar .action-label {
display: flex;
}
.quick-input-tree > .monaco-list:focus .monaco-list-row.focused {
outline: 1px solid var(--vscode-list-focusOutline) !important;
outline-offset: -1px;
}
@@ -0,0 +1,271 @@
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';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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;
}
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 () => {
// Cancel any previous ask for picks and busy
picksCts?.dispose(true);
picker.busy = false;
// Setting the .value will call dispose() on the previous value, so we need to do this AFTER cancelling with dispose(true).
const picksDisposables = picksDisposable.value = new DisposableStore();
// Create new cancellation source for this run
picksCts = picksDisposables.add(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) ;
// 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], event.inBackground);
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;
}
}
export { PickerQuickAccessProvider, TriggerAction };
@@ -0,0 +1,209 @@
import { DeferredPromise } from '../../../base/common/async.js';
import { CancellationTokenSource } from '../../../base/common/cancellation.js';
import { Event } from '../../../base/common/event.js';
import { Disposable, toDisposable, isDisposable, DisposableStore } from '../../../base/common/lifecycle.js';
import { IInstantiationService } from '../../instantiation/common/instantiation.js';
import { Extensions, DefaultQuickAccessFilterValue } from '../common/quickAccess.js';
import { IQuickInputService, ItemActivation } from '../common/quickInput.js';
import { Registry } from '../../registry/common/platform.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
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;
this._register(toDisposable(() => {
for (const provider of this.mapProviderToDescriptor.values()) {
if (isDisposable(provider)) {
provider.dispose();
}
}
this.visibleQuickAccess?.picker.dispose();
}));
}
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({ useSeparators: true }));
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 };
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,189 @@
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 { quickInputTypeContextKeyValue, inQuickInputContext, endOfQuickInputBoxContext } from './quickInput.js';
import { QuickPickFocus, IQuickInputService } from '../common/quickInput.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function registerQuickInputCommandAndKeybindingRule(rule, options = {}) {
KeybindingsRegistry.registerCommandAndKeybindingRule({
weight: 200 /* KeybindingWeight.WorkbenchContrib */,
when: inQuickInputContext,
metadata: { description: localize(1758, "Used while in the context of any kind of quick input. If you change one keybinding for this command, you should change all of the other keybindings (modifier variants) of this command as well.") },
...rule,
secondary: getSecondary(rule.primary, rule.secondary ?? [], options)
});
}
function registerQuickPickCommandAndKeybindingRule(rule, options = {}) {
KeybindingsRegistry.registerCommandAndKeybindingRule({
weight: 200 /* KeybindingWeight.WorkbenchContrib */,
when: ContextKeyExpr.and(ContextKeyExpr.or(
// Only things that use Tree widgets
ContextKeyExpr.equals(quickInputTypeContextKeyValue, "quickPick" /* QuickInputType.QuickPick */), ContextKeyExpr.equals(quickInputTypeContextKeyValue, "quickTree" /* QuickInputType.QuickTree */)), inQuickInputContext),
metadata: { description: localize(1759, "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.") },
...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(1760, "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(1761, "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
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'quickInput.accept',
primary: 3 /* KeyCode.Enter */,
weight: 200 /* KeybindingWeight.WorkbenchContrib */,
when: ContextKeyExpr.and(
// All other kinds of Quick things handle Accept, except Widget. In other words, Accepting is a detail on the things
// that extend IQuickInput
ContextKeyExpr.notEquals(quickInputTypeContextKeyValue, "quickWidget" /* QuickInputType.QuickWidget */), inQuickInputContext, ContextKeyExpr.not('isComposing')),
metadata: { description: localize(1762, "Used while in the context of some quick input. If you change one keybinding for this command, you should change all of the other keybindings (modifier variants) of this command as well.") },
handler: (accessor) => {
const currentQuickPick = accessor.get(IQuickInputService).currentQuickInput;
currentQuickPick?.accept();
},
secondary: getSecondary(3 /* KeyCode.Enter */, [], { withAltMod: true, withCtrlMod: true, withCmdMod: true })
});
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(inQuickInputContext, ContextKeyExpr.equals(quickInputTypeContextKeyValue, "quickPick" /* QuickInputType.QuickPick */), 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 });
//#endregion
//#region Hide
registerQuickInputCommandAndKeybindingRule({
id: 'quickInput.hide',
primary: 9 /* KeyCode.Escape */,
handler: (accessor) => {
const currentQuickPick = accessor.get(IQuickInputService).currentQuickInput;
currentQuickPick?.hide();
}
}, { withAltMod: true, withCtrlMod: true, withCmdMod: true });
//#endregion
//#region Toggle Hover
registerQuickPickCommandAndKeybindingRule({
id: 'quickInput.toggleHover',
primary: ctrlKeyMod | 10 /* KeyCode.Space */,
handler: accessor => {
const quickInputService = accessor.get(IQuickInputService);
quickInputService.toggleHover();
}
});
//#endregion
@@ -0,0 +1,104 @@
import { append, $ as $$1 } 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';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const $ = $$1;
class QuickInputBox extends Disposable {
constructor(parent, inputBoxStyles, toggleStyles) {
super();
this.parent = parent;
this.onDidChange = (handler) => {
return this.findInput.onDidChange(handler);
};
this.container = 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 = 'textbox';
input.ariaHasPopup = 'menu';
input.ariaAutoComplete = 'list';
}
get onKeyDown() {
return this.findInput.onKeyDown;
}
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);
}
get ariaLabel() {
return this.findInput.inputBox.inputElement.getAttribute('aria-label') || '';
}
set ariaLabel(ariaLabel) {
this.findInput.inputBox.inputElement.setAttribute('aria-label', ariaLabel);
}
hasFocus() {
return this.findInput.inputBox.hasFocus();
}
setAttribute(name, value) {
this.findInput.inputBox.inputElement.setAttribute(name, value);
}
removeAttribute(name) {
this.findInput.inputBox.inputElement.removeAttribute(name);
}
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();
}
}
export { QuickInputBox };
@@ -0,0 +1,901 @@
import { onDidRegisterWindow, onWillUnregisterWindow, getWindow, EventType, addDisposableListener, append, $ as $$1, trackFocus, isAncestor, isHTMLElement, reset, isAncestorOfActiveElement, addDisposableGenericMouseUpListener, addDisposableGenericMouseDownListener, addDisposableGenericMouseMoveListener, getActiveWindow } from '../../../base/browser/dom.js';
import { createStyleSheet } from '../../../base/browser/domStylesheets.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 { isString } from '../../../base/common/types.js';
import { localize } from '../../../nls.js';
import { QuickInputHideReason } from '../common/quickInput.js';
import { QuickInputBox } from './quickInputBox.js';
import { InQuickInputContextKey, QuickInputTypeContextKey, EndOfQuickInputBoxContextKey, QuickPick, InputBox, backButton, QuickInputAlignmentContextKey } 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 { QuickInputList } from './quickInputList.js';
import { IContextKeyService } from '../../contextkey/common/contextkey.js';
import './quickInputActions.js';
import '../../../base/common/observableInternal/index.js';
import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js';
import { IStorageService } from '../../storage/common/storage.js';
import { IConfigurationService } from '../../configuration/common/configuration.js';
import { setTimeout0, platform } from '../../../base/common/platform.js';
import { getWindowControlsStyle } from '../../window/common/window.js';
import { getZoomFactor } from '../../../base/browser/browser.js';
import { TriStateCheckbox } from '../../../base/browser/ui/toggle/toggle.js';
import { defaultCheckboxStyles } from '../../theme/browser/defaultStyles.js';
import { QuickInputTreeController } from './tree/quickInputTreeController.js';
import { autorun } from '../../../base/common/observableInternal/reactions/autorun.js';
import { observableValue } from '../../../base/common/observableInternal/observables/observableValue.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var QuickInputController_1;
const $ = $$1;
const VIEWSTATE_STORAGE_KEY = 'workbench.quickInput.viewState';
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, storageService) {
super();
this.options = options;
this.layoutService = layoutService;
this.instantiationService = instantiationService;
this.storageService = storageService;
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(contextKeyService);
this.quickInputTypeContext = QuickInputTypeContextKey.bindTo(contextKeyService);
this.endOfQuickInputBoxContext = EndOfQuickInputBoxContextKey.bindTo(contextKeyService);
this.idPrefix = options.idPrefix;
this._container = options.container;
this.styles = options.styles;
this._register(Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => this.registerKeyModsListeners(window, disposables), { window: mainWindow, disposables: this._store }));
this._register(onWillUnregisterWindow(window => {
if (this.ui && 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);
}
}));
this.viewState = this.loadViewState();
}
registerKeyModsListeners(window, disposables) {
const listener = (e) => {
this.keyMods.ctrlCmd = e.ctrlKey || e.metaKey;
this.keyMods.alt = e.altKey;
};
for (const event of [EventType.KEY_DOWN, EventType.KEY_UP, EventType.MOUSE_DOWN]) {
disposables.add(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 (getWindow(this._container) !== getWindow(this.layoutService.activeContainer)) {
this.reparentUI(this.layoutService.activeContainer);
this.layout(this.layoutService.activeContainerDimension, this.layoutService.activeContainerOffset.quickPickTop);
}
}
return this.ui;
}
const container = append(this._container, $('.quick-input-widget.show-file-icons'));
container.tabIndex = -1;
container.style.display = 'none';
const styleSheet = createStyleSheet(container);
const titleBar = 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 = 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 = append(container, $('.quick-input-header'));
const checkAll = this._register(new TriStateCheckbox(localize(1763, "Toggle all checkboxes"), false, { ...defaultCheckboxStyles, size: 15 }));
append(headerContainer, checkAll.domNode);
this._register(checkAll.onChange(() => {
const checked = checkAll.checked;
list.setAllVisibleChecked(checked === true);
}));
this._register(addDisposableListener(checkAll.domNode, EventType.CLICK, e => {
if (e.x || e.y) { // Avoid 'click' triggered by 'space'...
inputBox.setFocus();
}
}));
const description2 = append(headerContainer, $('.quick-input-description'));
const inputContainer = append(headerContainer, $('.quick-input-and-message'));
const filterContainer = 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 = append(filterContainer, $('.quick-input-visible-count'));
visibleCountContainer.setAttribute('aria-live', 'polite');
visibleCountContainer.setAttribute('aria-atomic', 'true');
const visibleCount = this._register(new CountBadge(visibleCountContainer, { countFormat: localize(1764, "{0} Results") }, this.styles.countBadge));
const countContainer = append(filterContainer, $('.quick-input-count'));
countContainer.setAttribute('aria-live', 'polite');
const count = this._register(new CountBadge(countContainer, { countFormat: localize(1765, "{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 = append(headerContainer, $('.quick-input-action'));
const ok = this._register(new Button(okContainer, this.styles.button));
ok.label = localize(1766, "OK");
this._register(ok.onDidClick(e => {
this.onDidAcceptEmitter.fire();
}));
const customButtonContainer = append(headerContainer, $('.quick-input-action'));
const customButton = this._register(new Button(customButtonContainer, { ...this.styles.button, supportIcons: true }));
customButton.label = localize(1767, "Custom");
this._register(customButton.onDidClick(e => {
this.onDidCustomEmitter.fire();
}));
const message = 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 = append(container, $('.quick-input-html-widget'));
widget.tabIndex = -1;
const description1 = append(container, $('.quick-input-description'));
// List
const listId = this.idPrefix + 'list';
const list = this._register(this.instantiationService.createInstance(QuickInputList, container, this.options.hoverDelegate, this.options.linkOpenerDelegate, listId));
inputBox.setAttribute('aria-controls', listId);
this._register(list.onDidChangeFocus(() => {
if (inputBox.hasFocus()) {
inputBox.setAttribute('aria-activedescendant', list.getActiveDescendant() ?? '');
}
}));
this._register(list.onChangedAllVisibleChecked(checked => {
// TODO: Support tri-state checkbox when we remove the .indent property that is faking tree structure.
checkAll.checked = checked;
}));
this._register(list.onChangedVisibleCount(c => {
visibleCount.setCount(c);
}));
this._register(list.onChangedCheckedCount(c => {
// TODO@TylerLeonhardt: Without this setTimeout, the screen reader will not read out
// the final count of checked items correctly. Investigate a better way
// to do this. ref https://github.com/microsoft/vscode/issues/258617
setTimeout0(() => 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);
}));
// Tree
const tree = this._register(this.instantiationService.createInstance(QuickInputTreeController, container, this.options.hoverDelegate));
this._register(tree.tree.onDidChangeFocus(() => {
if (inputBox.hasFocus()) {
inputBox.setAttribute('aria-activedescendant', tree.getActiveDescendant() ?? '');
}
}));
this._register(tree.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();
tree.tree.setFocus([]);
}, 0);
}));
// Wire up tree's accept event to the UI's accept emitter for non-pickable items
this._register(tree.onDidAccept(() => {
this.onDidAcceptEmitter.fire();
}));
this._register(tree.tree.onDidChangeContentHeight(() => this.updateLayout()));
const focusTracker = trackFocus(container);
this._register(focusTracker);
this._register(addDisposableListener(container, EventType.FOCUS, e => {
const ui = this.getUI();
if (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 (isAncestor(e.relatedTarget, ui.container)) {
return;
}
this.inQuickInputContext.set(true);
this.previousFocusElement = 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);
}
// Allow screenreaders to read what's in the input
// Note: this works for arrow keys and selection changes,
// but not for deletions since that often triggers a
// change in the list.
inputBox.removeAttribute('aria-activedescendant');
}));
this._register(addDisposableListener(container, EventType.FOCUS, (e) => {
inputBox.setFocus();
}));
// Drag and Drop support
this.dndController = this._register(this.instantiationService.createInstance(QuickInputDragAndDropController, this._container, container, [
{
node: titleBar,
includeChildren: true
},
{
node: headerContainer,
includeChildren: false
}
], this.viewState));
// DnD update layout
this._register(autorun(reader => {
const dndViewState = this.dndController?.dndViewState.read(reader);
if (!dndViewState) {
return;
}
if (dndViewState.top !== undefined && dndViewState.left !== undefined) {
this.viewState = {
...this.viewState,
top: dndViewState.top,
left: dndViewState.left
};
}
else {
// Reset position/size
this.viewState = undefined;
}
this.updateLayout();
// Save position
if (dndViewState.done) {
this.saveViewState(this.viewState);
}
}));
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,
tree,
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;
append(this._container, this.ui.container);
this.dndController?.reparentUI(this._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({ useSeparators: true });
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;
if (options.value) {
input.value = options.value;
}
input.canSelectMany = !!options.canPickMany;
input.placeholder = options.placeHolder;
input.prompt = options.prompt;
input.ignoreFocusOut = !!options.ignoreFocusLost;
input.matchOnDescription = !!options.matchOnDescription;
input.matchOnDetail = !!options.matchOnDetail;
if (options.sortByLabel !== undefined) {
input.sortByLabel = options.sortByLabel;
}
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();
});
});
}
setValidationOnInput(input, validationResult) {
if (validationResult && isString(validationResult)) {
input.severity = Severity.Error;
input.validationMessage = validationResult;
}
else if (validationResult && !isString(validationResult)) {
input.severity = validationResult.severity;
input.validationMessage = validationResult.content;
}
else {
input.severity = Severity.Ignore;
input.validationMessage = undefined;
}
}
input(options = {}, token = CancellationToken.None) {
return new Promise((resolve) => {
if (token.isCancellationRequested) {
resolve(undefined);
return;
}
const input = this.createInputBox();
const validateInput = options.validateInput || (() => Promise.resolve(undefined));
const onDidValueChange = Event.debounce(input.onDidChangeValue, (last, cur) => cur, 100);
let validationValue = options.value || '';
let validation = Promise.resolve(validateInput(validationValue));
const disposables = [
input,
onDidValueChange(value => {
if (value !== validationValue) {
validation = Promise.resolve(validateInput(value));
validationValue = value;
}
validation.then(result => {
if (value === validationValue) {
this.setValidationOnInput(input, result);
}
});
}),
input.onDidAccept(() => {
const value = input.value;
if (value !== validationValue) {
validation = Promise.resolve(validateInput(value));
validationValue = value;
}
validation.then(result => {
if (!result || (!isString(result) && result.severity !== Severity.Error)) {
resolve(value);
input.hide();
}
else if (value === validationValue) {
this.setValidationOnInput(input, result);
}
});
}),
token.onCancellationRequested(() => {
input.hide();
}),
input.onDidHide(() => {
dispose(disposables);
resolve(undefined);
}),
];
input.title = options.title;
input.value = options.value || '';
input.valueSelection = options.valueSelection;
input.prompt = options.prompt;
input.placeholder = options.placeHolder;
input.password = !!options.password;
input.ignoreFocusOut = !!options.ignoreFocusLost;
input.show();
});
}
createQuickPick(options = { useSeparators: false }) {
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 = '';
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);
reset(ui.message);
ui.progressBar.stop();
ui.progressBar.getContainer().setAttribute('aria-hidden', 'true');
ui.list.setElements([]);
ui.list.matchOnDescription = false;
ui.list.matchOnDetail = false;
ui.list.matchOnLabel = true;
ui.list.sortByLabel = true;
ui.tree.updateFilterOptions({
matchOnDescription: false,
matchOnLabel: true
});
ui.tree.sortByLabel = true;
ui.ignoreFocusOut = false;
ui.inputBox.toggles = undefined;
const backKeybindingLabel = this.options.backKeybindingLabel();
backButton.tooltip = backKeybindingLabel ? localize(1768, "Back ({0})", backKeybindingLabel) : localize(1769, "Back");
ui.container.style.display = '';
this.updateLayout();
this.dndController?.layoutContainer();
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.domNode.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.tree.displayed = !!visibilities.tree;
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;
const ui = this.getUI();
for (const item of ui.leftActionBar.viewItems) {
item.action.enabled = enabled;
}
for (const item of ui.rightActionBar.viewItems) {
item.action.enabled = enabled;
}
if (enabled) {
ui.checkAll.enable();
}
else {
ui.checkAll.disable();
}
ui.inputBox.enabled = enabled;
ui.ok.enabled = enabled;
ui.list.enabled = enabled;
}
}
hide(reason) {
const controller = this.controller;
if (!controller) {
return;
}
controller.willHide(reason);
const container = this.ui?.container;
const focusChanged = container && !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);
}
toggleHover() {
if (this.isVisible() && this.controller instanceof QuickPick) {
this.getUI().list.toggleHover();
}
}
layout(dimension, titleBarOffset) {
this.dimension = dimension;
this.titleBarOffset = titleBarOffset;
this.updateLayout();
}
updateLayout() {
if (this.ui && this.isVisible()) {
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';
// Position
style.top = `${this.viewState?.top ? Math.round(this.dimension.height * this.viewState.top) : this.titleBarOffset}px`;
style.left = `${Math.round((this.dimension.width * (this.viewState?.left ?? 0.5 /* center */)) - (width / 2))}px`;
this.ui.inputBox.layout();
this.ui.list.layout(this.dimension && this.dimension.height * 0.4);
this.ui.tree.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);
this.ui.tree.tree.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;
}
}
}
loadViewState() {
try {
const data = JSON.parse(this.storageService.get(VIEWSTATE_STORAGE_KEY, -1 /* StorageScope.APPLICATION */, '{}'));
if (data.top !== undefined || data.left !== undefined) {
return data;
}
}
catch { }
return undefined;
}
saveViewState(viewState) {
const isMainWindow = this.layoutService.activeContainer === this.layoutService.mainContainer;
if (!isMainWindow) {
return;
}
if (viewState !== undefined) {
this.storageService.store(VIEWSTATE_STORAGE_KEY, JSON.stringify(viewState), -1 /* StorageScope.APPLICATION */, 1 /* StorageTarget.MACHINE */);
}
else {
this.storageService.remove(VIEWSTATE_STORAGE_KEY, -1 /* StorageScope.APPLICATION */);
}
}
};
QuickInputController = QuickInputController_1 = __decorate([
__param(1, ILayoutService),
__param(2, IInstantiationService),
__param(3, IContextKeyService),
__param(4, IStorageService)
], QuickInputController);
let QuickInputDragAndDropController = class QuickInputDragAndDropController extends Disposable {
constructor(_container, _quickInputContainer, _quickInputDragAreas, initialViewState, _layoutService, contextKeyService, configurationService) {
super();
this._container = _container;
this._quickInputContainer = _quickInputContainer;
this._quickInputDragAreas = _quickInputDragAreas;
this._layoutService = _layoutService;
this.configurationService = configurationService;
this.dndViewState = observableValue(this, undefined);
this._snapThreshold = 20;
this._snapLineHorizontalRatio = 0.25;
this._quickInputAlignmentContext = QuickInputAlignmentContextKey.bindTo(contextKeyService);
const customWindowControls = getWindowControlsStyle(this.configurationService) === "custom" /* WindowControlsStyle.CUSTOM */;
// Do not allow the widget to overflow or underflow window controls.
// Use CSS calculations to avoid having to force layout with `.clientWidth`
this._controlsOnLeft = customWindowControls && platform === 1 /* Platform.Mac */;
this._controlsOnRight = customWindowControls && (platform === 3 /* Platform.Windows */ || platform === 2 /* Platform.Linux */);
this._registerLayoutListener();
this.registerMouseListeners();
this.dndViewState.set({ ...initialViewState, done: true }, undefined);
}
reparentUI(container) {
this._container = container;
}
layoutContainer(dimension = this._layoutService.activeContainerDimension) {
const state = this.dndViewState.get();
const dragAreaRect = this._quickInputContainer.getBoundingClientRect();
if (state?.top && state?.left) {
const a = Math.round(state.left * 1e2) / 1e2;
const b = dimension.width;
const c = dragAreaRect.width;
const d = a * b - c / 2;
this._layout(state.top * dimension.height, d);
}
}
_registerLayoutListener() {
this._register(Event.filter(this._layoutService.onDidLayoutContainer, e => e.container === this._container)((e) => this.layoutContainer(e.dimension)));
}
registerMouseListeners() {
const dragArea = this._quickInputContainer;
// Double click
this._register(addDisposableGenericMouseUpListener(dragArea, (event) => {
const originEvent = new StandardMouseEvent(getWindow(dragArea), event);
if (originEvent.detail !== 2) {
return;
}
// Ignore event if the target is not the drag area
if (!this._quickInputDragAreas.some(({ node, includeChildren }) => includeChildren ? isAncestor(originEvent.target, node) : originEvent.target === node)) {
return;
}
this.dndViewState.set({ top: undefined, left: undefined, done: true }, undefined);
}));
// Mouse down
this._register(addDisposableGenericMouseDownListener(dragArea, (e) => {
const activeWindow = getWindow(this._layoutService.activeContainer);
const originEvent = new StandardMouseEvent(activeWindow, e);
// Ignore event if the target is not the drag area
if (!this._quickInputDragAreas.some(({ node, includeChildren }) => includeChildren ? isAncestor(originEvent.target, node) : originEvent.target === node)) {
return;
}
// Mouse position offset relative to dragArea
const dragAreaRect = this._quickInputContainer.getBoundingClientRect();
const dragOffsetX = originEvent.browserEvent.clientX - dragAreaRect.left;
const dragOffsetY = originEvent.browserEvent.clientY - dragAreaRect.top;
let isMovingQuickInput = false;
const mouseMoveListener = addDisposableGenericMouseMoveListener(activeWindow, (e) => {
const mouseMoveEvent = new StandardMouseEvent(activeWindow, e);
mouseMoveEvent.preventDefault();
if (!isMovingQuickInput) {
isMovingQuickInput = true;
}
this._layout(e.clientY - dragOffsetY, e.clientX - dragOffsetX);
});
const mouseUpListener = addDisposableGenericMouseUpListener(activeWindow, (e) => {
if (isMovingQuickInput) {
// Save position
const state = this.dndViewState.get();
this.dndViewState.set({ top: state?.top, left: state?.left, done: true }, undefined);
}
// Dispose listeners
mouseMoveListener.dispose();
mouseUpListener.dispose();
});
}));
}
_layout(topCoordinate, leftCoordinate) {
const snapCoordinateYTop = this._getTopSnapValue();
const snapCoordinateY = this._getCenterYSnapValue();
const snapCoordinateX = this._getCenterXSnapValue();
// Make sure the quick input is not moved outside the container
topCoordinate = Math.max(0, Math.min(topCoordinate, this._container.clientHeight - this._quickInputContainer.clientHeight));
if (topCoordinate < this._layoutService.activeContainerOffset.top) {
if (this._controlsOnLeft) {
leftCoordinate = Math.max(leftCoordinate, 80 / getZoomFactor(getActiveWindow()));
}
else if (this._controlsOnRight) {
leftCoordinate = Math.min(leftCoordinate, this._container.clientWidth - this._quickInputContainer.clientWidth - (140 / getZoomFactor(getActiveWindow())));
}
}
const snappingToTop = Math.abs(topCoordinate - snapCoordinateYTop) < this._snapThreshold;
topCoordinate = snappingToTop ? snapCoordinateYTop : topCoordinate;
const snappingToCenter = Math.abs(topCoordinate - snapCoordinateY) < this._snapThreshold;
topCoordinate = snappingToCenter ? snapCoordinateY : topCoordinate;
const top = topCoordinate / this._container.clientHeight;
// Make sure the quick input is not moved outside the container
leftCoordinate = Math.max(0, Math.min(leftCoordinate, this._container.clientWidth - this._quickInputContainer.clientWidth));
const snappingToCenterX = Math.abs(leftCoordinate - snapCoordinateX) < this._snapThreshold;
leftCoordinate = snappingToCenterX ? snapCoordinateX : leftCoordinate;
const b = this._container.clientWidth;
const c = this._quickInputContainer.clientWidth;
const d = leftCoordinate;
const left = (d + c / 2) / b;
this.dndViewState.set({ top, left, done: false }, undefined);
if (snappingToCenterX) {
if (snappingToTop) {
this._quickInputAlignmentContext.set('top');
return;
}
else if (snappingToCenter) {
this._quickInputAlignmentContext.set('center');
return;
}
}
this._quickInputAlignmentContext.set(undefined);
}
_getTopSnapValue() {
return this._layoutService.activeContainerOffset.quickPickTop;
}
_getCenterYSnapValue() {
return Math.round(this._container.clientHeight * this._snapLineHorizontalRatio);
}
_getCenterXSnapValue() {
return Math.round(this._container.clientWidth / 2) - Math.round(this._quickInputContainer.clientWidth / 2);
}
};
QuickInputDragAndDropController = __decorate([
__param(4, ILayoutService),
__param(5, IContextKeyService),
__param(6, IConfigurationService)
], QuickInputDragAndDropController);
export { QuickInputController };
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,197 @@
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 { getListStyles, defaultKeybindingLabelStyles, defaultProgressBarStyles, defaultButtonStyles, defaultCountBadgeStyles, defaultToggleStyles, defaultInputBoxStyles } from '../../theme/browser/defaultStyles.js';
import { asCssVariable } from '../../theme/common/colorUtils.js';
import { activeContrastBorder } from '../../theme/common/colors/baseColors.js';
import '../../theme/common/colors/chartsColors.js';
import { widgetShadow, widgetBorder } from '../../theme/common/colors/editorColors.js';
import '../../theme/common/colors/inputColors.js';
import '../../theme/common/colors/listColors.js';
import '../../theme/common/colors/menuColors.js';
import '../../theme/common/colors/minimapColors.js';
import '../../theme/common/colors/miscColors.js';
import { pickerGroupForeground, pickerGroupBorder, quickInputBackground, quickInputListFocusBackground, quickInputListFocusIconForeground, quickInputListFocusForeground, quickInputTitleBackground, quickInputForeground } from '../../theme/common/colors/quickpickColors.js';
import '../../theme/common/colors/searchColors.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';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
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);
}
input(options = {}, token = CancellationToken.None) {
return this.controller.input(options, token);
}
createQuickPick(options = { useSeparators: false }) {
return this.controller.createQuickPick(options);
}
createInputBox() {
return this.controller.createInputBox();
}
toggleHover() {
if (this.hasController) {
this.controller.toggleHover();
}
}
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,
treeStickyScrollBackground: quickInputBackground,
}),
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 };
@@ -0,0 +1,89 @@
import { reset, $, EventType, isEventLike, EventHelper } from '../../../base/browser/dom.js';
import { createCSSRule } from '../../../base/browser/domStylesheets.js';
import { asCSSUrl } from '../../../base/browser/cssValue.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 EventType$1 } 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';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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();
createCSSRule(`.${iconClass}, .hc-light .${iconClass}`, `background-image: ${asCSSUrl(iconPath.light || iconPath.dark)}`);
createCSSRule(`.vs-dark .${iconClass}, .hc-black .${iconClass}`, `background-image: ${asCSSUrl(iconPath.dark)}`);
iconPathToClass[key] = iconClass;
}
return iconClass;
}
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
};
}
function renderQuickInputDescription(description, container, actionHandler) {
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(1771, "Click to execute command '{0}'", node.href.substring('command:'.length));
}
else if (!title) {
title = node.href;
}
const anchor = $('a', { href: node.href, title, tabIndex: tabIndex++ }, node.label);
anchor.style.textDecoration = 'underline';
const handleOpen = (e) => {
if (isEventLike(e)) {
EventHelper.stop(e, true);
}
actionHandler.callback(node.href);
};
const onClick = actionHandler.disposables.add(new DomEmitter(anchor, EventType.CLICK)).event;
const onKeydown = actionHandler.disposables.add(new DomEmitter(anchor, 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, EventType$1.Tap)).event;
Event.any(onClick, onTap, onSpaceOrEnter)(handleOpen, null, actionHandler.disposables);
container.appendChild(anchor);
}
}
}
export { quickInputButtonToAction, renderQuickInputDescription };
@@ -0,0 +1,19 @@
import { QuickInputTreeRenderer } from './quickInputTreeRenderer.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Delegate for QuickInputTree that provides height and template information.
*/
class QuickInputTreeDelegate {
getHeight(_element) {
return 22;
}
getTemplateId(_element) {
return QuickInputTreeRenderer.ID;
}
}
export { QuickInputTreeDelegate };
@@ -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.
*--------------------------------------------------------------------------------------------*/
function getParentNodeState(parentChildren) {
let containsChecks = false;
let containsUnchecks = false;
let containsMixed = false;
for (const element of parentChildren) {
switch (element.element?.checked) {
case 'mixed':
containsMixed = true;
break;
case true:
containsChecks = true;
break;
default:
containsUnchecks = true;
break;
}
if (containsChecks && containsUnchecks && containsMixed) {
break;
}
}
const newState = containsUnchecks
? containsMixed
? 'mixed'
: containsChecks
? 'mixed'
: false
: containsMixed
? 'mixed'
: containsChecks;
return newState;
}
export { getParentNodeState };
@@ -0,0 +1,39 @@
import { Event } from '../../../../base/common/event.js';
import { getCodiconAriaLabel } from '../../../../base/common/iconLabels.js';
import { localize } from '../../../../nls.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Accessibility provider for QuickTree.
*/
class QuickTreeAccessibilityProvider {
constructor(onCheckedEvent) {
this.onCheckedEvent = onCheckedEvent;
}
getWidgetAriaLabel() {
return localize(1772, "Quick Tree");
}
getAriaLabel(element) {
return element.ariaLabel || [element.label, element.description]
.map(s => getCodiconAriaLabel(s))
.filter(s => !!s)
.join(', ');
}
getWidgetRole() {
return 'tree';
}
getRole(_element) {
return 'checkbox';
}
isChecked(element) {
return {
get value() { return element.checked === 'mixed' ? 'mixed' : !!element.checked; },
onDidChange: e => Event.filter(this.onCheckedEvent, e => e.item === element)(_ => e()),
};
}
}
export { QuickTreeAccessibilityProvider };
@@ -0,0 +1,181 @@
import { append, $ as $$1 } from '../../../../base/browser/dom.js';
import { RenderIndentGuides } from '../../../../base/browser/ui/tree/abstractTree.js';
import { Emitter } from '../../../../base/common/event.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { IInstantiationService } from '../../../instantiation/common/instantiation.js';
import { WorkbenchObjectTree } from '../../../list/browser/listService.js';
import { QuickInputTreeDelegate } from './quickInputDelegate.js';
import { getParentNodeState } from './quickInputTree.js';
import { QuickTreeAccessibilityProvider } from './quickInputTreeAccessibilityProvider.js';
import { QuickInputTreeFilter } from './quickInputTreeFilter.js';
import { QuickInputTreeRenderer } from './quickInputTreeRenderer.js';
import { QuickInputTreeSorter } from './quickInputTreeSorter.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
const $ = $$1;
let QuickInputTreeController = class QuickInputTreeController extends Disposable {
constructor(container, hoverDelegate, instantiationService) {
super();
this.instantiationService = instantiationService;
this._onDidTriggerButton = this._register(new Emitter());
this._onDidChangeCheckboxState = this._register(new Emitter());
this.onDidChangeCheckboxState = this._onDidChangeCheckboxState.event;
this._onDidCheckedLeafItemsChange = this._register(new Emitter);
this._onLeave = new Emitter();
/**
* Event that is fired when the tree would no longer have focus.
*/
this.onLeave = this._onLeave.event;
this._onDidAccept = this._register(new Emitter());
/**
* Event that is fired when a non-pickable item is clicked, indicating acceptance.
*/
this.onDidAccept = this._onDidAccept.event;
this._container = append(container, $('.quick-input-tree'));
this._renderer = this._register(this.instantiationService.createInstance(QuickInputTreeRenderer, hoverDelegate, this._onDidTriggerButton, this.onDidChangeCheckboxState));
this._filter = this.instantiationService.createInstance(QuickInputTreeFilter);
this._sorter = this._register(new QuickInputTreeSorter());
this._tree = this._register(this.instantiationService.createInstance((WorkbenchObjectTree), 'QuickInputTree', this._container, new QuickInputTreeDelegate(), [this._renderer], {
accessibilityProvider: new QuickTreeAccessibilityProvider(this.onDidChangeCheckboxState),
horizontalScrolling: false,
multipleSelectionSupport: false,
findWidgetEnabled: false,
alwaysConsumeMouseWheel: true,
hideTwistiesOfChildlessElements: true,
renderIndentGuides: RenderIndentGuides.None,
expandOnDoubleClick: true,
expandOnlyOnTwistieClick: true,
disableExpandOnSpacebar: true,
sorter: this._sorter,
filter: this._filter
}));
this.registerOnOpenListener();
}
get tree() {
return this._tree;
}
get displayed() {
return this._container.style.display !== 'none';
}
set displayed(value) {
this._container.style.display = value ? '' : 'none';
}
get sortByLabel() {
return this._sorter.sortByLabel;
}
set sortByLabel(value) {
this._sorter.sortByLabel = value;
this._tree.resort(null, true);
}
getActiveDescendant() {
return this._tree.getHTMLElement().getAttribute('aria-activedescendant');
}
updateFilterOptions(options) {
if (options.matchOnLabel !== undefined) {
this._filter.matchOnLabel = options.matchOnLabel;
}
if (options.matchOnDescription !== undefined) {
this._filter.matchOnDescription = options.matchOnDescription;
}
this._tree.refilter();
}
layout(maxHeight) {
this._tree.getHTMLElement().style.maxHeight = maxHeight ? `${
// Make sure height aligns with list item heights
Math.floor(maxHeight / 44) * 44
// Add some extra height so that it's clear there's more to scroll
+ 6}px` : '';
this._tree.layout();
}
registerOnOpenListener() {
this._register(this._tree.onDidOpen(e => {
const item = e.element;
if (!item) {
return;
}
if (item.disabled) {
return;
}
// Check if the item is pickable (defaults to true if not specified)
if (item.pickable === false) {
// For non-pickable items, set it as the active item and fire the accept event
this._tree.setFocus([item]);
this._onDidAccept.fire();
return;
}
const newState = item.checked !== true;
if ((item.checked ?? false) === newState) {
return; // No change
}
// Handle checked item
item.checked = newState;
this._tree.rerender(item);
// Handle children of the checked item
const updateSet = new Set();
const toUpdate = [...this._tree.getNode(item).children];
while (toUpdate.length) {
const pop = toUpdate.shift();
if (pop?.element && !updateSet.has(pop.element)) {
updateSet.add(pop.element);
if ((pop.element.checked ?? false) !== item.checked) {
pop.element.checked = item.checked;
this._tree.rerender(pop.element);
}
toUpdate.push(...pop.children);
}
}
// Handle parents of the checked item
let parent = this._tree.getParentElement(item);
while (parent) {
const parentChildren = [...this._tree.getNode(parent).children];
const newState = getParentNodeState(parentChildren);
if ((parent.checked ?? false) !== newState) {
parent.checked = newState;
this._tree.rerender(parent);
}
parent = this._tree.getParentElement(parent);
}
this._onDidChangeCheckboxState.fire({
item,
checked: item.checked ?? false
});
this._onDidCheckedLeafItemsChange.fire(this.getCheckedLeafItems());
}));
}
getCheckedLeafItems() {
const lookedAt = new Set();
const toLookAt = [...this._tree.getNode().children];
const checkedItems = new Array();
while (toLookAt.length) {
const lookAt = toLookAt.shift();
if (!lookAt?.element || lookedAt.has(lookAt.element)) {
continue;
}
if (lookAt.element.checked) {
lookedAt.add(lookAt.element);
toLookAt.push(...lookAt.children);
if (!lookAt.element.children) {
checkedItems.push(lookAt.element);
}
}
}
return checkedItems;
}
};
QuickInputTreeController = __decorate([
__param(2, IInstantiationService)
], QuickInputTreeController);
export { QuickInputTreeController };
@@ -0,0 +1,42 @@
import { matchesFuzzyIconAware, parseLabelWithIcons } from '../../../../base/common/iconLabels.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
class QuickInputTreeFilter {
constructor() {
this.filterValue = '';
this.matchOnLabel = true;
this.matchOnDescription = false;
}
filter(element, parentVisibility) {
if (!this.filterValue || !(this.matchOnLabel || this.matchOnDescription)) {
return element.children
? { visibility: 2 /* TreeVisibility.Recurse */, data: {} }
: { visibility: 1 /* TreeVisibility.Visible */, data: {} };
}
const labelHighlights = this.matchOnLabel ? matchesFuzzyIconAware(this.filterValue, parseLabelWithIcons(element.label)) ?? undefined : undefined;
const descriptionHighlights = this.matchOnDescription ? matchesFuzzyIconAware(this.filterValue, parseLabelWithIcons(element.description || '')) ?? undefined : undefined;
const visibility = parentVisibility === 1 /* TreeVisibility.Visible */
// Parent is visible because it had matches, so we show all children
? 1 /* TreeVisibility.Visible */
// This would only happen on Parent is recurse so...
: (labelHighlights || descriptionHighlights)
// If we have any highlights, we are visible
? 1 /* TreeVisibility.Visible */
// Otherwise, we defer to the children or if no children, we are hidden
: element.children
? 2 /* TreeVisibility.Recurse */
: 0 /* TreeVisibility.Hidden */;
return {
visibility,
data: {
labelHighlights,
descriptionHighlights
}
};
}
}
export { QuickInputTreeFilter };
@@ -0,0 +1,141 @@
import { asCSSUrl } from '../../../../base/browser/cssValue.js';
import { append, $ as $$1, prepend } from '../../../../base/browser/dom.js';
import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';
import { IconLabel } from '../../../../base/browser/ui/iconLabel/iconLabel.js';
import { TriStateCheckbox } from '../../../../base/browser/ui/toggle/toggle.js';
import { Event } from '../../../../base/common/event.js';
import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
import { URI } from '../../../../base/common/uri.js';
import { defaultCheckboxStyles } from '../../../theme/browser/defaultStyles.js';
import { isDark } from '../../../theme/common/theme.js';
import { escape } from '../../../../base/common/strings.js';
import { IThemeService } from '../../../theme/common/themeService.js';
import { quickInputButtonToAction } from '../quickInputUtils.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var QuickInputTreeRenderer_1;
const $ = $$1;
let QuickInputTreeRenderer = class QuickInputTreeRenderer extends Disposable {
static { QuickInputTreeRenderer_1 = this; }
static { this.ID = 'quickInputTreeElement'; }
constructor(_hoverDelegate, _buttonTriggeredEmitter, onCheckedEvent, _themeService) {
super();
this._hoverDelegate = _hoverDelegate;
this._buttonTriggeredEmitter = _buttonTriggeredEmitter;
this.onCheckedEvent = onCheckedEvent;
this._themeService = _themeService;
this.templateId = QuickInputTreeRenderer_1.ID;
}
renderTemplate(container) {
const store = new DisposableStore();
// Main entry container
const entry = append(container, $('.quick-input-tree-entry'));
const checkbox = store.add(new TriStateCheckbox('', false, { ...defaultCheckboxStyles, size: 15 }));
entry.appendChild(checkbox.domNode);
const checkboxLabel = append(entry, $('label.quick-input-tree-label'));
const rows = append(checkboxLabel, $('.quick-input-tree-rows'));
const row1 = append(rows, $('.quick-input-tree-row'));
const icon = prepend(row1, $('.quick-input-tree-icon'));
const label = store.add(new IconLabel(row1, {
supportHighlights: true,
supportDescriptionHighlights: true,
supportIcons: true,
hoverDelegate: this._hoverDelegate
}));
const actionBar = store.add(new ActionBar(entry, this._hoverDelegate ? { hoverDelegate: this._hoverDelegate } : undefined));
actionBar.domNode.classList.add('quick-input-tree-entry-action-bar');
return {
toDisposeTemplate: store,
entry,
checkbox,
icon,
label,
actionBar,
toDisposeElement: new DisposableStore(),
};
}
renderElement(node, index, templateData, _details) {
const store = templateData.toDisposeElement;
const quickTreeItem = node.element;
// Checkbox
if (quickTreeItem.pickable === false) {
// Hide checkbox for non-pickable items
templateData.checkbox.domNode.style.display = 'none';
}
else {
templateData.checkbox.domNode.style.display = '';
templateData.checkbox.checked = quickTreeItem.checked ?? false;
store.add(Event.filter(this.onCheckedEvent, e => e.item === quickTreeItem)(e => templateData.checkbox.checked = e.checked));
if (quickTreeItem.disabled) {
templateData.checkbox.disable();
}
}
// Icon
if (quickTreeItem.iconPath) {
const icon = isDark(this._themeService.getColorTheme().type) ? quickTreeItem.iconPath.dark : (quickTreeItem.iconPath.light ?? quickTreeItem.iconPath.dark);
const iconUrl = URI.revive(icon);
templateData.icon.className = 'quick-input-tree-icon';
templateData.icon.style.backgroundImage = asCSSUrl(iconUrl);
}
else {
templateData.icon.style.backgroundImage = '';
templateData.icon.className = quickTreeItem.iconClass ? `quick-input-tree-icon ${quickTreeItem.iconClass}` : '';
}
const { labelHighlights: matches, descriptionHighlights: descriptionMatches } = node.filterData || {};
// Label and Description
let descriptionTitle;
// NOTE: If we bring back quick tool tips, we need to check that here like we do in the QuickInputListRenderer
if (quickTreeItem.description) {
descriptionTitle = {
markdown: {
value: escape(quickTreeItem.description),
supportThemeIcons: true
},
markdownNotSupportedFallback: quickTreeItem.description
};
}
templateData.label.setLabel(quickTreeItem.label, quickTreeItem.description, {
matches,
descriptionMatches,
extraClasses: quickTreeItem.iconClasses,
italic: quickTreeItem.italic,
strikethrough: quickTreeItem.strikethrough,
labelEscapeNewLines: true,
descriptionTitle
});
// Action Bar
const buttons = quickTreeItem.buttons;
if (buttons && buttons.length) {
templateData.actionBar.push(buttons.map((button, index) => quickInputButtonToAction(button, `tree-${index}`, () => this._buttonTriggeredEmitter.fire({ item: quickTreeItem, button }))), { icon: true, label: false });
templateData.entry.classList.add('has-actions');
}
else {
templateData.entry.classList.remove('has-actions');
}
}
disposeElement(_element, _index, templateData, _details) {
templateData.toDisposeElement.clear();
templateData.actionBar.clear();
}
disposeTemplate(templateData) {
templateData.toDisposeElement.dispose();
templateData.toDisposeTemplate.dispose();
}
};
QuickInputTreeRenderer = QuickInputTreeRenderer_1 = __decorate([
__param(3, IThemeService)
], QuickInputTreeRenderer);
export { QuickInputTreeRenderer };
@@ -0,0 +1,48 @@
import { Disposable } from '../../../../base/common/lifecycle.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
class QuickInputTreeSorter extends Disposable {
constructor() {
super(...arguments);
this._sortByLabel = true;
}
get sortByLabel() {
return this._sortByLabel;
}
set sortByLabel(value) {
this._sortByLabel = value;
}
compare(a, b) {
// No-op
if (!this._sortByLabel) {
return 0;
}
if (a.label < b.label) {
return -1;
}
else if (a.label > b.label) {
return 1;
}
// use description to break ties
if (a.description && b.description) {
if (a.description < b.description) {
return -1;
}
else if (a.description > b.description) {
return 1;
}
}
else if (a.description) {
return -1;
}
else if (b.description) {
return 1;
}
return 0;
}
}
export { QuickInputTreeSorter };
@@ -0,0 +1,56 @@
import { coalesce } from '../../../base/common/arrays.js';
import { toDisposable } from '../../../base/common/lifecycle.js';
import { Registry } from '../../registry/common/platform.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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 = {}));
const Extensions = {
Quickaccess: 'workbench.contributions.quickaccess'
};
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());
export { DefaultQuickAccessFilterValue, Extensions, QuickAccessRegistry };
@@ -0,0 +1,108 @@
import { createDecorator } from '../../instantiation/common/instantiation.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const NO_KEY_MODS = { ctrlCmd: false, alt: false };
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).
*/
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.
*/
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 = {}));
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";
/**
* At the far end inside the input box.
* Used by the public API to create toggles.
*/
QuickInputButtonLocation[QuickInputButtonLocation["Input"] = 3] = "Input";
})(QuickInputButtonLocation || (QuickInputButtonLocation = {}));
//#endregion
const IQuickInputService = createDecorator('quickInputService');
//#endregion
export { IQuickInputService, ItemActivation, NO_KEY_MODS, QuickInputButtonLocation, QuickInputHideReason, QuickPickFocus };
@@ -0,0 +1,32 @@
import { ok } from '../../../base/common/assert.js';
import { isFunction, isString, isObject } from '../../../base/common/types.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
class RegistryImpl {
constructor() {
this.data = new Map();
}
add(id, data) {
ok(isString(id));
ok(isObject(data));
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;
}
dispose() {
this.data.forEach((value) => {
if (isFunction(value.dispose)) {
value.dispose();
}
});
this.data.clear();
}
}
const Registry = new RegistryImpl();
export { Registry };
@@ -0,0 +1,236 @@
import { RunOnceScheduler, runWhenGlobalIdle, Promises } from '../../../base/common/async.js';
import { PauseableEmitter, Emitter, Event } from '../../../base/common/event.js';
import { Disposable, MutableDisposable } from '../../../base/common/lifecycle.js';
import { isUndefinedOrNull } from '../../../base/common/types.js';
import { Storage, InMemoryStorageDatabase, StorageHint } from '../../../base/parts/storage/common/storage.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const TARGET_KEY = '__$__targetStorageMarker';
const IStorageService = createDecorator('storageService');
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 = {}));
function loadKeyTargets(storage) {
const keysRaw = storage.get(TARGET_KEY);
if (keysRaw) {
try {
return JSON.parse(keysRaw);
}
catch (error) {
// Fail gracefully
}
}
return Object.create(null);
}
class AbstractStorageService extends Disposable {
static { this.DEFAULT_FLUSH_INTERVAL = 60 * 1000; } // every minute
constructor(options = { flushInterval: AbstractStorageService.DEFAULT_FLUSH_INTERVAL }) {
super();
this._onDidChangeValue = this._register(new PauseableEmitter());
this._onDidChangeTarget = this._register(new PauseableEmitter());
this._onWillSaveState = this._register(new Emitter());
this.onWillSaveState = this._onWillSaveState.event;
this.runFlushWhenIdle = this._register(new MutableDisposable());
this._workspaceKeyTargets = undefined;
this._profileKeyTargets = undefined;
this._applicationKeyTargets = undefined;
this.flushWhenIdleScheduler = this._register(new RunOnceScheduler(() => this.doFlushWhenIdle(), options.flushInterval));
}
onDidChangeValue(scope, key, disposable) {
return Event.filter(this._onDidChangeValue.event, e => e.scope === scope && (key === undefined || e.key === key), disposable);
}
doFlushWhenIdle() {
this.runFlushWhenIdle.value = runWhenGlobalIdle(() => {
if (this.shouldFlushWhenIdle()) {
this.flush();
}
// repeat
this.flushWhenIdleScheduler.schedule();
});
}
shouldFlushWhenIdle() {
return true;
}
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);
}
async flush(reason = WillSaveStateReason.NONE) {
// Signal event to collect changes
this._onWillSaveState.fire({ reason });
const applicationStorage = this.getStorage(-1 /* StorageScope.APPLICATION */);
const profileStorage = this.getStorage(0 /* StorageScope.PROFILE */);
const workspaceStorage = this.getStorage(1 /* StorageScope.WORKSPACE */);
switch (reason) {
// Unspecific reason: just wait when data is flushed
case WillSaveStateReason.NONE:
await Promises.settled([
applicationStorage?.whenFlushed() ?? Promise.resolve(),
profileStorage?.whenFlushed() ?? Promise.resolve(),
workspaceStorage?.whenFlushed() ?? Promise.resolve()
]);
break;
// Shutdown: we want to flush as soon as possible
// and not hit any delays that might be there
case WillSaveStateReason.SHUTDOWN:
await Promises.settled([
applicationStorage?.flush(0) ?? Promise.resolve(),
profileStorage?.flush(0) ?? Promise.resolve(),
workspaceStorage?.flush(0) ?? Promise.resolve()
]);
break;
}
}
}
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;
}
}
shouldFlushWhenIdle() {
return false;
}
}
export { AbstractStorageService, IStorageService, InMemoryStorageService, TARGET_KEY, WillSaveStateReason, loadKeyTargets };

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