diff --git a/src/LogExpert.Configuration/ConfigManager.cs b/src/LogExpert.Configuration/ConfigManager.cs
index 4b80b9e3..f73c2c57 100644
--- a/src/LogExpert.Configuration/ConfigManager.cs
+++ b/src/LogExpert.Configuration/ConfigManager.cs
@@ -315,15 +315,11 @@ public void AddToFileHistory (string fileName)
Save(SettingsFlags.FileHistory);
}
- public void RemoveFromFileHistory (string fileName)
+ public void ClearFileHistory ()
{
- bool findName (string s) => s.ToUpperInvariant().Equals(fileName.ToUpperInvariant(), StringComparison.Ordinal);
-
- var index = Instance.Settings.FileHistoryList.FindIndex(findName);
-
- if (index != -1)
+ lock (_loadSaveLock)
{
- Instance.Settings.FileHistoryList.RemoveAt(index);
+ Instance.Settings.FileHistoryList.Clear();
}
Save(SettingsFlags.FileHistory);
diff --git a/src/LogExpert.Core/Interfaces/IConfigManager.cs b/src/LogExpert.Core/Interfaces/IConfigManager.cs
index ac523daf..8d430811 100644
--- a/src/LogExpert.Core/Interfaces/IConfigManager.cs
+++ b/src/LogExpert.Core/Interfaces/IConfigManager.cs
@@ -177,10 +177,9 @@ public interface IConfigManager
void AddToFileHistory (string fileName);
///
- /// Removes the specified file name from the file history list.
+ /// Removes all entries 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);
+ void ClearFileHistory ();
///
/// Clears the list of recently opened files.
diff --git a/src/LogExpert.Resources/Resources.Designer.cs b/src/LogExpert.Resources/Resources.Designer.cs
index 6bf4905d..5e9f174d 100644
--- a/src/LogExpert.Resources/Resources.Designer.cs
+++ b/src/LogExpert.Resources/Resources.Designer.cs
@@ -2383,6 +2383,15 @@ public static string LogTabWindow_UI_ToolStripMenuItem_cellSelectModeToolStripMe
}
}
+ ///
+ /// Looks up a localized string similar to Clear recently used.
+ ///
+ public static string LogTabWindow_UI_ToolStripMenuItem_clearFileHistoryToolStripMenuItem {
+ get {
+ return ResourceManager.GetString("LogTabWindow_UI_ToolStripMenuItem_clearFileHistoryToolStripMenuItem", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Close all tabs.
///
diff --git a/src/LogExpert.Resources/Resources.de.resx b/src/LogExpert.Resources/Resources.de.resx
index 67ef82bf..736bc8a4 100644
--- a/src/LogExpert.Resources/Resources.de.resx
+++ b/src/LogExpert.Resources/Resources.de.resx
@@ -1240,6 +1240,9 @@ Ein ausgewähltes Tool erscheint in der Iconbar. Alle anderen verfügbaren Tools
Zuletzt benutzt
+
+ Zuletzt benutzte löschen
+
Exit
diff --git a/src/LogExpert.Resources/Resources.resx b/src/LogExpert.Resources/Resources.resx
index bd9b3a53..441e4b87 100644
--- a/src/LogExpert.Resources/Resources.resx
+++ b/src/LogExpert.Resources/Resources.resx
@@ -1176,6 +1176,9 @@ Checked tools will appear in the icon bar. All other tools are available in the
Last used
+
+ Clear recently used
+
Exit
diff --git a/src/LogExpert.Resources/Resources.zh-CN.resx b/src/LogExpert.Resources/Resources.zh-CN.resx
index 9b875814..58f62e03 100644
--- a/src/LogExpert.Resources/Resources.zh-CN.resx
+++ b/src/LogExpert.Resources/Resources.zh-CN.resx
@@ -1046,6 +1046,9 @@
最近使用
+
+ 清除最近使用
+
退出
diff --git a/src/LogExpert.Tests/ConfigManagerTests/ClearFileHistoryTests.cs b/src/LogExpert.Tests/ConfigManagerTests/ClearFileHistoryTests.cs
new file mode 100644
index 00000000..7b24ebb0
--- /dev/null
+++ b/src/LogExpert.Tests/ConfigManagerTests/ClearFileHistoryTests.cs
@@ -0,0 +1,113 @@
+using LogExpert.Core.Config;
+
+using Newtonsoft.Json;
+
+using NUnit.Framework;
+
+namespace LogExpert.Tests.ConfigManagerTests;
+
+[TestFixture]
+public class ClearFileHistoryTests
+{
+ private string _testDir;
+ private Configuration.ConfigManager _configManager;
+ private object? _originalSettings;
+ private bool _originalIsInitialized;
+ private string _originalConfigDir;
+
+ [SetUp]
+ public void SetUp ()
+ {
+ _configManager = Configuration.ConfigManager.Instance;
+ _originalIsInitialized = ConfigManagerTestReflection.GetPrivateField(_configManager, "_isInitialized") is true;
+ _originalSettings = ConfigManagerTestReflection.GetPrivateField(_configManager, "_settings");
+ _originalConfigDir = _configManager.ConfigDir;
+
+ _testDir = Path.Join(Path.GetTempPath(), "LogExpert_ClearHistoryTest_" + Guid.NewGuid().ToString("N"));
+ _ = Directory.CreateDirectory(_testDir);
+
+ ConfigManagerTestReflection.ResetConfigManagerInitialization(_configManager);
+ _configManager.Initialize(_testDir, new Rectangle(0, 0, 1920, 1080));
+ _configManager.ConfigDir = _testDir;
+ }
+
+ [TearDown]
+ public void TearDown ()
+ {
+ ConfigManagerTestReflection.SetPrivateField(_configManager, "_isInitialized", _originalIsInitialized);
+ ConfigManagerTestReflection.SetPrivateField(_configManager, "_settings", _originalSettings);
+ _configManager.ConfigDir = _originalConfigDir;
+
+ try
+ {
+ if (Directory.Exists(_testDir))
+ {
+ Directory.Delete(_testDir, recursive: true);
+ }
+ }
+ catch (IOException)
+ {
+ // Ignore IO errors during cleanup
+ }
+ catch (UnauthorizedAccessException)
+ {
+ // Ignore access errors during cleanup
+ }
+ }
+
+ [Test]
+ public void ClearFileHistory_RemovesAllEntries ()
+ {
+ _configManager.AddToFileHistory(@"C:\logs\one.log");
+ _configManager.AddToFileHistory(@"C:\logs\two.log");
+ Assert.That(_configManager.Settings.FileHistoryList, Has.Count.EqualTo(2));
+
+ _configManager.ClearFileHistory();
+
+ Assert.That(_configManager.Settings.FileHistoryList, Is.Empty);
+ }
+
+ [Test]
+ public void ClearFileHistory_WritesEmptyListToSettingsFile ()
+ {
+ _configManager.AddToFileHistory(@"C:\logs\one.log");
+
+ _configManager.ClearFileHistory();
+
+ var settingsFile = Path.Join(_configManager.ActiveConfigDir, "settings.json");
+ Assert.That(File.Exists(settingsFile), Is.True);
+
+ var saved = JsonConvert.DeserializeObject(File.ReadAllText(settingsFile));
+ Assert.That(saved, Is.Not.Null);
+ Assert.That(saved.FileHistoryList, Is.Empty);
+ }
+
+ [Test]
+ public void ClearFileHistory_LeavesOtherHistoryListsUntouched ()
+ {
+ _configManager.AddToFileHistory(@"C:\logs\one.log");
+ _configManager.Settings.LastOpenFilesList.Add(@"C:\logs\one.log");
+ _configManager.Settings.SearchHistoryList.Add("error");
+ _configManager.Settings.FilterHistoryList.Add("WARN");
+ _configManager.Settings.FilterRangeHistoryList.Add("10");
+ _configManager.Settings.UriHistoryList.Add("https://example.org/one.log");
+ _configManager.Settings.RegexHistory.ExpressionHistoryList.Add(@"^\d+");
+ _configManager.Settings.RegexHistory.TesttextHistoryList.Add("42");
+ _configManager.Settings.ColumnizerHistoryList.Add(new ColumnizerHistoryEntry(@"C:\logs\one.log", "JsonColumnizer"));
+
+ _configManager.ClearFileHistory();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(_configManager.Settings.FileHistoryList, Is.Empty);
+ Assert.That(_configManager.Settings.LastOpenFilesList, Has.Count.EqualTo(1));
+ Assert.That(_configManager.Settings.SearchHistoryList, Has.Count.EqualTo(1));
+ Assert.That(_configManager.Settings.FilterHistoryList, Has.Count.EqualTo(1));
+ Assert.That(_configManager.Settings.FilterRangeHistoryList, Has.Count.EqualTo(1));
+ Assert.That(_configManager.Settings.UriHistoryList, Has.Count.EqualTo(1));
+ Assert.That(_configManager.Settings.RegexHistory.ExpressionHistoryList, Has.Count.EqualTo(1));
+ Assert.That(_configManager.Settings.RegexHistory.TesttextHistoryList, Has.Count.EqualTo(1));
+ Assert.That(_configManager.Settings.ColumnizerHistoryList, Has.Count.EqualTo(1));
+ });
+ }
+}
diff --git a/src/LogExpert.Tests/ConfigManagerTests/ConfigManagerPortableModeTests.cs b/src/LogExpert.Tests/ConfigManagerTests/ConfigManagerPortableModeTests.cs
index bb98741f..11729834 100644
--- a/src/LogExpert.Tests/ConfigManagerTests/ConfigManagerPortableModeTests.cs
+++ b/src/LogExpert.Tests/ConfigManagerTests/ConfigManagerPortableModeTests.cs
@@ -26,7 +26,7 @@ public void SetUp ()
_configManager = Configuration.ConfigManager.Instance;
// Reset the singleton's initialization state using reflection
- ResetConfigManagerInitialization();
+ ConfigManagerTestReflection.ResetConfigManagerInitialization(_configManager);
_configManager.Initialize(_testDir, new Rectangle(0, 0, 1920, 1080));
}
@@ -64,19 +64,6 @@ public void TearDown ()
#region Helper Methods
- ///
- /// Resets ConfigManager singleton initialization state via reflection.
- ///
- private void ResetConfigManagerInitialization ()
- {
- var isInitializedField = typeof(Configuration.ConfigManager).GetField("_isInitialized", BindingFlags.NonPublic | BindingFlags.Instance);
- isInitializedField?.SetValue(_configManager, false);
-
- // Reset settings so they reload from the new path
- var settingsField = typeof(Configuration.ConfigManager).GetField("_settings", BindingFlags.NonPublic | BindingFlags.Instance);
- settingsField?.SetValue(_configManager, null);
- }
-
///
/// Invokes a private instance method using reflection.
///
@@ -758,7 +745,7 @@ public void Load_NewPortableLayout_DetectedCorrectly ()
CreateSettingsFile(_configManager.PortableConfigDir);
// Reset settings to force reload
- ResetConfigManagerInitialization();
+ ConfigManagerTestReflection.ResetConfigManagerInitialization(_configManager);
_configManager.Initialize(_testDir, new Rectangle(0, 0, 1920, 1080));
// Act - Access Settings to trigger Load()
@@ -783,7 +770,7 @@ public void Load_OldPortableLayout_TriggersMigration ()
"""{ "Preferences": { "FontName": "OldLayout" }, "FilterList": [] }""");
// Reset settings to force reload
- ResetConfigManagerInitialization();
+ ConfigManagerTestReflection.ResetConfigManagerInitialization(_configManager);
_configManager.Initialize(_testDir, new Rectangle(0, 0, 1920, 1080));
// Act - Access Settings to trigger Load() which should migrate
diff --git a/src/LogExpert.Tests/ConfigManagerTests/ConfigManagerTestReflection.cs b/src/LogExpert.Tests/ConfigManagerTests/ConfigManagerTestReflection.cs
new file mode 100644
index 00000000..5b2f8084
--- /dev/null
+++ b/src/LogExpert.Tests/ConfigManagerTests/ConfigManagerTestReflection.cs
@@ -0,0 +1,40 @@
+using System.Reflection;
+
+using LogExpert.Core.Config;
+
+namespace LogExpert.Tests.ConfigManagerTests;
+
+///
+/// Shared reflection helpers for fixtures that reset the ConfigManager singleton
+/// between tests.
+///
+internal static class ConfigManagerTestReflection
+{
+ ///
+ /// Resets the ConfigManager singleton's initialization state so the next
+ /// Initialize call reloads settings from its own directory.
+ ///
+ internal static void ResetConfigManagerInitialization (Configuration.ConfigManager configManager)
+ {
+ SetPrivateField(configManager, "_isInitialized", false);
+
+ // Reset settings so they reload from the new path
+ SetPrivateField(configManager, "_settings", null);
+ }
+
+ internal static object? GetPrivateField (Configuration.ConfigManager configManager, string fieldName)
+ {
+ return Field(fieldName).GetValue(configManager);
+ }
+
+ internal static void SetPrivateField (Configuration.ConfigManager configManager, string fieldName, object? value)
+ {
+ Field(fieldName).SetValue(configManager, value);
+ }
+
+ private static FieldInfo Field (string fieldName)
+ {
+ return typeof(Configuration.ConfigManager).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance)
+ ?? throw new InvalidOperationException($"ConfigManager.{fieldName} is gone; update the shared reset helper.");
+ }
+}
diff --git a/src/LogExpert.UI.Tests/Services/MenuToolbarControllerTests.cs b/src/LogExpert.UI.Tests/Services/MenuToolbarControllerTests.cs
index f41d720d..c97bd246 100644
--- a/src/LogExpert.UI.Tests/Services/MenuToolbarControllerTests.cs
+++ b/src/LogExpert.UI.Tests/Services/MenuToolbarControllerTests.cs
@@ -268,8 +268,45 @@ public void PopulateFileHistory_CreatesMenuItems ()
_controller.PopulateFileHistory(history);
var lastUsed = FindMenuItem("lastUsedToolStripMenuItem");
- Assert.That(lastUsed.DropDownItems, Has.Count.EqualTo(2));
+ Assert.That(lastUsed.DropDownItems, Has.Count.EqualTo(4));
Assert.That(lastUsed.DropDownItems[0].Text, Is.EqualTo(@"C:\log1.txt"));
+ Assert.That(lastUsed.DropDownItems[2], Is.TypeOf());
+ Assert.That(lastUsed.DropDownItems[3].Name, Is.EqualTo("clearFileHistoryToolStripMenuItem"));
+ Assert.That(lastUsed.DropDownItems[3].Enabled, Is.True);
+ }
+
+ [Test]
+ public void PopulateFileHistory_DisablesClearItem_WhenHistoryIsEmpty ()
+ {
+ _controller.PopulateFileHistory([]);
+
+ var lastUsed = FindMenuItem("lastUsedToolStripMenuItem");
+ Assert.That(lastUsed.DropDownItems, Has.Count.EqualTo(1));
+ Assert.That(lastUsed.DropDownItems[0].Name, Is.EqualTo("clearFileHistoryToolStripMenuItem"));
+ Assert.That(lastUsed.DropDownItems[0].Enabled, Is.False);
+ }
+
+ [Test]
+ public void ClearHistoryItem_RaisesFileHistoryClearRequested_AndNotFileClicked ()
+ {
+ var clearRequested = 0;
+ string clickedFile = null;
+ _controller.FileHistoryClearRequested += (_, _) => clearRequested++;
+ _controller.HistoryItemClicked += (_, e) => clickedFile = e.FileName;
+
+ _controller.PopulateFileHistory(["test.log"]);
+ var lastUsed = FindMenuItem("lastUsedToolStripMenuItem");
+ var clearItem = lastUsed.DropDownItems[^1];
+
+ var args = new ToolStripItemClickedEventArgs(clearItem);
+ var onItemClicked = typeof(ToolStrip).GetMethod("OnItemClicked", BindingFlags.Instance | BindingFlags.NonPublic);
+ _ = onItemClicked.Invoke(lastUsed.DropDown, [args]);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(clearRequested, Is.EqualTo(1));
+ Assert.That(clickedFile, Is.Null);
+ });
}
[Test]
diff --git a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs
index 5ec992ae..3b1218b3 100644
--- a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs
+++ b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs
@@ -235,7 +235,7 @@ private void OnFileOperationServiceFileOpened (object? sender, FileOpenedEventAr
private void InitializeMenuToolbarControllerEvents ()
{
_menuToolbarController.HistoryItemClicked += OnMenuControllerHistoryItemClicked;
- _menuToolbarController.HistoryItemRemoveRequested += OnMenuControllerHistoryItemRemoveRequested;
+ _menuToolbarController.FileHistoryClearRequested += OnMenuControllerFileHistoryClearRequested;
_menuToolbarController.HighlightGroupSelected += OnMenuControllerHighlightGroupSelected;
}
@@ -244,10 +244,11 @@ private void OnMenuControllerHighlightGroupSelected (object? sender, HighlightGr
CurrentLogWindow?.SetCurrentHighlightGroup(e.GroupName);
}
- private void OnMenuControllerHistoryItemRemoveRequested (object? sender, HistoryItemClickedEventArgs e)
+ private void OnMenuControllerFileHistoryClearRequested (object? sender, EventArgs e)
{
- ConfigManager.RemoveFromFileHistory(e.FileName);
- FillHistoryMenu();
+ // ClearFileHistory persists via Save, whose ConfigChanged event reaches
+ // ApplySettings and rebuilds the history menu from there.
+ ConfigManager.ClearFileHistory();
}
private void OnMenuControllerHistoryItemClicked (object? sender, HistoryItemClickedEventArgs e)
diff --git a/src/LogExpert.UI/Interface/IMenuToolbarController.cs b/src/LogExpert.UI/Interface/IMenuToolbarController.cs
index da3a921a..8bd491d8 100644
--- a/src/LogExpert.UI/Interface/IMenuToolbarController.cs
+++ b/src/LogExpert.UI/Interface/IMenuToolbarController.cs
@@ -45,13 +45,15 @@ void InitializeMenus (MenuStrip mainMenu, ToolStrip buttonToolbar, ToolStrip ext
void UpdateHighlightGroups (IEnumerable groups, string selectedGroup);
///
- /// Populates file history menu with recent files.
- /// Includes right-click removal support.
+ /// Populates the file history menu with recent files, followed by a
+ /// separator and a "clear recently used" row that raises
+ /// when clicked.
+ /// The clear row is disabled when the history is empty.
///
void PopulateFileHistory (IEnumerable fileHistory);
// Events
event EventHandler HistoryItemClicked;
- event EventHandler HistoryItemRemoveRequested;
+ event EventHandler FileHistoryClearRequested;
event EventHandler HighlightGroupSelected;
}
diff --git a/src/LogExpert.UI/Services/MenuToolbarService/MenuToolbarController.cs b/src/LogExpert.UI/Services/MenuToolbarService/MenuToolbarController.cs
index 5e1644f0..55e6a30f 100644
--- a/src/LogExpert.UI/Services/MenuToolbarService/MenuToolbarController.cs
+++ b/src/LogExpert.UI/Services/MenuToolbarService/MenuToolbarController.cs
@@ -49,13 +49,15 @@ internal sealed class MenuToolbarController : IMenuToolbarController
// History menu
private ToolStripMenuItem _lastUsedMenuItem;
+ private ToolStripMenuItem _clearFileHistoryItem;
+
private readonly SynchronizationContext _uiContext;
private readonly int _uiThreadId = Environment.CurrentManagedThreadId;
private bool _disposed;
private bool _suppressEvents;
public event EventHandler HistoryItemClicked;
- public event EventHandler HistoryItemRemoveRequested;
+ public event EventHandler FileHistoryClearRequested;
public event EventHandler HighlightGroupSelected;
public MenuToolbarController ()
@@ -265,7 +267,6 @@ public void PopulateFileHistory (IEnumerable fileHistory)
if (_lastUsedMenuItem.DropDown != null)
{
_lastUsedMenuItem.DropDown.ItemClicked -= OnHistoryMenuItemClicked;
- _lastUsedMenuItem.DropDown.MouseUp -= OnHistoryStripMouseUp;
}
var strip = new ToolStripDropDownMenu();
@@ -275,8 +276,20 @@ public void PopulateFileHistory (IEnumerable fileHistory)
_ = strip.Items.Add(new ToolStripMenuItem(file));
}
+ if (strip.Items.Count > 0)
+ {
+ _ = strip.Items.Add(new ToolStripSeparator());
+ }
+
+ _clearFileHistoryItem = new ToolStripMenuItem(Resources.LogTabWindow_UI_ToolStripMenuItem_clearFileHistoryToolStripMenuItem)
+ {
+ Name = "clearFileHistoryToolStripMenuItem",
+ Enabled = strip.Items.Count > 0
+ };
+
+ _ = strip.Items.Add(_clearFileHistoryItem);
+
strip.ItemClicked += OnHistoryMenuItemClicked;
- strip.MouseUp += OnHistoryStripMouseUp;
_lastUsedMenuItem.DropDown = strip;
}
@@ -302,28 +315,16 @@ private void OnHighlightGroupComboSelectedIndexChanged (object sender, EventArgs
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)
+ if (ReferenceEquals(e.ClickedItem, _clearFileHistoryItem))
{
+ FileHistoryClearRequested?.Invoke(this, EventArgs.Empty);
return;
}
- if (sender is ToolStripDropDownMenu strip)
+ var fileName = e.ClickedItem?.Text;
+ if (!string.IsNullOrEmpty(fileName))
{
- var item = strip.GetItemAt(e.Location);
- if (item != null && !string.IsNullOrEmpty(item.Text))
- {
- HistoryItemRemoveRequested?.Invoke(this, new HistoryItemClickedEventArgs(item.Text));
- }
+ HistoryItemClicked?.Invoke(this, new HistoryItemClickedEventArgs(fileName));
}
}
@@ -415,7 +416,6 @@ public void Dispose ()
if (_lastUsedMenuItem?.DropDown != null)
{
_lastUsedMenuItem.DropDown.ItemClicked -= OnHistoryMenuItemClicked;
- _lastUsedMenuItem.DropDown.MouseUp -= OnHistoryStripMouseUp;
}
_disposed = true;