From 43eeab58e58b66e8c8797bd9b147d5b67d2a6422 Mon Sep 17 00:00:00 2001
From: Allan Blanchard <allan.blanchard@cea.fr>
Date: Tue, 8 Aug 2023 08:00:22 +0200
Subject: [PATCH] [ivette/wp] first basic GUI

---
 ivette/src/frama-c/plugins/wp/api/index.ts    | 30 +++++++--------
 .../frama-c/plugins/wp/{vcs.tsx => goals.tsx} | 38 +++++++++++++++++--
 ivette/src/frama-c/plugins/wp/index.tsx       |  4 +-
 src/plugins/wp/wpApi.ml                       |  2 +-
 4 files changed, 52 insertions(+), 22 deletions(-)
 rename ivette/src/frama-c/plugins/wp/{vcs.tsx => goals.tsx} (66%)

diff --git a/ivette/src/frama-c/plugins/wp/api/index.ts b/ivette/src/frama-c/plugins/wp/api/index.ts
index b08c22d7609..8de39b88f57 100644
--- a/ivette/src/frama-c/plugins/wp/api/index.ts
+++ b/ivette/src/frama-c/plugins/wp/api/index.ts
@@ -152,7 +152,7 @@ export const getAvailableProvers: Server.GetRequest<null,prover[]>= getAvailable
 /** Data for array rows [`goals`](#goals)  */
 export interface goalsData {
   /** Entry identifier. */
-  wpo: Json.key<'#wpo'>;
+  wpo: goal;
   /** Property Marker */
   property: marker;
   /** Informal name */
@@ -178,7 +178,7 @@ export interface goalsData {
 /** Decoder for `goalsData` */
 export const jGoalsData: Json.Decoder<goalsData> =
   Json.jObject({
-    wpo: Json.jKey<'#wpo'>('#wpo'),
+    wpo: jGoal,
     property: jMarker,
     name: Json.jString,
     fct: Json.jOption(jFct),
@@ -194,10 +194,10 @@ export const jGoalsData: Json.Decoder<goalsData> =
 /** Natural order for `goalsData` */
 export const byGoalsData: Compare.Order<goalsData> =
   Compare.byFields
-    <{ wpo: Json.key<'#wpo'>, property: marker, name: string, fct?: fct,
-       bhv?: string, thy?: string, smoke: boolean, passed: boolean,
-       stats: stats, script?: string, saved: boolean }>({
-    wpo: Compare.string,
+    <{ wpo: goal, property: marker, name: string, fct?: fct, bhv?: string,
+       thy?: string, smoke: boolean, passed: boolean, stats: stats,
+       script?: string, saved: boolean }>({
+    wpo: byGoal,
     property: byMarker,
     name: Compare.string,
     fct: Compare.defined(byFct),
@@ -227,15 +227,14 @@ export const reloadGoals: Server.GetRequest<null,null>= reloadGoals_internal;
 
 const fetchGoals_internal: Server.GetRequest<
   number,
-  { reload: boolean, removed: Json.key<'#wpo'>[], updated: goalsData[],
-    pending: number }
+  { reload: boolean, removed: goal[], updated: goalsData[], pending: number }
   > = {
   kind: Server.RqKind.GET,
   name:   'plugins.wp.fetchGoals',
   input:  Json.jNumber,
   output: Json.jObject({
             reload: Json.jBoolean,
-            removed: Json.jArray(Json.jKey<'#wpo'>('#wpo')),
+            removed: Json.jArray(jGoal),
             updated: Json.jArray(jGoalsData),
             pending: Json.jNumber,
           }),
@@ -244,11 +243,10 @@ const fetchGoals_internal: Server.GetRequest<
 /** Data fetcher for array [`goals`](#goals)  */
 export const fetchGoals: Server.GetRequest<
   number,
-  { reload: boolean, removed: Json.key<'#wpo'>[], updated: goalsData[],
-    pending: number }
+  { reload: boolean, removed: goal[], updated: goalsData[], pending: number }
   >= fetchGoals_internal;
 
-const goals_internal: State.Array<Json.key<'#wpo'>,goalsData> = {
+const goals_internal: State.Array<goal,goalsData> = {
   name: 'plugins.wp.goals',
   getkey: ((d:goalsData) => d.wpo),
   signal: signalGoals,
@@ -257,13 +255,13 @@ const goals_internal: State.Array<Json.key<'#wpo'>,goalsData> = {
   order: byGoalsData,
 };
 /** Generated Goals */
-export const goals: State.Array<Json.key<'#wpo'>,goalsData> = goals_internal;
+export const goals: State.Array<goal,goalsData> = goals_internal;
 
 /** Default value for `goalsData` */
 export const goalsDataDefault: goalsData =
-  { wpo: Json.jKey<'#wpo'>('#wpo')(''), property: markerDefault, name: '',
-    fct: undefined, bhv: undefined, thy: undefined, smoke: false,
-    passed: false, stats: statsDefault, script: undefined, saved: false };
+  { wpo: goalDefault, property: markerDefault, name: '', fct: undefined,
+    bhv: undefined, thy: undefined, smoke: false, passed: false,
+    stats: statsDefault, script: undefined, saved: false };
 
 /** Proof Server Activity */
 export const serverActivity: Server.Signal = {
diff --git a/ivette/src/frama-c/plugins/wp/vcs.tsx b/ivette/src/frama-c/plugins/wp/goals.tsx
similarity index 66%
rename from ivette/src/frama-c/plugins/wp/vcs.tsx
rename to ivette/src/frama-c/plugins/wp/goals.tsx
index 96709a56189..e0956858d83 100644
--- a/ivette/src/frama-c/plugins/wp/vcs.tsx
+++ b/ivette/src/frama-c/plugins/wp/goals.tsx
@@ -21,23 +21,55 @@
 /* ************************************************************************ */
 
 import React from 'react';
-import { Table, Column } from 'dome/table/views';
+import { Icon } from 'dome/controls/icons';
+import { Table, Column, Renderer } from 'dome/table/views';
 import * as Ivette from 'ivette';
 import * as States from 'frama-c/states';
 import * as WP from 'frama-c/plugins/wp/api';
 
+const renderPassed: Renderer<boolean> =
+  (passed: boolean): JSX.Element =>
+  (<Icon
+    id={passed ? 'CIRC.CHECK' : 'CIRC.QUESTION'}
+    fill={passed ? `var(--positive-button-color)` : `var(--negative-button-color)`}
+  />);
+
 function WPGoals(): JSX.Element {
   const model = States.useSyncArrayModel(WP.goals);
+
+  // TODO: from AST selection, find WPO
+  const [_astSelection, updateAstSelection] = States.useSelection();
+  const [wpoSelection, setWpoSelection] = React.useState(WP.goalDefault);
+
+  const onWpoSelection = React.useCallback(
+    ({ wpo, property: marker, fct }: WP.goalsData) => {
+      const location = { fct, marker };
+      updateAstSelection({ location });
+      setWpoSelection(wpo);
+    }, [updateAstSelection],
+  );
+
   return (
-    <Table model={model} settings='wp.goals'>
+    <Table
+      model={model}
+      settings='wp.goals'
+      onSelection={onWpoSelection}
+      selection={wpoSelection}
+    >
+      <Column id='fct' label='Function' />
       <Column id='name' label='Names' />
+      <Column
+        id='passed'
+        label='Success'
+        render={renderPassed}
+      />
     </Table>
   );
 }
 
 
 Ivette.registerComponent({
-  id: 'frama-c.plugins.wp.vcs',
+  id: 'frama-c.plugins.wp.goals',
   group: 'frama-c.plugins',
   rank: 10,
   label: 'WP Goals',
diff --git a/ivette/src/frama-c/plugins/wp/index.tsx b/ivette/src/frama-c/plugins/wp/index.tsx
index 4369acea587..aa17e7478a6 100644
--- a/ivette/src/frama-c/plugins/wp/index.tsx
+++ b/ivette/src/frama-c/plugins/wp/index.tsx
@@ -25,7 +25,7 @@
 // --------------------------------------------------------------------------
 
 import * as Ivette from 'ivette';
-import './vcs';
+import './goals';
 import './style.css';
 
 // --------------------------------------------------------------------------
@@ -38,7 +38,7 @@ Ivette.registerView({
   label: 'WP View',
   layout: [
     ['frama-c.astview', 'frama-c.astinfo'],
-    'frama-c.plugins.wp.vcs',
+    'frama-c.plugins.wp.goals',
   ],
 });
 
diff --git a/src/plugins/wp/wpApi.ml b/src/plugins/wp/wpApi.ml
index 3fc1548fc16..61da7c4d7db 100644
--- a/src/plugins/wp/wpApi.ml
+++ b/src/plugins/wp/wpApi.ml
@@ -214,7 +214,7 @@ let _ = S.register_array ~package ~name:"goals"
     ~descr:(Md.plain "Generated Goals")
     ~key:(fun g -> g.Wpo.po_gid)
     ~keyName:"wpo"
-    ~keyType:(Jkey "wpo")
+    ~keyType:Goal.jtype
     ~iter:Wpo.iter_on_goals
     ~add_update_hook:Wpo.add_modified_hook
     ~add_remove_hook:Wpo.add_removed_hook
-- 
GitLab