diff options
| author | Victoria Plitt <Victoria.Plitt@twine-s.com> | 2019-04-08 13:49:55 +0300 |
|---|---|---|
| committer | Victoria Plitt <Victoria.Plitt@twine-s.com> | 2019-04-08 13:49:55 +0300 |
| commit | fc8a05358a92cc3c77c5f1e30d536807ef0614fd (patch) | |
| tree | c65f696ebd60f3790145721307c255e5a339923f /Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search | |
| parent | b4a71931ea52636c6b36376aa9d71697ccf73524 (diff) | |
| download | Tango-fc8a05358a92cc3c77c5f1e30d536807ef0614fd.tar.gz Tango-fc8a05358a92cc3c77c5f1e30d536807ef0614fd.zip | |
were added scripting projects
Diffstat (limited to 'Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search')
12 files changed, 1118 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/DropDownButton.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/DropDownButton.cs new file mode 100644 index 000000000..dca5bea30 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/DropDownButton.cs @@ -0,0 +1,60 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; + +namespace Tango.Scripting.Editors.Search +{ + /// <summary> + /// A button that opens a drop-down menu when clicked. + /// </summary> + class DropDownButton : ButtonBase + { + public static readonly DependencyProperty DropDownContentProperty + = DependencyProperty.Register("DropDownContent", typeof(Popup), + typeof(DropDownButton), new FrameworkPropertyMetadata(null)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + protected static readonly DependencyPropertyKey IsDropDownContentOpenPropertyKey + = DependencyProperty.RegisterReadOnly("IsDropDownContentOpen", typeof(bool), + typeof(DropDownButton), new FrameworkPropertyMetadata(false)); + + public static readonly DependencyProperty IsDropDownContentOpenProperty = IsDropDownContentOpenPropertyKey.DependencyProperty; + + static DropDownButton() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(DropDownButton), new FrameworkPropertyMetadata(typeof(DropDownButton))); + } + + public Popup DropDownContent { + get { return (Popup)GetValue(DropDownContentProperty); } + set { SetValue(DropDownContentProperty, value); } + } + + public bool IsDropDownContentOpen { + get { return (bool)GetValue(IsDropDownContentOpenProperty); } + protected set { SetValue(IsDropDownContentOpenPropertyKey, value); } + } + + protected override void OnClick() + { + if (DropDownContent != null && !IsDropDownContentOpen) { + DropDownContent.Placement = PlacementMode.Bottom; + DropDownContent.PlacementTarget = this; + DropDownContent.IsOpen = true; + DropDownContent.Closed += DropDownContent_Closed; + this.IsDropDownContentOpen = true; + } + } + + void DropDownContent_Closed(object sender, EventArgs e) + { + ((Popup)sender).Closed -= DropDownContent_Closed; + this.IsDropDownContentOpen = false; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/DropDownButton.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/DropDownButton.xaml new file mode 100644 index 000000000..8c7649c00 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/DropDownButton.xaml @@ -0,0 +1,71 @@ +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:local="clr-namespace:Tango.Scripting.Editors.Search" +> + <!-- Colors for DropDownButton and SplitButton --> + <SolidColorBrush x:Key="{ComponentResourceKey {x:Type local:DropDownButton}, ActiveBorder}" Color="#FF0A246A"/> + <SolidColorBrush x:Key="{ComponentResourceKey {x:Type local:DropDownButton}, ActiveBackground}" Color="#FFB6BDD2"/> + + <!-- Style and Template for DropDownButton --> + <Style TargetType="{x:Type local:DropDownButton}"> + <Setter Property="TextElement.Foreground" Value = "{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" /> + <Setter Property="Control.Padding" Value="2,2,2,2"/> + <Setter Property="Border.BorderThickness" Value="1,1,1,1"/> + <Setter Property="Panel.Background" Value="Transparent"/> + <Setter Property="Border.BorderBrush" Value="Transparent"/> + <Setter Property="FrameworkElement.HorizontalAlignment" Value="Center"/> + <Setter Property="FrameworkElement.VerticalAlignment" Value="Center"/> + <Setter Property="Control.HorizontalContentAlignment" Value="Center"/> + <Setter Property="Control.VerticalContentAlignment" Value="Center"/> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="local:DropDownButton" + xmlns:s="clr-namespace:System;assembly=mscorlib"> + <Border + BorderThickness="{TemplateBinding Border.BorderThickness}" + BorderBrush="{TemplateBinding Border.BorderBrush}" + Background="{TemplateBinding Panel.Background}" + Name="OuterBorder" + SnapsToDevicePixels="True" + > + <StackPanel Orientation="Horizontal"> + <ContentPresenter + Margin="{TemplateBinding Control.Padding}" + Content="{TemplateBinding ContentControl.Content}" + ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}" + ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}" + HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}" + VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}" + SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" /> + <Path Margin="0,2,2,2" + Data = "M0,0 L1,0 0.5,1 z" + Fill = "{TemplateBinding TextElement.Foreground}" + Width = "7" + Height = "3.5" + Stretch = "Fill"/> + </StackPanel> + </Border> + <ControlTemplate.Triggers> + <Trigger Property="UIElement.IsMouseOver" Value="True"> + <Setter Property="Border.BorderBrush" TargetName="OuterBorder" Value="{DynamicResource {ComponentResourceKey {x:Type local:DropDownButton}, ActiveBorder}}" /> + <Setter Property="Panel.Background" TargetName="OuterBorder" Value="{DynamicResource {ComponentResourceKey {x:Type local:DropDownButton}, ActiveBackground}}"/> + </Trigger> + <Trigger Property="UIElement.IsKeyboardFocused" Value="True"> + <Setter Property="Border.BorderBrush" TargetName="OuterBorder" Value="{DynamicResource {ComponentResourceKey {x:Type local:DropDownButton}, ActiveBorder}}"/> + <Setter Property="Panel.Background" TargetName="OuterBorder" Value="{DynamicResource {ComponentResourceKey {x:Type local:DropDownButton}, ActiveBackground}}"/> + </Trigger> + <Trigger Property="UIElement.IsEnabled" Value="False"> + <Setter Property="TextElement.Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/> + </Trigger> + <Trigger Property="local:DropDownButton.IsDropDownContentOpen" Value="True"> + <Setter Property="Border.BorderBrush" TargetName="OuterBorder" Value="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}" /> + <Setter Property="Border.BorderThickness" TargetName="OuterBorder" Value="1,1,1,0" /> + <Setter Property="Border.Padding" TargetName="OuterBorder" Value="0,0,0,1" /> + <Setter Property="Panel.Background" TargetName="OuterBorder" Value="Transparent"/> + </Trigger> + </ControlTemplate.Triggers> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> +</ResourceDictionary>
\ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/ISearchStrategy.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/ISearchStrategy.cs new file mode 100644 index 000000000..dd4942f33 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/ISearchStrategy.cs @@ -0,0 +1,88 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Search +{ + /// <summary> + /// Basic interface for search algorithms. + /// </summary> + public interface ISearchStrategy : IEquatable<ISearchStrategy> + { + /// <summary> + /// Finds all matches in the given ITextSource and the given range. + /// </summary> + /// <remarks> + /// This method must be implemented thread-safe. + /// All segments in the result must be within the given range, and they must be returned in order + /// (e.g. if two results are returned, EndOffset of first result must be less than or equal StartOffset of second result). + /// </remarks> + IEnumerable<ISearchResult> FindAll(ITextSource document, int offset, int length); + + /// <summary> + /// Finds the next match in the given ITextSource and the given range. + /// </summary> + /// <remarks>This method must be implemented thread-safe.</remarks> + ISearchResult FindNext(ITextSource document, int offset, int length); + } + + /// <summary> + /// Represents a search result. + /// </summary> + public interface ISearchResult : ISegment + { + /// <summary> + /// Replaces parts of the replacement string with parts from the match. (e.g. $1) + /// </summary> + string ReplaceWith(string replacement); + } + + /// <summary> + /// Defines supported search modes. + /// </summary> + public enum SearchMode + { + /// <summary> + /// Standard search + /// </summary> + Normal, + /// <summary> + /// RegEx search + /// </summary> + RegEx, + /// <summary> + /// Wildcard search + /// </summary> + Wildcard + } + + /// <inheritdoc/> + public class SearchPatternException : Exception, ISerializable + { + /// <inheritdoc/> + public SearchPatternException() + { + } + + /// <inheritdoc/> + public SearchPatternException(string message) : base(message) + { + } + + /// <inheritdoc/> + public SearchPatternException(string message, Exception innerException) : base(message, innerException) + { + } + + // This constructor is needed for serialization. + /// <inheritdoc/> + protected SearchPatternException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/Localization.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/Localization.cs new file mode 100644 index 000000000..cbe8c9bb0 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/Localization.cs @@ -0,0 +1,64 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.ComponentModel; + +namespace Tango.Scripting.Editors.Search +{ + /// <summary> + /// Holds default texts for buttons and labels in the SearchPanel. Override properties to add other languages. + /// </summary> + public class Localization + { + /// <summary> + /// Default: 'Match case' + /// </summary> + public virtual string MatchCaseText { + get { return "Match case"; } + } + + /// <summary> + /// Default: 'Match whole words' + /// </summary> + public virtual string MatchWholeWordsText { + get { return "Match whole words"; } + } + + + /// <summary> + /// Default: 'Use regular expressions' + /// </summary> + public virtual string UseRegexText { + get { return "Use regular expressions"; } + } + + /// <summary> + /// Default: 'Find next (F3)' + /// </summary> + public virtual string FindNextText { + get { return "Find next (F3)"; } + } + + /// <summary> + /// Default: 'Find previous (Shift+F3)' + /// </summary> + public virtual string FindPreviousText { + get { return "Find previous (Shift+F3)"; } + } + + /// <summary> + /// Default: 'Error: ' + /// </summary> + public virtual string ErrorText { + get { return "Error: "; } + } + + /// <summary> + /// Default: 'No matches found!' + /// </summary> + public virtual string NoMatchesFoundText { + get { return "No matches found!"; } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/RegexSearchStrategy.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/RegexSearchStrategy.cs new file mode 100644 index 000000000..e7ca7f8ce --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/RegexSearchStrategy.cs @@ -0,0 +1,70 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Windows.Documents; + +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Search +{ + class RegexSearchStrategy : ISearchStrategy + { + readonly Regex searchPattern; + readonly bool matchWholeWords; + + public RegexSearchStrategy(Regex searchPattern, bool matchWholeWords) + { + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + this.searchPattern = searchPattern; + this.matchWholeWords = matchWholeWords; + } + + public IEnumerable<ISearchResult> FindAll(ITextSource document, int offset, int length) + { + int endOffset = offset + length; + foreach (Match result in searchPattern.Matches(document.Text)) { + int resultEndOffset = result.Length + result.Index; + if (offset > result.Index || endOffset < resultEndOffset) + continue; + if (matchWholeWords && (!IsWordBorder(document, result.Index) || !IsWordBorder(document, resultEndOffset))) + continue; + yield return new SearchResult { StartOffset = result.Index, Length = result.Length, Data = result }; + } + } + + static bool IsWordBorder(ITextSource document, int offset) + { + return TextUtilities.GetNextCaretPosition(document, offset - 1, LogicalDirection.Forward, CaretPositioningMode.WordBorder) == offset; + } + + public ISearchResult FindNext(ITextSource document, int offset, int length) + { + return FindAll(document, offset, length).FirstOrDefault(); + } + + public bool Equals(ISearchStrategy other) + { + var strategy = other as RegexSearchStrategy; + return strategy != null && + strategy.searchPattern.ToString() == searchPattern.ToString() && + strategy.searchPattern.Options == searchPattern.Options && + strategy.searchPattern.RightToLeft == searchPattern.RightToLeft; + } + } + + class SearchResult : TextSegment, ISearchResult + { + public Match Data { get; set; } + + public string ReplaceWith(string replacement) + { + return Data.Result(replacement); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchCommands.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchCommands.cs new file mode 100644 index 000000000..4bb102e21 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchCommands.cs @@ -0,0 +1,105 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Threading; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Editing; +using Tango.Scripting.Editors.Rendering; + +namespace Tango.Scripting.Editors.Search +{ + /// <summary> + /// Search commands for AvalonEdit. + /// </summary> + public static class SearchCommands + { + /// <summary> + /// Finds the next occurrence in the file. + /// </summary> + public static readonly RoutedCommand FindNext = new RoutedCommand( + "FindNext", typeof(SearchPanel), + new InputGestureCollection { new KeyGesture(Key.F3) } + ); + + /// <summary> + /// Finds the previous occurrence in the file. + /// </summary> + public static readonly RoutedCommand FindPrevious = new RoutedCommand( + "FindPrevious", typeof(SearchPanel), + new InputGestureCollection { new KeyGesture(Key.F3, ModifierKeys.Shift) } + ); + + /// <summary> + /// Closes the SearchPanel. + /// </summary> + public static readonly RoutedCommand CloseSearchPanel = new RoutedCommand( + "CloseSearchPanel", typeof(SearchPanel), + new InputGestureCollection { new KeyGesture(Key.Escape) } + ); + } + + /// <summary> + /// TextAreaInputHandler that registers all search-related commands. + /// </summary> + public class SearchInputHandler : TextAreaInputHandler + { + /// <summary> + /// Creates a new SearchInputHandler and registers the search-related commands. + /// </summary> + public SearchInputHandler(TextArea textArea) + : base(textArea) + { + RegisterCommands(this.CommandBindings); + panel = new SearchPanel(); + panel.Attach(TextArea); + } + + void RegisterCommands(ICollection<CommandBinding> commandBindings) + { + commandBindings.Add(new CommandBinding(ApplicationCommands.Find, ExecuteFind)); + commandBindings.Add(new CommandBinding(SearchCommands.FindNext, ExecuteFindNext)); + commandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, ExecuteFindPrevious)); + commandBindings.Add(new CommandBinding(SearchCommands.CloseSearchPanel, ExecuteCloseSearchPanel)); + } + + SearchPanel panel; + + void ExecuteFind(object sender, ExecutedRoutedEventArgs e) + { + panel.Open(); + if (!(TextArea.Selection.IsEmpty || TextArea.Selection.IsMultiline)) + panel.SearchPattern = TextArea.Selection.GetText(); + Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Input, (Action)delegate { panel.Reactivate(); }); + } + + void ExecuteFindNext(object sender, ExecutedRoutedEventArgs e) + { + panel.FindNext(); + } + + void ExecuteFindPrevious(object sender, ExecutedRoutedEventArgs e) + { + panel.FindPrevious(); + } + + void ExecuteCloseSearchPanel(object sender, ExecutedRoutedEventArgs e) + { + panel.Close(); + } + + /// <summary> + /// Fired when SearchOptions are modified inside the SearchPanel. + /// </summary> + public event EventHandler<SearchOptionsChangedEventArgs> SearchOptionsChanged { + add { panel.SearchOptionsChanged += value; } + remove { panel.SearchOptionsChanged -= value; } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchPanel.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchPanel.cs new file mode 100644 index 000000000..b4af99f54 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchPanel.cs @@ -0,0 +1,467 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Threading; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Editing; +using Tango.Scripting.Editors.Folding; +using Tango.Scripting.Editors.Rendering; + +namespace Tango.Scripting.Editors.Search +{ + /// <summary> + /// Provides search functionality for AvalonEdit. It is displayed in the top-right corner of the TextArea. + /// </summary> + public class SearchPanel : Control + { + TextArea textArea; + TextDocument currentDocument; + SearchResultBackgroundRenderer renderer; + TextBox searchTextBox; + SearchPanelAdorner adorner; + + #region DependencyProperties + /// <summary> + /// Dependency property for <see cref="UseRegex"/>. + /// </summary> + public static readonly DependencyProperty UseRegexProperty = + DependencyProperty.Register("UseRegex", typeof(bool), typeof(SearchPanel), + new FrameworkPropertyMetadata(false, SearchPatternChangedCallback)); + + /// <summary> + /// Gets/sets whether the search pattern should be interpreted as regular expression. + /// </summary> + public bool UseRegex { + get { return (bool)GetValue(UseRegexProperty); } + set { SetValue(UseRegexProperty, value); } + } + + /// <summary> + /// Dependency property for <see cref="MatchCase"/>. + /// </summary> + public static readonly DependencyProperty MatchCaseProperty = + DependencyProperty.Register("MatchCase", typeof(bool), typeof(SearchPanel), + new FrameworkPropertyMetadata(false, SearchPatternChangedCallback)); + + /// <summary> + /// Gets/sets whether the search pattern should be interpreted case-sensitive. + /// </summary> + public bool MatchCase { + get { return (bool)GetValue(MatchCaseProperty); } + set { SetValue(MatchCaseProperty, value); } + } + + /// <summary> + /// Dependency property for <see cref="WholeWords"/>. + /// </summary> + public static readonly DependencyProperty WholeWordsProperty = + DependencyProperty.Register("WholeWords", typeof(bool), typeof(SearchPanel), + new FrameworkPropertyMetadata(false, SearchPatternChangedCallback)); + + /// <summary> + /// Gets/sets whether the search pattern should only match whole words. + /// </summary> + public bool WholeWords { + get { return (bool)GetValue(WholeWordsProperty); } + set { SetValue(WholeWordsProperty, value); } + } + + /// <summary> + /// Dependency property for <see cref="SearchPattern"/>. + /// </summary> + public static readonly DependencyProperty SearchPatternProperty = + DependencyProperty.Register("SearchPattern", typeof(string), typeof(SearchPanel), + new FrameworkPropertyMetadata("", SearchPatternChangedCallback)); + + /// <summary> + /// Gets/sets the search pattern. + /// </summary> + public string SearchPattern { + get { return (string)GetValue(SearchPatternProperty); } + set { SetValue(SearchPatternProperty, value); } + } + + /// <summary> + /// Dependency property for <see cref="MarkerBrush"/>. + /// </summary> + public static readonly DependencyProperty MarkerBrushProperty = + DependencyProperty.Register("MarkerBrush", typeof(Brush), typeof(SearchPanel), + new FrameworkPropertyMetadata(Brushes.LightGreen, MarkerBrushChangedCallback)); + + /// <summary> + /// Gets/sets the Brush used for marking search results in the TextView. + /// </summary> + public Brush MarkerBrush { + get { return (Brush)GetValue(MarkerBrushProperty); } + set { SetValue(MarkerBrushProperty, value); } + } + + /// <summary> + /// Dependency property for <see cref="Localization"/>. + /// </summary> + public static readonly DependencyProperty LocalizationProperty = + DependencyProperty.Register("Localization", typeof(Localization), typeof(SearchPanel), + new FrameworkPropertyMetadata(new Localization())); + + /// <summary> + /// Gets/sets the localization for the SearchPanel. + /// </summary> + public Localization Localization { + get { return (Localization)GetValue(LocalizationProperty); } + set { SetValue(LocalizationProperty, value); } + } + #endregion + + static void MarkerBrushChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + SearchPanel panel = d as SearchPanel; + if (panel != null) { + panel.renderer.MarkerBrush = (Brush)e.NewValue; + } + } + + static SearchPanel() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(SearchPanel), new FrameworkPropertyMetadata(typeof(SearchPanel))); + } + + ISearchStrategy strategy; + + static void SearchPatternChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + SearchPanel panel = d as SearchPanel; + if (panel != null) { + panel.ValidateSearchText(); + panel.UpdateSearch(); + } + } + + void UpdateSearch() + { + // only reset as long as there are results + // if no results are found, the "no matches found" message should not flicker. + // if results are found by the next run, the message will be hidden inside DoSearch ... + if (renderer.CurrentResults.Any()) + messageView.IsOpen = false; + strategy = SearchStrategyFactory.Create(SearchPattern ?? "", !MatchCase, WholeWords, UseRegex ? SearchMode.RegEx : SearchMode.Normal); + OnSearchOptionsChanged(new SearchOptionsChangedEventArgs(SearchPattern, MatchCase, UseRegex, WholeWords)); + DoSearch(true); + } + + /// <summary> + /// Creates a new SearchPanel. + /// </summary> + public SearchPanel() + { + } + + /// <summary> + /// Attaches this SearchPanel to a TextArea instance. + /// </summary> + public void Attach(TextArea textArea) + { + if (textArea == null) + throw new ArgumentNullException("textArea"); + this.textArea = textArea; + adorner = new SearchPanelAdorner(textArea, this); + DataContext = this; + + renderer = new SearchResultBackgroundRenderer(); + currentDocument = textArea.Document; + if (currentDocument != null) + currentDocument.TextChanged += textArea_Document_TextChanged; + textArea.DocumentChanged += textArea_DocumentChanged; + KeyDown += SearchLayerKeyDown; + + this.CommandBindings.Add(new CommandBinding(SearchCommands.FindNext, (sender, e) => FindNext())); + this.CommandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, (sender, e) => FindPrevious())); + this.CommandBindings.Add(new CommandBinding(SearchCommands.CloseSearchPanel, (sender, e) => Close())); + IsClosed = true; + } + + void textArea_DocumentChanged(object sender, EventArgs e) + { + if (currentDocument != null) + currentDocument.TextChanged -= textArea_Document_TextChanged; + currentDocument = textArea.Document; + if (currentDocument != null) { + currentDocument.TextChanged += textArea_Document_TextChanged; + DoSearch(false); + } + } + + void textArea_Document_TextChanged(object sender, EventArgs e) + { + DoSearch(false); + } + + /// <inheritdoc/> + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + searchTextBox = Template.FindName("PART_searchTextBox", this) as TextBox; + } + + void ValidateSearchText() + { + if (searchTextBox == null) + return; + var be = searchTextBox.GetBindingExpression(TextBox.TextProperty); + try { + Validation.ClearInvalid(be); + UpdateSearch(); + } catch (SearchPatternException ex) { + var ve = new ValidationError(be.ParentBinding.ValidationRules[0], be, ex.Message, ex); + Validation.MarkInvalid(be, ve); + } + } + + /// <summary> + /// Reactivates the SearchPanel by setting the focus on the search box and selecting all text. + /// </summary> + public void Reactivate() + { + if (searchTextBox == null) + return; + searchTextBox.Focus(); + searchTextBox.SelectAll(); + } + + /// <summary> + /// Moves to the next occurrence in the file. + /// </summary> + public void FindNext() + { + SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset + 1); + if (result == null) + result = renderer.CurrentResults.FirstSegment; + if (result != null) { + SelectResult(result); + } + } + + /// <summary> + /// Moves to the previous occurrence in the file. + /// </summary> + public void FindPrevious() + { + SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset); + if (result != null) + result = renderer.CurrentResults.GetPreviousSegment(result); + if (result == null) + result = renderer.CurrentResults.LastSegment; + if (result != null) { + SelectResult(result); + } + } + + ToolTip messageView = new ToolTip { Placement = PlacementMode.Bottom, StaysOpen = false }; + + void DoSearch(bool changeSelection) + { + if (IsClosed) + return; + renderer.CurrentResults.Clear(); + + if (!string.IsNullOrEmpty(SearchPattern)) { + int offset = textArea.Caret.Offset; + if (changeSelection) { + textArea.ClearSelection(); + } + // We cast from ISearchResult to SearchResult; this is safe because we always use the built-in strategy + foreach (SearchResult result in strategy.FindAll(textArea.Document, 0, textArea.Document.TextLength)) { + if (changeSelection && result.StartOffset >= offset) { + SelectResult(result); + changeSelection = false; + } + renderer.CurrentResults.Add(result); + } + if (!renderer.CurrentResults.Any()) { + messageView.IsOpen = true; + messageView.Content = Localization.NoMatchesFoundText; + messageView.PlacementTarget = searchTextBox; + } else + messageView.IsOpen = false; + } + textArea.TextView.InvalidateLayer(KnownLayer.Selection); + } + + void SelectResult(SearchResult result) + { + textArea.Caret.Offset = result.StartOffset; + textArea.Selection = Selection.Create(textArea, result.StartOffset, result.EndOffset); + textArea.Caret.BringCaretToView(); + // show caret even if the editor does not have the Keyboard Focus + textArea.Caret.Show(); + } + + void SearchLayerKeyDown(object sender, KeyEventArgs e) + { + switch (e.Key) { + case Key.Enter: + e.Handled = true; + messageView.IsOpen = false; + if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) + FindPrevious(); + else + FindNext(); + if (searchTextBox != null) { + var error = Validation.GetErrors(searchTextBox).FirstOrDefault(); + if (error != null) { + messageView.Content = Localization.ErrorText + " " + error.ErrorContent; + messageView.PlacementTarget = searchTextBox; + messageView.IsOpen = true; + } + } + break; + case Key.Escape: + e.Handled = true; + Close(); + break; + } + } + + /// <summary> + /// Gets whether the Panel is already closed. + /// </summary> + public bool IsClosed { get; private set; } + + /// <summary> + /// Closes the SearchPanel. + /// </summary> + public void Close() + { + bool hasFocus = this.IsKeyboardFocusWithin; + + var layer = AdornerLayer.GetAdornerLayer(textArea); + if (layer != null) + layer.Remove(adorner); + messageView.IsOpen = false; + textArea.TextView.BackgroundRenderers.Remove(renderer); + if (hasFocus) + textArea.Focus(); + IsClosed = true; + + // Clear existing search results so that the segments don't have to be maintained + renderer.CurrentResults.Clear(); + } + + /// <summary> + /// Closes the SearchPanel and removes it. + /// </summary> + public void CloseAndRemove() + { + Close(); + textArea.DocumentChanged -= textArea_DocumentChanged; + if (currentDocument != null) + currentDocument.TextChanged -= textArea_Document_TextChanged; + } + + /// <summary> + /// Opens the an existing search panel. + /// </summary> + public void Open() + { + if (!IsClosed) return; + var layer = AdornerLayer.GetAdornerLayer(textArea); + if (layer != null) + layer.Add(adorner); + textArea.TextView.BackgroundRenderers.Add(renderer); + IsClosed = false; + DoSearch(false); + } + + /// <summary> + /// Fired when SearchOptions are changed inside the SearchPanel. + /// </summary> + public event EventHandler<SearchOptionsChangedEventArgs> SearchOptionsChanged; + + /// <summary> + /// Raises the <see cref="SearchPanel.SearchOptionsChanged" /> event. + /// </summary> + protected virtual void OnSearchOptionsChanged(SearchOptionsChangedEventArgs e) + { + if (SearchOptionsChanged != null) { + SearchOptionsChanged(this, e); + } + } + } + + /// <summary> + /// EventArgs for <see cref="SearchPanel.SearchOptionsChanged"/> event. + /// </summary> + public class SearchOptionsChangedEventArgs : EventArgs + { + /// <summary> + /// Gets the search pattern. + /// </summary> + public string SearchPattern { get; private set; } + + /// <summary> + /// Gets whether the search pattern should be interpreted case-sensitive. + /// </summary> + public bool MatchCase { get; private set; } + + /// <summary> + /// Gets whether the search pattern should be interpreted as regular expression. + /// </summary> + public bool UseRegex { get; private set; } + + /// <summary> + /// Gets whether the search pattern should only match whole words. + /// </summary> + public bool WholeWords { get; private set; } + + /// <summary> + /// Creates a new SearchOptionsChangedEventArgs instance. + /// </summary> + public SearchOptionsChangedEventArgs(string searchPattern, bool matchCase, bool useRegex, bool wholeWords) + { + this.SearchPattern = searchPattern; + this.MatchCase = matchCase; + this.UseRegex = useRegex; + this.WholeWords = wholeWords; + } + } + + class SearchPanelAdorner : Adorner + { + SearchPanel panel; + + public SearchPanelAdorner(TextArea textArea, SearchPanel panel) + : base(textArea) + { + this.panel = panel; + AddVisualChild(panel); + } + + protected override int VisualChildrenCount { + get { return 1; } + } + + protected override Visual GetVisualChild(int index) + { + if (index != 0) + throw new ArgumentOutOfRangeException(); + return panel; + } + + protected override Size ArrangeOverride(Size finalSize) + { + panel.Arrange(new Rect(new Point(0, 0), finalSize)); + return new Size(panel.ActualWidth, panel.ActualHeight); + } + } +}
\ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchPanel.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchPanel.xaml new file mode 100644 index 000000000..b787e9a4f --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchPanel.xaml @@ -0,0 +1,47 @@ +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:search="clr-namespace:Tango.Scripting.Editors.Search"> + <Style TargetType="search:SearchPanel"> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type search:SearchPanel}"> + <Border Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" BorderBrush="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}" BorderThickness="1" HorizontalAlignment="Right" VerticalAlignment="Top" Cursor="Arrow"> + <StackPanel Orientation="Horizontal"> + <TextBox Name="PART_searchTextBox" Focusable="True" Width="150" Height="24" Margin="3,3,3,0"> + <TextBox.Text> + <Binding Path="SearchPattern" RelativeSource="{RelativeSource TemplatedParent}" UpdateSourceTrigger="PropertyChanged"> + <Binding.ValidationRules> + <ExceptionValidationRule /> + </Binding.ValidationRules> + </Binding> + </TextBox.Text> + </TextBox> + <search:DropDownButton Height="24"> + <search:DropDownButton.DropDownContent> + <Popup StaysOpen="False"> + <Border Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" BorderBrush="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}" BorderThickness="1"> + <StackPanel Orientation="Vertical"> + <CheckBox IsChecked="{Binding MatchCase, RelativeSource={RelativeSource TemplatedParent}}" Content="{Binding Localization.MatchCaseText, RelativeSource={RelativeSource TemplatedParent}}" Margin="3" /> + <CheckBox IsChecked="{Binding WholeWords, RelativeSource={RelativeSource TemplatedParent}}" Content="{Binding Localization.MatchWholeWordsText, RelativeSource={RelativeSource TemplatedParent}}" Margin="3" /> + <CheckBox IsChecked="{Binding UseRegex, RelativeSource={RelativeSource TemplatedParent}}" Content="{Binding Localization.UseRegexText, RelativeSource={RelativeSource TemplatedParent}}" Margin="3" /> + </StackPanel> + </Border> + </Popup> + </search:DropDownButton.DropDownContent> + </search:DropDownButton> + <Button Margin="3" Height="24" Width="24" Command="search:SearchCommands.FindPrevious" ToolTip="{Binding Localization.FindPreviousText, RelativeSource={RelativeSource TemplatedParent}}"> + <Image Width="16" Height="16" Stretch="Fill" Source="prev.png" /> + </Button> + <Button Margin="3" Height="24" Width="24" Command="search:SearchCommands.FindNext" ToolTip="{Binding Localization.FindNextText, RelativeSource={RelativeSource TemplatedParent}}"> + <Image Width="16" Height="16" Stretch="Fill" Source="next.png" /> + </Button> + <Button Height="16" Width="16" HorizontalAlignment="Right" VerticalAlignment="Top" Command="search:SearchCommands.CloseSearchPanel" + VerticalContentAlignment="Center" HorizontalContentAlignment="Center"> + <Path Data="M 0,0 L 8,8 M 8,0 L 0,8" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" /> + </Button> + </StackPanel> + </Border> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> +</ResourceDictionary>
\ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchResultBackgroundRenderer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchResultBackgroundRenderer.cs new file mode 100644 index 000000000..35b9c0696 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchResultBackgroundRenderer.cs @@ -0,0 +1,78 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Editing; +using Tango.Scripting.Editors.Rendering; + +namespace Tango.Scripting.Editors.Search +{ + class SearchResultBackgroundRenderer : IBackgroundRenderer + { + TextSegmentCollection<SearchResult> currentResults = new TextSegmentCollection<SearchResult>(); + + public TextSegmentCollection<SearchResult> CurrentResults { + get { return currentResults; } + } + + public KnownLayer Layer { + get { + // draw behind selection + return KnownLayer.Selection; + } + } + + public SearchResultBackgroundRenderer() + { + markerBrush = Brushes.LightGreen; + markerPen = new Pen(markerBrush, 1); + } + + Brush markerBrush; + Pen markerPen; + + public Brush MarkerBrush { + get { return markerBrush; } + set { + this.markerBrush = value; + markerPen = new Pen(markerBrush, 1); + } + } + + public void Draw(TextView textView, DrawingContext drawingContext) + { + if (textView == null) + throw new ArgumentNullException("textView"); + if (drawingContext == null) + throw new ArgumentNullException("drawingContext"); + + if (currentResults == null || !textView.VisualLinesValid) + return; + + var visualLines = textView.VisualLines; + if (visualLines.Count == 0) + return; + + int viewStart = visualLines.First().FirstDocumentLine.Offset; + int viewEnd = visualLines.Last().LastDocumentLine.EndOffset; + + foreach (SearchResult result in currentResults.FindOverlappingSegments(viewStart, viewEnd - viewStart)) { + BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder(); + geoBuilder.AlignToMiddleOfPixels = true; + geoBuilder.CornerRadius = 3; + geoBuilder.AddSegment(textView, result); + Geometry geometry = geoBuilder.CreateGeometry(); + if (geometry != null) { + drawingContext.DrawGeometry(markerBrush, markerPen, geometry); + } + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchStrategyFactory.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchStrategyFactory.cs new file mode 100644 index 000000000..01633c62a --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchStrategyFactory.cs @@ -0,0 +1,68 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Text; +using System.Text.RegularExpressions; +using System.Windows.Controls; +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Search +{ + /// <summary> + /// Provides factory methods for ISearchStrategies. + /// </summary> + public class SearchStrategyFactory + { + /// <summary> + /// Creates a default ISearchStrategy with the given parameters. + /// </summary> + public static ISearchStrategy Create(string searchPattern, bool ignoreCase, bool matchWholeWords, SearchMode mode) + { + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + RegexOptions options = RegexOptions.Compiled | RegexOptions.Multiline; + if (ignoreCase) + options |= RegexOptions.IgnoreCase; + + switch (mode) { + case SearchMode.Normal: + searchPattern = Regex.Escape(searchPattern); + break; + case SearchMode.Wildcard: + searchPattern = ConvertWildcardsToRegex(searchPattern); + break; + } + try { + Regex pattern = new Regex(searchPattern, options); + return new RegexSearchStrategy(pattern, matchWholeWords); + } catch (ArgumentException ex) { + throw new SearchPatternException(ex.Message, ex); + } + } + + static string ConvertWildcardsToRegex(string searchPattern) + { + if (string.IsNullOrEmpty(searchPattern)) + return ""; + + StringBuilder builder = new StringBuilder(); + + foreach (char ch in searchPattern) { + switch (ch) { + case '?': + builder.Append("."); + break; + case '*': + builder.Append(".*"); + break; + default: + builder.Append(Regex.Escape(ch.ToString())); + break; + } + } + + return builder.ToString(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/next.png b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/next.png Binary files differnew file mode 100644 index 000000000..250330790 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/next.png diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/prev.png b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/prev.png Binary files differnew file mode 100644 index 000000000..f0454a237 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/prev.png |
