Awesome
Demo
You can try live demo that's avaliable at: https://csharp-colors.xyz/ or directly from Nuget linked below
CsharpToColouredHTML
This library tries to convert C# code into pure HTML with some lines of CSS that make it look as if that was inside Visual Studio or Visual Studio Code.
If this tool colours something in an undesired way e.g something should be coloured as a struct instead of a class, but there was no way to figure it out from the code, then it is very easy to change that by changing CSS class name.
Microsoft Docs default code highlighting (left) vs. this project (right)
Highlighting whole lines or just some elements
Why?
Motivation for doing it was that I wanted to put C# code fragments on website,
but I didn't like the colours that were provided by popular syntax highlighting solutions - they were not so IDE-ish,
but also I wanted to have server-side rendering, so users aren't required to have JavaScript enabled* in order to see colors.
* Ironically demo page uses a few lines of js just to make messing with the demo more sane
Security Considerations
Even despite performing escaping
I still recommend to use it only on trusted inputs, at least for now.
Usage
You can try live demo that's avaliable at: https://csharp-colors.xyz/
Or use it in your projects directly: https://www.nuget.org/packages/CsharpToColouredHTML.Core/
.NET CLI: dotnet add package CsharpToColouredHTML.Core
Sample Usage:
var html = new CsharpColourer().ProcessSourceCode(code, new HTMLEmitter());
Console.WriteLine(html);
⚠️ If you want to have pure HTML without Iframe and HTML being escaped, then use DisableIframe()
⚠️
var settings = new HTMLEmitterSettings().DisableIframe();
var html = new CsharpColourer().ProcessSourceCode(code, new HTMLEmitter(settings));
Console.WriteLine(html);
Custom CSS
var myCustomCSS = "<style>...</style>";
var settings = new HTMLEmitterSettings().UseCustomCSS(myCustomCSS);
var html = new CsharpColourer().ProcessSourceCode(code, new HTMLEmitter(settings));
Console.WriteLine(html);
Highlighting Lines - Approach 1
var settings = new HTMLEmitterSettings().HighlightThoseLines(x => x == 5);
var html = new CsharpColourer().ProcessSourceCode(code, new HTMLEmitter(settings));
Console.WriteLine(html);
Post-processing / Highlighting Lines - Approach 2
var colourerSettings = new CsharpColourerSettings
{
PostProcessingAction = (list) =>
{
foreach (var item in list)
if (item.Text.Contains("localhost"))
item.UseHighlighting = true;
}
};
var html = new CsharpColourer(colourerSettings).ProcessSourceCode(code, new HTMLEmitter());
Console.WriteLine(html);
Post-processing - Overriding Colours
Action<List<NodeAfterProcessing>> myUnityPostprocessor = (List<NodeAfterProcessing> nodes) =>
{
var list = new List<string>
{
"OnBecameInvisible"
};
foreach (var node in nodes)
{
if (list.Contains(node.Text))
{
node.Colour = NodeColors.Keyword;
}
else
{
node.Colour = NodeColors.Control;
}
}
};
var colourerSettings = new CsharpColourerSettings
{
PostProcessingAction = myUnityPostprocessor
};
var html = new CsharpColourer(colourerSettings).ProcessSourceCode(code, new HTMLEmitter());
Disabling Line Numbers
var settings = new HTMLEmitterSettings().DisableLineNumbers();
var html = new CsharpColourer().ProcessSourceCode(code, new HTMLEmitter(settings));
Console.WriteLine(html);
Advanced Scenarios
Manually affecting heuristics:
CsharpColourer exposes "Hints" property which contains various lists that are used when trying to figure out colour for structs/classes/types
var colourer = new CsharpColourer();
colourer.Hints.ReallyPopularStructsSubstrings.Add("SuperStruct");
colourer.Hints.ReallyPopularClasses.Add("DomainEvent");
colourer.Hints.BuiltInTypes.Add("int512");
var html = colourer.ProcessSourceCode(code, emitter);
Creating your own emitter:
public class MyEmitter : IEmitter
{
public string Emit(List<NodeAfterProcessing> nodes)
{
var sb = new StringBuilder();
foreach (var node in nodes)
sb.Append($"{node.TextWithTrivia} of colour {node.Colour}");
return sb.ToString();
}
}
var txt = new CsharpColourer().ProcessSourceCode(code, new MyEmitter());
Console.WriteLine(txt);
CSS Customizability
Change Log
Examples
Example input:
using System.Text;
using Microsoft.CodeAnalysis.Classification;
namespace CsharpToColouredHTML.Core;
public class ConsoleEmitter : IEmitter
{
public ConsoleEmitter(bool addDiagnosticInfo = false)
{
this.addDiagnosticInfo = addDiagnosticInfo;
}
private readonly StringBuilder _sb = new StringBuilder();
private readonly bool addDiagnosticInfo;
public string Text { get; private set; }
public void Emit(List<Node> nodes)
{
Console.ResetColor();
Text = "";
_sb.Clear();
foreach (var node in nodes)
{
EmitNode(node);
}
Text = _sb.ToString();
}
public void EmitNode(Node node)
{
if (node.ClassificationType == ClassificationTypeNames.ClassName)
{
Console.ForegroundColor = ConsoleColor.Red;
}
else if (node.ClassificationType == ClassificationTypeNames.NamespaceName)
{
Console.ForegroundColor = ConsoleColor.Green;
}
else if (node.ClassificationType == ClassificationTypeNames.Identifier)
{
Console.ForegroundColor = ConsoleColor.Green;
}
else if (node.ClassificationType == ClassificationTypeNames.Keyword)
{
Console.ForegroundColor = ConsoleColor.Yellow;
}
else if (node.ClassificationType == ClassificationTypeNames.StringLiteral)
{
Console.ForegroundColor = ConsoleColor.Cyan;
}
else if (node.ClassificationType == ClassificationTypeNames.LocalName)
{
Console.ForegroundColor = ConsoleColor.Blue;
}
else if (node.ClassificationType == ClassificationTypeNames.MethodName)
{
Console.ForegroundColor = ConsoleColor.Red;
}
else if (node.ClassificationType == ClassificationTypeNames.Punctuation)
{
Console.ForegroundColor = ConsoleColor.White;
}
else if (node.ClassificationType == ClassificationTypeNames.Operator)
{
Console.ForegroundColor = ConsoleColor.White;
}
else if (node.ClassificationType == ClassificationTypeNames.ControlKeyword)
{
Console.ForegroundColor = ConsoleColor.DarkRed;
}
else if (node.ClassificationType == ClassificationTypeNames.VerbatimStringLiteral)
{
Console.ForegroundColor = ConsoleColor.DarkRed;
}
else if (node.ClassificationType == ClassificationTypeNames.StringLiteral)
{
Console.ForegroundColor = ConsoleColor.DarkGreen;
}
else if (node.ClassificationType == ClassificationTypeNames.ParameterName)
{
Console.ForegroundColor = ConsoleColor.Yellow;
}
else
{
Console.ResetColor();
}
_sb.Append(node.TextWithTrivia);
Console.Write($"{node.TextWithTrivia}");
if (addDiagnosticInfo)
{
_sb.Append($"({node.ClassificationType})");
Console.Write($"({node.ClassificationType})");
}
Console.ResetColor();
}
}
<a name="ex1">How generated HTML looks in browser:</a>
Generated HTML Code:
<style>.background{font-family:monaco,Consolas,LucidaConsole,monospace;background-color:#1E1E1E;overflow:scroll;}.method{color:#DCDCAA;}.class{color:#4EC9B0;}.keyword{color:#569cd6;}.string{color:#ce9178;}.interface{color:#b8d7a3;}.control{color:#C586C0;}.namespace{color:#dfdfdf;}.identifier{color:#dfdfdf;}.punctuation{color:#dfdfdf;}.operator{color:#dfdfdf;}.propertyName{color:#dfdfdf;}.fieldName{color:#dfdfdf;}.localName{color:#9CDCFE;}.parameter{color:#9CDCFE;}table{white-space:pre;}.line_no::before{content:attr(line_no);color:white;}.code_column{padding-left:5px;color:#dfdfdf;}</style>
<pre class="background">
<table>
<tbody>
<tr><td class="line_no" line_no="0"></td><td class="code_column"><span class="keyword">using</span> System.Text;</td></tr><tr><td class="line_no" line_no="1"></td><td class="code_column"><span class="keyword">using</span> Microsoft.CodeAnalysis.Classification;</td></tr><tr><td class="line_no" line_no="2"></td><td></tr><tr><td class="line_no" line_no="3"></td><td class="code_column"><span class="keyword">namespace</span> CsharpToColouredHTML.Core;</td></tr><tr><td class="line_no" line_no="4"></td><td></tr><tr><td class="line_no" line_no="5"></td><td class="code_column"><span class="keyword">public class</span> <span class="class">ConsoleEmitter</span> : <span class="interface">IEmitter</span></td></tr><tr><td class="line_no" line_no="6"></td><td class="code_column">{</td></tr><tr><td class="line_no" line_no="7"></td><td class="code_column"> <span class="keyword">public</span> <span class="class">ConsoleEmitter</span>(<span class="keyword">bool</span> <span class="parameter">addDiagnosticInfo</span> = <span class="keyword">false</span>)</td></tr><tr><td class="line_no" line_no="8"></td><td class="code_column"> {</td></tr><tr><td class="line_no" line_no="9"></td><td class="code_column"> <span class="keyword">this</span>.addDiagnosticInfo = <span class="parameter">addDiagnosticInfo</span>;</td></tr><tr><td class="line_no" line_no="10"></td><td class="code_column"> }</td></tr><tr><td class="line_no" line_no="11"></td><td></tr><tr><td class="line_no" line_no="12"></td><td class="code_column"> <span class="keyword">private readonly</span> <span class="class">StringBuilder</span> _sb = <span class="keyword">new</span> <span class="class">StringBuilder</span>();</td></tr><tr><td class="line_no" line_no="13"></td><td></tr><tr><td class="line_no" line_no="14"></td><td class="code_column"> <span class="keyword">private readonly bool</span> addDiagnosticInfo;</td></tr><tr><td class="line_no" line_no="15"></td><td></tr><tr><td class="line_no" line_no="16"></td><td class="code_column"> <span class="keyword">public string</span> Text { <span class="keyword">get</span>; <span class="keyword">private set</span>; }</td></tr><tr><td class="line_no" line_no="17"></td><td></tr><tr><td class="line_no" line_no="18"></td><td class="code_column"> <span class="keyword">public void</span> <span class="method">Emit</span>(<span class="class">List</span><<span class="class">Node</span>> <span class="parameter">nodes</span>)</td></tr><tr><td class="line_no" line_no="19"></td><td class="code_column"> {</td></tr><tr><td class="line_no" line_no="20"></td><td class="code_column"> <span class="class">Console</span>.<span class="method">ResetColor</span>();</td></tr><tr><td class="line_no" line_no="21"></td><td></tr><tr><td class="line_no" line_no="22"></td><td class="code_column"> Text = <span class="string">""</span>;</td></tr><tr><td class="line_no" line_no="23"></td><td class="code_column"> _sb.<span class="method">Clear</span>();</td></tr><tr><td class="line_no" line_no="24"></td><td></tr><tr><td class="line_no" line_no="25"></td><td class="code_column"> <span class="control">foreach</span> (<span class="keyword">var</span> <span class="localName">node</span> <span class="control">in</span> <span class="parameter">nodes</span>)</td></tr><tr><td class="line_no" line_no="26"></td><td class="code_column"> {</td></tr><tr><td class="line_no" line_no="27"></td><td class="code_column"> <span class="method">EmitNode</span>(<span class="localName">node</span>);</td></tr><tr><td class="line_no" line_no="28"></td><td class="code_column"> }</td></tr><tr><td class="line_no" line_no="29"></td><td></tr><tr><td class="line_no" line_no="30"></td><td class="code_column"> Text = _sb.<span class="method">ToString</span>();</td></tr><tr><td class="line_no" line_no="31"></td><td class="code_column"> }</td></tr><tr><td class="line_no" line_no="32"></td><td></tr><tr><td class="line_no" line_no="33"></td><td class="code_column"> <span class="keyword">public void</span> <span class="method">EmitNode</span>(<span class="class">Node</span> <span class="parameter">node</span>)</td></tr><tr><td class="line_no" line_no="34"></td><td class="code_column"> {</td></tr><tr><td class="line_no" line_no="35"></td><td class="code_column"> <span class="control">if</span> (<span class="parameter">node</span>.ClassificationType == <span class="class">ClassificationTypeNames</span>.ClassName)</td></tr><tr><td class="line_no" line_no="36"></td><td class="code_column"> {</td></tr><tr><td class="line_no" line_no="37"></td><td class="code_column"> <span class="class">Console</span>.ForegroundColor = <span class="class">ConsoleColor</span>.Red;</td></tr><tr><td class="line_no" line_no="38"></td><td class="code_column"> }</td></tr><tr><td class="line_no" line_no="39"></td><td class="code_column"> <span class="control">else if</span> (<span class="parameter">node</span>.ClassificationType == <span class="class">ClassificationTypeNames</span>.NamespaceName)</td></tr><tr><td class="line_no" line_no="40"></td><td class="code_column"> {</td></tr><tr><td class="line_no" line_no="41"></td><td class="code_column"> <span class="class">Console</span>.ForegroundColor = <span class="class">ConsoleColor</span>.Green;</td></tr><tr><td class="line_no" line_no="42"></td><td class="code_column"> }</td></tr><tr><td class="line_no" line_no="43"></td><td class="code_column"> <span class="control">else if</span> (<span class="parameter">node</span>.ClassificationType == <span class="class">ClassificationTypeNames</span>.Identifier)</td></tr><tr><td class="line_no" line_no="44"></td><td class="code_column"> {</td></tr><tr><td class="line_no" line_no="45"></td><td class="code_column"> <span class="class">Console</span>.ForegroundColor = <span class="class">ConsoleColor</span>.Green;</td></tr><tr><td class="line_no" line_no="46"></td><td class="code_column"> }</td></tr><tr><td class="line_no" line_no="47"></td><td class="code_column"> <span class="control">else if</span> (<span class="parameter">node</span>.ClassificationType == <span class="class">ClassificationTypeNames</span>.Keyword)</td></tr><tr><td class="line_no" line_no="48"></td><td class="code_column"> {</td></tr><tr><td class="line_no" line_no="49"></td><td class="code_column"> <span class="class">Console</span>.ForegroundColor = <span class="class">ConsoleColor</span>.Yellow;</td></tr><tr><td class="line_no" line_no="50"></td><td class="code_column"> }</td></tr><tr><td class="line_no" line_no="51"></td><td class="code_column"> <span class="control">else if</span> (<span class="parameter">node</span>.ClassificationType == <span class="class">ClassificationTypeNames</span>.StringLiteral)</td></tr><tr><td class="line_no" line_no="52"></td><td class="code_column"> {</td></tr><tr><td class="line_no" line_no="53"></td><td class="code_column"> <span class="class">Console</span>.ForegroundColor = <span class="class">ConsoleColor</span>.Cyan;</td></tr><tr><td class="line_no" line_no="54"></td><td class="code_column"> }</td></tr><tr><td class="line_no" line_no="55"></td><td class="code_column"> <span class="control">else if</span> (<span class="parameter">node</span>.ClassificationType == <span class="class">ClassificationTypeNames</span>.LocalName)</td></tr><tr><td class="line_no" line_no="56"></td><td class="code_column"> {</td></tr><tr><td class="line_no" line_no="57"></td><td class="code_column"> <span class="class">Console</span>.ForegroundColor = <span class="class">ConsoleColor</span>.Blue;</td></tr><tr><td class="line_no" line_no="58"></td><td class="code_column"> }</td></tr><tr><td class="line_no" line_no="59"></td><td class="code_column"> <span class="control">else if</span> (<span class="parameter">node</span>.ClassificationType == <span class="class">ClassificationTypeNames</span>.MethodName)</td></tr><tr><td class="line_no" line_no="60"></td><td class="code_column"> {</td></tr><tr><td class="line_no" line_no="61"></td><td class="code_column"> <span class="class">Console</span>.ForegroundColor = <span class="class">ConsoleColor</span>.Red;</td></tr><tr><td class="line_no" line_no="62"></td><td class="code_column"> }</td></tr><tr><td class="line_no" line_no="63"></td><td class="code_column"> <span class="control">else if</span> (<span class="parameter">node</span>.ClassificationType == <span class="class">ClassificationTypeNames</span>.Punctuation)</td></tr><tr><td class="line_no" line_no="64"></td><td class="code_column"> {</td></tr><tr><td class="line_no" line_no="65"></td><td class="code_column"> <span class="class">Console</span>.ForegroundColor = <span class="class">ConsoleColor</span>.White;</td></tr><tr><td class="line_no" line_no="66"></td><td class="code_column"> }</td></tr><tr><td class="line_no" line_no="67"></td><td class="code_column"> <span class="control">else if</span> (<span class="parameter">node</span>.ClassificationType == <span class="class">ClassificationTypeNames</span>.Operator)</td></tr><tr><td class="line_no" line_no="68"></td><td class="code_column"> {</td></tr><tr><td class="line_no" line_no="69"></td><td class="code_column"> <span class="class">Console</span>.ForegroundColor = <span class="class">ConsoleColor</span>.White;</td></tr><tr><td class="line_no" line_no="70"></td><td class="code_column"> }</td></tr><tr><td class="line_no" line_no="71"></td><td class="code_column"> <span class="control">else if</span> (<span class="parameter">node</span>.ClassificationType == <span class="class">ClassificationTypeNames</span>.ControlKeyword)</td></tr><tr><td class="line_no" line_no="72"></td><td class="code_column"> {</td></tr><tr><td class="line_no" line_no="73"></td><td class="code_column"> <span class="class">Console</span>.ForegroundColor = <span class="class">ConsoleColor</span>.DarkRed;</td></tr><tr><td class="line_no" line_no="74"></td><td class="code_column"> }</td></tr><tr><td class="line_no" line_no="75"></td><td class="code_column"> <span class="control">else if</span> (<span class="parameter">node</span>.ClassificationType == <span class="class">ClassificationTypeNames</span>.VerbatimStringLiteral)</td></tr><tr><td class="line_no" line_no="76"></td><td class="code_column"> {</td></tr><tr><td class="line_no" line_no="77"></td><td class="code_column"> <span class="class">Console</span>.ForegroundColor = <span class="class">ConsoleColor</span>.DarkRed;</td></tr><tr><td class="line_no" line_no="78"></td><td class="code_column"> }</td></tr><tr><td class="line_no" line_no="79"></td><td class="code_column"> <span class="control">else if</span> (<span class="parameter">node</span>.ClassificationType == <span class="class">ClassificationTypeNames</span>.StringLiteral)</td></tr><tr><td class="line_no" line_no="80"></td><td class="code_column"> {</td></tr><tr><td class="line_no" line_no="81"></td><td class="code_column"> <span class="class">Console</span>.ForegroundColor = <span class="class">ConsoleColor</span>.DarkGreen;</td></tr><tr><td class="line_no" line_no="82"></td><td class="code_column"> }</td></tr><tr><td class="line_no" line_no="83"></td><td class="code_column"> <span class="control">else if</span> (<span class="parameter">node</span>.ClassificationType == <span class="class">ClassificationTypeNames</span>.ParameterName)</td></tr><tr><td class="line_no" line_no="84"></td><td class="code_column"> {</td></tr><tr><td class="line_no" line_no="85"></td><td class="code_column"> <span class="class">Console</span>.ForegroundColor = <span class="class">ConsoleColor</span>.Yellow;</td></tr><tr><td class="line_no" line_no="86"></td><td class="code_column"> }</td></tr><tr><td class="line_no" line_no="87"></td><td class="code_column"> <span class="control">else</span></td></tr><tr><td class="line_no" line_no="88"></td><td class="code_column"> {</td></tr><tr><td class="line_no" line_no="89"></td><td class="code_column"> <span class="class">Console</span>.<span class="method">ResetColor</span>();</td></tr><tr><td class="line_no" line_no="90"></td><td class="code_column"> }</td></tr><tr><td class="line_no" line_no="91"></td><td></tr><tr><td class="line_no" line_no="92"></td><td class="code_column"> _sb.<span class="method">Append</span>(<span class="parameter">node</span>.TextWithTrivia);</td></tr><tr><td class="line_no" line_no="93"></td><td class="code_column"> <span class="class">Console</span>.<span class="method">Write</span>(<span class="string">$"</span>{<span class="parameter">node</span>.TextWithTrivia}<span class="string">"</span>);</td></tr><tr><td class="line_no" line_no="94"></td><td></tr><tr><td class="line_no" line_no="95"></td><td class="code_column"> <span class="control">if</span> (addDiagnosticInfo)</td></tr><tr><td class="line_no" line_no="96"></td><td class="code_column"> {</td></tr><tr><td class="line_no" line_no="97"></td><td class="code_column"> _sb.<span class="method">Append</span>(<span class="string">$"(</span>{<span class="parameter">node</span>.ClassificationType}<span class="string">)"</span>);</td></tr><tr><td class="line_no" line_no="98"></td><td class="code_column"> <span class="class">Console</span>.<span class="method">Write</span>(<span class="string">$"(</span>{<span class="parameter">node</span>.ClassificationType}<span class="string">)"</span>);</td></tr><tr><td class="line_no" line_no="99"></td><td class="code_column"> }</td></tr><tr><td class="line_no" line_no="100"></td><td></tr><tr><td class="line_no" line_no="101"></td><td class="code_column"> <span class="class">Console</span>.<span class="method">ResetColor</span>();</td></tr><tr><td class="line_no" line_no="102"></td><td class="code_column"> }</td></tr><tr><td class="line_no" line_no="103"></td><td class="code_column">}</td></tr></tbody>
</table></pre>
Second Example:
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using EnvDTE;
using Microsoft;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Task = System.Threading.Tasks.Task;
namespace Roslyn.Compilers.Extension
{
[Guid("31C0675E-87A4-4061-A0DD-A4E510FCCF97")]
public sealed class CompilerPackage : AsyncPackage
{
public static string RoslynHive = null;
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
{
await base.InitializeAsync(cancellationToken, progress).ConfigureAwait(true);
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var reg = (ILocalRegistry2)await GetServiceAsync(typeof(SLocalRegistry)).ConfigureAwait(true);
cancellationToken.ThrowIfCancellationRequested();
Assumes.Present(reg);
var packagePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string localRegistryRoot;
reg.GetLocalRegistryRoot(out localRegistryRoot);
var registryParts = localRegistryRoot.Split('\\');
// Is it a valid Hive looks similar to:
// 'Software\Microsoft\VisualStudio\14.0' 'Software\Microsoft\VisualStudio\14.0Roslyn' 'Software\Microsoft\VSWinExpress\14.0'
if (registryParts.Length >= 4)
{
var skuName = registryParts[2];
var hiveName = registryParts[3];
RoslynHive = string.Format(@"{0}.{1}", registryParts[2], registryParts[3]);
await WriteMSBuildFilesAsync(packagePath, RoslynHive, cancellationToken).ConfigureAwait(true);
try
{
Microsoft.Build.Evaluation.ProjectCollection.GlobalProjectCollection.DisableMarkDirty = true;
Microsoft.Build.Evaluation.ProjectCollection.GlobalProjectCollection.SetGlobalProperty("RoslynHive", RoslynHive);
}
finally
{
Microsoft.Build.Evaluation.ProjectCollection.GlobalProjectCollection.DisableMarkDirty = false;
}
}
}
private async Task WriteMSBuildFilesAsync(string packagePath, string hiveName, CancellationToken cancellationToken)
{
// A map of the file name to the content we need to ensure exists in the file
var filesToWrite = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
// The props we want to be included as early as possible since we want our tasks to be used and
// to ensure our setting of targets path happens early enough
filesToWrite.Add(await GetMSBuildRelativePathAsync($@"Imports\Microsoft.Common.props\ImportBefore\Roslyn.Compilers.Extension.{hiveName}.props", cancellationToken).ConfigureAwait(true),
$@"<?xml version=""1.0"" encoding=""utf-8""?>
<Project xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
<PropertyGroup Condition=""'$(RoslynHive)' == '{hiveName}'"">
<CSharpCoreTargetsPath>{packagePath}\Microsoft.CSharp.Core.targets</CSharpCoreTargetsPath>
<VisualBasicCoreTargetsPath>{packagePath}\Microsoft.VisualBasic.Core.targets</VisualBasicCoreTargetsPath>
</PropertyGroup>
<UsingTask TaskName=""Microsoft.CodeAnalysis.BuildTasks.Csc"" AssemblyFile=""{packagePath}\Microsoft.Build.Tasks.CodeAnalysis.dll"" Condition=""'$(RoslynHive)' == '{hiveName}'"" />
<UsingTask TaskName=""Microsoft.CodeAnalysis.BuildTasks.Vbc"" AssemblyFile=""{packagePath}\Microsoft.Build.Tasks.CodeAnalysis.dll"" Condition=""'$(RoslynHive)' == '{hiveName}'"" />
</Project>");
// This targets content we want to be included later since the project file might touch UseSharedCompilation
var targetsContent =
$@"<?xml version=""1.0"" encoding=""utf-8""?>
<Project xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
<!-- If we're not using the compiler server, set ToolPath/Exe to direct to the exes in this package -->
<PropertyGroup Condition=""'$(RoslynHive)' == '{hiveName}' and '$(UseSharedCompilation)' == 'false'"">
<CscToolPath>{packagePath}</CscToolPath>
<CscToolExe>csc.exe</CscToolExe>
<VbcToolPath>{packagePath}</VbcToolPath>
<VbcToolExe>vbc.exe</VbcToolExe>
</PropertyGroup>
</Project>";
filesToWrite.Add(await GetMSBuildRelativePathAsync($@"Microsoft.CSharp.targets\ImportBefore\Roslyn.Compilers.Extension.{hiveName}.targets", cancellationToken).ConfigureAwait(true), targetsContent);
filesToWrite.Add(await GetMSBuildRelativePathAsync($@"Microsoft.VisualBasic.targets\ImportBefore\Roslyn.Compilers.Extension.{hiveName}.targets", cancellationToken).ConfigureAwait(true), targetsContent);
// First we want to ensure any Roslyn files with our hive name that we aren't writing -- this is probably
// leftovers from older extensions
var msbuildDirectory = new DirectoryInfo(await GetMSBuildPathAsync(cancellationToken).ConfigureAwait(true));
if (msbuildDirectory.Exists)
{
foreach (var file in msbuildDirectory.EnumerateFiles($"*Roslyn*{hiveName}*", SearchOption.AllDirectories))
{
if (!filesToWrite.ContainsKey(file.FullName))
{
file.Delete();
}
}
}
try
{
foreach (var fileAndContents in filesToWrite)
{
var parentDirectory = new DirectoryInfo(Path.GetDirectoryName(fileAndContents.Key));
parentDirectory.Create();
// If we already know the file has the same contents, then we can skip
if (File.Exists(fileAndContents.Key) && File.ReadAllText(fileAndContents.Key) == fileAndContents.Value)
{
continue;
}
File.WriteAllText(fileAndContents.Key, fileAndContents.Value);
}
}
catch (Exception e)
{
var msg =
$@"{e.Message}
To reload the Roslyn compiler package, close Visual Studio and any MSBuild processes, then restart Visual Studio.";
VsShellUtilities.ShowMessageBox(
this,
msg,
null,
OLEMSGICON.OLEMSGICON_WARNING,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
}
}
private async Task<string> GetMSBuildVersionStringAsync(CancellationToken cancellationToken)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var dte = (DTE)await GetServiceAsync(typeof(SDTE)).ConfigureAwait(true);
var parts = dte.Version.Split('.');
if (parts.Length != 2)
{
throw new Exception($"Unrecognized Visual Studio Version: {dte.Version}");
}
int majorVersion = int.Parse(parts[0]);
if (majorVersion >= 16)
{
// Starting in Visual Studio 2019, the folder is just called "Current". See
// https://github.com/Microsoft/msbuild/issues/4149 for further commentary.
return "Current";
}
else
{
return majorVersion + ".0";
}
}
private async Task<string> GetMSBuildPathAsync(CancellationToken cancellationToken)
{
var version = await GetMSBuildVersionStringAsync(cancellationToken).ConfigureAwait(true);
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
return Path.Combine(localAppData, $@"Microsoft\MSBuild\{version}");
}
private async Task<string> GetMSBuildRelativePathAsync(string relativePath, CancellationToken cancellationToken)
{
return Path.Combine(await GetMSBuildPathAsync(cancellationToken).ConfigureAwait(true), relativePath);
}
}
}
<a name="ex2">Generated HTML inside Browser:</a>