diff --git a/src/dev/DevToys.Startup/Package.appxmanifest b/src/dev/DevToys.Startup/Package.appxmanifest index 3bec993557..c4d57e3f1f 100644 --- a/src/dev/DevToys.Startup/Package.appxmanifest +++ b/src/dev/DevToys.Startup/Package.appxmanifest @@ -15,7 +15,7 @@ + Version="1.0.9.0" /> DevToys - Preview diff --git a/src/dev/impl/DevToys/Core/Collections/ExtendedObservableCollection.cs b/src/dev/impl/DevToys/Core/Collections/ExtendedObservableCollection.cs index 6e54bc466a..f24deec200 100644 --- a/src/dev/impl/DevToys/Core/Collections/ExtendedObservableCollection.cs +++ b/src/dev/impl/DevToys/Core/Collections/ExtendedObservableCollection.cs @@ -48,7 +48,7 @@ internal void Update(IEnumerable newItems) foreach (T? item in newItems) { int indexOfItemInOldMenu = IndexOf(item); - if (indexOfItemInOldMenu > -1) + if (indexOfItemInOldMenu > -1 && insertionIndex < Count) { if (indexOfItemInOldMenu != insertionIndex) { diff --git a/src/dev/impl/DevToys/LanguageManager.cs b/src/dev/impl/DevToys/LanguageManager.cs index 34fcb706ae..acbfc2baa2 100644 --- a/src/dev/impl/DevToys/LanguageManager.cs +++ b/src/dev/impl/DevToys/LanguageManager.cs @@ -2417,6 +2417,16 @@ public class RegExStrings : ObservableObject /// Gets the resource SearchKeywords. /// public string SearchKeywords => _resources.GetString("SearchKeywords"); + + /// + /// Gets the resource InputTitle. + /// + public string InputTitle => _resources.GetString("InputTitle"); + + /// + /// Gets the resource OutputTitle. + /// + public string OutputTitle => _resources.GetString("OutputTitle"); } public class SearchResultStrings : ObservableObject diff --git a/src/dev/impl/DevToys/Strings/en-US/RegEx.resw b/src/dev/impl/DevToys/Strings/en-US/RegEx.resw index 65e6f9bf48..7292dac888 100644 --- a/src/dev/impl/DevToys/Strings/en-US/RegEx.resw +++ b/src/dev/impl/DevToys/Strings/en-US/RegEx.resw @@ -186,4 +186,10 @@ Regular expression + + Input + + + Output + \ No newline at end of file diff --git a/src/dev/impl/DevToys/UI/Controls/CustomTextBox.xaml.cs b/src/dev/impl/DevToys/UI/Controls/CustomTextBox.xaml.cs index d87c9cdf17..e81e02445d 100644 --- a/src/dev/impl/DevToys/UI/Controls/CustomTextBox.xaml.cs +++ b/src/dev/impl/DevToys/UI/Controls/CustomTextBox.xaml.cs @@ -8,6 +8,7 @@ using DevToys.Core; using DevToys.Core.Settings; using DevToys.Core.Threading; +using DevToys.MonacoEditor.Monaco; using Microsoft.Toolkit.Mvvm.Input; using Windows.ApplicationModel.DataTransfer; using Windows.Storage; @@ -16,6 +17,7 @@ using Windows.UI.Text; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Documents; using Windows.UI.Xaml.Media; using Clipboard = Windows.ApplicationModel.DataTransfer.Clipboard; @@ -315,9 +317,6 @@ private void ExecuteSelectAllCommand() public void SetHighlights(IEnumerable? spans) { - IEnumerable? highlightsToRemove = _highlightedSpans?.Except(spans ?? Array.Empty()); - IEnumerable? highlightsToAdd = spans?.Except(_highlightedSpans ?? Array.Empty()); - _highlightedSpans = spans; if (!IsRichTextEdit) @@ -328,31 +327,49 @@ public void SetHighlights(IEnumerable? spans) RichEditBox? richEditBox = GetRichEditBox(); richEditBox.TextDocument.BatchDisplayUpdates(); - if (highlightsToRemove is not null) + if (spans is not null && spans.Any()) { - foreach (HighlightSpan span in highlightsToRemove) + HighlightSpan[] spansArray = spans.ToArray(); + int clearFormattingStartIndex = 0; + for (int i = 0; i < spansArray.Length; i++) { + HighlightSpan span = spansArray[i]; ITextRange range = richEditBox.TextDocument.GetRange( span.StartIndex, span.StartIndex + span.Length); - range.CharacterFormat.BackgroundColor = Colors.Transparent; - range.CharacterFormat.ForegroundColor = ActualTheme == ElementTheme.Dark ? Colors.White : Colors.Black; + range.CharacterFormat.BackgroundColor = span.BackgroundColor; + range.CharacterFormat.ForegroundColor = span.ForegroundColor; + + if (span.StartIndex - clearFormattingStartIndex > 0) + { + range + = richEditBox.TextDocument.GetRange( + clearFormattingStartIndex, + span.StartIndex); + range.CharacterFormat.BackgroundColor = Colors.Transparent; + range.CharacterFormat.ForegroundColor = ActualTheme == ElementTheme.Dark ? Colors.White : Colors.Black; + } + + clearFormattingStartIndex = span.StartIndex + span.Length; } - } - if (highlightsToAdd is not null) - { - foreach (HighlightSpan span in highlightsToAdd) + if (Text.Length - clearFormattingStartIndex > 0) { ITextRange range - = richEditBox.TextDocument.GetRange( - span.StartIndex, - span.StartIndex + span.Length); - range.CharacterFormat.BackgroundColor = span.BackgroundColor; - range.CharacterFormat.ForegroundColor = span.ForegroundColor; + = richEditBox.TextDocument.GetRange( + clearFormattingStartIndex, + Text.Length); + range.CharacterFormat.BackgroundColor = Colors.Transparent; + range.CharacterFormat.ForegroundColor = ActualTheme == ElementTheme.Dark ? Colors.White : Colors.Black; } } + else + { + ITextRange range = richEditBox.TextDocument.GetRange(0, Text.Length); + range.CharacterFormat.BackgroundColor = Colors.Transparent; + range.CharacterFormat.ForegroundColor = ActualTheme == ElementTheme.Dark ? Colors.White : Colors.Black; + } richEditBox.TextDocument.ApplyDisplayUpdates(); } diff --git a/src/dev/impl/DevToys/ViewModels/Tools/Text/RegEx/RegExToolViewModel.cs b/src/dev/impl/DevToys/ViewModels/Tools/Text/RegEx/RegExToolViewModel.cs index 1acad443bb..350a7d7822 100644 --- a/src/dev/impl/DevToys/ViewModels/Tools/Text/RegEx/RegExToolViewModel.cs +++ b/src/dev/impl/DevToys/ViewModels/Tools/Text/RegEx/RegExToolViewModel.cs @@ -3,11 +3,13 @@ using System; using System.Collections.Generic; using System.Composition; +using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using DevToys.Api.Core; using DevToys.Api.Core.Settings; using DevToys.Api.Tools; +using DevToys.Core.Collections; using DevToys.Core.Threading; using DevToys.Shared.Core.Threading; using DevToys.UI.Controls; @@ -196,6 +198,40 @@ internal string? Text } } + private string? _errorMsg; + internal string? ErrorMsg + { + get => _errorMsg; + set + { + SetProperty(ref _errorMsg, value); + } + } + + internal ExtendedObservableCollection MatchGroups { get; } = new(); + + private string? _inputValue; + internal string? InputValue + { + get => _inputValue; + set + { + SetProperty(ref _inputValue, value); + QueueRegExMatch(); + } + } + + private string? _outputValue; + internal string? OutputValue + { + get => _outputValue; + set + { + SetProperty(ref _outputValue, value); + QueueRegExMatch(); + } + } + internal ICustomTextBox? MatchTextBox { private get; set; } [ImportingConstructor] @@ -225,52 +261,86 @@ private async Task TreatQueueAsync() await ThreadHelper.RunOnUIThreadAsync(() => { ElementTheme currentTheme = ((Frame)Window.Current.Content).ActualTheme; - string? highlighterBackgroundResourceName = currentTheme == ElementTheme.Light ? "SystemAccentColorLight2" : "SystemAccentColorDark1"; + string? highlighterBackgroundResourceName = currentTheme == ElementTheme.Light + ? "SystemAccentColorLight2" + : "SystemAccentColorDark1"; highlighterForegroundColor = currentTheme == ElementTheme.Light ? Colors.Black : Colors.White; highlighterBackgroundColor = (Color)Application.Current.Resources[highlighterBackgroundResourceName]; }); await TaskScheduler.Default; - + string errorMsg = ""; while (_regExMatchingQueue.TryDequeue(out (string pattern, string text) data)) { var spans = new List(); - - try + MatchCollection? matches = null; + Regex? regex = null; + if (!String.IsNullOrWhiteSpace(data.pattern)) { - string? pattern = data.pattern.Trim('/'); - - var regex = new Regex(data.pattern, GetOptions()); - MatchCollection matches = regex.Matches(data.text); - - foreach (Match match in matches) + try { - int lineCount = CountLines(data.text, match.Index); - spans.Add( - new HighlightSpan() - { - StartIndex = match.Index - lineCount, - Length = match.Length, - BackgroundColor = highlighterBackgroundColor, - ForegroundColor = highlighterForegroundColor - }); + string? pattern = data.pattern.Trim('/'); + regex = new Regex(data.pattern, GetOptions()); + matches = regex.Matches(data.text); + foreach (Match match in matches) + { + int lineCount = CountLines(data.text, match.Index); + spans.Add( + new HighlightSpan() + { + StartIndex = match.Index - lineCount, + Length = match.Length, + BackgroundColor = highlighterBackgroundColor, + ForegroundColor = highlighterForegroundColor + }); + } + } + catch (Exception ex) + { + errorMsg = ex.Message; + // TODO: indicate the user that the regex is wrong. } - } - catch - { - // TODO: indicate the user that the regex is wrong. } ThreadHelper.RunOnUIThreadAsync( ThreadPriority.Low, () => { - MatchTextBox?.SetHighlights(spans); + ErrorMsg = errorMsg; + if (matches != null) + { + MatchTextBox?.SetHighlights(spans); + + IEnumerable matchesGroups + = matches + .Cast() + .SelectMany( + (c, inx) => c.Groups + .Cast() + .OrderBy(g => g.Index) + .Select(mm => new MatchDetails2 + { + Title = (mm.Name == "0" ? $"Match {inx + 1}:" : $" Group \"{mm.Name}\""), + Range = $"{mm.Index}-{mm.Index + mm.Length}", + Value = mm.Value + })); + MatchGroups.Update(matchesGroups); + + if (InputValue != null) + { + OutputValue = regex?.Replace(data.text, InputValue); + } - if (spans.Count > 0 && !_toolSuccessfullyWorked) + if (spans.Count > 0 && !_toolSuccessfullyWorked) + { + _toolSuccessfullyWorked = true; + _marketingService.NotifyToolSuccessfullyWorked(); + } + } + else { - _toolSuccessfullyWorked = true; - _marketingService.NotifyToolSuccessfullyWorked(); + MatchGroups.Clear(); + MatchTextBox?.SetHighlights(null); } }).ForgetSafely(); } @@ -315,6 +385,11 @@ private RegexOptions GetOptions() private int CountLines(string input, int maxLength) { + if (string.IsNullOrEmpty(input)) + { + return 0; + } + int lines = 0; int i = 0; while (i > -1 && i < maxLength) @@ -330,4 +405,11 @@ private int CountLines(string input, int maxLength) return lines; } } + + public record MatchDetails2 + { + public string Title { get; set; } + public string Range { get; set; } + public string Value { get; set; } + } } diff --git a/src/dev/impl/DevToys/Views/Tools/Text/RegEx/RegExToolPage.xaml b/src/dev/impl/DevToys/Views/Tools/Text/RegEx/RegExToolPage.xaml index b66d2512c1..c53bc3b4a7 100644 --- a/src/dev/impl/DevToys/Views/Tools/Text/RegEx/RegExToolPage.xaml +++ b/src/dev/impl/DevToys/Views/Tools/Text/RegEx/RegExToolPage.xaml @@ -4,94 +4,106 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:controls="using:DevToys.UI.Controls" xmlns:converters="using:DevToys.UI.Converters" - mc:Ignorable="d" - NavigationCacheMode="Required"> + xmlns:controls="using:DevToys.UI.Controls" + xmlns:converters="using:DevToys.UI.Converters" + xmlns:regEx="using:DevToys.ViewModels.Tools.RegEx" + xmlns:muxc="using:Microsoft.UI.Xaml.Controls" + NavigationCacheMode="Required" + mc:Ignorable="d"> - + + + + + - + - - - + + + + + - - + + - + - + - + Description="{x:Bind Path=ViewModel.Strings.EcmaScriptDescription}"> + - + Description="{x:Bind Path=ViewModel.Strings.CultureInvariantDescription}"> + - + Description="{x:Bind Path=ViewModel.Strings.IgnoreCaseDescription}"> + + Description="{x:Bind Path=ViewModel.Strings.IgnoreWhitespaceDescription}"> + IsEnabled="{x:Bind Converter={StaticResource InvertedBooleanConverter}, Mode=OneWay, Path=ViewModel.EcmaScript}" + IsOn="{x:Bind Mode=TwoWay, Path=ViewModel.IgnoreWhitespace, UpdateSourceTrigger=PropertyChanged}" + Style="{StaticResource RightAlignedToggleSwitchStyle}" /> + Description="{x:Bind Path=ViewModel.Strings.SinglelineDescription}"> + IsEnabled="{x:Bind Converter={StaticResource InvertedBooleanConverter}, Mode=OneWay, Path=ViewModel.EcmaScript}" + IsOn="{x:Bind Mode=TwoWay, Path=ViewModel.Singleline, UpdateSourceTrigger=PropertyChanged}" + Style="{StaticResource RightAlignedToggleSwitchStyle}" /> - + Description="{x:Bind Path=ViewModel.Strings.MultilineDescription}"> + + Description="{x:Bind Path=ViewModel.Strings.RightToLeftDescription}"> + IsEnabled="{x:Bind Converter={StaticResource InvertedBooleanConverter}, Mode=OneWay, Path=ViewModel.EcmaScript}" + IsOn="{x:Bind Mode=TwoWay, Path=ViewModel.RightToLeft, UpdateSourceTrigger=PropertyChanged}" + Style="{StaticResource RightAlignedToggleSwitchStyle}" /> @@ -100,16 +112,48 @@ + Header="{x:Bind Path=ViewModel.Strings.RegularExpression}" + Text="{x:Bind Mode=TwoWay, Path=ViewModel.RegularExpression, UpdateSourceTrigger=PropertyChanged}" /> + + + Text="{x:Bind Mode=TwoWay, Path=ViewModel.Text, UpdateSourceTrigger=PropertyChanged}" /> + + + + + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/src/dev/shared/SharedAssemblyInfo.cs b/src/dev/shared/SharedAssemblyInfo.cs index 10b703abb9..1984014948 100644 --- a/src/dev/shared/SharedAssemblyInfo.cs +++ b/src/dev/shared/SharedAssemblyInfo.cs @@ -37,7 +37,7 @@ // Please DO NOT commit changes on the 2 lines below unless // you're about to release a new version of the app on the Store. -[assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("0.0.0.0")] +[assembly: AssemblyVersion("1.0.9.0")] +[assembly: AssemblyFileVersion("1.0.9.0")] [assembly: InternalsVisibleTo("DevToys.Tests")]