Skip to content
Snippets Groups Projects
Commit 5897542b authored by Loïc Correnson's avatar Loïc Correnson
Browse files

[ivette/eva] stack layout

parent d1855e80
No related branches found
No related tags found
No related merge requests found
......@@ -490,11 +490,10 @@ export function useForceUpdate() {
export function useUpdate(...events: Event<any>[]) {
const fn = useForceUpdate();
React.useEffect(() => {
if (events.length === 0) events.push(update);
events.forEach((evt) => evt.on(fn));
return () => events.forEach((evt) => evt.off(fn));
}, [fn, ...events]); // eslint-disable-line react-hooks/exhaustive-deps
// The rule signals events is missing, probably because of « … »
const theEvents = events ? events.slice() : [update];
theEvents.forEach((evt) => evt.on(fn));
return () => theEvents.forEach((evt) => evt.off(fn));
});
}
// --------------------------------------------------------------------------
......
......@@ -24,7 +24,7 @@ import * as Ast from 'frama-c/api/kernel/ast';
// Locals
import { SizedArea, HSIZER, WSIZER } from './sized';
import { sizeof } from './cells';
import { RowKind } from './layout';
import { Row } from './layout';
import { Probe } from './probes';
import { Model, getModelInstance } from './model';
import './style.css';
......@@ -35,7 +35,7 @@ import './style.css';
function useModel(): Model {
const model = getModelInstance();
Dome.useUpdate(model.signal);
Dome.useUpdate(model.changed, model.laidout);
return model;
}
......@@ -90,8 +90,8 @@ function ProbeEditor() {
// --------------------------------------------------------------------------
interface TableCellProps {
kind: RowKind;
probe: Probe;
row: Row;
}
const CELLPADDING = 4;
......@@ -99,32 +99,36 @@ const CELLPADDING = 4;
function TableCell(props: TableCellProps) {
const model = useModel();
const [selection, setSelection] = States.useSelection();
const { probe, kind } = props;
const { probe, row } = props;
const { kind, callstack } = row;
const minWidth = CELLPADDING + WSIZER.dimension(probe.minCols);
const maxWidth = CELLPADDING + WSIZER.dimension(probe.maxCols);
const style = { width: minWidth, maxWidth };
let styling = 'dome-text-code';
let contents: React.ReactNode = props.probe.marker;
const { transient } = probe;
switch (kind) {
// ---- Probe Contents
case 'probes':
if (transient) {
styling = 'dome-text-label';
contents = '« Probe »';
contents = <span className="dome-text-label">« Probe »</span>;
} else {
const { rank, code, label } = probe;
const atpoint = rank && (
<span className='dome-text-code eva-probe-stmt'>@S{probe.rank}</span>
<span className="eva-probe-stmt">@S{rank}</span>
);
styling = 'dome-text-label';
contents = (
<>{label ?? code}{atpoint}</>
<span className="dome-text-label">{label ?? code}{atpoint}</span>
);
}
break;
// ---- Values Contents
case 'values':
case 'callstack':
{
const { values } = model.values.getValues(probe.marker);
const { values } = model.values.getValues(probe.marker, callstack);
const { cols, rows } = sizeof(values);
contents = (
<SizedArea cols={cols} rows={rows}>
......@@ -133,11 +137,13 @@ function TableCell(props: TableCellProps) {
);
}
break;
}
// --- Cell Packing
const isFocused = model.getFocused() === probe;
const className = classes(
'eva-cell',
styling,
transient && 'eva-transient',
!transient && isFocused && 'eva-focused',
);
......@@ -174,16 +180,23 @@ function TableRow(props: TableRowProps) {
if (!row) return null;
const { kind, probes } = row;
const className = `eva-${kind}`;
const sk = row.stackIndex;
const header = row.stacks && (
<div className="eva-cell eva-stack">
{sk === undefined ? '#' : `${1 + sk}`}
</div>
);
const contents = probes.map((probe) => (
<TableCell
key={probe.marker}
kind={kind}
probe={probe}
row={row}
/>
));
return (
<Hpack className={className} style={props.style}>
<div className="eva-row">
{header}
{contents}
</div>
<Filler />
......@@ -205,16 +218,13 @@ function ValuesPanel(props: Dimension) {
const { width, height } = props;
// --- reset line cache
const listRef = React.useRef<VariableSizeList>(null);
const forceGridLayout = React.useCallback(
() => {
const vlist = listRef.current;
if (vlist) vlist.resetAfterIndex(0, true);
},
[listRef],
);
Dome.useEvent(model.laidout, () => {
const vlist = listRef.current;
if (vlist) vlist.resetAfterIndex(0, true);
});
// --- compute line height
const getRowHeight = React.useCallback(
(k: number) => HSIZER.dimension(model.getRowHeight(k)),
(k: number) => HSIZER.dimension(model.getRowLines(k)),
[model],
);
// --- compute layout
......@@ -223,7 +233,7 @@ function ValuesPanel(props: Dimension) {
const [selection] = States.useSelection();
React.useEffect(() => {
const target = Ast.jMarker(selection?.current?.marker);
model.setLayout({ margin, target }, forceGridLayout);
model.setLayout({ margin, target });
});
// --- render list
return (
......
......@@ -2,8 +2,10 @@
/* --- Layout ---*/
/* --------------------------------------------------------------------------*/
import { Size, EMPTY, addH, ValueCache } from './cells';
import { callstack } from 'frama-c/api/plugins/eva/values';
import { Probe } from './probes';
import { StacksCache } from './stacks';
import { Size, EMPTY, addH, ValueCache } from './cells';
export interface LayoutProps {
zoom?: number;
......@@ -16,7 +18,11 @@ export interface Row {
key: string;
kind: RowKind;
probes: Probe[];
height: number;
headstack?: string;
stacks?: number;
stackIndex?: number;
callstack?: callstack;
hlines: number;
}
/* --------------------------------------------------------------------------*/
......@@ -31,16 +37,19 @@ export class LayoutEngine {
// --- Setup
private readonly cache: ValueCache;
private readonly values: ValueCache;
private readonly stacks: StacksCache;
private readonly hcrop: number;
private readonly vcrop: number;
private readonly margin: number;
constructor(
cache: ValueCache,
props: undefined | LayoutProps,
values: ValueCache,
stacks: StacksCache,
) {
this.cache = cache;
this.values = values;
this.stacks = stacks;
const zoom = Math.max(0, props?.zoom ?? 0);
this.vcrop = VCROP + 2 * zoom;
this.hcrop = HCROP + zoom;
......@@ -49,6 +58,7 @@ export class LayoutEngine {
}
// --- Probe Buffer
private byStacks?: string; // stmt
private rowSize: Size = EMPTY;
private buffer: Probe[] = [];
private rows: Row[] = [];
......@@ -61,33 +71,64 @@ export class LayoutEngine {
}
push(p: Probe) {
const probeSize = this.cache.getProbeSize(p.marker);
const probeSize = this.values.getProbeSize(p.marker);
const s = this.crop(probeSize);
p.minCols = s.cols;
p.maxCols = Math.max(p.minCols, probeSize.cols);
if (s.cols + this.rowSize.cols > this.margin) this.flush();
const stmt = p.byCallstacks ? p.stmt : undefined;
if (stmt !== this.byStacks) {
this.flush();
this.byStacks = stmt;
}
if (!stmt && s.cols + this.rowSize.cols > this.margin)
this.flush();
this.rowSize = addH(this.rowSize, s);
this.rowSize.cols += PADDING;
this.buffer.push(p);
}
// --- Flush Buffer
// --- Flush Rows
flush(): Row[] {
const ps = this.buffer;
const rs = this.rows;
if (ps.length > 0) {
const n = rs.length;
rs.push({
key: `P${n}`,
kind: 'probes',
probes: ps,
height: 1,
}, {
key: `V${n}`,
kind: 'values',
probes: ps,
height: this.rowSize.rows,
});
const stmt = this.byStacks;
if (stmt) {
// --- by callstacks
const wcs = this.stacks.getStacks(stmt);
rs.push({
key: `P${stmt}`,
kind: 'probes',
probes: ps,
stacks: wcs.length,
hlines: 1,
});
wcs.forEach((cs, k) => {
rs.push({
key: `C${cs}`,
kind: 'callstack',
probes: ps,
stackIndex: k,
stacks: wcs.length,
hlines: this.values.getStackSize(cs).rows,
});
});
} else {
// --- by callstacks
const n = rs.length;
rs.push({
key: `P${n}`,
kind: 'probes',
probes: ps,
hlines: 1,
}, {
key: `V${n}`,
kind: 'values',
probes: ps,
hlines: this.rowSize.rows,
});
}
}
this.buffer = [];
this.rowSize = EMPTY;
......
......@@ -14,7 +14,7 @@ import * as Ast from 'frama-c/api/kernel/ast';
// Model
import { Probe } from './probes';
import { StacksCache } from './stacks';
import { callback, StateCallbacks, ValueCache } from './cells';
import { StateCallbacks, ValueCache } from './cells';
import { LayoutProps, LayoutEngine, Row } from './layout';
export interface ModelLayout extends LayoutProps {
......@@ -35,7 +35,7 @@ export class Model implements StateCallbacks {
this.setLayout = throttle(this.setLayout.bind(this), 300);
this.getRowKey = this.getRowKey.bind(this);
this.getRowCount = this.getRowCount.bind(this);
this.getRowHeight = this.getRowHeight.bind(this);
this.getRowLines = this.getRowLines.bind(this);
Server.onSignal(Values.changed, this.forceReload);
}
......@@ -76,6 +76,36 @@ export class Model implements StateCallbacks {
private layout: ModelLayout = { margin: 80 };
private rows: Row[] = [];
getRow(index: number): Row | undefined {
return this.rows[index];
}
getRowCount() {
return this.rows.length;
}
getRowKey(index: number): string {
const row = this.rows[index];
return row ? row.key : `#${index}`;
}
getRowLines(index: number): number {
const row = this.rows[index];
return row ? row.hlines : 0;
}
// --- Throttled
setLayout(ly: ModelLayout) {
if (!equal(this.layout, ly)) {
this.layout = ly;
const target = Ast.jMarker(ly.target);
this.selected = target && this.getProbe(target);
this.forceUpdate();
}
}
// --- Recompute Layout
private computeLayout() {
this.forcedLayout = false;
const s = this.selected;
......@@ -98,39 +128,14 @@ export class Model implements StateCallbacks {
toLayout.push(p);
}
});
const engine = new LayoutEngine(this.values, this.layout);
const engine = new LayoutEngine(
this.layout,
this.values,
this.stacks,
);
toLayout.sort(Probe.order).forEach(engine.push);
this.rows = engine.flush();
this.forceUpdate();
}
getRow(index: number): Row | undefined {
return this.rows[index];
}
getRowCount() {
return this.rows.length;
}
getRowKey(index: number): string {
const row = this.rows[index];
return row ? row.key : `#${index}`;
}
getRowHeight(index: number): number {
const row = this.rows[index];
return row ? row.height : 0;
}
// --- Throttled
setLayout(ly: ModelLayout, forceGridLayout: callback) {
if (!equal(this.layout, ly)) {
this.layout = ly;
const target = Ast.jMarker(ly.target);
this.selected = target && this.getProbe(target);
this.forceLayout();
forceGridLayout();
}
this.laidout.emit();
}
// --- Force Reload (empty caches)
......@@ -144,6 +149,10 @@ export class Model implements StateCallbacks {
this.forceLayout();
}
// --- Events
readonly changed = new Dome.Event('eva-changed');
readonly laidout = new Dome.Event('eva-laidout');
// --- Force Layout
forceLayout() {
if (!this.forcedLayout) {
......@@ -153,10 +162,7 @@ export class Model implements StateCallbacks {
}
// --- Foce Update
readonly signal = new Dome.Event('eva-force-update');
forceUpdate() {
this.signal.emit();
}
forceUpdate() { this.changed.emit(); }
}
......
......@@ -71,6 +71,13 @@
/* --- Table Cells --- */
/* -------------------------------------------------------------------------- */
.eva-stack {
width: 12px;
padding-top: 1px;
color: #777;
text-align: center;
}
.eva-cell {
flex: 1 1 auto;
border-left: thin solid black;
......@@ -121,4 +128,8 @@
background: #def6ff;
}
.eva-cell.eva-stack {
background: #eee;
}
/* -------------------------------------------------------------------------- */
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment