Skip to content

Commit 60ca82c

Browse files
authored
Merge pull request #25 from idontsov/sub-tree-navigation
Drill-through navigation
2 parents b6682ef + c5b6ba8 commit 60ca82c

File tree

2 files changed

+177
-108
lines changed

2 files changed

+177
-108
lines changed

src/Depends/Depends.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<ItemGroup>
1919
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.8" />
2020
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="3.0.0" />
21-
<PackageReference Include="Terminal.Gui" Version="0.81.0" />
21+
<PackageReference Include="Terminal.Gui" Version="1.3.1" />
2222
<PackageReference Include="MinVer" Version="2.3.1">
2323
<PrivateAssets>all</PrivateAssets>
2424
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>

src/Depends/Program.cs

Lines changed: 176 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,16 @@ private void OnExecute()
111111
.SetMinimumLevel(Verbosity)
112112
.AddConsole());
113113

114+
var graph = GetDependencyGraph(loggerFactory);
115+
116+
Application.Init();
117+
Application.QuitKey = Key.Esc;
118+
Application.Top.Add(new AppWindow(graph));
119+
Application.Run();
120+
}
121+
122+
private DependencyGraph GetDependencyGraph(ILoggerFactory loggerFactory)
123+
{
114124
var analyzer = new DependencyAnalyzer(loggerFactory);
115125
DependencyGraph graph;
116126
if (!string.IsNullOrEmpty(Package))
@@ -125,140 +135,199 @@ private void OnExecute()
125135
{
126136
graph = analyzer.Analyze(Project, Framework);
127137
}
138+
return graph;
139+
}
128140

