CHAPTER 12
One of the common features that add-in modules offer is the ability to manipulate the code in windows. In this chapter, we will create an add-in to interact with the code in an open document window. We will explore how to pull code from the window, manipulate it, and write it back.
We are still going to use the wizard to create our basic add-in, but rather than attach our code to the Tools menu, this time we will attach it to the context menu of the code window. So let’s start up the wizard with the following:
What this add-in will do is allow you to send an email with a sample of code to your local guru (hopefully you have one) and ask him or her what the code is doing. The add-in will then leave a [TODO] comment indicating that the code was sent to a guru and we need to document what is being accomplished by this code when the guru replies.
Since our add-in module is going to attach to the code window instead of the main menu, we need to code our connection logic slightly differently.
|
object []contextGUIDS = new object[] { }; Commands2 commands = (Commands2)_applicationObject.Commands; // Create the command object. Command cmd = commands.AddNamedCommand2(_addInInstance, "CodeHelp", "CodeHelp, "Send sample to Guru..", true, 59, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton); // Create a command bar on the code window. CommandBar CmdBar = ((CommandBars)_applicationObject.CommandBars)["Code Window"]; // Add a command to the Code Window's shortcut menu. CommandBarControl cmdBarCtl = (CommandBarControl)cmd.AddControl(CmdBar, CmdBar.Controls.Count + 1); cmdBarCtl.Caption = "Send sample to Guru..."; |
We are searching for the “Code Window” (precise spelling is important) and adding a pop-up menu control to it, rather than to the usual menu.
When the user selects the menu item, the Exec() method will be called to process the Send code to the Guru… menu option.
Since the code will only be called from a code window (since our menu item is attached to the context menu), we can assume an active document will always be available.
Once we have the selected text and confirmed some text was selected:
|
// See if any selected text. Document theDoc = _applicationObject.ActiveDocument; TextSelection sel = (TextSelection)theDoc.Selection; if (sel.Text == "") { MessageBox.Show("Please select some text...", "Error"); handled = true; return; } |
We can build a mail message and send it to our guru.
|
// Let's make an e-mail for the guru. string subjectLine = "Having trouble understanding this code"; string msgbody = "<p>Can you review it and tell me what the #$@* it is doing<p>" + Environment.NewLine + Environment.NewLine + "<pre>"+sel.Text +"</pre>"+ Environment.NewLine + "<p>Thanks..."; MailMessage mail = new MailMessage(); mail.To.Add(GuruEmail); mail.From = new MailAddress("xxxx@"+fromDomain); mail.Subject = subjectLine; mail.Body = msgbody; mail.IsBodyHtml = true; SmtpClient smtp = new SmtpClient(); smtp.Host = "smtp.gmail.com"; //Or your SMTP server address. smtp.Credentials = new System.Net.NetworkCredential ("xxxxxxxx @gmail.com", "password "); smtp.EnableSsl = true; bool MailSent = false; try { Cursor.Current = Cursors.WaitCursor; smtp.Send(mail); MailSent = true; } catch { MessageBox.Show("Error sending mail", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error); } Cursor.Current = Cursors.Default; |
Note: You will need to add a reference to System.net.mail and define a string constant GuruEmail and a string constant fromDomain with your e-mail domain.
Now that we’ve sent our code to the guru for his or her comments and review, we want to mark the code with a to-do comment so we can add documentation when the guru replies to our email.
|
// Now we add the comment back to the code. string commentChar = "//"; string theTodoText = _applicationObject.ToolWindows.TaskList.DefaultCommentToken.ToString(); string revisedCode = commentChar + " " + theTodoText + " GURU - sent to guru on " + DateTime.Now.ToString() + Environment.NewLine + commentChar + " < Guru answer here >" + Environment.NewLine + sel.Text + commentChar + " ********" + Environment.NewLine; if (MailSent == false) { revisedCode = commentChar + " " + theTodoText + " Ask guru about this"+ Environment.NewLine+ sel.Text + Environment.NewLine+ commentChar + " ********" + Environment.NewLine; } |
In this example, we are adding the comment and the to-do token that the IDE currently uses for tasks. This will allow our comment to be found easily using the task list window within Visual Studio.
And now that we’ve assembled our revised code string, we need to update the editor with the revision. This is accomplished by copying the revised text to the Windows clipboard as text, and then pasting that text back to the editor.
|
DataObject theObj = new System.Windows.Forms.DataObject(); try { theObj.SetData(System.Windows.Forms.DataFormats.Text, revisedCode); System.Windows.Forms.Clipboard.SetDataObject(theObj); sel.Paste(); } catch { MessageBox.Show("couldn't paste comment, sorry"); } |
This is just a simple example of basic interaction with the contents of the code window. We don’t perform any analysis of the content. We simply grab the code, tweak it somehow, and then put it back. However, this is only part of the capabilities built into Visual Studio.
You do not have to put the samples or code revisions back where they came from. You can add code to the top or beginning of the file, or even at a random spot in the middle (although users are not likely to appreciate that).
In order to manipulate the document, you need access to the TextDocument object associated with the current document. This is accomplished with the following command:
Document theDoc = _applicationObject.ActiveDocument; TextDocument theText = (TextDocument)theDoc.Object(); |
The TextDocument object provides two point objects (StartPoint and EndPoint) referring to the expected locations in the body of text. In addition, there are some simple methods to mark and replace text within the document that matches a particular pattern.
For example, the following code sample will replace all occurrences of double with float in the document associated with the text document.
theText.ReplacePattern("double", "float"); |
You can also add FindOptions as the third parameter to control case matching, where to start searching the document, whether to match the whole word, etc. If you need to perform simple replacements on the entire document, the Text Document object provides the methods to do just that.
The Text Document object also allows you to create an EditPoint object, which can be used to position your code anywhere within the document and make edits, deletions, insertions, etc. To create the edit point variable, use the following command:
EditPoint thePoint = theText.CreateEditPoint(); |
This allows a much finer degree of control over text manipulation. An edit point is the location in the file where you want to manipulate text. If you create an EditPoint object with no parameters, it is the same as the starting point from the text document. You can also specify a point as a parameter, so you could create an edit point based on the last location in the document by using EndPoint.
Once you have the EditPoint object, you have a variety of navigation functions to move through the text, such as:
When the point is positioned, you can insert text into the document. You can also use the Get methods to extract text from the document. For example, the following sample looks for lines beginning with using and adds a comment indicating the line needs to be converted to imports when converting from C# to Visual Basic.
|
EditPoint thePoint = theText.CreateEditPoint(); for (int x = 1; x < theText.EndPoint.Line; x++) { if (thePoint.GetText(5).ToString() == "using") { thePoint.EndOfLine(); thePoint.Insert(" // convert to imports statement"); } thePoint.LineDown(1); } |
While the methods and examples in this chapter allow simple text manipulations, they do not provide much in the way of parsing your source code. Fortunately, Visual Studio has a built-in class system that allows us to analyze code windows much more efficiently, without writing our own code parsers or worrying about VB.NET or C#. This system is the code model, and is described in the next chapter.