From 0fda3de642109badfb77fef217e48003b36e09c8 Mon Sep 17 00:00:00 2001
From: Maxime Jacquemin <maxime2.jacquemin@gmail.com>
Date: Mon, 9 Jan 2023 15:39:48 +0100
Subject: [PATCH] [Ivette] Handling multiple occurrences of same tag

---
 ivette/src/dome/renderer/text/editor.tsx | 23 +++++------
 ivette/src/dome/renderer/text/style.css  |  2 +-
 ivette/src/frama-c/kernel/ASTview.tsx    | 49 ++++++++++++++----------
 3 files changed, 39 insertions(+), 35 deletions(-)

diff --git a/ivette/src/dome/renderer/text/editor.tsx b/ivette/src/dome/renderer/text/editor.tsx
index 37bc190091f..a7cf8b2a73e 100644
--- a/ivette/src/dome/renderer/text/editor.tsx
+++ b/ivette/src/dome/renderer/text/editor.tsx
@@ -32,21 +32,18 @@ import { showTooltip, Tooltip } from '@codemirror/view';
 import { DecorationSet } from '@codemirror/view';
 import { lineNumbers } from '@codemirror/view';
 
-export type { Extension } from '@codemirror/state';
-export { GutterMarker } from '@codemirror/view';
-export { Decoration } from '@codemirror/view';
-export { RangeSet } from '@codemirror/state';
-
 import { parser } from '@lezer/cpp';
 import { tags } from '@lezer/highlight';
 import { SyntaxNode } from '@lezer/common';
 import * as Language from '@codemirror/language';
-import { foldGutter, foldNodeProp } from '@codemirror/language';
-import { LRLanguage, LanguageSupport } from "@codemirror/language";
-import { syntaxHighlighting, HighlightStyle } from '@codemirror/language';
 
 import './style.css';
 
+export type { Extension } from '@codemirror/state';
+export { GutterMarker } from '@codemirror/view';
+export { Decoration } from '@codemirror/view';
+export { RangeSet } from '@codemirror/state';
+
 
 
 // -----------------------------------------------------------------------------
