diff --git a/src/LogExpert.Configuration/ConfigManager.cs b/src/LogExpert.Configuration/ConfigManager.cs index d3fa4903..63d918c4 100644 --- a/src/LogExpert.Configuration/ConfigManager.cs +++ b/src/LogExpert.Configuration/ConfigManager.cs @@ -281,6 +281,22 @@ public void AddToFileHistory (string fileName) Save(SettingsFlags.FileHistory); } + [SupportedOSPlatform("windows")] + public void RemoveFromFileHistory (string fileName) + { + bool findName (string s) => s.ToUpperInvariant().Equals(fileName.ToUpperInvariant(), StringComparison.Ordinal); + + var index = Instance.Settings.FileHistoryList.FindIndex(findName); + + if (index != -1) + { + Instance.Settings.FileHistoryList.RemoveAt(index); + } + + Save(SettingsFlags.FileHistory); + } + + public void ClearLastOpenFilesList () { lock (_loadSaveLock) diff --git a/src/LogExpert.Core/Classes/Log/PositionAwareStreamReaderBase.cs b/src/LogExpert.Core/Classes/Log/PositionAwareStreamReaderBase.cs index ba09f0fc..bfb53aa0 100644 --- a/src/LogExpert.Core/Classes/Log/PositionAwareStreamReaderBase.cs +++ b/src/LogExpert.Core/Classes/Log/PositionAwareStreamReaderBase.cs @@ -47,7 +47,7 @@ protected PositionAwareStreamReaderBase (Stream stream, EncodingOptions encoding MaximumLineLength = maximumLineLength; - _preambleLength = DetectPreambleLengthAndEncoding(out var detectedEncoding); + (_preambleLength, Encoding? detectedEncoding) = DetectPreambleLength(_stream); var usedEncoding = DetermineEncoding(encodingOptions, detectedEncoding); _posIncPrecomputed = GetPosIncPrecomputed(usedEncoding); @@ -165,11 +165,19 @@ protected void MovePosition (int offset) #region Private Methods + public static Encoding DetermineEncoding (EncodingOptions options, Encoding detectedEncoding) + { + return options?.Encoding != null + ? options.Encoding + : detectedEncoding ?? options?.DefaultEncoding ?? Encoding.Default; + } + /// - /// Determines the actual number of preamble bytes in the file. + /// Determines the actual number of preamble bytes in the file and the Encoding. /// - /// Number of preamble bytes in the file - private int DetectPreambleLengthAndEncoding (out Encoding detectedEncoding) + /// + /// Number of preamble bytes in the file and the Encoding if there is one + public static (int length, Encoding? detectedEncoding) DetectPreambleLength (Stream stream) { /* UTF-8: EF BB BF @@ -179,31 +187,15 @@ private int DetectPreambleLengthAndEncoding (out Encoding detectedEncoding) UTF-32-Little-Endian-Byteorder: FF FE 00 00 */ - var (length, encoding) = DetectPreambleLength(_stream); - // not found or less than 2 byte read - detectedEncoding = encoding; - - return length; - } - - public static Encoding DetermineEncoding (EncodingOptions options, Encoding detectedEncoding) - { - return options?.Encoding != null - ? options.Encoding - : detectedEncoding ?? options?.DefaultEncoding ?? Encoding.Default; - } - - public static (int length, Encoding? detectedEncoding) DetectPreambleLength (Stream stream) - { if (!stream.CanSeek) { return (0, null); } var originalPos = stream.Position; - var buffer = new byte[4]; + Span buffer = stackalloc byte[4]; _ = stream.Seek(0, SeekOrigin.Begin); - var readBytes = stream.Read(buffer, 0, buffer.Length); + var readBytes = stream.Read(buffer); _ = stream.Seek(originalPos, SeekOrigin.Begin); if (readBytes >= 2) diff --git a/src/LogExpert.Core/Classes/Log/PositionAwareStreamReaderSystem.cs b/src/LogExpert.Core/Classes/Log/PositionAwareStreamReaderSystem.cs index 0f3cc994..a1b4e95b 100644 --- a/src/LogExpert.Core/Classes/Log/PositionAwareStreamReaderSystem.cs +++ b/src/LogExpert.Core/Classes/Log/PositionAwareStreamReaderSystem.cs @@ -125,11 +125,15 @@ private int GuessNewLineSequenceLength (StreamReader reader) var secondChar = reader.Read(); if (secondChar == CHAR_LF) // check \n { - return Encoding.GetByteCount("\r\n"); + // Use stackalloc or SpanOwner instead of string + Span newline = ['\r', '\n']; + return Encoding.GetByteCount(newline); + //return Encoding.GetByteCount("\r\n"); } } - return Encoding.GetByteCount(((char)firstChar).ToString()); + Span singleChar = [(char)firstChar]; + return Encoding.GetByteCount(singleChar); } return 0; diff --git a/src/LogExpert.Core/Interface/IConfigManager.cs b/src/LogExpert.Core/Interface/IConfigManager.cs index a436cb23..9e1d34fa 100644 --- a/src/LogExpert.Core/Interface/IConfigManager.cs +++ b/src/LogExpert.Core/Interface/IConfigManager.cs @@ -151,6 +151,12 @@ public interface IConfigManager /// This method is supported only on Windows platforms. /// The name of the file to add to the file history list. Comparison is case-insensitive. void AddToFileHistory (string fileName); + + /// + /// Removes the specified file name from the file history list. + /// + /// The name of the file to remove from the file history list. Comparison is case-insensitive. + void RemoveFromFileHistory (string fileName); /// /// Clears the list of recently opened files. diff --git a/src/LogExpert.Tests/Services/LedIndicatorServiceTests.cs b/src/LogExpert.Tests/Services/LedIndicatorServiceTests.cs index 9153e21a..c44f3741 100644 --- a/src/LogExpert.Tests/Services/LedIndicatorServiceTests.cs +++ b/src/LogExpert.Tests/Services/LedIndicatorServiceTests.cs @@ -1,6 +1,7 @@ using System.Runtime.Versioning; -using LogExpert.UI.Services; +using LogExpert.UI.Interface.Services; +using LogExpert.UI.Services.LedService; using NUnit.Framework; @@ -9,11 +10,12 @@ namespace LogExpert.Tests.Services; [TestFixture] [Apartment(ApartmentState.STA)] // Required for UI components [SupportedOSPlatform("windows")] -public class LedIndicatorServiceTests +public class LedIndicatorServiceTests : IDisposable { private LedIndicatorService? _service; private ApplicationContext? _appContext; private WindowsFormsSynchronizationContext? _syncContext; + private bool _disposed; [SetUp] public void Setup () @@ -140,9 +142,9 @@ public void StartStop_DoesNotThrowException () _service!.Initialize(Color.Blue); // Act - _service.Start(); + _service.StartService(); Thread.Sleep(500); // Let timer tick a few times - _service.Stop(); + _service.StopService(); // Assert - no exception Assert.That(true, Is.True, "Service started and stopped without exceptions"); @@ -192,7 +194,7 @@ public void Dispose_DisposesAllResources () { // Arrange _service!.Initialize(Color.Blue); - _service.Start(); + _service.StartService(); // Act _service.Dispose(); @@ -217,7 +219,7 @@ public void Start_WithoutInitialize_ThrowsException () // Arrange - don't initialize // Act & Assert - _ = Assert.Throws(() => _service!.Start()); + _ = Assert.Throws(() => _service!.StartService()); } [Test] @@ -316,4 +318,25 @@ public void GetIcon_WithSyncedState_ReturnsSyncedIcon () // The icons should be different (synced has blue indicator on left side) Assert.That(iconSynced, Is.Not.EqualTo(iconNotSynced)); } + + public void Dispose () + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose (bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + _service?.Dispose(); + } + + _disposed = true; + } } \ No newline at end of file diff --git a/src/LogExpert.Tests/Services/MenuToolbarControllerTests.cs b/src/LogExpert.Tests/Services/MenuToolbarControllerTests.cs new file mode 100644 index 00000000..1a60cc6d --- /dev/null +++ b/src/LogExpert.Tests/Services/MenuToolbarControllerTests.cs @@ -0,0 +1,426 @@ +using System.Reflection; +using System.Runtime.Versioning; +using System.Text; +using System.Windows.Forms; + +using LogExpert.Core.EventArguments; +using LogExpert.Dialogs; +using LogExpert.UI.Controls; +using LogExpert.UI.Services; +using LogExpert.UI.Services.MenuToolbarService; + +using NUnit.Framework; + +namespace LogExpert.Tests.Services; + +[TestFixture] +[SupportedOSPlatform("windows")] +[Apartment(ApartmentState.STA)] +internal class MenuToolbarControllerTests : IDisposable +{ + private MenuToolbarController _controller; + private MenuStrip _mainMenu; + private ToolStrip _toolbar; + private DateTimeDragControl _dragControl; + private CheckBox _followTailCheckBox; + private ToolStripMenuItem _fileMenu; + private ToolStripMenuItem _viewMenu; + private ToolStripMenuItem _optionMenu; + private ToolStripMenuItem _encodingMenu; + private bool _disposed; + private ApplicationContext? _appContext; + private WindowsFormsSynchronizationContext? _syncContext; + + [SetUp] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Unit Tests")] + public void Setup () + { + // Ensure we have a WindowsFormsSynchronizationContext for the UI thread + if (SynchronizationContext.Current == null) + { + _syncContext = new WindowsFormsSynchronizationContext(); + SynchronizationContext.SetSynchronizationContext(_syncContext); + } + + // Create an application context to ensure we have a proper UI context + _appContext = new ApplicationContext(); + + _controller = new MenuToolbarController(); + + _mainMenu = new MenuStrip(); + _toolbar = new ToolStrip(); + _dragControl = new DateTimeDragControl(); + _followTailCheckBox = new CheckBox { Name = "checkBoxFollowTail" }; + + // Build realistic menu structure matching designer + _fileMenu = new ToolStripMenuItem("File") { Name = "fileToolStripMenuItem" }; + _ = _fileMenu.DropDownItems.Add(new ToolStripMenuItem("Close") { Name = "closeFileToolStripMenuItem" }); + _ = _fileMenu.DropDownItems.Add(new ToolStripMenuItem("Last Used") { Name = "lastUsedToolStripMenuItem" }); + + var multiFile = new ToolStripMenuItem("Multi-File") { Name = "multiFileToolStripMenuItem" }; + _ = multiFile.DropDownItems.Add(new ToolStripMenuItem("Enabled") { Name = "multiFileEnabledStripMenuItem" }); + _ = _fileMenu.DropDownItems.Add(multiFile); + + _viewMenu = new ToolStripMenuItem("View") { Name = "viewNavigateToolStripMenuItem" }; + _ = _viewMenu.DropDownItems.Add(new ToolStripMenuItem("Search") { Name = "searchToolStripMenuItem" }); + _ = _viewMenu.DropDownItems.Add(new ToolStripMenuItem("Filter") { Name = "filterToolStripMenuItem" }); + _ = _viewMenu.DropDownItems.Add(new ToolStripMenuItem("Column Finder") { Name = "columnFinderToolStripMenuItem" }); + + _encodingMenu = new ToolStripMenuItem("Encoding") { Name = "encodingToolStripMenuItem" }; + _ = _encodingMenu.DropDownItems.Add(new ToolStripMenuItem("ASCII") { Name = "encodingASCIIToolStripMenuItem" }); + _ = _encodingMenu.DropDownItems.Add(new ToolStripMenuItem("ANSI") { Name = "encodingANSIToolStripMenuItem" }); + _ = _encodingMenu.DropDownItems.Add(new ToolStripMenuItem("UTF-8") { Name = "encodingUTF8toolStripMenuItem" }); + _ = _encodingMenu.DropDownItems.Add(new ToolStripMenuItem("UTF-16") { Name = "encodingUTF16toolStripMenuItem" }); + _ = _encodingMenu.DropDownItems.Add(new ToolStripMenuItem("ISO-8859-1") { Name = "encodingISO88591toolStripMenuItem" }); + _ = _viewMenu.DropDownItems.Add(_encodingMenu); + + var timeshiftMenu = new ToolStripMenuItem("Timeshift") { Name = "timeshiftToolStripMenuItem" }; + _ = timeshiftMenu.DropDownItems.Add(new ToolStripTextBox { Name = "timeshiftToolStripTextBox" }); + _ = _viewMenu.DropDownItems.Add(timeshiftMenu); + + _optionMenu = new ToolStripMenuItem("Options") { Name = "optionToolStripMenuItem" }; + _ = _optionMenu.DropDownItems.Add(new ToolStripMenuItem("Cell Select") { Name = "cellSelectModeToolStripMenuItem" }); + + _mainMenu.Items.AddRange([_fileMenu, _viewMenu, _optionMenu]); + + // Toolbar + _ = _toolbar.Items.Add(new ToolStripButton("Bubbles") { Name = "toolStripButtonBubbles" }); + _ = _toolbar.Items.Add(new ToolStripComboBox { Name = "highlightGroupsToolStripComboBox" }); + + _controller.InitializeMenus(_mainMenu, _toolbar, null, _dragControl, _followTailCheckBox); + } + + [TearDown] + public void TearDown () + { + _controller?.Dispose(); + _mainMenu?.Dispose(); + _toolbar?.Dispose(); + _dragControl?.Dispose(); + _followTailCheckBox?.Dispose(); + _appContext?.Dispose(); + _syncContext?.Dispose(); + } + + [Test] + public void UpdateGuiState_SetsFollowTailChecked () + { + var state = new GuiStateEventArgs { FollowTail = true, MenuEnabled = true }; + _controller.UpdateGuiState(state, false); + Assert.That(_followTailCheckBox.Checked, Is.True); + } + + [Test] + public void UpdateGuiState_SetsTimeshiftMenuState () + { + var state = new GuiStateEventArgs + { + TimeshiftPossible = true, + TimeshiftEnabled = true, + TimeshiftText = "500", + MenuEnabled = true + }; + _controller.UpdateGuiState(state, false); + + var timeshiftItem = FindMenuItem("timeshiftToolStripMenuItem"); + Assert.That(timeshiftItem.Enabled, Is.True); + Assert.That(timeshiftItem.Checked, Is.True); + + var timeshiftTextBox = FindItemRecursive(_mainMenu.Items, "timeshiftToolStripTextBox"); + Assert.That(timeshiftTextBox.Text, Is.EqualTo("500")); + Assert.That(timeshiftTextBox.Enabled, Is.True); + } + + [Test] + public void UpdateGuiState_ShowsTimestampControl_WhenTimeshiftAndConfigEnabled () + { + var state = new GuiStateEventArgs + { + TimeshiftPossible = true, + MinTimestamp = new DateTime(2025, 1, 1), + MaxTimestamp = new DateTime(2025, 12, 31), + Timestamp = new DateTime(2025, 6, 15), + MenuEnabled = true + }; + + _controller.UpdateGuiState(state, timestampControlEnabled: true); + Assert.That(_dragControl.Visible, Is.True); + Assert.That(_dragControl.Enabled, Is.True); + } + + [Test] + public void UpdateGuiState_HidesTimestampControl_WhenTimeshiftNotPossible () + { + var state = new GuiStateEventArgs { TimeshiftPossible = false, MenuEnabled = true }; + _controller.UpdateGuiState(state, timestampControlEnabled: true); + Assert.That(_dragControl.Visible, Is.False); + } + + [Test] + public void UpdateEncodingMenu_Utf8_ChecksCorrectItem () + { + _controller.UpdateEncodingMenu(Encoding.UTF8); + + var utf8Item = FindMenuItem("encodingUTF8toolStripMenuItem"); + var asciiItem = FindMenuItem("encodingASCIIToolStripMenuItem"); + Assert.That(utf8Item.Checked, Is.True); + Assert.That(asciiItem.Checked, Is.False); + } + + [Test] + public void UpdateEncodingMenu_NullEncoding_UnchecksAll () + { + // First set one + _controller.UpdateEncodingMenu(Encoding.UTF8); + // Then clear + _controller.UpdateEncodingMenu(null); + + var utf8Item = FindMenuItem("encodingUTF8toolStripMenuItem"); + Assert.That(utf8Item.Checked, Is.False); + } + + [Test] + public void UpdateHighlightGroups_PopulatesComboBox () + { + var groups = new[] { "Default", "Errors", "Warnings" }; + _controller.UpdateHighlightGroups(groups, "Errors"); + + var combo = _toolbar.Items["highlightGroupsToolStripComboBox"] as ToolStripComboBox; + Assert.That(combo, Is.Not.Null); + Assert.That(combo.Items, Has.Count.EqualTo(3)); + Assert.That(combo.Text, Is.EqualTo("Errors")); + } + + [Test] + public void PopulateFileHistory_CreatesMenuItems () + { + var history = new[] { @"C:\log1.txt", @"C:\log2.txt" }; + _controller.PopulateFileHistory(history); + + var lastUsed = FindMenuItem("lastUsedToolStripMenuItem"); + Assert.That(lastUsed.DropDownItems, Has.Count.EqualTo(2)); + Assert.That(lastUsed.DropDownItems[0].Text, Is.EqualTo(@"C:\log1.txt")); + } + + [Test] + public void HistoryItemClicked_RaisesEvent () + { + string clickedFile = null; + _controller.HistoryItemClicked += (_, e) => clickedFile = e.FileName; + + _controller.PopulateFileHistory(["test.log"]); + var lastUsed = FindMenuItem("lastUsedToolStripMenuItem"); + + // Simulate click via reflection — OnItemClicked is protected on ToolStrip + var args = new ToolStripItemClickedEventArgs(lastUsed.DropDownItems[0]); + var onItemClicked = typeof(ToolStrip).GetMethod("OnItemClicked", BindingFlags.Instance | BindingFlags.NonPublic); + _ = onItemClicked.Invoke(lastUsed.DropDown, [args]); + + Assert.That(clickedFile, Is.EqualTo("test.log")); + } + + [Test] + public void HighlightGroupSelected_RaisesEvent_OnComboChange () + { + string selectedGroup = null; + _controller.HighlightGroupSelected += (_, e) => selectedGroup = e.GroupName; + + // Populate first + _controller.UpdateHighlightGroups(["Default", "Errors"], "Default"); + + // Simulate user selecting "Errors" + var combo = _toolbar.Items["highlightGroupsToolStripComboBox"] as ToolStripComboBox; + Assert.That(combo, Is.Not.Null, "Expected highlightGroupsToolStripComboBox to be a ToolStripComboBox."); + combo.SelectedIndex = 1; // This triggers SelectedIndexChanged + + Assert.That(selectedGroup, Is.EqualTo("Errors")); + } + + [Test] + public void Dispose_UnsubscribesEvents () + { + string selectedGroup = null; + _controller.HighlightGroupSelected += (_, e) => selectedGroup = e.GroupName; + + // Populate combo so we can change selection + _controller.UpdateHighlightGroups(["Default", "Errors"], "Errors"); + + _controller.Dispose(); + + // Changing selection after dispose should not raise HighlightGroupSelected + var combo = _toolbar.Items["highlightGroupsToolStripComboBox"] as ToolStripComboBox; + Assert.That(combo, Is.Not.Null, "Expected highlightGroupsToolStripComboBox to be a ToolStripComboBox."); + combo.SelectedIndex = 0; + + Assert.That(selectedGroup, Is.Null); + } + + [Test] + public void InitializeMenus_NullMainMenu_ThrowsArgumentNullException () + { + var controller = new MenuToolbarController(); + _ = Assert.Throws(() => + controller.InitializeMenus(null, _toolbar, null, _dragControl, _followTailCheckBox)); + } + + [Test] + public void InitializeMenus_NullButtonToolbar_ThrowsArgumentNullException () + { + var controller = new MenuToolbarController(); + _ = Assert.Throws(() => + controller.InitializeMenus(_mainMenu, null, null, _dragControl, _followTailCheckBox)); + } + + [Test] + public void InitializeMenus_NullDragControl_ThrowsArgumentNullException () + { + var controller = new MenuToolbarController(); + _ = Assert.Throws(() => + controller.InitializeMenus(_mainMenu, _toolbar, null, null, _followTailCheckBox)); + } + + [Test] + public void InitializeMenus_NullCheckBox_ThrowsArgumentNullException () + { + var controller = new MenuToolbarController(); + _ = Assert.Throws(() => + controller.InitializeMenus(_mainMenu, _toolbar, null, _dragControl, null)); + } + + [Test] + public void UpdateGuiState_NullState_ThrowsArgumentNullException () + { + _ = Assert.Throws(() => _controller.UpdateGuiState(null, false)); + } + + [Test] + public void UpdateGuiState_SetsMultiFileState () + { + var state = new GuiStateEventArgs + { + MultiFileEnabled = true, + IsMultiFileActive = true, + MenuEnabled = true + }; + _controller.UpdateGuiState(state, false); + + var multiFileItem = FindMenuItem("multiFileToolStripMenuItem"); + var multiFileEnabledItem = FindMenuItem("multiFileEnabledStripMenuItem"); + Assert.That(multiFileItem.Enabled, Is.True); + Assert.That(multiFileItem.Checked, Is.True); + Assert.That(multiFileEnabledItem.Checked, Is.True); + } + + [Test] + public void UpdateGuiState_SetsCellSelectMode () + { + var state = new GuiStateEventArgs { CellSelectMode = true, MenuEnabled = true }; + _controller.UpdateGuiState(state, false); + + var cellSelectItem = FindMenuItem("cellSelectModeToolStripMenuItem"); + Assert.That(cellSelectItem.Checked, Is.True); + } + + [Test] + public void UpdateGuiState_SetsBookmarkBubblesButton () + { + var state = new GuiStateEventArgs { ShowBookmarkBubbles = true, MenuEnabled = true }; + _controller.UpdateGuiState(state, false); + + var bubblesButton = _toolbar.Items["toolStripButtonBubbles"] as ToolStripButton; + Assert.That(bubblesButton, Is.Not.Null, "Expected toolStripButtonBubbles to be a ToolStripButton."); + Assert.That(bubblesButton.Checked, Is.True); + } + + [Test] + public void UpdateGuiState_SetsColumnFinderVisible () + { + var state = new GuiStateEventArgs { ColumnFinderVisible = true, MenuEnabled = true }; + _controller.UpdateGuiState(state, false); + + var columnFinderItem = FindMenuItem("columnFinderToolStripMenuItem"); + Assert.That(columnFinderItem.Checked, Is.True); + } + + [Test] + public void UpdateGuiState_SetsHighlightGroupName () + { + var state = new GuiStateEventArgs { HighlightGroupName = "Errors", MenuEnabled = true }; + _controller.UpdateGuiState(state, false); + + var combo = _toolbar.Items["highlightGroupsToolStripComboBox"] as ToolStripComboBox; + Assert.That(combo.Text, Is.EqualTo("Errors")); + } + + [Test] + public void UpdateGuiState_SetsMenuEnabled () + { + var state = new GuiStateEventArgs { MenuEnabled = false }; + _controller.UpdateGuiState(state, false); + + Assert.That(_mainMenu.Enabled, Is.False); + } + + [Test] + public void UpdateHighlightGroups_DoesNotRaiseEvent_DuringProgrammaticUpdate () + { + string selectedGroup = null; + _controller.HighlightGroupSelected += (_, e) => selectedGroup = e.GroupName; + + _controller.UpdateHighlightGroups(["Default", "Errors"], "Errors"); + + Assert.That(selectedGroup, Is.Null); + } + + private ToolStripMenuItem FindMenuItem (string name) + { + return FindItemRecursive(_mainMenu.Items, name); + } + + private static T FindItemRecursive (ToolStripItemCollection items, string name) where T : ToolStripItem + { + foreach (ToolStripItem item in items) + { + if (item.Name == name && item is T typed) + { + return typed; + } + + if (item is ToolStripDropDownItem dropDown) + { + var found = FindItemRecursive(dropDown.DropDownItems, name); + if (found != null) + { + return found; + } + } + } + + return null; + } + + public void Dispose () + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose (bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + _controller?.Dispose(); + _mainMenu?.Dispose(); + _toolbar?.Dispose(); + _dragControl?.Dispose(); + _followTailCheckBox?.Dispose(); + } + + _disposed = true; + } +} \ No newline at end of file diff --git a/src/LogExpert.Tests/Services/TabControllerTests.cs b/src/LogExpert.Tests/Services/TabControllerTests.cs index 13e65d4b..8c64d8ec 100644 --- a/src/LogExpert.Tests/Services/TabControllerTests.cs +++ b/src/LogExpert.Tests/Services/TabControllerTests.cs @@ -2,7 +2,7 @@ using System.Windows.Forms; using LogExpert.UI.Controls.LogWindow; -using LogExpert.UI.Services; +using LogExpert.UI.Services.TabControllerService; using NUnit.Framework; @@ -93,7 +93,7 @@ public void Constructor_WithDockPanel_InitializesSuccessfully() public void Constructor_WithNullDockPanel_ThrowsArgumentNullException() { // Arrange & Act & Assert - Assert.Throws(() => new TabController(null)); + _ = Assert.Throws(() => new TabController(null)); } [Test] @@ -131,7 +131,7 @@ public void InitializeDockPanel_WithNullDockPanel_ThrowsArgumentNullException() using var controller = new TabController(); // Act & Assert - Assert.Throws(() => controller.InitializeDockPanel(null)); + _ = Assert.Throws(() => controller.InitializeDockPanel(null)); } [Test] @@ -146,7 +146,7 @@ public void InitializeDockPanel_WhenAlreadyInitialized_ThrowsInvalidOperationExc form2.Controls.Add(dockPanel2); // Act & Assert - Assert.Throws(() => controller.InitializeDockPanel(dockPanel2)); + _ = Assert.Throws(() => controller.InitializeDockPanel(dockPanel2)); } #endregion @@ -305,7 +305,7 @@ public void AddWindow_WithNullWindow_ThrowsArgumentNullException() // Arrange - already done in Setup // Act & Assert - Assert.Throws(() => _tabController.AddWindow(null, "Test Window")); + _ = Assert.Throws(() => _tabController.AddWindow(null, "Test Window")); } [Test] @@ -358,7 +358,7 @@ public void CloseAllWindows_WhenEmpty_DoesNotThrow() // Arrange - already done in Setup // Act & Assert - should not throw - Assert.DoesNotThrow(() => _tabController.CloseAllWindows()); + Assert.DoesNotThrow(_tabController.CloseAllWindows); } #endregion @@ -397,7 +397,7 @@ public void SwitchToNextWindow_WhenEmpty_DoesNotThrow() // Arrange - already done in Setup // Act & Assert - should not throw - Assert.DoesNotThrow(() => _tabController.SwitchToNextWindow()); + Assert.DoesNotThrow(_tabController.SwitchToNextWindow); } #endregion @@ -410,7 +410,7 @@ public void SwitchToPreviousWindow_WhenEmpty_DoesNotThrow() // Arrange - already done in Setup // Act & Assert - should not throw - Assert.DoesNotThrow(() => _tabController.SwitchToPreviousWindow()); + Assert.DoesNotThrow(_tabController.SwitchToPreviousWindow); } #endregion diff --git a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs index a888e269..e2fc4513 100644 --- a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs +++ b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs @@ -23,7 +23,10 @@ using LogExpert.UI.Entities; using LogExpert.UI.Extensions; using LogExpert.UI.Extensions.LogWindow; -using LogExpert.UI.Services; +using LogExpert.UI.Interface.Services; +using LogExpert.UI.Services.LedService; +using LogExpert.UI.Services.MenuToolbarService; +using LogExpert.UI.Services.TabControllerService; using NLog; @@ -46,6 +49,7 @@ internal partial class LogTabWindow : Form, ILogTabWindow private readonly LedIndicatorService _ledService; private readonly TabController _tabController; + private readonly MenuToolbarController _menuToolbarController; private bool _disposed; @@ -84,11 +88,12 @@ public LogTabWindow (string[] fileNames, int instanceNumber, bool showInstanceNu ConfigureDockPanel(); _tabController = new TabController(dockPanel); - _tabController.WindowAdded += OnTabControllerWindowAdded; - _tabController.WindowRemoved += OnTabControllerWindowRemoved; - _tabController.WindowActivated += OnTabControllerWindowActivated; - _tabController.WindowClosing += OnTabControllerWindowClosing; + InitializeTabControllerEvents(); + _menuToolbarController = new MenuToolbarController(); + _menuToolbarController.InitializeMenus(mainMenuStrip, buttonToolStrip, externalToolsToolStrip, dragControlDateTime, checkBoxFollowTail); + InitializeMenuToolbarControllerEvents(); + ApplyTextResources(); ConfigManager = configManager; @@ -111,7 +116,7 @@ public LogTabWindow (string[] fileNames, int instanceNumber, bool showInstanceNu _ledService = new LedIndicatorService(); _ledService.Initialize(ConfigManager.Settings.Preferences.ShowTailColor); _ledService.IconChanged += OnLedIconChanged; - _ledService.Start(); + _ledService.StartService(); _deadIcon = _ledService.GetDeadIcon(); @@ -146,6 +151,37 @@ public LogTabWindow (string[] fileNames, int instanceNumber, bool showInstanceNu InitToolWindows(); } + private void InitializeMenuToolbarControllerEvents () + { + _menuToolbarController.HistoryItemClicked += OnMenuControllerHistoryItemClicked; + _menuToolbarController.HistoryItemRemoveRequested += OnMenuControllerHistoryItemRemoveRequested; + _menuToolbarController.HighlightGroupSelected += OnMenuControllerHighlightGroupSelected; + } + + private void OnMenuControllerHighlightGroupSelected (object? sender, HighlightGroupSelectedEventArgs e) + { + CurrentLogWindow?.SetCurrentHighlightGroup(e.GroupName); + } + + private void OnMenuControllerHistoryItemRemoveRequested (object? sender, HistoryItemClickedEventArgs e) + { + ConfigManager.RemoveFromFileHistory(e.FileName); + FillHistoryMenu(); + } + + private void OnMenuControllerHistoryItemClicked (object? sender, HistoryItemClickedEventArgs e) + { + _ = AddFileTab(e.FileName, false, null, false, null); + } + + private void InitializeTabControllerEvents () + { + _tabController.WindowAdded += OnTabControllerWindowAdded; + _tabController.WindowRemoved += OnTabControllerWindowRemoved; + _tabController.WindowActivated += OnTabControllerWindowActivated; + _tabController.WindowClosing += OnTabControllerWindowClosing; + } + #endregion #region Delegates @@ -190,11 +226,6 @@ public LogWindow.LogWindow CurrentLogWindow public List HighlightGroupList { get; private set; } = []; - //public Settings Settings - //{ - // get { return ConfigManager.Settings; } - //} - [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public ILogExpertProxy LogExpertProxy { get; set; } @@ -299,7 +330,6 @@ private void ConfigureDockPanel () private void ApplyTextResources () { - mainMenuStrip.Text = Resources.LogTabWindow_UI_MenuStrip_MainMenu; Text = Resources.LogExpert_Common_UI_Title_LogExpert; checkBoxHost.AccessibleName = Resources.LogTabWindow_UI_CheckBox_ToolTip_checkBoxHost; @@ -888,10 +918,11 @@ protected override void Dispose (bool disposing) if (disposing && (components != null)) { - _ledService?.Stop(); + _ledService?.StopService(); _ledService?.Dispose(); components.Dispose(); _tabStringFormat?.Dispose(); + _menuToolbarController?.Dispose(); } _disposed = true; @@ -1080,12 +1111,6 @@ private void DisconnectEventHandlers (LogWindow.LogWindow logWindow) logWindow.FilterListChanged -= OnLogWindowFilterListChanged; logWindow.CurrentHighlightGroupChanged -= OnLogWindowCurrentHighlightGroupChanged; logWindow.SyncModeChanged -= OnLogWindowSyncModeChanged; - - //var data = logWindow.Tag as LogWindowData; - //data.tabPage.MouseClick -= tabPage_MouseClick; - //data.tabPage.TabDoubleClick -= tabPage_TabDoubleClick; - //data.tabPage.ContextMenuStrip = null; - //data.tabPage = null; } [SupportedOSPlatform("windows")] @@ -1109,18 +1134,7 @@ private LogWindow.LogWindow FindWindowForFile (string fileName) [SupportedOSPlatform("windows")] private void FillHistoryMenu () { - ToolStripDropDown strip = new ToolStripDropDownMenu(); - - foreach (var file in ConfigManager.Settings.FileHistoryList) - { - ToolStripItem item = new ToolStripMenuItem(file); - _ = strip.Items.Add(item); - - } - - strip.ItemClicked += OnHistoryItemClicked; - strip.MouseUp += OnStripMouseUp; - lastUsedToolStripMenuItem.DropDown = strip; + _menuToolbarController.PopulateFileHistory(ConfigManager.Settings.FileHistoryList); } /// @@ -1169,16 +1183,9 @@ private void ShowHighlightSettingsDialog () [SupportedOSPlatform("windows")] private void FillHighlightComboBox () { - var currentGroupName = highlightGroupsToolStripComboBox.Text; - highlightGroupsToolStripComboBox.Items.Clear(); - foreach (var group in HighlightGroupList) - { - _ = highlightGroupsToolStripComboBox.Items.Add(group.GroupName); - if (group.GroupName.Equals(currentGroupName, StringComparison.Ordinal)) - { - highlightGroupsToolStripComboBox.Text = group.GroupName; - } - } + var groups = HighlightGroupList.Select(g => g.GroupName); + var selected = highlightGroupsToolStripComboBox.Text; + _menuToolbarController.UpdateHighlightGroups(groups, selected); } [SupportedOSPlatform("windows")] @@ -1424,38 +1431,7 @@ private void DisconnectBookmarkWindow () private void GuiStateUpdateWorker (GuiStateEventArgs e) { _skipEvents = true; - checkBoxFollowTail.Checked = e.FollowTail; - mainMenuStrip.Enabled = e.MenuEnabled; - timeshiftToolStripMenuItem.Enabled = e.TimeshiftPossible; - timeshiftToolStripMenuItem.Checked = e.TimeshiftEnabled; - timeshiftToolStripTextBox.Text = e.TimeshiftText; - timeshiftToolStripTextBox.Enabled = e.TimeshiftEnabled; - multiFileToolStripMenuItem.Enabled = e.MultiFileEnabled; // disabled for temp files - multiFileToolStripMenuItem.Checked = e.IsMultiFileActive; - multiFileEnabledStripMenuItem.Checked = e.IsMultiFileActive; - cellSelectModeToolStripMenuItem.Checked = e.CellSelectMode; - - RefreshEncodingMenuBar(e.CurrentEncoding); - - if (e.TimeshiftPossible && ConfigManager.Settings.Preferences.TimestampControl) - { - dragControlDateTime.MinDateTime = e.MinTimestamp; - dragControlDateTime.MaxDateTime = e.MaxTimestamp; - dragControlDateTime.DateTime = e.Timestamp; - dragControlDateTime.Visible = true; - dragControlDateTime.Enabled = true; - dragControlDateTime.Refresh(); - } - else - { - dragControlDateTime.Visible = false; - dragControlDateTime.Enabled = false; - } - - toolStripButtonBubbles.Checked = e.ShowBookmarkBubbles; - highlightGroupsToolStripComboBox.Text = e.HighlightGroupName; - columnFinderToolStripMenuItem.Checked = e.ColumnFinderVisible; - + _menuToolbarController.UpdateGuiState(e, ConfigManager.Settings.Preferences.TimestampControl); _skipEvents = false; } @@ -1579,39 +1555,7 @@ private Icon GetLedIcon (int diffSum, LogWindowData data) [SupportedOSPlatform("windows")] private void RefreshEncodingMenuBar (Encoding encoding) { - encodingASCIIToolStripMenuItem.Checked = false; - encodingANSIToolStripMenuItem.Checked = false; - encodingUTF8toolStripMenuItem.Checked = false; - encodingUTF16toolStripMenuItem.Checked = false; - encodingISO88591toolStripMenuItem.Checked = false; - - if (encoding == null) - { - return; - } - - if (encoding is ASCIIEncoding) - { - encodingASCIIToolStripMenuItem.Checked = true; - } - else if (encoding.Equals(Encoding.Default)) - { - encodingANSIToolStripMenuItem.Checked = true; - } - else if (encoding is UTF8Encoding) - { - encodingUTF8toolStripMenuItem.Checked = true; - } - else if (encoding is UnicodeEncoding) - { - encodingUTF16toolStripMenuItem.Checked = true; - } - else if (encoding.Equals(Encoding.GetEncoding("iso-8859-1"))) - { - encodingISO88591toolStripMenuItem.Checked = true; - } - - encodingANSIToolStripMenuItem.Text = Encoding.Default.HeaderName; + _menuToolbarController.UpdateEncodingMenu(encoding); } [SupportedOSPlatform("windows")] @@ -2176,22 +2120,6 @@ private void OnLogTabWindowFormClosing (object sender, CancelEventArgs e) } } - private void OnStripMouseUp (object sender, MouseEventArgs e) - { - if (sender is ToolStripDropDown dropDown) - { - _ = AddFileTab(dropDown.Text, false, null, false, null); - } - } - - private void OnHistoryItemClicked (object sender, ToolStripItemClickedEventArgs e) - { - if (!string.IsNullOrEmpty(e.ClickedItem.Text)) - { - _ = AddFileTab(e.ClickedItem.Text, false, null, false, null); - } - } - private void OnLogWindowDisposed (object sender, EventArgs e) { var logWindow = sender as LogWindow.LogWindow; @@ -2266,7 +2194,6 @@ private void OnSelectFilterToolStripMenuItemClick (object sender, EventArgs e) logWindow.ColumnizerConfigChanged(); } } - } } } @@ -2491,7 +2418,7 @@ private void OnFileSizeChanged (object sender, LogEventArgs e) return; } - if (logWindow.Tag is not LogWindowData data) + if (logWindow.Tag is not LogWindowData) { return; } @@ -2931,12 +2858,6 @@ private void OnHighlightGroupsComboBoxDropDownClosed (object sender, EventArgs e ApplySelectedHighlightGroup(); } - [SupportedOSPlatform("windows")] - private void OnHighlightGroupsComboBoxSelectedIndexChanged (object sender, EventArgs e) - { - ApplySelectedHighlightGroup(); - } - [SupportedOSPlatform("windows")] private void OnHighlightGroupsComboBoxMouseUp (object sender, MouseEventArgs e) { diff --git a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.designer.cs b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.designer.cs index f173f5c7..e5f9c152 100644 --- a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.designer.cs +++ b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.designer.cs @@ -965,7 +965,6 @@ private void InitializeComponent () highlightGroupsToolStripComboBox.Size = new Size(150, 23); highlightGroupsToolStripComboBox.ToolTipText = "Select the current highlight settings for the log file (right-click to open highlight settings)"; highlightGroupsToolStripComboBox.DropDownClosed += OnHighlightGroupsComboBoxDropDownClosed; - highlightGroupsToolStripComboBox.SelectedIndexChanged += OnHighlightGroupsComboBoxSelectedIndexChanged; highlightGroupsToolStripComboBox.MouseUp += OnHighlightGroupsComboBoxMouseUp; // // checkBoxFollowTail diff --git a/src/LogExpert.UI/Services/ILedIndicatorService.cs b/src/LogExpert.UI/Interface/ILedIndicatorService.cs similarity index 95% rename from src/LogExpert.UI/Services/ILedIndicatorService.cs rename to src/LogExpert.UI/Interface/ILedIndicatorService.cs index d6520a09..82b0c45c 100644 --- a/src/LogExpert.UI/Services/ILedIndicatorService.cs +++ b/src/LogExpert.UI/Interface/ILedIndicatorService.cs @@ -1,6 +1,7 @@ using LogExpert.UI.Controls.LogWindow; +using LogExpert.UI.Services.LedService; -namespace LogExpert.UI.Services; +namespace LogExpert.UI.Interface; /// /// Service for managing LED indicator icons on log window tabs @@ -35,12 +36,12 @@ internal interface ILedIndicatorService : IDisposable /// /// Starts the LED animation thread /// - void Start (); + void StartService (); /// /// Stops the LED animation thread /// - void Stop (); + void StopService (); /// /// Registers a window for LED state tracking diff --git a/src/LogExpert.UI/Interface/IMenuToolbarController.cs b/src/LogExpert.UI/Interface/IMenuToolbarController.cs new file mode 100644 index 00000000..9bd7c3a8 --- /dev/null +++ b/src/LogExpert.UI/Interface/IMenuToolbarController.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Text; + +using System.Runtime.Versioning; + +using LogExpert.Core.EventArguments; +using LogExpert.Dialogs; +using LogExpert.UI.Services.MenuToolbarService; + +namespace LogExpert.UI.Interface; + +/// +/// Controls menu and toolbar state based on application state. +/// Thread-safe UI updates via SynchronizationContext. +/// +[SupportedOSPlatform("windows")] +internal interface IMenuToolbarController : IDisposable +{ + /// + /// Initializes controller with menu, toolbar, and timestamp control references. + /// Must be called on UI thread. + /// + void InitializeMenus (MenuStrip mainMenu, ToolStrip buttonToolbar, ToolStrip externalToolsToolStrip, + DateTimeDragControl dragControlDateTime, CheckBox checkBoxFollowTail); + + /// + /// Updates all menus, toolbars, encoding, highlight group, and timestamp control + /// based on the GUI state event args from a LogWindow. + /// + /// + /// Consumes directly — no intermediate mapping needed. + /// + void UpdateGuiState (GuiStateEventArgs state, bool timestampControlEnabled); + + /// + /// Updates encoding menu to show current encoding. + /// Also updates the ANSI menu item header text. + /// + void UpdateEncodingMenu (Encoding currentEncoding); + + /// + /// Updates highlight groups combo box. + /// + void UpdateHighlightGroups (IEnumerable groups, string selectedGroup); + + /// + /// Populates file history menu with recent files. + /// Includes right-click removal support. + /// + void PopulateFileHistory (IEnumerable fileHistory); + + // Events + event EventHandler HistoryItemClicked; + event EventHandler HistoryItemRemoveRequested; + event EventHandler HighlightGroupSelected; +} diff --git a/src/LogExpert.UI/Services/ITabController.cs b/src/LogExpert.UI/Interface/ITabController.cs similarity index 97% rename from src/LogExpert.UI/Services/ITabController.cs rename to src/LogExpert.UI/Interface/ITabController.cs index 319cfe06..3f59ffe1 100644 --- a/src/LogExpert.UI/Services/ITabController.cs +++ b/src/LogExpert.UI/Interface/ITabController.cs @@ -1,8 +1,10 @@ using LogExpert.UI.Controls.LogWindow; +using LogExpert.UI.Interface.Services; +using LogExpert.UI.Services.TabControllerService; using WeifenLuo.WinFormsUI.Docking; -namespace LogExpert.UI.Services; +namespace LogExpert.UI.Interface; /// /// Controls the management of LogWindow tabs in the application. diff --git a/src/LogExpert.UI/Services/IconChangedEventArgs.cs b/src/LogExpert.UI/Services/LedService/IconChangedEventArgs.cs similarity index 87% rename from src/LogExpert.UI/Services/IconChangedEventArgs.cs rename to src/LogExpert.UI/Services/LedService/IconChangedEventArgs.cs index 79962397..08b72785 100644 --- a/src/LogExpert.UI/Services/IconChangedEventArgs.cs +++ b/src/LogExpert.UI/Services/LedService/IconChangedEventArgs.cs @@ -1,6 +1,6 @@ using LogExpert.UI.Controls.LogWindow; -namespace LogExpert.UI.Services; +namespace LogExpert.UI.Services.LedService; /// /// Event arguments for icon change notifications diff --git a/src/LogExpert.UI/Services/LedIndicatorService.cs b/src/LogExpert.UI/Services/LedService/LedIndicatorService.cs similarity index 98% rename from src/LogExpert.UI/Services/LedIndicatorService.cs rename to src/LogExpert.UI/Services/LedService/LedIndicatorService.cs index 392bd8ab..0a1f4270 100644 --- a/src/LogExpert.UI/Services/LedIndicatorService.cs +++ b/src/LogExpert.UI/Services/LedService/LedIndicatorService.cs @@ -4,10 +4,11 @@ using System.Runtime.Versioning; using LogExpert.UI.Controls.LogWindow; +using LogExpert.UI.Interface; using NLog; -namespace LogExpert.UI.Services; +namespace LogExpert.UI.Services.LedService; [SupportedOSPlatform("windows")] internal sealed class LedIndicatorService : ILedIndicatorService, IDisposable @@ -96,7 +97,7 @@ public void Dispose () } _disposed = true; - Stop(); + StopService(); Thread.Sleep(ANIMATION_INTERVAL_MS * 2); @@ -425,7 +426,7 @@ public void RegenerateIcons (Color tailColor) if (wasRunning) { - Stop(); + StopService(); Thread.Sleep(ANIMATION_INTERVAL_MS * 2); // Wait for pending ticks } @@ -459,7 +460,7 @@ public void RegenerateIcons (Color tailColor) if (wasRunning) { - Start(); + StartService(); } } @@ -483,7 +484,7 @@ public void RegisterWindow (LogWindow window) /// /// Starts the LED animation timer /// - public void Start () + public void StartService () { if (!_isInitialized) { @@ -510,7 +511,7 @@ public void Start () /// /// Stops the LED animation timer /// - public void Stop () + public void StopService () { if (_animationTimer == null) { diff --git a/src/LogExpert.UI/Services/LedState.cs b/src/LogExpert.UI/Services/LedService/LedState.cs similarity index 95% rename from src/LogExpert.UI/Services/LedState.cs rename to src/LogExpert.UI/Services/LedService/LedState.cs index 14a648eb..2d624a1d 100644 --- a/src/LogExpert.UI/Services/LedState.cs +++ b/src/LogExpert.UI/Services/LedService/LedState.cs @@ -1,4 +1,4 @@ -namespace LogExpert.UI.Services; +namespace LogExpert.UI.Services.LedService; /// /// LED state information diff --git a/src/LogExpert.UI/Services/TailFollowState.cs b/src/LogExpert.UI/Services/LedService/TailFollowState.cs similarity index 91% rename from src/LogExpert.UI/Services/TailFollowState.cs rename to src/LogExpert.UI/Services/LedService/TailFollowState.cs index 080d59c1..b6c5f358 100644 --- a/src/LogExpert.UI/Services/TailFollowState.cs +++ b/src/LogExpert.UI/Services/LedService/TailFollowState.cs @@ -1,4 +1,4 @@ -namespace LogExpert.UI.Services; +namespace LogExpert.UI.Services.LedService; /// /// Represents the tail follow state for a log window diff --git a/src/LogExpert.UI/Services/TimeSyncState.cs b/src/LogExpert.UI/Services/LedService/TimeSyncState.cs similarity index 89% rename from src/LogExpert.UI/Services/TimeSyncState.cs rename to src/LogExpert.UI/Services/LedService/TimeSyncState.cs index 1ebdc147..f5d832e0 100644 --- a/src/LogExpert.UI/Services/TimeSyncState.cs +++ b/src/LogExpert.UI/Services/LedService/TimeSyncState.cs @@ -1,4 +1,4 @@ -namespace LogExpert.UI.Services; +namespace LogExpert.UI.Services.LedService; /// /// Represents the time synchronization state for a log window diff --git a/src/LogExpert.UI/Services/MenuToolbarService/HighlightGroupSelectedEventArgs.cs b/src/LogExpert.UI/Services/MenuToolbarService/HighlightGroupSelectedEventArgs.cs new file mode 100644 index 00000000..61e83a78 --- /dev/null +++ b/src/LogExpert.UI/Services/MenuToolbarService/HighlightGroupSelectedEventArgs.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LogExpert.UI.Services.MenuToolbarService; + +/// Event args for highlight group combo box selection. +internal class HighlightGroupSelectedEventArgs (string groupName) : EventArgs +{ + public string GroupName { get; } = groupName; +} diff --git a/src/LogExpert.UI/Services/MenuToolbarService/HistoryItemClickedEventArgs.cs b/src/LogExpert.UI/Services/MenuToolbarService/HistoryItemClickedEventArgs.cs new file mode 100644 index 00000000..4ae700b8 --- /dev/null +++ b/src/LogExpert.UI/Services/MenuToolbarService/HistoryItemClickedEventArgs.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LogExpert.UI.Services.MenuToolbarService; + +/// Event args for file history item interactions. +internal class HistoryItemClickedEventArgs (string fileName) : EventArgs +{ + public string FileName { get; } = fileName; +} diff --git a/src/LogExpert.UI/Services/MenuToolbarService/MenuToolbarController.cs b/src/LogExpert.UI/Services/MenuToolbarService/MenuToolbarController.cs new file mode 100644 index 00000000..04cb3083 --- /dev/null +++ b/src/LogExpert.UI/Services/MenuToolbarService/MenuToolbarController.cs @@ -0,0 +1,444 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Versioning; +using System.Text; + +using LogExpert.Core.EventArguments; +using LogExpert.Dialogs; +using LogExpert.UI.Interface; + +using NLog; + +namespace LogExpert.UI.Services.MenuToolbarService; + +[SupportedOSPlatform("windows")] +internal sealed class MenuToolbarController : IMenuToolbarController +{ + private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); + + private MenuStrip _mainMenu; + private ToolStrip _buttonToolbar; + private ToolStrip _externalToolsToolStrip; + + // Controls passed in from LogTabWindow (not owned by this controller) + private DateTimeDragControl _dragControlDateTime; + private CheckBox _checkBoxFollowTail; + + // Menu items (cached during initialization for performance) + private ToolStripMenuItem _closeFileMenuItem; + private ToolStripMenuItem _searchMenuItem; + private ToolStripMenuItem _filterMenuItem; + private ToolStripMenuItem _goToLineMenuItem; + private ToolStripMenuItem _multiFileMenuItem; + private ToolStripMenuItem _multiFileEnabledMenuItem; + private ToolStripMenuItem _timeshiftMenuItem; + private ToolStripTextBox _timeshiftTextBox; + private ToolStripMenuItem _cellSelectMenuItem; + private ToolStripMenuItem _columnFinderMenuItem; + + // Encoding menu items + private ToolStripMenuItem _encodingAsciiMenuItem; + private ToolStripMenuItem _encodingAnsiMenuItem; + private ToolStripMenuItem _encodingUtf8MenuItem; + private ToolStripMenuItem _encodingUtf16MenuItem; + private ToolStripMenuItem _encodingIso88591MenuItem; + + // Toolbar items + private ToolStripButton _bubblesButton; + + // Highlight group combo + private ToolStripComboBox _highlightGroupCombo; + + // History menu + private ToolStripMenuItem _lastUsedMenuItem; + + private readonly SynchronizationContext _uiContext; + private bool _disposed; + private bool _suppressEvents; + + public event EventHandler HistoryItemClicked; + public event EventHandler HistoryItemRemoveRequested; + public event EventHandler HighlightGroupSelected; + + public MenuToolbarController () + { + _uiContext = SynchronizationContext.Current + ?? throw new InvalidOperationException("Must be created on UI thread"); + } + + public void InitializeMenus (MenuStrip mainMenu, ToolStrip buttonToolbar, + ToolStrip externalToolsToolStrip, DateTimeDragControl dragControlDateTime, + CheckBox checkBoxFollowTail) + { + ArgumentNullException.ThrowIfNull(mainMenu); + ArgumentNullException.ThrowIfNull(buttonToolbar); + ArgumentNullException.ThrowIfNull(dragControlDateTime); + ArgumentNullException.ThrowIfNull(checkBoxFollowTail); + + _mainMenu = mainMenu; + _buttonToolbar = buttonToolbar; + _externalToolsToolStrip = externalToolsToolStrip; + _dragControlDateTime = dragControlDateTime; + _checkBoxFollowTail = checkBoxFollowTail; + + // Cache menu items by designer name (recursive search into dropdowns) + _closeFileMenuItem = FindMenuItem("closeFileToolStripMenuItem"); + _searchMenuItem = FindMenuItem("searchToolStripMenuItem"); + _filterMenuItem = FindMenuItem("filterToolStripMenuItem"); + _goToLineMenuItem = FindMenuItem("goToLineToolStripMenuItem"); + _multiFileMenuItem = FindMenuItem("multiFileToolStripMenuItem"); + _multiFileEnabledMenuItem = FindMenuItem("multiFileEnabledStripMenuItem"); + _timeshiftMenuItem = FindMenuItem("timeshiftToolStripMenuItem"); + _timeshiftTextBox = FindToolStripItem(_mainMenu, "timeshiftToolStripTextBox"); + _cellSelectMenuItem = FindMenuItem("cellSelectModeToolStripMenuItem"); + _columnFinderMenuItem = FindMenuItem("columnFinderToolStripMenuItem"); + + // Encoding menu items + _encodingAsciiMenuItem = FindMenuItem("encodingASCIIToolStripMenuItem"); + _encodingAnsiMenuItem = FindMenuItem("encodingANSIToolStripMenuItem"); + _encodingUtf8MenuItem = FindMenuItem("encodingUTF8toolStripMenuItem"); + _encodingUtf16MenuItem = FindMenuItem("encodingUTF16toolStripMenuItem"); + _encodingIso88591MenuItem = FindMenuItem("encodingISO88591toolStripMenuItem"); + + // Toolbar items + _bubblesButton = FindToolStripItem(_buttonToolbar, "toolStripButtonBubbles"); + + // Highlight group combo (may be on buttonToolbar or externalToolsToolStrip) + _highlightGroupCombo = FindToolStripItem(_buttonToolbar, "highlightGroupsToolStripComboBox") ?? FindToolStripItem(_externalToolsToolStrip, "highlightGroupsToolStripComboBox"); + + _highlightGroupCombo?.SelectedIndexChanged += OnHighlightGroupComboSelectedIndexChanged; + + // History menu + _lastUsedMenuItem = FindMenuItem("lastUsedToolStripMenuItem"); + + LogMissingItems(); + } + + public void UpdateGuiState (GuiStateEventArgs state, bool timestampControlEnabled) + { + ArgumentNullException.ThrowIfNull(state); + + if (_uiContext != SynchronizationContext.Current) + { + _uiContext.Post(_ => UpdateGuiState(state, timestampControlEnabled), null); + return; + } + + _suppressEvents = true; + + try + { + _checkBoxFollowTail.Checked = state.FollowTail; + _mainMenu.Enabled = state.MenuEnabled; + + // Timeshift + if (_timeshiftMenuItem != null) + { + _timeshiftMenuItem.Enabled = state.TimeshiftPossible; + _timeshiftMenuItem.Checked = state.TimeshiftEnabled; + } + + if (_timeshiftTextBox != null) + { + _timeshiftTextBox.Text = state.TimeshiftText; + _timeshiftTextBox.Enabled = state.TimeshiftEnabled; + } + + // Multi-file + if (_multiFileMenuItem != null) + { + _multiFileMenuItem.Enabled = state.MultiFileEnabled; + _multiFileMenuItem.Checked = state.IsMultiFileActive; + } + + _ = (_multiFileEnabledMenuItem?.Checked = state.IsMultiFileActive); + + // Cell select + _ = (_cellSelectMenuItem?.Checked = state.CellSelectMode); + + // Encoding + UpdateEncodingMenu(state.CurrentEncoding); + + // Timestamp drag control + if (state.TimeshiftPossible && timestampControlEnabled) + { + _dragControlDateTime.MinDateTime = state.MinTimestamp; + _dragControlDateTime.MaxDateTime = state.MaxTimestamp; + _dragControlDateTime.DateTime = state.Timestamp; + _dragControlDateTime.Visible = true; + _dragControlDateTime.Enabled = true; + _dragControlDateTime.Refresh(); + } + else + { + _dragControlDateTime.Visible = false; + _dragControlDateTime.Enabled = false; + } + + // Toolbar + _ = (_bubblesButton?.Checked = state.ShowBookmarkBubbles); + + // Highlight group + _ = (_highlightGroupCombo?.Text = state.HighlightGroupName); + + // Column finder + _ = (_columnFinderMenuItem?.Checked = state.ColumnFinderVisible); + } + finally + { + _suppressEvents = false; + } + } + + public void UpdateEncodingMenu (Encoding currentEncoding) + { + if (_uiContext != SynchronizationContext.Current) + { + _uiContext.Post(_ => UpdateEncodingMenu(currentEncoding), null); + return; + } + + // Clear all checks + SetCheckedSafe(_encodingAsciiMenuItem, false); + SetCheckedSafe(_encodingAnsiMenuItem, false); + SetCheckedSafe(_encodingUtf8MenuItem, false); + SetCheckedSafe(_encodingUtf16MenuItem, false); + SetCheckedSafe(_encodingIso88591MenuItem, false); + + if (currentEncoding == null) + { + return; + } + + if (currentEncoding is ASCIIEncoding) + { + SetCheckedSafe(_encodingAsciiMenuItem, true); + } + else if (currentEncoding.Equals(Encoding.Default)) + { + SetCheckedSafe(_encodingAnsiMenuItem, true); + } + else if (currentEncoding is UTF8Encoding) + { + SetCheckedSafe(_encodingUtf8MenuItem, true); + } + else if (currentEncoding is UnicodeEncoding) + { + SetCheckedSafe(_encodingUtf16MenuItem, true); + } + else if (currentEncoding.Equals(Encoding.GetEncoding("iso-8859-1"))) + { + SetCheckedSafe(_encodingIso88591MenuItem, true); + } + + // Preserve existing behavior: update ANSI display name + _ = (_encodingAnsiMenuItem?.Text = Encoding.Default.HeaderName); + } + + public void UpdateHighlightGroups (IEnumerable groups, string selectedGroup) + { + if (_highlightGroupCombo == null) + { + return; + } + + if (_uiContext != SynchronizationContext.Current) + { + _uiContext.Post(_ => UpdateHighlightGroups(groups, selectedGroup), null); + return; + } + + _suppressEvents = true; + try + { + _highlightGroupCombo.Items.Clear(); + + foreach (var group in groups) + { + _ = _highlightGroupCombo.Items.Add(group); + + if (group.Equals(selectedGroup, StringComparison.Ordinal)) + { + _highlightGroupCombo.Text = group; + } + } + } + finally + { + _suppressEvents = false; + } + } + + public void PopulateFileHistory (IEnumerable fileHistory) + { + if (_lastUsedMenuItem == null) + { + return; + } + + if (_uiContext != SynchronizationContext.Current) + { + _uiContext.Post(_ => PopulateFileHistory(fileHistory), null); + return; + } + + // Unsubscribe from previous dropdown events + if (_lastUsedMenuItem.DropDown != null) + { + _lastUsedMenuItem.DropDown.ItemClicked -= OnHistoryMenuItemClicked; + _lastUsedMenuItem.DropDown.MouseUp -= OnHistoryStripMouseUp; + } + + var strip = new ToolStripDropDownMenu(); + + foreach (var file in fileHistory) + { + _ = strip.Items.Add(new ToolStripMenuItem(file)); + } + + strip.ItemClicked += OnHistoryMenuItemClicked; + strip.MouseUp += OnHistoryStripMouseUp; + _lastUsedMenuItem.DropDown = strip; + } + + #region Private Helpers + + private static void SetCheckedSafe (ToolStripMenuItem item, bool value) + { + _ = (item?.Checked = value); + } + + private void OnHighlightGroupComboSelectedIndexChanged (object sender, EventArgs e) + { + if (_suppressEvents) + { + return; + } + + if (_highlightGroupCombo.SelectedItem is string groupName && !string.IsNullOrEmpty(groupName)) + { + HighlightGroupSelected?.Invoke(this, new HighlightGroupSelectedEventArgs(groupName)); + } + } + + private void OnHistoryMenuItemClicked (object sender, ToolStripItemClickedEventArgs e) + { + var fileName = e.ClickedItem?.Text; + if (!string.IsNullOrEmpty(fileName)) + { + HistoryItemClicked?.Invoke(this, new HistoryItemClickedEventArgs(fileName)); + } + } + + private void OnHistoryStripMouseUp (object sender, MouseEventArgs e) + { + // Right-click to remove from history (preserves existing LogTabWindow behavior) + if (e.Button != MouseButtons.Right) + { + return; + } + + if (sender is ToolStripDropDownMenu strip) + { + var item = strip.GetItemAt(e.Location); + if (item != null && !string.IsNullOrEmpty(item.Text)) + { + HistoryItemRemoveRequested?.Invoke(this, new HistoryItemClickedEventArgs(item.Text)); + } + } + } + + private ToolStripMenuItem FindMenuItem (string name) + { + return FindToolStripItem(_mainMenu, name); + } + + private static T FindToolStripItem (ToolStrip strip, string name) where T : ToolStripItem + { + if (strip == null) + { + return null; + } + + foreach (ToolStripItem item in strip.Items) + { + if (item.Name == name && item is T typedItem) + { + return typedItem; + } + + if (item is ToolStripDropDownItem dropDown) + { + var found = FindToolStripItemRecursive(dropDown.DropDownItems, name); + if (found != null) + { + return found; + } + } + } + + return null; + } + + private static T FindToolStripItemRecursive (ToolStripItemCollection items, string name) where T : ToolStripItem + { + foreach (ToolStripItem item in items) + { + if (item.Name == name && item is T typedItem) + { + return typedItem; + } + + if (item is ToolStripDropDownItem dropDown) + { + var found = FindToolStripItemRecursive(dropDown.DropDownItems, name); + if (found != null) + { + return found; + } + } + } + + return null; + } + + private void LogMissingItems () + { + // Log warnings for any menu items that couldn't be found during initialization + LogIfNull(_closeFileMenuItem, "closeFileToolStripMenuItem"); + LogIfNull(_searchMenuItem, "searchToolStripMenuItem"); + LogIfNull(_filterMenuItem, "filterToolStripMenuItem"); + LogIfNull(_timeshiftMenuItem, "timeshiftToolStripMenuItem"); + LogIfNull(_encodingAsciiMenuItem, "encodingASCIIToolStripMenuItem"); + LogIfNull(_highlightGroupCombo, "highlightGroupsToolStripComboBox"); + LogIfNull(_lastUsedMenuItem, "lastUsedToolStripMenuItem"); + } + + private static void LogIfNull (object item, string name) + { + if (item == null) + { + _logger.Warn("MenuToolbarController: menu item '{0}' not found during initialization", name); + } + } + + #endregion + + public void Dispose () + { + if (_disposed) + { + return; + } + + _highlightGroupCombo?.SelectedIndexChanged -= OnHighlightGroupComboSelectedIndexChanged; + + if (_lastUsedMenuItem?.DropDown != null) + { + _lastUsedMenuItem.DropDown.ItemClicked -= OnHistoryMenuItemClicked; + _lastUsedMenuItem.DropDown.MouseUp -= OnHistoryStripMouseUp; + } + + _disposed = true; + } +} diff --git a/src/LogExpert.UI/Services/TabController.cs b/src/LogExpert.UI/Services/TabControllerService/TabController.cs similarity index 99% rename from src/LogExpert.UI/Services/TabController.cs rename to src/LogExpert.UI/Services/TabControllerService/TabController.cs index 21441e58..1065ca08 100644 --- a/src/LogExpert.UI/Services/TabController.cs +++ b/src/LogExpert.UI/Services/TabControllerService/TabController.cs @@ -2,10 +2,12 @@ using LogExpert.UI.Controls.LogWindow; using LogExpert.UI.Entities; +using LogExpert.UI.Interface; +using LogExpert.UI.Interface.Services; using WeifenLuo.WinFormsUI.Docking; -namespace LogExpert.UI.Services; +namespace LogExpert.UI.Services.TabControllerService; [SupportedOSPlatform("windows")] internal class TabController : ITabController diff --git a/src/LogExpert.UI/Services/WindowActivatedEventArgs.cs b/src/LogExpert.UI/Services/TabControllerService/WindowActivatedEventArgs.cs similarity index 82% rename from src/LogExpert.UI/Services/WindowActivatedEventArgs.cs rename to src/LogExpert.UI/Services/TabControllerService/WindowActivatedEventArgs.cs index 806f99cb..df248829 100644 --- a/src/LogExpert.UI/Services/WindowActivatedEventArgs.cs +++ b/src/LogExpert.UI/Services/TabControllerService/WindowActivatedEventArgs.cs @@ -1,6 +1,6 @@ using LogExpert.UI.Controls.LogWindow; -namespace LogExpert.UI.Services; +namespace LogExpert.UI.Services.TabControllerService; internal class WindowActivatedEventArgs (LogWindow window, LogWindow previousWindow) : EventArgs { diff --git a/src/LogExpert.UI/Services/WindowAddedEventArgs.cs b/src/LogExpert.UI/Services/TabControllerService/WindowAddedEventArgs.cs similarity index 74% rename from src/LogExpert.UI/Services/WindowAddedEventArgs.cs rename to src/LogExpert.UI/Services/TabControllerService/WindowAddedEventArgs.cs index f518ac79..d28e95aa 100644 --- a/src/LogExpert.UI/Services/WindowAddedEventArgs.cs +++ b/src/LogExpert.UI/Services/TabControllerService/WindowAddedEventArgs.cs @@ -1,6 +1,6 @@ using LogExpert.UI.Controls.LogWindow; -namespace LogExpert.UI.Services; +namespace LogExpert.UI.Services.TabControllerService; internal class WindowAddedEventArgs (LogWindow window) : EventArgs { diff --git a/src/LogExpert.UI/Services/WindowClosingEventArgs.cs b/src/LogExpert.UI/Services/TabControllerService/WindowClosingEventArgs.cs similarity index 86% rename from src/LogExpert.UI/Services/WindowClosingEventArgs.cs rename to src/LogExpert.UI/Services/TabControllerService/WindowClosingEventArgs.cs index df073838..e5707cf0 100644 --- a/src/LogExpert.UI/Services/WindowClosingEventArgs.cs +++ b/src/LogExpert.UI/Services/TabControllerService/WindowClosingEventArgs.cs @@ -3,7 +3,7 @@ using LogExpert.UI.Controls.LogWindow; -namespace LogExpert.UI.Services; +namespace LogExpert.UI.Interface.Services; internal class WindowClosingEventArgs (LogWindow window, bool skipConfirmation) : CancelEventArgs { diff --git a/src/LogExpert.UI/Services/WindowRemovedEventArgs.cs b/src/LogExpert.UI/Services/TabControllerService/WindowRemovedEventArgs.cs similarity index 74% rename from src/LogExpert.UI/Services/WindowRemovedEventArgs.cs rename to src/LogExpert.UI/Services/TabControllerService/WindowRemovedEventArgs.cs index 305d4a8d..3e264dec 100644 --- a/src/LogExpert.UI/Services/WindowRemovedEventArgs.cs +++ b/src/LogExpert.UI/Services/TabControllerService/WindowRemovedEventArgs.cs @@ -1,6 +1,6 @@ using LogExpert.UI.Controls.LogWindow; -namespace LogExpert.UI.Services; +namespace LogExpert.UI.Services.TabControllerService; internal class WindowRemovedEventArgs (LogWindow window) : EventArgs { diff --git a/src/LogExpert.sln b/src/LogExpert.sln index 66c80e97..eded07ab 100644 --- a/src/LogExpert.sln +++ b/src/LogExpert.sln @@ -26,6 +26,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig Solution Items\AssemblyInfo.cs = Solution Items\AssemblyInfo.cs ..\CHANGELOG.md = ..\CHANGELOG.md + ..\.github\copilot-instructions.md = ..\.github\copilot-instructions.md Directory.Build.props = Directory.Build.props Directory.Packages.props = Directory.Packages.props ..\GitVersion.yml = ..\GitVersion.yml diff --git a/src/PluginRegistry/PluginHashGenerator.Generated.cs b/src/PluginRegistry/PluginHashGenerator.Generated.cs index c748f387..2fbd3781 100644 --- a/src/PluginRegistry/PluginHashGenerator.Generated.cs +++ b/src/PluginRegistry/PluginHashGenerator.Generated.cs @@ -10,7 +10,7 @@ public static partial class PluginValidator { /// /// Gets pre-calculated SHA256 hashes for built-in plugins. - /// Generated: 2026-01-23 15:56:33 UTC + /// Generated: 2026-02-27 11:51:33 UTC /// Configuration: Release /// Plugin count: 22 /// @@ -18,28 +18,28 @@ public static Dictionary GetBuiltInPluginHashes() { return new Dictionary(StringComparer.OrdinalIgnoreCase) { - ["AutoColumnizer.dll"] = "1D07B11AE3270F10155D1D6AA54FE70ED0080415E43A1F2B2A0EF72875DA84AF", + ["AutoColumnizer.dll"] = "7077A562ABAAD963BEB2A6CFC5FA63CC1D19C76452995F4668728C0DB6D65826", ["BouncyCastle.Cryptography.dll"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6", ["BouncyCastle.Cryptography.dll (x86)"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6", - ["CsvColumnizer.dll"] = "4DDCDD5E767C265AE31FF09DE7C6A093DC66979CB8EC2D9A8CBCB9A79096ED51", - ["CsvColumnizer.dll (x86)"] = "4DDCDD5E767C265AE31FF09DE7C6A093DC66979CB8EC2D9A8CBCB9A79096ED51", - ["DefaultPlugins.dll"] = "9F2745A5376897CA4436C6A90B9B2BB0A29F4225F81BF95C0B868E66DFBB8D3C", - ["FlashIconHighlighter.dll"] = "D7D008CF7945D67C790BCC17649F01810B1B2F243483DBBAE08B653A9B62FBA4", - ["GlassfishColumnizer.dll"] = "E25C288D6C13B1B80A58EC25906D9BEBAC5C5B46AB98F28B6C4C9E90401F9E40", - ["JsonColumnizer.dll"] = "A74907FDB01478D84F89C8AEB1DD83B4A6F2FDF884760296EDCBD2BA3D46FF1B", - ["JsonCompactColumnizer.dll"] = "AEF65E2C43EE39EE7DC80FBD8D8F499E6E215D6839FF6BDA7CED98C6312DB5BA", - ["Log4jXmlColumnizer.dll"] = "F85079FDA7BB9F8837AA6F5A6449B5F222552B77886F78F3A16A125E4EDD288C", - ["LogExpert.Core.dll"] = "CACF8255B06CB4AF43595196419192CF8BC2CB630456F15E6BDBC7292AC8A86E", - ["LogExpert.Resources.dll"] = "ABA39DD2544E6F8D357008649500B8AEDC88BD233EC3ADEE9304035EB9FD1853", + ["CsvColumnizer.dll"] = "ACABF8FE90E75E88B58653253256F523673856C42BD313DC176B9BF89032FDB0", + ["CsvColumnizer.dll (x86)"] = "ACABF8FE90E75E88B58653253256F523673856C42BD313DC176B9BF89032FDB0", + ["DefaultPlugins.dll"] = "C25831E431B6902BDB48A5E2896365189BCC650EEB3632FC1999F1DC4C5C93D3", + ["FlashIconHighlighter.dll"] = "C7C20498D7C5F79EFF42BA8F9A5291C337A56DD36E5E3B4A6832113E44EAC95D", + ["GlassfishColumnizer.dll"] = "A51BA6FD40FCEBF495ACDC93990E310C42A54CC82F7AC89C0739AB5480F95D9D", + ["JsonColumnizer.dll"] = "4BF75A8E1060DD96AEDDA578AC9ADD46684EAB68E658F7A6929F01F7C2BCDF4C", + ["JsonCompactColumnizer.dll"] = "744BB326E206139929581046E2E5D827BE571A1645BB33545CAD8B6A982625D0", + ["Log4jXmlColumnizer.dll"] = "E578036CF907C018E0A94D001F5A15EC2C369F5F14331CB927FB8CA0606DAFEB", + ["LogExpert.Core.dll"] = "B2B7F38E5BDD3BCD45405EE635944E9661B0AE42D00F7DD48D87F7E7516B63FB", + ["LogExpert.Resources.dll"] = "D52BB5FF1F1EE3ED04D5516E3170C41EB5BC6F46960FB8807A0D106DADA2E216", ["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"] = "F44BF1FFF92E4D96C66E0B518D3E276BFE92E7DDB9EB0F93479ABC8EBE072CE2", - ["SftpFileSystem.dll"] = "1971EE85E9CEDDD0A38E846A3EC78F42D823B1DD1CA1A02B0A132B436FC1B32D", - ["SftpFileSystem.dll (x86)"] = "46D0701DB009188D5FADF47FDB54A5ADC85DB39D73CCE40C7823059CF9F19872", - ["SftpFileSystem.Resources.dll"] = "92C1963B793F646215C33E7B3E459564E0EDB6193D305739AEB871092572F318", - ["SftpFileSystem.Resources.dll (x86)"] = "92C1963B793F646215C33E7B3E459564E0EDB6193D305739AEB871092572F318", + ["RegexColumnizer.dll"] = "1564B50ABCF3B343C6E1A3247B6C94D3299D5185FC36DE8EB7DBBC8A29CE4DB1", + ["SftpFileSystem.dll"] = "EA142CB3F6C2B83008A3723ECC0C424374A297859E99B7DA7A6CE9DE2BBEB72C", + ["SftpFileSystem.dll (x86)"] = "0E57E9EED2F1637C8A6ABC32A1EEEA8AE8A7E855351CF832500DDE06C7382937", + ["SftpFileSystem.Resources.dll"] = "481373B967EF74F38A3A92BCE54D22635BFC1817218832AE3BC89EE20A858AAA", + ["SftpFileSystem.Resources.dll (x86)"] = "481373B967EF74F38A3A92BCE54D22635BFC1817218832AE3BC89EE20A858AAA", }; }