From e7c1a18ccd9d7eee6d333779dba60c687959b24b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr>
Date: Tue, 19 May 2020 17:35:28 +0200
Subject: [PATCH] [ivette] fix controller history

---
 ivette/src/renderer/Controller.tsx | 156 +++++++++++++++++------------
 ivette/src/renderer/style.css      |   7 +-
 2 files changed, 100 insertions(+), 63 deletions(-)

diff --git a/ivette/src/renderer/Controller.tsx b/ivette/src/renderer/Controller.tsx
index 125ce342ff0..68014cb3725 100644
--- a/ivette/src/renderer/Controller.tsx
+++ b/ivette/src/renderer/Controller.tsx
@@ -20,32 +20,31 @@ import 'codemirror/theme/ambiance.css';
 // --- Configure Server
 // --------------------------------------------------------------------------
 
-let cmdConfig: Server.Configuration;
-const cmdLine = new RichTextBuffer();
+const quoteRe = new RegExp('^[-./:a-zA-Z0-9]+$');
+const quote = (s: string) => (quoteRe.test(s) ? s : `"${s}"`);
 
-function dumpCmdLine(sc: Server.Configuration): void {
+function dumpServerConfig(sc: Server.Configuration): string {
+  let buffer = '';
   const { cwd, command, sockaddr, params } = sc;
-  cmdLine.clear();
-  if (cwd) cmdLine.log('--cwd', cwd);
-  if (command) cmdLine.log('--command', command);
-  if (sockaddr) cmdLine.log('--socket', sockaddr);
+  if (cwd) buffer += `--cwd ${quote(cwd)}\n`;
+  if (command) buffer += `--command ${command}\n`;
+  if (sockaddr) buffer += `--socket ${sockaddr}\n`;
   if (params) {
     params.forEach((v: string, i: number) => {
       if (i > 0) {
         if (v.startsWith('-') || v.endsWith('.c')
           || v.endsWith('.h') || v.endsWith('.i')) {
-          cmdLine.append('\n');
-        } else {
-          cmdLine.append(' ');
-        }
+          buffer += '\n';
+        } else
+          buffer += ' ';
       }
-      cmdLine.append(v);
+      buffer += v;
     });
   }
-  cmdLine.append('\n');
+  return buffer;
 }
 
