Visual Studio - inserting multi-line expressions into Watch Window while debugging - c#

While debugging in Visual Studio, how can I insert multi-line expressions into Watch Window, so that each line is not broken into a separate INVALID watch expression. This is really frustrating because I have many expressions spanning multiple lines that I need to watch. Note that both Pin to Source and Immediate Window do not work for tracking multiple values from many places in source code.
e.g.
PyFunc1(Py.kw("var1", var1),
Py.kw("var2", var2))
gets broken to:
PyFunc1(Py.kw("var1", var1),
and
Py.kw("var2", var2))

Repro
I dont think this is "By-Design", its just unavailable "out-of-the-box".
I agree, it'd be better behaviour for multi-line calls to be added to the Watch Window using line terminators instead of new lines:
Research
I found this similar question with a few "workarounds" to choose from:
Multi-Line Watch Window in Visual Studio 2010?
I also found this comment in the MSDN Forums by a MSFT Engineer:
I’m afraid that it is not supported, we often edit them one by one. Maybe you could submit this feature request: http://visualstudio.uservoice.com/forums/121579-visual-studio
Roll your own Visual Studio Add-In
So I had a go at it myself, this is by no means production code but it shows you how to do it:
(click image to enlarge)
namespace AddinMultiLineWatch
{
public class Connect : IDTExtensibility2, IDTCommandTarget
{
//ADD THESE MEMBER VARIABLES
//private DebuggerEvents _debuggerEvents = null;
//private _dispDebuggerEvents_OnEnterBreakModeEventHandler DebuggerEvents_OnEnterBreakMode;
private Window _watchWindow = null;
private CommandEvents _objCommandEvents;
private bool _isRecursive = false;
public Connect()
{
}
public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
{
_applicationObject = (DTE2)application;
_addInInstance = (AddIn)addInInst;
//SET THE MEMBER VARIABLES
//_debuggerEvents = _applicationObject.Events.DebuggerEvents;
//_debuggerEvents.OnEnterBreakMode += new _dispDebuggerEvents_OnEnterBreakModeEventHandler(BreakHandler);
//var watchWindow = _applicationObject.Windows.Item(EnvDTE.Constants.vsWindowKindWatch);
_objCommandEvents = _applicationObject.Events.CommandEvents;
_objCommandEvents.BeforeExecute += new _dispCommandEvents_BeforeExecuteEventHandler(BeforeExecute);
if(connectMode == ext_ConnectMode.ext_cm_UISetup)
{
object []contextGUIDS = new object[] { };
Commands2 commands = (Commands2)_applicationObject.Commands;
string toolsMenuName = "Tools";
Microsoft.VisualStudio.CommandBars.CommandBar menuBarCommandBar = ((Microsoft.VisualStudio.CommandBars.CommandBars)_applicationObject.CommandBars)["MenuBar"];
ar:
CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName];
CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;
try
{
Command command = commands.AddNamedCommand2(_addInInstance, "AddinMultiLineWatch", "AddinMultiLineWatch", "Executes the command for AddinMultiLineWatch", true, 59, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported+(int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);
if((command != null) && (toolsPopup != null))
{
command.AddControl(toolsPopup.CommandBar, 1);
}
}
catch(System.ArgumentException)
{
}
}
}
//ADD THIS METHOD TO INTERCEPT THE DEBUG.ADDWATCH COMMAND
public void BeforeExecute(string Guid, int ID, object CustomIn, object CustomOut, ref bool CancelDefault)
{
EnvDTE.Command objCommand = default(EnvDTE.Command);
try
{
objCommand = _applicationObject.Commands.Item(Guid, ID);
}
catch (Exception ex)
{
}
if ((objCommand != null))
{
if (objCommand.Name == "Debug.AddWatch")
{
//if (_isRecursive) return;
//_isRecursive = true;
TextSelection selection = (TextSelection)_applicationObject.ActiveDocument.Selection;
//TODO make selection goto next semi-colon/Line Terminator...
var selText = selection.Text;
if (string.IsNullOrEmpty(selText)) return;
//Only intercept multi-line Add Watch commands
if (selText.Contains(Environment.NewLine))
{
//THE BLACK MAGIC: make it fit in one line! lol
selText = selText.Replace(Environment.NewLine, string.Empty);
//THIS CALL IS RECURSIVE, I'LL LEAVE IT TO THE READER AS AN EXERCISE TO SOLVE..
_applicationObject.ExecuteCommand("Debug.AddWatch", selText);
}
}
}
}
Create a New Project > Other Project Types > Extensibility > Visual Studio Add-In > name it AddinMultiLineWatch
Go through the wizard
Add the code above to the Connect.cs class - see my //UPPERCASE comments with what stuff to add.
Put a break point on the line TextSelection selection = (TextSelection)_applicationObject.ActiveDocument.Selection;
Press F5 and a new instance of VS will launch > choose New Project > Console App > name it TestMultilineAddWatch
In the program.cs of the Console App, specify a code call over 2 lines and put a break point on it, as shown in the screenshot, eg:
Add(1, //Breakpoint here and select both lines
2);
}
static int Add(int i, int j)
{
return i + j;
}
F5 in the TestMultilineAddWatch solution and when the code control halts on the break point > select/highlight the two lines Add(1, \r\n 2) > right click > Add Watch
Clicking Add Watch in the VS IDE debugging context menu causes the VS AddinMultiLineWatch solution to intercept the call and activate, halting on the break point.... where you will see the black magic of replacing multi lined code in to a single line sent to the Watch Window.
The Visual Studio EXEC command calling itself makes this method recursive, if you debug it, exiting out of the recursion manually you will see the results as per my screenshot.
Happy debugging!

You could do it using autohotkey and a custom key binding ( e.g. Alt+Shift+V)
!+v means Alt+Shift+v
The macro below:
If in devenv.exe, and you press Alt+Shift+V, edit the clipboard contents, removing /r/n and replace them with nothing, then press Ctrl+V to paste
I tested this out cutting and pasting in a text document in visual studio.
#IfWinActive ahk_exe devenv.exe
!+v::
FixString = %clipboard%
StringReplace, FixString, FixString,`r`n,,A
Clipboard := FixString
Send, ^v

Related

VSIX: IErrorTag tooltip content not displaying

I am trying to write a code analysis extension for visual studio using MEF. I have implemented the ITagger interface for an IErrorTag along with the required ITaggerProvider. As a result, i get the expected squiggles in the editor window for the issues my code analysis finds. However, when hovering above the squiggles with the mouse, the respective tooltip content is never displayed.
Here is a minimalistic example which has the same problem:
using Microsoft.VisualStudio.Text.Adornments;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Tagging;
using System;
using System.Collections.Generic;
namespace CodeAnalyzer
{
struct DummyIssue
{
public int Line; // one based line
public string ToolTip;
public DummyIssue(int line, string toolTip)
{
Line = line;
ToolTip = toolTip;
}
}
internal class DummyCodeCheckTagger : ITagger<IErrorTag>
{
readonly List<DummyIssue> mIssues;
readonly ITextView TextView;
public DummyCodeCheckTagger(ITextView textView)
{
TextView = textView;
mIssues = new List<DummyIssue>
{
new DummyIssue(1, "asldfjoqwet"),
new DummyIssue(7, "ASASDAER")
};
textView.LayoutChanged += Update;
}
public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
private void Update(object sender, TextViewLayoutChangedEventArgs args)
{
TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(new SnapshotSpan(args.NewSnapshot, 0, args.NewSnapshot.Length)));
}
IEnumerable<ITagSpan<IErrorTag>> ITagger<IErrorTag>.GetTags(NormalizedSnapshotSpanCollection spans)
{
var issues = mIssues;
foreach (var span in spans)
{
foreach (var issue in issues)
{
int zeroBasedLine = issue.Line - 1;
ITextSnapshotLine snapshotLine = TextView.TextSnapshot.GetLineFromLineNumber(zeroBasedLine);
SnapshotSpan snapshotSpan = snapshotLine.Extent;
if (spans.IntersectsWith(snapshotSpan))
{
yield return new TagSpan<IErrorTag>(snapshotSpan, new ErrorTag(PredefinedErrorTypeNames.SyntaxError, issue.ToolTip));
}
}
}
}
}
}
The result looks like this:
tooltip not displaying
What am i missing to get the tooltip displayed?
Fater's comment above led me to think about the problem again. Since i already tried the suggestions in the document posted by fater without success, i started thinking if the problem could be somewhere else.
It turns out that the ITagger implementation was not the problem, but the ITaggerProvider implementation caused the strange behavior. For that,
I pretty much followed the VSIX ErrorList example implementing a SpellChecker, which contains the following code
/// <summary>
/// Create a tagger that does spell checking on the view/buffer combination.
/// </summary>
public ITagger<T> CreateTagger<T>(ITextView textView, ITextBuffer buffer) where T : ITag
{
ITagger<T> tagger = null;
// Only attempt to spell check on the view's edit buffer (and multiple views could have that buffer open simultaneously so
// only create one instance of the spell checker.
if ((buffer == textView.TextBuffer) && (typeof(T) == typeof(IErrorTag)))
{
var spellChecker = buffer.Properties.GetOrCreateSingletonProperty(typeof(SpellChecker), () => new SpellChecker(this, textView, buffer));
// This is a thin wrapper around the SpellChecker that can be disposed of without shutting down the SpellChecker
// (unless it was the last tagger on the spell checker).
tagger = new SpellCheckerTagger(spellChecker) as ITagger<T>;
}
return tagger;
}
The point is, that the code above only creates an ITagger for a certain view. In that case the created tagger is used only for providing the squiggles in the editor window view. Visual Studio uses a different tagger instance for providing the tooltips for the squiggles and another tagger instance for coloring the scroll bar in the editor window. I had assumed that this would be done by one single tagger instance.

