diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ca402fc
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+## Ignore Visual Studio temporary files, build and test results
+
+*.suo
+*.user
+*.cache
+*.vspscc
+
+[Bb]in/
+[Oo]bj/
+[Tt]est[Rr]esult*/
+[Pp]ackages/
\ No newline at end of file
diff --git a/.nuget/NuGet.Config b/.nuget/NuGet.Config
new file mode 100644
index 0000000..6a318ad
--- /dev/null
+++ b/.nuget/NuGet.Config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CodeAnalysisDictionary.xml b/CodeAnalysisDictionary.xml
new file mode 100644
index 0000000..44dda82
--- /dev/null
+++ b/CodeAnalysisDictionary.xml
@@ -0,0 +1,10 @@
+
+
+
+ Lex
+ LLoc
+ EOF
+ Polymorphically
+
+
+
\ No newline at end of file
diff --git a/CommonAssemblyInfo.cs b/CommonAssemblyInfo.cs
new file mode 100644
index 0000000..93d84cb
--- /dev/null
+++ b/CommonAssemblyInfo.cs
@@ -0,0 +1,37 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+#if DEBUG
+[assembly: AssemblyConfiguration("Debug")]
+#else
+[assembly: AssemblyConfiguration("Release")]
+#endif
+
+[assembly: AssemblyProduct(T4Toolbox.AssemblyInfo.Product)]
+[assembly: AssemblyDescription(T4Toolbox.AssemblyInfo.Description)]
+[assembly: AssemblyCompany("Oleg Sych")]
+[assembly: AssemblyCopyright("Copyright © Oleg Sych. All Rights Reserved.")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: AssemblyVersion(T4Toolbox.AssemblyInfo.Version)]
+[assembly: AssemblyFileVersion(T4Toolbox.AssemblyInfo.Version)]
+[assembly: ComVisible(false)]
+[assembly: NeutralResourcesLanguage("en-US")]
+
+// Allow all projects in this solution to access each-other's internals by default.
+// In many instances, we need this to enable testing as well as to access constants
+// in T4Toolbox.AssemblyInfo class. Revisit this decision when the number of assemblies
+// in the project increases to the point where limiting access to internals within the
+// solution becomes beneficial.
+[assembly: InternalsVisibleTo("T4Toolbox.DirectiveProcessors")]
+[assembly: InternalsVisibleTo("T4Toolbox.Tests")]
+[assembly: InternalsVisibleTo("T4Toolbox.VisualStudio")]
+[assembly: InternalsVisibleTo("T4Toolbox.VisualStudio.IntegrationTests")]
+[assembly: InternalsVisibleTo("T4Toolbox.VisualStudio.Tests")]
+[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
diff --git a/LICENSE b/LICENSE
index 5b3aa2f..15f0b1c 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2015 Oleg Sych
+Copyright (c) 2008 Oleg Sych
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/LocalTestRun.testrunconfig b/LocalTestRun.testrunconfig
new file mode 100644
index 0000000..c9b4e36
--- /dev/null
+++ b/LocalTestRun.testrunconfig
@@ -0,0 +1,9 @@
+
+
+ Test settings required to run Visual Studio integration tests.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Settings.StyleCop b/Settings.StyleCop
new file mode 100644
index 0000000..ea32c2c
--- /dev/null
+++ b/Settings.StyleCop
@@ -0,0 +1,91 @@
+
+
+
+ autogenerated
+ int
+ remoting
+ templating
+ tt
+ Intelli
+
+ NoMerge
+
+
+
+
+ False
+
+ \.g\.cs$
+ \.generated\.cs$
+ \.g\.i\.cs$
+ TemporaryGeneratedFile_.*\.cs$
+
+
+
+
+
+
+
+
+
+ True
+
+
+
+
+ True
+
+
+
+
+ True
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ Oleg Sych
+ Copyright © Oleg Sych. All Rights Reserved.
+
+
+
+
+
+
+ False
+
+
+
+
+
+
+
+
+ if
+ is
+ on
+ to
+
+
+
+
+
\ No newline at end of file
diff --git a/T4Toolbox.Common.props b/T4Toolbox.Common.props
new file mode 100644
index 0000000..2713e66
--- /dev/null
+++ b/T4Toolbox.Common.props
@@ -0,0 +1,34 @@
+
+
+
+ Debug
+ AnyCPU
+ AnyCPU
+ v4.5
+
+ true
+ full
+ prompt
+ 4
+ 512
+ true
+ bin\$(Configuration)\
+
+
+
+
+ $(OutputPath)$(AssemblyName).xml
+
+
+ true
+ $(MSBuildThisFileDirectory)\T4Toolbox.Common.ruleset
+
+
+ false
+ false
+
+
\ No newline at end of file
diff --git a/T4Toolbox.Common.ruleset b/T4Toolbox.Common.ruleset
new file mode 100644
index 0000000..9d5771e
--- /dev/null
+++ b/T4Toolbox.Common.ruleset
@@ -0,0 +1,593 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/T4Toolbox.sln b/T4Toolbox.sln
new file mode 100644
index 0000000..881d8b6
--- /dev/null
+++ b/T4Toolbox.sln
@@ -0,0 +1,98 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2013
+VisualStudioVersion = 12.0.31101.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{42F21B27-CBA5-4FB6-A855-8B74F05F56CD}"
+ ProjectSection(SolutionItems) = preProject
+ CodeAnalysisDictionary.xml = CodeAnalysisDictionary.xml
+ CommonAssemblyInfo.cs = CommonAssemblyInfo.cs
+ LocalTestRun.testrunconfig = LocalTestRun.testrunconfig
+ Settings.StyleCop = Settings.StyleCop
+ T4Toolbox.Common.props = T4Toolbox.Common.props
+ T4Toolbox.Common.ruleset = T4Toolbox.Common.ruleset
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3594248C-C8A9-4EAB-8E63-0914300D4ECE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "T4Toolbox", "src\T4Toolbox\T4Toolbox.csproj", "{682E771A-76F7-4972-BBDC-1250B67F399B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "T4Toolbox.VisualStudio", "src\T4Toolbox.VisualStudio\T4Toolbox.VisualStudio.csproj", "{1E1E9161-CBE4-4538-928C-539AA5E70153}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{994DFDA5-97F7-4688-A2FA-6D8E7AB2BC18}"
+ ProjectSection(SolutionItems) = preProject
+ test\Settings.StyleCop = test\Settings.StyleCop
+ test\T4Toolbox.Tests.props = test\T4Toolbox.Tests.props
+ test\T4Toolbox.Tests.ruleset = test\T4Toolbox.Tests.ruleset
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "T4Toolbox.Tests", "test\T4Toolbox.Tests\T4Toolbox.Tests.csproj", "{2A05BF5E-B2B2-4222-91A3-BB86AE8A94CE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "T4Toolbox.VisualStudio.IntegrationTests", "test\T4Toolbox.VisualStudio.IntegrationTests\T4Toolbox.VisualStudio.IntegrationTests.csproj", "{846B29AB-AAA2-4080-B4B4-A440948CC61A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "T4Toolbox.VisualStudio.Tests", "test\T4Toolbox.VisualStudio.Tests\T4Toolbox.VisualStudio.Tests.csproj", "{7CBACA4C-728A-4818-839C-E22C24677AFA}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "T4Toolbox.DirectiveProcessors", "src\T4Toolbox.DirectiveProcessors\T4Toolbox.DirectiveProcessors.csproj", "{E0282961-2D83-48CC-B4D4-8257449CF8F7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "T4Toolbox.VisualStudio.ItemTemplates", "src\T4Toolbox.VisualStudio.ItemTemplates\T4Toolbox.VisualStudio.ItemTemplates.csproj", "{EA04B345-97BE-4A49-9C9C-3EBD4F5D2250}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "T4Toolbox.vsix", "src\T4Toolbox.vsix\T4Toolbox.vsix.csproj", "{8E492B04-AF03-4A88-9A5D-D34D2386A4E5}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{71EF8BD3-A665-4304-8F9D-D6A849A9830D}"
+ ProjectSection(SolutionItems) = preProject
+ .nuget\NuGet.Config = .nuget\NuGet.Config
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {682E771A-76F7-4972-BBDC-1250B67F399B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {682E771A-76F7-4972-BBDC-1250B67F399B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {682E771A-76F7-4972-BBDC-1250B67F399B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {682E771A-76F7-4972-BBDC-1250B67F399B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1E1E9161-CBE4-4538-928C-539AA5E70153}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1E1E9161-CBE4-4538-928C-539AA5E70153}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1E1E9161-CBE4-4538-928C-539AA5E70153}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1E1E9161-CBE4-4538-928C-539AA5E70153}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2A05BF5E-B2B2-4222-91A3-BB86AE8A94CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2A05BF5E-B2B2-4222-91A3-BB86AE8A94CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2A05BF5E-B2B2-4222-91A3-BB86AE8A94CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2A05BF5E-B2B2-4222-91A3-BB86AE8A94CE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {846B29AB-AAA2-4080-B4B4-A440948CC61A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {846B29AB-AAA2-4080-B4B4-A440948CC61A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {846B29AB-AAA2-4080-B4B4-A440948CC61A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {846B29AB-AAA2-4080-B4B4-A440948CC61A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7CBACA4C-728A-4818-839C-E22C24677AFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7CBACA4C-728A-4818-839C-E22C24677AFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7CBACA4C-728A-4818-839C-E22C24677AFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7CBACA4C-728A-4818-839C-E22C24677AFA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E0282961-2D83-48CC-B4D4-8257449CF8F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E0282961-2D83-48CC-B4D4-8257449CF8F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E0282961-2D83-48CC-B4D4-8257449CF8F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E0282961-2D83-48CC-B4D4-8257449CF8F7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EA04B345-97BE-4A49-9C9C-3EBD4F5D2250}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EA04B345-97BE-4A49-9C9C-3EBD4F5D2250}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EA04B345-97BE-4A49-9C9C-3EBD4F5D2250}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EA04B345-97BE-4A49-9C9C-3EBD4F5D2250}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8E492B04-AF03-4A88-9A5D-D34D2386A4E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8E492B04-AF03-4A88-9A5D-D34D2386A4E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8E492B04-AF03-4A88-9A5D-D34D2386A4E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8E492B04-AF03-4A88-9A5D-D34D2386A4E5}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {682E771A-76F7-4972-BBDC-1250B67F399B} = {3594248C-C8A9-4EAB-8E63-0914300D4ECE}
+ {1E1E9161-CBE4-4538-928C-539AA5E70153} = {3594248C-C8A9-4EAB-8E63-0914300D4ECE}
+ {2A05BF5E-B2B2-4222-91A3-BB86AE8A94CE} = {994DFDA5-97F7-4688-A2FA-6D8E7AB2BC18}
+ {846B29AB-AAA2-4080-B4B4-A440948CC61A} = {994DFDA5-97F7-4688-A2FA-6D8E7AB2BC18}
+ {7CBACA4C-728A-4818-839C-E22C24677AFA} = {994DFDA5-97F7-4688-A2FA-6D8E7AB2BC18}
+ {E0282961-2D83-48CC-B4D4-8257449CF8F7} = {3594248C-C8A9-4EAB-8E63-0914300D4ECE}
+ {EA04B345-97BE-4A49-9C9C-3EBD4F5D2250} = {3594248C-C8A9-4EAB-8E63-0914300D4ECE}
+ {8E492B04-AF03-4A88-9A5D-D34D2386A4E5} = {3594248C-C8A9-4EAB-8E63-0914300D4ECE}
+ EndGlobalSection
+EndGlobal
diff --git a/src/T4Toolbox.DirectiveProcessors/AssemblyInfo.cs b/src/T4Toolbox.DirectiveProcessors/AssemblyInfo.cs
new file mode 100644
index 0000000..5b24072
--- /dev/null
+++ b/src/T4Toolbox.DirectiveProcessors/AssemblyInfo.cs
@@ -0,0 +1,21 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+using System;
+
+[assembly: CLSCompliant(false)]
+
+namespace T4Toolbox.DirectiveProcessors
+{
+ ///
+ /// Defines constants describing the T4Toolbox.VisualStudio assembly.
+ ///
+ internal abstract class AssemblyInfo : T4Toolbox.AssemblyInfo
+ {
+ ///
+ /// Gets the name of the assembly.
+ ///
+ public new const string Name = "T4Toolbox.DirectiveProcessors";
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.DirectiveProcessors/DirectiveProcessor.cs b/src/T4Toolbox.DirectiveProcessors/DirectiveProcessor.cs
new file mode 100644
index 0000000..541bff8
--- /dev/null
+++ b/src/T4Toolbox.DirectiveProcessors/DirectiveProcessor.cs
@@ -0,0 +1,420 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.DirectiveProcessors
+{
+ using System;
+ using System.CodeDom;
+ using System.CodeDom.Compiler;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.IO;
+ using System.Linq;
+ using Microsoft.VisualStudio.TextTemplating;
+
+ ///
+ /// Base class for directive processors.
+ ///
+ public abstract class DirectiveProcessor : IDirectiveProcessor, IDisposable
+ {
+ ///
+ /// Gets the collection to which the directive processor can add errors and warnings.
+ ///
+ /// A instance.
+ public CompilerErrorCollection Errors { get; private set; }
+
+ ///
+ /// Gets or sets a value indicating whether the directive processor requires the run to be host-specific.
+ ///
+ ///
+ /// True if the directive processor requires a host-specific processing run.
+ ///
+ public bool RequiresProcessingRunIsHostSpecific { get; protected set; }
+
+ ///
+ /// Gets a T4 engine host.
+ ///
+ ///
+ /// A object obtained by the method.
+ ///
+ protected ITextTemplatingEngineHost Host { get; private set; }
+
+ ///
+ /// Gets a collection of attribute declarations that will be applied to the generated transformation class.
+ ///
+ ///
+ /// A collection of objects.
+ ///
+ protected CodeAttributeDeclarationCollection ClassAttributes { get; private set; }
+
+ ///
+ /// Gets the class code buffer.
+ ///
+ ///
+ /// A object that serves as a buffer for generated code.
+ ///
+ protected StringWriter ClassCode { get; private set; }
+
+ ///
+ /// Gets the directive name.
+ ///
+ /// A that contains directive name.
+ ///
+ /// Override this property in the derived class to indicate which directive
+ /// the processor will handle.
+ ///
+ protected abstract string DirectiveName { get; }
+
+ ///
+ /// Gets dispose code buffer.
+ ///
+ ///
+ /// A object that serves as a buffer for generated code.
+ ///
+ protected StringWriter DisposeCode { get; private set; }
+
+ ///
+ /// Gets the namespace imports buffer.
+ ///
+ ///
+ /// A of namespaces.
+ ///
+ protected ICollection Imports { get; private set; }
+
+ ///
+ /// Gets the post-initialization code buffer.
+ ///
+ ///
+ /// A object that serves as a buffer for generated code.
+ ///
+ protected StringWriter PostInitializationCode { get; private set; }
+
+ ///
+ /// Gets the pre-initialization code buffer.
+ ///
+ ///
+ /// A object that serves as a buffer for generated code.
+ ///
+ protected StringWriter PreInitializationCode { get; private set; }
+
+ ///
+ /// Gets a value indicating whether the current processing run is host-specific.
+ ///
+ ///
+ /// True, if the current processing run is host-specific.
+ ///
+ protected bool ProcessingRunIsHostSpecific { get; private set; }
+
+ ///
+ /// Gets the assembly references buffer.
+ ///
+ ///
+ /// A of assembly references.
+ ///
+ protected ICollection References { get; private set; }
+
+ ///
+ /// Gets a language provider.
+ ///
+ ///
+ /// A object obtained by the method.
+ ///
+ protected CodeDomProvider LanguageProvider { get; private set; }
+
+ ///
+ /// Releases disposable resources owned by the directive processor.
+ ///
+ public void Dispose()
+ {
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ #region IDirectiveProcessor
+
+ ///
+ /// Notifies the directive processor that the processing run is finished.
+ ///
+ public void FinishProcessingRun()
+ {
+ if (this.DisposeCode.GetStringBuilder().Length > 0)
+ {
+ this.GenerateDisposeMethod();
+ }
+
+ // Release external references received from T4
+ this.LanguageProvider = null;
+
+ // DO NOT release the internal buffers. T4 Engine accesses them between the runs.
+ }
+
+ ///
+ /// This method is not used and returns an empty string.
+ ///
+ ///
+ /// A that contains the code to add to the generated
+ /// transformation class.
+ ///
+ public string GetClassCodeForProcessingRun()
+ {
+ return this.ClassCode.ToString();
+ }
+
+ ///
+ /// This method is not used and returns an empty array.
+ ///
+ ///
+ /// An array of type that contains the namespaces.
+ ///
+ public string[] GetImportsForProcessingRun()
+ {
+ return this.Imports.Distinct().ToArray();
+ }
+
+ ///
+ /// Gets a collection of custom attribute declarations to add to the generated template class.
+ ///
+ /// .
+ public CodeAttributeDeclarationCollection GetTemplateClassCustomAttributes()
+ {
+ return this.ClassAttributes;
+ }
+
+ ///
+ /// This method is not used and returns an empty string.
+ ///
+ ///
+ /// A that contains the code to add to the generated
+ /// transformation class.
+ ///
+ public string GetPostInitializationCodeForProcessingRun()
+ {
+ return this.PostInitializationCode.ToString();
+ }
+
+ ///
+ /// This method is not used and returns an empty string.
+ ///
+ ///
+ /// A that contains the code to add to the generated
+ /// transformation class.
+ ///
+ public string GetPreInitializationCodeForProcessingRun()
+ {
+ return this.PreInitializationCode.ToString();
+ }
+
+ ///
+ /// This method is not used and returns an empty array.
+ ///
+ ///
+ /// An array of type that contains the references.
+ ///
+ public string[] GetReferencesForProcessingRun()
+ {
+ return this.References.Distinct().ToArray();
+ }
+
+ ///
+ /// T4 engine calls this method in the beginning of template transformation.
+ ///
+ ///
+ /// The object hosting the transformation.
+ ///
+ public void Initialize(ITextTemplatingEngineHost host)
+ {
+ this.Host = host;
+ }
+
+ ///
+ /// Returns true when is "t4toolbox".
+ ///
+ /// Name of the directive.
+ ///
+ /// true if the directive is supported by the processor; otherwise, false.
+ ///
+ public bool IsDirectiveSupported(string directiveName)
+ {
+ if (directiveName == null)
+ {
+ throw new ArgumentNullException("directiveName");
+ }
+
+ return string.Compare(this.DirectiveName, directiveName, StringComparison.OrdinalIgnoreCase) == 0;
+ }
+
+ ///
+ /// This method is not used and left blank.
+ ///
+ ///
+ /// The name of the directive to process.
+ ///
+ ///
+ /// The arguments for the directive.
+ ///
+ public void ProcessDirective(string directiveName, IDictionary arguments)
+ {
+ // Validate directiveName argument
+ if (!this.IsDirectiveSupported(directiveName))
+ {
+ throw new ArgumentException(
+ string.Format(
+ CultureInfo.CurrentCulture,
+ "Unsupported directive name: '{0}'. Please use '{1}' instead.",
+ directiveName,
+ this.DirectiveName),
+ "directiveName");
+ }
+
+ // Validate arguments argument
+ if (arguments == null)
+ {
+ throw new ArgumentNullException("arguments");
+ }
+
+ this.Process(arguments);
+ }
+
+ ///
+ /// Informs the directive processor whether the run is host-specific.
+ ///
+ /// True, if the processing run is host-specific.
+ public void SetProcessingRunIsHostSpecific(bool hostSpecific)
+ {
+ this.ProcessingRunIsHostSpecific = hostSpecific;
+ }
+
+ ///
+ /// Begins a round of directive processing.
+ ///
+ /// CodeDom language provider for generating code.
+ /// Contents of the T4 template.
+ /// Compiler Errors.
+ public void StartProcessingRun(CodeDomProvider languageProvider, string templateContents, CompilerErrorCollection errors)
+ {
+ // Validate parameters
+ if (languageProvider == null)
+ {
+ throw new ArgumentNullException("languageProvider");
+ }
+
+ if (errors == null)
+ {
+ throw new ArgumentNullException("errors");
+ }
+
+ // Initialize references to external objects provided by T4
+ this.LanguageProvider = languageProvider;
+ this.Errors = errors;
+
+ // Clean up buffers here instead of FinishProcessingRun because T4 uses them between the runs
+ this.CleanupBuffers();
+ this.InitializeBuffers();
+ }
+
+ #endregion
+
+ ///
+ /// Disposes managed resources owned by this directive processor.
+ ///
+ ///
+ /// This parameter is always true. It is provided for consistency with pattern.
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ this.CleanupBuffers();
+ }
+ }
+
+ ///
+ /// Processes the directive.
+ ///
+ ///
+ /// A dictionary of arguments specified for the directive in the template.
+ ///
+ protected abstract void Process(IDictionary arguments);
+
+ ///
+ /// Reports a warning with the specified .
+ ///
+ protected void Warning(string message)
+ {
+ if (message == null)
+ {
+ throw new ArgumentNullException("message");
+ }
+
+ this.Errors.Add(new CompilerError { ErrorText = message, FileName = this.Host.TemplateFile, IsWarning = true });
+ }
+
+ private void CleanupBuffers()
+ {
+ this.ClassAttributes = null;
+ this.Imports = null;
+ this.References = null;
+
+ if (this.ClassCode != null)
+ {
+ this.ClassCode.Dispose();
+ this.ClassCode = null;
+ }
+
+ if (this.DisposeCode != null)
+ {
+ this.DisposeCode.Dispose();
+ this.DisposeCode = null;
+ }
+
+ if (this.PostInitializationCode != null)
+ {
+ this.PostInitializationCode.Dispose();
+ this.PostInitializationCode = null;
+ }
+
+ if (this.PreInitializationCode != null)
+ {
+ this.PreInitializationCode.Dispose();
+ this.PreInitializationCode = null;
+ }
+ }
+
+ private void GenerateDisposeMethod()
+ {
+ //// protected override void Dispose(bool disposing) {
+ var disposeMethod = new CodeMemberMethod { Name = "Dispose", Attributes = MemberAttributes.Family | MemberAttributes.Override };
+ var disposingArgument = new CodeParameterDeclarationExpression(new CodeTypeReference(typeof(bool)), "disposing");
+ disposeMethod.Parameters.Add(disposingArgument);
+
+ //// base.Dispose(disposing);
+ disposeMethod.Statements.Add(new CodeMethodInvokeExpression(
+ new CodeBaseReferenceExpression(),
+ "Dispose",
+ new CodeArgumentReferenceExpression(disposingArgument.Name)));
+
+ //// if (disposing) {
+ //// // DisposeCode
+ disposeMethod.Statements.Add(new CodeConditionStatement(
+ new CodeArgumentReferenceExpression(disposingArgument.Name),
+ new CodeSnippetStatement(this.DisposeCode.ToString())));
+
+ //// }
+ //// }
+ this.LanguageProvider.GenerateCodeFromMember(disposeMethod, this.ClassCode, null);
+ }
+
+ private void InitializeBuffers()
+ {
+ this.ClassAttributes = new CodeAttributeDeclarationCollection();
+ this.Imports = new List();
+ this.References = new List();
+
+ this.ClassCode = new StringWriter(CultureInfo.InvariantCulture);
+ this.DisposeCode = new StringWriter(CultureInfo.InvariantCulture);
+ this.PostInitializationCode = new StringWriter(CultureInfo.InvariantCulture);
+ this.PreInitializationCode = new StringWriter(CultureInfo.InvariantCulture);
+ }
+ }
+}
diff --git a/src/T4Toolbox.DirectiveProcessors/T4Toolbox.DirectiveProcessors.csproj b/src/T4Toolbox.DirectiveProcessors/T4Toolbox.DirectiveProcessors.csproj
new file mode 100644
index 0000000..3e8b13e
--- /dev/null
+++ b/src/T4Toolbox.DirectiveProcessors/T4Toolbox.DirectiveProcessors.csproj
@@ -0,0 +1,58 @@
+
+
+
+ 10.0.20506
+ 2.0
+ {E0282961-2D83-48CC-B4D4-8257449CF8F7}
+ Library
+ Properties
+ T4Toolbox.DirectiveProcessors
+ T4Toolbox.DirectiveProcessors
+ 3fedb98d
+
+
+
+ false
+ DEBUG;TRACE
+
+
+ true
+ TRACE
+
+
+
+
+
+
+ 3.5
+
+
+
+
+
+
+
+ Properties\CommonAssemblyInfo.cs
+
+
+
+
+
+
+
+ {682e771a-76f7-4972-bbdc-1250b67f399b}
+ T4Toolbox
+
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
\ No newline at end of file
diff --git a/src/T4Toolbox.DirectiveProcessors/TransformationContextProcessor.cs b/src/T4Toolbox.DirectiveProcessors/TransformationContextProcessor.cs
new file mode 100644
index 0000000..a433a26
--- /dev/null
+++ b/src/T4Toolbox.DirectiveProcessors/TransformationContextProcessor.cs
@@ -0,0 +1,78 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.DirectiveProcessors
+{
+ using System.CodeDom;
+ using System.Collections.Generic;
+
+ ///
+ /// Generates initialization and cleanup logic required for .
+ ///
+ public class TransformationContextProcessor : DirectiveProcessor
+ {
+ ///
+ /// Name of the directive processor, as referenced by the templates.
+ ///
+ public const string Name = "T4Toolbox.TransformationContextProcessor";
+
+ private bool directiveProcessed = false;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TransformationContextProcessor()
+ {
+ // Force T4 generate Host property, even if the template directive does not say so explicitly.
+ this.RequiresProcessingRunIsHostSpecific = true;
+ }
+
+ ///
+ /// Gets the directive name as it is supposed to be used in template code.
+ ///
+ ///
+ /// A that contains name of the TransformationContext directive.
+ ///
+ protected override string DirectiveName
+ {
+ get { return "TransformationContext"; }
+ }
+
+ ///
+ /// Generates code in the class area of the transformation class created by the T4 engine.
+ ///
+ /// The arguments for the directive.
+ protected override void Process(IDictionary arguments)
+ {
+ // Don't generate the same code more then once if T4Toolbox.tt happens to be included multiple times
+ if (this.directiveProcessed)
+ {
+ this.Warning("Multiple <#@ include file=\"T4Toolbox.tt\" #> directives were found in the template.");
+ return;
+ }
+
+ this.References.Add(typeof(TransformationContext).Assembly.Location);
+
+ // Add the following method call to the Initialize method of the generated text template.
+ // T4Toolbox.TransformationContext.Initialize(this, this.GenerationEnvironment);
+ var initialize = new CodeExpressionStatement(
+ new CodeMethodInvokeExpression(
+ new CodeTypeReferenceExpression(typeof(TransformationContext).FullName),
+ "Initialize",
+ new CodeThisReferenceExpression(),
+ new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), "GenerationEnvironment")));
+ this.LanguageProvider.GenerateCodeFromStatement(initialize, this.PreInitializationCode, null);
+
+ // Add the following method call to the Dispose(bool) method of the generated text template.
+ // T4Toolbox.TransformationContext.Cleanup();
+ var cleanup = new CodeExpressionStatement(
+ new CodeMethodInvokeExpression(
+ new CodeTypeReferenceExpression(typeof(TransformationContext).FullName),
+ "Cleanup"));
+ this.LanguageProvider.GenerateCodeFromStatement(cleanup, this.DisposeCode, null);
+
+ this.directiveProcessed = true;
+ }
+ }
+}
diff --git a/src/T4Toolbox.DirectiveProcessors/packages.config b/src/T4Toolbox.DirectiveProcessors/packages.config
new file mode 100644
index 0000000..a849236
--- /dev/null
+++ b/src/T4Toolbox.DirectiveProcessors/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio.ItemTemplates/C#/Generator/Generator.tt b/src/T4Toolbox.VisualStudio.ItemTemplates/C#/Generator/Generator.tt
new file mode 100644
index 0000000..43e9df6
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio.ItemTemplates/C#/Generator/Generator.tt
@@ -0,0 +1,13 @@
+<#+
+//
+// Copyright © $registeredorganization$. All Rights Reserved.
+//
+
+public class $fileinputname$ : Generator
+{
+ protected override void RunCore()
+ {
+
+ }
+}
+#>
diff --git a/src/T4Toolbox.VisualStudio.ItemTemplates/C#/Generator/Generator.vstemplate b/src/T4Toolbox.VisualStudio.ItemTemplates/C#/Generator/Generator.vstemplate
new file mode 100644
index 0000000..f77d46d
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio.ItemTemplates/C#/Generator/Generator.vstemplate
@@ -0,0 +1,28 @@
+
+
+
+ Generator.tt
+ Generator
+ An empty code generator
+ CSharp
+
+
+
+ Generator.tt
+
+
+
+
+
+
+ Microsoft.VSDesigner, Version=11.0.0.0, Culture=Neutral, PublicKeyToken=b03f5f7f11d50a3a
+ Microsoft.VSDesigner.ProjectWizard.ItemPropertyWizard
+
+
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio.ItemTemplates/C#/Script/Script.tt b/src/T4Toolbox.VisualStudio.ItemTemplates/C#/Script/Script.tt
new file mode 100644
index 0000000..7b0d7c8
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio.ItemTemplates/C#/Script/Script.tt
@@ -0,0 +1,10 @@
+<#@ template language="C#" debug="True" #>
+<#@ output extension="cs" #>
+<#@ include file="T4Toolbox.tt" #>
+<#
+//
+// Copyright © $registeredorganization$. All Rights Reserved.
+//
+
+
+#>
diff --git a/src/T4Toolbox.VisualStudio.ItemTemplates/C#/Script/Script.vstemplate b/src/T4Toolbox.VisualStudio.ItemTemplates/C#/Script/Script.vstemplate
new file mode 100644
index 0000000..5d7e12d
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio.ItemTemplates/C#/Script/Script.vstemplate
@@ -0,0 +1,25 @@
+
+
+
+ Script.tt
+ Script
+ A T4 text template that can generate multiple output files.
+ CSharp
+
+
+
+ Script.tt
+
+
+
+
+
+
+ Microsoft.VSDesigner, Version=11.0.0.0, Culture=Neutral, PublicKeyToken=b03f5f7f11d50a3a
+ Microsoft.VSDesigner.ProjectWizard.ItemPropertyWizard
+
+
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio.ItemTemplates/C#/Template/Template.tt b/src/T4Toolbox.VisualStudio.ItemTemplates/C#/Template/Template.tt
new file mode 100644
index 0000000..5809091
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio.ItemTemplates/C#/Template/Template.tt
@@ -0,0 +1,25 @@
+<#+
+//
+// Copyright © $registeredorganization$. All Rights Reserved.
+//
+
+public class $fileinputname$ : CSharpTemplate
+{
+ public override string TransformText()
+ {
+ base.TransformText();
+#>
+namespace <#= DefaultNamespace #>
+{
+ public class <#= Identifier("Sample Class") #>
+ {
+ private string <#= FieldName("Sample Field") #>;
+
+ public string <#= PropertyName("Sample Property") #> { get; set; }
+ }
+}
+<#+
+ return this.GenerationEnvironment.ToString();
+ }
+}
+#>
diff --git a/src/T4Toolbox.VisualStudio.ItemTemplates/C#/Template/Template.vstemplate b/src/T4Toolbox.VisualStudio.ItemTemplates/C#/Template/Template.vstemplate
new file mode 100644
index 0000000..e7e9770
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio.ItemTemplates/C#/Template/Template.vstemplate
@@ -0,0 +1,28 @@
+
+
+
+ Template.tt
+ Template
+ An empty code generation template
+ CSharp
+
+
+
+ Template.tt
+
+
+
+
+
+
+ Microsoft.VSDesigner, Version=11.0.0.0, Culture=Neutral, PublicKeyToken=b03f5f7f11d50a3a
+ Microsoft.VSDesigner.ProjectWizard.ItemPropertyWizard
+
+
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio.ItemTemplates/T4Toolbox.VisualStudio.ItemTemplates.csproj b/src/T4Toolbox.VisualStudio.ItemTemplates/T4Toolbox.VisualStudio.ItemTemplates.csproj
new file mode 100644
index 0000000..c9420a9
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio.ItemTemplates/T4Toolbox.VisualStudio.ItemTemplates.csproj
@@ -0,0 +1,69 @@
+
+
+
+ 12.0
+ 11.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+
+ Debug
+ AnyCPU
+ {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ {EA04B345-97BE-4A49-9C9C-3EBD4F5D2250}
+ Library
+ Properties
+ T4Toolbox.VisualStudio.ItemTemplates
+ T4Toolbox.VisualStudio.ItemTemplates
+
+ false
+
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+
+
+
+
+
+ T4 Toolbox
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio.ItemTemplates/VB/Generator/Generator.tt b/src/T4Toolbox.VisualStudio.ItemTemplates/VB/Generator/Generator.tt
new file mode 100644
index 0000000..e3e9c7a
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio.ItemTemplates/VB/Generator/Generator.tt
@@ -0,0 +1,14 @@
+<#+
+'
+' Copyright © $registeredorganization$. All Rights Reserved.
+'
+
+Public Class $fileinputname$
+ Inherits Generator
+
+ Protected Overrides Sub RunCore()
+
+ End Sub
+
+End Class
+#>
diff --git a/src/T4Toolbox.VisualStudio.ItemTemplates/VB/Generator/Generator.vstemplate b/src/T4Toolbox.VisualStudio.ItemTemplates/VB/Generator/Generator.vstemplate
new file mode 100644
index 0000000..8003592
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio.ItemTemplates/VB/Generator/Generator.vstemplate
@@ -0,0 +1,28 @@
+
+
+
+ Generator.tt
+ Generator
+ An empty code generator
+ VisualBasic
+
+
+
+ Generator.tt
+
+
+
+
+
+
+ Microsoft.VSDesigner, Version=11.0.0.0, Culture=Neutral, PublicKeyToken=b03f5f7f11d50a3a
+ Microsoft.VSDesigner.ProjectWizard.ItemPropertyWizard
+
+
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio.ItemTemplates/VB/Script/Script.tt b/src/T4Toolbox.VisualStudio.ItemTemplates/VB/Script/Script.tt
new file mode 100644
index 0000000..667c3ce
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio.ItemTemplates/VB/Script/Script.tt
@@ -0,0 +1,10 @@
+<#@ template language="VB" debug="True" #>
+<#@ output extension="vb" #>
+<#@ include file="T4Toolbox.tt" #>
+<#
+'
+' Copyright © $registeredorganization$. All Rights Reserved.
+'
+
+
+#>
diff --git a/src/T4Toolbox.VisualStudio.ItemTemplates/VB/Script/Script.vstemplate b/src/T4Toolbox.VisualStudio.ItemTemplates/VB/Script/Script.vstemplate
new file mode 100644
index 0000000..70203f6
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio.ItemTemplates/VB/Script/Script.vstemplate
@@ -0,0 +1,25 @@
+
+
+
+ Script.tt
+ Script
+ A T4 text template that can generate multiple output files.
+ VisualBasic
+
+
+
+ Script.tt
+
+
+
+
+
+
+ Microsoft.VSDesigner, Version=11.0.0.0, Culture=Neutral, PublicKeyToken=b03f5f7f11d50a3a
+ Microsoft.VSDesigner.ProjectWizard.ItemPropertyWizard
+
+
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio.ItemTemplates/VB/Template/Template.tt b/src/T4Toolbox.VisualStudio.ItemTemplates/VB/Template/Template.tt
new file mode 100644
index 0000000..6bb8f25
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio.ItemTemplates/VB/Template/Template.tt
@@ -0,0 +1,15 @@
+<#+
+'
+' Copyright © $registeredorganization$. All Rights Reserved.
+'
+
+Public Class $fileinputname$
+ Inherits Template
+
+ Public Overrides Function TransformText() As String
+
+ Return Me.GenerationEnvironment.ToString()
+ End Function
+
+End Class
+#>
diff --git a/src/T4Toolbox.VisualStudio.ItemTemplates/VB/Template/Template.vstemplate b/src/T4Toolbox.VisualStudio.ItemTemplates/VB/Template/Template.vstemplate
new file mode 100644
index 0000000..9389ac9
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio.ItemTemplates/VB/Template/Template.vstemplate
@@ -0,0 +1,28 @@
+
+
+
+ Template.tt
+ Template
+ An empty code generation template
+ VisualBasic
+
+
+
+ Template.tt
+
+
+
+
+
+
+ Microsoft.VSDesigner, Version=11.0.0.0, Culture=Neutral, PublicKeyToken=b03f5f7f11d50a3a
+ Microsoft.VSDesigner.ProjectWizard.ItemPropertyWizard
+
+
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/AssemblyInfo.cs b/src/T4Toolbox.VisualStudio/AssemblyInfo.cs
new file mode 100644
index 0000000..07b08f6
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/AssemblyInfo.cs
@@ -0,0 +1,44 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+using System;
+using System.Reflection;
+using Microsoft.VisualStudio.Shell;
+
+[assembly: AssemblyTitle(T4Toolbox.VisualStudio.AssemblyInfo.Name)]
+[assembly: CLSCompliant(false)]
+
+// Help Visual Studio resolve references to the T4Toolbox assembly.
+[assembly: ProvideCodeBase(
+ AssemblyName = T4Toolbox.AssemblyInfo.Name,
+ Version = T4Toolbox.AssemblyInfo.Version,
+ CodeBase = @"$PackageFolder$\" + T4Toolbox.AssemblyInfo.Name + ".dll")]
+
+// This is currently required because MPF enumerates attributes applied to T4ToolboxPackage class when instantiating
+// the T4ToolboxOptionsPage. This can be eliminated if I can figure out how to register directive processors via MEF instead
+// of ProvideDirectiveProcessorAttribute.
+[assembly: ProvideCodeBase(
+ AssemblyName = T4Toolbox.DirectiveProcessors.AssemblyInfo.Name,
+ Version = T4Toolbox.DirectiveProcessors.AssemblyInfo.Version,
+ CodeBase = @"$PackageFolder$\" + T4Toolbox.DirectiveProcessors.AssemblyInfo.Name + ".dll")]
+
+// Help Visual Studio resolve references to the T4Toolbox.VisualStudio assembly.
+[assembly: ProvideCodeBase(
+ AssemblyName = T4Toolbox.VisualStudio.AssemblyInfo.Name,
+ Version = T4Toolbox.VisualStudio.AssemblyInfo.Version,
+ CodeBase = @"$PackageFolder$\" + T4Toolbox.VisualStudio.AssemblyInfo.Name + ".dll")]
+
+namespace T4Toolbox.VisualStudio
+{
+ ///
+ /// Defines constants describing the T4Toolbox.VisualStudio assembly.
+ ///
+ internal abstract class AssemblyInfo : T4Toolbox.AssemblyInfo
+ {
+ ///
+ /// Gets the name of the assembly.
+ ///
+ public new const string Name = "T4Toolbox.VisualStudio";
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/BrowseObjectExtender.cs b/src/T4Toolbox.VisualStudio/BrowseObjectExtender.cs
new file mode 100644
index 0000000..9d2dafb
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/BrowseObjectExtender.cs
@@ -0,0 +1,157 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio
+{
+ using System;
+ using System.ComponentModel;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Drawing.Design;
+ using System.Globalization;
+ using System.Runtime.InteropServices;
+ using EnvDTE;
+ using Microsoft.VisualStudio;
+ using Microsoft.VisualStudio.Shell.Interop;
+
+ ///
+ /// Adds "Custom Tool Template" and "Custom ToolParameters" properties to the C# and Visual Basic file properties.
+ ///
+ [ComVisible(true), ClassInterface(ClassInterfaceType.AutoDispatch)]
+ [SuppressMessage("Microsoft.Interoperability", "CA1409:ComVisibleTypesShouldBeCreatable", Justification = "Instances of this type are created only by TemplatePropertyProvider.")]
+ public class BrowseObjectExtender
+ {
+ ///
+ /// Name used to register the extender with Visual Studio.
+ ///
+ ///
+ /// Visual Studio uses this name to determine name of the objects it creates.
+ /// Choosing this value allows us to create properties with clean names like T4Toolbox.CustomToolTemplate.
+ ///
+ internal const string Name = "T4Toolbox";
+
+ private const string TemplatePropertyDisplayName = "Custom Tool Template";
+
+ private readonly uint itemId;
+ private readonly IVsHierarchy hierarchy;
+ private readonly IVsBuildPropertyStorage propertyStorage;
+ private readonly IServiceProvider serviceProvider;
+ private readonly int cookie;
+ private readonly IExtenderSite site;
+ private ProjectItem projectItem;
+
+ internal BrowseObjectExtender(IServiceProvider serviceProvider, IVsBrowseObject browseObject, IExtenderSite site, int cookie)
+ {
+ Debug.Assert(serviceProvider != null, "serviceProvider");
+ Debug.Assert(browseObject != null, "browseObject");
+ Debug.Assert(site != null, "site");
+ Debug.Assert(cookie != 0, "cookie");
+
+ this.site = site;
+ this.cookie = cookie;
+ this.serviceProvider = serviceProvider;
+ ErrorHandler.ThrowOnFailure(browseObject.GetProjectItem(out this.hierarchy, out this.itemId));
+ this.propertyStorage = (IVsBuildPropertyStorage)this.hierarchy;
+ this.CustomToolParameters = new CustomToolParameters(this.serviceProvider, this.hierarchy, this.itemId);
+ }
+
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ///
+ /// This method notifies the extender site that the extender has been deleted in order to prevent Visual Studio
+ /// from crashing with an Access Violation exception.
+ ///
+ ~BrowseObjectExtender()
+ {
+ try
+ {
+ this.site.NotifyDelete(this.cookie);
+ }
+ catch (InvalidComObjectException)
+ {
+ // This exception occurs when the Runtime-Callable Wrapper (RCW) was already disconnected from the COM object.
+ // This typically happens when the extender is disposed when Visual Studio shuts down.
+ }
+ }
+
+ ///
+ /// Gets the object that represents parameters defined in a text template.
+ ///
+ [DisplayName("Custom Tool Parameters"), Category("Advanced")]
+ [Description("Specifies values for parameters defined in a T4 template transformed by the TextTemplatingFileGenerator or the " + TemplatedFileGenerator.Name + ".")]
+ public CustomToolParameters CustomToolParameters { get; private set; }
+
+ ///
+ /// Gets or sets the file name of the template used by the .
+ ///
+ [DisplayName(TemplatePropertyDisplayName), Category("Advanced")]
+ [Description("A T4 template used by the " + TemplatedFileGenerator.Name + " to generate code from this file.")]
+ [Editor(typeof(CustomToolTemplateEditor), typeof(UITypeEditor))]
+ public string CustomToolTemplate
+ {
+ get
+ {
+ string value;
+ if (ErrorHandler.Failed(this.propertyStorage.GetItemAttribute(this.itemId, ItemMetadata.Template, out value)))
+ {
+ // Metadata element is not defined. Return an empty string.
+ value = string.Empty;
+ }
+
+ return value;
+ }
+
+ set
+ {
+ if (!string.IsNullOrWhiteSpace(value))
+ {
+ // Report an error if the user tries to specify template for an incompatible custom tool.
+ if (!string.IsNullOrWhiteSpace((string)this.ProjectItem.Properties.Item(ProjectItemProperty.CustomTool).Value) &&
+ TemplatedFileGenerator.Name != (string)this.ProjectItem.Properties.Item(ProjectItemProperty.CustomTool).Value)
+ {
+ throw new InvalidOperationException(
+ string.Format(
+ CultureInfo.CurrentCulture,
+ "The '{0}' property is supported only by the {1}. Set the 'Custom Tool' property first.",
+ TemplatePropertyDisplayName,
+ TemplatedFileGenerator.Name));
+ }
+
+ // Report an error if the template cannot be found
+ string fullPath = value;
+ var templateLocator = (TemplateLocator)this.serviceProvider.GetService(typeof(TemplateLocator));
+ if (!templateLocator.LocateTemplate(this.ProjectItem.FileNames[1], ref fullPath))
+ {
+ throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Template '{0}' could not be found", value));
+ }
+ }
+
+ ErrorHandler.ThrowOnFailure(this.propertyStorage.SetItemAttribute(this.itemId, ItemMetadata.Template, value));
+
+ // If the file does not have a custom tool yet, assume that by specifying the template user wants to use the T4Toolbox.TemplatedFileGenerator.
+ if (!string.IsNullOrWhiteSpace(value) &&
+ string.IsNullOrWhiteSpace((string)this.ProjectItem.Properties.Item(ProjectItemProperty.CustomTool).Value))
+ {
+ this.ProjectItem.Properties.Item(ProjectItemProperty.CustomTool).Value = TemplatedFileGenerator.Name;
+ }
+ }
+ }
+
+ private ProjectItem ProjectItem
+ {
+ get
+ {
+ if (this.projectItem == null)
+ {
+ object extObject;
+ ErrorHandler.ThrowOnFailure(this.hierarchy.GetProperty(this.itemId, (int)__VSHPROPID.VSHPROPID_ExtObject, out extObject));
+ this.projectItem = (ProjectItem)extObject;
+ }
+
+ return this.projectItem;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/BrowseObjectExtenderProvider.cs b/src/T4Toolbox.VisualStudio/BrowseObjectExtenderProvider.cs
new file mode 100644
index 0000000..659bb22
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/BrowseObjectExtenderProvider.cs
@@ -0,0 +1,46 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio
+{
+ using System;
+ using EnvDTE;
+ using Microsoft.VisualStudio.Shell.Interop;
+
+ ///
+ /// Provides a "Template" property extender for C# and Visual Basic project items.
+ ///
+ internal sealed class BrowseObjectExtenderProvider : IExtenderProvider, IDisposable
+ {
+ private const string ExtenderName = "T4Toolbox";
+
+ private readonly string extenderCategory;
+ private readonly ObjectExtenders objectExtenders;
+ private readonly int providerCookie;
+ private readonly IServiceProvider serviceProvider;
+
+ public BrowseObjectExtenderProvider(IServiceProvider serviceProvider, string extenderCategory)
+ {
+ this.serviceProvider = serviceProvider;
+ this.objectExtenders = (ObjectExtenders)serviceProvider.GetService(typeof(ObjectExtenders));
+ this.extenderCategory = extenderCategory;
+ this.providerCookie = this.objectExtenders.RegisterExtenderProvider(extenderCategory, BrowseObjectExtenderProvider.ExtenderName, this);
+ }
+
+ public bool CanExtend(string extenderCategory, string extenderName, object extendee)
+ {
+ return extenderCategory == this.extenderCategory && extenderName == ExtenderName && extendee is IVsBrowseObject;
+ }
+
+ public void Dispose()
+ {
+ this.objectExtenders.UnregisterExtenderProvider(this.providerCookie);
+ }
+
+ public object GetExtender(string extenderCategory, string extenderName, object extendee, IExtenderSite site, int extenderCookie)
+ {
+ return new BrowseObjectExtender(this.serviceProvider, (IVsBrowseObject)extendee, site, extenderCookie);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/CustomToolParameter.cs b/src/T4Toolbox.VisualStudio/CustomToolParameter.cs
new file mode 100644
index 0000000..6fcea63
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/CustomToolParameter.cs
@@ -0,0 +1,145 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio
+{
+ using System;
+ using System.ComponentModel;
+ using System.Diagnostics;
+ using System.Globalization;
+
+ using Microsoft.VisualStudio;
+ using Microsoft.VisualStudio.Shell.Interop;
+
+ ///
+ /// Represents a single parameter defined in a text template in the Properties window of Visual Studio.
+ ///
+ internal class CustomToolParameter : PropertyDescriptor
+ {
+ private readonly Type parameterType;
+ private readonly string description;
+ private readonly TypeConverter converter;
+
+ public CustomToolParameter(string parameterName, Type parameterType, string description)
+ : base(parameterName, null)
+ {
+ Debug.Assert(!string.IsNullOrEmpty(parameterName), "parameterName");
+ Debug.Assert(parameterType != null, "parameterType");
+ Debug.Assert(description != null, "description");
+
+ this.parameterType = parameterType;
+ this.description = description;
+ this.converter = TypeDescriptor.GetConverter(parameterType);
+ }
+
+ public override Type ComponentType
+ {
+ get { return typeof(CustomToolParameters); }
+ }
+
+ public override bool IsReadOnly
+ {
+ get { return !this.converter.CanConvertTo(typeof(string)) || !this.converter.CanConvertFrom(typeof(string)); }
+ }
+
+ public override Type PropertyType
+ {
+ get { return this.parameterType; }
+ }
+
+ public override bool CanResetValue(object component)
+ {
+ return true;
+ }
+
+ public override object GetValue(object component)
+ {
+ IVsBuildPropertyStorage project;
+ uint itemId;
+ GetProjectItem(component, out project, out itemId);
+
+ string stringValue;
+ if (ErrorHandler.Failed(project.GetItemAttribute(itemId, this.Name, out stringValue)))
+ {
+ return this.GetDefaultValue();
+ }
+
+ return this.converter.ConvertFrom(stringValue);
+ }
+
+ public override void ResetValue(object component)
+ {
+ IVsBuildPropertyStorage project;
+ uint itemId;
+ GetProjectItem(component, out project, out itemId);
+
+ ErrorHandler.ThrowOnFailure(project.SetItemAttribute(itemId, this.Name, null));
+ }
+
+ public override void SetValue(object component, object value)
+ {
+ IVsBuildPropertyStorage project;
+ uint itemId;
+ GetProjectItem(component, out project, out itemId);
+
+ if (object.Equals(value, this.GetDefaultValue()))
+ {
+ ErrorHandler.ThrowOnFailure(project.SetItemAttribute(itemId, this.Name, null));
+ }
+ else
+ {
+ string stringValue = this.converter.ConvertToInvariantString(value);
+ ErrorHandler.ThrowOnFailure(project.SetItemAttribute(itemId, this.Name, stringValue));
+ }
+ }
+
+ ///
+ /// Returns true when property value is different than the default.
+ ///
+ ///
+ /// This is used by the PropertyGrid in Visual Studio to display values that are actually stored in bold font.
+ ///
+ public override bool ShouldSerializeValue(object component)
+ {
+ return !object.Equals(this.GetValue(component), this.GetDefaultValue());
+ }
+
+ protected override AttributeCollection CreateAttributeCollection()
+ {
+ if (!string.IsNullOrWhiteSpace(this.description))
+ {
+ return AttributeCollection.FromExisting(base.CreateAttributeCollection(), new DescriptionAttribute(this.description));
+ }
+
+ return base.CreateAttributeCollection();
+ }
+
+ private static void GetProjectItem(object component, out IVsBuildPropertyStorage project, out uint itemId)
+ {
+ if (component == null)
+ {
+ throw new ArgumentNullException("component");
+ }
+
+ var parent = component as CustomToolParameters;
+ if (parent == null)
+ {
+ throw new ArgumentException(
+ string.Format(
+ CultureInfo.CurrentCulture,
+ "Object of type {0} is expected, actual object is of type {1}.",
+ typeof(CustomToolParameters).FullName,
+ component.GetType().FullName),
+ "component");
+ }
+
+ parent.GetProjectItem(out project, out itemId);
+ }
+
+ private object GetDefaultValue()
+ {
+ return this.parameterType.IsValueType && this.parameterType != typeof(void) ? Activator.CreateInstance(this.parameterType) : null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/CustomToolParameters.cs b/src/T4Toolbox.VisualStudio/CustomToolParameters.cs
new file mode 100644
index 0000000..5b00a67
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/CustomToolParameters.cs
@@ -0,0 +1,306 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio
+{
+ using System;
+ using System.Collections.Generic;
+ using System.ComponentModel;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.IO;
+ using System.Linq;
+ using System.Reflection;
+ using System.Text.RegularExpressions;
+ using Microsoft.VisualStudio;
+ using Microsoft.VisualStudio.Shell.Interop;
+ using Microsoft.VisualStudio.TextTemplating;
+ using Microsoft.VisualStudio.TextTemplating.VSHost;
+
+ ///
+ /// Represents a collection of text template parameters in the Properties window of Visual Studio.
+ ///
+ ///
+ /// This class implements the interface to present template parameters
+ /// as a dynamic list of objects.
+ ///
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ public class CustomToolParameters : ICustomTypeDescriptor
+ {
+ private const string FileGroup = "F";
+ private const string NameGroup = "N";
+ private const string TypeGroup = "T";
+
+ private static readonly Regex IncludeExpression = new Regex(
+ "<\\#\\@\\s*include\\s+file\\s*=\\s*\"(?<" + FileGroup + ">[^\"]*)\"\\s*\\#>",
+ RegexOptions.IgnoreCase);
+
+ private static readonly Regex ParameterExpression = new Regex(
+ "<\\#\\@\\s*parameter\\s+name\\s*=\\s*\"(?<" + NameGroup + ">[^\"]*)\"\\s+type\\s*=\\s*\"(?<" + TypeGroup + ">[^\"]*)\"\\s*\\#>",
+ RegexOptions.IgnoreCase);
+
+ private readonly IServiceProvider serviceProvider;
+ private readonly IVsHierarchy project;
+ private readonly uint projectItemId;
+
+ private ITextTemplatingEngineHost templatingHost;
+ private ITextTemplating templatingService;
+ private string[] assemblyReferences;
+
+ internal CustomToolParameters(IServiceProvider serviceProvider, IVsHierarchy project, uint projectItemId)
+ {
+ Debug.Assert(serviceProvider != null, "serviceProvider");
+ Debug.Assert(project != null, "project");
+ Debug.Assert(projectItemId != 0, "projectItemId");
+
+ this.serviceProvider = serviceProvider;
+ this.project = project;
+ this.projectItemId = projectItemId;
+ }
+
+ ///
+ /// Returns a default collection of attributes applied to this class.
+ ///
+ public AttributeCollection GetAttributes()
+ {
+ return TypeDescriptor.GetAttributes(this, noCustomTypeDesc: true);
+ }
+
+ ///
+ /// Returns the default name of this class.
+ ///
+ public string GetClassName()
+ {
+ return TypeDescriptor.GetClassName(this, noCustomTypeDesc: true);
+ }
+
+ ///
+ /// Returns the default name of this component.
+ ///
+ public string GetComponentName()
+ {
+ return TypeDescriptor.GetComponentName(this, noCustomTypeDesc: true);
+ }
+
+ ///
+ /// Returns a specified in a applied to this class.
+ ///
+ public TypeConverter GetConverter()
+ {
+ return TypeDescriptor.GetConverter(this, noCustomTypeDesc: true);
+ }
+
+ ///
+ /// Returns a default event defined in this class.
+ ///
+ public EventDescriptor GetDefaultEvent()
+ {
+ return TypeDescriptor.GetDefaultEvent(this, noCustomTypeDesc: true);
+ }
+
+ ///
+ /// Returns a default property defined in this class.
+ ///
+ public PropertyDescriptor GetDefaultProperty()
+ {
+ return TypeDescriptor.GetDefaultProperty(this, noCustomTypeDesc: true);
+ }
+
+ ///
+ /// Returns a default editor of this class.
+ ///
+ public object GetEditor(Type editorBaseType)
+ {
+ return TypeDescriptor.GetEditor(this, editorBaseType, noCustomTypeDesc: true);
+ }
+
+ ///
+ /// Returns a default collection of events with the given attributes defined in this class.
+ ///
+ public EventDescriptorCollection GetEvents(Attribute[] attributes)
+ {
+ return TypeDescriptor.GetEvents(this, attributes, noCustomTypeDesc: true);
+ }
+
+ ///
+ /// Returns a default collection of events defined in this class.
+ ///
+ public EventDescriptorCollection GetEvents()
+ {
+ return TypeDescriptor.GetEvents(this, noCustomTypeDesc: true);
+ }
+
+ ///
+ /// Returns a collection of objects representing parameters defined in a text template.
+ ///
+ public PropertyDescriptorCollection GetProperties()
+ {
+ return this.GetProperties(null);
+ }
+
+ ///
+ /// Returns a collection of objects representing parameters defined in a text template.
+ ///
+ public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
+ {
+ this.templatingService = (ITextTemplating)this.serviceProvider.GetService(typeof(STextTemplating));
+ this.templatingHost = (ITextTemplatingEngineHost)this.templatingService;
+
+ string templateFileName;
+ if (this.ResolveTemplate(out templateFileName))
+ {
+ string templateContent = File.ReadAllText(templateFileName, EncodingHelper.GetEncoding(templateFileName));
+
+ this.templatingService.PreprocessTemplate(templateFileName, templateContent, null, "TemporaryClass", "T4Toolbox", out this.assemblyReferences);
+ for (int i = 0; i < this.assemblyReferences.Length; i++)
+ {
+ this.assemblyReferences[i] = this.templatingHost.ResolveAssemblyReference(this.assemblyReferences[i]);
+ }
+
+ var parameters = new List();
+ this.ParseParameters(templateContent, parameters);
+ return new PropertyDescriptorCollection(parameters.Cast().ToArray());
+ }
+
+ return PropertyDescriptorCollection.Empty;
+ }
+
+ ///
+ /// Returns an owner of the specified .
+ ///
+ public object GetPropertyOwner(PropertyDescriptor pd)
+ {
+ return this;
+ }
+
+ ///
+ /// Returns an empty string to prevent the type name from being displayed in Visual Studio Properties window.
+ ///
+ public override string ToString()
+ {
+ return string.Empty;
+ }
+
+ internal void GetProjectItem(out IVsBuildPropertyStorage project, out uint itemId)
+ {
+ project = (IVsBuildPropertyStorage)this.project;
+ itemId = this.projectItemId;
+ }
+
+ private void ParseParameters(string templateContent, List parameters)
+ {
+ // Parse any <#@ include #> directives from the template
+ MatchCollection includeMatches = IncludeExpression.Matches(templateContent);
+ foreach (Match includeMatch in includeMatches)
+ {
+ string includedFile = includeMatch.Groups[FileGroup].Value;
+ string loadedContent, loadedFile;
+ if (this.templatingHost.LoadIncludeText(includedFile, out loadedContent, out loadedFile))
+ {
+ this.ParseParameters(loadedContent, parameters);
+ }
+ }
+
+ // Parse any <#@ parameter #> directives from the template
+ MatchCollection matches = ParameterExpression.Matches(templateContent);
+ foreach (Match parameterMatch in matches)
+ {
+ parameters.Add(this.CreateParameter(parameterMatch));
+ }
+ }
+
+ private bool ResolveTemplate(out string templateFileName)
+ {
+ templateFileName = string.Empty;
+
+ string inputFileName;
+ ErrorHandler.ThrowOnFailure(this.project.GetCanonicalName(this.projectItemId, out inputFileName));
+
+ var propertyStorage = (IVsBuildPropertyStorage)this.project;
+
+ string generator;
+ if (ErrorHandler.Failed(propertyStorage.GetItemAttribute(this.projectItemId, ItemMetadata.Generator, out generator)))
+ {
+ return false;
+ }
+
+ if (string.Equals(generator, "TextTemplatingFileGenerator", StringComparison.OrdinalIgnoreCase))
+ {
+ templateFileName = inputFileName;
+ }
+ else if (string.Equals(generator, ScriptFileGenerator.Name, StringComparison.OrdinalIgnoreCase))
+ {
+ if (ErrorHandler.Failed(propertyStorage.GetItemAttribute(this.projectItemId, ItemMetadata.LastGenOutput, out templateFileName)))
+ {
+ return false;
+ }
+
+ templateFileName = Path.Combine(Path.GetDirectoryName(inputFileName), templateFileName);
+ }
+ else if (string.Equals(generator, TemplatedFileGenerator.Name, StringComparison.OrdinalIgnoreCase))
+ {
+ if (ErrorHandler.Failed(propertyStorage.GetItemAttribute(this.projectItemId, ItemMetadata.Template, out templateFileName)))
+ {
+ return false;
+ }
+
+ var templateLocator = (TemplateLocator)this.serviceProvider.GetService(typeof(TemplateLocator));
+ if (!templateLocator.LocateTemplate(inputFileName, ref templateFileName))
+ {
+ return false;
+ }
+ }
+
+ return File.Exists(templateFileName);
+ }
+
+ private CustomToolParameter CreateParameter(Match match)
+ {
+ // Resolve parameter type
+ string typeName = match.Groups[TypeGroup].Value;
+ string description = string.Empty;
+ Type type = null;
+ try
+ {
+ type = Type.GetType(typeName, throwOnError: true, assemblyResolver: null, typeResolver: this.ResolveType);
+ }
+ catch (TypeLoadException e)
+ {
+ type = typeof(void);
+ description = e.Message;
+ }
+
+ string name = match.Groups[NameGroup].Value;
+ return new CustomToolParameter(name, type, description);
+ }
+
+ [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom", Justification = "That's how the T4 Engine loads assemblies.")]
+ private Type ResolveType(Assembly assembly, string typeName, bool ignoreCase)
+ {
+ // Try among assemblies already loaded in the current AppDomain
+ Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
+ foreach (Assembly loadedAssembly in loadedAssemblies)
+ {
+ Type type = loadedAssembly.GetType(typeName, false, ignoreCase);
+ if (type != null)
+ {
+ return type;
+ }
+ }
+
+ // Try among assemblies referenced by the template
+ foreach (string assemblyFileName in this.assemblyReferences)
+ {
+ Assembly referencedAssembly = Assembly.LoadFrom(assemblyFileName);
+ Type type = referencedAssembly.GetType(typeName, false, ignoreCase);
+ if (type != null)
+ {
+ return type;
+ }
+ }
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/CustomToolTemplateEditor.cs b/src/T4Toolbox.VisualStudio/CustomToolTemplateEditor.cs
new file mode 100644
index 0000000..dd81e29
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/CustomToolTemplateEditor.cs
@@ -0,0 +1,94 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio
+{
+ using System;
+ using System.ComponentModel;
+ using System.Drawing.Design;
+ using System.IO;
+ using Microsoft.VisualStudio;
+ using Microsoft.VisualStudio.Shell.Interop;
+ using Microsoft.Win32;
+
+ ///
+ /// A specialized file name editor for the template property.
+ ///
+ public class CustomToolTemplateEditor : UITypeEditor
+ {
+ ///
+ /// Defines the editor as a modal dialog.
+ ///
+ public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
+ {
+ return UITypeEditorEditStyle.Modal;
+ }
+
+ ///
+ /// Uses the Windows Open File dialog to allow user to choose the template file.
+ ///
+ public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
+ {
+ string templateFullPath = GetFullTemplatePath(context, (string)value);
+
+ var dialog = new OpenFileDialog();
+ dialog.Title = "Select Custom Tool Template";
+ dialog.FileName = Path.GetFileName(templateFullPath);
+ dialog.InitialDirectory = Path.GetDirectoryName(templateFullPath);
+ dialog.Filter = "Text Templates (*.tt)|*.tt|All Files (*.*)|*.*";
+
+ if (dialog.ShowDialog() == true)
+ {
+ return GetRelativeTemplatePath(context, dialog.FileName);
+ }
+
+ return value;
+ }
+
+ private static string GetFullTemplatePath(ITypeDescriptorContext context, string fileName)
+ {
+ string inputFullPath = GetFullInputPath(context);
+
+ if (string.IsNullOrEmpty(fileName))
+ {
+ return Path.GetDirectoryName(inputFullPath) + Path.DirectorySeparatorChar;
+ }
+
+ string templateFullPath = fileName;
+ var templateLocator = (TemplateLocator)context.GetService(typeof(TemplateLocator));
+ if (!templateLocator.LocateTemplate(inputFullPath, ref templateFullPath))
+ {
+ return Path.Combine(Path.GetDirectoryName(inputFullPath), fileName);
+ }
+
+ return templateFullPath;
+ }
+
+ private static string GetRelativeTemplatePath(ITypeDescriptorContext context, string fullPath)
+ {
+ string inputPath = GetFullInputPath(context);
+ string relativePath = FileMethods.GetRelativePath(inputPath, fullPath);
+ if (relativePath.StartsWith("." + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
+ {
+ // Remove leading .\ from the path
+ relativePath = relativePath.Substring(relativePath.IndexOf(Path.DirectorySeparatorChar) + 1);
+ }
+
+ return relativePath;
+ }
+
+ private static string GetFullInputPath(ITypeDescriptorContext context)
+ {
+ var browseObject = (IVsBrowseObject)context.Instance;
+
+ IVsHierarchy hierarchy;
+ uint itemId;
+ ErrorHandler.ThrowOnFailure(browseObject.GetProjectItem(out hierarchy, out itemId));
+
+ string inputFileName;
+ ErrorHandler.ThrowOnFailure(hierarchy.GetCanonicalName(itemId, out inputFileName));
+ return inputFileName;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/Editor/ClassificationFormatDefinitions.cs b/src/T4Toolbox.VisualStudio/Editor/ClassificationFormatDefinitions.cs
new file mode 100644
index 0000000..ddb2b09
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/Editor/ClassificationFormatDefinitions.cs
@@ -0,0 +1,82 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio.Editor
+{
+ using System.ComponentModel.Composition;
+ using System.Windows.Media;
+ using Microsoft.VisualStudio.Text.Classification;
+ using Microsoft.VisualStudio.Utilities;
+
+ ///
+ /// Provides metadata information for registering text template format definitions with the Visual Studio editor.
+ ///
+ internal static class ClassificationFormatDefinitions
+ {
+ [Export(typeof(EditorFormatDefinition))]
+ [Name("Text Template Attribute Name")]
+ [ClassificationType(ClassificationTypeNames = ClassificationTypeName.AttributeName)]
+ [UserVisible(true)]
+ [Order(Before = Priority.Default)]
+ internal sealed class AttributeName : ClassificationFormatDefinition
+ {
+ internal AttributeName()
+ {
+ this.ForegroundColor = Colors.Red;
+ }
+ }
+
+ [Export(typeof(EditorFormatDefinition))]
+ [Name("Text Template Attribute Value")]
+ [ClassificationType(ClassificationTypeNames = ClassificationTypeName.AttributeValue)]
+ [UserVisible(true)]
+ [Order(Before = Priority.Default)]
+ internal sealed class AttributeValue : ClassificationFormatDefinition
+ {
+ internal AttributeValue()
+ {
+ this.ForegroundColor = Colors.Blue;
+ }
+ }
+
+ [Export(typeof(EditorFormatDefinition))]
+ [Name("Text Template Code Block")]
+ [ClassificationType(ClassificationTypeNames = ClassificationTypeName.CodeBlock)]
+ [UserVisible(true)]
+ [Order(Before = Priority.Default)]
+ internal sealed class CodeBlock : ClassificationFormatDefinition
+ {
+ internal CodeBlock()
+ {
+ this.BackgroundColor = Colors.Lavender;
+ }
+ }
+
+ [Export(typeof(EditorFormatDefinition))]
+ [Name("Text Template Delimiter")]
+ [ClassificationType(ClassificationTypeNames = ClassificationTypeName.Delimiter)]
+ [UserVisible(true)]
+ [Order(Before = Priority.Default)]
+ internal sealed class Delimiter : ClassificationFormatDefinition
+ {
+ internal Delimiter()
+ {
+ this.BackgroundColor = Colors.Yellow;
+ }
+ }
+
+ [Export(typeof(EditorFormatDefinition))]
+ [Name("Text Template Directive Name")]
+ [ClassificationType(ClassificationTypeNames = ClassificationTypeName.DirectiveName)]
+ [UserVisible(true)]
+ [Order(Before = Priority.Default)]
+ internal sealed class DirectiveName : ClassificationFormatDefinition
+ {
+ internal DirectiveName()
+ {
+ this.ForegroundColor = Colors.Maroon;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/Editor/ClassificationTypeDefinitions.cs b/src/T4Toolbox.VisualStudio/Editor/ClassificationTypeDefinitions.cs
new file mode 100644
index 0000000..4c289ab
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/Editor/ClassificationTypeDefinitions.cs
@@ -0,0 +1,31 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio.Editor
+{
+ using System.ComponentModel.Composition;
+ using Microsoft.VisualStudio.Text.Classification;
+ using Microsoft.VisualStudio.Utilities;
+
+ ///
+ /// Provides metadata information for registering text template classification types with the Visual Studio editor.
+ ///
+ internal static class ClassificationTypeDefinitions
+ {
+ [Export, Name(ClassificationTypeName.AttributeName)]
+ internal static ClassificationTypeDefinition AttributeName { get; set; }
+
+ [Export, Name(ClassificationTypeName.AttributeValue)]
+ internal static ClassificationTypeDefinition AttributeValue { get; set; }
+
+ [Export, Name(ClassificationTypeName.CodeBlock)]
+ internal static ClassificationTypeDefinition CodeBlock { get; set; }
+
+ [Export, Name(ClassificationTypeName.Delimiter)]
+ internal static ClassificationTypeDefinition Delimiter { get; set; }
+
+ [Export, Name(ClassificationTypeName.DirectiveName)]
+ internal static ClassificationTypeDefinition DirectiveName { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/Editor/ClassificationTypeName.cs b/src/T4Toolbox.VisualStudio/Editor/ClassificationTypeName.cs
new file mode 100644
index 0000000..17e75ee
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/Editor/ClassificationTypeName.cs
@@ -0,0 +1,18 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio.Editor
+{
+ ///
+ /// Defines names of Text Template classification types.
+ ///
+ internal static class ClassificationTypeName
+ {
+ internal const string AttributeName = TemplateContentType.Name + ".AttributeName";
+ internal const string AttributeValue = TemplateContentType.Name + ".AttributeValue";
+ internal const string CodeBlock = TemplateContentType.Name + ".CodeBlock";
+ internal const string Delimiter = TemplateContentType.Name + ".Delimiter";
+ internal const string DirectiveName = TemplateContentType.Name + ".DirectiveName";
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/Editor/TemplateClassificationTagger.cs b/src/T4Toolbox.VisualStudio/Editor/TemplateClassificationTagger.cs
new file mode 100644
index 0000000..5bfd117
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/Editor/TemplateClassificationTagger.cs
@@ -0,0 +1,102 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio.Editor
+{
+ using System;
+ using System.Diagnostics;
+ using System.Globalization;
+ using System.Windows;
+ using Microsoft.VisualStudio.Text;
+ using Microsoft.VisualStudio.Text.Classification;
+ using Microsoft.VisualStudio.Text.Tagging;
+ using T4Toolbox.VisualStudio.TemplateAnalysis;
+
+ internal sealed class TemplateClassificationTagger : SimpleTagger
+ {
+ private readonly ITextBuffer buffer;
+ private readonly IClassificationType attributeNameClassification;
+ private readonly IClassificationType attributeValueClassification;
+ private readonly IClassificationType codeBlockClassificaiton;
+ private readonly IClassificationType delimiterClassification;
+ private readonly IClassificationType directiveNameClassificaiton;
+
+ internal TemplateClassificationTagger(ITextBuffer buffer, IClassificationTypeRegistryService classificationTypeRegistry)
+ : base(buffer)
+ {
+ Debug.Assert(classificationTypeRegistry != null, "classificationTypeRegistry");
+
+ this.buffer = buffer;
+ WeakEventManager.AddHandler(this.buffer, "Changed", this.BufferChanged);
+
+ this.attributeNameClassification = classificationTypeRegistry.GetClassificationType(ClassificationTypeName.AttributeName);
+ this.attributeValueClassification = classificationTypeRegistry.GetClassificationType(ClassificationTypeName.AttributeValue);
+ this.delimiterClassification = classificationTypeRegistry.GetClassificationType(ClassificationTypeName.Delimiter);
+ this.directiveNameClassificaiton = classificationTypeRegistry.GetClassificationType(ClassificationTypeName.DirectiveName);
+ this.codeBlockClassificaiton = classificationTypeRegistry.GetClassificationType(ClassificationTypeName.CodeBlock);
+
+ this.UpdateTagSpans();
+ }
+
+ private void BufferChanged(object sender, TextContentChangedEventArgs e)
+ {
+ this.UpdateTagSpans();
+ }
+
+ private void CreateTagSpans(ITextSnapshot snapshot)
+ {
+ var scanner = new TemplateScanner(snapshot.GetText());
+ while (scanner.yylex() != (int)SyntaxKind.EOF)
+ {
+ SyntaxNode token = scanner.yylval;
+ IClassificationType classificationType;
+ switch (token.Kind)
+ {
+ case SyntaxKind.BlockEnd:
+ case SyntaxKind.ClassBlockStart:
+ case SyntaxKind.DirectiveBlockStart:
+ case SyntaxKind.ExpressionBlockStart:
+ case SyntaxKind.StatementBlockStart:
+ classificationType = this.delimiterClassification;
+ break;
+
+ case SyntaxKind.DirectiveName:
+ classificationType = this.directiveNameClassificaiton;
+ break;
+
+ case SyntaxKind.AttributeName:
+ classificationType = this.attributeNameClassification;
+ break;
+
+ case SyntaxKind.AttributeValue:
+ classificationType = this.attributeValueClassification;
+ break;
+
+ case SyntaxKind.Code:
+ classificationType = this.codeBlockClassificaiton;
+ break;
+
+ case SyntaxKind.Equals:
+ case SyntaxKind.DoubleQuote:
+ // Ignore
+ continue;
+
+ default:
+ throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Unexpected SyntaxKind value {0}.", token.Kind));
+ }
+
+ this.CreateTagSpan(snapshot.CreateTrackingSpan(token.Span, SpanTrackingMode.EdgeNegative), new ClassificationTag(classificationType));
+ }
+ }
+
+ private void UpdateTagSpans()
+ {
+ using (this.Update())
+ {
+ this.RemoveTagSpans(trackingTagSpan => true); // remove all tag spans
+ this.CreateTagSpans(this.buffer.CurrentSnapshot);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/Editor/TemplateClassificationTaggerProvider.cs b/src/T4Toolbox.VisualStudio/Editor/TemplateClassificationTaggerProvider.cs
new file mode 100644
index 0000000..2d2d0c8
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/Editor/TemplateClassificationTaggerProvider.cs
@@ -0,0 +1,29 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio.Editor
+{
+ using System.ComponentModel.Composition;
+ using Microsoft.VisualStudio.Text;
+ using Microsoft.VisualStudio.Text.Classification;
+ using Microsoft.VisualStudio.Text.Tagging;
+ using Microsoft.VisualStudio.Utilities;
+
+ [Export(typeof(ITaggerProvider)), TagType(typeof(ClassificationTag)), ContentType(TemplateContentType.Name)]
+ internal sealed class TemplateClassificationTaggerProvider : ITaggerProvider
+ {
+ [Import]
+ internal IClassificationTypeRegistryService ClassificationRegistry { private get; set; }
+
+ public ITagger CreateTagger(ITextBuffer buffer) where T : ITag
+ {
+ if (T4ToolboxOptions.Instance.SyntaxColorizationEnabled)
+ {
+ return buffer.Properties.GetOrCreateSingletonProperty(() => new TemplateClassificationTagger(buffer, this.ClassificationRegistry)) as ITagger;
+ }
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/Editor/TemplateCompletionBuilder.cs b/src/T4Toolbox.VisualStudio/Editor/TemplateCompletionBuilder.cs
new file mode 100644
index 0000000..2f4a796
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/Editor/TemplateCompletionBuilder.cs
@@ -0,0 +1,129 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio.Editor
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Linq;
+ using Microsoft.VisualStudio.Language.Intellisense;
+ using T4Toolbox.VisualStudio.TemplateAnalysis;
+ using Attribute = T4Toolbox.VisualStudio.TemplateAnalysis.Attribute;
+
+ ///
+ /// Worker for calculating a set of applicable to a syntax
+ /// at the specified position.
+ ///
+ internal sealed class TemplateCompletionBuilder : SyntaxNodeVisitor
+ {
+ private readonly int position;
+ private Attribute currentAttribute;
+ private Directive currentDirective;
+
+ public TemplateCompletionBuilder(int position)
+ {
+ Debug.Assert(position >= 0, "position");
+ this.position = position;
+ }
+
+ ///
+ /// Gets the list of objects applicable to the .
+ /// Returns null if position was not visited or if node does not support completions.
+ ///
+ public IReadOnlyList Completions { get; private set; }
+
+ ///
+ /// Gets the at position specified in the constructor.
+ /// Returns null if position was not visited.
+ ///
+ public SyntaxNode Node { get; private set; }
+
+ protected internal override void VisitAttribute(Attribute node)
+ {
+ this.currentAttribute = node;
+ base.VisitAttribute(node);
+ }
+
+ protected internal override void VisitAttributeName(AttributeName node)
+ {
+ base.VisitAttributeName(node);
+
+ if (node.Span.Start <= this.position && this.position <= node.Span.End)
+ {
+ Debug.Assert(this.currentDirective != null, "currentDirective");
+ var directiveDescriptor = DirectiveDescriptor.GetDirectiveDescriptor(this.currentDirective.GetType());
+
+ var completions = new List();
+ foreach (AttributeDescriptor attribute in directiveDescriptor.Attributes.Values)
+ {
+ if (!this.currentDirective.Attributes.ContainsKey(attribute.DisplayName))
+ {
+ completions.Add(CreateAttributeCompletion(attribute));
+ }
+ }
+
+ if (completions.Count > 0)
+ {
+ this.Completions = completions;
+ this.Node = node;
+ }
+ }
+ }
+
+ protected internal override void VisitAttributeValue(AttributeValue node)
+ {
+ base.VisitAttributeValue(node);
+
+ if (node.Span.Start <= this.position && this.position <= node.Span.End)
+ {
+ Debug.Assert(this.currentDirective != null, "currentDirective");
+ Debug.Assert(this.currentAttribute != null, "currentAttribute");
+ DirectiveDescriptor directiveDescriptor = DirectiveDescriptor.GetDirectiveDescriptor(this.currentDirective.GetType());
+ AttributeDescriptor attributeDescriptor;
+ if (directiveDescriptor.Attributes.TryGetValue(this.currentAttribute.Name, out attributeDescriptor))
+ {
+ this.Completions = new List(attributeDescriptor.Values.Values.Select(CreateAttributeValueCompletion));
+ this.Node = node;
+ }
+ }
+ }
+
+ protected internal override void VisitDirective(Directive node)
+ {
+ this.currentDirective = node;
+ base.VisitDirective(node);
+ }
+
+ protected internal override void VisitDirectiveName(DirectiveName node)
+ {
+ base.VisitDirectiveName(node);
+
+ if (node.Span.Start <= this.position && this.position <= node.Span.End)
+ {
+ this.Completions = DirectiveDescriptor.GetBuiltInDirectives()
+ .Where(descriptor => !string.IsNullOrEmpty(descriptor.DisplayName)) // Skip custom directives
+ .Select(CreateDirectiveCompletion)
+ .ToList();
+ this.Node = node;
+ }
+ }
+
+ private static Completion CreateAttributeCompletion(AttributeDescriptor attribute)
+ {
+ return new Completion(attribute.DisplayName, attribute.DisplayName, attribute.Description, null, null);
+ }
+
+ private static Completion CreateAttributeValueCompletion(ValueDescriptor value)
+ {
+ Debug.Assert(value != null, "value");
+ return new Completion(value.DisplayName, value.DisplayName, value.Description, null, null);
+ }
+
+ private static Completion CreateDirectiveCompletion(DirectiveDescriptor directive)
+ {
+ return new Completion(directive.DisplayName, directive.DisplayName, directive.Description, null, null);
+ }
+ }
+}
diff --git a/src/T4Toolbox.VisualStudio/Editor/TemplateCompletionHandler.cs b/src/T4Toolbox.VisualStudio/Editor/TemplateCompletionHandler.cs
new file mode 100644
index 0000000..6be72ff
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/Editor/TemplateCompletionHandler.cs
@@ -0,0 +1,106 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio.Editor
+{
+ using System;
+ using System.Diagnostics;
+ using System.Runtime.InteropServices;
+ using Microsoft.VisualStudio;
+ using Microsoft.VisualStudio.Language.Intellisense;
+ using Microsoft.VisualStudio.OLE.Interop;
+ using Microsoft.VisualStudio.Shell;
+ using Microsoft.VisualStudio.Text.Editor;
+ using IServiceProvider = System.IServiceProvider;
+
+ ///
+ /// Initiates completion sessions for Text Templates in Visual Studio editors when user is typing directives.
+ ///
+ internal sealed class TemplateCompletionHandler : IOleCommandTarget
+ {
+ internal ICompletionBroker CompletionBroker;
+ internal IOleCommandTarget NextHandler;
+ internal IServiceProvider ServiceProvider;
+ internal ITextView TextView;
+
+ private ICompletionSession completionSession;
+
+ public int Exec(ref Guid commandGroup, uint command, uint options, IntPtr input, IntPtr output)
+ {
+ Debug.Assert(this.CompletionBroker != null, "completionBroker");
+ Debug.Assert(this.NextHandler != null, "nextHandler");
+ Debug.Assert(this.ServiceProvider != null, "serviceProvider");
+ Debug.Assert(this.TextView != null, "textView");
+
+ if (VsShellUtilities.IsInAutomationFunction(this.ServiceProvider))
+ {
+ return this.NextHandler.Exec(ref commandGroup, command, options, input, output);
+ }
+
+ // Commit or dismiss the current completion session
+ if (this.completionSession != null && !this.completionSession.IsDismissed)
+ {
+ if (commandGroup == VSConstants.VSStd2K &&
+ (command == (uint)VSConstants.VSStd2KCmdID.RETURN || command == (uint)VSConstants.VSStd2KCmdID.TAB))
+ {
+ if (this.completionSession.SelectedCompletionSet.SelectionStatus.IsSelected)
+ {
+ this.completionSession.Commit();
+ return VSConstants.S_OK;
+ }
+
+ this.completionSession.Dismiss();
+ }
+ }
+
+ // Execute next handler to pass the command to the text buffer
+ int result = this.NextHandler.Exec(ref commandGroup, command, options, input, output);
+
+ // Trigger new or filter the current completion session
+ if (commandGroup == VSConstants.VSStd2K)
+ {
+ if (command == (uint)VSConstants.VSStd2KCmdID.TYPECHAR && char.IsLetter((char)(ushort)Marshal.GetObjectForNativeVariant(input)))
+ {
+ if (this.completionSession == null)
+ {
+ this.completionSession = this.CompletionBroker.TriggerCompletion(this.TextView);
+
+ // completion session may not have been created, perhaps because there are no completion sets at current caret position?
+ if (this.completionSession != null)
+ {
+ this.completionSession.Dismissed += this.CompletionSessionDismissed;
+ }
+ }
+
+ if (this.completionSession != null && !this.completionSession.IsDismissed)
+ {
+ this.completionSession.Filter();
+ }
+ }
+
+ if (command == (uint)VSConstants.VSStd2KCmdID.BACKSPACE || command == (uint)VSConstants.VSStd2KCmdID.DELETE)
+ {
+ if (this.completionSession != null && !this.completionSession.IsDismissed)
+ {
+ this.completionSession.Filter();
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public int QueryStatus(ref Guid commandGroup, uint numberOfCommands, OLECMD[] commands, IntPtr commandText)
+ {
+ Debug.Assert(this.NextHandler != null, "nextHandler");
+ return this.NextHandler.QueryStatus(ref commandGroup, numberOfCommands, commands, commandText);
+ }
+
+ private void CompletionSessionDismissed(object sender, EventArgs e)
+ {
+ this.completionSession.Dismissed -= this.CompletionSessionDismissed;
+ this.completionSession = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/Editor/TemplateCompletionHandlerProvider.cs b/src/T4Toolbox.VisualStudio/Editor/TemplateCompletionHandlerProvider.cs
new file mode 100644
index 0000000..f9be7ce
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/Editor/TemplateCompletionHandlerProvider.cs
@@ -0,0 +1,56 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio.Editor
+{
+ using System.ComponentModel.Composition;
+ using System.Diagnostics;
+ using Microsoft.VisualStudio;
+ using Microsoft.VisualStudio.Editor;
+ using Microsoft.VisualStudio.Language.Intellisense;
+ using Microsoft.VisualStudio.Shell;
+ using Microsoft.VisualStudio.Text.Editor;
+ using Microsoft.VisualStudio.TextManager.Interop;
+ using Microsoft.VisualStudio.Utilities;
+
+ ///
+ /// Creates a for text template files opened in Visual Studio editor.
+ ///
+ [Export(typeof(IVsTextViewCreationListener)), ContentType(TemplateContentType.Name), TextViewRole(PredefinedTextViewRoles.Editable)]
+ internal sealed class TemplateCompletionHandlerProvider : IVsTextViewCreationListener
+ {
+ [Import]internal IVsEditorAdaptersFactoryService AdapterFactory;
+ [Import]internal SVsServiceProvider ServiceProvider;
+ [Import]internal ICompletionBroker CompletionBroker;
+
+ public void VsTextViewCreated(IVsTextView viewAdapter)
+ {
+ Debug.Assert(this.AdapterFactory != null, "AdapterFactory");
+ Debug.Assert(viewAdapter != null, "viewAdapter");
+
+ if (!T4ToolboxOptions.Instance.CompletionListsEnabled)
+ {
+ return;
+ }
+
+ IWpfTextView textView = this.AdapterFactory.GetWpfTextView(viewAdapter);
+ if (textView == null)
+ {
+ return;
+ }
+
+ textView.Properties.GetOrCreateSingletonProperty(() => this.CreateHandler(viewAdapter, textView));
+ }
+
+ private TemplateCompletionHandler CreateHandler(IVsTextView viewAdapter, IWpfTextView textView)
+ {
+ var handler = new TemplateCompletionHandler();
+ handler.TextView = textView;
+ handler.ServiceProvider = this.ServiceProvider;
+ handler.CompletionBroker = this.CompletionBroker;
+ ErrorHandler.ThrowOnFailure(viewAdapter.AddCommandFilter(handler, out handler.NextHandler));
+ return handler;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/Editor/TemplateCompletionSource.cs b/src/T4Toolbox.VisualStudio/Editor/TemplateCompletionSource.cs
new file mode 100644
index 0000000..24ec1f8
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/Editor/TemplateCompletionSource.cs
@@ -0,0 +1,52 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio.Editor
+{
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Linq;
+ using Microsoft.VisualStudio.Language.Intellisense;
+ using Microsoft.VisualStudio.Text;
+ using T4Toolbox.VisualStudio.TemplateAnalysis;
+
+ internal sealed class TemplateCompletionSource : ICompletionSource
+ {
+ private readonly ITextBuffer buffer;
+ private readonly TemplateAnalyzer analyzer;
+
+ internal TemplateCompletionSource(ITextBuffer buffer)
+ {
+ Debug.Assert(buffer != null, "buffer");
+
+ this.buffer = buffer;
+ this.analyzer = TemplateAnalyzer.GetOrCreate(buffer);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "This method is called by the Visual Studio editor and expects to receive valid arguments")]
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "This method is called by the Visual Studio editor and expects to receive valid arguments")]
+ public void AugmentCompletionSession(ICompletionSession session, IList completionSets)
+ {
+ Debug.Assert(session != null, "session");
+ Debug.Assert(completionSets != null, "completionSets");
+
+ TemplateAnalysis current = this.analyzer.CurrentAnalysis;
+ var builder = new TemplateCompletionBuilder(session.GetTriggerPoint(current.TextSnapshot).Value.Position);
+ builder.Visit(current.Template);
+ if (builder.Completions != null)
+ {
+ ITrackingSpan applicableTo = current.TextSnapshot.CreateTrackingSpan(builder.Node.Span, SpanTrackingMode.EdgeInclusive);
+ IEnumerable completions = builder.Completions.OrderBy(completion => completion.DisplayText);
+ var completionSet = new CompletionSet("All", "All", applicableTo, completions, null);
+ completionSets.Add(completionSet);
+ }
+ }
+
+ public void Dispose()
+ {
+ this.buffer.Properties.RemoveProperty(this.GetType());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/Editor/TemplateCompletionSourceProvider.cs b/src/T4Toolbox.VisualStudio/Editor/TemplateCompletionSourceProvider.cs
new file mode 100644
index 0000000..82a5914
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/Editor/TemplateCompletionSourceProvider.cs
@@ -0,0 +1,24 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio.Editor
+{
+ using System.ComponentModel.Composition;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using Microsoft.VisualStudio.Language.Intellisense;
+ using Microsoft.VisualStudio.Text;
+ using Microsoft.VisualStudio.Utilities;
+
+ [Export(typeof(ICompletionSourceProvider)), Name(TemplateContentType.Name), ContentType(TemplateContentType.Name)]
+ internal sealed class TemplateCompletionSourceProvider : ICompletionSourceProvider
+ {
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "This method is called by only by the Visual Studio editor and assumes that a textBuffer is supplied")]
+ public ICompletionSource TryCreateCompletionSource(ITextBuffer textBuffer)
+ {
+ Debug.Assert(textBuffer != null, "textBuffer");
+ return textBuffer.Properties.GetOrCreateSingletonProperty(() => new TemplateCompletionSource(textBuffer));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/Editor/TemplateContentType.cs b/src/T4Toolbox.VisualStudio/Editor/TemplateContentType.cs
new file mode 100644
index 0000000..65ef703
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/Editor/TemplateContentType.cs
@@ -0,0 +1,20 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio.Editor
+{
+ using System.ComponentModel.Composition;
+ using Microsoft.VisualStudio.Utilities;
+
+ internal static class TemplateContentType
+ {
+ internal const string Name = "TextTemplate";
+
+ [Export, Name(Name), BaseDefinition("code")]
+ internal static ContentTypeDefinition Definition { get; set; } // Used for metadata only
+
+ [Export, FileExtension(".tt"), ContentType(Name)]
+ internal static FileExtensionToContentTypeDefinition FileAssociation { get; set; } // Used for metadata only
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/Editor/TemplateErrorReporter.cs b/src/T4Toolbox.VisualStudio/Editor/TemplateErrorReporter.cs
new file mode 100644
index 0000000..33d39b8
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/Editor/TemplateErrorReporter.cs
@@ -0,0 +1,119 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio.Editor
+{
+ using System;
+ using System.Diagnostics;
+ using System.Windows;
+ using Microsoft.VisualStudio;
+ using Microsoft.VisualStudio.Shell;
+ using Microsoft.VisualStudio.Shell.Interop;
+ using Microsoft.VisualStudio.Text;
+ using Microsoft.VisualStudio.TextManager.Interop;
+ using T4Toolbox.VisualStudio.TemplateAnalysis;
+
+ ///
+ /// Displays template errors in the Error List window.
+ ///
+ internal sealed class TemplateErrorReporter : IDisposable
+ {
+ private readonly TemplateAnalyzer analyzer;
+ private readonly ITextDocument document;
+ private readonly IServiceProvider serviceProvider;
+
+ private ErrorListProvider errorListProvider;
+
+ private TemplateErrorReporter(ITextBuffer buffer, IServiceProvider serviceProvider, ITextDocumentFactoryService documentFactory)
+ {
+ Debug.Assert(buffer != null, "buffer");
+ Debug.Assert(serviceProvider != null, "serviceProvider");
+ Debug.Assert(documentFactory != null, "documentFactory");
+
+ this.serviceProvider = serviceProvider;
+
+ documentFactory.TryGetTextDocument(buffer, out this.document);
+ WeakEventManager.AddHandler(documentFactory, "TextDocumentDisposed", this.DocumentDisposed);
+
+ this.analyzer = TemplateAnalyzer.GetOrCreate(buffer);
+ WeakEventManager.AddHandler(this.analyzer, "TemplateChanged", this.TemplateChanged);
+
+ this.UpdateErrorTasks(this.analyzer.CurrentAnalysis);
+ }
+
+ public static TemplateErrorReporter GetOrCreate(ITextBuffer buffer, IServiceProvider serviceProvider, ITextDocumentFactoryService documentFactory)
+ {
+ return buffer.Properties.GetOrCreateSingletonProperty(() => new TemplateErrorReporter(buffer, serviceProvider, documentFactory));
+ }
+
+ public void Dispose()
+ {
+ this.document.TextBuffer.Properties.RemoveProperty(typeof(TemplateErrorReporter));
+
+ if (this.errorListProvider != null)
+ {
+ this.errorListProvider.Dispose();
+ this.errorListProvider = null;
+ }
+ }
+
+ private void DocumentDisposed(object sender, TextDocumentEventArgs e)
+ {
+ if (e.TextDocument == this.document)
+ {
+ this.Dispose();
+ }
+ }
+
+ private void TemplateChanged(object sender, TemplateAnalysis e)
+ {
+ this.UpdateErrorTasks(e);
+ }
+
+ private void UpdateErrorTasks(TemplateAnalysis templateAnalysis)
+ {
+ if (this.errorListProvider != null)
+ {
+ this.errorListProvider.Tasks.Clear();
+ }
+ else if (templateAnalysis.Errors.Count > 0)
+ {
+ this.errorListProvider = new ErrorListProvider(this.serviceProvider);
+ }
+
+ foreach (TemplateError error in templateAnalysis.Errors)
+ {
+ var errorTask = new ErrorTask();
+ errorTask.Document = this.document.FilePath;
+ errorTask.Category = TaskCategory.BuildCompile;
+ errorTask.Text = error.Message;
+ errorTask.ErrorCategory = TaskErrorCategory.Error;
+ errorTask.Line = error.Position.Line;
+ errorTask.Column = error.Position.Column;
+ errorTask.Navigate += this.NavigateToError;
+ this.errorListProvider.Tasks.Add(errorTask);
+ }
+ }
+
+ private void NavigateToError(object sender, EventArgs e)
+ {
+ var errorTask = (ErrorTask)sender;
+
+ IVsUIHierarchy hierarchyItem;
+ uint num;
+ IVsWindowFrame windowFrame;
+ VsShellUtilities.OpenDocument(this.serviceProvider, errorTask.Document, Guid.Empty, out hierarchyItem, out num, out windowFrame);
+ if (windowFrame != null)
+ {
+ errorTask.HierarchyItem = hierarchyItem;
+ this.errorListProvider.Refresh();
+ IVsTextView textView = VsShellUtilities.GetTextView(windowFrame);
+ if (textView != null)
+ {
+ ErrorHandler.ThrowOnFailure(textView.SetSelection(errorTask.Line, errorTask.Column, errorTask.Line, errorTask.Column));
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/Editor/TemplateErrorReporterProvider.cs b/src/T4Toolbox.VisualStudio/Editor/TemplateErrorReporterProvider.cs
new file mode 100644
index 0000000..9d20ed2
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/Editor/TemplateErrorReporterProvider.cs
@@ -0,0 +1,32 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio.Editor
+{
+ using System.ComponentModel.Composition;
+ using Microsoft.VisualStudio.Shell;
+ using Microsoft.VisualStudio.Text;
+ using Microsoft.VisualStudio.Text.Tagging;
+ using Microsoft.VisualStudio.Utilities;
+
+ [Export(typeof(ITaggerProvider)), TagType(typeof(ErrorTag)), ContentType(TemplateContentType.Name)]
+ internal sealed class TemplateErrorReporterProvider : ITaggerProvider
+ {
+ [Import]
+ private SVsServiceProvider serviceProvider = null;
+
+ [Import]
+ private ITextDocumentFactoryService documentFactory = null;
+
+ public ITagger CreateTagger(ITextBuffer buffer) where T : ITag
+ {
+ if (T4ToolboxOptions.Instance.ErrorReportingEnabled)
+ {
+ TemplateErrorReporter.GetOrCreate(buffer, this.serviceProvider, this.documentFactory);
+ }
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/Editor/TemplateErrorTagger.cs b/src/T4Toolbox.VisualStudio/Editor/TemplateErrorTagger.cs
new file mode 100644
index 0000000..3fcb85e
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/Editor/TemplateErrorTagger.cs
@@ -0,0 +1,30 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio.Editor
+{
+ using System.Diagnostics.CodeAnalysis;
+ using Microsoft.VisualStudio.Text;
+ using Microsoft.VisualStudio.Text.Adornments;
+ using Microsoft.VisualStudio.Text.Tagging;
+ using T4Toolbox.VisualStudio.TemplateAnalysis;
+
+ internal sealed class TemplateErrorTagger : TemplateTagger
+ {
+ public TemplateErrorTagger(ITextBuffer buffer) : base(buffer)
+ {
+ this.UpdateTagSpans(this.Analyzer.CurrentAnalysis);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "This is an internal method, called by base class.")]
+ protected override void CreateTagSpans(TemplateAnalysis analysis)
+ {
+ ITextSnapshot snapshot = analysis.TextSnapshot;
+ foreach (TemplateError error in analysis.Errors)
+ {
+ this.CreateTagSpan(snapshot.CreateTrackingSpan(error.Span, SpanTrackingMode.EdgeNegative), new ErrorTag(PredefinedErrorTypeNames.SyntaxError, error.Message));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/Editor/TemplateErrorTaggerProvider.cs b/src/T4Toolbox.VisualStudio/Editor/TemplateErrorTaggerProvider.cs
new file mode 100644
index 0000000..3351472
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/Editor/TemplateErrorTaggerProvider.cs
@@ -0,0 +1,31 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio.Editor
+{
+ using System;
+ using System.ComponentModel.Composition;
+ using Microsoft.VisualStudio.Text;
+ using Microsoft.VisualStudio.Text.Tagging;
+ using Microsoft.VisualStudio.Utilities;
+
+ [Export(typeof(ITaggerProvider)), TagType(typeof(ErrorTag)), ContentType(TemplateContentType.Name)]
+ internal sealed class TemplateErrorTaggerProvider : ITaggerProvider
+ {
+ public ITagger CreateTagger(ITextBuffer buffer) where T : ITag
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException("buffer");
+ }
+
+ if (T4ToolboxOptions.Instance.ErrorUnderliningEnabled)
+ {
+ return buffer.Properties.GetOrCreateSingletonProperty(() => new TemplateErrorTagger(buffer)) as ITagger;
+ }
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/Editor/TemplateOutliningTagger.cs b/src/T4Toolbox.VisualStudio/Editor/TemplateOutliningTagger.cs
new file mode 100644
index 0000000..441d3fd
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/Editor/TemplateOutliningTagger.cs
@@ -0,0 +1,83 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio.Editor
+{
+ using System.Diagnostics.CodeAnalysis;
+ using System.IO;
+ using System.Linq;
+ using System.Text;
+ using Microsoft.VisualStudio.Text;
+ using Microsoft.VisualStudio.Text.Tagging;
+ using T4Toolbox.VisualStudio.TemplateAnalysis;
+
+ internal sealed class TemplateOutliningTagger : TemplateTagger
+ {
+ public TemplateOutliningTagger(ITextBuffer buffer) : base(buffer)
+ {
+ this.UpdateTagSpans(this.Analyzer.CurrentAnalysis);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "This is an internal method, called by base class.")]
+ protected override void CreateTagSpans(TemplateAnalysis analysis)
+ {
+ // If text buffer contains recognizable template
+ Template template = analysis.Template;
+ if (template != null)
+ {
+ ITextSnapshot snapshot = analysis.TextSnapshot;
+ string text = snapshot.GetText();
+ foreach (CodeBlock codeBlock in template.ChildNodes().OfType())
+ {
+ ITrackingSpan trackingSpan = snapshot.CreateTrackingSpan(codeBlock.Span, SpanTrackingMode.EdgeNegative);
+
+ string collapsedForm = GetCollapsedForm(codeBlock, text);
+ string collapsedHintForm = GetCollapsedHintForm(codeBlock, text);
+ var tag = new OutliningRegionTag(collapsedForm, collapsedHintForm);
+
+ this.CreateTagSpan(trackingSpan, tag);
+ }
+ }
+ }
+
+ private static string GetCollapsedForm(CodeBlock codeBlock, string template)
+ {
+ var text = new StringBuilder();
+ text.Append(codeBlock.Start.GetText(template));
+ text.Append("...");
+ text.Append(codeBlock.End.GetText(template));
+ return text.ToString();
+ }
+
+ private static string GetCollapsedHintForm(CodeBlock codeBlock, string template)
+ {
+ var text = new StringBuilder();
+
+ using (var reader = new StringReader(codeBlock.GetText(template)))
+ {
+ for (int i = 0; i < 10; i++)
+ {
+ string line = reader.ReadLine();
+ if (line == null)
+ {
+ return text.ToString();
+ }
+
+ // Append new line manually to avoid unnecessary \r\n at the end
+ if (i > 0)
+ {
+ text.AppendLine();
+ }
+
+ text.Append(line);
+ }
+
+ text.AppendLine();
+ text.Append("...");
+ }
+
+ return text.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/Editor/TemplateOutliningTaggerProvider.cs b/src/T4Toolbox.VisualStudio/Editor/TemplateOutliningTaggerProvider.cs
new file mode 100644
index 0000000..4767187
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/Editor/TemplateOutliningTaggerProvider.cs
@@ -0,0 +1,31 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio.Editor
+{
+ using System;
+ using System.ComponentModel.Composition;
+ using Microsoft.VisualStudio.Text;
+ using Microsoft.VisualStudio.Text.Tagging;
+ using Microsoft.VisualStudio.Utilities;
+
+ [Export(typeof(ITaggerProvider)), TagType(typeof(OutliningRegionTag)), ContentType(TemplateContentType.Name)]
+ internal sealed class TemplateOutliningTaggerProvider : ITaggerProvider
+ {
+ public ITagger CreateTagger(ITextBuffer buffer) where T : ITag
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException("buffer");
+ }
+
+ if (T4ToolboxOptions.Instance.TemplateOutliningEnabled)
+ {
+ return buffer.Properties.GetOrCreateSingletonProperty(() => new TemplateOutliningTagger(buffer)) as ITagger;
+ }
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/T4Toolbox.VisualStudio/Editor/TemplateQuickInfoSource.cs b/src/T4Toolbox.VisualStudio/Editor/TemplateQuickInfoSource.cs
new file mode 100644
index 0000000..6190c62
--- /dev/null
+++ b/src/T4Toolbox.VisualStudio/Editor/TemplateQuickInfoSource.cs
@@ -0,0 +1,58 @@
+//
+// Copyright © Oleg Sych. All Rights Reserved.
+//
+
+namespace T4Toolbox.VisualStudio.Editor
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using Microsoft.VisualStudio.Language.Intellisense;
+ using Microsoft.VisualStudio.Text;
+ using T4Toolbox.VisualStudio.TemplateAnalysis;
+
+ internal sealed class TemplateQuickInfoSource : IQuickInfoSource
+ {
+ private readonly TemplateAnalyzer analyzer;
+
+ public TemplateQuickInfoSource(ITextBuffer buffer)
+ {
+ Debug.Assert(buffer != null, "buffer");
+ this.analyzer = TemplateAnalyzer.GetOrCreate(buffer);
+ }
+
+ public void AugmentQuickInfoSession(IQuickInfoSession session, IList