Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

.NET Regular Expressions Find and Replace Add-In for Visual Studio 2008

, 12 Oct 2009 CPOL
A .NET Regular Expressions Find and Replace add-in for Visual Studio 2008
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using Extensibility;
using EnvDTE;
using EnvDTE80;
using System.Runtime.InteropServices;
using System.IO;

namespace RegexFindAndReplace
{
    public partial class FindAndReplaceForm : Form
    {
        /// <summary>
        /// The CaptureInfo struct is used to hold information about a captured
        /// sub-expression in the regex when building the "Captured sub-expressions"
        /// menu item
        /// </summary>
        struct CaptureInfo
        {
            public int Start;
            public int Length;
            public string Name;
            public int Number;

            public CaptureInfo( int start, int length, string name, int number )
            {
                Start = start;
                Length = length;
                Name = name;
                Number = number;
            }
        }

        private DTE2 applicationObject;
        private FinderAndReplacer finderAndReplacer;
        private MultipleFinderAndReplacer multipleFinderAndReplacer;
        private List<CaptureInfo> captureInfoList;
        private Dictionary<string, CaptureInfo> captureInfoHash;
        private int regexSelectionStart;
        private int regexSelectionLength;
        private SettingsCache settingsCache;

        /// <summary>
        /// Initializes a new instance of the FindAndReplaceForm class.
        /// </summary>
        private FindAndReplaceForm()
        {
            InitializeComponent();
            this.captureInfoList = new List<CaptureInfo>();
            this.captureInfoHash = new Dictionary<string, CaptureInfo>();
            this.settingsCache = new SettingsCache();

            // add the handler that highlights captured sub-expressions in the regex
            for ( int i = 0; i < this.regexHelpContextMenuStrip.Items.Count; i++ )
            {
                this.regexHelpContextMenuStrip.Items[ i ].MouseEnter += new EventHandler( this.HandleHelpContextMenuStripItemMouseEnter );
            }

            for ( int i = 0; i < this.replacementHelpContextMenuStrip.Items.Count; i++ )
            {
                this.replacementHelpContextMenuStrip.Items[ i ].MouseEnter += new EventHandler( this.HandleHelpContextMenuStripItemMouseEnter );
            }
        }

        /// <summary>
        /// Initializes a new instance of the FindAndReplaceForm class.
        /// </summary>
        /// <param name="applicationObject">The application object.</param>
        public FindAndReplaceForm( DTE2 applicationObject )
            : this()
        {
            this.applicationObject = applicationObject;
            this.finderAndReplacer = new FinderAndReplacer( this.applicationObject );
            this.multipleFinderAndReplacer = new MultipleFinderAndReplacer( this.applicationObject );

            int textDocumentCount = 0;

            // determine if the current document is a text document
            if ( this.applicationObject.ActiveDocument != null && this.applicationObject.ActiveDocument.Object( string.Empty ) is TextDocument )
            {
                // add the current document option
                this.lookInComboBox.Items.Add( Strings.CURRENT_DOCUMENT );
            }

            // determine if any text documents are open
            foreach ( Document document in this.applicationObject.Documents )
            {
                if ( document.Object( string.Empty ) is TextDocument )
                {
                    if ( ++textDocumentCount > 1 )
                    {
                        break;
                    }
                }
            }

            // if there is more than one text documents open
            if ( textDocumentCount > 0 )
            {
                if ( textDocumentCount > 1 )
                {
                    // add the all open documents option
                    this.lookInComboBox.Items.Add( Strings.ALL_OPEN_DOCUMENTS );
                }
            }

            // if a solution has been opened
            if ( this.applicationObject.Solution.IsOpen )
            {
                // add the current project option
                this.lookInComboBox.Items.Add( Strings.CURRENT_PROJECT );

                // if there is more than one project
                if ( this.applicationObject.Solution.Projects.Count > 1 )
                {
                    // add the entire solution option
                    this.lookInComboBox.Items.Add( Strings.ENTIRE_SOLUTION );
                }
            }

            // determine if text is selected in the current document
            if ( this.finderAndReplacer.GetTextSelection() != null && !this.finderAndReplacer.GetTextSelection().IsEmpty )
            {
                // add the selection option.

                int position = this.lookInComboBox.Items.Count > 0 ? 1 : 0;

                this.lookInComboBox.Items.Insert( position, Strings.SELECTION );
                this.lookInComboBox.SelectedIndex = position;
            }
            else if ( this.lookInComboBox.Items.Count > 0 )
            {
                this.lookInComboBox.SelectedIndex = 0;
            }
            else
            {
                // disable the buttons if nothing is available to search
                this.findNextButton.Enabled = false;
                this.replaceButton.Enabled = false;
                this.replaceAllButton.Enabled = false;
                this.skipFileButton.Enabled = false;
            }

            try
            {
                // try to get the text editor's font
                Properties textEditorProperties = this.applicationObject.get_Properties( "FontsAndColors", "TextEditor" );
                Property textEditorFontFamily = textEditorProperties.Item( "FontFamily" );
                Property textEditorFontSize = textEditorProperties.Item( "FontSize" );

                // set the font of the pattern editors
                this.regexComboBox.Font = this.regexTextBox.Font = this.replacementComboBox.Font = this.replacementTextBox.Font =
                    new Font( textEditorFontFamily.Value.ToString(), float.Parse( textEditorFontSize.Value.ToString() ) );
            }
            catch
            {
                // use a generic momospace font on failure
                this.regexComboBox.Font = this.regexTextBox.Font = this.replacementComboBox.Font = this.replacementTextBox.Font =
                    new Font( FontFamily.GenericMonospace, 8.25f );
            }

            Stream bitmapStream = null;

            try
            {
                // try to set the arrow image on the pattern help buttons
                bitmapStream = this.GetType().Assembly.GetManifestResourceStream( "RegexFindAndReplace.arrow.bmp" );

                if ( bitmapStream != null )
                {
                    Bitmap arrowBitmap = new Bitmap( bitmapStream );
                    arrowBitmap.MakeTransparent( Color.White );

                    this.regexHelpButton.Image = arrowBitmap;
                    this.replacementHelpButton.Image = arrowBitmap;
                }
            }
            catch
            {
                bitmapStream = null;
            }
            finally
            {
                if ( bitmapStream == null )
                {
                    this.regexHelpButton.Text = ">";
                    this.replacementHelpButton.Text = ">";
                }
            }
        }

