From b2e0be9fce7d87188603e9afe2786798644dc142 Mon Sep 17 00:00:00 2001
From: rlazarini <remi.lazarini@cea.fr>
Date: Tue, 7 Jan 2025 16:25:35 +0100
Subject: [PATCH] [Ivette] added doc Callgraph + added color in markdown
 iconTag + added markdown ledTag

---
 .../src/dome/renderer/controls/displays.tsx   |   7 +-
 ivette/src/dome/renderer/controls/icons.tsx   |   7 +-
 ivette/src/dome/renderer/controls/style.css   |   2 +-
 ivette/src/dome/renderer/style.css            |   2 +-
 ivette/src/dome/renderer/text/markdown.tsx    |  30 ++++-
 .../frama-c/plugins/callgraph/callgraph.css   |   5 -
 .../plugins/callgraph/components/node.tsx     |  18 ++-
 .../callgraph/components/threeStateButton.tsx |  10 +-
 .../plugins/callgraph/components/titlebar.tsx |  52 ++++----
 .../plugins/callgraph/components/toolbar.tsx  | 111 +++++++++++++++++-
 ivette/src/frama-c/plugins/plugins.md         |  80 +++++++++++++
 11 files changed, 264 insertions(+), 60 deletions(-)
 create mode 100644 ivette/src/frama-c/plugins/plugins.md

diff --git a/ivette/src/dome/renderer/controls/displays.tsx b/ivette/src/dome/renderer/controls/displays.tsx
index 3363c1486fc..dc1c52d1479 100644
--- a/ivette/src/dome/renderer/controls/displays.tsx
+++ b/ivette/src/dome/renderer/controls/displays.tsx
@@ -62,8 +62,11 @@ export function LCD(props: LabelProps): JSX.Element {
 // --- Led
 // --------------------------------------------------------------------------
 
-export type LEDstatus =
-  undefined | 'inactive' | 'active' | 'positive' | 'negative' | 'warning';
+export const LEDStatusList = [
+  'active', 'inactive', 'positive', 'negative', 'warning'
+] as const;
+
+export type LEDstatus = typeof LEDStatusList[number] | undefined;
 
 export interface LEDprops {
   /**
diff --git a/ivette/src/dome/renderer/controls/icons.tsx b/ivette/src/dome/renderer/controls/icons.tsx
index 3cfff841d62..e0055388c0f 100644
--- a/ivette/src/dome/renderer/controls/icons.tsx
+++ b/ivette/src/dome/renderer/controls/icons.tsx
@@ -95,8 +95,11 @@ export function SVG(props: SVGprops): null | JSX.Element {
 // --- Icon Component
 // --------------------------------------------------------------------------
 
-export type IconKind =
-  'disabled' | 'selected' | 'positive' | 'negative' | 'warning' | 'default';
+export const iconKindList = [
+  'disabled', 'selected', 'positive', 'negative', 'warning', 'default'
+] as const;
+
+export type IconKind = typeof iconKindList[number]
 
 /** Icon Component Properties */
 export interface IconProps extends SVGprops {
diff --git a/ivette/src/dome/renderer/controls/style.css b/ivette/src/dome/renderer/controls/style.css
index 2d21b7bcd7c..126ed6edcd9 100644
--- a/ivette/src/dome/renderer/controls/style.css
+++ b/ivette/src/dome/renderer/controls/style.css
@@ -267,7 +267,7 @@
 }
 
 .dome-xButton-led {
-    display: inline ;
+    display: inline-block ;
     position: relative ;
     border-color: var(--border) ;
     border-style: solid ;
diff --git a/ivette/src/dome/renderer/style.css b/ivette/src/dome/renderer/style.css
index 0c341b788d3..f3f86601cd0 100644
--- a/ivette/src/dome/renderer/style.css
+++ b/ivette/src/dome/renderer/style.css
@@ -278,7 +278,7 @@ input[type="checkbox"]:checked {
 }
 
 .dome-xModal-overlay {
-  z-index: 100;
+  z-index: 1000;
   position: fixed;
   top: 0;
   left: 0;
diff --git a/ivette/src/dome/renderer/text/markdown.tsx b/ivette/src/dome/renderer/text/markdown.tsx
index 8641cc6e7a7..64c924b2fbd 100644
--- a/ivette/src/dome/renderer/text/markdown.tsx
+++ b/ivette/src/dome/renderer/text/markdown.tsx
@@ -26,22 +26,37 @@ import remarkCustomHeaderId from 'remark-custom-header-id';
 
 import * as Themes from 'dome/themes';
 import { classes } from 'dome/misc/utils';
-import { Icon } from 'dome/controls/icons';
+import { Icon, iconKindList } from 'dome/controls/icons';
 import {
   CodeBlock, atomOneDark, atomOneLight
 } from "react-code-blocks";
+import { LED, LEDStatusList } from 'dome/controls/displays';
+
 export interface Pattern {
   pattern: RegExp,
   replace: (key: number, match?: RegExpExecArray) => JSX.Element | null
 }
 
 export const iconTag: Pattern = {
-  pattern: /(\[ex:\])?\[icon-([^\]]+)\]/g,
+  pattern: /(\[ex:\])?\[icon-([^-\]]+)(-([^\]]+))?\]/g,
   replace: (key: number, match?: RegExpExecArray) => {
-    if(match && match[1] === "[ex:]") {
-      return <span key={key}>{`[icon-${match[2]}]`}</span>;
+    if(!match) return null;
+    const id = match[2];
+    const kind = iconKindList.find(elt => elt === match[4]);
+    const color = !kind ? match[4] : undefined;
+    if(match[1] === "[ex:]") {
+      return <span key={key}>{`[icon-${id}-${match[4]}]`}</span>;
     }
-    return match ? <Icon key={key} id={match[2]}/> : null;
+    return <Icon key={key} id={id} kind={kind} fill={color}/>;
+  }
+};
+
+export const ledTag: Pattern = {
+  pattern: /\[led-([^\]]+)\]/g,
+  replace: (key: number, match?: RegExpExecArray) => {
+    if(!match) return null;
+    const status = LEDStatusList.find(elt => elt === match[1]);
+    return <LED key={key} status={status} />;
   }
 };
 
@@ -120,6 +135,7 @@ export function Markdown(
   const markdownClasses = classes(
     "dome-xMarkdown", "dome-pages", className
   );
+  let liKey: number = 0;
 
   const scroll = (id: string): void => {
     const elt = document.getElementById(id);
@@ -134,7 +150,9 @@ export function Markdown(
   };
   options.components = {
     p: ({ children }) => <div>{replaceTags(children, patterns)}</div>,
-    li: ({ children }) => <li>{replaceTags(children, patterns)}</li>,
+    li: ({ children }) => {
+      return <li key={liKey++}>{replaceTags(children, patterns)}</li>;
+    },
     /** Uses codeBlock if ``` is used in markdown with a language,
      *  otherwise the code-inline class is added */
     code: ({ className, children }) => {
diff --git a/ivette/src/frama-c/plugins/callgraph/callgraph.css b/ivette/src/frama-c/plugins/callgraph/callgraph.css
index 758c026c0ea..fb03f827b38 100644
--- a/ivette/src/frama-c/plugins/callgraph/callgraph.css
+++ b/ivette/src/frama-c/plugins/callgraph/callgraph.css
@@ -82,7 +82,6 @@
   .node-graph {
     display: flex;
     align-items: center;
-    display: flex;
     background-color: rgb(from var(--background-profound) r g b / .5);
     padding: .5em;
     border-radius: .5em;
@@ -96,10 +95,6 @@
     &.node-selected {
       border: solid var(--activated-button-color) 2px;
     }
-
-    .dome-xButton-led {
-      display: block;
-    }
   }
 
   .cg-display-mode {
diff --git a/ivette/src/frama-c/plugins/callgraph/components/node.tsx b/ivette/src/frama-c/plugins/callgraph/components/node.tsx
index 39eb1eb75b6..2eec2ab21f2 100644
--- a/ivette/src/frama-c/plugins/callgraph/components/node.tsx
+++ b/ivette/src/frama-c/plugins/callgraph/components/node.tsx
@@ -42,16 +42,14 @@ const isTaintedScope = (node: NodeObject3D<CGNode>): boolean => {
 
 const getNodeAlarms = (node: CGNode): JSX.Element => {
   return <>
-    <div>
-      {node.alarmStatuses && node.alarmStatuses.invalid > 0 && LED({
-        status: "negative",
-        title: node.alarmStatuses.invalid+" invalid",
-        })}
-      {node.alarmStatuses && node.alarmStatuses.unknown > 0 && LED({
-        status: "warning",
-        title: node.alarmStatuses.unknown+" unknown",
-        })}
-    </div>
+    {node.alarmStatuses && node.alarmStatuses.invalid > 0 && LED({
+      status: "negative",
+      title: node.alarmStatuses.invalid+" invalid",
+      })}
+    {node.alarmStatuses && node.alarmStatuses.unknown > 0 && LED({
+      status: "warning",
+      title: node.alarmStatuses.unknown+" unknown",
+      })}
   </>;
 };
 
diff --git a/ivette/src/frama-c/plugins/callgraph/components/threeStateButton.tsx b/ivette/src/frama-c/plugins/callgraph/components/threeStateButton.tsx
index 83eea62d370..fc104952a46 100644
--- a/ivette/src/frama-c/plugins/callgraph/components/threeStateButton.tsx
+++ b/ivette/src/frama-c/plugins/callgraph/components/threeStateButton.tsx
@@ -32,14 +32,16 @@ export interface IThreeStateButton {
   value: number,
 }
 
+export type TThreesButtonState = [
+  IThreeStateButton,
+  (newValue: IThreeStateButton) => void
+];
+
 interface ThreeStateButtonProps {
   label?: string;
   icon?: string;
   title?: string;
-  buttonState: [
-    IThreeStateButton,
-    (newValue: IThreeStateButton) => void
-  ];
+  buttonState: TThreesButtonState;
 }
 
 export function ThreeStateButton(
diff --git a/ivette/src/frama-c/plugins/callgraph/components/titlebar.tsx b/ivette/src/frama-c/plugins/callgraph/components/titlebar.tsx
index a83e80787b7..6ecf4e8128f 100644
--- a/ivette/src/frama-c/plugins/callgraph/components/titlebar.tsx
+++ b/ivette/src/frama-c/plugins/callgraph/components/titlebar.tsx
@@ -26,30 +26,26 @@ import * as Ivette from 'ivette';
 import * as Dome from 'dome';
 
 import { IconButton } from 'dome/controls/buttons';
-import { Inset } from 'dome/frame/toolbars';
-import * as Dialogs from 'dome/dialogs';
+import { Button, Inset } from 'dome/frame/toolbars';
+import { HelpIcon } from 'dome/help';
+import docPlugins from '../../plugins.md?raw';
+import { DocShowNodesButton } from './toolbar';
+import { ledTag, iconTag } from 'dome/text/markdown';
 
-// Help popup
-async function displayShortcuts(): Promise<void> {
-  await Dialogs.showMessageBox({
-    buttons: [{ label: "Ok" }],
-    details: (
-      'In the graph:\n' +
-      '  - Left-click: rotate the graph\n' +
-      '  - Right-click: move in the graph\n' +
-      '  - Mouse-wheel: zoom\n' +
-      '\n' +
-      'On nodes:\n' +
-      '  - Left-Click: select node (in the graph)\n'+
-      '  - Ctrl+click: add node to the selected nodes (multi-selection)\n' +
-      '  - Alt+click: select function (in all Ivette components)\n' +
-      '\n' +
-      'Function filters (in the titlebar of this component) are synchronized ' +
-      'with the filter of the functions sidebar.'
-    ),
-    message: 'Callgraph Help',
-  });
-}
+export const TSButtonTag = {
+  pattern: /\[button-displaymode\]/g,
+  replace: (key: number, match?: RegExpExecArray) => {
+    return match ? <span key={key}>{DocShowNodesButton()}</span> : null;
+  }
+};
+
+export const selectBtnTag = {
+  pattern: /\[button-select\]/g,
+  replace: (key: number, match?: RegExpExecArray) => {
+    return match ? <Button key={key} label="Select" title={`Nodes selection`}/>
+      : null;
+  }
+};
 
 /* -------------------------------------------------------------------------- */
 /* --- Callgraph titlebar component                                       --- */
@@ -89,11 +85,11 @@ export function CallgraphTitleBar(props: CallgraphTitleBarProps): JSX.Element {
         title={"Automatically select node of the function selected in AST"}
       />
       <Inset />
-      <IconButton
-        icon="HELP"
-        onClick={displayShortcuts}
-        title='Callgraph help'
-      />
+      <HelpIcon
+        label='Plugins - Callgraph'
+        scrollTo={'plugins-callgraph'}
+        patterns={[iconTag, ledTag, selectBtnTag, TSButtonTag]}
+      >{ docPlugins }</HelpIcon>
       <Inset />
     </Ivette.TitleBar>
   );
diff --git a/ivette/src/frama-c/plugins/callgraph/components/toolbar.tsx b/ivette/src/frama-c/plugins/callgraph/components/toolbar.tsx
index 2154ae91ebc..9f495ab78da 100644
--- a/ivette/src/frama-c/plugins/callgraph/components/toolbar.tsx
+++ b/ivette/src/frama-c/plugins/callgraph/components/toolbar.tsx
@@ -27,16 +27,125 @@ import * as Dome from 'dome';
 import { State } from 'dome/data/states';
 import {  Spinner } from 'dome/controls/buttons';
 import { ToolBar, ButtonGroup, Button, Filler } from 'dome/frame/toolbars';
+import * as Themes from 'dome/themes';
 
 import {
   ModeDisplay, SelectedNodesData
 } from "frama-c/plugins/callgraph/definitions";
 
-import { IThreeStateButton, ThreeStateButton } from "./threeStateButton";
+import {
+  IThreeStateButton, ThreeStateButton, TThreesButtonState
+} from "./threeStateButton";
 
 /* -------------------------------------------------------------------------- */
 /* --- Callgraph Toolsbar component                                       --- */
 /* -------------------------------------------------------------------------- */
+interface ShowNodesButtonProps {
+  displayModeState: [ModeDisplay, (newValue: ModeDisplay) => void],
+  selectedParentsState: TThreesButtonState,
+  selectedChildrenState: TThreesButtonState,
+}
+
+function ShowNodesButton(props: ShowNodesButtonProps): JSX.Element {
+  const {
+    displayModeState, selectedParentsState, selectedChildrenState
+  } = props;
+  const [ displayMode, setDisplayMode] = displayModeState;
+
+  return (
+    <ButtonGroup>
+      <Button
+        label='all'
+        title='show all nodes'
+        selected={displayMode === 'all'}
+        onClick={() => setDisplayMode("all")}
+        />
+      <Button
+        label='linked'
+        title='only show nodes linked to the selected ones'
+        selected={displayMode === 'linked'}
+        onClick={() => setDisplayMode("linked")}
+        />
+      <Button
+        label='selected'
+        title='only show selected nodes, their parents and their childrens'
+        selected={displayMode === 'selected'}
+        onClick={() => setDisplayMode("selected")}
+        />
+      { displayMode === "selected" ? (
+          <>
+            <ThreeStateButton
+              label={"Parents"}
+              title={"Choose how many parents you want to see."}
+              buttonState={selectedParentsState}
+              />
+            <ThreeStateButton
+              label={"Children"}
+              title={"Choose how many children you want to see."}
+              buttonState={selectedChildrenState}
+              />
+          </>
+        ) : <></>
+      }
+    </ButtonGroup>
+  );
+}
+
+export function DocShowNodesButton(): JSX.Element {
+  const displayModeState = React.useState<ModeDisplay>("all");
+  const selectedParentsState = React.useState<IThreeStateButton>(
+      { active: false, max: false, value: 1 });
+  const selectedChildrenState = React.useState<IThreeStateButton>(
+      { active: true, max: true, value: 1 });
+  const [ displayMode, ] = displayModeState;
+  const [ parent, ] = selectedParentsState;
+  const [ children, ] = selectedChildrenState;
+
+  const style = Themes.useStyle();
+  const infosStyle = { color: style.getPropertyValue('--text-highlighted') };
+
+  function getDocSelected(
+    parent: IThreeStateButton,
+    children: IThreeStateButton
+  ):JSX.Element {
+    function getDocTSB(name: string, tsb: IThreeStateButton):string {
+      return !tsb.active ? '' :
+        tsb.max ? ` all ${name}` :
+          tsb.value > 0 ?
+            (tsb.value+' level'+(tsb.value > 1 ? 's':'')+` of ${name}`):
+            "";
+    }
+    const p = getDocTSB('parents', parent);
+    const c = getDocTSB('children', children);
+
+    return (
+      <div style={infosStyle}>
+        Selected nodes displayed { (p || c) && " with " }
+        { p }{ p && c && " and " }{ c }
+        { !p && !c && " only " }.
+      </div>
+    );
+  }
+
+  const docAll = <div style={infosStyle}>All nodes displayed.</div>;
+  const docLinked = <div style={infosStyle}>Hide unlinked nodes.</div>;
+  const docSelected = getDocSelected(parent, children);
+
+  return (
+    <>
+      <ShowNodesButton
+        displayModeState={displayModeState}
+        selectedParentsState={selectedParentsState}
+        selectedChildrenState={selectedChildrenState}
+      />
+      { displayMode === 'all' ? docAll :
+        displayMode === 'linked' ? docLinked :
+        docSelected
+      }
+    </>
+  );
+}
+
 interface CallgraphToolsBarProps {
   /* eslint-disable max-len */
   displayModeState: [ModeDisplay, (newValue: ModeDisplay) => void],
diff --git a/ivette/src/frama-c/plugins/plugins.md b/ivette/src/frama-c/plugins/plugins.md
new file mode 100644
index 00000000000..896c3185a36
--- /dev/null
+++ b/ivette/src/frama-c/plugins/plugins.md
@@ -0,0 +1,80 @@
+# Plugins {#plugins}
+
+## Callgraph {#plugins-callgraph}
+
+This module provides a graphical display of the callgraph and makes it easy to highlight certain data, such as :
+
+* The location of unproven properties.
+* Functions containing tainted properties.
+* ...
+
+Below is a list of shortcuts:
+
+* In the graph:
+  * Left-click: rotate the graph
+  * Right-click: move in the graph
+  * Mouse-wheel: zoom
+* On nodes:
+  * Left-Click: select node (in the graph)
+  * Ctrl+click: add node to the selected nodes (multi-selection)
+  * Alt+click: select function (in all Ivette components)
+
+This component is divided into 4 parts, the [titlebar](#plugins-callgraph-titlebar), the [toolbar](#plugins-callgraph-toolbar), a [panel](#plugins-callgraph-panel) and a [graph](#plugins-callgraph-graph).
+
+### Titlebar {#plugins-callgraph-titlebar}
+
+The titlebar contains the name of the module on the left and the following buttons on the right:
+
+* [icon-TUNINGS] : Filter functions appearing in the graph. Ce filtre est synchronisé avec celui de la sidebar.
+* [icon-TARGET] : Move the camera to show each node after each render.
+* [icon-PIN] : Automatically select node of the function selected in AST.
+* [icon-HELP] : show this help modal.
+
+### Toolbar {#plugins-callgraph-toolbar}
+
+The toolbar contains display and selection parameters on the left and graph management parameters on the right.
+On the far right is the button for opening the side panel.
+
+On the left, there is a group of buttons for selecting the nodes that will appear in the graph :
+
+* The first group of buttons is used to select the nodes that will appear in the graph...
+  *  Try yourself : [button-displaymode]
+* [button-select] :This button allows you to select a list of predefined nodes (nodes with unproven properties, with tainted variables, etc.).
+
+On the right:
+
+* Horizontal and vertical distance management between graph nodes.
+* [icon-SIDEBAR] : Opens or closes the side panel.
+
+### Panel {#plugins-callgraph-panel}
+
+The panel displays additional information about the graph in general and about the properties of the selected nodes.
+The filters above the list can be used to limit the amount of information, and are synchronised with the filters in the `Properties` component.
+
+At the top right, 2 buttons allow you to change the side of the panel and close it.
+
+### Graph {#plugins-callgraph-graph}
+
+The graph is in 3D but is displayed as a tree. This type of display prevents cycles from appearing in the graph.
+
+If a cycle is detected :
+
+* In the case of a recursive function: The link is deleted and the [icon-REDO-orange] icon is added to the node.
+* In the case of a cycle on several functions: The cycles will be pre-selected and will appear in the selection button.
+
+#### Nodes
+
+The nodes display the name of the function and the following elements:
+
+* [led-warning] : The function contains unproven properties, a tooltip gives the quantity.
+* [led-negative] : The function contains false properties, a tooltip gives the quantity.
+* [icon-REDO-orange] : The function is recursive.
+* [icon-DROP.FILLED-#882288][icon-DROP.FILLED-#73BBBB]: The function contains tainted properties.
+
+#### Edges
+
+The edges are oriented and can take on different colours depending on the nodes selected.
+
+* Green: the edge connects 2 selected nodes.
+* Red: the edge links a selected node and one of its parents.
+* Blue: the edge links a selected node and one of its children.
-- 
GitLab