Skip to content

Commit

Permalink
Tracking event invocation and delete edges
Browse files Browse the repository at this point in the history
  • Loading branch information
ATrefzer committed Sep 14, 2024
1 parent 164499a commit 708ec2e
Show file tree
Hide file tree
Showing 19 changed files with 610 additions and 115 deletions.
31 changes: 28 additions & 3 deletions CSharpCodeAnalyst/Exploration/CodeGraphExplorer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ public SearchResult FollowIncomingCallsRecursive(string id)
GetDependencies(d => d.Type is DependencyType.Implements or DependencyType.Overrides);
var allCalls = GetDependencies(d => d.Type == DependencyType.Calls);

var allHandles = GetDependencies(d => d.Type == DependencyType.Handles);
var allInvokes = GetDependencies(d => d.Type == DependencyType.Invokes);

var method = _codeGraph.Nodes[id];

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

// An event is raised by the specialization
var specializations = allImplementsAndOverrides.Where(d => d.TargetId == element.Id).ToArray();
foundDependencies.UnionWith(specializations);
var specializedSources = specializations.Select(d => _codeGraph.Nodes[d.SourceId]).ToHashSet();
foundElements.UnionWith(specializedSources);

// Add all methods that invoke the event
var invokes = allInvokes.Where(call => call.TargetId == element.Id).ToArray();
foundDependencies.UnionWith(invokes);
var invokeSources = invokes.Select(d => _codeGraph.Nodes[d.SourceId]).ToHashSet();
foundElements.UnionWith(invokeSources);

// Add Events that are handled by this method.
var handles = allHandles.Where(h => h.SourceId == element.Id).ToArray();
foundDependencies.UnionWith(handles);
var events = handles.Select(h => _codeGraph.Nodes[h.TargetId]).ToHashSet();
foundElements.UnionWith(events);

// Calls
var calls = allCalls.Where(call => call.TargetId == element.Id).ToArray();
foundDependencies.UnionWith(calls);
Expand All @@ -217,10 +238,14 @@ public SearchResult FollowIncomingCallsRecursive(string id)
foundElements.UnionWith(abstractionTargets);

