Welcome to Part 12 of the series showing how to build plug-ins for Visual Studio using DXCore.
So far we've:
Today, in our final post of the 12-part series, we'll add a neat feature that shows how to paste code copied from one language into another.
Paste As Language Conversions
I know a number of developers who work in more than one language. And sometimes when researching a question online I'll find example code in the wrong language. While the DXCore was never specifically built to convert from one language to another, it has a pretty decent (but not always 100% perfect) ability to do so. And so I'm thinking it might be nice to offer the ability to convert code copied from one language into another just before pasting.
I also like this feature because it shows how to convert source code from one language to another using the DXCore.
So let's add a "Paste as..." menu item which can convert code in the clipboard history into the active language. This menu item would be available when the active language is different from the language of the entry.
Right-click the horizontal bar in our ContextMenuStrip and choose Insert | MenuItem.
Set these properties:
Property |
Value |
(Name) |
itmPasteAs |
Text |
Paste as |
Create an event handler for this menu item's Click event....
In the handler, add a call to PasteAsTargetLanguage (we'll implement this soon):
private void itmPasteAs_Click(object sender, EventArgs e)
{
PasteAsTargetLanguage();
}
Next, let's ensure this menu item is only available under the right conditions, e.g., when the selected clipboard entry contains source code in a language that is different from the language of the active source file.
In the FrmClipHistory.cs [Design] file, click the contextMenuStrip1 control.
In the Properties grid, click the Events tab.
Create a handler for the Opening event. This event fires just before the context menu appears.
Inside the Opening handler, add the following code:
private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
{
itmPasteAs.Visible = false;
LanguageExtensionBase sourceLanguage;
LanguageExtensionBase targetLanguage;
GetLanguagesForConversion(out sourceLanguage, out targetLanguage);
if (sourceLanguage == null || targetLanguage == null)
return;
itmPasteAs.Visible = true;
itmPasteAs.Text = "Paste as " + targetLanguage.LanguageID;
}
This code will make the itmPasteAs menu item visible only if the language from which the source code in the current entry was copied.... LEFT OFF HERE
GetLanguagesForConversion determines the source and target languages necessary for conversion, and looks like this:
private static void GetLanguagesForConversion(out LanguageExtensionBase sourceLanguage, out LanguageExtensionBase targetLanguage)
{
sourceLanguage = null;
targetLanguage = null;
ClipboardHistoryEntry entry = ClipboardHistory.GetEntry(CursorIndex);
if (entry == null || String.IsNullOrEmpty(entry.Language) || entry.Language == CodeRush.Language.Active)
return;
sourceLanguage = CodeRush.Language.GetLanguageExtension(entry.Language);
targetLanguage = CodeRush.Language.ActiveExtension;
// Only return languages that supports types....
if (sourceLanguage != null && !sourceLanguage.SupportsTypes)
sourceLanguage = null;
if (targetLanguage != null && !targetLanguage.SupportsTypes)
targetLanguage = null;
}
Notice the check in the code above to make sure both languages return true in their SupportsTypes properties. This check is performed because there are languages supported by the DXCore that do not support types, such as HTML, and converting from HTML to Visual Basic, for example, would probably not yield useful results. So this simple check should ensure that both the language of the clipboard entry and the language of the active file we're about to paste into, both have a good chance of producing a relatively useful conversion.
Finally, our PasteAsTargetLanguage method looks like this:
private void PasteAsTargetLanguage()
{
LanguageExtensionBase sourceLanguage;
LanguageExtensionBase targetLanguage;
GetLanguagesForConversion(out sourceLanguage, out targetLanguage);
if (sourceLanguage != null && targetLanguage != null)
{
ParserBase parser = CodeRush.Language.GetParserFromLanguageID(sourceLanguage.LanguageID);
if (parser != null)
{
LanguageElement rootNode = parser.ParseString(ClipboardHistory.GetText(CR_ClipboardHistory.FrmClipHistory.CursorIndex));
if (rootNode != null)
{
CloseAndPaste(CodeRush.Language.GenerateElement(rootNode, targetLanguage));
return;
}
}
}
CloseAndPaste();
}
This method gets the source languages necessary for conversion, and then gets a parser for the source language, calling ParseString to turn that code into a parse tree. Then it takes that parse tree and feeds it to GenerateElement to get the source code equivalent in the target language. With the conversion complete all that's needed is to pass those results on to CloseAndPaste. And speaking of CloseAndPaste, I needed to refactor that method so it could optionally accept the text to paste (instead of taking it directly from the ClipboardHistory). The refactored CloseAndPaste and its corresponding overload (to prevent existing code from breaking) look like this:
private void CloseAndPaste()
{
string thisText = ClipboardHistory.GetText(CursorIndex);
CloseAndPaste(thisText);
}
private void CloseAndPaste(string textToPaste)
{
Application.RemoveMessageFilter(this);
PasteOnClose = true;
if (textToPaste != Clipboard.GetText() && !String.IsNullOrEmpty(textToPaste)) // Need to put text on clipboard.
Clipboard.SetText(textToPaste);
// Simply calling close here will fail when a mouse double-clicks on a CodeView.
NativeMethods.PostMessage(this.Handle, WindowMessage.WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}
Adding this optional parameter makes it possible for us to condition the text before pasting (such as what we're doing here in converting code from one language to another).
Finally, let's add keyboard support for this so developers can paste translated source code without being forced to reach for the mouse. We've already assigned the Enter key to our Paste functionality, so it seems logical to make Alt+Enter be our alternate "Paste as..." shortcut. This requires a small addition to our ProcessCmdKey method:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
const int WM_KEYDOWN = 0x100;
if (msg.Msg == WM_KEYDOWN)
{
switch (keyData)
{
case Keys.Enter:
CloseAndPaste();
return true;
case Keys.Enter | Keys.Alt:
PasteAsTargetLanguage();
return true;
case Keys.Escape:
Close();
return true;
}
switch (keyData & ~Keys.Control)
{
case Keys.Left:
MoveCursor(0, -1);
return true;
case Keys.Right:
MoveCursor(0, 1);
return true;
case Keys.Up:
MoveCursor(-1, 0);
return true;
case Keys.Down:
MoveCursor(1, 0);
return true;
}
}
return base.ProcessCmdKey(ref msg, keyData);
}
And that's it for the code changes. One thing I've noticed, however, is when we bring up the context menu on the Clipboard History, there are no shortcuts shown in the menu. For example, Enter is already our Paste command trigger, however you see no mention of that when the menu is up:
So let's fix this. I noticed that the ShortcutKeys property of the ToolStripMenuItem control seems to be missing an option to compose a shortcut with the Enter key. Fortunately this control also has a string property ShortcutKeyDisplayString, which will work.
Set the ShortcutKeyDisplayString properties of the two Paste and Paste as menu items to "Enter" and "Alt+Enter", respectively.
The form should now look like this:
Now it's time to test. Click Run. Open a file in one language (e.g., C#), and copy some code to the clipboard. For example, I've selected this method with a C# initializer in it:
private static Form CreateNewWindow()
{
var window = new Form { Name = "New window", Width = 640, Height = 480 };
return window;
}
Open a file in a different language (e.g., VB), press Ctrl+Shift+Insert to bring up the Clipboard History, right click the entry to paste, and select "Paste as Basic" (or whatever language you've selected.
Here's the code I get when pasting in Visual Basic:
Private Shared Function CreateNewWindow() As Form
Dim window = New Form() With {.Name = "New window", .Width = 640, .Height = 480}
Return window
End Function
Excellent! As I alluded to at the beginning of this post, language conversion has never been the number one priority with the DXCore. It's really more a side-effect of the architecture that you get for free, so you may find examples where the results require some editing (especially in areas where features exist in the source language but not the target language). So it's not always perfect, but it's still pretty cool.
Wrap Up
For now I think this Clipboard History feature is done. We've come a long way since part one of this series, where we had an idea for a simple plug-in to provide quick access to a history of clipboard operations. Now we have syntax highlighting, ease of use, discoverability, persistence, customization (with preview) and language conversion, all in a professional package with concise code that should be easy to maintain. And this series should have given you a sense of what it takes to produce a high quality feature that many developers will enjoy.
Speaking of which, CodeRush customers will get this feature (and full source code) wrapped up into the next release (3.2). For eveyone else, you'll need to download the DXCore and follow the steps of this series (or you could purchase a license CodeRush -- it rocks on multiple levels)....
So what's next? Well, the next step is up to you. What cool feature will you build and share with your peers? Let us know what you're working on and what we can do to make writing plug-ins even easier and more enjoyable. Also, let us know what parts of the DXCore we should cover in more depth with tutorials like this.
Free DevExpress Products - Get Your Copy Today
The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the
DevExpress Support Center at your convenience. We'll be happy to follow-up.