Click here to Skip to main content
15,895,142 members
Articles / Programming Languages / XML

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

Rate me:
Please Sign up or sign in to vote.
4.91/5 (36 votes)
12 Oct 2009CPOL3 min read 186.2K   1.6K   117  
A .NET Regular Expressions Find and Replace add-in for Visual Studio 2008
using System;
using EnvDTE;
using System.Text.RegularExpressions;
using RegexFindAndReplace;
using System.Diagnostics;
using EnvDTE80;
using System.Windows.Forms;
using System.Collections.Generic;

namespace RegexFindAndReplace
{
    /// <summary>
    /// The FinderAndReplacer class finds and replaces using .NET regular expressions
    /// in the file that is currently open.
    /// </summary>
    public class FinderAndReplacer
    {
        private DTE2 applicationObject;

        private EditPoint searchStartEditPoint = null;
        private EditPoint searchLatestEditPoint = null;

        /// <summary>
        /// The MatchInfo struct holds information about the position of the current match.
        /// </summary>
        struct MatchPositionInfo
        {
            public int StartLine;
            public int StartLineCharOffset;
            public int EndLine;
            public int EndLineCharOffset;
        }

        private MatchPositionInfo matchPositionInfo;

        private int matchStartPoint;
        private int matchEndPoint;
        private IFindState findState;
        private string sourceText;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:FinderAndReplacer"/> class.
        /// </summary>
        /// <param name="applicationObject">The application object.</param>
        public FinderAndReplacer( DTE2 applicationObject )
        {
            this.applicationObject = applicationObject;
        }

        private string pattern = string.Empty;


        /// <summary>
        /// Gets or sets the pattern used in regex finds.
        /// </summary>
        /// <value>The pattern.</value>
        public string Pattern
        {
            get
            {
                return this.pattern;
            }
            set
            {
                try
                {
                    Regex testRegex = new Regex( value );
                }
                catch ( ArgumentException ex )
                {
                    MessageBox.Show( ex.Message );
                    throw;
                }

                this.pattern = value;
                this.findState = new FirstFindState();
            }
        }

        private RegexOptions regexOptions = RegexOptions.None;

        /// <summary>
        /// Gets or sets the RegexOptions.
        /// </summary>
        /// <value>The RegexOptions.</value>
        public RegexOptions RegexOptions
        {
            set
            {
                this.regexOptions = value;
            }
            get
            {
                return this.regexOptions;
            }
        }

        /// <summary>
        /// Sets the IFindState.
        /// </summary>
        /// <param name="newFindState">The new state</param>
        public void SetState( IFindState newFindState )
        {
            this.findState = newFindState;
        }

        /// <summary>
        /// Finds the next match
        /// </summary>
        internal void FindNext()
        {
            SetLatestFindStartPosition();

            this.findState.FindNext( this );
        }

        /// <summary>
        /// Attempts to find a match in the current file.
        /// </summary>
        /// <param name="noMatchInFile">if set to <c>true</c> [no match in file].</param>
        /// <returns>true if a match was found</returns>
        internal bool AttemptMatchInCurrentFile( out bool noMatchInFile )
        {
            bool matchSuccessful = false;

            noMatchInFile = false;

            SetLatestFindStartPosition();

            // see if this is the first match attempt in this file
            bool matchFromStartOfFile = this.searchLatestEditPoint.AbsoluteCharOffset == 1;

            if ( AttemptMatch() )
            {
                // select the match and report success
                SelectMatchedText();
                SaveMatchedPostion();
                matchSuccessful = true;
            }
            else
            {
                if ( matchFromStartOfFile )
                {
                    noMatchInFile = true;
                }

                ResetLatestFindStartPosition();
            }

            return matchSuccessful;
        }

        /// <summary>
        /// Performs a replace operation on the source string and returns the result
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="replacePattern">The replacement pattern.</param>
        /// <returns></returns>
        public string Replace( string source, string replacePattern )
        {
            Regex regex = new Regex( this.pattern, this.regexOptions );

            return regex.Replace( source, replacePattern );
        }