        /// <summary>
        /// Gets settings that were cached in the registry
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void HandleFindAndReplaceFormLoad( object sender, EventArgs e )
        {
            this.findAndReplaceTypeButton.Text = this.settingsCache.GetCachedSetting( Strings.Settings.DIALOG_TYPE, Strings.REGEX_FIND_AND_REPLACE );

            UpdateOptionsFromCache( this.regexComboBox, PatternType.Regex );
            UpdateOptionsFromCache( this.replacementComboBox, PatternType.Replacement );

            this.regexTextBox.Text = this.regexComboBox.Text = 
                this.settingsCache.GetCachedSetting( Strings.Settings.SELECTED_REGEX, string.Empty );
            this.replacementTextBox.Text = this.replacementComboBox.Text = 
                this.settingsCache.GetCachedSetting( Strings.Settings.SELECTED_REPLACEMENT, string.Empty );

            string cachedLookIn = this.settingsCache.GetCachedSetting( Strings.Settings.SELECTED_LOOK_IN, Strings.CURRENT_PROJECT );
            
            // set the look in field if it is a valid option or if it is a directory
            if ( this.lookInComboBox.Items.Contains( cachedLookIn ) || ( Directory.Exists( cachedLookIn ) && this.findAndReplaceTypeButton.Text != Strings.REGEX_FIND_AND_REPLACE ) )
            {
                this.lookInComboBox.Text = cachedLookIn;

                // enable the buttons
                this.findNextButton.Enabled = true;
                this.replaceButton.Enabled = true;
                this.replaceAllButton.Enabled = true;
                this.skipFileButton.Enabled = true;
            }
            
            this.includeSubDirectoriesCheckBox.Checked = bool.Parse( this.settingsCache.GetCachedSetting( Strings.Settings.INCLUDE_SUB_DIRECTORIES, "true" ) );

            SetRegexOptions( (RegexOptions)int.Parse( this.settingsCache.GetCachedSetting( Strings.Settings.REGEX_OPTIONS, "0" ) ) );

            this.fileTypesComboBox.Text = this.settingsCache.GetCachedSetting( Strings.Settings.SELECTED_FILE_TYPES, "*.*" );

            this.displayFileNamesCheckBox.Checked = bool.Parse( this.settingsCache.GetCachedSetting( Strings.Settings.DISPLAY_FILE_NAMES_ONLY, "false" ) );
            this.keepFilesOpenCheckBox.Checked = bool.Parse( this.settingsCache.GetCachedSetting( Strings.Settings.KEEP_MODIFIED_FILES_OPEN, "false" ) );
            this.matchContextBeforeNumericUpDown.Value = decimal.Parse( this.settingsCache.GetCachedSetting( Strings.Settings.MATCH_CONTEXT_BEFORE_LINE_COUNT, "0" ) );
            this.matchContextAfterNumericUpDown.Value = decimal.Parse( this.settingsCache.GetCachedSetting( Strings.Settings.MATCH_CONTEXT_AFTER_LINE_COUNT, "0" ) );

            UpdateConditionallyOptionalControls();
        }

        /// <summary>
        /// Caches settings on the dialog
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.Windows.Forms.FormClosedEventArgs"/> instance containing the event data.</param>
        private void HandleFindAndReplaceFormFormClosed( object sender, FormClosedEventArgs e )
        {
            this.settingsCache.CacheSetting( Strings.Settings.DIALOG_TYPE, this.findAndReplaceTypeButton.Text );

            if ( this.regexComboBox.SelectedIndex != -1 && this.regexComboBox.Text != string.Empty )
            {
                this.settingsCache.CacheSetting( Strings.Settings.SELECTED_REGEX, this.regexTextBox.Text );
            }

            if ( this.replacementComboBox.SelectedIndex != -1 && this.replacementComboBox.Text != string.Empty )
            {
                this.settingsCache.CacheSetting( Strings.Settings.SELECTED_REPLACEMENT, this.replacementTextBox.Text );
            }

            if ( this.lookInComboBox.Text != string.Empty )
            {
                this.settingsCache.CacheSetting( Strings.Settings.SELECTED_LOOK_IN, this.lookInComboBox.Text );
            }

            this.settingsCache.CacheSetting( Strings.Settings.INCLUDE_SUB_DIRECTORIES, this.includeSubDirectoriesCheckBox.Checked.ToString() );

            this.settingsCache.CacheSetting( Strings.Settings.REGEX_OPTIONS, ( (int)GetRegexOptions() ).ToString() );

            if ( this.fileTypesComboBox.Text != string.Empty )
            {
                this.settingsCache.CacheSetting( Strings.Settings.SELECTED_FILE_TYPES, this.fileTypesComboBox.Text );
            }

            this.settingsCache.CacheSetting( Strings.Settings.DISPLAY_FILE_NAMES_ONLY, this.displayFileNamesCheckBox.Checked.ToString() );
            this.settingsCache.CacheSetting( Strings.Settings.KEEP_MODIFIED_FILES_OPEN, this.keepFilesOpenCheckBox.Checked.ToString() );
            this.settingsCache.CacheSetting( Strings.Settings.MATCH_CONTEXT_BEFORE_LINE_COUNT, this.matchContextBeforeNumericUpDown.Value.ToString() );
            this.settingsCache.CacheSetting( Strings.Settings.MATCH_CONTEXT_AFTER_LINE_COUNT, this.matchContextAfterNumericUpDown.Value.ToString() );
        }

        /// <summary>
        /// Selects the text in the regex text box when it receives focus
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void HandleRegexTextBoxEnter( object sender, EventArgs e )
        {
            this.regexTextBox.Select( 0, this.regexTextBox.TextLength );
        }