Add code in specific point with Roslyn

Is there a way to add a code in a specific point with Roslyn (also without roslyn)? This is my problem: I develop a vspackage that add a command in the context menu (in CODE WINDOW). When I right click and I select this command it should add some code in that point.
How can I resolve this problem?
You have to:
receive the current text window of visual studio
get the position in the textbuffer (clicking the right mouse button will set the caret position)
insert your text
First things first; receive the text view:
public static IWpfTextView GetCurrentTextView(Package package)
{
try
{
var serviceProvider = package as IServiceProvider;
IVsTextManager textManager = (IVsTextManager)serviceProvider.GetService(typeof(SVsTextManager));
IVsTextView textView;
textManager.GetActiveView(1, null, out textView);
IComponentModel componentModel = (IComponentModel)serviceProvider.GetService(typeof(SComponentModel));
var factoryService = componentModel.GetService<IVsEditorAdaptersFactoryService>();
return factoryService.GetWpfTextView(textView);
}
catch
{
return null;
}
}
Get the caret position from that and insert your text:
IWpfTextView textView = GetCurrentTextView(package);
SnapshotPoint caretPosition = textView.Caret.Position.BufferPosition;
textView.TextBuffer.Insert(caretPosition, "HELLO WORLD");
Don't forget to add error handling.

