Version 1.0
This commit is contained in:
Generated
Vendored
+111
@@ -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 };
|
||||
Generated
Vendored
+25
@@ -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 };
|
||||
Generated
Vendored
+11
@@ -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 };
|
||||
Generated
Vendored
+331
@@ -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 };
|
||||
+15
@@ -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 };
|
||||
Generated
Vendored
+16
@@ -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 };
|
||||
Generated
Vendored
+344
@@ -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 };
|
||||
Generated
Vendored
+197
@@ -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;
|
||||
}
|
||||
Generated
Vendored
+236
@@ -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 };
|
||||
Generated
Vendored
+29
@@ -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 };
|
||||
Generated
Vendored
+63
@@ -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;
|
||||
}
|
||||
Generated
Vendored
+519
@@ -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 };
|
||||
+280
@@ -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 };
|
||||
+483
@@ -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 };
|
||||
Generated
Vendored
+436
@@ -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 };
|
||||
Generated
Vendored
+215
@@ -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 };
|
||||
Generated
Vendored
+9
@@ -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 };
|
||||
+83
@@ -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 };
|
||||
Generated
Vendored
+92
@@ -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 };
|
||||
Generated
Vendored
+614
@@ -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 };
|
||||
Generated
Vendored
+400
@@ -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 };
|
||||
Generated
Vendored
+46
@@ -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 };
|
||||
Generated
Vendored
+468
@@ -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 };
|
||||
Generated
Vendored
+1551
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+21
@@ -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 };
|
||||
+287
@@ -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 */: //  
|
||||
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 };
|
||||
Generated
Vendored
+126
@@ -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 };
|
||||
Generated
Vendored
+103
@@ -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 };
|
||||
Generated
Vendored
+10
@@ -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 };
|
||||
Generated
Vendored
+73
@@ -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 };
|
||||
Generated
Vendored
+60
@@ -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 };
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
import { createDecorator } from '../../instantiation/common/instantiation.js';
|
||||
|
||||
const IDataChannelService = createDecorator('dataChannelService');
|
||||
class NullDataChannelService {
|
||||
getDataChannel(_channelId) {
|
||||
return {
|
||||
sendData: () => { },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export { IDataChannelService, NullDataChannelService };
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { createDecorator } from '../../instantiation/common/instantiation.js';
|
||||
|
||||
const IDialogService = createDecorator('dialogService');
|
||||
|
||||
export { IDialogService };
|
||||
+49
@@ -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 };
|
||||
+15
@@ -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 };
|
||||
Generated
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
import { createDecorator } from '../../instantiation/common/instantiation.js';
|
||||
|
||||
const IEnvironmentService = createDecorator('environmentService');
|
||||
|
||||
export { IEnvironmentService };
|
||||
Generated
Vendored
+49
@@ -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 };
|
||||
+19
@@ -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 };
|
||||
Generated
Vendored
+106
@@ -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 };
|
||||
Generated
Vendored
+9
@@ -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 };
|
||||
+143
@@ -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;
|
||||
}
|
||||
+103
@@ -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 };
|
||||
Generated
Vendored
+542
@@ -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 };
|
||||
+605
@@ -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 };
|
||||
Generated
Vendored
+107
@@ -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 };
|
||||
Generated
Vendored
+13
@@ -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 };
|
||||
Generated
Vendored
+18
@@ -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 };
|
||||
Generated
Vendored
+91
@@ -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 };
|
||||
Generated
Vendored
+44
@@ -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 };
|
||||
Generated
Vendored
+403
@@ -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 };
|
||||
Generated
Vendored
+22
@@ -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 };
|
||||
Generated
Vendored
+42
@@ -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 };
|
||||
Generated
Vendored
+286
@@ -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 };
|
||||
Generated
Vendored
+56
@@ -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 };
|
||||
Generated
Vendored
+9
@@ -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 };
|
||||
Generated
Vendored
+304
@@ -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 };
|
||||
Generated
Vendored
+111
@@ -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 };
|
||||
Generated
Vendored
+35
@@ -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 };
|
||||
Generated
Vendored
+175
@@ -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 };
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { createDecorator } from '../../instantiation/common/instantiation.js';
|
||||
|
||||
const ILabelService = createDecorator('labelService');
|
||||
|
||||
export { ILabelService };
|
||||
Generated
Vendored
+9
@@ -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 };
|
||||
+1025
File diff suppressed because it is too large
Load Diff
+271
@@ -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 };
|
||||
+40
@@ -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 };
|
||||
Generated
Vendored
+73
@@ -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 };
|
||||
Generated
Vendored
+330
@@ -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 };
|
||||
+127
@@ -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 };
|
||||
Generated
Vendored
+9
@@ -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 };
|
||||
Generated
Vendored
+31
@@ -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 };
|
||||
Generated
Vendored
+41
@@ -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 };
|
||||
Generated
Vendored
+35
@@ -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 };
|
||||
+13
@@ -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);
|
||||
}
|
||||
|
||||
+95
@@ -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 };
|
||||
+43
@@ -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 };
|
||||
+70
@@ -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 };
|
||||
Generated
Vendored
+9
@@ -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 };
|
||||
+16
@@ -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 };
|
||||
Generated
Vendored
+388
@@ -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 };
|
||||
Generated
Vendored
+77
@@ -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 };
|
||||
Generated
Vendored
+486
@@ -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;
|
||||
}
|
||||
Generated
Vendored
+271
@@ -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 };
|
||||
Generated
Vendored
+209
@@ -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 };
|
||||
Generated
Vendored
+1026
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+189
@@ -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
|
||||
Generated
Vendored
+104
@@ -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 };
|
||||
Generated
Vendored
+901
@@ -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 };
|
||||
Generated
Vendored
+1391
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+197
@@ -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 };
|
||||
Generated
Vendored
+89
@@ -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 };
|
||||
Generated
Vendored
+19
@@ -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 };
|
||||
Generated
Vendored
+37
@@ -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 };
|
||||
Generated
Vendored
+39
@@ -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 };
|
||||
Generated
Vendored
+181
@@ -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 };
|
||||
Generated
Vendored
+42
@@ -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 };
|
||||
Generated
Vendored
+141
@@ -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 };
|
||||
Generated
Vendored
+48
@@ -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 };
|
||||
Generated
Vendored
+56
@@ -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 };
|
||||
Generated
Vendored
+108
@@ -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 };
|
||||
+32
@@ -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 };
|
||||
+236
@@ -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
Reference in New Issue
Block a user