Skip to content

Commit

Permalink
Allow to freeze content of a memory view instance (#83)
Browse files Browse the repository at this point in the history
* Allow to freeze content of a memory view instance

Add a button in the title bar that allows to freeze the current content of the memory view.
If the view is frozen, updates from the debug adapter are ignored and don't overwrite the shown data.
In addition, UI elements that would trigger a data change (core options widget, `load more memory` buttons) are disabled.

Closes #70
  • Loading branch information
tortmayr committed Mar 1, 2024
1 parent 222a9d9 commit 531bea1
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 15 deletions.
10 changes: 6 additions & 4 deletions media/memory-table.css
Expand Up @@ -32,14 +32,16 @@
border: 1px solid var(--vscode-dropdown-border);
background: var(--vscode-dropdown-background);
outline: none;
cursor: pointer;
}

.more-memory-select {
display: flex;
justify-content: center;
align-items: center;
font-style: italic;
}

.more-memory-select:not(.p-disabled) {
cursor: pointer;
}

Expand All @@ -52,7 +54,7 @@
border-color: transparent;
}

.more-memory-select:hover .more-memory-select-top {
.more-memory-select:hover:not(.p-disabled) .more-memory-select-top {
border-bottom: 1px solid;
padding-bottom: 0;
border-color: var(--vscode-sideBar-foreground);
Expand All @@ -68,7 +70,7 @@
font-style: italic;
}

.more-memory-select select:hover {
.more-memory-select select:hover:not(.p-disabled) {
background: var(--vscode-dropdown-background);
}

Expand All @@ -77,5 +79,5 @@
}

.radix-prefix {
opacity: .6;
opacity: 0.6;
}
12 changes: 10 additions & 2 deletions media/options-widget.css
Expand Up @@ -27,7 +27,7 @@
.memory-options-widget .title-container h1 {
flex-grow: 1;
font-size: 1.3em;
margin: 8px 0 4px;
margin: 8px 5px 4px;
}

.memory-options-widget .title-container input {
Expand All @@ -47,6 +47,14 @@
opacity: 1;
}

.memory-options-widget .freeze-content-toggle {
margin-top: 4px;
}

.memory-options-widget .freeze-content-toggle.toggled {
background-color: var(--vscode-button-secondaryBackground);
}

.core-options {
display: flex;
flex-direction: row;
Expand Down Expand Up @@ -116,4 +124,4 @@
border: none;
background-color: transparent;
cursor: pointer;
}
}
10 changes: 10 additions & 0 deletions media/theme/extensions.css
Expand Up @@ -22,10 +22,20 @@

.pm-top-label .p-inputtext-label {
margin-bottom: 2px;
}

.p-inputtext-label:not(.p-disabled) {
cursor: pointer;
}

small.p-invalid {
margin-top: 4px;
color: var(--vscode-errorForeground);
}

.p-disabled,
.p-disabled * {
pointer-events: auto;
cursor: not-allowed;
color: var(--vscode-disabledForeground);
}
11 changes: 8 additions & 3 deletions src/webview/components/memory-table.tsx
Expand Up @@ -33,9 +33,10 @@ export interface MoreMemorySelectProps {
direction: 'above' | 'below';
scrollingBehavior: ScrollingBehavior;
fetchMemory(partialOptions?: Partial<DebugProtocol.ReadMemoryArguments>): Promise<void>;
disabled: boolean
}

