diff --git a/dnGREP.Common/GrepApplicationSettings.cs b/dnGREP.Common/GrepApplicationSettings.cs index 3ec2cc00..a441bb98 100644 --- a/dnGREP.Common/GrepApplicationSettings.cs +++ b/dnGREP.Common/GrepApplicationSettings.cs @@ -90,6 +90,12 @@ public static class Key public const string PreviewWindowWrap = "PreviewWindowWrap"; public const string PreviewWindowSize = "PreviewWindowSize"; public const string PreviewWindowPosition = "PreviewWindowPosition"; + [DefaultValue(20)] + public const string MaxPathBookmarks = "MaxPathBookmarks"; + [DefaultValue(20)] + public const string MaxSearchBookmarks = "MaxSearchBookmarks"; + [DefaultValue(10)] + public const string MaxExtensionBookmarks = "MaxExtensionBookmarks"; } private static GrepSettings instance; diff --git a/dnGREP.Common/Utils.cs b/dnGREP.Common/Utils.cs index 92961bbe..a4aef34e 100644 --- a/dnGREP.Common/Utils.cs +++ b/dnGREP.Common/Utils.cs @@ -449,7 +449,7 @@ public static string[] SplitPath(string path) sb.Append(splitterIndex + subSplitterIndex < path.Length ? path[splitterIndex + subSplitterIndex].ToString() : ""); } if (!found && !string.IsNullOrWhiteSpace(paths[i])) - output.Add(paths[i]); + output.Add(paths[i].TrimStart()); } } return output.ToArray(); diff --git a/dnGREP.Engines/GrepEngineBase.cs b/dnGREP.Engines/GrepEngineBase.cs index 5eb9896e..7d74f64e 100644 --- a/dnGREP.Engines/GrepEngineBase.cs +++ b/dnGREP.Engines/GrepEngineBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Xml; @@ -20,6 +21,7 @@ public class GrepEngineBase protected int linesAfter = 0; protected double fuzzyMatchThreshold = 0.5; private GoogleMatch fuzzyMatchEngine = new GoogleMatch(); + private bool verboseMatchCount; public GrepEngineBase() { } @@ -42,6 +44,7 @@ public virtual bool Initialize(GrepEngineInitParams param) this.linesAfter = 0; } this.fuzzyMatchThreshold = param.FuzzyMatchThreshold; + verboseMatchCount = GrepSettings.Instance.Get(GrepSettings.Key.ShowVerboseMatchCount); return true; } @@ -76,7 +79,8 @@ public virtual void OpenFile(OpenFileArgs args) continue; } - globalMatches.Add(new GrepSearchResult.GrepMatch(0, matchLocation + counter, matchLength)); + int lineNumber = verboseMatchCount ? text.Take(matchLocation + counter).Count(c => c == '\n' || c == '\r') + 1 : 0; + globalMatches.Add(new GrepSearchResult.GrepMatch(lineNumber, matchLocation + counter, matchLength)); counter = counter + matchLocation + matchLength; } @@ -273,7 +277,7 @@ public GrepSearchResult.GrepMatch[] getFilePositions(string text, List) @@ -347,7 +351,8 @@ private bool xPathPositionsMatch(List currPos, List positions) List globalMatches = new List(); foreach (Match match in Regex.Matches(text, searchPattern, regexOptions)) { - globalMatches.Add(new GrepSearchResult.GrepMatch(0, match.Index, match.Length)); + int lineNumber = verboseMatchCount ? text.Take(match.Index).Count(c => c == '\n' || c == '\r') + 1 : 0; + globalMatches.Add(new GrepSearchResult.GrepMatch(lineNumber, match.Index, match.Length)); } return globalMatches; @@ -382,7 +387,8 @@ protected string doPatternReplacement(string replaceText) continue; } - globalMatches.Add(new GrepSearchResult.GrepMatch(0, index, searchText.Length)); + int lineNumber = verboseMatchCount? text.Take(index).Count(c => c == '\n' || c == '\r') + 1 : 0; + globalMatches.Add(new GrepSearchResult.GrepMatch(lineNumber, index, searchText.Length)); index++; } } @@ -408,7 +414,8 @@ protected string doPatternReplacement(string replaceText) continue; } - globalMatches.Add(new GrepSearchResult.GrepMatch(0, index, searchText.Length)); + int lineNumber = verboseMatchCount ? text.Take(index).Count(c => c == '\n' || c == '\r') + 1 : 0; + globalMatches.Add(new GrepSearchResult.GrepMatch(lineNumber, index, searchText.Length)); index++; } } diff --git a/dnGREP.Engines/GrepEnginePlainText.cs b/dnGREP.Engines/GrepEnginePlainText.cs index 136cad09..378daecb 100644 --- a/dnGREP.Engines/GrepEnginePlainText.cs +++ b/dnGREP.Engines/GrepEnginePlainText.cs @@ -189,6 +189,31 @@ private bool replace(Stream inputStream, Stream outputStream, string searchPatte string line = null; int counter = 1; + // use first line to determine eol character(s); + line = readStream.ReadLine(true); + if (line != null) + { + if (line.EndsWith("\r\n")) + { + writeStream.NewLine = "\r\n"; + line = line.Substring(0, line.Length - 2); + } + else if (line.EndsWith("\n")) + { + writeStream.NewLine = "\n"; + line = line.Substring(0, line.Length - 1); + } + else if (line.EndsWith("\r")) + { + writeStream.NewLine = "\r"; + line = line.Substring(0, line.Length - 1); + } + + line = replaceMethod(line, searchPattern, replacePattern, searchOptions); + writeStream.WriteLine(line); + counter++; + } + while ((line = readStream.ReadLine()) != null) { line = replaceMethod(line, searchPattern, replacePattern, searchOptions); diff --git a/dnGREP.WPF/Dictionaries/Styles.xaml b/dnGREP.WPF/Dictionaries/Styles.xaml index 2870db3a..e63e111e 100644 --- a/dnGREP.WPF/Dictionaries/Styles.xaml +++ b/dnGREP.WPF/Dictionaries/Styles.xaml @@ -1,10 +1,5 @@  - - @@ -25,7 +20,7 @@ - - + @@ -73,6 +76,9 @@ + + + @@ -83,6 +89,7 @@ + @@ -101,7 +108,6 @@ Event="RequestBringIntoView" Handler="tvSearchResult_RequestBringIntoView"/> - diff --git a/dnGREP.WPF/UserControls/ResultsTree.xaml.cs b/dnGREP.WPF/UserControls/ResultsTree.xaml.cs index 1e0d7ad9..b46118ea 100644 --- a/dnGREP.WPF/UserControls/ResultsTree.xaml.cs +++ b/dnGREP.WPF/UserControls/ResultsTree.xaml.cs @@ -28,6 +28,11 @@ public ResultsTree() { InitializeComponent(); this.DataContextChanged += ResultsTree_DataContextChanged; + + tvSearchResult.PreviewMouseWheel += TvSearchResult_PreviewMouseWheel; + tvSearchResult.PreviewTouchDown += TvSearchResult_PreviewTouchDown; + tvSearchResult.PreviewTouchMove += TvSearchResult_PreviewTouchMove; + tvSearchResult.PreviewTouchUp += TvSearchResult_PreviewTouchUp; } void ResultsTree_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) @@ -210,6 +215,139 @@ private void tvSearchResults_SelectedChanged(object sender, RoutedPropertyChange #endregion + #region Zoom + + private Dictionary touchIds = new Dictionary(); + + private void TvSearchResult_PreviewTouchDown(object sender, TouchEventArgs e) + { + IInputElement ctrl = sender as IInputElement; + if (ctrl != null && !touchIds.ContainsKey(e.TouchDevice.Id)) + { + var pt = e.GetTouchPoint(ctrl).Position; + touchIds.Add(e.TouchDevice.Id, pt); + } + } + + private void TvSearchResult_PreviewTouchUp(object sender, TouchEventArgs e) + { + if (touchIds.ContainsKey(e.TouchDevice.Id)) + touchIds.Remove(e.TouchDevice.Id); + } + + private void TvSearchResult_PreviewTouchMove(object sender, TouchEventArgs e) + { + IInputElement ctrl = sender as IInputElement; + + // sometimes a PreviewTouchUp event is lost when the user is on the scrollbar or edge of the window + // if our captured touches do not match the scrollviewer, resynch to the scrollviewer + ScrollViewer scrollViewer = e.OriginalSource as ScrollViewer; + if (scrollViewer != null) + { + var svTouches = scrollViewer.TouchesCaptured.Select(t => t.Id); + var myTouches = touchIds.Keys.Select(k => k); + bool equal = svTouches.OrderBy(i => i).SequenceEqual(myTouches.OrderBy(i => i)); + + if (!equal) + { + touchIds.Clear(); + foreach (var t in scrollViewer.TouchesCaptured) + { + var pt = t.GetTouchPoint(ctrl).Position; + touchIds.Add(t.Id, pt); + } + } + } + + if (inputData != null && inputData.Count > 0 && + ctrl != null && touchIds.ContainsKey(e.TouchDevice.Id) && touchIds.Count == 2) + { + var pNew = e.GetTouchPoint(ctrl).Position; + + var otherTouchId = touchIds.Keys.Where(k => k != e.TouchDevice.Id).FirstOrDefault(); + var p0 = touchIds[otherTouchId]; + var p1 = touchIds[e.TouchDevice.Id]; + + var dx = p1.X - p0.X; + var dy = p1.Y - p0.Y; + var dist1 = dx * dx + dy * dy; + + dx = pNew.X - p0.X; + dy = pNew.Y - p0.Y; + var dist2 = dx * dx + dy * dy; + + if (Math.Abs(dist2 - dist1) > 200) + { + if (dist1 < dist2 && inputData.ResultsScale < 2.0) + { + inputData.ResultsScale *= 1.005; + } + + if (dist1 > dist2 && inputData.ResultsScale > 0.8) + { + inputData.ResultsScale /= 1.005; + } + + if (dist1 < dist2 && inputData.ResultsMenuScale < 1.5) + { + inputData.ResultsMenuScale *= 1.0025; + } + + if (dist1 > dist2 && inputData.ResultsMenuScale > 1.0) + { + inputData.ResultsMenuScale /= 1.0025; + } + + e.Handled = true; + } + + // and update position for this touch + touchIds[e.TouchDevice.Id] = pNew; + } + } + + private void TvSearchResult_PreviewMouseWheel(object sender, MouseWheelEventArgs e) + { + bool handle = (Keyboard.Modifiers & ModifierKeys.Control) > 0 && + inputData != null && inputData.Count > 0; + + if (!handle) + return; + + if (e.Delta > 0 && inputData.ResultsScale < 2.0) + { + inputData.ResultsScale *= 1.05; + } + + if (e.Delta < 0 && inputData.ResultsScale > 0.8) + { + inputData.ResultsScale /= 1.05; + } + + if (e.Delta > 0 && inputData.ResultsMenuScale < 1.5) + { + inputData.ResultsMenuScale *= 1.025; + } + + if (e.Delta < 0 && inputData.ResultsMenuScale > 1.0) + { + inputData.ResultsMenuScale /= 1.025; + } + + e.Handled = true; + } + + private void btnResetZoom_Click(object sender, RoutedEventArgs e) + { + if (inputData != null) + { + inputData.ResultsScale = 1.0; + inputData.ResultsMenuScale = 1.0; + } + } + + #endregion + #region DragDropEvents private static UIElement _draggedElt; private static bool _isMouseDown = false; diff --git a/dnGREP.WPF/ViewModels/MainViewModel.cs b/dnGREP.WPF/ViewModels/MainViewModel.cs index a87e1608..6bf28889 100644 --- a/dnGREP.WPF/ViewModels/MainViewModel.cs +++ b/dnGREP.WPF/ViewModels/MainViewModel.cs @@ -863,6 +863,10 @@ private void replace() private void updateBookmarks() { + int maxSearchReplaceCount = settings.Get(GrepSettings.Key.MaxSearchBookmarks); + int maxPathCount = settings.Get(GrepSettings.Key.MaxPathBookmarks); + int maxExtCount = settings.Get(GrepSettings.Key.MaxExtensionBookmarks); + // Update bookmarks, moving current to the top of the list if (FastSearchBookmarks.IndexOf(SearchFor) != 0) { @@ -875,6 +879,8 @@ private void updateBookmarks() SearchFor = s; } } + while (FastSearchBookmarks.Count > maxSearchReplaceCount) + FastSearchBookmarks.RemoveAt(FastSearchBookmarks.Count - 1); if (FastReplaceBookmarks.IndexOf(ReplaceWith) != 0) { @@ -887,6 +893,8 @@ private void updateBookmarks() ReplaceWith = s; } } + while (FastReplaceBookmarks.Count > maxSearchReplaceCount) + FastReplaceBookmarks.RemoveAt(FastReplaceBookmarks.Count - 1); if (FastFileMatchBookmarks.IndexOf(FilePattern) != 0) { @@ -899,6 +907,8 @@ private void updateBookmarks() FilePattern = s; } } + while (FastFileMatchBookmarks.Count > maxExtCount) + FastFileMatchBookmarks.RemoveAt(FastFileMatchBookmarks.Count - 1); if (FastFileNotMatchBookmarks.IndexOf(FilePatternIgnore) != 0) { @@ -911,6 +921,8 @@ private void updateBookmarks() FilePatternIgnore = s; } } + while (FastFileNotMatchBookmarks.Count > maxExtCount) + FastFileNotMatchBookmarks.RemoveAt(FastFileNotMatchBookmarks.Count - 1); if (FastPathBookmarks.IndexOf(FileOrFolderPath) != 0) { @@ -923,6 +935,8 @@ private void updateBookmarks() FileOrFolderPath = s; } } + while (FastPathBookmarks.Count > maxPathCount) + FastPathBookmarks.RemoveAt(FastPathBookmarks.Count - 1); } private void cancel() @@ -961,6 +975,7 @@ private void showOptions() { SaveSettings(); var optionsForm = new OptionsView(); + optionsForm.Owner = ParentWindow; var optionsViewModel = new OptionsViewModel(); // When the ViewModel asks to be closed, // close the window. diff --git a/dnGREP.WPF/ViewModels/OptionsViewModel.cs b/dnGREP.WPF/ViewModels/OptionsViewModel.cs index 933e00fb..9b3ff183 100644 --- a/dnGREP.WPF/ViewModels/OptionsViewModel.cs +++ b/dnGREP.WPF/ViewModels/OptionsViewModel.cs @@ -48,7 +48,10 @@ public bool CanSave AutoExpandSearchTree != settings.Get(GrepSettings.Key.ExpandResults) || ShowVerboseMatchCount != settings.Get(GrepSettings.Key.ShowVerboseMatchCount) || EnableClearType != (settings.Get(GrepSettings.Key.TextFormatting) == TextFormattingMode.Ideal) || - MatchThreshold != settings.Get(GrepSettings.Key.FuzzyMatchThreshold)) + MatchThreshold != settings.Get(GrepSettings.Key.FuzzyMatchThreshold) || + MaxSearchBookmarks != settings.Get(GrepSettings.Key.MaxSearchBookmarks) || + MaxPathBookmarks != settings.Get(GrepSettings.Key.MaxPathBookmarks) || + MaxExtensionBookmarks != settings.Get(GrepSettings.Key.MaxExtensionBookmarks)) return true; else return false; @@ -339,7 +342,52 @@ public double MatchThreshold base.OnPropertyChanged(() => MatchThreshold); } } - + + private int maxPathBookmarks; + public int MaxPathBookmarks + { + get { return maxPathBookmarks; } + set + { + if (value == maxPathBookmarks) + return; + + maxPathBookmarks = value; + + base.OnPropertyChanged(() => MaxPathBookmarks); + } + } + + private int maxSearchBookmarks; + public int MaxSearchBookmarks + { + get { return maxSearchBookmarks; } + set + { + if (value == maxSearchBookmarks) + return; + + maxSearchBookmarks = value; + + base.OnPropertyChanged(() => MaxSearchBookmarks); + } + } + + private int maxExtensionBookmarks; + public int MaxExtensionBookmarks + { + get { return maxExtensionBookmarks; } + set + { + if (value == maxExtensionBookmarks) + return; + + maxExtensionBookmarks = value; + + base.OnPropertyChanged(() => MaxExtensionBookmarks); + } + } + #endregion #region Presentation Properties @@ -462,6 +510,9 @@ private void loadSetting() ShowLinesInContext = settings.Get(GrepSettings.Key.ShowLinesInContext); ContextLinesBefore = settings.Get(GrepSettings.Key.ContextLinesBefore); ContextLinesAfter = settings.Get(GrepSettings.Key.ContextLinesAfter); + MaxSearchBookmarks = settings.Get(GrepSettings.Key.MaxSearchBookmarks); + MaxPathBookmarks = settings.Get(GrepSettings.Key.MaxPathBookmarks); + MaxExtensionBookmarks = settings.Get(GrepSettings.Key.MaxExtensionBookmarks); } private void saveSettings() @@ -503,6 +554,9 @@ private void saveSettings() settings.Set(GrepSettings.Key.ShowLinesInContext, ShowLinesInContext); settings.Set(GrepSettings.Key.ContextLinesBefore, ContextLinesBefore); settings.Set(GrepSettings.Key.ContextLinesAfter, ContextLinesAfter); + settings.Set(GrepSettings.Key.MaxSearchBookmarks, MaxSearchBookmarks); + settings.Set(GrepSettings.Key.MaxPathBookmarks, MaxPathBookmarks); + settings.Set(GrepSettings.Key.MaxExtensionBookmarks, MaxExtensionBookmarks); settings.Save(); } diff --git a/dnGREP.WPF/Views/MainViewEx.xaml b/dnGREP.WPF/Views/MainViewEx.xaml index fb1d34fa..c0e40425 100644 --- a/dnGREP.WPF/Views/MainViewEx.xaml +++ b/dnGREP.WPF/Views/MainViewEx.xaml @@ -19,9 +19,6 @@ - - - @@ -29,7 +26,7 @@ - + @@ -37,11 +34,11 @@ - + - - + + @@ -145,13 +142,13 @@ ContextMenuService.Placement="Bottom" IsEnabled="{Binding CanSearchInResults}"> - - - + + + - - - + + + diff --git a/dnGREP.WPF/Views/MainViewEx.xaml.cs b/dnGREP.WPF/Views/MainViewEx.xaml.cs index 5d6742c9..5026b9a3 100644 --- a/dnGREP.WPF/Views/MainViewEx.xaml.cs +++ b/dnGREP.WPF/Views/MainViewEx.xaml.cs @@ -204,5 +204,12 @@ private void btnOtherActions_Click(object sender, RoutedEventArgs e) advanceContextMenu.PlacementTarget = (UIElement)sender; advanceContextMenu.IsOpen = true; } + + private void ManipulationBoundaryFeedbackHandler(object sender, ManipulationBoundaryFeedbackEventArgs e) + { + // disable feedback that list scroll has reached the limit + // -- the feedback is that the whole window moves + e.Handled = true; + } } } diff --git a/dnGREP.WPF/Views/OptionsView.xaml b/dnGREP.WPF/Views/OptionsView.xaml index 566047fc..cc9548b1 100644 --- a/dnGREP.WPF/Views/OptionsView.xaml +++ b/dnGREP.WPF/Views/OptionsView.xaml @@ -2,18 +2,22 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:my="clr-namespace:dnGREP.WPF" - Title="Options" Width="Auto" WindowStyle="ToolWindow" ResizeMode="NoResize" Height="Auto" MinHeight="260" + Title="Options" WindowStyle="ToolWindow" ResizeMode="NoResize" Height="Auto" MinHeight="300" Width="480" Background="{StaticResource GradientBackground}" - MinWidth="453" Icon="/dnGREP;component/nGREP.ico" WindowStartupLocation="CenterOwner" - my:DiginesisHelpProvider.HelpKeyword="Options" my:DiginesisHelpProvider.HelpNavigator="Topic" my:DiginesisHelpProvider.ShowHelp="True" SizeToContent="WidthAndHeight"> + Icon="/dnGREP;component/nGREP.ico" WindowStartupLocation="CenterOwner" + my:DiginesisHelpProvider.HelpKeyword="Options" my:DiginesisHelpProvider.HelpNavigator="Topic" my:DiginesisHelpProvider.ShowHelp="True" SizeToContent="Height"> - - + + + + + + @@ -21,6 +25,10 @@ General + + + + @@ -60,6 +68,7 @@ + @@ -69,37 +78,57 @@ Interface - - Show file path in results panel - - Show result lines in context - - - - + + + + + + Show file path in results panel + + Show result lines in context + + + + + + + + Allow searching for file name pattern only when "search for" is empty + + + + + Show results tree expanded + + + + + Show verbose match count + + + + + Enable ClearType (smoothes the way application displays text) + + + + + + + + + + + + + + + - - - Allow searching for file name pattern only when "search for" is empty - - - - - Show results tree expanded - - - - - Show verbose match count - - - - - Enable ClearType (smoothes the way application displays text) - - - - + @@ -109,31 +138,36 @@ Params - - - - - - + + + + + + + + + - - - - + + + - - - - - + + + + + + - + - + diff --git a/dnGREP.WPF/Views/OptionsView.xaml.cs b/dnGREP.WPF/Views/OptionsView.xaml.cs index 7ad45d44..89d87be4 100644 --- a/dnGREP.WPF/Views/OptionsView.xaml.cs +++ b/dnGREP.WPF/Views/OptionsView.xaml.cs @@ -30,5 +30,33 @@ public OptionsView() DiginesisHelpProvider.HelpNamespace = "https://github.com/dnGrep/dnGrep/wiki/"; DiginesisHelpProvider.ShowHelp = true; } + + private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e) + { + e.Handled = !IsTextAllowed(e.Text); + } + + private void TextBox_Pasting(object sender, DataObjectPastingEventArgs e) + { + if (e.DataObject.GetDataPresent(typeof(string))) + { + string text = (string)e.DataObject.GetData(typeof(string)); + if (!IsTextAllowed(text)) + e.CancelCommand(); + } + else + e.CancelCommand(); + } + + private bool IsTextAllowed(string text) + { + if (!string.IsNullOrEmpty(text)) + { + int value; + if (!int.TryParse(text, out value)) + return false; + } + return true; + } } } diff --git a/dnGREP.WPF/Views/TestPatternView.xaml b/dnGREP.WPF/Views/TestPatternView.xaml index bb6a7bb4..8a3e1cdc 100644 --- a/dnGREP.WPF/Views/TestPatternView.xaml +++ b/dnGREP.WPF/Views/TestPatternView.xaml @@ -5,11 +5,11 @@ Title="Test" Width="527" WindowStyle="ToolWindow" Height="460" MinHeight="460" MinWidth="500" Icon="/dnGREP;component/nGREP.ico" Background="{StaticResource GradientBackground}" - Loaded="Window_Loaded" Closing="Window_Closing" KeyDown="formKeyDown" Style="{StaticResource DefaultStyle}" + Loaded="Window_Loaded" Closing="Window_Closing" KeyDown="formKeyDown" SnapsToDevicePixels="True" ResizeMode="CanResizeWithGrip" TextOptions.TextFormattingMode="{Binding Path=TextFormatting}"> -