Skip to content

Commit 82f0e96

Browse files
committed
Add support for PageUp/PageDown viewport scrolling
1 parent aa177c9 commit 82f0e96

File tree

5 files changed

+124
-77
lines changed

5 files changed

+124
-77
lines changed

src/dotnet-krp/TerminalUi/KrpTerminalState.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
using Krp.Tool.TerminalUi.Extensions;
2-
using System.Collections.Generic;
2+
using System.Collections.Generic;
33

44
namespace Krp.Tool.TerminalUi;
55

66
public enum WindowSize { XS, SM, MD, LG }
77
public enum KrpTable { PortForwards, Logs }
88

9-
public class KrpTerminalState
10-
{
11-
public Dictionary<KrpTable, int> SelectedRow { get; set; } = new();
9+
public class KrpTerminalState
10+
{
11+
public Dictionary<KrpTable, int> AnchorRowIndex { get; } = new();
12+
public Dictionary<KrpTable, int> SelectedRow { get; set; } = new();
1213
public KrpTable SelectedTable { get; set; }
1314
public SortField SortField { get; set; }
1415
public bool SortAscending { get; set; }
@@ -58,4 +59,4 @@ public void UpdateWindowSize(int width)
5859
_ => WindowSize.LG,
5960
};
6061
}
61-
}
62+
}

src/dotnet-krp/TerminalUi/KrpTerminalUi.cs

Lines changed: 77 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,16 @@ await AnsiConsole.Live(layout).StartAsync(async ctx =>
7272
_state.WindowHeight = Console.WindowHeight;
7373
_state.WindowWidth = Console.WindowWidth;
7474

75-
var count = _state.SelectedTable switch
76-
{
77-
KrpTable.PortForwards => _portForwardTable.Count,
78-
KrpTable.Logs => _logsTable.Count,
79-
_ => 0,
80-
};
81-
82-
var redraw = init;
83-
var redrawInfo = false;
84-
var redrawContext = false;
75+
var count = _state.SelectedTable switch
76+
{
77+
KrpTable.PortForwards => _portForwardTable.Count,
78+
KrpTable.Logs => _logsTable.Count,
79+
_ => 0,
80+
};
81+
82+
var redraw = init;
83+
var redrawInfo = false;
84+
var redrawContext = false;
8585

8686
// Keyboard handling.
8787
if (Console.KeyAvailable)
@@ -98,27 +98,29 @@ await AnsiConsole.Live(layout).StartAsync(async ctx =>
9898

9999
var shift = (key.Modifiers & ConsoleModifiers.Shift) != 0;
100100
var ctrl = (key.Modifiers & ConsoleModifiers.Control) != 0;
101-
101+
102102
switch (key.Key)
103103
{
104-
case ConsoleKey.Home: _state.SelectedRow[_state.SelectedTable] = 0; redraw = true; break;
105-
case ConsoleKey.End: _state.SelectedRow[_state.SelectedTable] = count - 1; redraw = true; break;
106-
case ConsoleKey.LeftArrow: _state.ColumnOffset = Math.Max(0, _state.ColumnOffset - 1); redraw = true; break;
107-
case ConsoleKey.RightArrow: _state.ColumnOffset = Math.Min(_state.ColumnOffset + 1, _state.ColumnOffsetMax); redraw = true; break;
104+
case ConsoleKey.Home: redraw = true; _state.SelectedRow[_state.SelectedTable] = 0; break;
105+
case ConsoleKey.End: redraw = true; _state.SelectedRow[_state.SelectedTable] = Math.Max(0, count - 1); break;
106+
case ConsoleKey.PageUp: redraw = _state.SelectedRow[_state.SelectedTable] != (_state.SelectedRow[_state.SelectedTable] = Math.Max(0, _state.SelectedRow[_state.SelectedTable] - GetPageSize(_state.SelectedTable))); break;
107+
case ConsoleKey.PageDown: redraw = _state.SelectedRow[_state.SelectedTable] != (_state.SelectedRow[_state.SelectedTable] = Math.Min(Math.Max(0, count - 1), _state.SelectedRow[_state.SelectedTable] + GetPageSize(_state.SelectedTable))); break;
108+
case ConsoleKey.LeftArrow: redraw = true; _state.ColumnOffset = Math.Max(0, _state.ColumnOffset - 1); break;
109+
case ConsoleKey.RightArrow: redraw = true; _state.ColumnOffset = Math.Min(_state.ColumnOffset + 1, _state.ColumnOffsetMax); break;
108110
case ConsoleKey.UpArrow: redraw = _state.SelectedRow[_state.SelectedTable] != (_state.SelectedRow[_state.SelectedTable] = Math.Max(0, _state.SelectedRow[_state.SelectedTable] - 1)); break;
109111
case ConsoleKey.DownArrow: redraw = _state.SelectedRow[_state.SelectedTable] != (_state.SelectedRow[_state.SelectedTable] = Math.Min(count - 1, _state.SelectedRow[_state.SelectedTable] + 1)); break;
110-
case ConsoleKey.D1: _state.SelectedTable = KrpTable.PortForwards; redraw = true; redrawContext = true; break;
111-
case ConsoleKey.D2: _state.SelectedTable = KrpTable.Logs; redraw = true; redrawContext = true; break;
112-
case ConsoleKey.I when shift: ToggleSort(SortField.Ip, ref redraw); break;
113-
case ConsoleKey.N when shift: ToggleSort(SortField.Namespace, ref redraw); break;
114-
case ConsoleKey.P when shift: ToggleSort(SortField.PortForward, ref redraw); break;
115-
case ConsoleKey.R when shift: ToggleSort(SortField.Resource, ref redraw); break;
116-
case ConsoleKey.U when shift: ToggleSort(SortField.Url, ref redraw); break;
117-
case ConsoleKey.Enter when _state.SelectedTable == KrpTable.PortForwards: _ = _portForwardTable.ForceStart(); break;
118-
case ConsoleKey.D when ctrl && _state.SelectedTable == KrpTable.PortForwards: _portForwardTable.ForceStop(); break;
119-
case ConsoleKey.F5: return; // Abort inner loop to force a new AnsiConsole.Live instance, forcing a refresh.
120-
}
121-
}
112+
case ConsoleKey.D1: redraw = true; redrawContext = true; _state.SelectedTable = KrpTable.PortForwards; break;
113+
case ConsoleKey.D2: redraw = true; redrawContext = true; _state.SelectedTable = KrpTable.Logs; break;
114+
case ConsoleKey.I when shift: redraw = true; ToggleSort(SortField.Ip); break;
115+
case ConsoleKey.N when shift: redraw = true; ToggleSort(SortField.Namespace); break;
116+
case ConsoleKey.P when shift: redraw = true; ToggleSort(SortField.PortForward); break;
117+
case ConsoleKey.R when shift: redraw = true; ToggleSort(SortField.Resource); break;
118+
case ConsoleKey.U when shift: redraw = true; ToggleSort(SortField.Url); break;
119+
case ConsoleKey.Enter when _state.SelectedTable == KrpTable.PortForwards: _ = _portForwardTable.ForceStart(); break;
120+
case ConsoleKey.D when ctrl && _state.SelectedTable == KrpTable.PortForwards: _portForwardTable.ForceStop(); break;
121+
case ConsoleKey.F5: return; // Abort inner loop to force a new AnsiConsole.Live instance, forcing a refresh.
122+
}
123+
}
122124