129-
Application.Init();
141+
private class AppWindow : Window
142+
{
143+
private readonly DependencyGraph _graph;
144+
private readonly ImmutableList<Node> _dependencies;
145+
private ImmutableList<Node> _visibleDependencies;
146+
private bool _assembliesVisible;
147+
private int _lastSelectedDependencyIndex;
148+
149+
private readonly ListView _dependenciesView;
150+
private readonly ListView _runtimeDependsView;
151+
private readonly ListView _packageDependsView;
152+
private readonly ListView _reverseDependsView;
153+
154+
class DependsListItemModel
155+
{
156+
public Node Node { get; }
157+
public string DisplayText { get; }
130158

131-
var top = new CustomWindow();
159+
public DependsListItemModel(Node node, string label)
160+
{
161+
Node = node ?? throw new ArgumentNullException(nameof(node));
162+
DisplayText = $"{node}{(string.IsNullOrEmpty(label) ? string.Empty : " (Wanted: " + label + ")")}";
163+
}
132164

133-
var left = new FrameView("Dependencies")
134-
{
135-
Width = Dim.Percent(50),
136-
Height = Dim.Fill(1)
137-
};
138-
var right = new View()
139-
{
140-
X = Pos.Right(left),
141-
Width = Dim.Fill(),
142-
Height = Dim.Fill(1)
143-
};
144-
var helpText = new Label("Use arrow keys and Tab to move around. Ctrl+D to toggle assembly visibility. Esc to quit.")
145-
{
146-
Y = Pos.AnchorEnd(1)
147-
};
165+
public override string ToString()
166+
{
167+
return DisplayText;
168+
}
169+
}
148170

149-
var runtimeDepends = new FrameView("Runtime depends")
150-
{
151-
Width = Dim.Fill(),
152-
Height = Dim.Percent(33f)
153-
};
154-
var packageDepends = new FrameView("Package depends")
155-
{
156-
Y = Pos.Bottom(runtimeDepends),
157-
Width = Dim.Fill(),
158-
Height = Dim.Percent(50f)
159-
};
160-
var reverseDepends = new FrameView("Reverse depends")
171+
public AppWindow(DependencyGraph graph) : base("Depends", 0)
161172
{
162-
Y = Pos.Bottom(packageDepends),
163-
Width = Dim.Fill(),
164-
Height = Dim.Fill()
165-
};
173+
_graph = graph ?? throw new ArgumentNullException(nameof(graph));
174+
_dependencies = _graph.Nodes.OrderBy(x => x.Id).ToImmutableList();
175+
_visibleDependencies = _dependencies;
176+
_assembliesVisible = true;
177+
_lastSelectedDependencyIndex = -1;
166178

167-
var orderedDependencyList = graph.Nodes.OrderBy(x => x.Id).ToImmutableList();
168-
var dependenciesView = new ListView(orderedDependencyList)
169-
{
170-
CanFocus = true,
171-
AllowsMarking = false
172-
};
173-
left.Add(dependenciesView);
174-
var runtimeDependsView = new ListView(Array.Empty<Node>())
175-
{
176-
CanFocus = true,
177-
AllowsMarking = false
178-
};
179-
runtimeDepends.Add(runtimeDependsView);
180-
var packageDependsView = new ListView(Array.Empty<Node>())
181-
{
182-
CanFocus = true,
183-
AllowsMarking = false
184-
};
185-
packageDepends.Add(packageDependsView);
186-
var reverseDependsView = new ListView(Array.Empty<Node>())
187-
{
188-
CanFocus = true,
189-
AllowsMarking = false
190-
};
191-
reverseDepends.Add(reverseDependsView);
179+
ColorScheme = new ColorScheme
180+
{
181+
Focus = Application.Driver.MakeAttribute(Color.Black, Color.White),
182+
Normal = Application.Driver.MakeAttribute(Color.White, Color.Black),
183+
HotFocus = Application.Driver.MakeAttribute(Color.Black, Color.White),
184+
HotNormal = Application.Driver.MakeAttribute(Color.White, Color.Black)
185+
};
192186

193-
right.Add(runtimeDepends, packageDepends, reverseDepends);
194-
top.Add(left, right, helpText);
195-
Application.Top.Add(top);
187+
var left = new FrameView("Dependencies")
188+
{
189+
Width = Dim.Percent(50),
190+
Height = Dim.Fill(1)
191+
};
192+
var right = new View()
193+
{
194+
X = Pos.Right(left),
195+
Width = Dim.Fill(),
196+
Height = Dim.Fill(1)
197+
};
198+
var helpText = new Label("Use arrow keys and Tab to move around. Ctrl+D to toggle assembly visibility. Esc to quit.")
199+
{
200+
Y = Pos.AnchorEnd(1)
201+
};
196202

197-
top.Dependencies = orderedDependencyList;
198-
top.VisibleDependencies = orderedDependencyList;
199-
top.DependenciesView = dependenciesView;
203+
var runtimeDepends = new FrameView("Runtime depends")
204+
{
205+
Width = Dim.Fill(),
206+
Height = Dim.Percent(33f)
207+
};
208+
var packageDepends = new FrameView("Package depends")
209+
{
210+
Y = Pos.Bottom(runtimeDepends),
211+
Width = Dim.Fill(),
212+
Height = Dim.Percent(33f)
213+
};
214+
var reverseDepends = new FrameView("Reverse depends")
215+
{
216+
Y = Pos.Bottom(packageDepends),
217+
Width = Dim.Fill(),
218+
Height = Dim.Fill()
219+
};
220+
_dependenciesView = new ListView()
221+
{
222+
CanFocus = true,
223+
AllowsMarking = false,
224+
Width = Dim.Fill(),
225+
Height = Dim.Fill()
226+
};
227+
left.Add(_dependenciesView);
228+
_runtimeDependsView = new ListView()
229+
{
230+
CanFocus = true,
231+
AllowsMarking = false,
232+
Width = Dim.Fill(),
233+
Height = Dim.Fill()
234+
};
235+
runtimeDepends.Add(_runtimeDependsView);
236+
_packageDependsView = new ListView()
237+
{
238+
CanFocus = true,
239+
AllowsMarking = false,
240+
Width = Dim.Fill(),
241+
Height = Dim.Fill()
242+
};
243+
packageDepends.Add(_packageDependsView);
244+
_reverseDependsView = new ListView()
245+
{
246+
CanFocus = true,
247+
AllowsMarking = false,
248+
Width = Dim.Fill(),
249+
Height = Dim.Fill()
250+
};
251+
reverseDepends.Add(_reverseDependsView);
200252

201-
dependenciesView.SelectedItem = 0;
202-
UpdateLists();
253+
right.Add(runtimeDepends, packageDepends, reverseDepends);
254+
Add(left, right, helpText);
203255

204-
dependenciesView.SelectedChanged += UpdateLists;
256+
_runtimeDependsView.OpenSelectedItem += RuntimeDependsView_OpenSelectedItem;
257+
_packageDependsView.OpenSelectedItem += PackageDependsView_OpenSelectedItem;
258+
_reverseDependsView.OpenSelectedItem += ReverseDependsView_OpenSelectedItem;
205259

206-
Application.Run();
260+
_dependenciesView.SelectedItemChanged += DependenciesView_SelectedItemChanged; ;
261+
_dependenciesView.SetSource(_visibleDependencies);
262+
}
207263