        /// <summary>
        /// Replaces the selected text using the specified replacement pattern and finds the next match.
        /// </summary>
        /// <param name="replacePattern">The replace pattern.</param>
        public void ReplaceNext( string replacePattern )
        {
            if ( this.searchLatestEditPoint == null )
            {
                SetLatestFindStartPosition();
            }
            else
            {
                ReplaceSelectedText( replacePattern );
            }

            FindNext();

            SaveMatchedPostion();
        }

        /// <summary>
        /// Attempts to replace the selected text in the current file
        /// </summary>
        /// <param name="replacePattern">The replace pattern.</param>
        /// <param name="noMatchInFile">if set to <c>true</c> [no match in file].</param>
        /// <returns>true if the replacement was successful</returns>
        public bool AttemptReplaceInCurrentFile( string replacePattern, out bool noMatchInFile )
        {
            if ( this.searchLatestEditPoint == null )
            {
                SetLatestFindStartPositionToStartOfDocument();
            }
            else
            {
                ReplaceSelectedText( replacePattern );
            }

            return AttemptMatchInCurrentFile( out noMatchInFile );
        }

        /// <summary>
        /// Performs a replace operation on the source string and returns the result.  
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="replacePattern">The replacement pattern.</param>
        /// <param name="repeatCount">out parameter for the number of replacements made</param>
        /// <returns></returns>
        public string ReplaceAll( string source, string replacePattern, out int replacementCount )
        {
            Regex regex = new Regex( this.pattern, this.regexOptions );
            string result = string.Empty;

            replacementCount = regex.Matches( source ).Count;

            if ( replacementCount > 0 )
            {
                result = regex.Replace( source, replacePattern );
            }

            return result;
        }

        /// <summary>
        /// Performs a replace all operation on the current text file.
        /// </summary>
        /// <param name="replacePattern">The replace pattern.</param>
        /// <param name="replaceInMultipleFiles">if set to <c>true</c> [replace in multiple files].</param>
        /// <returns>The number of replacements</returns>
        public int ReplaceAll( string replacePattern, bool replaceInMultipleFiles )
        {
            SetSearchStartEditPoint();
            MoveToStartInTextWindow();

            TextSelection textSelection = GetTextSelection();
            TextDocument textDocument = GetCurrentTextDocument();

            string windowText = textSelection.ActivePoint.CreateEditPoint().GetText( textDocument.EndPoint );

            EditPoint editPoint = textSelection.ActivePoint.CreateEditPoint();
            TextPoint endPoint = textDocument.EndPoint.CreateEditPoint();

            int replacementCount;

            string replaceWindowText = ReplaceAll( windowText, replacePattern, out replacementCount );

            if ( replacementCount > 0 )
            {
                editPoint.ReplaceText( endPoint, Replace( windowText, replacePattern ), (int)vsEPReplaceTextOptions.vsEPReplaceTextAutoformat );

                if ( !replaceInMultipleFiles )
                {
                    // report the number of replacements if working on just the current file
                    ShowMessage( string.Format( "{0} occurance(s) replaced.", replacementCount ), false );
                }
            }

            textSelection.MoveToPoint( this.searchStartEditPoint, false );

            return replacementCount;
        }

        /// <summary>
        /// Performs a replace all operation on the currently selected text in the current text file.
        /// </summary>
        /// <param name="replacePattern">The replace pattern.</param>
        public void ReplaceAllInSelection( string replacePattern )
        {
            TextSelection textSelection = GetTextSelection();
            int topCharOffset = textSelection.AnchorPoint.AbsoluteCharOffset;

            Regex regex = new Regex( this.pattern, this.regexOptions );

            string replacement = textSelection.Text = regex.Replace( textSelection.Text, replacePattern );

            textSelection.MoveToAbsoluteOffset( topCharOffset, false );
            // adjust the end of the selection to account for changes in the length of replacement text
            textSelection.MoveToAbsoluteOffset( topCharOffset + Regex.Replace( replacement, "\r", "" ).Length, true );
        }