Visual Studio Extension : Changing the visible text of collapsed blocks

I'm writing a Visual Studio Extension to customize my editor. I would like to change the text that is displayed when a block of code is collapsed.
I have tried the following code :
ITagAggregator<IntraTextAdornmentTag> aggregator;
[...]
aggregator.BatchedTagsChanged += OnBatchedTagsChanged;
[...]
public void OnBatchedTagsChanged(object sender, BatchedTagsChangedEventArgs e)
{
string newText;
bool textCreated;
NormalizedSnapshotSpanCollection list = new NormalizedSnapshotSpanCollection(e.Spans.SelectMany(x => x.GetSpans(textView.TextBuffer)));
if (list.Count != 0)
{
IEnumerable<IMappingTagSpan<IntraTextAdornmentTag>> tags = aggregator.GetTags(list);
foreach (IMappingTagSpan<IntraTextAdornmentTag> tag in tags)
{
if (tag.Tag.Adornment is OutliningCollapsedAdornmentControl)
{
NormalizedSnapshotSpanCollection spans = tag.Span.GetSpans(textView.TextSnapshot);
if (spans.Count == 0) continue;
OutliningCollapsedAdornmentControl adornmentControl = (OutliningCollapsedAdornmentControl)tag.Tag.Adornment;
TextBlock textBlock = adornmentControl.GetChild<TextBlock>();
textCreated = TryCreateText(spans[0], out newText);
if (textCreated)
{
adornmentControl.Content = newText;
textBlock.Text = newText;
}
}
}
}
}
I does change the text, but when it's scrolled out of the the screen and back in, the text reverts back to the default value.
Edit :
I also tried MSDN's walkthrough.
I works fine if I collapse blocks by clicking on the "+" sign, but the blocks don't collapse when I use Ctrl+M+O.
I guess the problem comes from the fact that I'm creating regions that already exist.
Could someone please tell me what I could do?
(Tested both in VS2010 and VS2013 with the same result)

