Introduction
The "Search and Replace" and "Search in files" in VS.NET support Regular Expression, but the kind of Regular Expression is weak and wired. For instance, it uses some strange symbols like :z, :Lu, and doesn't support position-match like ?!>, ?=. Standard Regular Expression Searcher Add-in has the same functions with "Search and Replace" and "Search in files", and support standard Regular Expression, so by using it, you can take advantage of the power of standard Regular Expression and the convenient usability of VS.NET, and it provides some functions that VS.NET has not, for example, show the context of matched content.
Background
In The Code Project, there are a lot of articles involving how to write VS.NET add-ins, so we don't repeat it. We just use _DTE
, TextDocument
, TextSelection
, EditPoint
, and OutputWindowPane
.
Using the Add-in
After installing the add-in, You can find a menu item in Tools that is named SearcherAddin, and click it, and then write Regular Expression into the text box for Search.

Click "Replace" button, you can write Regular Expression for replacing to the textbox for Replace.

In "Search Scope", if you select "Active Document" or "All of opened files", the add-in works like "Find and Replace" of VS.NET; if you select "Current project" or "Whole solution" or click Browse button, it works like "Find in files", and executes the command once and closes, and then shows the results into OutputWindowPane
and double f on the corresponding line, then opens the relating file and goes to the right position.

If you input "Font Lines" and "Back Lines", the add-in will show the context front and back lines of the matched text in OutputWindowPane
.


How to show the results to WindowPane
The "Find in Files" of VS.NET uses FindResult1
and FindResult2
Window to show searching or replacing results, but the DTE component model doesn't expose FindResult1 and FindResult2, so we used OutputWindowPane
to do the same thing. We implement OutputWindowPaneWrapper
to wrap OutputWindowPane
, and encapsulate more functions in it.
public class OutputWindowPaneWrapper
{
private OutputWindowPane pane;
private DTE dte;
...
We named the OutputWindowPane
"Regex Searcher" windowName
, and uses this code to initialize it:
private void getWindowPane (string title) {
Window winItem = this.dte.Windows.Item (Constants.vsWindowKindOutput);
OutputWindow window = (OutputWindow) winItem.Object;
try {
this.pane = window.OutputWindowPanes.Item (title);
}
catch(System.ArgumentException) {
this.pane = window.OutputWindowPanes.Add (title);
}
}
In order to show OutputWindowPane
, first of all, we should activate Output Window of IDE, and activate our specific pane.
public void Activate ()
{
dte.Windows.Item(Constants.vsWindowKindOutput).Activate();
this.pane.Activate();
}
Use State Pattern to implement different FindNext in TextWindow
In TextWindow
, we use FindNext
command to search next matched text, but FindNext
will be fairly complicated by virtue of different position and showing different information by all kinds of results, if all of the logic is concentrated in FindNext
. If we use enum
type to define different states for FindNext
, there will be too much switch
-case
in FindNext
and it will be hard to understand and maintain. We chose State Pattern of GOF to tackle it, then all of it becomes clear and easy.
We defined IFindState
that includes two methods: FindNext
and ShowMessage
.
public interface IFindState
{
void ShowMessage();
void FindNext(WindowSearcher windowSearcher);
}
We implemented concrete States by deriving from IFindState
, it involves FirstFindState
, FirstFindSucceededState
, FirstFindFailedState
, FirstSearchSucceededAndBackToStartPointState
and FirstSearchFailedAndSecondSucceededState
. In concrete State, we did three things. First, we once search in TextWindow
; second, according to the result of searching, we set the new State for State Context -- WindowSearcher
; third, according to the result of searching, we show the corresponding message or do another FindNext
. For example:
public class FirstFindState : IFindState {
public void ShowMessage(){
}
public void FindNext(WindowSearcher windowSearcher){
windowSearcher.SetSearchStartPoint();
if(windowSearcher.SearchInTextWindowOnce()) {
windowSearcher.SelectMatchedText();
windowSearcher.SetState(new FirstFindSucceededState());
}
else{
windowSearcher.MoveToStartInTextWindow();
windowSearcher.SetState(new FirstFindFailedState());
windowSearcher.SetLatestFindStartPosToStartOfDoc();
windowSearcher.FindNext();
}
}
}
public class FirstFindFailedState: IFindState {
public void ShowMessage() {
UIHelper.ShowInfo("Didn't find");
}
public void FindNext(WindowSearcher windowSearcher) {
if(windowSearcher.SearchInTextWindowOnce()){
windowSearcher.SelectMatchedText();
windowSearcher.SetState(new FirstSearchFailedAndSecondSucceededState());
}
else{
windowSearcher.ResetLatestFindStartPos();
windowSearcher.SetState(new FirstFindState());
ShowMessage();
}
}
}
WindowSearcher
is State Context that has _state
to keep State information, and realizes FindNext
by different State.
public class WindowSearcher : Searcher {
private IFindState _state;
public void SetState(IFindState newState) {
_state = newState;
}
public void FindNext() {
setLatestFindStartPos();
_state.FindNext(this);
}
...
How to replace Text in TextWindow
The DTE component model provides three methods to replace text in TextWindow
. First, TextSelection
has ReplaceText
function, but it doesn't support standard Regular Expression; second, EditPoint
has ReplaceText
function, it can replace the text between two points with expected text; third, TextSelection
has Text
property, it can set an expected text by replacing selected text. The third one is in line with our need and convenience.
private void tryToReplaceSelectedText(string replacePattern) {
TextSelection objSel = getTextSelection();
if(_matchedStartPoint == objSel.AnchorPoint.AbsoluteCharOffset
&& _matchedEndPoint == objSel.ActivePoint.AbsoluteCharOffset){
objSel.Text =
getReplaceText(_searchLatestEditPoint.GetText(getTextDocument().EndPoint),
replacePattern);
}
setLatestFindStartPos();
}
How to use Regular Expression in searching and replacing
The FCL of .NET Framework provides support for Regular Expressions, and it's pretty good. The namespace is System.Text.RegularExpressions
, we just use Match()
, Matchs()
, Replace()
, Match
, MatchCollection
of the Regex
object to do Regular Expression searching and replacing.
public MatchInfo GetMatch() {
Regex rg = new Regex(_pattern, GetRegexOptions());
Match match = rg.IsMatch(_windowSource, _nextSearchStartPos);
if(!match.Success){
return null;
}
int startLine =
StringHandleHelp.GetLineCount(ref _windowSource, match.Index);
int startLineOffset =
StringHandleHelp.GetPosInLine(ref _windowSource, match.Index);
int endLine =
StringHandleHelp.GetLineCount(ref _windowSource,
match.Index + match.Length - 1);
int endLineOffset = StringHandleHelp.GetPosInLine(ref _windowSource,
match.Index + match.Length - 1);
return new MatchInfo(startLine, endLine, match.Value,
startLineOffset, match.Index, match.Length, endLineOffset);
}
public string Replace(string source, string replacePattern){
Regex rg = new Regex(_pattern, GetRegexOptions());
return rg.Replace(source, replacePattern);
}
public string Replace(string source, string replacePattern, int startPos){
Regex rg = new Regex(_pattern, GetRegexOptions());
return rg.Replace(source, replacePattern, 1, startPos);
}
protected string replaceAll(string source,
string replacePattern, out int repeatCount)
{
Regex rg = new Regex(_pattern, GetRegexOptions());
repeatCount = rg.Matches(source).Count;
return repeatCount > 0?rg.Replace(source,
replacePattern):string.Empty;
}