        /// <summary>
        /// Displays a message in the Visual Studio status bar
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="highlight">if set to <c>true</c> [highlight the text in the status bar].</param>
        public void ShowMessage( string message, bool highlight )
        {
            this.applicationObject.StatusBar.Text = message;
            this.applicationObject.StatusBar.Highlight( highlight );
        }

        /// <summary>
        /// Sets the search start edit point.
        /// </summary>
        public void SetSearchStartEditPoint()
        {
            TextSelection textSelection = (TextSelection)this.applicationObject.ActiveDocument.Selection;

            this.searchStartEditPoint = textSelection.ActivePoint.CreateEditPoint();
        }

        /// <summary>
        /// Moves to the start of the text window.
        /// </summary>
        public void MoveToStartInTextWindow()
        {
            TextSelection textSelection = GetTextSelection();

            textSelection.MoveToPoint( GetCurrentTextDocument().StartPoint, false );
        }

        /// <summary>
        /// Gets the MatchContextInfo for the current match.
        /// </summary>
        /// <returns>The MatchContextInfo</returns>
        private MatchContextInfo GetMatchContextInfo()
        {
            MatchContextInfo matchContextInfo = null;
            Regex regex = new Regex( this.pattern, this.regexOptions );

            Match match = regex.Match( this.sourceText );

            if ( match.Success && match.Length > 0 )
            {
                int startLine = MatchContextUtils.GetLineCount( this.sourceText, match.Index );
                int startLineOffset = MatchContextUtils.GetPositionInLine( this.sourceText, match.Index ) - 1;
                int endLine = MatchContextUtils.GetLineCount( this.sourceText, match.Index + match.Length );
                int endLineOffset = MatchContextUtils.GetPositionInLine( this.sourceText, match.Index + match.Length - 1 );

                matchContextInfo = new MatchContextInfo( startLine, endLine, match.Value, startLineOffset, match.Index, match.Length, endLineOffset );
            }

            return matchContextInfo;
        }

        /// <summary>
        /// Attempts a match from the current position in the file.
        /// </summary>
        /// <returns>true if a match was found</returns>
        public bool AttemptMatch()
        {
            bool matchFound = false;
            TextSelection textSelection = GetTextSelection();
            int activePointLine = textSelection.ActivePoint.Line;
            int activePointLineCharOffset = textSelection.ActivePoint.LineCharOffset;

            this.sourceText = textSelection.ActivePoint.CreateEditPoint().GetText( GetCurrentTextDocument().EndPoint );
            
            MatchContextInfo match = GetMatchContextInfo();

            if ( match != null )
            {
                if ( match.MatchContent.Length > 0 )
                {
                    matchFound = true;

                    // if the match only includes one line
                    if ( match.StartLine == 1 && match.EndLine == match.StartLine )
                    {
                        this.matchPositionInfo.StartLine = activePointLine;
                        this.matchPositionInfo.StartLineCharOffset = activePointLineCharOffset + match.StartLineOffset;
                        this.matchPositionInfo.EndLine = activePointLine + match.EndLine - 1;
                        this.matchPositionInfo.EndLineCharOffset = activePointLineCharOffset + match.EndLineOffset;
                    }
                    else
                    {
                        this.matchPositionInfo.StartLine = activePointLine + match.StartLine - 1;
                        this.matchPositionInfo.StartLineCharOffset = match.StartLineOffset + 1;
                        this.matchPositionInfo.EndLine = activePointLine + match.EndLine - 1;
                        this.matchPositionInfo.EndLineCharOffset = match.EndLineOffset + 1;
                    }
                }
            }

            return matchFound;
        }

        /// <summary>
        /// Selects the matched text.
        /// </summary>
        public void SelectMatchedText()
        {
            TextSelection textSelection = GetTextSelection();

            textSelection.MoveToLineAndOffset( this.matchPositionInfo.StartLine, this.matchPositionInfo.StartLineCharOffset, false );
            textSelection.MoveToLineAndOffset( this.matchPositionInfo.EndLine, this.matchPositionInfo.EndLineCharOffset, true );
        }

