From 7558ac03e789191ef168cc7dcbbb5ffa243bed84 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr>
Date: Sun, 7 Apr 2024 17:27:48 +0200
Subject: [PATCH] [ivette] move Status to Display

---
 ivette/src/frama-c/index.tsx                  |  2 -
 ivette/src/frama-c/kernel/Locations.tsx       | 44 ++++-----
 ivette/src/frama-c/kernel/PivotTable.tsx      | 18 ++--
 ivette/src/frama-c/kernel/Status.tsx          | 97 -------------------
 ivette/src/frama-c/menu.ts                    | 27 +-----
 ivette/src/frama-c/plugins/eva/valuetable.tsx | 17 ++--
 ivette/src/frama-c/plugins/studia/index.ts    | 11 +--
 ivette/src/ivette/display.tsx                 | 16 ++-
 ivette/src/ivette/prefs.tsx                   |  2 +-
 9 files changed, 58 insertions(+), 176 deletions(-)
 delete mode 100644 ivette/src/frama-c/kernel/Status.tsx

diff --git a/ivette/src/frama-c/index.tsx b/ivette/src/frama-c/index.tsx
index 5df09f7389c..5956727d634 100644
--- a/ivette/src/frama-c/index.tsx
+++ b/ivette/src/frama-c/index.tsx
@@ -29,7 +29,6 @@ import * as Ivette from 'ivette';
 
 import History from 'frama-c/kernel/History';
 import { Functions, Globals, Types } from 'frama-c/kernel/Globals';
-import Status from 'frama-c/kernel/Status';
 import ASTview from 'frama-c/kernel/ASTview';
 import ASTinfo from 'frama-c/kernel/ASTinfo';
 import SourceCode from 'frama-c/kernel/SourceCode';
@@ -58,7 +57,6 @@ Ivette.registerSidebar({
 });
 
 Ivette.registerToolbar({ id: 'ivette.history', children: <History /> });
-Ivette.registerStatusbar({ id: 'ivette.status', children: <Status /> });
 
 /* -------------------------------------------------------------------------- */
 /* --- Frama-C Kernel Groups                                              --- */
diff --git a/ivette/src/frama-c/kernel/Locations.tsx b/ivette/src/frama-c/kernel/Locations.tsx
index 557865901c8..7a4298db2da 100644
--- a/ivette/src/frama-c/kernel/Locations.tsx
+++ b/ivette/src/frama-c/kernel/Locations.tsx
@@ -36,7 +36,6 @@ import { Space } from 'dome/frame/toolbars';
 import { TitleBar } from 'ivette';
 import * as Display from 'ivette/display';
 import * as Ast from 'frama-c/kernel/api/ast';
-import * as Status from 'frama-c/kernel/Status';
 import * as Server from 'frama-c/server';
 
 // --------------------------------------------------------------------------
@@ -61,20 +60,16 @@ export function useSelection(): MultiSelection {
   return s;
 }
 
