From 8e52cbc941e9616c022db51a0480b05a2bb3d10c Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Wed, 24 Apr 2024 09:05:48 +0200 Subject: [PATCH] Enable extenders to overwrite default settings Extenders can use the `AdapterCapabilities` to specify their own default settings, including the visible columns (see `MemoryDisplaySettings` interface). If an extension specifies such settings for a specific debug session (or type), those settings take priority over the user's VS Code settings of the Memory Inspector. To indicate that default settings are overwritten by an extension, an additional message is shown in the hover of the reset button in the options overlay. Extenders can specify this message to give more specific reasons for overwriting the settings. Users can prevent extenders from overwriting the default settings via a dedicated VS Code setting (`allowSettingsExtension`). Fixes https://github.com/eclipse-cdt-cloud/vscode-memory-inspector/issues/77 --- package.json | 5 ++ src/common/memory-display-settings.ts | 48 +++++++++++++ .../adapter-registry/adapter-capabilities.ts | 4 ++ src/plugin/manifest.ts | 3 + src/plugin/memory-provider.ts | 8 +++ src/plugin/memory-webview-main.ts | 67 ++++++++++++------- src/webview/components/memory-table.tsx | 5 +- src/webview/components/memory-widget.tsx | 18 ++--- src/webview/components/options-widget.tsx | 9 ++- src/webview/hovers/hover-service.tsx | 5 +- src/webview/memory-webview-view.tsx | 17 +++-- src/webview/utils/view-types.ts | 30 ++------- 12 files changed, 150 insertions(+), 69 deletions(-) create mode 100644 src/common/memory-display-settings.ts diff --git a/package.json b/package.json index 3f3a852..3e3bbee 100644 --- a/package.json +++ b/package.json @@ -371,6 +371,11 @@ "type": "boolean", "default": true, "description": "Display the radix prefix (e.g., '0x' for hexadecimal, '0b' for binary) before memory addresses." + }, + "memory-inspector.allowSettingsExtension": { + "type": "boolean", + "default": true, + "description": "Allow other extensions to overwrite the default memory display settings." } } } diff --git a/src/common/memory-display-settings.ts b/src/common/memory-display-settings.ts new file mode 100644 index 0000000..24e35c4 --- /dev/null +++ b/src/common/memory-display-settings.ts @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (C) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Endianness, Radix } from './memory-range'; + +/** Specifies the settings for displaying memory addresses in the memory data table. */ +export interface MemoryAddressDisplaySettings { + addressPadding: AddressPadding; + addressRadix: Radix; + showRadixPrefix: boolean; +} + +export type AddressPadding = 'Min' | 0 | 32 | 64; + +/** Specifies the settings for displaying memory data in the memory data table, including the memory addresses. */ +export interface MemoryDataDisplaySettings extends MemoryAddressDisplaySettings { + bytesPerMau: number; + mausPerGroup: number; + groupsPerRow: 'Autofit' | number; + endianness: Endianness; + scrollingBehavior: ScrollingBehavior; +} + +export type ScrollingBehavior = 'Paginate' | 'Grow' | 'Auto-Append'; + +/** Specifies the display settings of the memory data table, including the memory data and addresses. */ +export interface MemoryDisplaySettings extends MemoryDataDisplaySettings { + visibleColumns: string[]; +} + +/** An extender's contribution to the `MemoryDisplaySettings` via the `AdapterCapabilities`. */ +export interface MemoryDisplaySettingsContribution { + message?: string; + settings?: Partial; +} diff --git a/src/plugin/adapter-registry/adapter-capabilities.ts b/src/plugin/adapter-registry/adapter-capabilities.ts index 0afd107..36e5b48 100644 --- a/src/plugin/adapter-registry/adapter-capabilities.ts +++ b/src/plugin/adapter-registry/adapter-capabilities.ts @@ -17,6 +17,7 @@ import { DebugProtocol } from '@vscode/debugprotocol'; import * as vscode from 'vscode'; import { isDebugRequest, isDebugResponse } from '../../common/debug-requests'; +import { MemoryDisplaySettingsContribution } from '../../common/memory-display-settings'; import { VariableRange } from '../../common/memory-range'; import { Logger } from '../logger'; @@ -30,6 +31,9 @@ export interface AdapterCapabilities { getAddressOfVariable?(session: vscode.DebugSession, variableName: string): Promise; /** Resolves the size of a given variable in bytes within the current context. */ getSizeOfVariable?(session: vscode.DebugSession, variableName: string): Promise; + /** Retrieve the enforced default display settings for the memory view. */ + getMemoryDisplaySettings?(session: vscode.DebugSession): Promise>; + /** Initialize the trackers of this adapter's for the debug session. */ initializeAdapterTracker?(session: vscode.DebugSession): vscode.DebugAdapterTracker | undefined; } diff --git a/src/plugin/manifest.ts b/src/plugin/manifest.ts index 98f76fc..23a10cb 100644 --- a/src/plugin/manifest.ts +++ b/src/plugin/manifest.ts @@ -64,3 +64,6 @@ export const DEFAULT_SHOW_RADIX_PREFIX = true; // Columns export const CONFIG_SHOW_VARIABLES_COLUMN = 'columns.variables'; export const CONFIG_SHOW_ASCII_COLUMN = 'columns.ascii'; + +// Extension Settings +export const CONFIG_ALLOW_SETTINGS_EXTENSION = 'allowSettingsExtension'; diff --git a/src/plugin/memory-provider.ts b/src/plugin/memory-provider.ts index 7a77996..786647b 100644 --- a/src/plugin/memory-provider.ts +++ b/src/plugin/memory-provider.ts @@ -18,6 +18,7 @@ import { DebugProtocol } from '@vscode/debugprotocol'; import * as vscode from 'vscode'; import { isDebugEvent, isDebugRequest, isDebugResponse, sendRequest } from '../common/debug-requests'; import { stringToBytesMemory } from '../common/memory'; +import { MemoryDisplaySettingsContribution } from '../common/memory-display-settings'; import { VariableRange, WrittenMemory } from '../common/memory-range'; import { ReadMemoryResult, SessionContext, WriteMemoryResult } from '../common/messaging'; import { AdapterRegistry } from './adapter-registry/adapter-registry'; @@ -183,4 +184,11 @@ export class MemoryProvider { const handler = this.adapterRegistry?.getHandlerForSession(session.type); return handler?.getSizeOfVariable?.(session, variableName); } + + public async getMemoryDisplaySettingsContribution(): Promise { + const session = this.assertActiveSession('get settings of variable'); + const handler = this.adapterRegistry?.getHandlerForSession(session.type); + return handler?.getMemoryDisplaySettings?.(session) ?? {}; + } + } diff --git a/src/plugin/memory-webview-main.ts b/src/plugin/memory-webview-main.ts index 38f9473..c9a2652 100644 --- a/src/plugin/memory-webview-main.ts +++ b/src/plugin/memory-webview-main.ts @@ -17,6 +17,7 @@ import * as vscode from 'vscode'; import { Messenger } from 'vscode-messenger'; import { WebviewIdMessageParticipant } from 'vscode-messenger-common'; +import { MemoryDisplaySettings, MemoryDisplaySettingsContribution, ScrollingBehavior } from '../common/memory-display-settings'; import { Endianness, VariableRange } from '../common/memory-range'; import { applyMemoryType, @@ -44,7 +45,7 @@ import { writeMemoryType, } from '../common/messaging'; import { getVisibleColumns, WebviewContext } from '../common/webview-context'; -import { AddressPaddingOptions, MemoryViewSettings, ScrollingBehavior } from '../webview/utils/view-types'; +import { AddressPaddingOptions, MemoryViewSettings } from '../webview/utils/view-types'; import { isVariablesContext } from './external-views'; import { outputChannelLogger } from './logger'; import * as manifest from './manifest'; @@ -204,9 +205,9 @@ export class MemoryWebview implements vscode.CustomReadonlyEditorProvider { const participant = this.messenger.registerWebviewPanel(panel); const disposables = [ - this.messenger.onNotification(readyType, () => { - this.setInitialSettings(participant, panel.title); + this.messenger.onNotification(readyType, async () => { this.setSessionContext(participant, this.memoryProvider.createContext()); + await this.setMemoryDisplaySettings(participant, panel.title); this.refresh(participant, options); }, { sender: participant }), this.messenger.onRequest(setOptionsType, o => { @@ -216,7 +217,7 @@ export class MemoryWebview implements vscode.CustomReadonlyEditorProvider { this.messenger.onRequest(readMemoryType, request => this.readMemory(request), { sender: participant }), this.messenger.onRequest(writeMemoryType, request => this.writeMemory(request), { sender: participant }), this.messenger.onRequest(getVariablesType, request => this.getVariables(request), { sender: participant }), - this.messenger.onNotification(resetMemoryViewSettingsType, () => this.setInitialSettings(participant, panel.title), { sender: participant }), + this.messenger.onNotification(resetMemoryViewSettingsType, () => this.setMemoryDisplaySettings(participant), { sender: participant }), this.messenger.onNotification(setTitleType, title => { panel.title = title; }, { sender: participant }), this.messenger.onRequest(storeMemoryType, args => this.storeMemory(args), { sender: participant }), this.messenger.onRequest(applyMemoryType, () => this.applyMemory(), { sender: participant }), @@ -237,39 +238,59 @@ export class MemoryWebview implements vscode.CustomReadonlyEditorProvider { panel.onDidDispose(() => disposables.forEach(disposable => disposable.dispose())); } - protected async refresh(participant: WebviewIdMessageParticipant, options: MemoryOptions = {}): Promise { - this.messenger.sendRequest(setOptionsType, participant, options); + protected async setMemoryDisplaySettings(participant: WebviewIdMessageParticipant, title?: string): Promise { + const defaultSettings = this.getDefaultMemoryDisplaySettings(); + const settingsContribution = await this.getSettingsContribution(); + this.messenger.sendNotification(setMemoryViewSettingsType, participant, { + title, + ...defaultSettings, + ...settingsContribution.settings, + contributionMessage: settingsContribution.message + }); } - protected setInitialSettings(webviewParticipant: WebviewIdMessageParticipant, title: string): void { - this.setMemoryViewSettings(webviewParticipant, this.getMemoryViewSettings(webviewParticipant, title)); + protected async refresh(participant: WebviewIdMessageParticipant, options: MemoryOptions = {}): Promise { + this.messenger.sendRequest(setOptionsType, participant, options); } protected setMemoryViewSettings(webviewParticipant: WebviewIdMessageParticipant, settings: Partial): void { this.messenger.sendNotification(setMemoryViewSettingsType, webviewParticipant, settings); } + protected applyDisplaySettingContributions(webviewParticipant: WebviewIdMessageParticipant, settingsContribution: Partial): void { + const { settings, message } = settingsContribution; + if (settings && Object.keys(settings).length) { + this.messenger.sendNotification(setMemoryViewSettingsType, webviewParticipant, { ...settings, contributionMessage: message }); + } + } + protected setSessionContext(webviewParticipant: WebviewIdMessageParticipant, context: SessionContext): void { this.messenger.sendNotification(sessionContextChangedType, webviewParticipant, context); } - protected getMemoryViewSettings(messageParticipant: WebviewIdMessageParticipant, title: string): MemoryViewSettings { - const memoryInspectorConfiguration = vscode.workspace.getConfiguration(manifest.PACKAGE_NAME); - const bytesPerMau = memoryInspectorConfiguration.get(manifest.CONFIG_BYTES_PER_MAU, manifest.DEFAULT_BYTES_PER_MAU); - const mausPerGroup = memoryInspectorConfiguration.get(manifest.CONFIG_MAUS_PER_GROUP, manifest.DEFAULT_MAUS_PER_GROUP); - const groupsPerRow = memoryInspectorConfiguration.get(manifest.CONFIG_GROUPS_PER_ROW, manifest.DEFAULT_GROUPS_PER_ROW); - const endianness = memoryInspectorConfiguration.get(manifest.CONFIG_ENDIANNESS, manifest.DEFAULT_ENDIANNESS); - const scrollingBehavior = memoryInspectorConfiguration.get(manifest.CONFIG_SCROLLING_BEHAVIOR, manifest.DEFAULT_SCROLLING_BEHAVIOR); + protected getDefaultMemoryDisplaySettings(): MemoryDisplaySettings { + const memoryInspectorSettings = vscode.workspace.getConfiguration(manifest.PACKAGE_NAME); + const bytesPerMau = memoryInspectorSettings.get(manifest.CONFIG_BYTES_PER_MAU, manifest.DEFAULT_BYTES_PER_MAU); + const mausPerGroup = memoryInspectorSettings.get(manifest.CONFIG_MAUS_PER_GROUP, manifest.DEFAULT_MAUS_PER_GROUP); + const groupsPerRow = memoryInspectorSettings.get(manifest.CONFIG_GROUPS_PER_ROW, manifest.DEFAULT_GROUPS_PER_ROW); + const endianness = memoryInspectorSettings.get(manifest.CONFIG_ENDIANNESS, manifest.DEFAULT_ENDIANNESS); + const scrollingBehavior = memoryInspectorSettings.get(manifest.CONFIG_SCROLLING_BEHAVIOR, manifest.DEFAULT_SCROLLING_BEHAVIOR); const visibleColumns = CONFIGURABLE_COLUMNS - .filter(column => vscode.workspace.getConfiguration(manifest.PACKAGE_NAME).get(column, false)) + .filter(column => memoryInspectorSettings.get(column, false)) .map(columnId => columnId.replace('columns.', '')); - const addressPadding = AddressPaddingOptions[memoryInspectorConfiguration.get(manifest.CONFIG_ADDRESS_PADDING, manifest.DEFAULT_ADDRESS_PADDING)]; - const addressRadix = memoryInspectorConfiguration.get(manifest.CONFIG_ADDRESS_RADIX, manifest.DEFAULT_ADDRESS_RADIX); - const showRadixPrefix = memoryInspectorConfiguration.get(manifest.CONFIG_SHOW_RADIX_PREFIX, manifest.DEFAULT_SHOW_RADIX_PREFIX); - return { - messageParticipant, title, bytesPerMau, mausPerGroup, groupsPerRow, - endianness, scrollingBehavior, visibleColumns, addressPadding, addressRadix, showRadixPrefix - }; + const addressPadding = AddressPaddingOptions[memoryInspectorSettings.get(manifest.CONFIG_ADDRESS_PADDING, manifest.DEFAULT_ADDRESS_PADDING)]; + const addressRadix = memoryInspectorSettings.get(manifest.CONFIG_ADDRESS_RADIX, manifest.DEFAULT_ADDRESS_RADIX); + const showRadixPrefix = memoryInspectorSettings.get(manifest.CONFIG_SHOW_RADIX_PREFIX, manifest.DEFAULT_SHOW_RADIX_PREFIX); + return { bytesPerMau, mausPerGroup, groupsPerRow, endianness, scrollingBehavior, visibleColumns, addressPadding, addressRadix, showRadixPrefix }; + } + + protected async getSettingsContribution(): Promise { + const memoryInspectorSettings = vscode.workspace.getConfiguration(manifest.PACKAGE_NAME); + const allowSettingsExtension = memoryInspectorSettings.get(manifest.CONFIG_ALLOW_SETTINGS_EXTENSION, true); + if (allowSettingsExtension) { + return this.memoryProvider.getMemoryDisplaySettingsContribution(); + } + return { settings: {}, message: '' }; } protected async readMemory(request: ReadMemoryArguments): Promise { diff --git a/src/webview/components/memory-table.tsx b/src/webview/components/memory-table.tsx index a860d76..bb79879 100644 --- a/src/webview/components/memory-table.tsx +++ b/src/webview/components/memory-table.tsx @@ -25,13 +25,14 @@ import { TooltipEvent } from 'primereact/tooltip/tooltipoptions'; import { classNames } from 'primereact/utils'; import React from 'react'; import { Memory } from '../../common/memory'; +import { MemoryDataDisplaySettings, ScrollingBehavior } from '../../common/memory-display-settings'; import { WebviewSelection } from '../../common/messaging'; import { MemoryOptions, ReadMemoryArguments } from '../../common/messaging'; import { tryToNumber } from '../../common/typescript'; import { TableRenderOptions } from '../columns/column-contribution-service'; import { DataColumn } from '../columns/data-column'; import type { HoverService } from '../hovers/hover-service'; -import { Decoration, isTrigger, MemoryDisplayConfiguration, ScrollingBehavior } from '../utils/view-types'; +import { Decoration, isTrigger } from '../utils/view-types'; import { createColumnVscodeContext, createSectionVscodeContext } from '../utils/vscode-contexts'; export interface MoreMemorySelectProps { @@ -128,7 +129,7 @@ export const MoreMemorySelect: React.FC; activeReadArguments: Required; memory?: Memory; diff --git a/src/webview/components/memory-widget.tsx b/src/webview/components/memory-widget.tsx index dcf09d4..285c097 100644 --- a/src/webview/components/memory-widget.tsx +++ b/src/webview/components/memory-widget.tsx @@ -17,16 +17,16 @@ import React from 'react'; import { WebviewIdMessageParticipant } from 'vscode-messenger-common'; import { Memory } from '../../common/memory'; -import { WebviewSelection } from '../../common/messaging'; -import { MemoryOptions, ReadMemoryArguments, SessionContext } from '../../common/messaging'; +import { MemoryDataDisplaySettings } from '../../common/memory-display-settings'; +import { MemoryOptions, ReadMemoryArguments, SessionContext, WebviewSelection } from '../../common/messaging'; import { ColumnStatus } from '../columns/column-contribution-service'; import { HoverService } from '../hovers/hover-service'; -import { Decoration, MemoryDisplayConfiguration, MemoryState } from '../utils/view-types'; +import { Decoration, MemoryState } from '../utils/view-types'; import { createAppVscodeContext, VscodeContext } from '../utils/vscode-contexts'; import { MemoryTable } from './memory-table'; import { OptionsWidget } from './options-widget'; -interface MemoryWidgetProps extends MemoryDisplayConfiguration { +interface MemoryWidgetProps extends MemoryDataDisplaySettings { messageParticipant: WebviewIdMessageParticipant; sessionContext: SessionContext; configuredReadArguments: Required; @@ -38,12 +38,13 @@ interface MemoryWidgetProps extends MemoryDisplayConfiguration { columns: ColumnStatus[]; effectiveAddressLength: number; isMemoryFetching: boolean; + settingsContributionMessage?: string; updateMemoryState: (state: Partial) => void; toggleColumn(id: string, active: boolean): void; isFrozen: boolean; toggleFrozen: () => void; - updateMemoryDisplayConfiguration: (memoryArguments: Partial) => void; - resetMemoryDisplayConfiguration: () => void; + updateMemoryDisplaySettings: (memoryArguments: Partial) => void; + resetMemoryDisplaySettings: () => void; updateTitle: (title: string) => void; fetchMemory(partialOptions?: MemoryOptions): Promise; storeMemory(): void; @@ -91,11 +92,12 @@ export class MemoryWidget extends React.Component; activeReadArguments: Required; title: string; + settingsContributionMessage?: string; updateRenderOptions: (options: Partial) => void; resetRenderOptions: () => void; updateTitle: (title: string) => void; @@ -133,6 +134,8 @@ export class OptionsWidget extends React.Component { if (userValue !== memoryValue) { return Actual: {memoryValue}; @@ -278,14 +281,14 @@ export class OptionsWidget extends React.Component - +