        /// <summary>
        /// Determines if the search in the current file has wrapped to where the search begain
        /// </summary>
        /// <returns>true if the search has return to the start</returns>
        public bool WrappedToStartPoint()
        {
            return ( ( this.matchPositionInfo.StartLine > this.searchStartEditPoint.Line ) ||
                ( this.matchPositionInfo.StartLine == this.searchStartEditPoint.Line && this.matchPositionInfo.StartLineCharOffset >= this.searchStartEditPoint.LineCharOffset ) );
        }

        /// <summary>
        /// Saves the matched postion.
        /// </summary>
        public void SaveMatchedPostion()
        {
            TextSelection textSelection = GetTextSelection();

            this.matchStartPoint = textSelection.AnchorPoint.AbsoluteCharOffset;
            this.matchEndPoint = textSelection.ActivePoint.AbsoluteCharOffset;
        }

        /// <summary>
        /// Gets the text selection in the current text file.
        /// </summary>
        /// <returns></returns>
        public TextSelection GetTextSelection()
        {
            TextSelection textSelection = null;

            if ( this.applicationObject.ActiveDocument != null )
            {
                textSelection = (TextSelection)this.applicationObject.ActiveDocument.Selection;
            }

            return textSelection;
        }

        /// <summary>
        /// Gets the current text document.
        /// </summary>
        /// <returns></returns>
        private TextDocument GetCurrentTextDocument()
        {
            return (TextDocument)this.applicationObject.ActiveDocument.Object( string.Empty );
        }

        /// <summary>
        /// Replaces the selected text using the specified replacement pattern.
        /// </summary>
        /// <param name="replacePattern">The replace pattern.</param>
        private void ReplaceSelectedText( string replacePattern )
        {
            TextSelection textSelection = GetTextSelection();

            if ( this.matchStartPoint == textSelection.AnchorPoint.AbsoluteCharOffset && this.matchEndPoint == textSelection.ActivePoint.AbsoluteCharOffset )
            {
                textSelection.Text = GetReplacementText( this.searchLatestEditPoint.GetText( GetCurrentTextDocument().EndPoint ), replacePattern );
            }

            SetLatestFindStartPosition();
        }

        /// <summary>
        /// Sets the latest find start position.
        /// </summary>
        private void SetLatestFindStartPosition()
        {
            TextSelection textSelection = GetTextSelection();

            this.searchLatestEditPoint = textSelection.ActivePoint.CreateEditPoint();
        }

        /// <summary>
        /// Resets the latest find start position.
        /// </summary>
        public void ResetLatestFindStartPosition()
        {
            this.searchLatestEditPoint = null;
        }

        /// <summary>
        /// Sets the latest find start position to the start of the current text file.
        /// </summary>
        public void SetLatestFindStartPositionToStartOfDocument()
        {
            GetTextSelection().StartOfDocument( false );

            this.searchLatestEditPoint = GetTextSelection().ActivePoint.CreateEditPoint();
        }

        /// <summary>
        /// Gets the replacement text to use.
        /// </summary>
        /// <param name="source">The source string.</param>
        /// <param name="replacePattern">The replace pattern.</param>
        /// <returns></returns>
        private string GetReplacementText( string source, string replacePattern )
        {
            string replacementText = string.Empty;
            string replacingSource = source;

            Regex regex = new Regex( this.pattern, this.regexOptions );
            Match match = regex.Match( replacingSource );

            if ( match.Success )
            {
                string changedSource = regex.Replace( replacingSource, replacePattern, 1 );
                int replaceTextStartPosition = match.Index;
                int replaceTextEndPosition = changedSource.Length - ( replacingSource.Length - match.Index - match.Length );

                replacementText = changedSource.Substring( replaceTextStartPosition, replaceTextEndPosition - replaceTextStartPosition );
            }

            return replacementText;
        }
    }
}

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)


Written By
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.

Comments and Discussions