Implementing Find References in Visual Studio 2010 Language Service

I'm implementing a Visual Studio Language Service for a custom scripting language. I've managed to implement syntax highlighting, error checking, code completion, and "Go To Definition". I am having trouble figuring out how to hook in to the "Find All References" menu option (or even get it to display at this point).
Can anyone point me to a useful resource for implementing "Find All References" functionality in Visual Studio for a custom language? I've tried Googling for any information on it, but I can't seem to find anything.
First of all, there are multiple locations where Find All References can be invoked. The primary ones are:
By right clicking on a node in Class View.
By right clicking within the text editor.
Others include:
The Call Hierarchy
Getting Started
In the ideal implementation, you'll have an implementation of IVsSimpleLibrary2 which integrates support for your language into the Class View and Object Browser windows. The implementation of Find All References centers around the IVsFindSymbol interface, which is provided by Visual Studio. Your code handles the relevant searches in the implementation of IVsSimpleLibrary2.GetList2.
Supporting right clicking on a node in Class View
Make sure your library capabilities includes _LIB_FLAGS2.LF_SUPPORTSLISTREFERENCES.
In your handler for IVsSimpleLibrary2.GetList2, you are interested in the case where all of the following are true.
pobSrch is a non-null array of length 1. I'll assume the first element is assigned to the local variable criteria for the remainder of these conditions.
criteria.eSrchType == VSOBSEARCHTYPE.SO_ENTIREWORD
criteria.grfOptions has the flag _VSOBSEARCHOPTIONS.VSOBSO_LOOKINREFS
criteria.grfOptions has the flag _VSOBSEARCHOPTIONS.VSOBSO_CASESENSITIVE
When the above conditions are met, return an IVsSimpleObjectList2 implementation whose children are lazily computed results of a Find All References command.
Supporting the Text Editor Command
In your ViewFilter.QueryCommandStatus implementation, when guidCmdGroup == VSConstants.GUID_VSStandardCommandSet97 and nCmdId == VSStd97CmdID.FindReferences you need to return OLECMDF.OLECMDF_SUPPORTED | OLECMDF.OLECMDF_ENABLED.
In Visual Studio 2005, note that nCmdId will be VSStd2KCmdID.FindReferences but the guidCmdGroup will be the same as mentioned before. This mismatch was corrected starting in Visual Studio 2008, after which point VSStd2KCmdID.FindReferences was no longer used.
Override ViewFilter.HandlePreExec for the case of the command GUID and ID listed above, and execute the following code for that case:
HandleFindReferences();
return true;
Add the following extension method class:
public static class IVsFindSymbolExtensions
{
public static void DoSearch(this IVsFindSymbol findSymbol, Guid symbolScope, VSOBSEARCHCRITERIA2 criteria)
{
if (findSymbol == null)
throw new ArgumentNullException("findSymbol");
VSOBSEARCHCRITERIA2[] criteriaArray = { criteria };
ErrorHandler.ThrowOnFailure(findSymbol.DoSearch(ref symbolScope, criteriaArray));
}
}
Add the following method to your ViewFilter class:
public virtual void HandleFindReferences()
{
int line;
int col;
// Get the caret position
ErrorHandler.ThrowOnFailure( TextView.GetCaretPos( out line, out col ) );
// Get the tip text at that location.
Source.BeginParse(line, col, new TokenInfo(), ParseReason.Autos, TextView, HandleFindReferencesResponse);
}
// this can be any constant value, it's just used in the next step.
public const int FindReferencesResults = 100;
void HandleFindReferencesResponse( ParseRequest req )
{
if ( req == null )
return;
// make sure the caret hasn't moved
int line;
int col;
ErrorHandler.ThrowOnFailure( TextView.GetCaretPos( out line, out col ) );
if ( req.Line != line || req.Col != col )
return;
IVsFindSymbol findSymbol = CodeWindowManager.LanguageService.GetService(typeof(SVsObjectSearch)) as IVsFindSymbol;
if ( findSymbol == null )
return;
// TODO: calculate references as an IEnumerable<IVsSimpleObjectList2>
// TODO: set the results on the IVsSimpleLibrary2 (used as described below)
VSOBSEARCHCRITERIA2 criteria =
new VSOBSEARCHCRITERIA2()
{
dwCustom = FindReferencesResults,
eSrchType = VSOBSEARCHTYPE.SO_ENTIREWORD,
grfOptions = (uint)_VSOBSEARCHOPTIONS2.VSOBSO_LISTREFERENCES,
pIVsNavInfo = null,
szName = "Find All References"
};
findSymbol.DoSearch(new Guid(SymbolScopeGuids80.All), criteria);
}
Update your implementation of IVsSimpleLibrary2.GetList2. When the search criteria's dwCustom value is set to FindReferencesResults, rather than compute the results of a Find All References command on a Class View or Object Browser node, you only need to return an IVsSimpleObjectList2 that wraps the results previously calculated by your HandleFindReferencesResponse method.