208-
void UpdateLists()
264+
public override bool ProcessKey(KeyEvent keyEvent)
209265
{
210-
var selectedNode = top.VisibleDependencies[dependenciesView.SelectedItem];
266+
if (keyEvent.Key == (Key.D | Key.CtrlMask))
267+
{
268+
_assembliesVisible = !_assembliesVisible;
269+
_visibleDependencies = _assembliesVisible ?
270+
_dependencies :
271+
_dependencies.Where(d => !(d is AssemblyReferenceNode)).ToImmutableList();
211272

212-
runtimeDependsView.SetSource(graph.Edges.Where(x => x.Start.Equals(selectedNode) && x.End is AssemblyReferenceNode)
213-
.Select(x => x.End).ToImmutableList());
214-
packageDependsView.SetSource(graph.Edges.Where(x => x.Start.Equals(selectedNode) && x.End is PackageReferenceNode)
215-
.Select(x => $"{x.End}{(string.IsNullOrEmpty(x.Label) ? string.Empty : " (Wanted: " + x.Label + ")")}").ToImmutableList());
216-
reverseDependsView.SetSource(graph.Edges.Where(x => x.End.Equals(selectedNode))
217-
.Select(x => $"{x.Start}{(string.IsNullOrEmpty(x.Label) ? string.Empty : " (Wanted: " + x.Label + ")")}").ToImmutableList());
273+
_dependenciesView.SetSource(_visibleDependencies);
274+
_lastSelectedDependencyIndex = -1;
275+
_dependenciesView.SetFocus();
276+
return true;
277+
}
278+
279+
return base.ProcessKey(keyEvent);
218280
}
219-
}
220281

221-
private class CustomWindow : Window
222-
{
223-
public CustomWindow() : base("Depends", 0)
282+
private void DependenciesView_SelectedItemChanged(ListViewItemEventArgs args)
224283
{
225-
ColorScheme = new ColorScheme
284+
// The ListView.SelectedItemChanged event is fired on enter (focus), see https://github.com/migueldeicaza/gui.cs/issues/831
285+
// To keep the current selection in the right pane (runtime, package & reverse depends lists), call UpdateLists() only if the selected item has actually been changed.
286+
if(_lastSelectedDependencyIndex != args.Item)
226287
{
227-
Focus = Application.Driver.MakeAttribute(Color.Black, Color.White),
228-
Normal = Application.Driver.MakeAttribute(Color.White, Color.Black),
229-
HotFocus = Application.Driver.MakeAttribute(Color.Black, Color.White),
230-
HotNormal = Application.Driver.MakeAttribute(Color.Black, Color.White)
231-
};
288+
_lastSelectedDependencyIndex = args.Item;
289+
UpdateLists();
290+
}
232291
}
233292

234-
public ListView DependenciesView { get; set; }
235-
public ImmutableList<Node> Dependencies { get; set; }
236-
public ImmutableList<Node> VisibleDependencies { get; set; }
237-
238-
private bool _assembliesVisible = true;
239-
240-
public override bool ProcessKey(KeyEvent keyEvent)
293+
private void RuntimeDependsView_OpenSelectedItem(ListViewItemEventArgs args)
241294
{
242-
if (keyEvent.Key == Key.Esc)
295+
if(_assembliesVisible)
243296
{
244-
Application.RequestStop();
245-
return true;
297+
SetSelectedDependency((Node)args.Value);
246298
}
247-
if (keyEvent.Key == Key.ControlD)
248-
{
249-
_assembliesVisible = !_assembliesVisible;
299+
// else: would be nice to provide a feedback so that the user understands that navigation is not possible.
300+
}
250301

251-
VisibleDependencies = _assembliesVisible ?
252-
Dependencies :
253-
Dependencies.Where(d => !(d is AssemblyReferenceNode)).ToImmutableList();
302+
private void PackageDependsView_OpenSelectedItem(ListViewItemEventArgs args)
303+
{
304+
var node = ((DependsListItemModel)args.Value).Node;
305+
SetSelectedDependency(node);
306+
}
254307

255-
DependenciesView.SetSource(VisibleDependencies);
308+
private void ReverseDependsView_OpenSelectedItem(ListViewItemEventArgs args)
309+
{
310+
var node = ((DependsListItemModel)args.Value).Node;
311+
SetSelectedDependency(node);
312+
}
256313

257-
DependenciesView.SelectedItem = 0;
258-
return true;
259-
}
314+
private void SetSelectedDependency(Node node)
315+
{
316+
var index = _visibleDependencies.FindIndex(x => x.Equals(node));
317+
_dependenciesView.SelectedItem = index;
318+
_dependenciesView.SetFocus();
319+
}
260320

261-
return base.ProcessKey(keyEvent);
321+
private void UpdateLists()
322+
{
323+
var selectedNode = _visibleDependencies[_dependenciesView.SelectedItem];
324+
325+
_runtimeDependsView.SetSource(_graph.Edges.Where(x => x.Start.Equals(selectedNode) && x.End is AssemblyReferenceNode)
326+
.Select(x => x.End).ToImmutableList());
327+
_packageDependsView.SetSource(_graph.Edges.Where(x => x.Start.Equals(selectedNode) && x.End is PackageReferenceNode)
328+
.Select(x => new DependsListItemModel(x.End, x.Label)).ToImmutableList());
329+
_reverseDependsView.SetSource(_graph.Edges.Where(x => x.End.Equals(selectedNode))
330+
.Select(x => new DependsListItemModel(x.Start, x.Label)).ToImmutableList());
262331
}
263332
}
264333
}

0 commit comments

Comments
 (0)