123125
// Handle kubernetes context (1s).
124126
if (!redraw && lastCtx.Elapsed >= TimeSpan.FromSeconds(1))
@@ -178,14 +180,20 @@ await AnsiConsole.Live(layout).StartAsync(async ctx =>
178180
return;
179181
}
180182

181-
redraw = true;
183+
redraw = true;
182184
}
183185

184-
// Redraw panels.
185-
if (redraw)
186+
// Keep viewport anchor in sync with selection for scrolling tables.
187+
if (_state.SelectedTable == KrpTable.PortForwards)
186188
{
187-
layout["main"].Update(BuildMainPanel());
188-
init = false;
189+
UpdateFirstRowAnchor(_state.SelectedTable, count);
190+
}
191+
192+
// Redraw panels.
193+
if (redraw)
194+
{
195+
layout["main"].Update(BuildMainPanel());
196+
init = false;
189197
lastRedrawMain.Restart();
190198
}
191199

@@ -300,11 +308,11 @@ private Panel BuildContextMenuPanel()
300308
return panel.NoBorder().Padding(0, 0, 0, 0).HeaderAlignment(Justify.Left);
301309
}
302310

303-
private Panel BuildLogoPanel()
304-
{
305-
var figlet = new FigletText(_logoFont, "KRP").Color(Color.Orange1);
306-
var panel = new Panel(figlet);
307-
var padding = 1;
311+
private Panel BuildLogoPanel()
312+
{
313+
var figlet = new FigletText(_logoFont, "KRP").Color(Color.Orange1);
314+
var panel = new Panel(figlet);
315+
var padding = 1;
308316

309317
if (_state.WindowSize == WindowSize.XS)
310318
{
@@ -315,11 +323,41 @@ private Panel BuildLogoPanel()
315323
return panel.NoBorder().Padding(padding, 0, 0, 0);
316324
}
317325

318-
private void ToggleSort(SortField field, ref bool redraw)
326+
private int GetPageSize(KrpTable table)
327+
{
328+
return table switch
329+
{
330+
KrpTable.PortForwards => Math.Max(1, _state.WindowHeight - (PortForwardTable.HEADER_SIZE + HEADER_SIZE)),
331+
KrpTable.Logs => Math.Max(1, _state.WindowHeight - (LogsTable.HEADER_SIZE + HEADER_SIZE)),
332+
_ => 1,
333+
};
334+
}
335+
336+
private void UpdateFirstRowAnchor(KrpTable table, int count)
337+
{
338+
var maxRows = Math.Max(1, _state.WindowHeight - (PortForwardTable.HEADER_SIZE + HEADER_SIZE));
339+
var maxFirst = Math.Max(0, count - maxRows);
340+
341+
var selected = Math.Clamp(_state.SelectedRow[table], 0, Math.Max(0, count - 1));
342+
var first = _state.AnchorRowIndex[_state.SelectedTable];
343+
344+
// Keep the selection visible; adjust just enough without paging jumps.
345+
if (selected < first)
346+
{
347+
first = selected;
348+
}
349+
else if (selected >= first + maxRows)
350+
{
351+
first = selected - maxRows + 1;
352+
}
353+
354+
_state.AnchorRowIndex[table] = Math.Clamp(first, 0, maxFirst);
355+
}
356+
357+
private void ToggleSort(SortField field)
319358
{
320359
_state.SortAscending = _state.SortField != field || !_state.SortAscending;
321360
_state.SortField = field;
322361
_state.SelectedRow[_state.SelectedTable] = 0; // Reset cursor to top.
323-
redraw = true;
324362
}
325363
}

src/dotnet-krp/TerminalUi/Tables/LogsTable.cs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,21 @@ namespace Krp.Tool.TerminalUi.Tables;
1111

1212
public class LogsTable
1313
{
14+
public const int HEADER_SIZE = 2;
15+
public int Count { get; private set; }
16+
1417
private readonly KrpTerminalState _state;
1518
private readonly InMemoryLoggingProvider _logProvider;
1619
private readonly List<ColumnDefinition<LogEntry>> _columnDefinitions;
17-
18-
public int Count { get; private set; }
1920

2021
public LogsTable(KrpTerminalState state, InMemoryLoggingProvider logProvider)
2122
{
2223
_state = state;
2324
_logProvider = logProvider;
2425

25-
// Initialize selected row for logs table.
26+
// Initialize rows for logs table.
2627
_state.SelectedRow.Add(KrpTable.Logs, 0);
28+
_state.AnchorRowIndex.Add(KrpTable.Logs, 0);
2729

2830
// Initialize column definitions.
2931
_columnDefinitions =
@@ -56,19 +58,20 @@ public bool DetectChanges()
5658

5759
public Panel BuildMainPanel()
5860
{
59-
var fixedRows = 2 + KrpTerminalUi.HEADER_SIZE;
60-
var rowsVis = Math.Max(1, _state.WindowHeight - fixedRows);
61+
// Rows that fit.
62+
var fixedRows = HEADER_SIZE + KrpTerminalUi.HEADER_SIZE;
63+
var maxRows = Math.Max(1, _state.WindowHeight - fixedRows);
6164

6265
var total = _logProvider.CountLogs();
63-
var maxStart = Math.Max(0, total - rowsVis);
66+
var maxStart = Math.Max(0, total - maxRows);
6467

6568
// _selectedRowIndex is "start row from the top"
6669
// • Down (index++) => start increases => scrolls down (newer)
6770
// • Up (index--) => start decreases => scrolls up (older)
6871
_state.SelectedRow[KrpTable.Logs] = Math.Clamp(_state.SelectedRow[KrpTable.Logs], 0, maxStart);
6972

7073
var start = _state.SelectedRow[KrpTable.Logs];
71-
var slice = _logProvider.ReadLogs(start, rowsVis).ToList();
74+
var slice = _logProvider.ReadLogs(start, maxRows).ToList();
7275

7376
var tbl = new Table().NoBorder().Expand().HideHeaders().Width(Console.WindowWidth).ShowRowSeparators(); ;
7477

src/dotnet-krp/TerminalUi/Tables/PortForwardTable.cs

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,23 @@ namespace Krp.Tool.TerminalUi.Tables;
1212

1313
public class PortForwardTable
1414
{
15+
public const int HEADER_SIZE = 3;
16+
public int Count { get; private set; }
17+
1518
private readonly KrpTerminalState _state;
1619
private readonly EndpointManager _endpointManager;
1720
private readonly Dictionary<string, int> _measurementLookup = new();
1821
private readonly List<ColumnDefinition<PortForwardEndpointHandler>> _columnDefinitions;
19-
2022
private int _handlersActiveCount;
21-
public int Count { get; private set; }
2223

2324
public PortForwardTable(KrpTerminalState state, EndpointManager endpointManager)
2425
{
25-
_state = state;
26-
_endpointManager = endpointManager;
27-
28-
// Initialize selected row for port forwards.
29-
_state.SelectedRow.Add(KrpTable.PortForwards, 0);
26+
_state = state;
27+
_endpointManager = endpointManager;
28+
29+
// Initialize rows for port-forwards table.
30+
_state.SelectedRow.Add(KrpTable.PortForwards, 0);
31+
_state.AnchorRowIndex.TryAdd(KrpTable.PortForwards, 0);
3032

3133
// Initialize column definitions.
3234
_columnDefinitions =
@@ -86,16 +88,20 @@ public Panel BuildPanel()
8688
_state.WindowGrew = false;
8789
}
8890

89-
_state.ColumnOffsetMax = Math.Max(0, _columnDefinitions.Count - totalVisibleColumns + (_state.LastColumnClipped ? 1 : 0));
90-
91-
// Rows that fit.
92-
var fixedRows = 3 + KrpTerminalUi.HEADER_SIZE;
93-
var maxRows = Math.Max(1, _state.WindowHeight - fixedRows);
94-
95-
var tbl = new Table().NoBorder();
96-
97-
// Print columns
98-
var shownCols = _columnDefinitions.Skip(_state.ColumnOffset).Take(totalVisibleColumns).ToList();
91+
_state.ColumnOffsetMax = Math.Max(0, _columnDefinitions.Count - totalVisibleColumns + (_state.LastColumnClipped ? 1 : 0));
92+
93+
// Rows that fit.
94+
var fixedRows = HEADER_SIZE + KrpTerminalUi.HEADER_SIZE;
95+
var maxRows = Math.Max(1, _state.WindowHeight - fixedRows);
96+
var maxFirst = Math.Max(0, items.Count - maxRows);
97+
var first = _state.AnchorRowIndex.TryGetValue(_state.SelectedTable, out var storedFirst) ? storedFirst : 0;
98+
first = Math.Clamp(first, 0, maxFirst);
99+
_state.AnchorRowIndex[_state.SelectedTable] = first;
100+
101+
var tbl = new Table().NoBorder();
102+
103+
// Print columns
104+
var shownCols = _columnDefinitions.Skip(_state.ColumnOffset).Take(totalVisibleColumns).ToList();
99105

100106
foreach (var col in shownCols)
101107
{
@@ -106,14 +112,13 @@ public Panel BuildPanel()
106112
Padding = new Padding(0),
107113
};
108114

109-
tbl.AddColumn(column);
110-
}
111-
112-
// Print rows
113-
var first = Math.Clamp(_state.SelectedRow[_state.SelectedTable] - maxRows + 1, 0, Math.Max(0, items.Count - maxRows));
114-
for (var i = first; i < Math.Min(items.Count, first + maxRows); i++)
115-
{
116-
var isSelected = i == _state.SelectedRow[_state.SelectedTable];
115+
tbl.AddColumn(column);
116+
}
117+
118+
// Print rows
119+
for (var i = first; i < Math.Min(items.Count, first + maxRows); i++)
120+
{
121+
var isSelected = i == _state.SelectedRow[_state.SelectedTable];
117122
var cells = new List<IRenderable>();
118123

119124
foreach (var col in shownCols)

test/Krp.Tests/Test1.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ public sealed class Test1
66
[TestMethod]
77
public void TestMethod1()
88
{
9-
Assert.IsTrue(false, "This test should always pass.");
9+
Assert.IsTrue(true, "This test should always pass.");
1010
}
1111
}
1212
}

0 commit comments

Comments
 (0)