FastColoredTextbox AutoWordSelection?

FastColoredTextbox is an user-control that can be downloaded in this url, it looks like this:
Its an amazing control but only can select one word when doubleclicking on the text, can't hold the mouse to select more words, so it only selects the entire current word over the mouse pointer even if you try to move the mouse cursor to left or right to select more text.
I have not found any information explaining the problem, and all of the official example projects has this problem.
Nobody means how to make an AutoWordSelection equivalent of a default TextBox for a FastcoloredTextbox control, but even the most important thing is:
How to select just more than one word with the mouse?
UPDATE:
#mostruash answer is very instructive but in all this time I could not carry out the modifications by myself.
I need a huge help from a C# programmer to carry out this task, my knowledge of C# is very low and the modifications that I made to the source did not work (don't compiled), I went back to the original user-control source to not end up spoiling more. I hate to say this but this time I need the job done, this source is too much for me.
If I'm requesting for too much then maybe with the necesary extended instructions of a C# developer, explaining how to accomplish this step by step, maybe I could carry it out by myself.
UPDATE
A video that demostrates the problem:
https://www.youtube.com/watch?v=Cs2Sh2tMvII
UPDATE
Another demo, I show what the FastColoredTextBox can't do but I would like to do like every other text-editor can do:
I've checked the source code of the project. Dragging is cancelled if a double click occurs and SelectWord is called.
You could modify the source code to include the feature that you request. (https://github.com/PavelTorgashov/FastColoredTextBox). In that case:
You must trace selections that start with double clicks.
Instead of calling SelectWord function, use the Selection class and draggedRange attribute to mark the selected word in OnMouseMove.
You also must handle deselection of words in OnMouseMove.
You must also select spaces between words in OnMouseMove.
The double click is handled in the code piece below:
if (!isLineSelect)
{
var p = PointToPlace(e.Location);
if (e.Clicks == 2)
{
mouseIsDrag = false; //Here, drag is cancelled.
mouseIsDragDrop = false;
draggedRange = null; //Drag range is nullified
SelectWord(p); //SelectWord is called to mark the word
return;
}
if (Selection.IsEmpty || !Selection.Contains(p) || this[p.iLine].Count <= p.iChar || ReadOnly)
OnMouseClickText(e);
else
{
mouseIsDragDrop = true;
mouseIsDrag = false;
}
}
EDIT:
This may require a lot of work to accomplish. So maybe you should use another tool/library. I have not studied the whole source code so there will probably be additional steps to those provided above.
For example, to trace double clicks you can do the following:
Define a class variable/property in FastColoredTextbox.cs: bool isDoubleClick.
Set it to true in OnMouseDown under if(e.Clicks == 2) condition. Set it to false in all other conditions.
Set it to false in OnMouseClick or OnMouseUp or in other relevant mouse event handlers.
That way you will know if series of mouse events had started with a double click event or not. Then you would act accordingly in OnMouseMove because that is where you (un)mark characters or (un)mark words.
LAST WORDS OF CAUTION:
The author of that project did not include any inline comments or any other means of documentation so you will be studying the code line by line to understand what each function/part does.
Add the following statement between Line 5276 and line 5277 in the class FastColoredTextBox.cs:
SelectWord(p);
mouseIsDrag = true; // here
return;
Note that implementing the ultimate behavior would require a good bunch of coding. Whereas the workaround mentioned above might satisfy your needs.
As #mostruash points out in his answer, that is the place where author cancels the mouse drag. Not sure why he deliberately prevents this feature. Only he knows.
if (e.Clicks == 2)//Line 5270
{
mouseIsDrag = false;
mouseIsDragDrop = false;
draggedRange = null;
SelectWord(p);
return;
}
I didn't read whole code, and I have no reason to do it. I just checked quickly and removed them. And it works as you expect.
if (e.Clicks == 2)//Line 5270
{
//Comment or remove completely.
//mouseIsDrag = false;
//mouseIsDragDrop = false;
//draggedRange = null;
SelectWord(p);
return;
}
Note: Am not sure this breaks something else, I've not tested. At least that works. Test it yourself.
My solution is a bit tweaky, but seems to work at first glance.
You have to make some changes in the Code:
Add mouseIsWholeWordSelection flag and a Range variable which can store the initial selected range after double click (best after line 100, I guess):
private bool mouseIsWholeWordSelection;
private Range mouseIsWholeWordSelectionBaseRange;
Change the selection code for double click event as stated above and extend it a bit (line 5222):
if (e.Clicks == 2)
{
//mouseIsDrag = false;
mouseIsDragDrop = false;
mouseIsWholeWordSelection = true;
//draggedRange = null;
SelectWord(p);
mouseIsWholeWordSelectionBaseRange = Selection.Clone();
return;
}
Add evaluation of dragging event for recreating selection (line 5566):
else if (place != Selection.Start)
{
if (mouseIsWholeWordSelection)
{
Selection.BeginUpdate();
var oldSelection = Selection.Clone();
SelectWord(place);
if (Selection.End >= mouseIsWholeWordSelectionBaseRange.End)
{
Selection.Start = (mouseIsWholeWordSelectionBaseRange.Start > Selection.Start) ? mouseIsWholeWordSelectionBaseRange.Start : Selection.Start;
Selection.End = mouseIsWholeWordSelectionBaseRange.End;
}
else if (Selection.Start < mouseIsWholeWordSelectionBaseRange.End)
{
Selection.Start = new Place(Selection.End.iChar, Selection.End.iLine);
Selection.End = mouseIsWholeWordSelectionBaseRange.Start;
}
Selection.EndUpdate();
DoCaretVisible();
Invalidate();
}
else
{
Place oldEnd = Selection.End;
Selection.BeginUpdate();
if (Selection.ColumnSelectionMode)
{
Selection.Start = place;
Selection.ColumnSelectionMode = true;
}
else
Selection.Start = place;
Selection.End = oldEnd;
Selection.EndUpdate();
DoCaretVisible();
Invalidate();
}
return;
}
Add at every place where isMouseDrag is being set to false:
isMouseWholeWordSelection = false;
And there you go.

Categories

Resources