-export function setSelection(s: MultiSelection): void
-{
+export function setSelection(s: MultiSelection): void {
   MultiSelection.setValue(s);
   const marker = s.index !== undefined ? s.markers[s.index] : undefined;
   if (marker) States.setSelected(marker);
   if (s.plugin && s.markers.length > 0) {
-    const text =
+    const label =
       `${s.plugin}: ${s.markers.length} locations selected, \
       listed in the 'Locations' panel`;
     const title = `${s.label}: ${s.markers.length} locations selected`;
-    Status.setMessage({
-      text, title,
-      kind: 'success'
-    });
+    Display.showMessage({ label, title });
     Display.alertComponent('fc.kernel.locations');
   }
 }
@@ -86,16 +81,14 @@ export function setIndex(index: number): void {
   setSelection({ ...s, index });
 }
 
-function sameMarkers(xs: Ast.marker[], ys: Ast.marker[]): boolean
-{
+function sameMarkers(xs: Ast.marker[], ys: Ast.marker[]): boolean {
   if (xs.length !== ys.length) return false;
   for (let k = 0; k < xs.length; k++)
     if (xs[k] !== ys[k]) return false;
   return true;
 }
 
-function sameSelection(u: MultiSelection, v: MultiSelection): boolean
-{
+function sameSelection(u: MultiSelection, v: MultiSelection): boolean {
   if (u.label !== v.label) return false;
   if (u.title !== v.title) return false;
   return sameMarkers(u.markers, v.markers);
@@ -105,12 +98,11 @@ function sameSelection(u: MultiSelection, v: MultiSelection): boolean
    Update the list of markers and select its first element,
    or cycle to the next element wrt current selection.
  */
-export function setNextSelection(s: MultiSelection): void
-{
+export function setNextSelection(s: MultiSelection): void {
   const selection = MultiSelection.getValue();
   if (s.index === undefined && sameSelection(selection, s)) {
     const { index, markers } = selection;
-    const target = index === undefined ? 0 : index+1;
+    const target = index === undefined ? 0 : index + 1;
     const select = target < markers.length ? target : 0;
     setSelection({ ...selection, index: select });
   } else {
@@ -143,14 +135,14 @@ class Model extends CompactModel<Ast.marker, Data> {
 }
 
 const renderIndex: Renderer<number> =
-  (index) => <Cell label={`${index+1}`}/>;
+  (index) => <Cell label={`${index + 1}`} />;
 
 const renderDecl: Renderer<Data> =
-(d) => {
-  const name = d.decl.name;
-  const label = d.decl.label;
-  return <Cell label={name} title={label} />;
-};
+  (d) => {
+    const name = d.decl.name;
+    const label = d.decl.label;
+    return <Cell label={name} title={label} />;
+  };
 
 const renderLocation: Renderer<Data> =
   (d) => {
@@ -183,7 +175,7 @@ export default function LocationsTable(): JSX.Element {
   const selected = index !== undefined ? markers[index] : undefined;
   const size = markers.length;
   const kindex = index === undefined ? (-1) : index;
-  const indexLabel = index === undefined ? '…' : index+1;
+  const indexLabel = index === undefined ? '…' : index + 1;
   const positionLabel = `${indexLabel} / ${size}`;
 
   // Component
@@ -194,13 +186,13 @@ export default function LocationsTable(): JSX.Element {
           icon='ANGLE.LEFT'
           title='Previous location'
           enabled={0 < kindex}
-          onClick={() => gotoIndex(kindex-1)}
+          onClick={() => gotoIndex(kindex - 1)}
         />
         <IconButton
           icon='ANGLE.RIGHT'
           title='Next location'
           enabled={(-1) <= kindex && kindex + 1 < size}
-          onClick={() => gotoIndex(kindex+1)}
+          onClick={() => gotoIndex(kindex + 1)}
         />
         <Space />
         <Label
@@ -218,11 +210,11 @@ export default function LocationsTable(): JSX.Element {
       <Label className='locations' label={label} title={title} />
       <Table
         model={model}
-        display={size >0}
+        display={size > 0}
         selection={selected}
         onSelection={(_row, _key, index) => gotoIndex(index)}
         settings="ivette.locations.table"
-        >
+      >
         <Column
           id='index' label='#' align='center' width={25}
           render={renderIndex} />
diff --git a/ivette/src/frama-c/kernel/PivotTable.tsx b/ivette/src/frama-c/kernel/PivotTable.tsx
index 89fe885aaf6..fdf6b2c53e5 100644
--- a/ivette/src/frama-c/kernel/PivotTable.tsx
+++ b/ivette/src/frama-c/kernel/PivotTable.tsx
@@ -25,16 +25,19 @@
 // --------------------------------------------------------------------------
 
 import React from 'react';
-import { TitleBar } from 'ivette';
-import * as Server from 'frama-c/server';
 import { Button } from 'dome/controls/buttons';
 import { LED } from 'dome/controls/displays';
 import { Scroll } from 'dome/layout/boxes';
-import * as Status from 'frama-c/kernel/Status';
-import * as States from 'frama-c/states';
 import { GlobalState, useGlobalState } from 'dome/data/states';
+
+import { TitleBar } from 'ivette';
+import * as Display from 'ivette/display';
+
+import * as Server from 'frama-c/server';
+import * as States from 'frama-c/states';
 import * as PivotState from 'frama-c/plugins/pivot/api/general';
 import PivotTableUI from 'react-pivottable/PivotTableUI';
+
 import 'frama-c/kernel/PivotTable-style.css';
 
 // --------------------------------------------------------------------------
@@ -78,10 +81,9 @@ function PivotTableBuild(): JSX.Element {
   const [computing, setComputing] = React.useState(false);
   const [error, setError] = React.useState('');
   async function handleError(err: string): Promise<void> {
-    const msg =
-      `The pivot table could not be built: an error has occured (${err}).`;
-    setError(msg);
-    Status.setMessage({ text: msg, kind: 'error' });
+    const label = 'Pivot Table Error';
+    const title = `Building error (${err})`;
+    Display.showError({ label, title });
   }
   async function compute(): Promise<void> {
     setComputing(true);
diff --git a/ivette/src/frama-c/kernel/Status.tsx b/ivette/src/frama-c/kernel/Status.tsx
deleted file mode 100644
index 5b1e0ff2761..00000000000
--- a/ivette/src/frama-c/kernel/Status.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-/* ************************************************************************ */
-/*                                                                          */
-/*   This file is part of Frama-C.                                          */
-/*                                                                          */
-/*   Copyright (C) 2007-2023                                                */
-/*     CEA (Commissariat à l'énergie atomique et aux énergies               */
-/*          alternatives)                                                   */
-/*                                                                          */
-/*   you can redistribute it and/or modify it under the terms of the GNU    */
-/*   Lesser General Public License as published by the Free Software        */
-/*   Foundation, version 2.1.                                               */
-/*                                                                          */
-/*   It is distributed in the hope that it will be useful,                  */
-/*   but WITHOUT ANY WARRANTY; without even the implied warranty of         */
-/*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          */
-/*   GNU Lesser General Public License for more details.                    */
-/*                                                                          */
-/*   See the GNU Lesser General Public License version 2.1                  */
-/*   for more details (enclosed in the file licenses/LGPLv2.1).             */
-/*                                                                          */
-/* ************************************************************************ */
-
-/* --------------------------------------------------------------------------*/
-/* --- Frama-C Status Message                                             ---*/
-/* --------------------------------------------------------------------------*/
-
-import _ from 'lodash';
-import React from 'react';
-import { Code, IconKind } from 'dome/controls/labels';
-import { IconButton } from 'dome/controls/buttons';
-import * as Toolbars from 'dome/frame/toolbars';
-import { GlobalState, useGlobalState } from 'dome/data/states';
-import * as Server from 'frama-c/server';
-
-export type kind =
-  'none' | 'info' | 'warning' | 'error' | 'success' | 'progress';
-
-export interface MessageProps {
-  kind?: kind;
-  text: string;
-  title?: string;
-}
-
-const emptyMessage: MessageProps = { text: '', kind: 'none' };
-
-
-const GlobalMessage = new GlobalState(emptyMessage);
-
-const clearMessage = (): void => setMessage(emptyMessage);
-const triggerClearMessage = _.debounce(clearMessage, 15000);
-
-export function setMessage(message: MessageProps): void {
-  GlobalMessage.setValue(message);
-  triggerClearMessage();
-}
-
-const msgIcon: { [kind: string]: string } = {
-  'progress': 'EXECUTE',
-  'success': 'CHECK',
-  'warning': 'WARNING',
-  'error': 'CROSS',
-  'info': 'HELP',
-};
-
-const msgKind: { [kind: string]: IconKind } = {
-  'success': 'positive',
-  'warning': 'warning',
-  'error': 'negative',
-};
-
-export default function Message(): JSX.Element {
-  const [message] = useGlobalState(GlobalMessage);
-  const { kind: mKind='none' } = message;
-  const icon = msgIcon[mKind];
-  const kind = msgKind[mKind];
-  return (
-    <Toolbars.Group display={message.kind !== 'none'} >
-      <Code icon={icon} kind={kind}
-            label={message.text} title={message.title} />
-      <IconButton
-        icon="CIRC.CLOSE"
-        onClick={clearMessage}
-        visible={message !== emptyMessage}
-        title="Hide current message"
-      />
-    </Toolbars.Group>
-  );
-}
-
-/* Callback on server events */
-
-{
-  Server.onReady(() => setMessage(emptyMessage));
-  Server.onShutdown(() => setMessage(emptyMessage));
-}
-
-/* --------------------------------------------------------------------------*/
diff --git a/ivette/src/frama-c/menu.ts b/ivette/src/frama-c/menu.ts
index b8dadcc57d8..bcb5ae8c7e5 100644
--- a/ivette/src/frama-c/menu.ts
+++ b/ivette/src/frama-c/menu.ts
@@ -24,14 +24,14 @@
 /* --- Frama-C MENU                                                       ---*/
 /* --------------------------------------------------------------------------*/
 
+import { ipcRenderer } from 'electron';
 import * as Dome from 'dome';
 import * as Dialogs from 'dome/dialogs';
+import * as Display from 'ivette/display';
 import * as Server from 'frama-c/server';
 import * as Services from 'frama-c/kernel/api/services';
 import * as Ast from 'frama-c/kernel/api/ast';
-import * as Status from 'frama-c/kernel/Status';
 import * as States from 'frama-c/states';
-import { ipcRenderer } from 'electron';
 
 const cFilter = {
   name: 'C source files',
@@ -43,10 +43,9 @@ const allFilter = {
 };
 
 async function parseFiles(files: string[]): Promise<void> {
-  Status.setMessage({ text: 'Parsing source files…', kind: 'progress' });
   await Server.send(Ast.setFiles, files);
   await Server.send(Ast.compute, {});
-  Status.setMessage({ text: 'Source files parsed.', kind: 'success' });
+  Display.showMessage('Source files parsed.');
   return;
 }
 
@@ -77,7 +76,6 @@ async function addFiles(): Promise<void> {
 }
 
 async function reparseFiles(): Promise<void> {
-  Status.setMessage({ text: 'Parsing source files…', kind: 'progress' });
   const files = await Server.send(Ast.getFiles, {});
   if (files) {
     await Server.send(Ast.setFiles, []);
@@ -88,15 +86,9 @@ async function reparseFiles(): Promise<void> {
 
 async function loadSession(): Promise<void> {
   const file = await Dialogs.showOpenFile({ title: 'Load a saved session' });
-  Status.setMessage({ text: 'Loading session…', kind: 'progress' });
   const error = await Server.send(Services.load, file);
   States.clearHistory();
   if (error) {
-    Status.setMessage({
-      text: 'Error when loading the session',
-      title: error,
-      kind: 'error',
-    });
     await Dialogs.showMessageBox({
       message: 'An error has occurred when loading the file',
       details: `File: ${file}\nError: ${error}`,
@@ -104,33 +96,20 @@ async function loadSession(): Promise<void> {
       buttons: [{ label: 'Cancel' }],
     });
   }
-  else
-    Status.setMessage({
-      text: 'Session successfully loaded.',
-      kind: 'success',
-    });
   return;
 }
 
 async function saveSession(): Promise<void> {
   const title = 'Save the current session';
   const file = await Dialogs.showSaveFile({ title });
-  Status.setMessage({ text: 'Saving session…', kind: 'progress' });
   const error = await Server.send(Services.save, file);
   if (error) {
-    Status.setMessage({
-      text: 'Error when saving the session',
-      title: error,
-      kind: 'error',
-    });
     await Dialogs.showMessageBox({
       message: 'An error has occurred when saving the session',
       kind: 'error',
       buttons: [{ label: 'Cancel' }],
     });
   }
-  else
-    Status.setMessage({ text: 'Session successfully saved.', kind: 'success' });
   return;
 }
 
diff --git a/ivette/src/frama-c/plugins/eva/valuetable.tsx b/ivette/src/frama-c/plugins/eva/valuetable.tsx
index e802c9e01ab..20862aeedbd 100644
--- a/ivette/src/frama-c/plugins/eva/valuetable.tsx
+++ b/ivette/src/frama-c/plugins/eva/valuetable.tsx
@@ -22,17 +22,17 @@
 
 import React from 'react';
 import _ from 'lodash';
-import * as Ivette from 'ivette';
 import * as Dome from 'dome';
 import * as System from 'dome/system';
+import { GlobalState, useGlobalState } from 'dome/data/states';
+import * as Ivette from 'ivette';
+import * as Display from 'ivette/display';
 import * as States from 'frama-c/states';
 import * as Server from 'frama-c/server';
-import * as Status from 'frama-c/kernel/Status';
 import * as Ast from 'frama-c/kernel/api/ast';
 import * as Eva from 'frama-c/plugins/eva/api/general';
 import * as Values from 'frama-c/plugins/eva/api/values';
 import EvaReady from './EvaReady';
-import { GlobalState, useGlobalState } from 'dome/data/states';
 
 import { classes } from 'dome/misc/utils';
 import { Icon } from 'dome/controls/icons';
@@ -921,7 +921,7 @@ interface EvaluationModeProps {
 }
 
 const evalShortcut = System.platform === 'macos' ? 'Cmd+E' : 'Ctrl+E';
-const evalMode : Ivette.SearchProps = {
+const evalMode: Ivette.SearchProps = {
   id: 'frama-c.eva.evalMode',
   label: 'Evaluation',
   title: `Evaluate an ACSL expression (shortcut: ${evalShortcut})`,
@@ -951,13 +951,14 @@ function useEvaluationMode(props: EvaluationModeProps): void {
       const onEnter = (pattern: string): void => {
         const data = { stmt: marker, term: pattern };
         const handleError = (err: string): void => {
-          const text = `${pattern} could not be evaluated: ${err}.`;
-          Status.setMessage({ text, kind: 'error' });
+          const label = 'Evaluation Error';
+          const title = `${pattern} could not be evaluated: ${err}.`;
+          Display.showWarning({ label, title });
         };
         const addProbe = (target: Ast.marker): void => {
           setLocPin(scope, target, true);
-          const text = `${pattern} evaluated in the 'Eva Values' panel`;
-          Status.setMessage({ text, kind: 'success' });
+          Display.showMessage('New Probe');
+          Display.alertComponent('fc.eva.values');
         };
         Server.send(Ast.parseExpr, data).then(addProbe).catch(handleError);
       };
diff --git a/ivette/src/frama-c/plugins/studia/index.ts b/ivette/src/frama-c/plugins/studia/index.ts
index 817a1779611..1cbbbf2dfd3 100644
--- a/ivette/src/frama-c/plugins/studia/index.ts
+++ b/ivette/src/frama-c/plugins/studia/index.ts
@@ -22,9 +22,9 @@
 
 import * as Dome from 'dome';
 import * as Ivette from 'ivette';
+import * as Display from 'ivette/display';
 import * as States from 'frama-c/states';
 import * as Server from 'frama-c/server';
-import * as Status from 'frama-c/kernel/Status';
 import * as Ast from 'frama-c/kernel/api/ast';
 import * as ASTview from 'frama-c/kernel/ASTview';
 import * as Locations from 'frama-c/kernel/Locations';
@@ -34,8 +34,7 @@ import './style.css';
 type access = 'Reads' | 'Writes';
 
 function handleError(err: string): void {
-  const text = `Studia failure: ${err}.`;
-  Status.setMessage({ text, kind: 'error' });
+  Display.showWarning({ label: 'Studia Failure', title: `Error (${err})` });
 }
 
 async function computeStudiaSelection(
@@ -68,7 +67,7 @@ export function buildMenu(
   attr: Ast.markerAttributesData,
 ): void {
   const { marker, kind } = attr;
-  switch(kind) {
+  switch (kind) {
     case 'LVAL':
       menu.push({
         label: 'Studia: select reads of l-value',
@@ -108,7 +107,7 @@ export function buildMenu(
 
 ASTview.registerMarkerMenuExtender(buildMenu);
 
-const studiaReadsMode : Ivette.SearchProps = {
+const studiaReadsMode: Ivette.SearchProps = {
   id: 'frama-c.plugins.studia.reads',
   rank: -1,
   label: 'Studia: reads',
@@ -119,7 +118,7 @@ const studiaReadsMode : Ivette.SearchProps = {
   onEnter: (p: string) => onEnter('Reads', p)
 };
 
-const studiaWritesMode : Ivette.SearchProps = {
+const studiaWritesMode: Ivette.SearchProps = {
   id: 'frama-c.plugins.studia.writes',
   rank: -1,
   label: 'Studia: writes',
diff --git a/ivette/src/ivette/display.tsx b/ivette/src/ivette/display.tsx
index 15d5f8ca4ae..962c21ad5b4 100644
--- a/ivette/src/ivette/display.tsx
+++ b/ivette/src/ivette/display.tsx
@@ -124,10 +124,12 @@ export function useComponentStatus(
   return Laboratory.getComponentStatus(state, id ?? '');
 }
 
-export type Message = undefined | null | string;
+export type ShortMessage = undefined | null | string;
+export type Message = ShortMessage | Laboratory.Notification;
+export type Warning = ShortMessage | { label: string, title: string };
 
 /** Message notification */
-export function showMessage(msg: Message | Laboratory.Notification): void {
+export function showMessage(msg: Message): void {
   if (!msg) return;
   Laboratory.showMessage(typeof msg === 'string' ? { label: msg } : msg);
 }
@@ -135,13 +137,19 @@ export function showMessage(msg: Message | Laboratory.Notification): void {
 /** Warning notification. */
 export function showWarning(msg: Message): void {
   if (!msg) return;
-  Laboratory.showMessage({ kind: 'warning', label: msg });
+  const short = typeof msg === 'string';
+  const label = short ? msg : msg.label;
+  const title = short ? msg : undefined;
+  Laboratory.showMessage({ kind: 'warning', label, title });
 }
 
 /** Error notification */
 export function showError(msg: Message): void {
   if (!msg) return;
-  Laboratory.showMessage({ kind: 'error', label: msg });
+  const short = typeof msg === 'string';
+  const label = short ? msg : msg.label;
+  const title = short ? msg : undefined;
+  Laboratory.showMessage({ kind: 'error', label, title });
 }
 
 export function clearMessages(): void {
diff --git a/ivette/src/ivette/prefs.tsx b/ivette/src/ivette/prefs.tsx
index 5b5f3ae2229..dd7d28a7263 100644
--- a/ivette/src/ivette/prefs.tsx
+++ b/ivette/src/ivette/prefs.tsx
@@ -104,6 +104,6 @@ export const ConsoleScrollback =
 
 // in seconds; 0 means no timeout
 export const NotificationTimer =
-  new Settings.GNumber('Laboratory.Notifications', 5);
+  new Settings.GNumber('Laboratory.Notifications', 10);
 
 // --------------------------------------------------------------------------
-- 
GitLab