diff --git a/src/StructuredLogViewer/Controls/BuildControl.xaml.cs b/src/StructuredLogViewer/Controls/BuildControl.xaml.cs index b45149622..49e6a41f8 100644 --- a/src/StructuredLogViewer/Controls/BuildControl.xaml.cs +++ b/src/StructuredLogViewer/Controls/BuildControl.xaml.cs @@ -46,6 +46,8 @@ public partial class BuildControl : UserControl private MenuItem searchThisNode; private MenuItem excludeSubtreeFromSearchItem; private MenuItem excludeNodeByNameFromSearch; + private MenuItem searchInclusiveWithinThisTimespan; // Search with Start OR End time overlaps this duration. + private MenuItem searchExclusiveWithinThisTimespan; // Search with Start AND End time overlaps this duration. private MenuItem goToTimeLineItem; private MenuItem goToTracingItem; private MenuItem copyChildrenItem; @@ -205,6 +207,8 @@ public BuildControl(Build build, string logFilePath) searchInSubtreeItem = new MenuItem() { Header = "Search in subtree" }; excludeSubtreeFromSearchItem = new MenuItem() { Header = "Exclude subtree from search" }; excludeNodeByNameFromSearch = new MenuItem() { Header = "Exclude node from search" }; + searchInclusiveWithinThisTimespan = new MenuItem() { Header = "Search overlapping this duration" }; + searchExclusiveWithinThisTimespan = new MenuItem() { Header = "Search within this duration" }; searchInNodeByNameItem = new MenuItem() { Header = "Search in this node." }; searchThisNode = new MenuItem() { Header = "Search This Node" }; goToTimeLineItem = new MenuItem() { Header = "Timeline" }; @@ -241,6 +245,8 @@ public BuildControl(Build build, string logFilePath) searchInSubtreeItem.Click += (s, a) => SearchInSubtree(); excludeSubtreeFromSearchItem.Click += (s, a) => ExcludeSubtreeFromSearch(); excludeNodeByNameFromSearch.Click += (s, a) => ExcludeNodeByNameFromSearch(); + searchInclusiveWithinThisTimespan.Click += (s, a) => SearchInclusiveWithinThisTimespan(); + searchExclusiveWithinThisTimespan.Click += (s, a) => SearchExclusiveWithinThisTimespan(); searchInNodeByNameItem.Click += (s, a) => SearchInNodeByName(); searchThisNode.Click += (s, a) => SearchThisNode(); goToTimeLineItem.Click += (s, a) => GoToTimeLine(); @@ -281,6 +287,8 @@ public BuildControl(Build build, string logFilePath) searchMenuGroup.AddItem(searchThisNode); searchMenuGroup.AddItem(excludeSubtreeFromSearchItem); searchMenuGroup.AddItem(excludeNodeByNameFromSearch); + searchMenuGroup.AddItem(searchInclusiveWithinThisTimespan); + searchMenuGroup.AddItem(searchExclusiveWithinThisTimespan); contextMenu.AddItem(gotoMenuGroup); gotoMenuGroup.AddItem(goToTimeLineItem); @@ -701,7 +709,7 @@ private void UpdateWatermark() Append [[$time]], [[$start]] and/or [[$end]] to show times and/or durations and sort the results by start time or duration descending (for tasks, targets and projects). -Use start<""2023-11-23 14:30:54.579"", start>, end< or end> to filter events that start or end before or after a given timestamp. Timestamp needs to be in quotes. +Use start<""2023-11-23 14:30:54.579"", start>, end<, or end> to filter events that start or end before or after a given timestamp. Timestamp needs to be in quotes. Use '$copy path' where path is a file or directory to find file copy operations involving the file or directory. `$copy substring` will search for copied files containing the substring. @@ -941,6 +949,8 @@ private void ContextMenu_Opened(object sender, RoutedEventArgs e) goToTimeLineItem.Visibility = Visibility.Visible; goToTracingItem.Visibility = Visibility.Visible; excludeNodeByNameFromSearch.Visibility = hasChildren ? Visibility.Visible : Visibility.Collapsed; + searchInclusiveWithinThisTimespan.Visibility = Visibility.Visible; + searchExclusiveWithinThisTimespan.Visibility = Visibility.Visible; searchInNodeByNameItem.Visibility = hasChildren ? Visibility.Visible : Visibility.Collapsed; if (excludeNodeByNameFromSearch.Visibility == Visibility.Visible) @@ -962,6 +972,8 @@ private void ContextMenu_Opened(object sender, RoutedEventArgs e) goToTimeLineItem.Visibility = Visibility.Collapsed; goToTracingItem.Visibility = Visibility.Collapsed; excludeNodeByNameFromSearch.Visibility = Visibility.Collapsed; + searchInclusiveWithinThisTimespan.Visibility = Visibility.Collapsed; + searchExclusiveWithinThisTimespan.Visibility = Visibility.Collapsed; searchInNodeByNameItem.Visibility = Visibility.Collapsed; if (!hasChildren) { @@ -1985,13 +1997,35 @@ public void ExcludeSubtreeFromSearch() public void ExcludeNodeByNameFromSearch() { - if (treeView.SelectedItem is TimedNode treeNode) + if (treeView.SelectedItem is NamedNode treeNode) { searchLogControl.SearchText += $" notunder(${treeNode.TypeName} {treeNode.Name})"; SelectSearchTab(); } } + public void SearchInclusiveWithinThisTimespan() + { + if (treeView.SelectedItem is TimedNode timedNode) + { + DateTime starTime = timedNode.StartTime; + DateTime endTime = timedNode.EndTime; // add half second to round up + searchLogControl.SearchText += $" start<\"{TextUtilities.Display(endTime, displayDate: true, fullPrecision: true)}\" end>\"{TextUtilities.Display(starTime, displayDate: true, fullPrecision: true)}\" "; + SelectSearchTab(); + } + } + + public void SearchExclusiveWithinThisTimespan() + { + if (treeView.SelectedItem is TimedNode timedNode) + { + DateTime starTime = timedNode.StartTime; + DateTime endTime = timedNode.EndTime; + searchLogControl.SearchText += $" start>\"{TextUtilities.Display(starTime, displayDate: true, fullPrecision: true)}\" end<\"{TextUtilities.Display(endTime, displayDate: true, fullPrecision: true)}\""; + SelectSearchTab(); + } + } + public void GoToTimeLine() { var treeNode = treeView.SelectedItem as TimedNode; diff --git a/src/StructuredLogger/Search/NodeQueryMatcher.cs b/src/StructuredLogger/Search/NodeQueryMatcher.cs index b6a836d9a..89d5f5b4b 100644 --- a/src/StructuredLogger/Search/NodeQueryMatcher.cs +++ b/src/StructuredLogger/Search/NodeQueryMatcher.cs @@ -20,7 +20,7 @@ public Term(string word, bool quotes = false) public static Term Get(string input) { - var trimmed = input.Trim('"'); + var trimmed = TrimQuotes(input); if (trimmed == input) { return new Term(input); @@ -42,6 +42,25 @@ public static Term Get(string input) return default; } + // Trim equal number of quotes from head and tail. + public static string TrimQuotes(string input) + { + int offset = 0; + + while (input.Length - (offset * 2) > 0 + && input[offset] == '\"' && input[input.Length - offset - 1] == '\"') + { + offset++; + } + + if (offset > 0) + { + return input.Substring(offset, input.Length - offset); + } + + return input; + } + public bool IsMatch(string field, HashSet superstrings) { if (Quotes)