Skip to content

Commit

Permalink
Hovers using PrimeReact Tooltip
Browse files Browse the repository at this point in the history
  • Loading branch information
gbodeen committed Mar 5, 2024
1 parent 531bea1 commit 0aea797
Show file tree
Hide file tree
Showing 13 changed files with 417 additions and 5 deletions.
56 changes: 56 additions & 0 deletions media/memory-table.css
Expand Up @@ -81,3 +81,59 @@
.radix-prefix {
opacity: 0.6;
}

.hoverable {
position: relative;
}

/* Basic hover formatting (copied from Monaco hovers) */
.memory-hover {
min-width: fit-content;
max-width: var(--vscode-hover-maxWidth,500px);
border: 1px solid var(--vscode-editorHoverWidget-border);
border-radius: 3px;

color: var(--vscode-editorHoverWidget-foreground);
background-color: var(--vscode-editorHoverWidget-background);

font-family: var(--vscode-editor-font-family);
font-size: var(--vscode-font-size);
}

/* Table formatting for hovers */
.memory-hover table {
border-collapse: collapse;
border-style: hidden;
}
.memory-hover table caption {
padding: 4px;
border-bottom: 1px solid var(--vscode-editorHoverWidget-border);
}
.memory-hover td {
border: 1px solid var(--vscode-editorHoverWidget-border);
padding: 2px 8px;
}
.memory-hover td:first-child {
text-align: right;
}

/* Colors for the hover fields */
.memory-hover .label-value-pair>.label {
color: var(--vscode-debugTokenExpression-string);
}
.memory-hover .label-value-pair>.value {
color: var(--vscode-debugTokenExpression-number);
}

/* Specialized colors for the address-hover fields */
.memory-hover .address-hover .primary {
background-color: var(--vscode-list-hoverBackground);
}

