Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Automatic variable replacement in Visual Studio code pane

0.00/5 (No votes)
26 Nov 2006 1  
This article describes how to create an add-in to automatically replace variables as code is changed or added in Visual Studio.

Note: The demo files need to be placed in the "C:\Documents and Settings\user\My Documents\Visual Studio 2005\Addins" directory.

Introduction

This article describes how to add variable replacements into your snippet generated code, or even a simple copy and paste function.

The first thing we need to do is trap the code changed event from the IDE, we need to have a mechanism that allows us to look up a value to replace. Now, we need to replace the inserted code. This will also allow us to use the functionality as an inline variable replacement, like Word uses auto-correct.

Secondly, we need this mechanism to work in conjunction with the snippet tool. This requires us to wrap our variables in a different way. The snippet uses the dollar ($) to pre and post sign the variables, so that they look like this: $username$. We will use a percent (%) like this %username%, to allow us to add static and variable values into our snippet design.

Thirdly, we want any changes made in our XML look up file to be reflected in the IDE immediately, this requires us to use a "FileSystemWatcher". If the file changes, the contents will automatically be reloaded into the snippet watcher.

Background

Have you ever wanted to add variables in your snippet code, variables that could be replaced at the time the code is being added to your IDE code pane. I wanted to utilise the built-in power of VS2005 snippets with the power of having a reusable template of lookup values that the snippet could look at before inserting the code. If you have a look at the vast examples of snippet code, you will more than likely see an example of how to create a "header.snippet"; see below. The snippet below is an example I put together to illustrate the point.

The fundamental problem with snippets is that you have no way of linking variables to a look up list of values like date (date format), time, or user name, so you have to hard code these values into the snippet or make the default not editable. This basically adds the value without putting that edit box over the value. This project rectifies this problem and opens up the snippet library to endless possibilities.

Example of snippet code

An example of a snippet code to generate a header section for your code block:

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  
       xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
           <Title>Header</Title>
           <Shortcut>Header</Shortcut>
           <Description>Code snippet Header statement</Description>
           <Author>Grant Simms</Author>
           <SnippetTypes>
            <SnippetType>Expansion</SnippetType>
           </SnippetTypes>
        </Header>
        <Snippet>
          <Declarations>
            <Literal default="true" Editable="true">
                        <ID>classname</ID>
              <Default>ClassName</Default>
                      </Literal>
          </Declarations>
<Code Language="csharp"> 
<![CDATA[#region Header 
//===================================================================
// Author:    %username%
// Date:        %date% time %timestamp%
// Description:     $classname$ , Description:
//
// 
// %copyright%
//===================================================================
#endregion $end$ ]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

The only problem with this is that you now have to enter data into the fields, i.e., the date, user name, and possibly some other information that would be impossible to have as a variable in the snippet.

The example above is what you are presented with after the snippet has completed, Fig 1.0. By tabbing, you move from block to block; however, if you could have a process that eliminates this tedious process by automatically replacing predefined variables with the corresponding data, see Fig 1.1, the process would be much simpler / quicker.

Images

Fig. 1.0

Fig. 1.1

Key stroke actions

The power of this process allows for a wide range of uses, from simple copy and paste actions that will convert the variables rapped in a percentage (%) to be looked up and replaced. If you want to type in a variable, i.e., %username% and press Enter, this will be converted to the value you placed in the XML document, like your name.

private void Form1_Load(object sender, EventArgs e)
{
    %username%
}
private void Form1_Load(object sender, EventArgs e)
{
    Grant Simms
}

The XML document is loaded at IDE start so that the values are always available, this helps with speed. I have also added a FileSystemWatcher to reload the XML file if it changes at any time, so that the new or updated variables are immediately available. I have made use of Regular Expression algorithms to find and replace the values for speed.

Points of interest

The code to trap the IDE code changed event is as follows. This must be placed in the OnConnection call. This is also where the variables will need to be loaded into the StringDictionary for quick access later.

_textEditorEvents = (TextEditorEvents)
   ((Events)_applicationObject.Events).get_TextEditorEvents(null);
_textEditorEvents.LineChanged += new 
   _dispTextEditorEvents_LineChangedEventHandler(
   _textEditorEvents_LineChanged);

The call to the _textEditorEvents_LineChanged event is as follows:

void _textEditorEvents_LineChanged(TextPoint StartPoint, 
                       TextPoint EndPoint, int Hint)
{

    //IF you want to write a status on the IDE Status bar.
    // _applicationObject.StatusBar.Text = "Line Changing..."; 
    EditPoint ep = EndPoint.CreateEditPoint();
    EditPoint sp = StartPoint.CreateEditPoint();

    sp.CharLeft(1);
    string txt = sp.GetText(ep);

    try
    {
        MatchCollection matches = Regex.Matches(txt, "%.*%");
        if (matches.Count > 0)
        {
            foreach (Match match in matches)
            {
                sp.Delete(ep);
                string newtext = txt;
                IEnumerator myEnum = _stringDictionary.GetEnumerator();
                foreach (DictionaryEntry de in _stringDictionary)
                {
                    switch (de.Key.ToString())
                    {
                        case "%date%":
                            newtext = newtext.Replace(de.Key.ToString(), 
                              DateTime.Now.ToString(de.Value.ToString()));
                            break;
                        case "%datetimestamp%":
                            goto case "%date%";
                        case "%timestamp%":
                            goto case "%date%";
                        default:
                            newtext = newtext.Replace(de.Key.ToString(), 
                                      de.Value.ToString());
                            break;
                    }
                }
                sp.Insert(newtext);
                return;
            }
        }
    }
    catch
    {
        //Not going to trap anything...
    }
}

Setting up a FileSystemWatcher

If you want to monitor a directory for changes in a file, you need to point the FileSystemWatcher to the file and enable the EnableRaisingEvents, and then trap the events and do what you need to to do.

#region fileSystemWatcher
fileSystemWatcher.BeginInit();
fileSystemWatcher.EnableRaisingEvents = true;
fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite;
fileSystemWatcher.Filter = filename;
fileSystemWatcher.IncludeSubdirectories = false;
fileSystemWatcher.Path = 
      System.IO.Path.GetDirectoryName(asmbly.Location);
fileSystemWatcher.Changed+=new 
      FileSystemEventHandler(fileSystemWatcher_Changed);
fileSystemWatcher.EndInit();
#endregion

This is where you trap the event:

void fileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
{
  //This may fire the event more than once, 
  //this is due to the attributes of
  //the file also updating...
  LoadVariables();
}

The demo project is a release build that is fully functional, but I have included all the source code to replicate the entire add-in.

Have fun coding, and I hope this helps you as much as it has helped me in creating a vast reusable library of extensible snippets.

Notes & To-Do features

If you find this useful or have any suggestions that I can put in the next release to enhance it, please let me know.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here