export const MoreMemorySelect: React.FC<MoreMemorySelectProps> = ({ count, offset, options, fetchMemory, direction, scrollingBehavior }) => {
export const MoreMemorySelect: React.FC<MoreMemorySelectProps> = ({ count, offset, options, fetchMemory, direction, scrollingBehavior, disabled }) => {
const [numBytes, setNumBytes] = React.useState<number>(options[0]);
const containerRef = React.createRef<HTMLDivElement>();
const onSelectChange = (e: React.ChangeEvent<HTMLSelectElement>): void => {
Expand Down Expand Up @@ -65,7 +66,7 @@ export const MoreMemorySelect: React.FC<MoreMemorySelectProps> = ({ count, offse

return (
<div
className='more-memory-select'
className={`more-memory-select ${disabled ? 'p-disabled' : ''}`}
tabIndex={0}
role='button'
onClick={loadMoreMemory}
Expand All @@ -75,9 +76,10 @@ export const MoreMemorySelect: React.FC<MoreMemorySelectProps> = ({ count, offse
<div className='more-memory-select-top no-select'>
Load
<select
className='bytes-select'
className={`bytes-select ${disabled ? 'p-disabled' : ''}`}
onChange={onSelectChange}
tabIndex={0}
disabled={disabled}
>
{options.map(option => (
<option
Expand All @@ -100,6 +102,7 @@ interface MemoryTableProps extends TableRenderOptions, MemoryDisplayConfiguratio
count: number;
fetchMemory(partialOptions?: Partial<DebugProtocol.ReadMemoryArguments>): Promise<void>;
isMemoryFetching: boolean;
isFrozen: boolean;
}

interface MemoryRowListOptions {
Expand Down Expand Up @@ -241,6 +244,7 @@ export class MemoryTable extends React.PureComponent<MemoryTableProps, MemoryTab
direction='above'
scrollingBehavior={scrollingBehavior}
fetchMemory={fetchMemory}
disabled={this.props.isFrozen}
/>
</div>;
}
Expand Down Expand Up @@ -274,6 +278,7 @@ export class MemoryTable extends React.PureComponent<MemoryTableProps, MemoryTab
direction='below'
scrollingBehavior={scrollingBehavior}
fetchMemory={fetchMemory}
disabled={this.props.isFrozen}
/>
</div>;
}
Expand Down
5 changes: 5 additions & 0 deletions src/webview/components/memory-widget.tsx
Expand Up @@ -33,6 +33,8 @@ interface MemoryWidgetProps extends MemoryDisplayConfiguration {
refreshMemory: () => void;
updateMemoryArguments: (memoryArguments: Partial<DebugProtocol.ReadMemoryArguments>) => void;
toggleColumn(id: string, active: boolean): void;
isFrozen: boolean;
toggleFrozen: () => void;
updateMemoryDisplayConfiguration: (memoryArguments: Partial<MemoryDisplayConfiguration>) => void;
resetMemoryDisplayConfiguration: () => void;
updateTitle: (title: string) => void;
Expand Down Expand Up @@ -73,6 +75,8 @@ export class MemoryWidget extends React.Component<MemoryWidgetProps, MemoryWidge
addressRadix={this.props.addressRadix}
showRadixPrefix={this.props.showRadixPrefix}
toggleColumn={this.props.toggleColumn}
toggleFrozen={this.props.toggleFrozen}
isFrozen={this.props.isFrozen}
/>
<MemoryTable
decorations={this.props.decorations}
Expand All @@ -89,6 +93,7 @@ export class MemoryWidget extends React.Component<MemoryWidgetProps, MemoryWidge
scrollingBehavior={this.props.scrollingBehavior}
addressRadix={this.props.addressRadix}
showRadixPrefix={this.props.showRadixPrefix}
isFrozen={this.props.isFrozen}
/>
</div>);
}
Expand Down
29 changes: 23 additions & 6 deletions src/webview/components/options-widget.tsx
Expand Up @@ -41,6 +41,8 @@ export interface OptionsWidgetProps
) => void;
refreshMemory: () => void;
toggleColumn(id: string, isVisible: boolean): void;
toggleFrozen: () => void;
isFrozen: boolean;
}

interface OptionsWidgetState {
Expand Down Expand Up @@ -140,10 +142,22 @@ export class OptionsWidget extends React.Component<OptionsWidgetProps, OptionsWi
override render(): React.ReactNode {
this.formConfig.initialValues = this.optionsFormValues;
const isLabelEditing = this.state.isTitleEditing;
const isFrozen = this.props.isFrozen;
const freezeContentToggleTitle = isFrozen ? 'Unfreeze Memory View' : 'Freeze Memory View';

return (
<div className='memory-options-widget px-4'>
<div className='title-container'>
<Button
type='button'
className={`freeze-content-toggle ${isFrozen ? 'toggled' : ''}`}
icon={'codicon codicon-' + (isFrozen ? 'lock' : 'unlock')}
onClick={this.props.toggleFrozen}
title={freezeContentToggleTitle}
aria-label={freezeContentToggleTitle}
rounded
aria-haspopup
/>
<InputText
ref={this.labelEditInput}
type='text'
Expand All @@ -168,11 +182,11 @@ export class OptionsWidget extends React.Component<OptionsWidgetProps, OptionsWi
)}
</div>
<div className='core-options py-2'>
<Formik {...this.formConfig}>
<Formik {...this.formConfig} >
{formik => (
<form onSubmit={formik.handleSubmit} className='form-options'>
<span className='pm-top-label form-texfield-long'>
<label htmlFor={InputId.Address} className='p-inputtext-label'>
<span className={'pm-top-label form-texfield-long'}>
<label htmlFor={InputId.Address} className={`p-inputtext-label ${isFrozen ? 'p-disabled' : ''}`} >
Address
</label>
<InputText
Expand All @@ -181,6 +195,7 @@ export class OptionsWidget extends React.Component<OptionsWidgetProps, OptionsWi
{...formik.getFieldProps('address')}
onKeyDown={this.handleKeyDown}
onBlur={ev => this.doHandleBlur(ev, formik)}
disabled={isFrozen}
/>
{formik.errors.address ?
(<small className='p-invalid'>
Expand All @@ -189,7 +204,7 @@ export class OptionsWidget extends React.Component<OptionsWidgetProps, OptionsWi
: undefined}
</span>
<span className='pm-top-label form-textfield'>
<label htmlFor={InputId.Offset} className='p-inputtext-label'>
<label htmlFor={InputId.Offset} className={`p-inputtext-label ${isFrozen ? 'p-disabled' : ''}`}>
Offset
</label>
<InputText
Expand All @@ -198,6 +213,7 @@ export class OptionsWidget extends React.Component<OptionsWidgetProps, OptionsWi
{...formik.getFieldProps('offset')}
onKeyDown={this.handleKeyDown}
onBlur={ev => this.doHandleBlur(ev, formik)}
disabled={isFrozen}
/>
{formik.errors.offset ?
(<small className='p-invalid'>
Expand All @@ -206,7 +222,7 @@ export class OptionsWidget extends React.Component<OptionsWidgetProps, OptionsWi
: undefined}
</span>
<span className='pm-top-label form-textfield'>
<label htmlFor={InputId.Length} className='p-inputtext-label'>
<label htmlFor={InputId.Length} className={`p-inputtext-label ${isFrozen ? 'p-disabled' : ''}`}>
Length
</label>
<InputText
Expand All @@ -215,14 +231,15 @@ export class OptionsWidget extends React.Component<OptionsWidgetProps, OptionsWi
{...formik.getFieldProps('count')}
onKeyDown={this.handleKeyDown}
onBlur={ev => this.doHandleBlur(ev, formik)}
disabled={isFrozen}
/>
{formik.errors.count ?
(<small className='p-invalid'>
{formik.errors.count}
</small>)
: undefined}
</span>
<Button type='submit' disabled={!formik.isValid}>
<Button type='submit' disabled={!formik.isValid || isFrozen}>
Go
</Button>
</form>
Expand Down
13 changes: 13 additions & 0 deletions src/webview/memory-webview-view.tsx
Expand Up @@ -43,6 +43,7 @@ export interface MemoryAppState extends MemoryState, MemoryDisplayConfiguration
title: string;
decorations: Decoration[];
columns: ColumnStatus[];
isFrozen: boolean;
}

const MEMORY_DISPLAY_CONFIGURATION_DEFAULTS: MemoryDisplayConfiguration = {
Expand Down Expand Up @@ -72,6 +73,7 @@ class App extends React.Component<{}, MemoryAppState> {
decorations: [],
columns: columnContributionService.getColumns(),
isMemoryFetching: false,
isFrozen: false,
...MEMORY_DISPLAY_CONFIGURATION_DEFAULTS
};
}
Expand Down Expand Up @@ -105,6 +107,8 @@ class App extends React.Component<{}, MemoryAppState> {
updateTitle={this.updateTitle}
refreshMemory={this.refreshMemory}
toggleColumn={this.toggleColumn}
toggleFrozen={this.toggleFrozen}
isFrozen={this.state.isFrozen}
fetchMemory={this.fetchMemory}
isMemoryFetching={this.state.isMemoryFetching}
bytesPerWord={this.state.bytesPerWord}
Expand Down Expand Up @@ -135,6 +139,9 @@ class App extends React.Component<{}, MemoryAppState> {

protected fetchMemory = async (partialOptions?: Partial<DebugProtocol.ReadMemoryArguments>): Promise<void> => this.doFetchMemory(partialOptions);
protected async doFetchMemory(partialOptions?: Partial<DebugProtocol.ReadMemoryArguments>): Promise<void> {
if (this.state.isFrozen) {
return;
}
this.setState(prev => ({ ...prev, isMemoryFetching: true }));
const completeOptions = {
memoryReference: partialOptions?.memoryReference || this.state.memoryReference,
Expand Down Expand Up @@ -177,6 +184,12 @@ class App extends React.Component<{}, MemoryAppState> {
const columns = isVisible ? await columnContributionService.show(id, this.state) : columnContributionService.hide(id);
this.setState(prevState => ({ ...prevState, columns }));
}

protected toggleFrozen = (): void => { this.doToggleFrozen(); };
protected doToggleFrozen(): void {
this.setState(prevState => ({ ...prevState, isFrozen: !prevState.isFrozen }));
}

}

const container = document.getElementById('root') as Element;
Expand Down

0 comments on commit 531bea1

Please sign in to comment.