Path: blob/main/src/vs/workbench/browser/actions/developerActions.ts
5238 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import './media/actions.css';67import { localize, localize2 } from '../../../nls.js';8import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js';9import { DomEmitter } from '../../../base/browser/event.js';10import { Color } from '../../../base/common/color.js';11import { Emitter, Event } from '../../../base/common/event.js';12import { IDisposable, toDisposable, dispose, DisposableStore, setDisposableTracker, DisposableTracker, DisposableInfo } from '../../../base/common/lifecycle.js';13import { getDomNodePagePosition, append, $, getActiveDocument, onDidRegisterWindow, getWindows } from '../../../base/browser/dom.js';14import { createCSSRule, createStyleSheet } from '../../../base/browser/domStylesheets.js';15import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';16import { ContextKeyExpr, IContextKeyService, RawContextKey } from '../../../platform/contextkey/common/contextkey.js';17import { Context } from '../../../platform/contextkey/browser/contextKeyService.js';18import { StandardKeyboardEvent } from '../../../base/browser/keyboardEvent.js';19import { RunOnceScheduler } from '../../../base/common/async.js';20import { ILayoutService } from '../../../platform/layout/browser/layoutService.js';21import { Registry } from '../../../platform/registry/common/platform.js';22import { registerAction2, Action2, MenuRegistry } from '../../../platform/actions/common/actions.js';23import { IStorageService, StorageScope, StorageTarget } from '../../../platform/storage/common/storage.js';24import { clamp } from '../../../base/common/numbers.js';25import { KeyCode } from '../../../base/common/keyCodes.js';26import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from '../../../platform/configuration/common/configurationRegistry.js';27import { ILogService } from '../../../platform/log/common/log.js';28import { IWorkingCopyService } from '../../services/workingCopy/common/workingCopyService.js';29import { ServicesAccessor } from '../../../platform/instantiation/common/instantiation.js';30import { Categories } from '../../../platform/action/common/actionCommonCategories.js';31import { IWorkingCopyBackupService } from '../../services/workingCopy/common/workingCopyBackup.js';32import { ResolutionResult, ResultKind } from '../../../platform/keybinding/common/keybindingResolver.js';33import { IDialogService } from '../../../platform/dialogs/common/dialogs.js';34import { IOutputService } from '../../services/output/common/output.js';35import { windowLogId } from '../../services/log/common/logConstants.js';36import { ByteSize } from '../../../platform/files/common/files.js';37import { IQuickInputService, IQuickPickItem } from '../../../platform/quickinput/common/quickInput.js';38import { IUserDataProfileService } from '../../services/userDataProfile/common/userDataProfile.js';39import { IEditorService } from '../../services/editor/common/editorService.js';40import product from '../../../platform/product/common/product.js';41import { CommandsRegistry } from '../../../platform/commands/common/commands.js';42import { IEnvironmentService } from '../../../platform/environment/common/environment.js';43import { IProductService } from '../../../platform/product/common/productService.js';44import { IDefaultAccountService } from '../../../platform/defaultAccount/common/defaultAccount.js';45import { IAuthenticationService } from '../../services/authentication/common/authentication.js';46import { IAuthenticationAccessService } from '../../services/authentication/browser/authenticationAccessService.js';47import { IPolicyService } from '../../../platform/policy/common/policy.js';4849class InspectContextKeysAction extends Action2 {5051constructor() {52super({53id: 'workbench.action.inspectContextKeys',54title: localize2('inspect context keys', 'Inspect Context Keys'),55category: Categories.Developer,56f1: true57});58}5960run(accessor: ServicesAccessor): void {61const contextKeyService = accessor.get(IContextKeyService);6263const disposables = new DisposableStore();6465const stylesheet = createStyleSheet(undefined, undefined, disposables);66createCSSRule('*', 'cursor: crosshair !important;', stylesheet);6768const hoverFeedback = document.createElement('div');69const activeDocument = getActiveDocument();70activeDocument.body.appendChild(hoverFeedback);71disposables.add(toDisposable(() => hoverFeedback.remove()));7273hoverFeedback.style.position = 'absolute';74hoverFeedback.style.pointerEvents = 'none';75hoverFeedback.style.backgroundColor = 'rgba(255, 0, 0, 0.5)';76hoverFeedback.style.zIndex = '1000';7778const onMouseMove = disposables.add(new DomEmitter(activeDocument, 'mousemove', true));79disposables.add(onMouseMove.event(e => {80const target = e.target as HTMLElement;81const position = getDomNodePagePosition(target);8283hoverFeedback.style.top = `${position.top}px`;84hoverFeedback.style.left = `${position.left}px`;85hoverFeedback.style.width = `${position.width}px`;86hoverFeedback.style.height = `${position.height}px`;87}));8889const onMouseDown = disposables.add(new DomEmitter(activeDocument, 'mousedown', true));90Event.once(onMouseDown.event)(e => { e.preventDefault(); e.stopPropagation(); }, null, disposables);9192const onMouseUp = disposables.add(new DomEmitter(activeDocument, 'mouseup', true));93Event.once(onMouseUp.event)(e => {94e.preventDefault();95e.stopPropagation();9697const context = contextKeyService.getContext(e.target as HTMLElement) as Context;98console.log(context.collectAllValues());99100dispose(disposables);101}, null, disposables);102}103}104105interface IScreencastKeyboardOptions {106readonly showKeys?: boolean;107readonly showKeybindings?: boolean;108readonly showCommands?: boolean;109readonly showCommandGroups?: boolean;110readonly showSingleEditorCursorMoves?: boolean;111}112113class ToggleScreencastModeAction extends Action2 {114115static disposable: IDisposable | undefined;116117constructor() {118super({119id: 'workbench.action.toggleScreencastMode',120title: localize2('toggle screencast mode', 'Toggle Screencast Mode'),121category: Categories.Developer,122f1: true123});124}125126run(accessor: ServicesAccessor): void {127if (ToggleScreencastModeAction.disposable) {128ToggleScreencastModeAction.disposable.dispose();129ToggleScreencastModeAction.disposable = undefined;130return;131}132133const layoutService = accessor.get(ILayoutService);134const configurationService = accessor.get(IConfigurationService);135const keybindingService = accessor.get(IKeybindingService);136137const disposables = new DisposableStore();138139const container = layoutService.activeContainer;140141const mouseMarker = append(container, $('.screencast-mouse'));142disposables.add(toDisposable(() => mouseMarker.remove()));143144const keyboardMarker = append(container, $('.screencast-keyboard'));145disposables.add(toDisposable(() => keyboardMarker.remove()));146147const onMouseDown = disposables.add(new Emitter<MouseEvent>());148const onMouseUp = disposables.add(new Emitter<MouseEvent>());149const onMouseMove = disposables.add(new Emitter<MouseEvent>());150151function registerContainerListeners(container: HTMLElement, windowDisposables: DisposableStore): void {152const listeners = new DisposableStore();153154listeners.add(listeners.add(new DomEmitter(container, 'mousedown', true)).event(e => onMouseDown.fire(e)));155listeners.add(listeners.add(new DomEmitter(container, 'mouseup', true)).event(e => onMouseUp.fire(e)));156listeners.add(listeners.add(new DomEmitter(container, 'mousemove', true)).event(e => onMouseMove.fire(e)));157158windowDisposables.add(listeners);159disposables.add(toDisposable(() => windowDisposables.delete(listeners)));160161disposables.add(listeners);162}163164for (const { window, disposables } of getWindows()) {165registerContainerListeners(layoutService.getContainer(window), disposables);166}167168disposables.add(onDidRegisterWindow(({ window, disposables }) => registerContainerListeners(layoutService.getContainer(window), disposables)));169170disposables.add(layoutService.onDidChangeActiveContainer(() => {171layoutService.activeContainer.appendChild(mouseMarker);172layoutService.activeContainer.appendChild(keyboardMarker);173}));174175const updateMouseIndicatorColor = () => {176mouseMarker.style.borderColor = Color.fromHex(configurationService.getValue<string>('screencastMode.mouseIndicatorColor')).toString();177};178179let mouseIndicatorSize: number;180const updateMouseIndicatorSize = () => {181mouseIndicatorSize = clamp(configurationService.getValue<number>('screencastMode.mouseIndicatorSize') || 20, 20, 100);182183mouseMarker.style.height = `${mouseIndicatorSize}px`;184mouseMarker.style.width = `${mouseIndicatorSize}px`;185};186187updateMouseIndicatorColor();188updateMouseIndicatorSize();189190disposables.add(onMouseDown.event(e => {191mouseMarker.style.top = `${e.clientY - mouseIndicatorSize / 2}px`;192mouseMarker.style.left = `${e.clientX - mouseIndicatorSize / 2}px`;193mouseMarker.style.display = 'block';194mouseMarker.style.transform = `scale(${1})`;195mouseMarker.style.transition = 'transform 0.1s';196197const mouseMoveListener = onMouseMove.event(e => {198mouseMarker.style.top = `${e.clientY - mouseIndicatorSize / 2}px`;199mouseMarker.style.left = `${e.clientX - mouseIndicatorSize / 2}px`;200mouseMarker.style.transform = `scale(${.8})`;201});202203Event.once(onMouseUp.event)(() => {204mouseMarker.style.display = 'none';205mouseMoveListener.dispose();206});207}));208209const updateKeyboardFontSize = () => {210keyboardMarker.style.fontSize = `${clamp(configurationService.getValue<number>('screencastMode.fontSize') || 56, 20, 100)}px`;211};212213const updateKeyboardMarker = () => {214keyboardMarker.style.bottom = `${clamp(configurationService.getValue<number>('screencastMode.verticalOffset') || 0, 0, 90)}%`;215};216217let keyboardMarkerTimeout!: number;218const updateKeyboardMarkerTimeout = () => {219keyboardMarkerTimeout = clamp(configurationService.getValue<number>('screencastMode.keyboardOverlayTimeout') || 800, 500, 5000);220};221222updateKeyboardFontSize();223updateKeyboardMarker();224updateKeyboardMarkerTimeout();225226disposables.add(configurationService.onDidChangeConfiguration(e => {227if (e.affectsConfiguration('screencastMode.verticalOffset')) {228updateKeyboardMarker();229}230231if (e.affectsConfiguration('screencastMode.fontSize')) {232updateKeyboardFontSize();233}234235if (e.affectsConfiguration('screencastMode.keyboardOverlayTimeout')) {236updateKeyboardMarkerTimeout();237}238239if (e.affectsConfiguration('screencastMode.mouseIndicatorColor')) {240updateMouseIndicatorColor();241}242243if (e.affectsConfiguration('screencastMode.mouseIndicatorSize')) {244updateMouseIndicatorSize();245}246}));247248const onKeyDown = disposables.add(new Emitter<KeyboardEvent>());249const onCompositionStart = disposables.add(new Emitter<CompositionEvent>());250const onCompositionUpdate = disposables.add(new Emitter<CompositionEvent>());251const onCompositionEnd = disposables.add(new Emitter<CompositionEvent>());252253function registerWindowListeners(window: Window, windowDisposables: DisposableStore): void {254const listeners = new DisposableStore();255256listeners.add(listeners.add(new DomEmitter(window, 'keydown', true)).event(e => onKeyDown.fire(e)));257listeners.add(listeners.add(new DomEmitter(window, 'compositionstart', true)).event(e => onCompositionStart.fire(e)));258listeners.add(listeners.add(new DomEmitter(window, 'compositionupdate', true)).event(e => onCompositionUpdate.fire(e)));259listeners.add(listeners.add(new DomEmitter(window, 'compositionend', true)).event(e => onCompositionEnd.fire(e)));260261windowDisposables.add(listeners);262disposables.add(toDisposable(() => windowDisposables.delete(listeners)));263264disposables.add(listeners);265}266267for (const { window, disposables } of getWindows()) {268registerWindowListeners(window, disposables);269}270271disposables.add(onDidRegisterWindow(({ window, disposables }) => registerWindowListeners(window, disposables)));272273let length = 0;274let composing: Element | undefined = undefined;275let imeBackSpace = false;276277const clearKeyboardScheduler = disposables.add(new RunOnceScheduler(() => {278keyboardMarker.textContent = '';279composing = undefined;280length = 0;281}, keyboardMarkerTimeout));282283disposables.add(onCompositionStart.event(e => {284imeBackSpace = true;285}));286287disposables.add(onCompositionUpdate.event(e => {288if (e.data && imeBackSpace) {289if (length > 20) {290keyboardMarker.innerText = '';291length = 0;292}293composing = composing ?? append(keyboardMarker, $('span.key'));294composing.textContent = e.data;295} else if (imeBackSpace) {296keyboardMarker.innerText = '';297append(keyboardMarker, $('span.key', {}, `Backspace`));298}299clearKeyboardScheduler.schedule(keyboardMarkerTimeout);300}));301302disposables.add(onCompositionEnd.event(e => {303composing = undefined;304length++;305}));306307disposables.add(onKeyDown.event(e => {308if (e.key === 'Process' || /[\uac00-\ud787\u3131-\u314e\u314f-\u3163\u3041-\u3094\u30a1-\u30f4\u30fc\u3005\u3006\u3024\u4e00-\u9fa5]/u.test(e.key)) {309if (e.code === 'Backspace') {310imeBackSpace = true;311} else if (!e.code.includes('Key')) {312composing = undefined;313imeBackSpace = false;314} else {315imeBackSpace = true;316}317clearKeyboardScheduler.schedule(keyboardMarkerTimeout);318return;319}320321if (e.isComposing) {322return;323}324325const options = configurationService.getValue<IScreencastKeyboardOptions>('screencastMode.keyboardOptions');326const event = new StandardKeyboardEvent(e);327const shortcut = keybindingService.softDispatch(event, event.target);328329// Hide the single arrow key pressed330if (shortcut.kind === ResultKind.KbFound && shortcut.commandId && !(options.showSingleEditorCursorMoves ?? true) && (331['cursorLeft', 'cursorRight', 'cursorUp', 'cursorDown'].includes(shortcut.commandId))332) {333return;334}335336if (337event.ctrlKey || event.altKey || event.metaKey || event.shiftKey338|| length > 20339|| event.keyCode === KeyCode.Backspace || event.keyCode === KeyCode.Escape340|| event.keyCode === KeyCode.UpArrow || event.keyCode === KeyCode.DownArrow341|| event.keyCode === KeyCode.LeftArrow || event.keyCode === KeyCode.RightArrow342) {343keyboardMarker.innerText = '';344length = 0;345}346347const keybinding = keybindingService.resolveKeyboardEvent(event);348const commandDetails = (this._isKbFound(shortcut) && shortcut.commandId) ? this.getCommandDetails(shortcut.commandId) : undefined;349350let commandAndGroupLabel = commandDetails?.title;351let keyLabel: string | undefined | null = keybinding.getLabel();352353if (commandDetails) {354if ((options.showCommandGroups ?? false) && commandDetails.category) {355commandAndGroupLabel = `${commandDetails.category}: ${commandAndGroupLabel} `;356}357358if (this._isKbFound(shortcut) && shortcut.commandId) {359const keybindings = keybindingService.lookupKeybindings(shortcut.commandId)360.filter(k => k.getLabel()?.endsWith(keyLabel ?? ''));361362if (keybindings.length > 0) {363keyLabel = keybindings[keybindings.length - 1].getLabel();364}365}366}367368if ((options.showCommands ?? true) && commandAndGroupLabel) {369append(keyboardMarker, $('span.title', {}, `${commandAndGroupLabel} `));370}371372if ((options.showKeys ?? true) || ((options.showKeybindings ?? true) && this._isKbFound(shortcut))) {373// Fix label for arrow keys374keyLabel = keyLabel?.replace('UpArrow', '↑')375?.replace('DownArrow', '↓')376?.replace('LeftArrow', '←')377?.replace('RightArrow', '→');378379append(keyboardMarker, $('span.key', {}, keyLabel ?? ''));380}381382length++;383clearKeyboardScheduler.schedule(keyboardMarkerTimeout);384}));385386ToggleScreencastModeAction.disposable = disposables;387}388389private _isKbFound(resolutionResult: ResolutionResult): resolutionResult is { kind: ResultKind.KbFound; commandId: string | null; commandArgs: unknown; isBubble: boolean } {390return resolutionResult.kind === ResultKind.KbFound;391}392393private getCommandDetails(commandId: string): { title: string; category?: string } | undefined {394const fromMenuRegistry = MenuRegistry.getCommand(commandId);395396if (fromMenuRegistry) {397return {398title: typeof fromMenuRegistry.title === 'string' ? fromMenuRegistry.title : fromMenuRegistry.title.value,399category: fromMenuRegistry.category ? (typeof fromMenuRegistry.category === 'string' ? fromMenuRegistry.category : fromMenuRegistry.category.value) : undefined400};401}402403const fromCommandsRegistry = CommandsRegistry.getCommand(commandId);404405if (fromCommandsRegistry?.metadata?.description) {406return { title: typeof fromCommandsRegistry.metadata.description === 'string' ? fromCommandsRegistry.metadata.description : fromCommandsRegistry.metadata.description.value };407}408409return undefined;410}411}412413class LogStorageAction extends Action2 {414415constructor() {416super({417id: 'workbench.action.logStorage',418title: localize2({ key: 'logStorage', comment: ['A developer only action to log the contents of the storage for the current window.'] }, "Log Storage Database Contents"),419category: Categories.Developer,420f1: true421});422}423424run(accessor: ServicesAccessor): void {425const storageService = accessor.get(IStorageService);426const dialogService = accessor.get(IDialogService);427428storageService.log();429430dialogService.info(localize('storageLogDialogMessage', "The storage database contents have been logged to the developer tools."), localize('storageLogDialogDetails', "Open developer tools from the menu and select the Console tab."));431}432}433434class LogWorkingCopiesAction extends Action2 {435436constructor() {437super({438id: 'workbench.action.logWorkingCopies',439title: localize2({ key: 'logWorkingCopies', comment: ['A developer only action to log the working copies that exist.'] }, "Log Working Copies"),440category: Categories.Developer,441f1: true442});443}444445async run(accessor: ServicesAccessor): Promise<void> {446const workingCopyService = accessor.get(IWorkingCopyService);447const workingCopyBackupService = accessor.get(IWorkingCopyBackupService);448const logService = accessor.get(ILogService);449const outputService = accessor.get(IOutputService);450451const backups = await workingCopyBackupService.getBackups();452453const msg = [454``,455`[Working Copies]`,456...(workingCopyService.workingCopies.length > 0) ?457workingCopyService.workingCopies.map(workingCopy => `${workingCopy.isDirty() ? '● ' : ''}${workingCopy.resource.toString(true)} (typeId: ${workingCopy.typeId || '<no typeId>'})`) :458['<none>'],459``,460`[Backups]`,461...(backups.length > 0) ?462backups.map(backup => `${backup.resource.toString(true)} (typeId: ${backup.typeId || '<no typeId>'})`) :463['<none>'],464];465466logService.info(msg.join('\n'));467468outputService.showChannel(windowLogId, true);469}470}471472class RemoveLargeStorageEntriesAction extends Action2 {473474private static SIZE_THRESHOLD = 1024 * 16; // 16kb475476constructor() {477super({478id: 'workbench.action.removeLargeStorageDatabaseEntries',479title: localize2('removeLargeStorageDatabaseEntries', 'Remove Large Storage Database Entries...'),480category: Categories.Developer,481f1: true482});483}484485async run(accessor: ServicesAccessor): Promise<void> {486const storageService = accessor.get(IStorageService);487const quickInputService = accessor.get(IQuickInputService);488const userDataProfileService = accessor.get(IUserDataProfileService);489const dialogService = accessor.get(IDialogService);490const environmentService = accessor.get(IEnvironmentService);491492interface IStorageItem extends IQuickPickItem {493readonly key: string;494readonly scope: StorageScope;495readonly target: StorageTarget;496readonly size: number;497}498499const items: IStorageItem[] = [];500501for (const scope of [StorageScope.APPLICATION, StorageScope.PROFILE, StorageScope.WORKSPACE]) {502if (scope === StorageScope.PROFILE && userDataProfileService.currentProfile.isDefault) {503continue; // avoid duplicates504}505506for (const target of [StorageTarget.MACHINE, StorageTarget.USER]) {507for (const key of storageService.keys(scope, target)) {508const value = storageService.get(key, scope);509if (value && (!environmentService.isBuilt /* show all keys in dev */ || value.length > RemoveLargeStorageEntriesAction.SIZE_THRESHOLD)) {510items.push({511key,512scope,513target,514size: value.length,515label: key,516description: ByteSize.formatSize(value.length),517detail: localize('largeStorageItemDetail', "Scope: {0}, Target: {1}", scope === StorageScope.APPLICATION ? localize('global', "Global") : scope === StorageScope.PROFILE ? localize('profile', "Profile") : localize('workspace', "Workspace"), target === StorageTarget.MACHINE ? localize('machine', "Machine") : localize('user', "User")),518});519}520}521}522}523524items.sort((itemA, itemB) => itemB.size - itemA.size);525526const selectedItems = await new Promise<readonly IStorageItem[]>(resolve => {527const disposables = new DisposableStore();528529const picker = disposables.add(quickInputService.createQuickPick<IStorageItem>());530picker.items = items;531picker.canSelectMany = true;532picker.ok = false;533picker.customButton = true;534picker.hideCheckAll = true;535picker.customLabel = localize('removeLargeStorageEntriesPickerButton', "Remove");536picker.placeholder = localize('removeLargeStorageEntriesPickerPlaceholder', "Select large entries to remove from storage");537538if (items.length === 0) {539picker.description = localize('removeLargeStorageEntriesPickerDescriptionNoEntries', "There are no large storage entries to remove.");540}541542picker.show();543544disposables.add(picker.onDidCustom(() => {545resolve(picker.selectedItems);546picker.hide();547}));548549disposables.add(picker.onDidHide(() => disposables.dispose()));550});551552if (selectedItems.length === 0) {553return;554}555556const { confirmed } = await dialogService.confirm({557type: 'warning',558message: localize('removeLargeStorageEntriesConfirmRemove', "Do you want to remove the selected storage entries from the database?"),559detail: localize('removeLargeStorageEntriesConfirmRemoveDetail', "{0}\n\nThis action is irreversible and may result in data loss!", selectedItems.map(item => item.label).join('\n')),560primaryButton: localize({ key: 'removeLargeStorageEntriesButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Remove")561});562563if (!confirmed) {564return;565}566567const scopesToOptimize = new Set<StorageScope>();568for (const item of selectedItems) {569storageService.remove(item.key, item.scope);570scopesToOptimize.add(item.scope);571}572573for (const scope of scopesToOptimize) {574await storageService.optimize(scope);575}576}577}578579let tracker: DisposableTracker | undefined = undefined;580let trackedDisposables = new Set<IDisposable>();581582const DisposablesSnapshotStateContext = new RawContextKey<'started' | 'pending' | 'stopped'>('dirtyWorkingCopies', 'stopped');583584class StartTrackDisposables extends Action2 {585586constructor() {587super({588id: 'workbench.action.startTrackDisposables',589title: localize2('startTrackDisposables', 'Start Tracking Disposables'),590category: Categories.Developer,591f1: true,592precondition: ContextKeyExpr.and(DisposablesSnapshotStateContext.isEqualTo('pending').negate(), DisposablesSnapshotStateContext.isEqualTo('started').negate())593});594}595596run(accessor: ServicesAccessor): void {597const disposablesSnapshotStateContext = DisposablesSnapshotStateContext.bindTo(accessor.get(IContextKeyService));598disposablesSnapshotStateContext.set('started');599600trackedDisposables.clear();601602tracker = new DisposableTracker();603setDisposableTracker(tracker);604}605}606607class SnapshotTrackedDisposables extends Action2 {608609constructor() {610super({611id: 'workbench.action.snapshotTrackedDisposables',612title: localize2('snapshotTrackedDisposables', 'Snapshot Tracked Disposables'),613category: Categories.Developer,614f1: true,615precondition: DisposablesSnapshotStateContext.isEqualTo('started')616});617}618619run(accessor: ServicesAccessor): void {620const disposablesSnapshotStateContext = DisposablesSnapshotStateContext.bindTo(accessor.get(IContextKeyService));621disposablesSnapshotStateContext.set('pending');622623trackedDisposables = new Set(tracker?.computeLeakingDisposables(1000)?.leaks.map(disposable => disposable.value));624}625}626627class StopTrackDisposables extends Action2 {628629constructor() {630super({631id: 'workbench.action.stopTrackDisposables',632title: localize2('stopTrackDisposables', 'Stop Tracking Disposables'),633category: Categories.Developer,634f1: true,635precondition: DisposablesSnapshotStateContext.isEqualTo('pending')636});637}638639run(accessor: ServicesAccessor): void {640const editorService = accessor.get(IEditorService);641642const disposablesSnapshotStateContext = DisposablesSnapshotStateContext.bindTo(accessor.get(IContextKeyService));643disposablesSnapshotStateContext.set('stopped');644645if (tracker) {646const disposableLeaks = new Set<DisposableInfo>();647648for (const disposable of new Set(tracker.computeLeakingDisposables(1000)?.leaks) ?? []) {649if (trackedDisposables.has(disposable.value)) {650disposableLeaks.add(disposable);651}652}653654const leaks = tracker.computeLeakingDisposables(1000, Array.from(disposableLeaks));655if (leaks) {656editorService.openEditor({ resource: undefined, contents: leaks.details });657}658}659660setDisposableTracker(null);661tracker = undefined;662trackedDisposables.clear();663}664}665666class PolicyDiagnosticsAction extends Action2 {667668constructor() {669super({670id: 'workbench.action.showPolicyDiagnostics',671title: localize2('policyDiagnostics', 'Policy Diagnostics'),672category: Categories.Developer,673f1: true674});675}676677async run(accessor: ServicesAccessor): Promise<void> {678const editorService = accessor.get(IEditorService);679const configurationService = accessor.get(IConfigurationService);680const productService = accessor.get(IProductService);681const defaultAccountService = accessor.get(IDefaultAccountService);682const authenticationService = accessor.get(IAuthenticationService);683const authenticationAccessService = accessor.get(IAuthenticationAccessService);684const policyService = accessor.get(IPolicyService);685686const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);687688let content = '# VS Code Policy Diagnostics\n\n';689content += '*WARNING: This file may contain sensitive information.*\n\n';690content += '## System Information\n\n';691content += '| Property | Value |\n';692content += '|----------|-------|\n';693content += `| Generated | ${new Date().toISOString()} |\n`;694content += `| Product | ${productService.nameLong} ${productService.version} |\n`;695content += `| Commit | ${productService.commit || 'n/a'} |\n\n`;696697// Account information698content += '## Account Information\n\n';699try {700const account = await defaultAccountService.getDefaultAccount();701const sensitiveKeys = ['sessionId', 'analytics_tracking_id'];702if (account) {703// Try to get username/display info from the authentication session704let username = 'Unknown';705let accountLabel = 'Unknown';706try {707const providerIds = authenticationService.getProviderIds();708for (const providerId of providerIds) {709const sessions = await authenticationService.getSessions(providerId);710const matchingSession = sessions.find(session => session.id === account.sessionId);711if (matchingSession) {712username = matchingSession.account.id;713accountLabel = matchingSession.account.label;714break;715}716}717} catch (error) {718// Fallback to just session info719}720721content += '### Default Account Summary\n\n';722content += `**Account ID/Username**: ${username}\n\n`;723content += `**Account Label**: ${accountLabel}\n\n`;724725content += '### Detailed Account Properties\n\n';726content += '| Property | Value |\n';727content += '|----------|-------|\n';728729// Iterate through all properties of the account object730for (const [key, value] of Object.entries(account)) {731if (value !== undefined && value !== null) {732let displayValue: string;733734// Mask sensitive information735if (sensitiveKeys.includes(key)) {736displayValue = '***';737} else if (typeof value === 'object') {738displayValue = JSON.stringify(value);739} else {740displayValue = String(value);741}742743content += `| ${key} | ${displayValue} |\n`;744}745}746const policyData = defaultAccountService.policyData;747content += `| policyData | ${policyData ? JSON.stringify(policyData) : 'No Policy Data'} |\n`;748content += '\n';749} else {750content += '*No default account configured*\n\n';751}752} catch (error) {753content += `*Error retrieving account information: ${error}*\n\n`;754}755756content += '## Policy-Controlled Settings\n\n';757758const policyConfigurations = configurationRegistry.getPolicyConfigurations();759const configurationProperties = configurationRegistry.getConfigurationProperties();760const excludedProperties = configurationRegistry.getExcludedConfigurationProperties();761762if (policyConfigurations.size > 0) {763// eslint-disable-next-line @typescript-eslint/no-explicit-any764const appliedPolicy: Array<{ name: string; key: string; property: any; inspection: any }> = [];765// eslint-disable-next-line @typescript-eslint/no-explicit-any766const notAppliedPolicy: Array<{ name: string; key: string; property: any; inspection: any }> = [];767768for (const [policyName, settingKey] of policyConfigurations) {769const property = configurationProperties[settingKey] ?? excludedProperties[settingKey];770if (property) {771const inspectValue = configurationService.inspect(settingKey);772const settingInfo = {773name: policyName,774key: settingKey,775property,776inspection: inspectValue777};778779if (inspectValue.policyValue !== undefined) {780appliedPolicy.push(settingInfo);781} else {782notAppliedPolicy.push(settingInfo);783}784}785}786787// Try to detect where the policy came from788const policySourceMemo = new Map<string, string>();789const getPolicySource = (policyName: string): string => {790if (policySourceMemo.has(policyName)) {791return policySourceMemo.get(policyName)!;792}793try {794const policyServiceConstructorName = policyService.constructor.name;795if (policyServiceConstructorName === 'MultiplexPolicyService') {796// eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any797const multiplexService = policyService as any;798if (multiplexService.policyServices) {799// eslint-disable-next-line @typescript-eslint/no-explicit-any800const componentServices = multiplexService.policyServices as ReadonlyArray<any>;801for (const service of componentServices) {802if (service.getPolicyValue && service.getPolicyValue(policyName) !== undefined) {803policySourceMemo.set(policyName, service.constructor.name);804return service.constructor.name;805}806}807}808}809return '';810} catch {811return 'Unknown';812}813};814815content += '### Applied Policy\n\n';816appliedPolicy.sort((a, b) => getPolicySource(a.name).localeCompare(getPolicySource(b.name)) || a.name.localeCompare(b.name));817if (appliedPolicy.length > 0) {818content += '| Setting Key | Policy Name | Policy Source | Default Value | Current Value | Policy Value |\n';819content += '|-------------|-------------|---------------|---------------|---------------|-------------|\n';820821for (const setting of appliedPolicy) {822const defaultValue = JSON.stringify(setting.property.default);823const currentValue = JSON.stringify(setting.inspection.value);824const policyValue = JSON.stringify(setting.inspection.policyValue);825const policySource = getPolicySource(setting.name);826827content += `| ${setting.key} | ${setting.name} | ${policySource} | \`${defaultValue}\` | \`${currentValue}\` | \`${policyValue}\` |\n`;828}829content += '\n';830} else {831content += '*No settings are currently controlled by policies*\n\n';832}833834content += '### Non-applied Policy\n\n';835if (notAppliedPolicy.length > 0) {836content += '| Setting Key | Policy Name \n';837content += '|-------------|-------------|\n';838839for (const setting of notAppliedPolicy) {840841content += `| ${setting.key} | ${setting.name}|\n`;842}843content += '\n';844} else {845content += '*All policy-controllable settings are currently being enforced*\n\n';846}847} else {848content += '*No policy-controlled settings found*\n\n';849}850851// Authentication diagnostics852content += '## Authentication Information\n\n';853try {854const providerIds = authenticationService.getProviderIds();855856if (providerIds.length > 0) {857content += '### Authentication Providers\n\n';858content += '| Provider ID | Sessions | Accounts |\n';859content += '|-------------|----------|----------|\n';860861for (const providerId of providerIds) {862try {863const sessions = await authenticationService.getSessions(providerId);864const accounts = sessions.map(session => session.account);865const uniqueAccounts = Array.from(new Set(accounts.map(account => account.label)));866867content += `| ${providerId} | ${sessions.length} | ${uniqueAccounts.join(', ') || 'None'} |\n`;868} catch (error) {869content += `| ${providerId} | Error | ${error} |\n`;870}871}872content += '\n';873874// Detailed session information875content += '### Detailed Session Information\n\n';876for (const providerId of providerIds) {877try {878const sessions = await authenticationService.getSessions(providerId);879880if (sessions.length > 0) {881content += `#### ${providerId}\n\n`;882content += '| Account | Scopes | Extensions with Access |\n';883content += '|---------|--------|------------------------|\n';884885for (const session of sessions) {886const accountName = session.account.label;887const scopes = session.scopes.join(', ') || 'Default';888889// Get extensions with access to this account890try {891const allowedExtensions = authenticationAccessService.readAllowedExtensions(providerId, accountName);892const extensionNames = allowedExtensions893.filter(ext => ext.allowed !== false)894.map(ext => `${ext.name}${ext.trusted ? ' (trusted)' : ''}`)895.join(', ') || 'None';896897content += `| ${accountName} | ${scopes} | ${extensionNames} |\n`;898} catch (error) {899content += `| ${accountName} | ${scopes} | Error: ${error} |\n`;900}901}902content += '\n';903}904} catch (error) {905content += `#### ${providerId}\n*Error retrieving sessions: ${error}*\n\n`;906}907}908} else {909content += '*No authentication providers found*\n\n';910}911} catch (error) {912content += `*Error retrieving authentication information: ${error}*\n\n`;913}914915await editorService.openEditor({916resource: undefined,917contents: content,918languageId: 'markdown',919options: { pinned: true, }920});921}922}923924// --- Actions Registration925registerAction2(InspectContextKeysAction);926registerAction2(ToggleScreencastModeAction);927registerAction2(LogStorageAction);928registerAction2(LogWorkingCopiesAction);929registerAction2(RemoveLargeStorageEntriesAction);930registerAction2(PolicyDiagnosticsAction);931if (!product.commit) {932registerAction2(StartTrackDisposables);933registerAction2(SnapshotTrackedDisposables);934registerAction2(StopTrackDisposables);935}936937// --- Configuration938939// Screen Cast Mode940const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);941configurationRegistry.registerConfiguration({942id: 'screencastMode',943order: 9,944title: localize('screencastModeConfigurationTitle', "Screencast Mode"),945type: 'object',946properties: {947'screencastMode.verticalOffset': {948type: 'number',949default: 20,950minimum: 0,951maximum: 90,952description: localize('screencastMode.location.verticalPosition', "Controls the vertical offset of the screencast mode overlay from the bottom as a percentage of the workbench height.")953},954'screencastMode.fontSize': {955type: 'number',956default: 56,957minimum: 20,958maximum: 100,959description: localize('screencastMode.fontSize', "Controls the font size (in pixels) of the screencast mode keyboard.")960},961'screencastMode.keyboardOptions': {962type: 'object',963description: localize('screencastMode.keyboardOptions.description', "Options for customizing the keyboard overlay in screencast mode."),964properties: {965'showKeys': {966type: 'boolean',967default: true,968description: localize('screencastMode.keyboardOptions.showKeys', "Show raw keys.")969},970'showKeybindings': {971type: 'boolean',972default: true,973description: localize('screencastMode.keyboardOptions.showKeybindings', "Show keyboard shortcuts.")974},975'showCommands': {976type: 'boolean',977default: true,978description: localize('screencastMode.keyboardOptions.showCommands', "Show command names.")979},980'showCommandGroups': {981type: 'boolean',982default: false,983description: localize('screencastMode.keyboardOptions.showCommandGroups', "Show command group names, when commands are also shown.")984},985'showSingleEditorCursorMoves': {986type: 'boolean',987default: true,988description: localize('screencastMode.keyboardOptions.showSingleEditorCursorMoves', "Show single editor cursor move commands.")989}990},991default: {992'showKeys': true,993'showKeybindings': true,994'showCommands': true,995'showCommandGroups': false,996'showSingleEditorCursorMoves': true997},998additionalProperties: false999},1000'screencastMode.keyboardOverlayTimeout': {1001type: 'number',1002default: 800,1003minimum: 500,1004maximum: 5000,1005description: localize('screencastMode.keyboardOverlayTimeout', "Controls how long (in milliseconds) the keyboard overlay is shown in screencast mode.")1006},1007'screencastMode.mouseIndicatorColor': {1008type: 'string',1009format: 'color-hex',1010default: '#FF0000',1011description: localize('screencastMode.mouseIndicatorColor', "Controls the color in hex (#RGB, #RGBA, #RRGGBB or #RRGGBBAA) of the mouse indicator in screencast mode.")1012},1013'screencastMode.mouseIndicatorSize': {1014type: 'number',1015default: 20,1016minimum: 20,1017maximum: 100,1018description: localize('screencastMode.mouseIndicatorSize', "Controls the size (in pixels) of the mouse indicator in screencast mode.")1019},1020}1021});102210231024