        /// <summary>
        /// Emulates common combobox keyboard operations when the regex text box has focus
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.Windows.Forms.KeyEventArgs"/> instance containing the event data.</param>
        private void HandleRegexTextBoxKeyDown( object sender, KeyEventArgs e )
        {
            if ( e.KeyCode == Keys.Up )
            {
                if ( this.regexTextBox.GetLineFromCharIndex( this.regexTextBox.SelectionStart ) == 0 )
                {
                    if ( this.regexComboBox.SelectedIndex > 0 )
                    {
                        --this.regexComboBox.SelectedIndex;
                    }
                }
            }
            else if ( e.KeyCode == Keys.Down )
            {
                if ( ( e.Modifiers & Keys.Alt ) == Keys.Alt )
                {
                    this.regexComboBox.DroppedDown = true;
                }
                else
                {
                    if ( this.regexTextBox.GetLineFromCharIndex( this.regexTextBox.SelectionStart ) == this.regexTextBox.Lines.Length - 1 )
                    {
                        if ( this.regexComboBox.SelectedIndex < this.regexComboBox.Items.Count - 1 )
                        {
                            ++this.regexComboBox.SelectedIndex;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Sets the text of the regex text box when the regex combo box selection changes
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void HandleRegexComboBoxSelectedIndexChanged( object sender, EventArgs e )
        {
            this.regexTextBox.Text = this.regexComboBox.Text;
            this.regexTextBox.Focus();
            this.regexTextBox.Select( 0, this.regexTextBox.TextLength );
        }

        /// <summary>
        /// Selects the text in the replacement text box when it receives focus
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void HandleReplacementTextBoxEnter( object sender, EventArgs e )
        {
            this.replacementTextBox.Select( 0, this.replacementTextBox.TextLength );
        }

        /// <summary>
        /// Emulates common combobox keyboard operations when the replacement text box has focus
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.Windows.Forms.KeyEventArgs"/> instance containing the event data.</param>
        private void HandleReplacementTextBoxKeyDown( object sender, KeyEventArgs e )
        {
            if ( e.KeyCode == Keys.Up )
            {
                if ( this.replacementTextBox.GetLineFromCharIndex( this.replacementTextBox.SelectionStart ) == 0 )
                {
                    if ( this.replacementComboBox.SelectedIndex > 0 )
                    {
                        --this.replacementComboBox.SelectedIndex;
                    }
                }
            }
            else if ( e.KeyCode == Keys.Down )
            {
                if ( ( e.Modifiers & Keys.Alt ) == Keys.Alt )
                {
                    this.replacementComboBox.DroppedDown = true;
                }
                else
                {
                    if ( this.replacementTextBox.GetLineFromCharIndex( this.replacementTextBox.SelectionStart ) == this.replacementTextBox.Lines.Length - 1 )
                    {
                        if ( this.replacementComboBox.SelectedIndex < this.replacementComboBox.Items.Count - 1 )
                        {
                            ++this.replacementComboBox.SelectedIndex;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Sets the text of the replacement text box when the replacement combo box selection changes
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void HandleReplacementComboBoxSelectedIndexChanged( object sender, EventArgs e )
        {
            this.replacementTextBox.Text = this.replacementComboBox.Text;
            this.replacementTextBox.Focus();
            this.replacementTextBox.Select( 0, this.replacementTextBox.TextLength );
        }

        /// <summary>
        /// Called by the owner-drawn pattern comboboxes to determin the height
        /// and width of the available options
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.Windows.Forms.MeasureItemEventArgs"/> instance containing the event data.</param>
        private void HandleComboBoxMeasureItem( object sender, MeasureItemEventArgs e )
        {
            ComboBox comboBox = (ComboBox)sender;
            SizeF optionSize = e.Graphics.MeasureString( comboBox.Items[ e.Index ].ToString(), comboBox.Font );

            e.ItemWidth = (int)optionSize.Width;
            e.ItemHeight = (int)optionSize.Height;

            // increase the width of the combobox to avoid showing a scrollbar
            if ( e.ItemWidth > comboBox.DropDownWidth )
            {
                comboBox.DropDownWidth = e.ItemWidth;
            }
        }

        /// <summary>
        /// Called by the owner-drawn pattern comboboxes to draw the options in the dropped-down menu
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.Windows.Forms.DrawItemEventArgs"/> instance containing the event data.</param>
        private void HandleComboBoxDrawItem( object sender, DrawItemEventArgs e )
        {
            ComboBox comboBox = (ComboBox)sender;

            if ( e.State == ( DrawItemState.Selected | DrawItemState.NoAccelerator | DrawItemState.NoFocusRect ) || e.State == DrawItemState.Selected )
            {
                e.Graphics.FillRectangle(
                    new SolidBrush( SystemColors.Highlight ),
                    new Rectangle( e.Bounds.X, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height ) );

                e.Graphics.DrawString(
                    comboBox.Items[ e.Index ].ToString(),
                    comboBox.Font,
                    new SolidBrush( SystemColors.HighlightText ),
                    new Rectangle( e.Bounds.X, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height ),
                    StringFormat.GenericDefault );
            }
            else
            {
                e.Graphics.FillRectangle(
                    new SolidBrush( Color.White ),
                    new Rectangle( e.Bounds.X, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height ) );

                e.Graphics.DrawString(
                    comboBox.Items[ e.Index ].ToString(),
                    comboBox.Font,
                    new SolidBrush( Color.Black ),
                    new Rectangle( e.Bounds.X, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height ),
                    StringFormat.GenericDefault );
            }
        }

        /// <summary>
        /// Displays the regex help context menu
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void HandleRegexHelpButtonClick( object sender, EventArgs e )
        {
            this.regexSelectionStart = this.regexTextBox.SelectionStart;
            this.regexSelectionLength = this.regexTextBox.SelectionLength;

            // check the regex for sub-expressions
            FindCapturedSubExpressions();

            while ( this.regexHelpContextMenuStrip.Items.Count > 18 )
            {
                this.regexHelpContextMenuStrip.Items.RemoveAt( 18 );
            }

            if ( this.captureInfoList.Count > 0 )
            {
                this.captureInfoHash.Clear();
                ToolStripMenuItem subExpressionsMenuItem = new ToolStripMenuItem( "Captured sub-expressions" );

                // create a menu item for each sub-expression in the regex
                for ( int i = 0; i < this.captureInfoList.Count; i++ )
                {
                    string menuItemText = string.Empty;

                    if ( this.captureInfoList[ i ].Name != null )
                    {
                        menuItemText = string.Format( @"\k<{0}>", this.captureInfoList[ i ].Name );
                    }
                    else
                    {
                        menuItemText = string.Format( @"\{0}", this.captureInfoList[ i ].Number );
                    }

                    subExpressionsMenuItem.DropDownItems.Add( menuItemText );
                    subExpressionsMenuItem.DropDownItems[ subExpressionsMenuItem.DropDownItems.Count - 1 ].MouseEnter += new EventHandler( HandleHelpContextMenuStripItemMouseEnter );
                    subExpressionsMenuItem.DropDownItems[ subExpressionsMenuItem.DropDownItems.Count - 1 ].Click += new EventHandler( HandleRegexSubExpressionItemClicked );

                    this.captureInfoHash[ menuItemText ] = this.captureInfoList[ i ];
                }

                this.regexHelpContextMenuStrip.Items.Add( new ToolStripSeparator() );
                this.regexHelpContextMenuStrip.Items.Add( subExpressionsMenuItem );
            }

            this.regexHelpContextMenuStrip.Show( this, new Point( this.regexHelpButton.Right, this.regexHelpButton.Top ) );
        }


        /// <summary>
        /// Redirects a click event to HandleRegexHelpContextMenuStripItemClicked
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        void HandleRegexSubExpressionItemClicked( object sender, EventArgs e )
        {
            HandleRegexHelpContextMenuStripItemClicked( sender, new ToolStripItemClickedEventArgs( (ToolStripItem)sender ) );
        }

        /// <summary>
        /// Called when a menu item in the regex help context menu is clicked.  Inserts a regex expression into the current regex
        /// at the current cursor position
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.Windows.Forms.ToolStripItemClickedEventArgs"/> instance containing the event data.</param>
        private void HandleRegexHelpContextMenuStripItemClicked( object sender, ToolStripItemClickedEventArgs e )
        {
            int selectionStart = this.regexSelectionStart;

            string genericNestedRegex = @"<Start>(?>[^<Start><End>]+|<Start>(?<Depth>)|<End>(?<-Depth>))*(?(Depth)(?!))<End>";
            string genericNestedRegexWithWhiteSpace =
@"<Start>
  (?>
    [^<Start><End>]+
  |
    <Start>(?<Depth>)
  |
    <End>(?<-Depth>)
  )*
  (?(Depth)(?!))
<End>";

            this.regexTextBox.SelectionStart = 0;
            this.regexTextBox.SelectionLength = this.regexTextBox.Text.Length;
            this.regexTextBox.SelectionColor = Color.Black;

            this.regexTextBox.SelectionStart = this.regexSelectionStart;
            this.regexTextBox.SelectionLength = this.regexSelectionLength;

            if ( e.ClickedItem == anySingleCharacterToolStripMenuItem )
            {
                this.regexTextBox.SelectedText = ".";
                selectionStart += 1;
            }
            else if ( e.ClickedItem == zeroOrMoreToolStripMenuItem )
            {
                this.regexTextBox.SelectedText = "*";
                selectionStart += 1;
            }
            else if ( e.ClickedItem == oneOrMoreToolStripMenuItem )
            {
                this.regexTextBox.SelectedText = "+";
                selectionStart += 1;
            }
            else if ( e.ClickedItem == alternationToolStripMenuItem )
            {
                this.regexTextBox.SelectedText = "|";
                selectionStart += 1;
            }
            else if ( e.ClickedItem == characterClassToolStripMenuItem )
            {
                this.regexTextBox.SelectedText = "[]";
                selectionStart += 1;
            }
            else if ( e.ClickedItem == negatedCharacterClassToolStripMenuItem )
            {
                this.regexTextBox.SelectedText = "[^]";
                selectionStart += 2;
            }
            else if ( e.ClickedItem == beginningOfLineToolStripMenuItem )
            {
                this.regexTextBox.SelectedText = "^";
                selectionStart += 1;
            }
            else if ( e.ClickedItem == endOfLineToolStripMenuItem )
            {
                this.regexTextBox.SelectedText = "$";
                selectionStart += 1;
            }
            else if ( e.ClickedItem == wordBoundryToolStripMenuItem )
            {
                this.regexTextBox.SelectedText = @"\b";
                selectionStart += 2;
            }
            else if ( e.ClickedItem == positiveLookaheadToolStripMenuItem )
            {
                this.regexTextBox.SelectedText = "(?=)";
                selectionStart += 3;
            }
            else if ( e.ClickedItem == negativeLookaheadToolStripMenuItem )
            {
                this.regexTextBox.SelectedText = "(?!)";
                selectionStart += 3;
            }
            else if ( e.ClickedItem == positiveLookbehindToolStripMenuItem )
            {
                this.regexTextBox.SelectedText = "(?<=)";
                selectionStart += 4;
            }
            else if ( e.ClickedItem == negativeLookbehindToolStripMenuItem )
            {
                this.regexTextBox.SelectedText = "(?<!)";
                selectionStart += 4;
            }
            else if ( e.ClickedItem == nameNamedCaptureToolStripMenuItem )
            {
                this.regexTextBox.SelectedText = "(?<Name>)";
                selectionStart += 8;
            }
            else if ( e.ClickedItem == groupingOnlyParenthesesToolStripMenuItem )
            {
                this.regexTextBox.SelectedText = "(?:)";
                selectionStart += 3;
            }
            else if ( e.ClickedItem == atomicGroupingToolStripMenuItem )
            {
                this.regexTextBox.SelectedText = "(?>)";
                selectionStart += 3;
            }
            else if ( e.ClickedItem == nestedBracesToolStripMenuItem )
            {
                string nestedBraceRegex = string.Empty;

                if ( this.ignoreWhitespaceCheckBox.Checked )
                {
                    nestedBraceRegex = genericNestedRegexWithWhiteSpace.Replace( "<Start>", "{" ).Replace( "<End>", "}" );
                }
                else
                {
                    nestedBraceRegex = genericNestedRegex.Replace( "<Start>", "{" ).Replace( "<End>", "}" );
                }

                this.regexTextBox.SelectedText = nestedBraceRegex;
                selectionStart += nestedBraceRegex.Length;
            }
            else if ( e.ClickedItem == nestedParenthesesToolStripMenuItem )
            {
                string nestedParenthesesRegex = string.Empty;

                if ( this.ignoreWhitespaceCheckBox.Checked )
                {
                    nestedParenthesesRegex = genericNestedRegexWithWhiteSpace.Replace( "<Start>", @"\(" ).Replace( "<End>", @"\)" );
                }
                else
                {
                    nestedParenthesesRegex = genericNestedRegex.Replace( "<Start>", @"\(" ).Replace( "<End>", @"\)" );
                }

                this.regexTextBox.SelectedText = nestedParenthesesRegex;
                selectionStart += nestedParenthesesRegex.Length;
            }
            else
            {
                this.regexTextBox.SelectedText = e.ClickedItem.Text;
                selectionStart += e.ClickedItem.Text.Length;
            }

            this.regexTextBox.Focus();
            this.regexSelectionStart = this.regexTextBox.SelectionStart = selectionStart;
        }

        /// <summary>
        /// Displays the replacement help context menu
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void HandleReplacementHelpButtonClick( object sender, EventArgs e )
        {
            this.regexSelectionStart = this.regexTextBox.SelectionStart;
            this.regexSelectionLength = this.regexTextBox.SelectionLength;

            // check the regex for sub-expressions
            FindCapturedSubExpressions();

            while ( this.replacementHelpContextMenuStrip.Items.Count > 3 )
            {
                this.replacementHelpContextMenuStrip.Items.RemoveAt( 3 );
            }

            if ( this.captureInfoList.Count > 0 )
            {
                this.captureInfoHash.Clear();

                ToolStripMenuItem subExpressionsMenuItem = new ToolStripMenuItem( "Captured sub-expressions" );

                // create a menu item for each captured sub-expression
                for ( int i = 0; i < this.captureInfoList.Count; i++ )
                {
                    string menuItemText = string.Empty;

                    if ( this.captureInfoList[ i ].Name != null )
                    {
                        menuItemText = string.Format( @"${{{0}}}", this.captureInfoList[ i ].Name );
                    }
                    else
                    {
                        menuItemText = string.Format( @"${0}", this.captureInfoList[ i ].Number );
                    }

                    subExpressionsMenuItem.DropDownItems.Add( menuItemText );
                    subExpressionsMenuItem.DropDownItems[ subExpressionsMenuItem.DropDownItems.Count - 1 ].MouseEnter += new EventHandler( HandleHelpContextMenuStripItemMouseEnter );
                    subExpressionsMenuItem.DropDownItems[ subExpressionsMenuItem.DropDownItems.Count - 1 ].Click += new EventHandler( HandleReplacementSubExpressionItemClicked );

                    this.captureInfoHash[ menuItemText ] = this.captureInfoList[ i ];
                }

                this.replacementHelpContextMenuStrip.Items.Add( new ToolStripSeparator() );
                this.replacementHelpContextMenuStrip.Items.Add( subExpressionsMenuItem );
            }

            this.replacementHelpContextMenuStrip.Show( this, new Point( replacementHelpButton.Right, replacementHelpButton.Top ) );
        }

        /// <summary>
        /// Redirects a click event to HandleReplacementHelpContextMenuStripItemClicked
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        void HandleReplacementSubExpressionItemClicked( object sender, EventArgs e )
        {
            HandleReplacementHelpContextMenuStripItemClicked( sender, new ToolStripItemClickedEventArgs( (ToolStripItem)sender ) );
        }

        /// <summary>
        /// Called when a menu item in the replacement help context menu is clicked.  Inserts a replacement expression into the current replacement
        /// at the current cursor position
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.Windows.Forms.ToolStripItemClickedEventArgs"/> instance containing the event data.</param>
        private void HandleReplacementHelpContextMenuStripItemClicked( object sender, ToolStripItemClickedEventArgs e )
        {
            int selectionStart = this.replacementTextBox.SelectionStart;

            if ( e.ClickedItem == matchedTextToolStripMenuItem )
            {
                this.replacementTextBox.SelectedText = "$&";
                selectionStart += 2;
            }
            else if ( e.ClickedItem == textBeforeMatchToolStripMenuItem )
            {
                this.replacementTextBox.SelectedText = "$`";
                selectionStart += 2;
            }
            else if ( e.ClickedItem == textAfterMatchToolStripMenuItem )
            {
                this.replacementTextBox.SelectedText = "$'";
                selectionStart += 2;
            }
            else
            {
                this.replacementTextBox.SelectedText = e.ClickedItem.Text;
                selectionStart += e.ClickedItem.Text.Length;
            }

            this.replacementTextBox.Focus();
            this.replacementTextBox.SelectionStart = selectionStart;

            this.regexTextBox.SelectionStart = 0;
            this.regexTextBox.SelectionLength = this.regexTextBox.Text.Length;
            this.regexTextBox.SelectionColor = Color.Black;
        }

        /// <summary>
        /// Parses the current regex for captured sub-expressions and stores information about each expression for the
        /// creation of menu items
        /// </summary>
        private void FindCapturedSubExpressions()
        {
            Regex balancedParenthesesRegex = new Regex( @"^\((?!\?:)(\?<(?<Name>\w+)>)?(?>[^()]+|\((?<Depth>)|\)(?<-Depth>))*(?(Depth)(?!))\)" );
            int iCapturedSubExpressionCount = 1;

            while ( this.replacementHelpContextMenuStrip.Items.Count > 3 )
            {
                this.replacementHelpContextMenuStrip.Items.RemoveAt( 3 );
            }

            this.captureInfoList.Clear();

            for ( int i = 0; i < this.regexTextBox.Text.Length; i++ )
            {
                int escapeCount = 0;

                for ( int j = i; j > 0; j-- )
                {
                    if ( this.regexTextBox.Text[ j ] == '\\' )
                    {
                        ++escapeCount;
                    }
                    else
                    {
                        break;
                    }
                }

                if ( ( escapeCount & 1 ) == 1 )
                {
                    continue;
                }

                Match balancedParenthesesMatch = balancedParenthesesRegex.Match( this.regexTextBox.Text.Substring( i ) );

                if ( balancedParenthesesMatch.Success )
                {
                    string menuItemText = string.Empty;

                    if ( balancedParenthesesMatch.Groups[ "Name" ].Value != string.Empty )
                    {
                        this.captureInfoList.Add( new CaptureInfo( i, balancedParenthesesMatch.Length, balancedParenthesesMatch.Groups[ "Name" ].Value, -1 ) );
                    }
                    else if ( !this.explicitCaptureCheckBox.Checked )
                    {
                        this.captureInfoList.Add( new CaptureInfo( i, balancedParenthesesMatch.Length, null, iCapturedSubExpressionCount++ ) );
                    }
                }
            }
        }

        /// <summary>
        /// Called when the mouse enters a menu item in the regex and replacement context menus.  If the item is for 
        /// a captured sub-expression, that expression is highlighted in blue in the regex.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void HandleHelpContextMenuStripItemMouseEnter( object sender, EventArgs e )
        {
            this.regexTextBox.SelectionStart = 0;
            this.regexTextBox.SelectionLength = this.regexTextBox.Text.Length;
            this.regexTextBox.SelectionColor = Color.Black;

            if ( this.captureInfoHash.ContainsKey( ( sender as ToolStripItem ).Text ) )
            {
                CaptureInfo captureInfo = this.captureInfoHash[ ( sender as ToolStripItem ).Text ];

                this.regexTextBox.SelectionStart = captureInfo.Start;
                this.regexTextBox.SelectionLength = captureInfo.Length;

                this.regexTextBox.SelectionColor = Color.Blue;
            }
        }

        /// <summary>
        /// Clears any highlighted text in the regex and reset the selection
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.Windows.Forms.ToolStripDropDownClosingEventArgs"/> instance containing the event data.</param>
        private void HelpContextMenuStripClosing( object sender, ToolStripDropDownClosingEventArgs e )
        {
            this.regexTextBox.SelectionStart = 0;
            this.regexTextBox.SelectionLength = this.regexTextBox.Text.Length;
            this.regexTextBox.SelectionColor = Color.Black;

            this.regexTextBox.SelectionStart = this.regexSelectionStart;
            this.regexTextBox.SelectionLength = this.regexSelectionLength;
        }

        /// <summary>
        /// Called when the dialog type is changed
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void HandleFindAndReplaceTypeMenuItemClick( object sender, EventArgs e )
        {
            this.findAndReplaceTypeButton.Text = ( (ToolStripMenuItem)sender ).Text;
        }

        /// <summary>
        /// Called when the type of the dialog is changed using the findAndReplaceTypeButton.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void HandleFindAndReplaceTypeButtonTextChanged( object sender, EventArgs e )
        {
            UpdateControlPositions();
        }

        /// <summary>
        /// Displays or hides controls depending on the type of the dialog, and the state of the group boxes
        /// </summary>
        private void UpdateControlPositions()
        {
            if ( this.findAndReplaceTypeButton.Text == Strings.REGEX_FIND_AND_REPLACE )
            {
                this.lookInComboBox.Width = this.lookInBrowseButton.Right - this.lookInComboBox.Left;

                this.includeSubDirectoriesPanel.Visible = false;
                this.standardButtonsPanel.Visible = true;
                this.skipFileButton.Visible = true;
                this.fileTypesPanel.Visible = false;
                this.resultOptionsPanel.Visible = false;
                this.findAllButtonPanel.Visible = false;
                this.regexOptionsPanel.Top = this.lookInComboBox.Bottom;
                this.standardButtonsPanel.Top = this.regexOptionsPanel.Bottom;
                this.ClientSize = new Size( this.ClientSize.Width, this.standardButtonsPanel.Bottom );
            }
            else if ( this.findAndReplaceTypeButton.Text == Strings.REGEX_FIND_IN_FILES )
            {
                this.lookInComboBox.Width = this.regexComboBox.Width;

                this.includeSubDirectoriesPanel.Visible = true;
                this.standardButtonsPanel.Visible = false;
                this.skipFileButton.Visible = false;
                this.fileTypesPanel.Visible = true;
                this.resultOptionsPanel.Visible = true;
                this.findAllButtonPanel.Visible = true;
                this.includeSubDirectoriesPanel.Top = this.lookInComboBox.Bottom;
                this.regexOptionsPanel.Top = this.includeSubDirectoriesPanel.Bottom;
                this.findAllButtonPanel.Top = this.resultOptionsPanel.Bottom;
                this.ClientSize = new Size( this.ClientSize.Width, this.findAllButtonPanel.Bottom );

                UpdateConditionallyOptionalControls();
            }
            else
            {
                this.lookInComboBox.Width = this.regexComboBox.Width;

                this.standardButtonsPanel.Visible = true;
                this.skipFileButton.Visible = true;
                this.fileTypesPanel.Visible = true;
                this.resultOptionsPanel.Visible = true;
                this.findAllButtonPanel.Visible = false;
                this.includeSubDirectoriesPanel.Top = this.lookInComboBox.Bottom;
                this.regexOptionsPanel.Top = this.includeSubDirectoriesPanel.Bottom;
                this.standardButtonsPanel.Top = this.resultOptionsPanel.Bottom;
                this.ClientSize = new Size( this.ClientSize.Width, this.standardButtonsPanel.Bottom );

                UpdateConditionallyOptionalControls();
            }
        }

        /// <summary>
        /// Displays a FolderBrowserDialog in a find or replace in files type dialog, so a directory can be chosen for a search.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void HandleLookInBrowseButtonClick( object sender, EventArgs e )
        {
            FolderBrowserDialog folderBrowserDialog = new FolderBrowserDialog();

            if ( Directory.Exists( this.lookInComboBox.Text ) )
            {
                folderBrowserDialog.SelectedPath = this.lookInComboBox.Text;
            }
            else
            {
                if ( this.applicationObject.Solution.FullName != string.Empty )
                {
                    folderBrowserDialog.SelectedPath = Path.GetDirectoryName( this.applicationObject.Solution.FullName );
                }
                else 
                {
                    Document document = null;
                    
                    if ( ( document = (Document)this.applicationObject.ActiveDocument ) != null )
                    {
                        folderBrowserDialog.SelectedPath = Path.GetDirectoryName( document.FullName );
                    }
                }
            }

            folderBrowserDialog.Description = "Choose a directory to search.";

            if ( folderBrowserDialog.ShowDialog() == DialogResult.OK )
            {
                this.lookInComboBox.Text = folderBrowserDialog.SelectedPath;
                
                UpdateConditionallyOptionalControls();
            }
        }

        /// <summary>
        /// Called when the "Find Next" button is clicked.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void HandleFindNextButtonClick( object sender, EventArgs e )
        {
            // validate the regex and RegexOptions
            if ( ValidateRegexAndOptions() )
            {
                try
                {
                    // update the cache.
                    CachePatterns();

                    if ( this.lookInComboBox.Text == Strings.CURRENT_DOCUMENT )
                    {
                        this.finderAndReplacer.FindNext();
                    }
                    else
                    {
                        UpdateItemsToProcess();

                        if ( this.findAndReplaceTypeButton.Text != Strings.REGEX_FIND_AND_REPLACE )
                        {
                            this.multipleFinderAndReplacer.FileTypes = this.fileTypesComboBox.Text;
                            this.multipleFinderAndReplacer.IncludeSubDirectories = this.includeSubDirectoriesCheckBox.Checked;
                        }

                        this.multipleFinderAndReplacer.FindNext();
                    }

                }
                catch ( Exception ex )
                {
                    MessageBox.Show( ex.Message, "RegexFindAndReplace Error!" );
                }
                finally
                {
                    UpdateOptionsFromCache( this.regexComboBox, PatternType.Regex );
                }
            }
        }

        /// <summary>
        /// Called when the "Find All" button is clicked.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void HandleFindAllButtonClick( object sender, EventArgs e )
        {
            // validate the regex and RegexOptions
            if ( ValidateRegexAndOptions() )
            {
                try
                {
                    // update the cache
                    CachePatterns();

                    this.multipleFinderAndReplacer.FileTypes = this.fileTypesComboBox.Text;
                    this.multipleFinderAndReplacer.DisplayOnlyFileNames = this.displayFileNamesCheckBox.Checked;
                    this.multipleFinderAndReplacer.IncludeSubDirectories = this.includeSubDirectoriesCheckBox.Checked;
                    this.multipleFinderAndReplacer.MatchContextBeforeLineCount = (int)this.matchContextBeforeNumericUpDown.Value;
                    this.multipleFinderAndReplacer.MatchContextAfterLineCount = (int)this.matchContextAfterNumericUpDown.Value;

                    this.multipleFinderAndReplacer.ContinueProcessing = true;
                    this.stopButton.Enabled = this.stopButton.Visible = true;

                    foreach ( Control control in this.Controls )
                    {
                        if ( control != this.stopButton )
                        {
                            control.Enabled = false;
                        }
                    }

                    this.multipleFinderAndReplacer.FindAll( GetProcessAllScope() );

                    foreach ( Control control in this.Controls )
                    {
                        control.Enabled = true;
                    }

                    this.stopButton.Enabled = this.stopButton.Visible = false;

                    this.applicationObject.DTE.Windows.Item( Constants.vsWindowKindOutput ).Visible = true;
                }
                catch ( Exception ex )
                {
                    MessageBox.Show( ex.Message, "RegexFindAndReplace Error!" );
                }
                finally
                {
                    UpdateOptionsFromCache( this.regexComboBox, PatternType.Regex );
                }
            }
        }

        /// <summary>
        /// Called when the "Replace" button is clicked
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void HandleReplaceButtonClick( object sender, EventArgs e )
        {
            // validate the regex and RegexOptions
            if ( ValidateRegexAndOptions() )
            {
                try
                {
                    // update the cache
                    CachePatterns();

                    if ( this.lookInComboBox.Text == Strings.CURRENT_DOCUMENT )
                    {
                        this.finderAndReplacer.ReplaceNext( this.replacementTextBox.Text );
                    }
                    else
                    {
                        UpdateItemsToProcess();
                        this.multipleFinderAndReplacer.ReplaceNext( this.replacementTextBox.Text );
                    }
                }
                catch ( Exception ex )
                {
                    MessageBox.Show( ex.Message, "RegexFindAndReplace Error!" );
                }
                finally
                {
                    UpdateOptionsFromCache( this.replacementComboBox, PatternType.Replacement );
                }
            }
        }

        /// <summary>
        /// Called when the "Replace All" button is clicked
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void HandleReplaceAllButtonClick( object sender, EventArgs e )
        {
            // validate the regex and RegexOptions
            if ( ValidateRegexAndOptions() )
            {
                try
                {
                    // update the cache
                    CachePatterns();

                    this.multipleFinderAndReplacer.FileTypes = this.fileTypesComboBox.Text;
                    this.multipleFinderAndReplacer.DisplayOnlyFileNames = this.displayFileNamesCheckBox.Checked;
                    this.multipleFinderAndReplacer.IncludeSubDirectories = this.includeSubDirectoriesCheckBox.Checked;
                    this.multipleFinderAndReplacer.MatchContextBeforeLineCount = (int)this.matchContextBeforeNumericUpDown.Value;
                    this.multipleFinderAndReplacer.MatchContextAfterLineCount = (int)this.matchContextAfterNumericUpDown.Value;
                    this.multipleFinderAndReplacer.KeepModifedFilesOpen = this.keepFilesOpenCheckBox.Checked;

                    this.multipleFinderAndReplacer.ContinueProcessing = true;
                    this.stopButton.Enabled = this.stopButton.Visible = true;

                    foreach ( Control control in this.Controls )
                    {
                        if ( control != this.stopButton )
                        {
                            control.Enabled = false;
                        }
                    }

                    this.multipleFinderAndReplacer.ReplaceAll( GetProcessAllScope(), this.replacementTextBox.Text, this.findAndReplaceTypeButton.Text == Strings.REGEX_REPLACE_IN_FILES );

                    foreach ( Control control in this.Controls )
                    {
                        control.Enabled = true;
                    }

                    this.stopButton.Enabled = this.stopButton.Visible = false;

                    this.applicationObject.DTE.Windows.Item( Constants.vsWindowKindOutput ).Visible = true;
                }
                catch ( Exception ex )
                {
                    MessageBox.Show( ex.Message, "RegexFindAndReplace Error!" );
                }
                finally
                {
                    UpdateOptionsFromCache( this.replacementComboBox, PatternType.Replacement );
                }
            }
        }

        /// <summary>
        /// Gets the scope of a find and replace operation based on the contents of the "Look in" combo box
        /// </summary>
        /// <returns></returns>
        private object GetProcessAllScope()
        {
            object findAllScope = string.Empty;

            switch ( this.lookInComboBox.Text )
            {
                case Strings.CURRENT_DOCUMENT:
                    findAllScope = this.applicationObject.ActiveDocument.FullName;
                    break;
                case Strings.ALL_OPEN_DOCUMENTS:
                    findAllScope = Strings.ALL_OPEN_DOCUMENTS;
                    break;
                case Strings.CURRENT_PROJECT:
                    findAllScope = (Project)( (Array)this.applicationObject.ActiveSolutionProjects ).GetValue( 0 );
                    break;
                case Strings.ENTIRE_SOLUTION:
                    findAllScope = Strings.ENTIRE_SOLUTION;
                    break;
                default:
                    if ( Directory.Exists( this.lookInComboBox.Text ) )
                    {
                        findAllScope = this.lookInComboBox.Text;
                    }
                    else
                    {
                        finderAndReplacer.ShowMessage( string.Format( "The specified directory does not exist: {0}", this.lookInComboBox.Text ), true );
                    }
                    break;
            }

            return findAllScope;
        }

        /// <summary>
        /// Called when the "Stop" button is clicked.  Stops a Find all or Replace all operation.
        /// </summary>
        private void HandleStopButtonClick( object sender, EventArgs e )
        {
            this.multipleFinderAndReplacer.ContinueProcessing = false;
        }

        /// <summary>
        /// Called when the "Skip File" button is clicked.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void HandleSkipFileButtonClick( object sender, EventArgs e )
        {
            if ( this.multipleFinderAndReplacer.ItemsToProcess.Count > 0 )
            {
                this.multipleFinderAndReplacer.ItemsToProcess.Pop();
            }
            else
            {
                UpdateItemsToProcess();
            }

            this.multipleFinderAndReplacer.FindNext();
        }

        /// <summary>
        /// Validates the regex and RegexOptions.  The FinderAndReplacer.Pattern and MultipleFinderAndReplacer.Pattern properties
        /// attempt to create a new Regex and throw an exception upon failure
        /// </summary>
        /// <returns></returns>
        private bool ValidateRegexAndOptions()
        {
            bool regexAndOptionsValid = true;

            try
            {
                this.finderAndReplacer.Pattern = this.regexTextBox.Text;
                this.finderAndReplacer.RegexOptions = GetRegexOptions();

                this.multipleFinderAndReplacer.Pattern = this.regexTextBox.Text;
                this.multipleFinderAndReplacer.RegexOptions = GetRegexOptions();
            }
            catch
            {
                regexAndOptionsValid = false;
            }

            return regexAndOptionsValid;
        }

        /// <summary>
        /// Caches the current selections in the regex and replacement combo boxes, and moves them to the top of the list
        /// </summary>
        private void CachePatterns()
        {
            if ( !this.settingsCache.IsPatternCached( PatternType.Regex, this.regexTextBox.Text ) )
            {
                this.settingsCache.AddPattern( this.regexTextBox.Text, PatternType.Regex );
            }
            else
            {
                this.settingsCache.MovePatternToTop( this.regexTextBox.Text, PatternType.Regex );
            }

            if ( !this.settingsCache.IsPatternCached( PatternType.Replacement, this.replacementTextBox.Text ) )
            {
                this.settingsCache.AddPattern( this.replacementTextBox.Text, PatternType.Replacement );
            }
            else
            {
                this.settingsCache.MovePatternToTop( this.replacementTextBox.Text, PatternType.Replacement );
            }
        }

        /// <summary>
        /// Updates the items that will be processed by the MultipleFinderAndReplacer.  
        /// </summary>
        private void UpdateItemsToProcess()
        {
            if ( this.multipleFinderAndReplacer.ItemsToProcess.Count == 0 )
            {
                List<string> filenames = new List<string>();

                // the MultipleFinderAndReplacer.ItemsToProcess property is a stack where the top always contains
                // the current item to process.  If that item is a directory, the MultipleFinderAndReplacer removes
                // it from the top of the stack and adds the files in that directory to the stack.  If the item is 
                // a EnvDTE.Project, the text files in that project will be added to the stack.

                try
                {
                    switch ( this.lookInComboBox.Text )
                    {
                        case Strings.ALL_OPEN_DOCUMENTS:
                            foreach ( Document document in this.applicationObject.Documents )
                            {
                                if ( document.Object( string.Empty ) is TextDocument )
                                {
                                    filenames.Add( document.FullName );
                                }
                            }

                            filenames.Sort();

                            // if the currently open file is in the list of file names, adjust the list so it will be 
                            // processed first
                            if ( filenames.Contains( this.applicationObject.ActiveDocument.FullName ) )
                            {
                                int currentFileIndex = filenames.IndexOf( this.applicationObject.ActiveDocument.FullName );

                                List<string> currentFileAndAfters = filenames.GetRange( currentFileIndex, filenames.Count - currentFileIndex );

                                filenames.RemoveRange( currentFileIndex, filenames.Count - currentFileIndex );

                                filenames.InsertRange( 0, currentFileAndAfters );
                            }

                            // reverse the list
                            filenames.Reverse();

                            foreach ( string filename in filenames )
                            {
                                this.multipleFinderAndReplacer.ItemsToProcess.Push( filename );
                            }
                            break;
                        case Strings.CURRENT_PROJECT:
                            {
                                Project project = (Project)( (Array)this.applicationObject.ActiveSolutionProjects ).GetValue( 0 );

                                this.multipleFinderAndReplacer.ItemsToProcess.Push( project );
                            }
                            break;
                        case Strings.ENTIRE_SOLUTION:
                            List<Project> projects = new List<Project>();

                            foreach ( Project project in this.applicationObject.Solution.Projects )
                            {
                                projects.Add( project );
                            }

                            projects.Sort( new ProjectComparer() );

                            if ( ( (Array)this.applicationObject.ActiveSolutionProjects ).Length > 0 )
                            {
                                Project activeProject = (Project)( (Array)this.applicationObject.ActiveSolutionProjects ).GetValue( 0 );

                                // if the currently active project is in the list of projects, adjust the list so it will be 
                                // processed first
                                if ( activeProject != null && projects.Contains( activeProject ) )
                                {
                                    int currentProjectIndex = projects.IndexOf( activeProject );

                                    List<Project> currentProjectAndAfters = projects.GetRange( currentProjectIndex, projects.Count - currentProjectIndex );

                                    projects.RemoveRange( currentProjectIndex, projects.Count - currentProjectIndex );

                                    projects.InsertRange( 0, currentProjectAndAfters );
                                }
                            }

                            projects.Reverse();

                            foreach ( Project project in projects )
                            {
                                this.multipleFinderAndReplacer.ItemsToProcess.Push( project );
                            }
                            break;
                        default:
                            if ( Directory.Exists( this.lookInComboBox.Text ) )
                            {
                                this.multipleFinderAndReplacer.ItemsToProcess.Push( this.lookInComboBox.Text );
                            }
                            break;
                    }
                }
                catch ( Exception ex )
                {
                    MessageBox.Show( ex.Message, "Error!" );
                }
            }
        }

        /// <summary>
        /// Gets the RegexOptions based on the state of the RegexOptions checkboxes
        /// </summary>
        /// <returns></returns>
        private RegexOptions GetRegexOptions()
        {
            RegexOptions regexOptions = RegexOptions.None;

            if ( this.ignoreCaseCheckBox.Checked )
            {
                regexOptions |= RegexOptions.IgnoreCase;
            }
            if ( this.multilineCheckBox.Checked )
            {
                regexOptions |= RegexOptions.Multiline;
            }
            if ( this.singlelineCheckBox.Checked )
            {
                regexOptions |= RegexOptions.Singleline;
            }
            if ( this.explicitCaptureCheckBox.Checked )
            {
                regexOptions |= RegexOptions.ExplicitCapture;
            }
            if ( this.ignoreWhitespaceCheckBox.Checked )
            {
                regexOptions |= RegexOptions.IgnorePatternWhitespace;
            }
            if ( this.ecmaScriptCheckBox.Checked )
            {
                regexOptions |= RegexOptions.ECMAScript;
            }
            if ( this.rightToLeftCheckBox.Checked )
            {
                regexOptions |= RegexOptions.RightToLeft;
            }
            if ( this.cultureInvariantCheckBox.Checked )
            {
                regexOptions |= RegexOptions.CultureInvariant;
            }

            return regexOptions;
        }

        /// <summary>
        /// Updates the RegexOptions checkboxes with the specified RegexOptions
        /// </summary>
        /// <param name="regexOptions">The regex options.</param>
        private void SetRegexOptions( RegexOptions regexOptions )
        {
            this.ignoreCaseCheckBox.Checked = ( regexOptions & RegexOptions.IgnoreCase ) == RegexOptions.IgnoreCase;
            this.multilineCheckBox.Checked = ( regexOptions & RegexOptions.Multiline ) == RegexOptions.Multiline;
            this.singlelineCheckBox.Checked = ( regexOptions & RegexOptions.Singleline ) == RegexOptions.Singleline;
            this.explicitCaptureCheckBox.Checked = ( regexOptions & RegexOptions.ExplicitCapture ) == RegexOptions.ExplicitCapture;
            this.ignoreWhitespaceCheckBox.Checked = ( regexOptions & RegexOptions.IgnorePatternWhitespace ) == RegexOptions.IgnorePatternWhitespace;
            this.ecmaScriptCheckBox.Checked = ( regexOptions & RegexOptions.ECMAScript ) == RegexOptions.ECMAScript;
            this.rightToLeftCheckBox.Checked = ( regexOptions & RegexOptions.RightToLeft ) == RegexOptions.RightToLeft;
            this.cultureInvariantCheckBox.Checked = ( regexOptions & RegexOptions.CultureInvariant ) == RegexOptions.CultureInvariant;
        }

        /// <summary>
        /// Updates the options in the specifed combobox with those that were cached
        /// </summary>
        /// <param name="comboBox">The combo box.</param>
        /// <param name="regexType">Type of the regex.</param>
        private void UpdateOptionsFromCache( ComboBox comboBox, PatternType regexType )
        {
            List<string> cachedOptionList = this.settingsCache.GetCachedPatterns( regexType );

            comboBox.Items.Clear();

            for ( int i = cachedOptionList.Count - 1; i >= 0; i-- )
            {
                comboBox.Items.Add( cachedOptionList[ i ] );
            }
        }

        /// <summary>
        /// Called when the selection in the lookInCombox changes.  Enables or disables the appropriate buttons and controls
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void HandleLookInComboBoxSelectedIndexChanged( object sender, EventArgs e )
        {
            if ( this.lookInComboBox.Text == Strings.SELECTION )
            {
                this.findNextButton.Enabled = false;
                this.replaceButton.Enabled = false;
            }
            else
            {
                this.findNextButton.Enabled = true;
                this.replaceButton.Enabled = true;
            }

            UpdateConditionallyOptionalControls();

            // clear the multipleFinderAndReplacer.ItemsToProcess property
            this.multipleFinderAndReplacer.ItemsToProcess.Clear();
        }

        /// <summary>
        /// Clears the multipleFinderAndReplacer.ItemsToProcess property when the lookInComboBox text changes
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void HandleLookInComboBoxTextChanged( object sender, EventArgs e )
        {
            this.multipleFinderAndReplacer.ItemsToProcess.Clear();

            UpdateConditionallyOptionalControls();
        }

        /// <summary>
        /// Updates the conditionally optional controls.
        /// </summary>
        private void UpdateConditionallyOptionalControls()
        {
            if ( this.findAndReplaceTypeButton.Text != Strings.REGEX_FIND_AND_REPLACE )
            {
                if ( Directory.Exists( this.lookInComboBox.Text ) )
                {
                    this.fileTypesComboBox.Enabled = true;
                    this.fileTypesPanel.Visible = true;

                    this.includeSubDirectoriesCheckBox.Checked = bool.Parse( this.settingsCache.GetCachedSetting( Strings.Settings.INCLUDE_SUB_DIRECTORIES, "true" ) );
                    this.includeSubDirectoriesCheckBox.Enabled = true;

                    this.regexOptionsPanel.Top = this.includeSubDirectoriesPanel.Bottom;
                    this.resultOptionsPanel.Top = this.fileTypesPanel.Bottom;
                    this.standardButtonsPanel.Top = this.resultOptionsPanel.Bottom;
                }
                else
                {
                    this.fileTypesComboBox.Enabled = false;
                    this.fileTypesPanel.Visible = false;

                    this.settingsCache.CacheSetting( Strings.Settings.INCLUDE_SUB_DIRECTORIES, this.includeSubDirectoriesCheckBox.Checked.ToString() );
                    this.includeSubDirectoriesCheckBox.Checked = true;
                    this.includeSubDirectoriesCheckBox.Enabled = false;

                    this.regexOptionsPanel.Top = this.lookInComboBox.Bottom;
                    this.resultOptionsPanel.Top = this.regexOptionsPanel.Bottom;
                    this.standardButtonsPanel.Top = this.resultOptionsPanel.Bottom;
                }

                if ( this.findAndReplaceTypeButton.Text == Strings.REGEX_FIND_IN_FILES )
                {
                    this.keepFilesOpenCheckBox.Enabled = false;

                    this.findAllButtonPanel.Top = this.resultOptionsPanel.Bottom;

                    this.ClientSize = new Size( this.ClientSize.Width, this.findAllButtonPanel.Bottom );
                }
                else
                {
                    this.keepFilesOpenCheckBox.Enabled = true;

                    this.findAllButtonPanel.Top = this.standardButtonsPanel.Bottom;

                    this.ClientSize = new Size( this.ClientSize.Width, this.standardButtonsPanel.Bottom );
                }
            }
            else
            {
                this.ClientSize = new Size( this.ClientSize.Width, this.standardButtonsPanel.Bottom );
            }

            this.stopButton.Top = this.ClientSize.Height - this.stopButton.Height - 5;
            this.stopButton.Enabled = this.stopButton.Visible = false;
        }

        /// <summary>
        /// Called when the excape key is clicked
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void HandleCloseButtonClick( object sender, EventArgs e )
        {
            this.Close();
        }

        private void HandleRegexOptionsGroupBoxCollapseBoxClicked( object sender )
        {
            if ( this.regexOptionsGroupBox.IsCollapsed )
            {
                this.regexOptionsPanel.Height = this.regexOptionsGroupBox.CollapsedHeight + 4;
            }
            else
            {
                this.regexOptionsPanel.Height = this.regexOptionsGroupBox.FullHeight + 4;
            }

            UpdateControlPositions();
        }

        private void HandleResultOptionsGroupBoxCollapseBoxClicked( object sender )
        {
            if ( this.resultOptionsGroupBox.IsCollapsed )
            {
                this.resultOptionsPanel.Height = this.resultOptionsGroupBox.CollapsedHeight + 4;
            }
            else
            {
                this.resultOptionsPanel.Height = this.resultOptionsGroupBox.FullHeight + 4;
            }

            UpdateControlPositions();
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

jhillman
Software Developer
United States United States
I am a software developer currently working in Salt Lake City, Utah. I work primarily with C# for my job, but I mess around with C, Perl, and Windows PowerShell for fun.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141216.1 | Last Updated 12 Oct 2009
Article Copyright 2008 by jhillman
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid