From 7f2284654b9a365e61b1241d6b7c772839e1dd9d Mon Sep 17 00:00:00 2001
From: Maxime Jacquemin <maxime2.jacquemin@gmail.com>
Date: Thu, 16 Sep 2021 16:25:44 +0200
Subject: [PATCH] [ivette] First steps for external code edition

For now I have the following:
  - A new preference that allows to specify which command to launch to
    edit the source code in the user's favorite editor;
  - Spawn a new process that launch the editor on the current selection
    using the specified command. The keybinding used is
    <Ctrl-LeftClick>, exactly as the old GUI.

However, there is a catch. First, the keybinding does not select a line
before launching the external editor. Second, the only selection that is
actually taken into consideration is the one done when we click on a
function.
---
 ivette/src/frama-c/kernel/SourceCode.tsx | 15 ++++++++++++---
 ivette/src/ivette/prefs.tsx              | 17 +++++++++++++++++
 ivette/src/renderer/Preferences.tsx      | 13 +++++++++++++
 3 files changed, 42 insertions(+), 3 deletions(-)

diff --git a/ivette/src/frama-c/kernel/SourceCode.tsx b/ivette/src/frama-c/kernel/SourceCode.tsx
index aaf770e7399..440f51a1ab9 100644
--- a/ivette/src/frama-c/kernel/SourceCode.tsx
+++ b/ivette/src/frama-c/kernel/SourceCode.tsx
@@ -28,7 +28,7 @@ import React from 'react';
 import * as States from 'frama-c/states';
 
 import * as Dome from 'dome';
-import { readFile } from 'dome/system';
+import * as System from 'dome/system';
 import { RichTextBuffer } from 'dome/text/buffers';
 import { Text } from 'dome/text/editors';
 import { TitleBar } from 'ivette';
@@ -37,6 +37,7 @@ import { functions, markerInfo } from 'frama-c/api/kernel/ast';
 import { Code } from 'dome/controls/labels';
 import { Hfill } from 'dome/layout/boxes';
 import * as Path from 'path';
+import * as Settings from 'dome/data/settings';
 
 import 'codemirror/addon/selection/active-line';
 import 'codemirror/addon/dialog/dialog.css';
@@ -86,12 +87,20 @@ export default function SourceCode() {
   // Updating the buffer content.
   const errorMsg = () => { D.error(`Fail to load source code file ${file}`); };
   const onError = () => { if (file) errorMsg(); return ''; };
-  const read = () => readFile(file).catch(onError);
+  const read = () => System.readFile(file).catch(onError);
   const text = React.useMemo(read, [file, onError]);
   const { result } = Dome.usePromise(text);
   React.useEffect(() => buffer.setValue(result), [buffer, result]);
   React.useEffect(() => buffer.setCursorOnTop(line), [buffer, line, result]);
 
+  const [command] = Settings.useGlobalSettings(Preferences.EditorCommand);
+  const launchEditor = () => {
+    const cmd = command.replace('\%s', file).replace('\%d', line.toString());
+    const args = cmd.split(' ');
+    const prog = args.shift();
+    if (prog) System.spawn(prog, args);
+  };
+
   // Building the React component.
   return (
     <>
@@ -109,7 +118,7 @@ export default function SourceCode() {
         selection={theMarker}
         lineNumbers={!!theFunction}
         styleActiveLine={!!theFunction}
-        extraKeys={{ 'Alt-F': 'findPersistent' }}
+        extraKeys={{ 'Ctrl-LeftClick': launchEditor }}
         readOnly
       />
     </>
diff --git a/ivette/src/ivette/prefs.tsx b/ivette/src/ivette/prefs.tsx
index 71df4d94ca4..b4fd8e08190 100644
--- a/ivette/src/ivette/prefs.tsx
+++ b/ivette/src/ivette/prefs.tsx
@@ -127,3 +127,20 @@ export function useThemeButtons(props: ThemeProps): ThemeControls {
 }
 
 // --------------------------------------------------------------------------
+// --- Editor configuration
+// --------------------------------------------------------------------------
+
+export const EditorCommand =
+  new Settings.GString('Editor.Command', 'emacs +\%d \%s');
+
+export interface EditorCommandProps {
+  command: Settings.GlobalSettings<string>;
+}
+
+// function produceCommand(command: string, file: string, line: number): string {
+//   command.replace('\%s', file);
+//   command.replace('\%d', line.toString());
+//   return "";
+// }
+
+// --------------------------------------------------------------------------
diff --git a/ivette/src/renderer/Preferences.tsx b/ivette/src/renderer/Preferences.tsx
index a16aea3fdb2..b6696ab22ad 100644
--- a/ivette/src/renderer/Preferences.tsx
+++ b/ivette/src/renderer/Preferences.tsx
@@ -83,6 +83,16 @@ function ThemeFields(props: P.ThemeProps) {
   );
 }
 
+// --------------------------------------------------------------------------
+// --- Editor Command Forms
+// --------------------------------------------------------------------------
+function EditorCommandFields(props : P.EditorCommandProps) {
+  const command = Forms.useDefined(
+    Forms.useValid(Settings.useGlobalSettings(props.command)
+  ));
+  return (<Forms.TextCodeField state={command} label="Command"/>);
+}
+
 // --------------------------------------------------------------------------
 // --- Export Components
 // --------------------------------------------------------------------------
@@ -105,6 +115,9 @@ export default (() => (
         wrapText={P.SourceWrapText}
       />
     </Forms.Section>
+    <Forms.Section label="Editor Command" unfold>
+      <EditorCommandFields command={P.EditorCommand}/>
+    </Forms.Section>
   </Forms.Page>
 ));
 
-- 
GitLab