/* Specialized colors for the variable-hover fields */
.memory-hover table caption {
color: var(--vscode-symbolIcon-variableForeground);
}
.memory-hover .variable-hover .value.type {
color: var(--vscode-debugTokenExpression-name);
}
1 change: 1 addition & 0 deletions src/plugin/adapter-registry/c-tracker.ts
Expand Up @@ -45,6 +45,7 @@ export class CTracker extends AdapterVariableTracker {
startAddress: toHexStringWithRadixMarker(startAddress),
endAddress: endAddress === undefined ? undefined : toHexStringWithRadixMarker(endAddress),
value: variable.value,
type: variable.type,
};
} catch (err) {
this.logger.warn('Unable to resolve location and size of', variable.name + (err instanceof Error ? ':\n\t' + err.message : ''));
Expand Down
2 changes: 1 addition & 1 deletion src/webview/columns/address-column.tsx
Expand Up @@ -29,7 +29,7 @@ export class AddressColumn implements ColumnContribution {
render(range: BigIntMemoryRange, _: Memory, options: MemoryDisplayConfiguration): ReactNode {
return <span className='memory-start-address'>
{options.showRadixPrefix && <span className='radix-prefix'>{getRadixMarker(options.addressRadix)}</span>}
<span className='address'>{getAddressString(range.startAddress, options.addressRadix)}</span>
<span className='address hoverable' data-column='address'>{getAddressString(range.startAddress, options.addressRadix)}</span>
</span>;
}
}
4 changes: 2 additions & 2 deletions src/webview/columns/data-column.tsx
Expand Up @@ -35,11 +35,11 @@ export class DataColumn implements ColumnContribution {
for (let i = range.startAddress; i < range.endAddress; i++) {
words.push(this.renderWord(memory, options, i));
if (words.length % options.wordsPerGroup === 0) {
groups.push(<span className='byte-group' key={i.toString(16)}>{words}</span>);
groups.push(<span className='byte-group hoverable' data-column='data' key={i.toString(16)}>{words}</span>);
words = [];
}
}
if (words.length) { groups.push(<span className='byte-group' key={(range.endAddress - BigInt(words.length)).toString(16)}>{words}</span>); }
if (words.length) { groups.push(<span className='byte-group hoverable' data-column='data' key={(range.endAddress - BigInt(words.length)).toString(16)}>{words}</span>); }
return groups;
}

Expand Down
32 changes: 32 additions & 0 deletions src/webview/components/memory-table.tsx
Expand Up @@ -19,12 +19,15 @@ import memoize from 'memoize-one';
import { Column } from 'primereact/column';
import { DataTable, DataTableCellSelection, DataTableProps, DataTableSelectionCellChangeEvent } from 'primereact/datatable';
import { ProgressSpinner } from 'primereact/progressspinner';
import { Tooltip } from 'primereact/tooltip';
import React from 'react';
import { TableRenderOptions } from '../columns/column-contribution-service';
import { Decoration, Memory, MemoryDisplayConfiguration, ScrollingBehavior, isTrigger } from '../utils/view-types';
import isDeepEqual from 'fast-deep-equal';
import { AddressColumn } from '../columns/address-column';
import { classNames } from 'primereact/utils';
import type { HoverService } from '../hovers/hover-service';
import { TooltipEvent } from 'primereact/tooltip/tooltipoptions';

export interface MoreMemorySelectProps {
count: number;
Expand Down Expand Up @@ -98,6 +101,7 @@ export const MoreMemorySelect: React.FC<MoreMemorySelectProps> = ({ count, offse
interface MemoryTableProps extends TableRenderOptions, MemoryDisplayConfiguration {
memory?: Memory;
decorations: Decoration[];
hovers: HoverService;
offset: number;
count: number;
fetchMemory(partialOptions?: Partial<DebugProtocol.ReadMemoryArguments>): Promise<void>;
Expand All @@ -119,6 +123,7 @@ interface MemoryRowData {

interface MemoryTableState {
selection: DataTableCellSelection<MemoryRowData[]> | null;
hoverContent: React.ReactNode;
}

type MemorySizeOptions = Pick<MemoryTableProps, 'bytesPerWord' | 'wordsPerGroup' | 'groupsPerRow'>;
Expand Down Expand Up @@ -151,6 +156,7 @@ export class MemoryTable extends React.PureComponent<MemoryTableProps, MemoryTab
this.state = {
// eslint-disable-next-line no-null/no-null
selection: null,
hoverContent: <></>,
};
}

Expand Down Expand Up @@ -182,6 +188,11 @@ export class MemoryTable extends React.PureComponent<MemoryTableProps, MemoryTab

return (
<div className='flex-1 overflow-auto px-4'>
<Tooltip
onBeforeShow={this.handleOnBeforeTooltipShow}
onHide={this.handleOnTooltipHide}
target=".hoverable"
>{this.state.hoverContent}</Tooltip>
<DataTable<MemoryRowData[]>
ref={this.datatableRef}
{...props}
Expand Down Expand Up @@ -319,6 +330,27 @@ export class MemoryTable extends React.PureComponent<MemoryTableProps, MemoryTab
endAddress: startAddress + memoryTableOptions.bigWordsPerRow
};
}

protected handleOnBeforeTooltipShow = async (event: TooltipEvent): Promise<void> => {
const textContent = event.target.textContent ?? '';
const columnId = event.target.dataset.column ?? '';
let extraData = {};
try {
extraData = JSON.parse(event.target.dataset[columnId] ?? '{}');
} catch { /* no-op */ }
const node = await this.props.hovers.render({ columnId, textContent, extraData });
this.setState(prev => ({
...prev,
hoverContent: node,
}));
};

protected handleOnTooltipHide = (): void => {
this.setState(prev => ({
...prev,
hoverContent: <></>,
}));
};
}

export namespace MemoryTable {
Expand Down
3 changes: 3 additions & 0 deletions src/webview/components/memory-widget.tsx
Expand Up @@ -20,11 +20,13 @@ import { ColumnStatus } from '../columns/column-contribution-service';
import { Decoration, Endianness, Memory, MemoryDisplayConfiguration } from '../utils/view-types';
import { MemoryTable } from './memory-table';
import { OptionsWidget } from './options-widget';
import { HoverService } from '../hovers/hover-service';

interface MemoryWidgetProps extends MemoryDisplayConfiguration {
memory?: Memory;
title: string;
decorations: Decoration[];
hovers: HoverService;
columns: ColumnStatus[];
memoryReference: string;
offset: number;
Expand Down Expand Up @@ -80,6 +82,7 @@ export class MemoryWidget extends React.Component<MemoryWidgetProps, MemoryWidge
/>
<MemoryTable
decorations={this.props.decorations}
hovers={this.props.hovers}
columnOptions={this.props.columns.filter(candidate => candidate.active)}
memory={this.props.memory}
endianness={this.state.endianness}
Expand Down
89 changes: 89 additions & 0 deletions src/webview/hovers/address-hover.tsx
@@ -0,0 +1,89 @@
/********************************************************************************
* Copyright (C) 2024 Ericsson 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 * as React from 'react';
import { Radix } from '../../common/memory-range';
import { HoverContribution, MemoryDetails } from './hover-service';

export class AddressHover implements HoverContribution {
readonly id = 'address-hover';
priority = 0;
async render({ columnId, textContent, addressRadix }: MemoryDetails): Promise<React.ReactNode> {
if (columnId !== 'address') { return; }

let binary = '';
let octal = '';
let decimal = '';
let hexadecimal = '';
let num = 0;
let primaryRadix = '';

switch (addressRadix) {
case Radix.Binary:
primaryRadix = 'binary';
binary = textContent;
num = parseInt(binary, 2);
octal = num.toString(8);
decimal = num.toString(10);
hexadecimal = num.toString(16);
break;
case Radix.Octal:
primaryRadix = 'octal';
octal = textContent;
num = parseInt(octal, 8);
binary = num.toString(2);
decimal = num.toString(10);
hexadecimal = num.toString(16);
break;
case Radix.Decimal:
primaryRadix = 'decimal';
decimal = textContent;
num = parseInt(decimal, 10);
binary = num.toString(2);
octal = num.toString(8);
hexadecimal = num.toString(16);
break;
case Radix.Hexadecimal:
primaryRadix = 'hexadecimal';
hexadecimal = textContent;
num = parseInt(hexadecimal, 16);
binary = num.toString(2);
octal = num.toString(8);
decimal = num.toString(10);
break;
default: return;
}

const hexCodePoint = (parseInt(hexadecimal.slice(-6), 16) > 0x10FFFF)
? parseInt(hexadecimal.slice(-5), 16)
: parseInt(hexadecimal.slice(-6), 16);
const utf8 = String.fromCodePoint(hexCodePoint);

const hoverItem = (
<table className='address-hover'>
{Object.entries({ binary, octal, decimal, hexadecimal, utf8 }).map(([label, value]) =>
value
? <tr className={`label-value-pair ${label === primaryRadix ? 'primary' : ''}`}>
<td className='label'>{label}</td>
<td className='value'>{value}</td>
</tr>
: ''
)}
</table>
);
return hoverItem;
}
}
51 changes: 51 additions & 0 deletions src/webview/hovers/data-hover.tsx
@@ -0,0 +1,51 @@
/********************************************************************************
* Copyright (C) 2024 Ericsson 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 * as React from 'react';
import { HoverContribution, MemoryDetails } from './hover-service';

export class DataHover implements HoverContribution {
readonly id = 'data-hover';
priority = 0;
async render({ columnId, textContent }: MemoryDetails): Promise<React.ReactNode> {
if (columnId !== 'data') { return; }

const hexadecimal = textContent;
const num = parseInt(hexadecimal, 16);
const binary = num.toString(2);
const octal = num.toString(8);
const decimal = num.toString(10);

const hexCodePoint = (parseInt(hexadecimal.slice(-6), 16) > 0x10FFFF)
? parseInt(hexadecimal.slice(-5), 16)
: parseInt(hexadecimal.slice(-6), 16);
const utf8 = String.fromCodePoint(hexCodePoint);

const hoverItem = (
<table className='data-hover'>
{Object.entries({ binary, octal, decimal, hexadecimal, utf8 }).map(([label, value]) =>
value
? <tr className='label-value-pair'>
<td className='label'>{label}</td>
<td className='value'>{value}</td>
</tr>
: ''
)}
</table>
);
return hoverItem;
}
}

0 comments on commit 0aea797

Please sign in to comment.