From 4224b8fe37dbb919926ee36066f15522cc23738e Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Mon, 16 Apr 2018 17:01:26 -0700 Subject: [PATCH 001/171] Update version number to 2.2.0 --- version.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.props b/version.props index b14f0a7488..784f6f825e 100644 --- a/version.props +++ b/version.props @@ -1,7 +1,7 @@ - 2.1.0 - preview3 + 2.2.0 + preview1 $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix)-final t000 From c8c6a6f0aeae5c7edf2b05b16daf1d437cc842c9 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 17 Apr 2018 10:26:26 -0700 Subject: [PATCH 002/171] Update versions in ProvideBindingRedirectAttribute --- build/dependencies.props | 16 +++++----- korebuild-lock.txt | 4 +-- .../Properties/BindingRedirectAttributes.cs | 32 +++++++++---------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 4cf541f129..6fa818d6f3 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -4,20 +4,20 @@ 0.10.13 - 2.1.0-preview3-17018 - 2.1.0-preview3-32233 - 2.1.0-preview3-32233 - 2.1.0-preview3-32233 + 2.2.0-preview1-17034 + 2.1.0-preview3-32251 + 2.1.0-preview3-32251 + 2.1.0-preview3-32251 15.6.82 15.6.82 15.6.82 2.6.1 2.6.1 - 2.1.0-preview3-32233 - 2.1.0-preview3-32233 + 2.1.0-preview3-32251 + 2.1.0-preview3-32251 2.1.0-preview3-26413-05 - 2.1.0-preview3-32233 - 2.1.0-preview3-32233 + 2.1.0-preview3-32251 + 2.1.0-preview3-32251 2.0.0 2.1.0-preview3-26413-05 15.6.1 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index ce2f277c53..431aac942d 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.1.0-preview3-17018 -commithash:af264ca131f212b5ba8aafbc5110fc0fc510a2be +version:2.2.0-preview1-17034 +commithash:60d8e6e904fbcf29050a34f187d6a0347a97e352 diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/Properties/BindingRedirectAttributes.cs b/tooling/Microsoft.VisualStudio.RazorExtension/Properties/BindingRedirectAttributes.cs index cbbdcdcba8..4e7d31d0bf 100644 --- a/tooling/Microsoft.VisualStudio.RazorExtension/Properties/BindingRedirectAttributes.cs +++ b/tooling/Microsoft.VisualStudio.RazorExtension/Properties/BindingRedirectAttributes.cs @@ -8,61 +8,61 @@ using Microsoft.VisualStudio.Shell; GenerateCodeBase = true, PublicKeyToken = "adb9793829ddae60", OldVersionLowerBound = "0.0.0.0", - OldVersionUpperBound = "2.1.0.0", - NewVersion = "2.1.0.0")] + OldVersionUpperBound = "2.2.0.0", + NewVersion = "2.2.0.0")] [assembly: ProvideBindingRedirection( AssemblyName = "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X", GenerateCodeBase = true, PublicKeyToken = "adb9793829ddae60", OldVersionLowerBound = "0.0.0.0", - OldVersionUpperBound = "2.1.0.0", - NewVersion = "2.1.0.0")] + OldVersionUpperBound = "2.2.0.0", + NewVersion = "2.2.0.0")] [assembly: ProvideBindingRedirection( AssemblyName = "Microsoft.AspNetCore.Razor.Language", GenerateCodeBase = true, PublicKeyToken = "adb9793829ddae60", OldVersionLowerBound = "0.0.0.0", - OldVersionUpperBound = "2.1.0.0", - NewVersion = "2.1.0.0")] + OldVersionUpperBound = "2.2.0.0", + NewVersion = "2.2.0.0")] [assembly: ProvideBindingRedirection( AssemblyName = "Microsoft.CodeAnalysis.Razor", GenerateCodeBase = true, PublicKeyToken = "adb9793829ddae60", OldVersionLowerBound = "0.0.0.0", - OldVersionUpperBound = "2.1.0.0", - NewVersion = "2.1.0.0")] + OldVersionUpperBound = "2.2.0.0", + NewVersion = "2.2.0.0")] [assembly: ProvideBindingRedirection( AssemblyName = "Microsoft.CodeAnalysis.Razor.Workspaces", GenerateCodeBase = true, PublicKeyToken = "adb9793829ddae60", OldVersionLowerBound = "0.0.0.0", - OldVersionUpperBound = "2.1.0.0", - NewVersion = "2.1.0.0")] + OldVersionUpperBound = "2.2.0.0", + NewVersion = "2.2.0.0")] [assembly: ProvideBindingRedirection( AssemblyName = "Microsoft.CodeAnalysis.Remote.Razor", GenerateCodeBase = true, PublicKeyToken = "adb9793829ddae60", OldVersionLowerBound = "0.0.0.0", - OldVersionUpperBound = "2.1.0.0", - NewVersion = "2.1.0.0")] + OldVersionUpperBound = "2.2.0.0", + NewVersion = "2.2.0.0")] [assembly: ProvideBindingRedirection( AssemblyName = "Microsoft.VisualStudio.Editor.Razor", GenerateCodeBase = true, PublicKeyToken = "adb9793829ddae60", OldVersionLowerBound = "0.0.0.0", - OldVersionUpperBound = "2.1.0.0", - NewVersion = "2.1.0.0")] + OldVersionUpperBound = "2.2.0.0", + NewVersion = "2.2.0.0")] [assembly: ProvideBindingRedirection( AssemblyName = "Microsoft.VisualStudio.LanguageServices.Razor", GenerateCodeBase = true, PublicKeyToken = "adb9793829ddae60", OldVersionLowerBound = "0.0.0.0", - OldVersionUpperBound = "2.1.0.0", - NewVersion = "2.1.0.0")] + OldVersionUpperBound = "2.2.0.0", + NewVersion = "2.2.0.0")] From 3adb7c966135e9dfb89b941ff8c0daeadee4b324 Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Baaskaran Date: Wed, 18 Apr 2018 15:46:06 -0700 Subject: [PATCH 003/171] Handle Travis failure on ubuntu --- .../IntegrationTests/BuildIncrementalismTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildIncrementalismTest.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildIncrementalismTest.cs index b7682b894f..288c46cf6a 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildIncrementalismTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildIncrementalismTest.cs @@ -35,9 +35,9 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests Path.Combine(directoryPath, "SimpleMvc.csproj.FileListAbsolute.txt"), }; - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - // There is some quirkiness with MsBuild in osx high sierra where it regenerates this file + // There is some quirkiness with MsBuild in unix where it regenerates this file // even though it shouldn't. This is tracked here https://github.com/aspnet/Razor/issues/2219. filesToIgnore.Add(Path.Combine(directoryPath, "SimpleMvc.TagHelpers.input.cache")); } From 2be905def4341a95a08fa85c80bd9c4483253af3 Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Baaskaran Date: Fri, 20 Apr 2018 11:05:26 -0700 Subject: [PATCH 004/171] Make newline comparison work crossplat --- .../Legacy/ParserHelpers.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Razor.Language/Legacy/ParserHelpers.cs b/src/Microsoft.AspNetCore.Razor.Language/Legacy/ParserHelpers.cs index 71835178b2..8c8ffc8b10 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Legacy/ParserHelpers.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Legacy/ParserHelpers.cs @@ -26,8 +26,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public static bool IsNewLine(string value) { + // We want to handle both LF and CRLF regardless of the platform. + // We explicitly check for CRLF and IsNewLine() should return true for LF. return (value.Length == 1 && (IsNewLine(value[0]))) || - (string.Equals(value, Environment.NewLine, StringComparison.Ordinal)); + (string.Equals(value, "\r\n", StringComparison.Ordinal)); } public static bool IsIdentifier(string value) From a643531c05dd464362b02b7275721e351af7c970 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Mon, 23 Apr 2018 12:22:04 -0700 Subject: [PATCH 005/171] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 26 +++++++++++++------------- korebuild-lock.txt | 4 ++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index de2eac728e..7be5f06089 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -1,25 +1,25 @@ - + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 0.10.13 - 2.1.0-rc1-15774 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 + 2.2.0-preview1-17037 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 15.6.82 15.6.82 15.6.82 2.8.0-beta3 2.8.0-beta3 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-26419-02 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.1.0-preview3-26413-05 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 2.0.0 - 2.1.0-rc1-26419-02 + 2.1.0-preview3-26413-05 15.6.1 15.0.26606 15.6.161-preview @@ -42,8 +42,8 @@ 2.0.1 11.0.2 1.1.92 - 4.5.0-rc1-26419-03 - 4.5.0-rc1-26419-03 + 4.5.0-preview3-26413-02 + 4.5.0-preview3-26413-02 9.0.1 2.8.0-beta3 2.8.0-beta3 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 431aac942d..790ae84e6d 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17034 -commithash:60d8e6e904fbcf29050a34f187d6a0347a97e352 +version:2.2.0-preview1-17037 +commithash:557055a86cbdc359c97d4fb1c2d23a3dc7ae731e From a9f818bf82fab80ad6776904f22b0940431e8695 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Sat, 21 Apr 2018 10:55:48 -0700 Subject: [PATCH 006/171] Disable node-reuse to allow rebuilding Recent builds of msbuild have node reuse enabled by default. This locks up the task dlls after the test's completed preventing rebuilds untill you kill the process. --- .../IntegrationTests/MSBuildIntegrationTestBase.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildIntegrationTestBase.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildIntegrationTestBase.cs index c9765901cd..b0fd6e39c9 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildIntegrationTestBase.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildIntegrationTestBase.cs @@ -62,7 +62,12 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests MSBuildProcessKind msBuildProcessKind = MSBuildProcessKind.Dotnet) { var timeout = suppressTimeout ? (TimeSpan?)Timeout.InfiniteTimeSpan : null; - var buildArgumentList = new List(); + var buildArgumentList = new List + { + // Disable node-reuse. We don't want msbuild processes to stick around + // once the test is completed. + "/nr:false", + }; if (!suppressRestore) { From a6a5e4ea10b72b4502975af6314645da0690bb63 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 20 Apr 2018 18:56:17 -0700 Subject: [PATCH 007/171] Simplify building Microsoft.AspNetCore.Razor.Design --- .../Microsoft.AspNetCore.Razor.Design.csproj | 109 +++++------------- .../Microsoft.AspNetCore.Razor.Design.nuspec | 4 +- .../Microsoft.AspNetCore.Razor.Tasks.csproj | 2 + .../AppWithP2PReference.csproj | 2 +- .../testapps/ClassLibrary/ClassLibrary.csproj | 2 +- .../ClassLibrary2/ClassLibrary2.csproj | 2 +- test/testapps/SimpleMvc/SimpleMvc.csproj | 2 +- .../SimpleMvcFSharp/SimpleMvcFSharp.fsproj | 2 +- test/testapps/SimplePages/SimplePages.csproj | 2 +- 9 files changed, 41 insertions(+), 86 deletions(-) diff --git a/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.csproj b/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.csproj index 13ab584df8..f1a883c4bf 100644 --- a/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.csproj +++ b/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.csproj @@ -1,14 +1,12 @@ - - - - - + Razor is a markup syntax for adding server-side logic to web pages. This package contains MSBuild support for Razor. - netstandard2.0 + netcoreapp2.0;net46 false + false + false $(MSBuildProjectName).nuspec @@ -20,84 +18,27 @@ We then need to include the output of those projects in our output directory (where it will be used by tests) and in the nukpg. --> - - - - - - - - - - - - - _BuildDependencyProjects;$(GenerateNuspecDependsOn) - _BuildDependencyProjects;$(BuildDependsOn) + ..\Microsoft.AspNetCore.Razor.Tools\Microsoft.AspNetCore.Razor.Tools.csproj + Microsoft.AspNetCore.Razor.Tasks - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <_RazorTool Include="$(OutputPath)tools\**\*" /> - - - - - - + true + + unknown + id=$(PackageId); version=$(PackageVersion); @@ -112,14 +53,26 @@ copyright=$(Copyright); - TaskAssemblyNet46=@(TaskAssemblyNet46); - TaskSymbolNet46=@(TaskSymbolNet46); - TaskAssemblyNetStandard=@(TaskAssemblyNetStandard); - TaskSymbolNetStandard=@(TaskSymbolNetStandard); + TaskAssemblyNet46=$(OutputPath)tasks\net46\$(TaskProject).dll; + TaskSymbolNet46=$(OutputPath)tasks\net46\$(TaskProject).pdb; + TaskAssemblyNetStandard=$(OutputPath)tasks\netstandard2.0\$(TaskProject).dll; + TaskSymbolNetStandard=$(OutputPath)tasks\netstandard2.0\$(TaskProject).pdb; - ToolAssembly=$(OutputPath)tools\**\*; + ToolFiles=$(OutputPath)tools\**\*; + + + + + + + <_RazorTool Include="$(OutputPath)tools\**\*" /> + + + + + diff --git a/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.nuspec b/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.nuspec index 687b5cf1d8..47d16cd554 100644 --- a/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.nuspec +++ b/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.nuspec @@ -26,6 +26,6 @@ - + - \ No newline at end of file + diff --git a/src/Microsoft.AspNetCore.Razor.Tasks/Microsoft.AspNetCore.Razor.Tasks.csproj b/src/Microsoft.AspNetCore.Razor.Tasks/Microsoft.AspNetCore.Razor.Tasks.csproj index 79eefc909b..c39999ba4c 100644 --- a/src/Microsoft.AspNetCore.Razor.Tasks/Microsoft.AspNetCore.Razor.Tasks.csproj +++ b/src/Microsoft.AspNetCore.Razor.Tasks/Microsoft.AspNetCore.Razor.Tasks.csproj @@ -9,6 +9,8 @@ false false + + ..\Microsoft.AspNetCore.Razor.Design\bin\$(Configuration)\tasks\ diff --git a/test/testapps/AppWithP2PReference/AppWithP2PReference.csproj b/test/testapps/AppWithP2PReference/AppWithP2PReference.csproj index b0ca8cfd76..aa525839e7 100644 --- a/test/testapps/AppWithP2PReference/AppWithP2PReference.csproj +++ b/test/testapps/AppWithP2PReference/AppWithP2PReference.csproj @@ -1,7 +1,7 @@ - <_RazorMSBuildRoot>$(SolutionRoot)src\Microsoft.AspNetCore.Razor.Design\bin\$(Configuration)\netstandard2.0\ + <_RazorMSBuildRoot>$(SolutionRoot)src\Microsoft.AspNetCore.Razor.Design\bin\$(Configuration)\ diff --git a/test/testapps/ClassLibrary/ClassLibrary.csproj b/test/testapps/ClassLibrary/ClassLibrary.csproj index 93f2b997a9..fc5b647326 100644 --- a/test/testapps/ClassLibrary/ClassLibrary.csproj +++ b/test/testapps/ClassLibrary/ClassLibrary.csproj @@ -1,7 +1,7 @@ - <_RazorMSBuildRoot>$(SolutionRoot)src\Microsoft.AspNetCore.Razor.Design\bin\$(Configuration)\netstandard2.0\ + <_RazorMSBuildRoot>$(SolutionRoot)src\Microsoft.AspNetCore.Razor.Design\bin\$(Configuration)\ diff --git a/test/testapps/ClassLibrary2/ClassLibrary2.csproj b/test/testapps/ClassLibrary2/ClassLibrary2.csproj index 86cbd96b61..110d5df467 100644 --- a/test/testapps/ClassLibrary2/ClassLibrary2.csproj +++ b/test/testapps/ClassLibrary2/ClassLibrary2.csproj @@ -1,7 +1,7 @@ - <_RazorMSBuildRoot>$(SolutionRoot)src\Microsoft.AspNetCore.Razor.Design\bin\$(Configuration)\netstandard2.0\ + <_RazorMSBuildRoot>$(SolutionRoot)src\Microsoft.AspNetCore.Razor.Design\bin\$(Configuration)\ diff --git a/test/testapps/SimpleMvc/SimpleMvc.csproj b/test/testapps/SimpleMvc/SimpleMvc.csproj index 21ea1afc3c..289aa1f146 100644 --- a/test/testapps/SimpleMvc/SimpleMvc.csproj +++ b/test/testapps/SimpleMvc/SimpleMvc.csproj @@ -1,7 +1,7 @@ - <_RazorMSBuildRoot>$(SolutionRoot)src\Microsoft.AspNetCore.Razor.Design\bin\$(Configuration)\netstandard2.0\ + <_RazorMSBuildRoot>$(SolutionRoot)src\Microsoft.AspNetCore.Razor.Design\bin\$(Configuration)\ diff --git a/test/testapps/SimpleMvcFSharp/SimpleMvcFSharp.fsproj b/test/testapps/SimpleMvcFSharp/SimpleMvcFSharp.fsproj index 4e469df733..21531d14bc 100644 --- a/test/testapps/SimpleMvcFSharp/SimpleMvcFSharp.fsproj +++ b/test/testapps/SimpleMvcFSharp/SimpleMvcFSharp.fsproj @@ -1,7 +1,7 @@  - <_RazorMSBuildRoot>$(SolutionRoot)src\Microsoft.AspNetCore.Razor.Design\bin\$(Configuration)\netstandard2.0\ + <_RazorMSBuildRoot>$(SolutionRoot)src\Microsoft.AspNetCore.Razor.Design\bin\$(Configuration)\ diff --git a/test/testapps/SimplePages/SimplePages.csproj b/test/testapps/SimplePages/SimplePages.csproj index 2928d33d45..ba3fcfe487 100644 --- a/test/testapps/SimplePages/SimplePages.csproj +++ b/test/testapps/SimplePages/SimplePages.csproj @@ -1,7 +1,7 @@ - <_RazorMSBuildRoot>$(SolutionRoot)src\Microsoft.AspNetCore.Razor.Design\bin\$(Configuration)\netstandard2.0\ + <_RazorMSBuildRoot>$(SolutionRoot)src\Microsoft.AspNetCore.Razor.Design\bin\$(Configuration)\ From 1e2a1405e57bb4dee487e63f54ede61ac2b27d5e Mon Sep 17 00:00:00 2001 From: Pranav K Date: Sat, 21 Apr 2018 10:45:07 -0700 Subject: [PATCH 008/171] Update tests to target netcoreapp2.1 --- .../Microsoft.AspNetCore.Razor.Design.csproj | 2 +- .../Microsoft.AspNetCore.Razor.Design.CodeGeneration.targets | 2 -- .../IntegrationTests/InitializeTestProjectAttribute.cs | 2 +- .../IntegrationTests/MSBuildIntegrationTestBase.cs | 5 ----- .../Microsoft.AspNetCore.Razor.Design.Test.csproj | 2 +- test/testapps/AppWithP2PReference/AppWithP2PReference.csproj | 2 +- test/testapps/SimpleMvc/SimpleMvc.csproj | 2 +- test/testapps/SimpleMvcFSharp/SimpleMvcFSharp.fsproj | 2 +- test/testapps/SimplePages/SimplePages.csproj | 2 +- 9 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.csproj b/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.csproj index f1a883c4bf..e90541fe97 100644 --- a/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.csproj +++ b/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.csproj @@ -27,7 +27,7 @@ - + diff --git a/src/Microsoft.AspNetCore.Razor.Design/build/netstandard2.0/Microsoft.AspNetCore.Razor.Design.CodeGeneration.targets b/src/Microsoft.AspNetCore.Razor.Design/build/netstandard2.0/Microsoft.AspNetCore.Razor.Design.CodeGeneration.targets index c390749c45..1eb7d2baad 100644 --- a/src/Microsoft.AspNetCore.Razor.Design/build/netstandard2.0/Microsoft.AspNetCore.Razor.Design.CodeGeneration.targets +++ b/src/Microsoft.AspNetCore.Razor.Design/build/netstandard2.0/Microsoft.AspNetCore.Razor.Design.CodeGeneration.targets @@ -72,7 +72,6 @@ ToolAssembly="$(_RazorToolAssembly)" UseServer="$(UseRazorBuildServer)" ForceServer="$(_RazorForceBuildServer)" - SuppressCurrentUserOnlyPipeOptions="$(_RazorSuppressCurrentUserOnlyPipeOptions)" PipeName="$(_RazorBuildServerPipeName)" Version="$(RazorLangVersion)" Configuration="@(ResolvedRazorConfiguration)" @@ -124,7 +123,6 @@ ToolAssembly="$(_RazorToolAssembly)" UseServer="$(UseRazorBuildServer)" ForceServer="$(_RazorForceBuildServer)" - SuppressCurrentUserOnlyPipeOptions="$(_RazorSuppressCurrentUserOnlyPipeOptions)" PipeName="$(_RazorBuildServerPipeName)" Version="$(RazorLangVersion)" Configuration="@(ResolvedRazorConfiguration)" diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/InitializeTestProjectAttribute.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/InitializeTestProjectAttribute.cs index 4500480c62..5ab0a04c31 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/InitializeTestProjectAttribute.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/InitializeTestProjectAttribute.cs @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests } MSBuildIntegrationTestBase.Project = ProjectDirectory.Create(_originalProjectName, _testProjectName, _baseDirectory, _additionalProjects, _language); - MSBuildIntegrationTestBase.TargetFramework = _originalProjectName.StartsWith("ClassLibrary") ? "netstandard2.0" : "netcoreapp2.0"; + MSBuildIntegrationTestBase.TargetFramework = _originalProjectName.StartsWith("ClassLibrary") ? "netstandard2.0" : "netcoreapp2.1"; } public override void After(MethodInfo methodUnderTest) diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildIntegrationTestBase.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildIntegrationTestBase.cs index b0fd6e39c9..60bc033cf7 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildIntegrationTestBase.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildIntegrationTestBase.cs @@ -77,11 +77,6 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests if (!suppressBuildServer) { buildArgumentList.Add($"/p:_RazorBuildServerPipeName={buildServerPipeName ?? BuildServer.PipeName}"); - - // The build server will not be used in netcoreapp2.0 because PipeOptions.CurrentUserOnly is not available. - // But we still want to make sure to run the tests on the server. So suppress that check. - // This can be removed once https://github.com/aspnet/Razor/issues/2237 is done. - buildArgumentList.Add($"/p:_RazorSuppressCurrentUserOnlyPipeOptions=true"); } if (!string.IsNullOrEmpty(target)) diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/Microsoft.AspNetCore.Razor.Design.Test.csproj b/test/Microsoft.AspNetCore.Razor.Design.Test/Microsoft.AspNetCore.Razor.Design.Test.csproj index 9f94326610..22a3b54243 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/Microsoft.AspNetCore.Razor.Design.Test.csproj +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/Microsoft.AspNetCore.Razor.Design.Test.csproj @@ -7,7 +7,7 @@ This is also a partial workaround for https://github.com/Microsoft/msbuild/issues/2661 - this project has netcoreapp2.0 dependencies that need to be built first. --> - netcoreapp2.0 + netcoreapp2.1 true $(DefineConstants);PRESERVE_WORKING_DIRECTORY diff --git a/test/testapps/AppWithP2PReference/AppWithP2PReference.csproj b/test/testapps/AppWithP2PReference/AppWithP2PReference.csproj index aa525839e7..6507b127b4 100644 --- a/test/testapps/AppWithP2PReference/AppWithP2PReference.csproj +++ b/test/testapps/AppWithP2PReference/AppWithP2PReference.csproj @@ -12,7 +12,7 @@ - netcoreapp2.0 + netcoreapp2.1 diff --git a/test/testapps/SimpleMvc/SimpleMvc.csproj b/test/testapps/SimpleMvc/SimpleMvc.csproj index 289aa1f146..53b253536c 100644 --- a/test/testapps/SimpleMvc/SimpleMvc.csproj +++ b/test/testapps/SimpleMvc/SimpleMvc.csproj @@ -13,7 +13,7 @@ - netcoreapp2.0 + netcoreapp2.1 diff --git a/test/testapps/SimpleMvcFSharp/SimpleMvcFSharp.fsproj b/test/testapps/SimpleMvcFSharp/SimpleMvcFSharp.fsproj index 21531d14bc..f3eb256786 100644 --- a/test/testapps/SimpleMvcFSharp/SimpleMvcFSharp.fsproj +++ b/test/testapps/SimpleMvcFSharp/SimpleMvcFSharp.fsproj @@ -13,7 +13,7 @@ - netcoreapp2.0 + netcoreapp2.1 diff --git a/test/testapps/SimplePages/SimplePages.csproj b/test/testapps/SimplePages/SimplePages.csproj index ba3fcfe487..4a35836799 100644 --- a/test/testapps/SimplePages/SimplePages.csproj +++ b/test/testapps/SimplePages/SimplePages.csproj @@ -13,7 +13,7 @@ - netcoreapp2.0 + netcoreapp2.1 From e2edc280c59ae84e65296b06b2b092022cff0225 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 11 Apr 2018 20:52:45 -0700 Subject: [PATCH 009/171] Add documents, engine, tag helpers to snapshot The project snapshot now maintains a RazorProjectEngine as well as set of Tag Helpers that are known for that snapshot. Pivoted some more services to be snapshot-centric. Also added the ability to track .cshtml documents to the project system. For now most components just ignore document changes. --- .../RazorConfiguration.cs | 55 +- ...aultProjectSnapshotProjectEngineFactory.cs | 102 +++ ...jectSnapshotProjectEngineFactoryFactory.cs | 46 ++ .../BackgroundDocumentGenerator.cs | 278 +++++++ .../ProjectSnapshotProjectEngineFactory.cs | 51 ++ .../ProjectSystem/DefaultDocumentSnapshot.cs | 54 ++ .../ProjectSystem/DefaultProjectSnapshot.cs | 196 ++--- .../DefaultProjectSnapshotManager.cs | 289 ++++--- .../DefaultProjectSnapshotManagerFactory.cs | 3 +- .../DefaultProjectSnapshotWorker.cs | 62 -- .../DefaultProjectSnapshotWorkerFactory.cs | 32 - .../DocumentGeneratedOutputTracker.cs | 111 +++ .../ProjectSystem/DocumentSnapshot.cs | 19 + .../ProjectSystem/DocumentState.cs | 73 ++ .../ProjectSystem/EphemeralProjectSnapshot.cs | 81 ++ .../ProjectSystem/HostDocument.cs | 61 ++ .../ProjectSystem/ProjectChangeEventArgs.cs | 6 +- .../ProjectSystem/ProjectChangeKind.cs | 9 +- .../ProjectSystem/ProjectDifference.cs | 18 + .../ProjectSystem/ProjectEngineTracker.cs | 66 ++ .../ProjectExtensibilityAssembly.cs | 42 - .../ProjectSystem/ProjectSnapshot.cs | 14 +- .../ProjectSystem/ProjectSnapshotManager.cs | 4 + .../ProjectSnapshotManagerBase.cs | 6 +- .../ProjectSnapshotManagerExtensions.cs | 25 - .../ProjectSnapshotUpdateContext.cs | 45 -- .../ProjectSystem/ProjectSnapshotWorker.cs | 14 - .../ProjectSnapshotWorkerQueue.cs | 203 ----- .../ProjectSystem/ProjectState.cs | 239 ++++++ .../ProjectSystem/ProjectTagHelperTracker.cs | 79 ++ .../RazorProjectEngineFactoryService.cs | 23 - .../GeneratedDocument.cs | 10 - .../RazorLanguageService.cs | 41 - .../RazorServiceBase.cs | 29 +- .../Rules/RazorGenerateWithTargetPath.xaml | 30 + .../DefaultImportDocumentManager.cs | 13 +- .../DefaultImportDocumentManagerFactory.cs | 7 +- .../DefaultProjectEngineFactoryService.cs | 196 ----- ...faultProjectEngineFactoryServiceFactory.cs | 48 -- .../DefaultTagHelperResolver.cs | 18 +- .../DefaultTagHelperResolverFactory.cs | 2 +- .../DefaultVisualStudioDocumentTracker.cs | 157 +++- .../DefaultVisualStudioRazorParser.cs | 17 +- .../DefaultVisualStudioRazorParserFactory.cs | 12 +- ...ltVisualStudioRazorParserFactoryFactory.cs | 4 +- .../VisualStudioDocumentTracker.cs | 3 + .../DefaultRazorEngineDirectiveResolver.cs | 41 - .../DefaultRazorEngineDocumentGenerator.cs | 39 - .../IRazorEngineDirectiveResolver.cs | 18 - .../IRazorEngineDocumentGenerator.cs | 16 - ...VisualStudio.LanguageServices.Razor.csproj | 18 +- .../OOPTagHelperResolver.cs | 14 +- .../OOPTagHelperResolverFactory.cs | 4 +- .../ProjectSystem/DefaultRazorProjectHost.cs | 149 +++- .../ProjectSystem/FallbackRazorProjectHost.cs | 22 +- .../ProjectSystem/RazorProjectHostBase.cs | 77 +- .../Rules/RazorConfiguration.xaml | 29 - .../ProjectSystem/Rules/RazorExtension.xaml | 37 - .../ProjectSystem/Rules/RazorGeneral.xaml | 36 - .../Rules/RazorGenerateWithTargetPath.cs | 212 +++++ .../RazorEngineDocument.cs | 12 - ...tionUpdatesProjectSnapshotChangeTrigger.cs | 13 +- .../ProjectBuildChangeTrigger.cs | 13 +- ...ageServices.cs => TestLanguageServices.cs} | 4 +- .../TestWorkspaceServices.cs | 18 +- .../DefaultProjectSnapshotTest.cs | 183 +++-- .../ProjectSystem/DocumentStateTest.cs | 93 +++ .../ProjectSystem/ProjectStateTest.cs | 359 +++++++++ ...rkspaceProjectSnapshotChangeTriggerTest.cs | 18 +- .../Shared/TestProjectSnapshotManager.cs | 41 + ...TestProjectSnapshotProjectEngineFactory.cs | 28 + .../Shared/TestTagHelperResolver.cs | 32 + ...ultImportDocumentManagerIntegrationTest.cs | 71 +- .../DefaultImportDocumentManagerTest.cs | 117 +-- ...rojectSnapshotProjectEngineFactoryTest.cs} | 106 +-- .../DefaultVisualStudioDocumentTrackerTest.cs | 375 ++++++--- ...tVisualStudioRazorParserIntegrationTest.cs | 28 +- .../DefaultVisualStudioRazorParserTest.cs | 50 +- ...soft.VisualStudio.Editor.Razor.Test.csproj | 6 + .../BackgroundDocumentGeneratorTest.cs} | 109 +-- ...lStudio.LanguageServices.Razor.Test.csproj | 6 + .../OOPTagHelperResolverTest.cs | 21 +- .../DefaultProjectSnapshotManagerTest.cs | 735 ++++++++---------- .../DefaultRazorProjectHostTest.cs | 448 ++++++----- .../FallbackRazorProjectHostTest.cs | 112 ++- .../ProjectSystem/ItemCollection.cs | 68 ++ .../ProjectSystem/PropertyCollection.cs | 51 ++ .../TestProjectChangeDescription.cs | 89 ++- .../ProjectSystem/TestProjectRuleSnapshot.cs | 14 +- .../TestProjectSystemServices.cs | 2 +- ...UpdatesProjectSnapshotChangeTriggerTest.cs | 110 ++- ...dio.Mac.LanguageServices.Razor.Test.csproj | 6 + .../ProjectBuildChangeTriggerTest.cs | 107 ++- ...crosoft.VisualStudio.RazorExtension.csproj | 6 +- ...del.cs => DirectiveDescriptorViewModel.cs} | 4 +- .../RazorInfo/DocumentInfoViewModel.cs | 21 - ...wModel.cs => DocumentSnapshotViewModel.cs} | 17 +- .../RazorInfo/DocumentViewModel.cs | 18 - .../RazorInfo/ProjectInfoViewModel.cs | 24 +- .../RazorInfo/ProjectSnapshotViewModel.cs | 31 +- .../RazorInfo/RazorInfoToolWindow.cs | 31 +- .../RazorInfo/RazorInfoToolWindowControl.xaml | 209 +++-- .../RazorInfo/RazorInfoViewModel.cs | 272 ++----- 103 files changed, 4694 insertions(+), 2989 deletions(-) create mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultProjectSnapshotProjectEngineFactory.cs create mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultProjectSnapshotProjectEngineFactoryFactory.cs create mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/DocumentGenerator/BackgroundDocumentGenerator.cs create mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSnapshotProjectEngineFactory.cs create mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultDocumentSnapshot.cs delete mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorker.cs delete mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorkerFactory.cs create mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentGeneratedOutputTracker.cs create mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs create mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs create mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/EphemeralProjectSnapshot.cs create mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/HostDocument.cs create mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectDifference.cs create mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectEngineTracker.cs delete mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityAssembly.cs delete mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerExtensions.cs delete mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotUpdateContext.cs delete mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotWorker.cs delete mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotWorkerQueue.cs create mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs create mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectTagHelperTracker.cs delete mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorProjectEngineFactoryService.cs delete mode 100644 src/Microsoft.CodeAnalysis.Remote.Razor/GeneratedDocument.cs create mode 100644 src/Microsoft.NET.Sdk.Razor/build/netstandard2.0/Rules/RazorGenerateWithTargetPath.xaml delete mode 100644 src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryService.cs delete mode 100644 src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryServiceFactory.cs delete mode 100644 src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultRazorEngineDirectiveResolver.cs delete mode 100644 src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultRazorEngineDocumentGenerator.cs delete mode 100644 src/Microsoft.VisualStudio.LanguageServices.Razor/IRazorEngineDirectiveResolver.cs delete mode 100644 src/Microsoft.VisualStudio.LanguageServices.Razor/IRazorEngineDocumentGenerator.cs delete mode 100644 src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorConfiguration.xaml delete mode 100644 src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorExtension.xaml delete mode 100644 src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorGeneral.xaml create mode 100644 src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorGenerateWithTargetPath.cs delete mode 100644 src/Microsoft.VisualStudio.LanguageServices.Razor/RazorEngineDocument.cs rename test/Microsoft.CodeAnalysis.Razor.Workspaces.Test.Common/{TestRazorLanguageServices.cs => TestLanguageServices.cs} (88%) create mode 100644 test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DocumentStateTest.cs create mode 100644 test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateTest.cs create mode 100644 test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Shared/TestProjectSnapshotManager.cs create mode 100644 test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Shared/TestProjectSnapshotProjectEngineFactory.cs create mode 100644 test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Shared/TestTagHelperResolver.cs rename test/Microsoft.VisualStudio.Editor.Razor.Test/{DefaultProjectEngineFactoryServiceTest.cs => DefaultProjectSnapshotProjectEngineFactoryTest.cs} (64%) rename test/Microsoft.VisualStudio.LanguageServices.Razor.Test/{ProjectSystem/ProjectSnapshotWorkerQueueTest.cs => DocumentGenerator/BackgroundDocumentGeneratorTest.cs} (55%) create mode 100644 test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/ItemCollection.cs create mode 100644 test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/PropertyCollection.cs rename tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/{DirectiveViewModel.cs => DirectiveDescriptorViewModel.cs} (87%) delete mode 100644 tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DocumentInfoViewModel.cs rename tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/{AssemblyViewModel.cs => DocumentSnapshotViewModel.cs} (51%) delete mode 100644 tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DocumentViewModel.cs diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorConfiguration.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorConfiguration.cs index e8ef287c0c..1094b0a432 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/RazorConfiguration.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorConfiguration.cs @@ -4,10 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Razor.Language { - public abstract class RazorConfiguration + public abstract class RazorConfiguration : IEquatable { public static readonly RazorConfiguration Default = new DefaultRazorConfiguration( RazorLanguageVersion.Latest, @@ -43,6 +44,58 @@ namespace Microsoft.AspNetCore.Razor.Language public abstract RazorLanguageVersion LanguageVersion { get; } + public override bool Equals(object obj) + { + return base.Equals(obj as RazorConfiguration); + } + + public virtual bool Equals(RazorConfiguration other) + { + if (object.ReferenceEquals(other, null)) + { + return false; + } + + if (LanguageVersion != other.LanguageVersion) + { + return false; + } + + if (ConfigurationName != other.ConfigurationName) + { + return false; + } + + if (Extensions.Count != other.Extensions.Count) + { + return false; + } + + for (var i = 0; i < Extensions.Count; i++) + { + if (Extensions[i].ExtensionName != other.Extensions[i].ExtensionName) + { + return false; + } + } + + return true; + } + + public override int GetHashCode() + { + var hash = new HashCodeCombiner(); + hash.Add(LanguageVersion); + hash.Add(ConfigurationName); + + for (var i = 0; i < Extensions.Count; i++) + { + hash.Add(Extensions[i].ExtensionName); + } + + return hash; + } + private class DefaultRazorConfiguration : RazorConfiguration { public DefaultRazorConfiguration( diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultProjectSnapshotProjectEngineFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultProjectSnapshotProjectEngineFactory.cs new file mode 100644 index 0000000000..cd9ac1ee51 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultProjectSnapshotProjectEngineFactory.cs @@ -0,0 +1,102 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; + +namespace Microsoft.CodeAnalysis.Razor +{ + internal class DefaultProjectSnapshotProjectEngineFactory : ProjectSnapshotProjectEngineFactory + { + private readonly static RazorConfiguration DefaultConfiguration = FallbackRazorConfiguration.MVC_2_1; + + private readonly IFallbackProjectEngineFactory _fallback; + private readonly Lazy[] _factories; + + public DefaultProjectSnapshotProjectEngineFactory( + IFallbackProjectEngineFactory fallback, + Lazy[] factories) + { + if (fallback == null) + { + throw new ArgumentNullException(nameof(fallback)); + } + + if (factories == null) + { + throw new ArgumentNullException(nameof(factories)); + } + + _fallback = fallback; + _factories = factories; + } + + public override RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action configure) + { + if (project == null) + { + throw new ArgumentNullException(nameof(project)); + } + + if (fileSystem == null) + { + throw new ArgumentNullException(nameof(fileSystem)); + } + + // When we're running in the editor, the editor provides a configure delegate that will include + // the editor settings and tag helpers. + // + // This service is only used in process in Visual Studio, and any other callers should provide these + // things also. + configure = configure ?? ((b) => { }); + + // The default configuration currently matches the newest MVC configuration. + // + // We typically want this because the language adds features over time - we don't want to a bunch of errors + // to show up when a document is first opened, and then go away when the configuration loads, we'd prefer the opposite. + var configuration = project.Configuration ?? DefaultConfiguration; + + // If there's no factory to handle the configuration then fall back to a very basic configuration. + // + // This will stop a crash from happening in this case (misconfigured project), but will still make + // it obvious to the user that something is wrong. + var factory = SelectFactory(configuration) ?? _fallback; + return factory.Create(configuration, fileSystem, configure); + } + + public override IProjectEngineFactory FindFactory(ProjectSnapshot project) + { + if (project == null) + { + throw new ArgumentNullException(nameof(project)); + } + + return SelectFactory(project.Configuration ?? DefaultConfiguration, requireSerializable: false); + } + + public override IProjectEngineFactory FindSerializableFactory(ProjectSnapshot project) + { + if (project == null) + { + throw new ArgumentNullException(nameof(project)); + } + + return SelectFactory(project.Configuration ?? DefaultConfiguration, requireSerializable: true); + } + + private IProjectEngineFactory SelectFactory(RazorConfiguration configuration, bool requireSerializable = false) + { + for (var i = 0; i < _factories.Length; i++) + { + var factory = _factories[i]; + if (string.Equals(configuration.ConfigurationName, factory.Metadata.ConfigurationName)) + { + return requireSerializable && !factory.Metadata.SupportsSerialization ? null : factory.Value; + } + } + + return null; + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultProjectSnapshotProjectEngineFactoryFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultProjectSnapshotProjectEngineFactoryFactory.cs new file mode 100644 index 0000000000..663bd8f2bc --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultProjectSnapshotProjectEngineFactoryFactory.cs @@ -0,0 +1,46 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.CodeAnalysis.Razor.Workspaces +{ + [ExportWorkspaceServiceFactory(typeof(ProjectSnapshotProjectEngineFactory))] + internal class DefaultProjectSnapshotProjectEngineFactoryFactory : IWorkspaceServiceFactory + { + private readonly IFallbackProjectEngineFactory _fallback; + private readonly Lazy[] _factories; + + [ImportingConstructor] + public DefaultProjectSnapshotProjectEngineFactoryFactory( + IFallbackProjectEngineFactory fallback, + [ImportMany] Lazy[] factories) + { + if (fallback == null) + { + throw new ArgumentNullException(nameof(fallback)); + } + + if (factories == null) + { + throw new ArgumentNullException(nameof(factories)); + } + + _fallback = fallback; + _factories = factories; + } + + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + { + if (workspaceServices == null) + { + throw new ArgumentNullException(nameof(workspaceServices)); + } + + return new DefaultProjectSnapshotProjectEngineFactory(_fallback, _factories); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DocumentGenerator/BackgroundDocumentGenerator.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DocumentGenerator/BackgroundDocumentGenerator.cs new file mode 100644 index 0000000000..6fd591d1ca --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DocumentGenerator/BackgroundDocumentGenerator.cs @@ -0,0 +1,278 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.Extensions.Internal; + +namespace Microsoft.CodeAnalysis.Razor +{ + // Deliberately not exported for now, until this feature is working end to end. + internal class BackgroundDocumentGenerator : ProjectSnapshotChangeTrigger + { + private ForegroundDispatcher _foregroundDispatcher; + private ProjectSnapshotManagerBase _projectManager; + + private readonly Dictionary _files; + private Timer _timer; + + [ImportingConstructor] + public BackgroundDocumentGenerator(ForegroundDispatcher foregroundDispatcher) + { + if (foregroundDispatcher == null) + { + throw new ArgumentNullException(nameof(foregroundDispatcher)); + } + + _foregroundDispatcher = foregroundDispatcher; + + _files = new Dictionary(); + } + + public bool HasPendingNotifications + { + get + { + lock (_files) + { + return _files.Count > 0; + } + } + } + + // Used in unit tests to control the timer delay. + public TimeSpan Delay { get; set; } = TimeSpan.FromSeconds(2); + + public bool IsScheduledOrRunning => _timer != null; + + // Used in unit tests to ensure we can control when background work starts. + public ManualResetEventSlim BlockBackgroundWorkStart { get; set; } + + // Used in unit tests to ensure we can know when background work finishes. + public ManualResetEventSlim NotifyBackgroundWorkStarting { get; set; } + + // Used in unit tests to ensure we can control when background work completes. + public ManualResetEventSlim BlockBackgroundWorkCompleting { get; set; } + + // Used in unit tests to ensure we can know when background work finishes. + public ManualResetEventSlim NotifyBackgroundWorkCompleted { get; set; } + + private void OnStartingBackgroundWork() + { + if (BlockBackgroundWorkStart != null) + { + BlockBackgroundWorkStart.Wait(); + BlockBackgroundWorkStart.Reset(); + } + + if (NotifyBackgroundWorkStarting != null) + { + NotifyBackgroundWorkStarting.Set(); + } + } + + private void OnCompletingBackgroundWork() + { + if (BlockBackgroundWorkCompleting != null) + { + BlockBackgroundWorkCompleting.Wait(); + BlockBackgroundWorkCompleting.Reset(); + } + } + + private void OnCompletedBackgroundWork() + { + if (NotifyBackgroundWorkCompleted != null) + { + NotifyBackgroundWorkCompleted.Set(); + } + } + + public override void Initialize(ProjectSnapshotManagerBase projectManager) + { + if (projectManager == null) + { + throw new ArgumentNullException(nameof(projectManager)); + } + + _projectManager = projectManager; + _projectManager.Changed += ProjectManager_Changed; + } + + protected virtual Task ProcessDocument(DocumentSnapshot document) + { + return document.GetGeneratedOutputAsync(); + } + + public void Enqueue(ProjectSnapshot project, DocumentSnapshot document) + { + if (project == null) + { + throw new ArgumentNullException(nameof(project)); + } + + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + _foregroundDispatcher.AssertForegroundThread(); + + lock (_files) + { + // We only want to store the last 'seen' version of any given document. That way when we pick one to process + // it's always the best version to use. + _files.Add(new Key(project.FilePath, document.FilePath), document); + + StartWorker(); + } + } + + protected virtual void StartWorker() + { + // Access to the timer is protected by the lock in Enqueue and in Timer_Tick + if (_timer == null) + { + // Timer will fire after a fixed delay, but only once. + _timer = new Timer(Timer_Tick, null, Delay, Timeout.InfiniteTimeSpan); + } + } + + private async void Timer_Tick(object state) // Yeah I know. + { + try + { + _foregroundDispatcher.AssertBackgroundThread(); + + // Timer is stopped. + _timer.Change(Timeout.Infinite, Timeout.Infinite); + + OnStartingBackgroundWork(); + + DocumentSnapshot[] work; + lock (_files) + { + work = _files.Values.ToArray(); + _files.Clear(); + } + + for (var i = 0; i < work.Length; i++) + { + var document = work[i]; + try + { + await ProcessDocument(document); + } + catch (Exception ex) + { + ReportError(document, ex); + } + } + + OnCompletingBackgroundWork(); + + lock (_files) + { + // Resetting the timer allows another batch of work to start. + _timer.Dispose(); + _timer = null; + + // If more work came in while we were running start the worker again. + if (_files.Count > 0) + { + StartWorker(); + } + } + + OnCompletedBackgroundWork(); + } + catch (Exception ex) + { + // This is something totally unexpected, let's just send it over to the workspace. + await Task.Factory.StartNew( + () => _projectManager.ReportError(ex), + CancellationToken.None, + TaskCreationOptions.None, + _foregroundDispatcher.ForegroundScheduler); + } + } + + private void ReportError(DocumentSnapshot document, Exception ex) + { + GC.KeepAlive(Task.Factory.StartNew( + () => _projectManager.ReportError(ex), + CancellationToken.None, + TaskCreationOptions.None, + _foregroundDispatcher.ForegroundScheduler)); + } + + private void ProjectManager_Changed(object sender, ProjectChangeEventArgs e) + { + switch (e.Kind) + { + case ProjectChangeKind.ProjectAdded: + case ProjectChangeKind.ProjectChanged: + case ProjectChangeKind.DocumentsChanged: + { + var project = _projectManager.GetLoadedProject(e.ProjectFilePath); + foreach (var documentFilePath in project.DocumentFilePaths) + { + Enqueue(project, project.GetDocument(documentFilePath)); + } + + break; + } + + case ProjectChangeKind.DocumentContentChanged: + { + throw null; + } + + case ProjectChangeKind.ProjectRemoved: + // ignore + break; + + default: + throw new InvalidOperationException($"Unknown ProjectChangeKind {e.Kind}"); + } + } + + private struct Key : IEquatable + { + public Key(string projectFilePath, string documentFilePath) + { + ProjectFilePath = projectFilePath; + DocumentFilePath = documentFilePath; + } + + public string ProjectFilePath { get; } + + public string DocumentFilePath { get; } + + public bool Equals(Key other) + { + return + FilePathComparer.Instance.Equals(ProjectFilePath, other.ProjectFilePath) && + FilePathComparer.Instance.Equals(DocumentFilePath, other.DocumentFilePath); + } + + public override bool Equals(object obj) + { + return obj is Key key ? Equals(key) : false; + } + + public override int GetHashCode() + { + var hash = new HashCodeCombiner(); + hash.Add(ProjectFilePath, FilePathComparer.Instance); + hash.Add(DocumentFilePath, FilePathComparer.Instance); + return hash; + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSnapshotProjectEngineFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSnapshotProjectEngineFactory.cs new file mode 100644 index 0000000000..cf2341878d --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSnapshotProjectEngineFactory.cs @@ -0,0 +1,51 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; + +namespace Microsoft.CodeAnalysis.Razor +{ + internal abstract class ProjectSnapshotProjectEngineFactory : IWorkspaceService + { + public abstract IProjectEngineFactory FindFactory(ProjectSnapshot project); + + public abstract IProjectEngineFactory FindSerializableFactory(ProjectSnapshot project); + + public RazorProjectEngine Create(ProjectSnapshot project) + { + return Create(project, RazorProjectFileSystem.Create(Path.GetDirectoryName(project.FilePath)), null); + } + + public RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem) + { + if (project == null) + { + throw new ArgumentNullException(nameof(project)); + } + + if (fileSystem == null) + { + throw new ArgumentNullException(nameof(fileSystem)); + } + + return Create(project, fileSystem, null); + } + + public RazorProjectEngine Create(ProjectSnapshot project, Action configure) + { + if (project == null) + { + throw new ArgumentNullException(nameof(project)); + } + + return Create(project, RazorProjectFileSystem.Create(Path.GetDirectoryName(project.FilePath)), configure); + } + + public abstract RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action configure); + + } +} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultDocumentSnapshot.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultDocumentSnapshot.cs new file mode 100644 index 0000000000..eb1c8be640 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultDocumentSnapshot.cs @@ -0,0 +1,54 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Language; + +namespace Microsoft.CodeAnalysis.Razor.ProjectSystem +{ + internal class DefaultDocumentSnapshot : DocumentSnapshot + { + public DefaultDocumentSnapshot(ProjectSnapshot project, DocumentState state) + { + if (project == null) + { + throw new ArgumentNullException(nameof(project)); + } + + if (state == null) + { + throw new ArgumentNullException(nameof(state)); + } + + Project = project; + State = state; + } + + public ProjectSnapshot Project { get; } + + public DocumentState State { get; } + + public override string FilePath => State.HostDocument.FilePath; + + public override string TargetPath => State.HostDocument.TargetPath; + + public override Task GetGeneratedOutputAsync() + { + // IMPORTANT: Don't put more code here. We want this to return a cached task. + return State.GeneratedOutput.GetGeneratedOutputInitializationTask(Project, this); + } + + public override bool TryGetGeneratedOutput(out RazorCodeDocument results) + { + if (State.GeneratedOutput.IsResultAvailable) + { + results = State.GeneratedOutput.GetGeneratedOutputInitializationTask(Project, this).Result; + return true; + } + + results = null; + return false; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshot.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshot.cs index b8646429c6..2b644e182c 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshot.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshot.cs @@ -3,182 +3,82 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { - // All of the public state of this is immutable - we create a new instance and notify subscribers - // when it changes. - // - // However we use the private state to track things like dirty/clean. - // - // See the private constructors... When we update the snapshot we either are processing a Workspace - // change (Project) or updating the computed state (ProjectSnapshotUpdateContext). We don't do both - // at once. internal class DefaultProjectSnapshot : ProjectSnapshot { - public DefaultProjectSnapshot(HostProject hostProject, Project workspaceProject, VersionStamp? version = null) + private readonly object _lock; + + private Dictionary _documents; + + public DefaultProjectSnapshot(ProjectState state) { - if (hostProject == null) + if (state == null) { - throw new ArgumentNullException(nameof(hostProject)); + throw new ArgumentNullException(nameof(state)); } - HostProject = hostProject; - WorkspaceProject = workspaceProject; // Might be null - - FilePath = hostProject.FilePath; - Version = version ?? VersionStamp.Default; + State = state; + + _lock = new object(); + _documents = new Dictionary(FilePathComparer.Instance); } - private DefaultProjectSnapshot(HostProject hostProject, DefaultProjectSnapshot other) - { - if (hostProject == null) - { - throw new ArgumentNullException(nameof(hostProject)); - } - - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } - - ComputedVersion = other.ComputedVersion; - - FilePath = other.FilePath; - TagHelpers = other.TagHelpers; - HostProject = hostProject; - WorkspaceProject = other.WorkspaceProject; - - Version = other.Version.GetNewerVersion(); - } - - private DefaultProjectSnapshot(Project workspaceProject, DefaultProjectSnapshot other) - { - if (workspaceProject == null) - { - throw new ArgumentNullException(nameof(workspaceProject)); - } - - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } - - ComputedVersion = other.ComputedVersion; - - FilePath = other.FilePath; - TagHelpers = other.TagHelpers; - HostProject = other.HostProject; - WorkspaceProject = workspaceProject; - - Version = other.Version.GetNewerVersion(); - } - - private DefaultProjectSnapshot(ProjectSnapshotUpdateContext update, DefaultProjectSnapshot other) - { - if (update == null) - { - throw new ArgumentNullException(nameof(update)); - } - - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } - - ComputedVersion = update.Version; - - FilePath = other.FilePath; - HostProject = other.HostProject; - TagHelpers = update.TagHelpers ?? Array.Empty(); - WorkspaceProject = other.WorkspaceProject; - - // This doesn't represent a new version of the underlying data. Keep the same version. - Version = other.Version; - } + public ProjectState State { get; } public override RazorConfiguration Configuration => HostProject.Configuration; - public override string FilePath { get; } + public override IEnumerable DocumentFilePaths => State.Documents.Keys; - public override HostProject HostProject { get; } + public override string FilePath => State.HostProject.FilePath; + + public HostProject HostProject => State.HostProject; public override bool IsInitialized => WorkspaceProject != null; - public override VersionStamp Version { get; } + public override VersionStamp Version => State.Version; - public override Project WorkspaceProject { get; } + public override Project WorkspaceProject => State.WorkspaceProject; - public override IReadOnlyList TagHelpers { get; } = Array.Empty(); - - // This is the version that the computed state is based on. - public VersionStamp? ComputedVersion { get; set; } - - // We know the project is dirty if we don't have a computed result, or it was computed for a different version. - // Since the PSM updates the snapshots synchronously, the snapshot can never be older than the computed state. - public bool IsDirty => ComputedVersion == null || ComputedVersion.Value != Version; - - public ProjectSnapshotUpdateContext CreateUpdateContext() + public override DocumentSnapshot GetDocument(string filePath) { - return new ProjectSnapshotUpdateContext(FilePath, HostProject, WorkspaceProject, Version); + lock (_lock) + { + if (!_documents.TryGetValue(filePath, out var result) && + State.Documents.TryGetValue(filePath, out var state)) + { + result = new DefaultDocumentSnapshot(this, state); + _documents.Add(filePath, result); + } + + return result; + } } - public DefaultProjectSnapshot WithHostProject(HostProject hostProject) + public override RazorProjectEngine GetProjectEngine() { - if (hostProject == null) + return State.ProjectEngine.GetProjectEngine(this); + } + + public override Task> GetTagHelpersAsync() + { + // IMPORTANT: Don't put more code here. We want this to return a cached task. + return State.TagHelpers.GetTagHelperInitializationTask(this); + } + + public override bool TryGetTagHelpers(out IReadOnlyList results) + { + if (State.TagHelpers.IsResultAvailable) { - throw new ArgumentNullException(nameof(hostProject)); + results = State.TagHelpers.GetTagHelperInitializationTask(this).Result; + return true; } - return new DefaultProjectSnapshot(hostProject, this); - } - - public DefaultProjectSnapshot RemoveWorkspaceProject() - { - // We want to get rid of all of the computed state since it's not really valid. - return new DefaultProjectSnapshot(HostProject, null, Version.GetNewerVersion()); - } - - public DefaultProjectSnapshot WithWorkspaceProject(Project workspaceProject) - { - if (workspaceProject == null) - { - throw new ArgumentNullException(nameof(workspaceProject)); - } - - return new DefaultProjectSnapshot(workspaceProject, this); - } - - public DefaultProjectSnapshot WithComputedUpdate(ProjectSnapshotUpdateContext update) - { - if (update == null) - { - throw new ArgumentNullException(nameof(update)); - } - - return new DefaultProjectSnapshot(update, this); - } - - public bool HasConfigurationChanged(DefaultProjectSnapshot original) - { - if (original == null) - { - throw new ArgumentNullException(nameof(original)); - } - - return !object.Equals(Configuration, original.Configuration); - } - - public bool HaveTagHelpersChanged(ProjectSnapshot original) - { - if (original == null) - { - throw new ArgumentNullException(nameof(original)); - } - - return !Enumerable.SequenceEqual(TagHelpers, original.TagHelpers); + results = null; + return false; } } } \ No newline at end of file diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs index c246fa14d7..8c3a5484a0 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem @@ -31,15 +30,14 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem private readonly ErrorReporter _errorReporter; private readonly ForegroundDispatcher _foregroundDispatcher; private readonly ProjectSnapshotChangeTrigger[] _triggers; - private readonly ProjectSnapshotWorkerQueue _workerQueue; - private readonly ProjectSnapshotWorker _worker; - private readonly Dictionary _projects; + // Each entry holds a ProjectState and an optional ProjectSnapshot. ProjectSnapshots are + // created lazily. + private readonly Dictionary _projects; public DefaultProjectSnapshotManager( ForegroundDispatcher foregroundDispatcher, ErrorReporter errorReporter, - ProjectSnapshotWorker worker, IEnumerable triggers, Workspace workspace) { @@ -53,11 +51,6 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem throw new ArgumentNullException(nameof(errorReporter)); } - if (worker == null) - { - throw new ArgumentNullException(nameof(worker)); - } - if (triggers == null) { throw new ArgumentNullException(nameof(triggers)); @@ -70,13 +63,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem _foregroundDispatcher = foregroundDispatcher; _errorReporter = errorReporter; - _worker = worker; _triggers = triggers.ToArray(); Workspace = workspace; - _projects = new Dictionary(FilePathComparer.Instance); - - _workerQueue = new ProjectSnapshotWorkerQueue(_foregroundDispatcher, this, worker); + _projects = new Dictionary(FilePathComparer.Instance); for (var i = 0; i < _triggers.Length; i++) { @@ -89,43 +79,109 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem get { _foregroundDispatcher.AssertForegroundThread(); - return _projects.Values.ToArray(); + + + var i = 0; + var projects = new ProjectSnapshot[_projects.Count]; + foreach (var entry in _projects) + { + if (entry.Value.Snapshot == null) + { + entry.Value.Snapshot = new DefaultProjectSnapshot(entry.Value.State); + } + + projects[i++] = entry.Value.Snapshot; + } + + return projects; } } public override Workspace Workspace { get; } - public override void ProjectUpdated(ProjectSnapshotUpdateContext update) + public override ProjectSnapshot GetLoadedProject(string filePath) { - if (update == null) + if (filePath == null) { - throw new ArgumentNullException(nameof(update)); + throw new ArgumentNullException(nameof(filePath)); } _foregroundDispatcher.AssertForegroundThread(); - if (_projects.TryGetValue(update.WorkspaceProject.FilePath, out var original)) + if (_projects.TryGetValue(filePath, out var entry)) { - if (!original.IsInitialized) + if (entry.Snapshot == null) { - // If the project has been uninitialized, just ignore the update. - return; + entry.Snapshot = new DefaultProjectSnapshot(entry.State); } - // This is an update to the project's computed values, so everything should be overwritten - var snapshot = original.WithComputedUpdate(update); - _projects[update.WorkspaceProject.FilePath] = snapshot; + return entry.Snapshot; + } - if (snapshot.IsDirty) + return null; + } + + public override ProjectSnapshot GetOrCreateProject(string filePath) + { + if (filePath == null) + { + throw new ArgumentNullException(nameof(filePath)); + } + + _foregroundDispatcher.AssertForegroundThread(); + + return GetLoadedProject(filePath) ?? new EphemeralProjectSnapshot(Workspace.Services, filePath); + } + + public override void DocumentAdded(HostProject hostProject, HostDocument document) + { + if (hostProject == null) + { + throw new ArgumentNullException(nameof(hostProject)); + } + + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + _foregroundDispatcher.AssertForegroundThread(); + + if (_projects.TryGetValue(hostProject.FilePath, out var entry)) + { + var state = entry.State.AddHostDocument(document); + + // Document updates can no-op. + if (!object.ReferenceEquals(state, entry.State)) { - // It's possible that the snapshot can still be dirty if we got a project update while computing state in - // the background. We need to trigger the background work to asynchronously compute the effect of the updates. - NotifyBackgroundWorker(snapshot.CreateUpdateContext()); + _projects[hostProject.FilePath] = new Entry(state); + NotifyListeners(new ProjectChangeEventArgs(hostProject.FilePath, ProjectChangeKind.DocumentsChanged)); } - - if (!object.Equals(snapshot.ComputedVersion, original.ComputedVersion)) + } + } + + public override void DocumentRemoved(HostProject hostProject, HostDocument document) + { + if (hostProject == null) + { + throw new ArgumentNullException(nameof(hostProject)); + } + + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + _foregroundDispatcher.AssertForegroundThread(); + if (_projects.TryGetValue(hostProject.FilePath, out var entry)) + { + var state = entry.State.RemoveHostDocument(document); + + // Document updates can no-op. + if (!object.ReferenceEquals(state, entry.State)) { - NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.TagHelpersChanged)); + _projects[hostProject.FilePath] = new Entry(state); + NotifyListeners(new ProjectChangeEventArgs(hostProject.FilePath, ProjectChangeKind.DocumentsChanged)); } } } @@ -149,17 +205,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // So if possible find a WorkspaceProject. var workspaceProject = GetWorkspaceProject(hostProject.FilePath); - var snapshot = new DefaultProjectSnapshot(hostProject, workspaceProject); - _projects[hostProject.FilePath] = snapshot; - - if (snapshot.IsInitialized && snapshot.IsDirty) - { - // Start computing background state if the project is fully initialized. - NotifyBackgroundWorker(snapshot.CreateUpdateContext()); - } + var state = new ProjectState(Workspace.Services, hostProject, workspaceProject); + _projects[hostProject.FilePath] = new Entry(state); // We need to notify listeners about every project add. - NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.Added)); + NotifyListeners(new ProjectChangeEventArgs(hostProject.FilePath, ProjectChangeKind.ProjectAdded)); } public override void HostProjectChanged(HostProject hostProject) @@ -171,22 +221,17 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem _foregroundDispatcher.AssertForegroundThread(); - if (_projects.TryGetValue(hostProject.FilePath, out var original)) + if (_projects.TryGetValue(hostProject.FilePath, out var entry)) { - // Doing an update to the project should keep computed values, but mark the project as dirty if the - // underlying project is newer. - var snapshot = original.WithHostProject(hostProject); - _projects[hostProject.FilePath] = snapshot; + var state = entry.State.WithHostProject(hostProject); - if (snapshot.IsInitialized && snapshot.IsDirty) + // HostProject updates can no-op. + if (!object.ReferenceEquals(state, entry.State)) { - // Start computing background state if the project is fully initialized. - NotifyBackgroundWorker(snapshot.CreateUpdateContext()); - } + _projects[hostProject.FilePath] = new Entry(state); - // Notify listeners right away because if the HostProject changes then it's likely that the Razor - // configuration changed. - NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.Changed)); + NotifyListeners(new ProjectChangeEventArgs(hostProject.FilePath, ProjectChangeKind.ProjectChanged)); + } } } @@ -204,37 +249,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem _projects.Remove(hostProject.FilePath); // We need to notify listeners about every project removal. - NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.Removed)); - } - } - - public override void HostProjectBuildComplete(HostProject hostProject) - { - if (hostProject == null) - { - throw new ArgumentNullException(nameof(hostProject)); - } - - _foregroundDispatcher.AssertForegroundThread(); - - if (_projects.TryGetValue(hostProject.FilePath, out var original)) - { - var workspaceProject = GetWorkspaceProject(hostProject.FilePath); - if (workspaceProject == null) - { - // Host project was built prior to a workspace project being associated. We have nothing to do without - // a workspace project so we short circuit. - return; - } - - // Doing an update to the project should keep computed values, but mark the project as dirty if the - // underlying project is newer. - var snapshot = original.WithWorkspaceProject(workspaceProject); - - _projects[hostProject.FilePath] = snapshot; - - // Notify the background worker so it can trigger tag helper discovery. - NotifyBackgroundWorker(snapshot.CreateUpdateContext()); + NotifyListeners(new ProjectChangeEventArgs(hostProject.FilePath, ProjectChangeKind.ProjectRemoved)); } } @@ -254,25 +269,16 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // The WorkspaceProject initialization never triggers a "Project Add" from out point of view, we // only care if the new WorkspaceProject matches an existing HostProject. - if (_projects.TryGetValue(workspaceProject.FilePath, out var original)) + if (_projects.TryGetValue(workspaceProject.FilePath, out var entry)) { // If this is a multi-targeting project then we are only interested in a single workspace project. If we already // found one in the past just ignore this one. - if (original.WorkspaceProject == null) + if (entry.State.WorkspaceProject == null) { - var snapshot = original.WithWorkspaceProject(workspaceProject); - _projects[workspaceProject.FilePath] = snapshot; + var state = entry.State.WithWorkspaceProject(workspaceProject); + _projects[workspaceProject.FilePath] = new Entry(state); - if (snapshot.IsInitialized && snapshot.IsDirty) - { - // We don't need to notify listeners yet because we don't have any **new** computed state. - // - // However we do need to trigger the background work to asynchronously compute the effect of the updates. - NotifyBackgroundWorker(snapshot.CreateUpdateContext()); - } - - // Notify listeners right away since WorkspaceProject was just added, the project is now initialized. - NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.Changed)); + NotifyListeners(new ProjectChangeEventArgs(workspaceProject.FilePath, ProjectChangeKind.ProjectChanged)); } } } @@ -293,25 +299,18 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // We also need to check the projectId here. If this is a multi-targeting project then we are only interested // in a single workspace project. Just use the one that showed up first. - if (_projects.TryGetValue(workspaceProject.FilePath, out var original) && - (original.WorkspaceProject == null || - original.WorkspaceProject.Id == workspaceProject.Id)) + if (_projects.TryGetValue(workspaceProject.FilePath, out var entry) && + (entry.State.WorkspaceProject == null || entry.State.WorkspaceProject.Id == workspaceProject.Id)) { - // Doing an update to the project should keep computed values, but mark the project as dirty if the - // underlying project is newer. - var snapshot = original.WithWorkspaceProject(workspaceProject); - _projects[workspaceProject.FilePath] = snapshot; - - if (snapshot.IsInitialized && snapshot.IsDirty) + var state = entry.State.WithWorkspaceProject(workspaceProject); + + // WorkspaceProject updates can no-op. This can be the case if a build is triggered, but we've + // already seen the update. + if (!object.ReferenceEquals(state, entry.State)) { - // We don't need to notify listeners yet because we don't have any **new** computed state. However we do - // need to trigger the background work to asynchronously compute the effect of the updates. - NotifyBackgroundWorker(snapshot.CreateUpdateContext()); - } + _projects[workspaceProject.FilePath] = new Entry(state); - if (snapshot.HaveTagHelpersChanged(original)) - { - NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.TagHelpersChanged)); + NotifyListeners(new ProjectChangeEventArgs(workspaceProject.FilePath, ProjectChangeKind.ProjectChanged)); } } } @@ -330,16 +329,16 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem return; } - if (_projects.TryGetValue(workspaceProject.FilePath, out var original)) + if (_projects.TryGetValue(workspaceProject.FilePath, out var entry)) { // We also need to check the projectId here. If this is a multi-targeting project then we are only interested // in a single workspace project. Make sure the WorkspaceProject we're using is the one that's being removed. - if (original.WorkspaceProject?.Id != workspaceProject.Id) + if (entry.State.WorkspaceProject?.Id != workspaceProject.Id) { return; } - DefaultProjectSnapshot snapshot; + ProjectState state; // So if the WorkspaceProject got removed, we should double check to make sure that there aren't others // hanging around. This could happen if a project is multi-targeting and one of the TFMs is removed. @@ -347,30 +346,19 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem if (otherWorkspaceProject != null && otherWorkspaceProject.Id != workspaceProject.Id) { // OK there's another WorkspaceProject, use that. - // - // Doing an update to the project should keep computed values, but mark the project as dirty if the - // underlying project is newer. - snapshot = original.WithWorkspaceProject(otherWorkspaceProject); - _projects[workspaceProject.FilePath] = snapshot; + state = entry.State.WithWorkspaceProject(otherWorkspaceProject); + _projects[otherWorkspaceProject.FilePath] = new Entry(state); - if (snapshot.IsInitialized && snapshot.IsDirty) - { - // We don't need to notify listeners yet because we don't have any **new** computed state. However we do - // need to trigger the background work to asynchronously compute the effect of the updates. - NotifyBackgroundWorker(snapshot.CreateUpdateContext()); - } - - // Notify listeners of a change because it's a different WorkspaceProject. - NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.Changed)); - - return; + NotifyListeners(new ProjectChangeEventArgs(otherWorkspaceProject.FilePath, ProjectChangeKind.ProjectChanged)); } + else + { + state = entry.State.WithWorkspaceProject(null); + _projects[workspaceProject.FilePath] = new Entry(state); - snapshot = original.RemoveWorkspaceProject(); - _projects[workspaceProject.FilePath] = snapshot; - - // Notify listeners of a change because we've removed computed state. - NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.Changed)); + // Notify listeners of a change because we've removed computed state. + NotifyListeners(new ProjectChangeEventArgs(workspaceProject.FilePath, ProjectChangeKind.ProjectChanged)); + } } } @@ -401,8 +389,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem throw new ArgumentNullException(nameof(exception)); } - var project = hostProject?.FilePath == null ? null : this.GetProjectWithFilePath(hostProject.FilePath); - _errorReporter.ReportError(exception, project); + var snapshot = hostProject?.FilePath == null ? null : GetLoadedProject(hostProject.FilePath); + _errorReporter.ReportError(exception, snapshot); } public override void ReportError(Exception exception, Project workspaceProject) @@ -411,7 +399,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { throw new ArgumentNullException(nameof(exception)); } - + _errorReporter.ReportError(exception, workspaceProject); } @@ -440,14 +428,6 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem return null; } - // virtual so it can be overridden in tests - protected virtual void NotifyBackgroundWorker(ProjectSnapshotUpdateContext context) - { - _foregroundDispatcher.AssertForegroundThread(); - - _workerQueue.Enqueue(context); - } - // virtual so it can be overridden in tests protected virtual void NotifyListeners(ProjectChangeEventArgs e) { @@ -459,5 +439,16 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem handler(this, e); } } + + private class Entry + { + public ProjectSnapshot Snapshot; + public readonly ProjectState State; + + public Entry(ProjectState state) + { + State = state; + } + } } } \ No newline at end of file diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManagerFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManagerFactory.cs index d82d82cd7c..b092174aa6 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManagerFactory.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManagerFactory.cs @@ -45,8 +45,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem return new DefaultProjectSnapshotManager( _foregroundDispatcher, languageServices.WorkspaceServices.GetRequiredService(), - languageServices.GetRequiredService(), - _triggers, + _triggers, languageServices.WorkspaceServices.Workspace); } } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorker.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorker.cs deleted file mode 100644 index 1b7480f940..0000000000 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorker.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.CodeAnalysis.Razor.ProjectSystem -{ - internal class DefaultProjectSnapshotWorker : ProjectSnapshotWorker - { - private readonly ForegroundDispatcher _foregroundDispatcher; - private readonly TagHelperResolver _tagHelperResolver; - - public DefaultProjectSnapshotWorker(ForegroundDispatcher foregroundDispatcher, TagHelperResolver tagHelperResolver) - { - if (foregroundDispatcher == null) - { - throw new ArgumentNullException(nameof(foregroundDispatcher)); - } - - if (tagHelperResolver == null) - { - throw new ArgumentNullException(nameof(tagHelperResolver)); - } - - _foregroundDispatcher = foregroundDispatcher; - _tagHelperResolver = tagHelperResolver; - } - - public override Task ProcessUpdateAsync(ProjectSnapshotUpdateContext update, CancellationToken cancellationToken = default(CancellationToken)) - { - if (update == null) - { - throw new ArgumentNullException(nameof(update)); - } - - // Don't block the main thread - if (_foregroundDispatcher.IsForegroundThread) - { - return Task.Factory.StartNew(ProjectUpdatesCoreAsync, update, cancellationToken, TaskCreationOptions.None, _foregroundDispatcher.BackgroundScheduler); - } - - return ProjectUpdatesCoreAsync(update); - } - - protected virtual void OnProcessingUpdate() - { - } - - private async Task ProjectUpdatesCoreAsync(object state) - { - var update = (ProjectSnapshotUpdateContext)state; - - OnProcessingUpdate(); - - var snapshot = new DefaultProjectSnapshot(update.HostProject, update.WorkspaceProject, update.Version); - var result = await _tagHelperResolver.GetTagHelpersAsync(snapshot, CancellationToken.None); - update.TagHelpers = result.Descriptors; - } - } -} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorkerFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorkerFactory.cs deleted file mode 100644 index bd36bf361d..0000000000 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorkerFactory.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Composition; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; - -namespace Microsoft.CodeAnalysis.Razor.ProjectSystem -{ - [Shared] - [ExportLanguageServiceFactory(typeof(ProjectSnapshotWorker), RazorLanguage.Name)] - internal class DefaultProjectSnapshotWorkerFactory : ILanguageServiceFactory - { - private readonly ForegroundDispatcher _foregroundDispatcher; - - [ImportingConstructor] - public DefaultProjectSnapshotWorkerFactory(ForegroundDispatcher foregroundDispatcher) - { - if (foregroundDispatcher == null) - { - throw new System.ArgumentNullException(nameof(foregroundDispatcher)); - } - - _foregroundDispatcher = foregroundDispatcher; - } - - public ILanguageService CreateLanguageService(HostLanguageServices languageServices) - { - return new DefaultProjectSnapshotWorker(_foregroundDispatcher, languageServices.GetRequiredService()); - } - } -} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentGeneratedOutputTracker.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentGeneratedOutputTracker.cs new file mode 100644 index 0000000000..e3114148b1 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentGeneratedOutputTracker.cs @@ -0,0 +1,111 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Language; + +namespace Microsoft.CodeAnalysis.Razor.ProjectSystem +{ + internal class DocumentGeneratedOutputTracker + { + // Don't keep anything if the configuration has changed. It's OK if the + // workspace project has changes, we'll consider whether the tag helpers are + // difference before reusing a previous result. + private const ProjectDifference Mask = ProjectDifference.ConfigurationChanged; + + private readonly object _lock; + + private DocumentGeneratedOutputTracker _older; + private Task _task; + + private IReadOnlyList _tagHelpers; + + public DocumentGeneratedOutputTracker(DocumentGeneratedOutputTracker older) + { + _older = older; + + _lock = new object(); + } + + public bool IsResultAvailable => _task?.IsCompleted == true; + + public Task GetGeneratedOutputInitializationTask(ProjectSnapshot project, DocumentSnapshot document) + { + if (project == null) + { + throw new ArgumentNullException(nameof(project)); + } + + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + if (_task == null) + { + lock (_lock) + { + if (_task == null) + { + _task = GetGeneratedOutputInitializationTaskCore(project, document); + } + } + } + + return _task; + } + + public DocumentGeneratedOutputTracker ForkFor(DocumentState state, ProjectDifference difference) + { + if (state == null) + { + throw new ArgumentNullException(nameof(state)); + } + + if ((difference & Mask) != 0) + { + return null; + } + + return new DocumentGeneratedOutputTracker(this); + } + + private async Task GetGeneratedOutputInitializationTaskCore(ProjectSnapshot project, DocumentSnapshot document) + { + var tagHelpers = await project.GetTagHelpersAsync().ConfigureAwait(false); + if (_older != null && _older.IsResultAvailable) + { + var difference = new HashSet(TagHelperDescriptorComparer.Default); + difference.UnionWith(_older._tagHelpers); + difference.SymmetricExceptWith(tagHelpers); + + if (difference.Count == 0) + { + // We can use the cached result. + var result = _older._task.Result; + + // Drop reference so it can be GC'ed + _older = null; + + // Cache the tag helpers so the next version can use them + _tagHelpers = tagHelpers; + + return result; + } + } + + // Drop reference so it can be GC'ed + _older = null; + + + // Cache the tag helpers so the next version can use them + _tagHelpers = tagHelpers; + + var projectEngine = project.GetProjectEngine(); + var projectItem = projectEngine.FileSystem.GetItem(document.FilePath); + return projectItem == null ? null : projectEngine.ProcessDesignTime(projectItem); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs new file mode 100644 index 0000000000..736d09c65c --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Language; + +namespace Microsoft.CodeAnalysis.Razor.ProjectSystem +{ + internal abstract class DocumentSnapshot + { + public abstract string FilePath { get; } + + public abstract string TargetPath { get; } + + public abstract Task GetGeneratedOutputAsync(); + + public abstract bool TryGetGeneratedOutput(out RazorCodeDocument results); + } +} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs new file mode 100644 index 0000000000..f53adf18b0 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs @@ -0,0 +1,73 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis.Razor.ProjectSystem +{ + internal class DocumentState + { + private readonly object _lock; + + private DocumentGeneratedOutputTracker _generatedOutput; + + public DocumentState(HostWorkspaceServices services, HostDocument hostDocument) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (hostDocument == null) + { + throw new ArgumentNullException(nameof(hostDocument)); + } + + Services = services; + HostDocument = hostDocument; + Version = VersionStamp.Create(); + + _lock = new object(); + } + + public DocumentState(DocumentState previous, ProjectDifference difference) + { + if (previous == null) + { + throw new ArgumentNullException(nameof(previous)); + } + + Services = previous.Services; + HostDocument = previous.HostDocument; + Version = previous.Version.GetNewerVersion(); + + _generatedOutput = previous._generatedOutput?.ForkFor(this, difference); + } + + public HostDocument HostDocument { get; } + + public HostWorkspaceServices Services { get; } + + public VersionStamp Version { get; } + + public DocumentGeneratedOutputTracker GeneratedOutput + { + get + { + if (_generatedOutput == null) + { + lock (_lock) + { + if (_generatedOutput == null) + { + _generatedOutput = new DocumentGeneratedOutputTracker(null); + } + } + } + + return _generatedOutput; + } + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/EphemeralProjectSnapshot.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/EphemeralProjectSnapshot.cs new file mode 100644 index 0000000000..c05c4f6e32 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/EphemeralProjectSnapshot.cs @@ -0,0 +1,81 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis.Razor.ProjectSystem +{ + internal class EphemeralProjectSnapshot : ProjectSnapshot + { + private static readonly Task> EmptyTagHelpers = Task.FromResult>(Array.Empty()); + + private readonly HostWorkspaceServices _services; + private readonly Lazy _projectEngine; + + public EphemeralProjectSnapshot(HostWorkspaceServices services, string filePath) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (filePath == null) + { + throw new ArgumentNullException(nameof(filePath)); + } + + _services = services; + FilePath = filePath; + + _projectEngine = new Lazy(CreateProjectEngine); + } + + public override RazorConfiguration Configuration => FallbackRazorConfiguration.MVC_2_1; + + public override IEnumerable DocumentFilePaths => Array.Empty(); + + public override string FilePath { get; } + + public override bool IsInitialized => false; + + public override VersionStamp Version { get; } = VersionStamp.Default; + + public override Project WorkspaceProject => null; + + public override DocumentSnapshot GetDocument(string filePath) + { + if (filePath == null) + { + throw new ArgumentNullException(nameof(filePath)); + } + + return null; + } + + public override RazorProjectEngine GetProjectEngine() + { + return _projectEngine.Value; + } + + public override Task> GetTagHelpersAsync() + { + return EmptyTagHelpers; + } + + public override bool TryGetTagHelpers(out IReadOnlyList results) + { + results = EmptyTagHelpers.Result; + return true; + } + + private RazorProjectEngine CreateProjectEngine() + { + var factory = _services.GetRequiredService(); + return factory.Create(this); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/HostDocument.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/HostDocument.cs new file mode 100644 index 0000000000..146943cab6 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/HostDocument.cs @@ -0,0 +1,61 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Extensions.Internal; + +namespace Microsoft.CodeAnalysis.Razor.ProjectSystem +{ + internal class HostDocument : IEquatable + { + public HostDocument(string filePath, string targetPath) + { + if (filePath == null) + { + throw new ArgumentNullException(nameof(filePath)); + } + + if (targetPath == null) + { + throw new ArgumentNullException(nameof(targetPath)); + } + + FilePath = filePath; + TargetPath = targetPath; + } + + public string FilePath { get; } + + public string TargetPath { get; } + + public override bool Equals(object obj) + { + return base.Equals(obj as DocumentSnapshot); + } + + public bool Equals(DocumentSnapshot other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + return + FilePathComparer.Instance.Equals(FilePath, other.FilePath) && + FilePathComparer.Instance.Equals(TargetPath, other.TargetPath); + } + + public bool Equals(HostDocument other) + { + throw new NotImplementedException(); + } + + public override int GetHashCode() + { + var hash = new HashCodeCombiner(); + hash.Add(FilePath, FilePathComparer.Instance); + hash.Add(TargetPath, FilePathComparer.Instance); + return hash; + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectChangeEventArgs.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectChangeEventArgs.cs index eca6b56774..0c0faa3db8 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectChangeEventArgs.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectChangeEventArgs.cs @@ -7,13 +7,13 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { internal class ProjectChangeEventArgs : EventArgs { - public ProjectChangeEventArgs(ProjectSnapshot project, ProjectChangeKind kind) + public ProjectChangeEventArgs(string projectFilePath, ProjectChangeKind kind) { - Project = project; + ProjectFilePath = projectFilePath; Kind = kind; } - public ProjectSnapshot Project { get; } + public string ProjectFilePath { get; } public ProjectChangeKind Kind { get; } } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectChangeKind.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectChangeKind.cs index c2ff3feacf..53b6b36529 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectChangeKind.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectChangeKind.cs @@ -5,9 +5,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { internal enum ProjectChangeKind { - Added, - Removed, - Changed, - TagHelpersChanged, + ProjectAdded, + ProjectRemoved, + ProjectChanged, + DocumentsChanged, + DocumentContentChanged, } } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectDifference.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectDifference.cs new file mode 100644 index 0000000000..d450393f3c --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectDifference.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.CodeAnalysis.Razor.ProjectSystem +{ + [Flags] + internal enum ProjectDifference + { + None = 0, + ConfigurationChanged = 1, + WorkspaceProjectAdded = 2, + WorkspaceProjectRemoved = 4, + WorkspaceProjectChanged = 8, + DocumentsChanged = 16, + } +} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectEngineTracker.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectEngineTracker.cs new file mode 100644 index 0000000000..6e54daed0b --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectEngineTracker.cs @@ -0,0 +1,66 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis.Razor.ProjectSystem +{ + internal class ProjectEngineTracker + { + private const ProjectDifference Mask = ProjectDifference.ConfigurationChanged; + + private readonly object _lock = new object(); + + private readonly HostWorkspaceServices _services; + private RazorProjectEngine _projectEngine; + + public ProjectEngineTracker(ProjectState state) + { + if (state == null) + { + throw new ArgumentNullException(nameof(state)); + } + + _services = state.Services; + } + + public ProjectEngineTracker ForkFor(ProjectState state, ProjectDifference difference) + { + if (state == null) + { + throw new ArgumentNullException(nameof(state)); + } + + if ((difference & Mask) != 0) + { + return null; + } + + return this; + } + + public RazorProjectEngine GetProjectEngine(ProjectSnapshot snapshot) + { + if (snapshot == null) + { + throw new ArgumentNullException(nameof(snapshot)); + } + + if (_projectEngine == null) + { + lock (_lock) + { + if (_projectEngine == null) + { + var factory = _services.GetRequiredService(); + _projectEngine = factory.Create(snapshot); + } + } + } + + return _projectEngine; + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityAssembly.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityAssembly.cs deleted file mode 100644 index 43839f3664..0000000000 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityAssembly.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.CodeAnalysis.Razor.ProjectSystem -{ - internal sealed class ProjectExtensibilityAssembly : IEquatable - { - public ProjectExtensibilityAssembly(AssemblyIdentity identity) - { - if (identity == null) - { - throw new ArgumentNullException(nameof(identity)); - } - - Identity = identity; - } - - public AssemblyIdentity Identity { get; } - - public bool Equals(ProjectExtensibilityAssembly other) - { - if (other == null) - { - return false; - } - - return Identity.Equals(other.Identity); - } - - public override int GetHashCode() - { - return Identity.GetHashCode(); - } - - public override bool Equals(object obj) - { - return base.Equals(obj as ProjectExtensibilityAssembly); - } - } -} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs index 538b3cb9fa..1b6ce7abff 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs @@ -1,8 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Collections.Generic; +using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem @@ -11,16 +11,22 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { public abstract RazorConfiguration Configuration { get; } + public abstract IEnumerable DocumentFilePaths { get; } + public abstract string FilePath { get; } public abstract bool IsInitialized { get; } - public abstract IReadOnlyList TagHelpers { get; } - public abstract VersionStamp Version { get; } public abstract Project WorkspaceProject { get; } - public abstract HostProject HostProject { get; } + public abstract RazorProjectEngine GetProjectEngine(); + + public abstract DocumentSnapshot GetDocument(string filePath); + + public abstract Task> GetTagHelpersAsync(); + + public abstract bool TryGetTagHelpers(out IReadOnlyList results); } } \ No newline at end of file diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManager.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManager.cs index b9e39e00b1..e491674399 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManager.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManager.cs @@ -12,5 +12,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem public abstract event EventHandler Changed; public abstract IReadOnlyList Projects { get; } + + public abstract ProjectSnapshot GetLoadedProject(string filePath); + + public abstract ProjectSnapshot GetOrCreateProject(string filePath); } } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerBase.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerBase.cs index 026b27956f..87306d1b5a 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerBase.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerBase.cs @@ -9,7 +9,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { public abstract Workspace Workspace { get; } - public abstract void ProjectUpdated(ProjectSnapshotUpdateContext update); + public abstract void DocumentAdded(HostProject hostProject, HostDocument hostDocument); + + public abstract void DocumentRemoved(HostProject hostProject, HostDocument hostDocument); public abstract void HostProjectAdded(HostProject hostProject); @@ -17,8 +19,6 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem public abstract void HostProjectRemoved(HostProject hostProject); - public abstract void HostProjectBuildComplete(HostProject hostProject); - public abstract void WorkspaceProjectAdded(Project workspaceProject); public abstract void WorkspaceProjectChanged(Project workspaceProject); diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerExtensions.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerExtensions.cs deleted file mode 100644 index d6299717ca..0000000000 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.CodeAnalysis.Razor.ProjectSystem -{ - internal static class ProjectSnapshotManagerExtensions - { - public static ProjectSnapshot GetProjectWithFilePath(this ProjectSnapshotManager snapshotManager, string filePath) - { - var projects = snapshotManager.Projects; - for (var i = 0; i< projects.Count; i++) - { - var project = projects[i]; - if (FilePathComparer.Instance.Equals(filePath, project.FilePath)) - { - return project; - } - } - - return null; - } - } -} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotUpdateContext.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotUpdateContext.cs deleted file mode 100644 index cddb3b08c1..0000000000 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotUpdateContext.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using Microsoft.AspNetCore.Razor.Language; - -namespace Microsoft.CodeAnalysis.Razor.ProjectSystem -{ - internal class ProjectSnapshotUpdateContext - { - public ProjectSnapshotUpdateContext(string filePath, HostProject hostProject, Project workspaceProject, VersionStamp version) - { - if (filePath == null) - { - throw new ArgumentNullException(nameof(filePath)); - } - - if (hostProject == null) - { - throw new ArgumentNullException(nameof(hostProject)); - } - - if (workspaceProject == null) - { - throw new ArgumentNullException(nameof(workspaceProject)); - } - - FilePath = filePath; - HostProject = hostProject; - WorkspaceProject = workspaceProject; - Version = version; - } - - public string FilePath { get; } - - public HostProject HostProject { get; } - - public Project WorkspaceProject { get; } - - public IReadOnlyList TagHelpers { get; set; } - - public VersionStamp Version { get; } - } -} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotWorker.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotWorker.cs deleted file mode 100644 index 5c6288ee22..0000000000 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotWorker.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host; - -namespace Microsoft.CodeAnalysis.Razor.ProjectSystem -{ - internal abstract class ProjectSnapshotWorker : ILanguageService - { - public abstract Task ProcessUpdateAsync(ProjectSnapshotUpdateContext update, CancellationToken cancellationToken = default(CancellationToken)); - } -} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotWorkerQueue.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotWorkerQueue.cs deleted file mode 100644 index 69c0062f05..0000000000 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotWorkerQueue.cs +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.CodeAnalysis.Razor.ProjectSystem -{ - internal class ProjectSnapshotWorkerQueue - { - private readonly ForegroundDispatcher _foregroundDispatcher; - private readonly DefaultProjectSnapshotManager _projectManager; - private readonly ProjectSnapshotWorker _projectWorker; - - private readonly Dictionary _projects; - private Timer _timer; - - public ProjectSnapshotWorkerQueue(ForegroundDispatcher foregroundDispatcher, DefaultProjectSnapshotManager projectManager, ProjectSnapshotWorker projectWorker) - { - if (foregroundDispatcher == null) - { - throw new ArgumentNullException(nameof(foregroundDispatcher)); - } - - if (projectManager == null) - { - throw new ArgumentNullException(nameof(projectManager)); - } - - if (projectWorker == null) - { - throw new ArgumentNullException(nameof(projectWorker)); - } - - _foregroundDispatcher = foregroundDispatcher; - _projectManager = projectManager; - _projectWorker = projectWorker; - - _projects = new Dictionary(FilePathComparer.Instance); - } - - public bool HasPendingNotifications - { - get - { - lock (_projects) - { - return _projects.Count > 0; - } - } - } - - // Used in unit tests to control the timer delay. - public TimeSpan Delay { get; set; } = TimeSpan.FromSeconds(2); - - public bool IsScheduledOrRunning => _timer != null; - - // Used in unit tests to ensure we can control when background work starts. - public ManualResetEventSlim BlockBackgroundWorkStart { get; set; } - - // Used in unit tests to ensure we can know when background work finishes. - public ManualResetEventSlim NotifyBackgroundWorkFinish { get; set; } - - // Used in unit tests to ensure we can be notified when all completes. - public ManualResetEventSlim NotifyForegroundWorkFinish { get; set; } - - private void OnStartingBackgroundWork() - { - if (BlockBackgroundWorkStart != null) - { - BlockBackgroundWorkStart.Wait(); - BlockBackgroundWorkStart.Reset(); - } - } - - private void OnFinishingBackgroundWork() - { - if (NotifyBackgroundWorkFinish != null) - { - NotifyBackgroundWorkFinish.Set(); - } - } - - private void OnFinishingForegroundWork() - { - if (NotifyForegroundWorkFinish != null) - { - NotifyForegroundWorkFinish.Set(); - } - } - - public void Enqueue(ProjectSnapshotUpdateContext context) - { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - _foregroundDispatcher.AssertForegroundThread(); - - lock (_projects) - { - // We only want to store the last 'seen' version of any given project. That way when we pick one to process - // it's always the best version to use. - _projects[context.FilePath] = context; - - StartWorker(); - } - } - - protected virtual void StartWorker() - { - // Access to the timer is protected by the lock in Enqueue and in Timer_Tick - if (_timer == null) - { - // Timer will fire after a fixed delay, but only once. - _timer = new Timer(Timer_Tick, null, Delay, Timeout.InfiniteTimeSpan); - } - } - - private async void Timer_Tick(object state) // Yeah I know. - { - try - { - _foregroundDispatcher.AssertBackgroundThread(); - - // Timer is stopped. - _timer.Change(Timeout.Infinite, Timeout.Infinite); - - OnStartingBackgroundWork(); - - ProjectSnapshotUpdateContext[] work; - lock (_projects) - { - work = _projects.Values.ToArray(); - _projects.Clear(); - } - - var updates = new(ProjectSnapshotUpdateContext context, Exception exception)[work.Length]; - for (var i = 0; i < work.Length; i++) - { - try - { - updates[i] = (work[i], null); - await _projectWorker.ProcessUpdateAsync(updates[i].context); - } - catch (Exception projectException) - { - updates[i] = (updates[i].context, projectException); - } - } - - OnFinishingBackgroundWork(); - - // We need to get back to the UI thread to update the project system. - await Task.Factory.StartNew(PersistUpdates, updates, CancellationToken.None, TaskCreationOptions.None, _foregroundDispatcher.ForegroundScheduler); - - lock (_projects) - { - // Resetting the timer allows another batch of work to start. - _timer.Dispose(); - _timer = null; - - // If more work came in while we were running start the worker again. - if (_projects.Count > 0) - { - StartWorker(); - } - } - - OnFinishingForegroundWork(); - } - catch (Exception ex) - { - // This is something totally unexpected, let's just send it over to the workspace. - await Task.Factory.StartNew(() => _projectManager.ReportError(ex), CancellationToken.None, TaskCreationOptions.None, _foregroundDispatcher.ForegroundScheduler); - } - } - - private void PersistUpdates(object state) - { - _foregroundDispatcher.AssertForegroundThread(); - - var updates = ((ProjectSnapshotUpdateContext context, Exception exception)[])state; - - for (var i = 0; i < updates.Length; i++) - { - var update = updates[i]; - if (update.exception == null) - { - _projectManager.ProjectUpdated(update.context); - } - else - { - _projectManager.ReportError(update.exception, update.context?.WorkspaceProject); - } - } - } - } -} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs new file mode 100644 index 0000000000..05ecf2be49 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs @@ -0,0 +1,239 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis.Razor.ProjectSystem +{ + // Internal tracker for DefaultProjectSnapshot + internal class ProjectState + { + private static readonly IReadOnlyDictionary EmptyDocuments = new Dictionary(); + + private readonly object _lock; + + private ProjectEngineTracker _projectEngine; + private ProjectTagHelperTracker _tagHelpers; + + public ProjectState( + HostWorkspaceServices services, + HostProject hostProject, + Project workspaceProject) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (hostProject == null) + { + throw new ArgumentNullException(nameof(hostProject)); + } + + Services = services; + HostProject = hostProject; + WorkspaceProject = workspaceProject; + Documents = EmptyDocuments; + Version = VersionStamp.Create(); + + _lock = new object(); + } + + public ProjectState( + ProjectState older, + ProjectDifference difference, + HostProject hostProject, + Project workspaceProject, + IReadOnlyDictionary documents) + { + if (older == null) + { + throw new ArgumentNullException(nameof(older)); + } + + if (hostProject == null) + { + throw new ArgumentNullException(nameof(hostProject)); + } + + if (documents == null) + { + throw new ArgumentNullException(nameof(documents)); + } + + Services = older.Services; + Version = older.Version.GetNewerVersion(); + + HostProject = hostProject; + WorkspaceProject = workspaceProject; + Documents = documents; + + _lock = new object(); + + _projectEngine = older._projectEngine?.ForkFor(this, difference); + _tagHelpers = older._tagHelpers?.ForkFor(this, difference); + } + + public IReadOnlyDictionary Documents { get; } + + public HostProject HostProject { get; } + + public HostWorkspaceServices Services { get; } + + public Project WorkspaceProject { get; } + + public VersionStamp Version { get; } + + // Computed State + public ProjectEngineTracker ProjectEngine + { + get + { + if (_projectEngine == null) + { + lock (_lock) + { + if (_projectEngine == null) + { + _projectEngine = new ProjectEngineTracker(this); + } + } + } + + return _projectEngine; + } + } + + // Computed State + public ProjectTagHelperTracker TagHelpers + { + get + { + if (_tagHelpers == null) + { + lock (_lock) + { + if (_tagHelpers == null) + { + _tagHelpers = new ProjectTagHelperTracker(this); + } + } + } + + return _tagHelpers; + } + } + + public ProjectState AddHostDocument(HostDocument hostDocument) + { + if (hostDocument == null) + { + throw new ArgumentNullException(nameof(hostDocument)); + } + + // Ignore attempts to 'add' a document with different data, we only + // care about one, so it might as well be the one we have. + if (Documents.ContainsKey(hostDocument.FilePath)) + { + return this; + } + + var documents = new Dictionary(FilePathComparer.Instance); + foreach (var kvp in Documents) + { + documents.Add(kvp.Key, kvp.Value); + } + + documents.Add(hostDocument.FilePath, new DocumentState(Services, hostDocument)); + + var difference = ProjectDifference.DocumentsChanged; + var state = new ProjectState(this, difference, HostProject, WorkspaceProject, documents); + return state; + } + + public ProjectState RemoveHostDocument(HostDocument hostDocument) + { + if (hostDocument == null) + { + throw new ArgumentNullException(nameof(hostDocument)); + } + + if (!Documents.ContainsKey(hostDocument.FilePath)) + { + return this; + } + + var documents = new Dictionary(FilePathComparer.Instance); + foreach (var kvp in Documents) + { + documents.Add(kvp.Key, kvp.Value); + } + + documents.Remove(hostDocument.FilePath); + + var difference = ProjectDifference.DocumentsChanged; + var state = new ProjectState(this, difference, HostProject, WorkspaceProject, documents); + return state; + } + + public ProjectState WithHostProject(HostProject hostProject) + { + if (hostProject == null) + { + throw new ArgumentNullException(nameof(hostProject)); + } + + if (HostProject.Configuration.Equals(hostProject.Configuration)) + { + return this; + } + + var difference = ProjectDifference.ConfigurationChanged; + var documents = new Dictionary(FilePathComparer.Instance); + foreach (var kvp in Documents) + { + documents.Add(kvp.Key, new DocumentState(kvp.Value, difference)); + } + + var state = new ProjectState(this, difference, hostProject, WorkspaceProject, documents); + return state; + } + + public ProjectState WithWorkspaceProject(Project workspaceProject) + { + var difference = ProjectDifference.None; + if (WorkspaceProject == null && workspaceProject != null) + { + difference |= ProjectDifference.WorkspaceProjectAdded; + } + else if (WorkspaceProject != null && workspaceProject == null) + { + difference |= ProjectDifference.WorkspaceProjectRemoved; + } + else if ( + WorkspaceProject?.Id != workspaceProject?.Id || + WorkspaceProject?.Version != workspaceProject?.Version) + { + // For now this is very naive. We will want to consider changing + // our logic here to be more robust. + difference |= ProjectDifference.WorkspaceProjectChanged; + } + + if (difference == ProjectDifference.None) + { + return this; + } + + var documents = new Dictionary(FilePathComparer.Instance); + foreach (var kvp in Documents) + { + documents.Add(kvp.Key, new DocumentState(kvp.Value, difference)); + } + + var state = new ProjectState(this, difference, HostProject, workspaceProject, documents); + return state; + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectTagHelperTracker.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectTagHelperTracker.cs new file mode 100644 index 0000000000..0c3be4ddec --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectTagHelperTracker.cs @@ -0,0 +1,79 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis.Razor.ProjectSystem +{ + internal class ProjectTagHelperTracker + { + private const ProjectDifference Mask = + ProjectDifference.ConfigurationChanged | + ProjectDifference.WorkspaceProjectAdded | + ProjectDifference.WorkspaceProjectChanged | + ProjectDifference.WorkspaceProjectRemoved; + + private readonly object _lock = new object(); + private readonly HostWorkspaceServices _services; + + private Task> _task; + + public ProjectTagHelperTracker(ProjectState state) + { + if (state == null) + { + throw new ArgumentNullException(nameof(state)); + } + + _services = state.Services; + } + + public bool IsResultAvailable => _task?.IsCompleted == true; + + public ProjectTagHelperTracker ForkFor(ProjectState state, ProjectDifference difference) + { + if (state == null) + { + throw new ArgumentNullException(nameof(state)); + } + + if ((difference & Mask) != 0) + { + return null; + } + + return this; + } + + public Task> GetTagHelperInitializationTask(ProjectSnapshot snapshot) + { + if (snapshot == null) + { + throw new ArgumentNullException(nameof(snapshot)); + } + + if (_task == null) + { + lock (_lock) + { + if (_task == null) + { + _task = GetTagHelperInitializationTaskCore(snapshot); + } + } + } + + return _task; + } + + private async Task> GetTagHelperInitializationTaskCore(ProjectSnapshot snapshot) + { + var resolver = _services.GetLanguageServices(RazorLanguage.Name).GetRequiredService(); + return (await resolver.GetTagHelpersAsync(snapshot)).Descriptors; + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorProjectEngineFactoryService.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorProjectEngineFactoryService.cs deleted file mode 100644 index 4defb21947..0000000000 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorProjectEngineFactoryService.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.AspNetCore.Razor.Language; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; - -namespace Microsoft.CodeAnalysis.Razor -{ - internal abstract class RazorProjectEngineFactoryService : ILanguageService - { - public abstract IProjectEngineFactory FindFactory(ProjectSnapshot project); - - public abstract IProjectEngineFactory FindSerializableFactory(ProjectSnapshot project); - - public abstract RazorProjectEngine Create(ProjectSnapshot project, Action configure); - - public abstract RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action configure); - - public abstract RazorProjectEngine Create(string directoryPath, Action configure); - } -} \ No newline at end of file diff --git a/src/Microsoft.CodeAnalysis.Remote.Razor/GeneratedDocument.cs b/src/Microsoft.CodeAnalysis.Remote.Razor/GeneratedDocument.cs deleted file mode 100644 index 8299fce595..0000000000 --- a/src/Microsoft.CodeAnalysis.Remote.Razor/GeneratedDocument.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.CodeAnalysis.Remote.Razor -{ - internal class GeneratedDocument - { - public string Text { get; set; } - } -} diff --git a/src/Microsoft.CodeAnalysis.Remote.Razor/RazorLanguageService.cs b/src/Microsoft.CodeAnalysis.Remote.Razor/RazorLanguageService.cs index ecde04c752..16ababe0db 100644 --- a/src/Microsoft.CodeAnalysis.Remote.Razor/RazorLanguageService.cs +++ b/src/Microsoft.CodeAnalysis.Remote.Razor/RazorLanguageService.cs @@ -2,13 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.ProjectSystem; @@ -27,42 +23,5 @@ namespace Microsoft.CodeAnalysis.Remote.Razor return await RazorServices.TagHelperResolver.GetTagHelpersAsync(project, factoryTypeName, cancellationToken); } - - public Task> GetDirectivesAsync(Guid projectIdBytes, string projectDebugName, CancellationToken cancellationToken = default(CancellationToken)) - { - var projectId = ProjectId.CreateFromSerialized(projectIdBytes, projectDebugName); - - var projectEngine = RazorProjectEngine.Create(); - var directives = projectEngine.EngineFeatures.OfType().FirstOrDefault()?.Directives; - return Task.FromResult(directives ?? Enumerable.Empty()); - } - - public Task GenerateDocumentAsync(Guid projectIdBytes, string projectDebugName, string filePath, string text, CancellationToken cancellationToken = default(CancellationToken)) - { - var projectId = ProjectId.CreateFromSerialized(projectIdBytes, projectDebugName); - - var projectEngine = RazorProjectEngine.Create(); - - RazorSourceDocument source; - using (var stream = new MemoryStream()) - { - var bytes = Encoding.UTF8.GetBytes(text); - stream.Write(bytes, 0, bytes.Length); - - stream.Seek(0L, SeekOrigin.Begin); - source = RazorSourceDocument.ReadFrom(stream, filePath, Encoding.UTF8); - } - - var code = RazorCodeDocument.Create(source); - projectEngine.Engine.Process(code); - - var csharp = code.GetCSharpDocument(); - if (csharp == null) - { - throw new InvalidOperationException(); - } - - return Task.FromResult(new GeneratedDocument() { Text = csharp.GeneratedCode, }); - } } } diff --git a/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs b/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs index 8fdbdd81da..91ac18ae16 100644 --- a/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs +++ b/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs @@ -48,9 +48,7 @@ namespace Microsoft.CodeAnalysis.Remote.Razor { FilePath = filePath; Configuration = configuration; - HostProject = new HostProject(filePath, configuration); WorkspaceProject = workspaceProject; - TagHelpers = Array.Empty(); IsInitialized = true; Version = VersionStamp.Default; @@ -58,6 +56,8 @@ namespace Microsoft.CodeAnalysis.Remote.Razor public override RazorConfiguration Configuration { get; } + public override IEnumerable DocumentFilePaths => Array.Empty(); + public override string FilePath { get; } public override bool IsInitialized { get; } @@ -66,9 +66,30 @@ namespace Microsoft.CodeAnalysis.Remote.Razor public override Project WorkspaceProject { get; } - public override HostProject HostProject { get; } + public override DocumentSnapshot GetDocument(string filePath) + { + if (filePath == null) + { + throw new ArgumentNullException(nameof(filePath)); + } - public override IReadOnlyList TagHelpers { get; } + return null; + } + + public override RazorProjectEngine GetProjectEngine() + { + throw new NotImplementedException(); + } + + public override Task> GetTagHelpersAsync() + { + throw new NotImplementedException(); + } + + public override bool TryGetTagHelpers(out IReadOnlyList results) + { + throw new NotImplementedException(); + } } } } diff --git a/src/Microsoft.NET.Sdk.Razor/build/netstandard2.0/Rules/RazorGenerateWithTargetPath.xaml b/src/Microsoft.NET.Sdk.Razor/build/netstandard2.0/Rules/RazorGenerateWithTargetPath.xaml new file mode 100644 index 0000000000..bab10bf249 --- /dev/null +++ b/src/Microsoft.NET.Sdk.Razor/build/netstandard2.0/Rules/RazorGenerateWithTargetPath.xaml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultImportDocumentManager.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultImportDocumentManager.cs index 2e8c5a450a..68985bc9c6 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultImportDocumentManager.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultImportDocumentManager.cs @@ -16,7 +16,6 @@ namespace Microsoft.VisualStudio.Editor.Razor private readonly FileChangeTrackerFactory _fileChangeTrackerFactory; private readonly ForegroundDispatcher _foregroundDispatcher; private readonly ErrorReporter _errorReporter; - private readonly RazorProjectEngineFactoryService _projectEngineFactoryService; private readonly Dictionary _importTrackerCache; public override event EventHandler Changed; @@ -24,8 +23,7 @@ namespace Microsoft.VisualStudio.Editor.Razor public DefaultImportDocumentManager( ForegroundDispatcher foregroundDispatcher, ErrorReporter errorReporter, - FileChangeTrackerFactory fileChangeTrackerFactory, - RazorProjectEngineFactoryService projectEngineFactoryService) + FileChangeTrackerFactory fileChangeTrackerFactory) { if (foregroundDispatcher == null) { @@ -42,15 +40,9 @@ namespace Microsoft.VisualStudio.Editor.Razor throw new ArgumentNullException(nameof(fileChangeTrackerFactory)); } - if (projectEngineFactoryService == null) - { - throw new ArgumentNullException(nameof(projectEngineFactoryService)); - } - _foregroundDispatcher = foregroundDispatcher; _errorReporter = errorReporter; _fileChangeTrackerFactory = fileChangeTrackerFactory; - _projectEngineFactoryService = projectEngineFactoryService; _importTrackerCache = new Dictionary(StringComparer.OrdinalIgnoreCase); } @@ -115,8 +107,7 @@ namespace Microsoft.VisualStudio.Editor.Razor private IEnumerable GetImportItems(VisualStudioDocumentTracker tracker) { - var projectDirectory = Path.GetDirectoryName(tracker.ProjectPath); - var projectEngine = _projectEngineFactoryService.Create(projectDirectory, _ => { }); + var projectEngine = tracker.ProjectSnapshot.GetProjectEngine(); var trackerItem = projectEngine.FileSystem.GetItem(tracker.FilePath); var importFeature = projectEngine.ProjectFeatures.OfType().FirstOrDefault(); diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultImportDocumentManagerFactory.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultImportDocumentManagerFactory.cs index 47efa8b96c..c44417d69f 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultImportDocumentManagerFactory.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultImportDocumentManagerFactory.cs @@ -35,13 +35,8 @@ namespace Microsoft.VisualStudio.Editor.Razor var errorReporter = languageServices.WorkspaceServices.GetRequiredService(); var fileChangeTrackerFactory = languageServices.GetRequiredService(); - var projectEngineFactoryService = languageServices.GetRequiredService(); - return new DefaultImportDocumentManager( - _foregroundDispatcher, - errorReporter, - fileChangeTrackerFactory, - projectEngineFactoryService); + return new DefaultImportDocumentManager(_foregroundDispatcher, errorReporter, fileChangeTrackerFactory); } } } diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryService.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryService.cs deleted file mode 100644 index d70f82619e..0000000000 --- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryService.cs +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using Microsoft.AspNetCore.Razor.Language; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Razor; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; - -namespace Microsoft.VisualStudio.Editor.Razor -{ - internal class DefaultProjectEngineFactoryService : RazorProjectEngineFactoryService - { - private readonly static RazorConfiguration DefaultConfiguration = FallbackRazorConfiguration.MVC_2_1; - - private readonly Workspace _workspace; - private readonly IFallbackProjectEngineFactory _defaultFactory; - private readonly Lazy[] _customFactories; - private ProjectSnapshotManager _projectManager; - - public DefaultProjectEngineFactoryService( - Workspace workspace, - IFallbackProjectEngineFactory defaultFactory, - Lazy[] customFactories) - { - if (workspace == null) - { - throw new ArgumentNullException(nameof(workspace)); - } - - if (defaultFactory == null) - { - throw new ArgumentNullException(nameof(defaultFactory)); - } - - if (customFactories == null) - { - throw new ArgumentNullException(nameof(customFactories)); - } - - _workspace = workspace; - _defaultFactory = defaultFactory; - _customFactories = customFactories; - } - - // Internal for testing - internal DefaultProjectEngineFactoryService( - ProjectSnapshotManager projectManager, - IFallbackProjectEngineFactory defaultFactory, - Lazy[] customFactories) - { - if (projectManager == null) - { - throw new ArgumentNullException(nameof(projectManager)); - } - - if (defaultFactory == null) - { - throw new ArgumentNullException(nameof(defaultFactory)); - } - - if (customFactories == null) - { - throw new ArgumentNullException(nameof(customFactories)); - } - - _projectManager = projectManager; - _defaultFactory = defaultFactory; - _customFactories = customFactories; - } - - public override IProjectEngineFactory FindFactory(ProjectSnapshot project) - { - if (project == null) - { - throw new ArgumentNullException(nameof(project)); - } - - return SelectFactory(project.Configuration ?? DefaultConfiguration, requireSerializable: false); - } - - public override IProjectEngineFactory FindSerializableFactory(ProjectSnapshot project) - { - if (project == null) - { - throw new ArgumentNullException(nameof(project)); - } - - return SelectFactory(project.Configuration ?? DefaultConfiguration, requireSerializable: true); - } - - public override RazorProjectEngine Create(ProjectSnapshot project, Action configure) - { - if (project == null) - { - throw new ArgumentNullException(nameof(project)); - } - - return CreateCore(project, RazorProjectFileSystem.Create(Path.GetDirectoryName(project.FilePath)), configure); - } - - public override RazorProjectEngine Create(string directoryPath, Action configure) - { - if (directoryPath == null) - { - throw new ArgumentNullException(nameof(directoryPath)); - } - - var project = FindProjectByDirectory(directoryPath); - return CreateCore(project, RazorProjectFileSystem.Create(directoryPath), configure); - } - - public override RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action configure) - { - if (project == null) - { - throw new ArgumentNullException(nameof(project)); - } - - if (fileSystem == null) - { - throw new ArgumentNullException(nameof(fileSystem)); - } - - return CreateCore(project, fileSystem, configure); - } - - private RazorProjectEngine CreateCore(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action configure) - { - // When we're running in the editor, the editor provides a configure delegate that will include - // the editor settings and tag helpers. - // - // This service is only used in process in Visual Studio, and any other callers should provide these - // things also. - configure = configure ?? ((b) => { }); - - // The default configuration currently matches the newest MVC configuration. - // - // We typically want this because the language adds features over time - we don't want to a bunch of errors - // to show up when a document is first opened, and then go away when the configuration loads, we'd prefer the opposite. - var configuration = project?.Configuration ?? DefaultConfiguration; - - // If there's no factory to handle the configuration then fall back to a very basic configuration. - // - // This will stop a crash from happening in this case (misconfigured project), but will still make - // it obvious to the user that something is wrong. - var factory = SelectFactory(configuration) ?? _defaultFactory; - return factory.Create(configuration, fileSystem, configure); - } - - private IProjectEngineFactory SelectFactory(RazorConfiguration configuration, bool requireSerializable = false) - { - for (var i = 0; i < _customFactories.Length; i++) - { - var factory = _customFactories[i]; - if (string.Equals(configuration.ConfigurationName, factory.Metadata.ConfigurationName)) - { - return requireSerializable && !factory.Metadata.SupportsSerialization ? null : factory.Value; - } - } - - return null; - } - - private ProjectSnapshot FindProjectByDirectory(string directory) - { - directory = NormalizeDirectoryPath(directory); - - if (_projectManager == null) - { - _projectManager = _workspace.Services.GetLanguageServices(RazorLanguage.Name).GetRequiredService(); - } - - var projects = _projectManager.Projects; - for (var i = 0; i < projects.Count; i++) - { - var project = projects[i]; - if (project.FilePath != null) - { - if (string.Equals(directory, NormalizeDirectoryPath(Path.GetDirectoryName(project.FilePath)), StringComparison.OrdinalIgnoreCase)) - { - return project; - } - } - } - - return null; - } - - private string NormalizeDirectoryPath(string path) - { - return path.Replace('\\', '/').TrimEnd('/'); - } - } -} diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryServiceFactory.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryServiceFactory.cs deleted file mode 100644 index 7f22134479..0000000000 --- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryServiceFactory.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Composition; -using System.Linq; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Razor; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; - -namespace Microsoft.VisualStudio.Editor.Razor -{ - [ExportLanguageServiceFactory(typeof(RazorProjectEngineFactoryService), RazorLanguage.Name, ServiceLayer.Default)] - internal class DefaultProjectEngineFactoryServiceFactory : ILanguageServiceFactory - { - private readonly Lazy[] _customFactories; - private readonly IFallbackProjectEngineFactory _fallbackFactory; - - [ImportingConstructor] - public DefaultProjectEngineFactoryServiceFactory( - IFallbackProjectEngineFactory fallbackFactory, - [ImportMany] IEnumerable> customFactories) - { - if (fallbackFactory == null) - { - throw new ArgumentNullException(nameof(fallbackFactory)); - } - - if (customFactories == null) - { - throw new ArgumentNullException(nameof(customFactories)); - } - - _fallbackFactory = fallbackFactory; - _customFactories = customFactories.ToArray(); - } - - public ILanguageService CreateLanguageService(HostLanguageServices languageServices) - { - return new DefaultProjectEngineFactoryService( - languageServices.WorkspaceServices.Workspace, - _fallbackFactory, - _customFactories); - } - } -} \ No newline at end of file diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolver.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolver.cs index 47453f6f80..400a0ce2b8 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolver.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolver.cs @@ -4,7 +4,6 @@ using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.ProjectSystem; @@ -12,18 +11,6 @@ namespace Microsoft.VisualStudio.Editor.Razor { internal class DefaultTagHelperResolver : TagHelperResolver { - private readonly RazorProjectEngineFactoryService _engineFactory; - - public DefaultTagHelperResolver(RazorProjectEngineFactoryService engineFactory) - { - if (engineFactory == null) - { - throw new ArgumentNullException(nameof(engineFactory)); - } - - _engineFactory = engineFactory; - } - public override Task GetTagHelpersAsync(ProjectSnapshot project, CancellationToken cancellationToken = default) { if (project == null) @@ -35,9 +22,8 @@ namespace Microsoft.VisualStudio.Editor.Razor { return Task.FromResult(TagHelperResolutionResult.Empty); } - - var engine = _engineFactory.Create(project, RazorProjectFileSystem.Empty, b => { }); - return GetTagHelpersAsync(project, engine); + + return GetTagHelpersAsync(project, project.GetProjectEngine()); } } } diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolverFactory.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolverFactory.cs index 1a238ad473..50e2e96179 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolverFactory.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolverFactory.cs @@ -14,7 +14,7 @@ namespace Microsoft.VisualStudio.Editor.Razor { public ILanguageService CreateLanguageService(HostLanguageServices languageServices) { - return new DefaultTagHelperResolver(languageServices.GetRequiredService()); + return new DefaultTagHelperResolver(); } } } \ No newline at end of file diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs index c6e6937b18..bc31eba667 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs @@ -3,6 +3,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Razor; @@ -25,7 +28,13 @@ namespace Microsoft.VisualStudio.Editor.Razor private readonly List _textViews; private readonly Workspace _workspace; private bool _isSupportedProject; - private ProjectSnapshot _project; + private ProjectSnapshot _projectSnapshot; + + // Only allow a single tag helper computation task at a time. + private (ProjectSnapshot project, Task task) _computingTagHelpers; + + // Stores the result from the last time we computed tag helpers. + private IReadOnlyList _tagHelpers; public override event EventHandler ContextChanged; @@ -89,17 +98,23 @@ namespace Microsoft.VisualStudio.Editor.Razor _workspace = workspace; // For now we assume that the workspace is the always default VS workspace. _textViews = new List(); + _tagHelpers = Array.Empty(); } - public override RazorConfiguration Configuration => _project?.Configuration; + public override RazorConfiguration Configuration => _projectSnapshot?.Configuration; public override EditorSettings EditorSettings => _workspaceEditorSettings.Current; - public override IReadOnlyList TagHelpers => _project?.TagHelpers ?? Array.Empty(); + public override IReadOnlyList TagHelpers => _tagHelpers; public override bool IsSupportedProject => _isSupportedProject; - public override Project Project => _workspace.CurrentSolution.GetProject(_project.WorkspaceProject.Id); + public override Project Project => + _projectSnapshot.WorkspaceProject == null ? + null : + _workspace.CurrentSolution.GetProject(_projectSnapshot.WorkspaceProject.Id); + + internal override ProjectSnapshot ProjectSnapshot => _projectSnapshot; public override ITextBuffer TextBuffer => _textBuffer; @@ -111,6 +126,8 @@ namespace Microsoft.VisualStudio.Editor.Razor public override string ProjectPath => _projectPath; + public Task PendingTagHelperTask => _computingTagHelpers.task ?? Task.CompletedTask; + internal void AddTextView(ITextView textView) { if (textView == null) @@ -118,6 +135,8 @@ namespace Microsoft.VisualStudio.Editor.Razor throw new ArgumentNullException(nameof(textView)); } + _foregroundDispatcher.AssertForegroundThread(); + if (!_textViews.Contains(textView)) { _textViews.Add(textView); @@ -131,6 +150,8 @@ namespace Microsoft.VisualStudio.Editor.Razor throw new ArgumentNullException(nameof(textView)); } + _foregroundDispatcher.AssertForegroundThread(); + if (_textViews.Contains(textView)) { _textViews.Remove(textView); @@ -139,6 +160,8 @@ namespace Microsoft.VisualStudio.Editor.Razor public override ITextView GetFocusedTextView() { + _foregroundDispatcher.AssertForegroundThread(); + for (var i = 0; i < TextViews.Count; i++) { if (TextViews[i].HasAggregateFocus) @@ -152,20 +175,23 @@ namespace Microsoft.VisualStudio.Editor.Razor public void Subscribe() { + _foregroundDispatcher.AssertForegroundThread(); + + _projectSnapshot = _projectManager.GetOrCreateProject(_projectPath); + _isSupportedProject = true; + + _projectManager.Changed += ProjectManager_Changed; + _workspaceEditorSettings.Changed += EditorSettingsManager_Changed; + _importDocumentManager.Changed += Import_Changed; _importDocumentManager.OnSubscribed(this); - _workspaceEditorSettings.Changed += EditorSettingsManager_Changed; - _projectManager.Changed += ProjectManager_Changed; - _importDocumentManager.Changed += Import_Changed; - - _isSupportedProject = true; - _project = _projectManager.GetProjectWithFilePath(_projectPath); - - OnContextChanged(_project, ContextChangeKind.ProjectChanged); + OnContextChanged(ContextChangeKind.ProjectChanged); } public void Unsubscribe() { + _foregroundDispatcher.AssertForegroundThread(); + _importDocumentManager.OnUnsubscribed(this); _projectManager.Changed -= ProjectManager_Changed; @@ -174,37 +200,112 @@ namespace Microsoft.VisualStudio.Editor.Razor // Detached from project. _isSupportedProject = false; - _project = null; - - OnContextChanged(project: null, kind: ContextChangeKind.ProjectChanged); + _projectSnapshot = null; + OnContextChanged(kind: ContextChangeKind.ProjectChanged); } - private void OnContextChanged(ProjectSnapshot project, ContextChangeKind kind) + private void StartComputingTagHelpers() { _foregroundDispatcher.AssertForegroundThread(); - _project = project; + Debug.Assert(_projectSnapshot != null); + Debug.Assert(_computingTagHelpers.project == null && _computingTagHelpers.task == null); + + if (_projectSnapshot.TryGetTagHelpers(out var results)) + { + _tagHelpers = results; + OnContextChanged(ContextChangeKind.TagHelpersChanged); + return; + } + + // if we get here then we know the tag helpers aren't available, so force async for ease of testing + var task = _projectSnapshot + .GetTagHelpersAsync() + .ContinueWith(TagHelpersUpdated, CancellationToken.None, TaskContinuationOptions.RunContinuationsAsynchronously, _foregroundDispatcher.ForegroundScheduler); + _computingTagHelpers = (_projectSnapshot, task); + } + + private void TagHelpersUpdated(Task> task) + { + _foregroundDispatcher.AssertForegroundThread(); + + Debug.Assert(_computingTagHelpers.project != null && _computingTagHelpers.task != null); + + if (!_isSupportedProject) + { + return; + } + + _tagHelpers = task.Exception == null ? task.Result : Array.Empty(); + OnContextChanged(ContextChangeKind.TagHelpersChanged); + + var projectHasChanges = _projectSnapshot != null && _projectSnapshot != _computingTagHelpers.project; + _computingTagHelpers = (null, null); + + if (projectHasChanges) + { + // More changes, keep going. + StartComputingTagHelpers(); + } + } + + private void OnContextChanged(ContextChangeKind kind) + { + _foregroundDispatcher.AssertForegroundThread(); var handler = ContextChanged; if (handler != null) { handler(this, new ContextChangeEventArgs(kind)); } + + if (kind == ContextChangeKind.ProjectChanged && + _projectSnapshot != null && + _computingTagHelpers.project == null) + { + StartComputingTagHelpers(); + } } // Internal for testing internal void ProjectManager_Changed(object sender, ProjectChangeEventArgs e) { + _foregroundDispatcher.AssertForegroundThread(); + if (_projectPath != null && - string.Equals(_projectPath, e.Project.FilePath, StringComparison.OrdinalIgnoreCase)) + string.Equals(_projectPath, e.ProjectFilePath, StringComparison.OrdinalIgnoreCase)) { - if (e.Kind == ProjectChangeKind.TagHelpersChanged) + // This will be the new snapshot unless the project was removed. + _projectSnapshot = _projectManager.GetLoadedProject(e.ProjectFilePath); + + switch (e.Kind) { - OnContextChanged(e.Project, ContextChangeKind.TagHelpersChanged); - } - else - { - OnContextChanged(e.Project, ContextChangeKind.ProjectChanged); + case ProjectChangeKind.DocumentsChanged: + + // Nothing to do. + break; + + case ProjectChangeKind.ProjectAdded: + case ProjectChangeKind.ProjectChanged: + + // Just an update + OnContextChanged(ContextChangeKind.ProjectChanged); + break; + + case ProjectChangeKind.ProjectRemoved: + + // Fall back to ephemeral project + _projectSnapshot = _projectManager.GetOrCreateProject(ProjectPath); + OnContextChanged(ContextChangeKind.ProjectChanged); + break; + + case ProjectChangeKind.DocumentContentChanged: + + // Do nothing + break; + + default: + throw new InvalidOperationException($"Unknown ProjectChangeKind {e.Kind}"); } } } @@ -212,17 +313,21 @@ namespace Microsoft.VisualStudio.Editor.Razor // Internal for testing internal void EditorSettingsManager_Changed(object sender, EditorSettingsChangedEventArgs args) { - OnContextChanged(_project, ContextChangeKind.EditorSettingsChanged); + _foregroundDispatcher.AssertForegroundThread(); + + OnContextChanged(ContextChangeKind.EditorSettingsChanged); } // Internal for testing internal void Import_Changed(object sender, ImportChangedEventArgs args) { + _foregroundDispatcher.AssertForegroundThread(); + foreach (var path in args.AssociatedDocuments) { if (string.Equals(_filePath, path, StringComparison.OrdinalIgnoreCase)) { - OnContextChanged(_project, ContextChangeKind.ImportsChanged); + OnContextChanged(ContextChangeKind.ImportsChanged); break; } } diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParser.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParser.cs index ddbd000f0b..0feda57bce 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParser.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParser.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -31,7 +32,7 @@ namespace Microsoft.VisualStudio.Editor.Razor private readonly VisualStudioCompletionBroker _completionBroker; private readonly VisualStudioDocumentTracker _documentTracker; private readonly ForegroundDispatcher _dispatcher; - private readonly RazorProjectEngineFactoryService _projectEngineFactory; + private readonly ProjectSnapshotProjectEngineFactory _projectEngineFactory; private readonly ErrorReporter _errorReporter; private RazorProjectEngine _projectEngine; private RazorCodeDocument _codeDocument; @@ -47,7 +48,7 @@ namespace Microsoft.VisualStudio.Editor.Razor public DefaultVisualStudioRazorParser( ForegroundDispatcher dispatcher, VisualStudioDocumentTracker documentTracker, - RazorProjectEngineFactoryService projectEngineFactory, + ProjectSnapshotProjectEngineFactory projectEngineFactory, ErrorReporter errorReporter, VisualStudioCompletionBroker completionBroker) { @@ -167,8 +168,18 @@ namespace Microsoft.VisualStudio.Editor.Razor { _dispatcher.AssertForegroundThread(); + // Make sure any tests use the real thing or a good mock. These tests can cause failures + // that are hard to understand when this throws. + Debug.Assert(_documentTracker.IsSupportedProject); + Debug.Assert(_documentTracker.ProjectSnapshot != null); + + _projectEngine = _projectEngineFactory.Create(_documentTracker.ProjectSnapshot, ConfigureProjectEngine); + + Debug.Assert(_projectEngine != null); + Debug.Assert(_projectEngine.Engine != null); + Debug.Assert(_projectEngine.FileSystem != null); + var projectDirectory = Path.GetDirectoryName(_documentTracker.ProjectPath); - _projectEngine = _projectEngineFactory.Create(projectDirectory, ConfigureProjectEngine); _parser = new BackgroundParser(_projectEngine, FilePath, projectDirectory); _parser.ResultsReady += OnResultsReady; _parser.Start(); diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParserFactory.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParserFactory.cs index 5318ede596..8807802090 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParserFactory.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParserFactory.cs @@ -9,7 +9,7 @@ namespace Microsoft.VisualStudio.Editor.Razor internal class DefaultVisualStudioRazorParserFactory : VisualStudioRazorParserFactory { private readonly ForegroundDispatcher _dispatcher; - private readonly RazorProjectEngineFactoryService _projectEngineFactoryService; + private readonly ProjectSnapshotProjectEngineFactory _projectEngineFactory; private readonly VisualStudioCompletionBroker _completionBroker; private readonly ErrorReporter _errorReporter; @@ -17,7 +17,7 @@ namespace Microsoft.VisualStudio.Editor.Razor ForegroundDispatcher dispatcher, ErrorReporter errorReporter, VisualStudioCompletionBroker completionBroker, - RazorProjectEngineFactoryService projectEngineFactoryService) + ProjectSnapshotProjectEngineFactory projectEngineFactory) { if (dispatcher == null) { @@ -34,15 +34,15 @@ namespace Microsoft.VisualStudio.Editor.Razor throw new ArgumentNullException(nameof(completionBroker)); } - if (projectEngineFactoryService == null) + if (projectEngineFactory == null) { - throw new ArgumentNullException(nameof(projectEngineFactoryService)); + throw new ArgumentNullException(nameof(projectEngineFactory)); } _dispatcher = dispatcher; _errorReporter = errorReporter; _completionBroker = completionBroker; - _projectEngineFactoryService = projectEngineFactoryService; + _projectEngineFactory = projectEngineFactory; } public override VisualStudioRazorParser Create(VisualStudioDocumentTracker documentTracker) @@ -57,7 +57,7 @@ namespace Microsoft.VisualStudio.Editor.Razor var parser = new DefaultVisualStudioRazorParser( _dispatcher, documentTracker, - _projectEngineFactoryService, + _projectEngineFactory, _errorReporter, _completionBroker); return parser; diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParserFactoryFactory.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParserFactoryFactory.cs index 38cfe5f189..4e0ff7406e 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParserFactoryFactory.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioRazorParserFactoryFactory.cs @@ -35,13 +35,13 @@ namespace Microsoft.VisualStudio.Editor.Razor var workspaceServices = languageServices.WorkspaceServices; var errorReporter = workspaceServices.GetRequiredService(); var completionBroker = languageServices.GetRequiredService(); - var projectEngineFactoryService = languageServices.GetRequiredService(); + var projectEngineFactory = workspaceServices.GetRequiredService(); return new DefaultVisualStudioRazorParserFactory( _foregroundDispatcher, errorReporter, completionBroker, - projectEngineFactoryService); + projectEngineFactory); } } } \ No newline at end of file diff --git a/src/Microsoft.VisualStudio.Editor.Razor/VisualStudioDocumentTracker.cs b/src/Microsoft.VisualStudio.Editor.Razor/VisualStudioDocumentTracker.cs index 7cde4da716..57c46bfc52 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/VisualStudioDocumentTracker.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/VisualStudioDocumentTracker.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Razor.Editor; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; @@ -29,6 +30,8 @@ namespace Microsoft.VisualStudio.Editor.Razor public abstract Project Project { get; } + internal abstract ProjectSnapshot ProjectSnapshot { get; } + public abstract Workspace Workspace { get; } public abstract ITextBuffer TextBuffer { get; } diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultRazorEngineDirectiveResolver.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultRazorEngineDirectiveResolver.cs deleted file mode 100644 index 9c1eadae88..0000000000 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultRazorEngineDirectiveResolver.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -#if RAZOR_EXTENSION_DEVELOPER_MODE -using System; -using System.Collections.Generic; -using System.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Razor.Language; -using Microsoft.CodeAnalysis; - -namespace Microsoft.VisualStudio.LanguageServices.Razor -{ - [Export(typeof(IRazorEngineDirectiveResolver))] - internal class DefaultRazorEngineDirectiveResolver : IRazorEngineDirectiveResolver - { - public async Task> GetRazorEngineDirectivesAsync(Workspace workspace, Project project, CancellationToken cancellationToken = default(CancellationToken)) - { - try - { - var client = await RazorLanguageServiceClientFactory.CreateAsync(workspace, cancellationToken); - - using (var session = await client.CreateSessionAsync(project.Solution)) - { - var directives = await session.InvokeAsync>("GetDirectivesAsync", new object[] { project.Id.Id, "Foo", }, cancellationToken).ConfigureAwait(false); - return directives; - } - } - catch (Exception exception) - { - throw new InvalidOperationException( - Resources.FormatUnexpectedException( - typeof(DefaultRazorEngineDirectiveResolver).FullName, - nameof(GetRazorEngineDirectivesAsync)), - exception); - } - } - } -} -#endif \ No newline at end of file diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultRazorEngineDocumentGenerator.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultRazorEngineDocumentGenerator.cs deleted file mode 100644 index b7c0cce061..0000000000 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultRazorEngineDocumentGenerator.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -#if RAZOR_EXTENSION_DEVELOPER_MODE -using System; -using System.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; - -namespace Microsoft.VisualStudio.LanguageServices.Razor -{ - [Export(typeof(IRazorEngineDocumentGenerator))] - internal class DefaultRazorEngineDocumentGenerator : IRazorEngineDocumentGenerator - { - public async Task GenerateDocumentAsync(Workspace workspace, Project project, string filePath, string text, CancellationToken cancellationToken = default(CancellationToken)) - { - try - { - var client = await RazorLanguageServiceClientFactory.CreateAsync(workspace, cancellationToken); - - using (var session = await client.CreateSessionAsync(project.Solution)) - { - var document = await session.InvokeAsync("GenerateDocumentAsync", new object[] { project.Id.Id, "Foo", filePath, text }, cancellationToken).ConfigureAwait(false); - return document; - } - } - catch (Exception exception) - { - throw new InvalidOperationException( - Resources.FormatUnexpectedException( - typeof(DefaultRazorEngineDocumentGenerator).FullName, - nameof(GenerateDocumentAsync)), - exception); - } - } - } -} -#endif \ No newline at end of file diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/IRazorEngineDirectiveResolver.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/IRazorEngineDirectiveResolver.cs deleted file mode 100644 index c884eedca1..0000000000 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/IRazorEngineDirectiveResolver.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -#if RAZOR_EXTENSION_DEVELOPER_MODE -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Razor.Language; -using Microsoft.CodeAnalysis; - -namespace Microsoft.VisualStudio.LanguageServices.Razor -{ - internal interface IRazorEngineDirectiveResolver - { - Task> GetRazorEngineDirectivesAsync(Workspace workspace, Project project, CancellationToken cancellationToken = default(CancellationToken)); - } -} -#endif \ No newline at end of file diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/IRazorEngineDocumentGenerator.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/IRazorEngineDocumentGenerator.cs deleted file mode 100644 index 4b39c82bdb..0000000000 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/IRazorEngineDocumentGenerator.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -#if RAZOR_EXTENSION_DEVELOPER_MODE -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; - -namespace Microsoft.VisualStudio.LanguageServices.Razor -{ - internal interface IRazorEngineDocumentGenerator - { - Task GenerateDocumentAsync(Workspace workspace, Project project, string filePath, string text, CancellationToken cancellationToken = default(CancellationToken)); - } -} -#endif \ No newline at end of file diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Microsoft.VisualStudio.LanguageServices.Razor.csproj b/src/Microsoft.VisualStudio.LanguageServices.Razor/Microsoft.VisualStudio.LanguageServices.Razor.csproj index a3bbd0f3c9..26d1ac3025 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/Microsoft.VisualStudio.LanguageServices.Razor.csproj +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Microsoft.VisualStudio.LanguageServices.Razor.csproj @@ -43,7 +43,7 @@ - @@ -21,7 +22,11 @@ Outputs="$(MSBuildLocationFileOutput)"> - MSBuildLocation=$(VisualStudioMSBuildx86Path) + + MSBuildLocation=$(VisualStudioMSBuildx86Path); + MicrosoftNETCoreAppVersion=$(MicrosoftNETCoreApp21PackageVersion); + NETStandardLibraryVersion=$(NETStandardLibrary20PackageVersion) + Date: Thu, 26 Apr 2018 17:17:32 -0700 Subject: [PATCH 013/171] Skip flaky test --- test/Microsoft.AspNetCore.Razor.Tools.Test/ServerCommandTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Microsoft.AspNetCore.Razor.Tools.Test/ServerCommandTest.cs b/test/Microsoft.AspNetCore.Razor.Tools.Test/ServerCommandTest.cs index 1acae3ef23..cc77e3f240 100644 --- a/test/Microsoft.AspNetCore.Razor.Tools.Test/ServerCommandTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Tools.Test/ServerCommandTest.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Razor.Tools { public class ServerCommandTest { - [Fact] + [Fact(Skip = "https://github.com/aspnet/Razor/issues/2310")] public void WritePidFile_WorksAsExpected() { // Arrange From ee9537bebd989cba0164800aa434d6490d03c883 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Fri, 27 Apr 2018 10:56:58 -0700 Subject: [PATCH 014/171] Add Razor.LiveShare IVT to Editor.Razor. --- .../Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.VisualStudio.Editor.Razor/Properties/AssemblyInfo.cs b/src/Microsoft.VisualStudio.Editor.Razor/Properties/AssemblyInfo.cs index fce64fd49b..03f3c3d59b 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/Properties/AssemblyInfo.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/Properties/AssemblyInfo.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Runtime.CompilerServices; - +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.LiveShare.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.VisualStudio.Editor.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.VisualStudio.Mac.LanguageServices.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] From 8bc4c7a15c2f7b6435d8f86e8c1126ceb3ad8d54 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 29 Apr 2018 12:28:29 -0700 Subject: [PATCH 015/171] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 5c38a7f51a..0c1ab5ca83 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,19 +5,19 @@ 0.10.13 2.2.0-preview1-17042 - 2.2.0-preview1-34054 - 2.2.0-preview1-34054 - 2.2.0-preview1-34054 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 15.6.82 15.6.82 15.6.82 2.8.0-beta3 2.8.0-beta3 - 2.2.0-preview1-34054 - 2.2.0-preview1-34054 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 2.2.0-preview1-26424-04 - 2.2.0-preview1-34054 - 2.2.0-preview1-34054 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 2.0.0 2.2.0-preview1-26424-04 15.6.1 From 2885b4b13804511a76836f63fc5bdf732ab800dc Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Fri, 27 Apr 2018 11:47:34 -0700 Subject: [PATCH 016/171] Make document tracker resilient to multiple subscribes. - Added tests to verify new functionality. #2314 --- .../DefaultVisualStudioDocumentTracker.cs | 12 ++++ .../DefaultVisualStudioDocumentTrackerTest.cs | 66 +++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs index bc31eba667..b9c976bdb9 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs @@ -29,6 +29,7 @@ namespace Microsoft.VisualStudio.Editor.Razor private readonly Workspace _workspace; private bool _isSupportedProject; private ProjectSnapshot _projectSnapshot; + private int _subscribeCount; // Only allow a single tag helper computation task at a time. private (ProjectSnapshot project, Task task) _computingTagHelpers; @@ -177,12 +178,18 @@ namespace Microsoft.VisualStudio.Editor.Razor { _foregroundDispatcher.AssertForegroundThread(); + if (_subscribeCount++ > 0) + { + return; + } + _projectSnapshot = _projectManager.GetOrCreateProject(_projectPath); _isSupportedProject = true; _projectManager.Changed += ProjectManager_Changed; _workspaceEditorSettings.Changed += EditorSettingsManager_Changed; _importDocumentManager.Changed += Import_Changed; + _importDocumentManager.OnSubscribed(this); OnContextChanged(ContextChangeKind.ProjectChanged); @@ -192,6 +199,11 @@ namespace Microsoft.VisualStudio.Editor.Razor { _foregroundDispatcher.AssertForegroundThread(); + if (_subscribeCount == 0 || _subscribeCount-- > 1) + { + return; + } + _importDocumentManager.OnUnsubscribed(this); _projectManager.Changed -= ProjectManager_Changed; diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioDocumentTrackerTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioDocumentTrackerTest.cs index 0e47ecfe9c..f14f8bbfd8 100644 --- a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioDocumentTrackerTest.cs +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioDocumentTrackerTest.cs @@ -98,6 +98,72 @@ namespace Microsoft.VisualStudio.Editor.Razor private DefaultVisualStudioDocumentTracker DocumentTracker { get; } + [ForegroundFact] + public void Subscribe_NoopsIfAlreadySubscribed() + { + // Arrange + var callCount = 0; + DocumentTracker.ContextChanged += (sender, args) => + { + callCount++; + }; + DocumentTracker.Subscribe(); + + // Call count is 2 right now: + // 1 trigger for initial subscribe context changed. + // 1 trigger for TagHelpers being changed (computed). + + // Act + DocumentTracker.Subscribe(); + + // Assert + Assert.Equal(2, callCount); + } + + [ForegroundFact] + public void Unsubscribe_NoopsIfAlreadyUnsubscribed() + { + // Arrange + var callCount = 0; + DocumentTracker.Subscribe(); + DocumentTracker.ContextChanged += (sender, args) => + { + callCount++; + }; + DocumentTracker.Unsubscribe(); + + // Act + DocumentTracker.Unsubscribe(); + + // Assert + Assert.Equal(1, callCount); + } + + [ForegroundFact] + public void Unsubscribe_NoopsIfSubscribeHasBeenCalledMultipleTimes() + { + // Arrange + var callCount = 0; + DocumentTracker.Subscribe(); + DocumentTracker.Subscribe(); + DocumentTracker.ContextChanged += (sender, args) => + { + callCount++; + }; + + // Act - 1 + DocumentTracker.Unsubscribe(); + + // Assert - 1 + Assert.Equal(0, callCount); + + // Act - 2 + DocumentTracker.Unsubscribe(); + + // Assert - 2 + Assert.Equal(1, callCount); + } + [ForegroundFact] public void EditorSettingsManager_Changed_TriggersContextChanged() { From f67458f15687dcebe30e07415365956bb2c14e8e Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Mon, 30 Apr 2018 11:52:41 -0700 Subject: [PATCH 017/171] Fix #2298 simplify TagHelperDescriptor hash The hash code implementation here is exhaustive when it doesn't need to be. Slimming this down to a much more reasonable set of things for perf reasons. --- .../AllowedChildTagDescriptorComparer.cs | 12 +++---- .../BoundAttributeDescriptorComparer.cs | 19 +++-------- .../RequiredAttributeDescriptorComparer.cs | 16 +++------ .../TagHelperDescriptorComparer.cs | 34 ++++--------------- .../TagMatchingRuleDescriptorComparer.cs | 19 +++-------- .../DefaultTagHelperCompletionService.cs | 22 ++++++------ 6 files changed, 37 insertions(+), 85 deletions(-) diff --git a/src/Microsoft.AspNetCore.Razor.Language/AllowedChildTagDescriptorComparer.cs b/src/Microsoft.AspNetCore.Razor.Language/AllowedChildTagDescriptorComparer.cs index d73e3ec71e..37f0a55417 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/AllowedChildTagDescriptorComparer.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/AllowedChildTagDescriptorComparer.cs @@ -54,20 +54,18 @@ namespace Microsoft.AspNetCore.Razor.Language return false; } - return descriptorX != null && + return string.Equals(descriptorX.Name, descriptorY.Name, _stringComparison) && - string.Equals(descriptorX.DisplayName, descriptorY.DisplayName, StringComparison.Ordinal) && - Enumerable.SequenceEqual(descriptorX.Diagnostics, descriptorY.Diagnostics); + string.Equals(descriptorX.DisplayName, descriptorY.DisplayName, StringComparison.Ordinal); } /// public virtual int GetHashCode(AllowedChildTagDescriptor descriptor) { - var hashCodeCombiner = HashCodeCombiner.Start(); - hashCodeCombiner.Add(descriptor.Name, _stringComparer); - hashCodeCombiner.Add(descriptor.DisplayName, StringComparer.Ordinal); + var hash = HashCodeCombiner.Start(); + hash.Add(descriptor.Name, _stringComparer); - return hashCodeCombiner.CombinedHash; + return hash.CombinedHash; } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/BoundAttributeDescriptorComparer.cs b/src/Microsoft.AspNetCore.Razor.Language/BoundAttributeDescriptorComparer.cs index 21b610e45b..fbf68929eb 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/BoundAttributeDescriptorComparer.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/BoundAttributeDescriptorComparer.cs @@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.Razor.Language return false; } - return descriptorX != null && + return string.Equals(descriptorX.Kind, descriptorY.Kind, StringComparison.Ordinal) && descriptorX.IsIndexerStringProperty == descriptorY.IsIndexerStringProperty && descriptorX.IsEnum == descriptorY.IsEnum && @@ -61,7 +61,6 @@ namespace Microsoft.AspNetCore.Razor.Language string.Equals(descriptorX.IndexerTypeName, descriptorY.IndexerTypeName, StringComparison.Ordinal) && string.Equals(descriptorX.Documentation, descriptorY.Documentation, StringComparison.Ordinal) && string.Equals(descriptorX.DisplayName, descriptorY.DisplayName, StringComparison.Ordinal) && - Enumerable.SequenceEqual(descriptorX.Diagnostics, descriptorY.Diagnostics) && Enumerable.SequenceEqual( descriptorX.Metadata.OrderBy(propertyX => propertyX.Key, StringComparer.Ordinal), descriptorY.Metadata.OrderBy(propertyY => propertyY.Key, StringComparer.Ordinal)); @@ -74,19 +73,11 @@ namespace Microsoft.AspNetCore.Razor.Language throw new ArgumentNullException(nameof(descriptor)); } - var hashCodeCombiner = HashCodeCombiner.Start(); - hashCodeCombiner.Add(descriptor.Kind); - hashCodeCombiner.Add(descriptor.IsIndexerStringProperty); - hashCodeCombiner.Add(descriptor.IsEnum); - hashCodeCombiner.Add(descriptor.HasIndexer); - hashCodeCombiner.Add(descriptor.Name, _stringComparer); - hashCodeCombiner.Add(descriptor.IndexerNamePrefix, _stringComparer); - hashCodeCombiner.Add(descriptor.TypeName, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.IndexerTypeName, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.Documentation, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.DisplayName, StringComparer.Ordinal); + var hash = HashCodeCombiner.Start(); + hash.Add(descriptor.Kind); + hash.Add(descriptor.Name, _stringComparer); - return hashCodeCombiner.CombinedHash; + return hash.CombinedHash; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Language/RequiredAttributeDescriptorComparer.cs b/src/Microsoft.AspNetCore.Razor.Language/RequiredAttributeDescriptorComparer.cs index 78186cfb49..149f6a0c3d 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/RequiredAttributeDescriptorComparer.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/RequiredAttributeDescriptorComparer.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Razor.Language @@ -58,26 +57,21 @@ namespace Microsoft.AspNetCore.Razor.Language return false; } - return descriptorX != null && + return descriptorX.NameComparison == descriptorY.NameComparison && descriptorX.ValueComparison == descriptorY.ValueComparison && string.Equals(descriptorX.Name, descriptorY.Name, _stringComparison) && string.Equals(descriptorX.Value, descriptorY.Value, StringComparison.Ordinal) && - string.Equals(descriptorX.DisplayName, descriptorY.DisplayName, StringComparison.Ordinal) && - Enumerable.SequenceEqual(descriptorX.Diagnostics, descriptorY.Diagnostics); + string.Equals(descriptorX.DisplayName, descriptorY.DisplayName, StringComparison.Ordinal); } /// public virtual int GetHashCode(RequiredAttributeDescriptor descriptor) { - var hashCodeCombiner = HashCodeCombiner.Start(); - hashCodeCombiner.Add(descriptor.NameComparison); - hashCodeCombiner.Add(descriptor.ValueComparison); - hashCodeCombiner.Add(descriptor.Name, _stringComparer); - hashCodeCombiner.Add(descriptor.Value, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.DisplayName, StringComparer.Ordinal); + var hash = HashCodeCombiner.Start(); + hash.Add(descriptor.Name, _stringComparer); - return hashCodeCombiner.CombinedHash; + return hash.CombinedHash; } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorComparer.cs b/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorComparer.cs index 90cead0e50..64f5206a7b 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorComparer.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorComparer.cs @@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Razor.Language public virtual bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY) { - if (descriptorX == descriptorY) + if (object.ReferenceEquals(descriptorX, descriptorY)) { return true; } @@ -62,6 +62,7 @@ namespace Microsoft.AspNetCore.Razor.Language return descriptorX != null && string.Equals(descriptorX.Kind, descriptorY.Kind, StringComparison.Ordinal) && string.Equals(descriptorX.AssemblyName, descriptorY.AssemblyName, StringComparison.Ordinal) && + string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.Ordinal) && Enumerable.SequenceEqual( descriptorX.BoundAttributes.OrderBy(attribute => attribute.Name, _stringComparer), descriptorY.BoundAttributes.OrderBy(attribute => attribute.Name, _stringComparer), @@ -94,33 +95,12 @@ namespace Microsoft.AspNetCore.Razor.Language throw new ArgumentNullException(nameof(descriptor)); } - var hashCodeCombiner = HashCodeCombiner.Start(); - hashCodeCombiner.Add(descriptor.Kind); - hashCodeCombiner.Add(descriptor.AssemblyName, StringComparer.Ordinal); + var hash = HashCodeCombiner.Start(); + hash.Add(descriptor.Kind, StringComparer.Ordinal); + hash.Add(descriptor.AssemblyName, StringComparer.Ordinal); + hash.Add(descriptor.Name, StringComparer.Ordinal); - var childTags = descriptor.AllowedChildTags.OrderBy(childTag => childTag.Name, _stringComparer); - foreach (var childTag in childTags) - { - hashCodeCombiner.Add(_AllowedChildTagDescriptorComparer.GetHashCode(childTag)); - } - - var boundAttributes = descriptor.BoundAttributes.OrderBy(attribute => attribute.Name, _stringComparer); - foreach (var attribute in boundAttributes) - { - hashCodeCombiner.Add(_boundAttributeComparer.GetHashCode(attribute)); - } - - var rules = descriptor.TagMatchingRules.OrderBy(rule => rule.TagName, _stringComparer); - foreach (var rule in rules) - { - hashCodeCombiner.Add(_tagMatchingRuleComparer.GetHashCode(rule)); - } - - hashCodeCombiner.Add(descriptor.Documentation, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.DisplayName, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.TagOutputHint, _stringComparer); - - return hashCodeCombiner.CombinedHash; + return hash.CombinedHash; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Language/TagMatchingRuleDescriptorComparer.cs b/src/Microsoft.AspNetCore.Razor.Language/TagMatchingRuleDescriptorComparer.cs index c77bdfbddb..734485b3c3 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/TagMatchingRuleDescriptorComparer.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/TagMatchingRuleDescriptorComparer.cs @@ -54,12 +54,11 @@ namespace Microsoft.AspNetCore.Razor.Language return false; } - return ruleX != null && + return string.Equals(ruleX.TagName, ruleY.TagName, _stringComparison) && string.Equals(ruleX.ParentTag, ruleY.ParentTag, _stringComparison) && ruleX.TagStructure == ruleY.TagStructure && - Enumerable.SequenceEqual(ruleX.Attributes, ruleY.Attributes, _requiredAttributeComparer) && - Enumerable.SequenceEqual(ruleX.Diagnostics, ruleY.Diagnostics); + Enumerable.SequenceEqual(ruleX.Attributes, ruleY.Attributes, _requiredAttributeComparer); } public virtual int GetHashCode(TagMatchingRuleDescriptor rule) @@ -69,18 +68,10 @@ namespace Microsoft.AspNetCore.Razor.Language throw new ArgumentNullException(nameof(rule)); } - var hashCodeCombiner = HashCodeCombiner.Start(); - hashCodeCombiner.Add(rule.TagName, _stringComparer); - hashCodeCombiner.Add(rule.ParentTag, _stringComparer); - hashCodeCombiner.Add(rule.TagStructure); + var hash = HashCodeCombiner.Start(); + hash.Add(rule.TagName, _stringComparer); - var attributes = rule.Attributes.OrderBy(attribute => attribute.Name, _stringComparer); - foreach (var attribute in attributes) - { - hashCodeCombiner.Add(_requiredAttributeComparer.GetHashCode(attribute)); - } - - return hashCodeCombiner.CombinedHash; + return hash.CombinedHash; } } } \ No newline at end of file diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperCompletionService.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperCompletionService.cs index dc8d55cb03..1c53c97a44 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperCompletionService.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperCompletionService.cs @@ -29,18 +29,16 @@ namespace Microsoft.VisualStudio.Editor.Razor _tagHelperFactsService = tagHelperFactsService; } - /* - * This API attempts to understand a users context as they're typing in a Razor file to provide TagHelper based attribute IntelliSense. - * - * Scenarios for TagHelper attribute IntelliSense follows: - * 1. TagHelperDescriptor's have matching required attribute names - * -> Provide IntelliSense for the required attributes of those descriptors to lead users towards a TagHelperified element. - * 2. TagHelperDescriptor entirely applies to current element. Tag name, attributes, everything is fulfilled. - * -> Provide IntelliSense for the bound attributes for the applied descriptors. - * - * Within each of the above scenarios if an attribute completion has a corresponding bound attribute we associate it with the corresponding - * BoundAttributeDescriptor. By doing this a user can see what C# type a TagHelper expects for the attribute. - */ + // This API attempts to understand a users context as they're typing in a Razor file to provide TagHelper based attribute IntelliSense. + // + // Scenarios for TagHelper attribute IntelliSense follows: + // 1. TagHelperDescriptor's have matching required attribute names + // -> Provide IntelliSense for the required attributes of those descriptors to lead users towards a TagHelperified element. + // 2. TagHelperDescriptor entirely applies to current element. Tag name, attributes, everything is fulfilled. + // -> Provide IntelliSense for the bound attributes for the applied descriptors. + // + // Within each of the above scenarios if an attribute completion has a corresponding bound attribute we associate it with the corresponding + // BoundAttributeDescriptor. By doing this a user can see what C# type a TagHelper expects for the attribute. public override AttributeCompletionResult GetAttributeCompletions(AttributeCompletionContext completionContext) { if (completionContext == null) From c59dcb9b4c9cfec893d878b72b29b407fbfce815 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Mon, 30 Apr 2018 16:13:24 -0700 Subject: [PATCH 018/171] Add IVT for Razor.LiveShare.Test. --- .../Properties/AssemblyInfo.cs | 1 + .../Properties/AssemblyInfo.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/Properties/AssemblyInfo.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/Properties/AssemblyInfo.cs index 15d863d491..fefb5cd1de 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/Properties/AssemblyInfo.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/Properties/AssemblyInfo.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.LiveShare.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.VisualStudio.LiveShare.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Razor.Performance, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.VisualStudio.Mac.RazorAddin, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test.Common/Properties/AssemblyInfo.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test.Common/Properties/AssemblyInfo.cs index 2ef25d5390..e3c5250b76 100644 --- a/test/Microsoft.VisualStudio.Editor.Razor.Test.Common/Properties/AssemblyInfo.cs +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test.Common/Properties/AssemblyInfo.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.LiveShare.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Razor.Workspaces.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.VisualStudio.Editor.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.VisualStudio.LanguageServices.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] From 7cba5ed59309b832aafe47f338399e3676b1afda Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Baaskaran Date: Mon, 30 Apr 2018 13:02:15 -0700 Subject: [PATCH 019/171] Fix flaky test failure in ServerCommandTest --- .../ServerCommand.cs | 58 ++++++++++++----- .../Infrastructure/ServerUtilities.cs | 7 +- .../ServerCommandTest.cs | 65 ++++++++++++++++--- 3 files changed, 103 insertions(+), 27 deletions(-) diff --git a/src/Microsoft.AspNetCore.Razor.Tools/ServerCommand.cs b/src/Microsoft.AspNetCore.Razor.Tools/ServerCommand.cs index 44bb594b1c..a4aee01b3d 100644 --- a/src/Microsoft.AspNetCore.Razor.Tools/ServerCommand.cs +++ b/src/Microsoft.AspNetCore.Razor.Tools/ServerCommand.cs @@ -25,7 +25,10 @@ namespace Microsoft.AspNetCore.Razor.Tools internal ServerCommand(Application parent, string pipeName, int? keepAlive = null) : this(parent) { - Pipe.Values.Add(pipeName); + if (!string.IsNullOrEmpty(pipeName)) + { + Pipe.Values.Add(pipeName); + } if (keepAlive.HasValue) { @@ -119,8 +122,21 @@ namespace Microsoft.AspNetCore.Razor.Tools dispatcher.Run(); } - internal FileStream WritePidFile() + protected virtual FileStream WritePidFile() { + var path = GetPidFilePath(env => Environment.GetEnvironmentVariable(env)); + return WritePidFile(path); + } + + // Internal for testing. + internal virtual FileStream WritePidFile(string directoryPath) + { + if (string.IsNullOrEmpty(directoryPath)) + { + // Invalid path. Bail. + return null; + } + // To make all the running rzc servers more discoverable, We want to write the process Id and pipe name to a file. // The file contents will be in the following format, // @@ -133,24 +149,10 @@ namespace Microsoft.AspNetCore.Razor.Tools var processId = Process.GetCurrentProcess().Id; var fileName = $"rzc-{processId}"; - var path = Environment.GetEnvironmentVariable("DOTNET_BUILD_PIDFILE_DIRECTORY"); - if (string.IsNullOrEmpty(path)) - { - var homeEnvVariable = PlatformInformation.IsWindows ? "USERPROFILE" : "HOME"; - var homePath = Environment.GetEnvironmentVariable(homeEnvVariable); - if (string.IsNullOrEmpty(homePath)) - { - // Couldn't locate the user profile directory. Bail. - return null; - } - - path = Path.Combine(homePath, ".dotnet", "pids", "build"); - } - // Make sure the directory exists. - Directory.CreateDirectory(path); + Directory.CreateDirectory(directoryPath); - path = Path.Combine(path, fileName); + var path = Path.Combine(directoryPath, fileName); var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, DefaultBufferSize, FileOptions.DeleteOnClose); using (var writer = new StreamWriter(fileStream, Encoding.UTF8, DefaultBufferSize, leaveOpen: true)) @@ -162,5 +164,25 @@ namespace Microsoft.AspNetCore.Razor.Tools return fileStream; } + + // Internal for testing. + internal virtual string GetPidFilePath(Func getEnvironmentVariable) + { + var path = getEnvironmentVariable("DOTNET_BUILD_PIDFILE_DIRECTORY"); + if (string.IsNullOrEmpty(path)) + { + var homeEnvVariable = PlatformInformation.IsWindows ? "USERPROFILE" : "HOME"; + var homePath = getEnvironmentVariable(homeEnvVariable); + if (string.IsNullOrEmpty(homePath)) + { + // Couldn't locate the user profile directory. Bail. + return null; + } + + path = Path.Combine(homePath, ".dotnet", "pids", "build"); + } + + return path; + } } } diff --git a/test/Microsoft.AspNetCore.Razor.Tools.Test/Infrastructure/ServerUtilities.cs b/test/Microsoft.AspNetCore.Razor.Tools.Test/Infrastructure/ServerUtilities.cs index d6494d1d37..65e407f644 100644 --- a/test/Microsoft.AspNetCore.Razor.Tools.Test/Infrastructure/ServerUtilities.cs +++ b/test/Microsoft.AspNetCore.Razor.Tools.Test/Infrastructure/ServerUtilities.cs @@ -116,7 +116,6 @@ namespace Microsoft.AspNetCore.Razor.Tools private readonly CancellationToken _cancellationToken; private readonly TimeSpan? _keepAlive; - public TestableServerCommand( ConnectionHost host, CompilerHost compilerHost, @@ -146,6 +145,12 @@ namespace Microsoft.AspNetCore.Razor.Tools _eventBus ?? eventBus, _keepAlive ?? keepAlive); } + + protected override FileStream WritePidFile() + { + // Disable writing PID file as it is tested separately. + return null; + } } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Razor.Tools.Test/ServerCommandTest.cs b/test/Microsoft.AspNetCore.Razor.Tools.Test/ServerCommandTest.cs index cc77e3f240..d61fe7e09e 100644 --- a/test/Microsoft.AspNetCore.Razor.Tools.Test/ServerCommandTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Tools.Test/ServerCommandTest.cs @@ -13,15 +13,15 @@ namespace Microsoft.AspNetCore.Razor.Tools { public class ServerCommandTest { - [Fact(Skip = "https://github.com/aspnet/Razor/issues/2310")] + [Fact] public void WritePidFile_WorksAsExpected() { // Arrange var expectedProcessId = Process.GetCurrentProcess().Id; var expectedRzcPath = typeof(ServerCommand).Assembly.Location; var expectedFileName = $"rzc-{expectedProcessId}"; - var homeEnvVariable = PlatformInformation.IsWindows ? "USERPROFILE" : "HOME"; - var path = Path.Combine(Environment.GetEnvironmentVariable(homeEnvVariable), ".dotnet", "pids", "build", expectedFileName); + var directoryPath = Path.Combine(Path.GetTempPath(), "RazorTest", Guid.NewGuid().ToString()); + var path = Path.Combine(directoryPath, expectedFileName); var pipeName = Guid.NewGuid().ToString(); var server = GetServerCommand(pipeName); @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Razor.Tools // Act & Assert try { - using (var _ = server.WritePidFile()) + using (var _ = server.WritePidFile(directoryPath)) { Assert.True(File.Exists(path)); @@ -47,15 +47,64 @@ namespace Microsoft.AspNetCore.Razor.Tools } finally { - // Delete the file in case the test fails. - if (File.Exists(path)) + // Cleanup after the test. + if (Directory.Exists(directoryPath)) { - File.Delete(path); + Directory.Delete(directoryPath, recursive: true); } } } - private ServerCommand GetServerCommand(string pipeName) + [Fact] + public void GetPidFilePath_ReturnsCorrectDefaultPath() + { + // Arrange + var expectedPath = Path.Combine("homeDir", ".dotnet", "pids", "build"); + var server = GetServerCommand(); + + // Act + var directoryPath = server.GetPidFilePath(getEnvironmentVariable: env => + { + if (env == "DOTNET_BUILD_PIDFILE_DIRECTORY") + { + return null; + } + + return "homeDir"; + }); + + // Assert + Assert.Equal(expectedPath, directoryPath); + } + + [Fact] + public void GetPidFilePath_UsesEnvironmentVariablePathIfSpecified() + { + // Arrange + var expectedPath = "/Some/directory/path/"; + var server = GetServerCommand(); + + // Act + var directoryPath = server.GetPidFilePath(getEnvironmentVariable: env => expectedPath); + + // Assert + Assert.Equal(expectedPath, directoryPath); + } + + [Fact] + public void GetPidFilePath_NullEnvironmentVariableValue_ReturnsNull() + { + // Arrange + var server = GetServerCommand(); + + // Act + var directoryPath = server.GetPidFilePath(getEnvironmentVariable: env => null); + + // Assert + Assert.Null(directoryPath); + } + + private ServerCommand GetServerCommand(string pipeName = null) { var application = new Application( CancellationToken.None, From 20816636352f2ec759d87dd95b1cf39bfe799e68 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 1 May 2018 12:02:21 -0700 Subject: [PATCH 020/171] Re-enable desktop msbuild tests Fixes #2208 --- build/buildpipeline/windows.groovy | 2 +- .../IntegrationTests/BuildIntegrationTest.cs | 5 ++--- .../IntegrationTests/BuildServerIntegrationTest.cs | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/build/buildpipeline/windows.groovy b/build/buildpipeline/windows.groovy index 8d26f313d4..9c0b86f089 100644 --- a/build/buildpipeline/windows.groovy +++ b/build/buildpipeline/windows.groovy @@ -2,7 +2,7 @@ // 'node' indicates to Jenkins that the enclosed block runs on a node that matches // the label 'windows-with-vs' -simpleNode('Windows_NT','latest') { +simpleNode('Windows.10.Enterprise.RS3.ASPNET') { stage ('Checking out source') { checkout scm } diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildIntegrationTest.cs index 9ac5e9ab13..541cac3523 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildIntegrationTest.cs @@ -25,9 +25,8 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests public Task Build_SimpleMvc_UsingDotnetMSBuildAndWithoutBuildServer_CanBuildSuccessfully() => Build_SimpleMvc_WithoutBuildServer_CanBuildSuccessfully(MSBuildProcessKind.Dotnet); - [ConditionalFact(Skip = "https://github.com/aspnet/Razor/issues/2208")] - [OSSkipCondition(OperatingSystems.Linux)] - [OSSkipCondition(OperatingSystems.MacOSX)] + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] [InitializeTestProject("SimpleMvc")] public Task Build_SimpleMvc_UsingDesktopMSBuildAndWithoutBuildServer_CanBuildSuccessfully() => Build_SimpleMvc_WithoutBuildServer_CanBuildSuccessfully(MSBuildProcessKind.Desktop); diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildServerIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildServerIntegrationTest.cs index 478ee4fee6..60076a7a67 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildServerIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildServerIntegrationTest.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests public Task Build_SimpleMvc_WithServer_UsingDotnetMSBuild_CanBuildSuccessfully() => Build_SimpleMvc_CanBuildSuccessfully(MSBuildProcessKind.Dotnet); - [ConditionalFact(Skip = "https://github.com/aspnet/Razor/issues/2208")] + [ConditionalFact] [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] [InitializeTestProject("SimpleMvc")] public Task Build_SimpleMvc_WithServer_UsingDesktopMSBuild_CanBuildSuccessfully() From e0612d7e078f6718c7c68a39362f0450108bb146 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 2 May 2018 15:15:02 -0700 Subject: [PATCH 021/171] Fixes for the document window state --- .../ProjectSystem/ProjectState.cs | 8 +++---- .../DefaultProjectSnapshotManagerTest.cs | 22 ++++++++++++++++++- .../DocumentInfo/RazorDocumentInfoWindow.cs | 4 ++++ .../RazorInfo/RazorInfoToolWindow.cs | 1 + .../RazorInfo/RazorInfoToolWindowControl.xaml | 15 ++++++++----- .../RazorInfo/RazorInfoViewModel.cs | 6 ++--- 6 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs index 05ecf2be49..e25da4db2d 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs @@ -212,12 +212,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { difference |= ProjectDifference.WorkspaceProjectRemoved; } - else if ( - WorkspaceProject?.Id != workspaceProject?.Id || - WorkspaceProject?.Version != workspaceProject?.Version) + else { - // For now this is very naive. We will want to consider changing - // our logic here to be more robust. + // We always update the snapshot right now when the project changes. This is how + // we deal with changes to the content of C# sources. difference |= ProjectDifference.WorkspaceProjectChanged; } diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs index 05fb0f19f0..415ecc6da9 100644 --- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs +++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs @@ -538,8 +538,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem Assert.Equal(ProjectChangeKind.ProjectChanged, ProjectManager.ListenersNotifiedOf); } + // We always update the snapshot when someone calls WorkspaceProjectChanged. This is how we deal + // with changes to source code, which wouldn't result in a new project. [ForegroundFact] - public void WorkspaceProjectChanged_WithHostProject_CanNoOp() + public void WorkspaceProjectChanged_WithHostProject_NotifiesListeners() { // Arrange ProjectManager.HostProjectAdded(HostProject); @@ -553,6 +555,24 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem var snapshot = ProjectManager.GetSnapshot(WorkspaceProject); Assert.True(snapshot.IsInitialized); + Assert.Equal(ProjectChangeKind.ProjectChanged, ProjectManager.ListenersNotifiedOf); + } + + [ForegroundFact] + public void WorkspaceProjectChanged_WithHostProject_CanNoOpForSecondProject() + { + // Arrange + ProjectManager.HostProjectAdded(HostProject); + ProjectManager.WorkspaceProjectAdded(WorkspaceProject); + ProjectManager.Reset(); + + // Act + ProjectManager.WorkspaceProjectChanged(WorkspaceProjectWithDifferentTfm); + + // Assert + var snapshot = ProjectManager.GetSnapshot(WorkspaceProject); + Assert.True(snapshot.IsInitialized); + Assert.Null(ProjectManager.ListenersNotifiedOf); } diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/DocumentInfo/RazorDocumentInfoWindow.cs b/tooling/Microsoft.VisualStudio.RazorExtension/DocumentInfo/RazorDocumentInfoWindow.cs index ae2a02e6d1..08a40cc240 100644 --- a/tooling/Microsoft.VisualStudio.RazorExtension/DocumentInfo/RazorDocumentInfoWindow.cs +++ b/tooling/Microsoft.VisualStudio.RazorExtension/DocumentInfo/RazorDocumentInfoWindow.cs @@ -81,6 +81,10 @@ namespace Microsoft.VisualStudio.RazorExtension.DocumentInfo } var textBuffer = textView.BufferGraph.GetRazorBuffers().FirstOrDefault(); + if (textBuffer == null) + { + return; + } if (!_editorFactoryService.TryGetDocumentTracker(textBuffer, out _documentTracker)) { diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoToolWindow.cs b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoToolWindow.cs index 4f4640435e..c391b59a6c 100644 --- a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoToolWindow.cs +++ b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoToolWindow.cs @@ -120,6 +120,7 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo { changed = project; changed.Snapshot = new ProjectSnapshotViewModel(_projectManager.GetLoadedProject(e.ProjectFilePath)); + DataContext.LoadProjectInfo(); break; } } diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoToolWindowControl.xaml b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoToolWindowControl.xaml index 06840264d8..1db8a28d62 100644 --- a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoToolWindowControl.xaml +++ b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoToolWindowControl.xaml @@ -119,7 +119,7 @@ Command="{Binding UpdateCommand}" /> - + + - - + + - diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoViewModel.cs b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoViewModel.cs index 7dd7d34022..aaec39b6df 100644 --- a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoViewModel.cs +++ b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoViewModel.cs @@ -56,7 +56,7 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo _currentProject = value; OnPropertyChanged(); - LoadProjectInfo(_currentProject.Snapshot.Project); + LoadProjectInfo(); } } @@ -100,11 +100,11 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo } } } - - private async void LoadProjectInfo(ProjectSnapshot snapshot) + public async void LoadProjectInfo() { CurrentProjectInfo = new ProjectInfoViewModel(); + var snapshot = CurrentProject?.Snapshot.Project; if (snapshot == null) { return; From dbed73da328f3ad13da40c93ff0c8d93c2be42f9 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Thu, 3 May 2018 11:09:01 -0700 Subject: [PATCH 022/171] Remove ProjectService requirements for Razor text buffer initialization. - Now that we have our own content type the editor does the work of determining if a Razor file opened in a core project should flow to our code. --- .../DefaultRazorDocumentManager.cs | 35 +----------- .../DefaultRazorDocumentManagerTest.cs | 53 +++---------------- 2 files changed, 7 insertions(+), 81 deletions(-) diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultRazorDocumentManager.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultRazorDocumentManager.cs index 6be7e06fa9..7ceda03678 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultRazorDocumentManager.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultRazorDocumentManager.cs @@ -17,13 +17,11 @@ namespace Microsoft.VisualStudio.Editor.Razor { private readonly ForegroundDispatcher _foregroundDispatcher; private readonly RazorEditorFactoryService _editorFactoryService; - private readonly TextBufferProjectService _projectService; [ImportingConstructor] public DefaultRazorDocumentManager( ForegroundDispatcher dispatcher, - RazorEditorFactoryService editorFactoryService, - TextBufferProjectService projectService) + RazorEditorFactoryService editorFactoryService) { if (dispatcher == null) { @@ -35,14 +33,8 @@ namespace Microsoft.VisualStudio.Editor.Razor throw new ArgumentNullException(nameof(editorFactoryService)); } - if (projectService == null) - { - throw new ArgumentNullException(nameof(projectService)); - } - _foregroundDispatcher = dispatcher; _editorFactoryService = editorFactoryService; - _projectService = projectService; } public override void OnTextViewOpened(ITextView textView, IEnumerable subjectBuffers) @@ -66,11 +58,6 @@ namespace Microsoft.VisualStudio.Editor.Razor continue; } - if (!IsSupportedProject(textBuffer)) - { - return; - } - if (!_editorFactoryService.TryGetDocumentTracker(textBuffer, out var documentTracker) || !(documentTracker is DefaultVisualStudioDocumentTracker tracker)) { @@ -120,25 +107,5 @@ namespace Microsoft.VisualStudio.Editor.Razor } } } - - private bool IsSupportedProject(ITextBuffer textBuffer) - { - // Fundamentally we have a Razor half of the world as soon as the document is open - and then later - // the C# half of the world will be initialized. This code is in general pretty tolerant of - // unexpected /impossible states. - // - // We also want to successfully shut down if the buffer is something other than .cshtml. - object project = null; - var isSupportedProject = false; - - // We expect the document to have a hierarchy even if it's not a real 'project'. - // However the hierarchy can be null when the document is in the process of closing. - if ((project = _projectService.GetHostProject(textBuffer)) != null) - { - isSupportedProject = _projectService.IsSupportedProject(project); - } - - return isSupportedProject; - } } } diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultRazorDocumentManagerTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultRazorDocumentManagerTest.cs index 3cd9f5fb81..32ec4b10c7 100644 --- a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultRazorDocumentManagerTest.cs +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultRazorDocumentManagerTest.cs @@ -33,35 +33,12 @@ namespace Microsoft.VisualStudio.Editor.Razor private Workspace Workspace => TestWorkspace.Create(); - private TextBufferProjectService SupportedProjectService { get; } = Mock.Of( - s => s.GetHostProject(It.IsAny()) == Mock.Of() && - s.IsSupportedProject(It.IsAny()) == true && - s.GetProjectPath(It.IsAny()) == "C:/Some/Path/TestProject.csproj"); - - private TextBufferProjectService UnsupportedProjectService { get; } = Mock.Of(s => s.IsSupportedProject(It.IsAny()) == false); - - [ForegroundFact] - public void OnTextViewOpened_ForNonRazorCoreProject_DoesNothing() - { - // Arrange - var editorFactoryService = new Mock(MockBehavior.Strict); - var documentManager = new DefaultRazorDocumentManager(Dispatcher, editorFactoryService.Object, UnsupportedProjectService); - var textView = Mock.Of(); - var buffers = new Collection() - { - Mock.Of(b => b.ContentType == RazorCoreContentType && b.Properties == new PropertyCollection()), - }; - - // Act & Assert - documentManager.OnTextViewOpened(textView, buffers); - } - [ForegroundFact] public void OnTextViewOpened_ForNonRazorTextBuffer_DoesNothing() { // Arrange var editorFactoryService = new Mock(MockBehavior.Strict); - var documentManager = new DefaultRazorDocumentManager(Dispatcher, editorFactoryService.Object, SupportedProjectService); + var documentManager = new DefaultRazorDocumentManager(Dispatcher, editorFactoryService.Object); var textView = Mock.Of(); var buffers = new Collection() { @@ -83,7 +60,7 @@ namespace Microsoft.VisualStudio.Editor.Razor }; var documentTracker = new DefaultVisualStudioDocumentTracker(Dispatcher, FilePath, ProjectPath, ProjectManager, WorkspaceEditorSettings, Workspace, buffers[0], ImportDocumentManager) as VisualStudioDocumentTracker; var editorFactoryService = Mock.Of(factoryService => factoryService.TryGetDocumentTracker(It.IsAny(), out documentTracker) == true); - var documentManager = new DefaultRazorDocumentManager(Dispatcher, editorFactoryService, SupportedProjectService); + var documentManager = new DefaultRazorDocumentManager(Dispatcher, editorFactoryService); // Act documentManager.OnTextViewOpened(textView, buffers); @@ -104,7 +81,7 @@ namespace Microsoft.VisualStudio.Editor.Razor }; var documentTracker = new DefaultVisualStudioDocumentTracker(Dispatcher, FilePath, ProjectPath, ProjectManager, WorkspaceEditorSettings, Workspace, buffers[0], ImportDocumentManager) as VisualStudioDocumentTracker; var editorFactoryService = Mock.Of(f => f.TryGetDocumentTracker(It.IsAny(), out documentTracker) == true); - var documentManager = new DefaultRazorDocumentManager(Dispatcher, editorFactoryService, SupportedProjectService); + var documentManager = new DefaultRazorDocumentManager(Dispatcher, editorFactoryService); // Assert 1 Assert.False(documentTracker.IsSupportedProject); @@ -116,29 +93,11 @@ namespace Microsoft.VisualStudio.Editor.Razor Assert.True(documentTracker.IsSupportedProject); } - [ForegroundFact] - public void OnTextViewClosed_FoNonRazorCoreProject_DoesNothing() - { - // Arrange - var documentManager = new DefaultRazorDocumentManager(Dispatcher, Mock.Of(), UnsupportedProjectService); - var textView = Mock.Of(); - var buffers = new Collection() - { - Mock.Of(b => b.ContentType == RazorCoreContentType && b.Properties == new PropertyCollection()), - }; - - // Act - documentManager.OnTextViewClosed(textView, buffers); - - // Assert - Assert.False(buffers[0].Properties.ContainsProperty(typeof(VisualStudioDocumentTracker))); - } - [ForegroundFact] public void OnTextViewClosed_TextViewWithoutDocumentTracker_DoesNothing() { // Arrange - var documentManager = new DefaultRazorDocumentManager(Dispatcher, Mock.Of(), SupportedProjectService); + var documentManager = new DefaultRazorDocumentManager(Dispatcher, Mock.Of()); var textView = Mock.Of(); var buffers = new Collection() { @@ -176,7 +135,7 @@ namespace Microsoft.VisualStudio.Editor.Razor buffers[1].Properties.AddProperty(typeof(VisualStudioDocumentTracker), documentTracker); var editorFactoryService = Mock.Of(); - var documentManager = new DefaultRazorDocumentManager(Dispatcher, editorFactoryService, SupportedProjectService); + var documentManager = new DefaultRazorDocumentManager(Dispatcher, editorFactoryService); // Act documentManager.OnTextViewClosed(textView2, buffers); @@ -203,7 +162,7 @@ namespace Microsoft.VisualStudio.Editor.Razor var documentTracker = new DefaultVisualStudioDocumentTracker(Dispatcher, FilePath, ProjectPath, ProjectManager, WorkspaceEditorSettings, Workspace, buffers[0], ImportDocumentManager); buffers[0].Properties.AddProperty(typeof(VisualStudioDocumentTracker), documentTracker); var editorFactoryService = Mock.Of(); - var documentManager = new DefaultRazorDocumentManager(Dispatcher, editorFactoryService, SupportedProjectService); + var documentManager = new DefaultRazorDocumentManager(Dispatcher, editorFactoryService); // Populate the text views documentTracker.Subscribe(); From b538f22ad58da4b4cd79b9c1c5e7f353f11fc52c Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 3 May 2018 11:42:16 -0700 Subject: [PATCH 023/171] Add more UI for document generation --- .../RazorInfo/DocumentSnapshotViewModel.cs | 26 +++++++++++++++++++ .../RazorInfo/RazorInfoToolWindowControl.xaml | 16 ++++++++++++ 2 files changed, 42 insertions(+) diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DocumentSnapshotViewModel.cs b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DocumentSnapshotViewModel.cs index 653046b412..29aafc8cdb 100644 --- a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DocumentSnapshotViewModel.cs +++ b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DocumentSnapshotViewModel.cs @@ -9,9 +9,13 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo { public class DocumentSnapshotViewModel : NotifyPropertyChanged { + private double _progress; + internal DocumentSnapshotViewModel(DocumentSnapshot document) { Document = document; + + InitializeGeneratedDocument(); } internal DocumentSnapshot Document { get; } @@ -19,6 +23,28 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo public string FilePath => Document.FilePath; public string TargetPath => Document.TargetPath; + + public bool CodeGenerationInProgress => _progress < 100; + + public double CodeGenerationProgress => _progress; + + private async void InitializeGeneratedDocument() + { + _progress = 0; + OnPropertyChanged(nameof(CodeGenerationInProgress)); + OnPropertyChanged(nameof(CodeGenerationProgress)); + + try + { + await Document.GetGeneratedOutputAsync(); + } + finally + { + _progress = 100; + OnPropertyChanged(nameof(CodeGenerationInProgress)); + OnPropertyChanged(nameof(CodeGenerationProgress)); + } + } } } #endif \ No newline at end of file diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoToolWindowControl.xaml b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoToolWindowControl.xaml index 1db8a28d62..5ebd94fae5 100644 --- a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoToolWindowControl.xaml +++ b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoToolWindowControl.xaml @@ -301,6 +301,22 @@ + + + + + + + + + + Date: Sun, 6 May 2018 12:27:39 -0700 Subject: [PATCH 024/171] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 16 ++++++++-------- korebuild-lock.txt | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 0c1ab5ca83..16c2ef56ae 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -4,20 +4,20 @@ 0.10.13 - 2.2.0-preview1-17042 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 + 2.2.0-preview1-17047 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 15.6.82 15.6.82 15.6.82 2.8.0-beta3 2.8.0-beta3 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 2.2.0-preview1-26424-04 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 2.0.0 2.2.0-preview1-26424-04 15.6.1 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 5a9689541e..18df6940ae 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17042 -commithash:edf0705d014293c260de763543784330514db9a3 +version:2.2.0-preview1-17047 +commithash:e1957b52ddc8b62bd39c5c400322fccb5364624c From a9e7e4668dd0b4432bb504327d6f716f71ff10f5 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 3 May 2018 14:41:00 -0700 Subject: [PATCH 025/171] Bump Roslyn dependency to 2.8.0 (cherry picked from commit e9fc15de02b7376f81dedea494d8bbed82673ce1) --- build/dependencies.props | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 16c2ef56ae..dc0ff9df55 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -11,8 +11,8 @@ 15.6.82 15.6.82 15.6.82 - 2.8.0-beta3 - 2.8.0-beta3 + 2.8.0 + 2.8.0 2.2.0-preview1-34135 2.2.0-preview1-34135 2.2.0-preview1-26424-04 @@ -46,15 +46,15 @@ 4.3.0 4.5.0-preview3-26423-04 9.0.1 - 2.8.0-beta3 - 2.8.0-beta3 - 2.8.0-beta3 - 2.8.0-beta3 - 2.8.0-beta3 + 2.8.0 + 2.8.0 + 2.8.0 + 2.8.0 + 2.8.0 2.8.0-beta2-62721-09 - 2.8.0-beta3 - 2.8.0-beta3 - 2.8.0-beta3 + 2.8.0 + 2.8.0 + 2.8.0 2.8.0-beta2-62721-09 0.8.0 2.3.1 From efaaec6546c28d8d96f0112ebcd303840da8f05e Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Mon, 7 May 2018 15:14:53 -0700 Subject: [PATCH 026/171] Upgrade to netcoreapp22 --- Directory.Build.targets | 5 ++- build/dependencies.props | 39 ++++++++++--------- build/repo.props | 3 +- korebuild-lock.txt | 4 +- test/Directory.Build.props | 4 +- ...rosoft.AspNetCore.Razor.Design.Test.csproj | 2 +- ...soft.AspNetCore.Razor.Language.Test.csproj | 3 +- .../IntegrationTests/IntegrationTestBase.cs | 4 +- ...rosoft.AspNetCore.Razor.Test.Common.csproj | 4 +- ...tCore.Razor.Test.MvcShim.Version1_X.csproj | 4 +- ...osoft.AspNetCore.Razor.Test.MvcShim.csproj | 4 +- ...crosoft.AspNetCore.Razor.Tools.Test.csproj | 2 +- .../RazorPageGenerator.Test.csproj | 4 +- .../AppWithP2PReference.csproj | 2 +- test/testapps/SimpleMvc/SimpleMvc.csproj | 2 +- test/testapps/SimplePages/SimplePages.csproj | 2 +- 16 files changed, 46 insertions(+), 42 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index 53b3f6e1da..78626b773e 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,7 +1,10 @@ - + $(MicrosoftNETCoreApp20PackageVersion) $(MicrosoftNETCoreApp21PackageVersion) + $(MicrosoftNETCoreApp22PackageVersion) $(NETStandardLibrary20PackageVersion) + + 99.9 diff --git a/build/dependencies.props b/build/dependencies.props index dc0ff9df55..f315d1c9c2 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -1,25 +1,26 @@ - + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 0.10.13 - 2.2.0-preview1-17047 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 + 2.2.0-preview1-17048 + 2.2.0-preview1-34140 + 2.2.0-preview1-34140 + 2.2.0-preview1-34140 15.6.82 15.6.82 15.6.82 - 2.8.0 - 2.8.0 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 + 2.8.0-beta3 + 2.8.0-beta3 + 2.2.0-preview1-34140 + 2.2.0-preview1-34140 2.2.0-preview1-26424-04 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 + 2.2.0-preview1-34140 + 2.2.0-preview1-34140 2.0.0 2.2.0-preview1-26424-04 + 2.2.0-preview1-26502-01 15.6.1 15.0.26606 15.6.161-preview @@ -46,15 +47,15 @@ 4.3.0 4.5.0-preview3-26423-04 9.0.1 - 2.8.0 - 2.8.0 - 2.8.0 - 2.8.0 - 2.8.0 + 2.8.0-beta3 + 2.8.0-beta3 + 2.8.0-beta3 + 2.8.0-beta3 + 2.8.0-beta3 2.8.0-beta2-62721-09 - 2.8.0 - 2.8.0 - 2.8.0 + 2.8.0-beta3 + 2.8.0-beta3 + 2.8.0-beta3 2.8.0-beta2-62721-09 0.8.0 2.3.1 diff --git a/build/repo.props b/build/repo.props index fd60435af5..fef2fdc0eb 100644 --- a/build/repo.props +++ b/build/repo.props @@ -1,4 +1,4 @@ - + @@ -27,5 +27,6 @@ + diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 18df6940ae..da5dcd1202 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17047 -commithash:e1957b52ddc8b62bd39c5c400322fccb5364624c +version:2.2.0-preview1-17048 +commithash:de14a0ee5fb48508ee8a29c14280a2928f8dabf8 diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 8ad3d56d0e..1b12a7a57d 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -1,8 +1,8 @@ - + - netcoreapp2.1 + netcoreapp2.2 $(DeveloperBuildTestTfms) $(StandardTestTfms) net461;$(StandardTestTfms) diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/Microsoft.AspNetCore.Razor.Design.Test.csproj b/test/Microsoft.AspNetCore.Razor.Design.Test/Microsoft.AspNetCore.Razor.Design.Test.csproj index 22a3b54243..128fb066bf 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/Microsoft.AspNetCore.Razor.Design.Test.csproj +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/Microsoft.AspNetCore.Razor.Design.Test.csproj @@ -7,7 +7,7 @@ This is also a partial workaround for https://github.com/Microsoft/msbuild/issues/2661 - this project has netcoreapp2.0 dependencies that need to be built first. --> - netcoreapp2.1 + netcoreapp2.2 true $(DefineConstants);PRESERVE_WORKING_DIRECTORY diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Microsoft.AspNetCore.Razor.Language.Test.csproj b/test/Microsoft.AspNetCore.Razor.Language.Test/Microsoft.AspNetCore.Razor.Language.Test.csproj index 68d1243acf..57a0000191 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Microsoft.AspNetCore.Razor.Language.Test.csproj +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Microsoft.AspNetCore.Razor.Language.Test.csproj @@ -1,8 +1,7 @@ - + $(DeveloperBuildTestTfms) - $(TargetFrameworks) $(TargetFrameworks);net46 $(DefaultItemExcludes);TestFiles\**\* $(DefineConstants);GENERATE_BASELINES diff --git a/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntegrationTestBase.cs b/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntegrationTestBase.cs index 0d6dd241ce..c8c41c1f77 100644 --- a/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntegrationTestBase.cs +++ b/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntegrationTestBase.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -54,7 +54,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests { CallContext.LogicalSetData("IntegrationTestBase_FileName", new ObjectHandle(value)); } -#elif NETCOREAPP2_0 || NETCOREAPP2_1 +#elif NETCOREAPP2_2 get { return _fileName.Value; } set { _fileName.Value = value; } #endif diff --git a/test/Microsoft.AspNetCore.Razor.Test.Common/Microsoft.AspNetCore.Razor.Test.Common.csproj b/test/Microsoft.AspNetCore.Razor.Test.Common/Microsoft.AspNetCore.Razor.Test.Common.csproj index cf35d8f8d7..d1f72c96bb 100644 --- a/test/Microsoft.AspNetCore.Razor.Test.Common/Microsoft.AspNetCore.Razor.Test.Common.csproj +++ b/test/Microsoft.AspNetCore.Razor.Test.Common/Microsoft.AspNetCore.Razor.Test.Common.csproj @@ -1,10 +1,10 @@ - + $(DefineConstants);GENERATE_BASELINES $(DefineConstants);__RemoveThisBitTo__GENERATE_BASELINES - netcoreapp2.1;netcoreapp2.0;net46 + netcoreapp2.2;net46 diff --git a/test/Microsoft.AspNetCore.Razor.Test.MvcShim.Version1_X/Microsoft.AspNetCore.Razor.Test.MvcShim.Version1_X.csproj b/test/Microsoft.AspNetCore.Razor.Test.MvcShim.Version1_X/Microsoft.AspNetCore.Razor.Test.MvcShim.Version1_X.csproj index 1562a234c3..8c0ce3583f 100644 --- a/test/Microsoft.AspNetCore.Razor.Test.MvcShim.Version1_X/Microsoft.AspNetCore.Razor.Test.MvcShim.Version1_X.csproj +++ b/test/Microsoft.AspNetCore.Razor.Test.MvcShim.Version1_X/Microsoft.AspNetCore.Razor.Test.MvcShim.Version1_X.csproj @@ -1,7 +1,7 @@ - + - netcoreapp2.1;netcoreapp2.0;net461 + netcoreapp2.2;net461 true diff --git a/test/Microsoft.AspNetCore.Razor.Test.MvcShim/Microsoft.AspNetCore.Razor.Test.MvcShim.csproj b/test/Microsoft.AspNetCore.Razor.Test.MvcShim/Microsoft.AspNetCore.Razor.Test.MvcShim.csproj index 1562a234c3..8c0ce3583f 100644 --- a/test/Microsoft.AspNetCore.Razor.Test.MvcShim/Microsoft.AspNetCore.Razor.Test.MvcShim.csproj +++ b/test/Microsoft.AspNetCore.Razor.Test.MvcShim/Microsoft.AspNetCore.Razor.Test.MvcShim.csproj @@ -1,7 +1,7 @@ - + - netcoreapp2.1;netcoreapp2.0;net461 + netcoreapp2.2;net461 true diff --git a/test/Microsoft.AspNetCore.Razor.Tools.Test/Microsoft.AspNetCore.Razor.Tools.Test.csproj b/test/Microsoft.AspNetCore.Razor.Tools.Test/Microsoft.AspNetCore.Razor.Tools.Test.csproj index cef7bd4594..352f3a9e7d 100644 --- a/test/Microsoft.AspNetCore.Razor.Tools.Test/Microsoft.AspNetCore.Razor.Tools.Test.csproj +++ b/test/Microsoft.AspNetCore.Razor.Tools.Test/Microsoft.AspNetCore.Razor.Tools.Test.csproj @@ -1,7 +1,7 @@ - netcoreapp2.1 + netcoreapp2.2 diff --git a/test/RazorPageGenerator.Test/RazorPageGenerator.Test.csproj b/test/RazorPageGenerator.Test/RazorPageGenerator.Test.csproj index 6562721f98..414da38a02 100644 --- a/test/RazorPageGenerator.Test/RazorPageGenerator.Test.csproj +++ b/test/RazorPageGenerator.Test/RazorPageGenerator.Test.csproj @@ -1,7 +1,7 @@ - + - netcoreapp2.1 + netcoreapp2.2 $(DefineConstants);GENERATE_BASELINES $(DefineConstants);__RemoveThisBitTo__GENERATE_BASELINES diff --git a/test/testapps/AppWithP2PReference/AppWithP2PReference.csproj b/test/testapps/AppWithP2PReference/AppWithP2PReference.csproj index 6507b127b4..c15d826f9f 100644 --- a/test/testapps/AppWithP2PReference/AppWithP2PReference.csproj +++ b/test/testapps/AppWithP2PReference/AppWithP2PReference.csproj @@ -12,7 +12,7 @@ - netcoreapp2.1 + netcoreapp2.2 diff --git a/test/testapps/SimpleMvc/SimpleMvc.csproj b/test/testapps/SimpleMvc/SimpleMvc.csproj index 53b253536c..021c90eaa2 100644 --- a/test/testapps/SimpleMvc/SimpleMvc.csproj +++ b/test/testapps/SimpleMvc/SimpleMvc.csproj @@ -13,7 +13,7 @@ - netcoreapp2.1 + netcoreapp2.2 diff --git a/test/testapps/SimplePages/SimplePages.csproj b/test/testapps/SimplePages/SimplePages.csproj index 4a35836799..db5a44c5c5 100644 --- a/test/testapps/SimplePages/SimplePages.csproj +++ b/test/testapps/SimplePages/SimplePages.csproj @@ -13,7 +13,7 @@ - netcoreapp2.1 + netcoreapp2.2 From 0f2b315fe44aaff0212ff775d2bdcf6b73da5621 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 8 May 2018 14:44:51 -0700 Subject: [PATCH 027/171] Fixup Razor Sdk tests to work on netcoreapp2.2 --- build/dependencies.props | 42 +++++++++---------- build/repo.targets | 4 +- korebuild-lock.txt | 4 +- .../BuildVariables.cs | 21 ---------- .../BuildVariables.cs.template | 2 - .../InitializeTestProjectAttribute.cs | 2 +- .../MSBuildIntegrationTestBase.cs | 3 -- test/testapps/Directory.Build.props | 9 ++++ test/testapps/Directory.Build.targets | 4 ++ .../SimpleMvcFSharp/SimpleMvcFSharp.fsproj | 2 +- 10 files changed, 39 insertions(+), 54 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index f315d1c9c2..b0d3c2bcad 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -1,26 +1,26 @@ - + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 0.10.13 - 2.2.0-preview1-17048 - 2.2.0-preview1-34140 - 2.2.0-preview1-34140 - 2.2.0-preview1-34140 + 2.2.0-preview1-17049 + 2.2.0-preview1-34146 + 2.2.0-preview1-34146 + 2.2.0-preview1-34146 15.6.82 15.6.82 15.6.82 - 2.8.0-beta3 - 2.8.0-beta3 - 2.2.0-preview1-34140 - 2.2.0-preview1-34140 + 2.8.0 + 2.8.0 + 2.2.0-preview1-34146 + 2.2.0-preview1-34146 2.2.0-preview1-26424-04 - 2.2.0-preview1-34140 - 2.2.0-preview1-34140 + 2.2.0-preview1-34146 + 2.2.0-preview1-34146 2.0.0 - 2.2.0-preview1-26424-04 - 2.2.0-preview1-26502-01 + 2.1.0-rc1 + 2.2.0-preview1-26424-04 15.6.1 15.0.26606 15.6.161-preview @@ -47,15 +47,15 @@ 4.3.0 4.5.0-preview3-26423-04 9.0.1 - 2.8.0-beta3 - 2.8.0-beta3 - 2.8.0-beta3 - 2.8.0-beta3 - 2.8.0-beta3 + 2.8.0 + 2.8.0 + 2.8.0 + 2.8.0 + 2.8.0 2.8.0-beta2-62721-09 - 2.8.0-beta3 - 2.8.0-beta3 - 2.8.0-beta3 + 2.8.0 + 2.8.0 + 2.8.0 2.8.0-beta2-62721-09 0.8.0 2.3.1 diff --git a/build/repo.targets b/build/repo.targets index bf561aef6a..d4686f95a0 100644 --- a/build/repo.targets +++ b/build/repo.targets @@ -23,9 +23,7 @@ - MSBuildLocation=$(VisualStudioMSBuildx86Path); - MicrosoftNETCoreAppVersion=$(MicrosoftNETCoreApp21PackageVersion); - NETStandardLibraryVersion=$(NETStandardLibrary20PackageVersion) + MSBuildLocation=$(VisualStudioMSBuildx86Path) diff --git a/korebuild-lock.txt b/korebuild-lock.txt index da5dcd1202..eb63d54c23 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17048 -commithash:de14a0ee5fb48508ee8a29c14280a2928f8dabf8 +version:2.2.0-preview1-17049 +commithash:9c4fefdcd2b950f8b6da5aa3784fe200eb0664c2 diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/BuildVariables.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/BuildVariables.cs index cbf8a0f2a5..cb8b132417 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/BuildVariables.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/BuildVariables.cs @@ -6,9 +6,6 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests internal static partial class BuildVariables { private static string _msBuildPath = string.Empty; - private static string _microsoftNETCoreAppVersion = string.Empty; - - private static string _netStandardLibraryVersion = string.Empty; static partial void InitializeVariables(); @@ -20,23 +17,5 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests return _msBuildPath; } } - - public static string MicrosoftNETCoreAppVersion - { - get - { - InitializeVariables(); - return _microsoftNETCoreAppVersion; - } - } - - public static string NETStandardLibraryVersion - { - get - { - InitializeVariables(); - return _netStandardLibraryVersion; - } - } } } diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/BuildVariables.cs.template b/test/Microsoft.AspNetCore.Razor.Design.Test/BuildVariables.cs.template index 1fd8c7dcfe..e31b16adc7 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/BuildVariables.cs.template +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/BuildVariables.cs.template @@ -8,8 +8,6 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests static partial void InitializeVariables() { _msBuildPath = @"${MSBuildLocation}"; - _microsoftNETCoreAppVersion = "${MicrosoftNETCoreAppVersion}"; - _netStandardLibraryVersion = "${NETStandardLibraryVersion}"; } } } diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/InitializeTestProjectAttribute.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/InitializeTestProjectAttribute.cs index 5ab0a04c31..bcd954bb55 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/InitializeTestProjectAttribute.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/InitializeTestProjectAttribute.cs @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests } MSBuildIntegrationTestBase.Project = ProjectDirectory.Create(_originalProjectName, _testProjectName, _baseDirectory, _additionalProjects, _language); - MSBuildIntegrationTestBase.TargetFramework = _originalProjectName.StartsWith("ClassLibrary") ? "netstandard2.0" : "netcoreapp2.1"; + MSBuildIntegrationTestBase.TargetFramework = _originalProjectName.StartsWith("ClassLibrary") ? "netstandard2.0" : "netcoreapp2.2"; } public override void After(MethodInfo methodUnderTest) diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildIntegrationTestBase.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildIntegrationTestBase.cs index 44ba2256a4..60bc033cf7 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildIntegrationTestBase.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildIntegrationTestBase.cs @@ -67,9 +67,6 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests // Disable node-reuse. We don't want msbuild processes to stick around // once the test is completed. "/nr:false", - - $"/p:RuntimeFrameworkVersion={BuildVariables.MicrosoftNETCoreAppVersion}", - $"/p:NETStandardImplicitPackageVersion={BuildVariables.NETStandardLibraryVersion}", }; if (!suppressRestore) diff --git a/test/testapps/Directory.Build.props b/test/testapps/Directory.Build.props index 71e223d53c..50781d1e7c 100644 --- a/test/testapps/Directory.Build.props +++ b/test/testapps/Directory.Build.props @@ -10,6 +10,15 @@ $(SolutionRoot)src\Microsoft.NET.Sdk.Razor\build\netstandard2.0\Sdk.Razor.CurrentVersion.targets + + + + + 99.9 + + $(NETStandardLibrary20PackageVersion) + + false diff --git a/test/testapps/Directory.Build.targets b/test/testapps/Directory.Build.targets index e0191e25d7..fb43a5b4be 100644 --- a/test/testapps/Directory.Build.targets +++ b/test/testapps/Directory.Build.targets @@ -1,3 +1,7 @@ + + + $(MicrosoftNETCoreApp22PackageVersion) + diff --git a/test/testapps/SimpleMvcFSharp/SimpleMvcFSharp.fsproj b/test/testapps/SimpleMvcFSharp/SimpleMvcFSharp.fsproj index f3eb256786..ec09cf7a67 100644 --- a/test/testapps/SimpleMvcFSharp/SimpleMvcFSharp.fsproj +++ b/test/testapps/SimpleMvcFSharp/SimpleMvcFSharp.fsproj @@ -13,7 +13,7 @@ - netcoreapp2.1 + netcoreapp2.2 From 1d5245c4217d5640603e8117de1e4ce98417f694 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Tue, 8 May 2018 15:36:33 -0700 Subject: [PATCH 028/171] Reorganize file tracker This is precursor to a much bigger change. Pushing out some of the changes that have broad impact early to make it easier to review the real change. --- .../ProjectSystem/DefaultDocumentSnapshot.cs | 6 +- .../ProjectSystem/DefaultProjectSnapshot.cs | 6 +- .../DefaultProjectSnapshotManager.cs | 4 +- .../ProjectSystem/DocumentSnapshot.cs | 2 +- .../ProjectSystem/EphemeralProjectSnapshot.cs | 4 +- .../ProjectSystem/ProjectSnapshot.cs | 2 +- .../ProjectSystem/ProjectState.cs | 4 +- .../RazorServiceBase.cs | 2 +- .../DefaultImportDocumentManager.cs | 2 +- .../DefaultImportDocumentManagerFactory.cs | 3 +- .../Documents/DefaultFileChangeTracker.cs | 37 ++++++++++++ .../DefaultFileChangeTrackerFactory.cs | 22 +++++++ .../{ => Documents}/FileChangeEventArgs.cs | 2 +- .../{ => Documents}/FileChangeKind.cs | 2 +- .../{ => Documents}/FileChangeTracker.cs | 2 +- .../FileChangeTrackerFactory.cs | 4 +- .../ImportChangedEventArgs.cs | 1 + .../VisaulStudioFileChangeTracker.cs} | 8 +-- .../VisualStudioFileChangeTrackerFactory.cs} | 9 ++- ...lStudioFileChangeTrackerFactoryFactory.cs} | 20 +++---- .../VisualStudioMacFileChangeTracker.cs} | 7 +-- ...isualStudioMacFileChangeTrackerFactory.cs} | 9 ++- ...udioMacFileChangeTrackerFactoryFactory.cs} | 17 +++--- .../DefaultProjectSnapshotTest.cs | 6 +- .../ProjectSystem/ProjectStateTest.cs | 58 +++++++++---------- ...ultImportDocumentManagerIntegrationTest.cs | 4 +- .../DefaultImportDocumentManagerTest.cs | 1 + .../DefaultVisualStudioDocumentTrackerTest.cs | 1 + .../VisualStudioFileChangeTrackerTest.cs} | 15 +++-- .../VisualStudioMacFileChangeTrackerTest.cs} | 10 +--- 30 files changed, 159 insertions(+), 111 deletions(-) create mode 100644 src/Microsoft.VisualStudio.Editor.Razor/Documents/DefaultFileChangeTracker.cs create mode 100644 src/Microsoft.VisualStudio.Editor.Razor/Documents/DefaultFileChangeTrackerFactory.cs rename src/Microsoft.VisualStudio.Editor.Razor/{ => Documents}/FileChangeEventArgs.cs (89%) rename src/Microsoft.VisualStudio.Editor.Razor/{ => Documents}/FileChangeKind.cs (82%) rename src/Microsoft.VisualStudio.Editor.Razor/{ => Documents}/FileChangeTracker.cs (89%) rename src/Microsoft.VisualStudio.Editor.Razor/{ => Documents}/FileChangeTrackerFactory.cs (69%) rename src/Microsoft.VisualStudio.LanguageServices.Razor/{DefaultFileChangeTracker.cs => Documents/VisaulStudioFileChangeTracker.cs} (94%) rename src/Microsoft.VisualStudio.LanguageServices.Razor/{DefaultFileChangeTrackerFactory.cs => Documents/VisualStudioFileChangeTrackerFactory.cs} (80%) rename src/Microsoft.VisualStudio.LanguageServices.Razor/{DefaultFileChangeTrackerFactoryFactory.cs => Documents/VisualStudioFileChangeTrackerFactoryFactory.cs} (56%) rename src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/{DefaultFileChangeTracker.cs => Documents/VisualStudioMacFileChangeTracker.cs} (94%) rename src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/{DefaultFileChangeTrackerFactory.cs => Documents/VisualStudioMacFileChangeTrackerFactory.cs} (69%) rename src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/{DefaultFileChangeTrackerFactoryFactory.cs => Documents/VisualStudioMacFileChangeTrackerFactoryFactory.cs} (51%) rename test/Microsoft.VisualStudio.LanguageServices.Razor.Test/{DefaultFileChangeTrackerTest.cs => Documents/VisualStudioFileChangeTrackerTest.cs} (84%) rename test/Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test/{DefaultFileChangeTrackerTest.cs => Documents/VisualStudioMacFileChangeTrackerTest.cs} (89%) diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultDocumentSnapshot.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultDocumentSnapshot.cs index eb1c8be640..6ce3edb596 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultDocumentSnapshot.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultDocumentSnapshot.cs @@ -39,15 +39,15 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem return State.GeneratedOutput.GetGeneratedOutputInitializationTask(Project, this); } - public override bool TryGetGeneratedOutput(out RazorCodeDocument results) + public override bool TryGetGeneratedOutput(out RazorCodeDocument result) { if (State.GeneratedOutput.IsResultAvailable) { - results = State.GeneratedOutput.GetGeneratedOutputInitializationTask(Project, this).Result; + result = State.GeneratedOutput.GetGeneratedOutputInitializationTask(Project, this).Result; return true; } - results = null; + result = null; return false; } } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshot.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshot.cs index 2b644e182c..aba5e7a916 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshot.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshot.cs @@ -69,15 +69,15 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem return State.TagHelpers.GetTagHelperInitializationTask(this); } - public override bool TryGetTagHelpers(out IReadOnlyList results) + public override bool TryGetTagHelpers(out IReadOnlyList result) { if (State.TagHelpers.IsResultAvailable) { - results = State.TagHelpers.GetTagHelperInitializationTask(this).Result; + result = State.TagHelpers.GetTagHelperInitializationTask(this).Result; return true; } - results = null; + result = null; return false; } } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs index 8c3a5484a0..174b669c86 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs @@ -149,7 +149,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem if (_projects.TryGetValue(hostProject.FilePath, out var entry)) { - var state = entry.State.AddHostDocument(document); + var state = entry.State.WithAddedHostDocument(document); // Document updates can no-op. if (!object.ReferenceEquals(state, entry.State)) @@ -175,7 +175,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem _foregroundDispatcher.AssertForegroundThread(); if (_projects.TryGetValue(hostProject.FilePath, out var entry)) { - var state = entry.State.RemoveHostDocument(document); + var state = entry.State.WithRemovedHostDocument(document); // Document updates can no-op. if (!object.ReferenceEquals(state, entry.State)) diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs index 736d09c65c..5a86527bc9 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs @@ -14,6 +14,6 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem public abstract Task GetGeneratedOutputAsync(); - public abstract bool TryGetGeneratedOutput(out RazorCodeDocument results); + public abstract bool TryGetGeneratedOutput(out RazorCodeDocument result); } } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/EphemeralProjectSnapshot.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/EphemeralProjectSnapshot.cs index c05c4f6e32..cd87c074de 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/EphemeralProjectSnapshot.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/EphemeralProjectSnapshot.cs @@ -66,9 +66,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem return EmptyTagHelpers; } - public override bool TryGetTagHelpers(out IReadOnlyList results) + public override bool TryGetTagHelpers(out IReadOnlyList result) { - results = EmptyTagHelpers.Result; + result = EmptyTagHelpers.Result; return true; } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs index 1b6ce7abff..418d117ff2 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs @@ -27,6 +27,6 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem public abstract Task> GetTagHelpersAsync(); - public abstract bool TryGetTagHelpers(out IReadOnlyList results); + public abstract bool TryGetTagHelpers(out IReadOnlyList result); } } \ No newline at end of file diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs index e25da4db2d..53e6cc3b15 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs @@ -126,7 +126,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem } } - public ProjectState AddHostDocument(HostDocument hostDocument) + public ProjectState WithAddedHostDocument(HostDocument hostDocument) { if (hostDocument == null) { @@ -153,7 +153,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem return state; } - public ProjectState RemoveHostDocument(HostDocument hostDocument) + public ProjectState WithRemovedHostDocument(HostDocument hostDocument) { if (hostDocument == null) { diff --git a/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs b/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs index 91ac18ae16..289d75cc0f 100644 --- a/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs +++ b/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs @@ -86,7 +86,7 @@ namespace Microsoft.CodeAnalysis.Remote.Razor throw new NotImplementedException(); } - public override bool TryGetTagHelpers(out IReadOnlyList results) + public override bool TryGetTagHelpers(out IReadOnlyList result) { throw new NotImplementedException(); } diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultImportDocumentManager.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultImportDocumentManager.cs index 68985bc9c6..b095cfdbb7 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultImportDocumentManager.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultImportDocumentManager.cs @@ -4,10 +4,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Linq; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.Razor; +using Microsoft.VisualStudio.Editor.Razor.Documents; namespace Microsoft.VisualStudio.Editor.Razor { diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultImportDocumentManagerFactory.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultImportDocumentManagerFactory.cs index c44417d69f..1a0596d25e 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultImportDocumentManagerFactory.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultImportDocumentManagerFactory.cs @@ -6,6 +6,7 @@ using System.Composition; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Razor; +using Microsoft.VisualStudio.Editor.Razor.Documents; namespace Microsoft.VisualStudio.Editor.Razor { @@ -34,7 +35,7 @@ namespace Microsoft.VisualStudio.Editor.Razor } var errorReporter = languageServices.WorkspaceServices.GetRequiredService(); - var fileChangeTrackerFactory = languageServices.GetRequiredService(); + var fileChangeTrackerFactory = languageServices.WorkspaceServices.GetRequiredService(); return new DefaultImportDocumentManager(_foregroundDispatcher, errorReporter, fileChangeTrackerFactory); } diff --git a/src/Microsoft.VisualStudio.Editor.Razor/Documents/DefaultFileChangeTracker.cs b/src/Microsoft.VisualStudio.Editor.Razor/Documents/DefaultFileChangeTracker.cs new file mode 100644 index 0000000000..95cfcfa5eb --- /dev/null +++ b/src/Microsoft.VisualStudio.Editor.Razor/Documents/DefaultFileChangeTracker.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.VisualStudio.Editor.Razor.Documents +{ + // A noop implementation for non-ide cases + internal class DefaultFileChangeTracker : FileChangeTracker + { + public override event EventHandler Changed; + + public DefaultFileChangeTracker(string filePath) + { + if (filePath == null) + { + throw new ArgumentNullException(nameof(filePath)); + } + + FilePath = filePath; + + GC.KeepAlive(Changed); + } + + public override string FilePath { get; } + + public override void StartListening() + { + // Do nothing + } + + public override void StopListening() + { + // Do nothing + } + } +} diff --git a/src/Microsoft.VisualStudio.Editor.Razor/Documents/DefaultFileChangeTrackerFactory.cs b/src/Microsoft.VisualStudio.Editor.Razor/Documents/DefaultFileChangeTrackerFactory.cs new file mode 100644 index 0000000000..6a982d119d --- /dev/null +++ b/src/Microsoft.VisualStudio.Editor.Razor/Documents/DefaultFileChangeTrackerFactory.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.VisualStudio.Editor.Razor.Documents +{ + [ExportWorkspaceService(typeof(FileChangeTrackerFactory), layer: ServiceLayer.Editor)] + internal class DefaultFileChangeTrackerFactory : FileChangeTrackerFactory + { + public override FileChangeTracker Create(string filePath) + { + if (filePath == null) + { + throw new ArgumentNullException(nameof(filePath)); + } + + return new DefaultFileChangeTracker(filePath); + } + } +} diff --git a/src/Microsoft.VisualStudio.Editor.Razor/FileChangeEventArgs.cs b/src/Microsoft.VisualStudio.Editor.Razor/Documents/FileChangeEventArgs.cs similarity index 89% rename from src/Microsoft.VisualStudio.Editor.Razor/FileChangeEventArgs.cs rename to src/Microsoft.VisualStudio.Editor.Razor/Documents/FileChangeEventArgs.cs index 508c27381a..b7541308d4 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/FileChangeEventArgs.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/Documents/FileChangeEventArgs.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.VisualStudio.Editor.Razor +namespace Microsoft.VisualStudio.Editor.Razor.Documents { internal sealed class FileChangeEventArgs : EventArgs { diff --git a/src/Microsoft.VisualStudio.Editor.Razor/FileChangeKind.cs b/src/Microsoft.VisualStudio.Editor.Razor/Documents/FileChangeKind.cs similarity index 82% rename from src/Microsoft.VisualStudio.Editor.Razor/FileChangeKind.cs rename to src/Microsoft.VisualStudio.Editor.Razor/Documents/FileChangeKind.cs index f5be58db77..7c3fff9d8f 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/FileChangeKind.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/Documents/FileChangeKind.cs @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -namespace Microsoft.VisualStudio.Editor.Razor +namespace Microsoft.VisualStudio.Editor.Razor.Documents { internal enum FileChangeKind { diff --git a/src/Microsoft.VisualStudio.Editor.Razor/FileChangeTracker.cs b/src/Microsoft.VisualStudio.Editor.Razor/Documents/FileChangeTracker.cs similarity index 89% rename from src/Microsoft.VisualStudio.Editor.Razor/FileChangeTracker.cs rename to src/Microsoft.VisualStudio.Editor.Razor/Documents/FileChangeTracker.cs index 3c51bebdf6..d34c42572a 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/FileChangeTracker.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/Documents/FileChangeTracker.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.VisualStudio.Editor.Razor +namespace Microsoft.VisualStudio.Editor.Razor.Documents { internal abstract class FileChangeTracker { diff --git a/src/Microsoft.VisualStudio.Editor.Razor/FileChangeTrackerFactory.cs b/src/Microsoft.VisualStudio.Editor.Razor/Documents/FileChangeTrackerFactory.cs similarity index 69% rename from src/Microsoft.VisualStudio.Editor.Razor/FileChangeTrackerFactory.cs rename to src/Microsoft.VisualStudio.Editor.Razor/Documents/FileChangeTrackerFactory.cs index f3b57f72a0..febad89890 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/FileChangeTrackerFactory.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/Documents/FileChangeTrackerFactory.cs @@ -3,9 +3,9 @@ using Microsoft.CodeAnalysis.Host; -namespace Microsoft.VisualStudio.Editor.Razor +namespace Microsoft.VisualStudio.Editor.Razor.Documents { - internal abstract class FileChangeTrackerFactory : ILanguageService + internal abstract class FileChangeTrackerFactory : IWorkspaceService { public abstract FileChangeTracker Create(string filePath); } diff --git a/src/Microsoft.VisualStudio.Editor.Razor/ImportChangedEventArgs.cs b/src/Microsoft.VisualStudio.Editor.Razor/ImportChangedEventArgs.cs index 8444943bd6..ffe70a4788 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/ImportChangedEventArgs.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/ImportChangedEventArgs.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.VisualStudio.Editor.Razor.Documents; namespace Microsoft.VisualStudio.Editor.Razor { diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultFileChangeTracker.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/VisaulStudioFileChangeTracker.cs similarity index 94% rename from src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultFileChangeTracker.cs rename to src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/VisaulStudioFileChangeTracker.cs index e2d164e806..86e0fab955 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultFileChangeTracker.cs +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/VisaulStudioFileChangeTracker.cs @@ -2,15 +2,13 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.CodeAnalysis.Razor; -using Microsoft.VisualStudio.Editor.Razor; using Microsoft.VisualStudio.Shell.Interop; -namespace Microsoft.VisualStudio.LanguageServices.Razor +namespace Microsoft.VisualStudio.Editor.Razor.Documents { - internal class DefaultFileChangeTracker : FileChangeTracker, IVsFileChangeEvents + internal class VisualStudioFileChangeTracker : FileChangeTracker, IVsFileChangeEvents { private const uint FileChangeFlags = (uint)(_VSFILECHANGEFLAGS.VSFILECHG_Time | _VSFILECHANGEFLAGS.VSFILECHG_Size | _VSFILECHANGEFLAGS.VSFILECHG_Del | _VSFILECHANGEFLAGS.VSFILECHG_Add); @@ -21,7 +19,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor public override event EventHandler Changed; - public DefaultFileChangeTracker( + public VisualStudioFileChangeTracker( string filePath, ForegroundDispatcher foregroundDispatcher, ErrorReporter errorReporter, diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultFileChangeTrackerFactory.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/VisualStudioFileChangeTrackerFactory.cs similarity index 80% rename from src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultFileChangeTrackerFactory.cs rename to src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/VisualStudioFileChangeTrackerFactory.cs index 6c6515be4a..820d79fbbe 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultFileChangeTrackerFactory.cs +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/VisualStudioFileChangeTrackerFactory.cs @@ -3,18 +3,17 @@ using System; using Microsoft.CodeAnalysis.Razor; -using Microsoft.VisualStudio.Editor.Razor; using Microsoft.VisualStudio.Shell.Interop; -namespace Microsoft.VisualStudio.LanguageServices.Razor +namespace Microsoft.VisualStudio.Editor.Razor.Documents { - internal partial class DefaultFileChangeTrackerFactory : FileChangeTrackerFactory + internal class VisualStudioFileChangeTrackerFactory : FileChangeTrackerFactory { private readonly ForegroundDispatcher _foregroundDispatcher; private readonly ErrorReporter _errorReporter; private readonly IVsFileChangeEx _fileChangeService; - public DefaultFileChangeTrackerFactory( + public VisualStudioFileChangeTrackerFactory( ForegroundDispatcher foregroundDispatcher, ErrorReporter errorReporter, IVsFileChangeEx fileChangeService) @@ -46,7 +45,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(filePath)); } - var fileChangeTracker = new DefaultFileChangeTracker(filePath, _foregroundDispatcher, _errorReporter, _fileChangeService); + var fileChangeTracker = new VisualStudioFileChangeTracker(filePath, _foregroundDispatcher, _errorReporter, _fileChangeService); return fileChangeTracker; } } diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultFileChangeTrackerFactoryFactory.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/VisualStudioFileChangeTrackerFactoryFactory.cs similarity index 56% rename from src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultFileChangeTrackerFactoryFactory.cs rename to src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/VisualStudioFileChangeTrackerFactoryFactory.cs index 4ecc2a7230..705f1b3e52 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultFileChangeTrackerFactoryFactory.cs +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/VisualStudioFileChangeTrackerFactoryFactory.cs @@ -6,21 +6,20 @@ using System.Composition; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Razor; -using Microsoft.VisualStudio.Editor.Razor; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; -namespace Microsoft.VisualStudio.LanguageServices.Razor +namespace Microsoft.VisualStudio.Editor.Razor.Documents { [Shared] - [ExportLanguageServiceFactory(typeof(FileChangeTrackerFactory), RazorLanguage.Name, ServiceLayer.Default)] - internal class DefaultFileChangeTrackerFactoryFactory : ILanguageServiceFactory + [ExportWorkspaceServiceFactory(typeof(FileChangeTrackerFactory), ServiceLayer.Host)] + internal class VisualStudioFileChangeTrackerFactoryFactory : IWorkspaceServiceFactory { private readonly IVsFileChangeEx _fileChangeService; private readonly ForegroundDispatcher _foregroundDispatcher; [ImportingConstructor] - public DefaultFileChangeTrackerFactoryFactory(ForegroundDispatcher foregroundDispatcher, SVsServiceProvider serviceProvider) + public VisualStudioFileChangeTrackerFactoryFactory(ForegroundDispatcher foregroundDispatcher, SVsServiceProvider serviceProvider) { if (foregroundDispatcher == null) { @@ -35,16 +34,15 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor _foregroundDispatcher = foregroundDispatcher; _fileChangeService = serviceProvider.GetService(typeof(SVsFileChangeEx)) as IVsFileChangeEx; } - - public ILanguageService CreateLanguageService(HostLanguageServices languageServices) + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) { - if (languageServices == null) + if (workspaceServices == null) { - throw new ArgumentNullException(nameof(languageServices)); + throw new ArgumentNullException(nameof(workspaceServices)); } - var errorReporter = languageServices.WorkspaceServices.GetRequiredService(); - return new DefaultFileChangeTrackerFactory(_foregroundDispatcher, errorReporter, _fileChangeService); + var errorReporter = workspaceServices.GetRequiredService(); + return new VisualStudioFileChangeTrackerFactory(_foregroundDispatcher, errorReporter, _fileChangeService); } } } diff --git a/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/DefaultFileChangeTracker.cs b/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/Documents/VisualStudioMacFileChangeTracker.cs similarity index 94% rename from src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/DefaultFileChangeTracker.cs rename to src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/Documents/VisualStudioMacFileChangeTracker.cs index 2848dadb88..327f632e36 100644 --- a/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/DefaultFileChangeTracker.cs +++ b/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/Documents/VisualStudioMacFileChangeTracker.cs @@ -3,12 +3,11 @@ using System; using Microsoft.CodeAnalysis.Razor; -using Microsoft.VisualStudio.Editor.Razor; using MonoDevelop.Core; -namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor +namespace Microsoft.VisualStudio.Editor.Razor.Documents { - internal class DefaultFileChangeTracker : FileChangeTracker + internal class VisualStudioMacFileChangeTracker : FileChangeTracker { private readonly ForegroundDispatcher _foregroundDispatcher; private readonly string _normalizedFilePath; @@ -16,7 +15,7 @@ namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor public override event EventHandler Changed; - public DefaultFileChangeTracker( + public VisualStudioMacFileChangeTracker( string filePath, ForegroundDispatcher foregroundDispatcher) { diff --git a/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/DefaultFileChangeTrackerFactory.cs b/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/Documents/VisualStudioMacFileChangeTrackerFactory.cs similarity index 69% rename from src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/DefaultFileChangeTrackerFactory.cs rename to src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/Documents/VisualStudioMacFileChangeTrackerFactory.cs index 33aa73fd41..81c6e4431c 100644 --- a/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/DefaultFileChangeTrackerFactory.cs +++ b/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/Documents/VisualStudioMacFileChangeTrackerFactory.cs @@ -3,15 +3,14 @@ using System; using Microsoft.CodeAnalysis.Razor; -using Microsoft.VisualStudio.Editor.Razor; -namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor +namespace Microsoft.VisualStudio.Editor.Razor.Documents { - internal class DefaultFileChangeTrackerFactory : FileChangeTrackerFactory + internal class VisualStudioMacFileChangeTrackerFactory : FileChangeTrackerFactory { private readonly ForegroundDispatcher _foregroundDispatcher; - public DefaultFileChangeTrackerFactory(ForegroundDispatcher foregroundDispatcher) + public VisualStudioMacFileChangeTrackerFactory(ForegroundDispatcher foregroundDispatcher) { if (foregroundDispatcher == null) { @@ -28,7 +27,7 @@ namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(filePath)); } - var fileChangeTracker = new DefaultFileChangeTracker(filePath, _foregroundDispatcher); + var fileChangeTracker = new VisualStudioMacFileChangeTracker(filePath, _foregroundDispatcher); return fileChangeTracker; } } diff --git a/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/DefaultFileChangeTrackerFactoryFactory.cs b/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/Documents/VisualStudioMacFileChangeTrackerFactoryFactory.cs similarity index 51% rename from src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/DefaultFileChangeTrackerFactoryFactory.cs rename to src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/Documents/VisualStudioMacFileChangeTrackerFactoryFactory.cs index d30ae0e667..7eb83a3a48 100644 --- a/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/DefaultFileChangeTrackerFactoryFactory.cs +++ b/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/Documents/VisualStudioMacFileChangeTrackerFactoryFactory.cs @@ -6,18 +6,17 @@ using System.Composition; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Razor; -using Microsoft.VisualStudio.Editor.Razor; -namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor +namespace Microsoft.VisualStudio.Editor.Razor.Documents { [Shared] - [ExportLanguageServiceFactory(typeof(FileChangeTrackerFactory), RazorLanguage.Name, ServiceLayer.Default)] - internal class DefaultFileChangeTrackerFactoryFactory : ILanguageServiceFactory + [ExportWorkspaceService(typeof(FileChangeTrackerFactory), ServiceLayer.Host)] + internal class VisualStudioMacFileChangeTrackerFactoryFactory : IWorkspaceServiceFactory { private readonly ForegroundDispatcher _foregroundDispatcher; [ImportingConstructor] - public DefaultFileChangeTrackerFactoryFactory(ForegroundDispatcher foregroundDispatcher) + public VisualStudioMacFileChangeTrackerFactoryFactory(ForegroundDispatcher foregroundDispatcher) { if (foregroundDispatcher == null) { @@ -27,14 +26,14 @@ namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor _foregroundDispatcher = foregroundDispatcher; } - public ILanguageService CreateLanguageService(HostLanguageServices languageServices) + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) { - if (languageServices == null) + if (workspaceServices == null) { - throw new ArgumentNullException(nameof(languageServices)); + throw new ArgumentNullException(nameof(workspaceServices)); } - return new DefaultFileChangeTrackerFactory(_foregroundDispatcher); + return new VisualStudioMacFileChangeTrackerFactory(_foregroundDispatcher); } } } diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotTest.cs index b3b0ba50d7..611a110435 100644 --- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotTest.cs +++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotTest.cs @@ -77,9 +77,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { // Arrange var state = new ProjectState(Workspace.Services, HostProject, WorkspaceProject) - .AddHostDocument(Documents[0]) - .AddHostDocument(Documents[1]) - .AddHostDocument(Documents[2]); + .WithAddedHostDocument(Documents[0]) + .WithAddedHostDocument(Documents[1]) + .WithAddedHostDocument(Documents[2]); var snapshot = new DefaultProjectSnapshot(state); // Act diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateTest.cs index 1641639e1a..53e3f000de 100644 --- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateTest.cs +++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateTest.cs @@ -106,7 +106,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem var original = new ProjectState(Workspace.Services, HostProject, WorkspaceProject); // Act - var state = original.AddHostDocument(Documents[0]); + var state = original.WithAddedHostDocument(Documents[0]); // Assert Assert.NotEqual(original.Version, state.Version); @@ -121,11 +121,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { // Arrange var original = new ProjectState(Workspace.Services, HostProject, WorkspaceProject) - .AddHostDocument(Documents[2]) - .AddHostDocument(Documents[1]); + .WithAddedHostDocument(Documents[2]) + .WithAddedHostDocument(Documents[1]); // Act - var state = original.AddHostDocument(Documents[0]); + var state = original.WithAddedHostDocument(Documents[0]); // Assert Assert.NotEqual(original.Version, state.Version); @@ -142,15 +142,15 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { // Arrange var original = new ProjectState(Workspace.Services, HostProject, WorkspaceProject) - .AddHostDocument(Documents[2]) - .AddHostDocument(Documents[1]); + .WithAddedHostDocument(Documents[2]) + .WithAddedHostDocument(Documents[1]); // Force init GC.KeepAlive(original.ProjectEngine); GC.KeepAlive(original.TagHelpers); // Act - var state = original.AddHostDocument(Documents[0]); + var state = original.WithAddedHostDocument(Documents[0]); // Assert Assert.Same(original.ProjectEngine, state.ProjectEngine); @@ -165,11 +165,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { // Arrange var original = new ProjectState(Workspace.Services, HostProject, WorkspaceProject) - .AddHostDocument(Documents[2]) - .AddHostDocument(Documents[1]); + .WithAddedHostDocument(Documents[2]) + .WithAddedHostDocument(Documents[1]); // Act - var state = original.AddHostDocument(new HostDocument(Documents[1].FilePath, "SomePath.cshtml")); + var state = original.WithAddedHostDocument(new HostDocument(Documents[1].FilePath, "SomePath.cshtml")); // Assert Assert.Same(original, state); @@ -180,11 +180,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { // Arrange var original = new ProjectState(Workspace.Services, HostProject, WorkspaceProject) - .AddHostDocument(Documents[2]) - .AddHostDocument(Documents[1]); + .WithAddedHostDocument(Documents[2]) + .WithAddedHostDocument(Documents[1]); // Act - var state = original.RemoveHostDocument(Documents[1]); + var state = original.WithRemovedHostDocument(Documents[1]); // Assert Assert.NotEqual(original.Version, state.Version); @@ -199,15 +199,15 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { // Arrange var original = new ProjectState(Workspace.Services, HostProject, WorkspaceProject) - .AddHostDocument(Documents[2]) - .AddHostDocument(Documents[1]); + .WithAddedHostDocument(Documents[2]) + .WithAddedHostDocument(Documents[1]); // Force init GC.KeepAlive(original.ProjectEngine); GC.KeepAlive(original.TagHelpers); // Act - var state = original.RemoveHostDocument(Documents[2]); + var state = original.WithRemovedHostDocument(Documents[2]); // Assert Assert.Same(original.ProjectEngine, state.ProjectEngine); @@ -221,11 +221,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { // Arrange var original = new ProjectState(Workspace.Services, HostProject, WorkspaceProject) - .AddHostDocument(Documents[2]) - .AddHostDocument(Documents[1]); + .WithAddedHostDocument(Documents[2]) + .WithAddedHostDocument(Documents[1]); // Act - var state = original.RemoveHostDocument(Documents[0]); + var state = original.WithRemovedHostDocument(Documents[0]); // Assert Assert.Same(original, state); @@ -236,8 +236,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { // Arrange var original = new ProjectState(Workspace.Services, HostProject, WorkspaceProject) - .AddHostDocument(Documents[2]) - .AddHostDocument(Documents[1]); + .WithAddedHostDocument(Documents[2]) + .WithAddedHostDocument(Documents[1]); // Force init GC.KeepAlive(original.ProjectEngine); @@ -262,8 +262,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { // Arrange var original = new ProjectState(Workspace.Services, HostProject, WorkspaceProject) - .AddHostDocument(Documents[2]) - .AddHostDocument(Documents[1]); + .WithAddedHostDocument(Documents[2]) + .WithAddedHostDocument(Documents[1]); // Force init GC.KeepAlive(original.ProjectEngine); @@ -281,8 +281,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { // Arrange var original = new ProjectState(Workspace.Services, HostProject, WorkspaceProject) - .AddHostDocument(Documents[2]) - .AddHostDocument(Documents[1]); + .WithAddedHostDocument(Documents[2]) + .WithAddedHostDocument(Documents[1]); // Force init GC.KeepAlive(original.ProjectEngine); @@ -307,8 +307,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { // Arrange var original = new ProjectState(Workspace.Services, HostProject, null) - .AddHostDocument(Documents[2]) - .AddHostDocument(Documents[1]); + .WithAddedHostDocument(Documents[2]) + .WithAddedHostDocument(Documents[1]); // Force init GC.KeepAlive(original.ProjectEngine); @@ -333,8 +333,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { // Arrange var original = new ProjectState(Workspace.Services, HostProject, WorkspaceProject) - .AddHostDocument(Documents[2]) - .AddHostDocument(Documents[1]); + .WithAddedHostDocument(Documents[2]) + .WithAddedHostDocument(Documents[1]); // Force init GC.KeepAlive(original.ProjectEngine); diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerIntegrationTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerIntegrationTest.cs index 941a7c7645..9fd3cf04ea 100644 --- a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerIntegrationTest.cs +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerIntegrationTest.cs @@ -1,16 +1,14 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.IO; -using Microsoft.AspNetCore.Mvc.Razor.Extensions; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Moq; using Xunit; -namespace Microsoft.VisualStudio.Editor.Razor +namespace Microsoft.VisualStudio.Editor.Razor.Documents { public class DefaultImportDocumentManagerIntegrationTest : ForegroundDispatcherTestBase { diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerTest.cs index fc6e83f8e6..a84a26fb04 100644 --- a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerTest.cs +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerTest.cs @@ -6,6 +6,7 @@ using System.IO; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.VisualStudio.Editor.Razor.Documents; using Moq; using Xunit; diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioDocumentTrackerTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioDocumentTrackerTest.cs index f14f8bbfd8..352c49ecc3 100644 --- a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioDocumentTrackerTest.cs +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioDocumentTrackerTest.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.Editor; using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.VisualStudio.Editor.Razor.Documents; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities; diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/DefaultFileChangeTrackerTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Documents/VisualStudioFileChangeTrackerTest.cs similarity index 84% rename from test/Microsoft.VisualStudio.LanguageServices.Razor.Test/DefaultFileChangeTrackerTest.cs rename to test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Documents/VisualStudioFileChangeTrackerTest.cs index bb7d32669d..fcef648173 100644 --- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/DefaultFileChangeTrackerTest.cs +++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Documents/VisualStudioFileChangeTrackerTest.cs @@ -3,14 +3,13 @@ using System; using Microsoft.CodeAnalysis.Razor; -using Microsoft.VisualStudio.Editor.Razor; using Microsoft.VisualStudio.Shell.Interop; using Moq; using Xunit; -namespace Microsoft.VisualStudio.LanguageServices.Razor +namespace Microsoft.VisualStudio.Editor.Razor.Documents { - public class DefaultFileChangeTrackerTest : ForegroundDispatcherTestBase + public class VisualStudioFileChangeTrackerTest : ForegroundDispatcherTestBase { private ErrorReporter ErrorReporter { get; } = new DefaultErrorReporter(); @@ -24,7 +23,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor .Setup(f => f.AdviseFileChange(It.IsAny(), It.IsAny(), It.IsAny(), out cookie)) .Returns(VSConstants.S_OK) .Verifiable(); - var tracker = new DefaultFileChangeTracker("C:/_ViewImports.cshtml", Dispatcher, ErrorReporter, fileChangeService.Object); + var tracker = new VisualStudioFileChangeTracker("C:/_ViewImports.cshtml", Dispatcher, ErrorReporter, fileChangeService.Object); // Act tracker.StartListening(); @@ -44,7 +43,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor .Setup(f => f.AdviseFileChange(It.IsAny(), It.IsAny(), It.IsAny(), out cookie)) .Returns(VSConstants.S_OK) .Callback(() => callCount++); - var tracker = new DefaultFileChangeTracker("C:/_ViewImports.cshtml", Dispatcher, ErrorReporter, fileChangeService.Object); + var tracker = new VisualStudioFileChangeTracker("C:/_ViewImports.cshtml", Dispatcher, ErrorReporter, fileChangeService.Object); tracker.StartListening(); // Act @@ -68,7 +67,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor .Setup(f => f.UnadviseFileChange(cookie)) .Returns(VSConstants.S_OK) .Verifiable(); - var tracker = new DefaultFileChangeTracker("C:/_ViewImports.cshtml", Dispatcher, ErrorReporter, fileChangeService.Object); + var tracker = new VisualStudioFileChangeTracker("C:/_ViewImports.cshtml", Dispatcher, ErrorReporter, fileChangeService.Object); tracker.StartListening(); // Start listening for changes. // Act @@ -87,7 +86,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor fileChangeService .Setup(f => f.UnadviseFileChange(cookie)) .Throws(new InvalidOperationException()); - var tracker = new DefaultFileChangeTracker("C:/_ViewImports.cshtml", Dispatcher, ErrorReporter, fileChangeService.Object); + var tracker = new VisualStudioFileChangeTracker("C:/_ViewImports.cshtml", Dispatcher, ErrorReporter, fileChangeService.Object); // Act & Assert tracker.StopListening(); @@ -107,7 +106,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor fileChangeService .Setup(f => f.AdviseFileChange(It.IsAny(), It.IsAny(), It.IsAny(), out cookie)) .Returns(VSConstants.S_OK); - var tracker = new DefaultFileChangeTracker(filePath, Dispatcher, ErrorReporter, fileChangeService.Object); + var tracker = new VisualStudioFileChangeTracker(filePath, Dispatcher, ErrorReporter, fileChangeService.Object); var called = false; tracker.Changed += (sender, args) => diff --git a/test/Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test/DefaultFileChangeTrackerTest.cs b/test/Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test/Documents/VisualStudioMacFileChangeTrackerTest.cs similarity index 89% rename from test/Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test/DefaultFileChangeTrackerTest.cs rename to test/Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test/Documents/VisualStudioMacFileChangeTrackerTest.cs index a605ae7258..6fc7306986 100644 --- a/test/Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test/DefaultFileChangeTrackerTest.cs +++ b/test/Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test/Documents/VisualStudioMacFileChangeTrackerTest.cs @@ -1,16 +1,12 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using Microsoft.CodeAnalysis.Razor; -using Microsoft.VisualStudio.Editor.Razor; -using MonoDevelop.Core; -using Moq; using Xunit; -namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor +namespace Microsoft.VisualStudio.Editor.Razor.Documents { - public class DefaultFileChangeTrackerTest : ForegroundDispatcherTestBase + public class VisualStudioMacFileChangeTrackerTest : ForegroundDispatcherTestBase { [ForegroundFact] public void StartListening_AdvisesForFileChange() @@ -69,7 +65,7 @@ namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor Assert.Equal(0, tracker.DetachFromFileServiceEventsCount); } - private class TestFileChangeTracker : DefaultFileChangeTracker + private class TestFileChangeTracker : VisualStudioMacFileChangeTracker { public TestFileChangeTracker( string filePath, From e0e1c39cce95573f6e68c0d9779730a7ac9cbf77 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Tue, 8 May 2018 11:54:08 -0700 Subject: [PATCH 029/171] Added a LiveShareWorkspaceProvider to enable location of the remote workspace in live share scenarios. - MEF is the primary means of resolving the new live share provider therefore we allow it to not be registered. - The new contract is in the Editor.Razor binary so the LiveShare bits don't have to take the dependency on the windows binary in Razor (has a lot of baggage). - This is specific to live share but providing a generic way to resolve workspaces didn't seem reasonable given the varying expectations in VS4Mac. If we need to make a more generic solution in the future we'll revisit this; for now this is a straight forward inclusion of live share functionality. - Added tests to validate the new behavior. - This unblocks the live share scenario of resolving the remote workspace. We can't rely on the projection buffers to provide the correct workspace because that workspace is wired up too late in the process of opening a Razor file. #2335 --- .../LiveShareWorkspaceProvider.cs | 13 +++ .../DefaultVisualStudioWorkspaceAccessor.cs | 28 +++++- ...efaultVisualStudioWorkspaceAccessorTest.cs | 91 ++++++++++++++++++- 3 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 src/Microsoft.VisualStudio.Editor.Razor/LiveShareWorkspaceProvider.cs diff --git a/src/Microsoft.VisualStudio.Editor.Razor/LiveShareWorkspaceProvider.cs b/src/Microsoft.VisualStudio.Editor.Razor/LiveShareWorkspaceProvider.cs new file mode 100644 index 0000000000..0eac9d38b6 --- /dev/null +++ b/src/Microsoft.VisualStudio.Editor.Razor/LiveShareWorkspaceProvider.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis; +using Microsoft.VisualStudio.Text; + +namespace Microsoft.VisualStudio.Editor.Razor +{ + internal abstract class LiveShareWorkspaceProvider + { + public abstract bool TryGetWorkspace(ITextBuffer textBuffer, out Workspace workspace); + } +} diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultVisualStudioWorkspaceAccessor.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultVisualStudioWorkspaceAccessor.cs index 0e9079b84e..64171020a8 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultVisualStudioWorkspaceAccessor.cs +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultVisualStudioWorkspaceAccessor.cs @@ -19,12 +19,14 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor private readonly IBufferGraphFactoryService _bufferGraphService; private readonly TextBufferProjectService _projectService; private readonly Workspace _defaultWorkspace; + private readonly LiveShareWorkspaceProvider _liveShareWorkspaceProvider; [ImportingConstructor] public DefaultVisualStudioWorkspaceAccessor( IBufferGraphFactoryService bufferGraphService, TextBufferProjectService projectService, - [Import(typeof(VisualStudioWorkspace))] Workspace defaultWorkspace) + [Import(typeof(VisualStudioWorkspace))] Workspace defaultWorkspace, + [Import(typeof(LiveShareWorkspaceProvider), AllowDefault = true)] LiveShareWorkspaceProvider liveShareWorkspaceProvider) { if (bufferGraphService == null) { @@ -44,6 +46,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor _bufferGraphService = bufferGraphService; _projectService = projectService; _defaultWorkspace = defaultWorkspace; + _liveShareWorkspaceProvider = liveShareWorkspaceProvider; } public override bool TryGetWorkspace(ITextBuffer textBuffer, out Workspace workspace) @@ -56,12 +59,18 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor // We do a best effort approach in this method to get the workspace that belongs to the TextBuffer. // The approaches we take to find the workspace are: // - // 1. Look for a C# projection buffer associated with the Razor buffer. If we can find one we let + // 1. If we have a live share workspace provider, ask it for the workspace. + // 2. Look for a C# projection buffer associated with the Razor buffer. If we can find one we let // Roslyn take control of finding the Workspace (projectionBuffer.GetWorkspace()). If not, // fall back to determining if we can use the default workspace. - // 2. Look to see if this ITextBuffer is associated with a host project. If we find that our Razor + // 3. Look to see if this ITextBuffer is associated with a host project. If we find that our Razor // buffer has a host project, we make the assumption that we should use the default VisualStudioWorkspace. + if (TryGetWorkspaceFromLiveShare(textBuffer, out workspace)) + { + return true; + } + if (TryGetWorkspaceFromProjectionBuffer(textBuffer, out workspace)) { return true; @@ -76,6 +85,19 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor return false; } + // Internal for testing + internal bool TryGetWorkspaceFromLiveShare(ITextBuffer textBuffer, out Workspace workspace) + { + if (_liveShareWorkspaceProvider != null && + _liveShareWorkspaceProvider.TryGetWorkspace(textBuffer, out workspace)) + { + return true; + } + + workspace = null; + return false; + } + // Internal virtual for testing internal virtual bool TryGetWorkspaceFromProjectionBuffer(ITextBuffer textBuffer, out Workspace workspace) { diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/DefaultVisualStudioWorkspaceAccessorTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/DefaultVisualStudioWorkspaceAccessorTest.cs index 95a0cb7105..4905f8572d 100644 --- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/DefaultVisualStudioWorkspaceAccessorTest.cs +++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/DefaultVisualStudioWorkspaceAccessorTest.cs @@ -14,6 +14,27 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor { public class DefaultVisualStudioWorkspaceAccessorTest { + private static readonly LiveShareWorkspaceProvider NoLiveShare = null; + + [Fact] + public void TryGetWorkspace_PrioritizesLiveShareWhenResolvingWorkspaces() + { + // Arrange + var expectedWorkspace = TestWorkspace.Create(); + var liveShareWorkspaceProvider = new Mock(); + liveShareWorkspaceProvider.Setup(provider => provider.TryGetWorkspace(It.IsAny(), out expectedWorkspace)) + .Returns(true); + var workspaceAccessor = new TestWorkspaceAccessor(canGetWorkspaceFromProjectionBuffer: true, canGetWorkspaceFromHostProject: true, liveShareWorkspaceProvider.Object); + var textBuffer = Mock.Of(); + + // Act + var result = workspaceAccessor.TryGetWorkspace(textBuffer, out var workspace); + + // Assert + Assert.True(result); + Assert.Same(expectedWorkspace, workspace); + } + [Fact] public void TryGetWorkspace_CanGetWorkspaceFromProjectionBuffersOnly() { @@ -56,6 +77,57 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor Assert.True(result); } + [Fact] + public void TryGetWorkspaceFromLiveShare_NoLiveShareProvider_ReturnsFalse() + { + // Arrange + var workspaceAccessor = new DefaultVisualStudioWorkspaceAccessor(Mock.Of(), Mock.Of(), TestWorkspace.Create(), NoLiveShare); + var textBuffer = Mock.Of(); + + // Act + var result = workspaceAccessor.TryGetWorkspaceFromLiveShare(textBuffer, out var workspace); + + // Assert + Assert.False(result); + } + + [Fact] + public void TryGetWorkspaceFromLiveShare_CanNotFindWorkspace_ReturnsFalse() + { + // Arrange + Workspace nullWorkspace = null; + var liveShareWorkspaceProvider = new Mock(); + liveShareWorkspaceProvider.Setup(provider => provider.TryGetWorkspace(It.IsAny(), out nullWorkspace)) + .Returns(false); + var workspaceAccessor = new DefaultVisualStudioWorkspaceAccessor(Mock.Of(), Mock.Of(), TestWorkspace.Create(), liveShareWorkspaceProvider.Object); + var textBuffer = Mock.Of(); + + // Act + var result = workspaceAccessor.TryGetWorkspaceFromLiveShare(textBuffer, out var workspace); + + // Assert + Assert.False(result); + } + + [Fact] + public void TryGetWorkspaceFromLiveShare_CanFindWorkspace_ReturnsTrue() + { + // Arrange + var expectedWorkspace = TestWorkspace.Create(); + var liveShareWorkspaceProvider = new Mock(); + liveShareWorkspaceProvider.Setup(provider => provider.TryGetWorkspace(It.IsAny(), out expectedWorkspace)) + .Returns(true); + var workspaceAccessor = new DefaultVisualStudioWorkspaceAccessor(Mock.Of(), Mock.Of(), TestWorkspace.Create(), liveShareWorkspaceProvider.Object); + var textBuffer = Mock.Of(); + + // Act + var result = workspaceAccessor.TryGetWorkspaceFromLiveShare(textBuffer, out var workspace); + + // Assert + Assert.True(result); + Assert.Same(expectedWorkspace, workspace); + } + [Fact] public void TryGetWorkspaceFromProjectionBuffer_NoProjectionBuffer_ReturnsFalse() { @@ -66,7 +138,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor var bufferGraphService = new Mock(); bufferGraphService.Setup(service => service.CreateBufferGraph(It.IsAny())) .Returns(bufferGraph.Object); - var workspaceAccessor = new DefaultVisualStudioWorkspaceAccessor(bufferGraphService.Object, Mock.Of(), TestWorkspace.Create()); + var workspaceAccessor = new DefaultVisualStudioWorkspaceAccessor(bufferGraphService.Object, Mock.Of(), TestWorkspace.Create(), NoLiveShare); var textBuffer = Mock.Of(); // Act @@ -80,7 +152,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor public void TryGetWorkspaceFromHostProject_NoHostProject_ReturnsFalse() { // Arrange - var workspaceAccessor = new DefaultVisualStudioWorkspaceAccessor(Mock.Of(), Mock.Of(), TestWorkspace.Create()); + var workspaceAccessor = new DefaultVisualStudioWorkspaceAccessor(Mock.Of(), Mock.Of(), TestWorkspace.Create(), NoLiveShare); var textBuffer = Mock.Of(); // Act @@ -97,7 +169,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor var textBuffer = Mock.Of(); var projectService = Mock.Of(service => service.GetHostProject(textBuffer) == new object()); var defaultWorkspace = TestWorkspace.Create(); - var workspaceAccessor = new DefaultVisualStudioWorkspaceAccessor(Mock.Of(), projectService, defaultWorkspace); + var workspaceAccessor = new DefaultVisualStudioWorkspaceAccessor(Mock.Of(), projectService, defaultWorkspace, NoLiveShare); // Act var result = workspaceAccessor.TryGetWorkspaceFromHostProject(textBuffer, out var workspace); @@ -113,10 +185,19 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor private readonly bool _canGetWorkspaceFromHostProject; internal TestWorkspaceAccessor(bool canGetWorkspaceFromProjectionBuffer, bool canGetWorkspaceFromHostProject) : + this(canGetWorkspaceFromProjectionBuffer, canGetWorkspaceFromHostProject, NoLiveShare) + { + } + + internal TestWorkspaceAccessor( + bool canGetWorkspaceFromProjectionBuffer, + bool canGetWorkspaceFromHostProject, + LiveShareWorkspaceProvider liveShareWorkspaceProvider) : base( Mock.Of(), Mock.Of(), - TestWorkspace.Create()) + TestWorkspace.Create(), + liveShareWorkspaceProvider) { _canGetWorkspaceFromProjectionBuffer = canGetWorkspaceFromProjectionBuffer; _canGetWorkspaceFromHostProject = canGetWorkspaceFromHostProject; @@ -147,4 +228,4 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor } } } -} +} \ No newline at end of file From fafdd7e3af6d44904f7faee867ba1e8c2b047e70 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Tue, 8 May 2018 22:51:17 -0700 Subject: [PATCH 030/171] Track the open/closed state of documents This change intoduces content changes to our project snapshots. We now know the open/closed state of documents that are initialized by the Razor project system and listen to the correct data source based on whether the file is open in the editor. There are a few other random improvements in here as well like a workaround for the upcoming name change to our OOP client type. --- .../BackgroundDocumentGenerator.cs | 57 ++-- .../DocumentKey.cs | 41 +++ .../ProjectSystem/DefaultDocumentSnapshot.cs | 22 ++ .../DefaultProjectSnapshotManager.cs | 214 ++++++++++++++- .../DocumentGeneratedOutputTracker.cs | 19 +- .../ProjectSystem/DocumentSnapshot.cs | 10 + .../ProjectSystem/DocumentState.cs | 164 +++++++++-- .../ProjectSystem/HostDocument.cs | 33 +-- .../ProjectSystem/ProjectChangeEventArgs.cs | 19 ++ .../ProjectSystem/ProjectChangeKind.cs | 7 +- .../ProjectSystem/ProjectDifference.cs | 4 +- .../ProjectSystem/ProjectSnapshotManager.cs | 2 + .../ProjectSnapshotManagerBase.cs | 12 +- .../ProjectSystem/ProjectState.cs | 90 ++++++- .../WorkspaceProjectSnapshotChangeTrigger.cs | 39 ++- .../Properties/AssemblyInfo.cs | 1 + .../DefaultVisualStudioDocumentTracker.cs | 9 +- .../Documents/EditorDocument.cs | 162 +++++++++++ .../Documents/EditorDocumentManager.cs | 25 ++ .../Documents/EditorDocumentManagerBase.cs | 221 +++++++++++++++ .../EditorDocumentManagerListener.cs | 100 +++++++ .../Documents/SnapshotChangeTracker.cs | 61 +++++ .../RunningDocumentTableEventSink.cs | 62 +++++ .../VisualStudioEditorDocumentManager.cs | 254 ++++++++++++++++++ ...isualStudioEditorDocumentManagerFactory.cs | 60 +++++ .../Documents/VsTextBufferDataEventsSink.cs | 56 ++++ ...VisualStudio.LanguageServices.Razor.csproj | 13 +- .../OOPTagHelperResolverFactory.cs | 30 +++ .../ProjectSystem/RazorProjectHostBase.cs | 2 +- .../DefaultProjectSnapshotTest.cs | 18 +- .../ProjectSystem/DocumentStateTest.cs | 127 ++++++++- .../ProjectSystem/ProjectStateTest.cs | 225 ++++++++++++---- .../StringTextImage.cs | 70 +++++ .../StringTextSnapshot.cs | 12 +- ...ProjectSnapshotProjectEngineFactoryTest.cs | 10 +- .../EditorDocumentManagerBaseTest.cs | 212 +++++++++++++++ .../Documents/EditorDocumentTest.cs | 110 ++++++++ .../Infrastructure/TestTextBuffer.cs | 9 + .../BackgroundDocumentGeneratorTest.cs | 8 +- .../DefaultProjectSnapshotManagerTest.cs | 199 ++++++++++++-- ...rkspaceProjectSnapshotChangeTriggerTest.cs | 27 +- ...UpdatesProjectSnapshotChangeTriggerTest.cs | 12 +- .../ProjectBuildChangeTriggerTest.cs | 12 +- ...crosoft.VisualStudio.RazorExtension.csproj | 22 +- .../RazorInfo/DirectiveCollectionViewModel.cs | 35 +++ ...ViewModel.cs => DirectiveItemViewModel.cs} | 4 +- .../RazorInfo/DocumentCollectionViewModel.cs | 81 ++++++ .../RazorInfo/DocumentItemViewModel.cs | 70 +++++ .../RazorInfo/DocumentSnapshotViewModel.cs | 50 ---- .../RazorInfo/NullToEnabledConverter.cs | 30 +++ .../RazorInfo/ProjectInfoViewModel.cs | 62 ----- .../ProjectPropertyCollectionViewModel.cs | 41 +++ ...del.cs => ProjectPropertyItemViewModel.cs} | 4 +- .../RazorInfo/ProjectSnapshotViewModel.cs | 55 ---- .../RazorInfo/ProjectViewModel.cs | 15 -- .../RazorInfo/RazorInfoToolWindow.cs | 75 +----- .../RazorInfo/RazorInfoToolWindowControl.xaml | 32 ++- .../RazorInfo/RazorInfoViewModel.cs | 200 +++++++++----- .../RazorInfo/TagHelperCollectionViewModel.cs | 72 +++++ .../RazorInfo/TagHelperItemViewModel.cs | 28 ++ .../RazorInfo/TagHelperViewModel.cs | 26 -- 61 files changed, 3102 insertions(+), 640 deletions(-) create mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/DocumentKey.cs create mode 100644 src/Microsoft.VisualStudio.Editor.Razor/Documents/EditorDocument.cs create mode 100644 src/Microsoft.VisualStudio.Editor.Razor/Documents/EditorDocumentManager.cs create mode 100644 src/Microsoft.VisualStudio.Editor.Razor/Documents/EditorDocumentManagerBase.cs create mode 100644 src/Microsoft.VisualStudio.Editor.Razor/Documents/EditorDocumentManagerListener.cs create mode 100644 src/Microsoft.VisualStudio.Editor.Razor/Documents/SnapshotChangeTracker.cs create mode 100644 src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/RunningDocumentTableEventSink.cs create mode 100644 src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/VisualStudioEditorDocumentManager.cs create mode 100644 src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/VisualStudioEditorDocumentManagerFactory.cs create mode 100644 src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/VsTextBufferDataEventsSink.cs create mode 100644 test/Microsoft.VisualStudio.Editor.Razor.Test.Common/StringTextImage.cs create mode 100644 test/Microsoft.VisualStudio.Editor.Razor.Test/Documents/EditorDocumentManagerBaseTest.cs create mode 100644 test/Microsoft.VisualStudio.Editor.Razor.Test/Documents/EditorDocumentTest.cs rename test/{Microsoft.CodeAnalysis.Razor.Workspaces.Test => Microsoft.VisualStudio.LanguageServices.Razor.Test}/ProjectSystem/WorkspaceProjectSnapshotChangeTriggerTest.cs (93%) create mode 100644 tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DirectiveCollectionViewModel.cs rename tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/{DirectiveDescriptorViewModel.cs => DirectiveItemViewModel.cs} (87%) create mode 100644 tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DocumentCollectionViewModel.cs create mode 100644 tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DocumentItemViewModel.cs delete mode 100644 tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DocumentSnapshotViewModel.cs create mode 100644 tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/NullToEnabledConverter.cs delete mode 100644 tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/ProjectInfoViewModel.cs create mode 100644 tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/ProjectPropertyCollectionViewModel.cs rename tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/{PropertyViewModel.cs => ProjectPropertyItemViewModel.cs} (74%) delete mode 100644 tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/ProjectSnapshotViewModel.cs create mode 100644 tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/TagHelperCollectionViewModel.cs create mode 100644 tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/TagHelperItemViewModel.cs delete mode 100644 tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/TagHelperViewModel.cs diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DocumentGenerator/BackgroundDocumentGenerator.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DocumentGenerator/BackgroundDocumentGenerator.cs index 6fd591d1ca..16d7f11959 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DocumentGenerator/BackgroundDocumentGenerator.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DocumentGenerator/BackgroundDocumentGenerator.cs @@ -8,17 +8,17 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Razor.ProjectSystem; -using Microsoft.Extensions.Internal; namespace Microsoft.CodeAnalysis.Razor { // Deliberately not exported for now, until this feature is working end to end. + // [Export(typeof(ProjectSnapshotChangeTrigger))] internal class BackgroundDocumentGenerator : ProjectSnapshotChangeTrigger { private ForegroundDispatcher _foregroundDispatcher; private ProjectSnapshotManagerBase _projectManager; - private readonly Dictionary _files; + private readonly Dictionary _files; private Timer _timer; [ImportingConstructor] @@ -31,7 +31,7 @@ namespace Microsoft.CodeAnalysis.Razor _foregroundDispatcher = foregroundDispatcher; - _files = new Dictionary(); + _files = new Dictionary(); } public bool HasPendingNotifications @@ -127,7 +127,7 @@ namespace Microsoft.CodeAnalysis.Razor { // We only want to store the last 'seen' version of any given document. That way when we pick one to process // it's always the best version to use. - _files.Add(new Key(project.FilePath, document.FilePath), document); + _files[new DocumentKey(project.FilePath, document.FilePath)] = document; StartWorker(); } @@ -217,7 +217,6 @@ namespace Microsoft.CodeAnalysis.Razor { case ProjectChangeKind.ProjectAdded: case ProjectChangeKind.ProjectChanged: - case ProjectChangeKind.DocumentsChanged: { var project = _projectManager.GetLoadedProject(e.ProjectFilePath); foreach (var documentFilePath in project.DocumentFilePaths) @@ -228,12 +227,21 @@ namespace Microsoft.CodeAnalysis.Razor break; } - case ProjectChangeKind.DocumentContentChanged: + case ProjectChangeKind.ProjectRemoved: + // ignore + break; + + case ProjectChangeKind.DocumentAdded: + case ProjectChangeKind.DocumentChanged: { - throw null; + var project = _projectManager.GetLoadedProject(e.ProjectFilePath); + Enqueue(project, project.GetDocument(e.DocumentFilePath)); + + break; } - case ProjectChangeKind.ProjectRemoved: + + case ProjectChangeKind.DocumentRemoved: // ignore break; @@ -241,38 +249,5 @@ namespace Microsoft.CodeAnalysis.Razor throw new InvalidOperationException($"Unknown ProjectChangeKind {e.Kind}"); } } - - private struct Key : IEquatable - { - public Key(string projectFilePath, string documentFilePath) - { - ProjectFilePath = projectFilePath; - DocumentFilePath = documentFilePath; - } - - public string ProjectFilePath { get; } - - public string DocumentFilePath { get; } - - public bool Equals(Key other) - { - return - FilePathComparer.Instance.Equals(ProjectFilePath, other.ProjectFilePath) && - FilePathComparer.Instance.Equals(DocumentFilePath, other.DocumentFilePath); - } - - public override bool Equals(object obj) - { - return obj is Key key ? Equals(key) : false; - } - - public override int GetHashCode() - { - var hash = new HashCodeCombiner(); - hash.Add(ProjectFilePath, FilePathComparer.Instance); - hash.Add(DocumentFilePath, FilePathComparer.Instance); - return hash; - } - } } } \ No newline at end of file diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DocumentKey.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DocumentKey.cs new file mode 100644 index 0000000000..2fe707a707 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DocumentKey.cs @@ -0,0 +1,41 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Extensions.Internal; + +namespace Microsoft.CodeAnalysis.Razor +{ + public struct DocumentKey : IEquatable + { + public DocumentKey(string projectFilePath, string documentFilePath) + { + ProjectFilePath = projectFilePath; + DocumentFilePath = documentFilePath; + } + + public string ProjectFilePath { get; } + + public string DocumentFilePath { get; } + + public bool Equals(DocumentKey other) + { + return + FilePathComparer.Instance.Equals(ProjectFilePath, other.ProjectFilePath) && + FilePathComparer.Instance.Equals(DocumentFilePath, other.DocumentFilePath); + } + + public override bool Equals(object obj) + { + return obj is DocumentKey key ? Equals(key) : false; + } + + public override int GetHashCode() + { + var hash = new HashCodeCombiner(); + hash.Add(ProjectFilePath, FilePathComparer.Instance); + hash.Add(DocumentFilePath, FilePathComparer.Instance); + return hash; + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultDocumentSnapshot.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultDocumentSnapshot.cs index 6ce3edb596..0cc4512f2e 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultDocumentSnapshot.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultDocumentSnapshot.cs @@ -2,8 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { @@ -33,12 +35,32 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem public override string TargetPath => State.HostDocument.TargetPath; + public override Task GetTextAsync() + { + return State.GetTextAsync(); + } + + public override Task GetTextVersionAsync() + { + return State.GetTextVersionAsync(); + } + public override Task GetGeneratedOutputAsync() { // IMPORTANT: Don't put more code here. We want this to return a cached task. return State.GeneratedOutput.GetGeneratedOutputInitializationTask(Project, this); } + public override bool TryGetText(out SourceText result) + { + return State.TryGetText(out result); + } + + public override bool TryGetTextVersion(out VersionStamp result) + { + return State.TryGetTextVersion(out result); + } + public override bool TryGetGeneratedOutput(out RazorCodeDocument result) { if (State.GeneratedOutput.IsResultAvailable) diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs index 174b669c86..5a810cc803 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs @@ -4,6 +4,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { @@ -34,6 +37,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // Each entry holds a ProjectState and an optional ProjectSnapshot. ProjectSnapshots are // created lazily. private readonly Dictionary _projects; + private readonly HashSet _openDocuments; public DefaultProjectSnapshotManager( ForegroundDispatcher foregroundDispatcher, @@ -67,6 +71,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem Workspace = workspace; _projects = new Dictionary(FilePathComparer.Instance); + _openDocuments = new HashSet(FilePathComparer.Instance); for (var i = 0; i < _triggers.Length; i++) { @@ -133,7 +138,19 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem return GetLoadedProject(filePath) ?? new EphemeralProjectSnapshot(Workspace.Services, filePath); } - public override void DocumentAdded(HostProject hostProject, HostDocument document) + public override bool IsDocumentOpen(string documentFilePath) + { + if (documentFilePath == null) + { + throw new ArgumentNullException(nameof(documentFilePath)); + } + + _foregroundDispatcher.AssertForegroundThread(); + + return _openDocuments.Contains(documentFilePath); + } + + public override void DocumentAdded(HostProject hostProject, HostDocument document, TextLoader textLoader) { if (hostProject == null) { @@ -149,13 +166,17 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem if (_projects.TryGetValue(hostProject.FilePath, out var entry)) { - var state = entry.State.WithAddedHostDocument(document); + var loader = textLoader == null ? DocumentState.EmptyLoader : (Func>)(() => + { + return textLoader.LoadTextAndVersionAsync(Workspace, null, CancellationToken.None); + }); + var state = entry.State.WithAddedHostDocument(document, loader); // Document updates can no-op. if (!object.ReferenceEquals(state, entry.State)) { _projects[hostProject.FilePath] = new Entry(state); - NotifyListeners(new ProjectChangeEventArgs(hostProject.FilePath, ProjectChangeKind.DocumentsChanged)); + NotifyListeners(new ProjectChangeEventArgs(hostProject.FilePath, document.FilePath, ProjectChangeKind.DocumentAdded)); } } } @@ -181,7 +202,187 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem if (!object.ReferenceEquals(state, entry.State)) { _projects[hostProject.FilePath] = new Entry(state); - NotifyListeners(new ProjectChangeEventArgs(hostProject.FilePath, ProjectChangeKind.DocumentsChanged)); + NotifyListeners(new ProjectChangeEventArgs(hostProject.FilePath, document.FilePath, ProjectChangeKind.DocumentRemoved)); + } + } + } + + public override void DocumentOpened(string projectFilePath, string documentFilePath, SourceText sourceText) + { + if (projectFilePath == null) + { + throw new ArgumentNullException(nameof(projectFilePath)); + } + + if (documentFilePath == null) + { + throw new ArgumentNullException(nameof(documentFilePath)); + } + + if (sourceText == null) + { + throw new ArgumentNullException(nameof(sourceText)); + } + + _foregroundDispatcher.AssertForegroundThread(); + if (_projects.TryGetValue(projectFilePath, out var entry) && + entry.State.Documents.TryGetValue(documentFilePath, out var older)) + { + ProjectState state; + SourceText olderText; + VersionStamp olderVersion; + + var currentText = sourceText; + if (older.TryGetText(out olderText) && + older.TryGetTextVersion(out olderVersion)) + { + var version = currentText.ContentEquals(olderText) ? olderVersion : olderVersion.GetNewerVersion(); + state = entry.State.WithChangedHostDocument(older.HostDocument, currentText, version); + } + else + { + state = entry.State.WithChangedHostDocument(older.HostDocument, async () => + { + olderText = await older.GetTextAsync().ConfigureAwait(false); + olderVersion = await older.GetTextVersionAsync().ConfigureAwait(false); + + var version = currentText.ContentEquals(olderText) ? olderVersion : olderVersion.GetNewerVersion(); + return TextAndVersion.Create(currentText, version, documentFilePath); + }); + } + + _openDocuments.Add(documentFilePath); + + // Document updates can no-op. + if (!object.ReferenceEquals(state, entry.State)) + { + _projects[projectFilePath] = new Entry(state); + NotifyListeners(new ProjectChangeEventArgs(projectFilePath, documentFilePath, ProjectChangeKind.DocumentChanged)); + } + } + } + + public override void DocumentClosed(string projectFilePath, string documentFilePath, TextLoader textLoader) + { + if (projectFilePath == null) + { + throw new ArgumentNullException(nameof(projectFilePath)); + } + + if (documentFilePath == null) + { + throw new ArgumentNullException(nameof(documentFilePath)); + } + + if (textLoader == null) + { + throw new ArgumentNullException(nameof(textLoader)); + } + + _foregroundDispatcher.AssertForegroundThread(); + if (_projects.TryGetValue(projectFilePath, out var entry) && + entry.State.Documents.TryGetValue(documentFilePath, out var older)) + { + var state = entry.State.WithChangedHostDocument(older.HostDocument, async () => + { + return await textLoader.LoadTextAndVersionAsync(Workspace, default, default); + }); + + _openDocuments.Remove(documentFilePath); + + // Document updates can no-op. + if (!object.ReferenceEquals(state, entry.State)) + { + _projects[projectFilePath] = new Entry(state); + NotifyListeners(new ProjectChangeEventArgs(projectFilePath, documentFilePath, ProjectChangeKind.DocumentChanged)); + } + } + } + + public override void DocumentChanged(string projectFilePath, string documentFilePath, SourceText sourceText) + { + if (projectFilePath == null) + { + throw new ArgumentNullException(nameof(projectFilePath)); + } + + if (documentFilePath == null) + { + throw new ArgumentNullException(nameof(documentFilePath)); + } + + if (sourceText == null) + { + throw new ArgumentNullException(nameof(sourceText)); + } + + _foregroundDispatcher.AssertForegroundThread(); + if (_projects.TryGetValue(projectFilePath, out var entry) && + entry.State.Documents.TryGetValue(documentFilePath, out var older)) + { + ProjectState state; + SourceText olderText; + VersionStamp olderVersion; + + var currentText = sourceText; + if (older.TryGetText(out olderText) && + older.TryGetTextVersion(out olderVersion)) + { + var version = currentText.ContentEquals(olderText) ? olderVersion : olderVersion.GetNewerVersion(); + state = entry.State.WithChangedHostDocument(older.HostDocument, currentText, version); + } + else + { + state = entry.State.WithChangedHostDocument(older.HostDocument, async () => + { + olderText = await older.GetTextAsync().ConfigureAwait(false); + olderVersion = await older.GetTextVersionAsync().ConfigureAwait(false); + + var version = currentText.ContentEquals(olderText) ? olderVersion : olderVersion.GetNewerVersion(); + return TextAndVersion.Create(currentText, version, documentFilePath); + }); + } + + // Document updates can no-op. + if (!object.ReferenceEquals(state, entry.State)) + { + _projects[projectFilePath] = new Entry(state); + NotifyListeners(new ProjectChangeEventArgs(projectFilePath, documentFilePath, ProjectChangeKind.DocumentChanged)); + } + } + } + + public override void DocumentChanged(string projectFilePath, string documentFilePath, TextLoader textLoader) + { + if (projectFilePath == null) + { + throw new ArgumentNullException(nameof(projectFilePath)); + } + + if (documentFilePath == null) + { + throw new ArgumentNullException(nameof(documentFilePath)); + } + + if (textLoader == null) + { + throw new ArgumentNullException(nameof(textLoader)); + } + + _foregroundDispatcher.AssertForegroundThread(); + if (_projects.TryGetValue(projectFilePath, out var entry) && + entry.State.Documents.TryGetValue(documentFilePath, out var older)) + { + var state = entry.State.WithChangedHostDocument(older.HostDocument, async () => + { + return await textLoader.LoadTextAndVersionAsync(Workspace, default, default); + }); + + // Document updates can no-op. + if (!object.ReferenceEquals(state, entry.State)) + { + _projects[projectFilePath] = new Entry(state); + NotifyListeners(new ProjectChangeEventArgs(projectFilePath, documentFilePath, ProjectChangeKind.DocumentChanged)); } } } @@ -205,7 +406,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // So if possible find a WorkspaceProject. var workspaceProject = GetWorkspaceProject(hostProject.FilePath); - var state = new ProjectState(Workspace.Services, hostProject, workspaceProject); + var state = ProjectState.Create(Workspace.Services, hostProject, workspaceProject); _projects[hostProject.FilePath] = new Entry(state); // We need to notify listeners about every project add. @@ -300,7 +501,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // We also need to check the projectId here. If this is a multi-targeting project then we are only interested // in a single workspace project. Just use the one that showed up first. if (_projects.TryGetValue(workspaceProject.FilePath, out var entry) && - (entry.State.WorkspaceProject == null || entry.State.WorkspaceProject.Id == workspaceProject.Id)) + (entry.State.WorkspaceProject == null || entry.State.WorkspaceProject.Id == workspaceProject.Id) && + (entry.State.WorkspaceProject == null || entry.State.WorkspaceProject.Version.GetNewerVersion(workspaceProject.Version) == workspaceProject.Version)) { var state = entry.State.WithWorkspaceProject(workspaceProject); diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentGeneratedOutputTracker.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentGeneratedOutputTracker.cs index e3114148b1..20329b954e 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentGeneratedOutputTracker.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentGeneratedOutputTracker.cs @@ -10,11 +10,6 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { internal class DocumentGeneratedOutputTracker { - // Don't keep anything if the configuration has changed. It's OK if the - // workspace project has changes, we'll consider whether the tag helpers are - // difference before reusing a previous result. - private const ProjectDifference Mask = ProjectDifference.ConfigurationChanged; - private readonly object _lock; private DocumentGeneratedOutputTracker _older; @@ -31,6 +26,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem public bool IsResultAvailable => _task?.IsCompleted == true; + public DocumentGeneratedOutputTracker Older => _older; + public Task GetGeneratedOutputInitializationTask(ProjectSnapshot project, DocumentSnapshot document) { if (project == null) @@ -57,18 +54,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem return _task; } - public DocumentGeneratedOutputTracker ForkFor(DocumentState state, ProjectDifference difference) + public DocumentGeneratedOutputTracker Fork() { - if (state == null) - { - throw new ArgumentNullException(nameof(state)); - } - - if ((difference & Mask) != 0) - { - return null; - } - return new DocumentGeneratedOutputTracker(this); } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs index 5a86527bc9..ac94c6a1cd 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs @@ -1,8 +1,10 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { @@ -12,8 +14,16 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem public abstract string TargetPath { get; } + public abstract Task GetTextAsync(); + + public abstract Task GetTextVersionAsync(); + public abstract Task GetGeneratedOutputAsync(); + public abstract bool TryGetText(out SourceText result); + + public abstract bool TryGetTextVersion(out VersionStamp result); + public abstract bool TryGetGeneratedOutput(out RazorCodeDocument result); } } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs index f53adf18b0..192a038f1d 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs @@ -2,17 +2,33 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { internal class DocumentState { + private static readonly TextAndVersion EmptyText = TextAndVersion.Create( + SourceText.From(string.Empty), + VersionStamp.Default); + + public static readonly Func> EmptyLoader = () => Task.FromResult(EmptyText); + private readonly object _lock; + private Func> _loader; + private Task _loaderTask; + private SourceText _sourceText; + private VersionStamp? _version; + private DocumentGeneratedOutputTracker _generatedOutput; - public DocumentState(HostWorkspaceServices services, HostDocument hostDocument) + public static DocumentState Create( + HostWorkspaceServices services, + HostDocument hostDocument, + Func> loader) { if (services == null) { @@ -24,33 +40,29 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem throw new ArgumentNullException(nameof(hostDocument)); } - Services = services; - HostDocument = hostDocument; - Version = VersionStamp.Create(); - - _lock = new object(); + loader = loader ?? EmptyLoader; + return new DocumentState(services, hostDocument, null, null, loader); } - public DocumentState(DocumentState previous, ProjectDifference difference) + private DocumentState( + HostWorkspaceServices services, + HostDocument hostDocument, + SourceText text, + VersionStamp? version, + Func> loader) { - if (previous == null) - { - throw new ArgumentNullException(nameof(previous)); - } - - Services = previous.Services; - HostDocument = previous.HostDocument; - Version = previous.Version.GetNewerVersion(); - - _generatedOutput = previous._generatedOutput?.ForkFor(this, difference); + Services = services; + HostDocument = hostDocument; + _sourceText = text; + _version = version; + _loader = loader; + _lock = new object(); } public HostDocument HostDocument { get; } public HostWorkspaceServices Services { get; } - public VersionStamp Version { get; } - public DocumentGeneratedOutputTracker GeneratedOutput { get @@ -69,5 +81,119 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem return _generatedOutput; } } + + public async Task GetTextAsync() + { + if (TryGetText(out var text)) + { + return text; + } + + lock (_lock) + { + _loaderTask = _loader(); + } + + return (await _loaderTask.ConfigureAwait(false)).Text; + } + + public async Task GetTextVersionAsync() + { + if (TryGetTextVersion(out var version)) + { + return version; + } + + lock (_lock) + { + _loaderTask = _loader(); + } + + return (await _loaderTask.ConfigureAwait(false)).Version; + } + + public bool TryGetText(out SourceText result) + { + if (_sourceText != null) + { + result = _sourceText; + return true; + } + + if (_loaderTask != null && _loaderTask.IsCompleted) + { + result = _loaderTask.Result.Text; + return true; + } + + result = null; + return false; + } + + public bool TryGetTextVersion(out VersionStamp result) + { + if (_version != null) + { + result = _version.Value; + return true; + } + + if (_loaderTask != null && _loaderTask.IsCompleted) + { + result = _loaderTask.Result.Version; + return true; + } + + result = default; + return false; + } + + public DocumentState WithConfigurationChange() + { + var state = new DocumentState(Services, HostDocument, _sourceText, _version, _loader); + + // The source could not have possibly changed. + state._sourceText = _sourceText; + state._version = _version; + state._loaderTask = _loaderTask; + + return state; + } + + public DocumentState WithWorkspaceProjectChange() + { + var state = new DocumentState(Services, HostDocument, _sourceText, _version, _loader); + + // The source could not have possibly changed. + state._sourceText = _sourceText; + state._version = _version; + state._loaderTask = _loaderTask; + + // Opportunistically cache the generated code + state._generatedOutput = _generatedOutput?.Fork(); + + return state; + } + + public DocumentState WithText(SourceText sourceText, VersionStamp version) + { + if (sourceText == null) + { + throw new ArgumentNullException(nameof(sourceText)); + } + + return new DocumentState(Services, HostDocument, sourceText, version, null); + } + + + public DocumentState WithTextLoader(Func> loader) + { + if (loader == null) + { + throw new ArgumentNullException(nameof(loader)); + } + + return new DocumentState(Services, HostDocument, null, null, loader); + } } } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/HostDocument.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/HostDocument.cs index 146943cab6..859cc0df32 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/HostDocument.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/HostDocument.cs @@ -2,11 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.Extensions.Internal; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { - internal class HostDocument : IEquatable + internal class HostDocument { public HostDocument(string filePath, string targetPath) { @@ -27,35 +26,5 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem public string FilePath { get; } public string TargetPath { get; } - - public override bool Equals(object obj) - { - return base.Equals(obj as DocumentSnapshot); - } - - public bool Equals(DocumentSnapshot other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - return - FilePathComparer.Instance.Equals(FilePath, other.FilePath) && - FilePathComparer.Instance.Equals(TargetPath, other.TargetPath); - } - - public bool Equals(HostDocument other) - { - throw new NotImplementedException(); - } - - public override int GetHashCode() - { - var hash = new HashCodeCombiner(); - hash.Add(FilePath, FilePathComparer.Instance); - hash.Add(TargetPath, FilePathComparer.Instance); - return hash; - } } } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectChangeEventArgs.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectChangeEventArgs.cs index 0c0faa3db8..a163ba5f1b 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectChangeEventArgs.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectChangeEventArgs.cs @@ -9,12 +9,31 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { public ProjectChangeEventArgs(string projectFilePath, ProjectChangeKind kind) { + if (projectFilePath == null) + { + throw new ArgumentNullException(nameof(projectFilePath)); + } + ProjectFilePath = projectFilePath; Kind = kind; } + public ProjectChangeEventArgs(string projectFilePath, string documentFilePath, ProjectChangeKind kind) + { + if (projectFilePath == null) + { + throw new ArgumentNullException(nameof(projectFilePath)); + } + + ProjectFilePath = projectFilePath; + DocumentFilePath = documentFilePath; + Kind = kind; + } + public string ProjectFilePath { get; } + public string DocumentFilePath { get; } + public ProjectChangeKind Kind { get; } } } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectChangeKind.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectChangeKind.cs index 53b6b36529..98bd0a4010 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectChangeKind.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectChangeKind.cs @@ -8,7 +8,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem ProjectAdded, ProjectRemoved, ProjectChanged, - DocumentsChanged, - DocumentContentChanged, + DocumentAdded, + DocumentRemoved, + + // This could be a state change (opened/closed) or a content change. + DocumentChanged, } } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectDifference.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectDifference.cs index d450393f3c..138acdb312 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectDifference.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectDifference.cs @@ -13,6 +13,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem WorkspaceProjectAdded = 2, WorkspaceProjectRemoved = 4, WorkspaceProjectChanged = 8, - DocumentsChanged = 16, + DocumentAdded = 16, + DocumentRemoved = 32, + DocumentChanged = 64, } } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManager.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManager.cs index e491674399..40dfec08a9 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManager.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManager.cs @@ -13,6 +13,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem public abstract IReadOnlyList Projects { get; } + public abstract bool IsDocumentOpen(string documentFilePath); + public abstract ProjectSnapshot GetLoadedProject(string filePath); public abstract ProjectSnapshot GetOrCreateProject(string filePath); diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerBase.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerBase.cs index 87306d1b5a..5545bbb3a8 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerBase.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerBase.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { @@ -9,7 +10,16 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { public abstract Workspace Workspace { get; } - public abstract void DocumentAdded(HostProject hostProject, HostDocument hostDocument); + public abstract void DocumentAdded(HostProject hostProject, HostDocument hostDocument, TextLoader textLoader); + + // Yeah this is kinda ugly. + public abstract void DocumentOpened(string projectFilePath, string documentFilePath, SourceText sourceText); + + public abstract void DocumentClosed(string projectFilePath, string documentFilePath, TextLoader textLoader); + + public abstract void DocumentChanged(string projectFilePath, string documentFilePath, TextLoader textLoader); + + public abstract void DocumentChanged(string projectFilePath, string documentFilePath, SourceText sourceText); public abstract void DocumentRemoved(HostProject hostProject, HostDocument hostDocument); diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs index 53e6cc3b15..5dece86c8f 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { @@ -17,10 +19,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem private ProjectEngineTracker _projectEngine; private ProjectTagHelperTracker _tagHelpers; - public ProjectState( - HostWorkspaceServices services, - HostProject hostProject, - Project workspaceProject) + public static ProjectState Create(HostWorkspaceServices services, HostProject hostProject, Project workspaceProject = null) { if (services == null) { @@ -32,6 +31,14 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem throw new ArgumentNullException(nameof(hostProject)); } + return new ProjectState(services, hostProject, workspaceProject); + } + + private ProjectState( + HostWorkspaceServices services, + HostProject hostProject, + Project workspaceProject) + { Services = services; HostProject = hostProject; WorkspaceProject = workspaceProject; @@ -41,7 +48,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem _lock = new object(); } - public ProjectState( + private ProjectState( ProjectState older, ProjectDifference difference, HostProject hostProject, @@ -126,13 +133,18 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem } } - public ProjectState WithAddedHostDocument(HostDocument hostDocument) + public ProjectState WithAddedHostDocument(HostDocument hostDocument, Func> loader) { if (hostDocument == null) { throw new ArgumentNullException(nameof(hostDocument)); } + if (loader == null) + { + throw new ArgumentNullException(nameof(loader)); + } + // Ignore attempts to 'add' a document with different data, we only // care about one, so it might as well be the one we have. if (Documents.ContainsKey(hostDocument.FilePath)) @@ -145,10 +157,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { documents.Add(kvp.Key, kvp.Value); } + + documents.Add(hostDocument.FilePath, DocumentState.Create(Services, hostDocument, loader)); - documents.Add(hostDocument.FilePath, new DocumentState(Services, hostDocument)); - - var difference = ProjectDifference.DocumentsChanged; + var difference = ProjectDifference.DocumentAdded; var state = new ProjectState(this, difference, HostProject, WorkspaceProject, documents); return state; } @@ -173,11 +185,65 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem documents.Remove(hostDocument.FilePath); - var difference = ProjectDifference.DocumentsChanged; + var difference = ProjectDifference.DocumentRemoved; var state = new ProjectState(this, difference, HostProject, WorkspaceProject, documents); return state; } + public ProjectState WithChangedHostDocument(HostDocument hostDocument, SourceText sourceText, VersionStamp version) + { + if (hostDocument == null) + { + throw new ArgumentNullException(nameof(hostDocument)); + } + + if (!Documents.ContainsKey(hostDocument.FilePath)) + { + return this; + } + + var documents = new Dictionary(FilePathComparer.Instance); + foreach (var kvp in Documents) + { + documents.Add(kvp.Key, kvp.Value); + } + + if (documents.TryGetValue(hostDocument.FilePath, out var document)) + { + documents[hostDocument.FilePath] = document.WithText(sourceText, version); + } + + var state = new ProjectState(this, ProjectDifference.DocumentChanged, HostProject, WorkspaceProject, documents); + return state; + } + + public ProjectState WithChangedHostDocument(HostDocument hostDocument, Func> loader) + { + if (hostDocument == null) + { + throw new ArgumentNullException(nameof(hostDocument)); + } + + if (!Documents.ContainsKey(hostDocument.FilePath)) + { + return this; + } + + var documents = new Dictionary(FilePathComparer.Instance); + foreach (var kvp in Documents) + { + documents.Add(kvp.Key, kvp.Value); + } + + if (documents.TryGetValue(hostDocument.FilePath, out var document)) + { + documents[hostDocument.FilePath] = document.WithTextLoader(loader); + } + + var state = new ProjectState(this, ProjectDifference.DocumentChanged, HostProject, WorkspaceProject, documents); + return state; + } + public ProjectState WithHostProject(HostProject hostProject) { if (hostProject == null) @@ -194,7 +260,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem var documents = new Dictionary(FilePathComparer.Instance); foreach (var kvp in Documents) { - documents.Add(kvp.Key, new DocumentState(kvp.Value, difference)); + documents.Add(kvp.Key, kvp.Value.WithConfigurationChange()); } var state = new ProjectState(this, difference, hostProject, WorkspaceProject, documents); @@ -227,7 +293,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem var documents = new Dictionary(FilePathComparer.Instance); foreach (var kvp in Documents) { - documents.Add(kvp.Key, new DocumentState(kvp.Value, difference)); + documents.Add(kvp.Key, kvp.Value.WithConfigurationChange()); } var state = new ProjectState(this, difference, HostProject, workspaceProject, documents); diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/WorkspaceProjectSnapshotChangeTrigger.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/WorkspaceProjectSnapshotChangeTrigger.cs index fb2deeec32..f5bbee95de 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/WorkspaceProjectSnapshotChangeTrigger.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/WorkspaceProjectSnapshotChangeTrigger.cs @@ -1,8 +1,10 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using System.Composition; using System.Diagnostics; +using System.Threading.Tasks; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { @@ -11,11 +13,21 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { private ProjectSnapshotManagerBase _projectManager; + public int ProjectChangeDelay { get; set; } = 3 * 1000; + + // We throttle updates to projects to prevent doing too much work while the projects + // are being initialized. + // + // Internal for testing + internal Dictionary _deferredUpdates; + public override void Initialize(ProjectSnapshotManagerBase projectManager) { _projectManager = projectManager; _projectManager.Workspace.WorkspaceChanged += Workspace_WorkspaceChanged; + _deferredUpdates = new Dictionary(); + InitializeSolution(_projectManager.Workspace.CurrentSolution); } @@ -47,10 +59,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem case WorkspaceChangeKind.ProjectChanged: case WorkspaceChangeKind.ProjectReloaded: { - project = e.NewSolution.GetProject(e.ProjectId); - Debug.Assert(project != null); - - _projectManager.WorkspaceProjectChanged(project); + EnqueueUpdate(e.ProjectId); break; } @@ -81,5 +90,27 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem break; } } + + private void EnqueueUpdate(ProjectId projectId) + { + // A race is not possible here because we use the main thread to synchronize the updates + // by capturing the sync context. + if (!_deferredUpdates.TryGetValue(projectId, out var update) || update.IsCompleted) + { + _deferredUpdates[projectId] = UpdateAfterDelay(projectId); + } + } + + private async Task UpdateAfterDelay(ProjectId projectId) + { + await Task.Delay(ProjectChangeDelay); + + var solution = _projectManager.Workspace.CurrentSolution; + var workspaceProject = solution.GetProject(projectId); + if (workspaceProject != null) + { + _projectManager.WorkspaceProjectChanged(workspaceProject); + } + } } } diff --git a/src/Microsoft.CodeAnalysis.Razor/Properties/AssemblyInfo.cs b/src/Microsoft.CodeAnalysis.Razor/Properties/AssemblyInfo.cs index 52df72dac7..768c23b6a9 100644 --- a/src/Microsoft.CodeAnalysis.Razor/Properties/AssemblyInfo.cs +++ b/src/Microsoft.CodeAnalysis.Razor/Properties/AssemblyInfo.cs @@ -9,6 +9,7 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Razor.Workspaces, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Razor.Workspaces.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Remote.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Editor.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.VisualStudio.LanguageServices.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.VisualStudio.RazorExtension, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs index b9c976bdb9..02311c217b 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs @@ -292,7 +292,9 @@ namespace Microsoft.VisualStudio.Editor.Razor switch (e.Kind) { - case ProjectChangeKind.DocumentsChanged: + case ProjectChangeKind.DocumentAdded: + case ProjectChangeKind.DocumentRemoved: + case ProjectChangeKind.DocumentChanged: // Nothing to do. break; @@ -311,11 +313,6 @@ namespace Microsoft.VisualStudio.Editor.Razor OnContextChanged(ContextChangeKind.ProjectChanged); break; - case ProjectChangeKind.DocumentContentChanged: - - // Do nothing - break; - default: throw new InvalidOperationException($"Unknown ProjectChangeKind {e.Kind}"); } diff --git a/src/Microsoft.VisualStudio.Editor.Razor/Documents/EditorDocument.cs b/src/Microsoft.VisualStudio.Editor.Razor/Documents/EditorDocument.cs new file mode 100644 index 0000000000..b048defba7 --- /dev/null +++ b/src/Microsoft.VisualStudio.Editor.Razor/Documents/EditorDocument.cs @@ -0,0 +1,162 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Text; + +namespace Microsoft.VisualStudio.Editor.Razor.Documents +{ + // Tracks the mutable state associated with a document - in contrast to DocumentSnapshot + // which tracks the state at a point in time. + internal sealed class EditorDocument : IDisposable + { + private readonly EditorDocumentManager _documentManager; + private readonly FileChangeTracker _fileTracker; + private readonly SnapshotChangeTracker _snapshotTracker; + private readonly EventHandler _changedOnDisk; + private readonly EventHandler _changedInEditor; + private readonly EventHandler _opened; + private readonly EventHandler _closed; + + private bool _disposed; + + public EditorDocument( + EditorDocumentManager documentManager, + string projectFilePath, + string documentFilePath, + TextLoader textLoader, + FileChangeTracker fileTracker, + ITextBuffer textBuffer, + EventHandler changedOnDisk, + EventHandler changedInEditor, + EventHandler opened, + EventHandler closed) + { + if (documentManager == null) + { + throw new ArgumentNullException(nameof(documentManager)); + } + + if (projectFilePath == null) + { + throw new ArgumentNullException(nameof(projectFilePath)); + } + + if (documentFilePath == null) + { + throw new ArgumentNullException(nameof(documentFilePath)); + } + + if (textLoader == null) + { + throw new ArgumentNullException(nameof(textLoader)); + } + + if (fileTracker == null) + { + throw new ArgumentNullException(nameof(fileTracker)); + } + + _documentManager = documentManager; + ProjectFilePath = projectFilePath; + DocumentFilePath = documentFilePath; + TextLoader = textLoader; + _fileTracker = fileTracker; + _changedOnDisk = changedOnDisk; + _changedInEditor = changedInEditor; + _opened = opened; + _closed = closed; + + _snapshotTracker = new SnapshotChangeTracker(); + _fileTracker.Changed += ChangeTracker_Changed; + + // Only one of these should be active at a time. + if (textBuffer == null) + { + _fileTracker.StartListening(); + } + else + { + _snapshotTracker.StartTracking(textBuffer); + + EditorTextBuffer = textBuffer; + EditorTextContainer = textBuffer.AsTextContainer(); + EditorTextContainer.TextChanged += TextContainer_Changed; + } + } + + public string ProjectFilePath { get; } + + public string DocumentFilePath { get; } + + public bool IsOpenInEditor => EditorTextBuffer != null; + + public SourceTextContainer EditorTextContainer { get; private set; } + + public ITextBuffer EditorTextBuffer { get; private set; } + + public TextLoader TextLoader { get; } + + public void ProcessOpen(ITextBuffer textBuffer) + { + if (textBuffer == null) + { + throw new ArgumentNullException(nameof(textBuffer)); + } + + _fileTracker.StopListening(); + + _snapshotTracker.StartTracking(textBuffer); + EditorTextBuffer = textBuffer; + EditorTextContainer = textBuffer.AsTextContainer(); + EditorTextContainer.TextChanged += TextContainer_Changed; + + _opened?.Invoke(this, EventArgs.Empty); + } + + public void ProcessClose() + { + _closed?.Invoke(this, EventArgs.Empty); + + _snapshotTracker.StopTracking(EditorTextBuffer); + + EditorTextContainer.TextChanged -= TextContainer_Changed; + EditorTextContainer = null; + EditorTextBuffer = null; + + _fileTracker.StartListening(); + } + + private void ChangeTracker_Changed(object sender, FileChangeEventArgs e) + { + if (e.Kind == FileChangeKind.Changed) + { + _changedOnDisk?.Invoke(this, EventArgs.Empty); + } + } + + private void TextContainer_Changed(object sender, TextChangeEventArgs e) + { + _changedInEditor?.Invoke(this, EventArgs.Empty); + } + + public void Dispose() + { + if (!_disposed) + { + _fileTracker.Changed -= ChangeTracker_Changed; + _fileTracker.StopListening(); + + EditorTextContainer.TextChanged -= TextContainer_Changed; + EditorTextContainer = null; + EditorTextBuffer = null; + + _documentManager.RemoveDocument(this); + + _disposed = true; + } + } + } +} diff --git a/src/Microsoft.VisualStudio.Editor.Razor/Documents/EditorDocumentManager.cs b/src/Microsoft.VisualStudio.Editor.Razor/Documents/EditorDocumentManager.cs new file mode 100644 index 0000000000..89f8745e6c --- /dev/null +++ b/src/Microsoft.VisualStudio.Editor.Razor/Documents/EditorDocumentManager.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Razor; + +namespace Microsoft.VisualStudio.Editor.Razor.Documents +{ + internal abstract class EditorDocumentManager : IWorkspaceService + { + public abstract EditorDocument GetOrCreateDocument( + DocumentKey key, + EventHandler changedOnDisk, + EventHandler changedInEditor, + EventHandler opened, + EventHandler closed); + + public abstract bool TryGetDocument(DocumentKey key, out EditorDocument document); + + public abstract bool TryGetMatchingDocuments(string filePath, out EditorDocument[] documents); + + public abstract void RemoveDocument(EditorDocument document); + } +} diff --git a/src/Microsoft.VisualStudio.Editor.Razor/Documents/EditorDocumentManagerBase.cs b/src/Microsoft.VisualStudio.Editor.Razor/Documents/EditorDocumentManagerBase.cs new file mode 100644 index 0000000000..34abfe79d2 --- /dev/null +++ b/src/Microsoft.VisualStudio.Editor.Razor/Documents/EditorDocumentManagerBase.cs @@ -0,0 +1,221 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Razor; +using Microsoft.VisualStudio.Text; + +namespace Microsoft.VisualStudio.Editor.Razor.Documents +{ + // Similar to the DocumentProvider in dotnet/Roslyn - but simplified quite a bit to remove + // concepts that we don't need. Responsible for providing data about text changes for documents + // and editor open/closed state. + internal abstract class EditorDocumentManagerBase : EditorDocumentManager + { + private readonly FileChangeTrackerFactory _fileChangeTrackerFactory; + private readonly ForegroundDispatcher _foregroundDispatcher; + + private readonly Dictionary _documents; + private readonly Dictionary> _documentsByFilePath; + protected readonly object _lock; + + public EditorDocumentManagerBase( + ForegroundDispatcher foregroundDispatcher, + FileChangeTrackerFactory fileChangeTrackerFactory) + { + if (foregroundDispatcher == null) + { + throw new ArgumentNullException(nameof(foregroundDispatcher)); + } + + if (fileChangeTrackerFactory == null) + { + throw new ArgumentNullException(nameof(fileChangeTrackerFactory)); + } + + _foregroundDispatcher = foregroundDispatcher; + _fileChangeTrackerFactory = fileChangeTrackerFactory; + + _documents = new Dictionary(); + _documentsByFilePath = new Dictionary>(FilePathComparer.Instance); + _lock = new object(); + } + + protected ForegroundDispatcher ForegroundDispatcher => _foregroundDispatcher; + + protected abstract ITextBuffer GetTextBufferForOpenDocument(string filePath); + + protected abstract void OnDocumentOpened(EditorDocument document); + + protected abstract void OnDocumentClosed(EditorDocument document); + + public sealed override bool TryGetDocument(DocumentKey key, out EditorDocument document) + { + _foregroundDispatcher.AssertForegroundThread(); + + lock (_lock) + { + return _documents.TryGetValue(key, out document); + } + } + + public sealed override bool TryGetMatchingDocuments(string filePath, out EditorDocument[] documents) + { + _foregroundDispatcher.AssertForegroundThread(); + + lock (_lock) + { + if (!_documentsByFilePath.TryGetValue(filePath, out var keys)) + { + documents = null; + return false; + } + + documents = new EditorDocument[keys.Count]; + for (var i = 0; i < keys.Count; i++) + { + documents[i] = _documents[keys[i]]; + } + + return true; + } + } + + public sealed override EditorDocument GetOrCreateDocument( + DocumentKey key, + EventHandler changedOnDisk, + EventHandler changedInEditor, + EventHandler opened, + EventHandler closed) + { + _foregroundDispatcher.AssertForegroundThread(); + + EditorDocument document; + + lock (_lock) + { + if (TryGetDocument(key, out document)) + { + return document; + } + + // Check if the document is already open and initialized, and associate a buffer if possible. + var textBuffer = GetTextBufferForOpenDocument(key.DocumentFilePath); + document = new EditorDocument( + this, + key.ProjectFilePath, + key.DocumentFilePath, + new FileTextLoader(key.DocumentFilePath, defaultEncoding: null), + _fileChangeTrackerFactory.Create(key.DocumentFilePath), + textBuffer, + changedOnDisk, + changedInEditor, + opened, + closed); + + _documents.Add(key, document); + + if (!_documentsByFilePath.TryGetValue(key.DocumentFilePath, out var documents)) + { + documents = new List(); + _documentsByFilePath.Add(key.DocumentFilePath, documents); + } + + if (!documents.Contains(key)) + { + documents.Add(key); + } + + if (document.IsOpenInEditor) + { + OnDocumentOpened(document); + } + + return document; + } + } + + protected void DocumentOpened(string filePath, ITextBuffer textBuffer) + { + if (filePath == null) + { + throw new ArgumentNullException(nameof(filePath)); + } + + if (textBuffer == null) + { + throw new ArgumentNullException(nameof(textBuffer)); + } + + _foregroundDispatcher.AssertForegroundThread(); + + lock (_lock) + { + if (TryGetMatchingDocuments(filePath, out var documents)) + { + for (var i = 0; i < documents.Length; i++) + { + var document = documents[i]; + + document.ProcessOpen(textBuffer); + OnDocumentOpened(document); + } + } + } + } + + protected void DocumentClosed(string filePath) + { + if (filePath == null) + { + throw new ArgumentNullException(nameof(filePath)); + } + + _foregroundDispatcher.AssertForegroundThread(); + + lock (_lock) + { + if (TryGetMatchingDocuments(filePath, out var documents)) + { + for (var i = 0; i < documents.Length; i++) + { + var document = documents[i]; + + document.ProcessClose(); + OnDocumentClosed(document); + } + } + } + } + + public sealed override void RemoveDocument(EditorDocument document) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + _foregroundDispatcher.AssertForegroundThread(); + + var key = new DocumentKey(document.ProjectFilePath, document.DocumentFilePath); + if (_documentsByFilePath.TryGetValue(document.DocumentFilePath, out var documents)) + { + documents.Remove(key); + + if (documents.Count == 0) + { + _documentsByFilePath.Remove(document.DocumentFilePath); + } + } + + _documents.Remove(key); + + if (document.IsOpenInEditor) + { + OnDocumentClosed(document); + } + } + } +} diff --git a/src/Microsoft.VisualStudio.Editor.Razor/Documents/EditorDocumentManagerListener.cs b/src/Microsoft.VisualStudio.Editor.Razor/Documents/EditorDocumentManagerListener.cs new file mode 100644 index 0000000000..33abac5804 --- /dev/null +++ b/src/Microsoft.VisualStudio.Editor.Razor/Documents/EditorDocumentManagerListener.cs @@ -0,0 +1,100 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis.Razor; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; + +namespace Microsoft.VisualStudio.Editor.Razor.Documents +{ + // Hooks up the document manager to project snapshot events. The project snapshot manager + // tracks the existance of projects/files and the the document manager watches for changes. + // + // This class forwards notifications in both directions. + [Export(typeof(ProjectSnapshotChangeTrigger))] + internal class EditorDocumentManagerListener : ProjectSnapshotChangeTrigger + { + private readonly EventHandler _onChangedOnDisk; + private readonly EventHandler _onChangedInEditor; + private readonly EventHandler _onOpened; + private readonly EventHandler _onClosed; + + private EditorDocumentManager _documentManager; + private ProjectSnapshotManagerBase _projectManager; + + [ImportingConstructor] + public EditorDocumentManagerListener() + { + _onChangedOnDisk = Document_ChangedOnDisk; + _onChangedInEditor = Document_ChangedInEditor; + _onOpened = Document_Opened; + _onClosed = Document_Closed; + } + + public override void Initialize(ProjectSnapshotManagerBase projectManager) + { + if (projectManager == null) + { + throw new ArgumentNullException(nameof(projectManager)); + } + + _projectManager = projectManager; + _documentManager = projectManager.Workspace.Services.GetRequiredService(); + + _projectManager.Changed += ProjectManager_Changed; + } + + private void ProjectManager_Changed(object sender, ProjectChangeEventArgs e) + { + switch (e.Kind) + { + case ProjectChangeKind.DocumentAdded: + { + var key = new DocumentKey(e.ProjectFilePath, e.DocumentFilePath); + var document = _documentManager.GetOrCreateDocument(key, _onChangedOnDisk, _onChangedOnDisk, _onOpened, _onClosed); + if (document.IsOpenInEditor) + { + Document_Opened(document, EventArgs.Empty); + } + + break; + } + + case ProjectChangeKind.DocumentRemoved: + { + // This class 'owns' the document entry so it's safe for us to dispose it. + if (_documentManager.TryGetDocument(new DocumentKey(e.ProjectFilePath, e.DocumentFilePath), out var document)) + { + document.Dispose(); + } + break; + } + } + } + + private void Document_ChangedOnDisk(object sender, EventArgs e) + { + var document = (EditorDocument)sender; + _projectManager.DocumentChanged(document.ProjectFilePath, document.DocumentFilePath, document.TextLoader); + } + + private void Document_ChangedInEditor(object sender, EventArgs e) + { + var document = (EditorDocument)sender; + _projectManager.DocumentChanged(document.ProjectFilePath, document.DocumentFilePath, document.EditorTextContainer.CurrentText); + } + + private void Document_Opened(object sender, EventArgs e) + { + var document = (EditorDocument)sender; + _projectManager.DocumentOpened(document.ProjectFilePath, document.DocumentFilePath, document.EditorTextContainer.CurrentText); + } + + private void Document_Closed(object sender, EventArgs e) + { + var document = (EditorDocument)sender; + _projectManager.DocumentClosed(document.ProjectFilePath, document.DocumentFilePath, document.TextLoader); + } + } +} diff --git a/src/Microsoft.VisualStudio.Editor.Razor/Documents/SnapshotChangeTracker.cs b/src/Microsoft.VisualStudio.Editor.Razor/Documents/SnapshotChangeTracker.cs new file mode 100644 index 0000000000..13a75d5ea7 --- /dev/null +++ b/src/Microsoft.VisualStudio.Editor.Razor/Documents/SnapshotChangeTracker.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.VisualStudio.Text; + +namespace Microsoft.VisualStudio.Editor.Razor.Documents +{ + // See ReiteratedVersionSnapshotTracker in dotnet/Roslyn -- this is primarily here for the + // side-effect of making sure the last 'reiterated' snapshot is retained in memory. + // + // Since we're interacting with the workspace in the same way, we're doing the same thing. + internal class SnapshotChangeTracker + { + private ITextBuffer _textBuffer; + private ITextSnapshot _snapshot; + + public void StartTracking(ITextBuffer buffer) + { + // buffer has changed. stop tracking old buffer + if (_textBuffer != null && buffer != _textBuffer) + { + _textBuffer.ChangedHighPriority -= OnTextBufferChanged; + + _textBuffer = null; + _snapshot = null; + } + + // start tracking new buffer + if (buffer != null && _snapshot == null) + { + _snapshot = buffer.CurrentSnapshot; + _textBuffer = buffer; + + buffer.ChangedHighPriority += OnTextBufferChanged; + } + } + + public void StopTracking(ITextBuffer buffer) + { + if (_textBuffer == buffer && buffer != null && _snapshot != null) + { + buffer.ChangedHighPriority -= OnTextBufferChanged; + + _textBuffer = null; + _snapshot = null; + } + } + + private void OnTextBufferChanged(object sender, TextContentChangedEventArgs e) + { + if (sender is ITextBuffer buffer) + { + var snapshot = _snapshot; + if (snapshot != null && snapshot.Version != null && e.AfterVersion != null && + snapshot.Version.ReiteratedVersionNumber < e.AfterVersion.ReiteratedVersionNumber) + { + _snapshot = e.After; + } + } + } + } +} diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/RunningDocumentTableEventSink.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/RunningDocumentTableEventSink.cs new file mode 100644 index 0000000000..76390ba81b --- /dev/null +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/RunningDocumentTableEventSink.cs @@ -0,0 +1,62 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.VisualStudio.Shell.Interop; + +namespace Microsoft.VisualStudio.Editor.Razor.Documents +{ + internal class RunningDocumentTableEventSink : IVsRunningDocTableEvents3 + { + private readonly VisualStudioEditorDocumentManager _documentManager; + + public RunningDocumentTableEventSink(VisualStudioEditorDocumentManager documentManager) + { + if (documentManager == null) + { + throw new ArgumentNullException(nameof(documentManager)); + } + + _documentManager = documentManager; + } + + public int OnAfterAttributeChangeEx(uint docCookie, uint grfAttribs, IVsHierarchy pHierOld, uint itemidOld, string pszMkDocumentOld, IVsHierarchy pHierNew, uint itemidNew, string pszMkDocumentNew) + { + // Document has been initialized. + if ((grfAttribs & (uint)__VSRDTATTRIB3.RDTA_DocumentInitialized) != 0) + { + _documentManager.DocumentOpened(docCookie); + } + + if ((grfAttribs & (uint)__VSRDTATTRIB.RDTA_MkDocument) != 0) + { + _documentManager.DocumentRenamed(docCookie, pszMkDocumentOld, pszMkDocumentNew); + } + + return VSConstants.S_OK; + } + + public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) + { + // Document is being closed + if (dwReadLocksRemaining + dwEditLocksRemaining == 0) + { + _documentManager.DocumentClosed(docCookie); + } + + return VSConstants.S_OK; + } + + public int OnBeforeSave(uint docCookie) => VSConstants.S_OK; + + public int OnAfterSave(uint docCookie) => VSConstants.S_OK; + + public int OnAfterAttributeChange(uint docCookie, uint grfAttribs) => VSConstants.S_OK; + + public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame) => VSConstants.S_OK; + + public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame) => VSConstants.S_OK; + + public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) => VSConstants.S_OK; + } +} diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/VisualStudioEditorDocumentManager.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/VisualStudioEditorDocumentManager.cs new file mode 100644 index 0000000000..ab6397d772 --- /dev/null +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/VisualStudioEditorDocumentManager.cs @@ -0,0 +1,254 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis.Razor; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.TextManager.Interop; + +namespace Microsoft.VisualStudio.Editor.Razor.Documents +{ + // Similar to the DocumentProvider in dotnet/Roslyn - but simplified quite a bit to remove + // concepts that we don't need. Responsible for providing data about text changes for documents + // and editor open/closed state. + internal class VisualStudioEditorDocumentManager : EditorDocumentManagerBase + { + private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactory; + + private readonly IVsRunningDocumentTable4 _runningDocumentTable; + private readonly uint _rdtCookie; + + private readonly Dictionary> _documentsByCookie; + private readonly Dictionary _cookiesByDocument; + + public VisualStudioEditorDocumentManager( + ForegroundDispatcher foregroundDispatcher, + FileChangeTrackerFactory fileChangeTrackerFactory, + IVsRunningDocumentTable runningDocumentTable, + IVsEditorAdaptersFactoryService editorAdaptersFactory) + : base(foregroundDispatcher, fileChangeTrackerFactory) + { + if (runningDocumentTable == null) + { + throw new ArgumentNullException(nameof(runningDocumentTable)); + } + + if (editorAdaptersFactory == null) + { + throw new ArgumentNullException(nameof(editorAdaptersFactory)); + } + + if (foregroundDispatcher == null) + { + throw new ArgumentNullException(nameof(foregroundDispatcher)); + } + + if (fileChangeTrackerFactory == null) + { + throw new ArgumentNullException(nameof(fileChangeTrackerFactory)); + } + + _runningDocumentTable = (IVsRunningDocumentTable4)runningDocumentTable; + _editorAdaptersFactory = editorAdaptersFactory; + + var hr = runningDocumentTable.AdviseRunningDocTableEvents(new RunningDocumentTableEventSink(this), out _rdtCookie); + Marshal.ThrowExceptionForHR(hr); + + _documentsByCookie = new Dictionary>(); + _cookiesByDocument = new Dictionary(); + } + + protected override ITextBuffer GetTextBufferForOpenDocument(string filePath) + { + if (filePath == null) + { + throw new ArgumentNullException(nameof(filePath)); + } + + // Check if the document is already open and initialized, and associate a buffer if possible. + var cookie = VSConstants.VSCOOKIE_NIL; + ITextBuffer textBuffer = null; + if (_runningDocumentTable.IsMonikerValid(filePath) && + ((cookie = _runningDocumentTable.GetDocumentCookie(filePath)) != VSConstants.VSCOOKIE_NIL) && + (_runningDocumentTable.GetDocumentFlags(cookie) & (uint)_VSRDTFLAGS4.RDT_PendingInitialization) == 0) + { + var vsTextBuffer = ((object)_runningDocumentTable.GetDocumentData(cookie)) as VsTextBuffer; + textBuffer = vsTextBuffer == null ? null : _editorAdaptersFactory.GetDocumentBuffer(vsTextBuffer); + return textBuffer; + } + + return null; + } + + protected override void OnDocumentOpened(EditorDocument document) + { + var cookie = _runningDocumentTable.GetDocumentCookie(document.DocumentFilePath); + if (cookie != VSConstants.VSCOOKIE_NIL) + { + TrackOpenDocument(cookie, new DocumentKey(document.ProjectFilePath, document.DocumentFilePath)); + } + } + + protected override void OnDocumentClosed(EditorDocument document) + { + var key = new DocumentKey(document.ProjectFilePath, document.DocumentFilePath); + if (_cookiesByDocument.TryGetValue(key, out var cookie)) + { + UntrackOpenDocument(cookie, key); + } + } + + public void DocumentOpened(uint cookie) + { + ForegroundDispatcher.AssertForegroundThread(); + + lock (_lock) + { + // Casts avoid dynamic + if ((object)(_runningDocumentTable.GetDocumentData(cookie)) is IVsTextBuffer vsTextBuffer) + { + var filePath = _runningDocumentTable.GetDocumentMoniker(cookie); + if (!TryGetMatchingDocuments(filePath, out var documents)) + { + // This isn't a document that we're interesting in. + return; + } + + var textBuffer = _editorAdaptersFactory.GetDataBuffer(vsTextBuffer); + if (textBuffer == null) + { + // The text buffer has not been created yet, register to be notified when it is. + VsTextBufferDataEventsSink.Subscribe(vsTextBuffer, () => + { + BufferLoaded(vsTextBuffer, filePath); + }); + + return; + } + + // It's possible that events could be fired out of order and that this is a rename. + if (_documentsByCookie.ContainsKey(cookie)) + { + DocumentClosed(cookie, exceptFilePath: filePath); + } + + BufferLoaded(textBuffer, filePath, documents); + } + } + } + + public void BufferLoaded(IVsTextBuffer vsTextBuffer, string filePath) + { + ForegroundDispatcher.AssertForegroundThread(); + + var textBuffer = _editorAdaptersFactory.GetDocumentBuffer(vsTextBuffer); + if (textBuffer != null) + { + // We potentially waited for the editor to initialize on this code path, so requery + // the documents. + if (TryGetMatchingDocuments(filePath, out var documents)) + { + BufferLoaded(textBuffer, filePath, documents); + } + } + } + + public void BufferLoaded(ITextBuffer textBuffer, string filePath, EditorDocument[] documents) + { + ForegroundDispatcher.AssertForegroundThread(); + + lock (_lock) + { + for (var i = 0; i < documents.Length; i++) + { + DocumentOpened(filePath, textBuffer); + } + } + } + + public void DocumentClosed(uint cookie, string exceptFilePath = null) + { + ForegroundDispatcher.AssertForegroundThread(); + + lock (_lock) + { + if (!_documentsByCookie.TryGetValue(cookie, out var documents)) + { + return; + } + + // We have to deal with some complications here due to renames and event ordering and such. + // We we might see multiple documents open for a cookie (due to linked files), but only one of them + // has been renamed. In that case, we just process the change that we know about. + var filePaths = new HashSet(documents.Select(d => d.DocumentFilePath)); + filePaths.Remove(exceptFilePath); + + foreach (var filePath in filePaths) + { + DocumentClosed(filePath); + } + } + } + + public void DocumentRenamed(uint cookie, string fromFilePath, string toFilePath) + { + ForegroundDispatcher.AssertForegroundThread(); + + // Ignore changes is casing + if (FilePathComparer.Instance.Equals(fromFilePath, toFilePath)) + { + return; + } + + lock (_lock) + { + // Treat a rename as a close + reopen. + // + // Due to ordering issues, we could see a partial rename. This is why we need to pass the new + // file path here. + DocumentClosed(cookie, exceptFilePath: toFilePath); + } + + // Try to open any existing documents that match the new name. + if ((_runningDocumentTable.GetDocumentFlags(cookie) & (uint)_VSRDTFLAGS4.RDT_PendingInitialization) == 0) + { + DocumentOpened(cookie); + } + } + + private void TrackOpenDocument(uint cookie, DocumentKey key) + { + if (!_documentsByCookie.TryGetValue(cookie, out var documents)) + { + documents = new List(); + _documentsByCookie.Add(cookie, documents); + } + + if (!documents.Contains(key)) + { + documents.Add(key); + } + + _cookiesByDocument[key] = cookie; + } + + private void UntrackOpenDocument(uint cookie, DocumentKey key) + { + if (_documentsByCookie.TryGetValue(cookie, out var documents)) + { + documents.Remove(key); + + if (documents.Count == 0) + { + _documentsByCookie.Remove(cookie); + } + } + + _cookiesByDocument.Remove(key); + } + } +} diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/VisualStudioEditorDocumentManagerFactory.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/VisualStudioEditorDocumentManagerFactory.cs new file mode 100644 index 0000000000..1fd0f508d3 --- /dev/null +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/VisualStudioEditorDocumentManagerFactory.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Razor; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; + +namespace Microsoft.VisualStudio.Editor.Razor.Documents +{ + [Shared] + [ExportWorkspaceServiceFactory(typeof(EditorDocumentManager), ServiceLayer.Host)] + internal class VisualStudioEditorDocumentManagerFactory : IWorkspaceServiceFactory + { + private readonly SVsServiceProvider _serviceProvider; + private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactory; + private readonly ForegroundDispatcher _foregroundDispatcher; + + [ImportingConstructor] + public VisualStudioEditorDocumentManagerFactory( + SVsServiceProvider serviceProvider, + IVsEditorAdaptersFactoryService editorAdaptersFactory, + ForegroundDispatcher foregroundDispatcher) + { + if (serviceProvider == null) + { + throw new ArgumentNullException(nameof(serviceProvider)); + } + + if (editorAdaptersFactory == null) + { + throw new ArgumentNullException(nameof(editorAdaptersFactory)); + } + + if (foregroundDispatcher == null) + { + throw new ArgumentNullException(nameof(foregroundDispatcher)); + } + + _serviceProvider = serviceProvider; + _editorAdaptersFactory = editorAdaptersFactory; + _foregroundDispatcher = foregroundDispatcher; + } + + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + { + if (workspaceServices == null) + { + throw new ArgumentNullException(nameof(workspaceServices)); + } + + var runningDocumentTable = (IVsRunningDocumentTable)_serviceProvider.GetService(typeof(SVsRunningDocumentTable)); + var fileChangeTrackerFactory = workspaceServices.GetRequiredService(); + return new VisualStudioEditorDocumentManager(_foregroundDispatcher, fileChangeTrackerFactory, runningDocumentTable, _editorAdaptersFactory); + } + } +} diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/VsTextBufferDataEventsSink.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/VsTextBufferDataEventsSink.cs new file mode 100644 index 0000000000..c629f929d5 --- /dev/null +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/VsTextBufferDataEventsSink.cs @@ -0,0 +1,56 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.VisualStudio.OLE.Interop; +using Microsoft.VisualStudio.TextManager.Interop; + +namespace Microsoft.VisualStudio.Editor.Razor.Documents +{ + internal class VsTextBufferDataEventsSink : IVsTextBufferDataEvents + { + private readonly Action _action; + private readonly IConnectionPoint _connectionPoint; + private uint _cookie; + + public static void Subscribe(IVsTextBuffer vsTextBuffer, Action action) + { + if (vsTextBuffer == null) + { + throw new ArgumentNullException(nameof(vsTextBuffer)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var connectionPointContainer = (IConnectionPointContainer)vsTextBuffer; + + var guid = typeof(IVsTextBufferDataEvents).GUID; + connectionPointContainer.FindConnectionPoint(ref guid, out var connectionPoint); + + var sink = new VsTextBufferDataEventsSink(connectionPoint, action); + connectionPoint.Advise(sink, out sink._cookie); + } + + private VsTextBufferDataEventsSink(IConnectionPoint connectionPoint, Action action) + { + _connectionPoint = connectionPoint; + _action = action; + } + + public void OnFileChanged(uint grfChange, uint dwFileAttrs) + { + // ignore + } + + public int OnLoadCompleted(int fReload) + { + _connectionPoint.Unadvise(_cookie); + _action(); + + return VSConstants.S_OK; + } + } +} diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Microsoft.VisualStudio.LanguageServices.Razor.csproj b/src/Microsoft.VisualStudio.LanguageServices.Razor/Microsoft.VisualStudio.LanguageServices.Razor.csproj index 26d1ac3025..32296b51c6 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/Microsoft.VisualStudio.LanguageServices.Razor.csproj +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Microsoft.VisualStudio.LanguageServices.Razor.csproj @@ -40,10 +40,19 @@ - + + + + + + + false + + + - - + @@ -268,7 +271,6 @@ - @@ -276,15 +278,12 @@ MSBuild Microsoft\VisualStudio\Razor\ - true MSBuild Microsoft\VisualStudio\Razor\Rules\ - - - diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DirectiveCollectionViewModel.cs b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DirectiveCollectionViewModel.cs new file mode 100644 index 0000000000..ea04f1512b --- /dev/null +++ b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DirectiveCollectionViewModel.cs @@ -0,0 +1,35 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#if RAZOR_EXTENSION_DEVELOPER_MODE + +using System; +using System.Collections.ObjectModel; +using System.Linq; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; + +namespace Microsoft.VisualStudio.RazorExtension.RazorInfo +{ + public class DirectiveCollectionViewModel : NotifyPropertyChanged + { + private readonly ProjectSnapshot _project; + + internal DirectiveCollectionViewModel(ProjectSnapshot project) + { + _project = project; + + Directives = new ObservableCollection(); + + var feature = _project.GetProjectEngine().EngineFeatures.OfType().FirstOrDefault(); + foreach (var directive in feature?.Directives ?? Array.Empty()) + { + Directives.Add(new DirectiveItemViewModel(directive)); + } + } + + public ObservableCollection Directives { get; } + } +} + +#endif diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DirectiveDescriptorViewModel.cs b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DirectiveItemViewModel.cs similarity index 87% rename from tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DirectiveDescriptorViewModel.cs rename to tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DirectiveItemViewModel.cs index d37ec05168..7dcd0a7fbb 100644 --- a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DirectiveDescriptorViewModel.cs +++ b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DirectiveItemViewModel.cs @@ -7,11 +7,11 @@ using Microsoft.AspNetCore.Razor.Language; namespace Microsoft.VisualStudio.RazorExtension.RazorInfo { - public class DirectiveDescriptorViewModel : NotifyPropertyChanged + public class DirectiveItemViewModel : NotifyPropertyChanged { private readonly DirectiveDescriptor _directive; - internal DirectiveDescriptorViewModel(DirectiveDescriptor directive) + internal DirectiveItemViewModel(DirectiveDescriptor directive) { _directive = directive; diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DocumentCollectionViewModel.cs b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DocumentCollectionViewModel.cs new file mode 100644 index 0000000000..b384dae501 --- /dev/null +++ b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DocumentCollectionViewModel.cs @@ -0,0 +1,81 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#if RAZOR_EXTENSION_DEVELOPER_MODE + +using System; +using System.Collections.ObjectModel; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; + +namespace Microsoft.VisualStudio.RazorExtension.RazorInfo +{ + public class DocumentCollectionViewModel : NotifyPropertyChanged + { + private readonly ProjectSnapshotManager _projectManager; + private readonly Action _errorHandler; + + private ProjectSnapshot _project; + + internal DocumentCollectionViewModel(ProjectSnapshotManager projectManager, ProjectSnapshot project, Action errorHandler) + { + _projectManager = projectManager; + _project = project; + _errorHandler = errorHandler; + + Documents = new ObservableCollection(); + + foreach (var filePath in project.DocumentFilePaths) + { + Documents.Add(new DocumentItemViewModel(projectManager, project.GetDocument(filePath), _errorHandler)); + } + } + + public ObservableCollection Documents { get; } + + internal void OnChange(ProjectChangeEventArgs e) + { + switch (e.Kind) + { + case ProjectChangeKind.DocumentAdded: + { + _project = _projectManager.GetLoadedProject(e.ProjectFilePath); + Documents.Add(new DocumentItemViewModel(_projectManager, _project.GetDocument(e.DocumentFilePath), _errorHandler)); + break; + } + + case ProjectChangeKind.DocumentRemoved: + { + _project = _projectManager.GetLoadedProject(e.ProjectFilePath); + + for (var i = Documents.Count - 1; i >= 0; i--) + { + if (Documents[i].FilePath == e.DocumentFilePath) + { + Documents.RemoveAt(i); + break; + } + } + + break; + } + + case ProjectChangeKind.DocumentChanged: + { + _project = _projectManager.GetLoadedProject(e.ProjectFilePath); + for (var i = Documents.Count - 1; i >= 0; i--) + { + if (Documents[i].FilePath == e.DocumentFilePath) + { + Documents[i] = new DocumentItemViewModel(_projectManager, _project.GetDocument(e.DocumentFilePath), _errorHandler); + break; + } + } + + break; + } + } + } + } +} + +#endif diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DocumentItemViewModel.cs b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DocumentItemViewModel.cs new file mode 100644 index 0000000000..6bcf750c85 --- /dev/null +++ b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DocumentItemViewModel.cs @@ -0,0 +1,70 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#if RAZOR_EXTENSION_DEVELOPER_MODE + +using System; +using System.Threading.Tasks; +using System.Windows; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; + +namespace Microsoft.VisualStudio.RazorExtension.RazorInfo +{ + public class DocumentItemViewModel : NotifyPropertyChanged + { + private readonly ProjectSnapshotManager _snapshotManager; + private readonly DocumentSnapshot _document; + private readonly Action _errorHandler; + + private Visibility _progressVisibility; + + internal DocumentItemViewModel(ProjectSnapshotManager snapshotManager, DocumentSnapshot document, Action errorHandler) + { + _snapshotManager = snapshotManager; + _document = document; + _errorHandler = errorHandler; + + InitializeGeneratedDocument(); + } + + public string FilePath => _document.FilePath; + + public string StatusText => _snapshotManager.IsDocumentOpen(_document.FilePath) ? "Open" : "Closed"; + + public string TargetPath => _document.TargetPath; + + public Visibility ProgressVisibility + { + get => _progressVisibility; + set + { + _progressVisibility = value; + OnPropertyChanged(); + } + } + + private async void InitializeGeneratedDocument() + { + ProgressVisibility = Visibility.Hidden; + + try + { + if (!_document.TryGetGeneratedOutput(out var result)) + { + ProgressVisibility = Visibility.Visible; + await _document.GetGeneratedOutputAsync(); + await Task.Delay(250); // Force a delay for the UI + } + } + catch (Exception ex) + { + _errorHandler(ex); + } + finally + { + ProgressVisibility = Visibility.Hidden; + } + } + } +} +#endif \ No newline at end of file diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DocumentSnapshotViewModel.cs b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DocumentSnapshotViewModel.cs deleted file mode 100644 index 29aafc8cdb..0000000000 --- a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/DocumentSnapshotViewModel.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -#if RAZOR_EXTENSION_DEVELOPER_MODE - -using Microsoft.CodeAnalysis.Razor.ProjectSystem; - -namespace Microsoft.VisualStudio.RazorExtension.RazorInfo -{ - public class DocumentSnapshotViewModel : NotifyPropertyChanged - { - private double _progress; - - internal DocumentSnapshotViewModel(DocumentSnapshot document) - { - Document = document; - - InitializeGeneratedDocument(); - } - - internal DocumentSnapshot Document { get; } - - public string FilePath => Document.FilePath; - - public string TargetPath => Document.TargetPath; - - public bool CodeGenerationInProgress => _progress < 100; - - public double CodeGenerationProgress => _progress; - - private async void InitializeGeneratedDocument() - { - _progress = 0; - OnPropertyChanged(nameof(CodeGenerationInProgress)); - OnPropertyChanged(nameof(CodeGenerationProgress)); - - try - { - await Document.GetGeneratedOutputAsync(); - } - finally - { - _progress = 100; - OnPropertyChanged(nameof(CodeGenerationInProgress)); - OnPropertyChanged(nameof(CodeGenerationProgress)); - } - } - } -} -#endif \ No newline at end of file diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/NullToEnabledConverter.cs b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/NullToEnabledConverter.cs new file mode 100644 index 0000000000..89d7f6263d --- /dev/null +++ b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/NullToEnabledConverter.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#if RAZOR_EXTENSION_DEVELOPER_MODE + +using System; +using System.Globalization; +using System.Windows.Data; + +namespace Microsoft.VisualStudio.RazorExtension.RazorInfo +{ + public class NullToEnabledConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (targetType == typeof(bool)) + { + return value != null; + } + + return null; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return null; + } + } +} +#endif diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/ProjectInfoViewModel.cs b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/ProjectInfoViewModel.cs deleted file mode 100644 index 8cfa8451a6..0000000000 --- a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/ProjectInfoViewModel.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -#if RAZOR_EXTENSION_DEVELOPER_MODE -using System.Collections.ObjectModel; -using System.Windows; - -namespace Microsoft.VisualStudio.RazorExtension.RazorInfo -{ - public class ProjectInfoViewModel : NotifyPropertyChanged - { - private ObservableCollection _directives; - private ObservableCollection _documents; - private ObservableCollection _tagHelpers; - private bool _tagHelpersLoading; - - public ObservableCollection Directives - { - get { return _directives; } - set - { - _directives = value; - OnPropertyChanged(); - } - } - - public ObservableCollection Documents - { - get { return _documents; } - set - { - _documents = value; - OnPropertyChanged(); - } - } - - public ObservableCollection TagHelpers - { - get { return _tagHelpers; } - set - { - _tagHelpers = value; - OnPropertyChanged(); - } - } - - public bool TagHelpersLoading - { - get { return _tagHelpersLoading; } - set - { - _tagHelpersLoading = value; - OnPropertyChanged(); - OnPropertyChanged(nameof(TagHelperProgressVisibility)); - } - } - - public Visibility TagHelperProgressVisibility => TagHelpersLoading ? Visibility.Visible : Visibility.Hidden; - - } -} -#endif diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/ProjectPropertyCollectionViewModel.cs b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/ProjectPropertyCollectionViewModel.cs new file mode 100644 index 0000000000..4a3c63d088 --- /dev/null +++ b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/ProjectPropertyCollectionViewModel.cs @@ -0,0 +1,41 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#if RAZOR_EXTENSION_DEVELOPER_MODE + +using System.Collections.ObjectModel; +using System.Linq; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; + +namespace Microsoft.VisualStudio.RazorExtension.RazorInfo +{ + public class ProjectPropertyCollectionViewModel : NotifyPropertyChanged + { + private readonly ProjectSnapshot _project; + + internal ProjectPropertyCollectionViewModel(ProjectSnapshot project) + { + _project = project; + + Properties = new ObservableCollection(); + Properties.Add(new ProjectPropertyItemViewModel("Language Version", _project.Configuration?.LanguageVersion.ToString())); + Properties.Add(new ProjectPropertyItemViewModel("Configuration", FormatConfiguration(_project))); + Properties.Add(new ProjectPropertyItemViewModel("Extensions", FormatExtensions(_project))); + Properties.Add(new ProjectPropertyItemViewModel("Workspace Project", _project.WorkspaceProject?.Name)); + } + + public ObservableCollection Properties { get; } + + private static string FormatConfiguration(ProjectSnapshot project) + { + return $"{project.Configuration.ConfigurationName} ({project.Configuration.GetType().Name})"; + } + + private static string FormatExtensions(ProjectSnapshot project) + { + return $"{string.Join(", ", project.Configuration.Extensions.Select(e => e.ExtensionName))}"; + } + } +} + +#endif diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/PropertyViewModel.cs b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/ProjectPropertyItemViewModel.cs similarity index 74% rename from tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/PropertyViewModel.cs rename to tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/ProjectPropertyItemViewModel.cs index e7586847ac..4a229a461a 100644 --- a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/PropertyViewModel.cs +++ b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/ProjectPropertyItemViewModel.cs @@ -5,9 +5,9 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo { - public class PropertyViewModel : NotifyPropertyChanged + public class ProjectPropertyItemViewModel : NotifyPropertyChanged { - internal PropertyViewModel(string name, string value) + internal ProjectPropertyItemViewModel(string name, string value) { Name = name; Value = value; diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/ProjectSnapshotViewModel.cs b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/ProjectSnapshotViewModel.cs deleted file mode 100644 index 4d786de67a..0000000000 --- a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/ProjectSnapshotViewModel.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -#if RAZOR_EXTENSION_DEVELOPER_MODE -using System; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; - -namespace Microsoft.VisualStudio.RazorExtension.RazorInfo -{ - public class ProjectSnapshotViewModel : NotifyPropertyChanged - { - internal ProjectSnapshotViewModel(ProjectSnapshot project) - { - Project = project; - - Id = project.WorkspaceProject?.Id; - Properties = new ObservableCollection(); - - InitializeProperties(); - } - - internal ProjectSnapshot Project { get; } - - public string Name => Path.GetFileNameWithoutExtension(Project.FilePath); - - public ProjectId Id { get; } - - public ObservableCollection Properties { get; } - - private void InitializeProperties() - { - Properties.Clear(); - - Properties.Add(new PropertyViewModel("Language Version", Project.Configuration?.LanguageVersion.ToString())); - Properties.Add(new PropertyViewModel("Configuration", FormatConfiguration(Project))); - Properties.Add(new PropertyViewModel("Extensions", FormatExtensions(Project))); - Properties.Add(new PropertyViewModel("Workspace Project", Project.WorkspaceProject?.Name)); - } - - private static string FormatConfiguration(ProjectSnapshot project) - { - return $"{project.Configuration.ConfigurationName} ({project.Configuration.GetType().Name})"; - } - - private static string FormatExtensions(ProjectSnapshot project) - { - return $"{string.Join(", ", project.Configuration.Extensions.Select(e => e.ExtensionName))}"; - } - } -} -#endif \ No newline at end of file diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/ProjectViewModel.cs b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/ProjectViewModel.cs index 1d1442c05f..fe385d58bd 100644 --- a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/ProjectViewModel.cs +++ b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/ProjectViewModel.cs @@ -8,8 +8,6 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo { public class ProjectViewModel : NotifyPropertyChanged { - private ProjectSnapshotViewModel _snapshot; - internal ProjectViewModel(string filePath) { FilePath = filePath; @@ -18,19 +16,6 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo public string FilePath { get; } public string Name => Path.GetFileNameWithoutExtension(FilePath); - - public bool HasSnapshot => Snapshot != null; - - public ProjectSnapshotViewModel Snapshot - { - get => _snapshot; - set - { - _snapshot = value; - OnPropertyChanged(); - OnPropertyChanged(nameof(HasSnapshot)); - } - } } } #endif \ No newline at end of file diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoToolWindow.cs b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoToolWindow.cs index c391b59a6c..f7ac4d9e2a 100644 --- a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoToolWindow.cs +++ b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoToolWindow.cs @@ -42,18 +42,16 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo _projectManager = _workspace.Services.GetLanguageServices(RazorLanguage.Name).GetRequiredService(); _projectManager.Changed += ProjectManager_Changed; - DataContext = new RazorInfoViewModel(this, _workspace, _projectManager, OnException); + DataContext = new RazorInfoViewModel(_workspace, _projectManager, OnException); + foreach (var project in _projectManager.Projects) { - DataContext.Projects.Add(new ProjectViewModel(project.FilePath) - { - Snapshot = new ProjectSnapshotViewModel(project), - }); + DataContext.Projects.Add(new ProjectViewModel(project.FilePath)); } if (DataContext.Projects.Count > 0) { - DataContext.CurrentProject = DataContext.Projects[0]; + DataContext.SelectedProject = DataContext.Projects[0]; } } @@ -69,70 +67,7 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo private void ProjectManager_Changed(object sender, ProjectChangeEventArgs e) { - switch (e.Kind) - { - case ProjectChangeKind.ProjectAdded: - { - var added = new ProjectViewModel(e.ProjectFilePath) - { - Snapshot = new ProjectSnapshotViewModel(_projectManager.GetLoadedProject(e.ProjectFilePath)), - }; - - DataContext.Projects.Add(added); - - if (DataContext.Projects.Count == 1) - { - DataContext.CurrentProject = added; - } - break; - } - - case ProjectChangeKind.ProjectRemoved: - { - ProjectViewModel removed = null; - for (var i = DataContext.Projects.Count - 1; i >= 0; i--) - { - var project = DataContext.Projects[i]; - if (project.FilePath == e.ProjectFilePath) - { - removed = project; - DataContext.Projects.RemoveAt(i); - break; - } - } - - if (DataContext.CurrentProject == removed) - { - DataContext.CurrentProject = null; - } - - break; - } - - case ProjectChangeKind.ProjectChanged: - case ProjectChangeKind.DocumentsChanged: - { - ProjectViewModel changed = null; - for (var i = DataContext.Projects.Count - 1; i >= 0; i--) - { - var project = DataContext.Projects[i]; - if (project.FilePath == e.ProjectFilePath) - { - changed = project; - changed.Snapshot = new ProjectSnapshotViewModel(_projectManager.GetLoadedProject(e.ProjectFilePath)); - DataContext.LoadProjectInfo(); - break; - } - } - - break; - } - - case ProjectChangeKind.DocumentContentChanged: - { - break; - } - } + DataContext.OnChange(e); } private void OnException(Exception ex) diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoToolWindowControl.xaml b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoToolWindowControl.xaml index 5ebd94fae5..f7fd186d6e 100644 --- a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoToolWindowControl.xaml +++ b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/RazorInfoToolWindowControl.xaml @@ -13,6 +13,7 @@ d:DesignWidth="300" Name="RazorInfoToolWindow"> +