aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search
diff options
context:
space:
mode:
authorRoy Ben Shabat <Roy.mail.net@gmail.com>2019-04-09 01:47:48 +0300
committerRoy Ben Shabat <Roy.mail.net@gmail.com>2019-04-09 01:47:48 +0300
commit080f1697e97e13461ec6df4d31c8924d01257a1b (patch)
treeb1fe0285de7bc9bc52e9e2195e66fe022bf8f5b3 /Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search
parent1608e69a417bc5e40a607c3958c4a60f19f66f1a (diff)
downloadTango-080f1697e97e13461ec6df4d31c8924d01257a1b.tar.gz
Tango-080f1697e97e13461ec6df4d31c8924d01257a1b.zip
MERGE
Diffstat (limited to 'Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search')
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/DropDownButton.cs60
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/DropDownButton.xaml71
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/ISearchStrategy.cs88
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/Localization.cs64
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/RegexSearchStrategy.cs70
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchCommands.cs105
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchPanel.cs467
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchPanel.xaml47
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchResultBackgroundRenderer.cs78
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchStrategyFactory.cs68
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/next.pngbin0 -> 1304 bytes
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/prev.pngbin0 -> 1300 bytes
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
new file mode 100644
index 000000000..250330790
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/next.png
Binary files differ
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/prev.png b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/prev.png
new file mode 100644
index 000000000..f0454a237
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/prev.png
Binary files differ