📜 ⬆️ ⬇️

Small Add-In for Visual Studio

When a solution-file contains a sufficiently large number of projects, the assembly of binary files turns into a process that requires a considerable amount of time, and the report on the assembly into a sheet of several megabytes. Personally, I have a similar line at the very end at such scales puzzling:

========== Build: 258 succeeded, 1 failed, 40 up-to-date, 1 skipped ==========

And besides bewilderment, the logical question is: what, in fact, broke? There is, of course, the tab “Error list”, but unfortunately it does not show the names of the projects - only the files, and with this amount of source code, and given the fact that a large team is working on this solution, it’s quite difficult to determine for memory the ownership of a file to a specific project (read to identify those responsible for the broken assembly). Scrolling through the report in search of the name of the project containing errors, in my opinion, is not entirely appropriate.

Taking into account all the above, I decided to combine business with pleasure, having received a small idea of ​​what VS Exstensibility is, while providing the studio with a little “convenience”. "Convenience" should allow one click of the mouse to remove all unnecessary from the report on the assembly, leaving only error messages and the names of the projects containing them.
')

Everything turned out to be quite simple. Starting with the finished project template:

image

and ending with a fairly obvious set of classes, which allows you to practically not look into the documentation, using only the editor’s hints.

After creating the project, quite a lot of code is generated (the choice of language is provided; in my case, C #). In fact, only two places are interesting there: the OnConnect and Exec methods, where the first is, as it is easy to guess initialization, and the second is the immediate implementation of our logic.

OnConnect we need only to make a new item in the Tools menu. In fact, in the code generated for us, all this is already there, we just have to correct the creation of the command itself:
try
{
//Add a command to the Commands collection:
Command command = commands.AddNamedCommand2(_addInInstance,
"BuildStat" ,
"Show failed build parts" ,
"Provides information about failed parts of performed solution build" ,
true ,
56,
ref contextGUIDS,
( int )vsCommandStatus.vsCommandStatusSupported + ( int )vsCommandStatus.vsCommandStatusEnabled,
( int )vsCommandStyle.vsCommandStylePictAndText,
vsCommandControlType.vsCommandControlTypeButton);

//Add a control for the command to the tools menu:
if ((command != null ) && (toolsPopup != null ))
{
command.AddControl(toolsPopup.CommandBar, 1);
}
}
catch (System.ArgumentException)
{
//If we are here, then the exception is probably because a command with that name
// already exists. If so there is no need to recreate the command and we can
// safely ignore the exception.
}

* This source code was highlighted with Source Code Highlighter .
try
{
//Add a command to the Commands collection:
Command command = commands.AddNamedCommand2(_addInInstance,
"BuildStat" ,
"Show failed build parts" ,
"Provides information about failed parts of performed solution build" ,
true ,
56,
ref contextGUIDS,
( int )vsCommandStatus.vsCommandStatusSupported + ( int )vsCommandStatus.vsCommandStatusEnabled,
( int )vsCommandStyle.vsCommandStylePictAndText,
vsCommandControlType.vsCommandControlTypeButton);

//Add a control for the command to the tools menu:
if ((command != null ) && (toolsPopup != null ))
{
command.AddControl(toolsPopup.CommandBar, 1);
}
}
catch (System.ArgumentException)
{
//If we are here, then the exception is probably because a command with that name
// already exists. If so there is no need to recreate the command and we can
// safely ignore the exception.
}

* This source code was highlighted with Source Code Highlighter .
try
{
//Add a command to the Commands collection:
Command command = commands.AddNamedCommand2(_addInInstance,
"BuildStat" ,
"Show failed build parts" ,
"Provides information about failed parts of performed solution build" ,
true ,
56,
ref contextGUIDS,
( int )vsCommandStatus.vsCommandStatusSupported + ( int )vsCommandStatus.vsCommandStatusEnabled,
( int )vsCommandStyle.vsCommandStylePictAndText,
vsCommandControlType.vsCommandControlTypeButton);

//Add a control for the command to the tools menu:
if ((command != null ) && (toolsPopup != null ))
{
command.AddControl(toolsPopup.CommandBar, 1);
}
}
catch (System.ArgumentException)
{
//If we are here, then the exception is probably because a command with that name
// already exists. If so there is no need to recreate the command and we can
// safely ignore the exception.
}

* This source code was highlighted with Source Code Highlighter .


As a result, we get:

image

It remains to implement the assembly report filtering. To do this, we need to get the report itself from the “Output / Build” window, leave only what is needed, create a new panel in the “Output” window and fill it with the work results. Accordingly, the Exec method will take the following form:
public void Exec( string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)
{
handled = false ;
if (executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
{
if (commandName == "BuildStat.Connect.BuildStat" )
{
handled = true ;

foreach (OutputWindowPane pane in _applicationObject.ToolWindows.OutputWindow.OutputWindowPanes)
{
if (pane.Name == "Build" )
{
//

TextDocument textDocument = pane.TextDocument;
textDocument.Selection.StartOfDocument( false );
textDocument.Selection.EndOfDocument( true );

String buildLog = textDocument.Selection.Text;
if (buildLog.Length > 0)
{
string parsedLog = Parse(buildLog);
if (parsedLog.Length > 0)
Output(parsedLog);
}

break ;
}
}

return ;
}
}
}

* This source code was highlighted with Source Code Highlighter .
public void Exec( string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)
{
handled = false ;
if (executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
{
if (commandName == "BuildStat.Connect.BuildStat" )
{
handled = true ;

foreach (OutputWindowPane pane in _applicationObject.ToolWindows.OutputWindow.OutputWindowPanes)
{
if (pane.Name == "Build" )
{
//

TextDocument textDocument = pane.TextDocument;
textDocument.Selection.StartOfDocument( false );
textDocument.Selection.EndOfDocument( true );

String buildLog = textDocument.Selection.Text;
if (buildLog.Length > 0)
{
string parsedLog = Parse(buildLog);
if (parsedLog.Length > 0)
Output(parsedLog);
}

break ;
}
}

return ;
}
}
}

* This source code was highlighted with Source Code Highlighter .
public void Exec( string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)
{
handled = false ;
if (executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
{
if (commandName == "BuildStat.Connect.BuildStat" )
{
handled = true ;

foreach (OutputWindowPane pane in _applicationObject.ToolWindows.OutputWindow.OutputWindowPanes)
{
if (pane.Name == "Build" )
{
//

TextDocument textDocument = pane.TextDocument;
textDocument.Selection.StartOfDocument( false );
textDocument.Selection.EndOfDocument( true );

String buildLog = textDocument.Selection.Text;
if (buildLog.Length > 0)
{
string parsedLog = Parse(buildLog);
if (parsedLog.Length > 0)
Output(parsedLog);
}

break ;
}
}

return ;
}
}
}

* This source code was highlighted with Source Code Highlighter .


A bit of work with strings:

private string Parse( string buildLog)
{
String parseResult = "" ;
StringReader logReader = new StringReader (buildLog);

Regex failedProjects = new Regex( "[1-9][0-9]* error\\(s\\), [0-9]+ warning\\(s\\)" );

for ( string line = logReader.ReadLine(); line != null ; line = logReader.ReadLine())
{
if (failedProjects.IsMatch(line))
{
parseResult += line;
parseResult += "\r\n" ;
}

if ((line.IndexOf( ": error" ) != -1) || (line.IndexOf( ": fatal error" ) != -1))
{
parseResult += line;
parseResult += "\r\n" ;
}
}

return parseResult;
}

* This source code was highlighted with Source Code Highlighter .
private string Parse( string buildLog)
{
String parseResult = "" ;
StringReader logReader = new StringReader (buildLog);

Regex failedProjects = new Regex( "[1-9][0-9]* error\\(s\\), [0-9]+ warning\\(s\\)" );

for ( string line = logReader.ReadLine(); line != null ; line = logReader.ReadLine())
{
if (failedProjects.IsMatch(line))
{
parseResult += line;
parseResult += "\r\n" ;
}

if ((line.IndexOf( ": error" ) != -1) || (line.IndexOf( ": fatal error" ) != -1))
{
parseResult += line;
parseResult += "\r\n" ;
}
}

return parseResult;
}

* This source code was highlighted with Source Code Highlighter .
private string Parse( string buildLog)
{
String parseResult = "" ;
StringReader logReader = new StringReader (buildLog);

Regex failedProjects = new Regex( "[1-9][0-9]* error\\(s\\), [0-9]+ warning\\(s\\)" );

for ( string line = logReader.ReadLine(); line != null ; line = logReader.ReadLine())
{
if (failedProjects.IsMatch(line))
{
parseResult += line;
parseResult += "\r\n" ;
}

if ((line.IndexOf( ": error" ) != -1) || (line.IndexOf( ": fatal error" ) != -1))
{
parseResult += line;
parseResult += "\r\n" ;
}
}

return parseResult;
}

* This source code was highlighted with Source Code Highlighter .


And the output itself to the screen:

private void Output( string outputString)
{
OutputWindowPane myPane = null ;

foreach (OutputWindowPane pane in _applicationObject.ToolWindows.OutputWindow.OutputWindowPanes)
{
if (pane.Name == "Build [failed]" )
{
myPane = pane;
break ;
}
}

if (myPane == null )
myPane = _applicationObject.ToolWindows.OutputWindow.OutputWindowPanes.Add( "Build [failed]" );

myPane.Clear();
myPane.OutputString(outputString);

myPane.Activate();
}

* This source code was highlighted with Source Code Highlighter .
private void Output( string outputString)
{
OutputWindowPane myPane = null ;

foreach (OutputWindowPane pane in _applicationObject.ToolWindows.OutputWindow.OutputWindowPanes)
{
if (pane.Name == "Build [failed]" )
{
myPane = pane;
break ;
}
}

if (myPane == null )
myPane = _applicationObject.ToolWindows.OutputWindow.OutputWindowPanes.Add( "Build [failed]" );

myPane.Clear();
myPane.OutputString(outputString);

myPane.Activate();
}

* This source code was highlighted with Source Code Highlighter .
private void Output( string outputString)
{
OutputWindowPane myPane = null ;

foreach (OutputWindowPane pane in _applicationObject.ToolWindows.OutputWindow.OutputWindowPanes)
{
if (pane.Name == "Build [failed]" )
{
myPane = pane;
break ;
}
}

if (myPane == null )
myPane = _applicationObject.ToolWindows.OutputWindow.OutputWindowPanes.Add( "Build [failed]" );

myPane.Clear();
myPane.OutputString(outputString);

myPane.Activate();
}

* This source code was highlighted with Source Code Highlighter .


And here is the result:

image

To install the module, you must copy the file that is present in the .AddIn project into “My Documents \ Visual Studio <version> \ Addins”, without forgetting to correct the necessary fields in it, for example, paths.

PS The feeling of having invented a bicycle is present, but a combination of factors such as just 30 minutes of time spent and a small portion of the pleasure received compensates for everything.

Source: https://habr.com/ru/post/91276/


All Articles