65.9K
CodeProject is changing. Read more.
Home

A Find and Replace Tool for AvalonEdit

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (16 votes)

May 4, 2014

CPOL
viewsIcon

34554

A bare-bones Find and Replace tool for AvalonEdit

My tip is based on the excellent article A Universal WPF Find / Replace Dialog by Thomas Willwacher. For anyone interested, I've distilled this project down to a bare-bones Find and Replace tool for the AvalonEdit editor only.

Here's the XAML for your "FindReplaceDialog.xaml" file...

<Window x:Class="FindReplace.FindReplaceDialog"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Find and Replace" WindowStartupLocation="CenterOwner"
        Width="300" SizeToContent="Height" ResizeMode="NoResize" 
        WindowStyle="ToolWindow" ShowInTaskbar="False" Closed="Window_Closed">
    
    <Grid Margin="0,4">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <TabControl Name="tabMain" Height="Auto" Grid.ColumnSpan="2">
            <TabItem Header="Find">
                <StackPanel>
                    <TextBlock Margin="3">Text to Find:</TextBlock>
                    <TextBox Margin="3" Name="txtFind" />
                    <Button Margin="5" HorizontalAlignment="Right" 
                    Width="80" Content="Find Next" Click="FindNextClick" />
                </StackPanel>
            </TabItem>
            <TabItem Header="Replace">
                <StackPanel>
                    <TextBlock Margin="3">Text to Find:</TextBlock>
                    <TextBox Margin="3" Name="txtFind2" />
                    <TextBlock Margin="3" Text="Replace with:" />
                    <TextBox Margin="3" Name="txtReplace" />
                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
                        <Button Margin="5" HorizontalAlignment="Right" 
                        Width="80" Content="Find Next" Click="FindNext2Click" />
                        <Button Margin="5" HorizontalAlignment="Right" 
                        Width="80" Content="Replace" Click="ReplaceClick" />
                        <Button Margin="5" HorizontalAlignment="Right" 
                        Width="80" Content="Replace All" Click="ReplaceAllClick" />
                    </StackPanel>
                </StackPanel>
            </TabItem>
        </TabControl>

        <CheckBox Grid.Row="1" Grid.Column="0" Margin="10,2" 
        Name="cbCaseSensitive" Content="Match case" IsChecked="true" />
        <CheckBox Grid.Row="2" Grid.Column="0" Margin="10,2" 
        Name="cbWholeWord" Content="Match whole word" IsChecked="true" />
        <CheckBox Grid.Row="1" Grid.Column="1" Margin="10,2" 
        Name="cbRegex" Content="Regular Expression" />
        <CheckBox Grid.Row="2" Grid.Column="1" Margin="10,2" 
        Name="cbWildcards" Content="Wildcards" />
        <CheckBox Grid.Row="3" Grid.Column="1" Margin="10,2" 
        Name="cbSearchUp" Content="Search up" />
    </Grid>
</Window>

And here's the code-behind for your "FindReplaceDialog.xaml.cs" file...

using System.Text.RegularExpressions;
using System.Windows;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;

namespace FindReplace
{
    /// <summary>
    /// Interaction logic for FindReplaceDialog.xaml
    /// </summary>
    public partial class FindReplaceDialog : Window
    {
        private static string textToFind = "";
        private static bool caseSensitive = true;
        private static bool wholeWord = true;
        private static bool useRegex = false;
        private static bool useWildcards = false;
        private static bool searchUp = false;

        private TextEditor editor;

        public FindReplaceDialog(TextEditor editor)
        {            
            InitializeComponent();

            this.editor = editor;

            txtFind.Text = txtFind2.Text = textToFind;
            cbCaseSensitive.IsChecked = caseSensitive;
            cbWholeWord.IsChecked = wholeWord;
            cbRegex.IsChecked = useRegex;
            cbWildcards.IsChecked = useWildcards;
            cbSearchUp.IsChecked = searchUp;
        }

        private void Window_Closed(object sender, System.EventArgs e)
        {
            textToFind = txtFind2.Text;
            caseSensitive = (cbCaseSensitive.IsChecked == true);
            wholeWord = (cbWholeWord.IsChecked == true);
            useRegex = (cbRegex.IsChecked == true);
            useWildcards = (cbWildcards.IsChecked == true);
            searchUp = (cbSearchUp.IsChecked == true);

            theDialog = null;
        }

