Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/src/LogExpert.Tests/bin/Debug/LogExpert.Tests.dll",
"args": [],
"cwd": "${workspaceFolder}/src/LogExpert.Tests",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}
41 changes: 41 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/src/LogExpert.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/src/LogExpert.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/src/LogExpert.sln"
],
"problemMatcher": "$msCompile"
}
]
}
61 changes: 44 additions & 17 deletions src/LogExpert.Tests/Controls/FilterSplitterLayoutTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,57 +13,84 @@ namespace LogExpert.Tests.Controls;
public class FilterSplitterLayoutTests
{
[Test]
public void ClampSplitterDistance_DesiredWithinBounds_ReturnedUnchanged ()
public void TryClampSplitterDistance_DesiredWithinBounds_ReturnedUnchanged ()
{
var result = FilterSplitterLayout.ClampSplitterDistance(
var ok = FilterSplitterLayout.TryClampSplitterDistance(
desiredDistance: 600,
containerWidth: 1855,
splitterWidth: 4,
panel1MinSize: 200,
panel2MinSize: 660);
panel2MinSize: 660,
out var distance);

Assert.That(result, Is.EqualTo(600));
Assert.That(ok, Is.True);
Assert.That(distance, Is.EqualTo(600));
}

[Test]
public void ClampSplitterDistance_DesiredTooLarge_ClampedSoPanel2KeepsMinWidth ()
public void TryClampSplitterDistance_DesiredTooLarge_ClampedSoPanel2KeepsMinWidth ()
{
// 1855 - 4 (splitter) - 660 (panel2 min) = 1191 is the furthest the splitter may go.
var result = FilterSplitterLayout.ClampSplitterDistance(
var ok = FilterSplitterLayout.TryClampSplitterDistance(
desiredDistance: 1800,
containerWidth: 1855,
splitterWidth: 4,
panel1MinSize: 200,
panel2MinSize: 660);
panel2MinSize: 660,
out var distance);

Assert.That(result, Is.EqualTo(1191));
Assert.That(ok, Is.True);
Assert.That(distance, Is.EqualTo(1191));
}

[Test]
public void ClampSplitterDistance_DesiredTooSmall_ClampedUpToPanel1MinWidth ()
public void TryClampSplitterDistance_DesiredTooSmall_ClampedUpToPanel1MinWidth ()
{
var result = FilterSplitterLayout.ClampSplitterDistance(
var ok = FilterSplitterLayout.TryClampSplitterDistance(
desiredDistance: 50,
containerWidth: 1855,
splitterWidth: 4,
panel1MinSize: 200,
panel2MinSize: 660);
panel2MinSize: 660,
out var distance);

Assert.That(result, Is.EqualTo(200));
Assert.That(ok, Is.True);
Assert.That(distance, Is.EqualTo(200));
}

[Test]
public void ClampSplitterDistance_ContainerTooSmallForBothMinimums_DoesNotThrowAndKeepsPanel1Min ()
public void TryClampSplitterDistance_ContainerTooSmallForBothMinimums_ReturnsFalse ()
{
// 700 - 4 - 660 = 36, which is below panel1MinSize. Must not throw (min > max for Math.Clamp).
var result = FilterSplitterLayout.ClampSplitterDistance(
// 700 - 4 - 660 = 36, which is below panel1MinSize: no distance honours both minimums,
// so the caller must skip assignment. Returning a value here is what previously made the
// SplitContainer.SplitterDistance setter throw on a narrow window.
var ok = FilterSplitterLayout.TryClampSplitterDistance(
desiredDistance: 500,
containerWidth: 700,
splitterWidth: 4,
panel1MinSize: 200,
panel2MinSize: 660);
panel2MinSize: 660,
out _);

Assert.That(result, Is.EqualTo(200));
Assert.That(ok, Is.False);
}

[Test]
public void TryClampSplitterDistance_NarrowWindowRegression_ReturnsFalseInsteadOfThrowingValue ()
{
// Regression for the #560 follow-up crash: with the real filter-row minimums (Panel2 = 726),
// shrinking the LogExpert window forces the Dock=Fill container below ~730px. The old
// ClampSplitterDistance returned Panel1MinSize (200), and assigning it threw
// InvalidOperationException ("SplitterDistance must be between Panel1MinSize and Width - Panel2MinSize").
var ok = FilterSplitterLayout.TryClampSplitterDistance(
desiredDistance: 5000,
containerWidth: 684,
splitterWidth: 4,
panel1MinSize: 200,
panel2MinSize: 726,
out _);

Assert.That(ok, Is.False);
}

[Test]
Expand Down
27 changes: 20 additions & 7 deletions src/LogExpert.UI/Controls/LogWindow/FilterSplitterLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,27 @@ internal static class FilterSplitterLayout
/// <param name="splitterWidth">Width of the splitter bar.</param>
/// <param name="panel1MinSize">Minimum width of Panel1 (the text filter).</param>
/// <param name="panel2MinSize">Minimum width of Panel2 (the buttons/checkboxes).</param>
/// <returns>A distance guaranteed to keep Panel2 at least <paramref name="panel2MinSize"/> wide.</returns>
public static int ClampSplitterDistance (int desiredDistance, int containerWidth, int splitterWidth, int panel1MinSize, int panel2MinSize)
/// <param name="distance">The clamped distance to apply; only meaningful when this method returns <c>true</c>.</param>
/// <returns>
/// <c>true</c> when a distance honouring both minimums exists and <paramref name="distance"/> was set;
/// <c>false</c> when the container is too narrow to honour both minimums at once. In that degenerate
/// state there is no valid distance, and the caller must NOT assign one: WinForms'
/// <c>SplitContainer.SplitterDistance</c> setter throws <see cref="InvalidOperationException"/> when the
/// value cannot fit between <c>Panel1MinSize</c> and <c>Width - Panel2MinSize - SplitterWidth</c>.
/// </returns>
public static bool TryClampSplitterDistance (int desiredDistance, int containerWidth, int splitterWidth, int panel1MinSize, int panel2MinSize, out int distance)
{
// When the container is too small to honour both minimums there is no perfect answer;
// keep Panel1 at its minimum (matching the SplitContainer's own preference) and avoid
// an invalid (min > max) clamp range.
var maxDistance = Math.Max(containerWidth - splitterWidth - panel2MinSize, panel1MinSize);
return Math.Clamp(desiredDistance, panel1MinSize, maxDistance);
var maxDistance = containerWidth - splitterWidth - panel2MinSize;
if (maxDistance < panel1MinSize)
{
// Both minimums cannot fit (a narrow window forced the container below their sum).
// No splitter distance is valid here; the caller leaves the splitter where it is.
distance = panel1MinSize;
return false;
}

distance = Math.Clamp(desiredDistance, panel1MinSize, maxDistance);
return true;
}

/// <summary>
Expand Down
16 changes: 9 additions & 7 deletions src/LogExpert.UI/Controls/LogWindow/LogWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -744,8 +744,10 @@ void ILogWindow.WritePipeTab (IList<LineEntryMemory> lineEntryList, string title
private void AutoResizeFilterBox ()
{
var desired = filterComboBox.Left + filterComboBox.GetMaxTextWidth();
filterSplitContainer.SplitterDistance = FilterSplitterLayout.ClampSplitterDistance(
desired, filterSplitContainer.Width, filterSplitContainer.SplitterWidth, filterSplitContainer.Panel1MinSize, filterSplitContainer.Panel2MinSize);
if (FilterSplitterLayout.TryClampSplitterDistance(desired, filterSplitContainer.Width, filterSplitContainer.SplitterWidth, filterSplitContainer.Panel1MinSize, filterSplitContainer.Panel2MinSize, out var distance))
{
filterSplitContainer.SplitterDistance = distance;
}
}

#region Events handler
Expand Down Expand Up @@ -1449,11 +1451,11 @@ private void OnFilterSplitContainerMouseMove (object sender, MouseEventArgs e)
var desired = isVertical ? e.X : e.Y;
var containerSize = isVertical ? splitContainer.Width : splitContainer.Height;

// Keep the splitter inside the panels' min sizes so the text filter (Panel1) can
// never be grown large enough to push the Panel2 controls outside the app (issue #560).
splitContainer.SplitterDistance = FilterSplitterLayout.ClampSplitterDistance(
desired, containerSize, splitContainer.SplitterWidth, splitContainer.Panel1MinSize, splitContainer.Panel2MinSize);
splitContainer.Refresh();
if (FilterSplitterLayout.TryClampSplitterDistance(desired, containerSize, splitContainer.SplitterWidth, splitContainer.Panel1MinSize, splitContainer.Panel2MinSize, out var distance))
{
splitContainer.SplitterDistance = distance;
splitContainer.Refresh();
}
}
else
{
Expand Down
32 changes: 16 additions & 16 deletions src/PluginRegistry/PluginHashGenerator.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,35 @@ public static partial class PluginValidator
{
/// <summary>
/// Gets pre-calculated SHA256 hashes for built-in plugins.
/// Generated: 2026-06-25 14:06:29 UTC
/// Generated: 2026-06-25 15:09:22 UTC
/// Configuration: Release
/// Plugin count: 21
/// </summary>
public static Dictionary<string, string> GetBuiltInPluginHashes()
{
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["AutoColumnizer.dll"] = "E6BBCB994A140BC595E67D19A7E6E9975469718628C5BA8E423DCF13E076FA81",
["AutoColumnizer.dll"] = "ED598729A6BCB3175F02FF54576DDD5DAD7CBBC0E47A1E07ADC8A9B4AB330675",
["BouncyCastle.Cryptography.dll"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6",
["BouncyCastle.Cryptography.dll (x86)"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6",
["CsvColumnizer.dll"] = "024575D2CBD09E58AE7CFF058B92C3785006AD2280301B0DE0724409934F4BCA",
["CsvColumnizer.dll (x86)"] = "024575D2CBD09E58AE7CFF058B92C3785006AD2280301B0DE0724409934F4BCA",
["DefaultPlugins.dll"] = "19B0A154B48F99A616A8468E8AC45ACCF86B4402F9F4E7DC860C6FE1D7D92B13",
["FlashIconHighlighter.dll"] = "4A488895316B01053DD3287604DC4344CCEA918EB09EB664009BC419F29FC021",
["GlassfishColumnizer.dll"] = "93DF4EEDC1A695F06E7E09AB483782FA0FDC867BED61FB63BD25458389FA8E0E",
["JsonColumnizer.dll"] = "C9538CA60C8E1ADFEAC74C75CAE7F1627311FF500804C6FD8B2EFEF068C6FFFE",
["JsonCompactColumnizer.dll"] = "1F30ABDC119D4459A2272D96080F4BF0683D7420A624CC256C2F07EEE02B192A",
["Log4jXmlColumnizer.dll"] = "D99A998A0E0C3969EB8B49CB6E8BD9CCC934641EEE18852B202244C837ADC7F5",
["LogExpert.Resources.dll"] = "43FB2C2C6D53D1E2CDB0D8FD663AEE5CF3E4FC51A05D69DBFBBF2123E8F67935",
["CsvColumnizer.dll"] = "D2200DE16E125F7B3FD4A0603F094455712987773C98AE71B46EBEB1F181D5FC",
["CsvColumnizer.dll (x86)"] = "D2200DE16E125F7B3FD4A0603F094455712987773C98AE71B46EBEB1F181D5FC",
["DefaultPlugins.dll"] = "9E7CD9B83067F83273D4D0BDD0E50DEBD3073015B55F63E7683AAF768FFB913F",
["FlashIconHighlighter.dll"] = "0E9DB7648FF41A3AAF7924A2A8A01476B803A0471711B9A3FE1D3C927EC02199",
["GlassfishColumnizer.dll"] = "F65B6305AF29B1F883C2523140C230CBB900046022572618A41C3C128703FF3D",
["JsonColumnizer.dll"] = "514EDAC4465FF75D604989F20B0B12ADBFD2092E514B4E80FE8CBF2272CF7C9A",
["JsonCompactColumnizer.dll"] = "04B454BDBA03DD8683F9065AF8080E7ED5A443A63611D738AC3B2B11A912E537",
["Log4jXmlColumnizer.dll"] = "7CEB38D227528C08C21D403F7CA91DFE689655022EFB7280761167C7F7C2AFBC",
["LogExpert.Resources.dll"] = "A4AC70A6FCB3997821127C3F59BF78933794CF0B07F26DFFCD9004FD506F03C5",
["Microsoft.Extensions.DependencyInjection.Abstractions.dll"] = "67FA4325000DB017DC0C35829B416F024F042D24EFB868BCF17A895EE6500A93",
["Microsoft.Extensions.DependencyInjection.Abstractions.dll (x86)"] = "67FA4325000DB017DC0C35829B416F024F042D24EFB868BCF17A895EE6500A93",
["Microsoft.Extensions.Logging.Abstractions.dll"] = "BB853130F5AFAF335BE7858D661F8212EC653835100F5A4E3AA2C66A4D4F685D",
["Microsoft.Extensions.Logging.Abstractions.dll (x86)"] = "BB853130F5AFAF335BE7858D661F8212EC653835100F5A4E3AA2C66A4D4F685D",
["RegexColumnizer.dll"] = "926229B7C704E367BA768E44981D133E6B26356CCEC4BCF0108EC77B2521C33A",
["SftpFileSystem.dll"] = "066BDA9245EDCECCD49E3C36B6ACC05E52C8C34D25A1A9406E403987E603FC57",
["SftpFileSystem.dll (x86)"] = "A30EB5C2D2E8A403C0ADF179AB626278689198E64935DA8BA108B33FB164AECA",
["SftpFileSystem.Resources.dll"] = "E68455B141D714A19779E717558DEEA6C9A9829ECF5A3C5EDCD65F18C06E6D33",
["SftpFileSystem.Resources.dll (x86)"] = "E68455B141D714A19779E717558DEEA6C9A9829ECF5A3C5EDCD65F18C06E6D33",
["RegexColumnizer.dll"] = "7D343457626A42062C885716047E6DC6649983E2AD612B7C38D73F695250355A",
["SftpFileSystem.dll"] = "3011E98941B793FBBC891AA38939058CC057F3CBDC0AD1E410A7B1715821FF3B",
["SftpFileSystem.dll (x86)"] = "5D79B06F7109C61C5A5A9049DB355627F4E4EB85E557DB7786C0412954A85B72",
["SftpFileSystem.Resources.dll"] = "0CE8807D06B4027FA3358773581C0B1DBC646BB2B607F4F358853AC8640C4976",
["SftpFileSystem.Resources.dll (x86)"] = "0CE8807D06B4027FA3358773581C0B1DBC646BB2B607F4F358853AC8640C4976",

};
}
Expand Down