@@ -265,7 +262,7 @@ export function createEventHandler<I extends Dict>(
 // -----------------------------------------------------------------------------
 
 // Plugin specifying how to highlight the code. The theme is handled by the CSS.
-const Highlight = syntaxHighlighting(HighlightStyle.define([
+const Highlight = Language.syntaxHighlighting(Language.HighlightStyle.define([
   { tag: tags.comment, class: 'cm-comment' },
   { tag: tags.typeName, class: 'cm-type' },
   { tag: tags.number, class: 'cm-number' },
@@ -277,9 +274,9 @@ const Highlight = syntaxHighlighting(HighlightStyle.define([
 // highlighting and folding information. Only comments can be folded.
 // (Source: https://github.com/lezer-parser/cpp)
 const comment = (t: SyntaxNode): Range => ({ from: t.from + 2, to: t.to - 2});
-const folder = foldNodeProp.add({ BlockComment: comment });
+const folder = Language.foldNodeProp.add({ BlockComment: comment });
 const stringPrefixes = [ "L", "u", "U", "u8", "LR", "UR", "uR", "u8R", "R" ];
-const cppLanguage = LRLanguage.define({
+const cppLanguage = Language.LRLanguage.define({
   parser: parser.configure({ props: [ folder ] }),
   languageData: {
     commentTokens: { line: "//", block: { open: "/*", close: "*/" } },
@@ -290,7 +287,7 @@ const cppLanguage = LRLanguage.define({
 
 // This extension enables all the language highlighting features.
 export const LanguageHighlighter: Extension =
-  [Highlight, new LanguageSupport(cppLanguage)];
+  [Highlight, new Language.LanguageSupport(cppLanguage)];
 
 // -----------------------------------------------------------------------------
 
@@ -327,7 +324,7 @@ function createLineNumbers(): Extension {
 
 export const FoldGutter = createFoldGutter();
 function createFoldGutter(): Extension {
-  return foldGutter();
+  return Language.foldGutter();
 }
 
 export function foldAll(view: EditorView | null): void {
diff --git a/ivette/src/dome/renderer/text/style.css b/ivette/src/dome/renderer/text/style.css
index 51e441417e5..53ff7035136 100644
--- a/ivette/src/dome/renderer/text/style.css
+++ b/ivette/src/dome/renderer/text/style.css
@@ -138,7 +138,7 @@
   background: var(--code-bullet);
 }
 
-.cm-hovered-code:hover {
+.cm-hovered-code {
   background-color: var(--code-hover);
 }
 
diff --git a/ivette/src/frama-c/kernel/ASTview.tsx b/ivette/src/frama-c/kernel/ASTview.tsx
index a2c4934ab44..91cb9a99de9 100644
--- a/ivette/src/frama-c/kernel/ASTview.tsx
+++ b/ivette/src/frama-c/kernel/ASTview.tsx
@@ -115,17 +115,24 @@ function textToString(text: text): string {
 
 // Computes, for each markers of a tree, its range. Returns the map containing
 // all those bindings.
-function markersRanges(tree: Tree): Map<string, Range>{
-  const ranges: Map<string, Range> = new Map();
+function markersRanges(tree: Tree): Map<string, Range[]>{
+  const ranges: Map<string, Range[]> = new Map();
   const toRanges = (tree: Tree): void => {
     if (!isNode(tree)) return;
-    ranges.set(tree.id, tree);
+    const trees = ranges.get(tree.id) ?? [];
+    trees.push(tree);
+    ranges.set(tree.id, trees);
     for (const child of tree.children) toRanges(child);
   };
   toRanges(tree);
   return ranges;
 }
 
+function uniqueRange(m: string, rs: Map<string, Range[]>): Range | undefined {
+  const ranges = rs.get(m);
+  return (ranges && ranges.length > 0) ? ranges[0] : undefined;
+}
+
 // Find the closest covering tagged node of a given position. Returns
 // undefined if there is not relevant covering node.
 function coveringNode(tree: Tree, pos: number): Node | undefined {
@@ -226,7 +233,7 @@ function createHoveredUpdater(): Editor.Extension {
       const horizontallyOk = left <= coords.x && coords.x <= right;
       const verticallyOk = top <= coords.y && coords.y <= bottom;
       if (!horizontallyOk || !verticallyOk) return;
-      const marker = Ast.jMarker(hov?.id);
+      const marker = Ast.jMarker(hov.id);
       updateHovered(marker ? { fct, marker } : undefined);
     }
   });
@@ -244,12 +251,11 @@ function createCodeDecorator(): Editor.Extension {
   const selectedClass = Editor.Decoration.mark({ class: 'cm-selected-code' });
   const deps = { ranges: Ranges, marker: Marker, hovered: Hovered };
   return Editor.createDecorator(deps, ({ ranges, marker: m, hovered: h }) => {
-    const selected = m && ranges.get(m);
-    const hovered = h && ranges.get(h);
-    const range = selected && selectedClass.range(selected.from, selected.to);
-    const add = hovered && [ hoveredClass.range(hovered.from, hovered.to) ];
-    const set = range ? Editor.RangeSet.of(range) : Editor.RangeSet.empty;
-    return set.update({ add, sort: true });
+    const hoveredRanges = (h && ranges.get(h)) ?? [];
+    const selectedRanges = (m && ranges.get(m)) ?? [];
+    const hovered = hoveredRanges.map(r => hoveredClass.range(r.from, r.to));
+    const selected = selectedRanges.map(r => selectedClass.range(r.from, r.to));
+    return Editor.RangeSet.of(selected).update({ add: hovered, sort: true });
   });
 }
 
@@ -269,10 +275,10 @@ function createDeadCodeDecorator(): Editor.Extension {
   const tClass = Editor.Decoration.mark({ class: 'cm-non-term-code' });
   const deps = { dead: Dead, ranges: Ranges };
   return Editor.createDecorator(deps, ({ dead, ranges }) => {
-    const range = (marker: string): Range | undefined => ranges.get(marker);
-    const unreachableRanges = mapFilter(dead.unreachable, range);
+    const range = (m: string): Range[] | undefined => ranges.get(m);
+    const unreachableRanges = mapFilter(dead.unreachable, range).flat();
     const unreachable = unreachableRanges.map(r => uClass.range(r.from, r.to));
-    const nonTermRanges = mapFilter(dead.nonTerminating, range);
+    const nonTermRanges = mapFilter(dead.nonTerminating, range).flat();
     const nonTerm = nonTermRanges.map(r => tClass.range(r.from, r.to));
     return Editor.RangeSet.of(unreachable.concat(nonTerm), true);
   });
@@ -296,11 +302,11 @@ const PropertiesStatuses = Editor.createField<Properties.statusData[]>([]);
 const PropertiesRanges = createPropertiesRange();
 interface PropertyRange extends Properties.statusData { range: Range }
 function createPropertiesRange(): Editor.Aspect<PropertyRange[]> {
-  const deps = { ps: PropertiesStatuses, ranges: Ranges };
-  return Editor.createAspect(deps, ({ ps, ranges }) => mapFilter(ps, (p) => {
-    const range = ranges.get(p.key);
-    return range && { ...p, range };
-  }));
+  const deps = { properties: PropertiesStatuses, ranges: Ranges };
+  return Editor.createAspect(deps, ({ properties, ranges }) => {
+    const get = (p: Properties.statusData): Range[] => ranges.get(p.key) ?? [];
+    return properties.map(p => get(p).map(r => ({ ...p, range: r }))).flat();
+  });
 }
 
 // This aspect computes the tag associated to each property.
@@ -502,8 +508,9 @@ function createTaintedLvaluesDecorator(): Editor.Extension {
   const mark = Editor.Decoration.mark({ class: 'cm-tainted' });
   const deps = { ranges: Ranges, tainted: TaintedLvalues };
   return Editor.createDecorator(deps, ({ ranges, tainted = [] }) => {
-    const find = (t: Taints): Range | undefined => ranges.get(t.lval);
-    const marks = mapFilter(tainted, find).map(r => mark.range(r.from, r.to));
+    const find = (t: Taints): Range[] | undefined => ranges.get(t.lval);
+    const taintedRanges = mapFilter(tainted, find).flat();
+    const marks = taintedRanges.map(r => mark.range(r.from, r.to));
     return Editor.RangeSet.of(marks, true);
   });
 }
@@ -513,7 +520,7 @@ function createTaintTooltip(): Editor.Extension {
   const deps = { hovered: Hovered, ranges: Ranges, tainted: TaintedLvalues };
   return Editor.createTooltip(deps, ({ hovered, ranges, tainted }) => {
     const hoveredTaint = tainted?.find(t => t.lval === hovered);
-    const hoveredNode = hovered && ranges.get(hovered);
+    const hoveredNode = hovered && uniqueRange(hovered, ranges);
     if (!hoveredTaint || !hoveredNode) return undefined;
     return {
       pos: hoveredNode.from,
-- 
GitLab