Skip to content

Commit 708ec2e

Browse files
committed
Tracking event invocation and delete edges
1 parent 164499a commit 708ec2e

19 files changed

+610
-115
lines changed

CSharpCodeAnalyst/Exploration/CodeGraphExplorer.cs

+28-3
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,9 @@ public SearchResult FollowIncomingCallsRecursive(string id)
186186
GetDependencies(d => d.Type is DependencyType.Implements or DependencyType.Overrides);
187187
var allCalls = GetDependencies(d => d.Type == DependencyType.Calls);
188188

189+
var allHandles = GetDependencies(d => d.Type == DependencyType.Handles);
190+
var allInvokes = GetDependencies(d => d.Type == DependencyType.Invokes);
191+
189192
var method = _codeGraph.Nodes[id];
190193

191194
var processingQueue = new Queue<CodeElement>();
@@ -204,6 +207,24 @@ public SearchResult FollowIncomingCallsRecursive(string id)
204207
continue;
205208
}
206209

210+
// An event is raised by the specialization
211+
var specializations = allImplementsAndOverrides.Where(d => d.TargetId == element.Id).ToArray();
212+
foundDependencies.UnionWith(specializations);
213+
var specializedSources = specializations.Select(d => _codeGraph.Nodes[d.SourceId]).ToHashSet();
214+
foundElements.UnionWith(specializedSources);
215+
216+
// Add all methods that invoke the event
217+
var invokes = allInvokes.Where(call => call.TargetId == element.Id).ToArray();
218+
foundDependencies.UnionWith(invokes);
219+
var invokeSources = invokes.Select(d => _codeGraph.Nodes[d.SourceId]).ToHashSet();
220+
foundElements.UnionWith(invokeSources);
221+
222+
// Add Events that are handled by this method.
223+
var handles = allHandles.Where(h => h.SourceId == element.Id).ToArray();
224+
foundDependencies.UnionWith(handles);
225+
var events = handles.Select(h => _codeGraph.Nodes[h.TargetId]).ToHashSet();
226+
foundElements.UnionWith(events);
227+
207228
// Calls
208229
var calls = allCalls.Where(call => call.TargetId == element.Id).ToArray();
209230
foundDependencies.UnionWith(calls);
@@ -217,10 +238,14 @@ public SearchResult FollowIncomingCallsRecursive(string id)
217238
foundElements.UnionWith(abstractionTargets);
218239

219240
// Follow new leads
220-
var methodsToExplore = abstractionTargets.Union(callSources);
221-
foreach (var methodToExplore in methodsToExplore)
241+
var elementsToExplore = abstractionTargets
242+
.Union(callSources)
243+
.Union(events)
244+
.Union(invokeSources)
245+
.Union(specializedSources);
246+
foreach (var elementToExplore in elementsToExplore)
222247
{
223-
processingQueue.Enqueue(_codeGraph.Nodes[methodToExplore.Id]);
248+
processingQueue.Enqueue(_codeGraph.Nodes[elementToExplore.Id]);
224249
}
225250
}
226251

CSharpCodeAnalyst/GraphArea/ContextCommand.cs renamed to CSharpCodeAnalyst/GraphArea/CodeElementContextCommand.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
namespace CSharpCodeAnalyst.GraphArea;
44

