From 0de4b1df9e7cdd7806bddfd8c076ed4f159ab2e0 Mon Sep 17 00:00:00 2001 From: Zhenya Date: Tue, 21 Dec 2021 10:25:44 +0300 Subject: [PATCH 01/46] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5b985b1..d864403 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # LogGrokCore [![Gitter](https://badges.gitter.im/LogGrokCore/community.svg)](https://gitter.im/LogGrokCore/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Run Unit tests](https://github.com/pekabon/LogGrokCore/actions/workflows/run-tests.yml/badge.svg)](https://github.com/pekabon/LogGrokCore/actions/workflows/run-tests.yml) -[![Upload Binaries](https://github.com/pekabon/LogGrokCore/actions/workflows/build_upload.yml/badge.svg)](https://github.com/pekabon/LogGrokCore/actions/workflows/build_upload.yml) +[![Run Unit tests](https://github.com/zhenyatnk/LogGrokCore/actions/workflows/run-tests.yml/badge.svg)](https://github.com/zhenyatnk/LogGrokCore/actions/workflows/run-tests.yml) +[![Upload Binaries](https://github.com/zhenyatnk/LogGrokCore/actions/workflows/build_upload.yml/badge.svg)](https://github.com/zhenyatnk/LogGrokCore/actions/workflows/build_upload.yml) From 5199bd422c81bce4a9c4c3db1c6bb31c5d5945b3 Mon Sep 17 00:00:00 2001 From: Zhenya Date: Sat, 25 Dec 2021 12:09:36 +0300 Subject: [PATCH 02/46] =?UTF-8?q?=D0=A1=D0=BA=D0=BE=D1=80=D1=80=D0=B5?= =?UTF-8?q?=D0=BA=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B0=20=D0=BB?= =?UTF-8?q?=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0=20=D0=B2=D1=8B=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B7=D0=B0=D0=BA=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=BE=D0=BA=20(#6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update logic marked toggle --- LogGrokCore.Data/ILineMark.cs | 9 +++ LogGrokCore.Tests/LineMarkMock.cs | 9 +++ LogGrokCore.Tests/ToggleMarksHandlerTests.cs | 79 +++++++++++++++++++ LogGrokCore/BaseLogLineViewModel.cs | 2 +- LogGrokCore/Controls/ListControls/ListView.cs | 9 +-- LogGrokCore/RoutedCommands.cs | 21 +++++ 6 files changed, 121 insertions(+), 8 deletions(-) create mode 100644 LogGrokCore.Data/ILineMark.cs create mode 100644 LogGrokCore.Tests/LineMarkMock.cs create mode 100644 LogGrokCore.Tests/ToggleMarksHandlerTests.cs diff --git a/LogGrokCore.Data/ILineMark.cs b/LogGrokCore.Data/ILineMark.cs new file mode 100644 index 0000000..9766ba7 --- /dev/null +++ b/LogGrokCore.Data/ILineMark.cs @@ -0,0 +1,9 @@ +using System; + +namespace LogGrokCore.Data +{ + public interface ILineMark + { + public bool IsMarked { get; set; } + } +} \ No newline at end of file diff --git a/LogGrokCore.Tests/LineMarkMock.cs b/LogGrokCore.Tests/LineMarkMock.cs new file mode 100644 index 0000000..46f41e6 --- /dev/null +++ b/LogGrokCore.Tests/LineMarkMock.cs @@ -0,0 +1,9 @@ +using System; + +namespace LogGrokCore.Tests +{ + public class LineMarkMock : Data.ILineMark + { + public bool IsMarked { get; set; } + } +} diff --git a/LogGrokCore.Tests/ToggleMarksHandlerTests.cs b/LogGrokCore.Tests/ToggleMarksHandlerTests.cs new file mode 100644 index 0000000..117d78f --- /dev/null +++ b/LogGrokCore.Tests/ToggleMarksHandlerTests.cs @@ -0,0 +1,79 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; + +namespace LogGrokCore.Tests +{ + [TestClass] + public class ToggleMarksHandlerTests + { + [TestMethod] + public void EmptyItems() + { + List items = new List(); + + RoutedCommands.ToggleMarksHandler(items); + } + [TestMethod] + public void OneUnmarked() + { + List items = new List(); + items.Add(new LineMarkMock() { IsMarked = false }); + + RoutedCommands.ToggleMarksHandler(items); + Assert.AreEqual(true, items[0].IsMarked); + } + [TestMethod] + public void TwoUnmarked() + { + List items = new List(); + items.Add(new LineMarkMock() { IsMarked = false }); + items.Add(new LineMarkMock() { IsMarked = false }); + + RoutedCommands.ToggleMarksHandler(items); + Assert.AreEqual(true, items[0].IsMarked); + Assert.AreEqual(true, items[1].IsMarked); + } + [TestMethod] + public void OneMarked() + { + List items = new List(); + items.Add(new LineMarkMock() { IsMarked = true }); + + RoutedCommands.ToggleMarksHandler(items); + Assert.AreEqual(false, items[0].IsMarked); + } + [TestMethod] + public void TwoMarked() + { + List items = new List(); + items.Add(new LineMarkMock() { IsMarked = true }); + items.Add(new LineMarkMock() { IsMarked = true }); + + RoutedCommands.ToggleMarksHandler(items); + Assert.AreEqual(false, items[0].IsMarked); + Assert.AreEqual(false, items[1].IsMarked); + } + [TestMethod] + public void MarkedUnmarked1() + { + List items = new List(); + items.Add(new LineMarkMock() { IsMarked = false }); + items.Add(new LineMarkMock() { IsMarked = true }); + + RoutedCommands.ToggleMarksHandler(items); + Assert.AreEqual(true, items[0].IsMarked); + Assert.AreEqual(true, items[1].IsMarked); + } + [TestMethod] + public void MarkedUnmarked2() + { + List items = new List(); + items.Add(new LineMarkMock() { IsMarked = true }); + items.Add(new LineMarkMock() { IsMarked = false }); + + RoutedCommands.ToggleMarksHandler(items); + Assert.AreEqual(true, items[0].IsMarked); + Assert.AreEqual(true, items[1].IsMarked); + } + } +} \ No newline at end of file diff --git a/LogGrokCore/BaseLogLineViewModel.cs b/LogGrokCore/BaseLogLineViewModel.cs index 6afb330..5501673 100644 --- a/LogGrokCore/BaseLogLineViewModel.cs +++ b/LogGrokCore/BaseLogLineViewModel.cs @@ -2,7 +2,7 @@ namespace LogGrokCore { - public abstract class BaseLogLineViewModel : ItemViewModel + public abstract class BaseLogLineViewModel : ItemViewModel, Data.ILineMark { private readonly Selection _markedLines; diff --git a/LogGrokCore/Controls/ListControls/ListView.cs b/LogGrokCore/Controls/ListControls/ListView.cs index cd6db82..124e0cc 100644 --- a/LogGrokCore/Controls/ListControls/ListView.cs +++ b/LogGrokCore/Controls/ListControls/ListView.cs @@ -46,12 +46,7 @@ public ListView() CommandBindings.Add(new CommandBinding(RoutedCommands.ToggleMarks, (_, args) => { - var items = GetSelectedIndices().Select(i => Items[i]).OfType(); - foreach (var item in items) - { - item.IsMarked = !item.IsMarked; - } - + RoutedCommands.ToggleMarksHandler(GetSelectedIndices().Select(i => Items[i]).OfType()); args.Handled = true; }, (_, args) => @@ -273,4 +268,4 @@ void ResetWidth(System.Windows.Controls.GridView view, VirtualizingStackPanel.Vi return _panel; } } -} \ No newline at end of file +} diff --git a/LogGrokCore/RoutedCommands.cs b/LogGrokCore/RoutedCommands.cs index 6e4a87c..c05f4ac 100644 --- a/LogGrokCore/RoutedCommands.cs +++ b/LogGrokCore/RoutedCommands.cs @@ -1,5 +1,6 @@ using System.Windows; using System.Windows.Input; +using System.Collections.Generic; namespace LogGrokCore { @@ -21,6 +22,26 @@ public static class RoutedCommands "Mark", "Mark lines", typeof(UIElement), new InputGestureCollection { new KeyGesture(Key.Space) }); + public static void ToggleMarksHandler(IEnumerable items) + { + var existsUnMarked = false; + foreach (var item in items) + { + if (!item.IsMarked) + { + item.IsMarked = !item.IsMarked; + existsUnMarked = true; + } + } + if (!existsUnMarked) + { + foreach (var item in items) + { + item.IsMarked = !item.IsMarked; + } + } + } + static RoutedCommands() { CopyToClipboard = new RoutedUICommand( From a3326a9ecbc5bd15e22046663fe61cf9df72cedd Mon Sep 17 00:00:00 2001 From: Zhenya Date: Sat, 25 Dec 2021 12:10:21 +0300 Subject: [PATCH 03/46] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d864403..ae8e52f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # LogGrokCore [![Gitter](https://badges.gitter.im/LogGrokCore/community.svg)](https://gitter.im/LogGrokCore/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Run Unit tests](https://github.com/zhenyatnk/LogGrokCore/actions/workflows/run-tests.yml/badge.svg)](https://github.com/zhenyatnk/LogGrokCore/actions/workflows/run-tests.yml) -[![Upload Binaries](https://github.com/zhenyatnk/LogGrokCore/actions/workflows/build_upload.yml/badge.svg)](https://github.com/zhenyatnk/LogGrokCore/actions/workflows/build_upload.yml) +[![Run Unit tests](https://github.com/pekabon/LogGrokCore/actions/workflows/run-tests.yml/badge.svg)](https://github.com/pekabon/LogGrokCore/actions/workflows/run-tests.yml) +[![Upload Binaries](https://github.com/pekabon/LogGrokCore/actions/workflows/build_upload.yml/badge.svg)](https://github.com/pekabon/LogGrokCore/actions/workflows/build_upload.yml) + From c41b53e136eca4644bad35af27f6f1cb08c07fc3 Mon Sep 17 00:00:00 2001 From: Zhenya Date: Sat, 25 Dec 2021 12:11:07 +0300 Subject: [PATCH 04/46] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index ae8e52f..5b985b1 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,3 @@ [![Gitter](https://badges.gitter.im/LogGrokCore/community.svg)](https://gitter.im/LogGrokCore/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Run Unit tests](https://github.com/pekabon/LogGrokCore/actions/workflows/run-tests.yml/badge.svg)](https://github.com/pekabon/LogGrokCore/actions/workflows/run-tests.yml) [![Upload Binaries](https://github.com/pekabon/LogGrokCore/actions/workflows/build_upload.yml/badge.svg)](https://github.com/pekabon/LogGrokCore/actions/workflows/build_upload.yml) - From 25b8ca7d668ef9b2a7cbf900ff2e389b8b73b102 Mon Sep 17 00:00:00 2001 From: Zhenya Date: Fri, 11 Sep 2026 00:29:25 +0300 Subject: [PATCH 05/46] Add saved searches and copy-as-displayed (#7) * Add saved searches and copy-as-displayed Retarget projects to net10.0. Add a persistent store for saved search patterns with a popup UI to save, apply, filter, edit and delete searches. Split copying into 'Copy' (respects collapsed/folded JSON as displayed) and 'Copy as native' (raw source text) for log and marked-lines views. Add TextModel.GetDisplayedText to render folded ranges and expose ParseResult.ComponentCount. Add tests for transformations, text model folding and inline JSON formatting. * fix pipeline fix pipeline --- .github/workflows/build_upload.yml | 4 +- .github/workflows/run-tests.yml | 4 +- .../LogGrokCore.Data.Tests.csproj | 2 +- .../TransformationPerformerTests.cs | 30 +++ LogGrokCore.Data/LogGrokCore.Data.csproj | 2 +- LogGrokCore.Data/ParseResult.cs | 2 + LogGrokCore.Tests/LogGrokCore.Tests.csproj | 2 +- LogGrokCore.Tests/TextModelTests.cs | 62 ++++++ LogGrokCore.Tests/TextOperationsTests.cs | 17 ++ LogGrokCore/BaseLogLineViewModel.cs | 4 + LogGrokCore/Bootstrap/App.xaml.cs | 2 + .../Controls/ListControls/BaseListView.cs | 38 ++-- .../ListControls/MarkedLinesListView.cs | 33 +++- LogGrokCore/Controls/TextRender/TextView.cs | 40 ++-- LogGrokCore/DocumentContainer.cs | 4 +- LogGrokCore/LineViewModel.cs | 147 +++++++++----- LogGrokCore/LogGrokCore.csproj | 2 +- LogGrokCore/MainWindowViewModel.cs | 5 +- .../MarkedLines/MarkedLineViewModel.cs | 13 ++ .../MarkedLines/MarkedLinesViewModel.cs | 15 -- LogGrokCore/RoutedCommands.cs | 12 +- LogGrokCore/Search/SavedSearchPattern.cs | 92 +++++++++ LogGrokCore/Search/SavedSearchPatternStore.cs | 74 +++++++ LogGrokCore/Search/SearchViewModel.cs | 98 +++++++++- .../Styles/DocumentSearchTemplate.xaml | 181 +++++++++++++++++- LogGrokCore/Styles/LogViewContextMenu.xaml | 15 +- .../Styles/MarkedLinesViewTemplate.xaml | 2 +- LogGrokCore/TextModel.cs | 34 ++++ LogGrokCore/TextOperations.cs | 8 + global.json | 2 +- 30 files changed, 826 insertions(+), 120 deletions(-) create mode 100644 LogGrokCore.Data.Tests/TransformationPerformerTests.cs create mode 100644 LogGrokCore.Tests/TextModelTests.cs create mode 100644 LogGrokCore/Search/SavedSearchPattern.cs create mode 100644 LogGrokCore/Search/SavedSearchPatternStore.cs diff --git a/.github/workflows/build_upload.yml b/.github/workflows/build_upload.yml index 28af591..420b48e 100644 --- a/.github/workflows/build_upload.yml +++ b/.github/workflows/build_upload.yml @@ -12,9 +12,9 @@ jobs: steps: # Install the .NET Core workload - name: Install .NET Core - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 7.0.x + dotnet-version: 10.0.x include-prerelease: true # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 5a8c104..d11bd12 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -25,9 +25,9 @@ jobs: # Install the .NET Core workload - name: Install .NET Core - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 7.0.x + dotnet-version: 10.0.x include-prerelease: true # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild diff --git a/LogGrokCore.Data.Tests/LogGrokCore.Data.Tests.csproj b/LogGrokCore.Data.Tests/LogGrokCore.Data.Tests.csproj index dae742c..971416b 100644 --- a/LogGrokCore.Data.Tests/LogGrokCore.Data.Tests.csproj +++ b/LogGrokCore.Data.Tests/LogGrokCore.Data.Tests.csproj @@ -5,7 +5,7 @@ enable NU1605, CS8604, CS8625, CS8602, CS8618, CS8601, CS8603, CS8614, CS8600, CS8610 false - net7.0-windows + net10.0-windows diff --git a/LogGrokCore.Data.Tests/TransformationPerformerTests.cs b/LogGrokCore.Data.Tests/TransformationPerformerTests.cs new file mode 100644 index 0000000..d4d2b8b --- /dev/null +++ b/LogGrokCore.Data.Tests/TransformationPerformerTests.cs @@ -0,0 +1,30 @@ +using System; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace LogGrokCore.Data.Tests; + +[TestClass] +public class TransformationPerformerTests +{ + [TestMethod] + public void Base64DecodeTransformation() + { + var performer = new TransformationPerformer(new[] { @"(?[A-Za-z0-9+/=]+)" }); + + var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("{\"a\":1}")); + var result = performer.Transform($"###{base64}"); + + Assert.AreEqual("###{\"a\":1}", result); + } + + [TestMethod] + public void DisabledJsonTransformationIsLeftUntouched() + { + var performer = new TransformationPerformer(new[] { @"^before (?\{.*\}) after$" }); + + var result = performer.Transform("before {\"a\":1,\"b\":[1,2]} after"); + + Assert.AreEqual("before {\"a\":1,\"b\":[1,2]} after", result); + } +} diff --git a/LogGrokCore.Data/LogGrokCore.Data.csproj b/LogGrokCore.Data/LogGrokCore.Data.csproj index 8908b51..da39cbc 100644 --- a/LogGrokCore.Data/LogGrokCore.Data.csproj +++ b/LogGrokCore.Data/LogGrokCore.Data.csproj @@ -1,7 +1,7 @@  - net7.0 + net10.0 latest enable NU1605, CS8604, CS8625, CS8602, CS8618, CS8601, CS8603, CS8614, CS8600, CS8610 diff --git a/LogGrokCore.Data/ParseResult.cs b/LogGrokCore.Data/ParseResult.cs index 0511bfb..6c1139a 100644 --- a/LogGrokCore.Data/ParseResult.cs +++ b/LogGrokCore.Data/ParseResult.cs @@ -14,6 +14,8 @@ internal ParseResult(int componentCount, int[] metaPlaceHolder) _componentCount = componentCount; } + public int ComponentCount => _componentCount; + public LineMetaInformation Get() { return new(_metaPlaceHolder.AsSpan(), _componentCount); diff --git a/LogGrokCore.Tests/LogGrokCore.Tests.csproj b/LogGrokCore.Tests/LogGrokCore.Tests.csproj index d752a21..0849c42 100644 --- a/LogGrokCore.Tests/LogGrokCore.Tests.csproj +++ b/LogGrokCore.Tests/LogGrokCore.Tests.csproj @@ -1,7 +1,7 @@ - net7.0-windows + net10.0-windows false diff --git a/LogGrokCore.Tests/TextModelTests.cs b/LogGrokCore.Tests/TextModelTests.cs new file mode 100644 index 0000000..e4358d8 --- /dev/null +++ b/LogGrokCore.Tests/TextModelTests.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace LogGrokCore.Tests +{ + [TestClass] + public class TextModelTests + { + private const string Json = "{\"a\":1,\"b\":[1,2]}"; + + [TestMethod] + public void ExpandedJsonIsIndented() + { + var model = new TextModel(1, Json); + + var displayed = model.GetDisplayedText(new HashSet()); + + StringAssert.Contains(displayed, "\"a\": 1"); + StringAssert.Contains(displayed, "\"b\": ["); + } + + [TestMethod] + public void CollapsedOuterJsonIsInlined() + { + var model = new TextModel(1, Json); + Assert.IsNotNull(model.CollapsibleRanges); + + var root = model.CollapsibleRanges!.OrderByDescending(r => r.length).First(); + + var displayed = model.GetDisplayedText(new HashSet { root.start }); + + StringAssert.Contains(displayed, "\"a\": 1"); + StringAssert.Contains(displayed, "\"b\": [1,2]"); + Assert.IsFalse(displayed.Contains('\n'), $"Expected inline json but got: {displayed}"); + } + + [TestMethod] + public void CollapsedInnerJsonIsInlined() + { + var model = new TextModel(1, Json); + Assert.IsNotNull(model.CollapsibleRanges); + + var inner = model.CollapsibleRanges!.OrderBy(r => r.length).First(); + + var displayed = model.GetDisplayedText(new HashSet { inner.start }); + + StringAssert.Contains(displayed, "\"b\": [1,2]"); + StringAssert.Contains(displayed, "\"a\": 1"); + } + + [TestMethod] + public void NullFoldingStateKeepsExpandedJson() + { + var model = new TextModel(1, Json); + + var displayed = model.GetDisplayedText(null); + + StringAssert.Contains(displayed, "\"a\": 1"); + } + } +} diff --git a/LogGrokCore.Tests/TextOperationsTests.cs b/LogGrokCore.Tests/TextOperationsTests.cs index 7860d25..3904e2e 100644 --- a/LogGrokCore.Tests/TextOperationsTests.cs +++ b/LogGrokCore.Tests/TextOperationsTests.cs @@ -65,5 +65,22 @@ public void TestTrimSomeLines2() Assert.AreEqual("Hello\r\nworld\r\n", resultString); Assert.AreEqual(1, linesTrimmed); } + + [TestMethod] + public void FormatInlineJsonAlignsJson() + { + var result = TextOperations.FormatInlineJson("prefix {\"a\":1,\"b\":[1,2]} suffix"); + + StringAssert.Contains(result, "\"a\": 1"); + StringAssert.Contains(result, "\"b\": ["); + } + + [TestMethod] + public void FormatInlineJsonLeavesNonJsonUntouched() + { + const string source = "no json here"; + + Assert.AreEqual(source, TextOperations.FormatInlineJson(source)); } + } } \ No newline at end of file diff --git a/LogGrokCore/BaseLogLineViewModel.cs b/LogGrokCore/BaseLogLineViewModel.cs index e8042f3..9ee34f7 100644 --- a/LogGrokCore/BaseLogLineViewModel.cs +++ b/LogGrokCore/BaseLogLineViewModel.cs @@ -1,5 +1,6 @@ using System; using LogGrokCore.Controls; +using LogGrokCore.Controls.TextRender; using LogGrokCore.MarkedLines; namespace LogGrokCore @@ -19,6 +20,9 @@ protected BaseLogLineViewModel(int index, Selection markedLines) public int Index { get; } public LinePartViewModel IndexViewModel { get; } + + public virtual string GetDisplayText(TextViewSharedFoldingState? foldingState) => + ToString() ?? string.Empty; public bool IsMarked { diff --git a/LogGrokCore/Bootstrap/App.xaml.cs b/LogGrokCore/Bootstrap/App.xaml.cs index 2811643..143bcda 100644 --- a/LogGrokCore/Bootstrap/App.xaml.cs +++ b/LogGrokCore/Bootstrap/App.xaml.cs @@ -69,6 +69,7 @@ protected override void OnStartup(StartupEventArgs e) protected override void OnExit(ExitEventArgs e) { _container.Resolve().Save(); + _container.Resolve().Save(); _container.Dispose(); } @@ -77,6 +78,7 @@ private void RegisterDependencies(IRegistrator container) container.RegisterDelegate(ApplicationSettings.Instance); container.Register(Reuse.Singleton); container.Register(Reuse.Singleton); + container.Register(Reuse.Singleton); container.Register(); container.Register(); } diff --git a/LogGrokCore/Controls/ListControls/BaseListView.cs b/LogGrokCore/Controls/ListControls/BaseListView.cs index 69c5b40..d472c71 100644 --- a/LogGrokCore/Controls/ListControls/BaseListView.cs +++ b/LogGrokCore/Controls/ListControls/BaseListView.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using System.Windows.Input; +using LogGrokCore.Controls.TextRender; namespace LogGrokCore.Controls.ListControls { @@ -11,20 +12,27 @@ public abstract class BaseListView : System.Windows.Controls.ListView protected BaseListView() { CommandBindings.Add(new CommandBinding(RoutedCommands.CopyToClipboard, - (_, args) => - { - Trace.TraceInformation("CopyToClipboard.Execute"); - CopySelectedItemsToClipboard(); - args.Handled = true; - }, - (_, args) => - { - args.CanExecute = GetSelectedIndices().Any(); - args.Handled = true; - })); + (_, args) => CopyToClipboard("CopyToClipboard", false, args), + CanExecuteCopyToClipboard)); + CommandBindings.Add(new CommandBinding(RoutedCommands.CopyAsDisplayed, + (_, args) => CopyToClipboard("CopyAsDisplayed", true, args), + CanExecuteCopyToClipboard)); + } + + private void CopyToClipboard(string commandName, bool asDisplayed, ExecutedRoutedEventArgs args) + { + Trace.TraceInformation($"{commandName}.Execute"); + CopySelectedItemsToClipboard(asDisplayed); + args.Handled = true; + } + + private void CanExecuteCopyToClipboard(object _, CanExecuteRoutedEventArgs args) + { + args.CanExecute = GetSelectedIndices().Any(); + args.Handled = true; } - private void CopySelectedItemsToClipboard() + private void CopySelectedItemsToClipboard(bool asDisplayed) { var indices = GetSelectedIndices(); @@ -33,10 +41,14 @@ private void CopySelectedItemsToClipboard() .OrderBy(i => i) .Select(i => Items[i]); + var foldingState = asDisplayed ? TextView.GetSharedFoldingState(this) : null; var text = new StringBuilder(); foreach (var line in items) { - _ = text.Append(line.ToString()?.TrimEnd()); + var lineText = asDisplayed && line is BaseLogLineViewModel viewModel + ? viewModel.GetDisplayText(foldingState) + : line.ToString(); + _ = text.Append(lineText?.TrimEnd()); _ = text.Append("\r\n"); } _ = text.Replace("\0", string.Empty); diff --git a/LogGrokCore/Controls/ListControls/MarkedLinesListView.cs b/LogGrokCore/Controls/ListControls/MarkedLinesListView.cs index 20bd0f9..ce85c20 100644 --- a/LogGrokCore/Controls/ListControls/MarkedLinesListView.cs +++ b/LogGrokCore/Controls/ListControls/MarkedLinesListView.cs @@ -1,8 +1,11 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Windows; using System.Windows.Controls; +using LogGrokCore.Controls.TextRender; +using LogGrokCore.MarkedLines; namespace LogGrokCore.Controls.ListControls { @@ -18,6 +21,34 @@ public IEnumerable ReadonlySelectedItems set => SetValue(ReadonlySelectedItemsProperty, value); } + public DelegateCommand CopyDocumentLinesCommand { get; } + + public MarkedLinesListView() + { + CopyDocumentLinesCommand = new DelegateCommand(CopyDocumentLines); + } + + private void CopyDocumentLines(object parameter) + { + if (parameter is not DocumentViewModel document) + return; + + var foldingState = TextView.GetSharedFoldingState(this); + var text = new StringBuilder(); + text.Append(document.Title); + text.Append(':'); + + foreach (var line in Items.OfType() + .Where(m => m.Document == document) + .OrderBy(m => m.Index)) + { + text.Append("\r\n"); + text.Append(line.GetDisplayText(foldingState).Replace("\0", string.Empty).TrimEnd()); + } + + TextCopy.ClipboardService.SetText(text.ToString()); + } + protected override void OnSelectionChanged(SelectionChangedEventArgs e) { ReadonlySelectedItems = SelectedItems; @@ -39,4 +70,4 @@ protected override IEnumerable GetSelectedIndices() } } } -} \ No newline at end of file +} diff --git a/LogGrokCore/Controls/TextRender/TextView.cs b/LogGrokCore/Controls/TextRender/TextView.cs index efca0d7..1c07166 100644 --- a/LogGrokCore/Controls/TextRender/TextView.cs +++ b/LogGrokCore/Controls/TextRender/TextView.cs @@ -256,22 +256,30 @@ static TextView() FrameworkPropertyMetadataOptions.AffectsRender, static (d, _) => (d as TextView)?._textControl.InvalidateVisual())); - CommandManager.RegisterClassCommandBinding(typeof(TextView), - new CommandBinding( - RoutedCommands.CopyToClipboard, - (sender, args) => - { - var text = ((TextView)sender).SelectedText; - TextCopy.ClipboardService.SetText(text); - args.Handled = true; - }, - (sender, args) => - { - if (sender is not TextView selectableTextBlock) return; - var haveSelectedText = !string.IsNullOrEmpty(selectableTextBlock.SelectedText); - args.CanExecute = haveSelectedText; - args.Handled = haveSelectedText; - })); + void CopySelectedTextHandler(object sender, ExecutedRoutedEventArgs args) + { + var text = ((TextView)sender).SelectedText; + TextCopy.ClipboardService.SetText(text); + args.Handled = true; + } + + void CanCopySelectedTextHandler(object sender, CanExecuteRoutedEventArgs args) + { + if (sender is not TextView selectableTextBlock) return; + var haveSelectedText = !string.IsNullOrEmpty(selectableTextBlock.SelectedText); + args.CanExecute = haveSelectedText; + args.Handled = haveSelectedText; + } + + foreach (var command in new RoutedCommand[] + { + RoutedCommands.CopyToClipboard, + RoutedCommands.CopyAsDisplayed + }) + { + CommandManager.RegisterClassCommandBinding(typeof(TextView), + new CommandBinding(command, CopySelectedTextHandler, CanCopySelectedTextHandler)); + } } protected override int VisualChildrenCount => _children?.Count ?? 0; diff --git a/LogGrokCore/DocumentContainer.cs b/LogGrokCore/DocumentContainer.cs index acf39a2..550338b 100644 --- a/LogGrokCore/DocumentContainer.cs +++ b/LogGrokCore/DocumentContainer.cs @@ -28,7 +28,8 @@ private enum GridViewType } private readonly Container _container; - public DocumentContainer(string fileName, ApplicationSettings applicationSettings, SearchAutocompleteCache autocompleteCache) + public DocumentContainer(string fileName, ApplicationSettings applicationSettings, + SearchAutocompleteCache autocompleteCache, SavedSearchPatternStore savedSearchPatternStore) { _container = new Container(rules => rules @@ -39,6 +40,7 @@ public DocumentContainer(string fileName, ApplicationSettings applicationSetting LoggerRegistrationHelper.Register(_container); _container.RegisterInstance(autocompleteCache); + _container.RegisterInstance(savedSearchPatternStore); _container.Register(Reuse.Singleton); _container.Register( made: Parameters.Of.Type(serviceKey: ParserType.Full)); diff --git a/LogGrokCore/LineViewModel.cs b/LogGrokCore/LineViewModel.cs index 9523e06..a6217b3 100644 --- a/LogGrokCore/LineViewModel.cs +++ b/LogGrokCore/LineViewModel.cs @@ -1,53 +1,94 @@ -using System; -using LogGrokCore.Controls; -using LogGrokCore.Data; - -namespace LogGrokCore -{ - public class LineViewModel : BaseLogLineViewModel - { - private readonly string _sourceString; - - private readonly ParseResult _parseResult; - private readonly string _transformResult; - - public LineViewModel(int index, string sourceString, ILineParser parser, Selection markedLines, - TransformationPerformer transformationPerformer) - : base(index, markedLines) - { - _sourceString = sourceString; - _transformResult = transformationPerformer.Transform(sourceString); - _parseResult = parser.Parse( - _transformResult); - } - - public LinePartViewModel this[int index] => GetValue(index); - - private LinePartViewModel GetValue(int index) - { - var uniqueId = HashCode.Combine(base.Index, index); - var lineMeta = _parseResult.Get().ParsedLineComponents; - var text = _transformResult.Substring(lineMeta.ComponentStart(index), - lineMeta.ComponentLength(index)); - - return new LinePartViewModel(uniqueId, text); - } - - public override bool Equals(object? o) - { - if (o is LineViewModel other) - return other.Index == Index && other._sourceString == _sourceString; - return false; - } - - public override int GetHashCode() - { - return HashCode.Combine(Index, _sourceString); - } - - public override string ToString() - { - return _transformResult.TrimEnd(); - } - } -} \ No newline at end of file +using System; +using System.Text; +using LogGrokCore.Controls; +using LogGrokCore.Controls.TextRender; +using LogGrokCore.Data; + +namespace LogGrokCore +{ + public class LineViewModel : BaseLogLineViewModel + { + private readonly string _sourceString; + + private readonly ParseResult _parseResult; + private readonly string _transformResult; + + public LineViewModel(int index, string sourceString, ILineParser parser, Selection markedLines, + TransformationPerformer transformationPerformer) + : base(index, markedLines) + { + _sourceString = sourceString; + _transformResult = transformationPerformer.Transform(sourceString); + _parseResult = parser.Parse( + _transformResult); + } + + public LinePartViewModel this[int index] => GetValue(index); + + private LinePartViewModel GetValue(int index) + { + var uniqueId = HashCode.Combine(base.Index, index); + var lineMeta = _parseResult.Get().ParsedLineComponents; + var text = _transformResult.Substring(lineMeta.ComponentStart(index), + lineMeta.ComponentLength(index)); + + return new LinePartViewModel(uniqueId, text); + } + + public override bool Equals(object? o) + { + if (o is LineViewModel other) + return other.Index == Index && other._sourceString == _sourceString; + return false; + } + + public override int GetHashCode() + { + return HashCode.Combine(Index, _sourceString); + } + + public override string ToString() + { + return _transformResult.TrimEnd(); + } + + public override string GetDisplayText(TextViewSharedFoldingState? foldingState) + { + var lineMeta = _parseResult.Get().ParsedLineComponents; + var componentCount = _parseResult.ComponentCount; + var builder = new StringBuilder(); + var offset = 0; + + for (var i = 0; i < componentCount; i++) + { + var start = lineMeta.ComponentStart(i); + var length = lineMeta.ComponentLength(i); + + if (start < offset || start + length > _transformResult.Length) + continue; + + builder.Append(_transformResult, offset, start - offset); + var componentText = _transformResult.Substring(start, length); + builder.Append(GetComponentDisplayText(i, componentText, foldingState)); + offset = start + length; + } + + builder.Append(_transformResult, offset, _transformResult.Length - offset); + return builder.ToString().TrimEnd(); + } + + private string GetComponentDisplayText(int componentIndex, string componentText, + TextViewSharedFoldingState? foldingState) + { + var textModel = new TextModel(HashCode.Combine(Index, componentIndex), componentText); + + if (foldingState == null || textModel.CollapsibleRanges == null) + return textModel.GetDisplayedText(null); + + var collapsedLines = foldingState[textModel.UniqueId] + ?? foldingState.GetDefaultSettings(textModel.CollapsibleRanges, textModel.Count); + + return textModel.GetDisplayedText(collapsedLines); + } + } +} diff --git a/LogGrokCore/LogGrokCore.csproj b/LogGrokCore/LogGrokCore.csproj index fecf1a9..5ef2c0e 100644 --- a/LogGrokCore/LogGrokCore.csproj +++ b/LogGrokCore/LogGrokCore.csproj @@ -2,7 +2,7 @@ WinExe - net7.0-windows + net10.0-windows true latest enable diff --git a/LogGrokCore/MainWindowViewModel.cs b/LogGrokCore/MainWindowViewModel.cs index 29b27a7..043823f 100644 --- a/LogGrokCore/MainWindowViewModel.cs +++ b/LogGrokCore/MainWindowViewModel.cs @@ -20,6 +20,7 @@ public class MainWindowViewModel : ViewModelBase, IContentProvider, IDisposable private DocumentViewModel? _currentDocument; private readonly ApplicationSettings _applicationSettings; private readonly SearchAutocompleteCache _searchAutocompleteCache; + private readonly SavedSearchPatternStore _savedSearchPatternStore; public ObservableCollection Documents { get; } @@ -38,10 +39,12 @@ public class MainWindowViewModel : ViewModelBase, IContentProvider, IDisposable public MainWindowViewModel(ApplicationSettings applicationSettings, SearchAutocompleteCache searchAutocompleteCache, + SavedSearchPatternStore savedSearchPatternStore, Func, MarkedLinesViewModel> markedLinesViewModelFactory) { _applicationSettings = applicationSettings; _searchAutocompleteCache = searchAutocompleteCache; + _savedSearchPatternStore = savedSearchPatternStore; Documents = new ObservableCollection(); MarkedLinesViewModel = markedLinesViewModelFactory(Documents); OpenSettings = new DelegateCommand(() => @@ -149,7 +152,7 @@ public void AddDocument(string fileName) private DocumentViewModel CreateDocument(string fileName) { - var container = new DocumentContainer(fileName, _applicationSettings, _searchAutocompleteCache); + var container = new DocumentContainer(fileName, _applicationSettings, _searchAutocompleteCache, _savedSearchPatternStore); var viewModel = container.GetDocumentViewModel(); Documents.Add(viewModel); Documents.CollectionChanged += (o, e) => diff --git a/LogGrokCore/MarkedLines/MarkedLineViewModel.cs b/LogGrokCore/MarkedLines/MarkedLineViewModel.cs index 66b5181..3971372 100644 --- a/LogGrokCore/MarkedLines/MarkedLineViewModel.cs +++ b/LogGrokCore/MarkedLines/MarkedLineViewModel.cs @@ -1,5 +1,6 @@ using System; using LogGrokCore.Colors; +using LogGrokCore.Controls.TextRender; namespace LogGrokCore.MarkedLines { @@ -20,5 +21,17 @@ public MarkedLineViewModel(DocumentViewModel document, int lineNumber, string te } public override string ToString() => Text.OriginalText ?? string.Empty; + + public override string GetDisplayText(TextViewSharedFoldingState? foldingState) + { + var textModel = Text.TextModel; + if (foldingState == null || textModel.CollapsibleRanges == null) + return textModel.GetDisplayedText(null); + + var collapsedLines = foldingState[textModel.UniqueId] + ?? foldingState.GetDefaultSettings(textModel.CollapsibleRanges, textModel.Count); + + return textModel.GetDisplayedText(collapsedLines); + } } } \ No newline at end of file diff --git a/LogGrokCore/MarkedLines/MarkedLinesViewModel.cs b/LogGrokCore/MarkedLines/MarkedLinesViewModel.cs index 35efa4b..e7af734 100644 --- a/LogGrokCore/MarkedLines/MarkedLinesViewModel.cs +++ b/LogGrokCore/MarkedLines/MarkedLinesViewModel.cs @@ -17,8 +17,6 @@ public class MarkedLinesViewModel : ViewModelBase public ObservableCollection MarkedLines => _markedLines; - public DelegateCommand CopyLinesCommand { get; } - public DelegateCommand ItemActivatedCommand { get; } public bool HaveMarkedLines => _markedLines.Count != 0; @@ -40,19 +38,6 @@ public MarkedLinesViewModel(ObservableCollection documents) UpdateLinesCollection(); }; - CopyLinesCommand = new DelegateCommand( - o => - { - var document = (DocumentViewModel) o; - var linesToCopy = - $"{document.Title}:".Yield().Concat( - MarkedLines.Where(m => m.Document == document) - .OrderBy(m => m.Index) - .Select(m => m.ToString().Trim('\0').TrimEnd())); - - TextCopy.ClipboardService.SetText(string.Join("\r\n", linesToCopy)); - }); - ItemActivatedCommand = new DelegateCommand( o => { diff --git a/LogGrokCore/RoutedCommands.cs b/LogGrokCore/RoutedCommands.cs index d53cf81..da7cba8 100644 --- a/LogGrokCore/RoutedCommands.cs +++ b/LogGrokCore/RoutedCommands.cs @@ -17,7 +17,12 @@ public static class RoutedCommands public static readonly RoutedCommand ClearFilters = new RoutedUICommand( "Clear filters", "Clear filters", typeof(UIElement)); - public static readonly RoutedUICommand CopyToClipboard; + public static readonly RoutedUICommand CopyToClipboard = new RoutedUICommand( + "Copy as native", nameof(CopyToClipboard), typeof(UIElement)); + + public static readonly RoutedUICommand CopyAsDisplayed = new RoutedUICommand( + "Copy", nameof(CopyAsDisplayed), typeof(UIElement), + new InputGestureCollection(ApplicationCommands.Copy.InputGestures)); public static readonly RoutedCommand ToggleMarks = new RoutedUICommand( "Mark", "Mark lines", typeof(UIElement), @@ -45,11 +50,6 @@ public static void ToggleMarksHandler(IEnumerable items) static RoutedCommands() { - CopyToClipboard = new RoutedUICommand( - ApplicationCommands.Copy.Text, - nameof(CopyToClipboard), - typeof(UIElement), new InputGestureCollection(ApplicationCommands.Copy.InputGestures)); - ApplicationCommands.Copy.InputGestures.Clear(); } } diff --git a/LogGrokCore/Search/SavedSearchPattern.cs b/LogGrokCore/Search/SavedSearchPattern.cs new file mode 100644 index 0000000..64da501 --- /dev/null +++ b/LogGrokCore/Search/SavedSearchPattern.cs @@ -0,0 +1,92 @@ +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Text.Json.Serialization; + +namespace LogGrokCore.Search +{ + public class SavedSearchPattern : INotifyPropertyChanged + { + private string _name = string.Empty; + private string _pattern = string.Empty; + + public string Name + { + get => _name; + set + { + if (_name == value) return; + _name = value; + InvokePropertyChanged(); + } + } + + public string Pattern + { + get => _pattern; + set + { + if (_pattern == value) return; + _pattern = value; + InvokePropertyChanged(); + } + } + + public bool IsCaseSensitive { get; set; } + public bool UseRegex { get; set; } + + [JsonIgnore] + public bool IsEditing { get; private set; } + + [JsonIgnore] + public string EditName { get; set; } = string.Empty; + + [JsonIgnore] + public string EditPattern { get; set; } = string.Empty; + + public event PropertyChangedEventHandler? PropertyChanged; + + public SearchPattern ToSearchPattern() => new(Pattern, IsCaseSensitive, UseRegex); + + public static SavedSearchPattern FromSearchPattern(string name, in SearchPattern searchPattern) => + new() + { + Name = name, + Pattern = searchPattern.Pattern, + IsCaseSensitive = searchPattern.IsCaseSensitive, + UseRegex = searchPattern.UseRegex + }; + + public bool MatchesFilter(string filter) => + string.IsNullOrWhiteSpace(filter) || + Name.Contains(filter, StringComparison.OrdinalIgnoreCase) || + Pattern.Contains(filter, StringComparison.OrdinalIgnoreCase); + + public void BeginEdit() + { + EditName = Name; + EditPattern = Pattern; + IsEditing = true; + InvokePropertyChanged(nameof(IsEditing)); + InvokePropertyChanged(nameof(EditName)); + InvokePropertyChanged(nameof(EditPattern)); + } + + public void CancelEdit() + { + IsEditing = false; + InvokePropertyChanged(nameof(IsEditing)); + } + + public void CommitEdit() + { + Name = string.IsNullOrWhiteSpace(EditName) ? EditPattern : EditName.Trim(); + Pattern = EditPattern; + IsEditing = false; + InvokePropertyChanged(nameof(IsEditing)); + } + + private void InvokePropertyChanged([CallerMemberName] string? propertyName = null) => + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/LogGrokCore/Search/SavedSearchPatternStore.cs b/LogGrokCore/Search/SavedSearchPatternStore.cs new file mode 100644 index 0000000..3597e0b --- /dev/null +++ b/LogGrokCore/Search/SavedSearchPatternStore.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text.Json; + +namespace LogGrokCore.Search +{ + public class SavedSearchPatternStore + { + private readonly string _storeFileName = + HomeDirectoryPathProvider.GetDataFileFullPath("SavedSearches.json"); + + public ObservableCollection Items { get; } + + public SavedSearchPatternStore() + { + Items = Load(); + } + + public void Save() + { + try + { + using var createStream = File.Create(_storeFileName); + var options = new JsonSerializerOptions { WriteIndented = true }; + JsonSerializer.Serialize(createStream, Items, options); + } + catch (Exception) + { + // ignored + } + } + + public void AddOrUpdate(string name, in SearchPattern searchPattern) + { + var existing = Items.FirstOrDefault( + item => string.Equals(item.Name, name, StringComparison.OrdinalIgnoreCase)); + + if (existing != null) + Items.Remove(existing); + + Items.Insert(0, SavedSearchPattern.FromSearchPattern(name, searchPattern)); + Save(); + } + + public void Remove(SavedSearchPattern savedSearchPattern) + { + if (!Items.Remove(savedSearchPattern)) return; + Save(); + } + + private ObservableCollection Load() + { + var result = new ObservableCollection(); + try + { + if (!File.Exists(_storeFileName)) return result; + using var stream = File.OpenRead(_storeFileName); + var items = JsonSerializer.Deserialize>(stream); + if (items == null) return result; + + foreach (var item in items) + result.Add(item); + } + catch (Exception) + { + // ignored + } + + return result; + } + } +} diff --git a/LogGrokCore/Search/SearchViewModel.cs b/LogGrokCore/Search/SearchViewModel.cs index dbc9bbd..9451423 100644 --- a/LogGrokCore/Search/SearchViewModel.cs +++ b/LogGrokCore/Search/SearchViewModel.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; +using System.Linq; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; +using System.Windows.Data; using System.Windows.Input; using System.Windows.Threading; using LogGrokCore.Controls; @@ -28,9 +30,13 @@ public class SearchViewModel : ViewModelBase, IDataErrorInfo private SearchDocumentViewModel? _currentDocument; private readonly SearchAutocompleteCache _searchAutocompleteCache; + private readonly SavedSearchPatternStore _savedSearchPatternStore; + private string _savedSearchFilter = string.Empty; + private string _newSearchName = string.Empty; public SearchViewModel(Func searchDocumentViewModelFactory, - SearchAutocompleteCache searchAutocompleteCache) + SearchAutocompleteCache searchAutocompleteCache, + SavedSearchPatternStore savedSearchPatternStore) { _searchDocumentViewModelFactory = pattern => @@ -45,9 +51,17 @@ public SearchViewModel(Func searchDocume AddNewSearchCommand = new DelegateCommand(() => AddNewSearch(_searchPattern.Clone())); SearchTextCommand = new DelegateCommand(SearchText, text => !string.IsNullOrEmpty(text as string)); Activate = new DelegateCommand(() => SetFocusRequest.Invoke()); + SaveSearchCommand = new DelegateCommand(SaveCurrentSearch); + ApplySavedSearchCommand = DelegateCommand.Create(ApplySavedSearch); + BeginEditSavedSearchCommand = DelegateCommand.Create(BeginEditSavedSearch); + CommitEditSavedSearchCommand = DelegateCommand.Create(CommitEditSavedSearch); + CancelEditSavedSearchCommand = DelegateCommand.Create(CancelEditSavedSearch); + DeleteSavedSearchCommand = DelegateCommand.Create(DeleteSavedSearch); Documents = new ObservableCollection(); _searchAutocompleteCache = searchAutocompleteCache; + _savedSearchPatternStore = savedSearchPatternStore; + SavedSearches = new ListCollectionView(_savedSearchPatternStore.Items); } public event Action? CurrentLineChanged; @@ -108,6 +122,29 @@ public bool UseRegex public IEnumerable AutoCompleteList => _searchAutocompleteCache.Items; + public ListCollectionView SavedSearches { get; } + + public string SavedSearchFilter + { + get => _savedSearchFilter; + set + { + if (_savedSearchFilter == value) return; + _savedSearchFilter = value; + InvokePropertyChanged(); + SavedSearches.Filter = string.IsNullOrWhiteSpace(value) + ? (Predicate?) null + : o => o is SavedSearchPattern pattern && pattern.MatchesFilter(value); + SavedSearches.Refresh(); + } + } + + public string NewSearchName + { + get => _newSearchName; + set => SetAndRaiseIfChanged(ref _newSearchName, value); + } + public string this[string columnName] => columnName switch { @@ -126,6 +163,65 @@ public bool UseRegex public ICommand AddNewSearchCommand { get; } + public ICommand SaveSearchCommand { get; } + + public ICommand ApplySavedSearchCommand { get; } + + public ICommand BeginEditSavedSearchCommand { get; } + + public ICommand CommitEditSavedSearchCommand { get; } + + public ICommand CancelEditSavedSearchCommand { get; } + + public ICommand DeleteSavedSearchCommand { get; } + + private void SaveCurrentSearch() + { + if (_searchPattern.IsEmpty) return; + + var name = string.IsNullOrWhiteSpace(NewSearchName) + ? _searchPattern.Pattern + : NewSearchName.Trim(); + + _savedSearchPatternStore.AddOrUpdate(name, _searchPattern); + NewSearchName = string.Empty; + } + + private void ApplySavedSearch(SavedSearchPattern savedSearchPattern) + { + var searchPattern = savedSearchPattern.ToSearchPattern(); + if (!searchPattern.IsValid) return; + + _textToSearch = searchPattern.Pattern; + _isCaseSensitive = searchPattern.IsCaseSensitive; + _useRegex = searchPattern.UseRegex; + + InvokePropertyChanged(nameof(TextToSearch)); + InvokePropertyChanged(nameof(IsCaseSensitive)); + InvokePropertyChanged(nameof(UseRegex)); + + CommitSearchPatternImmediately(_textToSearch, _isCaseSensitive, _useRegex); + } + + private void BeginEditSavedSearch(SavedSearchPattern savedSearchPattern) => + savedSearchPattern.BeginEdit(); + + private void CommitEditSavedSearch(SavedSearchPattern savedSearchPattern) + { + var editedPattern = new SearchPattern(savedSearchPattern.EditPattern, + savedSearchPattern.IsCaseSensitive, savedSearchPattern.UseRegex); + if (!editedPattern.IsValid) return; + + savedSearchPattern.CommitEdit(); + _savedSearchPatternStore.Save(); + } + + private void CancelEditSavedSearch(SavedSearchPattern savedSearchPattern) => + savedSearchPattern.CancelEdit(); + + private void DeleteSavedSearch(SavedSearchPattern savedSearchPattern) => + _savedSearchPatternStore.Remove(savedSearchPattern); + private void CommitSearchPattern(ref T field, T newValue, TimeSpan timeSpan, [CallerMemberName] string? propertyName = null) { if (Equals(field, newValue)) return; diff --git a/LogGrokCore/Styles/DocumentSearchTemplate.xaml b/LogGrokCore/Styles/DocumentSearchTemplate.xaml index 9af7302..2312992 100644 --- a/LogGrokCore/Styles/DocumentSearchTemplate.xaml +++ b/LogGrokCore/Styles/DocumentSearchTemplate.xaml @@ -8,6 +8,7 @@ xmlns:listControls="clr-namespace:LogGrokCore.Controls.ListControls" xmlns:virtualizingStackPanel="clr-namespace:LogGrokCore.Controls.ListControls.VirtualizingStackPanel" xmlns:behaviors="clr-namespace:WPFTextBoxAutoComplete;assembly=WPFTextBoxAutoComplete" + xmlns:filterPopup="clr-namespace:LogGrokCore.Controls.FilterPopup" xmlns:textRender="clr-namespace:LogGrokCore.Controls.TextRender" xmlns:avalonDockExtensions="clr-namespace:LogGrokCore.AvalonDockExtensions" mc:Ignorable="d"> @@ -74,8 +75,8 @@ - + @@ -113,7 +114,7 @@ Text="(.*)" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +