One of the really useful Visual Studio add-ins is Smart Paster. It adds a "Paste As." context menu that allows you to paste in the clipboard text as a comment, a correctly quoted string or a string builder.
There are versions for VS 2003, 2005 and 2008. But not 2010.
Sometimes you can just copy in the dll and addin file into the VS 2010 Addins folder (.\Documents\Visual Studio 2010\Addins) and edit the addin file (it's just xml) to say "10.0" instead of "9.0". But that doesn't work for SmartPaster - VS 2010 shows an error and insists on disabling the addin.
The VS 2008 download includes the source, so I tried to upgrade it.
It turns out the problem is when it creates the context menus it sets the CommandBarButton.FaceId property (to show an image next to the text). But in VS2010 that throws a DeprecatedException.
Ok, simple fix, but the original source is old code with a fairly high WTF-per-line ratio (well, it was written 2004, .Net 1.1). Before long I had ported it from VB.Net to C# (thanks Telerik) and rewritten large parts (mostly refactoring with Coderush). I simplified by dropping the "regionize" stuff (never use it), the VB support and the configuration form. Here's my code- you can create a new Extensibility Addin project, replace the Connect class and add the SmartPaster class- see below.
It's still a port, so certainly not as clean as something just written from scratch. And perhaps VS2010 has nicer ways of doing all these things now the code window is a WPF control - the EnvDTE objects are ugly and hard to use. Anyway, thanks to Alex Papadimoulis for the original code.
Update: Originally hosted a binary here, but now see below
Update 2: source and binary are also on Codeplex. (It's exactly the same as shown here.)
Update 3 (March 2012): The Codeplex version is updated to support VB.Net and is compatible with Visual Studio 11
Update 4 (2013): Now on Githhub as SmartPaster 2013 (also supports VS 2015)
1: using System;
2: using System.Collections;
3: using EnvDTE;
4: using EnvDTE80;
5: using Extensibility;
6: using Microsoft.VisualStudio.CommandBars;
7:
8: namespace SmartPaster2010
9: {
10: ///<summary>The object for implementing an Add-in.</summary>
11: ///<seealso class='IDTExtensibility2' />
12: public class Connect : IDTExtensibility2
13: {
14: private readonly ArrayList _pasteAsButtons;
15: private readonly SmartPaster _smartPaster;
16: private CommandBarPopup _pasteAsPopup;
17:
18: ///<summary>Implements the constructor for the Add-in object. Place your initialization code within this method.</summary>
19: public Connect()
20: {
21: _pasteAsButtons = new ArrayList();
22: _smartPaster = new SmartPaster();
23: }
24:
25: ///<summary>Implements the OnConnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being loaded.</summary>
26: ///<param term='application'>Root object of the host application.</param>
27: ///<param term='connectMode'>Describes how the Add-in is being loaded.</param>
28: ///<param term='addInInst'>Object representing this Add-in.</param>
29: ///<seealso class='IDTExtensibility2' />
30: public void OnConnection(object application, ext_ConnectMode connectMode,
object addInInst, ref Array custom)
31: {
32: _applicationObject = (DTE2)application;
33: _addInInstance = (AddIn)addInInst;
34:
35:
36: //check for the commands
37: bool cmdExists = false;
38: foreach (Command cmd in _applicationObject.Commands)
39: {
40: if (cmd.Name.EndsWith("PasteAsComment", StringComparison.OrdinalIgnoreCase))
41: {
42: cmdExists = true;
43: break;
44: }
45: }
46:
47: try
48: {
49: if (!cmdExists)
50: {
51: AddPasteAsCommands();
52: }
53:
54: if (connectMode == ext_ConnectMode.ext_cm_Startup && _pasteAsPopup == null)
55: {
56: //Add items to the Context (Right-Click) Menu
57: //find the position of the &Paste command
58: int position = 0;
59:
60: CommandBar codeWindow = _applicationObject.CommandBars["Code Window"];
61:
62: for (int i = 1; i <= codeWindow.Controls.Count; i++)
63: {
64: if (codeWindow.Controls[i].Caption == "&Paste")
65: {
66: position = i;
67: break;
68: }
69: }
70:
71: //add the popup menu "Paste As...", which may already be on the menu
72: _pasteAsPopup = (CommandBarPopup)codeWindow.Controls.Add(
(int)MsoControlType.msoControlPopup, 1, Type.Missing, position + 1, Type.Missing);
73: _pasteAsPopup.Caption = "Paste As ...";
74: AddPasteAsButtons();
75: }
76:
77: }
78: catch (Exception ex)
79: {
80: System.Diagnostics.Debug.WriteLine(ex.Message);
81: }
82: }
83:
84: private void AddPasteAsCommands()
85: {
86: //no configure or regionize because I never use 'em
87: _applicationObject.Commands.AddNamedCommand(_addInInstance, "PasteAsComment", "Paste As Comment", "Pastes clipboard text as a comment.", true, 22, null, Convert.ToInt32(vsCommandStatus.vsCommandStatusSupported) + Convert.ToInt32(vsCommandStatus.vsCommandStatusEnabled));
88:
89: _applicationObject.Commands.AddNamedCommand(_addInInstance, "PasteAsString", "Paste As String", "Pastes clipboard text as a string literal.", true, 22, null, Convert.ToInt32(vsCommandStatus.vsCommandStatusSupported) + Convert.ToInt32(vsCommandStatus.vsCommandStatusEnabled));
90:
91: _applicationObject.Commands.AddNamedCommand(_addInInstance, "PasteAsStringBuilder", "Paste As StringBuilder", "Pastes clipboard text as a stringbuilder.", true, 22, null, Convert.ToInt32(vsCommandStatus.vsCommandStatusSupported) + Convert.ToInt32(vsCommandStatus.vsCommandStatusEnabled));
92: }
93:
94:
95: private void AddPasteAsButtons()
96: {
97:
98: //now the buttons
99: CommandBarButton pasteAsButton;
100:
101: //add "Comment"
102: pasteAsButton = AddCommandButton();
103: pasteAsButton.Caption = "Comment";
104: pasteAsButton.TooltipText = "Inserts clipboard with each line prefixed with a comment character";
105: pasteAsButton.Click += PasteAsComment;
106: _pasteAsButtons.Add(pasteAsButton);
107:
108: //add "String"
109: pasteAsButton = AddCommandButton();
110: pasteAsButton.Caption = "String";
111: pasteAsButton.TooltipText = "Inserts enquoted clipboard text with line breaks and other characters escaped";
112: pasteAsButton.Click += PasteAsString;
113: _pasteAsButtons.Add(pasteAsButton);
114:
115: //add "StringBuilder"
116: pasteAsButton = AddCommandButton();
117: pasteAsButton.Caption = "StringBuilder";
118: pasteAsButton.TooltipText = "Inserts enquoted clipboard text built up by a stringbuilder.";
119: pasteAsButton.Click += PasteAsStringBuilder;
120: _pasteAsButtons.Add(pasteAsButton);
121:
122: }
123:
124: private CommandBarButton AddCommandButton()
125: {
126: var pasteAsButton = (CommandBarButton)_pasteAsPopup.Controls.Add((int)MsoControlType.msoControlButton);
127: //in 2010, CommandBarButton.FaceId throws a DeprecatedException.
128: //pasteAsButton.FaceId = 22;
129: pasteAsButton.Visible = true;
130: return pasteAsButton;
131: }
132:
133: #region "PasteAs Handlers"
134:
135: ///<summary>
136: ///Occurs when the user clicks the PasteAsString button.
137: ///</summary>
138: ///<param name="ctrl">
139: ///Denotes the CommandBarButton control that initiated the event.
140: ///</param>
141: ///<param name="cancelDefault">
142: ///False if the default behavior associated with the CommandBarButton control occurs, unless its canceled by another process or add-in.
143: ///</param>
144: private void PasteAsString(CommandBarButton ctrl, ref bool cancelDefault)
145: {
146: _smartPaster.PasteAsString(_applicationObject);
147: }
148:
149:
150: ///<summary>
151: ///Occurs when the user clicks the PasteAsComment button.
152: ///</summary>
153: ///<param name="ctrl">
154: ///Denotes the CommandBarButton control that initiated the event.
155: ///</param>
156: ///<param name="cancelDefault">
157: ///False if the default behavior associated with the CommandBarButton control occurs, unless its canceled by another process or add-in.
158: ///</param>
159: private void PasteAsComment(CommandBarButton ctrl, ref bool cancelDefault)
160: {
161: _smartPaster.PasteAsComment(_applicationObject);
162: }
163:
164: ///<summary>
165: ///Occurs when the user clicks the PasteAsStringBuilder button.
166: ///</summary>
167: ///<param name="ctrl">
168: ///Denotes the CommandBarButton control that initiated the event.
169: ///</param>
170: ///<param name="cancelDefault">
171: ///False if the default behavior associated with the CommandBarButton control occurs, unless its canceled by another process or add-in.
172: ///</param>
173: private void PasteAsStringBuilder(CommandBarButton ctrl, ref bool cancelDefault)
174: {
175: _smartPaster.PasteAsStringBuilder(_applicationObject);
176: }
177: #endregion
178:
179: #region "Exec"
180:
181: ///<summary>
182: /// Implements the Exec method of the IDTCommandTarget interface.
183: /// This is called when the command is invoked.
184: ///</summary>
185: ///<param name='commandName'>
186: /// The name of the command to execute.
187: ///</param>
188: ///<param name='executeOption'>
189: /// Describes how the command should be run.
190: ///</param>
191: ///<param name='varIn'>
192: /// Parameters passed from the caller to the command handler.
193: ///</param>
194: ///<param name='varOut'>
195: /// Parameters passed from the command handler to the caller.
196: ///</param>
197: ///<param name='handled'>
198: /// Informs the caller if the command was handled or not.
199: ///</param>
200: ///<seealso class='Exec' />
201: public void Exec(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)
202: {
203: handled = false;
204: if ((executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault))
205: {
206: handled = true;
207: switch (commandName)
208: {
209: //case "SmartPaster.Connect.Configure":
210: // //show the config form
211: // SmartPasterForm myForm = new SmartPasterForm();
212: // myForm.ShowDialog();
213: // //since config may have changed, show/hide buttons
214: // EnableContextMenuButtons();
215:
216: // break;
217: case "SmartPaster.Connect.PasteAsComment":
218: _smartPaster.PasteAsComment(_applicationObject);
219: break;
220: case "SmartPaster.Connect.PasteAsString":
221: _smartPaster.PasteAsString(_applicationObject);
222: break;
223: case "SmartPaster.Connect.PasteAsStringBuilder":
224: _smartPaster.PasteAsStringBuilder(_applicationObject);
225: break;
226: //case "SmartPaster.Connect.PasteAsRegion":
227: // _smartPaster.PasteAsRegion(_applicationObject);
228: // break;
229: default:
230: handled = false;
231: break;
232: }
233: }
234: }
235: #endregion
236:
237: #region Standard Template Stuff
238: ///<summary>
239: ///Implements the OnDisconnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being unloaded.
240: ///</summary>
241: ///<param name="disconnectMode">The disconnect mode.</param>
242: ///<param name="custom">The custom.</param>
243: ///<seealso class="IDTExtensibility2"/>
244: public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
245: {
246: if (_pasteAsPopup != null &&
247: (disconnectMode == ext_DisconnectMode.ext_dm_UserClosed || disconnectMode == ext_DisconnectMode.ext_dm_HostShutdown))
248: {
249: _pasteAsPopup.Delete(true);
250: }
251: }
252:
253: ///<summary>Implements the OnAddInsUpdate method of the IDTExtensibility2 interface. Receives notification when the collection of Add-ins has changed.</summary>
254: ///<param term='custom'>Array of parameters that are host application specific.</param>
255: ///<seealso class='IDTExtensibility2' />
256: public void OnAddInsUpdate(ref Array custom)
257: {
258: }
259:
260: ///<summary>Implements the OnStartupComplete method of the IDTExtensibility2 interface. Receives notification that the host application has completed loading.</summary>
261: ///<param term='custom'>Array of parameters that are host application specific.</param>
262: ///<seealso class='IDTExtensibility2' />
263: public void OnStartupComplete(ref Array custom)
264: {
265: }
266:
267: ///<summary>Implements the OnBeginShutdown method of the IDTExtensibility2 interface. Receives notification that the host application is being unloaded.</summary>
268: ///<param term='custom'>Array of parameters that are host application specific.</param>
269: ///<seealso class='IDTExtensibility2' />
270: public void OnBeginShutdown(ref Array custom)
271: {
272: }
273:
274: private DTE2 _applicationObject;
275: private AddIn _addInInstance;
276: #endregion
277:
278: ///<summary>
279: ///Queries the status.
280: ///</summary>
281: ///<param name="commandName">Name of the command.</param>
282: ///<param name="neededText">The needed text.</param>
283: ///<param name="statusOption">The status option.</param>
284: ///<param name="commandText">The command text.</param>
285: public void QueryStatus(string commandName, vsCommandStatusTextWanted neededText, ref vsCommandStatus statusOption, ref object commandText)
286: {
287: if (neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
288: {
289: if (commandName.StartsWith("SmartPaster.Connect"))
290: {
291: if (((_applicationObject.ActiveDocument != null)) && ((_applicationObject.ActiveDocument.Object("TextDocument") != null)))
292: {
293: statusOption = vsCommandStatus.vsCommandStatusEnabled | vsCommandStatus.vsCommandStatusSupported;
294: }
295: else
296: {
297: statusOption = vsCommandStatus.vsCommandStatusSupported;
298: }
299: }
300: else
301: {
302: statusOption = vsCommandStatus.vsCommandStatusUnsupported;
303: }
304: }
305: }
306: }
307: }
Here's the SmartPaster class.
1: using System;
2: using System.IO;
3: using System.Text;
4: using System.Windows.Forms; //clipboard
5: using EnvDTE;
6: using EnvDTE80;
7:
8: namespace SmartPaster2010
9: {
10: /// <summary>
11: /// Class responsible for doing the pasting/manipulating of clipdata.
12: /// </summary>
13: internal sealed class SmartPaster
14: {
15: /// <summary>
16: /// Convient property to retrieve the clipboard text from the clipboard
17: /// </summary>
18: private static string ClipboardText
19: {
20: get
21: {
22: IDataObject iData = Clipboard.GetDataObject();
23: if (iData.GetDataPresent(DataFormats.Text))
24: return Convert.ToString(iData.GetData(DataFormats.Text));
25: return string.Empty;
26: }
27: }
28:
29: #region "Stringinize"
30: /// <summary>
31: /// Stringinizes text passed to it for use in C#
32: /// </summary>
33: /// <param name="txt">Text to be Stringinized</param>
34: /// <returns>C# Stringinized text</returns>
35: private static string StringinizeInCs(string txt)
36: {
37: //c# quote character -- really just a "
38: const string qChr = "\"";
39:
40: //sb to work with
41: var sb = new StringBuilder(txt);
42:
43: //escape appropriately
44: //escape the quotes with ""
45: sb.Replace(qChr, qChr + qChr);
46:
47: //insert " at beginning and end
48: sb.Insert(0, "@" + qChr);
49: sb.Append(qChr);
50: return sb.ToString();
51: }
52: #endregion
53:
54: #region "Commentize"
55: /// <summary>
56: /// Commentizes text passed to it for use in C#
57: /// </summary>
58: /// <param name="txt">Text to be Stringinized</param>
59: /// <returns>C# Commentized text</returns>
60: private static string CommentizeInCs(string txt)
61: {
62: const string cmtChar = "//";
63:
64: var sb = new StringBuilder(txt.Length);
65:
66: //process the passed string (txt), one line at a time
67: //the original was horrible WTF code
68: using (var reader = new StringReader(txt))
69: {
70: string line;
71: while ((line = reader.ReadLine()) != null)
72: {
73: sb.AppendLine(cmtChar + line);
74: }
75: }
76:
77: return sb.ToString();
78: }
79: #endregion
80:
81: #region "Stringbuilderize"
82: private static string StringbuilderizeInCs(string txt, string sbName)
83: {
84: //c# quote character -- really just a "
85: const string qChr = "\"";
86:
87: //sb to work with
88: var sb = new StringBuilder(txt);
89:
90: //escape \,", and {}
91: sb.Replace(qChr, qChr + qChr);
92:
93: //process the passed string (txt), one line at a time
94:
95: //dump the stringbuilder into a temp string
96: string fullString = sb.ToString();
97: sb.Clear(); //lovely .net 4 - sb.Remove(0, sb.Length);
98:
99: //the original was horrible WTF code
100: using (var reader = new StringReader(fullString))
101: {
102: string line;
103: while ((line = reader.ReadLine()) != null)
104: {
105: sb.Append(sbName + ".AppendLine(");
106: sb.Append("@" + qChr);
107: sb.Append(line.Replace("\t", "\\t"));
108: sb.AppendLine(qChr + ");");
109: }
110: }
111:
112: //TODO: Better '@"" + ' replacement to not cover inside strings
113: sb.Replace("@" + qChr + qChr + " + ", "");
114:
115: //add the dec statement
116: sb.Insert(0, "StringBuilder " + sbName + " = new StringBuilder(" + txt.Length + ");" + Environment.NewLine);
117:
118: //and return
119: return sb.ToString();
120: }
121: #endregion
122:
123: /// <summary>
124: /// Inserts text at current cursor location in the application
125: /// </summary>
126: /// <param name="application">application with activewindow</param>
127: /// <param name="text">text to insert</param>
128: private static void Paste(DTE2 application, string text)
129: {
130: //get the text document
131: var txt = (TextDocument)application.ActiveDocument.Object("TextDocument");
132:
133: //get an edit point
134: EditPoint ep = txt.Selection.ActivePoint.CreateEditPoint();
135:
136: //get a start point
137: EditPoint sp = txt.Selection.ActivePoint.CreateEditPoint();
138:
139: //open the undo context
140: bool isOpen = application.UndoContext.IsOpen;
141: if (!isOpen)
142: application.UndoContext.Open("SmartPaster");
143:
144: //clear the selection
145: if (!txt.Selection.IsEmpty)
146: txt.Selection.Delete();
147:
148: //insert the text
149: //ep.Insert(Indent(text, ep.LineCharOffset))
150: ep.Insert(text);
151:
152: //smart format
153: sp.SmartFormat(ep);
154:
155: //close the context
156: if (!isOpen)
157: application.UndoContext.Close();
158: }
159:
160: #region "Paste As ..."
161:
162: /// <summary>
163: /// Public method to paste and format clipboard text as string the cursor
164: /// location for the configured or active window's langage .
165: /// </summary>
166: /// <param name="application">application to insert</param>
167: public void PasteAsString(DTE2 application)
168: {
169: Paste(application, StringinizeInCs(ClipboardText));
170: }
171:
172: /// <summary>
173: /// Public method to paste and format clipboard text as comment the cursor
174: /// location for the configured or active window's langage .
175: /// </summary>
176: /// <param name="application">application to insert</param>
177: public void PasteAsComment(DTE2 application)
178: {
179: Paste(application, CommentizeInCs(ClipboardText));
180: }
181:
182:
183: /// <summary>
184: /// Public method to paste format clipboard text into a specified region
185: /// </summary>
186: /// <param name="application">application to insert</param>
187: public void PasteAsRegion(DTE2 application)
188: {
189: //get the region name
190: const string region = "myRegion";
191:
192: //it's so simple, we really don't need a function
193: string csRegionized = "#region " + region + Environment.NewLine + ClipboardText + Environment.NewLine + "#endregion";
194:
195: //and paste
196: Paste(application, csRegionized);
197: }
198:
199: /// <summary>
200: /// Public method to paste and format clipboard text as stringbuilder the cursor
201: /// location for the configured or active window's langage .
202: /// </summary>
203: /// <param name="application">application to insert</param>
204: public void PasteAsStringBuilder(DTE2 application)
205: {
206: const string stringbuilder = "sb";
207: Paste(application, StringbuilderizeInCs(ClipboardText, stringbuilder));
208: }
209:
210: #endregion
211: }
212: }