5-
public class ContextCommand : IContextCommand
5+
public class CodeElementContextCommand : ICodeElementContextCommand
66
{
77
private readonly Action<CodeElement> _action;
88
private readonly Func<CodeElement, bool>? _canExecute;
99
private readonly CodeElementType? _type;
1010

11-
public ContextCommand(string label, CodeElementType type, Action<CodeElement> action)
11+
public CodeElementContextCommand(string label, CodeElementType type, Action<CodeElement> action)
1212
{
1313
_type = type;
1414
_action = action;
@@ -18,7 +18,7 @@ public ContextCommand(string label, CodeElementType type, Action<CodeElement> ac
1818
/// <summary>
1919
/// Generic for all code elements
2020
/// </summary>
21-
public ContextCommand(string label, Action<CodeElement> action, Func<CodeElement, bool>? canExecute = null)
21+
public CodeElementContextCommand(string label, Action<CodeElement> action, Func<CodeElement, bool>? canExecute = null)
2222
{
2323
_type = null;
2424
_action = action;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using Contracts.Graph;
2+
3+
namespace CSharpCodeAnalyst.GraphArea;
4+
5+
public class DependencyContextCommand : IDependencyContextCommand
6+
{
7+
private readonly Action<List<Dependency>> _action;
8+
private readonly Func<List<Dependency>, bool>? _canExecute;
9+
private readonly DependencyType? _type;
10+
11+
public DependencyContextCommand(string label, DependencyType type, Action<List<Dependency>> action)
12+
{
13+
_type = type;
14+
_action = action;
15+
Label = label;
16+
}
17+
18+
/// <summary>
19+
/// Generic for all code elements
20+
/// </summary>
21+
public DependencyContextCommand(string label, Action<List<Dependency>> action,
22+
Func<List<Dependency>, bool>? canExecute = null)
23+
{
24+
_type = null;
25+
_action = action;
26+
_canExecute = canExecute;
27+
Label = label;
28+
}
29+
30+
public string Label { get; }
31+
32+
public bool CanHandle(List<Dependency> dependencies)
33+
{
34+
// This is a dummy dependency to visualize hierarchical relationships in flat graph.
35+
if (dependencies.Any(d => d.Type == DependencyType.Containment))
36+
{
37+
return false;
38+
}
39+
40+
if (_type != null)
41+
{
42+
if (dependencies.All(d => d.Type == _type) is false)
43+
{
44+
return false;
45+
}
46+
}
47+
48+
var canHandle = true;
49+
if (_canExecute != null)
50+
{
51+
// Further restrict the handling
52+
canHandle = _canExecute.Invoke(dependencies);
53+
}
54+
55+
return canHandle;
56+
}
57+
58+
59+
public void Invoke(List<Dependency> dependencies)
60+
{
61+
_action.Invoke(dependencies);
62+
}
63+
}

CSharpCodeAnalyst/GraphArea/DependencyGraphViewer.cs

+115-37
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ namespace CSharpCodeAnalyst.GraphArea;
2323
/// </summary>
2424
internal class DependencyGraphViewer : IDependencyGraphViewer, IDependencyGraphBinding, INotifyPropertyChanged
2525
{
26-
private readonly List<IContextCommand> _contextMenuCommands = [];
27-
private readonly List<IGlobalContextCommand> _globalContextMenuCommands = [];
26+
private readonly List<IDependencyContextCommand> _edgeCommands = [];
27+
private readonly List<IGlobalContextCommand> _globalCommands = [];
2828
private readonly MsaglBuilder _msaglBuilder;
29+
private readonly List<ICodeElementContextCommand> _nodeCommands = [];
2930
private readonly IPublisher _publisher;
3031

3132
private IHighlighting _activeHighlighting = new EdgeHoveredHighlighting();
@@ -86,14 +87,19 @@ public void AddToGraph(IEnumerable<CodeElement> originalCodeElements, IEnumerabl
8687
RefreshGraph();
8788
}
8889

89-
public void AddContextMenuCommand(IContextCommand command)
90+
public void AddContextMenuCommand(ICodeElementContextCommand command)
9091
{
91-
_contextMenuCommands.Add(command);
92+
_nodeCommands.Add(command);
93+
}
94+
95+
public void AddContextMenuCommand(IDependencyContextCommand command)
96+
{
97+
_edgeCommands.Add(command);
9298
}
9399

94100
public void AddGlobalContextMenuCommand(IGlobalContextCommand command)
95101
{
96-
_globalContextMenuCommands.Add(command);
102+
_globalCommands.Add(command);
97103
}
98104

99105
public void Layout()
@@ -126,7 +132,7 @@ public void SaveToSvg(FileStream stream)
126132

127133
public void SetHighlightMode(HighlightMode valueMode)
128134
{
129-
_activeHighlighting?.Clear(_msaglViewer);
135+
_activeHighlighting.Clear(_msaglViewer);
130136
switch (valueMode)
131137
{
132138
case HighlightMode.EdgeHovered:
@@ -166,7 +172,7 @@ public void ShowGlobalContextMenu()
166172
.Select(id => _clonedCodeGraph.Nodes[id])
167173
.ToList();
168174

169-
foreach (var command in _globalContextMenuCommands)
175+
foreach (var command in _globalCommands)
170176
{
171177
if (command.CanHandle(markedElements) is false)
172178
{
@@ -201,6 +207,21 @@ public void Clear()
201207
RefreshGraph();
202208
}
203209

210+
public void DeleteFromGraph(List<Dependency> dependencies)
211+
{
212+
if (_msaglViewer is null)
213+
{
214+
return;
215+
}
216+
217+
foreach (var dependency in dependencies)
218+
{
219+
_clonedCodeGraph.Nodes[dependency.SourceId].Dependencies.Remove(dependency);
220+
}
221+
222+
RefreshGraph();
223+
}
224+
204225
public void DeleteFromGraph(HashSet<string> idsToRemove)
205226
{
206227
if (_msaglViewer is null)
@@ -390,59 +411,116 @@ bool IsCtrlPressed()
390411

391412
if (e.RightButtonIsPressed)
392413
{
393-
if (_msaglViewer?.ObjectUnderMouseCursor is not IViewerNode clickedObject)
414+
if (_msaglViewer?.ObjectUnderMouseCursor is IViewerNode clickedObject)
394415
{
395-
return;
416+
// Click on specific node
417+
var node = clickedObject.Node;
418+
var contextMenu = new ContextMenu();
419+
var element = GetCodeElementFromUserData(node);
420+
AddToContextMenuEntries(element, contextMenu);
421+
if (contextMenu.Items.Count > 0)
422+
{
423+
contextMenu.IsOpen = true;
424+
}
425+
}
426+
else if (_msaglViewer?.ObjectUnderMouseCursor is IViewerEdge viewerEdge)
427+
{
428+
// Click on specific edge
429+
var edge = viewerEdge.Edge;
430+
var contextMenu = new ContextMenu();
431+
var dependencies = GetDependenciesFromUserData(edge);
432+
AddContextMenuEntries(dependencies, contextMenu);
433+
if (contextMenu.Items.Count > 0)
434+
{
435+
contextMenu.IsOpen = true;
436+
}
437+
}
438+
else
439+
{
440+
// Click on free space
441+
ShowGlobalContextMenu();
396442
}
397-
398-
// Click on specific node
399-
var node = clickedObject.Node;
400-
var contextMenu = new ContextMenu();
401-
402-
AddToContextMenuEntries(node, contextMenu);
403-
404-
contextMenu.IsOpen = true;
405443
}
406444
else
407445
{
408446
e.Handled = false;
409447
}
410448
}
411449

450+
private void AddContextMenuEntries(List<Dependency> dependencies, ContextMenu contextMenu)
451+
{
452+
if (dependencies.Count == 0)
453+
{
454+
return;
455+
}
456+
457+
foreach (var cmd in _edgeCommands)
458+
{
459+
var menuItem = new MenuItem { Header = cmd.Label };
460+
if (cmd.CanHandle(dependencies))
461+
{
462+
menuItem.Click += (_, _) => cmd.Invoke(dependencies);
463+
contextMenu.Items.Add(menuItem);
464+
}
465+
}
466+
}
467+
468+
private static List<Dependency> GetDependenciesFromUserData(Edge edge)
469+
{
470+
var result = new List<Dependency>();
471+
switch (edge.UserData)
472+
{
473+
case Dependency dependency:
474+
result.Add(dependency);
475+
break;
476+
case List<Dependency> dependencies:
477+
result.AddRange(dependencies);
478+
break;
479+
}
480+
481+
return result;
482+
}
483+
484+
private CodeElement? GetCodeElementFromUserData(Node node)
485+
{
486+
return node.UserData as CodeElement;
487+
}
412488

413489
/// <summary>
414490
/// Commands registered for nodes
415491
/// </summary>
416-
private void AddToContextMenuEntries(Node node, ContextMenu contextMenu)
492+
private void AddToContextMenuEntries(CodeElement? element, ContextMenu contextMenu)
417493
{
494+
if (element is null)
495+
{
496+
return;
497+
}
498+
418499
var lastItemIsSeparator = true;
419500

420-
if (node.UserData is CodeElement element)
501+
foreach (var cmd in _nodeCommands)
421502
{
422-
foreach (var cmd in _contextMenuCommands)
503+
// Add separator command only if the last element was a real menu item.
504+
if (cmd is SeparatorCommand)
423505
{
424-
// Add separator command only if the last element was a real menu item.
425-
if (cmd is SeparatorCommand)
506+
if (lastItemIsSeparator is false)
426507
{
427-
if (lastItemIsSeparator is false)
428-
{
429-
contextMenu.Items.Add(new Separator());
430-
lastItemIsSeparator = true;
431-
}
432-
433-
continue;
508+
contextMenu.Items.Add(new Separator());
509+
lastItemIsSeparator = true;
434510
}
435511

436-
if (!cmd.CanHandle(element))
437-
{
438-
continue;
439-
}
512+
continue;
513+
}
440514

441-
var menuItem = new MenuItem { Header = cmd.Label };
442-
menuItem.Click += (_, _) => cmd.Invoke(element);
443-
contextMenu.Items.Add(menuItem);
444-
lastItemIsSeparator = false;
515+
if (!cmd.CanHandle(element))
516+
{
517+
continue;
445518
}
519+
520+
var menuItem = new MenuItem { Header = cmd.Label };
521+
menuItem.Click += (_, _) => cmd.Invoke(element);
522+
contextMenu.Items.Add(menuItem);
523+
lastItemIsSeparator = false;
446524
}
447525
}
448526
}

0 commit comments

Comments
 (0)