diff --git a/ivette/src/frama-c/server.ts b/ivette/src/frama-c/server.ts index 990ad2cc51d520b8f6f898c009bbf4c0572bf1cb..d400a9745fe2cb5429a904414641a6def97d3a0e 100644 --- a/ivette/src/frama-c/server.ts +++ b/ivette/src/frama-c/server.ts @@ -756,7 +756,10 @@ export interface Killable<Data> extends Promise<Data> { * You may _kill_ the request before its normal termination by * invoking `kill()` on the returned promised. */ -export function send<In, Out>(request: Request<RqKind, In, Out>, param: In): Killable<Out> { +export function send<In, Out>( + request: Request<RqKind, In, Out>, + param: In, +): Killable<Out> { if (!isRunning()) return Promise.reject(new Error('Server not running')); if (!request.name) return Promise.reject(new Error('Undefined request')); const rid = `RQ.${rqCount}`; diff --git a/ivette/src/frama-c/states.ts b/ivette/src/frama-c/states.ts index 7cabcbad64ff3c851c1cc28e1b747bdf79e0626f..fdb800f823f6122c261fb0e74d7055f096ade0ff 100644 --- a/ivette/src/frama-c/states.ts +++ b/ivette/src/frama-c/states.ts @@ -130,11 +130,12 @@ export function useRequest<In, Out>( const project = useProject(); const [response, setResponse] = React.useState<Out | undefined>(options.offline ?? undefined); - const footprint = project ? JSON.stringify([project, rq.name, params]) : undefined; + const footprint = + project ? JSON.stringify([project, rq.name, params]) : undefined; const update = (opt: Out | undefined | null) => { if (opt !== null) setResponse(opt); - } + }; async function trigger() { if (project && rq && params !== undefined) { @@ -169,7 +170,7 @@ export type Tag = { name: string; label?: string; descr?: string; -} +}; const holdCurrent = { offline: null, pending: null, onError: null }; @@ -179,9 +180,10 @@ export function useTags(rq: GetTags): Map<string, Tag> { const tags = useRequest(rq, null, holdCurrent); return React.useMemo(() => { const m = new Map<string, Tag>(); - tags && tags.forEach((tg) => m.set(tg.name, tg)); + if (tags !== undefined) + tags.forEach((tg) => m.set(tg.name, tg)); return m; - }, tags); + }, [tags]); } // -------------------------------------------------------------------------- @@ -205,7 +207,7 @@ export interface Fetches<K, A> { reload: boolean; pending: number; updated: A[]; - removed: Json.key<K>[] + removed: Json.key<K>[]; } export interface Array<K, A> { @@ -216,7 +218,7 @@ export interface Array<K, A> { reload: Server.GetRequest<null, null>; } -type id = { project: string, state: string }; +type id = { project: string; state: string }; // -------------------------------------------------------------------------- // --- Handler for Synchronized St byates @@ -265,7 +267,8 @@ class SyncState<A> { Dome.emit(this.UPDATE); } catch (error) { PP.error( - `Fail to set value of syncState '${this.handler.name}'. ${error.toString()}`, + `Fail to set value of syncState '${this.handler.name}'.`, + `${error.toString()}`, ); } } @@ -277,7 +280,10 @@ class SyncState<A> { this.value = v; Dome.emit(this.UPDATE); } catch (error) { - PP.error(`Fail to update syncState '${this.handler.name}'. ${error.toString()}`); + PP.error( + `Fail to update syncState '${this.handler.name}'.`, + `${error.toString()}`, + ); } } } @@ -307,7 +313,9 @@ Server.onShutdown(() => syncStates.clear()); /** Synchronization with a (projectified) server state. */ -export function useSyncState<A>(st: State<A>): [A | undefined, (value: A) => void] { +export function useSyncState<A>( + st: State<A>, +): [A | undefined, (value: A) => void] { const s = getSyncState(st); Dome.useUpdate(PROJECT, s.UPDATE); Server.useSignal(s.handler.signal, s.update); @@ -328,20 +336,13 @@ export function useSyncValue<A>(va: Value<A>): A | undefined { // --- Synchronized Arrays // -------------------------------------------------------------------------- -export interface Model<A> { - clear(): void; - remove(key: string): void; - add(entry: A): void; - clear(): void; -} - // one per project class SyncArray<K, A> { handler: Array<K, A>; - model: Model<A>; + model: ArrayModel<A>; insync: boolean; - constructor(h: Array<K, A>, m?: Model<A>) { + constructor(h: Array<K, A>, m?: ArrayModel<A>) { this.handler = h; this.insync = false; this.model = m ?? new ArrayModel<A>(h.key); @@ -354,7 +355,7 @@ class SyncArray<K, A> { this.insync = true; const data = await Server.send(this.handler.fetch, 50); const { reload = false, removed = [], updated = [], pending = 0 } = data; - const model = this.model; + const { model } = this; if (reload) model.clear(); removed.forEach((k) => model.remove(k)); updated.forEach((d) => model.add(d)); @@ -388,18 +389,19 @@ class SyncArray<K, A> { const syncArrays = new Map<id, SyncArray<any, any>>(); -function getSyncArray<K, A>(arr: Array<K, A>, model?: Model<A>): SyncArray<K, A> { +function getSyncArray<K, A>( + arr: Array<K, A>, + model?: ArrayModel<A>, +): SyncArray<K, A> { const id = { project: currentProject ?? '', state: arr.name }; let a = syncArrays.get(id); if (!a) { a = new SyncArray(arr, model); syncArrays.set(id, a); - } else { - if (model && a.model !== model) { - model.clear(); - a.reload(); - a.model = model; - } + } else if (model && a.model !== model) { + model.clear(); + a.reload(); + a.model = model; } return a; } @@ -420,7 +422,10 @@ export function reloadArray<K, A>(arr: Array<K, A>) { /** Use Synchronized Array (Custom React Hook). */ -export function useSyncArray<K, A>(arr: Array<K, A>, model?: Model<A>): Model<A> { +export function useSyncArray<K, A>( + arr: Array<K, A>, + model?: ArrayModel<A>, +): ArrayModel<A> { const a = getSyncArray(arr, model); Dome.useUpdate(PROJECT); Server.useSignal(arr.signal, a.fetch); @@ -431,7 +436,8 @@ export function useSyncArray<K, A>(arr: Array<K, A>, model?: Model<A>): Model<A> // --- Selection // -------------------------------------------------------------------------- -type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U]; +type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = + Partial<T> & U[keyof U]; export interface FullLocation { /** Function name. */ diff --git a/ivette/src/renderer/ASTview.tsx b/ivette/src/renderer/ASTview.tsx index d786d7cf0384dced4a6d83799be9a40f57214523..5ec7d4272ee271fe08ada8ae7259e25ae2c9f4cc 100644 --- a/ivette/src/renderer/ASTview.tsx +++ b/ivette/src/renderer/ASTview.tsx @@ -7,11 +7,15 @@ import * as Server from 'frama-c/server'; import * as States from 'frama-c/states'; import * as Dome from 'dome'; +import { key } from 'dome/data/json'; import { RichTextBuffer } from 'dome/text/buffers'; import { Text } from 'dome/text/editors'; import { IconButton } from 'dome/controls/buttons'; import { Component, TitleBar } from 'frama-c/LabViews'; +import { printFunction, markerInfo } from 'api/kernel/ast'; + + import 'codemirror/mode/clike/clike'; import 'codemirror/theme/ambiance.css'; import 'codemirror/theme/solarized.css'; @@ -41,10 +45,7 @@ async function loadAST( buffer.log('// Loading', theFunction, '…'); (async () => { try { - const data = await Server.GET({ - endpoint: 'kernel.ast.printFunction', - params: theFunction, - }); + const data = await Server.send(printFunction, theFunction); buffer.operation(() => { buffer.clear(); if (!data) { @@ -77,7 +78,7 @@ const ASTview = () => { const [theme, setTheme] = Dome.useGlobalSetting('ASTview.theme', 'default'); const [fontSize, setFontSize] = Dome.useGlobalSetting('ASTview.fontSize', 12); const [wrapText, setWrapText] = Dome.useSwitch('ASTview.wrapText', false); - const markers = States.useSyncArray('kernel.ast.markerKind'); + const markers = States.useSyncArray(markerInfo); const theFunction = selection?.current?.function; const theMarker = selection?.current?.marker; @@ -99,15 +100,15 @@ const ASTview = () => { const zoomIn = () => fontSize < 48 && setFontSize(fontSize + 2); const zoomOut = () => fontSize > 4 && setFontSize(fontSize - 2); - function onTextSelection(id: string) { + function onTextSelection(id: key<'#markerIndo'>) { if (selection.current) { const location = { ...selection.current, marker: id }; updateSelection({ location }); } } - function onContextMenu(id: string) { - const marker = markers[id]; + function onContextMenu(id: key<'#markerInfo'>) { + const marker = markers.getData(id); if (marker && marker.kind === 'function') { const item = { label: `Go to definition of ${marker.name}`, diff --git a/ivette/src/renderer/Globals.tsx b/ivette/src/renderer/Globals.tsx index 62a3c702e9b56067a1c3acb5cf63148e1867e5bb..94a501716c0d18f29a6ed499b6cbf46e716ddb99 100644 --- a/ivette/src/renderer/Globals.tsx +++ b/ivette/src/renderer/Globals.tsx @@ -3,22 +3,11 @@ // -------------------------------------------------------------------------- import React from 'react'; -import { toArray, Dictionary } from 'lodash'; +import * as Dome from 'dome'; import { Section, Item } from 'dome/frame/sidebars'; import * as States from 'frama-c/states'; import { alpha } from 'dome/data/compare'; - -// -------------------------------------------------------------------------- -// --- Globals API -// -------------------------------------------------------------------------- - -interface Gfun { - key: string; - name: string; - signature: string; -} - -type Gfuns = undefined | Dictionary<Gfun>; +import { functions, functionsData } from 'api/kernel/ast'; // -------------------------------------------------------------------------- // --- Globals Section @@ -28,13 +17,25 @@ export default () => { // Hooks const [selection, updateSelection] = States.useSelection(); - const gfuns: Gfuns = States.useSyncArray('kernel.ast.functions'); + const model = States.useSyncArray(functions); + const forceUpdate = Dome.useForceUpdate() as (() => void); + React.useEffect(() => { + model.setNaturalOrder((f1, f2) => alpha(f1.name, f2.name)); + const client = model.link(); + client.onReload(forceUpdate); + client.onUpdate(forceUpdate); + return client.unlink; + }, [model, forceUpdate]); // Functions - const functions = toArray(gfuns).sort((f1, f2) => alpha(f1.name, f2.name)); + const n = model.getRowCount(); + const fcts: functionsData[] = []; + for (let i = 0; i < n; i++) { + fcts.push(model.getRowAt(i)); + } const current: undefined | string = selection?.current?.function; - const makeFctItem = (fct: Gfun) => { + const makeFctItem = (fct: functionsData) => { const kf = fct.name; return ( <Item @@ -49,7 +50,7 @@ export default () => { return ( <Section label="Functions"> - {functions.map(makeFctItem)} + {fcts.map(makeFctItem)} </Section> ); diff --git a/ivette/src/renderer/Properties.tsx b/ivette/src/renderer/Properties.tsx index ea50893a20a871bc6cfa182fa547e3f99796ab26..6e44c4a4d550204ecd981c136c6af1170f36804e 100644 --- a/ivette/src/renderer/Properties.tsx +++ b/ivette/src/renderer/Properties.tsx @@ -87,7 +87,10 @@ const defaultFilter = }; -function filterStatus(f: typeof defaultStatusFilter, status: Properties.propStatus) { +function filterStatus( + f: typeof defaultStatusFilter, + status: Properties.propStatus, +) { switch (status) { case 'valid': case 'valid_but_dead': return f.valid; @@ -104,7 +107,10 @@ function filterStatus(f: typeof defaultStatusFilter, status: Properties.propStat } } -function filterKind(f: typeof defaultKindFilter, kind: Properties.propKind) { +function filterKind( + f: typeof defaultKindFilter, + kind: Properties.propKind, +) { switch (kind) { case 'assert': return f.assert; case 'loop_invariant': @@ -232,7 +238,7 @@ const byColumn: Arrays.ByColumns<Property> = { file: Compare.byFields<Property>({ source: byFile }), }; -class PropertyModel extends Arrays.ArrayModel<Property> implements States.Model<Property> { +class PropertyModel extends Arrays.ArrayModel<Property> { private filterFun?: string; private filterProp = _.cloneDeep(defaultFilter); @@ -431,7 +437,7 @@ const RenderTable = () => { // Hooks const model = React.useMemo(() => new PropertyModel(), []); - States.useSyncArray<"#status", Property>(Properties.status, model); + States.useSyncArray<'#status', Property>(Properties.status, model); const [selection, updateSelection] = States.useSelection();