From 8e6dc720dc282c5389932de99427988e24a8951b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr>
Date: Wed, 8 Nov 2023 16:41:53 +0100
Subject: [PATCH] [dome/richtext] fixed gutters

---
 ivette/src/dome/renderer/text/richtext.tsx | 19 +++++----
 ivette/src/dome/renderer/text/style.css    |  2 +-
 ivette/src/sandbox/sandbox.css             |  3 +-
 ivette/src/sandbox/text.tsx                | 47 ++++++++++++++++++----
 4 files changed, 52 insertions(+), 19 deletions(-)

diff --git a/ivette/src/dome/renderer/text/richtext.tsx b/ivette/src/dome/renderer/text/richtext.tsx
index 90713ed06b1..57acb9b6cde 100644
--- a/ivette/src/dome/renderer/text/richtext.tsx
+++ b/ivette/src/dome/renderer/text/richtext.tsx
@@ -35,8 +35,8 @@ export interface Range extends Offset { length: number }
 export interface Position extends Offset { line: number }
 export interface Selection extends Range { fromLine: number, toLine: number }
 
-export const empty : Range & Selection =
-  { offset: 0, length: 0, fromLine: 0, toLine: 0 };
+export const emptySelection : Range & Selection =
+  { offset: 0, length: 0, fromLine: 1, toLine: 1 };
 
 export function byDepth(a : Range, b : Range): number
 {
@@ -434,6 +434,7 @@ function isGutterDecoration(d : Decoration) : d is GutterDecoration
 
 class GutterMark extends CM.GutterMarker {
   private spec: GutterDecoration;
+
   constructor(spec: GutterDecoration) {
     super();
     this.spec = spec;
@@ -441,14 +442,15 @@ class GutterMark extends CM.GutterMarker {
 
   toDOM(): Node {
     const {  gutter, className, title } = this.spec;
-    const textNode = document.createElement(gutter);
+    const textNode = document.createTextNode(gutter);
     if (!className && !title) return textNode;
-    const span = document.createElement("span");
+    const span = document.createElement("div");
     span.appendChild(textNode);
     if (className) span.className = className;
     if (title) span.title = title;
     return span;
   }
+
 }
 
 const GutterMarks : Map<string, GutterMark> = new Map();
@@ -608,10 +610,8 @@ const Decorations: CS.Extension = [
   DecoratorState,
   CM.EditorView.decorations.from(DecoratorState, ({ ranges }) => ranges),
   CM.gutter({
-    initialSpacer: () => gutterMark(GutterInit),
+    initialSpacer: () => new GutterMark(GutterInit),
     markers: (view) => view.state.field(DecoratorState).gutters,
-    lineMarkerChange: (update) =>
-      update.transactions.some((tr) => !tr.annotation(DecoratorSpec))
   }),
 ];
 
@@ -621,7 +621,10 @@ const Decorations: CS.Extension = [
 
 function createView(parent: Element): CM.EditorView {
   const extensions : CS.Extension[] = [
-    ReadOnly, OnChange, OnSelect, Decorations
+    CM.lineNumbers(),
+    CM.highlightActiveLine(),
+    CM.highlightActiveLineGutter(),
+    ReadOnly, OnChange, OnSelect, Decorations,
   ];
   const state = CS.EditorState.create({ extensions });
   return new CM.EditorView({ state, parent });
diff --git a/ivette/src/dome/renderer/text/style.css b/ivette/src/dome/renderer/text/style.css
index 3b26fd8a537..ee063ac5ca4 100644
--- a/ivette/src/dome/renderer/text/style.css
+++ b/ivette/src/dome/renderer/text/style.css
@@ -124,7 +124,7 @@
 
 .cm-editor .cm-gutters {
   border-right: 0px;
-  min-width: 2.15em;
+  /*min-width: 2.15em;*/
   background: var(--background-report);
 }
 
diff --git a/ivette/src/sandbox/sandbox.css b/ivette/src/sandbox/sandbox.css
index 026652934fc..305b7d9465b 100644
--- a/ivette/src/sandbox/sandbox.css
+++ b/ivette/src/sandbox/sandbox.css
@@ -12,7 +12,6 @@
     font-style: italic;
 }
 
-.line-decoration {
+.cm-global-box .line-decoration {
     background: lightgreen;
 }
-
diff --git a/ivette/src/sandbox/text.tsx b/ivette/src/sandbox/text.tsx
index fbf585882e1..4f578a8e7f7 100644
--- a/ivette/src/sandbox/text.tsx
+++ b/ivette/src/sandbox/text.tsx
@@ -34,7 +34,7 @@ import {
   TextView,
   TextProxy,
   TextBuffer,
-  empty,
+  emptySelection,
   Decoration,
 } from 'dome/text/richtext';
 import { registerSandbox } from 'ivette';
@@ -45,15 +45,18 @@ import { registerSandbox } from 'ivette';
 
 function UseText(): JSX.Element {
   const [prefix, setPrefix] = React.useState('');
+  const [useLines, flipUseLines] = Dome.useFlipState(true);
   const [readOnly, flipReadOnly] = Dome.useFlipState(false);
   const [useProxy, flipUseProxy] = Dome.useFlipState(false);
+  const [changed, setChanged] = React.useState(false);
   const [changes, setChanges] = React.useState(0);
-  const [s, onSelection] = React.useState(empty);
+  const [s, onSelection] = React.useState(emptySelection);
   const proxy = React.useMemo(() => new TextProxy(), []);
   const buffer = React.useMemo(() => new TextBuffer(), []);
   const text = useProxy ? proxy : buffer;
   const updatePrefix = React.useCallback(
     () => {
+      setChanged(true);
       setChanges((n) => 1+n);
       setPrefix(text.toString().substring(0, 20).trim());
     }, [text]);
@@ -63,9 +66,21 @@ function UseText(): JSX.Element {
   }, [text]);
   const onChange = Dome.useDebounced(updatePrefix, 200);
   const [decorations, setDecorations] = React.useState<Decoration[]>([]);
-  const clearDecorations = React.useCallback(() => setDecorations([]), []);
+  const inconsistent = decorations.length > 0 && changed;
+
+  const clearDecorations = React.useCallback(() => {
+    setChanged(false);
+    setDecorations([]);
+  }, []);
+
+  const clearText = React.useCallback(() => {
+    setChanged(false);
+    setDecorations([]);
+    text.clear();
+  }, [text]);
 
   const addDecoration = React.useCallback(() => {
+    setChanged(false);
     setDecorations([...decorations, {
       offset: s.offset,
       length: s.length,
@@ -75,6 +90,7 @@ function UseText(): JSX.Element {
   }, [decorations, s]);
 
   const addLineDecoration = React.useCallback(() => {
+    setChanged(false);
     setDecorations([...decorations, {
       line: s.fromLine,
       className: 'line-decoration',
@@ -83,15 +99,25 @@ function UseText(): JSX.Element {
   }, [decorations, s]);
 
   const addGutterDecoration = React.useCallback(() => {
+    setChanged(false);
     setDecorations([...decorations, {
       line: s.fromLine,
       gutter: '*',
     }]);
   }, [decorations, s]);
 
+  const isLine = s.fromLine === s.toLine;
+  const isRange = s.length > 0;
+
   return (
     <>
       <ToolBar>
+        <Button
+          icon="ITEMS.LIST"
+          selected={useLines}
+          title={'Line Numbers'}
+          onClick={flipUseLines}
+        />
         <Button
           icon={readOnly ? 'LOCK' : 'EDIT'}
           title={readOnly ? 'Read Only' : 'Editable'}
@@ -104,34 +130,39 @@ function UseText(): JSX.Element {
         />
         <Code label={`Offset ${s.offset}-${s.offset + s.length}`} />
         <Code label={`Line ${s.fromLine}-${s.toLine}`} />
-        <Code label={`Decorations ${decorations.length}`} />
+        <Code
+          icon={inconsistent ? 'WARNING' : undefined}
+          title={inconsistent ? 'Iconsistent (modified text)' : undefined}
+          label={`Decorations ${decorations.length}`}
+        />
         <IconButton
-          display={s.length === 0}
+          display={isLine}
           icon="CIRC.INFO"
           title="Add Gutter Decoration"
           onClick={addGutterDecoration}
         />
         <IconButton
-          display={s.length === 0}
+          display={isLine}
           icon="CIRC.CHECK"
           title="Add Line Decoration"
           onClick={addLineDecoration}
         />
         <IconButton
-          display={s.length > 0}
+          display={isRange}
           icon="CIRC.PLUS"
           title="Add Decoration"
           onClick={addDecoration}
         />
         <IconButton
           display={decorations.length > 0}
+          kind={inconsistent ? 'negative' : 'default'}
           icon="CIRC.CLOSE"
           title="Clear Decorations"
           onClick={clearDecorations} />
         <Filler />
         <Code>{`"${prefix}" (${changes})`}</Code>
         <Button label="Push" onClick={push} />
-        <Button label="Clear" kind='negative' onClick={text.clear}  />
+        <Button label="Clear" kind='negative' onClick={clearText}  />
       </ToolBar>
       <TextView
         text={text}
-- 
GitLab