-function buildServerConfiguration(argv: string[], cwd?: string) {
+function buildServerConfig(argv: string[], cwd?: string) {
   const params = [];
   let command;
   let sockaddr;
@@ -77,10 +76,23 @@ function buildServerConfiguration(argv: string[], cwd?: string) {
   };
 }
 
+function insertConfig(hs: string[], cfg: Server.Configuration) {
+  const cmd = dumpServerConfig(cfg).trim();
+  const newhs = hs
+    .map((h) => h.trim())
+    .filter((h: string) => h !== cmd && h !== '')
+    .slice(0, 50);
+  newhs.unshift(cmd);
+  return newhs;
+}
+
+// --------------------------------------------------------------------------
+// --- Start Server on Command
+// --------------------------------------------------------------------------
+
 Dome.onCommand((argv: string[], cwd: string) => {
-  cmdConfig = buildServerConfiguration(argv, cwd);
-  dumpCmdLine(cmdConfig);
-  Server.configure(cmdConfig);
+  const cfg = buildServerConfig(argv, cwd);
+  Server.configure(cfg);
   Server.start();
 });
 
@@ -136,58 +148,70 @@ export const Control = () => {
 // --- Server Console
 // --------------------------------------------------------------------------
 
+const editor = new RichTextBuffer();
+
 const RenderConsole = () => {
   const scratch = React.useRef([] as string[]);
   const [cursor, setCursor] = React.useState(-1);
-  const [h0, setHistory] = Dome.useState('Controller.history', []);
-  const history = Array.isArray(h0) ? h0 : [];
+  const [H0, setH0] = Dome.useState('Controller.history', []);
+  const [isEmpty, setEmpty] = React.useState(true);
+  Dome.useEmitter(editor, 'change', () => {
+    setEmpty(editor.getValue().trim() === '');
+  });
 
-  const doExec = () => {
-    const cmd = cmdLine.getDoc().getValue().trim();
-    cmdLine.clear();
-    const argv = cmd.split(/[ \t\n]+/);
-    const cfg = buildServerConfiguration(argv);
-    Server.configure(cfg);
-    Server.restart();
-    setCursor(-1);
+  // Cope with merge settings that keeps previous array entries (BUG in DOME)
+  const history = Array.isArray(H0) ? H0.filter((h) => h !== '') : [];
+  const setHistory = (hs: string[]) => {
+    const n = hs.length;
+    setH0(n < 50 ? hs.concat(Array(50 - n).fill('')) : hs);
   };
 
-  const doEdit = () => {
+  const doSwitch = () => {
     if (cursor < 0) {
-      dumpCmdLine(Server.getConfig());
-      const cmd = cmdLine.getDoc().getValue().trim();
-      const hs = history
-        .filter((h: string) => h !== cmd && h !== '')
-        .slice(0, 50);
-      hs.unshift(cmd);
-      scratch.current = hs.slice();
-      setHistory(hs);
+      const cfg = Server.getConfig();
+      const hst = insertConfig(history, cfg);
+      scratch.current = hst.slice();
+      editor.setValue(hst[0]);
+      setHistory(hst);
       setCursor(0);
     } else {
-      cmdLine.clear();
+      editor.clear();
       scratch.current = [];
       setCursor(-1);
     }
   };
 
+  const doExec = () => {
+    const cmd = editor.getValue().trim();
+    const argv = cmd.split(/[ \t\n]+/);
+    const cfg = buildServerConfig(argv);
+    const hst = insertConfig(history, cfg);
+    setHistory(hst);
+    setCursor(-1);
+    editor.clear();
+    Server.configure(cfg);
+    Server.restart();
+  };
+
   const doMove = (target: number) => {
-    if (0 <= target && target < history.length)
+    if (0 <= target && target < history.length && target !== cursor)
       return () => {
-        const doc = cmdLine.getDoc();
-        const cmd = scratch.current;
-        cmd[cursor] = doc.getValue();
-        doc.setValue(cmd[target]);
+        const cmd = editor.getValue();
+        const pad = scratch.current;
+        pad[cursor] = cmd;
+        editor.setValue(pad[target]);
         setCursor(target);
       };
     return undefined;
   };
 
   const doReload = () => {
-    const doc = cmdLine.getDoc();
-    const cmd = scratch.current;
-    if (cursor !== 0) cmd[cursor] = doc.getValue();
-    dumpCmdLine(Server.getConfig());
-    cmd[0] = doc.getValue();
+    const cfg = Server.getConfig();
+    const cmd = dumpServerConfig(cfg);
+    const pad = scratch.current;
+    if (cursor !== 0) pad[cursor] = editor.getValue();
+    pad[0] = cmd;
+    editor.setValue(cmd);
     setCursor(0);
   };
 
@@ -195,16 +219,18 @@ const RenderConsole = () => {
     const n = history.length;
     if (n > 1) {
       const hst = history.slice();
-      const cmd = scratch.current;
-      const next = cursor - 1;
+      const pad = scratch.current;
       hst.splice(cursor, 1);
-      cmd.splice(cursor, 1);
-      cmdLine.getDoc().setValue(scratch.current[next]);
+      pad.splice(cursor, 1);
       setHistory(hst);
+      const next = cursor > 0 ? cursor - 1 : 0;
+      editor.setValue(pad[next]);
       setCursor(next);
     } else {
+      // Do not share the two arrays!
+      setHistory(['']);
       scratch.current = [''];
-      cmdLine.getDoc().setValue('');
+      editor.clear();
     }
   };
 
@@ -213,9 +239,21 @@ const RenderConsole = () => {
   const edited = 0 <= cursor;
   const n = history.length;
 
+  let LABEL = <>Console</>;
+  if (edited) {
+    const RANK = n > 1 ? 'controller-rank' : 'controller-norank';
+    LABEL = (
+      <Label>
+        Command
+        <span className={RANK}>
+          {1 + cursor} / {n}
+        </span>
+      </Label>
+    );
+  }
   return (
     <>
-      <TitleBar label={edited ? 'Command Line' : 'Console'}>
+      <TitleBar label={LABEL}>
         <IconButton
           icon="TRASH"
           display={edited}
@@ -235,13 +273,6 @@ const RenderConsole = () => {
           onClick={doPrev}
           title="Previous Command"
         />
-        <Label
-          className="dimmed"
-          display={edited && n > 0}
-          title="Rank in History"
-        >
-          {1 + cursor}/{n}
-        </Label>
         <IconButton
           icon="MEDIA.NEXT"
           display={edited}
@@ -252,18 +283,19 @@ const RenderConsole = () => {
         <IconButton
           icon="MEDIA.PLAY"
           display={edited}
+          disabled={isEmpty}
           onClick={doExec}
           title="Execute Command"
         />
         <IconButton
           icon="TERMINAL"
           selected={edited}
-          onClick={doEdit}
+          onClick={doSwitch}
           title="Edit Command"
         />
       </TitleBar>
       <Text
-        buffer={edited ? cmdLine : Server.buffer}
+        buffer={edited ? editor : Server.buffer}
         mode="text"
         readOnly={!edited}
         theme="ambiance"
diff --git a/ivette/src/renderer/style.css b/ivette/src/renderer/style.css
index de68fb842c2..295ddc069c7 100644
--- a/ivette/src/renderer/style.css
+++ b/ivette/src/renderer/style.css
@@ -2,8 +2,13 @@
 /* --- Frama-C Styling                                                    --- */
 /* -------------------------------------------------------------------------- */
 
-.dimmed {
+.controller-rank {
     color: #888 ;
+    margin-left: 12px ;
+}
+
+.controller-norank {
+    display: none ;
 }
 
 /* -------------------------------------------------------------------------- */
-- 
GitLab