// Follow new leads
var methodsToExplore = abstractionTargets.Union(callSources);
foreach (var methodToExplore in methodsToExplore)
var elementsToExplore = abstractionTargets
.Union(callSources)
.Union(events)
.Union(invokeSources)
.Union(specializedSources);
foreach (var elementToExplore in elementsToExplore)
{
processingQueue.Enqueue(_codeGraph.Nodes[methodToExplore.Id]);
processingQueue.Enqueue(_codeGraph.Nodes[elementToExplore.Id]);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

namespace CSharpCodeAnalyst.GraphArea;

public class ContextCommand : IContextCommand
public class CodeElementContextCommand : ICodeElementContextCommand
{
private readonly Action<CodeElement> _action;
private readonly Func<CodeElement, bool>? _canExecute;
private readonly CodeElementType? _type;

public ContextCommand(string label, CodeElementType type, Action<CodeElement> action)
public CodeElementContextCommand(string label, CodeElementType type, Action<CodeElement> action)
{
_type = type;
_action = action;
Expand All @@ -18,7 +18,7 @@ public ContextCommand(string label, CodeElementType type, Action<CodeElement> ac
/// <summary>
/// Generic for all code elements
/// </summary>
public ContextCommand(string label, Action<CodeElement> action, Func<CodeElement, bool>? canExecute = null)
public CodeElementContextCommand(string label, Action<CodeElement> action, Func<CodeElement, bool>? canExecute = null)
{
_type = null;
_action = action;
Expand Down
63 changes: 63 additions & 0 deletions CSharpCodeAnalyst/GraphArea/DependencyContextCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using Contracts.Graph;

namespace CSharpCodeAnalyst.GraphArea;

public class DependencyContextCommand : IDependencyContextCommand
{
private readonly Action<List<Dependency>> _action;
private readonly Func<List<Dependency>, bool>? _canExecute;
private readonly DependencyType? _type;

public DependencyContextCommand(string label, DependencyType type, Action<List<Dependency>> action)
{
_type = type;
_action = action;
Label = label;
}

/// <summary>
/// Generic for all code elements
/// </summary>
public DependencyContextCommand(string label, Action<List<Dependency>> action,
Func<List<Dependency>, bool>? canExecute = null)
{
_type = null;
_action = action;
_canExecute = canExecute;
Label = label;
}

public string Label { get; }

public bool CanHandle(List<Dependency> dependencies)
{
// This is a dummy dependency to visualize hierarchical relationships in flat graph.
if (dependencies.Any(d => d.Type == DependencyType.Containment))
{
return false;
}

if (_type != null)
{
if (dependencies.All(d => d.Type == _type) is false)
{
return false;
}
}

var canHandle = true;
if (_canExecute != null)
{
// Further restrict the handling
canHandle = _canExecute.Invoke(dependencies);
}

return canHandle;
}


public void Invoke(List<Dependency> dependencies)
{
_action.Invoke(dependencies);
}
}
152 changes: 115 additions & 37 deletions CSharpCodeAnalyst/GraphArea/DependencyGraphViewer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ namespace CSharpCodeAnalyst.GraphArea;
/// </summary>
internal class DependencyGraphViewer : IDependencyGraphViewer, IDependencyGraphBinding, INotifyPropertyChanged
{
private readonly List<IContextCommand> _contextMenuCommands = [];
private readonly List<IGlobalContextCommand> _globalContextMenuCommands = [];
private readonly List<IDependencyContextCommand> _edgeCommands = [];
private readonly List<IGlobalContextCommand> _globalCommands = [];
private readonly MsaglBuilder _msaglBuilder;
private readonly List<ICodeElementContextCommand> _nodeCommands = [];
private readonly IPublisher _publisher;

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

public void AddContextMenuCommand(IContextCommand command)
public void AddContextMenuCommand(ICodeElementContextCommand command)
{
_contextMenuCommands.Add(command);
_nodeCommands.Add(command);
}

public void AddContextMenuCommand(IDependencyContextCommand command)
{
_edgeCommands.Add(command);
}

public void AddGlobalContextMenuCommand(IGlobalContextCommand command)
{
_globalContextMenuCommands.Add(command);
_globalCommands.Add(command);
}

public void Layout()
Expand Down Expand Up @@ -126,7 +132,7 @@ public void SaveToSvg(FileStream stream)

public void SetHighlightMode(HighlightMode valueMode)
{
_activeHighlighting?.Clear(_msaglViewer);
_activeHighlighting.Clear(_msaglViewer);
switch (valueMode)
{
case HighlightMode.EdgeHovered:
Expand Down Expand Up @@ -166,7 +172,7 @@ public void ShowGlobalContextMenu()
.Select(id => _clonedCodeGraph.Nodes[id])
.ToList();

foreach (var command in _globalContextMenuCommands)
foreach (var command in _globalCommands)
{
if (command.CanHandle(markedElements) is false)
{
Expand Down Expand Up @@ -201,6 +207,21 @@ public void Clear()
RefreshGraph();
}

public void DeleteFromGraph(List<Dependency> dependencies)
{
if (_msaglViewer is null)
{
return;
}

foreach (var dependency in dependencies)
{
_clonedCodeGraph.Nodes[dependency.SourceId].Dependencies.Remove(dependency);
}

RefreshGraph();
}

public void DeleteFromGraph(HashSet<string> idsToRemove)
{
if (_msaglViewer is null)
Expand Down Expand Up @@ -390,59 +411,116 @@ bool IsCtrlPressed()

if (e.RightButtonIsPressed)
{
if (_msaglViewer?.ObjectUnderMouseCursor is not IViewerNode clickedObject)
if (_msaglViewer?.ObjectUnderMouseCursor is IViewerNode clickedObject)
{
return;
// Click on specific node
var node = clickedObject.Node;
var contextMenu = new ContextMenu();
var element = GetCodeElementFromUserData(node);
AddToContextMenuEntries(element, contextMenu);
if (contextMenu.Items.Count > 0)
{
contextMenu.IsOpen = true;
}
}
else if (_msaglViewer?.ObjectUnderMouseCursor is IViewerEdge viewerEdge)
{
// Click on specific edge
var edge = viewerEdge.Edge;
var contextMenu = new ContextMenu();
var dependencies = GetDependenciesFromUserData(edge);
AddContextMenuEntries(dependencies, contextMenu);
if (contextMenu.Items.Count > 0)
{
contextMenu.IsOpen = true;
}
}
else
{
// Click on free space
ShowGlobalContextMenu();
}

// Click on specific node
var node = clickedObject.Node;
var contextMenu = new ContextMenu();

AddToContextMenuEntries(node, contextMenu);

contextMenu.IsOpen = true;
}
else
{
e.Handled = false;
}
}

private void AddContextMenuEntries(List<Dependency> dependencies, ContextMenu contextMenu)
{
if (dependencies.Count == 0)
{
return;
}

foreach (var cmd in _edgeCommands)
{
var menuItem = new MenuItem { Header = cmd.Label };
if (cmd.CanHandle(dependencies))
{
menuItem.Click += (_, _) => cmd.Invoke(dependencies);
contextMenu.Items.Add(menuItem);
}
}
}

private static List<Dependency> GetDependenciesFromUserData(Edge edge)
{
var result = new List<Dependency>();
switch (edge.UserData)
{
case Dependency dependency:
result.Add(dependency);
break;
case List<Dependency> dependencies:
result.AddRange(dependencies);
break;
}

return result;
}

private CodeElement? GetCodeElementFromUserData(Node node)
{
return node.UserData as CodeElement;
}

/// <summary>
/// Commands registered for nodes
/// </summary>
private void AddToContextMenuEntries(Node node, ContextMenu contextMenu)
private void AddToContextMenuEntries(CodeElement? element, ContextMenu contextMenu)
{
if (element is null)
{
return;
}

var lastItemIsSeparator = true;

if (node.UserData is CodeElement element)
foreach (var cmd in _nodeCommands)
{
foreach (var cmd in _contextMenuCommands)
// Add separator command only if the last element was a real menu item.
if (cmd is SeparatorCommand)
{
// Add separator command only if the last element was a real menu item.
if (cmd is SeparatorCommand)
if (lastItemIsSeparator is false)
{
if (lastItemIsSeparator is false)
{
contextMenu.Items.Add(new Separator());
lastItemIsSeparator = true;
}

continue;
contextMenu.Items.Add(new Separator());
lastItemIsSeparator = true;
}

if (!cmd.CanHandle(element))
{
continue;
}
continue;
}

var menuItem = new MenuItem { Header = cmd.Label };
menuItem.Click += (_, _) => cmd.Invoke(element);
contextMenu.Items.Add(menuItem);
lastItemIsSeparator = false;
if (!cmd.CanHandle(element))
{
continue;
}

var menuItem = new MenuItem { Header = cmd.Label };
menuItem.Click += (_, _) => cmd.Invoke(element);
contextMenu.Items.Add(menuItem);
lastItemIsSeparator = false;
}
}
}
Loading

0 comments on commit 708ec2e

Please sign in to comment.