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:
="1.0" ="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[]]>
</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)
{
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
{
}
}
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)
{
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.