        private void FindNextClick(object sender, RoutedEventArgs e)
        {
            if (!FindNext(txtFind.Text))
                SystemSounds.Beep.Play();
        }

        private void FindNext2Click(object sender, RoutedEventArgs e)
        {
            if (!FindNext(txtFind2.Text))
                SystemSounds.Beep.Play();
        }

        private void ReplaceClick(object sender, RoutedEventArgs e)
        {
            Regex regex = GetRegEx(txtFind2.Text);
            string input = editor.Text.Substring(editor.SelectionStart, editor.SelectionLength);
            Match match = regex.Match(input);
            bool replaced = false;
            if (match.Success && match.Index == 0 && match.Length == input.Length)
            {
                editor.Document.Replace(editor.SelectionStart, editor.SelectionLength, txtReplace.Text);
                replaced = true;
            }

            if (!FindNext(txtFind2.Text) && !replaced)
                SystemSounds.Beep.Play();
        }

        private void ReplaceAllClick(object sender, RoutedEventArgs e)
        {
            if (MessageBox.Show("Are you sure you want to Replace All occurences of \"" + 
            txtFind2.Text + "\" with \"" + txtReplace.Text + "\"?",
                "Replace All", MessageBoxButton.OKCancel, MessageBoxImage.Question) == MessageBoxResult.OK)
            {
                Regex regex = GetRegEx(txtFind2.Text, true);
                int offset = 0;
                editor.BeginChange();
                foreach (Match match in regex.Matches(editor.Text))
                {
                    editor.Document.Replace(offset + match.Index, match.Length, txtReplace.Text);
                    offset += txtReplace.Text.Length - match.Length;
                }
                editor.EndChange();
            }
        }

        private bool FindNext(string textToFind)
        {
            Regex regex = GetRegEx(textToFind);
            int start = regex.Options.HasFlag(RegexOptions.RightToLeft) ? 
            editor.SelectionStart : editor.SelectionStart + editor.SelectionLength;
            Match match = regex.Match(editor.Text, start);

            if (!match.Success)  // start again from beginning or end
            {
                if (regex.Options.HasFlag(RegexOptions.RightToLeft))
                    match = regex.Match(editor.Text, editor.Text.Length);
                else
                    match = regex.Match(editor.Text, 0);
            }

            if (match.Success)
            {
                editor.Select(match.Index, match.Length);
                TextLocation loc = editor.Document.GetLocation(match.Index);
                editor.ScrollTo(loc.Line, loc.Column);
            }

            return match.Success;
        }

        private Regex GetRegEx(string textToFind, bool leftToRight = false)
        {
            RegexOptions options = RegexOptions.None;
            if (cbSearchUp.IsChecked == true && !leftToRight)
                options |= RegexOptions.RightToLeft;
            if (cbCaseSensitive.IsChecked == false)
                options |= RegexOptions.IgnoreCase;

            if (cbRegex.IsChecked == true)
            {
                return new Regex(textToFind, options);
            }
            else
            {
                string pattern = Regex.Escape(textToFind);
                if (cbWildcards.IsChecked == true)
                    pattern = pattern.Replace("\\*", ".*").Replace("\\?", ".");
                if (cbWholeWord.IsChecked == true)
                    pattern = "\\b" + pattern + "\\b";
                return new Regex(pattern, options);
            }
        }

        private static FindReplaceDialog theDialog = null;

        public static void ShowForReplace(TextEditor editor)
        {
            if (theDialog == null)
            {
                theDialog = new FindReplaceDialog(editor);
                theDialog.tabMain.SelectedIndex = 1;
                theDialog.Show();
                theDialog.Activate();
            }
            else
            {
                theDialog.tabMain.SelectedIndex = 1;
                theDialog.Activate();
            }

            if (!editor.TextArea.Selection.IsMultiline)
            {
                theDialog.txtFind.Text = theDialog.txtFind2.Text = editor.TextArea.Selection.GetText();
                theDialog.txtFind.SelectAll();
                theDialog.txtFind2.SelectAll();
                theDialog.txtFind2.Focus();
            }
        }
    }
} 

You can open this Find and Replace tool in your ApplicationCommands.Replace handler by simply calling the static method...

 FindReplaceDialog.ShowForReplace(myAvalonEditor); 

I hope that you find this code useful!

-Bruce