diff --git a/build/Targets/Packages.props b/build/Targets/Packages.props index df4cb4948fbb493cec134416576e47e617c931dc..fc99a3c56ff4a037de988a6cfbef585224a16eb1 100644 --- a/build/Targets/Packages.props +++ b/build/Targets/Packages.props @@ -134,6 +134,7 @@ 10.0.30320 11.0.61031 12.1.30328 + 15.7.1 8.0.50728 15.8.27812-alpha 8.0.0.0-alpha diff --git a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests.cs b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests.cs index b21fc3ffe650277929de306c9bd1be60e3019653..1857d8b402559c80307653762e968d7b4dd5bff8 100644 --- a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests.cs +++ b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests.cs @@ -404,9 +404,10 @@ public void TestRemoveOpenedDocument() workspace.AddTestProject(project1); workspace.OnDocumentOpened(document.Id, document.GetOpenTextContainer()); - Assert.Throws(() => workspace.OnDocumentRemoved(document.Id)); + workspace.OnDocumentRemoved(document.Id); + + Assert.Empty(workspace.CurrentSolution.Projects.Single().Documents); - workspace.CloseDocument(document.Id); workspace.OnProjectRemoved(project1.Id); } } diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs index 76891ec92c87c0adb016617aacffe0d601e58705..7411e2e95e2aae0aa6d69ad52e3a42a3fd1f04d1 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs @@ -94,8 +94,6 @@ private void OnWorkspaceRegistrationChanged(object sender, EventArgs e) private void ConnectToWorkspace(Workspace workspace) { - AssertIsForeground(); - // If we disconnected before the workspace ever connected, just disregard if (_disconnected) { @@ -105,8 +103,26 @@ private void ConnectToWorkspace(Workspace workspace) _workspace = workspace; _workspace.WorkspaceChanged += this.OnWorkspaceChanged; - // For the first time you open the file, we'll start immediately - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0, updateUIWhenDone: true); + void connectToNewWorkspace() + { + // For the first time you open the file, we'll start immediately + StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0, updateUIWhenDone: true); + } + + if (IsForeground()) + { + connectToNewWorkspace(); + } + else + { + var asyncToken = _asyncListener.BeginAsyncOperation(nameof(ConnectToWorkspace)); + Task.Run(async () => + { + await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); + + connectToNewWorkspace(); + }).CompletesAsyncOperation(asyncToken); + } } private void DisconnectFromWorkspace() diff --git a/src/VisualStudio/CSharp/Impl/LanguageService/CSharpLanguageService_ICSharpProjectHost.cs b/src/VisualStudio/CSharp/Impl/LanguageService/CSharpLanguageService_ICSharpProjectHost.cs index 9a2a01bf74cfe03f1ddb9c84ec3c7e0166e4b575..60dd6e906794f844a07ed8edce73b806422b4466 100644 --- a/src/VisualStudio/CSharp/Impl/LanguageService/CSharpLanguageService_ICSharpProjectHost.cs +++ b/src/VisualStudio/CSharp/Impl/LanguageService/CSharpLanguageService_ICSharpProjectHost.cs @@ -2,6 +2,7 @@ using System.IO; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host; using Microsoft.VisualStudio.LanguageServices.CSharp.ProjectSystemShim; using Microsoft.VisualStudio.LanguageServices.CSharp.ProjectSystemShim.Interop; @@ -16,16 +17,12 @@ public void BindToProject(ICSharpProjectRoot projectRoot, IVsHierarchy hierarchy { var projectName = Path.GetFileName(projectRoot.GetFullProjectName()); // GetFullProjectName returns the path to the project file w/o the extension? - var projectTracker = Workspace.GetProjectTrackerAndInitializeIfNecessary(SystemServiceProvider); - var project = new CSharpProjectShim( projectRoot, - projectTracker, - id => new ProjectExternalErrorReporter(id, "CS", this.SystemServiceProvider), projectName, hierarchy, this.SystemServiceProvider, - this.Workspace, + this.Package.ComponentModel.GetService(), this.HostDiagnosticUpdateSource, this.Workspace.Services.GetLanguageServices(LanguageNames.CSharp).GetService()); diff --git a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSCompiler.cs b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSCompiler.cs index 39a323c905bed09d0c0d151ba712a03812364231..97b497283b8a82e14802df8e2184eff8a76043e5 100644 --- a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSCompiler.cs +++ b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSCompiler.cs @@ -49,8 +49,7 @@ public void BuildForEnc(ICSCompileProgress progress, ICSEncProjectServices encSe public string GetOutputFileName() { - // in-proc compiler, return the output we got set. - return this.ObjOutputPath; + return VisualStudioProject.IntermediateOutputFilePath; } public object CreateParser() diff --git a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSCompilerConfig.cs b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSCompilerConfig.cs index e7f0ff7a0e4040c6466a9cd3d0a89fd45e2e7d96..855672d25ef1e76bacbf8fe00f4e240a702518fa 100644 --- a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSCompilerConfig.cs +++ b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSCompilerConfig.cs @@ -12,7 +12,7 @@ public int GetOptionCount() { // NOTE: We return the length minus 1 to ensure that we're never called // with LARGEST_OPTION_ID. - return _options.Length - 1; + return (int)CompilerOptions.LARGEST_OPTION_ID - 1; } public void GetOptionInfoAt(int index, out CompilerOptions optionID, out string switchName, out string switchDescription, out uint flags) @@ -27,23 +27,18 @@ public void GetOptionInfoAtEx(int index, out CompilerOptions optionID, out strin public void ResetAllOptions() { - _options[(int)CompilerOptions.OPTID_CCSYMBOLS] = string.Empty; - _options[(int)CompilerOptions.OPTID_KEYFILE] = string.Empty; - _options[(int)CompilerOptions.OPTID_NOWARNLIST] = string.Empty; - _options[(int)CompilerOptions.OPTID_WARNASERRORLIST] = string.Empty; - _options[(int)CompilerOptions.OPTID_WARNNOTASERRORLIST] = string.Empty; - _options[(int)CompilerOptions.OPTID_UNSAFE] = false; - _options[(int)CompilerOptions.OPTID_XML_DOCFILE] = string.Empty; + VisualStudioProjectOptionsProcessor[CompilerOptions.OPTID_CCSYMBOLS] = string.Empty; + VisualStudioProjectOptionsProcessor[CompilerOptions.OPTID_KEYFILE] = string.Empty; + VisualStudioProjectOptionsProcessor[CompilerOptions.OPTID_NOWARNLIST] = string.Empty; + VisualStudioProjectOptionsProcessor[CompilerOptions.OPTID_WARNASERRORLIST] = string.Empty; + VisualStudioProjectOptionsProcessor[CompilerOptions.OPTID_WARNNOTASERRORLIST] = string.Empty; + VisualStudioProjectOptionsProcessor[CompilerOptions.OPTID_UNSAFE] = false; + VisualStudioProjectOptionsProcessor[CompilerOptions.OPTID_XML_DOCFILE] = string.Empty; } public int SetOption(CompilerOptions optionID, HACK_VariantStructure value) { - return SetOptionWithMarshaledValue(optionID, value.ConvertToObject()); - } - - public int SetOptionWithMarshaledValue(CompilerOptions optionID, object value) - { - SetOption(ref _options[(int)optionID], value); + VisualStudioProjectOptionsProcessor[optionID] = value.ConvertToObject(); if (optionID == CompilerOptions.OPTID_COMPATIBILITY) { @@ -60,12 +55,7 @@ public int SetOptionWithMarshaledValue(CompilerOptions optionID, object value) public void GetOption(CompilerOptions optionID, IntPtr variant) { - if (optionID < 0 || optionID >= CompilerOptions.LARGEST_OPTION_ID) - { - throw new ArgumentOutOfRangeException(nameof(optionID)); - } - - Marshal.GetNativeVariantForObject(_options[(int)optionID], variant); + Marshal.GetNativeVariantForObject(VisualStudioProjectOptionsProcessor[optionID], variant); } public int CommitChanges(ref ICSError error) diff --git a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSInputSet.cs b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSInputSet.cs index aa392289e5d029bee0b22b76ac5fc39c220f8447..cb41f5831fa3922546ebb7ab2ec7d2441899e225 100644 --- a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSInputSet.cs +++ b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSInputSet.cs @@ -1,7 +1,7 @@ // 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 System; -using Microsoft.CodeAnalysis; +using System.IO; using Microsoft.VisualStudio.LanguageServices.CSharp.ProjectSystemShim.Interop; namespace Microsoft.VisualStudio.LanguageServices.CSharp.ProjectSystemShim @@ -45,44 +45,17 @@ public void SetWin32Resource(string filename) public void SetOutputFileName(string filename) { - SetOutputPathAndRelatedData(filename); - } + VisualStudioProject.IntermediateOutputFilePath = filename; - public void SetOutputFileType(OutputFileType fileType) - { - OutputKind newOutputKind; - switch (fileType) + if (filename != null) { - case OutputFileType.Console: - newOutputKind = OutputKind.ConsoleApplication; - break; - - case OutputFileType.Windows: - newOutputKind = OutputKind.WindowsApplication; - break; - - case OutputFileType.Library: - newOutputKind = OutputKind.DynamicallyLinkedLibrary; - break; - - case OutputFileType.Module: - newOutputKind = OutputKind.NetModule; - break; - - case OutputFileType.AppContainer: - newOutputKind = OutputKind.WindowsRuntimeApplication; - break; - - case OutputFileType.WinMDObj: - newOutputKind = OutputKind.WindowsRuntimeMetadata; - break; - - default: - - throw new ArgumentException("fileType was not a valid OutputFileType", nameof(fileType)); + VisualStudioProject.AssemblyName = Path.GetFileNameWithoutExtension(filename); } + } - SetOption(ref _outputKind, newOutputKind); + public void SetOutputFileType(OutputFileType fileType) + { + VisualStudioProjectOptionsProcessor.SetOutputFileType(fileType); } public void SetImageBase(uint imageBase) @@ -92,7 +65,7 @@ public void SetImageBase(uint imageBase) public void SetMainClass(string fullyQualifiedClassName) { - SetOption(ref _mainTypeName, fullyQualifiedClassName); + VisualStudioProjectOptionsProcessor.SetMainTypeName(fullyQualifiedClassName); } public void SetWin32Icon(string iconFileName) @@ -124,14 +97,5 @@ public void SetWin32Manifest(string manifestFileName) { // This option is used only during emit. Since we no longer use our in-proc workspace to emit, we can ignore this value. } - - private void SetOption(ref T value, T newValue) - { - if (!object.Equals(value, newValue)) - { - value = newValue; - UpdateOptions(); - } - } } } diff --git a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSharpProjectSite.cs b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSharpProjectSite.cs index 044b8797928e3ada3a23fa31e6e8e3a78898bb51..f47c19b1db8d8e1939bb8dcb57d20821e40bbd38 100644 --- a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSharpProjectSite.cs +++ b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSharpProjectSite.cs @@ -1,16 +1,11 @@ // 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 System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.VisualStudio.LanguageServices.CSharp.ProjectSystemShim.Interop; -using Microsoft.VisualStudio.Shell.Interop; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.CSharp.ProjectSystemShim @@ -49,22 +44,11 @@ public void Unused() public void OnSourceFileAdded(string filename) { - var extension = Path.GetExtension(filename); - - // The Workflow MSBuild targets and CompileWorkflowTask choose to pass the .xoml files to the language - // service as if they were actual C# files. We should just ignore them. - if (extension.Equals(".xoml", StringComparison.OrdinalIgnoreCase)) - { - AddUntrackedFile(filename); - return; - } - // TODO: uncomment when fixing https://github.com/dotnet/roslyn/issues/5325 //var sourceCodeKind = extension.Equals(".csx", StringComparison.OrdinalIgnoreCase) // ? SourceCodeKind.Script // : SourceCodeKind.Regular; - var sourceCodeKind = SourceCodeKind.Regular; - AddFile(filename, sourceCodeKind); + AddFile(filename, SourceCodeKind.Regular); } public void OnSourceFileRemoved(string filename) @@ -91,34 +75,32 @@ public int OnImportAdded(string filename, string project) public int OnImportAddedEx(string filename, string project, CompilerOptions optionID) { - filename = FileUtilities.NormalizeAbsolutePath(filename); - if (optionID != CompilerOptions.OPTID_IMPORTS && optionID != CompilerOptions.OPTID_IMPORTSUSINGNOPIA) { throw new ArgumentException("optionID was an unexpected value.", nameof(optionID)); } bool embedInteropTypes = optionID == CompilerOptions.OPTID_IMPORTSUSINGNOPIA; - var properties = new MetadataReferenceProperties(embedInteropTypes: embedInteropTypes); + VisualStudioProject.AddMetadataReference(filename, new MetadataReferenceProperties(embedInteropTypes: embedInteropTypes)); - return AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(filename, properties); + return VSConstants.S_OK; } public void OnImportRemoved(string filename, string project) { filename = FileUtilities.NormalizeAbsolutePath(filename); - RemoveMetadataReference(filename); + VisualStudioProject.RemoveMetadataReference(filename, VisualStudioProject.GetPropertiesForMetadataReference(filename).Single()); } public void OnOutputFileChanged(string filename) { - // We have nothing to do here (yet) + // We have nothing to do here } public void OnActiveConfigurationChanged(string configName) { - // We have nothing to do here (yet) + // We have nothing to do here } public void OnProjectLoadCompletion() @@ -153,10 +135,10 @@ public int GetValidStartupClasses(IntPtr[] classNames, ref int count) { // If classNames is NULL, then we need to populate the number of valid startup // classes only - var project = Workspace.CurrentSolution.GetProject(Id); + var project = Workspace.CurrentSolution.GetProject(VisualStudioProject.Id); var compilation = project.GetCompilationAsync(CancellationToken.None).WaitAndGetResult(CancellationToken.None); - var entryPoints = GetEntryPoints(project, compilation); + var entryPoints = EntryPointFinder.FindEntryPoints(compilation.Assembly.GlobalNamespace); if (classNames == null) { @@ -165,7 +147,7 @@ public int GetValidStartupClasses(IntPtr[] classNames, ref int count) } else { - // We return s_false if we have more entrypoints than places in the array. + // We return S_FALSE if we have more entrypoints than places in the array. var entryPointNames = entryPoints.Select(e => e.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted))).ToArray(); if (entryPointNames.Length > classNames.Length) @@ -194,14 +176,14 @@ public int GetValidStartupClasses(IntPtr[] classNames, ref int count) } } - private IEnumerable GetEntryPoints(Project project, Compilation compilation) - { - return EntryPointFinder.FindEntryPoints(compilation.Assembly.GlobalNamespace); - } - public void OnAliasesChanged(string file, string project, int previousAliasesCount, string[] previousAliases, int currentAliasesCount, string[] currentAliases) { - UpdateMetadataReferenceAliases(file, ImmutableArray.CreateRange(currentAliases)); + using (VisualStudioProject.CreateBatchScope()) + { + var existingProperties = VisualStudioProject.GetPropertiesForMetadataReference(file).Single(); + VisualStudioProject.RemoveMetadataReference(file, existingProperties); + VisualStudioProject.AddMetadataReference(file, existingProperties.WithAliases(currentAliases)); + } } } } diff --git a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSharpVenusProjectSite.cs b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSharpVenusProjectSite.cs index 47fd48cd178b9354f8d6a6f57f1f772a461c2cd3..e5f75c1c1686b207738744a1531adffaffa7f675 100644 --- a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSharpVenusProjectSite.cs +++ b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSharpVenusProjectSite.cs @@ -19,16 +19,16 @@ public void RemoveReferenceToCodeDirectory(string assemblyFileName, ICSharpProje { CSharpProjectShim projectSite = GetProjectSite(project); - if (!this.CurrentProjectReferencesContains(projectSite.Id)) + var projectReferencesToRemove = VisualStudioProject.GetProjectReferences().Where(p => p.ProjectId == projectSite.VisualStudioProject.Id).ToList(); + + if (projectReferencesToRemove.Count == 0) { - throw new ArgumentException("The finalProject reference is not currently referenced by this finalProject.", "finalProject"); + throw new ArgumentException($"The project {nameof(project)} is not currently referenced by this project."); } - var projectReferences = GetCurrentProjectReferences().Where(r => r.ProjectId == projectSite.Id); - - foreach (var projectReference in projectReferences) + foreach (var projectReferenceToRemove in projectReferencesToRemove) { - RemoveProjectReference(projectReference); + VisualStudioProject.RemoveProjectReference(new ProjectReference(projectSite.VisualStudioProject.Id)); } } @@ -41,14 +41,20 @@ public void OnCodeDirectoryAliasesChanged(ICSharpProjectRoot project, int previo { CSharpProjectShim projectSite = GetProjectSite(project); - UpdateProjectReferenceAliases(projectSite, ImmutableArray.Create(currentAliases)); + using (VisualStudioProject.CreateBatchScope()) + { + var existingProjectReference = VisualStudioProject.GetProjectReferences().Single(p => p.ProjectId == projectSite.VisualStudioProject.Id); + + VisualStudioProject.RemoveProjectReference(existingProjectReference); + VisualStudioProject.AddProjectReference(new ProjectReference(existingProjectReference.ProjectId, ImmutableArray.Create(currentAliases), existingProjectReference.EmbedInteropTypes)); + } } - public void AddReferenceToCodeDirectoryEx(string assemblyFileName, ICSharpProjectRoot project, CompilerOptions optionID) + public void AddReferenceToCodeDirectoryEx(string assemblyFileName, ICSharpProjectRoot projectRoot, CompilerOptions optionID) { - CSharpProjectShim projectSite = GetProjectSite(project); + CSharpProjectShim projectSite = GetProjectSite(projectRoot); - AddProjectReference(new ProjectReference(projectSite.Id)); + VisualStudioProject.AddProjectReference(new ProjectReference(projectSite.VisualStudioProject.Id, embedInteropTypes: optionID == CompilerOptions.OPTID_IMPORTSUSINGNOPIA)); } /// @@ -66,7 +72,7 @@ private static CSharpProjectShim GetProjectSite(ICSharpProjectRoot project) // the project system. if (projectSite == null) { - throw new ArgumentException("finalProject was not properly sited with the languageServices service.", "finalProject"); + throw new ArgumentException($"{project} was not properly sited with the language service.", nameof(project)); } return projectSite; diff --git a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.IServiceProvider.cs b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.IServiceProvider.cs index 54da2c26cdffb126d27f88b8210f7c5a56a855cb..bd73cb422e16d8733b20f1973c6b9c4a16add172 100644 --- a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.IServiceProvider.cs +++ b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.IServiceProvider.cs @@ -10,7 +10,7 @@ internal partial class CSharpProjectShim : Microsoft.VisualStudio.OLE.Interop.IS { public int QueryService(ref Guid guidService, ref Guid riid, out IntPtr ppvObject) { - var serviceProvider = (Microsoft.VisualStudio.OLE.Interop.IServiceProvider)ServiceProvider; + var serviceProvider = (Microsoft.VisualStudio.OLE.Interop.IServiceProvider)_serviceProvider; return serviceProvider.QueryService(ref guidService, ref riid, out ppvObject); } } diff --git a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.OptionsProcessor.cs b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.OptionsProcessor.cs new file mode 100644 index 0000000000000000000000000000000000000000..c3da52850d19b3ab6839fa0b779a29cfad6ea8ba --- /dev/null +++ b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.OptionsProcessor.cs @@ -0,0 +1,288 @@ +// 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 System; +using System.Collections.Generic; +using System.IO; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Host; +using Microsoft.VisualStudio.LanguageServices.CSharp.ProjectSystemShim.Interop; +using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.CSharp.ProjectSystemShim +{ + internal partial class CSharpProjectShim + { + private class OptionsProcessor : VisualStudioProjectOptionsProcessor + { + private readonly VisualStudioProject _visualStudioProject; + + private readonly object[] _options = new object[(int)CompilerOptions.LARGEST_OPTION_ID]; + private string _mainTypeName; + private OutputKind _outputKind; + + public OptionsProcessor(VisualStudioProject visualStudioProject, HostWorkspaceServices workspaceServices) + : base(visualStudioProject, workspaceServices) + { + _visualStudioProject = visualStudioProject; + } + + public object this[CompilerOptions compilerOption] + { + get + { + return _options[(int)compilerOption]; + } + + set + { + if (object.Equals(_options[(int)compilerOption], value)) + { + return; + } + + _options[(int)compilerOption] = value; + UpdateProjectForNewHostValues(); + } + } + + protected override CompilationOptions ComputeCompilationOptionsWithHostValues(CompilationOptions compilationOptions, IRuleSetFile ruleSetFileOpt) + { + IDictionary ruleSetSpecificDiagnosticOptions = null; + + // Get options from the ruleset file, if any, first. That way project-specific + // options can override them. + ReportDiagnostic? ruleSetGeneralDiagnosticOption = null; + + // TODO: merge this core logic back down to the base of OptionsProcessor, since this should be the same for all languages. The CompilationOptions + // would then already contain the right information, and could be updated accordingly by the language-specific logic. + if (ruleSetFileOpt != null) + { + ruleSetGeneralDiagnosticOption = ruleSetFileOpt.GetGeneralDiagnosticOption(); + ruleSetSpecificDiagnosticOptions = new Dictionary(ruleSetFileOpt.GetSpecificDiagnosticOptions()); + } + else + { + ruleSetSpecificDiagnosticOptions = new Dictionary(); + } + + ReportDiagnostic generalDiagnosticOption; + var warningsAreErrors = GetNullableBooleanOption(CompilerOptions.OPTID_WARNINGSAREERRORS); + if (warningsAreErrors.HasValue) + { + generalDiagnosticOption = warningsAreErrors.Value ? ReportDiagnostic.Error : ReportDiagnostic.Default; + } + else if (ruleSetGeneralDiagnosticOption.HasValue) + { + generalDiagnosticOption = ruleSetGeneralDiagnosticOption.Value; + } + else + { + generalDiagnosticOption = ReportDiagnostic.Default; + } + + // Start with the rule set options + var diagnosticOptions = new Dictionary(ruleSetSpecificDiagnosticOptions); + + // Update the specific options based on the general settings + if (warningsAreErrors.HasValue && warningsAreErrors.Value == true) + { + foreach (var pair in ruleSetSpecificDiagnosticOptions) + { + if (pair.Value == ReportDiagnostic.Warn) + { + diagnosticOptions[pair.Key] = ReportDiagnostic.Error; + } + } + } + + // Update the specific options based on the specific settings + foreach (var diagnosticID in ParseWarningCodes(CompilerOptions.OPTID_WARNASERRORLIST)) + { + diagnosticOptions[diagnosticID] = ReportDiagnostic.Error; + } + + foreach (var diagnosticID in ParseWarningCodes(CompilerOptions.OPTID_WARNNOTASERRORLIST)) + { + if (ruleSetSpecificDiagnosticOptions.TryGetValue(diagnosticID, out var ruleSetOption)) + { + diagnosticOptions[diagnosticID] = ruleSetOption; + } + else + { + diagnosticOptions[diagnosticID] = ReportDiagnostic.Default; + } + } + + foreach (var diagnosticID in ParseWarningCodes(CompilerOptions.OPTID_NOWARNLIST)) + { + diagnosticOptions[diagnosticID] = ReportDiagnostic.Suppress; + } + + if (!Enum.TryParse(GetStringOption(CompilerOptions.OPTID_PLATFORM, ""), ignoreCase: true, result: out Platform platform)) + { + platform = Platform.AnyCpu; + } + + if (!int.TryParse(GetStringOption(CompilerOptions.OPTID_WARNINGLEVEL, defaultValue: ""), out var warningLevel)) + { + warningLevel = 4; + } + + // TODO: appConfigPath: GetFilePathOption(CompilerOptions.OPTID_FUSIONCONFIG), bug #869604 + + return ((CSharpCompilationOptions)compilationOptions).WithAllowUnsafe(GetBooleanOption(CompilerOptions.OPTID_UNSAFE)) + .WithOverflowChecks(GetBooleanOption(CompilerOptions.OPTID_CHECKED)) + .WithCryptoKeyContainer(GetStringOption(CompilerOptions.OPTID_KEYNAME, defaultValue: null)) + .WithCryptoKeyFile(GetFilePathRelativeOption(CompilerOptions.OPTID_KEYFILE)) + .WithDelaySign(GetNullableBooleanOption(CompilerOptions.OPTID_DELAYSIGN)) + .WithGeneralDiagnosticOption(generalDiagnosticOption) + .WithMainTypeName(_mainTypeName) + .WithModuleName(GetStringOption(CompilerOptions.OPTID_MODULEASSEMBLY, defaultValue: null)) + .WithOptimizationLevel(GetBooleanOption(CompilerOptions.OPTID_OPTIMIZATIONS) ? OptimizationLevel.Release : OptimizationLevel.Debug) + .WithOutputKind(_outputKind) + .WithPlatform(platform) + .WithSpecificDiagnosticOptions(diagnosticOptions) + .WithWarningLevel(warningLevel); + } + + private static string GetIdForErrorCode(int errorCode) + { + return "CS" + errorCode.ToString("0000"); + } + + private IEnumerable ParseWarningCodes(CompilerOptions compilerOptions) + { + Contract.ThrowIfFalse( + compilerOptions == CompilerOptions.OPTID_NOWARNLIST || + compilerOptions == CompilerOptions.OPTID_WARNASERRORLIST || + compilerOptions == CompilerOptions.OPTID_WARNNOTASERRORLIST); + + foreach (var warning in GetStringOption(compilerOptions, defaultValue: "").Split(new[] { ' ', ',', ';' }, StringSplitOptions.RemoveEmptyEntries)) + { + var warningStringID = warning; + if (int.TryParse(warning, out var warningId)) + { + warningStringID = GetIdForErrorCode(warningId); + } + + yield return warningStringID; + } + } + + private bool? GetNullableBooleanOption(CompilerOptions optionID) + { + return (bool?)_options[(int)optionID]; + } + + private bool GetBooleanOption(CompilerOptions optionID) + { + return GetNullableBooleanOption(optionID).GetValueOrDefault(defaultValue: false); + } + + private string GetFilePathRelativeOption(CompilerOptions optionID) + { + var path = GetStringOption(optionID, defaultValue: null); + + if (string.IsNullOrEmpty(path)) + { + return null; + } + + var directory = Path.GetDirectoryName(_visualStudioProject.FilePath); + + if (!string.IsNullOrEmpty(directory)) + { + return FileUtilities.ResolveRelativePath(path, directory); + } + + return null; + } + + private string GetStringOption(CompilerOptions optionID, string defaultValue) + { + string value = (string)_options[(int)optionID]; + + if (string.IsNullOrEmpty(value)) + { + return defaultValue; + } + else + { + return value; + } + } + + protected override ParseOptions ComputeParseOptionsWithHostValues(ParseOptions parseOptions) + { + var symbols = GetStringOption(CompilerOptions.OPTID_CCSYMBOLS, defaultValue: "").Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + + // The base implementation of OptionsProcessor already tried this, but it didn't have the real documentation + // path so we have to do it a second time + DocumentationMode documentationMode = DocumentationMode.Parse; + if (GetStringOption(CompilerOptions.OPTID_XML_DOCFILE, defaultValue: null) != null) + { + documentationMode = DocumentationMode.Diagnose; + } + + LanguageVersionFacts.TryParse(GetStringOption(CompilerOptions.OPTID_COMPATIBILITY, defaultValue: ""), out var languageVersion); + + return ((CSharpParseOptions)parseOptions).WithKind(SourceCodeKind.Regular) + .WithLanguageVersion(languageVersion) + .WithPreprocessorSymbols(symbols.AsImmutable()) + .WithDocumentationMode(documentationMode); + } + + public void SetOutputFileType(OutputFileType fileType) + { + OutputKind newOutputKind; + switch (fileType) + { + case OutputFileType.Console: + newOutputKind = OutputKind.ConsoleApplication; + break; + + case OutputFileType.Windows: + newOutputKind = OutputKind.WindowsApplication; + break; + + case OutputFileType.Library: + newOutputKind = OutputKind.DynamicallyLinkedLibrary; + break; + + case OutputFileType.Module: + newOutputKind = OutputKind.NetModule; + break; + + case OutputFileType.AppContainer: + newOutputKind = OutputKind.WindowsRuntimeApplication; + break; + + case OutputFileType.WinMDObj: + newOutputKind = OutputKind.WindowsRuntimeMetadata; + break; + + default: + + throw new ArgumentException("fileType was not a valid OutputFileType", nameof(fileType)); + } + + if (_outputKind != newOutputKind) + { + _outputKind = newOutputKind; + UpdateProjectForNewHostValues(); + } + } + + public void SetMainTypeName(string mainTypeName) + { + if (_mainTypeName != mainTypeName) + { + _mainTypeName = mainTypeName; + UpdateProjectForNewHostValues(); + } + } + } + } +} diff --git a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.cs b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.cs index a046e50ca576ef54da737e9ae41047fbfe6e6df0..33b89fb353cd65b4086d10c16b17e632df927728 100644 --- a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.cs +++ b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.cs @@ -1,24 +1,17 @@ // 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 System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -using EnvDTE; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Utilities; -using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host; +using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.LanguageServices.CSharp.ProjectSystemShim.Interop; using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Legacy; using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.TextManager.Interop; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.CSharp.ProjectSystemShim { @@ -30,7 +23,6 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp.ProjectSystemShim /// are in a separate files. Methods that are shared across multiple interfaces (which are /// effectively methods that just QI from one interface to another), are implemented here. /// - [ExcludeFromCodeCoverage] internal sealed partial class CSharpProjectShim : AbstractLegacyProject, ICodeModelInstanceFactory { /// @@ -41,40 +33,45 @@ internal sealed partial class CSharpProjectShim : AbstractLegacyProject, ICodeMo private ICSharpProjectRoot _projectRoot; - private OutputKind _outputKind = OutputKind.DynamicallyLinkedLibrary; - private string _mainTypeName; - private object[] _options = new object[(int)CompilerOptions.LARGEST_OPTION_ID]; + private readonly IServiceProvider _serviceProvider; + + /// + /// Fetches the options processor for this C# project. Equivalent to the underlying member, but fixed to the derived type. + /// + private new OptionsProcessor VisualStudioProjectOptionsProcessor + { + get => (OptionsProcessor)base.VisualStudioProjectOptionsProcessor; + set => base.VisualStudioProjectOptionsProcessor = value; + } public CSharpProjectShim( ICSharpProjectRoot projectRoot, - VisualStudioProjectTracker projectTracker, - Func reportExternalErrorCreatorOpt, string projectSystemName, IVsHierarchy hierarchy, IServiceProvider serviceProvider, - VisualStudioWorkspaceImpl visualStudioWorkspaceOpt, + IThreadingContext threadingContext, HostDiagnosticUpdateSource hostDiagnosticUpdateSourceOpt, ICommandLineParserService commandLineParserServiceOpt) - : base(projectTracker, - reportExternalErrorCreatorOpt, - projectSystemName, + : base(projectSystemName, hierarchy, LanguageNames.CSharp, serviceProvider, - visualStudioWorkspaceOpt, - hostDiagnosticUpdateSourceOpt, - commandLineParserServiceOpt) + threadingContext, + externalErrorReportingPrefix: "CS", + hostDiagnosticUpdateSourceOpt: hostDiagnosticUpdateSourceOpt, + commandLineParserServiceOpt: commandLineParserServiceOpt) { _projectRoot = projectRoot; + _serviceProvider = serviceProvider; _warningNumberArrayPointer = Marshal.AllocHGlobal(0); - // Ensure the default options are set up - ResetAllOptions(); - UpdateOptions(); + var componentModel = (IComponentModel)serviceProvider.GetService(typeof(SComponentModel)); - projectTracker.AddProject(this); + this.ProjectCodeModel = componentModel.GetService().CreateProjectCodeModel(VisualStudioProject.Id, this); + this.VisualStudioProjectOptionsProcessor = new OptionsProcessor(this.VisualStudioProject, Workspace.Services); - ProjectCodeModel = new ProjectCodeModel(projectTracker.ThreadingContext, this.Id, this, visualStudioWorkspaceOpt, ServiceProvider); + // Ensure the default options are set up + ResetAllOptions(); } public override void Disconnect() @@ -84,202 +81,6 @@ public override void Disconnect() base.Disconnect(); } - private string GetIdForErrorCode(int errorCode) - { - return "CS" + errorCode.ToString("0000"); - } - - protected override bool CanUseTextBuffer(ITextBuffer textBuffer) - { - // In Web scenarios, the project system tells us about all files in the project, including ".aspx" and ".cshtml" files. - // The impact of this is that we try to add a StandardTextDocument for the file, and parse it on disk, etc, which won't - // end well. We prevent that from happening by not allowing buffers that aren't of our content type to be used for - // StandardTextDocuments. In the web scenarios, we will instead end up creating a ContainedDocument that actually - // knows about the secondary buffer that contains valid code in our content type. - return textBuffer.ContentType.IsOfType(ContentTypeNames.CSharpContentType); - } - - protected override CompilationOptions CreateCompilationOptions(CommandLineArguments commandLineArguments, ParseOptions newParseOptions) - { - // Get the base options from command line arguments + common workspace defaults. - var options = (CSharpCompilationOptions)base.CreateCompilationOptions(commandLineArguments, newParseOptions); - - // Now override these with the options from our state. - IDictionary ruleSetSpecificDiagnosticOptions = null; - - // Get options from the ruleset file, if any, first. That way project-specific - // options can override them. - ReportDiagnostic? ruleSetGeneralDiagnosticOption = null; - if (this.RuleSetFile != null) - { - ruleSetGeneralDiagnosticOption = this.RuleSetFile.Target.GetGeneralDiagnosticOption(); - ruleSetSpecificDiagnosticOptions = new Dictionary(this.RuleSetFile.Target.GetSpecificDiagnosticOptions()); - } - else - { - ruleSetSpecificDiagnosticOptions = new Dictionary(); - } - - UpdateRuleSetError(this.RuleSetFile?.Target); - - ReportDiagnostic generalDiagnosticOption; - var warningsAreErrors = GetNullableBooleanOption(CompilerOptions.OPTID_WARNINGSAREERRORS); - if (warningsAreErrors.HasValue) - { - generalDiagnosticOption = warningsAreErrors.Value ? ReportDiagnostic.Error : ReportDiagnostic.Default; - } - else if (ruleSetGeneralDiagnosticOption.HasValue) - { - generalDiagnosticOption = ruleSetGeneralDiagnosticOption.Value; - } - else - { - generalDiagnosticOption = ReportDiagnostic.Default; - } - - // Start with the rule set options - IDictionary diagnosticOptions = new Dictionary(ruleSetSpecificDiagnosticOptions); - - // Update the specific options based on the general settings - if (warningsAreErrors.HasValue && warningsAreErrors.Value == true) - { - foreach (var pair in ruleSetSpecificDiagnosticOptions) - { - if (pair.Value == ReportDiagnostic.Warn) - { - diagnosticOptions[pair.Key] = ReportDiagnostic.Error; - } - } - } - - // Update the specific options based on the specific settings - foreach (var diagnosticID in ParseWarningCodes(CompilerOptions.OPTID_WARNASERRORLIST)) - { - diagnosticOptions[diagnosticID] = ReportDiagnostic.Error; - } - - foreach (var diagnosticID in ParseWarningCodes(CompilerOptions.OPTID_WARNNOTASERRORLIST)) - { - if (ruleSetSpecificDiagnosticOptions.TryGetValue(diagnosticID, out var ruleSetOption)) - { - diagnosticOptions[diagnosticID] = ruleSetOption; - } - else - { - diagnosticOptions[diagnosticID] = ReportDiagnostic.Default; - } - } - - foreach (var diagnosticID in ParseWarningCodes(CompilerOptions.OPTID_NOWARNLIST)) - { - diagnosticOptions[diagnosticID] = ReportDiagnostic.Suppress; - } - - if (!Enum.TryParse(GetStringOption(CompilerOptions.OPTID_PLATFORM, ""), ignoreCase: true, result: out Platform platform)) - { - platform = Platform.AnyCpu; - } - - if (!int.TryParse(GetStringOption(CompilerOptions.OPTID_WARNINGLEVEL, defaultValue: ""), out var warningLevel)) - { - warningLevel = 4; - } - - // TODO: appConfigPath: GetFilePathOption(CompilerOptions.OPTID_FUSIONCONFIG), bug #869604 - - return options.WithAllowUnsafe(GetBooleanOption(CompilerOptions.OPTID_UNSAFE)) - .WithOverflowChecks(GetBooleanOption(CompilerOptions.OPTID_CHECKED)) - .WithCryptoKeyContainer(GetStringOption(CompilerOptions.OPTID_KEYNAME, defaultValue: null)) - .WithCryptoKeyFile(GetFilePathRelativeOption(CompilerOptions.OPTID_KEYFILE)) - .WithDelaySign(GetNullableBooleanOption(CompilerOptions.OPTID_DELAYSIGN)) - .WithGeneralDiagnosticOption(generalDiagnosticOption) - .WithMainTypeName(_mainTypeName) - .WithModuleName(GetStringOption(CompilerOptions.OPTID_MODULEASSEMBLY, defaultValue: null)) - .WithOptimizationLevel(GetBooleanOption(CompilerOptions.OPTID_OPTIMIZATIONS) ? OptimizationLevel.Release : OptimizationLevel.Debug) - .WithOutputKind(_outputKind) - .WithPlatform(platform) - .WithSpecificDiagnosticOptions(diagnosticOptions) - .WithWarningLevel(warningLevel); - } - - private IEnumerable ParseWarningCodes(CompilerOptions compilerOptions) - { - Contract.ThrowIfFalse(compilerOptions == CompilerOptions.OPTID_NOWARNLIST || compilerOptions == CompilerOptions.OPTID_WARNASERRORLIST || compilerOptions == CompilerOptions.OPTID_WARNNOTASERRORLIST); - foreach (var warning in GetStringOption(compilerOptions, defaultValue: "").Split(new[] { ' ', ',', ';' }, StringSplitOptions.RemoveEmptyEntries)) - { - var warningStringID = warning; - if (int.TryParse(warning, out var warningId)) - { - warningStringID = GetIdForErrorCode(warningId); - } - - yield return warningStringID; - } - } - - private bool? GetNullableBooleanOption(CompilerOptions optionID) - { - return (bool?)_options[(int)optionID]; - } - - private bool GetBooleanOption(CompilerOptions optionID) - { - return GetNullableBooleanOption(optionID).GetValueOrDefault(defaultValue: false); - } - - private string GetFilePathRelativeOption(CompilerOptions optionID) - { - var path = GetStringOption(optionID, defaultValue: null); - - if (string.IsNullOrEmpty(path)) - { - return null; - } - - var directory = this.ContainingDirectoryPathOpt; - - if (!string.IsNullOrEmpty(directory)) - { - return FileUtilities.ResolveRelativePath(path, directory); - } - - return null; - } - - private string GetStringOption(CompilerOptions optionID, string defaultValue) - { - string value = (string)_options[(int)optionID]; - - if (string.IsNullOrEmpty(value)) - { - return defaultValue; - } - else - { - return value; - } - } - - protected override ParseOptions CreateParseOptions(CommandLineArguments commandLineArguments) - { - // Get the base parse options and override the defaults with the options from state. - var options = (CSharpParseOptions)base.CreateParseOptions(commandLineArguments); - var symbols = GetStringOption(CompilerOptions.OPTID_CCSYMBOLS, defaultValue: "").Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); - - DocumentationMode documentationMode = DocumentationMode.Parse; - if (GetStringOption(CompilerOptions.OPTID_XML_DOCFILE, defaultValue: null) != null) - { - documentationMode = DocumentationMode.Diagnose; - } - - LanguageVersionFacts.TryParse(GetStringOption(CompilerOptions.OPTID_COMPATIBILITY, defaultValue: ""), out var languageVersion); - - return options.WithKind(SourceCodeKind.Regular) - .WithLanguageVersion(languageVersion) - .WithPreprocessorSymbols(symbols.AsImmutable()) - .WithDocumentationMode(documentationMode); - } - ~CSharpProjectShim() { // Free the unmanaged memory we allocated in the constructor diff --git a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/HACK_VariantStructure.cs b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/HACK_VariantStructure.cs index f0bf3c377ce35a81ee70af4017271caf6b1286f9..1515e15eaf34d5c1cbe680ffe0d94fa004d3efa0 100644 --- a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/HACK_VariantStructure.cs +++ b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/HACK_VariantStructure.cs @@ -19,14 +19,16 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp.ProjectSystemShim /// as this structure. We can then pick out this broken pattern, and convert /// it to null instead of true. /// - [StructLayout(LayoutKind.Explicit, Size = 16)] internal struct HACK_VariantStructure { - [FieldOffset(0)] private short _type; - [FieldOffset(8)] + private short _padding1; + private short _padding2; + private short _padding3; + private short _booleanValue; + private IntPtr _padding4; // this will be aligned to the IntPtr-sized address public unsafe object ConvertToObject() { diff --git a/src/VisualStudio/CSharp/Test/CodeModel/FileCodeModelTestHelpers.cs b/src/VisualStudio/CSharp/Test/CodeModel/FileCodeModelTestHelpers.cs index 14bb3ab516be440232ef66702ebb6c58cc77fae2..babe8314ae25546414b363b341ef94917a3b6e1c 100644 --- a/src/VisualStudio/CSharp/Test/CodeModel/FileCodeModelTestHelpers.cs +++ b/src/VisualStudio/CSharp/Test/CodeModel/FileCodeModelTestHelpers.cs @@ -9,6 +9,7 @@ using Microsoft.VisualStudio.LanguageServices.Implementation.Interop; using Microsoft.VisualStudio.LanguageServices.UnitTests; using static Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel.CodeModelTestHelpers; +using Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel.Mocks; namespace Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.CodeModel { @@ -34,8 +35,14 @@ internal static class FileCodeModelTestHelpers WrapperPolicy.s_ComWrapperFactory = MockComWrapperFactory.Instance; var visualStudioWorkspaceMock = new MockVisualStudioWorkspace(workspace); - - var state = new CodeModelState(workspace.ExportProvider.GetExportedValue(), serviceProvider, project.LanguageServices, visualStudioWorkspaceMock); + var threadingContext = workspace.ExportProvider.GetExportedValue(); + + var state = new CodeModelState( + threadingContext, + serviceProvider, + project.LanguageServices, + visualStudioWorkspaceMock, + new ProjectCodeModelFactory(visualStudioWorkspaceMock, serviceProvider, threadingContext)); var codeModel = FileCodeModel.Create(state, null, document, new MockTextManagerAdapter()).Handle; diff --git a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj index 8aa4803c6d600ab580a5d1df5515e63f785da29d..3e739cf3ab48e318ca78ef58efe38606a1d7aad9 100644 --- a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj +++ b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj @@ -9,6 +9,7 @@ net46 $(RoslynDesktopRuntimeIdentifier) UnitTest + true diff --git a/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/AnalyzersTests.cs b/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/AnalyzersTests.cs index 5a52e1b3429ae1c3d3ff2c7afce359672b08fca5..1830a6acb3885330d003c1af9d10f134d39e7c8c 100644 --- a/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/AnalyzersTests.cs +++ b/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/AnalyzersTests.cs @@ -36,7 +36,6 @@ public void RuleSet_GeneralOption_CPS() Assert.Equal(expected: ReportDiagnostic.Default, actual: options.GeneralDiagnosticOption); - project.SetRuleSetFile(ruleSetFile.Path); project.SetOptions($"/ruleset:{ruleSetFile.Path}"); workspaceProject = environment.Workspace.CurrentSolution.Projects.Single(); @@ -58,36 +57,19 @@ public void RuleSet_SpecificOptions_CPS() "; - string ruleSetSource2 = @" - - - - - - -"; + using (var ruleSetFile = new DisposableFile()) using (var environment = new TestEnvironment()) using (var project = CSharpHelpers.CreateCSharpCPSProject(environment, "Test")) { - Assert.Null(project.RuleSetFile); - // Verify SetRuleSetFile updates the ruleset. File.WriteAllText(ruleSetFile.Path, ruleSetSource); - project.SetRuleSetFile(ruleSetFile.Path); - Assert.Equal(ruleSetFile.Path, project.RuleSetFile.Target.FilePath); + project.SetOptions($"/ruleset:{ruleSetFile.Path}"); // We need to explicitly update the command line arguments so the new ruleset is used to update options. project.SetOptions($"/ruleset:{ruleSetFile.Path}"); - var ca1012DiagnosticOption = project.CurrentCompilationOptions.SpecificDiagnosticOptions["CA1012"]; + var ca1012DiagnosticOption = environment.Workspace.CurrentSolution.Projects.Single().CompilationOptions.SpecificDiagnosticOptions["CA1012"]; Assert.Equal(expected: ReportDiagnostic.Error, actual: ca1012DiagnosticOption); - - // Verify edits to the ruleset file updates options. - var lastOptions = project.CurrentCompilationOptions; - File.WriteAllText(ruleSetFile.Path, ruleSetSource2); - project.OnRuleSetFileUpdateOnDisk(this, EventArgs.Empty); - ca1012DiagnosticOption = project.CurrentCompilationOptions.SpecificDiagnosticOptions["CA1012"]; - Assert.Equal(expected: ReportDiagnostic.Warn, actual: ca1012DiagnosticOption); } } } diff --git a/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/CSharpCompilerOptionsTests.cs b/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/CSharpCompilerOptionsTests.cs index 0eabca7b9b30e121439cfa7f39ff42fb3d5ea17c..1240070aa5ca470a5c14bc8533700c889bd7b563 100644 --- a/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/CSharpCompilerOptionsTests.cs +++ b/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/CSharpCompilerOptionsTests.cs @@ -2,6 +2,7 @@ using System; using System.IO; +using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.VisualStudio.LanguageServices.ProjectSystem; @@ -22,7 +23,8 @@ public void DocumentationModeSetToDiagnoseIfProducingDocFile_CPS() using (var environment = new TestEnvironment()) using (var project = CSharpHelpers.CreateCSharpCPSProject(environment, "Test", commandLineArguments: @"/doc:DocFile.xml")) { - Assert.Equal(DocumentationMode.Diagnose, project.CurrentParseOptions.DocumentationMode); + var parseOptions = environment.Workspace.CurrentSolution.Projects.Single().ParseOptions; + Assert.Equal(DocumentationMode.Diagnose, parseOptions.DocumentationMode); } } @@ -33,7 +35,8 @@ public void DocumentationModeSetToParseIfNotProducingDocFile_CPS() using (var environment = new TestEnvironment()) using (var project = CSharpHelpers.CreateCSharpCPSProject(environment, "Test", commandLineArguments: @"/doc:")) { - Assert.Equal(DocumentationMode.Parse, project.CurrentParseOptions.DocumentationMode); + var parseOptions = environment.Workspace.CurrentSolution.Projects.Single().ParseOptions; + Assert.Equal(DocumentationMode.Parse, parseOptions.DocumentationMode); } } @@ -63,57 +66,73 @@ public void ProjectOutputBinPathChange_CPS() using (var environment = new TestEnvironment()) using (var project = CSharpHelpers.CreateCSharpCPSProject(environment, "Test", commandLineArguments: $"/out:{initialObjPath}")) { - Assert.Equal(initialObjPath, project.ObjOutputPath); + Assert.Equal(initialObjPath, project.GetIntermediateOutputFilePath()); Assert.Equal(initialBinPath, project.BinOutputPath); // Change obj output folder from command line arguments - verify that objOutputPath changes, but binOutputPath is the same. var newObjPath = @"C:\NewFolder\test.dll"; project.SetOptions($"/out:{newObjPath}"); - Assert.Equal(newObjPath, project.ObjOutputPath); + Assert.Equal(newObjPath, project.GetIntermediateOutputFilePath()); Assert.Equal(initialBinPath, project.BinOutputPath); // Change output file name - verify that objOutputPath changes, but binOutputPath is the same. newObjPath = @"C:\NewFolder\test2.dll"; project.SetOptions($"/out:{newObjPath}"); - Assert.Equal(newObjPath, project.ObjOutputPath); + Assert.Equal(newObjPath, project.GetIntermediateOutputFilePath()); Assert.Equal(initialBinPath, project.BinOutputPath); // Change output file name and folder - verify that objOutputPath changes, but binOutputPath is the same. newObjPath = @"C:\NewFolder3\test3.dll"; project.SetOptions($"/out:{newObjPath}"); - Assert.Equal(newObjPath, project.ObjOutputPath); + Assert.Equal(newObjPath, project.GetIntermediateOutputFilePath()); Assert.Equal(initialBinPath, project.BinOutputPath); // Change bin output folder - verify that binOutputPath changes, but objOutputPath is the same. var newBinPath = @"C:\NewFolder4\test.dll"; - ((IWorkspaceProjectContext)project).BinOutputPath = newBinPath; - Assert.Equal(newObjPath, project.ObjOutputPath); + project.BinOutputPath = newBinPath; + Assert.Equal(newObjPath, project.GetIntermediateOutputFilePath()); Assert.Equal(newBinPath, project.BinOutputPath); // Change bin output folder to non-normalized path - verify that binOutputPath changes to normalized path, but objOutputPath is the same. newBinPath = @"test.dll"; var expectedNewBinPath = Path.Combine(Path.GetTempPath(), newBinPath); - ((IWorkspaceProjectContext)project).BinOutputPath = newBinPath; - Assert.Equal(newObjPath, project.ObjOutputPath); + project.BinOutputPath = newBinPath; + Assert.Equal(newObjPath, project.GetIntermediateOutputFilePath()); Assert.Equal(expectedNewBinPath, project.BinOutputPath); } } [WpfFact, WorkItem(14520, "https://github.com/dotnet/roslyn/issues/14520")] [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] - public void InvalidProjectOutputBinPaths_CPS() + public void InvalidProjectOutputBinPaths_CPS1() { using (var environment = new TestEnvironment()) using (var project1 = CSharpHelpers.CreateCSharpCPSProject(environment, "Test", binOutputPath: null)) // Null binOutputPath - using (var project2 = CSharpHelpers.CreateCSharpCPSProject(environment, "Test2", binOutputPath: String.Empty)) // Empty binOutputPath - using (var project3 = CSharpHelpers.CreateCSharpCPSProject(environment, "Test3", binOutputPath: "Test.dll")) // Non-rooted binOutputPath { // Null output path is allowed. Assert.Equal(null, project1.BinOutputPath); + } + } + [WpfFact, WorkItem(14520, "https://github.com/dotnet/roslyn/issues/14520")] + [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] + public void InvalidProjectOutputBinPaths_CPS2() + { + using (var environment = new TestEnvironment()) + using (var project2 = CSharpHelpers.CreateCSharpCPSProject(environment, "Test2", binOutputPath: String.Empty)) // Empty binOutputPath + { // Empty output path is not allowed, it gets reset to null. Assert.Equal(null, project2.BinOutputPath); + } + } + [WpfFact, WorkItem(14520, "https://github.com/dotnet/roslyn/issues/14520")] + [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] + public void InvalidProjectOutputBinPaths_CPS3() + { + using (var environment = new TestEnvironment()) + using (var project3 = CSharpHelpers.CreateCSharpCPSProject(environment, "Test3", binOutputPath: "Test.dll")) // Non-rooted binOutputPath + { // Non-rooted output path is not allowed, it gets reset to a temp rooted path. Assert.Equal(Path.Combine(Path.GetTempPath(), "Test.dll"), project3.BinOutputPath); } diff --git a/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/CSharpReferencesTests.cs b/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/CSharpReferencesTests.cs index 44ebac667e411a35f9126f6b3a069de793145dea..e0c8b558118bcf36153d67ff30e9d11807e93ee3 100644 --- a/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/CSharpReferencesTests.cs +++ b/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/CSharpReferencesTests.cs @@ -1,15 +1,17 @@ // 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 System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.VisualStudio.LanguageServices.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework; using Roslyn.Test.Utilities; using Xunit; namespace Roslyn.VisualStudio.CSharp.UnitTests.ProjectSystemShim.CPS { - using Microsoft.VisualStudio.LanguageServices.ProjectSystem; using static CSharpHelpers; [UseExportProvider] @@ -39,16 +41,29 @@ public void AddRemoveProjectAndMetadataReference_CPS() var metadaRefFilePath = @"c:\someAssembly.dll"; project3.AddMetadataReference(metadaRefFilePath, new MetadataReferenceProperties(embedInteropTypes: true)); - Assert.True(project3.GetCurrentProjectReferences().Any(pr => pr.ProjectId == project1.Id)); - Assert.True(project3.GetCurrentProjectReferences().Any(pr => pr.ProjectId == project2.Id)); - Assert.True(project3.GetCurrentProjectReferences().Any(pr => pr.ProjectId == project4.Id)); - Assert.True(project3.GetCurrentMetadataReferences().Any(mr => mr.FilePath == metadaRefFilePath)); + IEnumerable GetProject3ProjectReferences() + { + return environment.Workspace + .CurrentSolution.GetProject(project3.Id).ProjectReferences; + } + + IEnumerable GetProject3MetadataReferences() + { + return environment.Workspace.CurrentSolution.GetProject(project3.Id) + .MetadataReferences + .Cast(); + } + + Assert.True(GetProject3ProjectReferences().Any(pr => pr.ProjectId == project1.Id)); + Assert.True(GetProject3ProjectReferences().Any(pr => pr.ProjectId == project2.Id)); + Assert.True(GetProject3ProjectReferences().Any(pr => pr.ProjectId == project4.Id)); + Assert.True(GetProject3MetadataReferences().Any(mr => mr.FilePath == metadaRefFilePath)); // Change output path for project reference and verify the reference. ((IWorkspaceProjectContext)project4).BinOutputPath = @"C:\project4.dll"; Assert.Equal(@"C:\project4.dll", project4.BinOutputPath); - Assert.True(project3.GetCurrentProjectReferences().Any(pr => pr.ProjectId == project4.Id)); + Assert.True(GetProject3ProjectReferences().Any(pr => pr.ProjectId == project4.Id)); // Remove project reference project3.RemoveProjectReference(project1); @@ -59,9 +74,9 @@ public void AddRemoveProjectAndMetadataReference_CPS() // Remove metadata reference project3.RemoveMetadataReference(metadaRefFilePath); - Assert.False(project3.GetCurrentProjectReferences().Any(pr => pr.ProjectId == project1.Id)); - Assert.False(project3.GetCurrentProjectReferences().Any(pr => pr.ProjectId == project2.Id)); - Assert.False(project3.GetCurrentMetadataReferences().Any(mr => mr.FilePath == metadaRefFilePath)); + Assert.False(GetProject3ProjectReferences().Any(pr => pr.ProjectId == project1.Id)); + Assert.False(GetProject3ProjectReferences().Any(pr => pr.ProjectId == project2.Id)); + Assert.False(GetProject3MetadataReferences().Any(mr => mr.FilePath == metadaRefFilePath)); project1.Dispose(); project2.Dispose(); @@ -79,12 +94,20 @@ public void AddRemoveAnalyzerReference_CPS() { // Add analyzer reference var analyzerAssemblyFullPath = @"c:\someAssembly.dll"; + + bool AnalyzersContainsAnalyzer() + { + return environment.Workspace.CurrentSolution.Projects.Single() + .AnalyzerReferences.Cast() + .Any(a => a.FullPath == analyzerAssemblyFullPath); + } + project.AddAnalyzerReference(analyzerAssemblyFullPath); - Assert.True(project.CurrentProjectAnalyzersContains(analyzerAssemblyFullPath)); + Assert.True(AnalyzersContainsAnalyzer()); // Remove analyzer reference project.RemoveAnalyzerReference(analyzerAssemblyFullPath); - Assert.False(project.CurrentProjectAnalyzersContains(analyzerAssemblyFullPath)); + Assert.False(AnalyzersContainsAnalyzer()); } } } diff --git a/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/SourceFileHandlingTests.cs b/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/SourceFileHandlingTests.cs index bbe5f485c9476310d33a8be8a78483c2a7290edb..19f812ecbdf41f0c2fa0d59f1c8a4fa9dfc89713 100644 --- a/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/SourceFileHandlingTests.cs +++ b/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/SourceFileHandlingTests.cs @@ -1,6 +1,7 @@ // 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 System.Linq; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework; using Roslyn.Test.Utilities; @@ -8,6 +9,7 @@ namespace Roslyn.VisualStudio.CSharp.UnitTests.ProjectSystemShim.CPS { + using System.Collections.Generic; using static CSharpHelpers; [UseExportProvider] @@ -20,16 +22,18 @@ public void AddRemoveSourceFile_CPS() using (var environment = new TestEnvironment()) using (var project = CreateCSharpCPSProject(environment, "project1")) { - Assert.Empty(project.GetCurrentDocuments()); + IEnumerable GetCurrentDocuments() => environment.Workspace.CurrentSolution.Projects.Single().Documents; + + Assert.Empty(GetCurrentDocuments()); // Add source file var sourceFileFullPath = @"c:\source.cs"; project.AddSourceFile(sourceFileFullPath); - Assert.True(project.GetCurrentDocuments().Any(s => s.FilePath == sourceFileFullPath)); + Assert.True(GetCurrentDocuments().Any(s => s.FilePath == sourceFileFullPath)); // Remove source file project.RemoveSourceFile(sourceFileFullPath); - Assert.Empty(project.GetCurrentDocuments()); + Assert.Empty(GetCurrentDocuments()); } } @@ -40,16 +44,17 @@ public void AddRemoveAdditionalFile_CPS() using (var environment = new TestEnvironment()) using (var project = CreateCSharpCPSProject(environment, "project1")) { - Assert.Empty(project.GetCurrentAdditionalDocuments()); + IEnumerable GetCurrentAdditionalDocuments() => environment.Workspace.CurrentSolution.Projects.Single().AdditionalDocuments; + Assert.Empty(GetCurrentAdditionalDocuments()); // Add additional file var additionalFileFullPath = @"c:\source.cs"; project.AddAdditionalFile(additionalFileFullPath); - Assert.True(project.GetCurrentAdditionalDocuments().Any(s => s.FilePath == additionalFileFullPath)); + Assert.True(GetCurrentAdditionalDocuments().Any(s => s.FilePath == additionalFileFullPath)); // Remove additional file project.RemoveAdditionalFile(additionalFileFullPath); - Assert.Empty(project.GetCurrentAdditionalDocuments()); + Assert.Empty(GetCurrentAdditionalDocuments()); } } } diff --git a/src/VisualStudio/CSharp/Test/ProjectSystemShim/CSharpHelpers.cs b/src/VisualStudio/CSharp/Test/ProjectSystemShim/CSharpHelpers.cs index 5677f255da32091237a64c5183795144b48d0a53..7d6abec2dcc141457c5caa3d238a3609a4d36b40 100644 --- a/src/VisualStudio/CSharp/Test/ProjectSystemShim/CSharpHelpers.cs +++ b/src/VisualStudio/CSharp/Test/ProjectSystemShim/CSharpHelpers.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; @@ -12,9 +13,12 @@ using Microsoft.VisualStudio; using Microsoft.VisualStudio.LanguageServices.CSharp.ProjectSystemShim; using Microsoft.VisualStudio.LanguageServices.CSharp.ProjectSystemShim.Interop; +using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.CPS; +using Microsoft.VisualStudio.LanguageServices.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework; using Microsoft.VisualStudio.Shell.Interop; +using Xunit; namespace Roslyn.VisualStudio.CSharp.UnitTests.ProjectSystemShim { @@ -27,12 +31,10 @@ public static CSharpProjectShim CreateCSharpProject(TestEnvironment environment, return new CSharpProjectShim( new MockCSharpProjectRoot(hierarchy), - environment.ProjectTracker, - reportExternalErrorCreatorOpt: null, projectSystemName: projectName, hierarchy: hierarchy, serviceProvider: environment.ServiceProvider, - visualStudioWorkspaceOpt: null, + threadingContext: environment.ThreadingContext, hostDiagnosticUpdateSourceOpt: null, commandLineParserServiceOpt: new CSharpCommandLineParserService()); } @@ -56,19 +58,26 @@ public static CPSProject CreateCSharpCPSProject(TestEnvironment environment, str return CreateCSharpCPSProject(environment, projectName, projectFilePath, binOutputPath, projectGuid: Guid.NewGuid(), commandLineArguments: commandLineArguments); } + public static unsafe void SetOption(this CSharpProjectShim csharpProject, CompilerOptions optionID, object value) + { + Assert.Equal(8 + 2 * IntPtr.Size, sizeof(HACK_VariantStructure)); + Assert.Equal(8, (int)Marshal.OffsetOf("_booleanValue")); + + HACK_VariantStructure variant = default; + Marshal.GetNativeVariantForObject(value, (IntPtr)(&variant)); + csharpProject.SetOption(optionID, variant); + } + public static CPSProject CreateCSharpCPSProject(TestEnvironment environment, string projectName, string projectFilePath, string binOutputPath, Guid projectGuid, params string[] commandLineArguments) { var hierarchy = environment.CreateHierarchy(projectName, projectFilePath, "CSharp"); - - var cpsProject = CPSProjectFactory.CreateCPSProject( - environment.ProjectTracker, - environment.ServiceProvider, - hierarchy, + var cpsProjectFactory = environment.ExportProvider.GetExportedValue(); + var cpsProject = (CPSProject)cpsProjectFactory.CreateProjectContext( + LanguageNames.CSharp, projectName, projectFilePath, projectGuid, - LanguageNames.CSharp, - new TestCSharpCommandLineParserService(), + hierarchy, binOutputPath); var commandLineForOptions = string.Join(" ", commandLineArguments); @@ -80,16 +89,14 @@ public static CPSProject CreateCSharpCPSProject(TestEnvironment environment, str public static CPSProject CreateNonCompilableProject(TestEnvironment environment, string projectName, string projectFilePath) { var hierarchy = environment.CreateHierarchy(projectName, projectFilePath, ""); + var cpsProjectFactory = environment.ExportProvider.GetExportedValue(); - return CPSProjectFactory.CreateCPSProject( - environment.ProjectTracker, - environment.ServiceProvider, - hierarchy, + return (CPSProject)cpsProjectFactory.CreateProjectContext( + NoCompilationConstants.LanguageName, projectName, projectFilePath, Guid.NewGuid(), - NoCompilationConstants.LanguageName, - commandLineParserService: null, + hierarchy, binOutputPath: null); } diff --git a/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/AnalyzersTests.cs b/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/AnalyzersTests.cs index 974f2741e61c176e2fd79cf2a7b0df66cab7a9f6..ef190b88796af76be7a5a6e5b8cdd5b940b6a538 100644 --- a/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/AnalyzersTests.cs +++ b/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/AnalyzersTests.cs @@ -7,6 +7,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.VisualStudio.LanguageServices.CSharp.ProjectSystemShim.Interop; +using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Interop; using Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework; using Roslyn.Test.Utilities; using Xunit; @@ -52,15 +53,13 @@ public void RuleSet_GeneralOption() var project = CSharpHelpers.CreateCSharpProject(environment, "Test"); - var workspaceProject = environment.Workspace.CurrentSolution.Projects.Single(); - var options = (CSharpCompilationOptions)workspaceProject.CompilationOptions; + var options = (CSharpCompilationOptions)environment.GetUpdatedCompilationOptionOfSingleProject(); Assert.Equal(expected: ReportDiagnostic.Default, actual: options.GeneralDiagnosticOption); - project.SetRuleSetFile(ruleSetFile.Path); + ((IAnalyzerHost)project).SetRuleSetFile(ruleSetFile.Path); - workspaceProject = environment.Workspace.CurrentSolution.Projects.Single(); - options = (CSharpCompilationOptions)workspaceProject.CompilationOptions; + options = (CSharpCompilationOptions)environment.GetUpdatedCompilationOptionOfSingleProject(); Assert.Equal(expected: ReportDiagnostic.Error, actual: options.GeneralDiagnosticOption); } @@ -83,14 +82,14 @@ public void RuleSet_ProjectSettingOverridesGeneralOption() var project = CSharpHelpers.CreateCSharpProject(environment, "Test"); - project.SetRuleSetFile(ruleSetFile.Path); + ((IAnalyzerHost)project).SetRuleSetFile(ruleSetFile.Path); var workspaceProject = environment.Workspace.CurrentSolution.Projects.Single(); var options = (CSharpCompilationOptions)workspaceProject.CompilationOptions; Assert.Equal(expected: ReportDiagnostic.Warn, actual: options.GeneralDiagnosticOption); - project.SetOptionWithMarshaledValue(CompilerOptions.OPTID_WARNINGSAREERRORS, true); + project.SetOption(CompilerOptions.OPTID_WARNINGSAREERRORS, true); workspaceProject = environment.Workspace.CurrentSolution.Projects.Single(); options = (CSharpCompilationOptions)workspaceProject.CompilationOptions; @@ -119,10 +118,9 @@ public void RuleSet_SpecificOptions() var project = CSharpHelpers.CreateCSharpProject(environment, "Test"); - project.SetRuleSetFile(ruleSetFile.Path); + ((IAnalyzerHost)project).SetRuleSetFile(ruleSetFile.Path); - var workspaceProject = environment.Workspace.CurrentSolution.Projects.Single(); - var options = (CSharpCompilationOptions)workspaceProject.CompilationOptions; + var options = (CSharpCompilationOptions)environment.GetUpdatedCompilationOptionOfSingleProject(); var ca1012DiagnosticOption = options.SpecificDiagnosticOptions["CA1012"]; Assert.Equal(expected: ReportDiagnostic.Error, actual: ca1012DiagnosticOption); @@ -149,11 +147,10 @@ public void RuleSet_ProjectSettingsOverrideSpecificOptions() var project = CSharpHelpers.CreateCSharpProject(environment, "Test"); - project.SetRuleSetFile(ruleSetFile.Path); - project.SetOptionWithMarshaledValue(CompilerOptions.OPTID_WARNASERRORLIST, "1014"); + ((IAnalyzerHost)project).SetRuleSetFile(ruleSetFile.Path); + project.SetOption(CompilerOptions.OPTID_WARNASERRORLIST, "1014"); - var workspaceProject = environment.Workspace.CurrentSolution.Projects.Single(); - var options = (CSharpCompilationOptions)workspaceProject.CompilationOptions; + var options = (CSharpCompilationOptions)environment.GetUpdatedCompilationOptionOfSingleProject(); var ca1014DiagnosticOption = options.SpecificDiagnosticOptions["CS1014"]; Assert.Equal(expected: ReportDiagnostic.Error, actual: ca1014DiagnosticOption); @@ -170,11 +167,11 @@ public void SetRuleSetFile_RemoveExtraBackslashes() var project = CSharpHelpers.CreateCSharpProject(environment, "Test"); var pathWithExtraBackslashes = ruleSetFile.Path.Replace(@"\", @"\\"); - project.SetRuleSetFile(pathWithExtraBackslashes); + ((IAnalyzerHost)project).SetRuleSetFile(pathWithExtraBackslashes); - var projectRuleSetFile = project.RuleSetFile; + var projectRuleSetFile = project.VisualStudioProjectOptionsProcessor.ExplicitRuleSetFilePath; - Assert.Equal(expected: ruleSetFile.Path, actual: projectRuleSetFile.Target.FilePath); + Assert.Equal(expected: ruleSetFile.Path, actual: projectRuleSetFile); } } @@ -202,21 +199,21 @@ public void RuleSet_ProjectSettingsOverrideSpecificOptionsAndRestore() var project = CSharpHelpers.CreateCSharpProject(environment, "Test"); - project.SetRuleSetFile(ruleSetFile.Path); + ((IAnalyzerHost)project).SetRuleSetFile(ruleSetFile.Path); - project.SetOptionWithMarshaledValue(CompilerOptions.OPTID_WARNASERRORLIST, "1014"); + project.SetOption(CompilerOptions.OPTID_WARNASERRORLIST, "1014"); var options = environment.GetUpdatedCompilationOptionOfSingleProject(); Assert.Equal(expected: ReportDiagnostic.Error, actual: options.SpecificDiagnosticOptions["CS1014"]); - project.SetOptionWithMarshaledValue(CompilerOptions.OPTID_WARNNOTASERRORLIST, "1014"); + project.SetOption(CompilerOptions.OPTID_WARNNOTASERRORLIST, "1014"); options = environment.GetUpdatedCompilationOptionOfSingleProject(); Assert.Equal(expected: ReportDiagnostic.Suppress, actual: options.SpecificDiagnosticOptions["CS1014"]); - project.SetOptionWithMarshaledValue(CompilerOptions.OPTID_WARNNOTASERRORLIST, null); + project.SetOption(CompilerOptions.OPTID_WARNNOTASERRORLIST, null); options = environment.GetUpdatedCompilationOptionOfSingleProject(); Assert.Equal(expected: ReportDiagnostic.Error, actual: options.SpecificDiagnosticOptions["CS1014"]); - project.SetOptionWithMarshaledValue(CompilerOptions.OPTID_WARNASERRORLIST, null); + project.SetOption(CompilerOptions.OPTID_WARNASERRORLIST, null); options = environment.GetUpdatedCompilationOptionOfSingleProject(); Assert.Equal(expected: ReportDiagnostic.Suppress, actual: options.SpecificDiagnosticOptions["CS1014"]); } @@ -243,9 +240,9 @@ public void RuleSet_ProjectNoWarnOverridesOtherSettings() var project = CSharpHelpers.CreateCSharpProject(environment, "Test"); - project.SetRuleSetFile(ruleSetFile.Path); - project.SetOptionWithMarshaledValue(CompilerOptions.OPTID_NOWARNLIST, "1014"); - project.SetOptionWithMarshaledValue(CompilerOptions.OPTID_WARNASERRORLIST, "1014"); + ((IAnalyzerHost)project).SetRuleSetFile(ruleSetFile.Path); + project.SetOption(CompilerOptions.OPTID_NOWARNLIST, "1014"); + project.SetOption(CompilerOptions.OPTID_WARNASERRORLIST, "1014"); var workspaceProject = environment.Workspace.CurrentSolution.Projects.Single(); var options = (CSharpCompilationOptions)workspaceProject.CompilationOptions; diff --git a/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/CSharpCompilerOptionsTests.cs b/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/CSharpCompilerOptionsTests.cs index 8a4aa84d417a88cc71b05569eb30727cfb8178e6..0b788d289ea9314e4abaa736868a1db2ad8b969a 100644 --- a/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/CSharpCompilerOptionsTests.cs +++ b/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/CSharpCompilerOptionsTests.cs @@ -23,7 +23,7 @@ public void DocumentationModeSetToDiagnoseIfProducingDocFile() { var project = CSharpHelpers.CreateCSharpProject(environment, "Test"); - project.SetOptionWithMarshaledValue(CompilerOptions.OPTID_XML_DOCFILE, "DocFile.xml"); + project.SetOption(CompilerOptions.OPTID_XML_DOCFILE, "DocFile.xml"); var workspaceProject = environment.Workspace.CurrentSolution.Projects.Single(); var options = (CSharpParseOptions)workspaceProject.ParseOptions; @@ -41,7 +41,7 @@ public void DocumentationModeSetToParseIfNotProducingDocFile() { var project = CSharpHelpers.CreateCSharpProject(environment, "Test"); - project.SetOptionWithMarshaledValue(CompilerOptions.OPTID_XML_DOCFILE, ""); + project.SetOption(CompilerOptions.OPTID_XML_DOCFILE, ""); var workspaceProject = environment.Workspace.CurrentSolution.Projects.Single(); var options = (CSharpParseOptions)workspaceProject.ParseOptions; @@ -58,7 +58,7 @@ public void UseOPTID_COMPATIBILITY() { var project = CSharpHelpers.CreateCSharpProject(environment, "Test"); - project.SetOptionWithMarshaledValue(CompilerOptions.OPTID_COMPATIBILITY, "6"); + project.SetOption(CompilerOptions.OPTID_COMPATIBILITY, "6"); var workspaceProject = environment.Workspace.CurrentSolution.Projects.Single(); var options = (CSharpParseOptions)workspaceProject.ParseOptions; @@ -95,11 +95,11 @@ public void ProjectSettingsOptionAddAndRemove() { var project = CSharpHelpers.CreateCSharpProject(environment, "Test"); - project.SetOptionWithMarshaledValue(CompilerOptions.OPTID_WARNASERRORLIST, "1111"); + project.SetOption(CompilerOptions.OPTID_WARNASERRORLIST, "1111"); var options = environment.GetUpdatedCompilationOptionOfSingleProject(); Assert.Equal(expected: ReportDiagnostic.Error, actual: options.SpecificDiagnosticOptions["CS1111"]); - project.SetOptionWithMarshaledValue(CompilerOptions.OPTID_WARNASERRORLIST, null); + project.SetOption(CompilerOptions.OPTID_WARNASERRORLIST, null); options = environment.GetUpdatedCompilationOptionOfSingleProject(); Assert.False(options.SpecificDiagnosticOptions.ContainsKey("CS1111")); } diff --git a/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/CSharpReferencesTests.cs b/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/CSharpReferencesTests.cs deleted file mode 100644 index ef46e8c8f15489f1404fe4a8eb89cbf0eacbbd43..0000000000000000000000000000000000000000 --- a/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/CSharpReferencesTests.cs +++ /dev/null @@ -1,196 +0,0 @@ -// 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 System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework; -using Roslyn.Test.Utilities; -using Xunit; - -namespace Roslyn.VisualStudio.CSharp.UnitTests.ProjectSystemShim.LegacyProject -{ - using static CSharpHelpers; - - [UseExportProvider] - public class CSharpReferenceTests - { - [WpfFact] - [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] - public void AddingReferenceToProjectMetadataPromotesToProjectReference() - { - using (var environment = new TestEnvironment()) - { - var project1 = CreateCSharpProject(environment, "project1"); - project1.SetBinOutputPathAndRelatedData(@"c:\project1.dll"); - - var project2 = CreateCSharpProject(environment, "project2"); - project2.SetBinOutputPathAndRelatedData(@"c:\project2.dll"); - - // since this is known to be the output path of project1, the metadata reference is converted to a project reference - project2.OnImportAdded(@"c:\project1.dll", "project1"); - - Assert.Equal(true, project2.GetCurrentProjectReferences().Any(pr => pr.ProjectId == project1.Id)); - - project2.Disconnect(); - project1.Disconnect(); - } - } - - [WpfFact] - [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] - public void AddCyclicProjectMetadataReferences() - { - using (var environment = new TestEnvironment()) - { - var project1 = CreateCSharpProject(environment, "project1"); - project1.SetBinOutputPathAndRelatedData(@"c:\project1.dll"); - - var project2 = CreateCSharpProject(environment, "project2"); - project2.SetBinOutputPathAndRelatedData(@"c:\project2.dll"); - - project1.AddProjectReference(new ProjectReference(project2.Id)); - - // normally this metadata reference would be elevated to a project reference, but fails because of cyclicness - project2.OnImportAdded(@"c:\project1.dll", "project1"); - - Assert.Equal(true, project1.GetCurrentProjectReferences().Any(pr => pr.ProjectId == project2.Id)); - Assert.Equal(false, project2.GetCurrentProjectReferences().Any(pr => pr.ProjectId == project1.Id)); - - project2.Disconnect(); - project1.Disconnect(); - } - } - - [WpfFact] - [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] - public void AddCyclicProjectReferences() - { - using (var environment = new TestEnvironment()) - { - var project1 = CreateCSharpProject(environment, "project1"); - var project2 = CreateCSharpProject(environment, "project2"); - - project1.AddProjectReference(new ProjectReference(project2.Id)); - project2.AddProjectReference(new ProjectReference(project1.Id)); - - Assert.Equal(true, project1.GetCurrentProjectReferences().Any(pr => pr.ProjectId == project2.Id)); - Assert.Equal(false, project2.GetCurrentProjectReferences().Any(pr => pr.ProjectId == project1.Id)); - - project2.Disconnect(); - project1.Disconnect(); - } - } - - [WpfFact] - [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] - public void AddCyclicProjectReferencesDeep() - { - using (var environment = new TestEnvironment()) - { - var project1 = CreateCSharpProject(environment, "project1"); - var project2 = CreateCSharpProject(environment, "project2"); - var project3 = CreateCSharpProject(environment, "project3"); - var project4 = CreateCSharpProject(environment, "project4"); - - project1.AddProjectReference(new ProjectReference(project2.Id)); - project2.AddProjectReference(new ProjectReference(project3.Id)); - project3.AddProjectReference(new ProjectReference(project4.Id)); - project4.AddProjectReference(new ProjectReference(project1.Id)); - - Assert.Equal(true, project1.GetCurrentProjectReferences().Any(pr => pr.ProjectId == project2.Id)); - Assert.Equal(true, project2.GetCurrentProjectReferences().Any(pr => pr.ProjectId == project3.Id)); - Assert.Equal(true, project3.GetCurrentProjectReferences().Any(pr => pr.ProjectId == project4.Id)); - Assert.Equal(false, project4.GetCurrentProjectReferences().Any(pr => pr.ProjectId == project1.Id)); - - project4.Disconnect(); - project3.Disconnect(); - project2.Disconnect(); - project1.Disconnect(); - } - } - - [WorkItem(12707, "https://github.com/dotnet/roslyn/issues/12707")] - [WpfFact] - [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] - public void AddingProjectReferenceAndUpdateReferenceBinPath() - { - using (var environment = new TestEnvironment()) - { - var project1 = CreateCSharpProject(environment, "project1"); - project1.SetBinOutputPathAndRelatedData(@"c:\project1.dll"); - - var project2 = CreateCSharpProject(environment, "project2"); - project2.SetBinOutputPathAndRelatedData(@"c:\project2.dll"); - - // since this is known to be the output path of project1, the metadata reference is converted to a project reference - project2.OnImportAdded(@"c:\project1.dll", "project1"); - - Assert.Single(project2.GetCurrentProjectReferences().Where(pr => pr.ProjectId == project1.Id)); - - // update bin bath for project1. - project1.SetBinOutputPathAndRelatedData(@"c:\new_project1.dll"); - - // Verify project reference updated after bin path change. - Assert.Empty(project2.GetCurrentProjectReferences()); - - // This is a metadata reference to the original path - var metadataReference = Assert.Single(project2.GetCurrentMetadataReferences()); - Assert.Equal(@"c:\project1.dll", metadataReference.FilePath); - - project2.Disconnect(); - project1.Disconnect(); - } - } - - [WorkItem(12707, "https://github.com/dotnet/roslyn/issues/12707")] - [WpfFact] - [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] - public void DisconnectingProjectShouldConvertConvertedReferencesBack() - { - using (var environment = new TestEnvironment()) - { - var project1 = CreateCSharpProject(environment, "project1"); - project1.SetBinOutputPathAndRelatedData(@"c:\project1.dll"); - - var project2 = CreateCSharpProject(environment, "project2"); - project2.SetBinOutputPathAndRelatedData(@"c:\project2.dll"); - - // since this is known to be the output path of project1, the metadata reference is converted to a project reference - project2.OnImportAdded(@"c:\project1.dll", "project1"); - - Assert.Single(project2.GetCurrentProjectReferences().Where(pr => pr.ProjectId == project1.Id)); - - project1.Disconnect(); - - // Verify project reference updated after bin path change. - Assert.Empty(project2.GetCurrentProjectReferences()); - Assert.Single(project2.GetCurrentMetadataReferences().Where(r => r.FilePath == @"c:\project1.dll")); - - project2.Disconnect(); - } - } - - [WorkItem(461967, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/461967")] - [WpfFact()] - [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] - public void AddingMetadataReferenceToProjectThatCannotCompileInTheIdeKeepsMetadataReference() - { - using (var environment = new TestEnvironment()) - { - var project1 = CreateCSharpProject(environment, "project1"); - project1.SetBinOutputPathAndRelatedData(@"c:\project1.dll"); - - var project2 = CreateNonCompilableProject(environment, "project2", @"C:\project2.fsproj"); - project2.SetBinOutputPathAndRelatedData(@"c:\project2.dll"); - - project1.OnImportAdded(@"c:\project2.dll", "project2"); - - // We shoudl not have converted that to a project reference, because we would have no way to produce the compilation - Assert.Empty(project1.GetCurrentProjectReferences()); - - project2.Disconnect(); - project1.Disconnect(); - } - } - } -} diff --git a/src/VisualStudio/Core/Def/Implementation/AbstractVsTextViewFilter`2.cs b/src/VisualStudio/Core/Def/Implementation/AbstractVsTextViewFilter`2.cs index 558c822da0a7ab834b19e999958e6441ebe2d0eb..7386c8fec3625f2332aeb3247baf418b7eab815f 100644 --- a/src/VisualStudio/Core/Def/Implementation/AbstractVsTextViewFilter`2.cs +++ b/src/VisualStudio/Core/Def/Implementation/AbstractVsTextViewFilter`2.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Editor; +using Microsoft.VisualStudio.LanguageServices.Implementation.EditAndContinue; using Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.Text; @@ -140,10 +141,11 @@ int IVsReadOnlyViewNotification.OnDisabledEditingCommand(ref Guid pguidCmdGuid, foreach (var documentId in vsWorkspace.GetRelatedDocumentIds(container)) { - var hostProject = vsWorkspace.GetHostProject(documentId.ProjectId) as AbstractProject; - if (hostProject?.EditAndContinueImplOpt != null) + var project = VsENCRebuildableProjectImpl.TryGetRebuildableProject(documentId.ProjectId); + + if (project != null) { - if (hostProject.EditAndContinueImplOpt.OnEdit(documentId)) + if (project.OnEdit(documentId)) { break; } diff --git a/src/VisualStudio/Core/Def/Implementation/AnalyzerDependency/AnalyzerFileWatcherService.cs b/src/VisualStudio/Core/Def/Implementation/AnalyzerDependency/AnalyzerFileWatcherService.cs index d11978d2fc6603b964c766c0924de945f2357128..56a050a461beba6a39e1bcf436e5f0aff5f03c1d 100644 --- a/src/VisualStudio/Core/Def/Implementation/AnalyzerDependency/AnalyzerFileWatcherService.cs +++ b/src/VisualStudio/Core/Def/Implementation/AnalyzerDependency/AnalyzerFileWatcherService.cs @@ -152,10 +152,14 @@ private void Tracker_UpdatedOnDisk(object sender, EventArgs e) // Traverse the chain of requesting assemblies to get back to the original analyzer // assembly. - var projectsWithAnalyzer = _workspace.DeferredState.ProjectTracker.ImmutableProjects.Where(p => p.CurrentProjectAnalyzersContains(filePath)).ToArray(); - foreach (var project in projectsWithAnalyzer) + foreach (var project in _workspace.CurrentSolution.Projects) { - RaiseAnalyzerChangedWarning(project.Id, filePath); + var analyzerFileReferences = project.AnalyzerReferences.OfType(); + + if (analyzerFileReferences.Any(a => a.FullPath.Equals(filePath, StringComparison.OrdinalIgnoreCase))) + { + RaiseAnalyzerChangedWarning(project.Id, filePath); + } } } } diff --git a/src/VisualStudio/Core/Def/Implementation/CodeModel/IProjectCodeModelFactory.cs b/src/VisualStudio/Core/Def/Implementation/CodeModel/IProjectCodeModelFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..0384ed93655c4e442c661c9d376df4e7fc149d8f --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/CodeModel/IProjectCodeModelFactory.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.CodeAnalysis; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel +{ + internal interface IProjectCodeModelFactory + { + IProjectCodeModel CreateProjectCodeModel(ProjectId id, ICodeModelInstanceFactory codeModelInstanceFactory); + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/ContainedLanguageRefactorNotifyService.cs b/src/VisualStudio/Core/Def/Implementation/ContainedLanguageRefactorNotifyService.cs index bc7293043eec4a84722974de29558eb02bffed22..637a617761131b0d816c22d44c4bca39a9387bbc 100644 --- a/src/VisualStudio/Core/Def/Implementation/ContainedLanguageRefactorNotifyService.cs +++ b/src/VisualStudio/Core/Def/Implementation/ContainedLanguageRefactorNotifyService.cs @@ -31,9 +31,10 @@ public bool TryOnAfterGlobalSymbolRenamed(Workspace workspace, IEnumerable { - var vsDocument = workspace.GetHostDocument(documentId); - if (vsDocument == null) + var hierarchy = workspace.GetHierarchy(documentId.ProjectId); + if (hierarchy == null) { return; } - uint itemId = vsDocument.GetItemId(); - if (itemId == (uint)VSConstants.VSITEMID.Nil) + uint itemId = hierarchy.TryGetItemId(document.FilePath); + + if (itemId == VSConstants.VSITEMID_NIL) { - // it is no longer part of the solution return; } - if (ErrorHandler.Succeeded(vsDocument.Project.Hierarchy.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_ItemSubType, out var currentValue))) + if (ErrorHandler.Succeeded(hierarchy.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_ItemSubType, out var currentValue))) { var currentStringValue = string.IsNullOrEmpty(currentValue as string) ? null : (string)currentValue; if (string.Equals(currentStringValue, designerAttributeArgument, StringComparison.OrdinalIgnoreCase)) @@ -229,7 +229,7 @@ private void RegisterDesignerAttribute(Document document, string designerAttribu var designer = GetDesignerFromForegroundThread(); if (designer != null) { - designer.RegisterDesignViewAttribute(vsDocument.Project.Hierarchy, (int)itemId, dwClass: 0, pwszAttributeValue: designerAttributeArgument); + designer.RegisterDesignViewAttribute(hierarchy, (int)itemId, dwClass: 0, pwszAttributeValue: designerAttributeArgument); } } catch diff --git a/src/VisualStudio/Core/Def/Implementation/Diagnostics/VisualStudioDiagnosticAnalyzerService.cs b/src/VisualStudio/Core/Def/Implementation/Diagnostics/VisualStudioDiagnosticAnalyzerService.cs index b3ba9971736047af4214759c7bf674a3df647638..768a2f5610da693ee6252cacb2b3729147fecdf8 100644 --- a/src/VisualStudio/Core/Def/Implementation/Diagnostics/VisualStudioDiagnosticAnalyzerService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Diagnostics/VisualStudioDiagnosticAnalyzerService.cs @@ -15,11 +15,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics [Export(typeof(IVisualStudioDiagnosticAnalyzerService))] internal partial class VisualStudioDiagnosticAnalyzerService : IVisualStudioDiagnosticAnalyzerService { - private readonly VisualStudioWorkspaceImpl _workspace; + private readonly VisualStudioWorkspace _workspace; private readonly IDiagnosticAnalyzerService _diagnosticService; [ImportingConstructor] - public VisualStudioDiagnosticAnalyzerService(VisualStudioWorkspaceImpl workspace, IDiagnosticAnalyzerService diagnosticService) + public VisualStudioDiagnosticAnalyzerService(VisualStudioWorkspace workspace, IDiagnosticAnalyzerService diagnosticService) { _workspace = workspace; _diagnosticService = diagnosticService; @@ -35,10 +35,9 @@ public VisualStudioDiagnosticAnalyzerService(VisualStudioWorkspaceImpl workspace } // Analyzers are only supported for C# and VB currently. - var projectsWithHierarchy = (_workspace.DeferredState?.ProjectTracker.ImmutableProjects ?? ImmutableArray.Empty) + var projectsWithHierarchy = _workspace.CurrentSolution.Projects .Where(p => p.Language == LanguageNames.CSharp || p.Language == LanguageNames.VisualBasic) - .Where(p => p.Hierarchy == hierarchyOpt) - .Select(p => _workspace.CurrentSolution.GetProject(p.Id)); + .Where(p => _workspace.GetHierarchy(p.Id) == hierarchyOpt); if (projectsWithHierarchy.Count() <= 1) { diff --git a/src/VisualStudio/Core/Def/Implementation/Diagnostics/VisualStudioVenusSpanMappingService.cs b/src/VisualStudio/Core/Def/Implementation/Diagnostics/VisualStudioVenusSpanMappingService.cs index fa0df6a5ccf0b92dfc13224c931d04418b0e9097..2f83d2d2d4bd822dba35c695f17dc073f2cf36ca 100644 --- a/src/VisualStudio/Core/Def/Implementation/Diagnostics/VisualStudioVenusSpanMappingService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Diagnostics/VisualStudioVenusSpanMappingService.cs @@ -159,7 +159,7 @@ private static bool TryAdjustSpanIfNeededForVenus(VisualStudioWorkspaceImpl work return false; } - var containedDocument = workspace.GetHostDocument(documentId) as ContainedDocument; + var containedDocument = workspace.TryGetContainedDocument(documentId); if (containedDocument == null) { return false; @@ -173,9 +173,8 @@ private static bool TryAdjustSpanIfNeededForVenus(VisualStudioWorkspaceImpl work iEndIndex = originalColumn }; - var containedLanguage = containedDocument.ContainedLanguage; - var bufferCoordinator = containedLanguage.BufferCoordinator; - var containedLanguageHost = containedLanguage.ContainedLanguageHost; + var bufferCoordinator = containedDocument.BufferCoordinator; + var containedLanguageHost = containedDocument.ContainedLanguageHost; var spansOnPrimaryBuffer = new TextManager.Interop.TextSpan[1]; if (VSConstants.S_OK == bufferCoordinator.MapSecondaryToPrimarySpan(originalSpanOnSecondaryBuffer, spansOnPrimaryBuffer)) diff --git a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/VsENCRebuildableProjectImpl.cs b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/VsENCRebuildableProjectImpl.cs index 411f34735012d03f055c1c6177c2bbcee6945188..41dd9b3a50f975df8f1aef02434260acc4c953ce 100644 --- a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/VsENCRebuildableProjectImpl.cs +++ b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/VsENCRebuildableProjectImpl.cs @@ -1,6 +1,7 @@ // 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 System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -9,6 +10,7 @@ using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -39,7 +41,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.EditAndContinue { internal sealed class VsENCRebuildableProjectImpl { - private readonly AbstractProject _vsProject; + private readonly VisualStudioWorkspace _workspace; + private readonly VisualStudioProject _project; // number of projects that are in the debug state: private static int s_debugStateProjectCount; @@ -89,27 +92,33 @@ internal sealed class VsENCRebuildableProjectImpl #endregion + private readonly static ConditionalWeakTable _rebuildableProjectImpls = new ConditionalWeakTable(); + private bool IsDebuggable => _mvid != Guid.Empty; - internal VsENCRebuildableProjectImpl(AbstractProject project) + internal VsENCRebuildableProjectImpl(VisualStudioWorkspace workspace, VisualStudioProject project, IServiceProvider serviceProvider) { + Debug.Assert(workspace != null); Debug.Assert(project != null); - _vsProject = project; + _workspace = workspace; + _project = project; - _debuggingService = _vsProject.Workspace.Services.GetService(); - _trackingService = _vsProject.Workspace.Services.GetService(); - _notifications = _vsProject.Workspace.Services.GetService(); + _debuggingService = _workspace.Services.GetRequiredService(); + _trackingService = _workspace.Services.GetRequiredService(); + _notifications = _workspace.Services.GetService(); - _debugEncNotify = (IDebugEncNotify)project.ServiceProvider.GetService(typeof(SVsShellDebugger)); + _debugEncNotify = (IDebugEncNotify)serviceProvider.GetService(typeof(SVsShellDebugger)); - var componentModel = (IComponentModel)project.ServiceProvider.GetService(typeof(SComponentModel)); + var componentModel = (IComponentModel)serviceProvider.GetService(typeof(SComponentModel)); _threadingContext = componentModel.GetService(); _diagnosticProvider = componentModel.GetService(); _editorAdaptersFactoryService = componentModel.GetService(); _moduleMetadataProvider = componentModel.GetService(); _encService = _debuggingService.EditAndContinueServiceOpt; + _rebuildableProjectImpls.Add(project.Id, this); + Debug.Assert(_debugEncNotify != null); Debug.Assert(_encService != null); Debug.Assert(_trackingService != null); @@ -118,6 +127,12 @@ internal VsENCRebuildableProjectImpl(AbstractProject project) Debug.Assert(_moduleMetadataProvider != null); } + internal static VsENCRebuildableProjectImpl TryGetRebuildableProject(ProjectId projectId) + { + _rebuildableProjectImpls.TryGetValue(projectId, out var rebuildableProject); + return rebuildableProject; + } + // called from an edit filter if an edit of a read-only buffer is attempted: internal bool OnEdit(DocumentId documentId) { @@ -141,11 +156,9 @@ internal bool OnEdit(DocumentId documentId) return; } - var visualStudioWorkspace = _vsProject.Workspace as VisualStudioWorkspaceImpl; - var hostProject = visualStudioWorkspace?.GetHostProject(documentId.ProjectId) as AbstractProject; - if (hostProject?.EditAndContinueImplOpt?._mvid != Guid.Empty) + if (documentId.ProjectId == _project.Id && _mvid != Guid.Empty) { - _debugEncNotify.NotifyEncEditDisallowedByProject(hostProject.Hierarchy); + _debugEncNotify.NotifyEncEditDisallowedByProject(_workspace.GetHierarchy(documentId.ProjectId)); return; } @@ -204,7 +217,7 @@ public int StartDebuggingPE() { try { - log.Write("Enter Debug Mode: project '{0}'", _vsProject.DisplayName); + log.Write("Enter Debug Mode: project '{0}'", _project.Id.ToString()); // EnC service is global (per solution), but the debugger calls this for each project. // Avoid starting the debug session if it has already been started. @@ -217,13 +230,13 @@ public int StartDebuggingPE() _debuggingService.OnBeforeDebuggingStateChanged(DebuggingState.Design, DebuggingState.Run); - _encService.StartDebuggingSession(_vsProject.Workspace.CurrentSolution); + _encService.StartDebuggingSession(_workspace.CurrentSolution); s_encDebuggingSessionInfo = new EncDebuggingSessionInfo(); s_readOnlyDocumentTracker = new VsReadOnlyDocumentTracker(_threadingContext, _encService, _editorAdaptersFactoryService); } - string outputPath = _vsProject.ObjOutputPath; + string outputPath = _project.IntermediateOutputFilePath; // The project doesn't produce a debuggable binary or we can't read it. // Continue on since the debugger ignores HResults and we need to handle subsequent calls. @@ -231,26 +244,25 @@ public int StartDebuggingPE() { try { - InjectFault_MvidRead(); _mvid = ReadMvid(outputPath); } catch (Exception e) when (e is FileNotFoundException || e is DirectoryNotFoundException) { // If the project isn't referenced by the project being debugged it might not be built. // In that case EnC is never allowed for the project, and thus we can assume the project hasn't entered debug state. - log.Write("StartDebuggingPE: '{0}' metadata file not found: '{1}'", _vsProject.DisplayName, outputPath); + log.Write("StartDebuggingPE: '{0}' metadata file not found: '{1}'", _project.Id.ToString(), outputPath); _mvid = Guid.Empty; } catch (Exception e) { - log.Write("StartDebuggingPE: error reading MVID of '{0}' ('{1}'): {2}", _vsProject.DisplayName, outputPath, e.Message); + log.Write("StartDebuggingPE: error reading MVID of '{0}' ('{1}'): {2}", _project.Id.ToString(), outputPath, e.Message); _mvid = Guid.Empty; ReportInternalError(InternalErrorCode.ErrorReadingFile, new[] { outputPath, e.Message }); } } else { - log.Write("StartDebuggingPE: project has no output path '{0}'", _vsProject.DisplayName); + log.Write("StartDebuggingPE: project has no output path '{0}'", _project.Id.ToString()); _mvid = Guid.Empty; } @@ -296,7 +308,7 @@ public int StopDebuggingPE() { try { - log.Write("Exit Debug Mode: project '{0}'", _vsProject.DisplayName); + log.Write("Exit Debug Mode: project '{0}'", _project.Id.ToString()); Debug.Assert(s_breakStateEnteredProjects.Count == 0); Debug.Assert(s_pendingNonRemappableRegions.Count == 0); @@ -329,7 +341,7 @@ public int StopDebuggingPE() { // an error might have been reported: var errorId = new EncErrorId(_encService.DebuggingSession, EditAndContinueDiagnosticUpdateSource.InternalErrorId); - _diagnosticProvider.ClearDiagnostics(errorId, _vsProject.Workspace.CurrentSolution, _vsProject.Id, documentIdOpt: null); + _diagnosticProvider.ClearDiagnostics(errorId, _workspace.CurrentSolution, _project.Id, documentIdOpt: null); } _committedBaseline = null; @@ -400,7 +412,7 @@ public int GetPEidentity(Guid[] pMVID, string[] pbstrPEName) if (pbstrPEName != null && pbstrPEName.Length != 0) { - var outputPath = _vsProject.ObjOutputPath; + var outputPath = _project.IntermediateOutputFilePath; Debug.Assert(outputPath != null); pbstrPEName[0] = Path.GetFileName(outputPath); @@ -421,7 +433,7 @@ public int EnterBreakStateOnPE(ENC_BREAKSTATE_REASON encBreakReason, ENC_ACTIVE_ { using (NonReentrantContext) { - log.Write("Enter {2}Break Mode: project '{0}', AS#: {1}", _vsProject.DisplayName, pActiveStatements != null ? pActiveStatements.Length : -1, encBreakReason == ENC_BREAKSTATE_REASON.ENC_BREAK_EXCEPTION ? "Exception " : ""); + log.Write("Enter {2}Break Mode: project '{0}', AS#: {1}", _project.Id.ToString(), pActiveStatements != null ? pActiveStatements.Length : -1, encBreakReason == ENC_BREAKSTATE_REASON.ENC_BREAK_EXCEPTION ? "Exception " : ""); Debug.Assert(cActiveStatements == (pActiveStatements != null ? pActiveStatements.Length : 0)); Debug.Assert(s_breakStateProjectCount < s_debugStateProjectCount); @@ -433,7 +445,7 @@ public int EnterBreakStateOnPE(ENC_BREAKSTATE_REASON encBreakReason, ENC_ACTIVE_ { _debuggingService.OnBeforeDebuggingStateChanged(DebuggingState.Run, DebuggingState.Break); - s_breakStateEntrySolution = _vsProject.Workspace.CurrentSolution; + s_breakStateEntrySolution = _workspace.CurrentSolution; // TODO: This is a workaround for a debugger bug in which not all projects exit the break state. // Reset the project count. @@ -453,7 +465,7 @@ public int EnterBreakStateOnPE(ENC_BREAKSTATE_REASON encBreakReason, ENC_ACTIVE_ // If pActiveStatements is null the EnC Manager failed to retrieve the module corresponding // to the project in the debuggee. We won't include such projects in the edit session. - s_breakStateEnteredProjects.Add(KeyValuePairUtil.Create(_vsProject.Id, state)); + s_breakStateEnteredProjects.Add(KeyValuePairUtil.Create(_project.Id, state)); s_breakStateProjectCount++; // EnC service is global, but the debugger calls this for each project. @@ -564,15 +576,17 @@ public int GetENCBuildState(ENC_BUILD_STATE[] pENCBuildState) { // Fetch the latest snapshot of the project and get an analysis summary for any changes // made since the break mode was entered. - var currentProject = _vsProject.Workspace.CurrentSolution.GetProject(_vsProject.Id); + var currentProject = _workspace.CurrentSolution.GetProject(_project.Id); if (currentProject == null) { // If the project has yet to be loaded into the solution (which may be the case, // since they are loaded on-demand), then it stands to reason that it has not yet // been modified. - // TODO (https://github.com/dotnet/roslyn/issues/1204): this check should be unnecessary. + // TODO (https://github.com/dotnet/roslyn/issues/1204): this check should be unnecessary, + // especially because projects themselves are always added to the workspace, even if their contents + // are not. _lastEditSessionSummary = ProjectAnalysisSummary.NoChanges; - log.Write("Project '{0}' has not yet been loaded into the solution", _vsProject.DisplayName); + log.Write("Project '{0}' has not yet been loaded into the solution", _project.Id.ToString()); } else { @@ -608,7 +622,7 @@ public int GetENCBuildState(ENC_BUILD_STATE[] pENCBuildState) } log.Write("EnC state of '{0}' queried: {1}{2}", - _vsProject.DisplayName, + _project.Id.ToString(), EncStateToString(pENCBuildState[0]), _encService.EditSession != null ? "" : " (no session)"); @@ -656,7 +670,7 @@ public int ExitBreakStateOnPE() return VSConstants.S_OK; } - log.Write("Exit Break Mode: project '{0}'", _vsProject.DisplayName); + log.Write("Exit Break Mode: project '{0}'", _project.Id.ToString()); // EnC service is global, but the debugger calls this for each project. // Avoid ending the edit session if it has already been ended. @@ -693,8 +707,8 @@ public int ExitBreakStateOnPE() _diagnosticProvider.ClearDiagnostics( new EncErrorId(_encService.DebuggingSession, EditAndContinueDiagnosticUpdateSource.EmitErrorId), - _vsProject.Workspace.CurrentSolution, - _vsProject.Id, + _workspace.CurrentSolution, + _project.Id, _documentsWithEmitError); _documentsWithEmitError = ImmutableArray.Empty; @@ -725,7 +739,7 @@ public unsafe int BuildForEnc(object pUpdatePE) { try { - log.Write("Applying changes to {0}", _vsProject.DisplayName); + log.Write("Applying changes to {0}", _project.Id.ToString()); Debug.Assert(_encService.EditSession != null); Debug.Assert(!_encService.EditSession.StoppedAtException); @@ -735,7 +749,7 @@ public unsafe int BuildForEnc(object pUpdatePE) if (_changesApplied) { - log.Write("Changes already applied to {0}, can't apply again", _vsProject.DisplayName); + log.Write("Changes already applied to {0}, can't apply again", _project.Id.ToString()); throw ExceptionUtilities.Unreachable; } @@ -768,12 +782,12 @@ public unsafe int BuildForEnc(object pUpdatePE) var errorId = new EncErrorId(_encService.DebuggingSession, EditAndContinueDiagnosticUpdateSource.EmitErrorId); // Clear diagnostics, in case the project was built before and failed due to errors. - _diagnosticProvider.ClearDiagnostics(errorId, _projectBeingEmitted.Solution, _vsProject.Id, _documentsWithEmitError); + _diagnosticProvider.ClearDiagnostics(errorId, _projectBeingEmitted.Solution, _project.Id, _documentsWithEmitError); if (!delta.EmitResult.Success) { var errors = delta.EmitResult.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error); - _documentsWithEmitError = _diagnosticProvider.ReportDiagnostics(errorId, _projectBeingEmitted.Solution, _vsProject.Id, errors); + _documentsWithEmitError = _diagnosticProvider.ReportDiagnostics(errorId, _projectBeingEmitted.Solution, _project.Id, errors); _encService.EditSession.LogEmitProjectDeltaErrors(errors.Select(e => e.Id)); return VSConstants.E_FAIL; @@ -898,7 +912,7 @@ internal static ENCPROG_EXCEPTION_RANGE[] GetExceptionRanges(ImmutableArray<(Act { var (documentId, deltas) = edits[f]; - fileUpdates[f].FileName = _vsProject.GetDocumentOrAdditionalDocument(documentId).FilePath; + fileUpdates[f].FileName = _workspace.CurrentSolution.GetProject(_project.Id).GetDocument(documentId).FilePath; fileUpdates[f].LineUpdateCount = (uint)deltas.Length; fileUpdates[f].LineUpdates = (IntPtr)(lineUpdatesPtr + index); @@ -936,11 +950,11 @@ private Deltas EmitProjectDelta() if (baseline == null || baseline.OriginalMetadata.IsDisposed) { - var moduleName = PathUtilities.GetFileName(_vsProject.ObjOutputPath); + var moduleName = PathUtilities.GetFileName(_project.IntermediateOutputFilePath); // The metadata blob is guaranteed to not be disposed while BuildForEnc is being executed. // If it is disposed it means it had been disposed when entering BuildForEnc. - log.Write("Module has been unloaded: module '{0}', project '{1}', MVID: {2}", moduleName, _vsProject.DisplayName, _mvid.ToString()); + log.Write("Module has been unloaded: module '{0}', project '{1}', MVID: {2}", moduleName, _project.Id.ToString(), _mvid.ToString()); ReportInternalError(InternalErrorCode.CantApplyChangesModuleHasBeenUnloaded, new[] { moduleName }); return null; @@ -1075,7 +1089,7 @@ public int EncApplySucceeded(int hrApplyResult) { try { - log.Write("Change applied to {0}", _vsProject.DisplayName); + log.Write("Change applied to {0}", _project.Id.ToString()); Debug.Assert(IsDebuggable); Debug.Assert(_encService.EditSession != null); Debug.Assert(!_encService.EditSession.StoppedAtException); @@ -1146,7 +1160,7 @@ private void ReportInternalError(InternalErrorCode errorId, object[] args) _diagnosticProvider.ReportDiagnostics( new EncErrorId(_encService.DebuggingSession, EditAndContinueDiagnosticUpdateSource.InternalErrorId), _encService.DebuggingSession.InitialSolution, - _vsProject.Id, + _project.Id, new[] { Diagnostic.Create(descriptor, Location.None, args) }); } catch (Exception e) when (FatalError.ReportWithoutCrash(e)) @@ -1154,27 +1168,5 @@ private void ReportInternalError(InternalErrorCode errorId, object[] args) // nop } } - - #region Testing - -#if DEBUG - // Fault injection: - // If set we'll fail to read MVID of specified projects to test error reporting. - internal static ImmutableArray InjectMvidReadingFailure; - - private void InjectFault_MvidRead() - { - if (!InjectMvidReadingFailure.IsDefault && InjectMvidReadingFailure.Contains(_vsProject.DisplayName)) - { - throw new IOException("Fault injection"); - } - } -#else - [Conditional("DEBUG")] - private void InjectFault_MvidRead() - { - } -#endif - #endregion } } diff --git a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/VsReadOnlyDocumentTracker.cs b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/VsReadOnlyDocumentTracker.cs index 2edf6eb81e4d08d545a8b49bcb7f4518da7419ed..2d09f2e8fa670afe11ef85d4e707590ca1c4f8ae 100644 --- a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/VsReadOnlyDocumentTracker.cs +++ b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/VsReadOnlyDocumentTracker.cs @@ -6,6 +6,8 @@ using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Editor; +using Microsoft.VisualStudio.LanguageServices; +using Microsoft.VisualStudio.LanguageServices.Implementation.EditAndContinue; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.Implementation.Venus; using Microsoft.VisualStudio.Text; @@ -78,10 +80,9 @@ public void Dispose() private void SetReadOnly(Document document) { // Only set documents read-only if they're part of a project that supports Enc. - var workspace = document.Project.Solution.Workspace as VisualStudioWorkspaceImpl; - var project = workspace?.DeferredState?.ProjectTracker?.GetProject(document.Project.Id); + var workspace = document.Project.Solution.Workspace as VisualStudioWorkspace; - if (project?.EditAndContinueImplOpt != null) + if (workspace != null && VsENCRebuildableProjectImpl.TryGetRebuildableProject(document.Project.Id) != null) { SetReadOnly(document.Id, _encService.IsProjectReadOnly(document.Project.Id, out var sessionReason, out var projectReason) && AllowsReadOnly(document.Id)); } @@ -93,9 +94,8 @@ private bool AllowsReadOnly(DocumentId documentId) // However, ASP.NET doesn’t want its views (aspx, cshtml, or vbhtml) to be read-only, so they can be editable // while the code is running and get refreshed next time the web page is hit. - // Note that Razor-like views are modelled as a ContainedDocument but normal code including code-behind are modelled as a StandardTextDocument. - var visualStudioWorkspace = _workspace as VisualStudioWorkspaceImpl; - var containedDocument = visualStudioWorkspace?.GetHostDocument(documentId) as ContainedDocument; + // Note that Razor-like views are modelled as a ContainedDocument + var containedDocument = ContainedDocument.TryGetContainedDocument(documentId); return containedDocument == null; } diff --git a/src/VisualStudio/Core/Def/Implementation/Extensions/VisualStudioWorkspaceImplExtensions.cs b/src/VisualStudio/Core/Def/Implementation/Extensions/VisualStudioWorkspaceImplExtensions.cs index b2da7e9e9a4ccb798e941dbaab54a8e859011b36..2a1ec3d4159ad8fca854c3bfdef45919ff0b96dd 100644 --- a/src/VisualStudio/Core/Def/Implementation/Extensions/VisualStudioWorkspaceImplExtensions.cs +++ b/src/VisualStudio/Core/Def/Implementation/Extensions/VisualStudioWorkspaceImplExtensions.cs @@ -75,7 +75,7 @@ public static bool TryGetImageListAndIndex(this VisualStudioWorkspaceImpl worksp public static bool TryGetImageListAndIndex(this VisualStudioWorkspaceImpl workspace, IVsImageService2 imageService, ProjectId id, out IntPtr imageList, out ushort index) { - var hierarchy = workspace.GetHostProject(id)?.Hierarchy; + var hierarchy = workspace.GetHierarchy(id); if (hierarchy != null) { return TryGetImageListAndIndex(hierarchy, imageService, VSConstants.VSITEMID_ROOT, out imageList, out index); @@ -88,11 +88,11 @@ public static bool TryGetImageListAndIndex(this VisualStudioWorkspaceImpl worksp public static bool TryGetImageListAndIndex(this VisualStudioWorkspaceImpl workspace, IVsImageService2 imageService, DocumentId id, out IntPtr imageList, out ushort index) { - var hostDocument = workspace.GetHostDocument(id); - if (hostDocument != null) + var hierarchy = workspace.GetHierarchy(id.ProjectId); + var document = workspace.CurrentSolution.GetDocument(id); + if (hierarchy != null) { - var hierarchy = hostDocument.Project.Hierarchy; - var itemId = hostDocument.GetItemId(); + var itemId = hierarchy.TryGetItemId(document.FilePath); return TryGetImageListAndIndex(hierarchy, imageService, itemId, out imageList, out index); } diff --git a/src/VisualStudio/Core/Def/Implementation/Extensions/VsTextSpanExtensions.cs b/src/VisualStudio/Core/Def/Implementation/Extensions/VsTextSpanExtensions.cs index 79c779a4691beaf76693eafc71f25a37fb361c42..94fb11b5192f5113c9530fd741dd88e860b1450b 100644 --- a/src/VisualStudio/Core/Def/Implementation/Extensions/VsTextSpanExtensions.cs +++ b/src/VisualStudio/Core/Def/Implementation/Extensions/VsTextSpanExtensions.cs @@ -19,13 +19,13 @@ public static bool TryMapSpanFromSecondaryBufferToPrimaryBuffer(this VsTextSpan return false; } - var containedDocument = visualStudioWorkspace.GetHostDocument(documentId) as ContainedDocument; + var containedDocument = visualStudioWorkspace.TryGetContainedDocument(documentId); if (containedDocument == null) { return false; } - var bufferCoordinator = containedDocument.ContainedLanguage.BufferCoordinator; + var bufferCoordinator = containedDocument.BufferCoordinator; var primary = new VsTextSpan[1]; var hresult = bufferCoordinator.MapSecondaryToPrimarySpan(spanInSecondaryBuffer, primary); diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs index 4f3a169ce5bb6471a6cf902def675a0f260b1f42..6d1f335fd7dd8e04155d95d8833431109972a9d5 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs @@ -254,11 +254,10 @@ protected async Task<(Guid, string projectName, SourceText)> GetGuidAndProjectNa // in cases like Any-Code (which does not use a VSWorkspace). So we are tolerant // when we have another type of workspace. This means we will show results, but // certain features (like filtering) may not work in that context. - var workspace = document.Project.Solution.Workspace as VisualStudioWorkspaceImpl; - var hostProject = workspace?.GetHostProject(document.Project.Id); + var workspace = document.Project.Solution.Workspace as VisualStudioWorkspace; - var projectName = hostProject?.DisplayName ?? document.Project.Name; - var guid = hostProject?.Guid ?? Guid.Empty; + var projectName = document.Project.Name; + var guid = workspace.GetProjectGuid(document.Project.Id); var sourceText = await document.GetTextAsync(CancellationToken).ConfigureAwait(false); return (guid, projectName, sourceText); diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/VisualStudioDefinitionsAndReferencesFactory.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/VisualStudioDefinitionsAndReferencesFactory.cs index 968a282a19ac683421135a4efac553b45adfe4d8..5459ea6c337fc0f9252ebefa5481cc2d052854d6 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/VisualStudioDefinitionsAndReferencesFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/VisualStudioDefinitionsAndReferencesFactory.cs @@ -64,7 +64,7 @@ public VisualStudioDefinitionsAndReferencesFactory(SVsServiceProvider servicePro private string GetSourceLine(string filePath, int lineNumber) { using (var invisibleEditor = new InvisibleEditor( - _serviceProvider, filePath, projectOpt: null, needsSave: false, needsUndoDisabled: false)) + _serviceProvider, filePath, hierarchyOpt: null, needsSave: false, needsUndoDisabled: false)) { var vsTextLines = invisibleEditor.VsTextLines; if (vsTextLines != null && diff --git a/src/VisualStudio/Core/Def/Implementation/GenerateType/GenerateTypeDialogViewModel.cs b/src/VisualStudio/Core/Def/Implementation/GenerateType/GenerateTypeDialogViewModel.cs index 3e7280478704c7a56ad2298740c5c274c441ce85..ce3a6a02efe8d2cf89406716a345e2da10c198cf 100644 --- a/src/VisualStudio/Core/Def/Implementation/GenerateType/GenerateTypeDialogViewModel.cs +++ b/src/VisualStudio/Core/Def/Implementation/GenerateType/GenerateTypeDialogViewModel.cs @@ -671,9 +671,12 @@ public bool AreFoldersValidIdentifiers { if (_areFoldersValidIdentifiers) { + /* var workspace = this.SelectedProject.Solution.Workspace as VisualStudioWorkspaceImpl; var project = workspace?.GetHostProject(this.SelectedProject.Id) as AbstractProject; return !(project?.IsWebSite == true); + */ + return false; } return false; diff --git a/src/VisualStudio/Core/Def/Implementation/HierarchyItemToProjectIdMap.cs b/src/VisualStudio/Core/Def/Implementation/HierarchyItemToProjectIdMap.cs index c768122055fb1363e5be917702d154d594e44142..92e7db0fe88e55d6c683fdc0945bed7e24fc05b8 100644 --- a/src/VisualStudio/Core/Def/Implementation/HierarchyItemToProjectIdMap.cs +++ b/src/VisualStudio/Core/Def/Implementation/HierarchyItemToProjectIdMap.cs @@ -13,22 +13,16 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation [ExportWorkspaceService(typeof(IHierarchyItemToProjectIdMap), ServiceLayer.Host), Shared] internal class HierarchyItemToProjectIdMap : IHierarchyItemToProjectIdMap { - private readonly VisualStudioWorkspaceImpl _workspace; + private readonly VisualStudioWorkspace _workspace; [ImportingConstructor] - public HierarchyItemToProjectIdMap(VisualStudioWorkspaceImpl workspace) + public HierarchyItemToProjectIdMap(VisualStudioWorkspace workspace) { _workspace = workspace; } public bool TryGetProjectId(IVsHierarchyItem hierarchyItem, string targetFrameworkMoniker, out ProjectId projectId) { - if (_workspace.DeferredState == null) - { - projectId = default(ProjectId); - return false; - } - // A project node is represented in two different hierarchies: the solution's IVsHierarchy (where it is a leaf node) // and the project's own IVsHierarchy (where it is the root node). The IVsHierarchyItem joins them together for the // purpose of creating the tree displayed in Solution Explorer. The project's hierarchy is what is passed from the @@ -46,7 +40,7 @@ public bool TryGetProjectId(IVsHierarchyItem hierarchyItem, string targetFramewo // First filter the projects by matching up properties on the input hierarchy against properties on each // project's hierarchy. - var candidateProjects = _workspace.DeferredState.ProjectTracker.ImmutableProjects + var candidateProjects = _workspace.CurrentSolution.Projects .Where(p => { // We're about to access various properties of the IVsHierarchy associated with the project. @@ -67,9 +61,11 @@ public bool TryGetProjectId(IVsHierarchyItem hierarchyItem, string targetFramewo // if the two projects are in the same folder. // Note that if a project has been loaded with Lightweight Solution Load it won't even have a // hierarchy, so we need to check for null first. - if (p.Hierarchy != null - && p.Hierarchy.TryGetCanonicalName((uint)VSConstants.VSITEMID.Root, out string projectCanonicalName) - && p.Hierarchy.TryGetItemName((uint)VSConstants.VSITEMID.Root, out string projectName) + var hierarchy = _workspace.GetHierarchy(p.Id); + + if (hierarchy != null + && hierarchy.TryGetCanonicalName((uint)VSConstants.VSITEMID.Root, out string projectCanonicalName) + && hierarchy.TryGetItemName((uint)VSConstants.VSITEMID.Root, out string projectName) && projectCanonicalName.Equals(nestedCanonicalName, System.StringComparison.OrdinalIgnoreCase) && projectName.Equals(nestedName)) { @@ -78,7 +74,7 @@ public bool TryGetProjectId(IVsHierarchyItem hierarchyItem, string targetFramewo return true; } - return p.Hierarchy.TryGetTargetFrameworkMoniker((uint)VSConstants.VSITEMID.Root, out string projectTargetFrameworkMoniker) + return hierarchy.TryGetTargetFrameworkMoniker((uint)VSConstants.VSITEMID.Root, out string projectTargetFrameworkMoniker) && projectTargetFrameworkMoniker.Equals(targetFrameworkMoniker); } @@ -99,7 +95,7 @@ public bool TryGetProjectId(IVsHierarchyItem hierarchyItem, string targetFramewo // without a ContainedDocument. foreach (var candidateProject in candidateProjects) { - if (!candidateProject.GetCurrentDocuments().Any(doc => doc is ContainedDocument)) + if (!candidateProject.DocumentIds.Any(id => ContainedDocument.TryGetContainedDocument(id) != null)) { projectId = candidateProject.Id; return true; diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.IVsContainedLanguageFactory.cs b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.IVsContainedLanguageFactory.cs index 0b572f0dc6003317dacf2eda6c672e58fd1d4e8b..ce0a398f39fa3f4498c888fe491da4401c4b4fc1 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.IVsContainedLanguageFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.IVsContainedLanguageFactory.cs @@ -12,7 +12,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService { internal abstract partial class AbstractLanguageService : IVsContainedLanguageFactory { - private AbstractProject FindMatchingProject(IVsHierarchy hierarchy, uint itemid) + private VisualStudioProject FindMatchingProject(IVsHierarchy hierarchy, uint itemid) { // Here we must determine the project that this file's document is to be a part of. // Venus creates a separate Project for a .aspx or .ascx file, and so we must associate @@ -60,10 +60,7 @@ private AbstractProject FindMatchingProject(IVsHierarchy hierarchy, uint itemid) return null; } - return this.Workspace.DeferredState.ProjectTracker.ImmutableProjects - .Where(p => p.Hierarchy == hierarchy) - .Where(p => p.ProjectSystemName == projectName) - .SingleOrDefault(); + return this.Workspace.GetProjectForUniqueName(projectName); } public int GetLanguage(IVsHierarchy hierarchy, uint itemid, IVsTextBufferCoordinator bufferCoordinator, out IVsContainedLanguage language) diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.cs b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.cs index ec963ef060abea5cc20bd872504b220f11cc5b8f..24ecf55621fa1aa1746f332fc054d5bd15668aaa 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.cs @@ -241,10 +241,15 @@ protected virtual void SetupNewTextView(IVsTextView textView) new StandaloneCommandFilter( (TLanguageService)this, v, commandHandlerFactory, EditorAdaptersFactoryService).AttachToVsTextView()); + // Ensure we start sending save events + var saveEventsService = Package.ComponentModel.GetService(); + saveEventsService.StartSendingSaveEvents(); + var openDocument = wpfTextView.TextBuffer.AsTextContainer().GetRelatedDocuments().FirstOrDefault(); var isOpenMetadataAsSource = openDocument != null && openDocument.Project.Solution.Workspace.Kind == WorkspaceKind.MetadataAsSource; ConditionallyCollapseOutliningRegions(textView, wpfTextView, workspace, isOpenMetadataAsSource); + // If this is a metadata-to-source view, we want to consider the file read-only if (isOpenMetadataAsSource && ErrorHandler.Succeeded(textView.GetBuffer(out var vsTextLines))) { @@ -388,12 +393,12 @@ private void UninitializeDebugMode() } protected virtual IVsContainedLanguage CreateContainedLanguage( - IVsTextBufferCoordinator bufferCoordinator, AbstractProject project, + IVsTextBufferCoordinator bufferCoordinator, VisualStudioProject project, IVsHierarchy hierarchy, uint itemid) { return new ContainedLanguage( bufferCoordinator, this.Package.ComponentModel, project, hierarchy, itemid, - (TLanguageService)this, SourceCodeKind.Regular); + (TLanguageService)this); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractPackage`2.cs b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractPackage`2.cs index ac2b328216b1189ae433c4c54d8d912dc113e531..2055fa5e8cf75a06e12740bbf9cae96c0082cafd 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractPackage`2.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractPackage`2.cs @@ -92,8 +92,6 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke // start remote host EnableRemoteHostClientService(); - - Workspace.AdviseSolutionEvents(solution); } LoadComponentsInUIContextOnceSolutionFullyLoadedAsync(cancellationToken).Forget(); diff --git a/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/Extensions.cs b/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/Extensions.cs index bbe4bc1cde641e8d9e536ff662b81f15e921ff6b..f9f6a48f57e1ee707e4f22317d97dae09a33fe12 100644 --- a/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/Extensions.cs +++ b/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/Extensions.cs @@ -1,10 +1,9 @@ // 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 System; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; -using Microsoft.VisualStudio.LanguageServices.Implementation.Venus; +using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Library.ObjectBrowser @@ -52,7 +51,20 @@ public static string GetProjectDisplayName(this Project project) { if (project.Solution.Workspace is VisualStudioWorkspaceImpl workspace) { - return workspace.GetProjectDisplayName(project); + var hierarchy = workspace.GetHierarchy(project.Id); + if (hierarchy != null) + { + var solution = (IVsSolution3)ServiceProvider.GlobalProvider.GetService(typeof(SVsSolution)); + if (solution != null) + { + if (ErrorHandler.Succeeded(solution.GetUniqueUINameOfProject(hierarchy, out string name)) && name != null) + { + return name; + } + } + } + + return project.Name; } return project.Name; @@ -68,7 +80,7 @@ public static bool IsVenus(this Project project) foreach (var documentId in project.DocumentIds) { - if (workspace.GetHostDocument(documentId) is ContainedDocument) + if (workspace.TryGetContainedDocument(documentId) != null) { return true; } diff --git a/src/VisualStudio/Core/Def/Implementation/NavigateTo/VisualStudioNavigateToPreviewService.cs b/src/VisualStudio/Core/Def/Implementation/NavigateTo/VisualStudioNavigateToPreviewService.cs index 59d3001e1046a7eb614f558040417d8395110a0d..1779952bfb818c5dea5619d5bb49737a870037fa 100644 --- a/src/VisualStudio/Core/Def/Implementation/NavigateTo/VisualStudioNavigateToPreviewService.cs +++ b/src/VisualStudio/Core/Def/Implementation/NavigateTo/VisualStudioNavigateToPreviewService.cs @@ -30,7 +30,7 @@ public bool CanPreview(Document document) return false; } - return !(visualStudioWorkspace.GetHostDocument(document.Id) is ContainedDocument); + return visualStudioWorkspace.TryGetContainedDocument(document.Id) == null; } public void PreviewItem(INavigateToItemDisplay itemDisplay) diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphNodeIdCreation.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphNodeIdCreation.cs index c9c8148dd6a34051512ef1be9ab0475b94835e26..68cebd8f89b1007e6cbbc69dd390555c68141e29 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphNodeIdCreation.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphNodeIdCreation.cs @@ -400,17 +400,12 @@ private static async Task GetAssemblyFullPathAsync(IAssemblySymbol containi Project foundProject = solution.GetProject(containingAssembly, cancellationToken); if (foundProject != null) { - if (solution.Workspace is VisualStudioWorkspaceImpl workspace) + if (solution.Workspace is VisualStudioWorkspace workspace) { - // We have found a project in the solution, so clearly the deferred state has been loaded - var vsProject = workspace.DeferredState.ProjectTracker.GetProject(foundProject.Id); - if (vsProject != null) + // TODO: audit the OutputFilePath and whether this is bin or obj + if (!string.IsNullOrWhiteSpace(foundProject.OutputFilePath)) { - var output = vsProject.BinOutputPath; - if (!string.IsNullOrWhiteSpace(output)) - { - return new Uri(output, UriKind.RelativeOrAbsolute); - } + return new Uri(foundProject.OutputFilePath, UriKind.RelativeOrAbsolute); } return null; diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectInfo/DefaultProjectInfoService.cs b/src/VisualStudio/Core/Def/Implementation/ProjectInfo/DefaultProjectInfoService.cs index 8cefe5a46909bee51281c8f4b691f32a66729024..5dc01a8648925f79a20b8bbd417cab0378049e4a 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectInfo/DefaultProjectInfoService.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectInfo/DefaultProjectInfoService.cs @@ -16,10 +16,7 @@ public bool GeneratedTypesMustBePublic(Project project) return false; } - if (workspace.GetHostProject(project.Id) is AbstractProject hostProject) - { - return hostProject.IsWebSite; - } + // TODO: reimplement return false; } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject.cs index 9819b7b754a42363adac720c6339818f67d8cd0a..0b3abca65ef6177087fb46a9600dcad17403263d 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject.cs @@ -1,14 +1,10 @@ // 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 System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.IO; using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.Shared.Options; @@ -16,20 +12,15 @@ using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel; using Microsoft.VisualStudio.LanguageServices.Implementation.EditAndContinue; using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; -using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.LanguageServices.Implementation.Venus; using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.TextManager.Interop; -using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; -using Task = System.Threading.Tasks.Task; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem { @@ -43,62 +34,9 @@ internal abstract partial class AbstractProject : ForegroundThreadAffinitizedObj internal const string ProjectGuidPropertyName = "ProjectGuid"; internal static object RuleSetErrorId = new object(); - private readonly object _gate = new object(); - #region Mutable fields accessed from foreground or background threads - need locking for access. - private readonly List _projectReferences = new List(); - private readonly List _metadataReferences = new List(); - private readonly Dictionary _documents = new Dictionary(); - private readonly Dictionary _documentMonikers = new Dictionary(StringComparer.OrdinalIgnoreCase); - private readonly Dictionary _analyzers = new Dictionary(StringComparer.OrdinalIgnoreCase); - private readonly Dictionary _additionalDocuments = new Dictionary(); - - /// - /// The list of files which have been added to the project but we aren't tracking since they - /// aren't real source files. Sometimes we're asked to add silly things like HTML files or XAML - /// files, and if those are open in a strange editor we just bail. - /// - private readonly ISet _untrackedDocuments = new HashSet(StringComparer.OrdinalIgnoreCase); - - #endregion - - #region Mutable fields accessed only from the foreground thread - does not need locking for access. - /// - /// When a reference changes on disk we start a delayed task to update the . - /// It is delayed for two reasons: first, there are often a bunch of change notifications in quick succession - /// as the file is written. Second, we often get the first notification while something is still writing the - /// file, so we're unable to actually load it. To avoid both of these issues, we wait five seconds before - /// reloading the metadata. This holds on to - /// s that allow us to cancel the existing reload task if another file - /// change comes in before we process it. - /// - private readonly Dictionary _donotAccessDirectlyChangedReferencesPendingUpdate - = new Dictionary(); - private Dictionary ChangedReferencesPendingUpdate - { - get - { - AssertIsForeground(); - return _donotAccessDirectlyChangedReferencesPendingUpdate; - } - } - - private readonly HashSet<(AbstractProject, MetadataReferenceProperties)> _projectsReferencingMe = new HashSet<(AbstractProject, MetadataReferenceProperties)>(); - - /// - /// Maps from the output path of a project that was converted to - /// - private readonly Dictionary _metadataFileNameToConvertedProjectReference = new Dictionary(StringComparer.OrdinalIgnoreCase); - - #endregion - - // PERF: Create these event handlers once to be shared amongst all documents (the sender arg identifies which document and project) - private static readonly EventHandler s_documentOpenedEventHandler = OnDocumentOpened; - private static readonly EventHandler s_documentClosingEventHandler = OnDocumentClosing; - private static readonly EventHandler s_documentUpdatedOnDiskEventHandler = OnDocumentUpdatedOnDisk; - private static readonly EventHandler s_additionalDocumentOpenedEventHandler = OnAdditionalDocumentOpened; - private static readonly EventHandler s_additionalDocumentClosingEventHandler = OnAdditionalDocumentClosing; - private static readonly EventHandler s_additionalDocumentUpdatedOnDiskEventHandler = OnAdditionalDocumentUpdatedOnDisk; + private string _displayName; + private readonly VisualStudioWorkspace _visualStudioWorkspace; private readonly DiagnosticDescriptor _errorReadingRulesetRule = new DiagnosticDescriptor( id: IDEDiagnosticIds.ErrorReadingRulesetId, @@ -108,6 +46,7 @@ internal abstract partial class AbstractProject : ForegroundThreadAffinitizedObj defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); + public AbstractProject( VisualStudioProjectTracker projectTracker, Func reportExternalErrorCreatorOpt, @@ -116,38 +55,29 @@ internal abstract partial class AbstractProject : ForegroundThreadAffinitizedObj IVsHierarchy hierarchy, string language, Guid projectGuid, - IServiceProvider serviceProvider, - VisualStudioWorkspaceImpl visualStudioWorkspaceOpt, + IServiceProvider serviceProviderNotUsed, // not used, but left for compat with TypeScript + VisualStudioWorkspaceImpl workspace, HostDiagnosticUpdateSource hostDiagnosticUpdateSourceOpt, ICommandLineParserService commandLineParserServiceOpt = null) : base(projectTracker.ThreadingContext) { - Contract.ThrowIfNull(projectSystemName); - - ServiceProvider = serviceProvider; - Language = language; Hierarchy = hierarchy; Guid = projectGuid; + Language = language; + _visualStudioWorkspace = workspace; - var componentModel = (IComponentModel)serviceProvider.GetService(typeof(SComponentModel)); - ContentTypeRegistryService = componentModel.GetService(); - - this.RunningDocumentTable = (IVsRunningDocumentTable4)serviceProvider.GetService(typeof(SVsRunningDocumentTable)); - - var displayName = hierarchy != null && hierarchy.TryGetName(out var name) ? name : projectSystemName; - this.DisplayName = displayName; - - this.ProjectTracker = projectTracker; + this.DisplayName = hierarchy != null && hierarchy.TryGetName(out var name) ? name : projectSystemName; ProjectSystemName = projectSystemName; - Workspace = visualStudioWorkspaceOpt; - CommandLineParserService = commandLineParserServiceOpt; HostDiagnosticUpdateSource = hostDiagnosticUpdateSourceOpt; // Set the default value for last design time build result to be true, until the project system lets us know that it failed. LastDesignTimeBuildSucceeded = true; - UpdateProjectDisplayNameAndFilePath(displayName, projectFilePath); + if (projectFilePath != null && File.Exists(projectFilePath)) + { + ProjectFilePath = projectFilePath; + } if (ProjectFilePath != null) { @@ -158,64 +88,34 @@ internal abstract partial class AbstractProject : ForegroundThreadAffinitizedObj Version = VersionStamp.Create(); } - Id = this.ProjectTracker.GetOrCreateProjectIdForPath(ProjectFilePath ?? ProjectSystemName, ProjectSystemName); if (reportExternalErrorCreatorOpt != null) { ExternalErrorReporter = reportExternalErrorCreatorOpt(Id); } - - if (visualStudioWorkspaceOpt != null) - { - if (Language == LanguageNames.CSharp || Language == LanguageNames.VisualBasic) - { - this.EditAndContinueImplOpt = new VsENCRebuildableProjectImpl(this); - } - - this.MetadataService = visualStudioWorkspaceOpt.Services.GetService(); - } - - UpdateAssemblyName(); - - Logger.Log(FunctionId.AbstractProject_Created, - KeyValueLogMessage.Create(LogType.Trace, m => - { - m[ProjectGuidPropertyName] = Guid; - })); } - internal IServiceProvider ServiceProvider { get; } - - /// - /// Indicates whether this project is a website type. - /// - public bool IsWebSite { get; protected set; } - - /// - /// A full path to the project obj output binary, or null if the project doesn't have an obj output binary. - /// - internal string ObjOutputPath { get; private set; } - /// /// A full path to the project bin output binary, or null if the project doesn't have an bin output binary. /// - internal string BinOutputPath { get; private set; } - - public IReferenceCountedDisposable RuleSetFile { get; private set; } + // FYI: this can't be made virtual because there are calls to this where a 'call' instead of 'callvirt' is being used to call + // the method. + internal string BinOutputPath => GetOutputFilePath(); - protected VisualStudioProjectTracker ProjectTracker { get; } + protected virtual string GetOutputFilePath() + { + return VisualStudioProject.OutputFilePath; + } - protected IVsRunningDocumentTable4 RunningDocumentTable { get; } + public IReferenceCountedDisposable RuleSetFile { get; private set; } protected IVsReportExternalErrors ExternalErrorReporter { get; } internal HostDiagnosticUpdateSource HostDiagnosticUpdateSource { get; } - public ProjectId Id { get; } + public virtual ProjectId Id => VisualStudioProject.Id; public string Language { get; } - private ICommandLineParserService CommandLineParserService { get; } - /// /// The for this project. NOTE: May be null in Deferred Project Load cases. /// @@ -232,8 +132,6 @@ internal abstract partial class AbstractProject : ForegroundThreadAffinitizedObj public VersionStamp Version { get; } - public IMetadataService MetadataService { get; } - public IProjectCodeModel ProjectCodeModel { get; protected set; } /// @@ -267,7 +165,16 @@ protected string ContainingDirectoryPathOpt /// between multiple projects, especially in cases like Venus where the intellisense /// projects will match the name of their logical parent project. /// - public string DisplayName { get; private set; } + public string DisplayName + { + get => _displayName; + set + { + _displayName = value; + + UpdateVisualStudioProjectProperties(); + } + } internal string AssemblyName { get; private set; } @@ -280,1346 +187,73 @@ protected string ContainingDirectoryPathOpt /// public string ProjectSystemName { get; } - protected DocumentProvider DocumentProvider => this.ProjectTracker.DocumentProvider; - - protected VisualStudioMetadataReferenceManager MetadataReferenceProvider => this.ProjectTracker.MetadataReferenceProvider; - - protected IContentTypeRegistryService ContentTypeRegistryService { get; } - /// /// Flag indicating if the latest design time build has succeeded for current project state. /// /// Default value is true. protected bool LastDesignTimeBuildSucceeded { get; private set; } - internal VsENCRebuildableProjectImpl EditAndContinueImplOpt { get; private set; } - - public ProjectInfo CreateProjectInfoForCurrentState() - { - lock (_gate) - { - var info = ProjectInfo.Create( - this.Id, - this.Version, - this.DisplayName, - this.AssemblyName ?? this.ProjectSystemName, - this.Language, - filePath: this.ProjectFilePath, - outputFilePath: this.ObjOutputPath, - compilationOptions: this.CurrentCompilationOptions, - parseOptions: this.CurrentParseOptions, - documents: _documents.Values.Select(d => d.GetInitialState()), - metadataReferences: _metadataReferences.Select(r => r.CurrentSnapshot), - projectReferences: _projectReferences, - analyzerReferences: _analyzers.Values.Select(a => a.GetReference()), - additionalDocuments: _additionalDocuments.Values.Select(d => d.GetInitialState())); - - return info.WithHasAllInformation(hasAllInformation: LastDesignTimeBuildSucceeded); - } - } - - protected void SetIntellisenseBuildResultAndNotifyWorkspace(bool succeeded) - { - // set IntelliSense related info - LastDesignTimeBuildSucceeded = succeeded; - - Logger.Log(FunctionId.AbstractProject_SetIntelliSenseBuild, - KeyValueLogMessage.Create(LogType.Trace, m => - { - m[ProjectGuidPropertyName] = Guid; - m[nameof(LastDesignTimeBuildSucceeded)] = LastDesignTimeBuildSucceeded; - - // Use old name for consistency - m["PushingChangesToWorkspaceHosts"] = PushingChangesToWorkspace; - })); - - if (PushingChangesToWorkspace) - { - // set workspace reference info - ProjectTracker.NotifyWorkspace(workspace => workspace.OnHasAllInformationChanged(Id, succeeded)); - } - } - - protected ImmutableArray GetStrongNameKeyPaths() - { - var outputPath = this.ObjOutputPath; - - if (this.ContainingDirectoryPathOpt == null && outputPath == null) - { - return ImmutableArray.Empty; - } - - var builder = ArrayBuilder.GetInstance(); - if (this.ContainingDirectoryPathOpt != null) - { - builder.Add(this.ContainingDirectoryPathOpt); - } - - if (outputPath != null) - { - builder.Add(Path.GetDirectoryName(outputPath)); - } - - return builder.ToImmutableAndFree(); - } - - public ImmutableArray GetCurrentProjectReferences() - { - lock (_gate) - { - return ImmutableArray.CreateRange(_projectReferences); - } - } - - public ImmutableArray GetCurrentMetadataReferences() - { - lock (_gate) - { - return ImmutableArray.CreateRange(_metadataReferences); - } - } - - public ImmutableArray GetCurrentAnalyzers() - { - lock (_gate) - { - return ImmutableArray.CreateRange(_analyzers.Values); - } - } - - public IVisualStudioHostDocument GetDocumentOrAdditionalDocument(DocumentId id) - { - lock (_gate) - { - _documents.TryGetValue(id, out var doc); - if (doc == null) - { - _additionalDocuments.TryGetValue(id, out doc); - } - - return doc; - } - } - - public ImmutableArray GetCurrentDocuments() - { - lock (_gate) - { - return _documents.Values.ToImmutableArrayOrEmpty(); - } - } - - public ImmutableArray GetCurrentAdditionalDocuments() - { - lock (_gate) - { - return _additionalDocuments.Values.ToImmutableArrayOrEmpty(); - } - } - - public bool ContainsFile(string moniker) - { - lock (_gate) - { - return _documentMonikers.ContainsKey(moniker); - } - } - - public IVisualStudioHostDocument GetCurrentDocumentFromPath(string filePath) - { - lock (_gate) - { - _documentMonikers.TryGetValue(filePath, out var document); - return document; - } - } - - public bool HasMetadataReference(string filename) - { - lock (_gate) - { - return _metadataReferences.Any(r => StringComparer.OrdinalIgnoreCase.Equals(r.FilePath, filename)); - } - } - - public VisualStudioMetadataReference TryGetCurrentMetadataReference(string filename) - { - // We must normalize the file path, since the paths we're comparing to are always normalized - filename = FileUtilities.NormalizeAbsolutePath(filename); - - lock (_gate) - { - return _metadataReferences.SingleOrDefault(r => StringComparer.OrdinalIgnoreCase.Equals(r.FilePath, filename)); - } - } - - public bool CurrentProjectReferencesContains(ProjectId projectId) - { - lock (_gate) - { - return _projectReferences.Any(r => r.ProjectId == projectId); - } - } - - private bool TryGetAnalyzer(string analyzerAssemblyFullPath, out VisualStudioAnalyzer analyzer) - { - lock (_gate) - { - return _analyzers.TryGetValue(analyzerAssemblyFullPath, out analyzer); - } - } + public VisualStudioProject VisualStudioProject { get; internal set; } - private void AddOrUpdateAnalyzer(string analyzerAssemblyFullPath, VisualStudioAnalyzer analyzer) + internal void UpdateVisualStudioProjectProperties() { - lock (_gate) + if (VisualStudioProject != null) { - _analyzers[analyzerAssemblyFullPath] = analyzer; + VisualStudioProject.DisplayName = this.DisplayName; } } - private void RemoveAnalyzer(string analyzerAssemblyFullPath) + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] + protected void UpdateProjectDisplayName(string displayName) { - lock (_gate) - { - _analyzers.Remove(analyzerAssemblyFullPath); - } + this.DisplayName = displayName; } - public bool CurrentProjectAnalyzersContains(string fullPath) + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] + internal void AddDocument(IVisualStudioHostDocument document, bool isCurrentContext, bool hookupHandlers) { - lock (_gate) - { - return _analyzers.ContainsKey(fullPath); - } - } + var shimDocument = (DocumentProvider.ShimDocument)document; - /// - /// Returns a map from full path to . - /// - public ImmutableDictionary GetProjectAnalyzersMap() - { - lock (_gate) - { - return _analyzers.ToImmutableDictionary(); - } + VisualStudioProject.AddSourceFile(shimDocument.FilePath, shimDocument.SourceCodeKind); } - private static string GetAssemblyNameFromPath(string outputPath) + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] + internal void RemoveDocument(IVisualStudioHostDocument document) { - Debug.Assert(outputPath != null); + var shimDocument = (DocumentProvider.ShimDocument)document; - // dev11 sometimes gives us output path w/o extension, so removing extension becomes problematic - if (outputPath.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) || - outputPath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) || - outputPath.EndsWith(".netmodule", StringComparison.OrdinalIgnoreCase) || - outputPath.EndsWith(".winmdobj", StringComparison.OrdinalIgnoreCase)) + var containedDocument = ContainedDocument.TryGetContainedDocument(document.Id); + if (containedDocument != null) { - return Path.GetFileNameWithoutExtension(outputPath); + VisualStudioProject.RemoveSourceTextContainer(containedDocument.SubjectBuffer.AsTextContainer()); + containedDocument.Dispose(); } else { - return Path.GetFileName(outputPath); - } - } - - protected bool CanConvertToProjectReferences - { - get - { - if (this.Workspace != null) - { - return this.Workspace.Options.GetOption(InternalFeatureOnOffOptions.ProjectReferenceConversion); - } - else - { - return InternalFeatureOnOffOptions.ProjectReferenceConversion.DefaultValue; - } - } - } - - protected int AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(string filePath, MetadataReferenceProperties properties) - { - AssertIsForeground(); - - // If this file is coming from a project, then we should convert it to a project reference instead - if (this.CanConvertToProjectReferences && ProjectTracker.TryGetProjectByBinPath(filePath, out var project)) - { - var projectReference = new ProjectReference(project.Id, properties.Aliases, properties.EmbedInteropTypes); - if (CanAddProjectReference(projectReference)) - { - AddProjectReference(projectReference); - _metadataFileNameToConvertedProjectReference.Add(filePath, projectReference); - return VSConstants.S_OK; - } - } - - // regardless whether the file exists or not, we still record it. one of reason - // we do that is some cross language p2p references might be resolved - // after they are already reported as metadata references. since we use bin path - // as a way to discover them, if we don't previously record the reference ourselves, - // cross p2p references won't be resolved as p2p references when we finally have - // all required information. - // - // it looks like - // 1. project system sometimes won't guarantee build dependency for intellisense build - // if it is cross language dependency - // 2. output path of referenced cross language project might be changed to right one - // once it is already added as a metadata reference. - // - // but this has one consequence. even if a user adds a project in the solution as - // a metadata reference explicitly, that dll will be automatically converted back to p2p - // reference. - // - // unfortunately there is no way to prevent this using information we have since, - // at this point, we don't know whether it is a metadata reference added because - // we don't have enough information yet for p2p reference or user explicitly added it - // as a metadata reference. - AddMetadataReferenceCore(this.MetadataReferenceProvider.CreateMetadataReference(filePath, properties)); - - // here, we change behavior compared to old C# language service. regardless of file being exist or not, - // we will always return S_OK. this is to support cross language p2p reference better. - // - // this should make project system to cache all cross language p2p references regardless - // whether it actually exist in disk or not. - // (see Roslyn bug 7315 for history - http://vstfdevdiv:8080/DevDiv_Projects/Roslyn/_workitems?_a=edit&id=7315) - // - // after this point, Roslyn will take care of non-exist metadata reference. - // - // But, this doesn't sovle the issue where actual metadata reference - // (not cross language p2p reference) is missing at the time project is opened. - // - // in that case, msbuild filter those actual metadata references out, so project system doesn't know - // path to the reference. since it doesn't know where dll is, it can't (or currently doesn't) - // setup file change notification either to find out when dll becomes available. - // - // at this point, user has 2 ways to recover missing metadata reference once it becomes available. - // - // one way is explicitly clicking that missing reference from solution explorer reference node. - // the other is building the project. at that point, project system will refresh references - // which will discover new dll and connect to us. once it is connected, we will take care of it. - return VSConstants.S_OK; - } - - protected void RemoveMetadataReference(string filePath) - { - AssertIsForeground(); - - // Is this a reference we converted to a project reference? - if (_metadataFileNameToConvertedProjectReference.TryGetValue(filePath, out var projectReference)) - { - // We converted this, so remove the project reference instead - RemoveProjectReference(projectReference); - - Contract.ThrowIfFalse(_metadataFileNameToConvertedProjectReference.Remove(filePath)); - } - - // Just a metadata reference, so remove all of those - var referenceToRemove = TryGetCurrentMetadataReference(filePath); - if (referenceToRemove != null) - { - RemoveMetadataReferenceCore(referenceToRemove, disposeReference: true); - } - } - - private void AddMetadataReferenceCore(VisualStudioMetadataReference reference) - { - lock (_gate) - { - if (_metadataReferences.Contains(r => StringComparer.OrdinalIgnoreCase.Equals(r.FilePath, reference.FilePath))) - { - // TODO: Added in order to diagnose why duplicate references get added to the project. See https://github.com/dotnet/roslyn/issues/26437 - FatalError.ReportWithoutCrash(new InvalidOperationException($"Reference with path '{reference.FilePath}' already exists in project '{DisplayName}'.")); - return; - } - - _metadataReferences.Add(reference); - } - - if (PushingChangesToWorkspace) - { - var snapshot = reference.CurrentSnapshot; - this.ProjectTracker.NotifyWorkspace(workspace => workspace.OnMetadataReferenceAdded(this.Id, snapshot)); - } - - reference.UpdatedOnDisk += OnImportChanged; - } - - private void RemoveMetadataReferenceCore(VisualStudioMetadataReference reference, bool disposeReference) - { - lock (_gate) - { - _metadataReferences.Remove(reference); - } - - if (PushingChangesToWorkspace) - { - var snapshot = reference.CurrentSnapshot; - this.ProjectTracker.NotifyWorkspace(workspace => workspace.OnMetadataReferenceRemoved(this.Id, snapshot)); - } - - reference.UpdatedOnDisk -= OnImportChanged; - - if (disposeReference) - { - reference.Dispose(); - } - } - - /// - /// Called when a referenced metadata file changes on disk. - /// - private void OnImportChanged(object sender, EventArgs e) - { - AssertIsForeground(); - - VisualStudioMetadataReference reference = (VisualStudioMetadataReference)sender; - if (ChangedReferencesPendingUpdate.TryGetValue(reference, out var delayTaskCancellationTokenSource)) - { - delayTaskCancellationTokenSource.Cancel(); - } - - delayTaskCancellationTokenSource = new CancellationTokenSource(); - ChangedReferencesPendingUpdate[reference] = delayTaskCancellationTokenSource; - - var task = Task.Delay(TimeSpan.FromSeconds(5), delayTaskCancellationTokenSource.Token) - .ContinueWith( - OnImportChangedAfterDelay, - reference, - delayTaskCancellationTokenSource.Token, - TaskContinuationOptions.None, - TaskScheduler.FromCurrentSynchronizationContext()); - } - - private void OnImportChangedAfterDelay(Task previous, object state) - { - AssertIsForeground(); - - var reference = (VisualStudioMetadataReference)state; - ChangedReferencesPendingUpdate.Remove(reference); - - lock (_gate) - { - // Ensure that we are still referencing this binary - if (_metadataReferences.Contains(reference)) - { - // remove the old metadata reference - this.RemoveMetadataReferenceCore(reference, disposeReference: false); - - // Signal to update the underlying reference snapshot - reference.UpdateSnapshot(); - - // add it back (it will now be based on the new file contents) - this.AddMetadataReferenceCore(reference); - } + VisualStudioProject.RemoveSourceFile(shimDocument.FilePath); } } - private void OnAnalyzerChanged(object sender, EventArgs e) - { - AssertIsForeground(); - - // Postpone handler's actions to prevent deadlock. This AnalyzeChanged event can - // be invoked while the FileChangeService lock is held, and VisualStudioAnalyzer's - // efforts to listen to file changes can lead to a deadlock situation. - // Postponing the VisualStudioAnalyzer operations gives this thread the opportunity - // to release the lock. - ThreadingContext.JoinableTaskFactory.RunAsync(async () => - { - await Task.Yield(); - - var analyzer = (VisualStudioAnalyzer)sender; - - RemoveAnalyzerReference(analyzer.FullPath); - AddAnalyzerReference(analyzer.FullPath); - }); - } - - // Internal for unit testing - internal void AddProjectReference(ProjectReference projectReference) + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] + internal IVisualStudioHostDocument GetCurrentDocumentFromPath(string filePath) { - AssertIsForeground(); - - // dev11 is sometimes calling us multiple times for the same data - if (!CanAddProjectReference(projectReference)) - { - return; - } + var id = _visualStudioWorkspace.CurrentSolution.GetDocumentIdsWithFilePath(filePath).FirstOrDefault(d => d.ProjectId == Id); - lock (_gate) + if (id != null) { - // always manipulate current state after workspace is told so it will correctly observe the initial state - _projectReferences.Add(projectReference); - - var otherProject = ProjectTracker.GetProject(projectReference.ProjectId); - otherProject?.RecordNewReferencingProject(this, new MetadataReferenceProperties(aliases: projectReference.Aliases, embedInteropTypes: projectReference.EmbedInteropTypes)); + return new DocumentProvider.ShimDocument(this, id, filePath); } - - if (PushingChangesToWorkspace) + else { - // This project is already pushed to listening workspace hosts, but it's possible that our target - // project hasn't been yet. Get the dependent project into the workspace as well. - var targetProject = this.ProjectTracker.GetProject(projectReference.ProjectId); - this.ProjectTracker.StartPushingToWorkspaceAndNotifyOfOpenDocuments(SpecializedCollections.SingletonEnumerable(targetProject)); - this.ProjectTracker.NotifyWorkspace(workspace => workspace.OnProjectReferenceAdded(this.Id, projectReference)); + return null; } } - private void RecordNewReferencingProject(AbstractProject referencingProject, MetadataReferenceProperties properties) + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] + internal ImmutableArray GetCurrentDocuments() { - _projectsReferencingMe.Add((referencingProject, properties)); - } - - private void RecordNoLongerReferencingProject(AbstractProject referencingProject, MetadataReferenceProperties properties) - { - if (!_projectsReferencingMe.Remove((referencingProject, properties))) - { - FatalError.ReportWithoutCrash(new Exception($"We didn't know that {nameof(referencingProject)} was referencing us.")); - } - } - - protected bool CanAddProjectReference(ProjectReference projectReference) - { - if (projectReference.ProjectId == this.Id) - { - // cannot self reference - return false; - } - - lock (_gate) - { - if (_projectReferences.Contains(projectReference)) - { - // already have this reference - return false; - } - } - - var project = this.ProjectTracker.GetProject(projectReference.ProjectId); - if (project != null) - { - // We won't allow project-to-project references if this one supports compilation and the other one doesn't. - // This causes problems because if we then try to create a compilation, we'll fail even though it would have worked with - // a metadata reference. If neither supports compilation, we'll let the reference go through on the assumption the - // language (TypeScript/F#, etc.) is doing that intentionally. - if (this.Language != project.Language && - this.ProjectTracker.WorkspaceServices.GetLanguageServices(this.Language).GetService() != null && - this.ProjectTracker.WorkspaceServices.GetLanguageServices(project.Language).GetService() == null) - { - return false; - } - - // cannot add a reference to a project that references us (it would make a cycle) - return !project.TransitivelyReferences(this.Id); - } - - return true; - } - - private bool TransitivelyReferences(ProjectId projectId) - { - return TransitivelyReferencesWorker(projectId, new HashSet()); - } - - private bool TransitivelyReferencesWorker(ProjectId projectId, HashSet visited) - { - visited.Add(this.Id); - - foreach (var pr in GetCurrentProjectReferences()) - { - if (projectId == pr.ProjectId) - { - return true; - } - - if (!visited.Contains(pr.ProjectId)) - { - var project = this.ProjectTracker.GetProject(pr.ProjectId); - if (project != null) - { - if (project.TransitivelyReferencesWorker(projectId, visited)) - { - return true; - } - } - } - } - - return false; - } - - protected void RemoveProjectReference(ProjectReference projectReference) - { - AssertIsForeground(); - - lock (_gate) - { - Contract.ThrowIfFalse(_projectReferences.Remove(projectReference)); - - var otherProject = ProjectTracker.GetProject(projectReference.ProjectId); - - otherProject?.RecordNoLongerReferencingProject(this, new MetadataReferenceProperties(aliases: projectReference.Aliases, embedInteropTypes: projectReference.EmbedInteropTypes)); - } - - if (PushingChangesToWorkspace) - { - this.ProjectTracker.NotifyWorkspace(workspace => workspace.OnProjectReferenceRemoved(this.Id, projectReference)); - } - } - - private static void OnDocumentOpened(object sender, bool isCurrentContext) - { - IVisualStudioHostDocument document = (IVisualStudioHostDocument)sender; - AbstractProject project = document.Project; - - project.AssertIsForeground(); - - if (project.PushingChangesToWorkspace) - { - project.ProjectTracker.NotifyWorkspace(workspace => - { - workspace.OnDocumentOpened(document.Id, document.GetOpenTextBuffer().AsTextContainer(), isCurrentContext); - (workspace as VisualStudioWorkspaceImpl)?.ConnectToSharedHierarchyEvents(document); - }); - } - else - { - StartPushingToWorkspaceAndNotifyOfOpenDocuments(project); - } - } - - private static void OnDocumentClosing(object sender, bool updateActiveContext) - { - IVisualStudioHostDocument document = (IVisualStudioHostDocument)sender; - AbstractProject project = document.Project; - var projectTracker = project.ProjectTracker; - - project.AssertIsForeground(); - - if (project.PushingChangesToWorkspace) - { - projectTracker.NotifyWorkspace(workspace => workspace.OnDocumentClosed(document.Id, document.Loader, updateActiveContext)); - } - } - - private static void OnDocumentUpdatedOnDisk(object sender, EventArgs e) - { - IVisualStudioHostDocument document = (IVisualStudioHostDocument)sender; - AbstractProject project = document.Project; - - project.AssertIsForeground(); - - if (project.PushingChangesToWorkspace) - { - project.ProjectTracker.NotifyWorkspace(workspace => workspace.OnDocumentTextLoaderChanged(document.Id, document.Loader)); - } - } - - private static void OnAdditionalDocumentOpened(object sender, bool isCurrentContext) - { - IVisualStudioHostDocument document = (IVisualStudioHostDocument)sender; - AbstractProject project = (AbstractProject)document.Project; - - project.AssertIsForeground(); - - if (project.PushingChangesToWorkspace) - { - project.ProjectTracker.NotifyWorkspace(workspace => workspace.OnAdditionalDocumentOpened(document.Id, document.GetOpenTextBuffer().AsTextContainer(), isCurrentContext)); - } - else - { - StartPushingToWorkspaceAndNotifyOfOpenDocuments(project); - } - } - - private static void OnAdditionalDocumentClosing(object sender, bool notUsed) - { - IVisualStudioHostDocument document = (IVisualStudioHostDocument)sender; - AbstractProject project = document.Project; - var projectTracker = project.ProjectTracker; - - project.AssertIsForeground(); - - if (project.PushingChangesToWorkspace) - { - projectTracker.NotifyWorkspace(workspace => workspace.OnAdditionalDocumentClosed(document.Id, document.Loader)); - } - } - - private static void OnAdditionalDocumentUpdatedOnDisk(object sender, EventArgs e) - { - IVisualStudioHostDocument document = (IVisualStudioHostDocument)sender; - AbstractProject project = document.Project; - - project.AssertIsForeground(); - - if (project.PushingChangesToWorkspace) - { - project.ProjectTracker.NotifyWorkspace(workspace => workspace.OnAdditionalDocumentTextLoaderChanged(document.Id, document.Loader)); - } - } - - protected void AddFile( - string filename, - SourceCodeKind sourceCodeKind, - Func getIsCurrentContext, - ImmutableArray folderNames) - { - AssertIsForeground(); - - // We can currently be on a background thread. - // So, hookup the handlers when creating the standard text document, as we might receive these handler notifications on the UI thread. - var document = this.DocumentProvider.TryGetDocumentForFile( - this, - filePath: filename, - sourceCodeKind: sourceCodeKind, - folderNames: folderNames, - canUseTextBuffer: CanUseTextBuffer, - updatedOnDiskHandler: s_documentUpdatedOnDiskEventHandler, - openedHandler: s_documentOpenedEventHandler, - closingHandler: s_documentClosingEventHandler); - - if (document == null) - { - // It's possible this file is open in some very strange editor. In that case, we'll just ignore it. - // This might happen if somebody decides to mark a non-source-file as something to compile. - - // TODO: Venus does this for .aspx/.cshtml files which is completely unnecessary for Roslyn. We should remove that code. - AddUntrackedFile(filename); - return; - } - - AddDocument(document, getIsCurrentContext(document), hookupHandlers: false); - } - - protected virtual bool CanUseTextBuffer(ITextBuffer textBuffer) - { - return true; - } - - protected void AddUntrackedFile(string filename) - { - lock (_gate) - { - _untrackedDocuments.Add(filename); - } - } - - protected void RemoveFile(string filename) - { - AssertIsForeground(); - - lock (_gate) - { - // Remove this as an untracked file, if it is - if (_untrackedDocuments.Remove(filename)) - { - return; - } - } - - IVisualStudioHostDocument document = this.GetCurrentDocumentFromPath(filename); - if (document == null) - { - throw new InvalidOperationException("The document is not a part of the finalProject."); - } - - RemoveDocument(document); - } - - internal void AddDocument(IVisualStudioHostDocument document, bool isCurrentContext, bool hookupHandlers) - { - AssertIsForeground(); - - // We do not want to allow message pumping/reentrancy when processing project system changes. - using (Dispatcher.CurrentDispatcher.DisableProcessing()) - { - lock (_gate) - { - // This condition ensures that if we throw an exception for either Add operation, the document will - // not be added to either collection. - if (!_documentMonikers.ContainsKey(document.Key.Moniker)) - { - _documents.Add(document.Id, document); - } - - _documentMonikers.Add(document.Key.Moniker, document); - } - - if (PushingChangesToWorkspace) - { - this.ProjectTracker.NotifyWorkspace(workspace => workspace.OnDocumentAdded(document.GetInitialState())); - - if (document.IsOpen) - { - this.ProjectTracker.NotifyWorkspace(workspace => - { - workspace.OnDocumentOpened(document.Id, document.GetOpenTextBuffer().AsTextContainer(), isCurrentContext); - (workspace as VisualStudioWorkspaceImpl)?.ConnectToSharedHierarchyEvents(document); - }); - } - } - - if (hookupHandlers) - { - document.Opened += s_documentOpenedEventHandler; - document.Closing += s_documentClosingEventHandler; - document.UpdatedOnDisk += s_documentUpdatedOnDiskEventHandler; - } - - DocumentProvider.NotifyDocumentRegisteredToProjectAndStartToRaiseEvents(document); - - if (!PushingChangesToWorkspace && document.IsOpen) - { - StartPushingToWorkspaceAndNotifyOfOpenDocuments(); - } - } - } - - internal void RemoveDocument(IVisualStudioHostDocument document) - { - AssertIsForeground(); - - // We do not want to allow message pumping/reentrancy when processing project system changes. - using (Dispatcher.CurrentDispatcher.DisableProcessing()) - { - lock (_gate) - { - _documents.Remove(document.Id); - _documentMonikers.Remove(document.Key.Moniker); - } - - UninitializeDocument(document); - ProjectCodeModel?.OnSourceFileRemoved(document.Key.Moniker); - } - } - - internal void AddAdditionalDocument(IVisualStudioHostDocument document, bool isCurrentContext) - { - AssertIsForeground(); - - lock (_gate) - { - _additionalDocuments.Add(document.Id, document); - _documentMonikers.Add(document.Key.Moniker, document); - } - - if (PushingChangesToWorkspace) - { - this.ProjectTracker.NotifyWorkspace(workspace => workspace.OnAdditionalDocumentAdded(document.GetInitialState())); - - if (document.IsOpen) - { - this.ProjectTracker.NotifyWorkspace(workspace => workspace.OnAdditionalDocumentOpened(document.Id, document.GetOpenTextBuffer().AsTextContainer(), isCurrentContext)); - } - } - - DocumentProvider.NotifyDocumentRegisteredToProjectAndStartToRaiseEvents(document); - - if (!PushingChangesToWorkspace && document.IsOpen) - { - StartPushingToWorkspaceAndNotifyOfOpenDocuments(); - } - } - - internal void RemoveAdditionalDocument(IVisualStudioHostDocument document) - { - AssertIsForeground(); - - lock (_gate) - { - _additionalDocuments.Remove(document.Id); - _documentMonikers.Remove(document.Key.Moniker); - } - - UninitializeAdditionalDocument(document); - } - - public virtual void Disconnect() - { - AssertIsForeground(); - - using (Workspace?.Services.GetService()?.Start("Disconnect Project")) - { - lock (_gate) - { - // No sense in reloading any metadata references anymore. - foreach (var cancellationTokenSource in ChangedReferencesPendingUpdate.Values) - { - cancellationTokenSource.Cancel(); - } - - ChangedReferencesPendingUpdate.Clear(); - - ProjectCodeModel?.OnProjectClosed(); - - var wasPushing = PushingChangesToWorkspace; - - // disable pushing down to workspaces, so we don't get redundant workspace document removed events - PushingChangesToWorkspace = false; - - // The project is going away, so let's remove ourselves from the host. First, we - // close and dispose of any remaining documents - foreach (var document in _documents.Values) - { - UninitializeDocument(document); - } - - foreach (var document in _additionalDocuments.Values) - { - UninitializeAdditionalDocument(document); - } - - // Dispose metadata references. - foreach (var reference in _metadataReferences) - { - reference.Dispose(); - } - - foreach (var analyzer in _analyzers.Values) - { - analyzer.Dispose(); - } - - // Make sure we clear out any external errors left when closing the project. - ExternalErrorReporter?.ClearAllErrors(); - - // Make sure we clear out any host errors left when closing the project. - HostDiagnosticUpdateSource?.ClearAllDiagnosticsForProject(this.Id); - - ClearAnalyzerRuleSet(); - - // reinstate pushing down to workspace, so the workspace project remove event fires - PushingChangesToWorkspace = wasPushing; - - if (_projectsReferencingMe.Count > 0) - { - // We shouldn't be able to get here, but for reasons we don't entirely - // understand we sometimes do. We've long assumed that by the time a project is - // disconnected, all references to that project have been removed. However, it - // appears that this isn't always true when closing a solution (which includes - // reloading the solution, or opening a different solution) or when reloading a - // project that has changed on disk, or when deleting a project from a - // solution. - - // Clear just so we don't cause a leak - _projectsReferencingMe.Clear(); - } - - this.ProjectTracker.RemoveProject(this); - - PushingChangesToWorkspace = false; - - this.EditAndContinueImplOpt = null; - } - } - } - - internal void TryProjectConversionForIntroducedOutputPath(string binPath, AbstractProject projectToReference) - { - AssertIsForeground(); - - if (this.CanConvertToProjectReferences) - { - // We should not already have references for this, since we're only introducing the path for the first time - Contract.ThrowIfTrue(_metadataFileNameToConvertedProjectReference.ContainsKey(binPath)); - - var metadataReference = TryGetCurrentMetadataReference(binPath); - if (metadataReference != null) - { - var projectReference = new ProjectReference( - projectToReference.Id, - metadataReference.Properties.Aliases, - metadataReference.Properties.EmbedInteropTypes); - - if (CanAddProjectReference(projectReference)) - { - RemoveMetadataReferenceCore(metadataReference, disposeReference: true); - AddProjectReference(projectReference); - - _metadataFileNameToConvertedProjectReference.Add(binPath, projectReference); - } - } - } - } - - internal void UndoProjectReferenceConversionForDisappearingOutputPath(string binPath) - { - AssertIsForeground(); - - if (_metadataFileNameToConvertedProjectReference.TryGetValue(binPath, out var projectReference)) - { - // We converted this, so convert it back to a metadata reference - RemoveProjectReference(projectReference); - - var metadataReferenceProperties = new MetadataReferenceProperties( - MetadataImageKind.Assembly, - projectReference.Aliases, - projectReference.EmbedInteropTypes); - - AddMetadataReferenceCore(MetadataReferenceProvider.CreateMetadataReference(binPath, metadataReferenceProperties)); - - Contract.ThrowIfFalse(_metadataFileNameToConvertedProjectReference.Remove(binPath)); - } - } - - protected void UpdateMetadataReferenceAliases(string file, ImmutableArray aliases) - { - AssertIsForeground(); - - file = FileUtilities.NormalizeAbsolutePath(file); - // Have we converted these to project references? - - if (_metadataFileNameToConvertedProjectReference.TryGetValue(file, out var convertedProjectReference)) - { - var project = ProjectTracker.GetProject(convertedProjectReference.ProjectId); - UpdateProjectReferenceAliases(project, aliases); - } - else - { - var existingReference = TryGetCurrentMetadataReference(file); - Contract.ThrowIfNull(existingReference); - - var newProperties = existingReference.Properties.WithAliases(aliases); - - RemoveMetadataReferenceCore(existingReference, disposeReference: true); - - AddMetadataReferenceCore(this.MetadataReferenceProvider.CreateMetadataReference(file, newProperties)); - } - } - - protected void UpdateProjectReferenceAliases(AbstractProject referencedProject, ImmutableArray aliases) - { - AssertIsForeground(); - - var projectReference = GetCurrentProjectReferences().Single(r => r.ProjectId == referencedProject.Id); - - var newProjectReference = new ProjectReference(referencedProject.Id, aliases, projectReference.EmbedInteropTypes); - - // Is this a project with converted references? If so, make sure we track it - string referenceBinPath = referencedProject.BinOutputPath; - if (referenceBinPath != null && _metadataFileNameToConvertedProjectReference.ContainsKey(referenceBinPath)) - { - _metadataFileNameToConvertedProjectReference[referenceBinPath]= newProjectReference; - } - - // Remove the existing reference first - RemoveProjectReference(projectReference); - - AddProjectReference(newProjectReference); - } - - private void UninitializeDocument(IVisualStudioHostDocument document) - { - AssertIsForeground(); - - if (PushingChangesToWorkspace) - { - if (document.IsOpen) - { - this.ProjectTracker.NotifyWorkspace(workspace => workspace.OnDocumentClosed(document.Id, document.Loader, updateActiveContext: true)); - } - - this.ProjectTracker.NotifyWorkspace(workspace => workspace.OnDocumentRemoved(document.Id)); - } - - document.Opened -= s_documentOpenedEventHandler; - document.Closing -= s_documentClosingEventHandler; - document.UpdatedOnDisk -= s_documentUpdatedOnDiskEventHandler; - - document.Dispose(); - } - - private void UninitializeAdditionalDocument(IVisualStudioHostDocument document) - { - AssertIsForeground(); - - if (PushingChangesToWorkspace) - { - if (document.IsOpen) - { - this.ProjectTracker.NotifyWorkspace(workspace => workspace.OnAdditionalDocumentClosed(document.Id, document.Loader)); - } - - this.ProjectTracker.NotifyWorkspace(host => host.OnAdditionalDocumentRemoved(document.Id)); - } - - document.Opened -= s_additionalDocumentOpenedEventHandler; - document.Closing -= s_additionalDocumentClosingEventHandler; - document.UpdatedOnDisk -= s_additionalDocumentUpdatedOnDiskEventHandler; - - document.Dispose(); - } - - internal void StartPushingToWorkspaceAndNotifyOfOpenDocuments() - { - AssertIsForeground(); - StartPushingToWorkspaceAndNotifyOfOpenDocuments(this); - } - - internal bool PushingChangesToWorkspace { get; set; } - - protected void UpdateRuleSetError(IRuleSetFile ruleSetFile) - { - AssertIsForeground(); - - if (this.HostDiagnosticUpdateSource == null) - { - return; - } - - if (ruleSetFile == null || - ruleSetFile.GetException() == null) - { - this.HostDiagnosticUpdateSource.ClearDiagnosticsForProject(this.Id, RuleSetErrorId); - } - else - { - var messageArguments = new string[] { ruleSetFile.FilePath, ruleSetFile.GetException().Message }; - if (DiagnosticData.TryCreate(_errorReadingRulesetRule, messageArguments, this.Id, this.Workspace, out var diagnostic)) - { - this.HostDiagnosticUpdateSource.UpdateDiagnosticsForProject(this.Id, RuleSetErrorId, SpecializedCollections.SingletonEnumerable(diagnostic)); - } - } - } - - protected void SetObjOutputPathAndRelatedData(string objOutputPath) - { - AssertIsForeground(); - - var currentObjOutputPath = this.ObjOutputPath; - if (PathUtilities.IsAbsolute(objOutputPath) && !string.Equals(currentObjOutputPath, objOutputPath, StringComparison.OrdinalIgnoreCase)) - { - // set obj output path - this.ObjOutputPath = objOutputPath; - - // Workspace/services can be null for tests. - if (this.MetadataService != null) - { - var newCompilationOptions = CurrentCompilationOptions.WithMetadataReferenceResolver(CreateMetadataReferenceResolver( - metadataService: this.MetadataService, - projectDirectory: this.ContainingDirectoryPathOpt, - outputDirectory: Path.GetDirectoryName(objOutputPath))); - SetOptionsCore(newCompilationOptions); - } - - if (PushingChangesToWorkspace) - { - this.ProjectTracker.NotifyWorkspace(workspace => workspace.OnCompilationOptionsChanged(this.Id, CurrentCompilationOptions)); - this.ProjectTracker.NotifyWorkspace(workspace => workspace.OnParseOptionsChanged(this.Id, CurrentParseOptions)); - this.ProjectTracker.NotifyWorkspace(workspace => workspace.OnOutputFilePathChanged(this.Id, objOutputPath)); - } - - UpdateAssemblyName(); - } - } - - private void UpdateAssemblyName() - { - AssertIsForeground(); - - // set assembly name if changed - // we use designTimeOutputPath to get assembly name since it is more reliable way to get the assembly name. - // otherwise, friend assembly all get messed up. - var newAssemblyName = GetAssemblyNameFromPath(this.ObjOutputPath ?? this.ProjectSystemName); - if (!string.Equals(AssemblyName, newAssemblyName, StringComparison.Ordinal)) - { - AssemblyName = newAssemblyName; - - if (PushingChangesToWorkspace) - { - this.ProjectTracker.NotifyWorkspace(workspace => workspace.OnAssemblyNameChanged(this.Id, newAssemblyName)); - } - } - } - - protected internal void SetBinOutputPathAndRelatedData(string binOutputPath) - { - AssertIsForeground(); - - // refresh final output path - var currentBinOutputPath = this.BinOutputPath; - if (binOutputPath != null && !string.Equals(currentBinOutputPath, binOutputPath, StringComparison.OrdinalIgnoreCase)) - { - this.BinOutputPath = binOutputPath; - - // If the project has been hooked up with the project tracker, then update the bin path with the tracker. - if (this.ProjectTracker.GetProject(Id) != null) - { - this.ProjectTracker.UpdateProjectBinPath(this, currentBinOutputPath, binOutputPath); - } - } - } - - protected void UpdateProjectDisplayName(string newDisplayName) - { - UpdateProjectDisplayNameAndFilePath(newDisplayName, newFilePath: null); - } - - protected void UpdateProjectFilePath(string newFilePath) - { - UpdateProjectDisplayNameAndFilePath(newDisplayName: null, newFilePath: newFilePath); - } - - protected void UpdateProjectDisplayNameAndFilePath(string newDisplayName, string newFilePath) - { - AssertIsForeground(); - - bool updateMade = false; - - if (newDisplayName != null && this.DisplayName != newDisplayName) - { - this.DisplayName = newDisplayName; - updateMade = true; - } - - if (newFilePath != null && File.Exists(newFilePath) && this.ProjectFilePath != newFilePath) - { - Debug.Assert(PathUtilities.IsAbsolute(newFilePath)); - this.ProjectFilePath = newFilePath; - updateMade = true; - } - - if (updateMade && PushingChangesToWorkspace) - { - this.ProjectTracker.NotifyWorkspace(workspace => workspace.OnProjectNameChanged(Id, this.DisplayName, this.ProjectFilePath)); - } - } - - private static void StartPushingToWorkspaceAndNotifyOfOpenDocuments(AbstractProject project) - { - project.AssertIsForeground(); - - // If a document is opened in a project but we haven't started pushing yet, we want to stop doing lazy - // loading for this project and get it up to date so the user gets a fast experience there. If the file - // was presented as open to us right away, then we'll never do this in OnDocumentOpened, so we should do - // it here. It's important to do this after everything else happens in this method, so we don't get - // strange ordering issues. It's still possible that this won't actually push changes if the workspace - // host isn't ready to receive events yet. - project.ProjectTracker.StartPushingToWorkspaceAndNotifyOfOpenDocuments(SpecializedCollections.SingletonEnumerable(project)); - } - - private static MetadataReferenceResolver CreateMetadataReferenceResolver(IMetadataService metadataService, string projectDirectory, string outputDirectory) - { - ImmutableArray assemblySearchPaths; - if (projectDirectory != null && outputDirectory != null) - { - assemblySearchPaths = ImmutableArray.Create(projectDirectory, outputDirectory); - } - else if (projectDirectory != null) - { - assemblySearchPaths = ImmutableArray.Create(projectDirectory); - } - else if (outputDirectory != null) - { - assemblySearchPaths = ImmutableArray.Create(outputDirectory); - } - else - { - assemblySearchPaths = ImmutableArray.Empty; - } - - return new WorkspaceMetadataFileReferenceResolver(metadataService, new RelativePathResolver(assemblySearchPaths, baseDirectory: projectDirectory)); - } - - /// - /// Used for unit testing: don't crash the process if something bad happens. - /// - internal static bool CrashOnException = true; - - protected static bool FilterException(Exception e) - { - if (CrashOnException) - { - FatalError.Report(e); - } - - // Nothing fancy, so don't catch - return false; - } - - #region FolderNames - private readonly List _tmpFolders = new List(); - private readonly Dictionary> _folderNameMap = new Dictionary>(); - - public ImmutableArray GetFolderNamesFromHierarchy(uint documentItemID) - { - AssertIsForeground(); - - if (documentItemID != (uint)VSConstants.VSITEMID.Nil && Hierarchy.GetProperty(documentItemID, (int)VsHierarchyPropID.Parent, out var parentObj) == VSConstants.S_OK) - { - var parentID = UnboxVSItemId(parentObj); - if (parentID != (uint)VSConstants.VSITEMID.Nil && parentID != (uint)VSConstants.VSITEMID.Root) - { - return GetFolderNamesForFolder(parentID); - } - } - - return ImmutableArray.Empty; - } - - private ImmutableArray GetFolderNamesForFolder(uint folderItemID) - { - AssertIsForeground(); - - // note: use of tmpFolders is assuming this API is called on UI thread only. - _tmpFolders.Clear(); - if (!_folderNameMap.TryGetValue(folderItemID, out var names)) - { - ComputeFolderNames(folderItemID, _tmpFolders, Hierarchy); - names = _tmpFolders.ToImmutableArray(); - _folderNameMap.Add(folderItemID, names); - } - else - { - // verify names, and change map if we get a different set. - // this is necessary because we only get document adds/removes from the project system - // when a document name or folder name changes. - ComputeFolderNames(folderItemID, _tmpFolders, Hierarchy); - if (!Enumerable.SequenceEqual(names, _tmpFolders)) - { - names = _tmpFolders.ToImmutableArray(); - _folderNameMap[folderItemID] = names; - } - } - - return names; - } - - // Different hierarchies are inconsistent on whether they return ints or uints for VSItemIds. - // Technically it should be a uint. However, there's no enforcement of this, and marshalling - // from native to managed can end up resulting in boxed ints instead. Handle both here so - // we're resilient to however the IVsHierarchy was actually implemented. - private static uint UnboxVSItemId(object id) - { - return id is uint ? (uint)id : unchecked((uint)(int)id); - } - - private static void ComputeFolderNames(uint folderItemID, List names, IVsHierarchy hierarchy) - { - if (hierarchy.GetProperty((uint)folderItemID, (int)VsHierarchyPropID.Name, out var nameObj) == VSConstants.S_OK) - { - // For 'Shared' projects, IVSHierarchy returns a hierarchy item with < character in its name (i.e. ) - // as a child of the root item. There is no such item in the 'visual' hierarchy in solution explorer and no such folder - // is present on disk either. Since this is not a real 'folder', we exclude it from the contents of Document.Folders. - // Note: The parent of the hierarchy item that contains < character in its name is VSITEMID.Root. So we don't need to - // worry about accidental propagation out of the Shared project to any containing 'Solution' folders - the check for - // VSITEMID.Root below already takes care of that. - var name = (string)nameObj; - if (!name.StartsWith("<", StringComparison.OrdinalIgnoreCase)) - { - names.Insert(0, name); - } - } - - if (hierarchy.GetProperty((uint)folderItemID, (int)VsHierarchyPropID.Parent, out var parentObj) == VSConstants.S_OK) - { - var parentID = UnboxVSItemId(parentObj); - if (parentID != (uint)VSConstants.VSITEMID.Nil && parentID != (uint)VSConstants.VSITEMID.Root) - { - ComputeFolderNames(parentID, names, hierarchy); - } - } + return _visualStudioWorkspace.CurrentSolution.GetProject(Id).Documents.SelectAsArray( + d => (IVisualStudioHostDocument)new DocumentProvider.ShimDocument(this, d.Id, d.FilePath, d.SourceCodeKind)); } - #endregion } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject_Analyzers.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject_Analyzers.cs deleted file mode 100644 index e66cf3250bc171b7d8ea784be14298bcf0b81acc..0000000000000000000000000000000000000000 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject_Analyzers.cs +++ /dev/null @@ -1,220 +0,0 @@ -// 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 System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.IO; -using System.Linq; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Host; -using Microsoft.VisualStudio.ComponentModelHost; -using Microsoft.VisualStudio.Shell.Interop; -using Roslyn.Utilities; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -{ - internal partial class AbstractProject - { - private AnalyzerFileWatcherService _analyzerFileWatcherService = null; - private AnalyzerDependencyCheckingService _dependencyCheckingService = null; - - public void AddAnalyzerReference(string analyzerAssemblyFullPath) - { - AssertIsForeground(); - - if (CurrentProjectAnalyzersContains(analyzerAssemblyFullPath)) - { - return; - } - - var fileChangeService = (IVsFileChangeEx)this.ServiceProvider.GetService(typeof(SVsFileChangeEx)); - if (Workspace == null) - { - // This can happen only in tests. - var testAnalyzer = new VisualStudioAnalyzer(analyzerAssemblyFullPath, fileChangeService, this.HostDiagnosticUpdateSource, this.Id, this.Workspace, loader: null, language: this.Language); - this.AddOrUpdateAnalyzer(analyzerAssemblyFullPath, testAnalyzer); - return; - } - - var analyzerLoader = Workspace.Services.GetRequiredService().GetLoader(); - analyzerLoader.AddDependencyLocation(analyzerAssemblyFullPath); - var analyzer = new VisualStudioAnalyzer(analyzerAssemblyFullPath, fileChangeService, this.HostDiagnosticUpdateSource, this.Id, this.Workspace, analyzerLoader, this.Language); - this.AddOrUpdateAnalyzer(analyzerAssemblyFullPath, analyzer); - - if (PushingChangesToWorkspace) - { - var analyzerReference = analyzer.GetReference(); - this.ProjectTracker.NotifyWorkspace(workspace => workspace.OnAnalyzerReferenceAdded(Id, analyzerReference)); - - List existingReferencesWithLoadErrors = GetCurrentAnalyzers().Where(a => a.HasLoadErrors).ToList(); - - foreach (var existingReference in existingReferencesWithLoadErrors) - { - this.ProjectTracker.NotifyWorkspace(workspace => workspace.OnAnalyzerReferenceRemoved(Id, existingReference.GetReference())); - existingReference.Reset(); - this.ProjectTracker.NotifyWorkspace(workspace => workspace.OnAnalyzerReferenceAdded(Id, existingReference.GetReference())); - } - - GetAnalyzerDependencyCheckingService().ReanalyzeSolutionForConflicts(); - } - - if (File.Exists(analyzerAssemblyFullPath)) - { - GetAnalyzerFileWatcherService().TrackFilePathAndReportErrorIfChanged(analyzerAssemblyFullPath, projectId: Id); - } - else - { - analyzer.UpdatedOnDisk += OnAnalyzerChanged; - } - } - - public void RemoveAnalyzerReference(string analyzerAssemblyFullPath) - { - AssertIsForeground(); - - if (!TryGetAnalyzer(analyzerAssemblyFullPath, out var analyzer)) - { - return; - } - - if (Workspace == null) - { - // This can happen only in tests. - RemoveAnalyzer(analyzerAssemblyFullPath); - analyzer.Dispose(); - return; - } - - GetAnalyzerFileWatcherService().RemoveAnalyzerAlreadyLoadedDiagnostics(Id, analyzerAssemblyFullPath); - - RemoveAnalyzer(analyzerAssemblyFullPath); - - if (PushingChangesToWorkspace) - { - var analyzerReference = analyzer.GetReference(); - this.ProjectTracker.NotifyWorkspace(workspace => workspace.OnAnalyzerReferenceRemoved(Id, analyzerReference)); - - GetAnalyzerDependencyCheckingService().ReanalyzeSolutionForConflicts(); - } - - analyzer.Dispose(); - } - - public void SetRuleSetFile(string ruleSetFileFullPath) - { - AssertIsForeground(); - - if (ruleSetFileFullPath == null) - { - ruleSetFileFullPath = string.Empty; - } - - if (ruleSetFileFullPath.Length > 0) - { - // This is already a full path, but run it through GetFullPath to clean it (e.g., remove - // extra backslashes). - ruleSetFileFullPath = Path.GetFullPath(ruleSetFileFullPath); - } - - if (this.RuleSetFile != null && - this.RuleSetFile.Target.FilePath.Equals(ruleSetFileFullPath, StringComparison.OrdinalIgnoreCase)) - { - return; - } - - ResetAnalyzerRuleSet(ruleSetFileFullPath); - } - - public void AddAdditionalFile(string additionalFilePath, Func getIsInCurrentContext) - { - AssertIsForeground(); - - var document = this.DocumentProvider.TryGetDocumentForFile( - this, - filePath: additionalFilePath, - sourceCodeKind: SourceCodeKind.Regular, - folderNames: ImmutableArray.Empty, - canUseTextBuffer: _ => true, - updatedOnDiskHandler: s_additionalDocumentUpdatedOnDiskEventHandler, - openedHandler: s_additionalDocumentOpenedEventHandler, - closingHandler: s_additionalDocumentClosingEventHandler); - - if (document == null) - { - return; - } - - AddAdditionalDocument(document, isCurrentContext: getIsInCurrentContext(document)); - } - - public void RemoveAdditionalFile(string additionalFilePath) - { - IVisualStudioHostDocument document = this.GetCurrentDocumentFromPath(additionalFilePath); - if (document == null) - { - throw new InvalidOperationException("The document is not a part of the finalProject."); - } - - RemoveAdditionalDocument(document); - } - - private void ResetAnalyzerRuleSet(string ruleSetFileFullPath) - { - ClearAnalyzerRuleSet(); - SetAnalyzerRuleSet(ruleSetFileFullPath); - ResetArgumentsAndUpdateOptions(); - } - - private void SetAnalyzerRuleSet(string ruleSetFileFullPath) - { - if (ruleSetFileFullPath.Length != 0) - { - this.RuleSetFile = this.ProjectTracker.RuleSetFileManager.GetOrCreateRuleSet(ruleSetFileFullPath); - this.RuleSetFile.Target.UpdatedOnDisk += OnRuleSetFileUpdateOnDisk; - } - } - - private void ClearAnalyzerRuleSet() - { - if (this.RuleSetFile != null) - { - this.RuleSetFile.Target.UpdatedOnDisk -= OnRuleSetFileUpdateOnDisk; - this.RuleSetFile.Dispose(); - this.RuleSetFile = null; - } - } - - // internal for testing purpose. - internal void OnRuleSetFileUpdateOnDisk(object sender, EventArgs e) - { - AssertIsForeground(); - - var filePath = this.RuleSetFile.Target.FilePath; - - ResetAnalyzerRuleSet(filePath); - } - - private AnalyzerFileWatcherService GetAnalyzerFileWatcherService() - { - if (_analyzerFileWatcherService == null) - { - var componentModel = (IComponentModel)this.ServiceProvider.GetService(typeof(SComponentModel)); - Interlocked.CompareExchange(ref _analyzerFileWatcherService, componentModel.GetService(), null); - } - - return _analyzerFileWatcherService; - } - - private AnalyzerDependencyCheckingService GetAnalyzerDependencyCheckingService() - { - if (_dependencyCheckingService == null) - { - var componentModel = (IComponentModel)this.ServiceProvider.GetService(typeof(SComponentModel)); - Interlocked.CompareExchange(ref _dependencyCheckingService, componentModel.GetService(), null); - } - - return _dependencyCheckingService; - } - } -} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject_Options.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject_Options.cs deleted file mode 100644 index 0d1235ae03cc82ce286f5c4865009ae6190f1012..0000000000000000000000000000000000000000 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject_Options.cs +++ /dev/null @@ -1,195 +0,0 @@ -// 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 System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Host; -using Roslyn.Utilities; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -{ - internal abstract partial class AbstractProject - { - #region Options - private string _lastParsedCompilerOptions; - - /// Can be null if there is no available. - private CommandLineArguments _lastParsedCommandLineArguments; - private CompilationOptions _currentCompilationOptions; - private ParseOptions _currentParseOptions; - - // internal for testing purposes. - internal CompilationOptions CurrentCompilationOptions => _currentCompilationOptions; - internal ParseOptions CurrentParseOptions => _currentParseOptions; - - private void SetOptionsCore(CompilationOptions newCompilationOptions) - { - lock (_gate) - { - _currentCompilationOptions = newCompilationOptions; - } - } - - private void SetOptionsCore(CompilationOptions newCompilationOptions, ParseOptions newParseOptions) - { - lock (_gate) - { - _currentCompilationOptions = newCompilationOptions; - _currentParseOptions = newParseOptions; - } - } - - private void SetArgumentsCore(string commandLine, CommandLineArguments commandLineArguments) - { - lock (_gate) - { - _lastParsedCompilerOptions = commandLine; - _lastParsedCommandLineArguments = commandLineArguments; - } - } - #endregion - - /// - /// Creates and sets new options using the last parsed command line arguments. In the case that the last - /// parsed options are then this call is a NOP. - /// - protected void UpdateOptions() - { - AssertIsForeground(); - - CommandLineArguments lastParsedCommandLineArguments = _lastParsedCommandLineArguments; - if (lastParsedCommandLineArguments != null) - { - // do nothing if the last parsed arguments aren't present, which is the case for languages that don't - // export an ICommandLineParserService like F# - var newParseOptions = CreateParseOptions(lastParsedCommandLineArguments); - var newCompilationOptions = CreateCompilationOptions(lastParsedCommandLineArguments, newParseOptions); - if (newCompilationOptions == CurrentCompilationOptions && newParseOptions == CurrentParseOptions) - { - return; - } - - SetOptions(newCompilationOptions, newParseOptions); - } - } - - /// - /// Parses the given command line and sets new command line arguments. - /// Subsequently, creates and sets new options using the last parsed command line arguments. - /// - protected CommandLineArguments SetArgumentsAndUpdateOptions(string commandLine) - { - AssertIsForeground(); - - var commandLineArguments = SetArguments(commandLine); - UpdateOptions(); - return commandLineArguments; - } - - /// - /// Resets the last parsed command line and updates options with the same command line. - /// - /// - /// Use this method when options can go stale due to side effects, even though the command line is identical. - /// For example, changes to contents of a ruleset file needs to force update the options for the same command line. - /// - protected CommandLineArguments ResetArgumentsAndUpdateOptions() - { - // Clear last parsed command line. - string savedLastParsedCompilerOptions = _lastParsedCompilerOptions; - SetArgumentsCore(commandLine: null, commandLineArguments: null); - - // Now set arguments and update options with the saved command line. - return SetArgumentsAndUpdateOptions(savedLastParsedCompilerOptions); - } - - /// - /// Parses the given command line and sets new command line arguments. - /// - protected CommandLineArguments SetArguments(string commandLine) - { - // Command line options have changed, so update options with new parsed CommandLineArguments. - var splitArguments = CommandLineParser.SplitCommandLineIntoArguments(commandLine, removeHashComments: false); - var parsedCommandLineArguments = CommandLineParserService?.Parse(splitArguments, this.ContainingDirectoryPathOpt, isInteractive: false, sdkDirectory: null); - SetArgumentsCore(commandLine, parsedCommandLineArguments); - return parsedCommandLineArguments; - } - - /// - /// Sets the given compilation and parse options. - /// - protected void SetOptions(CompilationOptions newCompilationOptions, ParseOptions newParseOptions) - { - AssertIsForeground(); - - this.UpdateRuleSetError(this.RuleSetFile?.Target); - - // Set options. - this.SetOptionsCore(newCompilationOptions, newParseOptions); - - if (PushingChangesToWorkspace) - { - this.ProjectTracker.NotifyWorkspace(workspace => workspace.OnCompilationOptionsChanged(Id, newCompilationOptions)); - this.ProjectTracker.NotifyWorkspace(workspace => workspace.OnParseOptionsChanged(Id, newParseOptions)); - } - } - - /// - /// Creates new compilation options from parsed command line arguments, with additional workspace specific options appended. - /// It is expected that derived types which need to add more specific options will fetch the base options and override those options. - /// - protected virtual CompilationOptions CreateCompilationOptions(CommandLineArguments commandLineArguments, ParseOptions newParseOptions) - { - Contract.ThrowIfNull(commandLineArguments); - - // Get options from command line arguments. - var options = commandLineArguments.CompilationOptions; - - // Now set the default workspace options (these are not set by the command line parser). - string projectDirectory = this.ContainingDirectoryPathOpt; - - // TODO: #r support, should it include bin path? - var referenceSearchPaths = ImmutableArray.Empty; - - // TODO: #load support - var sourceSearchPaths = ImmutableArray.Empty; - - MetadataReferenceResolver referenceResolver; - if (Workspace != null) - { - referenceResolver = new WorkspaceMetadataFileReferenceResolver( - Workspace.CurrentSolution.Services.MetadataService, - new RelativePathResolver(referenceSearchPaths, projectDirectory)); - } - else - { - // can only happen in tests - referenceResolver = null; - } - - // Explicitly disable concurrent build. - options = options.WithConcurrentBuild(concurrent: false); - - // Set default resolvers. - options = options.WithMetadataReferenceResolver(referenceResolver) - .WithXmlReferenceResolver(new XmlFileResolver(projectDirectory)) - .WithSourceReferenceResolver(new SourceFileResolver(sourceSearchPaths, projectDirectory)) - .WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default) - .WithStrongNameProvider(new DesktopStrongNameProvider(GetStrongNameKeyPaths())); - - return options; - } - - /// - /// Creates new parse options from parsed command line arguments (with overridden default DocumentationMode). - /// It is expected that derived types which need to add more specific options will fetch the base options and override those options. - /// - protected virtual ParseOptions CreateParseOptions(CommandLineArguments commandLineArguments) - { - Contract.ThrowIfNull(commandLineArguments); - - // Override the default documentation mode. - var documentationMode = commandLineArguments.DocumentationPath != null ? DocumentationMode.Diagnose : DocumentationMode.Parse; - return commandLineArguments.ParseOptions.WithDocumentationMode(documentationMode); - } - } -} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CPS/IWorkspaceProjectContext.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CPS/IWorkspaceProjectContext.cs index 07774c1c2de8ff19b3e76bd5a16f826365a7cfeb..52fdb3316f919870e5faab0ee99192a47f5affab 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CPS/IWorkspaceProjectContext.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CPS/IWorkspaceProjectContext.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; namespace Microsoft.VisualStudio.LanguageServices.ProjectSystem @@ -18,6 +20,8 @@ internal interface IWorkspaceProjectContext : IDisposable bool LastDesignTimeBuildSucceeded { get; set; } string BinOutputPath { get; set; } + ProjectId Id { get; } + // Options. void SetOptions(string commandLineForOptions); @@ -34,6 +38,11 @@ internal interface IWorkspaceProjectContext : IDisposable void RemoveSourceFile(string filePath); void AddAdditionalFile(string filePath, bool isInCurrentContext = true); void RemoveAdditionalFile(string filePath); + void AddDynamicFile(string filePath, IEnumerable folderNames = null); + void RemoveDynamicFile(string fullPath); void SetRuleSetFile(string filePath); + + void StartBatch(); + void EndBatch(); } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CPS/IWorkspaceProjectContextFactory.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CPS/IWorkspaceProjectContextFactory.cs index d8dab25de2fe2b7b427dccefe3905afc97108c87..ee47b9dd633120e14ef3a86dcd01d345633a5649 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CPS/IWorkspaceProjectContextFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CPS/IWorkspaceProjectContextFactory.cs @@ -14,29 +14,25 @@ internal interface IWorkspaceProjectContextFactory { /// /// Creates and initializes a new Workspace project and returns a to lazily initialize the properties and items for the project. - /// This method can be invoked on a background thread and doesn't access any members of the given UI , - /// allowing the UI hierarchy to be published lazily, as long as has been called once. /// /// Project language. - /// Display name for the project. + /// Unique name for the project. /// Full path to the project file for the project. /// Project guid. /// for the project, an be null in deferred project load cases. /// Initial project binary output path. - IWorkspaceProjectContext CreateProjectContext(string languageName, string projectDisplayName, string projectFilePath, Guid projectGuid, object hierarchy, string binOutputPath); + IWorkspaceProjectContext CreateProjectContext(string languageName, string projectUniqueName, string projectFilePath, Guid projectGuid, object hierarchy, string binOutputPath); /// /// Creates and initializes a new Workspace project and returns a to lazily initialize the properties and items for the project. - /// This method can be invoked on a background thread and doesn't access any members of the given UI , - /// allowing the UI hierarchy to be published lazily, as long as has been called once. /// /// Project language. - /// Display name for the project. + /// Display name for the project. /// Full path to the project file for the project. /// Project guid. /// for the project, an be null in deferred project load cases. /// Initial project binary output path. /// Error reporter object. - IWorkspaceProjectContext CreateProjectContext(string languageName, string projectDisplayName, string projectFilePath, Guid projectGuid, object hierarchy, string binOutputPath, ProjectExternalErrorReporter errorReporter); + IWorkspaceProjectContext CreateProjectContext(string languageName, string projectUniqueName, string projectFilePath, Guid projectGuid, object hierarchy, string binOutputPath, ProjectExternalErrorReporter errorReporter); } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/DocumentKey.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/DocumentKey.cs deleted file mode 100644 index 6426d36cbd48af07b46cd096dc9a676c534a3aa4..0000000000000000000000000000000000000000 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/DocumentKey.cs +++ /dev/null @@ -1,51 +0,0 @@ -// 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 System; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Shell.Interop; -using Roslyn.Utilities; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -{ - /// - /// Represents the information which uniquely defines a document -- the project which contains - /// it and the moniker. - /// - /// Immutable, since this object is used as a key into some dictionaries. - /// - internal class DocumentKey : IEquatable - { - private readonly AbstractProject _hostProject; - private readonly string _moniker; - - public AbstractProject HostProject { get { return _hostProject; } } - public string Moniker { get { return _moniker; } } - - public DocumentKey(AbstractProject hostProject, string moniker) - { - Contract.ThrowIfNull(hostProject); - Contract.ThrowIfNull(moniker); - - _hostProject = hostProject; - _moniker = moniker; - } - - public bool Equals(DocumentKey other) - { - return other != null && - HostProject == other.HostProject && - Moniker.Equals(other.Moniker, StringComparison.OrdinalIgnoreCase); - } - - public override int GetHashCode() - { - return Hash.Combine(HostProject.GetHashCode(), StringComparer.OrdinalIgnoreCase.GetHashCode(Moniker)); - } - - public override bool Equals(object obj) - { - return this.Equals(obj as DocumentKey); - } - } -} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/DocumentProvider.RunningDocTableEventsSink.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/DocumentProvider.RunningDocTableEventsSink.cs deleted file mode 100644 index abd86f6b68c35330285be563e747651f2af4f69e..0000000000000000000000000000000000000000 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/DocumentProvider.RunningDocTableEventsSink.cs +++ /dev/null @@ -1,69 +0,0 @@ -// 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.Shell.Interop; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -{ - internal partial class DocumentProvider - { - private class RunningDocTableEventsSink : IVsRunningDocTableEvents3 - { - private readonly DocumentProvider _documentProvider; - - public RunningDocTableEventsSink(DocumentProvider documentProvider) - { - _documentProvider = documentProvider; - } - - public int OnAfterAttributeChange(uint docCookie, uint grfAttribs) - { - return VSConstants.S_OK; - } - - public int OnAfterAttributeChangeEx(uint docCookie, uint grfAttribs, IVsHierarchy pHierOld, uint itemidOld, string pszMkDocumentOld, IVsHierarchy pHierNew, uint itemidNew, string pszMkDocumentNew) - { - _documentProvider.OnAfterAttributeChangeEx(docCookie, grfAttribs, pHierOld, itemidOld, pszMkDocumentOld, pHierNew, itemidNew, pszMkDocumentNew); - - return VSConstants.S_OK; - } - - public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame) - { - return VSConstants.S_OK; - } - - public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) - { - return VSConstants.S_OK; - } - - public int OnAfterSave(uint docCookie) - { - return VSConstants.S_OK; - } - - public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame) - { - _documentProvider.OnBeforeDocumentWindowShow(pFrame, docCookie, fFirstShow != 0); - - return VSConstants.S_OK; - } - - public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) - { - // If we have no remaining locks, then we're done - if (dwReadLocksRemaining + dwEditLocksRemaining == 0) - { - _documentProvider.CloseDocuments(docCookie, monikerToKeep: null); - } - - return VSConstants.S_OK; - } - - public int OnBeforeSave(uint docCookie) - { - return VSConstants.S_OK; - } - } - } -} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/DocumentProvider.StandardTextDocument.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/DocumentProvider.StandardTextDocument.cs deleted file mode 100644 index f660c2647739d92e5120e13da36264d6f205867e..0000000000000000000000000000000000000000 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/DocumentProvider.StandardTextDocument.cs +++ /dev/null @@ -1,268 +0,0 @@ -// 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 System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.Text; -using Roslyn.Utilities; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -{ - internal partial class DocumentProvider - { - [DebuggerDisplay("{GetDebuggerDisplay(),nq}")] - private class StandardTextDocument : ForegroundThreadAffinitizedObject, IVisualStudioHostDocument - { - /// - /// The IDocumentProvider that created us. - /// - private readonly DocumentProvider _documentProvider; - private readonly string _itemMoniker; - private readonly FileChangeTracker _fileChangeTracker; - private readonly ReiteratedVersionSnapshotTracker _snapshotTracker; - private readonly TextLoader _doNotAccessDirectlyLoader; - - /// - /// The text buffer that is open in the editor. When the file is closed, this is null. - /// - private ITextBuffer _openTextBuffer; - - public DocumentId Id { get; } - public IReadOnlyList Folders { get; } - public AbstractProject Project { get; } - public SourceCodeKind SourceCodeKind { get; } - public DocumentKey Key { get; } - - public event EventHandler UpdatedOnDisk; - public event EventHandler Opened; - public event EventHandler Closing; - - /// - /// Creates a . - /// - public StandardTextDocument( - DocumentProvider documentProvider, - AbstractProject project, - DocumentKey documentKey, - ImmutableArray folderNames, - SourceCodeKind sourceCodeKind, - IVsFileChangeEx fileChangeService, - ITextBuffer openTextBuffer, - DocumentId id, - EventHandler updatedOnDiskHandler, - EventHandler openedHandler, - EventHandler closingHandler) - : base(documentProvider.ThreadingContext) - { - Contract.ThrowIfNull(documentProvider); - - this.Project = project; - this.Id = id ?? DocumentId.CreateNewId(project.Id, documentKey.Moniker); - _itemMoniker = documentKey.Moniker; - - this.Folders = folderNames; - - _documentProvider = documentProvider; - - this.Key = documentKey; - this.SourceCodeKind = sourceCodeKind; - _fileChangeTracker = new FileChangeTracker(fileChangeService, this.FilePath); - _fileChangeTracker.UpdatedOnDisk += OnUpdatedOnDisk; - - _openTextBuffer = openTextBuffer; - _snapshotTracker = new ReiteratedVersionSnapshotTracker(openTextBuffer); - - // The project system does not tell us the CodePage specified in the proj file, so - // we use null to auto-detect. - _doNotAccessDirectlyLoader = new FileChangeTrackingTextLoader(_fileChangeTracker, new FileTextLoader(documentKey.Moniker, defaultEncoding: null)); - - // If we aren't already open in the editor, then we should create a file change notification - if (openTextBuffer == null) - { - _fileChangeTracker.StartFileChangeListeningAsync(); - } - - if (updatedOnDiskHandler != null) - { - UpdatedOnDisk += updatedOnDiskHandler; - } - - if (openedHandler != null) - { - Opened += openedHandler; - } - - if (closingHandler != null) - { - Closing += closingHandler; - } - } - - public bool IsOpen - { - get { return _openTextBuffer != null; } - } - - public string FilePath - { - get { return Key.Moniker; } - } - - public string Name - { - get - { - try - { - return Path.GetFileName(this.FilePath); - } - catch (ArgumentException) - { - return this.FilePath; - } - } - } - - public TextLoader Loader - { - get - { - return _doNotAccessDirectlyLoader; - } - } - - public DocumentInfo GetInitialState() - { - return DocumentInfo.Create( - id: this.Id, - name: this.Name, - folders: this.Folders, - sourceCodeKind: this.SourceCodeKind, - loader: this.Loader, - filePath: this.FilePath); - } - - internal void ProcessOpen(ITextBuffer openedBuffer, bool isCurrentContext) - { - Debug.Assert(openedBuffer != null); - - _fileChangeTracker.StopFileChangeListening(); - _snapshotTracker.StartTracking(openedBuffer); - - _openTextBuffer = openedBuffer; - Opened?.Invoke(this, isCurrentContext); - } - - internal void ProcessClose(bool updateActiveContext) - { - // Todo: it might already be closed... - // For now, continue asserting as it can be clicked through. - Debug.Assert(_openTextBuffer != null); - Closing?.Invoke(this, updateActiveContext); - - var buffer = _openTextBuffer; - _openTextBuffer = null; - - _snapshotTracker.StopTracking(buffer); - _fileChangeTracker.StartFileChangeListeningAsync(); - } - - public SourceTextContainer GetOpenTextContainer() - { - return _openTextBuffer.AsTextContainer(); - } - - public ITextBuffer GetOpenTextBuffer() - { - return _openTextBuffer; - } - - private void OnUpdatedOnDisk(object sender, EventArgs e) - { - UpdatedOnDisk?.Invoke(this, EventArgs.Empty); - } - - public void Dispose() - { - _fileChangeTracker.Dispose(); - _fileChangeTracker.UpdatedOnDisk -= OnUpdatedOnDisk; - - _documentProvider.StopTrackingDocument(this); - } - - public void UpdateText(SourceText newText) - { - // Avoid opening the invisible editor if we already have a buffer. It takes a relatively - // expensive source control check if the file is already checked out. - if (_openTextBuffer != null) - { - TextEditApplication.UpdateText(newText, _openTextBuffer, EditOptions.DefaultMinimalChange); - } - else - { - using (var invisibleEditor = ((VisualStudioWorkspaceImpl)this.Project.Workspace).OpenInvisibleEditor(this)) - { - TextEditApplication.UpdateText(newText, invisibleEditor.TextBuffer, EditOptions.None); - } - } - } - - public ITextBuffer GetTextUndoHistoryBuffer() - { - return GetOpenTextBuffer(); - } - - private string GetDebuggerDisplay() - { - return this.Name; - } - - public uint GetItemId() - { - AssertIsForeground(); - - if (_itemMoniker == null || Project.Hierarchy == null) - { - return (uint)VSConstants.VSITEMID.Nil; - } - - return Project.Hierarchy.TryGetItemId(_itemMoniker); - } - - /// - /// A wrapper for a that ensures we are watching file contents prior to reading the file. - /// - private sealed class FileChangeTrackingTextLoader : TextLoader - { - private readonly FileChangeTracker _fileChangeTracker; - private readonly TextLoader _innerTextLoader; - - public FileChangeTrackingTextLoader(FileChangeTracker fileChangeTracker, TextLoader innerTextLoader) - { - _fileChangeTracker = fileChangeTracker; - _innerTextLoader = innerTextLoader; - } - - public override Task LoadTextAndVersionAsync(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken) - { - _fileChangeTracker.EnsureSubscription(); - return _innerTextLoader.LoadTextAndVersionAsync(workspace, documentId, cancellationToken); - } - - internal override TextAndVersion LoadTextAndVersionSynchronously(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken) - { - _fileChangeTracker.EnsureSubscription(); - return _innerTextLoader.LoadTextAndVersionSynchronously(workspace, documentId, cancellationToken); - } - } - } - } -} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/DocumentProvider.TextBufferDataEventsSink.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/DocumentProvider.TextBufferDataEventsSink.cs deleted file mode 100644 index de5572d3b0aca3d3b2fb9e795a85c51db7ac6d3a..0000000000000000000000000000000000000000 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/DocumentProvider.TextBufferDataEventsSink.cs +++ /dev/null @@ -1,44 +0,0 @@ -// 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 System; -using Microsoft.VisualStudio.TextManager.Interop; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -{ - internal partial class DocumentProvider - { - private class TextBufferDataEventsSink : IVsTextBufferDataEvents - { - private readonly Action _onDocumentLoadCompleted; - - private ComEventSink _sink; - - /// - /// Helper method for creating and hooking up a TextBufferDataEventsSink. - /// - public static void HookupHandler(IVsTextBuffer textBuffer, Action onDocumentLoadCompleted) - { - var eventHandler = new TextBufferDataEventsSink(onDocumentLoadCompleted); - - eventHandler._sink = ComEventSink.Advise(textBuffer, eventHandler); - } - - private TextBufferDataEventsSink(Action onDocumentLoadCompleted) - { - _onDocumentLoadCompleted = onDocumentLoadCompleted; - } - - public void OnFileChanged(uint grfChange, uint dwFileAttrs) - { - } - - public int OnLoadCompleted(int fReload) - { - _sink.Unadvise(); - _onDocumentLoadCompleted(); - - return VSConstants.S_OK; - } - } - } -} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/DocumentProvider.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/DocumentProvider.cs index aed7629451966b283e23f6620ca151c579052a14..99d885fb93feb194d7cab3ba51890c06389cfa7a 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/DocumentProvider.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/DocumentProvider.cs @@ -1,92 +1,17 @@ -// 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 System; +using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; -using System.Runtime.InteropServices; -using System.Threading; +using System.Text; using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.VisualStudio.ComponentModelHost; -using Microsoft.VisualStudio.Editor; -using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Operations; -using Microsoft.VisualStudio.TextManager.Interop; -using Microsoft.VisualStudio.Utilities; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem { - /// - /// This service provides a central place where the workspace/project system layer may create - /// Document objects that represent items from the project system. These IDocuments are useful - /// in that they watch the running document table, tracking open/close events, and also file - /// change events while the file is closed. - /// - internal sealed partial class DocumentProvider : ForegroundThreadAffinitizedObject + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] + internal sealed class DocumentProvider { - #region Immutable readonly fields/properties that can be accessed from foreground or background threads - do not need locking for access. - private readonly object _gate = new object(); - private readonly uint _runningDocumentTableEventCookie; - private readonly VisualStudioProjectTracker _projectTracker; - private readonly IVsFileChangeEx _fileChangeService; - private readonly IVsTextManager _textManager; - private readonly IVsRunningDocumentTable4 _runningDocumentTable; - private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService; - private readonly IContentTypeRegistryService _contentTypeRegistryService; - private readonly LinkedFileUtilities _linkedFileUtilities; - private readonly VisualStudioDocumentTrackingService _documentTrackingServiceOpt; - #endregion - - #region Mutable fields accessed from foreground or background threads - need locking for access. - /// - /// The core data structure of this entire class. - /// - private readonly Dictionary _documentMap = new Dictionary(); - private readonly Dictionary> _docCookiesToOpenDocumentKeys = new Dictionary>(); - private readonly Dictionary _docCookiesToNonRoslynDocumentBuffers = new Dictionary(); - private readonly Dictionary _documentIdHints = new Dictionary(StringComparer.OrdinalIgnoreCase); - private readonly Dictionary _pendingDocumentInitializationTasks = new Dictionary(); - #endregion - - /// - /// Creates a document provider. - /// - /// Service provider - /// An optional to track active and visible documents. - public DocumentProvider( - VisualStudioProjectTracker projectTracker, - IServiceProvider serviceProvider, - VisualStudioDocumentTrackingService documentTrackingService, - LinkedFileUtilities linkedFileUtilities) - : base(projectTracker.ThreadingContext) - { - var componentModel = (IComponentModel)serviceProvider.GetService(typeof(SComponentModel)); - - _projectTracker = projectTracker; - this._documentTrackingServiceOpt = documentTrackingService; - this._runningDocumentTable = (IVsRunningDocumentTable4)serviceProvider.GetService(typeof(SVsRunningDocumentTable)); - this._editorAdaptersFactoryService = componentModel.GetService(); - this._contentTypeRegistryService = componentModel.GetService(); - _linkedFileUtilities = linkedFileUtilities; - _textManager = (IVsTextManager)serviceProvider.GetService(typeof(SVsTextManager)); - - _fileChangeService = (IVsFileChangeEx)serviceProvider.GetService(typeof(SVsFileChangeEx)); - - var shell = (IVsShell)serviceProvider.GetService(typeof(SVsShell)); - if (shell == null) - { - // This can happen only in tests, bail out. - return; - } - - var runningDocumentTableForEvents = (IVsRunningDocumentTable)_runningDocumentTable; - Marshal.ThrowExceptionForHR(runningDocumentTableForEvents.AdviseRunningDocTableEvents(new RunningDocTableEventsSink(this), out _runningDocumentTableEventCookie)); - } - [Obsolete("This overload is a compatibility shim for TypeScript; please do not use it.")] public IVisualStudioHostDocument TryGetDocumentForFile( AbstractProject hostProject, @@ -98,697 +23,26 @@ internal sealed partial class DocumentProvider : ForegroundThreadAffinitizedObje EventHandler openedHandler = null, EventHandler closingHandler = null) { - var itemid = hostProject.Hierarchy.TryGetItemId(filePath); - - var folderNames = getFolderNames(itemid).AsImmutableOrEmpty(); - return TryGetDocumentForFile( - hostProject, - filePath, - sourceCodeKind, - canUseTextBuffer, - folderNames, - updatedOnDiskHandler, - openedHandler, - closingHandler); - } - - /// - /// Gets the for the file at the given filePath. - /// If we are on the foreground thread and this document is already open in the editor, - /// then we also attempt to associate the text buffer with it. - /// Otherwise, if we are on a background thread, then this text buffer association will happen on a scheduled task - /// whenever is invoked for the returned document. - /// - public IVisualStudioHostDocument TryGetDocumentForFile( - AbstractProject hostProject, - string filePath, - SourceCodeKind sourceCodeKind, - Func canUseTextBuffer, - ImmutableArray folderNames, - EventHandler updatedOnDiskHandler = null, - EventHandler openedHandler = null, - EventHandler closingHandler = null) - { - var documentKey = new DocumentKey(hostProject, filePath); - StandardTextDocument document; - - lock (_gate) - { - if (_documentMap.TryGetValue(documentKey, out document)) - { - return document; - } - } - - ITextBuffer openTextBuffer = null; - uint foundCookie = VSConstants.VSCOOKIE_NIL; - - if (IsForeground()) - { - // If we are on the foreground thread and this document is already open in the editor we want to associate the text buffer with it. - // Otherwise, we are on a background thread, and this text buffer association will happen on a scheduled task - // whenever NotifyDocumentRegisteredToProjectAndStartToRaiseEvents is invoked for the returned document. - // However, determining if a document is already open is a bit complicated. With the introduction - // of the lazy tabs feature in Dev12, a document may be open (i.e. it has a tab in the shell) but not - // actually initialized (no data has been loaded for it because its contents have not actually been - // viewed or used). We only care about documents that are open AND initialized. - // That means we can't call IVsRunningDocumentTable::FindAndLockDocument to find the document; if the - // document is open but not initialized, the call will force initialization. This is bad for two - // reasons: - // 1.) It circumvents lazy tabs for any document that is part of a VB or C# project. - // 2.) Initialization may cause a whole host of other code to run synchronously, such as taggers. - // Instead, we check if the document is already initialized, and avoid asking for the doc data and - // hierarchy if it is not. - if (_runningDocumentTable.TryGetCookieForInitializedDocument(documentKey.Moniker, out foundCookie)) - { - object foundDocData = _runningDocumentTable.GetDocumentData(foundCookie); - openTextBuffer = TryGetTextBufferFromDocData(foundDocData); - if (openTextBuffer == null) - { - // We're open but not open as a normal text buffer. This can happen if the - // project system (say in ASP.NET cases) is telling us to add a file which - // actually isn't a normal text file at all. - return null; - } - - if (!canUseTextBuffer(openTextBuffer)) - { - return null; - } - } - } - - lock (_gate) - { - // If this is being added through a public call to Workspace.AddDocument (say, ApplyChanges) then we might - // already have a document ID that we should be using here. - _documentIdHints.TryGetValue(filePath, out var id); - - document = new StandardTextDocument( - this, - hostProject, - documentKey, - folderNames, - sourceCodeKind, - _fileChangeService, - openTextBuffer, - id, - updatedOnDiskHandler, - openedHandler, - closingHandler); - - // Add this to our document map - _documentMap.Add(documentKey, document); - - if (openTextBuffer != null) - { - AddCookieOpenDocumentPair_NoLock(foundCookie, documentKey); - } - } - - return document; - } - - /// - /// Tries to return an ITextBuffer representing the document from the document's DocData. - /// - /// The DocData from the running document table. - /// The ITextBuffer. If one could not be found, this returns null. - private ITextBuffer TryGetTextBufferFromDocData(object docData) - { - AssertIsForeground(); - - - if (docData is IVsTextBuffer shimTextBuffer) - { - return _editorAdaptersFactoryService.GetDocumentBuffer(shimTextBuffer); - } - else - { - return null; - } - } - - private void NewBufferOpened(uint docCookie, ITextBuffer textBuffer, DocumentKey documentKey, bool isCurrentContext) - { - AssertIsForeground(); - - lock (_gate) - { - NewBufferOpened_NoLock(docCookie, textBuffer, documentKey, isCurrentContext); - } - } - - private void NewBufferOpened_NoLock(uint docCookie, ITextBuffer textBuffer, DocumentKey documentKey, bool isCurrentContext) - { - if (_documentMap.TryGetValue(documentKey, out var document)) - { - document.ProcessOpen(textBuffer, isCurrentContext); - AddCookieOpenDocumentPair_NoLock(docCookie, documentKey); - } - } - - /// - /// Notifies the document provider that this document is now registered in a project. - /// If we are on a foregroud thread, then this is done right away. - /// Otherwise, we schedule a task on foreground task scheduler. - /// - public void NotifyDocumentRegisteredToProjectAndStartToRaiseEvents(IVisualStudioHostDocument document) - { - if (IsForeground()) - { - NotifyDocumentRegisteredToProjectAndStartToRaiseEvents_Core(document, cancellationToken: CancellationToken.None); - } - else - { - var cts = new CancellationTokenSource(); - var task = InvokeBelowInputPriority(() => NotifyDocumentRegisteredToProjectAndStartToRaiseEvents_Core(document, cts.Token), cts.Token); - AddPendingDocumentInitializationTask(document, task, cts); - } - } - - private void AddPendingDocumentInitializationTask(IVisualStudioHostDocument document, Task task, CancellationTokenSource cts) - { - var taskAndTokenSource = new TaskAndTokenSource() { Task = task, CancellationTokenSource = cts }; - lock (_gate) - { - // Add taskAndTokenSource to the pending document initialization tasks. - // Check for cancellation before adding as the task might already have been completed/cancelled/faulted before we reached here. - if (!cts.IsCancellationRequested && !task.IsCompleted) - { - _pendingDocumentInitializationTasks.Add(document.Id, taskAndTokenSource); - } - } + return new ShimDocument(hostProject, DocumentId.CreateNewId(hostProject.Id), filePath, sourceCodeKind); } - private void CancelPendingDocumentInitializationTasks_NoLock(IEnumerable documentKeys) + internal class ShimDocument : IVisualStudioHostDocument { - foreach (var documentKey in documentKeys) + public ShimDocument(AbstractProject hostProject, DocumentId id, string filePath, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular) { - if (_documentMap.TryGetValue(documentKey, out var document)) - { - CancelPendingDocumentInitializationTask_NoLock(document); - } + Project = hostProject; + Id = id ?? DocumentId.CreateNewId(hostProject.Id, filePath); + FilePath = filePath; + SourceCodeKind = sourceCodeKind; } - } - private void CancelPendingDocumentInitializationTask(IVisualStudioHostDocument document) - { - lock (_gate) - { - CancelPendingDocumentInitializationTask_NoLock(document); - } - } - - private void CancelPendingDocumentInitializationTask_NoLock(IVisualStudioHostDocument document) - { - // Remove pending initialization task for the document, if any, and dispose the cancellation token source. - if (_pendingDocumentInitializationTasks.TryGetValue(document.Id, out var taskAndTokenSource)) - { - taskAndTokenSource.CancellationTokenSource.Cancel(); - taskAndTokenSource.CancellationTokenSource.Dispose(); - _pendingDocumentInitializationTasks.Remove(document.Id); - } - } + public AbstractProject Project { get; } - private void NotifyDocumentRegisteredToProjectAndStartToRaiseEvents_Core(IVisualStudioHostDocument document, CancellationToken cancellationToken) - { - AssertIsForeground(); + public DocumentId Id { get; } - cancellationToken.ThrowIfCancellationRequested(); + public string FilePath { get; } - // Ignore any other unknown kinds of documents - var standardDocument = document as StandardTextDocument; - if (standardDocument == null) - { - return; - } - - // If it's already open, then we have nothing more to do here. - if (standardDocument.IsOpen) - { - return; - } - - if (_runningDocumentTable.TryGetCookieForInitializedDocument(document.Key.Moniker, out var docCookie)) - { - TryProcessOpenForDocCookie(docCookie, cancellationToken); - } - - cancellationToken.ThrowIfCancellationRequested(); - CancelPendingDocumentInitializationTask(document); - } - - private void TryProcessOpenForDocCookie(uint docCookie, CancellationToken cancellationToken) - { - AssertIsForeground(); - - lock (_gate) - { - cancellationToken.ThrowIfCancellationRequested(); - - TryProcessOpenForDocCookie_NoLock(docCookie); - } - } - - private void TryProcessOpenForDocCookie_NoLock(uint docCookie) - { - string moniker = _runningDocumentTable.GetDocumentMoniker(docCookie); - _runningDocumentTable.GetDocumentHierarchyItem(docCookie, out var hierarchy, out var itemid); - - // The cast from dynamic to object doesn't change semantics, but avoids loading the dynamic binder - // which saves us JIT time in this method. - if ((object)_runningDocumentTable.GetDocumentData(docCookie) is IVsTextBuffer shimTextBuffer) - { - var hasAssociatedRoslynDocument = false; - foreach (var project in _projectTracker.ImmutableProjects) - { - var documentKey = new DocumentKey(project, moniker); - - if (_documentMap.ContainsKey(documentKey)) - { - hasAssociatedRoslynDocument = true; - var textBuffer = _editorAdaptersFactoryService.GetDocumentBuffer(shimTextBuffer); - - // If we already have an ITextBuffer for this document, then we can open it now. - // Otherwise, setup an event handler that will do it when the buffer loads. - if (textBuffer != null) - { - // We might already have this docCookie marked as open an older document. This can happen - // if we're in the middle of a rename but this class hasn't gotten the notification yet but - // another listener for RDT events got it - if (_docCookiesToOpenDocumentKeys.ContainsKey(docCookie)) - { - CloseDocuments_NoLock(docCookie, monikerToKeep: moniker); - } - - if (hierarchy == project.Hierarchy) - { - // This is the current context - NewBufferOpened_NoLock(docCookie, textBuffer, documentKey, isCurrentContext: true); - } - else - { - // This is a non-current linked context - NewBufferOpened_NoLock(docCookie, textBuffer, documentKey, isCurrentContext: false); - } - } - else - { - TextBufferDataEventsSink.HookupHandler(shimTextBuffer, onDocumentLoadCompleted: () => OnDocumentLoadCompleted(shimTextBuffer, documentKey, moniker)); - } - } - } - - if (!hasAssociatedRoslynDocument && this._documentTrackingServiceOpt != null && !_docCookiesToNonRoslynDocumentBuffers.ContainsKey(docCookie)) - { - // Non-Roslyn document opened. - var textBuffer = _editorAdaptersFactoryService.GetDocumentBuffer(shimTextBuffer); - if (textBuffer != null) - { - OnNonRoslynBufferOpened_NoLock(textBuffer, docCookie); - } - else - { - TextBufferDataEventsSink.HookupHandler(shimTextBuffer, onDocumentLoadCompleted: () => OnDocumentLoadCompleted(shimTextBuffer, documentKeyOpt: null, moniker: moniker)); - } - } - } - else - { - // This is opening some other designer or property page. If it's tied to our IVsHierarchy, we should - // let the workspace know - foreach (var project in _projectTracker.ImmutableProjects) - { - if (hierarchy == project.Hierarchy) - { - _projectTracker.NotifyNonDocumentOpenedForProject(project); - } - } - } - } - - private void OnNonRoslynBufferOpened_NoLock(ITextBuffer textBuffer, uint docCookie) - { - AssertIsForeground(); - Contract.ThrowIfNull(textBuffer); - Contract.ThrowIfNull(_documentTrackingServiceOpt); - - this._documentTrackingServiceOpt.OnNonRoslynBufferOpened(textBuffer); - _docCookiesToNonRoslynDocumentBuffers.Add(docCookie, textBuffer); - } - - private void OnBeforeDocumentWindowShow(IVsWindowFrame frame, uint docCookie, bool firstShow) - { - AssertIsForeground(); - - var ids = GetDocumentIdsFromDocCookie(docCookie); - foreach (var id in ids) - { - this._documentTrackingServiceOpt?.DocumentFrameShowing(frame, id, firstShow); - } - } - - private IList GetDocumentIdsFromDocCookie(uint docCookie) - { - lock (_gate) - { - return GetDocumentIdsFromDocCookie_NoLock(docCookie); - } - } - - private IList GetDocumentIdsFromDocCookie_NoLock(uint docCookie) - { - AssertIsForeground(); - if (!_docCookiesToOpenDocumentKeys.TryGetValue(docCookie, out var documentKeys)) - { - return SpecializedCollections.EmptyList(); - } - - IList documentIds = new List(); - - foreach (var documentKey in documentKeys) - { - documentIds.Add(_documentMap[documentKey].Id); - } - - return documentIds; - } - - /// - /// Closes all documents that match the cookie and predicate. - /// - /// The cookie that we should close documents for. - /// The moniker that we should _not_ close documents for. When a rename is happening, - /// we might have documents with both the old and new moniker attached to the same docCookie. In those cases - /// we only want to close anything that doesn't match the new name. Can be null to close everything. - private void CloseDocuments(uint docCookie, string monikerToKeep) - { - AssertIsForeground(); - - lock (_gate) - { - CloseDocuments_NoLock(docCookie, monikerToKeep); - } - } - - private void CloseDocuments_NoLock(uint docCookie, string monikerToKeep) - { - if (!_docCookiesToOpenDocumentKeys.TryGetValue(docCookie, out var documentKeys)) - { - // Handle non-Roslyn document close. - if (this._documentTrackingServiceOpt != null && _docCookiesToNonRoslynDocumentBuffers.TryGetValue(docCookie, out ITextBuffer textBuffer)) - { - var moniker = _runningDocumentTable.GetDocumentMoniker(docCookie); - if (!StringComparer.OrdinalIgnoreCase.Equals(moniker, monikerToKeep)) - { - this._documentTrackingServiceOpt.OnNonRoslynBufferClosed(textBuffer); - _docCookiesToNonRoslynDocumentBuffers.Remove(docCookie); - } - } - - return; - } - - // We will remove from documentKeys the things we successfully closed, - // so clone the list so we can mutate while enumerating - var documentsToClose = documentKeys.Where(key => !StringComparer.OrdinalIgnoreCase.Equals(key.Moniker, monikerToKeep)).ToList(); - - // Cancel any pending scheduled tasks to register document opened for the documents we are closing. - CancelPendingDocumentInitializationTasks_NoLock(documentsToClose); - - // For a given set of open linked or shared files, we may be closing one of the - // documents (e.g. excluding a linked file from one of its owning projects or - // unloading one of the head projects for a shared project) or the entire set of - // documents (e.g. closing the tab of a shared document). If the entire set of - // documents is closing, then we should avoid the process of updating the active - // context document between the closing of individual documents in the set. In the - // case of closing the tab of a shared document, this avoids updating the shared - // item context hierarchy for the entire shared project to head project owning the - // last documentKey in this list. - var updateActiveContext = documentsToClose.Count == 1; - - foreach (var documentKey in documentsToClose) - { - var document = _documentMap[documentKey]; - document.ProcessClose(updateActiveContext); - Contract.ThrowIfFalse(documentKeys.Remove(documentKey)); - } - - // If we removed all the keys, then remove the list entirely - if (documentKeys.Count == 0) - { - _docCookiesToOpenDocumentKeys.Remove(docCookie); - } - } - - private void OnAfterAttributeChangeEx(uint docCookie, uint grfAttribs, IVsHierarchy pHierOld, uint itemidOld, string pszMkDocumentOld, IVsHierarchy pHierNew, uint itemidNew, string pszMkDocumentNew) - { - if ((grfAttribs & (uint)__VSRDTATTRIB3.RDTA_DocumentInitialized) != 0) - { - TryProcessOpenForDocCookie(docCookie, CancellationToken.None); - } - - if ((grfAttribs & (uint)__VSRDTATTRIB.RDTA_MkDocument) != 0) - { - OnDocumentMonikerChanged(docCookie, pszMkDocumentOld, pszMkDocumentNew); - } - - if ((grfAttribs & (uint)__VSRDTATTRIB.RDTA_Hierarchy) != 0) - { - bool itemidChanged = (grfAttribs & (uint)__VSRDTATTRIB.RDTA_ItemID) != 0; - OnHierarchyChanged(docCookie, pHierOld, itemidOld, pHierNew, itemidNew, itemidChanged); - } - } - - private void OnHierarchyChanged(uint docCookie, IVsHierarchy pHierOld, uint itemidOld, IVsHierarchy pHierNew, uint itemidNew, bool itemidChanged) - { - AssertIsForeground(); - - lock (_gate) - { - if (_docCookiesToOpenDocumentKeys.TryGetValue(docCookie, out var documentKeys)) - { - foreach (var documentKey in documentKeys) - { - var document = _documentMap[documentKey]; - var currDocHier = document.Project.Hierarchy; - - if (currDocHier == pHierNew) - { - documentKey.HostProject.Workspace.OnDocumentContextUpdated(document.Id, document.GetOpenTextContainer()); - } - } - } - } - } - - private void OnDocumentMonikerChanged(uint docCookie, string oldMoniker, string newMoniker) - { - AssertIsForeground(); - - // If the moniker change only involves casing differences then the project system will - // not remove & add the file again with the new name, so we should not clear any state. - // Leaving the old casing in the DocumentKey is safe because DocumentKey equality - // checks ignore the casing of the moniker. - if (oldMoniker.Equals(newMoniker, StringComparison.OrdinalIgnoreCase)) - { - return; - } - - // While we don't natively track source file renames in the Visual Studio workspace, we do - // need to track them for Code Model's FileCodeModel instances. The lifetime of a FileCodeModel - // needs to be resilient through the source file add/remove that happens during a file rename. - RenameFileCodeModelInstances(docCookie, oldMoniker, newMoniker); - - // Since our moniker changed, any old DocumentKeys are invalid. DocumentKeys are immutable, - // and HostDocuments implicitly have an immutable filename. Therefore, we choose to close - // all files associated with this docCookie, so they are no longer associated with an RDT document that - // no longer matches their filenames. This removes all tracking information and associations - // between cookies and documents in this class. - - // In the case of miscellaneous files, we're also watching the RDT. If that saw the RDT event - // before we did, it's possible we've already updated state to handle the rename. Therefore, we - // should only handle the close if the moniker we had was out of date. - CloseDocuments(docCookie, monikerToKeep: newMoniker); - - // We might also have new documents that now need to be opened, so handle them too. If the document - // isn't initialized we will wait until it's actually initialized to trigger the open; we see - // from the OnAfterAttributeChangeEx notification. - if (_runningDocumentTable.IsDocumentInitialized(docCookie)) - { - TryProcessOpenForDocCookie(docCookie, CancellationToken.None); - } - } - - private void RenameFileCodeModelInstances(uint docCookie, string oldMoniker, string newMoniker) - { - AssertIsForeground(); - - List documents; - lock (_gate) - { - if (!_docCookiesToOpenDocumentKeys.TryGetValue(docCookie, out var documentKeys)) - { - return; - } - - // We will remove from documentKeys the things we successfully closed, - // so clone the list so we can mutate while enumerating - documents = documentKeys - .Where(key => StringComparer.OrdinalIgnoreCase.Equals(key.Moniker, oldMoniker)) - .Select(key => _documentMap[key]) - .ToList(); - } - - foreach (var document in documents) - { - if (document.Project.Workspace is VisualStudioWorkspace workspace) - { - document.Project.ProjectCodeModel?.OnSourceFileRenaming(document.FilePath, newMoniker); - } - } - } - - /// - /// Called by a VisualStudioDocument when that document is disposed. - /// - /// The document to stop tracking. - private void StopTrackingDocument(StandardTextDocument document) - { - CancelPendingDocumentInitializationTask(document); - - if (IsForeground()) - { - StopTrackingDocument_Core(document); - } - else - { - InvokeBelowInputPriority(() => StopTrackingDocument_Core(document), CancellationToken.None); - } - } - - private void StopTrackingDocument_Core(StandardTextDocument document) - { - AssertIsForeground(); - - lock (_gate) - { - StopTrackingDocument_Core_NoLock(document); - } - } - - private void StopTrackingDocument_Core_NoLock(StandardTextDocument document) - { - if (document.IsOpen) - { - // TODO: This was previously faster, need a bidirectional 1-to-many map - - foreach (var cookie in _docCookiesToOpenDocumentKeys.Keys) - { - var documentKeys = _docCookiesToOpenDocumentKeys[cookie]; - - if (documentKeys.Contains(document.Key)) - { - documentKeys.Remove(document.Key); - if (documentKeys.IsEmpty()) - { - _docCookiesToOpenDocumentKeys.Remove(cookie); - } - - break; - } - } - } - - _documentMap.Remove(document.Key); - } - - private void AddCookieOpenDocumentPair_NoLock(uint foundCookie, DocumentKey documentKey) - { - if (_docCookiesToOpenDocumentKeys.TryGetValue(foundCookie, out var documentKeys)) - { - if (!documentKeys.Contains(documentKey)) - { - documentKeys.Add(documentKey); - } - } - else - { - _docCookiesToOpenDocumentKeys.Add(foundCookie, new List { documentKey }); - } - } - - private void OnDocumentLoadCompleted(IVsTextBuffer shimTextBuffer, DocumentKey documentKeyOpt, string moniker) - { - AssertIsForeground(); - // This is called when IVsTextBufferDataEvents.OnLoadComplete() has been triggered for a - // newly-created buffer. - if (!_runningDocumentTable.TryGetCookieForInitializedDocument(moniker, out var docCookie)) - { - return; - } - - var textBuffer = _editorAdaptersFactoryService.GetDocumentBuffer(shimTextBuffer); - if (textBuffer == null) - { - throw new InvalidOperationException("The IVsTextBuffer has been populated but the underlying ITextBuffer does not exist!"); - } - - if (documentKeyOpt == null) - { - // Non-Roslyn document. - OnNonRoslynBufferOpened_NoLock(textBuffer, docCookie); - } - else - { - NewBufferOpened(docCookie, textBuffer, documentKeyOpt, IsCurrentContext(documentKeyOpt)); - } - } - - private bool IsCurrentContext(DocumentKey documentKey) - { - AssertIsForeground(); - var document = documentKey.HostProject.GetCurrentDocumentFromPath(documentKey.Moniker); - return document != null && _linkedFileUtilities.IsCurrentContextHierarchy(document, _runningDocumentTable); - } - - public IDisposable ProvideDocumentIdHint(string filePath, DocumentId documentId) - { - lock (_gate) - { - _documentIdHints[filePath] = documentId; - return new DocumentIdHint(this, filePath); - } - } - - /// - /// A small IDisposable object that's returned from ProvideDocumentIdHint. - /// - private class DocumentIdHint : IDisposable - { - private readonly DocumentProvider _documentProvider; - private readonly string _filePath; - - public DocumentIdHint(DocumentProvider documentProvider, string filePath) - { - _documentProvider = documentProvider; - _filePath = filePath; - } - - public void Dispose() - { - lock (_documentProvider._gate) - { - _documentProvider._documentIdHints.Remove(_filePath); - } - } - } - - private struct TaskAndTokenSource - { - public Task Task { get; set; } - public CancellationTokenSource CancellationTokenSource { get; set; } + public SourceCodeKind SourceCodeKind { get; } } } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Extensions/IVsHierarchyExtensions.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Extensions/IVsHierarchyExtensions.cs index c8ff0e5fe14a0abeba2e4b866189bf26ccba9e50..e3789c5c1034d038f608b755fd8e1688b781c13a 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Extensions/IVsHierarchyExtensions.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Extensions/IVsHierarchyExtensions.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Immutable; +using System.Runtime.InteropServices; using Microsoft.VisualStudio.Shell.Interop; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem @@ -93,5 +94,11 @@ public static uint TryGetItemId(this IVsHierarchy hierarchy, string moniker) return VSConstants.VSITEMID_NIL; } + + public static string GetProjectFilePath(this IVsHierarchy hierarchy) + { + Marshal.ThrowExceptionForHR(((IVsProject3)hierarchy).GetMkDocument((uint)VSConstants.VSITEMID.Root, out var projectFilePath)); + return projectFilePath; + } } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Extensions/IVsRunningDocumentTableExtensions.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Extensions/IVsRunningDocumentTableExtensions.cs index 2309c3fa4f2f4a2b60aef1aed1089536c452fa2c..380cba77d1353f0c020914feba09b93f45cc0b23 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Extensions/IVsRunningDocumentTableExtensions.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Extensions/IVsRunningDocumentTableExtensions.cs @@ -7,24 +7,6 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem { internal static class IVsRunningDocumentTableExtensions { - public static bool TryGetCookieForInitializedDocument(this IVsRunningDocumentTable4 runningDocTable, string moniker, out uint docCookie) - { - docCookie = VSConstants.VSCOOKIE_NIL; - - if (runningDocTable != null && runningDocTable.IsMonikerValid(moniker)) - { - var foundDocCookie = runningDocTable.GetDocumentCookie(moniker); - - if (runningDocTable.IsDocumentInitialized(foundDocCookie)) - { - docCookie = foundDocCookie; - return true; - } - } - - return false; - } - public static bool IsDocumentInitialized(this IVsRunningDocumentTable4 runningDocTable, uint docCookie) { var flags = runningDocTable.GetDocumentFlags(docCookie); diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/FileChangeWatcher.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/FileChangeWatcher.cs new file mode 100644 index 0000000000000000000000000000000000000000..2c7be7d4cccaffbfa2803322ff48251e9de4677c --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/FileChangeWatcher.cs @@ -0,0 +1,274 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Shell.Interop; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +{ + /// + /// A service that wraps the Visual Studio file watching APIs to make them more convenient for use. With this, a consumer can create + /// an which lets you add/remove files being watched, and an event is raised when a file is modified. + /// + internal sealed class FileChangeWatcher + { + /// + /// Gate that is used to guard modifications to . + /// + private readonly object _taskQueueGate = new object(); + + /// + /// We create a queue of tasks against the IVsFileChangeEx service for two reasons. First, we are obtaining the service asynchronously, and don't want to + /// block on it being available, so anybody who wants to do anything must wait for it. Secondly, the service itself is single-threaded; in the past + /// we've blocked up a bunch of threads all trying to use it at once. If the latter ever changes, we probably want to reconsider the implementation of this. + /// For performance and correctness reasons, NOTHING should ever do a block on this; figure out how to do your work without a block and add any work to + /// the end of the queue. + /// + private Task _taskQueue; + private readonly static Func, object, IVsFileChangeEx> _executeActionDelegate = + (precedingTask, state) => { ((Action)state)(precedingTask.Result); return precedingTask.Result; }; + + public FileChangeWatcher(Task fileChangeService) + { + _taskQueue = fileChangeService; + } + + private void EnqueueWork(Action action) + { + lock (_taskQueueGate) + { + _taskQueue = _taskQueue.ContinueWith( + _executeActionDelegate, + action, + CancellationToken.None, + TaskContinuationOptions.None, + TaskScheduler.Default); + } + } + + // TODO: remove this when there is a mechanism for a caller of EnqueueWatchingFile + // to explicitly wait on that being complete. + public void WaitForQueue_TestOnly() + { + Task queue; + + lock (_taskQueueGate) + { + queue = _taskQueue; + } + + queue.Wait(); + } + + public IContext CreateContext() + { + return new Context(this, null); + } + + /// + /// Creates an that watches all files in a directory, in addition to any files explicitly requested by . + /// + public IContext CreateContextForDirectory(string directoryFilePath) + { + if (directoryFilePath == null) + { + throw new ArgumentNullException(nameof(directoryFilePath)); + } + + return new Context(this, directoryFilePath); + } + + /// + /// A context that is watching one or more files. + /// + /// This is only implemented today by but we don't want to leak implementation details out. + public interface IContext : IDisposable + { + /// + /// Raised when a file has been changed. This may be a file watched explicitly by or it could be any + /// file in the directory if the was watching a directory. + /// + event EventHandler FileChanged; + + /// + /// Starts watching a file but doesn't wait for the file watcher to be registered with the operating system. Good if you know + /// you'll need a file watched (eventually) but it's not worth blocking yet. + /// + IFileWatchingToken EnqueueWatchingFile(string filePath); + void StopWatchingFile(IFileWatchingToken token); + } + + /// + /// A marker interface for tokens returned from . This is just to ensure type safety and avoid + /// leaking the full surface area of the nested types. + /// + public interface IFileWatchingToken + { + } + + private sealed class Context : IVsFreeThreadedFileChangeEvents, IContext + { + private readonly FileChangeWatcher _fileChangeWatcher; + private readonly string _directoryFilePathOpt; + private readonly IFileWatchingToken _noOpFileWatchingToken; + + /// + /// Gate to guard mutable fields in this class and any mutation of any s. + /// + private readonly object _gate = new object(); + private bool _disposed = false; + private readonly HashSet _activeFileWatchingTokens = new HashSet(); + private uint _directoryWatchCookie; + + public Context(FileChangeWatcher fileChangeWatcher, string directoryFilePath) + { + _fileChangeWatcher = fileChangeWatcher; + _noOpFileWatchingToken = new FileWatchingToken(); + + if (directoryFilePath != null) + { + if (!directoryFilePath.EndsWith("\\")) + { + directoryFilePath = directoryFilePath + "\\"; + } + + _directoryFilePathOpt = directoryFilePath; + + _fileChangeWatcher.EnqueueWork( + service => { ErrorHandler.ThrowOnFailure(service.AdviseDirChange(_directoryFilePathOpt, fWatchSubDir: 1, this, out _directoryWatchCookie)); }); + } + } + + public void Dispose() + { + lock (_gate) + { + if (_disposed) + { + return; + } + + _disposed = true; + } + + _fileChangeWatcher.EnqueueWork( + service => + { + // Since we put all of our work in a queue, we know that if we had tried to advise file or directory changes, + // it must have happened before now + if (_directoryFilePathOpt != null) + { + ErrorHandler.ThrowOnFailure(service.UnadviseDirChange(_directoryWatchCookie)); + } + + foreach (var token in _activeFileWatchingTokens) + { + ErrorHandler.ThrowOnFailure(service.UnadviseFileChange(token.Cookie.Value)); + } + }); + } + + public IFileWatchingToken EnqueueWatchingFile(string filePath) + { + // If we already have this file under our path, we don't have to do additional watching + if (_directoryFilePathOpt != null && filePath.StartsWith(_directoryFilePathOpt)) + { + return _noOpFileWatchingToken; + } + + var token = new FileWatchingToken(); + + lock (_gate) + { + _activeFileWatchingTokens.Add(token); + } + + _fileChangeWatcher.EnqueueWork(service => + { + uint cookie; + ErrorHandler.ThrowOnFailure(service.AdviseFileChange(filePath, (uint)(_VSFILECHANGEFLAGS.VSFILECHG_Size | _VSFILECHANGEFLAGS.VSFILECHG_Time), this, out cookie)); + + lock (_gate) + { + token.Cookie = cookie; + } + }); + + return token; + } + + public void StopWatchingFile(IFileWatchingToken token) + { + var typedToken = token as FileWatchingToken; + + Contract.ThrowIfNull(typedToken, "The token passed did not originate from this service."); + + if (typedToken == _noOpFileWatchingToken) + { + // This file never required a direct file watch, our main subscription covered it. + return; + } + + lock (_gate) + { + Contract.ThrowIfFalse(_activeFileWatchingTokens.Remove(typedToken), "This token was no longer being watched."); + } + + _fileChangeWatcher.EnqueueWork(service => + ErrorHandler.ThrowOnFailure(service.UnadviseFileChange(typedToken.Cookie.Value))); + } + + public event EventHandler FileChanged; + + int IVsFreeThreadedFileChangeEvents.FilesChanged(uint cChanges, string[] rgpszFile, uint[] rggrfChange) + { + for (int i = 0; i < cChanges; i++) + { + FileChanged?.Invoke(this, rgpszFile[i]); + } + + return VSConstants.S_OK; + } + + int IVsFreeThreadedFileChangeEvents.DirectoryChanged(string pszDirectory) + { + return VSConstants.E_NOTIMPL; + } + + int IVsFreeThreadedFileChangeEvents.DirectoryChangedEx(string pszDirectory, string pszFile) + { + FileChanged?.Invoke(this, pszFile); + + return VSConstants.S_OK; + } + + int IVsFileChangeEvents.FilesChanged(uint cChanges, string[] rgpszFile, uint[] rggrfChange) + { + for (int i = 0; i < cChanges; i++) + { + FileChanged?.Invoke(this, rgpszFile[i]); + } + + return VSConstants.S_OK; + } + + int IVsFileChangeEvents.DirectoryChanged(string pszDirectory) + { + return VSConstants.E_NOTIMPL; + } + + public class FileWatchingToken : IFileWatchingToken + { + /// + /// The cookie we have for requesting a watch on this file. Any files that didn't need + /// to be watched specifically are equal to , so + /// any other instance is something that should be watched. Null means we either haven't + /// done the subscription (and it's still in the queue) or we had some sort of error + /// subscribing in the first place. + /// + public uint? Cookie; + } + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/FileChangeWatcherProvider.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/FileChangeWatcherProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..1089d0274a48152e04bcd83e73652b25ae9107cc --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/FileChangeWatcherProvider.cs @@ -0,0 +1,26 @@ +using System; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Shell.Interop; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +{ + [Export(typeof(FileChangeWatcherProvider))] + internal sealed class FileChangeWatcherProvider + { + private readonly TaskCompletionSource _fileChangeService = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + private readonly Lazy _fileChangeWatcher; + + public FileChangeWatcherProvider() + { + _fileChangeWatcher = new Lazy(() => new FileChangeWatcher(_fileChangeService.Task)); + } + + public FileChangeWatcher Watcher => _fileChangeWatcher.Value; + + internal void SetFileChangeService(IVsFileChangeEx fileChangeService) + { + _fileChangeService.TrySetResult(fileChangeService); + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/HierarchyEventsSink.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/HierarchyEventsSink.cs deleted file mode 100644 index 33399793a8ac73227a0feaaf021e35e05ac18225..0000000000000000000000000000000000000000 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/HierarchyEventsSink.cs +++ /dev/null @@ -1,61 +0,0 @@ -// 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 System; -using System.Runtime.InteropServices; -using Microsoft.CodeAnalysis; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Shell.Interop; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -{ - internal sealed class HierarchyEventsSink : IVsHierarchyEvents - { - private readonly DocumentId _documentId; - private readonly IVsHierarchy _sharedHierarchy; - private readonly VisualStudioWorkspaceImpl _workspace; - - public HierarchyEventsSink(VisualStudioWorkspaceImpl visualStudioWorkspace, IVsHierarchy sharedHierarchy, DocumentId documentId) - { - _workspace = visualStudioWorkspace; - _sharedHierarchy = sharedHierarchy; - _documentId = documentId; - } - - public int OnPropertyChanged(uint itemid, int propid, uint flags) - { - if (propid == (int)__VSHPROPID7.VSHPROPID_SharedItemContextHierarchy || - propid == (int)__VSHPROPID8.VSHPROPID_ActiveIntellisenseProjectContext) - { - _workspace.UpdateDocumentContextIfContainsDocument(_sharedHierarchy, _documentId); - return VSConstants.S_OK; - } - - return VSConstants.DISP_E_MEMBERNOTFOUND; - } - - public int OnInvalidateIcon(IntPtr hicon) - { - return VSConstants.E_NOTIMPL; - } - - public int OnInvalidateItems([ComAliasName("Microsoft.VisualStudio.Shell.Interop.VSITEMID")]uint itemidParent) - { - return VSConstants.E_NOTIMPL; - } - - public int OnItemAdded([ComAliasName("Microsoft.VisualStudio.Shell.Interop.VSITEMID")]uint itemidParent, [ComAliasName("Microsoft.VisualStudio.Shell.Interop.VSITEMID")]uint itemidSiblingPrev, [ComAliasName("Microsoft.VisualStudio.Shell.Interop.VSITEMID")]uint itemidAdded) - { - return VSConstants.E_NOTIMPL; - } - - public int OnItemDeleted([ComAliasName("Microsoft.VisualStudio.Shell.Interop.VSITEMID")]uint itemid) - { - return VSConstants.E_NOTIMPL; - } - - public int OnItemsAppended([ComAliasName("Microsoft.VisualStudio.Shell.Interop.VSITEMID")]uint itemidParent) - { - return VSConstants.E_NOTIMPL; - } - } -} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/IVisualStudioHostDocument.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/IVisualStudioHostDocument.cs index 7689adb7c402949212a3a333897d1624d53de270..1897016e4d26dee3e9cbcedc461009e250128d38 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/IVisualStudioHostDocument.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/IVisualStudioHostDocument.cs @@ -10,111 +10,12 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem { - /// - /// Represents a source document that comes from the used in Visual Studio. - /// - /// - /// It guarantees the existence of a Dispose method, which allows the workspace/project system layer to clean up file system watchers for this - /// document when they are no longer needed. - /// - internal interface IVisualStudioHostDocument : IDisposable + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] + internal interface IVisualStudioHostDocument { - /// - /// The visual studio project this document is part of. - /// - AbstractProject Project { get; } - - /// - /// The Visual Studio identity of the document within its project. - /// - DocumentKey Key { get; } - /// /// The workspace document Id for this document. /// DocumentId Id { get; } - - /// - /// The path to the document's file on disk. - /// - string FilePath { get; } - - /// - /// The name of the document. - /// - string Name { get; } - - /// - /// The logical folders associated with the document. This may be different than the actual folders - /// in the file path. - /// - IReadOnlyList Folders { get; } - - /// - /// A loader that can access the current stored text of the document. - /// - TextLoader Loader { get; } - - /// - /// Returns true if the document is currently open in an editor. - /// - bool IsOpen { get; } - - /// - /// Fired after the file is updated on disk. If the file is open in the editor, this event is not fired. - /// - event EventHandler UpdatedOnDisk; - - /// - /// Fired after the document has been opened in Visual Studio. GetTextBuffer() will return the actual live - /// editor. - /// - event EventHandler Opened; - - /// - /// Fired as the document is being closed in Visual Studio. GetTextBuffer() still returns the editor that was - /// live in Visual Studio, but is going away shortly. - /// - event EventHandler Closing; - - /// - /// Returns and IDocumentInfo with the initial state of this document when it was first loaded. - /// - /// - DocumentInfo GetInitialState(); - - /// - /// The ItemID for this document. This method must be called on the UI thread, and the - /// returned value must be used while still on the UI thread, or must be appropriately - /// invalidated when the relevant are triggered. - /// Otherwise, this ItemId may be stale or destroyed within its - /// before this document is removed from its project. These are only really useful for - /// "normal" files, that is regular .cs files that are compiled in a normal project. - /// It may be in the case of files that have very - /// recently been removed or that are in miscellaneous files projects, or it may not even - /// be stable in the case of strange files like .g.i.cs files. - /// - uint GetItemId(); - - /// - /// Gets the text container associated with the document when it is in an opened state. - /// - /// - SourceTextContainer GetOpenTextContainer(); - - /// - /// Gets the text buffer associated with the document when it is in an opened state. - /// - ITextBuffer GetOpenTextBuffer(); - - /// - /// Updates the text of the document. - /// - void UpdateText(SourceText newText); - - /// - /// Fetches the that should be used to undo edits to this document. - /// - ITextBuffer GetTextUndoHistoryBuffer(); } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/IVisualStudioHostProject.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/IVisualStudioHostProject.cs index b0d7ab0a503793da86d8c6543df97dc9fab750cb..ea634bdf2269c4d0938862c56f0e5b5bf54509ff 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/IVisualStudioHostProject.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/IVisualStudioHostProject.cs @@ -5,10 +5,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem { - /// - /// This interface only exists to maintain an overload of . - /// - [Obsolete("This overload is a compatibility shim for TypeScript; please do not use it.")] + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] internal interface IVisualStudioHostProject { ProjectId Id { get; } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/InvisibleEditor.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/InvisibleEditor.cs index deaf7be84b9642c22380c6c78d7bce080b9e42d4..c429638179986809bc835ad3b421872a1c1caebe 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/InvisibleEditor.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/InvisibleEditor.cs @@ -35,14 +35,14 @@ internal partial class InvisibleEditor : IInvisibleEditor /// , which performs a much slower query of all /// projects in the solution. /// - public InvisibleEditor(IServiceProvider serviceProvider, string filePath, AbstractProject projectOpt, bool needsSave, bool needsUndoDisabled) + public InvisibleEditor(IServiceProvider serviceProvider, string filePath, IVsHierarchy hierarchyOpt, bool needsSave, bool needsUndoDisabled) { _serviceProvider = serviceProvider; _filePath = filePath; _needsSave = needsSave; var invisibleEditorManager = (IIntPtrReturningVsInvisibleEditorManager)serviceProvider.GetService(typeof(SVsInvisibleEditorManager)); - var vsProject = TryGetProjectOfHierarchy(projectOpt?.Hierarchy); + var vsProject = TryGetProjectOfHierarchy(hierarchyOpt); var invisibleEditorPtr = IntPtr.Zero; Marshal.ThrowExceptionForHR(invisibleEditorManager.RegisterInvisibleEditor(filePath, vsProject, 0, null, out invisibleEditorPtr)); diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject.cs index fb6f7cdf2a762442ebdc7a26dce2a2beb2e97003..4357a46d36ef27f4d1efdde9cb4ad3685d16095c 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject.cs @@ -1,135 +1,189 @@ // 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 System; +using System.Collections.Generic; using System.Collections.Immutable; using System.IO; +using System.Linq; +using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host; +using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel; +using Microsoft.VisualStudio.LanguageServices.Implementation.EditAndContinue; using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; +using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.TextManager.Interop; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Legacy { /// /// Base type for legacy C# and VB project system shim implementations. - /// These legachy shims are based on legacy project system interfaces defined in csproj/msvbprj. + /// These legacy shims are based on legacy project system interfaces defined in csproj/msvbprj. /// - internal abstract partial class AbstractLegacyProject : AbstractProject + internal abstract partial class AbstractLegacyProject : ForegroundThreadAffinitizedObject { + public IVsHierarchy Hierarchy { get; } + protected VisualStudioProject VisualStudioProject { get; } + internal VisualStudioProjectOptionsProcessor VisualStudioProjectOptionsProcessor { get; set; } + protected IProjectCodeModel ProjectCodeModel { get; set; } + protected VisualStudioWorkspace Workspace { get; } + + #region Mutable fields that should only be used from the UI thread + + private readonly VsENCRebuildableProjectImpl _editAndContinueProject; + + private readonly SolutionEventsBatchScopeCreator _batchScopeCreator; + + #endregion + public AbstractLegacyProject( - VisualStudioProjectTracker projectTracker, - Func reportExternalErrorCreatorOpt, string projectSystemName, IVsHierarchy hierarchy, string language, IServiceProvider serviceProvider, - VisualStudioWorkspaceImpl visualStudioWorkspaceOpt, + IThreadingContext threadingContext, + string externalErrorReportingPrefix, HostDiagnosticUpdateSource hostDiagnosticUpdateSourceOpt, - ICommandLineParserService commandLineParserServiceOpt = null) - : base(projectTracker, - reportExternalErrorCreatorOpt, - projectSystemName, - projectFilePath: GetProjectFilePath(hierarchy), - hierarchy: hierarchy, - projectGuid: GetProjectIDGuid(hierarchy), - language: language, - serviceProvider: serviceProvider, - visualStudioWorkspaceOpt: visualStudioWorkspaceOpt, - hostDiagnosticUpdateSourceOpt: hostDiagnosticUpdateSourceOpt, - commandLineParserServiceOpt: commandLineParserServiceOpt) + ICommandLineParserService commandLineParserServiceOpt) + : base(threadingContext) { - if (Hierarchy != null) + Contract.ThrowIfNull(hierarchy); + + var componentModel = (IComponentModel)serviceProvider.GetService(typeof(SComponentModel)); + Workspace = componentModel.GetService(); + + var projectFilePath = hierarchy.GetProjectFilePath(); + + if (projectFilePath != null && !File.Exists(projectFilePath)) + { + projectFilePath = null; + } + + var projectFactory = componentModel.GetService(); + VisualStudioProject = projectFactory.CreateAndAddToWorkspace( + projectSystemName, + language, + new VisualStudioProjectCreationInfo + { + // The workspace requires an assembly name so we can make compilations. We'll use + // projectSystemName because they'll have a better one eventually. + AssemblyName = projectSystemName, + FilePath = projectFilePath, + Hierarchy = hierarchy, + ProjectGuid = GetProjectIDGuid(hierarchy), + }); + + Hierarchy = hierarchy; + ConnectHierarchyEvents(); + RefreshBinOutputPath(); + + // TODO: remove this terrible hack, which is working around shims throwing in not-good ways + try + { + _externalErrorReporter = new ProjectExternalErrorReporter(VisualStudioProject.Id, externalErrorReportingPrefix, serviceProvider); + _editAndContinueProject = new VsENCRebuildableProjectImpl(Workspace, VisualStudioProject, serviceProvider); + } + catch (Exception) { - ConnectHierarchyEvents(); - this.IsWebSite = GetIsWebsiteProject(Hierarchy); } - // Initialize command line arguments. - base.SetArguments(commandLine: string.Empty); + _batchScopeCreator = componentModel.GetService(); + _batchScopeCreator.StartTrackingProject(VisualStudioProject, Hierarchy); } - protected LinkedFileUtilities LinkedFileUtilities - => ProjectTracker.LinkedFileUtilities; + public string AssemblyName => VisualStudioProject.AssemblyName; - public override void Disconnect() + public virtual void Disconnect() { - base.Disconnect(); + _batchScopeCreator.StopTrackingProject(VisualStudioProject); + + VisualStudioProjectOptionsProcessor?.Dispose(); + ProjectCodeModel.OnProjectClosed(); + VisualStudioProject.RemoveFromWorkspace(); // Unsubscribe IVsHierarchyEvents DisconnectHierarchyEvents(); } - protected void AddFile(string filename, SourceCodeKind sourceCodeKind) + protected void AddFile( + string filename, + SourceCodeKind sourceCodeKind) { - bool getIsCurrentContext(IVisualStudioHostDocument document) => LinkedFileUtilities.IsCurrentContextHierarchy(document, RunningDocumentTable); - var itemid = Hierarchy?.TryGetItemId(filename) ?? VSConstants.VSITEMID_NIL; + AssertIsForeground(); - var folderNames = ImmutableArray.Empty; + // We have tests that assert that XOML files should not get added; this was similar + // behavior to how ASP.NET projects would add .aspx files even though we ultimately ignored + // them. XOML support is planned to go away for Dev16, but for now leave the logic there. + if (filename.EndsWith(".xoml")) + { + return; + } + + ImmutableArray folders = default; + var itemid = Hierarchy.TryGetItemId(filename); if (itemid != VSConstants.VSITEMID_NIL) { - folderNames = GetFolderNamesFromHierarchy(itemid); + folders = GetFolderNamesForDocument(itemid); } - AddFile(filename, sourceCodeKind, getIsCurrentContext, folderNames); + VisualStudioProject.AddSourceFile(filename, sourceCodeKind, folders); } - protected void SetOutputPathAndRelatedData(string objOutputPath) + protected void RemoveFile(string filename) { - // Update the objOutputPath and related data. - SetObjOutputPathAndRelatedData(objOutputPath); - // Also fetch and update the new binOutputPath. - if (TryGetOutputPathFromHierarchy(this.Hierarchy, this.ContainingDirectoryPathOpt, out var binOutputPath)) + AssertIsForeground(); + + // We have tests that assert that XOML files should not get added; this was similar + // behavior to how ASP.NET projects would add .aspx files even though we ultimately ignored + // them. XOML support is planned to go away for Dev16, but for now leave the logic there. + if (filename.EndsWith(".xoml")) { - SetBinOutputPathAndRelatedData(binOutputPath); + return; } + + VisualStudioProject.RemoveSourceFile(filename); } - private static bool TryGetOutputPathFromHierarchy(IVsHierarchy hierarchy, string containingDirectoryPathOpt, out string binOutputPath) + private void RefreshBinOutputPath() { - binOutputPath = null; - var storage = hierarchy as IVsBuildPropertyStorage; + var storage = Hierarchy as IVsBuildPropertyStorage; if (storage == null) { - return false; + return; } if (ErrorHandler.Failed(storage.GetPropertyValue("OutDir", null, (uint)_PersistStorageType.PST_PROJECT_FILE, out var outputDirectory)) || ErrorHandler.Failed(storage.GetPropertyValue("TargetFileName", null, (uint)_PersistStorageType.PST_PROJECT_FILE, out var targetFileName))) { - return false; + return; + } + + if (targetFileName == null) + { + return; } // web app case if (!PathUtilities.IsAbsolute(outputDirectory)) { - if (containingDirectoryPathOpt == null) + if (VisualStudioProject.FilePath == null) { - return false; + return; } - outputDirectory = FileUtilities.ResolveRelativePath(outputDirectory, containingDirectoryPathOpt); + outputDirectory = FileUtilities.ResolveRelativePath(outputDirectory, Path.GetDirectoryName(VisualStudioProject.FilePath)); } - if (outputDirectory == null || targetFileName == null) + if (outputDirectory == null) { - return false; + return; } - binOutputPath = FileUtilities.NormalizeAbsolutePath(Path.Combine(outputDirectory, targetFileName)); - return true; - } - - private static string GetProjectDisplayName(IVsHierarchy hierarchy) - { - return hierarchy.TryGetName(out var name) ? name : null; - } - - internal static string GetProjectFilePath(IVsHierarchy hierarchy) - { - return ErrorHandler.Succeeded(((IVsProject3)hierarchy).GetMkDocument((uint)VSConstants.VSITEMID.Root, out var filePath)) ? filePath : null; + VisualStudioProject.OutputFilePath = FileUtilities.NormalizeAbsolutePath(Path.Combine(outputDirectory, targetFileName)); } private static Guid GetProjectIDGuid(IVsHierarchy hierarchy) @@ -157,5 +211,96 @@ private static bool GetIsWebsiteProject(IVsHierarchy hierarchy) return false; } + + /// + /// Map of folder item IDs in the workspace to the string version of their path. + /// + /// Using item IDs as a key like this in a long-lived way is considered unsupported by CPS and other + /// IVsHierarchy providers, but this code (which is fairly old) still makes the assumptions anyways. + private readonly Dictionary> _folderNameMap = new Dictionary>(); + + private ImmutableArray GetFolderNamesForDocument(uint documentItemID) + { + AssertIsForeground(); + + if (documentItemID != (uint)VSConstants.VSITEMID.Nil && Hierarchy.GetProperty(documentItemID, (int)VsHierarchyPropID.Parent, out var parentObj) == VSConstants.S_OK) + { + var parentID = UnboxVSItemId(parentObj); + if (parentID != (uint)VSConstants.VSITEMID.Nil && parentID != (uint)VSConstants.VSITEMID.Root) + { + return GetFolderNamesForFolder(parentID); + } + } + + return ImmutableArray.Empty; + } + + private ImmutableArray GetFolderNamesForFolder(uint folderItemID) + { + AssertIsForeground(); + + using (var pooledObject = SharedPools.Default>().GetPooledObject()) + { + var newFolderNames = pooledObject.Object; + ImmutableArray folderNames; + + if (!_folderNameMap.TryGetValue(folderItemID, out folderNames)) + { + ComputeFolderNames(folderItemID, newFolderNames, Hierarchy); + folderNames = newFolderNames.ToImmutableArray(); + _folderNameMap.Add(folderItemID, folderNames); + } + else + { + // verify names, and change map if we get a different set. + // this is necessary because we only get document adds/removes from the project system + // when a document name or folder name changes. + ComputeFolderNames(folderItemID, newFolderNames, Hierarchy); + if (!Enumerable.SequenceEqual(folderNames, newFolderNames)) + { + folderNames = newFolderNames.ToImmutableArray(); + _folderNameMap[folderItemID] = folderNames; + } + } + + return folderNames; + } + } + + // Different hierarchies are inconsistent on whether they return ints or uints for VSItemIds. + // Technically it should be a uint. However, there's no enforcement of this, and marshalling + // from native to managed can end up resulting in boxed ints instead. Handle both here so + // we're resilient to however the IVsHierarchy was actually implemented. + private static uint UnboxVSItemId(object id) + { + return id is uint ? (uint)id : unchecked((uint)(int)id); + } + + private static void ComputeFolderNames(uint folderItemID, List names, IVsHierarchy hierarchy) + { + if (hierarchy.GetProperty((uint)folderItemID, (int)VsHierarchyPropID.Name, out var nameObj) == VSConstants.S_OK) + { + // For 'Shared' projects, IVSHierarchy returns a hierarchy item with < character in its name (i.e. ) + // as a child of the root item. There is no such item in the 'visual' hierarchy in solution explorer and no such folder + // is present on disk either. Since this is not a real 'folder', we exclude it from the contents of Document.Folders. + // Note: The parent of the hierarchy item that contains < character in its name is VSITEMID.Root. So we don't need to + // worry about accidental propagation out of the Shared project to any containing 'Solution' folders - the check for + // VSITEMID.Root below already takes care of that. + var name = (string)nameObj; + if (!name.StartsWith("<", StringComparison.OrdinalIgnoreCase)) + { + names.Insert(0, name); + } + } + + if (hierarchy.GetProperty((uint)folderItemID, (int)VsHierarchyPropID.Parent, out var parentObj) == VSConstants.S_OK) + { + var parentID = UnboxVSItemId(parentObj); + if (parentID != (uint)VSConstants.VSITEMID.Nil && parentID != (uint)VSConstants.VSITEMID.Root) + { + ComputeFolderNames(parentID, names, hierarchy); + } + } + } } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_IAnalyzerHost.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_IAnalyzerHost.cs index b8da074ee85cd3c283506e2d22fabc78654cf648..2c60151ed8fe2fa74ba917102a472c1484019e5b 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_IAnalyzerHost.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_IAnalyzerHost.cs @@ -1,14 +1,48 @@ // 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 System; +using System.IO; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Interop; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Legacy { - internal abstract partial class AbstractLegacyProject : AbstractProject, IAnalyzerHost + internal abstract partial class AbstractLegacyProject : IAnalyzerHost { - public void AddAdditionalFile(string additionalFilePath) + void IAnalyzerHost.AddAnalyzerReference(string analyzerAssemblyFullPath) { - AddAdditionalFile(additionalFilePath, getIsInCurrentContext: document => LinkedFileUtilities.IsCurrentContextHierarchy(document, RunningDocumentTable)); + VisualStudioProject.AddAnalyzerReference(analyzerAssemblyFullPath); + } + + void IAnalyzerHost.RemoveAnalyzerReference(string analyzerAssemblyFullPath) + { + VisualStudioProject.RemoveAnalyzerReference(analyzerAssemblyFullPath); + } + + void IAnalyzerHost.SetRuleSetFile(string ruleSetFileFullPath) + { + // Sometimes the project system hands us paths with extra backslashes + // and passing that to other parts of the shell causes issues + // http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1087250 + if (!string.IsNullOrEmpty(ruleSetFileFullPath)) + { + ruleSetFileFullPath = Path.GetFullPath(ruleSetFileFullPath); + } + else + { + ruleSetFileFullPath = null; + } + + VisualStudioProjectOptionsProcessor.ExplicitRuleSetFilePath = ruleSetFileFullPath; + } + + void IAnalyzerHost.AddAdditionalFile(string additionalFilePath) + { + VisualStudioProject.AddAdditionalFile(additionalFilePath); + } + + void IAnalyzerHost.RemoveAdditionalFile(string additionalFilePath) + { + VisualStudioProject.RemoveAdditionalFile(additionalFilePath); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_ICompilerOptionsHostObject.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_ICompilerOptionsHostObject.cs index 031bc306094a853c67c633e58a51d54c27a662c2..fd91c512047fe86e6cabded6eba4aa721f6f9419 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_ICompilerOptionsHostObject.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_ICompilerOptionsHostObject.cs @@ -8,7 +8,7 @@ internal partial class AbstractLegacyProject : ICompilerOptionsHostObject { int ICompilerOptionsHostObject.SetCompilerOptions(string compilerOptions, out bool supported) { - SetArgumentsAndUpdateOptions(compilerOptions); + VisualStudioProjectOptionsProcessor.CommandLine = compilerOptions; supported = true; return VSConstants.S_OK; } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_IIntellisenseBuildTarget.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_IIntellisenseBuildTarget.cs index 8cebf6787ac822ede71c898efcc2e2e605306cc1..9fdc85c448a0ba976529cb3808af97738c07ab9d 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_IIntellisenseBuildTarget.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_IIntellisenseBuildTarget.cs @@ -16,13 +16,14 @@ internal partial class AbstractLegacyProject : IIntellisenseBuildTarget void IIntellisenseBuildTarget.SetIntellisenseBuildResult(bool succeeded, string reason) { - SetIntellisenseBuildResultAndNotifyWorkspace(succeeded); +// SetIntellisenseBuildResultAndNotifyWorkspace(succeeded); UpdateIntellisenseBuildFailureDiagnostic(succeeded, reason); } private void UpdateIntellisenseBuildFailureDiagnostic(bool succeeded, string reason) { + /* if (!succeeded) { // report intellisense build failure to error list @@ -34,6 +35,7 @@ private void UpdateIntellisenseBuildFailureDiagnostic(bool succeeded, string rea // clear intellisense build failure diagnostic from error list. this.HostDiagnosticUpdateSource?.ClearDiagnosticsForProject(Id, s_diagnosticKey); } + */ } private DiagnosticData CreateIntellisenseBuildFailureDiagnostic(string reason) @@ -50,7 +52,7 @@ private DiagnosticData CreateIntellisenseBuildFailureDiagnostic(string reason) isEnabledByDefault: true, warningLevel: 0, workspace: Workspace, - projectId: Id, + projectId: VisualStudioProject.Id, title: ServicesVSResources.Project_loading_failed, description: GetDescription(reason), helpLink: "http://go.microsoft.com/fwlink/p/?LinkID=734719"); @@ -58,7 +60,7 @@ private DiagnosticData CreateIntellisenseBuildFailureDiagnostic(string reason) private string GetDescription(string reason) { - var logFilePath = $"{Path.GetTempPath()}\\{Path.GetFileNameWithoutExtension(this.ProjectFilePath)}_*.designtime.log"; + var logFilePath = $"{Path.GetTempPath()}\\{Path.GetFileNameWithoutExtension(this.VisualStudioProject.FilePath)}_*.designtime.log"; var logFileDescription = string.Format(ServicesVSResources.To_see_what_caused_the_issue_please_try_below_1_Close_Visual_Studio_long_paragraph_follows, logFilePath); if (string.IsNullOrWhiteSpace(reason)) diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject_IVsENCRebuildableProjectCfg.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_IVsENCRebuildableProjectCfg.cs similarity index 60% rename from src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject_IVsENCRebuildableProjectCfg.cs rename to src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_IVsENCRebuildableProjectCfg.cs index e3155ac790e3576937859851df316d03a64b128f..70d46267a9d80a79a6e224c50d68a63c5e5efd7f 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject_IVsENCRebuildableProjectCfg.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_IVsENCRebuildableProjectCfg.cs @@ -5,11 +5,11 @@ using ShellInterop = Microsoft.VisualStudio.Shell.Interop; using VsTextSpan = Microsoft.VisualStudio.TextManager.Interop.TextSpan; -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Legacy { // Dev11 implementation: csharp\radmanaged\Features\EditAndContinue\EncProject.cs - internal partial class AbstractProject : EncInterop.IVsENCRebuildableProjectCfg2, EncInterop.IVsENCRebuildableProjectCfg4 + internal partial class AbstractLegacyProject : EncInterop.IVsENCRebuildableProjectCfg2, EncInterop.IVsENCRebuildableProjectCfg4 { public int HasCustomMetadataEmitter(out bool value) { @@ -19,58 +19,58 @@ public int HasCustomMetadataEmitter(out bool value) public int StartDebuggingPE() { - return EditAndContinueImplOpt?.StartDebuggingPE() ?? VSConstants.S_OK; + return _editAndContinueProject.StartDebuggingPE(); } public int StopDebuggingPE() { - return EditAndContinueImplOpt?.StopDebuggingPE() ?? VSConstants.S_OK; + return _editAndContinueProject.StopDebuggingPE(); } public int GetPEidentity(Guid[] pMVID, string[] pbstrPEName) { - return EditAndContinueImplOpt?.GetPEidentity(pMVID, pbstrPEName) ?? VSConstants.E_FAIL; + return _editAndContinueProject.GetPEidentity(pMVID, pbstrPEName); } public int EnterBreakStateOnPE(EncInterop.ENC_BREAKSTATE_REASON encBreakReason, ShellInterop.ENC_ACTIVE_STATEMENT[] pActiveStatements, uint cActiveStatements) { - return EditAndContinueImplOpt?.EnterBreakStateOnPE(encBreakReason, pActiveStatements, cActiveStatements) ?? VSConstants.S_OK; + return _editAndContinueProject.EnterBreakStateOnPE(encBreakReason, pActiveStatements, cActiveStatements); } public int GetExceptionSpanCount(out uint pcExceptionSpan) { pcExceptionSpan = default; - return EditAndContinueImplOpt?.GetExceptionSpanCount(out pcExceptionSpan) ?? VSConstants.E_FAIL; + return _editAndContinueProject.GetExceptionSpanCount(out pcExceptionSpan); } public int GetExceptionSpans(uint celt, ShellInterop.ENC_EXCEPTION_SPAN[] rgelt, ref uint pceltFetched) { - return EditAndContinueImplOpt?.GetExceptionSpans(celt, rgelt, ref pceltFetched) ?? VSConstants.E_FAIL; + return _editAndContinueProject.GetExceptionSpans(celt, rgelt, ref pceltFetched); } public int GetCurrentExceptionSpanPosition(uint id, VsTextSpan[] ptsNewPosition) { - return EditAndContinueImplOpt?.GetCurrentExceptionSpanPosition(id, ptsNewPosition) ?? VSConstants.E_FAIL; + return _editAndContinueProject.GetCurrentExceptionSpanPosition(id, ptsNewPosition); } public int GetENCBuildState(ShellInterop.ENC_BUILD_STATE[] pENCBuildState) { - return EditAndContinueImplOpt?.GetENCBuildState(pENCBuildState) ?? VSConstants.E_FAIL; + return _editAndContinueProject.GetENCBuildState(pENCBuildState); } public int ExitBreakStateOnPE() { - return EditAndContinueImplOpt?.ExitBreakStateOnPE() ?? VSConstants.S_OK; + return _editAndContinueProject.ExitBreakStateOnPE(); } public int GetCurrentActiveStatementPosition(uint id, VsTextSpan[] ptsNewPosition) { - return EditAndContinueImplOpt?.GetCurrentActiveStatementPosition(id, ptsNewPosition) ?? VSConstants.E_FAIL; + return _editAndContinueProject.GetCurrentActiveStatementPosition(id, ptsNewPosition); } public int EncApplySucceeded(int hrApplyResult) { - return EditAndContinueImplOpt?.EncApplySucceeded(hrApplyResult) ?? VSConstants.S_OK; + return _editAndContinueProject.EncApplySucceeded(hrApplyResult); } public int GetPEBuildTimeStamp(Microsoft.VisualStudio.OLE.Interop.FILETIME[] pTimeStamp) @@ -80,7 +80,7 @@ public int GetPEBuildTimeStamp(Microsoft.VisualStudio.OLE.Interop.FILETIME[] pTi public int BuildForEnc(object pUpdatePE) { - return EditAndContinueImplOpt?.BuildForEnc(pUpdatePE) ?? VSConstants.S_OK; + return _editAndContinueProject.BuildForEnc(pUpdatePE); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_IVsHierarchyEvents.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_IVsHierarchyEvents.cs index c587f3e03ef2ef3134ad359dab1659583494d5c3..2040f9f06b415683cb23694e368bd94a01b9c766 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_IVsHierarchyEvents.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_IVsHierarchyEvents.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics; +using System.IO; using Microsoft.VisualStudio.Shell.Interop; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Legacy @@ -66,17 +67,17 @@ int IVsHierarchyEvents.OnPropertyChanged(uint itemid, int propid, uint flags) propid == (int)__VSHPROPID.VSHPROPID_Name) && itemid == (uint)VSConstants.VSITEMID.Root) { - string newDisplayName = GetProjectDisplayName(Hierarchy); - string newPath = GetProjectFilePath(Hierarchy); + var filePath = Hierarchy.GetProjectFilePath(); - UpdateProjectDisplayNameAndFilePath(newDisplayName, newPath); - } + if (File.Exists(filePath)) + { + VisualStudioProject.FilePath = filePath; + } - if ((propid == (int)__VSHPROPID.VSHPROPID_ProjectIDGuid) && - itemid == (uint)VSConstants.VSITEMID.Root) - { - // this should happen while project loading if it ever happens - Guid = GetProjectIDGuid(Hierarchy); + if (Hierarchy.TryGetName(out var name)) + { + VisualStudioProject.DisplayName = name; + } } return VSConstants.S_OK; diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject_IVsReportExternalErrors.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_IVsReportExternalErrors.cs similarity index 53% rename from src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject_IVsReportExternalErrors.cs rename to src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_IVsReportExternalErrors.cs index 3f697df7758d75d79e25d078f0633305b1d1462b..00ab29739144c5356069b6e2c06c87bcc52ebba3 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject_IVsReportExternalErrors.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject_IVsReportExternalErrors.cs @@ -2,62 +2,50 @@ using System.Runtime.InteropServices; using Microsoft.VisualStudio; +using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.TextManager.Interop; -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Legacy { - internal partial class AbstractProject : IVsReportExternalErrors, IVsLanguageServiceBuildErrorReporter2 + internal partial class AbstractLegacyProject : IVsReportExternalErrors, IVsLanguageServiceBuildErrorReporter2 { + private readonly ProjectExternalErrorReporter _externalErrorReporter; + int IVsReportExternalErrors.AddNewErrors(IVsEnumExternalErrors pErrors) { - if (ExternalErrorReporter != null) - { - return ExternalErrorReporter.AddNewErrors(pErrors); - } - - return VSConstants.E_NOTIMPL; + return _externalErrorReporter.AddNewErrors(pErrors); } int IVsReportExternalErrors.ClearAllErrors() { - if (ExternalErrorReporter != null) - { - return ExternalErrorReporter.ClearAllErrors(); - } - - return VSConstants.E_NOTIMPL; + return _externalErrorReporter.ClearAllErrors(); } int IVsLanguageServiceBuildErrorReporter.ClearErrors() { - return ((IVsLanguageServiceBuildErrorReporter2)this).ClearErrors(); + return _externalErrorReporter.ClearErrors(); } int IVsLanguageServiceBuildErrorReporter2.ClearErrors() { - if (ExternalErrorReporter != null) - { - return ((IVsLanguageServiceBuildErrorReporter2)ExternalErrorReporter).ClearErrors(); - } - - return VSConstants.E_NOTIMPL; + return _externalErrorReporter.ClearErrors(); } int IVsReportExternalErrors.GetErrors(out IVsEnumExternalErrors pErrors) { - pErrors = null; - if (ExternalErrorReporter != null) - { - return ExternalErrorReporter.GetErrors(out pErrors); - } - - return VSConstants.E_NOTIMPL; + return _externalErrorReporter.GetErrors(out pErrors); } int IVsLanguageServiceBuildErrorReporter.ReportError(string bstrErrorMessage, string bstrErrorId, VSTASKPRIORITY nPriority, int iLine, int iColumn, string bstrFileName) { - return ((IVsLanguageServiceBuildErrorReporter2)this).ReportError(bstrErrorMessage, bstrErrorId, nPriority, iLine, iColumn, bstrFileName); + return _externalErrorReporter.ReportError( + bstrErrorMessage, + bstrErrorId, + nPriority, + iLine, + iColumn, + bstrFileName); } int IVsLanguageServiceBuildErrorReporter2.ReportError( @@ -68,18 +56,13 @@ int IVsLanguageServiceBuildErrorReporter.ReportError(string bstrErrorMessage, st int iColumn, string bstrFileName) { - if (ExternalErrorReporter != null) - { - return ((IVsLanguageServiceBuildErrorReporter2)ExternalErrorReporter).ReportError( - bstrErrorMessage, - bstrErrorId, - nPriority, - iLine, - iColumn, - bstrFileName); - } - - return VSConstants.S_OK; + return _externalErrorReporter.ReportError( + bstrErrorMessage, + bstrErrorId, + nPriority, + iLine, + iColumn, + bstrFileName); } void IVsLanguageServiceBuildErrorReporter2.ReportError2( @@ -92,9 +75,7 @@ int IVsLanguageServiceBuildErrorReporter.ReportError(string bstrErrorMessage, st int iEndColumn, string bstrFileName) { - if (ExternalErrorReporter != null) - { - ((IVsLanguageServiceBuildErrorReporter2)ExternalErrorReporter).ReportError2( + _externalErrorReporter.ReportError2( bstrErrorMessage, bstrErrorId, nPriority, @@ -103,7 +84,6 @@ int IVsLanguageServiceBuildErrorReporter.ReportError(string bstrErrorMessage, st iEndLine, iEndColumn, bstrFileName); - } } } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/SolutionEventsBatchScopeCreator.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/SolutionEventsBatchScopeCreator.cs new file mode 100644 index 0000000000000000000000000000000000000000..a66628ac9cbde2948e9a821eae266dfd28d4a4db --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/SolutionEventsBatchScopeCreator.cs @@ -0,0 +1,321 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Legacy +{ + /// + /// Creates batch scopes for projects based on IVsSolutionEvents. This is useful for projects types that don't otherwise have + /// good batching concepts. + /// + /// All members of this class are affinitized to the UI thread. + [Export(typeof(SolutionEventsBatchScopeCreator))] + internal sealed class SolutionEventsBatchScopeCreator : ForegroundThreadAffinitizedObject + { + private readonly List<(VisualStudioProject project, IVsHierarchy hierarchy, VisualStudioProject.BatchScope batchScope)> _fullSolutionLoadScopes = new List<(VisualStudioProject, IVsHierarchy, VisualStudioProject.BatchScope)>(); + + private uint? _runningDocumentTableEventsCookie; + + private readonly IServiceProvider _serviceProvider; + + private bool _isSubscribedToSolutionEvents = false; + private bool _solutionLoaded = false; + + [ImportingConstructor] + public SolutionEventsBatchScopeCreator(IThreadingContext threadingContext, [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) + : base(threadingContext, assertIsForeground: false) + { + _serviceProvider = serviceProvider; + } + + public void StartTrackingProject(VisualStudioProject project, IVsHierarchy hierarchy) + { + AssertIsForeground(); + + EnsureSubscribedToSolutionEvents(); + + if (!_solutionLoaded) + { + _fullSolutionLoadScopes.Add((project, hierarchy, project.CreateBatchScope())); + + EnsureSubscribedToRunningDocumentTableEvents(); + } + } + + public void StopTrackingProject(VisualStudioProject project) + { + AssertIsForeground(); + + foreach (var scope in _fullSolutionLoadScopes) + { + if (scope.project == project) + { + scope.batchScope.Dispose(); + _fullSolutionLoadScopes.Remove(scope); + break; + } + } + + EnsureUnsubscribedFromRunningDocumentTableEventsIfNoLongerNeeded(); + } + + private void StopTrackingAllProjects() + { + AssertIsForeground(); + + foreach (var scope in _fullSolutionLoadScopes) + { + scope.batchScope.Dispose(); + } + + _fullSolutionLoadScopes.Clear(); + + EnsureUnsubscribedFromRunningDocumentTableEventsIfNoLongerNeeded(); + } + + private void StopTrackingAllProjectsMatchingHierarchy(IVsHierarchy hierarchy) + { + AssertIsForeground(); + + for (int i = 0; i < _fullSolutionLoadScopes.Count; i++) + { + if (_fullSolutionLoadScopes[i].hierarchy == hierarchy) + { + _fullSolutionLoadScopes[i].batchScope.Dispose(); + _fullSolutionLoadScopes.RemoveAt(i); + + // Go back by one so we re-check the same index + i--; + } + } + + EnsureUnsubscribedFromRunningDocumentTableEventsIfNoLongerNeeded(); + } + + private void EnsureSubscribedToSolutionEvents() + { + AssertIsForeground(); + + if (_isSubscribedToSolutionEvents) + { + return; + } + + var solution = (IVsSolution)_serviceProvider.GetService(typeof(SVsSolution)); + + // We never unsubscribe from these, so we just throw out the cookie. We could consider unsubscribing if/when all our + // projects are unloaded, but it seems fairly unecessary -- it'd only be useful if somebody closed one solution but then + // opened other solutions in entirely different languages from there. + solution.AdviseSolutionEvents(new SolutionEventsEventSink(this), out _); + + // It's possible that we're loading after the solution has already fully loaded, so see if we missed the event + var shellMonitorSelection = (IVsMonitorSelection)_serviceProvider.GetService(typeof(SVsShellMonitorSelection)); + + if (ErrorHandler.Succeeded(shellMonitorSelection.GetCmdUIContextCookie(VSConstants.UICONTEXT.SolutionExistsAndFullyLoaded_guid, out var fullyLoadedContextCookie))) + { + if (ErrorHandler.Succeeded(shellMonitorSelection.IsCmdUIContextActive(fullyLoadedContextCookie, out var fActive)) && fActive != 0) + { + _solutionLoaded = true; + } + } + } + + private void EnsureSubscribedToRunningDocumentTableEvents() + { + AssertIsForeground(); + + if (_runningDocumentTableEventsCookie.HasValue) + { + return; + } + + var runningDocumentTable = (IVsRunningDocumentTable)_serviceProvider.GetService(typeof(SVsRunningDocumentTable)); + + if (ErrorHandler.Succeeded(runningDocumentTable.AdviseRunningDocTableEvents(new RunningDocumentTableEventSink(this, runningDocumentTable), out var runningDocumentTableEventsCookie))) + { + _runningDocumentTableEventsCookie = runningDocumentTableEventsCookie; + } + } + + private void EnsureUnsubscribedFromRunningDocumentTableEventsIfNoLongerNeeded() + { + AssertIsForeground(); + + if (!_runningDocumentTableEventsCookie.HasValue) + { + return; + } + + // If we don't have any scopes left, then there is no reason to be subscribed to Running Document Table events, because + // there won't be any scopes to complete. + if (_fullSolutionLoadScopes.Count > 0) + { + return; + } + + var runningDocumentTable = (IVsRunningDocumentTable)_serviceProvider.GetService(typeof(SVsRunningDocumentTable)); + runningDocumentTable.UnadviseRunningDocTableEvents(_runningDocumentTableEventsCookie.Value); + _runningDocumentTableEventsCookie = null; + } + + private class SolutionEventsEventSink : IVsSolutionEvents, IVsSolutionLoadEvents + { + private readonly SolutionEventsBatchScopeCreator _scopeCreator; + + public SolutionEventsEventSink(SolutionEventsBatchScopeCreator scopeCreator) + { + _scopeCreator = scopeCreator; + } + + int IVsSolutionLoadEvents.OnBeforeOpenSolution(string pszSolutionFilename) + { + Contract.ThrowIfTrue(_scopeCreator._fullSolutionLoadScopes.Any()); + + _scopeCreator._solutionLoaded = false; + + return VSConstants.S_OK; + } + + int IVsSolutionEvents.OnBeforeCloseSolution(object pUnkReserved) + { + _scopeCreator._solutionLoaded = false; + + return VSConstants.S_OK; + } + + int IVsSolutionLoadEvents.OnAfterBackgroundSolutionLoadComplete() + { + _scopeCreator.StopTrackingAllProjects(); + + return VSConstants.S_OK; + } + + #region Unimplemented Members + + int IVsSolutionEvents.OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) + { + return VSConstants.E_NOTIMPL; + } + + int IVsSolutionEvents.OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel) + { + return VSConstants.E_NOTIMPL; + } + + int IVsSolutionEvents.OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved) + { + return VSConstants.E_NOTIMPL; + } + + int IVsSolutionEvents.OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy) + { + return VSConstants.E_NOTIMPL; + } + + int IVsSolutionEvents.OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel) + { + return VSConstants.E_NOTIMPL; + } + + int IVsSolutionEvents.OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy) + { + return VSConstants.E_NOTIMPL; + } + + int IVsSolutionEvents.OnAfterOpenSolution(object pUnkReserved, int fNewSolution) + { + return VSConstants.E_NOTIMPL; + } + + int IVsSolutionEvents.OnQueryCloseSolution(object pUnkReserved, ref int pfCancel) + { + return VSConstants.E_NOTIMPL; + } + + int IVsSolutionEvents.OnAfterCloseSolution(object pUnkReserved) + { + return VSConstants.E_NOTIMPL; + } + + int IVsSolutionLoadEvents.OnBeforeBackgroundSolutionLoadBegins() + { + return VSConstants.E_NOTIMPL; + } + + int IVsSolutionLoadEvents.OnQueryBackgroundLoadProjectBatch(out bool pfShouldDelayLoadToNextIdle) + { + pfShouldDelayLoadToNextIdle = false; + return VSConstants.E_NOTIMPL; + } + + int IVsSolutionLoadEvents.OnBeforeLoadProjectBatch(bool fIsBackgroundIdleBatch) + { + return VSConstants.E_NOTIMPL; + } + + int IVsSolutionLoadEvents.OnAfterLoadProjectBatch(bool fIsBackgroundIdleBatch) + { + return VSConstants.E_NOTIMPL; + } + + #endregion + } + + private class RunningDocumentTableEventSink : IVsRunningDocTableEvents + { + private readonly SolutionEventsBatchScopeCreator _scopeCreator; + private readonly IVsRunningDocumentTable4 _runningDocumentTable; + + public RunningDocumentTableEventSink(SolutionEventsBatchScopeCreator scopeCreator, IVsRunningDocumentTable runningDocumentTable) + { + _scopeCreator = scopeCreator; + _runningDocumentTable = (IVsRunningDocumentTable4)runningDocumentTable; + } + + int IVsRunningDocTableEvents.OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) + { + _runningDocumentTable.GetDocumentHierarchyItem(docCookie, out var hierarchy, out var itemID); + + // Some document is being opened in this project; we need to ensure the project is fully updated so any requests + // for CodeModel or the workspace are successful. + _scopeCreator.StopTrackingAllProjectsMatchingHierarchy(hierarchy); + + return VSConstants.S_OK; + } + + #region Unimplemented Members + + int IVsRunningDocTableEvents.OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) + { + return VSConstants.E_NOTIMPL; + } + + int IVsRunningDocTableEvents.OnAfterSave(uint docCookie) + { + return VSConstants.E_NOTIMPL; + } + + int IVsRunningDocTableEvents.OnAfterAttributeChange(uint docCookie, uint grfAttribs) + { + return VSConstants.E_NOTIMPL; + } + + int IVsRunningDocTableEvents.OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame) + { + return VSConstants.E_NOTIMPL; + } + + int IVsRunningDocTableEvents.OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame) + { + return VSConstants.E_NOTIMPL; + } + + #endregion + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/LinkedFileUtilities.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/LinkedFileUtilities.cs deleted file mode 100644 index a9facd1121053557dc005b74e71cf6eb533bc94f..0000000000000000000000000000000000000000 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/LinkedFileUtilities.cs +++ /dev/null @@ -1,191 +0,0 @@ -// 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 System; -using System.Composition; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.VisualStudio.Shell.Interop; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -{ - [Export] - [Shared] - internal sealed class LinkedFileUtilities : ForegroundThreadAffinitizedObject - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public LinkedFileUtilities(IThreadingContext threadingContext) - : base(threadingContext) - { - } - - public bool IsCurrentContextHierarchy(IVisualStudioHostDocument document, IVsRunningDocumentTable4 runningDocumentTable) - { - // runningDocumentTable might be null for tests. - return runningDocumentTable != null && document.Project.Hierarchy == GetContextHierarchy(document, runningDocumentTable); - } - - /// - /// Finds the current context hierarchy for the given document. If the document is in a - /// Shared Code project, this returns that project's SharedItemContextHierarchy. If the - /// document is linked into multiple projects, this returns the hierarchy in which it is - /// currently open as indicated by the running document table. Otherwise, it returns the - /// hierarchy of the document's project. - /// - public IVsHierarchy GetContextHierarchy(IVisualStudioHostDocument document, IVsRunningDocumentTable4 runningDocumentTable) - { - AssertIsForeground(); - - return GetSharedItemContextHierarchy(document) ?? GetContextHierarchyFromRunningDocumentTable(document, runningDocumentTable) ?? document.Project.Hierarchy; - } - - /// - /// If the document is open in the running document table, this returns the hierarchy in - /// which it is currently open. Otherwise, it returns null. - /// - private IVsHierarchy GetContextHierarchyFromRunningDocumentTable(IVisualStudioHostDocument document, IVsRunningDocumentTable4 runningDocumentTable) - { - AssertIsForeground(); - if (!runningDocumentTable.TryGetCookieForInitializedDocument(document.Key.Moniker, out var docCookie)) - { - return null; - } - - runningDocumentTable.GetDocumentHierarchyItem(docCookie, out var hierarchy, out var itemid); - - return hierarchy; - } - - /// - /// If the document is in a Shared Code project, this returns that project's - /// SharedItemContextHierarchy. Otherwise, it returns null. - /// - private IVsHierarchy GetSharedItemContextHierarchy(IVisualStudioHostDocument document) - { - AssertIsForeground(); - - var itemId = document.GetItemId(); - if (itemId == (uint)VSConstants.VSITEMID.Nil) - { - // the document is no longer part of the solution - return null; - } - - var sharedHierarchy = GetSharedHierarchyForItem(document.Project.Hierarchy, itemId); - if (sharedHierarchy == null) - { - return null; - } - - return GetSharedItemContextHierarchy(sharedHierarchy); - } - - /// - /// If the project is in a Shared Code project, this returns its - /// SharedItemContextHierarchy. Otherwise, it returns null. - /// - private IVsHierarchy GetSharedItemContextHierarchy(IVsHierarchy hierarchy) - { - if (hierarchy.GetProperty((uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID7.VSHPROPID_SharedItemContextHierarchy, out var contextHierarchy) != VSConstants.S_OK) - { - return null; - } - - return contextHierarchy as IVsHierarchy; - } - - /// - /// If the itemId represents a document from a Shared Code project, this returns the - /// SharedProjectHierarchy to which it belongs. Otherwise, it returns null. - /// - public IVsHierarchy GetSharedHierarchyForItem(IVsHierarchy headProjectHierarchy, uint itemId) - { - AssertIsForeground(); - if (headProjectHierarchy.GetProperty(itemId, (int)__VSHPROPID7.VSHPROPID_IsSharedItem, out var isShared) != VSConstants.S_OK || !(bool)isShared) - { - return null; - } - - return headProjectHierarchy.GetProperty(itemId, (int)__VSHPROPID7.VSHPROPID_SharedProjectHierarchy, out var sharedHierarchy) == VSConstants.S_OK - ? sharedHierarchy as IVsHierarchy - : null; - } - - public AbstractProject GetContextHostProject(IVsHierarchy hierarchy, VisualStudioProjectTracker projectTracker) - { - hierarchy = GetSharedItemContextHierarchy(hierarchy) ?? hierarchy; - var projectName = GetActiveIntellisenseProjectContextInternal(hierarchy); - - if (projectName != null) - { - return projectTracker.ImmutableProjects.FirstOrDefault(p => p.ProjectSystemName == projectName); - } - else - { - return projectTracker.ImmutableProjects.FirstOrDefault(p => p.Hierarchy == hierarchy); - } - } - - private string GetActiveIntellisenseProjectContextInternal(IVsHierarchy hierarchy) - { - AssertIsForeground(); - - hierarchy = GetSharedItemContextHierarchy(hierarchy) ?? hierarchy; - return hierarchy.GetProperty((uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID8.VSHPROPID_ActiveIntellisenseProjectContext, out var intellisenseProjectName) == VSConstants.S_OK - ? intellisenseProjectName as string - : null; - } - - public bool TryGetSharedHierarchyAndItemId(IVsHierarchy hierarchy, uint itemId, out IVsHierarchy sharedHierarchy, out uint itemIdInSharedHierarchy) - { - AssertIsForeground(); - - sharedHierarchy = null; - itemIdInSharedHierarchy = (uint)VSConstants.VSITEMID.Nil; - - if (hierarchy == null) - { - return false; - } - - sharedHierarchy = GetSharedHierarchyForItem(hierarchy, itemId); - - return sharedHierarchy == null - ? false - : TryGetItemIdInSharedHierarchyInternal(hierarchy, itemId, sharedHierarchy, out itemIdInSharedHierarchy); - } - - private bool TryGetItemIdInSharedHierarchyInternal(IVsHierarchy hierarchy, uint itemId, IVsHierarchy sharedHierarchy, out uint itemIdInSharedHierarchy) - { - VSDOCUMENTPRIORITY[] priority = new VSDOCUMENTPRIORITY[1]; - - if (ErrorHandler.Succeeded(((IVsProject)hierarchy).GetMkDocument(itemId, out var fullPath)) - && ErrorHandler.Succeeded(((IVsProject)sharedHierarchy).IsDocumentInProject(fullPath, out var found, priority, out itemIdInSharedHierarchy)) - && found != 0 - && itemIdInSharedHierarchy != (uint)VSConstants.VSITEMID.Nil) - { - return true; - } - - itemIdInSharedHierarchy = (uint)VSConstants.VSITEMID.Nil; - return false; - } - - /// - /// Check whether given project is project k project. - /// - public static bool IsProjectKProject(Project project) - { - // TODO: we need better way to see whether a project is project k project or not. - if (project.FilePath == null) - { - return false; - } - - return project.FilePath.EndsWith(".xproj", StringComparison.InvariantCultureIgnoreCase) || - project.FilePath.EndsWith(".kproj", StringComparison.InvariantCultureIgnoreCase); - } - } -} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManager.RuleSetFile.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManager.RuleSetFile.cs index c33c8879241ca6afd98c202bc618708fff14ab46..69086c11f5384a04d4d9ecba0c7ce06da12061a2 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManager.RuleSetFile.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManager.RuleSetFile.cs @@ -16,11 +16,7 @@ private sealed class RuleSetFile : IRuleSetFile, IDisposable private readonly VisualStudioRuleSetManager _ruleSetManager; private readonly object _gate = new object(); - /// - /// The list of file trackers we have. This is null if we haven't even computed trackers yet; but can be empty if we - /// already know the file was modified and we unsubscribed. - /// - private List _trackers; + private FileChangeWatcher.IContext _fileChangeContext; private ReportDiagnostic _generalDiagnosticOption; private ImmutableDictionary _specificDiagnosticOptions; @@ -36,11 +32,11 @@ public RuleSetFile(string filePath, VisualStudioRuleSetManager ruleSetManager) _ruleSetManager = ruleSetManager; } - public void InitializeFileTracking(IVsFileChangeEx fileChangeService) + public void InitializeFileTracking(FileChangeWatcher fileChangeWatcher) { lock (_gate) { - if (_trackers == null) + if (_fileChangeContext == null) { ImmutableArray includes; @@ -60,15 +56,12 @@ public void InitializeFileTracking(IVsFileChangeEx fileChangeService) includes = ImmutableArray.Create(FilePath); } - _trackers = new List(capacity: includes.Length); + _fileChangeContext = fileChangeWatcher.CreateContext(); + _fileChangeContext.FileChanged += IncludeUpdated; foreach (var include in includes) { - var tracker = new FileChangeTracker(fileChangeService, include); - tracker.UpdatedOnDisk += IncludeUpdated; - tracker.StartFileChangeListeningAsync(); - - _trackers.Add(tracker); + _fileChangeContext.EnqueueWatchingFile(include); } } } @@ -108,11 +101,7 @@ private void EnsureSubscriptions() { if (!_subscribed) { - foreach (var tracker in _trackers) - { - tracker.EnsureSubscription(); - } - + // TODO: ensure subscriptions now _subscribed = true; } } @@ -155,13 +144,7 @@ private void RemoveFromRuleSetManagerAndDisconnectFileTrackers() { lock (_gate) { - foreach (var tracker in _trackers) - { - tracker.UpdatedOnDisk -= IncludeUpdated; - tracker.Dispose(); - } - - _trackers.Clear(); + _fileChangeContext.Dispose(); if (_removedFromRuleSetManager) { @@ -169,14 +152,13 @@ private void RemoveFromRuleSetManagerAndDisconnectFileTrackers() } _removedFromRuleSetManager = true; - } // Call outside of lock to avoid general surprises; we skip this with the return above inside the lock. _ruleSetManager.StopTrackingRuleSetFile(this); } - private void IncludeUpdated(object sender, EventArgs e) + private void IncludeUpdated(object sender, string fileChanged) { // The file change service is going to notify us of updates on the foreground thread. // This is going to cause us to drop our existing subscriptions and create new ones. diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManager.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManager.cs index b73a53ef7e29e2c3c02d6265be02ae02ead4e881..60d81409fe1e442ff72fc3d0fa67376168e9e346 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManager.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManager.cs @@ -11,7 +11,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem { internal sealed partial class VisualStudioRuleSetManager : IWorkspaceService { - private readonly IVsFileChangeEx _fileChangeService; + private readonly FileChangeWatcher _fileChangeWatcher; private readonly IForegroundNotificationService _foregroundNotificationService; private readonly IAsynchronousOperationListener _listener; @@ -30,9 +30,9 @@ internal sealed partial class VisualStudioRuleSetManager : IWorkspaceService new Dictionary.WeakReference>(); public VisualStudioRuleSetManager( - IVsFileChangeEx fileChangeService, IForegroundNotificationService foregroundNotificationService, IAsynchronousOperationListener listener) + FileChangeWatcher fileChangeWatcher, IForegroundNotificationService foregroundNotificationService, IAsynchronousOperationListener listener) { - _fileChangeService = fileChangeService; + _fileChangeWatcher = fileChangeWatcher; _foregroundNotificationService = foregroundNotificationService; _listener = listener; } @@ -64,7 +64,7 @@ public IReferenceCountedDisposable GetOrCreateRuleSet(string ruleS // Call InitializeFileTracking outside the lock, so we don't have requests for other files blocking behind the initialization of this one. // RuleSetFile itself will ensure InitializeFileTracking is locked as appropriate. - disposable.Target.InitializeFileTracking(_fileChangeService); + disposable.Target.InitializeFileTracking(_fileChangeWatcher); return disposable; } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManagerFactory.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManagerFactory.cs index f51c8775c1eb1cce4b248e5cfac2208087ed112b..a9ef043c9683398f21ca0026a11e05706d7dd4aa 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManagerFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RuleSets/VisualStudioRuleSetManagerFactory.cs @@ -16,25 +16,24 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem [ExportWorkspaceServiceFactory(typeof(VisualStudioRuleSetManager), ServiceLayer.Host), Shared] internal sealed class VisualStudioRuleSetManagerFactory : IWorkspaceServiceFactory { - private readonly IServiceProvider _serviceProvider; + private readonly FileChangeWatcherProvider _fileChangeWatcherProvider; private readonly IForegroundNotificationService _foregroundNotificationService; private readonly IAsynchronousOperationListener _listener; [ImportingConstructor] public VisualStudioRuleSetManagerFactory( - SVsServiceProvider serviceProvider, + FileChangeWatcherProvider fileChangeWatcherProvider, IForegroundNotificationService foregroundNotificationService, IAsynchronousOperationListenerProvider listenerProvider) { - _serviceProvider = serviceProvider; + _fileChangeWatcherProvider = fileChangeWatcherProvider; _foregroundNotificationService = foregroundNotificationService; _listener = listenerProvider.GetListener(FeatureAttribute.RuleSetEditor); } public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) { - IVsFileChangeEx fileChangeService = (IVsFileChangeEx)_serviceProvider.GetService(typeof(SVsFileChangeEx)); - return new VisualStudioRuleSetManager(fileChangeService, _foregroundNotificationService, _listener); + return new VisualStudioRuleSetManager(_fileChangeWatcherProvider.Watcher, _foregroundNotificationService, _listener); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioAnalyzer.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioAnalyzer.cs index 726ff0ff59f9af53931242778e0f3234d2eb9ddd..d4d5854f91f5e253e2970b2529f916b364080376 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioAnalyzer.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioAnalyzer.cs @@ -6,53 +6,35 @@ using System.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host; using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; -using Microsoft.VisualStudio.Shell.Interop; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem { - using Workspace = Microsoft.CodeAnalysis.Workspace; - internal sealed class VisualStudioAnalyzer : IDisposable { - private readonly string _fullPath; - private readonly FileChangeTracker _tracker; - private readonly HostDiagnosticUpdateSource _hostDiagnosticUpdateSource; - private readonly ProjectId _projectId; private readonly Workspace _workspace; - private readonly IAnalyzerAssemblyLoader _loader; + private readonly ProjectId _projectId; + private readonly HostDiagnosticUpdateSource _hostDiagnosticUpdateSource; private readonly string _language; + private readonly IAnalyzerAssemblyLoader _analyzerAssemblyLoader; // these 2 are mutable states that must be guarded under the _gate. private readonly object _gate = new object(); private AnalyzerReference _analyzerReference = null; private ImmutableArray _analyzerLoadErrors = ImmutableArray.Empty; - public VisualStudioAnalyzer(string fullPath, IVsFileChangeEx fileChangeService, HostDiagnosticUpdateSource hostDiagnosticUpdateSource, ProjectId projectId, Workspace workspace, IAnalyzerAssemblyLoader loader, string language) + public VisualStudioAnalyzer(string fullPath, HostDiagnosticUpdateSource hostDiagnosticUpdateSource, ProjectId projectId, Workspace workspace, string language) { - _fullPath = fullPath; + FullPath = fullPath; _hostDiagnosticUpdateSource = hostDiagnosticUpdateSource; _projectId = projectId; _workspace = workspace; - _loader = loader; _language = language; - - _tracker = new FileChangeTracker(fileChangeService, fullPath); - _tracker.UpdatedOnDisk += OnUpdatedOnDisk; - _tracker.StartFileChangeListeningAsync(); + _analyzerAssemblyLoader = _workspace.Services.GetRequiredService().GetLoader(); } - public event EventHandler UpdatedOnDisk; - - public string FullPath - { - get { return _fullPath; } - } - - public bool HasLoadErrors - { - get { return !_analyzerLoadErrors.IsEmpty; } - } + public string FullPath { get; } public AnalyzerReference GetReference() { @@ -60,16 +42,18 @@ public AnalyzerReference GetReference() { if (_analyzerReference == null) { - if (File.Exists(_fullPath)) + if (File.Exists(FullPath)) { // Pass down a custom loader that will ensure we are watching for file changes once we actually load the assembly. var assemblyLoaderForFileTracker = new AnalyzerAssemblyLoaderThatEnsuresFileBeingWatched(this); - _analyzerReference = new AnalyzerFileReference(_fullPath, assemblyLoaderForFileTracker); - ((AnalyzerFileReference)_analyzerReference).AnalyzerLoadFailed += OnAnalyzerLoadError; + var analyzerFileReference = new AnalyzerFileReference(FullPath, assemblyLoaderForFileTracker); + analyzerFileReference.AnalyzerLoadFailed += OnAnalyzerLoadError; + + _analyzerReference = analyzerFileReference; } else { - _analyzerReference = new VisualStudioUnresolvedAnalyzerReference(_fullPath, this); + _analyzerReference = new VisualStudioUnresolvedAnalyzerReference(FullPath, this); } } @@ -79,25 +63,16 @@ public AnalyzerReference GetReference() private void OnAnalyzerLoadError(object sender, AnalyzerLoadFailureEventArgs e) { - var data = AnalyzerHelper.CreateAnalyzerLoadFailureDiagnostic(_workspace, _projectId, _language, _fullPath, e); + var data = AnalyzerHelper.CreateAnalyzerLoadFailureDiagnostic(_workspace, _projectId, _language, FullPath, e); lock (_gate) { _analyzerLoadErrors = _analyzerLoadErrors.Add(data); + _hostDiagnosticUpdateSource.UpdateDiagnosticsForProject(_projectId, this, _analyzerLoadErrors); } - - _hostDiagnosticUpdateSource.UpdateDiagnosticsForProject(_projectId, this, _analyzerLoadErrors); } public void Dispose() - { - Reset(); - - _tracker.Dispose(); - _tracker.UpdatedOnDisk -= OnUpdatedOnDisk; - } - - public void Reset() { ResetReferenceAndErrors(out var reference, out var loadErrors); @@ -126,11 +101,6 @@ private void ResetReferenceAndErrors(out AnalyzerReference reference, out Immuta } } - private void OnUpdatedOnDisk(object sender, EventArgs e) - { - UpdatedOnDisk?.Invoke(this, EventArgs.Empty); - } - /// /// This custom loader just wraps an existing loader, but ensures that we start listening to the file /// for changes once we've actually looked at the file. @@ -146,13 +116,14 @@ public AnalyzerAssemblyLoaderThatEnsuresFileBeingWatched(VisualStudioAnalyzer an public void AddDependencyLocation(string fullPath) { - _analyzer._loader.AddDependencyLocation(fullPath); + _analyzer._analyzerAssemblyLoader.AddDependencyLocation(fullPath); } public Assembly LoadFromPath(string fullPath) { - _analyzer._tracker.EnsureSubscription(); - return _analyzer._loader.LoadFromPath(fullPath); + // TODO: ensure the file watcher is subscribed + // (tracked by https://devdiv.visualstudio.com/DevDiv/_workitems/edit/661546) + return _analyzer._analyzerAssemblyLoader.LoadFromPath(fullPath); } } @@ -161,7 +132,7 @@ public Assembly LoadFromPath(string fullPath) /// but ensure that we start listening to the file for changes once we've actually observed it, so that if the /// file then gets created on disk, we are notified. /// - private class VisualStudioUnresolvedAnalyzerReference : AnalyzerReference + private sealed class VisualStudioUnresolvedAnalyzerReference : AnalyzerReference { private readonly UnresolvedAnalyzerReference _underlying; private readonly VisualStudioAnalyzer _visualStudioAnalyzer; @@ -180,7 +151,8 @@ public override object Id public override ImmutableArray GetAnalyzers(string language) { - _visualStudioAnalyzer._tracker.EnsureSubscription(); + // TODO: ensure the file watcher is subscribed + // (tracked by https://devdiv.visualstudio.com/DevDiv/_workitems/edit/661546) return _underlying.GetAnalyzers(language); } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs new file mode 100644 index 0000000000000000000000000000000000000000..c739a1f09c25cf007d10c5cda0a21360c63dc49d --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs @@ -0,0 +1,1162 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +{ + internal sealed class VisualStudioProject + { + private readonly VisualStudioWorkspaceImpl _workspace; + private readonly HostDiagnosticUpdateSource _hostDiagnosticUpdateSource; + private readonly string _projectUniqueName; + + /// + /// A gate taken for all mutation of any mutable field in this type. + /// + /// This is, for now, intentionally pessimistic. There are no doubt ways that we could allow more to run in parallel, + /// but the current tradeoff is for simplicity of code and "obvious correctness" than something that is subtle, fast, and wrong. + private readonly object _gate = new object(); + + /// + /// The number of active batch scopes. If this is zero, we are not batching, non-zero means we are batching. + /// + private int _activeBatchScopes = 0; + + private readonly List<(string path, MetadataReferenceProperties properties)> _metadataReferencesAddedInBatch = new List<(string path, MetadataReferenceProperties properties)>(); + private readonly List<(string path, MetadataReferenceProperties properties)> _metadataReferencesRemovedInBatch = new List<(string path, MetadataReferenceProperties properties)>(); + private readonly List _projectReferencesAddedInBatch = new List(); + private readonly List _projectReferencesRemovedInBatch = new List(); + + private readonly Dictionary _analyzerPathsToAnalyzers = new Dictionary(); + private readonly List _analyzersAddedInBatch = new List(); + private readonly List _analyzersRemovedInBatch = new List(); + + private readonly List> _projectPropertyModificationsInBatch = new List>(); + + private string _assemblyName; + private string _displayName; + private string _filePath; + private CompilationOptions _compilationOptions; + private ParseOptions _parseOptions; + private bool _hasAllInformation = true; + private string _intermediateOutputFilePath; + private string _outputFilePath; + private string _outputRefFilePath; + + private readonly Dictionary> _allMetadataReferences = new Dictionary>(); + + /// + /// The file watching tokens for the documents in this project. We get the tokens even when we're in a batch, so the files here + /// may not be in the actual workspace yet. + /// + private readonly Dictionary _fileWatchingTokens = new Dictionary(); + + /// + /// A file change context used to watch source files and additional files for this project. It's automatically set to watch the user's project + /// directory so we avoid file-by-file watching. + /// + private readonly FileChangeWatcher.IContext _documentFileChangeContext; + + /// + /// A file change context used to watch metadata and analyzer references. + /// + private readonly FileChangeWatcher.IContext _fileReferenceChangeContext; + + private readonly BatchingDocumentCollection _sourceFiles; + private readonly BatchingDocumentCollection _additionalFiles; + + public ProjectId Id { get; } + public string Language { get; } + + internal VisualStudioProject(VisualStudioWorkspaceImpl workspace, HostDiagnosticUpdateSource hostDiagnosticUpdateSource, ProjectId id, string projectUniqueName, string language, string directoryNameOpt) + { + _workspace = workspace; + _hostDiagnosticUpdateSource = hostDiagnosticUpdateSource; + + Id = id; + Language = language; + _displayName = projectUniqueName; + _projectUniqueName = projectUniqueName; + + if (directoryNameOpt != null) + { + // TODO: use directoryNameOpt to create a directory watcher. For now, there's perf hits due to the flood of events we'll need to sort out later. + // _documentFileChangeContext = _workspace.FileChangeWatcher.CreateContextForDirectory(directoryNameOpt); + _documentFileChangeContext = workspace.FileChangeWatcher.CreateContext(); + } + else + { + _documentFileChangeContext = workspace.FileChangeWatcher.CreateContext(); + } + + _documentFileChangeContext.FileChanged += FileChangeContext_FileChanged; + + // TODO: set this to watch the NuGet directory or the reference assemblies directory; since those change rarely and most references + // will come from them, we can avoid creating a bunch of explicit file watchers. + _fileReferenceChangeContext = workspace.FileChangeWatcher.CreateContext(); + + _sourceFiles = new BatchingDocumentCollection(this, (s, d) => s.ContainsDocument(d), (w, d) => w.OnDocumentAdded(d), (w, documentId) => w.OnDocumentRemoved(documentId)); + _additionalFiles = new BatchingDocumentCollection(this, (s, d) => s.ContainsAdditionalDocument(d), (w, d) => w.OnAdditionalDocumentAdded(d), (w, documentId) => w.OnAdditionalDocumentRemoved(documentId)); + } + + + private void ChangeProjectProperty(ref T field, T newValue, Func withNewValue, Action changeValue) + { + lock (_gate) + { + // If nothing is changing, we can skip entirely + if (object.Equals(field, newValue)) + { + return; + } + + field = newValue; + + if (_activeBatchScopes > 0) + { + _projectPropertyModificationsInBatch.Add(withNewValue); + } + else + { + _workspace.ApplyChangeToWorkspace(changeValue); + } + } + } + + private void ChangeProjectOutputPath(ref string field, string newValue, Func withNewValue, Action changeValue) + { + lock (_gate) + { + // Skip if nothing changing + if (field == newValue) + { + return; + } + + if (field != null) + { + _workspace.RemoveProjectOutputPath(Id, field); + } + + if (newValue != null) + { + _workspace.AddProjectOutputPath(Id, newValue); + } + + ChangeProjectProperty(ref field, newValue, withNewValue, changeValue); + } + } + public string AssemblyName + { + get => _assemblyName; + set => ChangeProjectProperty( + ref _assemblyName, + value, + s => s.WithProjectAssemblyName(Id, value), + w => w.OnAssemblyNameChanged(Id, value)); + } + + public CompilationOptions CompilationOptions + { + get => _compilationOptions; + set => ChangeProjectProperty( + ref _compilationOptions, + value, + s => s.WithProjectCompilationOptions(Id, value), + w => w.OnCompilationOptionsChanged(Id, value)); + } + + public ParseOptions ParseOptions + { + get => _parseOptions; + set => ChangeProjectProperty( + ref _parseOptions, + value, + s => s.WithProjectParseOptions(Id, value), + w => w.OnParseOptionsChanged(Id, value)); + } + + /// + /// The path to the output in obj. + /// + /// This is internal for now, as it's only consumed by + /// which directly takes a . + internal string IntermediateOutputFilePath + { + get => _intermediateOutputFilePath; + set + { + // Unlike OutputFilePath and OutputRefFilePath, the intermediate output path isn't represented in the workspace anywhere; + // thus, we won't mutate the solution. We'll still call ChangeProjectOutputPath so we have the rest of the output path tracking + // for any P2P reference conversion. + ChangeProjectOutputPath(ref _intermediateOutputFilePath, value, s => s, w => { }); + } + } + + public string OutputFilePath + { + get => _outputFilePath; + set => ChangeProjectOutputPath(ref _outputFilePath, + value, + s => s.WithProjectOutputFilePath(Id, value), + w => w.OnOutputFilePathChanged(Id, value)); + } + + public string OutputRefFilePath + { + get => _outputRefFilePath; + set => ChangeProjectOutputPath(ref _outputRefFilePath, + value, + s => s.WithProjectOutputRefFilePath(Id, value), + w => w.OnOutputRefFilePathChanged(Id, value)); + } + + public string FilePath + { + get => _filePath; + set => ChangeProjectProperty(ref _filePath, + value, + s => s.WithProjectFilePath(Id, value), + w => w.OnProjectNameChanged(Id, _displayName, value)); + } + + public string DisplayName + { + get => _displayName; + set => ChangeProjectProperty(ref _displayName, + value, + s => s.WithProjectName(Id, value), + w => w.OnProjectNameChanged(Id, value, _filePath)); + } + + // internal to match the visibility of the Workspace-level API -- this is something + // we use but we haven't made officially public yet. + internal bool HasAllInformation + { + get => _hasAllInformation; + set => ChangeProjectProperty(ref _hasAllInformation, + value, + s => s.WithHasAllInformation(Id, value), + w => w.OnHasAllInformationChanged(Id, value)); + } + + + #region Batching + + public BatchScope CreateBatchScope() + { + lock (_gate) + { + _activeBatchScopes++; + return new BatchScope(this); + } + } + + public sealed class BatchScope : IDisposable + { + private readonly VisualStudioProject _project; + + /// + /// Flag to control if this has already been disposed. Not a boolean only so it can be used with Interlocked.CompareExchange. + /// + private volatile int _disposed = 0; + + internal BatchScope(VisualStudioProject visualStudioProject) + { + _project = visualStudioProject; + } + + public void Dispose() + { + if (Interlocked.CompareExchange(ref _disposed, 1, 0) == 0) + { + _project.OnBatchScopeDisposed(); + } + } + } + + private void OnBatchScopeDisposed() + { + lock (_gate) + { + _activeBatchScopes--; + + if (_activeBatchScopes > 0) + { + return; + } + + var documentFileNamesAdded = ImmutableArray.CreateBuilder(); + var documentsToOpen = new List<(DocumentId, SourceTextContainer)>(); + + _workspace.ApplyBatchChangeToProject(Id, solution => + { + solution = _sourceFiles.UpdateSolutionForBatch( + solution, + documentFileNamesAdded, + documentsToOpen, + (s, documents) => solution.AddDocuments(documents), + (s, id) => + { + // Clear any document-specific data now (like open file trackers, etc.) + _workspace.ClearDocumentData(id); + return s.RemoveDocument(id); + }); + + solution = _additionalFiles.UpdateSolutionForBatch( + solution, + documentFileNamesAdded, + documentsToOpen, + (s, documents) => + { + foreach (var document in documents) + { + s = s.AddAdditionalDocument(document); + } + + return s; + }, + (s, id) => + { + // Clear any document-specific data now (like open file trackers, etc.) + _workspace.ClearDocumentData(id); + return s.RemoveAdditionalDocument(id); + }); + + // Metadata reference adding... + if (_metadataReferencesAddedInBatch.Count > 0) + { + var projectReferencesCreated = new List(); + var metadataReferencesCreated = new List(); + + foreach (var metadataReferenceAddedInBatch in _metadataReferencesAddedInBatch) + { + var projectReference = _workspace.TryCreateConvertedProjectReference(Id, metadataReferenceAddedInBatch.path, metadataReferenceAddedInBatch.properties); + + if (projectReference != null) + { + projectReferencesCreated.Add(projectReference); + } + else + { + metadataReferencesCreated.Add(_workspace.CreateMetadataReference(metadataReferenceAddedInBatch.path, metadataReferenceAddedInBatch.properties)); + } + } + + solution = solution.AddProjectReferences(Id, projectReferencesCreated) + .AddMetadataReferences(Id, metadataReferencesCreated); + + ClearAndZeroCapacity(_metadataReferencesAddedInBatch); + } + + // Metadata reference removing... + foreach (var metadataReferenceRemovedInBatch in _metadataReferencesRemovedInBatch) + { + var projectReference = _workspace.TryRemoveConvertedProjectReference(Id, metadataReferenceRemovedInBatch.path, metadataReferenceRemovedInBatch.properties); + + if (projectReference != null) + { + solution = solution.RemoveProjectReference(Id, projectReference); + } + else + { + // TODO: find a cleaner way to fetch this + var metadataReference = _workspace.CurrentSolution.GetProject(Id).MetadataReferences.Cast() + .Single(m => m.FilePath == metadataReferenceRemovedInBatch.path && m.Properties == metadataReferenceRemovedInBatch.properties); + + solution = solution.RemoveMetadataReference(Id, metadataReference); + } + } + + ClearAndZeroCapacity(_metadataReferencesRemovedInBatch); + + // Project reference adding... + solution = solution.AddProjectReferences(Id, _projectReferencesAddedInBatch); + ClearAndZeroCapacity(_projectReferencesAddedInBatch); + + // Project reference removing... + foreach (var projectReference in _projectReferencesRemovedInBatch) + { + solution = solution.RemoveProjectReference(Id, projectReference); + } + + ClearAndZeroCapacity(_projectReferencesRemovedInBatch); + + // Analyzer reference adding... + solution = solution.AddAnalyzerReferences(Id, _analyzersAddedInBatch.Select(a => a.GetReference())); + ClearAndZeroCapacity(_analyzersAddedInBatch); + + // Analyzer reference removing... + foreach (var analyzerReference in _analyzersRemovedInBatch) + { + solution = solution.RemoveAnalyzerReference(Id, analyzerReference.GetReference()); + } + + ClearAndZeroCapacity(_analyzersRemovedInBatch); + + // Other property modifications... + foreach (var propertyModification in _projectPropertyModificationsInBatch) + { + solution = propertyModification(solution); + } + + ClearAndZeroCapacity(_projectPropertyModificationsInBatch); + + return solution; + }); + + foreach (var (documentId, textContainer) in documentsToOpen) + { + _workspace.ApplyChangeToWorkspace(w => w.OnDocumentOpened(documentId, textContainer)); + } + + // Check for those files being opened to start wire-up if necessary + _workspace.CheckForOpenDocuments(documentFileNamesAdded.ToImmutable()); + } + } + + #endregion + + #region Source File Addition/Removal + + public void AddSourceFile(string fullPath, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular, ImmutableArray folders = default) + { + _sourceFiles.AddFile(fullPath, sourceCodeKind, folders); + } + + public DocumentId AddSourceTextContainer(SourceTextContainer textContainer, string fullPath, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular, ImmutableArray folders = default) + { + return _sourceFiles.AddTextContainer(textContainer, fullPath, sourceCodeKind, folders); + } + + public bool ContainsSourceFile(string fullPath) + { + return _sourceFiles.ContainsFile(fullPath); + } + + public void RemoveSourceFile(string fullPath) + { + _sourceFiles.RemoveFile(fullPath); + } + + public void RemoveSourceTextContainer(SourceTextContainer textContainer) + { + _sourceFiles.RemoveTextContainer(textContainer); + } + + #endregion + + #region Additional File Addition/Removal + + // TODO: should AdditionalFiles have source code kinds? + public void AddAdditionalFile(string fullPath, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular) + { + _additionalFiles.AddFile(fullPath, sourceCodeKind, folders: default); + } + + public bool ContainsAdditionalFile(string fullPath) + { + return _additionalFiles.ContainsFile(fullPath); + } + + public void RemoveAdditionalFile(string fullPath) + { + _additionalFiles.RemoveFile(fullPath); + } + + #endregion + + #region Analyzer Addition/Removal + + public void AddAnalyzerReference(string fullPath) + { + if (string.IsNullOrEmpty(fullPath)) + { + throw new ArgumentException("message", nameof(fullPath)); + } + + var visualStudioAnalyzer = new VisualStudioAnalyzer( + fullPath, + _hostDiagnosticUpdateSource, + Id, + _workspace, + Language); + + lock (_gate) + { + if (_analyzerPathsToAnalyzers.ContainsKey(fullPath)) + { + throw new ArgumentException($"'{fullPath}' has already been added to this project.", nameof(fullPath)); + } + + _analyzerPathsToAnalyzers.Add(fullPath, visualStudioAnalyzer); + + if (_activeBatchScopes > 0) + { + _analyzersAddedInBatch.Add(visualStudioAnalyzer); + } + else + { + _workspace.ApplyChangeToWorkspace(w => w.OnAnalyzerReferenceAdded(Id, visualStudioAnalyzer.GetReference())); + } + } + } + + public void RemoveAnalyzerReference(string fullPath) + { + if (string.IsNullOrEmpty(fullPath)) + { + throw new ArgumentException("message", nameof(fullPath)); + } + + lock (_gate) + { + if (!_analyzerPathsToAnalyzers.TryGetValue(fullPath, out var visualStudioAnalyzer)) + { + throw new ArgumentException($"'{fullPath}' is not an analyzer of this project.", nameof(fullPath)); + } + + _analyzerPathsToAnalyzers.Remove(fullPath); + + if (_activeBatchScopes > 0) + { + _analyzersRemovedInBatch.Add(visualStudioAnalyzer); + } + else + { + _workspace.ApplyChangeToWorkspace(w => w.OnAnalyzerReferenceRemoved(Id, visualStudioAnalyzer.GetReference())); + } + } + } + + #endregion + + private void FileChangeContext_FileChanged(object sender, string fullFilePath) + { + _sourceFiles.ProcessFileChange(fullFilePath); + _additionalFiles.ProcessFileChange(fullFilePath); + } + + #region Metadata Reference Addition/Removal + + public void AddMetadataReference(string fullPath, MetadataReferenceProperties properties) + { + if (string.IsNullOrEmpty(fullPath)) + { + throw new ArgumentException($"{nameof(fullPath)} isn't a valid path.", nameof(fullPath)); + } + + lock (_gate) + { + if (ContainsMetadataReference(fullPath, properties)) + { + throw new InvalidOperationException("The metadata reference has already been added to the project."); + } + + _allMetadataReferences.MultiAdd(fullPath, properties); + + if (_activeBatchScopes > 0) + { + if (!_metadataReferencesRemovedInBatch.Remove((fullPath, properties))) + { + _metadataReferencesAddedInBatch.Add((fullPath, properties)); + } + } + else + { + _workspace.ApplyChangeToWorkspace(w => + { + var projectReference = _workspace.TryCreateConvertedProjectReference(Id, fullPath, properties); + + if (projectReference != null) + { + w.OnProjectReferenceAdded(Id, projectReference); + } + else + { + w.OnMetadataReferenceAdded(Id, _workspace.CreateMetadataReference(fullPath, properties)); + } + }); + } + + } + } + + public bool ContainsMetadataReference(string fullPath, MetadataReferenceProperties properties) + { + lock (_gate) + { + return GetPropertiesForMetadataReference(fullPath).Contains(properties); + } + } + + /// + /// Returns the properties being used for the current metadata reference added to this project. May return multiple properties if + /// the reference has been added multiple times with different properties. + /// + public ImmutableArray GetPropertiesForMetadataReference(string fullPath) + { + lock (_gate) + { + _allMetadataReferences.TryGetValue(fullPath, out var list); + + // Note: AsImmutableOrEmpty accepts null recievers and treats that as an empty array + return list.AsImmutableOrEmpty(); + } + } + + public void RemoveMetadataReference(string fullPath, MetadataReferenceProperties properties) + { + if (string.IsNullOrEmpty(fullPath)) + { + throw new ArgumentException($"{nameof(fullPath)} isn't a valid path.", nameof(fullPath)); + } + + lock (_gate) + { + if (!ContainsMetadataReference(fullPath, properties)) + { + throw new InvalidOperationException("The metadata reference does not exist in this project."); + } + + _allMetadataReferences.MultiRemove(fullPath, properties); + + if (_activeBatchScopes > 0) + { + if (!_metadataReferencesAddedInBatch.Remove((fullPath, properties))) + { + _metadataReferencesRemovedInBatch.Add((fullPath, properties)); + } + } + else + { + _workspace.ApplyChangeToWorkspace(w => + { + var projectReference = _workspace.TryRemoveConvertedProjectReference(Id, fullPath, properties); + + // If this was converted to a project reference, we have now recorded the removal -- let's remove it here too + if (projectReference != null) + { + w.OnProjectReferenceRemoved(Id, projectReference); + } + else + { + // TODO: find a cleaner way to fetch this + var metadataReference = w.CurrentSolution.GetProject(Id).MetadataReferences.Cast() + .Single(m => m.FilePath == fullPath && m.Properties == properties); + + w.OnMetadataReferenceRemoved(Id, metadataReference); + } + }); + } + } + } + + #endregion + + #region Project Reference Addition/Removal + + public void AddProjectReference(ProjectReference projectReference) + { + if (projectReference == null) + { + throw new ArgumentNullException(nameof(projectReference)); + } + + lock (_gate) + { + if (ContainsProjectReference(projectReference)) + { + throw new ArgumentException("The project reference has already been added to the project."); + } + + if (_activeBatchScopes > 0) + { + if (!_projectReferencesRemovedInBatch.Remove(projectReference)) + { + _projectReferencesAddedInBatch.Add(projectReference); + } + } + else + { + _workspace.ApplyChangeToWorkspace(w => w.OnProjectReferenceAdded(Id, projectReference)); + } + } + } + + public bool ContainsProjectReference(ProjectReference projectReference) + { + if (projectReference == null) + { + throw new ArgumentNullException(nameof(projectReference)); + } + + lock (_gate) + { + if (_projectReferencesRemovedInBatch.Contains(projectReference)) + { + return false; + } + + if (_projectReferencesAddedInBatch.Contains(projectReference)) + { + return true; + } + + return _workspace.CurrentSolution.GetProject(Id).AllProjectReferences.Contains(projectReference); + } + } + + public IReadOnlyList GetProjectReferences() + { + lock (_gate) + { + // If we're not batching, then this is cheap: just fetch from the workspace and we're done + var projectReferencesInWorkspace = _workspace.CurrentSolution.GetProject(Id).AllProjectReferences; + + if (_activeBatchScopes == 0) + { + return projectReferencesInWorkspace; + } + + // Not, so we get to compute a new list instead + var newList = projectReferencesInWorkspace.ToList(); + newList.AddRange(_projectReferencesAddedInBatch); + newList.RemoveAll(p => _projectReferencesRemovedInBatch.Contains(p)); + + return newList; + } + } + + public void RemoveProjectReference(ProjectReference projectReference) + { + if (projectReference == null) + { + throw new ArgumentNullException(nameof(projectReference)); + } + + lock (_gate) + { + if (_activeBatchScopes > 0) + { + if (!_projectReferencesAddedInBatch.Remove(projectReference)) + { + _projectReferencesRemovedInBatch.Add(projectReference); + } + } + else + { + _workspace.ApplyChangeToWorkspace(w => w.OnProjectReferenceRemoved(Id, projectReference)); + } + } + } + + #endregion + + public void RemoveFromWorkspace() + { + _documentFileChangeContext.Dispose(); + + _workspace.ApplyChangeToWorkspace(w => w.OnProjectRemoved(Id)); + } + + /// + /// Adds an additional output path that can be used for automatic conversion of metadata references to P2P references. + /// Any projects with metadata references to the path given here will be converted to project-to-project references. + /// + public void AddOutputPath(string outputPath) + { + if (string.IsNullOrEmpty(outputPath)) + { + throw new ArgumentException($"{nameof(outputPath)} isn't a valid path.", nameof(outputPath)); + } + + _workspace.AddProjectOutputPath(Id, outputPath); + } + + /// + /// Removes an additional output path that was added by . + /// + public void RemoveOutputPath(string outputPath) + { + if (string.IsNullOrEmpty(outputPath)) + { + throw new ArgumentException($"{nameof(outputPath)} isn't a valid path.", nameof(outputPath)); + } + + _workspace.RemoveProjectOutputPath(Id, outputPath); + } + + /// + /// Clears a list and zeros out the capacity. The lists we use for batching are likely to get large during an initial load, but after + /// that point should never get that large again. + /// + private static void ClearAndZeroCapacity(List list) + { + list.Clear(); + list.Capacity = 0; + } + + /// + /// Clears a list and zeros out the capacity. The lists we use for batching are likely to get large during an initial load, but after + /// that point should never get that large again. + /// + private static void ClearAndZeroCapacity(ImmutableArray.Builder list) + { + list.Clear(); + list.Capacity = 0; + } + + /// + /// Helper class to manage collections of source-file like things; this exists just to avoid duplicating all the logic for regular source files + /// and additional files. + /// + /// This class should be free-threaded, and any synchronization is done via . + /// This class is otehrwise free to operate on private members of if needed. + private sealed class BatchingDocumentCollection + { + private readonly VisualStudioProject _project; + + /// + /// The map of file paths to the underlying . This document may exist in or has been + /// pushed to the actual workspace. + /// + private readonly Dictionary _documentPathsToDocumentIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// A map of explicitly-added "always open" and their associated . This does not contain + /// any regular files that have been open. + /// + private IBidirectionalMap _sourceTextContainersToDocumentIds = BidirectionalMap.Empty; + + /// + /// The current list of documents that are to be added in this batch. + /// + private readonly ImmutableArray.Builder _documentsAddedInBatch = ImmutableArray.CreateBuilder(); + + /// + /// The current list of documents that are being removed in this batch. Once the document is in this list, it is no longer in . + /// + private readonly List _documentsRemovedInBatch = new List(); + + private readonly Func _documentAlreadyInWorkspace; + private readonly Action _documentAddAction; + private readonly Action _documentRemoveAction; + + public BatchingDocumentCollection(VisualStudioProject project, + Func documentAlreadyInWorkspace, + Action documentAddAction, + Action documentRemoveAction) + { + _project = project; + _documentAlreadyInWorkspace = documentAlreadyInWorkspace; + _documentAddAction = documentAddAction; + _documentRemoveAction = documentRemoveAction; + } + + public DocumentId AddFile(string fullPath, SourceCodeKind sourceCodeKind, ImmutableArray folders) + { + if (string.IsNullOrEmpty(fullPath)) + { + throw new ArgumentException($"{nameof(fullPath)} isn't a valid path.", nameof(fullPath)); + } + + var documentId = DocumentId.CreateNewId(_project.Id, fullPath); + var textLoader = new FileTextLoader(fullPath, defaultEncoding: null); + var documentInfo = DocumentInfo.Create( + documentId, + FileNameUtilities.GetFileName(fullPath), + folders: folders.IsDefault ? null : (IEnumerable)folders, + sourceCodeKind: sourceCodeKind, + loader: textLoader, + filePath: fullPath); + + lock (_project._gate) + { + if (_documentPathsToDocumentIds.ContainsKey(fullPath)) + { + throw new ArgumentException($"'{fullPath}' has already been added to this project.", nameof(fullPath)); + } + + _documentPathsToDocumentIds.Add(fullPath, documentId); + _project._fileWatchingTokens.Add(documentId, _project._documentFileChangeContext.EnqueueWatchingFile(fullPath)); + + if (_project._activeBatchScopes > 0) + { + _documentsAddedInBatch.Add(documentInfo); + } + else + { + _project._workspace.ApplyChangeToWorkspace(w => _documentAddAction(w, documentInfo)); + _project._workspace.CheckForOpenDocuments(ImmutableArray.Create(fullPath)); + } + } + + return documentId; + } + + public DocumentId AddTextContainer(SourceTextContainer textContainer, string fullPath, SourceCodeKind sourceCodeKind, ImmutableArray folders) + { + if (textContainer == null) + { + throw new ArgumentNullException(nameof(textContainer)); + } + + var documentId = DocumentId.CreateNewId(_project.Id, fullPath); + var textLoader = new SourceTextLoader(textContainer, fullPath); + var documentInfo = DocumentInfo.Create( + documentId, + FileNameUtilities.GetFileName(fullPath), + folders: folders.IsDefault ? null : (IEnumerable)folders, + sourceCodeKind: sourceCodeKind, + loader: textLoader, + filePath: fullPath); + + lock (_project._gate) + { + if (_sourceTextContainersToDocumentIds.ContainsKey(textContainer)) + { + throw new ArgumentException($"{nameof(textContainer)} is already added to this project.", nameof(textContainer)); + } + + if (fullPath != null) + { + if (_documentPathsToDocumentIds.ContainsKey(fullPath)) + { + throw new ArgumentException($"'{fullPath}' has already been added to this project."); + } + + _documentPathsToDocumentIds.Add(fullPath, documentId); + } + + _sourceTextContainersToDocumentIds = _sourceTextContainersToDocumentIds.Add(textContainer, documentInfo.Id); + + if (_project._activeBatchScopes > 0) + { + _documentsAddedInBatch.Add(documentInfo); + } + else + { + _project._workspace.ApplyChangeToWorkspace(w => { + _project._workspace.AddDocumentToDocumentsNotFromFiles(documentInfo.Id); + _documentAddAction(w, documentInfo); + w.OnDocumentOpened(documentInfo.Id, textContainer); + }); + } + } + + return documentId; + } + + public void RemoveFile(string fullPath) + { + if (string.IsNullOrEmpty(fullPath)) + { + throw new ArgumentException($"{nameof(fullPath)} isn't a valid path.", nameof(fullPath)); + } + + lock (_project._gate) + { + if (!_documentPathsToDocumentIds.TryGetValue(fullPath, out var documentId)) + { + throw new ArgumentException($"'{fullPath}' is not a source file of this project."); + } + + _documentPathsToDocumentIds.Remove(fullPath); + + _project._documentFileChangeContext.StopWatchingFile(_project._fileWatchingTokens[documentId]); + _project._fileWatchingTokens.Remove(documentId); + + // There are two cases: + // + // 1. This file is actually been pushed to the workspace, and we need to remove it (either + // as a part of the active batch or immediately) + // 2. It hasn't been pushed yet, but is contained in _documentsAddedInBatch + if (_documentAlreadyInWorkspace(_project._workspace.CurrentSolution, documentId)) + { + if (_project._activeBatchScopes > 0) + { + _documentsRemovedInBatch.Add(documentId); + } + else + { + _project._workspace.ApplyChangeToWorkspace(w => _documentRemoveAction(w, documentId)); + } + } + else + { + for (int i = 0; i < _documentsAddedInBatch.Count; i++) + { + if (_documentsAddedInBatch[i].Id == documentId) + { + _documentsAddedInBatch.RemoveAt(i); + break; + } + } + } + } + } + + public void RemoveTextContainer(SourceTextContainer textContainer) + { + if (textContainer == null) + { + throw new ArgumentNullException(nameof(textContainer)); + } + + lock (_project._gate) + { + if (!_sourceTextContainersToDocumentIds.TryGetValue(textContainer, out var documentId)) + { + throw new ArgumentException($"{nameof(textContainer)} is not a text container added to this project."); + } + + // TODO: clean up the file name if one was added + + _sourceTextContainersToDocumentIds = _sourceTextContainersToDocumentIds.RemoveKey(textContainer); + + // There are two cases: + // + // 1. This file is actually been pushed to the workspace, and we need to remove it (either + // as a part of the active batch or immediately) + // 2. It hasn't been pushed yet, but is contained in _documentsAddedInBatch + if (_project._workspace.CurrentSolution.GetDocument(documentId) != null) + { + if (_project._activeBatchScopes > 0) + { + _documentsRemovedInBatch.Add(documentId); + } + else + { + _project._workspace.ApplyChangeToWorkspace(w => { + w.OnDocumentClosed(documentId, new SourceTextLoader(textContainer, filePath: null)); + _documentRemoveAction(w, documentId); + _project._workspace.RemoveDocumentToDocumentsNotFromFiles(documentId); + }); + } + } + else + { + for (int i = 0; i < _documentsAddedInBatch.Count; i++) + { + if (_documentsAddedInBatch[i].Id == documentId) + { + _documentsAddedInBatch.RemoveAt(i); + break; + } + } + } + } + } + + public bool ContainsFile(string fullPath) + { + if (string.IsNullOrEmpty(fullPath)) + { + throw new ArgumentException($"{nameof(fullPath)} isn't a valid path.", nameof(fullPath)); + } + + lock (_project._gate) + { + return _documentPathsToDocumentIds.ContainsKey(fullPath); + } + } + + public void ProcessFileChange(string fullFilePath) + { + lock (_project._gate) + { + if (_documentPathsToDocumentIds.TryGetValue(fullFilePath, out var documentId)) + { + // We create file watching prior to pushing the file to the workspace in batching, so it's + // possible we might see a file change notification early. In this case, toss it out. Since + // all adds/removals of documents for this project happen under our lock, it's safe to do this + // check without taking the main workspace lock + var document = _project._workspace.CurrentSolution.GetDocument(documentId); + + if (document == null) + { + return; + } + + _project._workspace.ApplyChangeToWorkspace(w => + { + if (w.IsDocumentOpen(documentId)) + { + return; + } + + var documentInfo = + DocumentInfo.Create( + document.Id, + document.Name, + document.Folders, + document.SourceCodeKind, + new FileTextLoader(fullFilePath, defaultEncoding: null), + document.FilePath); + + w.OnDocumentReloaded(documentInfo); + }); + } + } + } + + internal Solution UpdateSolutionForBatch( + Solution solution, + ImmutableArray.Builder documentFileNamesAdded, + List<(DocumentId, SourceTextContainer)> documentsToOpen, + Func, Solution> addDocuments, + Func removeDocument) + { + // Document adding... + solution = addDocuments(solution, _documentsAddedInBatch.ToImmutable()); + + foreach (var documentInfo in _documentsAddedInBatch) + { + documentFileNamesAdded.Add(documentInfo.FilePath); + + if (_sourceTextContainersToDocumentIds.TryGetKey(documentInfo.Id, out var textContainer)) + { + documentsToOpen.Add((documentInfo.Id, textContainer)); + } + } + + ClearAndZeroCapacity(_documentsAddedInBatch); + + // Document removing... + foreach (var documentId in _documentsRemovedInBatch) + { + solution = removeDocument(solution, documentId); + } + + ClearAndZeroCapacity(_documentsRemovedInBatch); + + return solution; + } + + private sealed class SourceTextLoader : TextLoader + { + private readonly SourceTextContainer _textContainer; + private readonly string _filePath; + + public SourceTextLoader(SourceTextContainer textContainer, string filePath) + { + _textContainer = textContainer; + _filePath = filePath; + } + + public override Task LoadTextAndVersionAsync(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken) + { + return Task.FromResult(TextAndVersion.Create(_textContainer.CurrentText, VersionStamp.Create(), _filePath)); + } + } + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectCreationInfo.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectCreationInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..85a260b732686a622eaa9a40102326a5fb6ebab3 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectCreationInfo.cs @@ -0,0 +1,17 @@ +using System; +using Microsoft.CodeAnalysis; +using Microsoft.VisualStudio.Shell.Interop; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +{ + internal sealed class VisualStudioProjectCreationInfo + { + public string AssemblyName { get; set; } + public CompilationOptions CompilationOptions { get; set; } + public string FilePath { get; set; } + public ParseOptions ParseOptions { get; set; } + + public IVsHierarchy Hierarchy { get; set; } + public Guid ProjectGuid { get; set; } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectFactory.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..84716648363f3c071408f0ce545ac8bf1bca52d8 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectFactory.cs @@ -0,0 +1,95 @@ +using System.ComponentModel.Composition; +using System.IO; +using Microsoft.CodeAnalysis; +using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; +using Microsoft.VisualStudio.Shell.Interop; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +{ + [Export(typeof(VisualStudioProjectFactory))] + internal sealed class VisualStudioProjectFactory + { + private readonly VisualStudioWorkspaceImpl _visualStudioWorkspaceImpl; + private readonly HostDiagnosticUpdateSource _hostDiagnosticUpdateSource; + + [ImportingConstructor] + // TODO: remove the AllowDefault = true on HostDiagnosticUpdateSource by making it a proper mock + public VisualStudioProjectFactory(VisualStudioWorkspaceImpl visualStudioWorkspaceImpl, [Import(AllowDefault = true)] HostDiagnosticUpdateSource hostDiagnosticUpdateSource) + { + _visualStudioWorkspaceImpl = visualStudioWorkspaceImpl; + _hostDiagnosticUpdateSource = hostDiagnosticUpdateSource; + } + + public VisualStudioProject CreateAndAddToWorkspace(string projectUniqueName, string language) + { + return CreateAndAddToWorkspace(projectUniqueName, language, new VisualStudioProjectCreationInfo()); + } + + public VisualStudioProject CreateAndAddToWorkspace(string projectUniqueName, string language, VisualStudioProjectCreationInfo creationInfo) + { + // HACK: Fetch this service to ensure it's still created on the UI thread; once this is moved off we'll need to fix up it's constructor to be free-threaded. + _visualStudioWorkspaceImpl.Services.GetRequiredService(); + + var id = ProjectId.CreateNewId(projectUniqueName); + var directoryNameOpt = creationInfo.FilePath != null ? Path.GetDirectoryName(creationInfo.FilePath) : null; + var project = new VisualStudioProject(_visualStudioWorkspaceImpl, _hostDiagnosticUpdateSource, id, projectUniqueName, language, directoryNameOpt); + + var versionStamp = creationInfo.FilePath != null ? VersionStamp.Create(File.GetLastWriteTimeUtc(creationInfo.FilePath)) + : VersionStamp.Create(); + + var assemblyName = creationInfo.AssemblyName ?? projectUniqueName; + + _visualStudioWorkspaceImpl.AddProjectToInternalMaps(project, creationInfo.Hierarchy, creationInfo.ProjectGuid, projectUniqueName); + + _visualStudioWorkspaceImpl.ApplyChangeToWorkspace(w => + { + var projectInfo = ProjectInfo.Create( + id, + versionStamp, + name: projectUniqueName, + assemblyName: assemblyName, + language: language, + filePath: creationInfo.FilePath, + compilationOptions: creationInfo.CompilationOptions, + parseOptions: creationInfo.ParseOptions); + + // HACK: update this since we're still on the UI thread. Note we can only update this if we don't have projects -- the workspace + // only lets us really do this with OnSolutionAdded for now. + string solutionPathToSetWithOnSolutionAdded = null; + var solution = (IVsSolution)Shell.ServiceProvider.GlobalProvider.GetService(typeof(SVsSolution)); + if (solution != null && ErrorHandler.Succeeded(solution.GetSolutionInfo(out _, out var solutionFilePath, out _))) + { + if (w.CurrentSolution.FilePath != solutionFilePath && w.CurrentSolution.ProjectIds.Count == 0) + { + solutionPathToSetWithOnSolutionAdded = solutionFilePath; + } + } + + if (solutionPathToSetWithOnSolutionAdded != null) + { + w.OnSolutionAdded( + SolutionInfo.Create( + SolutionId.CreateNewId(solutionPathToSetWithOnSolutionAdded), + VersionStamp.Create(), + solutionPathToSetWithOnSolutionAdded, + projects: new[] { projectInfo })); + } + else + { + w.OnProjectAdded(projectInfo); + } + }); + + // We do all these sets after the w.OnProjectAdded, as the setting of these properties is going to try to modify the workspace + // again. Those modifications will all implicitly do nothing, since the workspace already has the values from above. + // We could pass these all through the constructor (but that gets verbose), or have some other control to ignore these, + // but that seems like overkill. + project.AssemblyName = assemblyName; + project.CompilationOptions = creationInfo.CompilationOptions; + project.FilePath = creationInfo.FilePath; + project.ParseOptions = creationInfo.ParseOptions; + + return project; + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectManagementService.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectManagementService.cs index 688eb22b3c3ebdbcc3c0474431309d603ed9afdb..565c126fb5157423f4413e1bf640eb6cc332354a 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectManagementService.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectManagementService.cs @@ -39,8 +39,8 @@ public string GetDefaultNamespace(Microsoft.CodeAnalysis.Project project, Worksp if (workspace is VisualStudioWorkspaceImpl vsWorkspace) { - vsWorkspace.GetProjectData(project.Id, - out var ivisualStudioHostProject, out var hierarchy, out var envDTEProject); + vsWorkspace.GetProjectData(project.Id, + out var hierarchy, out var envDTEProject); try { @@ -61,8 +61,8 @@ public IList GetFolders(ProjectId projectId, Workspace workspace) if (workspace is VisualStudioWorkspaceImpl vsWorkspace) { - vsWorkspace.GetProjectData(projectId, - out var ivisualStudioHostProject, out var hierarchy, out var envDTEProject); + vsWorkspace.GetProjectData(projectId, + out var hierarchy, out var envDTEProject); var projectItems = envDTEProject.ProjectItems; diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectOptionsProcessor.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectOptionsProcessor.cs new file mode 100644 index 0000000000000000000000000000000000000000..b956f1a1cdfeccd8a792a92b312abcd7691b3d5b --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectOptionsProcessor.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Immutable; +using System.IO; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Host; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +{ + internal class VisualStudioProjectOptionsProcessor : IDisposable + { + private readonly VisualStudioProject _project; + private readonly HostWorkspaceServices _workspaceServices; + private readonly ICommandLineParserService _commandLineParserService; + + /// + /// Gate to guard all mutable fields in this class. + /// The lock hierarchy means you are allowed to call out of this class and into while holding the lock. + /// + private object _gate = new object(); + private string _commandLine = ""; + private CommandLineArguments _commandLineArgumentsForCommandLine; + private string _explicitRuleSetFilePath; + private IReferenceCountedDisposable _ruleSetFile = null; + + public VisualStudioProjectOptionsProcessor(VisualStudioProject project, HostWorkspaceServices workspaceServices) + { + _project = project ?? throw new ArgumentNullException(nameof(project)); + _workspaceServices = workspaceServices; + _commandLineParserService = workspaceServices.GetLanguageServices(project.Language).GetRequiredService(); + + // Set up _commandLineArgumentsForCommandLine to a default. No lock taken since we're in the constructor so nothing can race. + ReparseCommandLine_NoLock(); + } + + public string CommandLine + { + get + { + return _commandLine; + } + + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + lock (_gate) + { + if (_commandLine == value) + { + return; + } + + _commandLine = value; + + ReparseCommandLine_NoLock(); + UpdateProjectOptions_NoLock(); + } + } + } + + public string ExplicitRuleSetFilePath + { + get => _explicitRuleSetFilePath; + + set + { + lock (_gate) + { + if (_explicitRuleSetFilePath == value) + { + return; + } + + _explicitRuleSetFilePath = value; + + UpdateProjectOptions_NoLock(); + } + } + } + + public HostWorkspaceServices WorkspaceServices => _workspaceServices; + + private void ReparseCommandLine_NoLock() + { + var arguments = CommandLineParser.SplitCommandLineIntoArguments(_commandLine, removeHashComments: false); + _commandLineArgumentsForCommandLine = _commandLineParserService.Parse(arguments, Path.GetDirectoryName(_project.FilePath), isInteractive: false, sdkDirectory: null); + } + + private void UpdateProjectOptions_NoLock() + { + var effectiveRuleSetPath = ExplicitRuleSetFilePath ?? _commandLineArgumentsForCommandLine.RuleSetPath; + + if (_ruleSetFile?.Target.FilePath != effectiveRuleSetPath) + { + // We're changing in some way. Be careful: this might mean the path is switching to or from null, so either side so far + // could be changed. + _ruleSetFile?.Dispose(); + _ruleSetFile = null; + + if (effectiveRuleSetPath != null) + { + _ruleSetFile = _workspaceServices.GetRequiredService().GetOrCreateRuleSet(effectiveRuleSetPath); + } + } + + // TODO: #r support, should it include bin path? + var referenceSearchPaths = ImmutableArray.Empty; + + // TODO: #load support + var sourceSearchPaths = ImmutableArray.Empty; + + var referenceResolver = new WorkspaceMetadataFileReferenceResolver( + WorkspaceServices.GetRequiredService(), + new RelativePathResolver(referenceSearchPaths, _commandLineArgumentsForCommandLine.BaseDirectory)); + + var compilationOptions =_commandLineArgumentsForCommandLine.CompilationOptions + .WithConcurrentBuild(concurrent: false) + .WithMetadataReferenceResolver(referenceResolver) + .WithXmlReferenceResolver(new XmlFileResolver(_commandLineArgumentsForCommandLine.BaseDirectory)) + .WithSourceReferenceResolver(new SourceFileResolver(sourceSearchPaths, _commandLineArgumentsForCommandLine.BaseDirectory)) + .WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default) + .WithStrongNameProvider(new DesktopStrongNameProvider(_commandLineArgumentsForCommandLine.KeyFileSearchPaths.WhereNotNull().ToImmutableArray())); + + // Override the default documentation mode. + var documentationMode = _commandLineArgumentsForCommandLine.DocumentationPath != null ? DocumentationMode.Diagnose : DocumentationMode.Parse; + var parseOptions = _commandLineArgumentsForCommandLine.ParseOptions + .WithDocumentationMode(documentationMode); + + // We've computed what the base values should be; we now give an opportunity for any host-specific settings to be computed + // before we apply them + compilationOptions = ComputeCompilationOptionsWithHostValues(compilationOptions, this._ruleSetFile?.Target); + parseOptions = ComputeParseOptionsWithHostValues(parseOptions); + + // For managed projects, AssemblyName has to be non-null, but the command line we get might be a partial command line + // and not contain the existing value. Only update if we have one. + _project.AssemblyName = _commandLineArgumentsForCommandLine.CompilationName ?? _project.AssemblyName; + _project.CompilationOptions = compilationOptions; + + string fullIntermediateOutputPath = _commandLineArgumentsForCommandLine.OutputDirectory != null && _commandLineArgumentsForCommandLine.OutputFileName != null + ? Path.Combine(_commandLineArgumentsForCommandLine.OutputDirectory, _commandLineArgumentsForCommandLine.OutputFileName) + : _commandLineArgumentsForCommandLine.OutputFileName; + + _project.IntermediateOutputFilePath = fullIntermediateOutputPath ?? _project.IntermediateOutputFilePath; + _project.ParseOptions = parseOptions; + } + + /// + /// Overridden by derived classes to provide a hook to modify a with any host-provided values that didn't come from + /// the command line string. + /// + protected virtual CompilationOptions ComputeCompilationOptionsWithHostValues(CompilationOptions compilationOptions, IRuleSetFile ruleSetFileOpt) + { + return compilationOptions; + } + + /// + /// Override by derived classes to provide a hook to modify a with any host-provided values that didn't come from + /// the command line string. + /// + protected virtual ParseOptions ComputeParseOptionsWithHostValues(ParseOptions parseOptions) + { + return parseOptions; + } + + /// + /// Called by a derived class to notify that we need to update the settings in the project system for something that will be provided + /// by either or . + /// + protected void UpdateProjectForNewHostValues() + { + lock (_gate) + { + UpdateProjectOptions_NoLock(); + } + } + + public void Dispose() + { + _ruleSetFile?.Dispose(); + _ruleSetFile = null; + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.cs index 020922745113db3a58bb19162a3edf78270a9e66..b031f8b85adc72b50ac26b84ae6d3cd7ee0edf4d 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.cs @@ -3,658 +3,136 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.IO; using System.Linq; -using System.Runtime.CompilerServices; -using System.Windows.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Notification; -using Microsoft.CodeAnalysis.Text; -using Microsoft.Internal.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.ComponentModelHost; -using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Extensions; -using Microsoft.VisualStudio.LanguageServices.Storage; -using Microsoft.VisualStudio.Shell.Interop; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem { - internal sealed partial class VisualStudioProjectTracker : ForegroundThreadAffinitizedObject + internal sealed partial class VisualStudioProjectTracker { - #region Readonly fields - - /// - /// The underlying workspace. This is an instance of in Visual Studio, but - /// of TestWorkpace if we're in tests. Type checking should be avoided for the most part. - /// private readonly Workspace _workspace; - private readonly LinkedFileUtilities _linkedFileUtilities; - private readonly IServiceProvider _serviceProvider; - private readonly IVsSolution _vsSolution; - private readonly IVsRunningDocumentTable4 _runningDocumentTable; - private readonly object _gate = new object(); - #endregion - - #region Mutable fields accessed only from foreground thread - don't need locking for access (all accessing methods must have AssertIsForeground). - - /// - /// Set to true while we're batching project loads. That is, between - /// and - /// . - /// - private bool _batchingProjectLoads = false; - - /// - /// The list of projects loaded in this batch between and - /// . - /// - private readonly List _projectsLoadedThisBatch = new List(); - - /// - /// Set to true while the solution is in the process of closing. That is, between - /// and . - /// - private bool _solutionIsClosing = false; - - /// - /// Set to true once the solution has already been completely loaded and all future changes - /// should be pushed immediately to the workspace hosts. - /// - private bool _solutionLoadComplete = false; + private readonly VisualStudioProjectFactory _projectFactory; + internal IThreadingContext ThreadingContext { get; } - /// - /// Set to true if we've already called . Set to false after the solution has closed. - /// - private bool _solutionAdded; + internal ImmutableArray ImmutableProjects => ImmutableArray.Empty; - /// - /// The projects that have already been added to the workspace. - /// - private readonly HashSet _pushedProjects = new HashSet(); + internal HostWorkspaceServices WorkspaceServices => _workspace.Services; - #endregion + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] + private readonly Dictionary _typeScriptProjects = new Dictionary(); - #region Mutable fields accessed from foreground or background threads - need locking for access. - /// - /// This is a multi-map, only so we don't have any edge cases if people have two projects with - /// the same output path. It makes state tracking notably easier. - /// - private readonly Dictionary> _projectsByBinPath = new Dictionary>(StringComparer.OrdinalIgnoreCase); - - private readonly Dictionary _projectMap; - private readonly Dictionary _projectPathToIdMap; - #endregion - - internal LinkedFileUtilities LinkedFileUtilities => _linkedFileUtilities; - - /// - /// Provided to not break CodeLens which has a dependency on this API until there is a - /// public release which calls . Once there is, we should - /// change this back to returning , and - /// Obsolete instead, and then remove that after a - /// second public release. - /// - [Obsolete("Use '" + nameof(ImmutableProjects) + "' instead.", true)] - internal IEnumerable Projects => ImmutableProjects; - - internal ImmutableArray ImmutableProjects + public VisualStudioProjectTracker(Workspace workspace, VisualStudioProjectFactory projectFactory, IThreadingContext threadingContext) { - get - { - lock (_gate) - { - return _projectMap.Values.ToImmutableArray(); - } - } - } - - internal HostWorkspaceServices WorkspaceServices { get; } - - internal void NotifyNonDocumentOpenedForProject(AbstractProject project) - { - AssertIsForeground(); - - var abstractProject = (AbstractProject)project; - StartPushingToWorkspaceAndNotifyOfOpenDocuments(SpecializedCollections.SingletonEnumerable(abstractProject)); - } - - public VisualStudioProjectTracker(IThreadingContext threadingContext, IServiceProvider serviceProvider, Workspace workspace, LinkedFileUtilities linkedFileUtilities) - : base(threadingContext, assertIsForeground: true) - { - _projectMap = new Dictionary(); - _projectPathToIdMap = new Dictionary(StringComparer.OrdinalIgnoreCase); - - _serviceProvider = serviceProvider; _workspace = workspace; - WorkspaceServices = workspace.Services; - _linkedFileUtilities = linkedFileUtilities; - - _vsSolution = (IVsSolution)serviceProvider.GetService(typeof(SVsSolution)); - _runningDocumentTable = (IVsRunningDocumentTable4)serviceProvider.GetService(typeof(SVsRunningDocumentTable)); - - // It's possible that we're loading after the solution has already fully loaded, so see if we missed the event - var shellMonitorSelection = (IVsMonitorSelection)serviceProvider.GetService(typeof(SVsShellMonitorSelection)); - if (ErrorHandler.Succeeded(shellMonitorSelection.GetCmdUIContextCookie(VSConstants.UICONTEXT.SolutionExistsAndFullyLoaded_guid, out var fullyLoadedContextCookie))) - { - if (ErrorHandler.Succeeded(shellMonitorSelection.IsCmdUIContextActive(fullyLoadedContextCookie, out var fActive)) && fActive != 0) - { - _solutionLoadComplete = true; - } - } - } - - public void InitializeProviders(DocumentProvider documentProvider, VisualStudioMetadataReferenceManager metadataReferenceProvider, VisualStudioRuleSetManager ruleSetFileProvider) - { - AssertIsForeground(); - - Contract.ThrowIfFalse(DocumentProvider == null); - Contract.ThrowIfFalse(MetadataReferenceProvider == null); - Contract.ThrowIfFalse(RuleSetFileManager == null); - - DocumentProvider = documentProvider; - MetadataReferenceProvider = metadataReferenceProvider; - RuleSetFileManager = ruleSetFileProvider; + _projectFactory = projectFactory; + ThreadingContext = threadingContext; } - public DocumentProvider DocumentProvider { get; private set; } - public VisualStudioMetadataReferenceManager MetadataReferenceProvider { get; private set; } - public VisualStudioRuleSetManager RuleSetFileManager { get; private set; } - - internal AbstractProject GetProject(ProjectId id) - { - lock (_gate) - { - _projectMap.TryGetValue(id, out var project); - return project; - } - } + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] + public DocumentProvider DocumentProvider => new DocumentProvider(); - internal bool ContainsProject(AbstractProject project) - { - lock (_gate) - { - return _projectMap.ContainsKey(project.Id); - } - } - - /// - /// Add a project to the workspace. - /// - /// This method must be called on the foreground thread. - internal void AddProject(AbstractProject project) + /* + + private void FinishLoad() { - AssertIsForeground(); - - lock (_gate) - { - _projectMap.Add(project.Id, project); - } - - // UpdateProjectBinPath is defensively executed on the foreground thread as it calls back into referencing projects to perform metadata to P2P reference conversions. - UpdateProjectBinPath(project, null, project.BinOutputPath); - - if (_solutionLoadComplete) - { - StartPushingToWorkspaceAndNotifyOfOpenDocuments(SpecializedCollections.SingletonEnumerable(project)); - } - else if (_batchingProjectLoads) - { - _projectsLoadedThisBatch.Add(project); - } + // Check that the set of analyzers is complete and consistent. + GetAnalyzerDependencyCheckingService()?.ReanalyzeSolutionForConflicts(); } - /// - /// Starts pushing events from the given projects to the workspace hosts and notifies about open documents. - /// - /// This method must be called on the foreground thread. - internal void StartPushingToWorkspaceAndNotifyOfOpenDocuments(IEnumerable projects) + private AnalyzerDependencyCheckingService GetAnalyzerDependencyCheckingService() { - AssertIsForeground(); - - // If the solution is closing we shouldn't do anything, because all of our state is - // in the process of going away. This can happen if we receive notification that a document has - // opened in the middle of the solution close operation. - if (_solutionIsClosing) - { - return; - } - - // We need to push these projects and any project dependencies we already know about. Therefore, compute the - // transitive closure of the projects that haven't already been pushed, keeping them in appropriate order. - var visited = new HashSet(); - var inOrderToPush = new List(); - - void addToInOrderToPush(AbstractProject project) - { - Contract.ThrowIfFalse(ContainsProject(project)); - - // Bail out if any of the following is true: - // 1. We have already started pushing changes for this project OR - // 2. We have already visited this project in a prior recursive call - if (_pushedProjects.Contains(project) || !visited.Add(project)) - { - return; - } - - foreach (var projectReference in project.GetCurrentProjectReferences()) - { - addToInOrderToPush(GetProject(projectReference.ProjectId)); - } - - inOrderToPush.Add(project); - } - - foreach (var project in projects) - { - addToInOrderToPush(project); - } - - var projectInfos = inOrderToPush.Select(p => p.CreateProjectInfoForCurrentState()).ToImmutableArray(); - - // We need to enable projects to start pushing changes to the workspace even before we add the solution/project to the host. - // This is required because between the point we capture the project info for current state and the point where we start pushing to the workspace, - // project system may send new events on the AbstractProject on a background thread, and these won't get pushed over to the workspace hosts as we didn't set the _pushingChangesToWorkspaceHost flag on the AbstractProject. - // By invoking StartPushingToWorkspaceHosts upfront, any project state changes on the background thread will enqueue notifications to workspace hosts on foreground scheduled tasks. - foreach (var project in inOrderToPush) - { - project.PushingChangesToWorkspace = true; - - Logger.Log(FunctionId.AbstractProject_PushedToWorkspace, - KeyValueLogMessage.Create(LogType.Trace, m => - { - m[AbstractProject.ProjectGuidPropertyName] = project.Guid; - })); - } - - using (WorkspaceServices.GetService()?.Start("Add Project to Workspace")) - { - if (!_solutionAdded) - { - string solutionFilePath = null; - VersionStamp? version = default; - // Figure out the solution version - if (ErrorHandler.Succeeded(_vsSolution.GetSolutionInfo(out var solutionDirectory, out var solutionFileName, out var userOptsFile)) && solutionFileName != null) - { - solutionFilePath = Path.Combine(solutionDirectory, solutionFileName); - if (File.Exists(solutionFilePath)) - { - version = VersionStamp.Create(File.GetLastWriteTimeUtc(solutionFilePath)); - } - } - - if (version == null) - { - version = VersionStamp.Create(); - } - - var id = SolutionId.CreateNewId(string.IsNullOrWhiteSpace(solutionFileName) ? null : solutionFileName); - - var solutionInfo = SolutionInfo.Create(id, version.Value, solutionFilePath, projects: projectInfos); - - NotifyWorkspace(workspace => workspace.OnSolutionAdded(solutionInfo)); - - _solutionAdded = true; - - var persistenceService = WorkspaceServices.GetRequiredService() as VisualStudioPersistentStorageLocationService; - persistenceService?.UpdateForVisualStudioWorkspace(_workspace); - - } - else - { - // The solution is already added, so we'll just do project added notifications from here - foreach (var projectInfo in projectInfos) - { - NotifyWorkspace(workspace => workspace.OnProjectAdded(projectInfo)); - } - } - - foreach (var project in inOrderToPush) - { - _pushedProjects.Add(project); + var componentModel = (IComponentModel)_serviceProvider.GetService(typeof(SComponentModel)); - foreach (var document in project.GetCurrentDocuments()) - { - if (document.IsOpen) - { - NotifyWorkspace(workspace => - { - workspace.OnDocumentOpened( - document.Id, - document.GetOpenTextBuffer().AsTextContainer(), - isCurrentContext: _linkedFileUtilities.IsCurrentContextHierarchy(document, _runningDocumentTable)); - (workspace as VisualStudioWorkspaceImpl)?.ConnectToSharedHierarchyEvents(document); - }); - } - } - } - } + return componentModel.GetService(); } - /// - /// Remove a project from the workspace. - /// - internal void RemoveProject(AbstractProject project) - { - AssertIsForeground(); - - lock (_gate) - { - Contract.ThrowIfFalse(_projectMap.Remove(project.Id)); - } - - UpdateProjectBinPath(project, project.BinOutputPath, null); - - if (_pushedProjects.Contains(project)) - { - NotifyWorkspace(workspace => workspace.OnProjectRemoved(project.Id)); - - _pushedProjects.Remove(project); - } - } + */ - /// - /// Updates the project tracker and referencing projects for binary output path change for the given project. - /// - internal void UpdateProjectBinPath(AbstractProject project, string oldBinPathOpt, string newBinPathOpt) + public ProjectId GetOrCreateProjectIdForPath(string filePath, string projectDisplayName) { - // UpdateProjectBinPath is defensively executed on the foreground thread as it calls back into referencing projects to perform metadata to P2P reference conversions. - AssertIsForeground(); - - if (oldBinPathOpt != null) - { - UpdateReferencesForBinPathChange(oldBinPathOpt, () => RemoveProjectByBinPath(oldBinPathOpt, project)); - } - - if (newBinPathOpt != null) - { - UpdateReferencesForBinPathChange(newBinPathOpt, () => AddProjectByBinPath(newBinPathOpt, project)); - } + // HACK: to keep F# working, we will ensure we return the ProjectId if there is a project that matches this path. Otherwise, we'll just return + // a random ProjectId, which is sufficient for their needs. They'll simply observe there is no project with that ID, and then go and create a + // new project. Then they call this function again, and fetch the real ID. + return _workspace.CurrentSolution.Projects.FirstOrDefault(p => p.FilePath == filePath)?.Id ?? ProjectId.CreateNewId("ProjectNotFound"); } - private void UpdateReferencesForBinPathChange(string path, Action updateProjects) + [Obsolete("This is a compatibility shim for TypeScript and F#; please do not use it.")] + public AbstractProject GetProject(ProjectId projectId) { - AssertIsForeground(); - // If we already have a single project that points to this path, we'll either be: - // - // (1) removing it, where it no longer exists, or - // (2) adding another path, where it's now ambiguous - // - // in either case, we want to undo file-to-P2P reference conversion - - if (TryGetProjectsByBinPath(path, out var existingProjects)) + // HACK: if we have a TypeScript project, they expect to return the real thing deriving from AbstractProject + if (_typeScriptProjects.TryGetValue(projectId, out var typeScriptProject)) { - if (existingProjects.Length == 1) - { - foreach (var projectToUpdate in ImmutableProjects) - { - projectToUpdate.UndoProjectReferenceConversionForDisappearingOutputPath(path); - } - } + return typeScriptProject; } - updateProjects(); + // HACK: to keep F# working, we will ensure that if there is a project with that ID, we will return a non-null value, otherwise we'll return null. + // It doesn't actually matter *what* the project is, so we'll just return something silly + var project = _workspace.CurrentSolution.GetProject(projectId); - if (TryGetProjectsByBinPath(path, out existingProjects)) + if (project != null) { - if (existingProjects.Length == 1) - { - foreach (var projectToUpdate in ImmutableProjects) - { - projectToUpdate.TryProjectConversionForIntroducedOutputPath(path, existingProjects[0]); - } - } + return new StubProject(this, project); } - } - - /// - /// Gets or creates a project ID for the given project file path and display name. - /// - /// This method may be called on a background thread. - internal ProjectId GetOrCreateProjectIdForPath(string projectPath, string projectSystemName) - { - lock (_gate) + else { - string key = projectPath + projectSystemName; - if (!_projectPathToIdMap.TryGetValue(key, out var id)) - { - id = ProjectId.CreateNewId(debugName: projectPath); - _projectPathToIdMap[key] = id; - } - - return id; + return null; } } - /// - /// Notifies the of the change with the appropriate threading handling. - /// - /// This method must be called on the foreground thread. - internal void NotifyWorkspace(Action action) - { - AssertIsForeground(); - - // We do not want to allow message pumping/reentrancy when processing project system changes. - using (Dispatcher.CurrentDispatcher.DisableProcessing()) - { - action(_workspace); - } - } - - /// - /// Attempts to get single project by given output binary filePath. - /// - /// This method may be called on a background thread. internal bool TryGetProjectByBinPath(string filePath, out AbstractProject project) { - lock (_gate) - { - project = null; - if (!_projectsByBinPath.TryGetValue(filePath, out var projects)) - { - // Workaround https://github.com/dotnet/roslyn/issues/20412 by checking to see if */ref/A.dll can be - // adjusted to */A.dll - only handles the default location for reference assemblies during a build. - if (!HACK_StripRefDirectoryFromPath(filePath, out string binFilePath) - || !_projectsByBinPath.TryGetValue(binFilePath, out projects)) - { - return false; - } - } - - // If for some reason we have more than one referencing project, it's ambiguous so bail - if (projects.Length == 1) - { - project = projects[0]; - return true; - } - - return false; - } - } - - private static readonly char[] s_directorySeparatorChars = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; - - private static bool HACK_StripRefDirectoryFromPath(string filePath, out string binFilePath) - { - const string refDirectoryName = "ref"; + var projectsWithBinPath = _workspace.CurrentSolution.Projects.Where(p => string.Equals(p.OutputFilePath, filePath, StringComparison.OrdinalIgnoreCase)).ToList(); - // looking for "/ref/" where: - // 1. the first / is a directory separator - // 2. 'ref' matches in a case-insensitive comparison - // 3. the second / is the last directory separator - var lastSeparator = filePath.LastIndexOfAny(s_directorySeparatorChars); - var secondToLastSeparator = lastSeparator - refDirectoryName.Length - 1; - if (secondToLastSeparator < 0) + if (projectsWithBinPath.Count == 1) { - // Failed condition 3 - binFilePath = null; - return false; - } - - if (filePath[secondToLastSeparator] != Path.DirectorySeparatorChar - && filePath[secondToLastSeparator] != Path.AltDirectorySeparatorChar) - { - // Failed condition 1 - binFilePath = null; - return false; + project = new StubProject(this, projectsWithBinPath[0]); + return true; } - - if (string.Compare(refDirectoryName, 0, filePath, secondToLastSeparator + 1, lastSeparator - secondToLastSeparator - 1, StringComparison.OrdinalIgnoreCase) != 0) + else { - // Failed condition 2 - binFilePath = null; - return false; - } - - binFilePath = filePath.Remove(secondToLastSeparator, lastSeparator - secondToLastSeparator); - return true; - } - - /// - /// Attempts to get the projects by given output binary filePath. - /// - /// This method may be called on a background thread. - internal bool TryGetProjectsByBinPath(string filePath, out ImmutableArray projects) - { - lock (_gate) - { - if (_projectsByBinPath.TryGetValue(filePath, out projects)) - { - return true; - } - - projects = ImmutableArray.Empty; + project = null; return false; } } - internal void AddProjectByBinPath(string filePath, AbstractProject project) - { - lock (_gate) - { - if (!_projectsByBinPath.TryGetValue(filePath, out var projects)) - { - projects = ImmutableArray.Empty; - } - - _projectsByBinPath[filePath] = projects.Add(project); - } - } - - internal void RemoveProjectByBinPath(string filePath, AbstractProject project) - { - lock (_gate) - { - if (_projectsByBinPath.TryGetValue(filePath, out var projects) && projects.Contains(project)) - { - if (projects.Length == 1) - { - _projectsByBinPath.Remove(filePath); - } - else - { - _projectsByBinPath[filePath] = projects.Remove(project); - } - } - } - } - - public void OnBeforeCloseSolution() + private sealed class StubProject : AbstractProject { - AssertIsForeground(); + private readonly ProjectId _id; - _solutionIsClosing = true; - - foreach (var p in this.ImmutableProjects) + public StubProject(VisualStudioProjectTracker projectTracker, Project project) + : base(projectTracker, null, project.Name + "_Stub", project.FilePath, null, project.Language, Guid.Empty, null, null, null, null) { - p.PushingChangesToWorkspace = false; + _id = project.Id; } - _solutionLoadComplete = false; - } - - public void OnAfterCloseSolution() - { - AssertIsForeground(); - - lock (_gate) - { - Contract.ThrowIfFalse(_projectMap.Count == 0); - } - - NotifyWorkspace(host => host.OnSolutionRemoved()); - NotifyWorkspace(host => (host as VisualStudioWorkspaceImpl)?.ClearReferenceCache()); - - lock (_gate) - { - _projectPathToIdMap.Clear(); - } - - _solutionAdded = false; - _pushedProjects.Clear(); - - _solutionIsClosing = false; - } - - private void FinishLoad() - { - // We are now completely done, so let's simply ensure all projects are added. - StartPushingToWorkspaceAndNotifyOfOpenDocuments(this.ImmutableProjects); - - // Also, all remaining project adds need to immediately pushed as well, since we're now "interactive" - _solutionLoadComplete = true; - - // Check that the set of analyzers is complete and consistent. - GetAnalyzerDependencyCheckingService()?.ReanalyzeSolutionForConflicts(); + public override ProjectId Id => _id; } - private AnalyzerDependencyCheckingService GetAnalyzerDependencyCheckingService() + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] + public void AddProject(AbstractProject project) { - var componentModel = (IComponentModel)_serviceProvider.GetService(typeof(SComponentModel)); - - return componentModel.GetService(); + project.VisualStudioProject = _projectFactory.CreateAndAddToWorkspace(project.ProjectSystemName, project.Language); + project.UpdateVisualStudioProjectProperties(); + + _typeScriptProjects[project.Id] = project; } - internal void OnBeforeLoadProjectBatch(bool fIsBackgroundIdleBatch) + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] + public bool ContainsProject(AbstractProject project) { - AssertIsForeground(); - - _batchingProjectLoads = true; - _projectsLoadedThisBatch.Clear(); - } - - internal void OnAfterLoadProjectBatch(bool fIsBackgroundIdleBatch) - { - AssertIsForeground(); - - if (!fIsBackgroundIdleBatch && _projectsLoadedThisBatch.Count > 0) - { - // This batch was loaded eagerly. This might be because the user is force expanding the projects in the - // Solution Explorer, or they had some files open in an .suo we need to push. - StartPushingToWorkspaceAndNotifyOfOpenDocuments(_projectsLoadedThisBatch); - } - - _batchingProjectLoads = false; - _projectsLoadedThisBatch.Clear(); - } - - internal void OnAfterBackgroundSolutionLoadComplete() - { - AssertIsForeground(); - - // In Non-DPL scenarios, this indicates that ASL is complete, and we should push any - // remaining information we have to the Workspace. If DPL is enabled, this is never - // called. - FinishLoad(); + // This will be set as long as the project has been added and not since removed + return project.VisualStudioProject != null; } - internal void OnBeforeOpenSolution() + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] + public void RemoveProject(AbstractProject project) { - AssertIsForeground(); + _typeScriptProjects.Remove(project.Id); - _solutionLoadComplete = false; + project.VisualStudioProject.RemoveFromWorkspace(); + project.VisualStudioProject = null; } } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspace.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspace.cs index 0147537c8d67c4568aaa58ab8a5efb91779f886d..52290b2b5653db1eefb706e78f901cb2c000542d 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspace.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspace.cs @@ -80,14 +80,19 @@ protected override void OnDocumentClosing(DocumentId documentId) /// The for the project. /// The , or null if the project doesn't have one. public abstract IVsHierarchy GetHierarchy(ProjectId projectId); - public abstract string GetFilePath(DocumentId documentId); + + internal abstract Guid GetProjectGuid(ProjectId projectId); + + public virtual string GetFilePath(DocumentId documentId) + { + return CurrentSolution.GetDocument(documentId)?.FilePath; + } /// /// Given a document id, opens an invisible editor for the document. /// /// A unique instance of IInvisibleEditor that must be disposed by the caller. internal abstract IInvisibleEditor OpenInvisibleEditor(DocumentId documentId); - internal abstract IInvisibleEditor OpenInvisibleEditor(IVisualStudioHostDocument document); /// /// Returns the for a given document. diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.DeferredInitialization.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.DeferredInitialization.cs deleted file mode 100644 index 365c62cd479e92211466e94fa43f16218ec34f5b..0000000000000000000000000000000000000000 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.DeferredInitialization.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor.Implementation.Workspaces; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Options; -using Microsoft.VisualStudio.ComponentModelHost; -using Microsoft.VisualStudio.Shell.Interop; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -{ - internal abstract partial class VisualStudioWorkspaceImpl - { - /// - /// The class that's created once the is finally - /// getting content inside of it. We have various bits of the implementation - /// of this workspace that need to start up on the UI thread, but we cannot - /// guarantee which thread will create the , - /// since it could be MEF imported on any thread. This class holds all that "real" state - /// which can't be touched during construction or in any codepath that - /// might run before a project is added. - /// - internal class DeferredInitializationState : ForegroundThreadAffinitizedObject - { - public VisualStudioProjectTracker ProjectTracker { get; } - public IServiceProvider ServiceProvider { get; } - public IVsUIShellOpenDocument ShellOpenDocumentService { get; } - - public DeferredInitializationState(IThreadingContext threadingContext, VisualStudioWorkspaceImpl workspace, IServiceProvider serviceProvider, LinkedFileUtilities linkedFileUtilities) - : base(threadingContext, assertIsForeground: true) - { - ServiceProvider = serviceProvider; - ShellOpenDocumentService = (IVsUIShellOpenDocument)serviceProvider.GetService(typeof(SVsUIShellOpenDocument)); - ProjectTracker = new VisualStudioProjectTracker(threadingContext, serviceProvider, workspace, linkedFileUtilities); - - // Ensure the document tracking service is initialized on the UI thread - var documentTrackingService = (VisualStudioDocumentTrackingService)workspace.Services.GetService(); - var documentProvider = new DocumentProvider(ProjectTracker, serviceProvider, documentTrackingService, linkedFileUtilities); - var metadataReferenceProvider = workspace.Services.GetService(); - var ruleSetFileProvider = workspace.Services.GetService(); - ProjectTracker.InitializeProviders(documentProvider, metadataReferenceProvider, ruleSetFileProvider); - - var componentModel = (IComponentModel)serviceProvider.GetService(typeof(SComponentModel)); - var saveEventsService = componentModel.GetService(); - saveEventsService.StartSendingSaveEvents(); - - VisualStudioProjectCacheHostServiceFactory.ConnectProjectCacheServiceToDocumentTracking(workspace.Services, (ProjectCacheService)workspace.CurrentSolution.Services.CacheService); - - // Ensure the options factory services are initialized on the UI thread - workspace.Services.GetService(); - } - } - - internal string GetProjectDisplayName(Project project) - { - var hierarchy = this.GetHierarchy(project.Id); - if (hierarchy != null) - { - var solution = (IVsSolution3)DeferredState.ServiceProvider.GetService(typeof(SVsSolution)); - if (solution != null) - { - if (ErrorHandler.Succeeded(solution.GetUniqueUINameOfProject(hierarchy, out string name)) && name != null) - { - return name; - } - } - } - - return project.Name; - } - } -} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs new file mode 100644 index 0000000000000000000000000000000000000000..769161590273c2f4500d820f20ed5e33f960fc43 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs @@ -0,0 +1,556 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.Editor; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.TextManager.Interop; +using IAsyncServiceProvider = Microsoft.VisualStudio.Shell.IAsyncServiceProvider; +using Task = System.Threading.Tasks.Task; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +{ + internal partial class VisualStudioWorkspaceImpl + { + /// + /// Singleton the subscribes to the running document table and connects/disconnects files to files that are opened. + /// + public sealed class OpenFileTracker + { + private readonly ForegroundThreadAffinitizedObject _foregroundAffinitization; + + private readonly VisualStudioWorkspaceImpl _workspace; + private readonly IVsRunningDocumentTable4 _runningDocumentTable; + private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService; + private readonly IAsynchronousOperationListener _asyncOperationListener; + + #region Fields read/written to from multiple threads to track files that need to be checked + + /// + /// A object to be used for a gate for modifications to , + /// and . These are the only mutable fields + /// in this class that are modified from multiple threads. + /// + private readonly object _gate = new object(); + private HashSet _fileNamesToCheckForOpenDocuments; + private bool _justEnumerateTheEntireRunningDocumentTable; + + private bool _taskPending; + + #endregion + + #region Fields read/and written to only on the UI thread to track active context for files + + private readonly Dictionary _hierarchyEventSinks = new Dictionary(); + + #endregion + + /// + /// A cutoff to use when we should stop checking the RDT for individual documents and just rescan all open documents. + /// + /// If a single document is added to a project, we need to check if it's already open. We can easily do + /// that by calling and going from there. That's fine + /// for a few documents, but is not wise during solution load when you have potentially thousands of files. In that + /// case, we can just enumerate all open files and check if we know about them, on the assumption the number of + /// open files is far less than the number of total files. + /// + /// This cutoff of 10 was chosen arbitrarily and with no evidence whatsoever. + private const int CutoffForCheckingAllRunningDocumentTableDocuments = 10; + + private OpenFileTracker(VisualStudioWorkspaceImpl workspace, IVsRunningDocumentTable4 runningDocumentTable, IComponentModel componentModel) + { + _workspace = workspace; + _foregroundAffinitization = new ForegroundThreadAffinitizedObject(workspace._threadingContext, assertIsForeground: true); + _runningDocumentTable = runningDocumentTable; + _editorAdaptersFactoryService = componentModel.GetService(); + _asyncOperationListener = componentModel.GetService().GetListener(FeatureAttribute.Workspace); + } + + public async static Task CreateAsync(VisualStudioWorkspaceImpl workspace, IAsyncServiceProvider asyncServiceProvider) + { + var runningDocumentTable = (IVsRunningDocumentTable4)await asyncServiceProvider.GetServiceAsync(typeof(SVsRunningDocumentTable)).ConfigureAwait(true); + var componentModel = (IComponentModel)await asyncServiceProvider.GetServiceAsync(typeof(SComponentModel)).ConfigureAwait(true); + + var openFileTracker = new OpenFileTracker(workspace, runningDocumentTable, componentModel); + openFileTracker.ConnectToRunningDocumentTable(); + + return openFileTracker; + } + + private void ConnectToRunningDocumentTable() + { + _foregroundAffinitization.AssertIsForeground(); + + // Some methods we need here only exist in IVsRunningDocumentTable and not the IVsRunningDocumentTable4 that we + // hold onto as a field + var runningDocumentTable = ((IVsRunningDocumentTable)_runningDocumentTable); + runningDocumentTable.AdviseRunningDocTableEvents(new RunningDocumentTableEventSink(this), out var docTableEventsCookie); + } + + public void CheckForOpenDocumentsByEnumeratingTheRunningDocumentTable() + { + _foregroundAffinitization.AssertIsForeground(); + + lock (_gate) + { + // Since we're scanning the full RDT, we can skip any explicit names we already have queued + ClearPendingFilesForBeingOpen_NoLock(); + } + + foreach (var cookie in GetInitializedRunningDocumentTableCookies()) + { + TryOpeningDocumentsForNewCookie(cookie); + } + } + + private IEnumerable GetInitializedRunningDocumentTableCookies() + { + // Some methods we need here only exist in IVsRunningDocumentTable and not the IVsRunningDocumentTable4 that we + // hold onto as a field + var runningDocumentTable = ((IVsRunningDocumentTable)_runningDocumentTable); + ErrorHandler.ThrowOnFailure(runningDocumentTable.GetRunningDocumentsEnum(out var enumRunningDocuments)); + uint[] cookies = new uint[16]; + + while (ErrorHandler.Succeeded(enumRunningDocuments.Next((uint)cookies.Length, cookies, out var cookiesFetched)) + && cookiesFetched > 0) + { + for (int cookieIndex = 0; cookieIndex < cookiesFetched; cookieIndex++) + { + var cookie = cookies[cookieIndex]; + + if (_runningDocumentTable.IsDocumentInitialized(cookie)) + { + yield return cookie; + } + } + } + } + + private void TryOpeningDocumentsForNewCookie(uint cookie) + { + _foregroundAffinitization.AssertIsForeground(); + + if (!_runningDocumentTable.IsDocumentInitialized(cookie)) + { + // We never want to touch documents that haven't been initialized yet, so immediately bail. Any further + // calls to the RDT might accidentally initialize it. + return; + } + + var moniker = _runningDocumentTable.GetDocumentMoniker(cookie); + _workspace.ApplyChangeToWorkspace(w => + { + var documentIds = _workspace.CurrentSolution.GetDocumentIdsWithFilePath(moniker); + if (documentIds.IsDefaultOrEmpty) + { + return; + } + + if (documentIds.All(w.IsDocumentOpen)) + { + return; + } + + ProjectId activeContextProjectId; + + if (documentIds.Length == 1) + { + activeContextProjectId = documentIds.Single().ProjectId; + } + else + { + _runningDocumentTable.GetDocumentHierarchyItem(cookie, out var hierarchy, out _); + activeContextProjectId = GetActiveContextProjectId(hierarchy, documentIds.Select(d => d.ProjectId)); + } + + if ((object)_runningDocumentTable.GetDocumentData(cookie) is IVsTextBuffer bufferAdapter) + { + var textBuffer = _editorAdaptersFactoryService.GetDocumentBuffer(bufferAdapter); + + if (textBuffer != null) + { + var textContainer = textBuffer.AsTextContainer(); + + foreach (var documentId in documentIds) + { + if (!w.IsDocumentOpen(documentId) && !_workspace._documentsNotFromFiles.Contains(documentId)) + { + w.OnDocumentOpened(documentId, textContainer, isCurrentContext: documentId.ProjectId == activeContextProjectId); + } + } + } + } + }); + } + + private ProjectId GetActiveContextProjectId(IVsHierarchy hierarchy, IEnumerable projectIds) + { + _foregroundAffinitization.AssertIsForeground(); + + if (hierarchy == null) + { + // Any item in the RDT should have a hierarchy associated; in this case we don't so there's absolutely nothing + // we can do at this point. + return projectIds.First(); + } + + // Take a snapshot of the immutable data structure here to avoid mutation underneath us + var projectToHierarchyMap = _workspace._projectToHierarchyMap; + var solution = _workspace.CurrentSolution; + + // We now must chase to the actual hierarchy that we know about. We'll do this as a loop as there may be multiple steps in order. + // intermediateHierarchy will be where we are so far, and we'll keep track of all of our intermediate steps (think a breadcrumb trail) + // in intermediateHierarchies. + var intermediateHierarchy = hierarchy; + var intermediateHierarchies = new HashSet(); + + while (true) + { + if (!intermediateHierarchies.Add(intermediateHierarchy)) + { + // We ended up somewhere we already were -- either we have a loop or we weren't able to make further progress. In this case, + // just bail. + break; + } + + // Have we already arrived at a hierarchy we know about? + var matchingProjectId = projectToHierarchyMap.FirstOrDefault(d => projectIds.Contains(d.Key) && + d.Value == intermediateHierarchy).Key; + + if (matchingProjectId != null) + { + return matchingProjectId; + } + + // This is some intermediate hierarchy which we need to direct us somewhere else. At this point, we need to add an event sink to be aware if the redirection + // ever changes. + if (!_hierarchyEventSinks.ContainsKey(hierarchy)) + { + var eventSink = new HierarchyEventSink(intermediateHierarchy, this); + if (eventSink.TryAdviseHierarchy()) + { + _hierarchyEventSinks.Add(intermediateHierarchy, eventSink); + } + } + + // If this is a shared hierarchy, we can possibly ask it for it's context + var contextHierarchy = intermediateHierarchy.GetActiveProjectContext(); + if (contextHierarchy != null) + { + intermediateHierarchy = contextHierarchy; + continue; + } + + if (ErrorHandler.Succeeded(intermediateHierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID8.VSHPROPID_ActiveIntellisenseProjectContext, out object contextProjectNameObject))) + { + if (contextProjectNameObject is string contextProjectName) + { + var contextProject = solution.Projects.FirstOrDefault(p => p.Name == contextProjectName); + if (contextProject != null) + { + return contextProject.Id; + } + } + } + } + + // If we had some trouble finding the project, we'll just pick one arbitrarily + return projectIds.First(); + } + + private void RefreshContextForRunningDocumentTableHierarchyChange(uint cookie) + { + _foregroundAffinitization.AssertIsForeground(); + + var moniker = _runningDocumentTable.GetDocumentMoniker(cookie); + _workspace.ApplyChangeToWorkspace(w => + { + var documentIds = _workspace.CurrentSolution.GetDocumentIdsWithFilePath(moniker); + if (documentIds.IsDefaultOrEmpty || documentIds.Length == 1) + { + return; + } + + if (!documentIds.All(w.IsDocumentOpen)) + { + return; + } + + _runningDocumentTable.GetDocumentHierarchyItem(cookie, out var hierarchy, out _); + var activeProjectId = GetActiveContextProjectId(hierarchy, documentIds.Select(d => d.ProjectId)); + w.OnDocumentContextUpdated(documentIds.FirstOrDefault(d => d.ProjectId == activeProjectId)); + }); + } + + private void RefreshContextForHierarchyPropertyChange(IVsHierarchy hierarchy) + { + // HACK: for now, just refresh all the things. This is expensive + _foregroundAffinitization.AssertIsForeground(); + + foreach (var cookie in GetInitializedRunningDocumentTableCookies()) + { + RefreshContextForRunningDocumentTableHierarchyChange(cookie); + } + } + + private void TryClosingDocumentsForCookie(uint cookie) + { + _foregroundAffinitization.AssertIsForeground(); + + if (!_runningDocumentTable.IsDocumentInitialized(cookie)) + { + // We never want to touch documents that haven't been initialized yet, so immediately bail. Any further + // calls to the RDT might accidentally initialize it. + return; + } + + var moniker = _runningDocumentTable.GetDocumentMoniker(cookie); + _workspace.ApplyChangeToWorkspace(w => + { + var documentIds = _workspace.CurrentSolution.GetDocumentIdsWithFilePath(moniker); + if (documentIds.IsDefaultOrEmpty) + { + return; + } + + foreach (var documentId in documentIds) + { + if (_workspace.IsDocumentOpen(documentId) && !_workspace._documentsNotFromFiles.Contains(documentId)) + { + w.OnDocumentClosed(documentId, new FileTextLoader(moniker, defaultEncoding: null)); + } + } + }); + } + + /// + /// Queues a new task to check for files being open for these file names. + /// + public void CheckForFilesBeingOpen(ImmutableArray newFileNames) + { + _foregroundAffinitization.ThisCanBeCalledOnAnyThread(); + + bool shouldStartTask = false; + + lock (_gate) + { + // If we've already decided to enumerate the full table, nothing further to do. + if (!_justEnumerateTheEntireRunningDocumentTable) + { + // If this is going to push us over our threshold for scanning the entire table then just give up + if ((_fileNamesToCheckForOpenDocuments?.Count ?? 0) + newFileNames.Length > CutoffForCheckingAllRunningDocumentTableDocuments) + { + _fileNamesToCheckForOpenDocuments = null; + _justEnumerateTheEntireRunningDocumentTable = true; + } + else + { + if (_fileNamesToCheckForOpenDocuments == null) + { + _fileNamesToCheckForOpenDocuments = new HashSet(newFileNames); + } + else + { + foreach (var filename in newFileNames) + { + _fileNamesToCheckForOpenDocuments.Add(filename); + } + } + } + } + + if (!_taskPending) + { + _taskPending = true; + shouldStartTask = true; + } + } + + if (shouldStartTask) + { + var asyncToken = _asyncOperationListener.BeginAsyncOperation(nameof(CheckForFilesBeingOpen)); + + Task.Run(async () => + { + await _foregroundAffinitization.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); + + CheckForFilesBeingOpenOnUIThread(); + }).CompletesAsyncOperation(asyncToken); + } + } + + private void CheckForFilesBeingOpenOnUIThread() + { + _foregroundAffinitization.AssertIsForeground(); + + // Just pulling off the values from the shared state to the local funtion... + HashSet fileNamesToCheckForOpenDocuments; + bool justEnumerateTheEntireRunningDocumentTable; + lock (_gate) + { + fileNamesToCheckForOpenDocuments = _fileNamesToCheckForOpenDocuments; + justEnumerateTheEntireRunningDocumentTable = _justEnumerateTheEntireRunningDocumentTable; + + ClearPendingFilesForBeingOpen_NoLock(); + } + + if (justEnumerateTheEntireRunningDocumentTable) + { + CheckForOpenDocumentsByEnumeratingTheRunningDocumentTable(); + } + else + { + foreach (var filename in fileNamesToCheckForOpenDocuments) + { + if (_runningDocumentTable.IsMonikerValid(filename)) + { + var cookie = _runningDocumentTable.GetDocumentCookie(filename); + TryOpeningDocumentsForNewCookie(cookie); + } + } + } + } + + private void ClearPendingFilesForBeingOpen_NoLock() + { + _fileNamesToCheckForOpenDocuments = null; + _justEnumerateTheEntireRunningDocumentTable = false; + + _taskPending = false; + } + + private class RunningDocumentTableEventSink : IVsRunningDocTableEvents3 + { + private readonly OpenFileTracker _openFileTracker; + + public RunningDocumentTableEventSink(OpenFileTracker openFileTracker) + { + _openFileTracker = openFileTracker; + } + + public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) + { + return VSConstants.E_NOTIMPL; + } + + public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) + { + if (dwReadLocksRemaining + dwEditLocksRemaining == 0) + { + _openFileTracker.TryClosingDocumentsForCookie(docCookie); + } + + return VSConstants.S_OK; + } + + public int OnAfterSave(uint docCookie) + { + return VSConstants.E_NOTIMPL; + } + + public int OnAfterAttributeChange(uint docCookie, uint grfAttribs) + { + return VSConstants.E_NOTIMPL; + } + + public int OnAfterAttributeChangeEx(uint docCookie, uint grfAttribs, IVsHierarchy pHierOld, uint itemidOld, string pszMkDocumentOld, IVsHierarchy pHierNew, uint itemidNew, string pszMkDocumentNew) + { + if ((grfAttribs & (uint)__VSRDTATTRIB3.RDTA_DocumentInitialized) != 0) + { + _openFileTracker.TryOpeningDocumentsForNewCookie(docCookie); + } + + if ((grfAttribs & (uint)__VSRDTATTRIB.RDTA_Hierarchy) != 0) + { + _openFileTracker.RefreshContextForRunningDocumentTableHierarchyChange(docCookie); + } + + return VSConstants.S_OK; + } + + public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame) + { + if (fFirstShow != 0) + { + _openFileTracker.TryOpeningDocumentsForNewCookie(docCookie); + } + + return VSConstants.S_OK; + } + + public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame) + { + return VSConstants.E_NOTIMPL; + } + + public int OnBeforeSave(uint docCookie) + { + return VSConstants.E_NOTIMPL; + } + } + + private class HierarchyEventSink : IVsHierarchyEvents + { + private uint _cookie; + private readonly IVsHierarchy _hierarchy; + private readonly OpenFileTracker _openFileTracker; + + public HierarchyEventSink(IVsHierarchy hierarchy, OpenFileTracker openFileTracker) + { + _hierarchy = hierarchy; + _openFileTracker = openFileTracker; + } + + public bool TryAdviseHierarchy() + { + return ErrorHandler.Succeeded(_hierarchy.AdviseHierarchyEvents(this, out _cookie)); + } + + int IVsHierarchyEvents.OnItemAdded(uint itemidParent, uint itemidSiblingPrev, uint itemidAdded) + { + return VSConstants.E_NOTIMPL; + } + + int IVsHierarchyEvents.OnItemsAppended(uint itemidParent) + { + return VSConstants.E_NOTIMPL; + } + + int IVsHierarchyEvents.OnItemDeleted(uint itemid) + { + return VSConstants.E_NOTIMPL; + } + + int IVsHierarchyEvents.OnPropertyChanged(uint itemid, int propid, uint flags) + { + if (propid == (int)__VSHPROPID7.VSHPROPID_SharedItemContextHierarchy || + propid == (int)__VSHPROPID8.VSHPROPID_ActiveIntellisenseProjectContext) + { + _openFileTracker.RefreshContextForHierarchyPropertyChange(_hierarchy); + } + + return VSConstants.S_OK; + } + + int IVsHierarchyEvents.OnInvalidateItems(uint itemidParent) + { + return VSConstants.E_NOTIMPL; + } + + int IVsHierarchyEvents.OnInvalidateIcon(IntPtr hicon) + { + return VSConstants.E_NOTIMPL; + } + } + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs index b5319b00d703fd20ee372bccdc6e31e94de02f20..f1a5a4ec42a70925fd367969869eaa49d0e8c021 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.ComponentModel.Composition.Hosting; using System.IO; using System.Linq; @@ -19,6 +20,7 @@ using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Extensions; +using Microsoft.VisualStudio.LanguageServices.Implementation.Venus; using Microsoft.VisualStudio.LanguageServices.Utilities; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; @@ -27,6 +29,7 @@ using Roslyn.Utilities; using VSLangProj; using VSLangProj140; +using IAsyncServiceProvider = Microsoft.VisualStudio.Shell.IAsyncServiceProvider; using OleInterop = Microsoft.VisualStudio.OLE.Interop; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem @@ -42,8 +45,11 @@ internal abstract partial class VisualStudioWorkspaceImpl : VisualStudioWorkspac private readonly IThreadingContext _threadingContext; private readonly ITextBufferFactoryService _textBufferFactoryService; private readonly IProjectionBufferFactoryService _projectionBufferFactoryService; + + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] + private readonly Lazy _projectFactory; + private readonly ITextBufferCloneService _textBufferCloneService; - private readonly LinkedFileUtilities _linkedFileUtilities; // document worker coordinator private ISolutionCrawlerRegistrationService _registrationService; @@ -53,83 +59,169 @@ internal abstract partial class VisualStudioWorkspaceImpl : VisualStudioWorkspac /// private readonly ForegroundThreadAffinitizedObject _foregroundObject; - private readonly Dictionary> _hierarchyEventSinks = new Dictionary>(); + private ImmutableDictionary _projectToHierarchyMap = ImmutableDictionary.Empty; + private ImmutableDictionary _projectToGuidMap = ImmutableDictionary.Empty; + private ImmutableDictionary _projectUniqueNameToProjectMap = ImmutableDictionary.Empty; /// - /// The that consists of the - /// and other UI-initialized types. It will be created as long as a single project has been created. + /// A set of documents that were added by , and aren't otherwise + /// tracked for opening/closing. /// - internal DeferredInitializationState DeferredState { get; private set; } + private ImmutableHashSet _documentsNotFromFiles = ImmutableHashSet.Empty; + + internal VisualStudioProjectTracker _projectTracker; - public VisualStudioWorkspaceImpl(ExportProvider exportProvider) - : base( - MefV1HostServices.Create(exportProvider)) + private OpenFileTracker _openFileTrackerOpt; + internal FileChangeWatcher FileChangeWatcher { get; } + + public VisualStudioWorkspaceImpl(ExportProvider exportProvider, IAsyncServiceProvider asyncServiceProvider) + : base(MefV1HostServices.Create(exportProvider)) { _threadingContext = exportProvider.GetExportedValue(); _textBufferCloneService = exportProvider.GetExportedValue(); _textBufferFactoryService = exportProvider.GetExportedValue(); _projectionBufferFactoryService = exportProvider.GetExportedValue(); - _linkedFileUtilities = exportProvider.GetExportedValue(); + + // We fetch this lazily because VisualStudioProjectFactory depends on VisualStudioWorkspaceImpl -- we have a circularity. Since this + // exists right now as a compat shim, we'll just do this. +#pragma warning disable CS0618 // Type or member is obsolete + _projectFactory = exportProvider.GetExport(); +#pragma warning restore CS0618 // Type or member is obsolete _foregroundObject = new ForegroundThreadAffinitizedObject(_threadingContext); _textBufferFactoryService.TextBufferCreated += AddTextBufferCloneServiceToBuffer; _projectionBufferFactoryService.ProjectionBufferCreated += AddTextBufferCloneServiceToBuffer; exportProvider.GetExportedValue().Register(this); + + System.Threading.Tasks.Task.Run(() => ConnectToOpenFileTrackerOnUIThreadAsync(asyncServiceProvider)); + + var fileChangeWatcherProvider = exportProvider.GetExportedValue(); + + FileChangeWatcher = fileChangeWatcherProvider.Watcher; + System.Threading.Tasks.Task.Run(async () => + { + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); + + var fileChangeService = (IVsFileChangeEx)ServiceProvider.GlobalProvider.GetService(typeof(SVsFileChangeEx)); + fileChangeWatcherProvider.SetFileChangeService(fileChangeService); + }); } - /// - /// Ensures the workspace is fully hooked up to the host by subscribing to all sorts of VS - /// UI thread affinitized events. - /// - internal VisualStudioProjectTracker GetProjectTrackerAndInitializeIfNecessary(IServiceProvider serviceProvider) + public async System.Threading.Tasks.Task ConnectToOpenFileTrackerOnUIThreadAsync(IAsyncServiceProvider asyncServiceProvider) { - if (DeferredState == null) + // Create services that are bound to the UI thread + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); + + var openFileTracker = await OpenFileTracker.CreateAsync(this, asyncServiceProvider).ConfigureAwait(true); + + // Update our fields first, so any asynchronous work that needs to use these is able to see the service. + lock (_gate) { - ThreadHelper.ThrowIfNotOnUIThread(); - DeferredState = new DeferredInitializationState(_threadingContext, this, serviceProvider, _linkedFileUtilities); + _openFileTrackerOpt = openFileTracker; } - return DeferredState.ProjectTracker; + openFileTracker.CheckForOpenDocumentsByEnumeratingTheRunningDocumentTable(); } - /// - /// A compatibility shim to ensure that F# and TypeScript continue to work after the deferred work goes in. This will be - /// removed once they move to calling . - /// + public void CheckForOpenDocuments(ImmutableArray newFileNames) + { + _openFileTrackerOpt?.CheckForFilesBeingOpen(newFileNames); + } + + internal void AddProjectToInternalMaps(VisualStudioProject project, IVsHierarchy hierarchy, Guid guid, string projectUniqueName) + { + lock (_gate) + { + _projectToHierarchyMap = _projectToHierarchyMap.Add(project.Id, hierarchy); + _projectToGuidMap = _projectToGuidMap.Add(project.Id, guid); + _projectUniqueNameToProjectMap = _projectUniqueNameToProjectMap.Add(projectUniqueName, project); + } + } + + internal void AddDocumentToDocumentsNotFromFiles(DocumentId documentId) + { + lock (_gate) + { + _documentsNotFromFiles = _documentsNotFromFiles.Add(documentId); + } + } + + internal void RemoveDocumentToDocumentsNotFromFiles(DocumentId documentId) + { + lock (_gate) + { + _documentsNotFromFiles = _documentsNotFromFiles.Remove(documentId); + } + } + + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] + internal VisualStudioProjectTracker GetProjectTrackerAndInitializeIfNecessary() + { + if (_projectTracker == null) + { + _projectTracker = new VisualStudioProjectTracker(this, _projectFactory.Value, _threadingContext); + } + + return _projectTracker; + } + + [Obsolete("This is a compatibility shim for TypeScript and F#; please do not use it.")] internal VisualStudioProjectTracker ProjectTracker { get { - return GetProjectTrackerAndInitializeIfNecessary(ServiceProvider.GlobalProvider); + return GetProjectTrackerAndInitializeIfNecessary(); } } - internal void ClearReferenceCache() + internal ContainedDocument TryGetContainedDocument(DocumentId documentId) { - DeferredState?.ProjectTracker.MetadataReferenceProvider.ClearCache(); + // TODO: move everybody off of this method + return ContainedDocument.TryGetContainedDocument(documentId); } - internal IVisualStudioHostDocument GetHostDocument(DocumentId documentId) + internal VisualStudioProject GetProjectForUniqueName(string projectName) + { + // This doesn't take a lock since _projectNameToProjectMap is immutable + return _projectUniqueNameToProjectMap.GetValueOrDefault(projectName, defaultValue: null); + } + + [Obsolete("This is a compatibility shim for Live Unit Testing; please do not use it.")] + internal AbstractProject GetHostProject(ProjectId projectId) { - var project = GetHostProject(documentId.ProjectId); - if (project != null) + var project = CurrentSolution.GetProject(projectId); + + if (project == null) { - return project.GetDocumentOrAdditionalDocument(documentId); + return null; } - return null; + return new StubProject(ProjectTracker, project, GetHierarchy(projectId), project.OutputFilePath); } - internal AbstractProject GetHostProject(ProjectId projectId) + private sealed class StubProject : AbstractProject { - return DeferredState?.ProjectTracker.GetProject(projectId); + private readonly string _outputPath; + + public StubProject(VisualStudioProjectTracker projectTracker, CodeAnalysis.Project project, IVsHierarchy hierarchy, string outputPath) + : base(projectTracker, null, project.Name + "_Stub", null, hierarchy, project.Language, Guid.Empty, null, null, null, null) + { + _outputPath = outputPath; + } + + protected override string GetOutputFilePath() + { + return _outputPath; + } } - private bool TryGetHostProject(ProjectId projectId, out AbstractProject project) + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] + internal IVisualStudioHostDocument GetHostDocument(DocumentId documentId) { - project = GetHostProject(projectId); - return project != null; + // TypeScript only calls this to immediately check if the document is a ContainedDocument. Because of that we can just check for + // ContainedDocuments + return ContainedDocument.TryGetContainedDocument(documentId); } internal override bool TryApplyChanges( @@ -156,13 +248,18 @@ private bool TryGetHostProject(ProjectId projectId, out AbstractProject project) pc.GetRemovedMetadataReferences().Any() || pc.GetRemovedProjectReferences().Any()) { - projectsToLoad.Add(GetHostProject(pc.ProjectId).Guid); + var projectGuid = GetProjectGuid(pc.ProjectId); + + if (projectGuid != Guid.Empty) + { + projectsToLoad.Add(projectGuid); + } } } if (projectsToLoad.Any()) { - var vsSolution4 = (IVsSolution4)DeferredState.ServiceProvider.GetService(typeof(SVsSolution)); + var vsSolution4 = (IVsSolution4)ServiceProvider.GlobalProvider.GetService(typeof(SVsSolution)); vsSolution4.EnsureProjectsAreLoaded( (uint)projectsToLoad.Count, projectsToLoad.ToArray(), @@ -259,19 +356,19 @@ public override bool CanApplyChange(ApplyChangesKind feature) } } - private bool TryGetProjectData(ProjectId projectId, out AbstractProject hostProject, out IVsHierarchy hierarchy, out EnvDTE.Project project) + private bool TryGetProjectData(ProjectId projectId, out IVsHierarchy hierarchy, out EnvDTE.Project project) { hierarchy = null; project = null; - return this.TryGetHostProject(projectId, out hostProject) - && this.TryGetHierarchy(projectId, out hierarchy) - && hierarchy.TryGetProject(out project); + return + this.TryGetHierarchy(projectId, out hierarchy) && + hierarchy.TryGetProject(out project); } - internal void GetProjectData(ProjectId projectId, out AbstractProject hostProject, out IVsHierarchy hierarchy, out EnvDTE.Project project) + internal void GetProjectData(ProjectId projectId, out IVsHierarchy hierarchy, out EnvDTE.Project project) { - if (!TryGetProjectData(projectId, out hostProject, out hierarchy, out project)) + if (!TryGetProjectData(projectId, out hierarchy, out project)) { throw new ArgumentException(string.Format(ServicesVSResources.Could_not_find_project_0, projectId)); } @@ -279,7 +376,7 @@ internal void GetProjectData(ProjectId projectId, out AbstractProject hostProjec internal EnvDTE.Project TryGetDTEProject(ProjectId projectId) { - return TryGetProjectData(projectId, out var hostProject, out var hierarchy, out var project) ? project : null; + return TryGetProjectData(projectId, out var hierarchy, out var project) ? project : null; } internal bool TryAddReferenceToProject(ProjectId projectId, string assemblyName) @@ -287,7 +384,7 @@ internal bool TryAddReferenceToProject(ProjectId projectId, string assemblyName) EnvDTE.Project project; try { - GetProjectData(projectId, out var hostProject, out var hierarchy, out project); + GetProjectData(projectId, out var hierarchy, out project); } catch (ArgumentException) { @@ -329,10 +426,10 @@ protected override void ApplyParseOptionsChanged(ProjectId projectId, ParseOptio string newVersion = parseOptionsService.GetLanguageVersion(options); - GetProjectData(projectId, out var hostProject, out var hierarchy, out var project); + GetProjectData(projectId, out var hierarchy, out var project); foreach (string configurationName in (object[])project.ConfigurationManager.ConfigurationRowNames) { - switch (hostProject.Language) + switch (CurrentSolution.GetProject(projectId).Language) { case LanguageNames.CSharp: var csharpProperties = (VSLangProj80.CSharpProjectConfigurationProperties3)project.ConfigurationManager @@ -362,7 +459,7 @@ protected override void ApplyAnalyzerReferenceAdded(ProjectId projectId, Analyze throw new ArgumentNullException(nameof(analyzerReference)); } - GetProjectData(projectId, out var hostProject, out var hierarchy, out var project); + GetProjectData(projectId, out var hierarchy, out var project); string filePath = GetAnalyzerPath(analyzerReference); if (filePath != null) @@ -384,7 +481,7 @@ protected override void ApplyAnalyzerReferenceRemoved(ProjectId projectId, Analy throw new ArgumentNullException(nameof(analyzerReference)); } - GetProjectData(projectId, out var hostProject, out var hierarchy, out var project); + GetProjectData(projectId, out var hierarchy, out var project); string filePath = GetAnalyzerPath(analyzerReference); if (filePath != null) @@ -417,7 +514,7 @@ private string GetMetadataPath(MetadataReference metadataReference) throw new ArgumentNullException(nameof(metadataReference)); } - GetProjectData(projectId, out var hostProject, out var hierarchy, out var project); + GetProjectData(projectId, out var hierarchy, out var project); string filePath = GetMetadataPath(metadataReference); if (filePath != null) @@ -443,7 +540,7 @@ private string GetMetadataPath(MetadataReference metadataReference) throw new ArgumentNullException(nameof(metadataReference)); } - GetProjectData(projectId, out var hostProject, out var hierarchy, out var project); + GetProjectData(projectId, out var hierarchy, out var project); string filePath = GetMetadataPath(metadataReference); if (filePath != null) @@ -475,8 +572,8 @@ private string GetMetadataPath(MetadataReference metadataReference) throw new ArgumentNullException(nameof(projectReference)); } - GetProjectData(projectId, out var hostProject, out var hierarchy, out var project); - GetProjectData(projectReference.ProjectId, out var refHostProject, out var refHierarchy, out var refProject); + GetProjectData(projectId, out var hierarchy, out var project); + GetProjectData(projectReference.ProjectId, out var refHierarchy, out var refProject); var vsProject = (VSProject)project.Object; vsProject.References.AddProject(refProject); @@ -494,7 +591,7 @@ private OleInterop.IOleUndoManager TryGetUndoManager() var documentId = documentTrackingService.GetActiveDocument() ?? documentTrackingService.GetVisibleDocuments().FirstOrDefault(); if (documentId != null) { - var composition = (IComponentModel)this.DeferredState.ServiceProvider.GetService(typeof(SComponentModel)); + var composition = (IComponentModel)ServiceProvider.GlobalProvider.GetService(typeof(SComponentModel)); var exportProvider = composition.DefaultExportProvider; var editorAdaptersService = exportProvider.GetExportedValue(); @@ -518,8 +615,8 @@ private OleInterop.IOleUndoManager TryGetUndoManager() throw new ArgumentNullException(nameof(projectReference)); } - GetProjectData(projectId, out var hostProject, out var hierarchy, out var project); - GetProjectData(projectReference.ProjectId, out var refHostProject, out var refHierarchy, out var refProject); + GetProjectData(projectId, out var hierarchy, out var project); + GetProjectData(projectReference.ProjectId, out var refHierarchy, out var refProject); var vsProject = (VSProject)project.Object; foreach (Reference reference in vsProject.References) @@ -545,7 +642,7 @@ protected override void ApplyAdditionalDocumentAdded(DocumentInfo info, SourceTe private void AddDocumentCore(DocumentInfo info, SourceText initialText, bool isAdditionalDocument) { - GetProjectData(info.Id.ProjectId, out var hostProject, out var hierarchy, out var project); + GetProjectData(info.Id.ProjectId, out var hierarchy, out var project); // If the first namespace name matches the name of the project, then we don't want to // generate a folder for that. The project is implicitly a folder with that name. @@ -559,15 +656,15 @@ private void AddDocumentCore(DocumentInfo info, SourceText initialText, bool isA if (IsWebsite(project)) { - AddDocumentToFolder(hostProject, project, info.Id, SpecializedCollections.SingletonEnumerable(AppCodeFolderName), info.Name, info.SourceCodeKind, initialText, isAdditionalDocument: isAdditionalDocument, filePath: info.FilePath); + AddDocumentToFolder(project, info.Id, SpecializedCollections.SingletonEnumerable(AppCodeFolderName), info.Name, info.SourceCodeKind, initialText, isAdditionalDocument: isAdditionalDocument, filePath: info.FilePath); } else if (folders.Any()) { - AddDocumentToFolder(hostProject, project, info.Id, folders, info.Name, info.SourceCodeKind, initialText, isAdditionalDocument: isAdditionalDocument, filePath: info.FilePath); + AddDocumentToFolder(project, info.Id, folders, info.Name, info.SourceCodeKind, initialText, isAdditionalDocument: isAdditionalDocument, filePath: info.FilePath); } else { - AddDocumentToProject(hostProject, project, info.Id, info.Name, info.SourceCodeKind, initialText, isAdditionalDocument: isAdditionalDocument, filePath: info.FilePath); + AddDocumentToProject(project, info.Id, info.Name, info.SourceCodeKind, initialText, isAdditionalDocument: isAdditionalDocument, filePath: info.FilePath); } var undoManager = TryGetUndoManager(); @@ -640,7 +737,6 @@ protected override void AddExistingDocument(DocumentId documentId, string filePa #endif private ProjectItem AddDocumentToProject( - AbstractProject hostProject, EnvDTE.Project project, DocumentId documentId, string documentName, @@ -656,11 +752,10 @@ protected override void AddExistingDocument(DocumentId documentId, string filePa throw new Exception(ServicesVSResources.Could_not_find_location_of_folder_on_disk); } - return AddDocumentToProjectItems(hostProject, project.ProjectItems, documentId, folderPath, documentName, sourceCodeKind, initialText, filePath, isAdditionalDocument); + return AddDocumentToProjectItems(project.ProjectItems, documentId, folderPath, documentName, sourceCodeKind, initialText, filePath, isAdditionalDocument); } private ProjectItem AddDocumentToFolder( - AbstractProject hostProject, EnvDTE.Project project, DocumentId documentId, IEnumerable folders, @@ -679,11 +774,10 @@ protected override void AddExistingDocument(DocumentId documentId, string filePa throw new Exception(ServicesVSResources.Could_not_find_location_of_folder_on_disk); } - return AddDocumentToProjectItems(hostProject, folder.ProjectItems, documentId, folderPath, documentName, sourceCodeKind, initialText, filePath, isAdditionalDocument); + return AddDocumentToProjectItems(folder.ProjectItems, documentId, folderPath, documentName, sourceCodeKind, initialText, filePath, isAdditionalDocument); } private ProjectItem AddDocumentToProjectItems( - AbstractProject hostProject, ProjectItems projectItems, DocumentId documentId, string folderPath, @@ -696,7 +790,7 @@ protected override void AddExistingDocument(DocumentId documentId, string filePa if (filePath == null) { var baseName = Path.GetFileNameWithoutExtension(documentName); - var extension = isAdditionalDocument ? Path.GetExtension(documentName) : GetPreferredExtension(hostProject, sourceCodeKind); + var extension = isAdditionalDocument ? Path.GetExtension(documentName) : GetPreferredExtension(documentId, sourceCodeKind); var uniqueName = projectItems.GetUniqueName(baseName, extension); filePath = Path.Combine(folderPath, uniqueName); } @@ -709,13 +803,12 @@ protected override void AddExistingDocument(DocumentId documentId, string filePa } } - using (var documentIdHint = DeferredState.ProjectTracker.DocumentProvider.ProvideDocumentIdHint(filePath, documentId)) - { - return projectItems.AddFromFile(filePath); - } + // TODO: restore document ID hinting -- we previously ensured that the AddFromFile will introduce the document ID being used here. + // (tracked by https://devdiv.visualstudio.com/DevDiv/_workitems/edit/677956) + return projectItems.AddFromFile(filePath); } - protected void RemoveDocumentCore( + private void RemoveDocumentCore( DocumentId documentId, bool isAdditionalDocument) { if (documentId == null) @@ -723,15 +816,14 @@ protected override void AddExistingDocument(DocumentId documentId, string filePa throw new ArgumentNullException(nameof(documentId)); } - var hostDocument = this.GetHostDocument(documentId); - if (hostDocument != null) + var document = this.CurrentSolution.GetDocument(documentId); + var hierarchy = this.GetHierarchy(documentId.ProjectId); + if (document != null) { - var document = this.CurrentSolution.GetDocument(documentId); var text = document.GetTextSynchronously(CancellationToken.None); + var project = hierarchy as IVsProject3; - var project = hostDocument.Project.Hierarchy as IVsProject3; - - var itemId = hostDocument.GetItemId(); + var itemId = hierarchy.TryGetItemId(document.FilePath); if (itemId == (uint)VSConstants.VSITEMID.Nil) { // it is no longer part of the solution @@ -796,8 +888,8 @@ public void OpenDocumentCore(DocumentId documentId, bool activate = true) throw new InvalidOperationException(ServicesVSResources.This_workspace_only_supports_opening_documents_on_the_UI_thread); } - var document = this.GetHostDocument(documentId); - if (document != null && document.Project != null) + var document = this.CurrentSolution.GetDocument(documentId); + if (document != null) { if (TryGetFrame(document, out var frame)) { @@ -813,18 +905,20 @@ public void OpenDocumentCore(DocumentId documentId, bool activate = true) } } - private bool TryGetFrame(IVisualStudioHostDocument document, out IVsWindowFrame frame) + private bool TryGetFrame(CodeAnalysis.Document document, out IVsWindowFrame frame) { frame = null; - var itemId = document.GetItemId(); + var hierarchy = GetHierarchy(document.Project.Id); + var itemId = hierarchy?.TryGetItemId(document.FilePath) ?? (uint)VSConstants.VSITEMID.Nil; if (itemId == (uint)VSConstants.VSITEMID.Nil) { // If the ItemId is Nil, then IVsProject would not be able to open the // document using its ItemId. Thus, we must use OpenDocumentViaProject, which only // depends on the file path. - return ErrorHandler.Succeeded(DeferredState.ShellOpenDocumentService.OpenDocumentViaProject( + var openDocumentService = ServiceProvider.GlobalProvider.GetService(); + return ErrorHandler.Succeeded(openDocumentService.OpenDocumentViaProject( document.FilePath, VSConstants.LOGVIEWID.TextView_guid, out var oleServiceProvider, @@ -845,7 +939,7 @@ private bool TryGetFrame(IVisualStudioHostDocument document, out IVsWindowFrame // OpenDocumentViaProject itself relies upon this QI working, so it should be OK to // use here. - var vsProject = document.Project.Hierarchy as IVsProject; + var vsProject = hierarchy as IVsProject; return vsProject != null && ErrorHandler.Succeeded(vsProject.OpenItem(itemId, VSConstants.LOGVIEWID.TextView_guid, s_docDataExisting_Unknown, out frame)); } @@ -860,10 +954,11 @@ public void CloseDocumentCore(DocumentId documentId) if (this.IsDocumentOpen(documentId)) { - var document = this.GetHostDocument(documentId); - if (document != null) + var filePath = this.CurrentSolution.GetDocument(documentId).FilePath; + if (filePath != null) { - if (ErrorHandler.Succeeded(DeferredState.ShellOpenDocumentService.IsDocumentOpen(null, 0, document.FilePath, Guid.Empty, 0, out var uiHierarchy, null, out var frame, out var isOpen))) + var openDocumentService = ServiceProvider.GlobalProvider.GetService(); + if (ErrorHandler.Succeeded(openDocumentService.IsDocumentOpen(null, 0, filePath, Guid.Empty, 0, out var uiHierarchy, null, out var frame, out var isOpen))) { // TODO: do we need save argument for CloseDocument? frame.CloseFrame((uint)__FRAMECLOSE.FRAMECLOSE_NoSave); @@ -874,22 +969,48 @@ public void CloseDocumentCore(DocumentId documentId) protected override void ApplyDocumentTextChanged(DocumentId documentId, SourceText newText) { - EnsureEditableDocuments(documentId); - var hostDocument = GetHostDocument(documentId); - hostDocument.UpdateText(newText); + ApplyTextDocumentChange(documentId, newText); } protected override void ApplyAdditionalDocumentTextChanged(DocumentId documentId, SourceText newText) + { + ApplyTextDocumentChange(documentId, newText); + } + + private void ApplyTextDocumentChange(DocumentId documentId, SourceText newText) { EnsureEditableDocuments(documentId); - var hostDocument = GetHostDocument(documentId); - hostDocument.UpdateText(newText); + var containedDocument = TryGetContainedDocument(documentId); + + if (containedDocument != null) + { + containedDocument.UpdateText(newText); + } + else + { + if (IsDocumentOpen(documentId)) + { + var textBuffer = this.CurrentSolution.GetDocument(documentId).GetTextAsync(CancellationToken.None).WaitAndGetResult(CancellationToken.None).Container.TryGetTextBuffer(); + + if (textBuffer != null) + { + TextEditApplication.UpdateText(newText, textBuffer, EditOptions.DefaultMinimalChange); + return; + } + } + + // The document wasn't open in a normal way, so invisible editor time + using (var invisibleEditor = OpenInvisibleEditor(documentId)) + { + TextEditApplication.UpdateText(newText, invisibleEditor.TextBuffer, EditOptions.None); + } + } } - private static string GetPreferredExtension(AbstractProject hostProject, SourceCodeKind sourceCodeKind) + private string GetPreferredExtension(DocumentId documentId, SourceCodeKind sourceCodeKind) { // No extension was provided. Pick a good one based on the type of host project. - switch (hostProject.Language) + switch (CurrentSolution.GetProject(documentId.ProjectId).Language) { case LanguageNames.CSharp: // TODO: uncomment when fixing https://github.com/dotnet/roslyn/issues/5325 @@ -906,192 +1027,66 @@ private static string GetPreferredExtension(AbstractProject hostProject, SourceC public override IVsHierarchy GetHierarchy(ProjectId projectId) { - var project = this.GetHostProject(projectId); - - if (project == null) - { - return null; - } + // This doesn't take a lock since _projectToHierarchyMap is immutable + return _projectToHierarchyMap.GetValueOrDefault(projectId, defaultValue: null); + } - return project.Hierarchy; + internal override Guid GetProjectGuid(ProjectId projectId) + { + // This doesn't take a lock since _projectToGuidMap is immutable + return _projectToGuidMap.GetValueOrDefault(projectId, defaultValue: Guid.Empty); } internal override void SetDocumentContext(DocumentId documentId) { - var hostDocument = GetHostDocument(documentId); - if (hostDocument == null) - { - // the document or project is not being tracked - return; - } + _foregroundObject.AssertIsForeground(); + + // Note: this method does not actually call into any workspace code here to change the workspace's context. The assumption is updating the running document table or + // IVsHierarchies will raise the appropriate events which we are subscribed to. - var itemId = hostDocument.GetItemId(); - if (itemId == (uint)VSConstants.VSITEMID.Nil) + lock (_gate) { - // the document has been removed from the solution - return; - } - - var hierarchy = hostDocument.Project.Hierarchy; - var sharedHierarchy = _linkedFileUtilities.GetSharedHierarchyForItem(hierarchy, itemId); - if (sharedHierarchy != null) - { - if (sharedHierarchy.SetProperty( - (uint)VSConstants.VSITEMID.Root, - (int)__VSHPROPID8.VSHPROPID_ActiveIntellisenseProjectContext, - DeferredState.ProjectTracker.GetProject(documentId.ProjectId).ProjectSystemName) == VSConstants.S_OK) + var hierarchy = GetHierarchy(documentId.ProjectId); + if (hierarchy == null) { - // The ASP.NET 5 intellisense project is now updated. + // If we don't have a hierarchy then there's nothing we can do return; } - else - { - // Universal Project shared files - // Change the SharedItemContextHierarchy of the project's parent hierarchy, then - // hierarchy events will trigger the workspace to update. - var hr = sharedHierarchy.SetProperty((uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID7.VSHPROPID_SharedItemContextHierarchy, hierarchy); - } - } - else - { - // Regular linked files - // Transfer the item (open buffer) to the new hierarchy, and then hierarchy events - // will trigger the workspace to update. - var vsproj = hierarchy as IVsProject3; - var hr = vsproj.TransferItem(hostDocument.FilePath, hostDocument.FilePath, punkWindowFrame: null); - } - } - - internal void UpdateDocumentContextIfContainsDocument(IVsHierarchy sharedHierarchy, DocumentId documentId) - { - // TODO: This is a very roundabout way to update the context - - // The sharedHierarchy passed in has a new context, but we don't know what it is. - // The documentId passed in is associated with this sharedHierarchy, and this method - // will be called once for each such documentId. During this process, one of these - // documentIds will actually belong to the new SharedItemContextHierarchy. Once we - // find that one, we can map back to the open buffer and set its active context to - // the appropriate project. - - // Note that if there is a single head project and it's in the process of being unloaded - // there might not be a host project. - var hostProject = _linkedFileUtilities.GetContextHostProject(sharedHierarchy, DeferredState.ProjectTracker); - if (hostProject?.Hierarchy == sharedHierarchy) - { - return; - } - - if (hostProject.Id != documentId.ProjectId) - { - // While this documentId is associated with one of the head projects for this - // sharedHierarchy, it is not associated with the new context hierarchy. Another - // documentId will be passed to this method and update the context. - return; - } - - // This documentId belongs to the new SharedItemContextHierarchy. Update the associated - // buffer. - OnDocumentContextUpdated(documentId); - } - - internal void ConnectToSharedHierarchyEvents(IVisualStudioHostDocument document) - { - Contract.ThrowIfFalse(document.IsOpen); - - var project = document.Project; - var itemId = document.GetItemId(); - if (itemId != (uint)VSConstants.VSITEMID.Nil) - { - var sharedHierarchy = _linkedFileUtilities.GetSharedHierarchyForItem(project.Hierarchy, itemId); - - if (sharedHierarchy != null) + var filePath = CurrentSolution.GetDocument(documentId)?.FilePath; + if (filePath == null) { - ProjectTracker.NotifyWorkspace(workspace => - { - var eventSink = new HierarchyEventsSink((VisualStudioWorkspaceImpl)workspace, sharedHierarchy, document.Id); - if (ErrorHandler.Succeeded(sharedHierarchy.AdviseHierarchyEvents(eventSink, out var cookie))) - { - _hierarchyEventSinks.MultiAdd(document.Id, (sharedHierarchy, cookie)); - } - }); + return; } - } - } - protected override void OnDocumentClosing(DocumentId documentId) - { - base.OnDocumentClosing(documentId); - - if (_hierarchyEventSinks.TryGetValue(documentId, out var subscribedSinks)) - { - foreach (var subscribedSink in subscribedSinks) + var itemId = hierarchy.TryGetItemId(filePath); + if (itemId == VSConstants.VSITEMID_NIL) { - subscribedSink.hierarchy.UnadviseHierarchyEvents(subscribedSink.cookie); + return; } - _hierarchyEventSinks.Remove(documentId); - } - } - - /// - /// Finds the related to the given that - /// is in the current context. For regular files (non-shared and non-linked) and closed - /// linked files, this is always the provided . For open linked - /// files and open shared files, the active context is already tracked by the - /// and can be looked up directly. For closed shared files, the - /// document in the shared project's - /// is preferred. - /// - internal override DocumentId GetDocumentIdInCurrentContext(DocumentId documentId) - { - // If the document is open, then the Workspace knows the current context for both - // linked and shared files - if (IsDocumentOpen(documentId)) - { - return base.GetDocumentIdInCurrentContext(documentId); - } - - var hostDocument = GetHostDocument(documentId); - if (hostDocument == null) - { - // This can happen if the document was temporary and has since been closed/deleted. - return base.GetDocumentIdInCurrentContext(documentId); - } - - var itemId = hostDocument.GetItemId(); - if (itemId == (uint)VSConstants.VSITEMID.Nil) - { - // An itemid is required to determine whether the file belongs to a Shared Project - return base.GetDocumentIdInCurrentContext(documentId); - } - - // If this is a regular document or a closed linked (non-shared) document, then use the - // default logic for determining current context. - var sharedHierarchy = _linkedFileUtilities.GetSharedHierarchyForItem(hostDocument.Project.Hierarchy, itemId); - if (sharedHierarchy == null) - { - return base.GetDocumentIdInCurrentContext(documentId); - } + // Is this owned by a shared project? If so, go recursively. We can put this in a loop because in the case of mixed + // scenarios where you have shared assets projects and multitargeting projects, this same code works in both cases. + // Some shared hierarchies, when queried about items also give themselves back, so we'll only loop if we're actually + // going somewhere else. + while (SharedProjectUtilities.TryGetItemInSharedAssetsProject(hierarchy, itemId, out IVsHierarchy sharedHierarchy, out uint sharedItemId) && + hierarchy != sharedHierarchy) + { + // Ensure the shared context is set correctly + if (sharedHierarchy.GetActiveProjectContext() != hierarchy) + { + ErrorHandler.ThrowOnFailure(sharedHierarchy.SetActiveProjectContext(hierarchy)); + } - // This is a closed shared document, so we must determine the correct context. - var hostProject = _linkedFileUtilities.GetContextHostProject(sharedHierarchy, DeferredState.ProjectTracker); - var matchingProject = CurrentSolution.GetProject(hostProject.Id); - if (matchingProject == null || hostProject.Hierarchy == sharedHierarchy) - { - return base.GetDocumentIdInCurrentContext(documentId); - } + // We now need to ensure the outer project is also set up + hierarchy = sharedHierarchy; + itemId = sharedItemId; + } - if (matchingProject.ContainsDocument(documentId)) - { - // The provided documentId is in the current context project - return documentId; + // Update the ownership of the file in the Running Document Table + var project = (IVsProject3)hierarchy; + project.TransferItem(filePath, filePath, punkWindowFrame: null); } - - // The current context document is from another project. - var linkedDocumentIds = CurrentSolution.GetDocument(documentId).GetLinkedDocumentIds(); - var matchingDocumentId = linkedDocumentIds.FirstOrDefault(id => id.ProjectId == matchingProject.Id); - return matchingDocumentId ?? base.GetDocumentIdInCurrentContext(documentId); } internal bool TryGetHierarchy(ProjectId projectId, out IVsHierarchy hierarchy) @@ -1100,20 +1095,6 @@ internal bool TryGetHierarchy(ProjectId projectId, out IVsHierarchy hierarchy) return hierarchy != null; } - public override string GetFilePath(DocumentId documentId) - { - var document = this.GetHostDocument(documentId); - - if (document == null) - { - return null; - } - else - { - return document.FilePath; - } - } - internal void StartSolutionCrawler() { if (_registrationService == null) @@ -1166,7 +1147,7 @@ protected override void Dispose(bool finalize) public void EnsureEditableDocuments(IEnumerable documents) { - var queryEdit = (IVsQueryEditQuerySave2)DeferredState.ServiceProvider.GetService(typeof(SVsQueryEditQuerySave)); + var queryEdit = (IVsQueryEditQuerySave2)ServiceProvider.GlobalProvider.GetService(typeof(SVsQueryEditQuerySave)); var fileNames = documents.Select(GetFilePath).ToArray(); @@ -1257,7 +1238,7 @@ internal override bool CanAddProjectReference(ProjectId referencingProject, Proj // In both directions things are still unknown. Fallback to the reference manager // to make the determination here. - var referenceManager = (IVsReferenceManager)DeferredState.ServiceProvider.GetService(typeof(SVsReferenceManager)); + var referenceManager = (IVsReferenceManager)ServiceProvider.GlobalProvider.GetService(typeof(SVsReferenceManager)); if (referenceManager == null) { // Couldn't get the reference manager. Have to assume it's not allowed. @@ -1269,5 +1250,276 @@ internal override bool CanAddProjectReference(ProjectId referencingProject, Proj var result = referenceManager.QueryCanReferenceProject(referencingHierarchy, referencedHierarchy); return result != (uint)__VSREFERENCEQUERYRESULT.REFERENCE_DENY; } + + private readonly object _gate = new object(); + + /// + /// Applies a single operation to the workspace. should be a call to one of the protected Workspace.On* methods. + /// + public void ApplyChangeToWorkspace(Action action) + { + lock (_gate) + { + action(this); + } + } + + /// + /// Applies a change to the workspace that can do any number of project changes. + /// + /// This is needed to synchronize with to avoid any races. This + /// method could be moved down to the core Workspace layer and then could use the synchronization lock there. + /// The to change. + /// A function that, given the old will produce a new one. + public void ApplyBatchChangeToProject(ProjectId projectId, Func mutation) + { + lock (_gate) + { + var oldSolution = this.CurrentSolution; + var newSolution = mutation(oldSolution); + + if (oldSolution == newSolution) + { + return; + } + + SetCurrentSolution(newSolution); + RaiseWorkspaceChangedEventAsync(WorkspaceChangeKind.ProjectChanged, oldSolution, newSolution, projectId); + } + } + + private Dictionary _projectReferenceInfo = new Dictionary(); + + private ProjectReferenceInformation GetReferenceInfo_NoLock(ProjectId projectId) + { + return _projectReferenceInfo.GetOrAdd(projectId, _ => new ProjectReferenceInformation()); + } + + protected internal override void OnProjectRemoved(ProjectId projectId) + { + lock (_gate) + { + _projectReferenceInfo.Remove(projectId); + _projectToGuidMap = _projectToGuidMap.Remove(projectId); + _projectToHierarchyMap = _projectToHierarchyMap.Remove(projectId); + + foreach (var pair in _projectUniqueNameToProjectMap) + { + if (pair.Value.Id == projectId) + { + _projectUniqueNameToProjectMap = _projectUniqueNameToProjectMap.Remove(pair.Key); + break; + } + } + + base.OnProjectRemoved(projectId); + } + } + + private class ProjectReferenceInformation + { + public List OutputPaths = new List(); + public List<(string path, ProjectReference projectReference)> ConvertedProjectReferences = new List<(string path, ProjectReference)>(); + } + + /// + /// A multimap from an output path to the project outputting to it. Ideally, this shouldn't ever + /// actually be a true multimap, since we shouldn't have two projects outputting to the same path, but + /// any bug by a project adding the wrong output path means we could end up with some duplication. + /// In that case, we'll temporarily have two until (hopefully) somebody removes it. + /// + private readonly Dictionary> _projectsByOutputPath = new Dictionary>(StringComparer.OrdinalIgnoreCase); + + public void AddProjectOutputPath(ProjectId projectId, string outputPath) + { + lock (_gate) + { + var projectReferenceInformation = GetReferenceInfo_NoLock(projectId); + + projectReferenceInformation.OutputPaths.Add(outputPath); + _projectsByOutputPath.MultiAdd(outputPath, projectId); + + var projectsForOutputPath = _projectsByOutputPath[outputPath]; + var distinctProjectsForOutputPath = projectsForOutputPath.Distinct().ToList(); + + // If we have exactly one, then we're definitely good to convert + if (projectsForOutputPath.Count == 1) + { + ConvertMetadataReferencesToProjectReferences_NoLock(projectId, outputPath); + } + else if (distinctProjectsForOutputPath.Count == 1) + { + // The same project has multiple output paths that are the same. Any project would have already been converted + // by the prior add, so nothing further to do + } + else + { + // We have more than one project outputting to the same path. This shouldn't happen but we'll convert back + // because now we don't know which project to reference. + ConvertProjectReferencesToMetadataReferences_NoLock(projectId, outputPath); + } + } + } + + private void ConvertMetadataReferencesToProjectReferences_NoLock(ProjectId projectId, string outputPath) + { + var modifiedSolution = this.CurrentSolution; + var projectIdsChanged = new HashSet(); + + foreach (var projectIdToRetarget in this.CurrentSolution.ProjectIds) + { + foreach (PortableExecutableReference reference in modifiedSolution.GetProject(projectIdToRetarget).MetadataReferences) + { + if (string.Equals(reference.FilePath, outputPath, StringComparison.OrdinalIgnoreCase)) + { + var projectReference = new ProjectReference(projectId, reference.Properties.Aliases, reference.Properties.EmbedInteropTypes); + modifiedSolution = modifiedSolution.RemoveMetadataReference(projectIdToRetarget, reference) + .AddProjectReference(projectIdToRetarget, projectReference); + + projectIdsChanged.Add(projectIdToRetarget); + + GetReferenceInfo_NoLock(projectIdToRetarget).ConvertedProjectReferences.Add( + (reference.FilePath, projectReference)); + + // We have converted one, but you could have more than one reference with different aliases + // that we need to convert, so we'll keep going + } + } + } + + SetSolutionAndRaiseWorkspaceChanged_NoLock(modifiedSolution, projectIdsChanged); + } + + private void ConvertProjectReferencesToMetadataReferences_NoLock(ProjectId projectId, string outputPath) + { + var modifiedSolution = this.CurrentSolution; + var projectIdsChanged = new HashSet(); + + foreach (var projectIdToRetarget in this.CurrentSolution.ProjectIds) + { + var referenceInfo = GetReferenceInfo_NoLock(projectIdToRetarget); + + foreach (var convertedReference in referenceInfo.ConvertedProjectReferences.ToList()) + { + if (string.Equals(convertedReference.path, outputPath, StringComparison.OrdinalIgnoreCase) && + convertedReference.projectReference.ProjectId == projectId) + { + var metadataReference = + CreateMetadataReference( + convertedReference.path, + new MetadataReferenceProperties( + aliases: convertedReference.projectReference.Aliases, + embedInteropTypes: convertedReference.projectReference.EmbedInteropTypes)); + + modifiedSolution = modifiedSolution.RemoveProjectReference(projectIdToRetarget, convertedReference.projectReference) + .AddMetadataReference(projectIdToRetarget, metadataReference); + + projectIdsChanged.Add(projectIdToRetarget); + + referenceInfo.ConvertedProjectReferences.Remove(convertedReference); + + // We have converted one, but you could have more than one reference with different aliases + // that we need to convert, so we'll keep going + } + } + } + + SetSolutionAndRaiseWorkspaceChanged_NoLock(modifiedSolution, projectIdsChanged); + } + + public ProjectReference TryCreateConvertedProjectReference(ProjectId referencingProject, string path, MetadataReferenceProperties properties) + { + lock (_gate) + { + if (_projectsByOutputPath.TryGetValue(path, out var ids) && ids.Distinct().Count() == 1) + { + var projectReference = new ProjectReference( + ids.First(), + aliases: properties.Aliases, + embedInteropTypes: properties.EmbedInteropTypes); + + GetReferenceInfo_NoLock(referencingProject).ConvertedProjectReferences.Add((path, projectReference)); + + return projectReference; + } + else + { + return null; + } + } + } + + public ProjectReference TryRemoveConvertedProjectReference(ProjectId referencingProject, string path, MetadataReferenceProperties properties) + { + lock (_gate) + { + var projectReferenceInformation = GetReferenceInfo_NoLock(referencingProject); + foreach (var convertedProject in projectReferenceInformation.ConvertedProjectReferences) + { + if (convertedProject.path == path && + convertedProject.projectReference.EmbedInteropTypes == properties.EmbedInteropTypes && + convertedProject.projectReference.Aliases.SequenceEqual(properties.Aliases)) + { + projectReferenceInformation.ConvertedProjectReferences.Remove(convertedProject); + return convertedProject.projectReference; + } + } + } + + return null; + } + + public MetadataReference CreateMetadataReference(string path, MetadataReferenceProperties properties) + { + return Services.GetRequiredService().GetReference(path, properties); + } + + private void SetSolutionAndRaiseWorkspaceChanged_NoLock(CodeAnalysis.Solution modifiedSolution, ICollection projectIdsChanged) + { + if (projectIdsChanged.Count > 0) + { + var originalSolution = this.CurrentSolution; + SetCurrentSolution(modifiedSolution); + + if (projectIdsChanged.Count == 1) + { + RaiseWorkspaceChangedEventAsync(WorkspaceChangeKind.ProjectChanged, originalSolution, this.CurrentSolution, projectIdsChanged.Single()); + } + else + { + RaiseWorkspaceChangedEventAsync(WorkspaceChangeKind.SolutionChanged, originalSolution, this.CurrentSolution); + } + } + } + + public void RemoveProjectOutputPath(ProjectId projectId, string outputPath) + { + lock (_gate) + { + var projectReferenceInformation = GetReferenceInfo_NoLock(projectId); + if (!projectReferenceInformation.OutputPaths.Contains(outputPath)) + { + throw new ArgumentException($"Project does not contain output path '{outputPath}'", nameof(outputPath)); + } + + projectReferenceInformation.OutputPaths.Remove(outputPath); + _projectsByOutputPath.MultiRemove(outputPath, projectId); + + if (_projectsByOutputPath.TryGetValue(outputPath, out var remainingProjectsForOutputPath)) + { + if (remainingProjectsForOutputPath.Distinct().Count() == 1) + { + // We had more than one project outputting to the same path. Now we're back down to one + // so we can reference that one again + ConvertMetadataReferencesToProjectReferences_NoLock(_projectsByOutputPath[outputPath].Single(), outputPath); + } + } + else + { + // No projects left, we need to convert back to metadata references + ConvertProjectReferencesToMetadataReferences_NoLock(projectId, outputPath); + } + } + } } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl_IVsSolutionEvents.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl_IVsSolutionEvents.cs deleted file mode 100644 index 1712be2e3dfae3e817c4cdfc6af39512fa79bfaf..0000000000000000000000000000000000000000 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl_IVsSolutionEvents.cs +++ /dev/null @@ -1,89 +0,0 @@ -// 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 System.Linq; -using System.Threading; -using Microsoft.VisualStudio.Shell.Interop; -using Roslyn.Utilities; -using Microsoft.CodeAnalysis.Host; -using System; -using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Interop; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -{ - internal partial class VisualStudioWorkspaceImpl : IVsSolutionEvents - { - private IVsSolution _vsSolution; - private uint? _solutionEventsCookie; - - public void AdviseSolutionEvents(IVsSolution solution) - { - _vsSolution = solution; - if (_solutionEventsCookie == null) - { - _vsSolution.AdviseSolutionEvents(this, out var solutionEventsCookie); - _solutionEventsCookie = solutionEventsCookie; - } - } - - public void UnadviseSolutionEvents() - { - if (_solutionEventsCookie != null) - { - _vsSolution.UnadviseSolutionEvents(_solutionEventsCookie.Value); - _solutionEventsCookie = null; - } - } - - int IVsSolutionEvents.OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy) - { - return VSConstants.S_OK; - } - - int IVsSolutionEvents.OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) - { - return VSConstants.S_OK; - } - - int IVsSolutionEvents.OnAfterOpenSolution(object pUnkReserved, int fNewSolution) - { - return VSConstants.S_OK; - } - - int IVsSolutionEvents.OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel) - { - return VSConstants.S_OK; - } - - int IVsSolutionEvents.OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved) - { - return VSConstants.S_OK; - } - - int IVsSolutionEvents.OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel) - { - return VSConstants.S_OK; - } - - int IVsSolutionEvents.OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy) - { - return VSConstants.S_OK; - } - - int IVsSolutionEvents.OnQueryCloseSolution(object pUnkReserved, ref int pfCancel) - { - return VSConstants.S_OK; - } - - int IVsSolutionEvents.OnBeforeCloseSolution(object pUnkReserved) - { - DeferredState?.ProjectTracker.OnBeforeCloseSolution(); - return VSConstants.S_OK; - } - - int IVsSolutionEvents.OnAfterCloseSolution(object pUnkReserved) - { - DeferredState?.ProjectTracker.OnAfterCloseSolution(); - return VSConstants.S_OK; - } - } -} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl_IVsSolutionLoadEvents.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl_IVsSolutionLoadEvents.cs deleted file mode 100644 index 27cccf0a6b1448b43f9850e4423467c17da94d99..0000000000000000000000000000000000000000 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl_IVsSolutionLoadEvents.cs +++ /dev/null @@ -1,48 +0,0 @@ -// 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.Shell; -using Microsoft.VisualStudio.Shell.Interop; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -{ - internal partial class VisualStudioWorkspaceImpl : IVsSolutionLoadEvents - { - int IVsSolutionLoadEvents.OnBeforeOpenSolution(string pszSolutionFilename) - { - GetProjectTrackerAndInitializeIfNecessary(ServiceProvider.GlobalProvider).OnBeforeOpenSolution(); - return VSConstants.S_OK; - } - - int IVsSolutionLoadEvents.OnBeforeBackgroundSolutionLoadBegins() - { - return VSConstants.S_OK; - } - - int IVsSolutionLoadEvents.OnQueryBackgroundLoadProjectBatch(out bool pfShouldDelayLoadToNextIdle) - { - pfShouldDelayLoadToNextIdle = false; - return VSConstants.S_OK; - } - - int IVsSolutionLoadEvents.OnBeforeLoadProjectBatch(bool fIsBackgroundIdleBatch) - { - _foregroundObject.AssertIsForeground(); - GetProjectTrackerAndInitializeIfNecessary(ServiceProvider.GlobalProvider).OnBeforeLoadProjectBatch(fIsBackgroundIdleBatch); - return VSConstants.S_OK; - } - - int IVsSolutionLoadEvents.OnAfterLoadProjectBatch(bool fIsBackgroundIdleBatch) - { - _foregroundObject.AssertIsForeground(); - GetProjectTrackerAndInitializeIfNecessary(ServiceProvider.GlobalProvider).OnAfterLoadProjectBatch(fIsBackgroundIdleBatch); - return VSConstants.S_OK; - } - - int IVsSolutionLoadEvents.OnAfterBackgroundSolutionLoadComplete() - { - _foregroundObject.AssertIsForeground(); - GetProjectTrackerAndInitializeIfNecessary(ServiceProvider.GlobalProvider).OnAfterBackgroundSolutionLoadComplete(); - return VSConstants.S_OK; - } - } -} diff --git a/src/VisualStudio/Core/Def/Implementation/SaveEventsService.cs b/src/VisualStudio/Core/Def/Implementation/SaveEventsService.cs index 10f232ce023f43aa0667de7a21045d655828b2a6..b6d0d5719bc7f15c3cc8995e09e599d036e44b15 100644 --- a/src/VisualStudio/Core/Def/Implementation/SaveEventsService.cs +++ b/src/VisualStudio/Core/Def/Implementation/SaveEventsService.cs @@ -101,25 +101,26 @@ private void OnBeforeSaveWorker(uint docCookie) // We want to raise a save event for this document. First let's try to get the docData Marshal.ThrowExceptionForHR(_runningDocumentTable.GetDocumentInfo(docCookie, out var flags, out var readLocks, out var writeLocks, out var moniker, out var hierarchy, out var itemid, out docData)); - var textBuffer = TryGetTextBufferFromDocData(docData); - - // Do a quick check that this is a Roslyn file at all before we go do more expensive things - if (textBuffer != null && textBuffer.ContentType.IsOfType(ContentTypeNames.RoslynContentType)) + if (Marshal.GetObjectForIUnknown(docData) is IVsTextBuffer textBufferAdapter) { - var textBufferAdapter = _editorAdaptersFactoryService.GetBufferAdapter(textBuffer); + var textBuffer = _editorAdaptersFactoryService.GetDocumentBuffer(textBufferAdapter); - if (textBufferAdapter != null) + // Do a quick check that this is a Roslyn file at all before we go do more expensive things + if (textBuffer != null && textBuffer.ContentType.IsOfType(ContentTypeNames.RoslynContentType)) { - // OK, we want to go and raise a save event. Currently, CommandArgs demands that we have a view, so let's try to go and find one. - _textManager.EnumViews(textBufferAdapter, out var enumTextViews); - IVsTextView[] views = new IVsTextView[1]; - uint fetched = 0; - - if (ErrorHandler.Succeeded(enumTextViews.Next(1, views, ref fetched)) && fetched == 1) + if (textBufferAdapter != null) { - var view = _editorAdaptersFactoryService.GetWpfTextView(views[0]); - var commandHandlerService = _commandHandlerServiceFactory.GetService(textBuffer); - commandHandlerService.Execute(textBuffer.ContentType, new SaveCommandArgs(view, textBuffer)); + // OK, we want to go and raise a save event. Currently, CommandArgs demands that we have a view, so let's try to go and find one. + _textManager.EnumViews(textBufferAdapter, out var enumTextViews); + IVsTextView[] views = new IVsTextView[1]; + uint fetched = 0; + + if (ErrorHandler.Succeeded(enumTextViews.Next(1, views, ref fetched)) && fetched == 1) + { + var view = _editorAdaptersFactoryService.GetWpfTextView(views[0]); + var commandHandlerService = _commandHandlerServiceFactory.GetService(textBuffer); + commandHandlerService.Execute(textBuffer.ContentType, new SaveCommandArgs(view, textBuffer)); + } } } } @@ -132,23 +133,5 @@ private void OnBeforeSaveWorker(uint docCookie) } } } - - /// - /// Tries to return an ITextBuffer representing the document from the document's DocData. - /// - /// The DocData from the running document table. - /// The ITextBuffer. If one could not be found, this returns null. - private ITextBuffer TryGetTextBufferFromDocData(IntPtr docData) - { - - if (Marshal.GetObjectForIUnknown(docData) is IVsTextBuffer shimTextBuffer) - { - return _editorAdaptersFactoryService.GetDocumentBuffer(shimTextBuffer); - } - else - { - return null; - } - } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs b/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs index 04878aa61107335b38b1a38493411c3b5607101b..e69226067128fc60fc0741f71269efd5f4e4be30 100644 --- a/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs +++ b/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs @@ -566,13 +566,13 @@ protected static bool TryAddImportsToContainedDocument(Document document, IEnume return false; } - var containedDocument = vsWorkspace.GetHostDocument(document.Id) as ContainedDocument; + var containedDocument = vsWorkspace.TryGetContainedDocument(document.Id); if (containedDocument == null) { return false; } - if (containedDocument.ContainedLanguage.ContainedLanguageHost is IVsContainedLanguageHostInternal containedLanguageHost) + if (containedDocument.ContainedLanguageHost is IVsContainedLanguageHostInternal containedLanguageHost) { foreach (var importClause in memberImportsNamespaces) { diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/Extensions.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/Extensions.cs index fb90f289c27ae1377fd870ddd4baae887e50bdab..a3aa7174ddf6f8d782d3726ed9e4e8776d6d9783 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/Extensions.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/Extensions.cs @@ -159,14 +159,8 @@ public static Guid GetProjectGuid(this Workspace workspace, ProjectId projectId) return Guid.Empty; } - var vsWorkspace = workspace as VisualStudioWorkspaceImpl; - var project = vsWorkspace?.GetHostProject(projectId); - if (project == null) - { - return Guid.Empty; - } - - return project.Guid; + var vsWorkspace = workspace as VisualStudioWorkspace; + return vsWorkspace?.GetProjectGuid(projectId) ?? Guid.Empty; } public static Guid[] GetProjectGuids(this Workspace workspace, ImmutableArray projectIds) diff --git a/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs b/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs index 1c5a243279ed5bb12bf988293cee8de15dceb83b..4d7d04bc07b5e51a66b7192b08e480cd38baf06c 100644 --- a/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs +++ b/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs @@ -653,8 +653,7 @@ public int GetHashCode(DiagnosticData obj) return ValueTuple.Create(data.DataLocation?.MappedStartLine ?? 0, data.DataLocation?.MappedStartColumn ?? 0); } - var containedDocument = workspace.GetHostDocument(data.DocumentId) as ContainedDocument; - if (containedDocument == null) + if (workspace.TryGetContainedDocument(data.DocumentId) == null) { return ValueTuple.Create(data.DataLocation?.MappedStartLine ?? 0, data.DataLocation?.MappedStartColumn ?? 0); } diff --git a/src/VisualStudio/Core/Def/Implementation/TaskList/ProjectExternalErrorReporter.cs b/src/VisualStudio/Core/Def/Implementation/TaskList/ProjectExternalErrorReporter.cs index f2e08e6e6285d932206be39be46a9c572786b600..ac832c420c5c6b9ba307d078fef28337829482b9 100644 --- a/src/VisualStudio/Core/Def/Implementation/TaskList/ProjectExternalErrorReporter.cs +++ b/src/VisualStudio/Core/Def/Implementation/TaskList/ProjectExternalErrorReporter.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Linq; using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -14,7 +15,6 @@ using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.TextManager.Interop; using Roslyn.Utilities; -using VsTextSpan = Microsoft.VisualStudio.TextManager.Interop.TextSpan; namespace Microsoft.VisualStudio.LanguageServices.Implementation.TaskList { @@ -26,19 +26,25 @@ internal class ProjectExternalErrorReporter : IVsReportExternalErrors, IVsLangua private readonly ProjectId _projectId; private readonly string _errorCodePrefix; - private readonly VisualStudioWorkspaceImpl _workspace; + private readonly VisualStudioWorkspace _workspace; private readonly ExternalErrorDiagnosticUpdateSource _diagnosticProvider; public ProjectExternalErrorReporter(ProjectId projectId, string errorCodePrefix, IServiceProvider serviceProvider) + : this(projectId, errorCodePrefix, serviceProvider.GetMefService(), serviceProvider.GetMefService()) { - _projectId = projectId; - _errorCodePrefix = errorCodePrefix; + } - _workspace = serviceProvider.GetMefService(); - _diagnosticProvider = serviceProvider.GetMefService(); + public ProjectExternalErrorReporter(ProjectId projectId, string errorCodePrefix, VisualStudioWorkspace workspace, ExternalErrorDiagnosticUpdateSource diagnosticProvider) + { + Debug.Assert(workspace != null); + + // TODO: re-enable this assert; right now it'll fail in unit tests + // Debug.Assert(diagnosticProvider != null); - Debug.Assert(_workspace != null); - Debug.Assert(_diagnosticProvider != null); + _projectId = projectId; + _errorCodePrefix = errorCodePrefix; + _workspace = workspace; + _diagnosticProvider = diagnosticProvider; } private bool CanHandle(string errorId) @@ -112,18 +118,24 @@ private DiagnosticData CreateProjectDiagnosticItem(ExternalError error) return GetDiagnosticData(error); } + private DocumentId TryGetDocumentId(string filePath) + { + return _workspace.CurrentSolution.GetDocumentIdsWithFilePath(filePath) + .Where(f => f.ProjectId == _projectId) + .FirstOrDefault(); + } + private DiagnosticData CreateDocumentDiagnosticItem(ExternalError error) { - var hostProject = _workspace.GetHostProject(_projectId); - if (!hostProject.ContainsFile(error.bstrFileName)) + var documentId = TryGetDocumentId(error.bstrFileName); + if (documentId == null) { return null; } - var hostDocument = hostProject.GetCurrentDocumentFromPath(error.bstrFileName); - var line = error.iLine; var column = error.iCol; + /* TODO: make work again if (hostDocument is ContainedDocument containedDocument) { var span = new VsTextSpan @@ -142,8 +154,9 @@ private DiagnosticData CreateDocumentDiagnosticItem(ExternalError error) line = spans[0].iStartLine; column = spans[0].iStartIndex; } + */ - return GetDiagnosticData(error, hostDocument.Id, line, column); + return GetDiagnosticData(error, documentId, line, column); } public int ReportError(string bstrErrorMessage, string bstrErrorId, [ComAliasName("VsShell.VSTASKPRIORITY")]VSTASKPRIORITY nPriority, int iLine, int iColumn, string bstrFileName) @@ -198,8 +211,8 @@ public void ReportError2(string bstrErrorMessage, string bstrErrorId, [ComAliasN return; } - var hostProject = _workspace.GetHostProject(_projectId); - if (!hostProject.ContainsFile(bstrFileName)) + var documentId = TryGetDocumentId(bstrFileName); + if (documentId == null) { var projectDiagnostic = GetDiagnosticData( null, bstrErrorId, bstrErrorMessage, severity, @@ -210,14 +223,12 @@ public void ReportError2(string bstrErrorMessage, string bstrErrorId, [ComAliasN return; } - var hostDocument = hostProject.GetCurrentDocumentFromPath(bstrFileName); - var diagnostic = GetDiagnosticData( - hostDocument.Id, bstrErrorId, bstrErrorMessage, severity, + documentId, bstrErrorId, bstrErrorMessage, severity, null, iStartLine, iStartColumn, iEndLine, iEndColumn, bstrFileName, iStartLine, iStartColumn, iEndLine, iEndColumn); - _diagnosticProvider.AddNewErrors(hostDocument.Id, diagnostic); + _diagnosticProvider.AddNewErrors(documentId, diagnostic); } public int ClearErrors() diff --git a/src/VisualStudio/Core/Def/Implementation/Venus/AbstractContainedLanguage.cs b/src/VisualStudio/Core/Def/Implementation/Venus/AbstractContainedLanguage.cs index ad2b6c68b8ce3866675bf53d4dc1d812f10519a0..4d609dddc2b03dd33397ed8c99fcccb7ec630134 100644 --- a/src/VisualStudio/Core/Def/Implementation/Venus/AbstractContainedLanguage.cs +++ b/src/VisualStudio/Core/Def/Implementation/Venus/AbstractContainedLanguage.cs @@ -1,65 +1,20 @@ -// 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 System; -using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; -using Microsoft.VisualStudio.Text; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; using Microsoft.VisualStudio.TextManager.Interop; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Venus { - internal abstract class AbstractContainedLanguage : IDisposable + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] + internal sealed class AbstractContainedLanguage { - public AbstractProject Project { get; } - - /// - /// The subject (secondary) buffer that contains the C# or VB code. - /// - public ITextBuffer SubjectBuffer { get; private set; } - - /// - /// The underlying buffer that contains C# or VB code. NOTE: This is NOT the "document" buffer - /// that is saved to disk. Instead it is the view that the user sees. The normal buffer graph - /// in Venus includes 4 buffers: - /// - /// SurfaceBuffer/Databuffer (projection) - /// / | - /// Subject Buffer (C#/VB projection) | - /// | | - /// Inert (generated) C#/VB Buffer Document (aspx) buffer - /// - /// In normal circumstance, the Subject and Inert C# buffer are identical in content, and the - /// Surface and Document are also identical. The Subject Buffer is the one that is part of the - /// workspace, that most language operations deal with. The surface buffer is the one that the - /// view is created over, and the Document buffer is the one that is saved to disk. - /// - public ITextBuffer DataBuffer { get; private set; } - - public IVsContainedLanguageHost ContainedLanguageHost { get; protected set; } - public IVsTextBufferCoordinator BufferCoordinator { get; protected set; } - - public AbstractContainedLanguage( - AbstractProject project) - { - this.Project = project ?? throw new ArgumentNullException(nameof(project)); - } - - /// - /// To be called from the derived class constructor! - /// - /// - protected void SetSubjectBuffer(ITextBuffer subjectBuffer) - { - this.SubjectBuffer = subjectBuffer ?? throw new ArgumentNullException(nameof(subjectBuffer)); - } - - /// - /// To be called from the derived class constructor! - /// - protected void SetDataBuffer(ITextBuffer dataBuffer) + public AbstractContainedLanguage(IVsContainedLanguageHost host) { - this.DataBuffer = dataBuffer ?? throw new ArgumentNullException(nameof(dataBuffer)); + ContainedLanguageHost = host; } - public abstract void Dispose(); + public IVsContainedLanguageHost ContainedLanguageHost { get; } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Venus/ContainedDocument.cs b/src/VisualStudio/Core/Def/Implementation/Venus/ContainedDocument.cs index 803552d2ff99af1f8548608df14987ccce398f7c..81d962253fe9ff971a151b0aceea752504b420f4 100644 --- a/src/VisualStudio/Core/Def/Implementation/Venus/ContainedDocument.cs +++ b/src/VisualStudio/Core/Def/Implementation/Venus/ContainedDocument.cs @@ -1,9 +1,9 @@ // 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 System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading; @@ -26,17 +26,15 @@ using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods; using Microsoft.VisualStudio.Text.Projection; -using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; +using IVsContainedLanguageHost = Microsoft.VisualStudio.TextManager.Interop.IVsContainedLanguageHost; +using IVsTextBufferCoordinator = Microsoft.VisualStudio.TextManager.Interop.IVsTextBufferCoordinator; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Venus { - using Workspace = Microsoft.CodeAnalysis.Workspace; - - /// - /// An IVisualStudioDocument which represents the secondary buffer to the workspace API. - /// +#pragma warning disable CS0618 // Type or member is obsolete internal sealed class ContainedDocument : ForegroundThreadAffinitizedObject, IVisualStudioHostDocument +#pragma warning restore CS0618 // Type or member is obsolete { private const string ReturnReplacementString = @"{|r|}"; private const string NewLineReplacementString = @"{|n|}"; @@ -60,71 +58,83 @@ internal sealed class ContainedDocument : ForegroundThreadAffinitizedObject, IVi IgnoreTrimWhiteSpace = false }); - private readonly AbstractContainedLanguage _containedLanguage; - private readonly SourceCodeKind _sourceCodeKind; + private static ConcurrentDictionary s_containedDocuments = new ConcurrentDictionary(); + public static ContainedDocument TryGetContainedDocument(DocumentId id) + { + ContainedDocument document; + s_containedDocuments.TryGetValue(id, out document); + + return document; + } + private readonly IComponentModel _componentModel; - private readonly Workspace _workspace; + private readonly VisualStudioWorkspace _workspace; private readonly ITextDifferencingSelectorService _differenceSelectorService; private readonly HostType _hostType; private readonly ReiteratedVersionSnapshotTracker _snapshotTracker; private readonly IFormattingRule _vbHelperFormattingRule; - private readonly string _itemMoniker; + private readonly VisualStudioProject _project; - public AbstractProject Project { get { return _containedLanguage.Project; } } public bool SupportsRename { get { return _hostType == HostType.Razor; } } public DocumentId Id { get; } - public IReadOnlyList Folders { get; } - public TextLoader Loader { get; } - public DocumentKey Key { get; } + public ITextBuffer SubjectBuffer { get; } + public ITextBuffer DataBuffer { get; } + public IVsTextBufferCoordinator BufferCoordinator { get; } + public IVsContainedLanguageHost ContainedLanguageHost { get; set; } public ContainedDocument( IThreadingContext threadingContext, - AbstractContainedLanguage containedLanguage, - SourceCodeKind sourceCodeKind, - Workspace workspace, + DocumentId documentId, + ITextBuffer subjectBuffer, + ITextBuffer dataBuffer, + IVsTextBufferCoordinator bufferCoordinator, + VisualStudioWorkspace workspace, + VisualStudioProject project, IVsHierarchy hierarchy, uint itemId, IComponentModel componentModel, IFormattingRule vbHelperFormattingRule) : base(threadingContext) { - Contract.ThrowIfNull(containedLanguage); - - _containedLanguage = containedLanguage; - _sourceCodeKind = sourceCodeKind; _componentModel = componentModel; _workspace = workspace; + _project = project; + + Id = documentId; + SubjectBuffer = subjectBuffer; + DataBuffer = dataBuffer; + BufferCoordinator = bufferCoordinator; + + _differenceSelectorService = componentModel.GetService(); + _snapshotTracker = new ReiteratedVersionSnapshotTracker(SubjectBuffer); + _vbHelperFormattingRule = vbHelperFormattingRule; + _hostType = GetHostType(); - if (!ErrorHandler.Succeeded(((IVsProject)hierarchy).GetMkDocument(itemId, out var filePath))) + s_containedDocuments.TryAdd(documentId, this); + } + + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] + internal AbstractProject Project + { + get { - // we couldn't look up the document moniker from an hierarchy for an itemid. - // Since we only use this moniker as a key, we could fall back to something else, like the document name. - Debug.Assert(false, "Could not get the document moniker for an item from its hierarchy."); - if (!hierarchy.TryGetItemName(itemId, out filePath)) - { - Environment.FailFast("Failed to get document moniker for a contained document"); - } + return _componentModel.GetService().GetProjectTrackerAndInitializeIfNecessary().GetProject(_project.Id); } + } - if (Project.Hierarchy != null) + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] + internal AbstractContainedLanguage ContainedLanguage + { + get { - Project.Hierarchy.GetCanonicalName(itemId, out var moniker); - _itemMoniker = moniker; + return new AbstractContainedLanguage(ContainedLanguageHost); } - - this.Key = new DocumentKey(Project, filePath); - this.Id = DocumentId.CreateNewId(Project.Id, filePath); - this.Folders = containedLanguage.Project.GetFolderNamesFromHierarchy(itemId); - this.Loader = TextLoader.From(containedLanguage.SubjectBuffer.AsTextContainer(), VersionStamp.Create(), filePath); - _differenceSelectorService = componentModel.GetService(); - _snapshotTracker = new ReiteratedVersionSnapshotTracker(_containedLanguage.SubjectBuffer); - _vbHelperFormattingRule = vbHelperFormattingRule; } private HostType GetHostType() { - if (_containedLanguage.DataBuffer is IProjectionBuffer projectionBuffer) + if (DataBuffer is IProjectionBuffer projectionBuffer) { // RazorCSharp has an HTMLX base type but should not be associated with // the HTML host type, so we check for it first. @@ -146,7 +156,7 @@ private HostType GetHostType() // XOML is set up differently. For XOML, the secondary buffer (i.e. SubjectBuffer) // is a projection buffer, while the primary buffer (i.e. DataBuffer) is not. Instead, // the primary buffer is a regular unprojected ITextBuffer with the HTML content type. - if (_containedLanguage.DataBuffer.CurrentSnapshot.ContentType.IsOfType(HTML)) + if (DataBuffer.CurrentSnapshot.ContentType.IsOfType(HTML)) { return HostType.XOML; } @@ -155,109 +165,42 @@ private HostType GetHostType() throw ExceptionUtilities.Unreachable; } - public DocumentInfo GetInitialState() - { - return DocumentInfo.Create( - this.Id, - this.Name, - folders: this.Folders, - sourceCodeKind: _sourceCodeKind, - loader: this.Loader, - filePath: this.Key.Moniker); - } - - public bool IsOpen - { - get - { - return true; - } - } - -#pragma warning disable 67 - - public event EventHandler UpdatedOnDisk; - public event EventHandler Opened; - public event EventHandler Closing; - -#pragma warning restore 67 - - public ITextBuffer GetOpenTextBuffer() - { - return _containedLanguage.SubjectBuffer; - } public SourceTextContainer GetOpenTextContainer() { - return this.GetOpenTextBuffer().AsTextContainer(); + return this.SubjectBuffer.AsTextContainer(); } - public IContentType ContentType + public void Dispose() { - get - { - return _containedLanguage.SubjectBuffer.ContentType; - } + _snapshotTracker.StopTracking(SubjectBuffer); + s_containedDocuments.TryRemove(Id, out _); } - public string Name + public DocumentId FindProjectDocumentIdWithItemId(uint itemidInsertionPoint) { - get + var hierarchy = _workspace.GetHierarchy(_project.Id); + + foreach (var document in _workspace.CurrentSolution.GetProject(_project.Id).Documents) { - try - { - return Path.GetFileName(this.FilePath); - } - catch (ArgumentException) + if (document.FilePath != null && hierarchy.TryGetItemId(document.FilePath) == itemidInsertionPoint) { - return this.FilePath; + return document.Id; } } - } - public SourceCodeKind SourceCodeKind - { - get - { - return _sourceCodeKind; - } - } - - public string FilePath - { - get - { - return Key.Moniker; - } - } - - public AbstractContainedLanguage ContainedLanguage - { - get - { - return _containedLanguage; - } - } - - public void Dispose() - { - _snapshotTracker.StopTracking(_containedLanguage.SubjectBuffer); - this.ContainedLanguage.Dispose(); - } - - public DocumentId FindProjectDocumentIdWithItemId(uint itemidInsertionPoint) - { - return Project.GetCurrentDocuments().SingleOrDefault(d => d.GetItemId() == itemidInsertionPoint).Id; + return null; } public uint FindItemIdOfDocument(Document document) { - return Project.GetDocumentOrAdditionalDocument(document.Id).GetItemId(); + var hierarchy = _workspace.GetHierarchy(_project.Id); + return hierarchy.TryGetItemId(_workspace.CurrentSolution.GetDocument(document.Id).FilePath); } public void UpdateText(SourceText newText) { - var subjectBuffer = (IProjectionBuffer)this.GetOpenTextBuffer(); + var subjectBuffer = (IProjectionBuffer)this.SubjectBuffer; var originalSnapshot = subjectBuffer.CurrentSnapshot; var originalText = originalSnapshot.AsText(); @@ -637,7 +580,7 @@ private bool TextAt(string text, int index, char ch1, char ch2 = default) private IHierarchicalDifferenceCollection DiffStrings(string leftTextWithReplacement, string rightTextWithReplacement) { var diffService = _differenceSelectorService.GetTextDifferencingService( - _workspace.Services.GetLanguageServices(this.Project.Language).GetService().GetDefaultContentType()); + _workspace.Services.GetLanguageServices(_project.Language).GetService().GetDefaultContentType()); diffService = diffService ?? _differenceSelectorService.DefaultTextDifferencingService; return diffService.DiffStrings(leftTextWithReplacement, rightTextWithReplacement, s_venusEditOptions.DifferenceOptions); @@ -735,9 +678,9 @@ private Span AdjustSpan(Span span, List> replacementMap) public IEnumerable GetEditorVisibleSpans() { - var subjectBuffer = (IProjectionBuffer)this.GetOpenTextBuffer(); + var subjectBuffer = (IProjectionBuffer)this.SubjectBuffer; - if (_containedLanguage.DataBuffer is IProjectionBuffer projectionDataBuffer) + if (DataBuffer is IProjectionBuffer projectionDataBuffer) { return projectionDataBuffer.CurrentSnapshot .GetSourceSpans() @@ -810,7 +753,7 @@ private void AdjustIndentation(IProjectionBuffer subjectBuffer, IEnumerable var root = document.GetSyntaxRootSynchronously(CancellationToken.None); var editorOptionsFactory = _componentModel.GetService(); - var editorOptions = editorOptionsFactory.GetOptions(_containedLanguage.DataBuffer); + var editorOptions = editorOptionsFactory.GetOptions(DataBuffer); var options = _workspace.Options .WithChangedOption(FormattingOptions.NewLine, root.Language, editorOptions.GetNewLineCharacter()) .WithChangedOption(FormattingOptions.UseTabs, root.Language, !editorOptions.IsConvertTabsToSpacesEnabled()) @@ -932,7 +875,7 @@ private int GetBaseIndentation(SyntaxNode root, SourceText text, TextSpan span) { // Is this right? We should probably get this from the IVsContainedLanguageHost instead. var editorOptionsFactory = _componentModel.GetService(); - var editorOptions = editorOptionsFactory.GetOptions(_containedLanguage.DataBuffer); + var editorOptions = editorOptionsFactory.GetOptions(DataBuffer); var additionalIndentation = GetAdditionalIndentation(root, text, span); int useTabs = 0, tabSize = 0; @@ -942,7 +885,7 @@ private int GetBaseIndentation(SyntaxNode root, SourceText text, TextSpan span) for (var line = startingLine; line.Start < span.End; line = text.Lines[line.LineNumber + 1]) { Marshal.ThrowExceptionForHR( - this.ContainedLanguage.ContainedLanguageHost.GetLineIndent( + ContainedLanguageHost.GetLineIndent( line.LineNumber, out var baseIndentationString, out var parent, @@ -1001,7 +944,7 @@ private int GetAdditionalIndentation(SyntaxNode root, SourceText text, TextSpan { if (_hostType == HostType.HTML) { - return _workspace.Options.GetOption(FormattingOptions.IndentationSize, this.Project.Language); + return _workspace.Options.GetOption(FormattingOptions.IndentationSize, _project.Language); } if (_hostType == HostType.Razor) @@ -1015,14 +958,14 @@ private int GetAdditionalIndentation(SyntaxNode root, SourceText text, TextSpan // in both subject and surface buffer and there is no easy way to figure out who owns } just typed. // in this case, we let razor owns it. later razor will remove } from subject buffer if it is something // razor owns. - if (this.Project.Language == LanguageNames.CSharp) + if (_project.Language == LanguageNames.CSharp) { var textSpan = GetVisibleTextSpan(text, span); var end = textSpan.End - 1; if (end >= 0 && text[end] == '}') { var token = root.FindToken(end); - var syntaxFact = _workspace.Services.GetLanguageServices(Project.Language).GetService(); + var syntaxFact = _workspace.Services.GetLanguageServices(_project.Language).GetService(); if (token.Span.Start == end && syntaxFact != null) { if (syntaxFact.TryGetCorrespondingOpenBrace(token, out var openBrace) && !textSpan.Contains(openBrace.Span)) @@ -1034,10 +977,10 @@ private int GetAdditionalIndentation(SyntaxNode root, SourceText text, TextSpan } // same as C#, but different text is in the buffer - if (this.Project.Language == LanguageNames.VisualBasic) + if (_project.Language == LanguageNames.VisualBasic) { var textSpan = GetVisibleTextSpan(text, span); - var subjectSnapshot = _containedLanguage.SubjectBuffer.CurrentSnapshot; + var subjectSnapshot = SubjectBuffer.CurrentSnapshot; var end = textSpan.End - 1; if (end >= 0) { @@ -1046,7 +989,7 @@ private int GetAdditionalIndentation(SyntaxNode root, SourceText text, TextSpan CheckCode(subjectSnapshot, textSpan.End, ch, FunctionsRazor, checkAt: false)) { var token = root.FindToken(end, findInsideTrivia: true); - var syntaxFact = _workspace.Services.GetLanguageServices(Project.Language).GetService(); + var syntaxFact = _workspace.Services.GetLanguageServices(_project.Language).GetService(); if (token.Span.End == textSpan.End && syntaxFact != null) { if (syntaxFact.IsSkippedTokensTrivia(token.Parent)) @@ -1058,7 +1001,7 @@ private int GetAdditionalIndentation(SyntaxNode root, SourceText text, TextSpan } } - return _workspace.Options.GetOption(FormattingOptions.IndentationSize, this.Project.Language); + return _workspace.Options.GetOption(FormattingOptions.IndentationSize, _project.Language); } } @@ -1069,9 +1012,9 @@ private RazorCodeBlockType GetRazorCodeBlockType(int position) { Debug.Assert(_hostType == HostType.Razor); - var subjectBuffer = (IProjectionBuffer)this.GetOpenTextBuffer(); + var subjectBuffer = (IProjectionBuffer)this.SubjectBuffer; var subjectSnapshot = subjectBuffer.CurrentSnapshot; - var surfaceSnapshot = ((IProjectionBuffer)_containedLanguage.DataBuffer).CurrentSnapshot; + var surfaceSnapshot = ((IProjectionBuffer)DataBuffer).CurrentSnapshot; var surfacePoint = surfaceSnapshot.MapFromSourceSnapshot(new SnapshotPoint(subjectSnapshot, position), PositionAffinity.Predecessor); if (!surfacePoint.HasValue) @@ -1103,13 +1046,13 @@ private RazorCodeBlockType GetRazorCodeBlockType(int position) private bool IsCodeBlock(ITextSnapshot surfaceSnapshot, int position, char ch) { - if (this.Project.Language == LanguageNames.CSharp) + if (_project.Language == LanguageNames.CSharp) { return CheckCode(surfaceSnapshot, position, ch, CSharpRazorBlock) || CheckCode(surfaceSnapshot, position, ch, FunctionsRazor, CSharpRazorBlock); } - if (this.Project.Language == LanguageNames.VisualBasic) + if (_project.Language == LanguageNames.VisualBasic) { return CheckCode(surfaceSnapshot, position, ch, VBRazorBlock) || CheckCode(surfaceSnapshot, position, ch, FunctionsRazor); @@ -1162,26 +1105,6 @@ private bool CheckCode(ITextSnapshot snapshot, int position, char ch, string tag return CheckCode(snapshot, position - tag2.Length, tag1); } - public ITextBuffer GetTextUndoHistoryBuffer() - { - // In Venus scenarios, the undo history is associated with the data buffer - return _containedLanguage.DataBuffer; - } - - public uint GetItemId() - { - AssertIsForeground(); - - if (_itemMoniker == null) - { - return (uint)VSConstants.VSITEMID.Nil; - } - - return Project.Hierarchy.ParseCanonicalName(_itemMoniker, out var itemId) == VSConstants.S_OK - ? itemId - : (uint)VSConstants.VSITEMID.Nil; - } - private enum RazorCodeBlockType { Block, diff --git a/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguage.IVsContainedLanguage.cs b/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguage.IVsContainedLanguage.cs index 74d53d47c2cb00b682ebdecf1ffa62cfcb0fa976..3f4e5113c90e2bd3b9f84cfb7772e5e063766993 100644 --- a/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguage.IVsContainedLanguage.cs +++ b/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguage.IVsContainedLanguage.cs @@ -78,12 +78,15 @@ public int SetBufferCoordinator(IVsTextBufferCoordinator pBC) public int SetHost(IVsContainedLanguageHost host) { // Are we going away due to the contained language being disconnected? - if (this.ContainedLanguageHost != null && host == null) + if (this.ContainedDocument.ContainedLanguageHost != null && host == null) { OnDisconnect(); } + else + { + ContainedDocument.ContainedLanguageHost = host; + } - this.ContainedLanguageHost = host; return VSConstants.S_OK; } diff --git a/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguage.cs b/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguage.cs index 9a4c20a4beb473b9fb28a227f9a2e384411b67c7..6a16ae35eda727332f498bbf44d60b8b89b14959 100644 --- a/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguage.cs +++ b/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguage.cs @@ -1,9 +1,14 @@ // 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 System; +using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Formatting.Rules; +using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService; @@ -18,7 +23,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Venus { using Workspace = Microsoft.CodeAnalysis.Workspace; - internal partial class ContainedLanguage : AbstractContainedLanguage + internal partial class ContainedLanguage where TPackage : AbstractPackage where TLanguageService : AbstractLanguageService { @@ -26,18 +31,45 @@ internal partial class ContainedLanguage : AbstractC private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService; private readonly TLanguageService _languageService; - protected readonly Workspace Workspace; + protected readonly VisualStudioWorkspace Workspace; protected readonly IComponentModel ComponentModel; + + public VisualStudioProject Project { get; } + protected readonly ContainedDocument ContainedDocument; + public IVsTextBufferCoordinator BufferCoordinator { get; protected set; } + + /// + /// The subject (secondary) buffer that contains the C# or VB code. + /// + public ITextBuffer SubjectBuffer { get; } + + /// + /// The underlying buffer that contains C# or VB code. NOTE: This is NOT the "document" buffer + /// that is saved to disk. Instead it is the view that the user sees. The normal buffer graph + /// in Venus includes 4 buffers: + /// + /// SurfaceBuffer/Databuffer (projection) + /// / | + /// Subject Buffer (C#/VB projection) | + /// | | + /// Inert (generated) C#/VB Buffer Document (aspx) buffer + /// + /// In normal circumstance, the Subject and Inert C# buffer are identical in content, and the + /// Surface and Document are also identical. The Subject Buffer is the one that is part of the + /// workspace, that most language operations deal with. The surface buffer is the one that the + /// view is created over, and the Document buffer is the one that is saved to disk. + /// + public ITextBuffer DataBuffer { get; } + // Set when a TextViewFIlter is set. We hold onto this to keep our TagSource objects alive even if Venus // disconnects the subject buffer from the view temporarily (which they do frequently). Otherwise, we have to // re-compute all of the tag data when they re-connect it, and this causes issues like classification // flickering. private ITagAggregator _bufferTagAggregator; - // BACKCOMPAT OVERLOAD -- DO NOT TOUCH - // This is required for the Typescript Language Service + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] public ContainedLanguage( IVsTextBufferCoordinator bufferCoordinator, IComponentModel componentModel, @@ -46,19 +78,19 @@ internal partial class ContainedLanguage : AbstractC uint itemid, TLanguageService languageService, SourceCodeKind sourceCodeKind, - IFormattingRule vbHelperFormattingRule) + IFormattingRule vbHelperFormattingRule, + Workspace workspace) : this(bufferCoordinator, componentModel, - project, + project.VisualStudioProject, hierarchy, itemid, languageService, - sourceCodeKind, - vbHelperFormattingRule, - workspace: null) + vbHelperFormattingRule) { } + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] public ContainedLanguage( IVsTextBufferCoordinator bufferCoordinator, IComponentModel componentModel, @@ -67,15 +99,32 @@ internal partial class ContainedLanguage : AbstractC uint itemid, TLanguageService languageService, SourceCodeKind sourceCodeKind, - IFormattingRule vbHelperFormattingRule = null, - Workspace workspace = null) - : base(project) + IFormattingRule vbHelperFormattingRule) + : this(bufferCoordinator, + componentModel, + project.VisualStudioProject, + hierarchy, + itemid, + languageService, + vbHelperFormattingRule) + { + } + + public ContainedLanguage( + IVsTextBufferCoordinator bufferCoordinator, + IComponentModel componentModel, + VisualStudioProject project, + IVsHierarchy hierarchy, + uint itemid, + TLanguageService languageService, + IFormattingRule vbHelperFormattingRule = null) { this.BufferCoordinator = bufferCoordinator; this.ComponentModel = componentModel; + this.Project = project; _languageService = languageService; - this.Workspace = workspace ?? componentModel.GetService(); + this.Workspace = componentModel.GetService(); _editorAdaptersFactoryService = componentModel.GetService(); _diagnosticAnalyzerService = componentModel.GetService(); @@ -83,28 +132,52 @@ internal partial class ContainedLanguage : AbstractC // Get the ITextBuffer for the secondary buffer Marshal.ThrowExceptionForHR(bufferCoordinator.GetSecondaryBuffer(out var secondaryTextLines)); var secondaryVsTextBuffer = (IVsTextBuffer)secondaryTextLines; - SetSubjectBuffer(_editorAdaptersFactoryService.GetDocumentBuffer(secondaryVsTextBuffer)); + SubjectBuffer = _editorAdaptersFactoryService.GetDocumentBuffer(secondaryVsTextBuffer); + // Get the ITextBuffer for the primary buffer + Marshal.ThrowExceptionForHR(bufferCoordinator.GetPrimaryBuffer(out var primaryTextLines)); + DataBuffer = _editorAdaptersFactoryService.GetDataBuffer((IVsTextBuffer)primaryTextLines); + + // Create our tagger var bufferTagAggregatorFactory = ComponentModel.GetService(); _bufferTagAggregator = bufferTagAggregatorFactory.CreateTagAggregator(SubjectBuffer); - Marshal.ThrowExceptionForHR(bufferCoordinator.GetPrimaryBuffer(out var primaryTextLines)); - var primaryVsTextBuffer = (IVsTextBuffer)primaryTextLines; - var dataBuffer = _editorAdaptersFactoryService.GetDataBuffer(primaryVsTextBuffer); - SetDataBuffer(dataBuffer); + + if (!ErrorHandler.Succeeded(((IVsProject)hierarchy).GetMkDocument(itemid, out var filePath))) + { + // we couldn't look up the document moniker from an hierarchy for an itemid. + // Since we only use this moniker as a key, we could fall back to something else, like the document name. + Debug.Assert(false, "Could not get the document moniker for an item from its hierarchy."); + if (!hierarchy.TryGetItemName(itemid, out filePath)) + { + FatalError.Report(new System.Exception("Failed to get document moniker for a contained document")); + } + } + + var documentId = this.Project.AddSourceTextContainer(SubjectBuffer.AsTextContainer(), filePath); this.ContainedDocument = new ContainedDocument( - project.ThreadingContext, - this, sourceCodeKind, this.Workspace, hierarchy, itemid, componentModel, vbHelperFormattingRule); + componentModel.GetService(), + documentId, + subjectBuffer: SubjectBuffer, + dataBuffer: DataBuffer, + bufferCoordinator, + this.Workspace, + project, + hierarchy, + itemid, + componentModel, + vbHelperFormattingRule); // TODO: Can contained documents be linked or shared? - this.Project.AddDocument(this.ContainedDocument, isCurrentContext: true, hookupHandlers: true); this.DataBuffer.Changed += OnDataBufferChanged; } private void OnDisconnect() { this.DataBuffer.Changed -= OnDataBufferChanged; - this.Project.RemoveDocument(this.ContainedDocument); + this.Project.RemoveSourceTextContainer(SubjectBuffer.AsTextContainer()); + + this.ContainedDocument.Dispose(); } private void OnDataBufferChanged(object sender, TextContentChangedEventArgs e) @@ -114,7 +187,7 @@ private void OnDataBufferChanged(object sender, TextContentChangedEventArgs e) _diagnosticAnalyzerService.Reanalyze(this.Workspace, documentIds: SpecializedCollections.SingletonEnumerable(this.ContainedDocument.Id)); } - public override void Dispose() + public void Dispose() { if (_bufferTagAggregator != null) { diff --git a/src/VisualStudio/Core/Def/Implementation/VisualStudioDocumentSupportsSuggestionService.cs b/src/VisualStudio/Core/Def/Implementation/VisualStudioDocumentSupportsSuggestionService.cs index 3785021dcaea589dedcef8a2996c56cde312d568..546a595e22489f50b3839724333d0fad09222e8d 100644 --- a/src/VisualStudio/Core/Def/Implementation/VisualStudioDocumentSupportsSuggestionService.cs +++ b/src/VisualStudio/Core/Def/Implementation/VisualStudioDocumentSupportsSuggestionService.cs @@ -36,12 +36,7 @@ public bool SupportsNavigationToAnyPosition(Document document) private static ContainedDocument GetContainedDocument(Document document) { var visualStudioWorkspace = document.Project.Solution.Workspace as VisualStudioWorkspaceImpl; - if (visualStudioWorkspace == null) - { - return null; - } - - return visualStudioWorkspace.GetHostDocument(document.Id) as ContainedDocument; + return visualStudioWorkspace?.TryGetContainedDocument(document.Id); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/VsRefactorNotifyService.cs b/src/VisualStudio/Core/Def/Implementation/VsRefactorNotifyService.cs index 79eca0f499643040daa21524abd5d72edc49750d..edb918f0eea5a8667dd18d68aaed39ecc663f356 100644 --- a/src/VisualStudio/Core/Def/Implementation/VsRefactorNotifyService.cs +++ b/src/VisualStudio/Core/Def/Implementation/VsRefactorNotifyService.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.Shell.Interop; namespace Microsoft.VisualStudio.LanguageServices.Implementation @@ -183,12 +184,9 @@ private bool TryGetRenamingRQNameForSymbol(ISymbol symbol, out string rqname) } var document = visualStudioWorkspace.CurrentSolution.GetDocument(documentId); - if (ErrorHandler.Failed(hierarchy.ParseCanonicalName(document.FilePath, out uint itemID))) - { - continue; - } + var itemID = hierarchy.TryGetItemId(document.FilePath); - if (itemID == (uint)VSConstants.VSITEMID.Nil) + if (itemID == VSConstants.VSITEMID_NIL) { continue; } diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/GlobalUndoServiceFactory.WorkspaceGlobalUndoTransaction.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/GlobalUndoServiceFactory.WorkspaceGlobalUndoTransaction.cs index 9f2f9c96b2aace512e628c8c8ce08287b0a5333e..3b4ad12000ae13ed24c945184c0731602bf2a84e 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/GlobalUndoServiceFactory.WorkspaceGlobalUndoTransaction.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/GlobalUndoServiceFactory.WorkspaceGlobalUndoTransaction.cs @@ -3,9 +3,12 @@ using System; using System.Diagnostics; using System.Runtime.InteropServices; +using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.Undo; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.Text.Operations; using Microsoft.VisualStudio.TextManager.Interop; @@ -49,34 +52,36 @@ private class WorkspaceUndoTransaction : ForegroundThreadAffinitizedObject, IWor public void AddDocument(DocumentId id) { - var vsWorkspace = (VisualStudioWorkspaceImpl)_workspace; - Contract.ThrowIfNull(vsWorkspace); + var visualStudioWorkspace = (VisualStudioWorkspace)_workspace; + Contract.ThrowIfNull(visualStudioWorkspace); - var solution = vsWorkspace.CurrentSolution; - if (!solution.ContainsDocument(id)) + var solution = visualStudioWorkspace.CurrentSolution; + var document = solution.GetDocument(id); + if (document == null) { // document is not part of the workspace (newly created document that is not applied to the workspace yet?) return; } - if (vsWorkspace.IsDocumentOpen(id)) + if (visualStudioWorkspace.IsDocumentOpen(id)) { - var document = vsWorkspace.GetHostDocument(id); - var undoHistory = _undoHistoryRegistry.RegisterHistory(document.GetOpenTextBuffer()); + var container = document.GetTextAsync().WaitAndGetResult(CancellationToken.None).Container; + var textBuffer = container.TryGetTextBuffer(); + var undoHistory = _undoHistoryRegistry.RegisterHistory(textBuffer); using (var undoTransaction = undoHistory.CreateTransaction(_description)) { undoTransaction.AddUndo(new NoOpUndoPrimitive()); undoTransaction.Complete(); } - - return; } - - // open and close the document so that it is included in the global undo transaction - using (vsWorkspace.OpenInvisibleEditor(id)) + else { - // empty + // open and close the document so that it is included in the global undo transaction + using (visualStudioWorkspace.OpenInvisibleEditor(id)) + { + // empty + } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentNavigationService.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentNavigationService.cs index a23d49cd722951ffbeacbefb1f4751d162f68f84..6b989c71c1eeb2aa289b78c54d60f56c254467c1 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentNavigationService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentNavigationService.cs @@ -331,7 +331,7 @@ private bool IsSecondaryBuffer(Workspace workspace, DocumentId documentId) return false; } - var containedDocument = visualStudioWorkspace.GetHostDocument(documentId) as ContainedDocument; + var containedDocument = visualStudioWorkspace.TryGetContainedDocument(documentId); if (containedDocument == null) { return false; diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioFormattingRuleFactoryServiceFactory.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioFormattingRuleFactoryServiceFactory.cs index 03c7e91f7fab62d7039f6f6dab30b389be24286d..4c46fd5931339eafe5b1f8bb855e16eebc857cdb 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioFormattingRuleFactoryServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioFormattingRuleFactoryServiceFactory.cs @@ -46,13 +46,7 @@ public bool ShouldNotFormatOrCommitOnPaste(Document document) private bool IsContainedDocument(Document document) { var visualStudioWorkspace = document.Project.Solution.Workspace as VisualStudioWorkspaceImpl; - if (visualStudioWorkspace == null) - { - return false; - } - - var containedDocument = visualStudioWorkspace.GetHostDocument(document.Id); - return containedDocument is ContainedDocument; + return visualStudioWorkspace?.TryGetContainedDocument(document.Id) != null; } public IFormattingRule CreateRule(Document document, int position) @@ -63,7 +57,7 @@ public IFormattingRule CreateRule(Document document, int position) return _noopRule; } - var containedDocument = visualStudioWorkspace.GetHostDocument(document.Id) as ContainedDocument; + var containedDocument = visualStudioWorkspace.TryGetContainedDocument(document.Id); if (containedDocument == null) { return _noopRule; @@ -123,7 +117,7 @@ public IEnumerable FilterFormattedChanges(Document document, TextSpa return changes; } - var containedDocument = visualStudioWorkspace.GetHostDocument(document.Id) as ContainedDocument; + var containedDocument = visualStudioWorkspace.TryGetContainedDocument(document.Id); if (containedDocument == null) { return changes; diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs index b579eb9d40df319a7e6e25dd76685c05ede1636e..fcbff4445b3401107bdef2b50255deb9834fa43b 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs @@ -20,6 +20,7 @@ using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.LanguageServices.Implementation.Extensions; using Microsoft.VisualStudio.LanguageServices.Implementation.Library; +using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text; @@ -311,7 +312,9 @@ private bool TryGetVsHierarchyAndItemId(Document document, out IVsHierarchy hier if (document.Project.Solution.Workspace is VisualStudioWorkspace visualStudioWorkspace) { hierarchy = visualStudioWorkspace.GetHierarchy(document.Project.Id); - if (ErrorHandler.Succeeded(hierarchy.ParseCanonicalName(document.FilePath, out itemID))) + itemID = hierarchy.TryGetItemId(document.FilePath); + + if (itemID != VSConstants.VSITEMID_NIL) { return true; } diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioTextUndoHistoryWorkspaceServiceFactory.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioTextUndoHistoryWorkspaceServiceFactory.cs index 344f7940781bb962e5b77a120dc2af5f8d4320e3..0206fdc5dc593033d5070ad50666ca555706d46e 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioTextUndoHistoryWorkspaceServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioTextUndoHistoryWorkspaceServiceFactory.cs @@ -55,7 +55,12 @@ public bool TryGetTextUndoHistory(Workspace editorWorkspace, ITextBuffer textBuf // In the Visual Studio case, there might be projection buffers involved for Venus, // where we associate undo history with the surface buffer and not the subject buffer. - textBuffer = visualStudioWorkspace.GetHostDocument(documentId).GetTextUndoHistoryBuffer(); + var containedDocument = visualStudioWorkspace.TryGetContainedDocument(documentId); + + if (containedDocument != null) + { + textBuffer = containedDocument.DataBuffer; + } break; diff --git a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj index 86bdbb73d54c24fb911a3923c365eff5a8e6d531..1b9244791b10f71216cd36723e607ff8f73f6972 100644 --- a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj +++ b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj @@ -157,6 +157,7 @@ + diff --git a/src/VisualStudio/Core/Def/PublicAPI.Shipped.txt b/src/VisualStudio/Core/Def/PublicAPI.Shipped.txt index 776a034dbc4b607ba1789a861dbb338c4b91dcc6..6fb5f40438ee012725f04a2bb0e5a2351814e24c 100644 --- a/src/VisualStudio/Core/Def/PublicAPI.Shipped.txt +++ b/src/VisualStudio/Core/Def/PublicAPI.Shipped.txt @@ -1,6 +1,6 @@ abstract Microsoft.VisualStudio.LanguageServices.VisualStudioWorkspace.DisplayReferencedSymbols(Microsoft.CodeAnalysis.Solution solution, System.Collections.Generic.IEnumerable referencedSymbols) -> void abstract Microsoft.VisualStudio.LanguageServices.VisualStudioWorkspace.GetFileCodeModel(Microsoft.CodeAnalysis.DocumentId documentId) -> EnvDTE.FileCodeModel -abstract Microsoft.VisualStudio.LanguageServices.VisualStudioWorkspace.GetFilePath(Microsoft.CodeAnalysis.DocumentId documentId) -> string +virtual Microsoft.VisualStudio.LanguageServices.VisualStudioWorkspace.GetFilePath(Microsoft.CodeAnalysis.DocumentId documentId) -> string abstract Microsoft.VisualStudio.LanguageServices.VisualStudioWorkspace.GetHierarchy(Microsoft.CodeAnalysis.ProjectId projectId) -> Microsoft.VisualStudio.Shell.Interop.IVsHierarchy abstract Microsoft.VisualStudio.LanguageServices.VisualStudioWorkspace.TryFindAllReferences(Microsoft.CodeAnalysis.ISymbol symbol, Microsoft.CodeAnalysis.Project project, System.Threading.CancellationToken cancellationToken) -> bool abstract Microsoft.VisualStudio.LanguageServices.VisualStudioWorkspace.TryGoToDefinition(Microsoft.CodeAnalysis.ISymbol symbol, Microsoft.CodeAnalysis.Project project, System.Threading.CancellationToken cancellationToken) -> bool diff --git a/src/VisualStudio/Core/Def/Telemetry/ProjectTelemetryIncrementalAnalyzerProvider.cs b/src/VisualStudio/Core/Def/Telemetry/ProjectTelemetryIncrementalAnalyzerProvider.cs index 095e54e334d2206ab67c738fdfa5b01d406a4a16..6bfd76450e4e5be8fa92225cde3bd470f0616d3a 100644 --- a/src/VisualStudio/Core/Def/Telemetry/ProjectTelemetryIncrementalAnalyzerProvider.cs +++ b/src/VisualStudio/Core/Def/Telemetry/ProjectTelemetryIncrementalAnalyzerProvider.cs @@ -163,11 +163,11 @@ public Task AnalyzeProjectAsync(Project project, bool semanticsChanged, Invocati try { var workspace = (VisualStudioWorkspaceImpl)project.Solution.Workspace; - var vsProject = workspace.GetHostProject(projectId); var telemetryEvent = TelemetryHelper.TelemetryService.CreateEvent(TelemetryEventPath); telemetryEvent.SetStringProperty(TelemetryProjectIdName, projectId.Id.ToString()); - telemetryEvent.SetStringProperty(TelemetryProjectGuidName, vsProject?.Guid.ToString() ?? Guid.Empty.ToString()); + // TODO: reconnect project GUID + telemetryEvent.SetStringProperty(TelemetryProjectGuidName, Guid.Empty.ToString()); telemetryEvent.SetStringProperty(TelemetryLanguageName, language); telemetryEvent.SetIntProperty(TelemetryAnalyzerReferencesCountName, analyzerReferencesCount); telemetryEvent.SetIntProperty(TelemetryProjectReferencesCountName, projectReferencesCount); diff --git a/src/VisualStudio/Core/Impl/CodeModel/AbstractCodeModelService.cs b/src/VisualStudio/Core/Impl/CodeModel/AbstractCodeModelService.cs index d9287b0f0055fdcc7d916a66f7f1af38bb84140f..1a7f24a5b2427f7c04f09efb7478a38094d09070 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/AbstractCodeModelService.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/AbstractCodeModelService.cs @@ -496,7 +496,7 @@ public void Rename(ISymbol symbol, string newName, Solution solution) // rename. This is silly. This functionality should be moved down // into the service layer. - var workspace = solution.Workspace as VisualStudioWorkspaceImpl; + var workspace = solution.Workspace as VisualStudioWorkspace; if (workspace == null) { throw Exceptions.ThrowEFail(); @@ -504,7 +504,7 @@ public void Rename(ISymbol symbol, string newName, Solution solution) // Save the node keys. var nodeKeyValidation = new NodeKeyValidation(); - foreach (var project in workspace.DeferredState.ProjectTracker.ImmutableProjects) + foreach (var project in workspace.CurrentSolution.Projects) { nodeKeyValidation.AddProject(project); } diff --git a/src/VisualStudio/Core/Impl/CodeModel/CodeModelIncrementalAnalyzer.cs b/src/VisualStudio/Core/Impl/CodeModel/CodeModelIncrementalAnalyzer.cs index b8d724b46877f5d5df8af17c6fa543e28e33cba4..2a2be2fdea6630bb0bb5a730670fd9781bfd2498 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/CodeModelIncrementalAnalyzer.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/CodeModelIncrementalAnalyzer.cs @@ -20,38 +20,43 @@ internal class CodeModelIncrementalAnalyzerProvider : IIncrementalAnalyzerProvid { private readonly IAsynchronousOperationListener _listener; private readonly IForegroundNotificationService _notificationService; + private readonly ProjectCodeModelFactory _projectCodeModelFactory; [ImportingConstructor] public CodeModelIncrementalAnalyzerProvider( IForegroundNotificationService notificationService, - IAsynchronousOperationListenerProvider listenerProvider) + IAsynchronousOperationListenerProvider listenerProvider, + ProjectCodeModelFactory projectCodeModelFactory) { _listener = listenerProvider.GetListener(FeatureAttribute.CodeModel); _notificationService = notificationService; + _projectCodeModelFactory = projectCodeModelFactory; } - public IIncrementalAnalyzer CreateIncrementalAnalyzer(Microsoft.CodeAnalysis.Workspace workspace) + public IIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace) { - var visualStudioWorkspace = workspace as VisualStudioWorkspaceImpl; + var visualStudioWorkspace = workspace as VisualStudioWorkspace; if (visualStudioWorkspace == null) { return null; } - return new Analyzer(_notificationService, _listener, visualStudioWorkspace); + return new Analyzer(_notificationService, _listener, visualStudioWorkspace, _projectCodeModelFactory); } private class Analyzer : IIncrementalAnalyzer { private readonly IForegroundNotificationService _notificationService; private readonly IAsynchronousOperationListener _listener; - private readonly VisualStudioWorkspaceImpl _workspace; + private readonly VisualStudioWorkspace _workspace; + private readonly ProjectCodeModelFactory _projectCodeModelFactory; - public Analyzer(IForegroundNotificationService notificationService, IAsynchronousOperationListener listener, VisualStudioWorkspaceImpl workspace) + public Analyzer(IForegroundNotificationService notificationService, IAsynchronousOperationListener listener, VisualStudioWorkspace workspace, ProjectCodeModelFactory projectCodeModelFactory) { _notificationService = notificationService; _listener = listener; _workspace = workspace; + _projectCodeModelFactory = projectCodeModelFactory; } public Task AnalyzeSyntaxAsync(Document document, InvocationReasons reasons, CancellationToken cancellationToken) @@ -71,13 +76,8 @@ public void FireEvents(DocumentId documentId, CancellationToken cancellationToke { _notificationService.RegisterNotification(() => { - var project = _workspace.DeferredState.ProjectTracker.GetProject(documentId.ProjectId); - if (project == null) - { - return false; - } + var projectCodeModel = _projectCodeModelFactory.TryGetProjectCodeModel(documentId.ProjectId); - var projectCodeModel = project.ProjectCodeModel as ProjectCodeModel; if (projectCodeModel == null) { return false; diff --git a/src/VisualStudio/Core/Impl/CodeModel/CodeModelProjectCache.cs b/src/VisualStudio/Core/Impl/CodeModel/CodeModelProjectCache.cs index b507d1458830eaf8b0aee4b3607301a7785ddb4a..0997ed179a94de21ab1002f1165fe3ef5160fcfa 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/CodeModelProjectCache.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/CodeModelProjectCache.cs @@ -29,9 +29,9 @@ internal sealed partial class CodeModelProjectCache private EnvDTE.CodeModel _rootCodeModel; private bool _zombied; - internal CodeModelProjectCache(IThreadingContext threadingContext, ProjectId projectId, ICodeModelInstanceFactory codeModelInstanceFactory, IServiceProvider serviceProvider, HostLanguageServices languageServices, VisualStudioWorkspace workspace) + internal CodeModelProjectCache(IThreadingContext threadingContext, ProjectId projectId, ICodeModelInstanceFactory codeModelInstanceFactory, ProjectCodeModelFactory projectFactory, IServiceProvider serviceProvider, HostLanguageServices languageServices, VisualStudioWorkspace workspace) { - _state = new CodeModelState(threadingContext, serviceProvider, languageServices, workspace); + _state = new CodeModelState(threadingContext, serviceProvider, languageServices, workspace, projectFactory); _projectId = projectId; _codeModelInstanceFactory = codeModelInstanceFactory; } diff --git a/src/VisualStudio/Core/Impl/CodeModel/CodeModelState.cs b/src/VisualStudio/Core/Impl/CodeModel/CodeModelState.cs index 0e6ed4ce55f8933be1833ad487322d3ecf53be0c..6ff73f9bfed454216b1bf2bff66f1dbdf88d877b 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/CodeModelState.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/CodeModelState.cs @@ -20,12 +20,14 @@ internal sealed class CodeModelState public ISyntaxFactsService SyntaxFactsService { get; } public ICodeGenerationService CodeGenerator { get; } public VisualStudioWorkspace Workspace { get; } + public ProjectCodeModelFactory ProjectCodeModelFactory { get; } public CodeModelState( IThreadingContext threadingContext, IServiceProvider serviceProvider, HostLanguageServices languageServices, - VisualStudioWorkspace workspace) + VisualStudioWorkspace workspace, + ProjectCodeModelFactory projectCodeModelFactory) { Debug.Assert(threadingContext != null); Debug.Assert(serviceProvider != null); @@ -37,6 +39,7 @@ internal sealed class CodeModelState this.CodeModelService = languageServices.GetService(); this.SyntaxFactsService = languageServices.GetService(); this.CodeGenerator = languageServices.GetService(); + this.ProjectCodeModelFactory = projectCodeModelFactory; this.Workspace = workspace; } } diff --git a/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel.cs b/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel.cs index b76608b15b617612102045d2d524f409e2d882c8..b91dd51530b33495521efbf8e1fad43db190d322 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel.cs @@ -138,24 +138,23 @@ private bool TryGetDocumentId(out DocumentId documentId) return false; } - var project = ((VisualStudioWorkspaceImpl)this.State.Workspace).DeferredState?.ProjectTracker.GetProject(_incomingProjectId); + var project = this.State.Workspace.CurrentSolution.GetProject(_incomingProjectId); if (project == null) { return false; } - var hostDocument = project.GetCurrentDocumentFromPath(_incomingFilePath); - if (hostDocument == null) + documentId = project.Solution.GetDocumentIdsWithFilePath(_incomingFilePath).FirstOrDefault(d => d.ProjectId == project.Id); + if (documentId == null) { return false; } - _documentId = hostDocument.Id; + _documentId = documentId; _incomingProjectId = null; _incomingFilePath = null; _previousDocument = null; - documentId = _documentId; return true; } @@ -415,11 +414,6 @@ internal ProjectId GetProjectId() return GetDocumentId().ProjectId; } - internal AbstractProject GetAbstractProject() - { - return ((VisualStudioWorkspaceImpl)Workspace).DeferredState.ProjectTracker.GetProject(GetProjectId()); - } - internal SyntaxNode LookupNode(SyntaxNodeKey nodeKey) { return CodeModelService.LookupNode(nodeKey, GetSyntaxTree()); diff --git a/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel_Events.cs b/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel_Events.cs index f118c8094e258a7bb1d1a4b87e3a91e9256d84f8..c202241dc76d5dedc7ab1eac04d1dd0f48780648 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel_Events.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel_Events.cs @@ -57,7 +57,7 @@ public bool FireEvents() return needMoreTime; } - var projectCodeModel = GetAbstractProject().ProjectCodeModel as ProjectCodeModel; + var projectCodeModel = this.State.ProjectCodeModelFactory.GetProjectCodeModel(document.Project.Id); if (projectCodeModel == null) { return needMoreTime; diff --git a/src/VisualStudio/Core/Impl/CodeModel/NodeKeyValidation.cs b/src/VisualStudio/Core/Impl/CodeModel/NodeKeyValidation.cs index 78a94a6a8484726eb51303978a74450310cbedd7..b71e48e6295b3c421f8eaa7e5ea5a800674b8311 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/NodeKeyValidation.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/NodeKeyValidation.cs @@ -1,8 +1,8 @@ // 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 System.Collections.Generic; +using Microsoft.CodeAnalysis; using Microsoft.VisualStudio.LanguageServices.Implementation.Interop; -using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; namespace Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel { @@ -15,8 +15,9 @@ public NodeKeyValidation() _nodeKeysMap = new Dictionary, List>(); } - public void AddProject(AbstractProject project) + public void AddProject(Project project) { + /* if (project.ProjectCodeModel is ProjectCodeModel projectCodeModel) { var fcms = projectCodeModel.GetCachedFileCodeModelInstances(); @@ -28,6 +29,7 @@ public void AddProject(AbstractProject project) _nodeKeysMap.Add(fcm, globalNodeKeys); } } + */ } public void AddFileCodeModel(FileCodeModel fileCodeModel) diff --git a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModel.cs b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModel.cs index 9c5daa3ba6cc911e3dac631d69f54c31f40573ee..e1d02ab23738d75940b186eace55adf644f190bf 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModel.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModel.cs @@ -20,23 +20,26 @@ internal sealed class ProjectCodeModel : IProjectCodeModel private readonly IThreadingContext _threadingContext; private readonly ProjectId _projectId; private readonly ICodeModelInstanceFactory _codeModelInstanceFactory; - private readonly VisualStudioWorkspaceImpl _visualStudioWorkspace; + private readonly VisualStudioWorkspace _visualStudioWorkspace; private readonly IServiceProvider _serviceProvider; + private readonly ProjectCodeModelFactory _projectCodeModelFactory; private CodeModelProjectCache _codeModelCache; - public ProjectCodeModel(IThreadingContext threadingContext, ProjectId projectId, ICodeModelInstanceFactory codeModelInstanceFactory, VisualStudioWorkspaceImpl visualStudioWorkspace, IServiceProvider serviceProvider) + public ProjectCodeModel(IThreadingContext threadingContext, ProjectId projectId, ICodeModelInstanceFactory codeModelInstanceFactory, VisualStudioWorkspace visualStudioWorkspace, IServiceProvider serviceProvider, ProjectCodeModelFactory projectCodeModelFactory) { _threadingContext = threadingContext; _projectId = projectId; _codeModelInstanceFactory = codeModelInstanceFactory; _visualStudioWorkspace = visualStudioWorkspace; _serviceProvider = serviceProvider; + _projectCodeModelFactory = projectCodeModelFactory; } public void OnProjectClosed() { _codeModelCache?.OnProjectClosed(); + _projectCodeModelFactory.OnProjectClosed(_projectId); } private CodeModelProjectCache GetCodeModelCache() @@ -49,19 +52,10 @@ private CodeModelProjectCache GetCodeModelCache() if (_codeModelCache == null) { var workspaceProject = _visualStudioWorkspace.CurrentSolution.GetProject(_projectId); - var hostProject = _visualStudioWorkspace.GetHostProject(_projectId); - if (workspaceProject == null && !hostProject.PushingChangesToWorkspace) - { - // if this project hasn't been pushed yet, push it now so that the user gets a useful experience here. - hostProject.StartPushingToWorkspaceAndNotifyOfOpenDocuments(); - - // re-check to see whether we now has the project in the workspace - workspaceProject = _visualStudioWorkspace.CurrentSolution.GetProject(_projectId); - } if (workspaceProject != null) { - _codeModelCache = new CodeModelProjectCache(_threadingContext, _projectId, _codeModelInstanceFactory, _serviceProvider, workspaceProject.LanguageServices, _visualStudioWorkspace); + _codeModelCache = new CodeModelProjectCache(_threadingContext, _projectId, _codeModelInstanceFactory, _projectCodeModelFactory, _serviceProvider, workspaceProject.LanguageServices, _visualStudioWorkspace); } } diff --git a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..d759c5881b0ded18f036af3999e21fcc32847bf7 --- /dev/null +++ b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs @@ -0,0 +1,62 @@ +// 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 System; +using System.Collections.Concurrent; +using System.ComponentModel.Composition; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.VisualStudio.Shell; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel +{ + [Export(typeof(IProjectCodeModelFactory))] + [Export(typeof(ProjectCodeModelFactory))] + internal sealed class ProjectCodeModelFactory : IProjectCodeModelFactory + { + private readonly ConcurrentDictionary _projectCodeModels = new ConcurrentDictionary(); + + private readonly VisualStudioWorkspace _visualStudioWorkspace; + private readonly IServiceProvider _serviceProvider; + private readonly IThreadingContext _threadingContext; + + [ImportingConstructor] + public ProjectCodeModelFactory(VisualStudioWorkspace visualStudioWorkspace, [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider, IThreadingContext threadingContext) + { + _visualStudioWorkspace = visualStudioWorkspace; + _serviceProvider = serviceProvider; + _threadingContext = threadingContext; + } + + public IProjectCodeModel CreateProjectCodeModel(ProjectId id, ICodeModelInstanceFactory codeModelInstanceFactory) + { + var projectCodeModel = new ProjectCodeModel(_threadingContext, id, codeModelInstanceFactory, _visualStudioWorkspace, _serviceProvider, this); + if (!_projectCodeModels.TryAdd(id, projectCodeModel)) + { + throw new InvalidOperationException($"A {nameof(IProjectCodeModel)} has already been created for project with ID {id}"); + } + + return projectCodeModel; + } + + public ProjectCodeModel GetProjectCodeModel(ProjectId id) + { + if (!_projectCodeModels.TryGetValue(id, out var projectCodeModel)) + { + throw new InvalidOperationException($"No {nameof(ProjectCodeModel)} exists for project with ID {id}"); + } + + return projectCodeModel; + } + + internal void OnProjectClosed(ProjectId projectId) + { + _projectCodeModels.TryRemove(projectId, out _); + } + + public ProjectCodeModel TryGetProjectCodeModel(ProjectId id) + { + _projectCodeModels.TryGetValue(id, out var projectCodeModel); + return projectCodeModel; + } + } +} diff --git a/src/VisualStudio/Core/Impl/CodeModel/RootCodeModel.cs b/src/VisualStudio/Core/Impl/CodeModel/RootCodeModel.cs index 2c7bfefb86bf91f8f516a3c6d6b9dfd0ecb8a865..3ba383c65d0cd438d9e3f46273d35ab4137b3d22 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/RootCodeModel.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/RootCodeModel.cs @@ -80,10 +80,7 @@ private Compilation GetCompilation() vsProjectItems.AddFromFile(absoluteFilePath); } - var hostProject = ((VisualStudioWorkspaceImpl)Workspace).DeferredState.ProjectTracker.GetProject(_projectId); - var projectCodeModel = (ProjectCodeModel)hostProject.ProjectCodeModel; - - return projectCodeModel.GetOrCreateFileCodeModel(absoluteFilePath); + return this.State.ProjectCodeModelFactory.GetProjectCodeModel(_projectId).GetOrCreateFileCodeModel(absoluteFilePath); } throw Exceptions.ThrowEInvalidArg(); diff --git a/src/VisualStudio/Core/Impl/CodeModel/TextManagerAdapter.cs b/src/VisualStudio/Core/Impl/CodeModel/TextManagerAdapter.cs index 61bf253f409613b7b4ab1e400a1d9d55c1c9d0c5..0106809c181bed424129482f564052efd5ae6d70 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/TextManagerAdapter.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/TextManagerAdapter.cs @@ -10,14 +10,14 @@ internal sealed class TextManagerAdapter : ITextManagerAdapter { public EnvDTE.TextPoint CreateTextPoint(FileCodeModel fileCodeModel, VirtualTreePoint point) { - var workspace = fileCodeModel.Workspace as VisualStudioWorkspaceImpl; - var hostDocument = workspace.GetHostDocument(fileCodeModel.GetDocumentId()); - if (hostDocument == null) + if (!fileCodeModel.TryGetDocument(out var document)) { return null; } - using (var invisibleEditor = new InvisibleEditor(fileCodeModel.ServiceProvider, hostDocument.FilePath, hostDocument.Project, needsSave: false, needsUndoDisabled: false)) + var hierarchyOpt = fileCodeModel.Workspace.GetHierarchy(document.Project.Id); + + using (var invisibleEditor = new InvisibleEditor(fileCodeModel.ServiceProvider, document.FilePath, hierarchyOpt, needsSave: false, needsUndoDisabled: false)) { var vsTextLines = invisibleEditor.VsTextLines; diff --git a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject.cs b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject.cs deleted file mode 100644 index 25b490c7b835c52a841ef9d0440872c58138f0b1..0000000000000000000000000000000000000000 --- a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject.cs +++ /dev/null @@ -1,82 +0,0 @@ -// 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 System; -using System.IO; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Host; -using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel; -using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.TextManager.Interop; -using Roslyn.Utilities; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.CPS -{ - internal sealed partial class CPSProject : AbstractProject - { - public CPSProject( - VisualStudioProjectTracker projectTracker, - Func reportExternalErrorCreatorOpt, - string projectDisplayName, - string projectFilePath, - IVsHierarchy hierarchy, - string language, - Guid projectGuid, - string binOutputPath, - IServiceProvider serviceProvider, - VisualStudioWorkspaceImpl visualStudioWorkspaceOpt, - HostDiagnosticUpdateSource hostDiagnosticUpdateSourceOpt, - ICommandLineParserService commandLineParserServiceOpt) - : base(projectTracker, reportExternalErrorCreatorOpt, projectDisplayName, projectFilePath, - hierarchy, language, projectGuid, serviceProvider, visualStudioWorkspaceOpt, hostDiagnosticUpdateSourceOpt, commandLineParserServiceOpt) - { - // We need to ensure that the bin output path for the project has been initialized before we hookup the project with the project tracker. - NormalizeAndSetBinOutputPathAndRelatedData(binOutputPath); - - // Now hook up the project to the project tracker. - projectTracker.AddProject(this); - - ProjectCodeModel = new ProjectCodeModel(projectTracker.ThreadingContext, this.Id, new CPSCodeModelInstanceFactory(this), (VisualStudioWorkspaceImpl)this.Workspace, ServiceProvider); - } - - private void NormalizeAndSetBinOutputPathAndRelatedData(string binOutputPath) - { - if (binOutputPath != null) - { - // Ensure that binOutputPath is either null or a rooted path. - // CPS might provide such invalid paths during initialization or when project is in unrestored state. - if (binOutputPath == String.Empty) - { - binOutputPath = null; - } - else if (!PathUtilities.IsAbsolute(binOutputPath)) - { - // Make it a rooted path. - var basePath = this.ContainingDirectoryPathOpt ?? Path.GetTempPath(); - binOutputPath = PathUtilities.CombineAbsoluteAndRelativePaths(basePath, binOutputPath); - } - } - - // We need to ensure that the bin output path for the project has been initialized before we hookup the project with the project tracker. - SetBinOutputPathAndRelatedData(binOutputPath); - } - - - private bool _disconnected; - - // We might we invoked from a background thread, so schedule the disconnect on foreground task scheduler. - public sealed override void Disconnect() - { - _disconnected = true; - - if (IsForeground()) - { - base.Disconnect(); - } - else - { - InvokeBelowInputPriority(base.Disconnect); - } - } - } -} diff --git a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProjectFactory.cs b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProjectFactory.cs index 9a65f652c06d16ac3968df25862ed663422503c1..f7827a610808acc7ea0327abf235f70850568e57 100644 --- a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProjectFactory.cs +++ b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProjectFactory.cs @@ -4,26 +4,26 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel.Composition; +using System.IO; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel; using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; using Microsoft.VisualStudio.LanguageServices.ProjectSystem; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.TextManager.Interop; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.CPS { [Export(typeof(IWorkspaceProjectContextFactory))] - internal partial class CPSProjectFactory : ForegroundThreadAffinitizedObject, IWorkspaceProjectContextFactory + internal partial class CPSProjectFactory : IWorkspaceProjectContextFactory { - private readonly IServiceProvider _serviceProvider; - private readonly VisualStudioWorkspaceImpl _visualStudioWorkspace; - private readonly HostDiagnosticUpdateSource _hostDiagnosticUpdateSource; + private readonly VisualStudioProjectFactory _projectFactory; + private readonly VisualStudioWorkspaceImpl _workspace; + private readonly IProjectCodeModelFactory _projectCodeModelFactory; + private readonly ExternalErrorDiagnosticUpdateSource _externalErrorDiagnosticUpdateSource; - private readonly ImmutableDictionary _projectLangaugeToErrorCodePrefixMap = + private static readonly ImmutableDictionary s_projectLanguageToErrorCodePrefixMap = ImmutableDictionary.CreateRange(StringComparer.OrdinalIgnoreCase, new[] { new KeyValuePair (LanguageNames.CSharp, "CS"), @@ -34,110 +34,73 @@ internal partial class CPSProjectFactory : ForegroundThreadAffinitizedObject, IW [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public CPSProjectFactory( - IThreadingContext threadingContext, - SVsServiceProvider serviceProvider, - VisualStudioWorkspaceImpl visualStudioWorkspace, - HostDiagnosticUpdateSource hostDiagnosticUpdateSource) - : base(threadingContext, assertIsForeground: false) + VisualStudioProjectFactory projectFactory, + VisualStudioWorkspaceImpl workspace, + IProjectCodeModelFactory projectCodeModelFactory, + [Import(AllowDefault = true)] /* not present in unit tests */ ExternalErrorDiagnosticUpdateSource externalErrorDiagnosticUpdateSource) { - _serviceProvider = serviceProvider; - _visualStudioWorkspace = visualStudioWorkspace; - _hostDiagnosticUpdateSource = hostDiagnosticUpdateSource; - } - - // internal for testing purposes only. - internal static CPSProject CreateCPSProject(VisualStudioProjectTracker projectTracker, IServiceProvider serviceProvider, IVsHierarchy hierarchy, string projectDisplayName, string projectFilePath, Guid projectGuid, string language, ICommandLineParserService commandLineParserService, string binOutputPath) - { - // this only runs under unit test - return new CPSProject(projectTracker, reportExternalErrorCreatorOpt: null, hierarchy: hierarchy, language: language, - serviceProvider: serviceProvider, visualStudioWorkspaceOpt: null, hostDiagnosticUpdateSourceOpt: null, - projectDisplayName: projectDisplayName, projectFilePath: projectFilePath, projectGuid: projectGuid, - binOutputPath: binOutputPath, commandLineParserServiceOpt: commandLineParserService); + _projectFactory = projectFactory; + _workspace = workspace; + _projectCodeModelFactory = projectCodeModelFactory; + _externalErrorDiagnosticUpdateSource = externalErrorDiagnosticUpdateSource; } IWorkspaceProjectContext IWorkspaceProjectContextFactory.CreateProjectContext( string languageName, - string projectDisplayName, + string projectUniqueName, string projectFilePath, Guid projectGuid, object hierarchy, string binOutputPath) { - AssertIsForeground(); + var visualStudioProject = CreateVisualStudioProject(languageName, projectUniqueName, projectFilePath, (IVsHierarchy)hierarchy, projectGuid); - EnsurePackageLoaded(languageName); + ProjectExternalErrorReporter errorReporter = null; - // NOTE: It is acceptable for hierarchy to be null in Deferred Project Load scenarios. - var vsHierarchy = hierarchy as IVsHierarchy; + if (s_projectLanguageToErrorCodePrefixMap.TryGetKey(languageName, out var prefix)) + { + errorReporter = new ProjectExternalErrorReporter(visualStudioProject.Id, prefix, _workspace, _externalErrorDiagnosticUpdateSource); + } - IVsReportExternalErrors getExternalErrorReporter(ProjectId id) => GetExternalErrorReporter(id, languageName); - return new CPSProject(_visualStudioWorkspace.GetProjectTrackerAndInitializeIfNecessary(ServiceProvider.GlobalProvider), getExternalErrorReporter, projectDisplayName, projectFilePath, - vsHierarchy, languageName, projectGuid, binOutputPath, _serviceProvider, _visualStudioWorkspace, _hostDiagnosticUpdateSource, - commandLineParserServiceOpt: _visualStudioWorkspace.Services.GetLanguageServices(languageName)?.GetService()); + return new CPSProject(visualStudioProject, _workspace, _projectCodeModelFactory, errorReporter, projectGuid, binOutputPath); } // TODO: this is a workaround. Factory has to be refactored so that all callers supply their own error reporters IWorkspaceProjectContext IWorkspaceProjectContextFactory.CreateProjectContext( string languageName, - string projectDisplayName, + string projectUniqueName, string projectFilePath, Guid projectGuid, object hierarchy, string binOutputPath, ProjectExternalErrorReporter errorReporter) { - AssertIsForeground(); - - EnsurePackageLoaded(languageName); - - // NOTE: It is acceptable for hierarchy to be null in Deferred Project Load scenarios. - var vsHierarchy = hierarchy as IVsHierarchy; - - IVsReportExternalErrors getExternalErrorReporter(ProjectId id) => errorReporter; - return new CPSProject(_visualStudioWorkspace.GetProjectTrackerAndInitializeIfNecessary(ServiceProvider.GlobalProvider), getExternalErrorReporter, projectDisplayName, projectFilePath, - vsHierarchy, languageName, projectGuid, binOutputPath, _serviceProvider, _visualStudioWorkspace, _hostDiagnosticUpdateSource, - commandLineParserServiceOpt: _visualStudioWorkspace.Services.GetLanguageServices(languageName)?.GetService()); + var visualStudioProject = CreateVisualStudioProject(languageName, projectUniqueName, projectFilePath, (IVsHierarchy)hierarchy, projectGuid); + return new CPSProject(visualStudioProject, _workspace, _projectCodeModelFactory, errorReporter, projectGuid, binOutputPath); } - private IVsReportExternalErrors GetExternalErrorReporter(ProjectId projectId, string languageName) + private VisualStudioProject CreateVisualStudioProject(string languageName, string projectUniqueName, string projectFilePath, IVsHierarchy hierarchy, Guid projectGuid) { - if (!_projectLangaugeToErrorCodePrefixMap.TryGetValue(languageName, out var errorCodePrefix)) + var creationInfo = new VisualStudioProjectCreationInfo { - throw new NotSupportedException(nameof(languageName)); - } + FilePath = projectFilePath, + Hierarchy = hierarchy, + ProjectGuid = projectGuid, + }; - return new ProjectExternalErrorReporter(projectId, errorCodePrefix, _serviceProvider); - } + var visualStudioProject = _projectFactory.CreateAndAddToWorkspace(projectUniqueName, languageName, creationInfo); - private void EnsurePackageLoaded(string language) - { - // we need to make sure we load required packages which initialize VS related services - // such as OB, FAR, Error list, Msic workspace, solution crawler before actually - // set roslyn CPS up. - var shell = (IVsShell)_serviceProvider.GetService(typeof(SVsShell)); - if (shell == null) + if (languageName == LanguageNames.FSharp) { - // no shell. can happen in unit test - return; - } + var shell = (IVsShell)ServiceProvider.GlobalProvider.GetService(typeof(SVsShell)); - IVsPackage unused; - switch (language) - { - case LanguageNames.CSharp: - shell.LoadPackage(Guids.CSharpPackageId, out unused); - break; - case LanguageNames.VisualBasic: - shell.LoadPackage(Guids.VisualBasicPackageId, out unused); - break; - case LanguageNames.FSharp: - shell.LoadPackage(Guids.FSharpPackageId, out unused); - break; - default: - // by default, load roslyn package for things like typescript and etc - shell.LoadPackage(Guids.RoslynPackageId, out unused); - break; + // Force the F# package to load; this is necessary because the F# package listens to WorkspaceChanged to + // set up some items, and the F# project system doesn't guarantee that the F# package has been loaded itself + // so we're caught in the middle doing this. + shell.LoadPackage(Guids.FSharpPackageId, out var unused); } + + return visualStudioProject; } } } diff --git a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_ExternalErrorReporting.cs b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_ExternalErrorReporting.cs new file mode 100644 index 0000000000000000000000000000000000000000..25a21686239c4b5d33715d0b7d68e0f6c5af59d7 --- /dev/null +++ b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_ExternalErrorReporting.cs @@ -0,0 +1,40 @@ +// 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.Shell.Interop; +using Microsoft.VisualStudio.TextManager.Interop; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.CPS +{ + internal sealed partial class CPSProject : IVsReportExternalErrors, IVsLanguageServiceBuildErrorReporter2 + { + public int ClearAllErrors() + { + return _externalErrorReporterOpt.ClearAllErrors(); + } + + public int AddNewErrors(IVsEnumExternalErrors pErrors) + { + return _externalErrorReporterOpt.AddNewErrors(pErrors); + } + + public int GetErrors(out IVsEnumExternalErrors pErrors) + { + return _externalErrorReporterOpt.GetErrors(out pErrors); + } + + public int ReportError(string bstrErrorMessage, string bstrErrorId, VSTASKPRIORITY nPriority, int iLine, int iColumn, string bstrFileName) + { + return _externalErrorReporterOpt.ReportError(bstrErrorMessage, bstrErrorId, nPriority, iLine, iColumn, bstrFileName); + } + + public int ClearErrors() + { + return _externalErrorReporterOpt.ClearErrors(); + } + + public void ReportError2(string bstrErrorMessage, string bstrErrorId, VSTASKPRIORITY nPriority, int iStartLine, int iStartColumn, int iEndLine, int iEndColumn, string bstrFileName) + { + _externalErrorReporterOpt.ReportError2(bstrErrorMessage, bstrErrorId, nPriority, iStartLine, iStartColumn, iEndLine, iEndColumn, bstrFileName); + } + } +} diff --git a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IProjectCodeModelProvider.cs b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IProjectCodeModelProvider.cs index 0d96512bf87865fbc9e224a6461b37283f8c7aa4..744355b864e6881f3de690bc1b038e7794915cb0 100644 --- a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IProjectCodeModelProvider.cs +++ b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IProjectCodeModelProvider.cs @@ -11,7 +11,7 @@ internal sealed partial class CPSProject { public EnvDTE.CodeModel GetCodeModel(EnvDTE.Project parent) { - return ProjectCodeModel.GetOrCreateRootCodeModel(parent); + return _projectCodeModel.GetOrCreateRootCodeModel(parent); } public EnvDTE.FileCodeModel GetFileCodeModel(EnvDTE.ProjectItem item) @@ -21,7 +21,7 @@ public EnvDTE.FileCodeModel GetFileCodeModel(EnvDTE.ProjectItem item) return null; } - return ProjectCodeModel.GetOrCreateFileCodeModel(filePath, item); + return _projectCodeModel.GetOrCreateFileCodeModel(filePath, item); } private class CPSCodeModelInstanceFactory : ICodeModelInstanceFactory @@ -41,12 +41,12 @@ EnvDTE.FileCodeModel ICodeModelInstanceFactory.TryCreateFileCodeModelThroughProj return null; } - return _project.ProjectCodeModel.GetOrCreateFileCodeModel(filePath, projectItem); + return _project._projectCodeModel.GetOrCreateFileCodeModel(filePath, projectItem); } private EnvDTE.ProjectItem GetProjectItem(string filePath) { - var dteProject = ((VisualStudioWorkspaceImpl)_project.Workspace).TryGetDTEProject(_project.Id); + var dteProject = _project._visualStudioWorkspace.TryGetDTEProject(_project._visualStudioProject.Id); if (dteProject == null) { return null; diff --git a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs index bb7754d1797429a6aca5b831e799d647c6bbd5e8..9ea80f2d5c3592be491ced347a4848cca98b746b 100644 --- a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs +++ b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs @@ -1,235 +1,218 @@ // 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 System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host; +using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel; +using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; using Microsoft.VisualStudio.LanguageServices.ProjectSystem; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.CPS { - internal sealed partial class CPSProject : AbstractProject, IWorkspaceProjectContext + internal sealed partial class CPSProject : IWorkspaceProjectContext { - /// - /// Holds the task with continuations to sequentially execute all the foreground affinitized actions on the foreground task scheduler. - /// More specifically, all the notifications to workspace hosts are executed on the foreground thread. However, the project system might make project state changes - /// and request notifications to workspace hosts on background thread. So we queue up all the notifications for project state changes onto this task and execute them on the foreground thread. - /// - private Task _foregroundTaskQueue = Task.CompletedTask; + private readonly VisualStudioProject _visualStudioProject; /// - /// Controls access to task queue + /// The we're using to parse command line options. Null if we don't + /// have the ability to parse command line options. /// - private readonly object _queueGate = new object(); + private readonly VisualStudioProjectOptionsProcessor _visualStudioProjectOptionsProcessor; + + private readonly VisualStudioWorkspaceImpl _visualStudioWorkspace; + private readonly IProjectCodeModel _projectCodeModel; + private readonly ProjectExternalErrorReporter _externalErrorReporterOpt; - private void ExecuteForegroundAction(Action action) + public string DisplayName { - if (IsForeground()) - { - action(); - } - else - { - lock (_queueGate) - { - _foregroundTaskQueue = _foregroundTaskQueue.SafeContinueWithFromAsync( - async _ => - { - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true); - - // since execution is now technically asynchronous - // only execute action if project is not disconnected and currently being tracked. - if (!_disconnected && this.ProjectTracker.ContainsProject(this)) - { - action(); - } - }, - CancellationToken.None, - TaskContinuationOptions.ExecuteSynchronously, - TaskScheduler.Default); - } - } + get => _visualStudioProject.DisplayName; + set => _visualStudioProject.DisplayName = value; } - #region Project properties - string IWorkspaceProjectContext.DisplayName + public string ProjectFilePath { - get - { - return base.DisplayName; - } - set - { - ExecuteForegroundAction(() => UpdateProjectDisplayName(value)); - } + get => _visualStudioProject.FilePath; + set => _visualStudioProject.FilePath = value; } - string IWorkspaceProjectContext.ProjectFilePath + public Guid Guid { - get - { - return base.ProjectFilePath; - } - set - { - ExecuteForegroundAction(() => UpdateProjectFilePath(value)); - } + get; + set; // VisualStudioProject doesn't allow GUID to be changed after creation } - Guid IWorkspaceProjectContext.Guid + public bool LastDesignTimeBuildSucceeded { - get - { - return base.Guid; - } - - set - { - base.Guid = value; - } + get => _visualStudioProject.HasAllInformation; + set => _visualStudioProject.HasAllInformation = value; } - bool IWorkspaceProjectContext.LastDesignTimeBuildSucceeded + public CPSProject(VisualStudioProject visualStudioProject, VisualStudioWorkspaceImpl visualStudioWorkspace, IProjectCodeModelFactory projectCodeModelFactory, ProjectExternalErrorReporter errorReporterOpt, Guid projectGuid, string binOutputPath) { - get - { - return LastDesignTimeBuildSucceeded; - } - set + _visualStudioProject = visualStudioProject; + _visualStudioWorkspace = visualStudioWorkspace; + _externalErrorReporterOpt = errorReporterOpt; + + _projectCodeModel = projectCodeModelFactory.CreateProjectCodeModel(visualStudioProject.Id, new CPSCodeModelInstanceFactory(this)); + + // If we have a command line parser service for this language, also set up our ability to process options if they come in + if (visualStudioWorkspace.Services.GetLanguageServices(visualStudioProject.Language).GetService() != null) { - ExecuteForegroundAction(() => SetIntellisenseBuildResultAndNotifyWorkspace(value)); + _visualStudioProjectOptionsProcessor = new VisualStudioProjectOptionsProcessor(_visualStudioProject, visualStudioWorkspace.Services); } + + Guid = projectGuid; + BinOutputPath = binOutputPath; } - string IWorkspaceProjectContext.BinOutputPath + public string BinOutputPath { - get - { - return base.BinOutputPath; - } + get => _visualStudioProject.OutputFilePath; set { - ExecuteForegroundAction(() => NormalizeAndSetBinOutputPathAndRelatedData(value)); - } - } + // If we don't have a path, always set it to null + if (string.IsNullOrEmpty(value)) + { + _visualStudioProject.OutputFilePath = null; + _visualStudioProject.OutputRefFilePath = null; + return; + } - #endregion + // If we only have a non-rooted path, make it full. This is apparently working around cases + // where CPS pushes us a temporary path when they're loading. It's possible this hack + // can be removed now, but we still have tests asserting it. + if (!PathUtilities.IsAbsolute(value)) + { + var rootDirectory = _visualStudioProject.FilePath != null + ? Path.GetDirectoryName(_visualStudioProject.FilePath) + : Path.GetTempPath(); - #region Options - public void SetOptions(string commandLineForOptions) - { - ExecuteForegroundAction(() => - { - var commandLineArguments = SetArgumentsAndUpdateOptions(commandLineForOptions); - if (commandLineArguments != null) + _visualStudioProject.OutputFilePath = Path.Combine(rootDirectory, value); + } + else { - // some languages (e.g., F#) don't expose a command line parser and this might be `null` - SetRuleSetFile(commandLineArguments.RuleSetPath); - PostSetOptions(commandLineArguments); + _visualStudioProject.OutputFilePath = value; } - }); + + // Compute the ref path based on the non-ref path. Ideally this should come from the + // project system but we don't have a way to fetch that. + _visualStudioProject.OutputRefFilePath = + Path.Combine(Path.GetDirectoryName(_visualStudioProject.OutputFilePath), + "ref", + Path.GetFileName(_visualStudioProject.OutputFilePath)); + } + } + + internal string GetIntermediateOutputFilePath() + { + return _visualStudioProject.IntermediateOutputFilePath; } - private void PostSetOptions(CommandLineArguments commandLineArguments) + public ProjectId Id => _visualStudioProject.Id; + + public void SetOptions(string commandLineForOptions) { - ExecuteForegroundAction(() => + if (_visualStudioProjectOptionsProcessor != null) { - // Invoke SetOutputPathAndRelatedData to update the project obj output path. - if (commandLineArguments.OutputFileName != null && commandLineArguments.OutputDirectory != null) - { - var objOutputPath = PathUtilities.CombinePathsUnchecked(commandLineArguments.OutputDirectory, commandLineArguments.OutputFileName); - SetObjOutputPathAndRelatedData(objOutputPath); - } - }); + _visualStudioProjectOptionsProcessor.CommandLine = commandLineForOptions; + } } - #endregion - #region References public void AddMetadataReference(string referencePath, MetadataReferenceProperties properties) { - ExecuteForegroundAction(() => - { - referencePath = FileUtilities.NormalizeAbsolutePath(referencePath); - AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(referencePath, properties); - }); + referencePath = FileUtilities.NormalizeAbsolutePath(referencePath); + _visualStudioProject.AddMetadataReference(referencePath, properties); } - public new void RemoveMetadataReference(string referencePath) + public void RemoveMetadataReference(string referencePath) { - ExecuteForegroundAction(() => - { - referencePath = FileUtilities.NormalizeAbsolutePath(referencePath); - base.RemoveMetadataReference(referencePath); - }); + referencePath = FileUtilities.NormalizeAbsolutePath(referencePath); + _visualStudioProject.RemoveMetadataReference(referencePath, _visualStudioProject.GetPropertiesForMetadataReference(referencePath).Single()); } public void AddProjectReference(IWorkspaceProjectContext project, MetadataReferenceProperties properties) { - ExecuteForegroundAction(() => - { - var abstractProject = GetAbstractProject(project); - AddProjectReference(new ProjectReference(abstractProject.Id, properties.Aliases, properties.EmbedInteropTypes)); - }); + var otherProjectId = ((CPSProject)project)._visualStudioProject.Id; + _visualStudioProject.AddProjectReference(new ProjectReference(otherProjectId, properties.Aliases, properties.EmbedInteropTypes)); } public void RemoveProjectReference(IWorkspaceProjectContext project) { - ExecuteForegroundAction(() => - { - var referencedProject = GetAbstractProject(project); - var projectReference = GetCurrentProjectReferences().Single(p => p.ProjectId == referencedProject.Id); - RemoveProjectReference(projectReference); - }); + var otherProjectId = ((CPSProject)project)._visualStudioProject.Id; + var otherProjectReference = _visualStudioProject.GetProjectReferences().Single(pr => pr.ProjectId == otherProjectId); + _visualStudioProject.RemoveProjectReference(otherProjectReference); } - private AbstractProject GetAbstractProject(IWorkspaceProjectContext project) + public void AddSourceFile(string filePath, bool isInCurrentContext = true, IEnumerable folderNames = null, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular) { - var abstractProject = project as AbstractProject; - if (abstractProject == null) - { - throw new ArgumentException("Unsupported project kind", nameof(project)); - } + _visualStudioProject.AddSourceFile(filePath, sourceCodeKind, folderNames.AsImmutableOrNull()); + } - return abstractProject; + public void RemoveSourceFile(string filePath) + { + _visualStudioProject.RemoveSourceFile(filePath); } - #endregion - #region Files - public void AddSourceFile(string filePath, bool isInCurrentContext = true, IEnumerable folderNames = null, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular) + public void AddAdditionalFile(string filePath, bool isInCurrentContext = true) { - ExecuteForegroundAction(() => - { - AddFile(filePath, sourceCodeKind, _ => isInCurrentContext, folderNames.ToImmutableArrayOrEmpty()); - }); + _visualStudioProject.AddAdditionalFile(filePath); } - public void RemoveSourceFile(string filePath) + public void Dispose() { - ExecuteForegroundAction(() => - { - RemoveFile(filePath); - }); + _projectCodeModel?.OnProjectClosed(); + _visualStudioProject.RemoveFromWorkspace(); } - public void AddAdditionalFile(string filePath, bool isInCurrentContext = true) + public void AddAnalyzerReference(string referencePath) { - ExecuteForegroundAction(() => - { - AddAdditionalFile(filePath, getIsInCurrentContext: _ => isInCurrentContext); - }); + _visualStudioProject.AddAnalyzerReference(referencePath); } - #endregion + public void RemoveAnalyzerReference(string referencePath) + { + _visualStudioProject.RemoveAnalyzerReference(referencePath); + } - #region IDisposable - public void Dispose() + public void RemoveAdditionalFile(string filePath) + { + _visualStudioProject.RemoveAdditionalFile(filePath); + } + + public void AddDynamicFile(string filePath, IEnumerable folderNames = null) + { + } + + public void RemoveDynamicFile(string fullPath) + { + } + + public void SetRuleSetFile(string filePath) + { + // This is now a no-op: we also recieve the rule set file through SetOptions, and we'll just use that one + } + + private readonly ConcurrentQueue _batchScopes = new ConcurrentQueue(); + + public void StartBatch() + { + _batchScopes.Enqueue(_visualStudioProject.CreateBatchScope()); + } + + public void EndBatch() + { + Contract.ThrowIfFalse(_batchScopes.TryDequeue(out var scope)); + scope.Dispose(); + } + + internal VisualStudioProject GetProject_TestOnly() { - Disconnect(); + return _visualStudioProject; } - #endregion } } diff --git a/src/VisualStudio/Core/Impl/RoslynVisualStudioWorkspace.cs b/src/VisualStudio/Core/Impl/RoslynVisualStudioWorkspace.cs index 655ab5475a196c3f949602400d35f06ce6a55d21..0b63ff288c16782307523e08374f5dde39e6ae84 100644 --- a/src/VisualStudio/Core/Impl/RoslynVisualStudioWorkspace.cs +++ b/src/VisualStudio/Core/Impl/RoslynVisualStudioWorkspace.cs @@ -17,7 +17,10 @@ using Microsoft.VisualStudio.LanguageServices.Implementation.Interop; using Microsoft.VisualStudio.LanguageServices.Implementation.Library.ObjectBrowser.Lists; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; using Roslyn.Utilities; +using IAsyncServiceProvider = Microsoft.VisualStudio.Shell.IAsyncServiceProvider; namespace Microsoft.VisualStudio.LanguageServices { @@ -32,7 +35,7 @@ internal class RoslynVisualStudioWorkspace : VisualStudioWorkspaceImpl ExportProvider exportProvider, [ImportMany] IEnumerable> streamingPresenters, [ImportMany] IEnumerable documentOptionsProviderFactories) - : base(exportProvider.AsExportProvider()) + : base(exportProvider.AsExportProvider(), AsyncServiceProvider.GlobalProvider) // TODO: switch to the cleaner MEF import { _streamingPresenters = streamingPresenters; @@ -49,11 +52,7 @@ public override EnvDTE.FileCodeModel GetFileCodeModel(DocumentId documentId) throw new ArgumentNullException(nameof(documentId)); } - if (DeferredState == null) - { - // We haven't gotten any projects added yet, so we don't know where this came from - throw new ArgumentException(ServicesVSResources.The_given_DocumentId_did_not_come_from_the_Visual_Studio_workspace, nameof(documentId)); - } +/* var project = DeferredState.ProjectTracker.GetProject(documentId.ProjectId); if (project == null) @@ -72,28 +71,24 @@ public override EnvDTE.FileCodeModel GetFileCodeModel(DocumentId documentId) return project.ProjectCodeModel.GetOrCreateFileCodeModel(document.FilePath); } + */ + return null; } internal override IInvisibleEditor OpenInvisibleEditor(DocumentId documentId) - { - var hostDocument = GetHostDocument(documentId); - return OpenInvisibleEditor(hostDocument); - } - - internal override IInvisibleEditor OpenInvisibleEditor(IVisualStudioHostDocument hostDocument) { var globalUndoService = this.Services.GetService(); var needsUndoDisabled = false; // Do not save the file if is open and there is not a global undo transaction. - var needsSave = globalUndoService.IsGlobalTransactionOpen(this) || !hostDocument.IsOpen; + var needsSave = globalUndoService.IsGlobalTransactionOpen(this) || !this.IsDocumentOpen(documentId); if (needsSave) { - if (this.CurrentSolution.ContainsDocument(hostDocument.Id)) + if (this.CurrentSolution.ContainsDocument(documentId)) { // Disable undo on generated documents - needsUndoDisabled = this.CurrentSolution.GetDocument(hostDocument.Id).IsGeneratedCode(CancellationToken.None); + needsUndoDisabled = this.CurrentSolution.GetDocument(documentId).IsGeneratedCode(CancellationToken.None); } else { @@ -102,7 +97,9 @@ internal override IInvisibleEditor OpenInvisibleEditor(IVisualStudioHostDocument } } - return new InvisibleEditor(DeferredState.ServiceProvider, hostDocument.FilePath, hostDocument.Project, needsSave, needsUndoDisabled); + var document = this.CurrentSolution.GetDocument(documentId) ?? this.CurrentSolution.GetAdditionalDocument(documentId); + + return new InvisibleEditor(ServiceProvider.GlobalProvider, document.FilePath, GetHierarchy(documentId.ProjectId), needsSave, needsUndoDisabled); } private static bool TryResolveSymbol(ISymbol symbol, Project project, CancellationToken cancellationToken, out ISymbol resolvedSymbol, out Project resolvedProject) diff --git a/src/VisualStudio/Core/SolutionExplorerShim/AnalyzerItem/AnalyzerItemSource.cs b/src/VisualStudio/Core/SolutionExplorerShim/AnalyzerItem/AnalyzerItemSource.cs index 4c88dc7a545288829908c20617a650a17adbf77b..33afc41e682913ef267e021e93773ece115fafd3 100644 --- a/src/VisualStudio/Core/SolutionExplorerShim/AnalyzerItem/AnalyzerItemSource.cs +++ b/src/VisualStudio/Core/SolutionExplorerShim/AnalyzerItem/AnalyzerItemSource.cs @@ -188,6 +188,7 @@ private ImmutableHashSet GetAnalyzersWithLoadErrors() { if (_analyzersFolder.Workspace is VisualStudioWorkspaceImpl vsWorkspace) { + /* var vsProject = vsWorkspace.DeferredState?.ProjectTracker.GetProject(_analyzersFolder.ProjectId); var vsAnalyzersMap = vsProject?.GetProjectAnalyzersMap(); @@ -195,6 +196,7 @@ private ImmutableHashSet GetAnalyzersWithLoadErrors() { return vsAnalyzersMap.Where(kvp => kvp.Value.HasLoadErrors).Select(kvp => kvp.Key).ToImmutableHashSet(); } + */ } return ImmutableHashSet.Empty; diff --git a/src/VisualStudio/Core/SolutionExplorerShim/AnalyzersCommandHandler.cs b/src/VisualStudio/Core/SolutionExplorerShim/AnalyzersCommandHandler.cs index 566fb287794adc35336eb2aebb48dff5ecc0ec48..cfcd1551194e9a49fb3dd32d56c7d814bd78bb92 100644 --- a/src/VisualStudio/Core/SolutionExplorerShim/AnalyzersCommandHandler.cs +++ b/src/VisualStudio/Core/SolutionExplorerShim/AnalyzersCommandHandler.cs @@ -259,7 +259,10 @@ private void UpdateSeverityMenuItemsChecked() foreach (var group in groups) { + // TODO: move this off GetHostProject (https://devdiv.visualstudio.com/DevDiv/_workitems/edit/698029) +#pragma warning disable CS0618 // Type or member is obsolete var project = (AbstractProject)workspace.GetHostProject(group.Key); +#pragma warning restore CS0618 // Type or member is obsolete IRuleSetFile ruleSet = project.RuleSetFile?.Target; if (ruleSet != null) @@ -369,7 +372,10 @@ internal void OpenRuleSetHandler(object sender, EventArgs args) var projectId = _tracker.SelectedFolder.ProjectId; if (workspace != null) { - var project = (AbstractProject)workspace.GetHostProject(projectId); + // TODO: move this off GetHostProject (https://devdiv.visualstudio.com/DevDiv/_workitems/edit/698029) +#pragma warning disable CS0618 // Type or member is obsolete + var project = workspace.GetHostProject(projectId); +#pragma warning restore CS0618 // Type or member is obsolete if (project == null) { SendUnableToOpenRuleSetNotification(workspace, string.Format(SolutionExplorerShim.Could_not_find_project_0, projectId)); @@ -415,7 +421,10 @@ private void SetSeverityHandler(object sender, EventArgs args) foreach (var selectedDiagnostic in _tracker.SelectedDiagnosticItems) { var projectId = selectedDiagnostic.ProjectId; - var project = (AbstractProject)workspace.GetHostProject(projectId); + // TODO: move this off GetHostProject (https://devdiv.visualstudio.com/DevDiv/_workitems/edit/698029) +#pragma warning disable CS0618 // Type or member is obsolete + var project = workspace.GetHostProject(projectId); +#pragma warning restore CS0618 // Type or member is obsolete if (project == null) { diff --git a/src/VisualStudio/Core/SolutionExplorerShim/AnalyzersFolderItem/AnalyzersFolderItemProvider.cs b/src/VisualStudio/Core/SolutionExplorerShim/AnalyzersFolderItem/AnalyzersFolderItemProvider.cs index 369941aca4a1b84f802a485137e28d0e4744c7b2..a7f4bf7e764f29ef1123dcc2dee72981c92bdcfd 100644 --- a/src/VisualStudio/Core/SolutionExplorerShim/AnalyzersFolderItem/AnalyzersFolderItemProvider.cs +++ b/src/VisualStudio/Core/SolutionExplorerShim/AnalyzersFolderItem/AnalyzersFolderItemProvider.cs @@ -21,6 +21,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplore [Order(Before = HierarchyItemsProviderNames.Contains)] internal class AnalyzersFolderItemProvider : AttachedCollectionSourceProvider { + // NOTE: the IComponentModel is used here rather than importing ISolutionExplorerWorkspaceProvider directly + // to avoid loading VisualStudioWorkspace and dependent assemblies directly private readonly IComponentModel _componentModel; private readonly IAnalyzersCommandHandler _commandHandler; private IHierarchyItemToProjectIdMap _projectMap; diff --git a/src/VisualStudio/Core/SolutionExplorerShim/SolutionExplorerWorkspaceProvider.cs b/src/VisualStudio/Core/SolutionExplorerShim/SolutionExplorerWorkspaceProvider.cs index 91e59a2e0664191119e0bc0d364c046380105622..b04b4f0120bef1d3508fe489f8c08f70e08159bc 100644 --- a/src/VisualStudio/Core/SolutionExplorerShim/SolutionExplorerWorkspaceProvider.cs +++ b/src/VisualStudio/Core/SolutionExplorerShim/SolutionExplorerWorkspaceProvider.cs @@ -2,22 +2,21 @@ using System.ComponentModel.Composition; using Microsoft.CodeAnalysis; -using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer { [Export(typeof(ISolutionExplorerWorkspaceProvider))] internal class SolutionExplorerWorkspaceProvider : ISolutionExplorerWorkspaceProvider { - private readonly VisualStudioWorkspaceImpl _workspace; + private readonly VisualStudioWorkspace _workspace; [ImportingConstructor] - public SolutionExplorerWorkspaceProvider(VisualStudioWorkspaceImpl workspace) + public SolutionExplorerWorkspaceProvider(VisualStudioWorkspace workspace) { _workspace = workspace; } - public Microsoft.CodeAnalysis.Workspace GetWorkspace() + public Workspace GetWorkspace() { return _workspace; } diff --git a/src/VisualStudio/Core/Test.Next/Services/VisualStudioSnapshotSerializationTests.cs b/src/VisualStudio/Core/Test.Next/Services/VisualStudioSnapshotSerializationTests.cs index 63c729f321d1c606b1bc288e45499ba70092b81b..397de550636c2c33b1bbb504684f93bc293f5178 100644 --- a/src/VisualStudio/Core/Test.Next/Services/VisualStudioSnapshotSerializationTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/VisualStudioSnapshotSerializationTests.cs @@ -2,64 +2,64 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Execution; using Microsoft.CodeAnalysis.Serialization; +using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.UnitTests; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; -using Microsoft.VisualStudio.Shell.Interop; -using Moq; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; namespace Roslyn.VisualStudio.Next.UnitTests.Services { + [UseExportProvider] public class VisualStudioSnapshotSerializationTests : SnapshotSerializationTestBase { [Fact, WorkItem(466282, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/466282")] public async Task TestUnresolvedAnalyzerReference() { - var workspace = new AdhocWorkspace(); - var project = workspace.CurrentSolution.AddProject("empty", "empty", LanguageNames.CSharp); - var mockFileChangeService = new Mock(); - using (var analyzer = new VisualStudioAnalyzer( - @"PathToAnalyzer", - fileChangeService: mockFileChangeService.Object, - hostDiagnosticUpdateSource: null, - projectId: project.Id, - workspace: workspace, - loader: null, - language: project.Language)) + using (var workspace = new TestWorkspace()) { - var analyzerReference = analyzer.GetReference(); - project = project.WithAnalyzerReferences(new AnalyzerReference[] + var project = workspace.CurrentSolution.AddProject("empty", "empty", LanguageNames.CSharp); + using (var analyzer = new VisualStudioAnalyzer( + @"PathToAnalyzer", + hostDiagnosticUpdateSource: null, + projectId: project.Id, + workspace: workspace, + language: project.Language)) { - analyzerReference, - }); - - var checksum = await project.State.GetChecksumAsync(CancellationToken.None).ConfigureAwait(false); - Assert.NotNull(checksum); + var analyzerReference = analyzer.GetReference(); + project = project.WithAnalyzerReferences(new AnalyzerReference[] + { + analyzerReference, + }); - var assetBuilder = new CustomAssetBuilder(workspace); - var serializer = workspace.Services.GetService(); + var checksum = await project.State.GetChecksumAsync(CancellationToken.None).ConfigureAwait(false); + Assert.NotNull(checksum); - var asset = assetBuilder.Build(analyzerReference, CancellationToken.None); + var assetBuilder = new CustomAssetBuilder(workspace); + var serializer = workspace.Services.GetService(); - using (var stream = SerializableBytes.CreateWritableStream()) - using (var writer = new ObjectWriter(stream)) - { - await asset.WriteObjectToAsync(writer, CancellationToken.None).ConfigureAwait(false); + var asset = assetBuilder.Build(analyzerReference, CancellationToken.None); - stream.Position = 0; - using (var reader = ObjectReader.TryGetReader(stream)) + using (var stream = SerializableBytes.CreateWritableStream()) + using (var writer = new ObjectWriter(stream)) { - var recovered = serializer.Deserialize(asset.Kind, reader, CancellationToken.None); - var assetFromStorage = assetBuilder.Build(recovered, CancellationToken.None); + await asset.WriteObjectToAsync(writer, CancellationToken.None).ConfigureAwait(false); + + stream.Position = 0; + using (var reader = ObjectReader.TryGetReader(stream)) + { + var recovered = serializer.Deserialize(asset.Kind, reader, CancellationToken.None); + var assetFromStorage = assetBuilder.Build(recovered, CancellationToken.None); - Assert.Equal(asset.Checksum, assetFromStorage.Checksum); + Assert.Equal(asset.Checksum, assetFromStorage.Checksum); - // This won't round trip, but we should get an UnresolvedAnalyzerReference, with the same path - Assert.Equal(analyzerReference.FullPath, recovered.FullPath); + // This won't round trip, but we should get an UnresolvedAnalyzerReference, with the same path + Assert.Equal(analyzerReference.FullPath, recovered.FullPath); + } } } } diff --git a/src/VisualStudio/Core/Test/ObjectBrowser/AbstractObjectBrowserTests.vb b/src/VisualStudio/Core/Test/ObjectBrowser/AbstractObjectBrowserTests.vb index 706a4d705036c58dd4b71a970e48af07b63f2774..c4246b5dbd6c4969bc9824c3d03a2fdb26dac0e2 100644 --- a/src/VisualStudio/Core/Test/ObjectBrowser/AbstractObjectBrowserTests.vb +++ b/src/VisualStudio/Core/Test/ObjectBrowser/AbstractObjectBrowserTests.vb @@ -6,6 +6,7 @@ Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.VisualStudio.ComponentModelHost Imports Microsoft.VisualStudio.LanguageServices.Implementation.Library.ObjectBrowser +Imports Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel.Mocks Imports Microsoft.VisualStudio.LanguageServices.UnitTests.ObjectBrowser.Mocks Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ObjectBrowser diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/ConvertedVisualBasicProjectOptionsTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/ConvertedVisualBasicProjectOptionsTests.vb index e5cc48e8d13096c620072a04e6335549354d94b0..20da8c83ee66a7e43b02da22156e7e73798304d4 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/ConvertedVisualBasicProjectOptionsTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/ConvertedVisualBasicProjectOptionsTests.vb @@ -151,7 +151,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Optional commandLineWarnNotAsErrors As String = "", Optional commandLineNoWarns As String = "") As VisualBasicCompilationOptions - ruleSetSpecificOptions = If(ruleSetSpecificOptions Is Nothing, ImmutableDictionary(Of String, ReportDiagnostic).Empty, ruleSetSpecificOptions) + ruleSetSpecificOptions = If(ruleSetSpecificOptions, ImmutableDictionary(Of String, ReportDiagnostic).Empty) Dim compilerOptions = New VBCompilerOptions With { @@ -161,15 +161,10 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim .wszDisabledWarnings = commandLineNoWarns } Dim compilerHost = New MockCompilerHost("C:\SDK") - Dim convertedOptions = VisualBasicProjectOptionsHelper.CreateCompilationOptions( - Nothing, - VisualBasicParseOptions.Default, - compilerOptions, - compilerHost, - SpecializedCollections.EmptyEnumerable(Of GlobalImport), - Nothing, - New MockRuleSetFile(ruleSetGeneralOption, ruleSetSpecificOptions)) - Return convertedOptions + Return VisualBasicProject.OptionsProcessor.ApplyCompilationOptionsFromVBCompilerOptions( + New VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary) _ + .WithParseOptions(VisualBasicParseOptions.Default), compilerOptions, + New MockRuleSetFile(ruleSetGeneralOption, ruleSetSpecificOptions)) End Function End Class End Namespace diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/DeferredProjectLoadingTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/DeferredProjectLoadingTests.vb deleted file mode 100644 index 3f1a8c9aab703860037ee996ec3941bdbdd2c3c8..0000000000000000000000000000000000000000 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/DeferredProjectLoadingTests.vb +++ /dev/null @@ -1,103 +0,0 @@ -' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework -Imports Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.VisualBasicHelpers -Imports Roslyn.Test.Utilities - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim - <[UseExportProvider]> - Public Class DeferredProjectLoadingTests - - - Public Sub SimpleDeferredLoading() - Using testEnvironment = New TestEnvironment(solutionIsFullyLoaded:=False) - CreateVisualBasicProject(testEnvironment, "TestProject") - - ' We should not yet have pushed this project to the workspace - Assert.Empty(testEnvironment.Workspace.CurrentSolution.Projects) - - testEnvironment.NotifySolutionAsFullyLoaded() - - Assert.Single(testEnvironment.Workspace.CurrentSolution.Projects) - End Using - End Sub - - - - Public Sub DoNotDeferLoadIfInNonBackgroundBatch() - Using testEnvironment = New TestEnvironment(solutionIsFullyLoaded:=False) - testEnvironment.ProjectTracker.OnBeforeLoadProjectBatch(fIsBackgroundIdleBatch:=False) - CreateVisualBasicProject(testEnvironment, "TestProject") - testEnvironment.ProjectTracker.OnAfterLoadProjectBatch(fIsBackgroundIdleBatch:=False) - - ' We should have pushed this project to the workspace - Assert.Single(testEnvironment.Workspace.CurrentSolution.Projects) - - ' This should (in theory) do nothing - testEnvironment.NotifySolutionAsFullyLoaded() - - Assert.Single(testEnvironment.Workspace.CurrentSolution.Projects) - End Using - End Sub - - - - Public Sub AddingProjectInBatchDoesntAddAllProjects() - Using testEnvironment = New TestEnvironment(solutionIsFullyLoaded:=False) - CreateVisualBasicProject(testEnvironment, "TestProject1") - - testEnvironment.ProjectTracker.OnBeforeLoadProjectBatch(fIsBackgroundIdleBatch:=False) - CreateVisualBasicProject(testEnvironment, "TestProject2") - testEnvironment.ProjectTracker.OnAfterLoadProjectBatch(fIsBackgroundIdleBatch:=False) - - ' We should have pushed the second project only - Assert.Equal("TestProject2", testEnvironment.Workspace.CurrentSolution.Projects.Single().Name) - - ' This pushes the other project too - testEnvironment.NotifySolutionAsFullyLoaded() - - Assert.Equal(2, testEnvironment.Workspace.CurrentSolution.Projects.Count()) - End Using - End Sub - - - - Public Sub AddingProjectReferenceInBatchMayPushOtherProjects() - Using testEnvironment = New TestEnvironment(solutionIsFullyLoaded:=False) - Dim project1 = CreateVisualBasicProject(testEnvironment, "TestProject1") - - ' Include a project reference in this batch. This means that project1 must also be pushed - testEnvironment.ProjectTracker.OnBeforeLoadProjectBatch(fIsBackgroundIdleBatch:=False) - Dim project2 = CreateVisualBasicProject(testEnvironment, "TestProject2") - project2.AddProjectReference(project1) - testEnvironment.ProjectTracker.OnAfterLoadProjectBatch(fIsBackgroundIdleBatch:=False) - - ' We should have pushed both projects - Assert.Equal(2, testEnvironment.Workspace.CurrentSolution.Projects.Count()) - End Using - End Sub - - - - Public Sub AddingProjectReferenceAfterBatchMayPushOtherProjects() - Using testEnvironment = New TestEnvironment(solutionIsFullyLoaded:=False) - Dim project1 = CreateVisualBasicProject(testEnvironment, "TestProject1") - - ' Include a project reference in this batch. This means that project1 must also be pushed - testEnvironment.ProjectTracker.OnBeforeLoadProjectBatch(fIsBackgroundIdleBatch:=False) - Dim project2 = CreateVisualBasicProject(testEnvironment, "TestProject2") - testEnvironment.ProjectTracker.OnAfterLoadProjectBatch(fIsBackgroundIdleBatch:=False) - - ' We should have pushed the second project only - Assert.Equal("TestProject2", testEnvironment.Workspace.CurrentSolution.Projects.Single().Name) - - project2.AddProjectReference(project1) - - ' We should have pushed both projects - Assert.Equal(2, testEnvironment.Workspace.CurrentSolution.Projects.Count()) - End Using - End Sub - End Class -End Namespace diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicCompilerOptionsTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicCompilerOptionsTests.vb index 33985c7e2ae1435a9a33f3001fb0076b987cd04f..56ec55fc7d4204504be116878a857af3dd9d1f61 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicCompilerOptionsTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicCompilerOptionsTests.vb @@ -63,9 +63,11 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Dim workspaceProject = environment.Workspace.CurrentSolution.Projects.Single() Dim options = DirectCast(workspaceProject.ParseOptions, VisualBasicParseOptions) - ' SetCompilerOptions only handles versions 15.3 and up + ' SetCompilerOptions only handles versions 15.3 and up, so we are ignoring the + ' /langversion:14 above in favor of the legacy value. Since the legacy value was + ' not set, it'll just be default. Assert.Equal(LanguageVersion.Default.MapSpecifiedToEffectiveVersion(), options.LanguageVersion) - Assert.Equal(LanguageVersion.Default.MapSpecifiedToEffectiveVersion(), options.SpecifiedLanguageVersion) + Assert.Equal(LanguageVersion.Default, options.SpecifiedLanguageVersion) project.Disconnect() End Using @@ -85,8 +87,11 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Dim workspaceProject = environment.Workspace.CurrentSolution.Projects.Single() Dim options = DirectCast(workspaceProject.ParseOptions, VisualBasicParseOptions) + ' SetCompilerOptions only handles versions 15.3 and up, so we are ignoring the + ' /langversion:14 above in favor of the legacy value. Since the legacy value was + ' not set, it'll just be default. Assert.Equal(LanguageVersion.Default.MapSpecifiedToEffectiveVersion(), options.LanguageVersion) - Assert.Equal(LanguageVersion.Default.MapSpecifiedToEffectiveVersion(), options.SpecifiedLanguageVersion) + Assert.Equal(LanguageVersion.Default, options.SpecifiedLanguageVersion) project.Disconnect() End Using @@ -107,7 +112,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Dim options = DirectCast(workspaceProject.ParseOptions, VisualBasicParseOptions) Assert.Equal(LanguageVersion.Default.MapSpecifiedToEffectiveVersion(), options.LanguageVersion) - Assert.Equal(LanguageVersion.Default.MapSpecifiedToEffectiveVersion(), options.SpecifiedLanguageVersion) + Assert.Equal(LanguageVersion.Default, options.SpecifiedLanguageVersion) project.Disconnect() End Using @@ -149,7 +154,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Dim options = DirectCast(workspaceProject.ParseOptions, VisualBasicParseOptions) Assert.Equal(LanguageVersion.Latest.MapSpecifiedToEffectiveVersion(), options.LanguageVersion) - Assert.Equal(LanguageVersion.Latest.MapSpecifiedToEffectiveVersion(), options.SpecifiedLanguageVersion) + Assert.Equal(LanguageVersion.Latest, options.SpecifiedLanguageVersion) project.Disconnect() End Using @@ -204,23 +209,5 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim project.Disconnect() End Using End Sub - - - - - Sub NullBinPath() - Using environment = New TestEnvironment() - Dim project = CreateVisualBasicProjectWithNullBinPath(environment, "Test") - - Dim compilerOptions = CreateMinimalCompilerOptions(project) - project.SetCompilerOptions(compilerOptions) - - ' Mostly, we're just validating that we didn't crash on the way here - Assert.NotNull(project) - Assert.Null(project.BinOutputPath) - project.Disconnect() - End Using - - End Sub End Class End Namespace diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicSpecialReferencesTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicSpecialReferencesTests.vb index 924475a8771e35aed9074e47b0ddc68e949fbdda..5569a8ab529c27a83631e12d18ee223eef51f267 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicSpecialReferencesTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicSpecialReferencesTests.vb @@ -143,97 +143,5 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim project.Disconnect() End Using End Sub - - - - Public Sub AddingReferenceToProjectMetadataPromotesToProjectReference() - Using environment = New TestEnvironment() - - Dim project1 = CreateVisualBasicProject(environment, "project1") - project1.SetBinOutputPathAndRelatedData("C:\project1.dll") - - Dim project2 = CreateVisualBasicProject(environment, "project2") - project2.SetBinOutputPathAndRelatedData("C:\project2.dll") - - ' since this is known to be the output path of project1, the metadata reference is converted to a project reference - project2.AddMetaDataReference("c:\project1.dll", True) - - Assert.Equal(True, project2.GetCurrentProjectReferences().Any(Function(pr) pr.ProjectId = project1.Id)) - - project2.Disconnect() - project1.Disconnect() - End Using - End Sub - - - - Public Sub AddCyclicProjectMetadataReferences() - Using environment = New TestEnvironment() - - Dim project1 = CreateVisualBasicProject(environment, "project1") - project1.SetBinOutputPathAndRelatedData("C:\project1.dll") - - Dim project2 = CreateVisualBasicProject(environment, "project2") - project2.SetBinOutputPathAndRelatedData("C:\project2.dll") - - project1.AddProjectReference(project2) - - ' normally this metadata reference would be elevated to a project reference, but fails because of cyclicness - project2.AddMetaDataReference("c:\project1.dll", True) - - Assert.Equal(True, project1.GetCurrentProjectReferences().Any(Function(pr) pr.ProjectId = project2.Id)) - Assert.Equal(False, project2.GetCurrentProjectReferences().Any(Function(pr) pr.ProjectId = project1.Id)) - - project2.Disconnect() - project1.Disconnect() - End Using - End Sub - - - - Public Sub AddCyclicProjectReferences() - Using environment = New TestEnvironment() - - Dim project1 = CreateVisualBasicProject(environment, "project1") - Dim project2 = CreateVisualBasicProject(environment, "project2") - - project1.AddProjectReference(project2) - project2.AddProjectReference(project1) - - Assert.Equal(True, project1.GetCurrentProjectReferences().Any(Function(pr) pr.ProjectId = project2.Id)) - Assert.Equal(False, project2.GetCurrentProjectReferences().Any(Function(pr) pr.ProjectId = project1.Id)) - - project2.Disconnect() - project1.Disconnect() - End Using - End Sub - - - - Public Sub AddCyclicProjectReferencesDeep() - Using environment = New TestEnvironment() - - Dim project1 = CreateVisualBasicProject(environment, "project1") - Dim project2 = CreateVisualBasicProject(environment, "project2") - Dim project3 = CreateVisualBasicProject(environment, "project3") - Dim project4 = CreateVisualBasicProject(environment, "project4") - - project1.AddProjectReference(project2) - project2.AddProjectReference(project3) - project3.AddProjectReference(project4) - project4.AddProjectReference(project1) - - Assert.Equal(True, project1.GetCurrentProjectReferences().Any(Function(pr) pr.ProjectId = project2.Id)) - Assert.Equal(True, project2.GetCurrentProjectReferences().Any(Function(pr) pr.ProjectId = project3.Id)) - Assert.Equal(True, project3.GetCurrentProjectReferences().Any(Function(pr) pr.ProjectId = project4.Id)) - Assert.Equal(False, project4.GetCurrentProjectReferences().Any(Function(pr) pr.ProjectId = project1.Id)) - - project4.Disconnect() - project3.Disconnect() - project2.Disconnect() - project1.Disconnect() - End Using - End Sub - End Class End Namespace diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioAnalyzerTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioAnalyzerTests.vb index e4c261e2316091e9fed5111a0b07cca981d95861..27aad121aaffed01e6513613bddc876924dac62f 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioAnalyzerTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioAnalyzerTests.vb @@ -5,6 +5,7 @@ Imports System.Reflection Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics +Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem Imports Microsoft.VisualStudio.LanguageServices.Implementation.TaskList @@ -12,14 +13,17 @@ Imports Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fram Imports Roslyn.Test.Utilities Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim + Public Class VisualStudioAnalyzerTests Public Sub GetReferenceCalledMultipleTimes() - Using analyzer = New VisualStudioAnalyzer("C:\Goo\Bar.dll", New MockVsFileChangeEx(), Nothing, Nothing, Nothing, Nothing, Nothing) - Dim reference1 = analyzer.GetReference() - Dim reference2 = analyzer.GetReference() + Using workspace = New TestWorkspace() + Using analyzer = New VisualStudioAnalyzer("C:\Goo\Bar.dll", Nothing, Nothing, workspace, Nothing) + Dim reference1 = analyzer.GetReference() + Dim reference2 = analyzer.GetReference() - Assert.True(Object.ReferenceEquals(reference1, reference2)) + Assert.True(Object.ReferenceEquals(reference1, reference2)) + End Using End Using End Sub @@ -32,15 +36,17 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim AddHandler hostDiagnosticUpdateSource.DiagnosticsUpdated, AddressOf eventHandler.DiagnosticAddedTest - Using analyzer = New VisualStudioAnalyzer(file, New MockVsFileChangeEx(), hostDiagnosticUpdateSource, ProjectId.CreateNewId(), Nothing, New MockAnalyzerAssemblyLoader(), LanguageNames.VisualBasic) - Dim reference = analyzer.GetReference() - reference.GetAnalyzers(LanguageNames.VisualBasic) + Using workspace = New TestWorkspace() + Using analyzer = New VisualStudioAnalyzer(file, hostDiagnosticUpdateSource, ProjectId.CreateNewId(), workspace, LanguageNames.VisualBasic) + Dim reference = analyzer.GetReference() + reference.GetAnalyzers(LanguageNames.VisualBasic) - RemoveHandler hostDiagnosticUpdateSource.DiagnosticsUpdated, AddressOf eventHandler.DiagnosticAddedTest - AddHandler hostDiagnosticUpdateSource.DiagnosticsUpdated, AddressOf eventHandler.DiagnosticRemovedTest - End Using + RemoveHandler hostDiagnosticUpdateSource.DiagnosticsUpdated, AddressOf eventHandler.DiagnosticAddedTest + AddHandler hostDiagnosticUpdateSource.DiagnosticsUpdated, AddressOf eventHandler.DiagnosticRemovedTest + End Using - IO.File.Delete(file) + IO.File.Delete(file) + End Using End Sub Private Class EventHandlers diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioRuleSetTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioRuleSetTests.vb index cc35c8ec9bf19ef6174aecdd036de3e9ab3c4605..78e073e448d639da5d7253468b04dca13d0c325a 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioRuleSetTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioRuleSetTests.vb @@ -7,6 +7,7 @@ Imports Microsoft.CodeAnalysis.Shared.TestHooks Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem Imports Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework +Imports Microsoft.VisualStudio.Shell.Interop Imports Roslyn.Test.Utilities Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim @@ -42,15 +43,18 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim File.WriteAllText(ruleSetPath, ruleSetSource) Dim fileChangeService = New MockVsFileChangeEx - Dim ruleSetManager = New VisualStudioRuleSetManager(fileChangeService, New TestForegroundNotificationService(), AsynchronousOperationListenerProvider.NullListener) + Dim fileChangeWatcher = New FileChangeWatcher(Task.FromResult(Of IVsFileChangeEx)(fileChangeService)) + Dim ruleSetManager = New VisualStudioRuleSetManager(fileChangeWatcher, New TestForegroundNotificationService(), AsynchronousOperationListenerProvider.NullListener) Using visualStudioRuleSet = ruleSetManager.GetOrCreateRuleSet(ruleSetPath) ' Signing up for file change notifications is lazy, so read the rule set to force it. Dim generalDiagnosticOption = visualStudioRuleSet.Target.GetGeneralDiagnosticOption() + fileChangeWatcher.WaitForQueue_TestOnly() Assert.Equal(expected:=1, actual:=fileChangeService.WatchedFileCount) End Using + fileChangeWatcher.WaitForQueue_TestOnly() Assert.Equal(expected:=0, actual:=fileChangeService.WatchedFileCount) End Sub @@ -81,15 +85,18 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim File.WriteAllText(includePath, includeSource) Dim fileChangeService = New MockVsFileChangeEx - Dim ruleSetManager = New VisualStudioRuleSetManager(fileChangeService, New TestForegroundNotificationService(), AsynchronousOperationListenerProvider.NullListener) + Dim fileChangeWatcher = New FileChangeWatcher(Task.FromResult(Of IVsFileChangeEx)(fileChangeService)) + Dim ruleSetManager = New VisualStudioRuleSetManager(fileChangeWatcher, New TestForegroundNotificationService(), AsynchronousOperationListenerProvider.NullListener) Using visualStudioRuleSet = ruleSetManager.GetOrCreateRuleSet(ruleSetPath) ' Signing up for file change notifications is lazy, so read the rule set to force it. Dim generalDiagnosticOption = visualStudioRuleSet.Target.GetGeneralDiagnosticOption() + fileChangeWatcher.WaitForQueue_TestOnly() Assert.Equal(expected:=2, actual:=fileChangeService.WatchedFileCount) End Using + fileChangeWatcher.WaitForQueue_TestOnly() Assert.Equal(expected:=0, actual:=fileChangeService.WatchedFileCount) End Sub @@ -120,7 +127,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim File.WriteAllText(includePath, includeSource) Dim fileChangeService = New MockVsFileChangeEx - Dim ruleSetManager = New VisualStudioRuleSetManager(fileChangeService, New TestForegroundNotificationService(), AsynchronousOperationListenerProvider.NullListener) + Dim fileChangeWatcher = New FileChangeWatcher(Task.FromResult(Of IVsFileChangeEx)(fileChangeService)) + Dim ruleSetManager = New VisualStudioRuleSetManager(fileChangeWatcher, New TestForegroundNotificationService(), AsynchronousOperationListenerProvider.NullListener) Using ruleSet1 = ruleSetManager.GetOrCreateRuleSet(ruleSetPath) Dim handlerCalled As Boolean = False AddHandler ruleSet1.Target.UpdatedOnDisk, Sub() handlerCalled = True @@ -128,6 +136,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim ' Signing up for file change notifications is lazy, so read the rule set to force it. Dim generalDiagnosticOption = ruleSet1.Target.GetGeneralDiagnosticOption() + fileChangeWatcher.WaitForQueue_TestOnly() fileChangeService.FireUpdate(includePath) Assert.True(handlerCalled) End Using @@ -150,11 +159,13 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim File.WriteAllText(ruleSetPath, ruleSetSource) Dim fileChangeService = New MockVsFileChangeEx - Dim ruleSetManager = New VisualStudioRuleSetManager(fileChangeService, New TestForegroundNotificationService(), AsynchronousOperationListenerProvider.NullListener) + Dim fileChangeWatcher = New FileChangeWatcher(Task.FromResult(Of IVsFileChangeEx)(fileChangeService)) + Dim ruleSetManager = New VisualStudioRuleSetManager(fileChangeWatcher, New TestForegroundNotificationService(), AsynchronousOperationListenerProvider.NullListener) Using ruleSet1 = ruleSetManager.GetOrCreateRuleSet(ruleSetPath) ' Signing up for file change notifications is lazy, so read the rule set to force it. Dim generalDiagnosticOption = ruleSet1.Target.GetGeneralDiagnosticOption() + fileChangeWatcher.WaitForQueue_TestOnly() fileChangeService.FireUpdate(ruleSetPath) Using ruleSet2 = ruleSetManager.GetOrCreateRuleSet(ruleSetPath) @@ -162,12 +173,14 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim ' Signing up for file change notifications is lazy, so read the rule set to force it. generalDiagnosticOption = ruleSet2.Target.GetGeneralDiagnosticOption() + fileChangeWatcher.WaitForQueue_TestOnly() Assert.Equal(expected:=1, actual:=fileChangeService.WatchedFileCount) Assert.NotSame(ruleSet1.Target, ruleSet2.Target) End Using End Using - Assert.Equal(expected:=0, actual:=fileChangeService.WatchedFileCount) + fileChangeWatcher.WaitForQueue_TestOnly() + Assert.Equal(expected:=0, actual:=fileChangeService.WatchedFileCount) End Sub @@ -187,7 +200,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim File.WriteAllText(ruleSetPath, ruleSetSource) Dim fileChangeService = New MockVsFileChangeEx - Dim ruleSetManager = New VisualStudioRuleSetManager(fileChangeService, New TestForegroundNotificationService(), AsynchronousOperationListenerProvider.NullListener) + Dim fileChangeWatcher = New FileChangeWatcher(Task.FromResult(Of IVsFileChangeEx)(fileChangeService)) + Dim ruleSetManager = New VisualStudioRuleSetManager(fileChangeWatcher, New TestForegroundNotificationService(), AsynchronousOperationListenerProvider.NullListener) Using ruleSet1 = ruleSetManager.GetOrCreateRuleSet(ruleSetPath) ' Signing up for file change notifications is lazy, so read the rule set to force it. @@ -195,11 +209,13 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Using ruleSet2 = ruleSetManager.GetOrCreateRuleSet(ruleSetPath) + fileChangeWatcher.WaitForQueue_TestOnly() Assert.Equal(expected:=1, actual:=fileChangeService.WatchedFileCount) Assert.Same(ruleSet1.Target, ruleSet2.Target) End Using End Using + fileChangeWatcher.WaitForQueue_TestOnly() Assert.Equal(expected:=0, actual:=fileChangeService.WatchedFileCount) End Sub @@ -220,10 +236,12 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim File.WriteAllText(ruleSetPath, ruleSetSource) Dim fileChangeService = New MockVsFileChangeEx - Dim ruleSetManager = New VisualStudioRuleSetManager(fileChangeService, New TestForegroundNotificationService(), AsynchronousOperationListenerProvider.NullListener) + Dim fileChangeWatcher = New FileChangeWatcher(Task.FromResult(Of IVsFileChangeEx)(fileChangeService)) + Dim ruleSetManager = New VisualStudioRuleSetManager(fileChangeWatcher, New TestForegroundNotificationService(), AsynchronousOperationListenerProvider.NullListener) Using ruleSet = ruleSetManager.GetOrCreateRuleSet(ruleSetPath) Dim generalDiagnosticOption = ruleSet.Target.GetGeneralDiagnosticOption() + fileChangeWatcher.WaitForQueue_TestOnly() Assert.Equal(expected:=ReportDiagnostic.Default, actual:=generalDiagnosticOption) diff --git a/src/VisualStudio/Core/Test/SolutionExplorer/AnalyzersFolderProviderTests.vb b/src/VisualStudio/Core/Test/SolutionExplorer/AnalyzersFolderProviderTests.vb index e5b3d9d4813c3ef6845d8eaf20ff83f48e154bc5..ec2c10d1bb2e08f06d9768bc7a5bb360aa2d8d93 100644 --- a/src/VisualStudio/Core/Test/SolutionExplorer/AnalyzersFolderProviderTests.vb +++ b/src/VisualStudio/Core/Test/SolutionExplorer/AnalyzersFolderProviderTests.vb @@ -3,6 +3,7 @@ Imports System.Collections.ObjectModel Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.Internal.VisualStudio.PlatformUI +Imports Microsoft.VisualStudio.LanguageServices.Implementation Imports Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer Imports Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework Imports Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.VisualBasicHelpers @@ -33,7 +34,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer Dim hierarchyItem = New MockHierarchyItem With {.HierarchyIdentity = Nothing} - Dim collectionSource = provider.CreateCollectionSource(Nothing, KnownRelationships.Contains) + Dim collectionSource = provider.CreateCollectionSource(hierarchyItem, KnownRelationships.Contains) Assert.Null(collectionSource) End Using @@ -60,7 +61,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer } } - Dim mapper = New HierarchyItemMapper(environment.ProjectTracker) + Dim mapper = New HierarchyItemToProjectIdMap(environment.Workspace) Dim provider As IAttachedCollectionSourceProvider = New AnalyzersFolderItemProvider(mapper, environment.Workspace, New FakeAnalyzersCommandHandler) diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/Workspace/WorkspaceBase.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/Workspace/WorkspaceBase.cs index 1ed4a9afd6ce80b77610b71b0eee09704d991821..ddc27eb2e4c8e682a40080837b5873699577ccf8 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/Workspace/WorkspaceBase.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/Workspace/WorkspaceBase.cs @@ -97,7 +97,7 @@ End Sub Assert.Equal("Sub‎ Program.M‎(p‎ As‎ Object‎)‎ ‎(‎+‎ 1‎ overload‎)", VisualStudio.Editor.GetQuickInfo()); } - [WpfFact] + [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/30599")] public void RenamingOpenFiles() { var project = new ProjectUtils.Project(ProjectName); diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/SolutionExplorer_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/SolutionExplorer_OutOfProc.cs index 0be408e9dfd3ae00469bd71c22b56698b3834bab..2af9be9abc8378100cb1c85fb2264bc07fc20618 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/SolutionExplorer_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/SolutionExplorer_OutOfProc.cs @@ -102,13 +102,21 @@ public void OpenFileWithDesigner(ProjectUtils.Project project, string fileName) => _inProc.OpenFileWithDesigner(project.Name, fileName); public void OpenFile(ProjectUtils.Project project, string fileName) - => _inProc.OpenFile(project.Name, fileName); + { + // Wireup to open files can happen asynchronously in the case we're being notified of changes on background threads. + _inProc.OpenFile(project.Name, fileName); + _instance.Workspace.WaitForAsyncOperations(FeatureAttribute.Workspace); + } public void UpdateFile(string projectName, string fileName, string contents, bool open = false) => _inProc.UpdateFile(projectName, fileName, contents, open); public void RenameFile(ProjectUtils.Project project, string oldFileName, string newFileName) - => _inProc.RenameFile(project.Name, oldFileName, newFileName); + { + // Wireup to open files can happen asynchronously in the case we're being notified of changes on background threads. + _inProc.RenameFile(project.Name, oldFileName, newFileName); + _instance.Workspace.WaitForAsyncOperations(FeatureAttribute.Workspace); + } public void CloseFile(ProjectUtils.Project project, string fileName, bool saveFile) => _inProc.CloseFile(project.Name, fileName, saveFile); diff --git a/src/VisualStudio/TestUtilities2/CodeModel/CodeModelTestHelpers.vb b/src/VisualStudio/TestUtilities2/CodeModel/CodeModelTestHelpers.vb index 4ccd608886b12554a62f93ea26e44937b92e9c0a..0d3cd61a4a57b42ae8c2c34146ed084550dab676 100644 --- a/src/VisualStudio/TestUtilities2/CodeModel/CodeModelTestHelpers.vb +++ b/src/VisualStudio/TestUtilities2/CodeModel/CodeModelTestHelpers.vb @@ -53,7 +53,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel workspace.ExportProvider.GetExportedValue(Of IThreadingContext), mockServiceProvider, project.LanguageServices, - mockVisualStudioWorkspace) + mockVisualStudioWorkspace, + Nothing) Dim mockTextManagerAdapter = New MockTextManagerAdapter() diff --git a/src/VisualStudio/TestUtilities2/MockVisualStudioWorkspace.vb b/src/VisualStudio/TestUtilities2/CodeModel/Mocks/MockVisualStudioWorkspace.vb similarity index 95% rename from src/VisualStudio/TestUtilities2/MockVisualStudioWorkspace.vb rename to src/VisualStudio/TestUtilities2/CodeModel/Mocks/MockVisualStudioWorkspace.vb index 3e21343457020021d19474ff8993e0ec5d9ea41e..211e99e7c3bbaec8082f7779dfa09e5db3958460 100644 --- a/src/VisualStudio/TestUtilities2/MockVisualStudioWorkspace.vb +++ b/src/VisualStudio/TestUtilities2/CodeModel/Mocks/MockVisualStudioWorkspace.vb @@ -9,7 +9,7 @@ Imports Microsoft.VisualStudio.LanguageServices.Implementation.Interop Imports Microsoft.VisualStudio.LanguageServices.Implementation.Library.ObjectBrowser.Lists Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests +Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel.Mocks Friend Class MockVisualStudioWorkspace Inherits VisualStudioWorkspace @@ -50,12 +50,12 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests Return Nothing End Function - Friend Overrides Function OpenInvisibleEditor(documentId As DocumentId) As IInvisibleEditor - Return New MockInvisibleEditor(documentId, _workspace) + Friend Overrides Function GetProjectGuid(projectId As ProjectId) As Guid + Return Guid.Empty End Function - Friend Overrides Function OpenInvisibleEditor(document As IVisualStudioHostDocument) As IInvisibleEditor - Return New MockInvisibleEditor(document.Id, _workspace) + Friend Overrides Function OpenInvisibleEditor(documentId As DocumentId) As IInvisibleEditor + Return New MockInvisibleEditor(documentId, _workspace) End Function Public Overrides Function GetFileCodeModel(documentId As DocumentId) As EnvDTE.FileCodeModel diff --git a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/HierarchyItemMapper.vb b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/HierarchyItemMapper.vb deleted file mode 100644 index 09538b170800b81fcd6fe600e621cdad153774dd..0000000000000000000000000000000000000000 --- a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/HierarchyItemMapper.vb +++ /dev/null @@ -1,35 +0,0 @@ -' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -Imports Microsoft.CodeAnalysis -Imports Microsoft.VisualStudio.LanguageServices.Implementation -Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -Imports Microsoft.VisualStudio.Shell - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework - Friend Class HierarchyItemMapper - Implements IHierarchyItemToProjectIdMap - - Private ReadOnly _tracker As VisualStudioProjectTracker - - Public Sub New(projectTracker As VisualStudioProjectTracker) - _tracker = projectTracker - End Sub - - Public Function TryGetProjectId(hierarchyItem As IVsHierarchyItem, targetFrameworkMoniker As String, ByRef projectId As ProjectId) As Boolean Implements IHierarchyItemToProjectIdMap.TryGetProjectId - - Dim project = _tracker.ImmutableProjects. - Where(Function(p) p.Hierarchy Is hierarchyItem.HierarchyIdentity.NestedHierarchy). - Where(Function(p) p.ProjectSystemName Is hierarchyItem.CanonicalName). - SingleOrDefault() - - If project Is Nothing Then - projectId = Nothing - Return False - Else - projectId = project.Id - Return True - End If - - End Function - End Class -End Namespace diff --git a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/MockHierarchy.vb b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/MockHierarchy.vb index 76a769824fb97da287f9c03b17853b32237552dc..7cbaac210fc204d137c393a6e3c905fdf369d20b 100644 --- a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/MockHierarchy.vb +++ b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/MockHierarchy.vb @@ -5,6 +5,7 @@ Imports Microsoft.VisualStudio.OLE.Interop Imports System.Runtime.InteropServices Imports Microsoft.VisualStudio.Shell Imports Roslyn.Utilities +Imports System.IO Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework Public NotInheritable Class MockHierarchy @@ -70,7 +71,11 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr End Function Public Function GetCanonicalName(itemid As UInteger, ByRef pbstrName As String) As Integer Implements IVsHierarchy.GetCanonicalName - Throw New NotImplementedException() + If _hierarchyItems.TryGetValue(itemid, pbstrName) Then + Return VSConstants.S_OK + Else + Return VSConstants.E_FAIL + End If End Function Public Function GetGuidProperty(itemid As UInteger, propid As Integer, ByRef pguid As Guid) As Integer Implements IVsHierarchy.GetGuidProperty diff --git a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/MockVsFileChangeEx.vb b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/MockVsFileChangeEx.vb index 2f0fc71147f1c14cb2021f70b12a36431a866354..ed58bfd61bc9d47f2c3874ba2424fac5bedda69c 100644 --- a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/MockVsFileChangeEx.vb +++ b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/MockVsFileChangeEx.vb @@ -11,7 +11,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr Private _nextCookie As UInteger = 0UI Public Function AdviseDirChange(pszDir As String, fWatchSubDir As Integer, pFCE As IVsFileChangeEvents, ByRef pvsCookie As UInteger) As Integer Implements IVsFileChangeEx.AdviseDirChange - Throw New NotImplementedException() + Return VSConstants.S_OK End Function Public Function AdviseFileChange(pszMkDocument As String, grfFilter As UInteger, pFCE As IVsFileChangeEvents, ByRef pvsCookie As UInteger) As Integer Implements IVsFileChangeEx.AdviseFileChange @@ -37,7 +37,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr End Function Public Function UnadviseDirChange(VSCOOKIE As UInteger) As Integer Implements IVsFileChangeEx.UnadviseDirChange - Throw New NotImplementedException() + Return VSConstants.S_OK End Function Public Function UnadviseFileChange(VSCOOKIE As UInteger) As Integer Implements IVsFileChangeEx.UnadviseFileChange diff --git a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb index 56f6a0dddb529aa5b3a2c762ef7317af6840eeba..ceea191e6e1e5534f15ed6dfa5a5dc8649bc513b 100644 --- a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb +++ b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb @@ -1,20 +1,30 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +Imports System.ComponentModel.Composition +Imports System.ComponentModel.Composition.Hosting Imports System.IO Imports System.Runtime.InteropServices +Imports System.Threading +Imports EnvDTE Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities Imports Microsoft.CodeAnalysis.Editor.UnitTests -Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces +Imports Microsoft.CodeAnalysis.FindSymbols Imports Microsoft.CodeAnalysis.Host Imports Microsoft.CodeAnalysis.Shared.TestHooks Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.VisualStudio.ComponentModelHost Imports Microsoft.VisualStudio.Composition Imports Microsoft.VisualStudio.LanguageServices.Implementation +Imports Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel +Imports Microsoft.VisualStudio.LanguageServices.Implementation.Library.ObjectBrowser.Lists Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.CPS +Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Legacy +Imports Microsoft.VisualStudio.Shell Imports Microsoft.VisualStudio.Shell.Interop Imports Moq +Imports Microsoft.VisualStudio.LanguageServices.Implementation.TaskList Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework @@ -27,76 +37,87 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr Private Shared ReadOnly s_exportProviderFactory As Lazy(Of IExportProviderFactory) = New Lazy(Of IExportProviderFactory)( Function() - Dim catalog = TestExportProvider.EntireAssemblyCatalogWithCSharpAndVisualBasic.WithPart(GetType(LinkedFileUtilities)) + Dim catalog = TestExportProvider.EntireAssemblyCatalogWithCSharpAndVisualBasic + catalog = catalog.WithParts(GetType(FileChangeWatcherProvider), + GetType(MockVisualStudioWorkspace), + GetType(VisualStudioProjectFactory), + GetType(MockServiceProvider), + GetType(SolutionEventsBatchScopeCreator), + GetType(ProjectCodeModelFactory), + GetType(CPSProjectFactory), + GetType(VisualStudioRuleSetManagerFactory), + GetType(VsMetadataServiceFactory), + GetType(VisualStudioMetadataReferenceManagerFactory)) Return ExportProviderCache.GetOrCreateExportProviderFactory(catalog) End Function) - Private ReadOnly _monitorSelectionMock As MockShellMonitorSelection - Private ReadOnly _workspace As TestWorkspace - Private ReadOnly _serviceProvider As MockServiceProvider - Private ReadOnly _projectTracker As VisualStudioProjectTracker + Private ReadOnly _workspace As VisualStudioWorkspaceImpl Private ReadOnly _projectFilePaths As New List(Of String) Public Sub New(Optional solutionIsFullyLoaded As Boolean = True) - ' As a policy, if anything goes wrong don't use exception filters, just throw exceptions for the - ' test harness to catch normally. Otherwise debugging things can be annoying when your test process - ' goes away - AbstractProject.CrashOnException = False - - _monitorSelectionMock = New MockShellMonitorSelection(solutionIsFullyLoaded) - _workspace = New TestWorkspace(s_exportProviderFactory.Value.CreateExportProvider()) - _serviceProvider = New MockServiceProvider(_monitorSelectionMock, _workspace) - _projectTracker = New VisualStudioProjectTracker( - _workspace.ExportProvider.GetExportedValue(Of IThreadingContext), - _serviceProvider, - _workspace, - _workspace.ExportProvider.GetExportedValue(Of LinkedFileUtilities)) - - Dim metadataReferenceProvider = New VisualStudioMetadataReferenceManager(_serviceProvider, _workspace.Services.GetService(Of ITemporaryStorageService)()) + ExportProvider = s_exportProviderFactory.Value.CreateExportProvider() + _workspace = ExportProvider.GetExportedValue(Of VisualStudioWorkspaceImpl) + ThreadingContext = ExportProvider.GetExportedValue(Of IThreadingContext)() + + Dim mockServiceProvider As MockServiceProvider = ExportProvider.GetExportedValue(Of MockServiceProvider)() + mockServiceProvider.MockMonitorSelection = New MockShellMonitorSelection(solutionIsFullyLoaded) + ServiceProvider = mockServiceProvider + Dim ruleSetFileProvider = New VisualStudioRuleSetManager( - DirectCast(_serviceProvider.GetService(GetType(SVsFileChangeEx)), IVsFileChangeEx), + New FileChangeWatcher(System.Threading.Tasks.Task.FromResult(DirectCast(ServiceProvider.GetService(GetType(SVsFileChangeEx)), IVsFileChangeEx))), New TestForegroundNotificationService(), AsynchronousOperationListenerProvider.NullListener) - Dim documentTrackingService = New VisualStudioDocumentTrackingService(_serviceProvider) - Dim documentProvider = New DocumentProvider( - _projectTracker, - _serviceProvider, - documentTrackingService, - _workspace.ExportProvider.GetExportedValue(Of LinkedFileUtilities)) - - _projectTracker.InitializeProviders(documentProvider, metadataReferenceProvider, ruleSetFileProvider) + Dim documentTrackingService = New VisualStudioDocumentTrackingService(ServiceProvider) End Sub - Public Sub NotifySolutionAsFullyLoaded() - _monitorSelectionMock.SolutionIsFullyLoaded = True - _projectTracker.OnAfterBackgroundSolutionLoadComplete() - End Sub + + + + Private Class MockVisualStudioWorkspace + Inherits VisualStudioWorkspaceImpl - Public ReadOnly Property ProjectTracker As VisualStudioProjectTracker - Get - Return _projectTracker - End Get - End Property + + Public Sub New(exportProvider As Composition.ExportProvider) + MyBase.New(exportProvider.AsExportProvider(), exportProvider.GetExportedValue(Of MockServiceProvider)) + End Sub - Public ReadOnly Property ServiceProvider As MockServiceProvider - Get - Return _serviceProvider - End Get - End Property + Public Overrides Sub DisplayReferencedSymbols(solution As Microsoft.CodeAnalysis.Solution, referencedSymbols As IEnumerable(Of ReferencedSymbol)) + Throw New NotImplementedException() + End Sub - Public ReadOnly Property Workspace As Microsoft.CodeAnalysis.Workspace + Public Overrides Function GetFileCodeModel(documentId As DocumentId) As EnvDTE.FileCodeModel + Throw New NotImplementedException() + End Function + + Public Overrides Function TryGoToDefinition(symbol As ISymbol, project As Microsoft.CodeAnalysis.Project, cancellationToken As CancellationToken) As Boolean + Throw New NotImplementedException() + End Function + + Public Overrides Function TryFindAllReferences(symbol As ISymbol, project As Microsoft.CodeAnalysis.Project, cancellationToken As CancellationToken) As Boolean + Throw New NotImplementedException() + End Function + + Friend Overrides Function OpenInvisibleEditor(documentId As DocumentId) As IInvisibleEditor + Throw New NotImplementedException() + End Function + + Friend Overrides Function GetBrowseObject(symbolListItem As SymbolListItem) As Object + Throw New NotImplementedException() + End Function + End Class + + Public ReadOnly Property ThreadingContext As IThreadingContext + Public ReadOnly Property ServiceProvider As IServiceProvider + Public ReadOnly Property ExportProvider As Composition.ExportProvider + + Public ReadOnly Property Workspace As VisualStudioWorkspace Get Return _workspace End Get End Property Public Sub Dispose() Implements IDisposable.Dispose - For Each project In _projectTracker.ImmutableProjects.ToArray() - project.Disconnect() - Next - - _projectTracker.OnAfterCloseSolution() _workspace.Dispose() For Each filePath In _projectFilePaths @@ -122,15 +143,21 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr Return Workspace.CurrentSolution.Projects.Single().CompilationOptions End Function - Friend Class MockServiceProvider + + + + Private Class MockServiceProvider Implements System.IServiceProvider + Implements SVsServiceProvider ' The shell service provider actually implements this too for people using that type directly + Implements Shell.IAsyncServiceProvider + + Private ReadOnly _exportProvider As Composition.ExportProvider - Private ReadOnly _mockMonitorSelection As IVsMonitorSelection - Private ReadOnly _workspace As TestWorkspace + Public MockMonitorSelection As IVsMonitorSelection - Public Sub New(mockMonitorSelection As IVsMonitorSelection, workspace As TestWorkspace) - _mockMonitorSelection = mockMonitorSelection - _workspace = workspace + + Public Sub New(exportProvider As Composition.ExportProvider) + _exportProvider = exportProvider End Sub Public Function GetService(serviceType As Type) As Object Implements System.IServiceProvider.GetService @@ -144,7 +171,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr Return GetComponentModelMock() Case GetType(SVsShellMonitorSelection) - Return _mockMonitorSelection + Return MockMonitorSelection Case GetType(SVsXMLMemberIndexService) Return New MockXmlMemberIndexService @@ -160,10 +187,12 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr End Select End Function + Public Function GetServiceAsync(serviceType As Type) As Task(Of Object) Implements Shell.IAsyncServiceProvider.GetServiceAsync + Return System.Threading.Tasks.Task.FromResult(GetService(serviceType)) + End Function + Friend Function GetComponentModelMock() As IComponentModel - Dim componentModel As New Mock(Of IComponentModel)(MockBehavior.Loose) - componentModel.SetupGet(Function(cm) cm.DefaultExportProvider).Returns(_workspace.ExportProvider.AsExportProvider()) - Return componentModel.Object + Return New MockComponentModel(_exportProvider) End Function End Class diff --git a/src/VisualStudio/TestUtilities2/ProjectSystemShim/VisualBasicHelpers/MockCompilerHost.vb b/src/VisualStudio/TestUtilities2/ProjectSystemShim/VisualBasicHelpers/MockCompilerHost.vb index 56f958f0e573f879a8cb39d85fea465f431c1505..5c76f301496742125c0cd6a3f8de09a99fb2bdb6 100644 --- a/src/VisualStudio/TestUtilities2/ProjectSystemShim/VisualBasicHelpers/MockCompilerHost.vb +++ b/src/VisualStudio/TestUtilities2/ProjectSystemShim/VisualBasicHelpers/MockCompilerHost.vb @@ -16,7 +16,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Vi Public Shared ReadOnly Property FullFrameworkCompilerHost As MockCompilerHost Get - Return New MockCompilerHost(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.1")) + Return New MockCompilerHost("Z:\FullFramework") End Get End Property diff --git a/src/VisualStudio/TestUtilities2/ProjectSystemShim/VisualBasicHelpers/VisualBasicHelpers.vb b/src/VisualStudio/TestUtilities2/ProjectSystemShim/VisualBasicHelpers/VisualBasicHelpers.vb index 441712af3fd40f94fbf8d9b91a27175133f99680..5ab70b2d5189e07ef8aad44427df81388e602058 100644 --- a/src/VisualStudio/TestUtilities2/ProjectSystemShim/VisualBasicHelpers/VisualBasicHelpers.vb +++ b/src/VisualStudio/TestUtilities2/ProjectSystemShim/VisualBasicHelpers/VisualBasicHelpers.vb @@ -10,26 +10,26 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Vi Friend Module VisualBasicHelpers Public Function CreateVisualBasicProject(environment As TestEnvironment, projectName As String, Optional compilerHost As IVbCompilerHost = Nothing) As VisualBasicProject Dim projectBinPath = Path.GetTempPath() - Return New VisualBasicProject(environment.ProjectTracker, - projectName, + Return New VisualBasicProject(projectName, If(compilerHost, MockCompilerHost.FullFrameworkCompilerHost), environment.CreateHierarchy(projectName, projectBinPath, "VB"), environment.ServiceProvider, + environment.ThreadingContext, commandLineParserServiceOpt:=New VisualBasicCommandLineParserService()) End Function Public Function CreateVisualBasicProjectWithNullBinPath(environment As TestEnvironment, projectName As String) As VisualBasicProject - Return New VisualBasicProject(environment.ProjectTracker, - projectName, + Return New VisualBasicProject(projectName, MockCompilerHost.FullFrameworkCompilerHost, environment.CreateHierarchy(projectName, projectBinPath:=Nothing, projectCapabilities:="VB"), environment.ServiceProvider, + environment.ThreadingContext, commandLineParserServiceOpt:=New VisualBasicCommandLineParserService()) End Function Public Function CreateMinimalCompilerOptions(project As VisualBasicProject) As VBCompilerOptions Dim options As VBCompilerOptions = Nothing - options.wszExeName = project.ProjectSystemName + ".exe" + options.wszExeName = project.AssemblyName + ".exe" options.OutputType = VBCompilerOutputTypes.OUTPUT_ConsoleEXE options.wszOutputPath = "C:\OutputPath" diff --git a/src/VisualStudio/VisualBasic/Impl/CodeModel/VisualBasicCodeModelInstanceFactory.vb b/src/VisualStudio/VisualBasic/Impl/CodeModel/VisualBasicCodeModelInstanceFactory.vb deleted file mode 100644 index d2afc9eb63a857a9ccc3f2c42516f044acdd8393..0000000000000000000000000000000000000000 --- a/src/VisualStudio/VisualBasic/Impl/CodeModel/VisualBasicCodeModelInstanceFactory.vb +++ /dev/null @@ -1,63 +0,0 @@ -' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -Imports EnvDTE -Imports Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel -Imports Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim -Imports Microsoft.VisualStudio.Shell.Interop - -Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic - - Friend NotInheritable Class VisualBasicCodeModelInstanceFactory - Implements ICodeModelInstanceFactory - - Private ReadOnly _project As VisualBasicProject - - Public Sub New(project As VisualBasicProject) - _project = project - End Sub - - Public Function TryCreateFileCodeModelThroughProjectSystem(filePath As String) As EnvDTE.FileCodeModel Implements ICodeModelInstanceFactory.TryCreateFileCodeModelThroughProjectSystem - ' In the Dev11 VB code base, FileCodeModels were created eagerly when a VB SourceFile was created. - ' In Roslyn, we'll take the same lazy approach that the Dev11 C# language service does. - ' - ' Essentially, the C# project system has two methods that the C# language service calls to ask the - ' project system to create a FileCodeModel for a specific file name: - ' - ' * HRESULT CCSharpBuildMgrSite::CanCreateFileCodeModel(PCWSTR pszFileName, BOOL *pRetVal); - ' * HRESULT CCSharpBuildMgrSite::CreateFileCodeModel(PCWSTR pszFileName, REFIID riid, void **ppObj)' - ' - ' If the project system can create a FileCodeModel it calls back into ICSharpProjectSite::CreateFileCodeModel - ' with the correct "parent" object. - ' - ' Because the VB project system lacks these hooks, we simulate the same operations that those hooks perform. - Dim document = _project.GetCurrentDocumentFromPath(filePath) - If document Is Nothing Then - Throw New ArgumentException(NameOf(filePath)) - End If - - Dim itemId = document.GetItemId() - If itemId = VSConstants.VSITEMID.Nil Then - Throw New ArgumentException(NameOf(filePath)) - Return Nothing - End If - - Dim pvar As Object = Nothing - If ErrorHandler.Failed(_project.Hierarchy.GetProperty(itemId, __VSHPROPID.VSHPROPID_ExtObject, pvar)) Then - Throw New ArgumentException(NameOf(filePath)) - End If - - Dim projectItem = TryCast(pvar, EnvDTE.ProjectItem) - If projectItem Is Nothing Then - Throw New ArgumentException(NameOf(filePath)) - End If - - Dim fileCodeModel As EnvDTE.FileCodeModel = Nothing - If ErrorHandler.Failed(_project.CreateFileCodeModel(projectItem.ContainingProject, projectItem, fileCodeModel)) Then - Throw New ArgumentException(NameOf(filePath)) - End If - - Return fileCodeModel - End Function - End Class - -End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicLanguageService.vb b/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicLanguageService.vb index 0c11c254bf2acc2484d363e039d15c704f00bb43..83ecf0644aaca5e798babd1b3d8b02d745914b60 100644 --- a/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicLanguageService.vb +++ b/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicLanguageService.vb @@ -72,7 +72,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic Protected Overrides Function CreateContainedLanguage( bufferCoordinator As IVsTextBufferCoordinator, - project As AbstractProject, + project As VisualStudioProject, hierarchy As IVsHierarchy, itemid As UInteger ) As IVsContainedLanguage diff --git a/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.IVbCompiler.vb b/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.IVbCompiler.vb index 9f37809c41d884cdf8e77fdecfa5b66b456cc185..445bbe8b0f227074fd56766af44bcfb340794a16 100644 --- a/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.IVbCompiler.vb +++ b/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.IVbCompiler.vb @@ -1,12 +1,10 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities Imports Microsoft.CodeAnalysis.Host Imports Microsoft.CodeAnalysis.VisualBasic -Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Legacy Imports Microsoft.VisualStudio.LanguageServices.Implementation.TaskList -Imports Microsoft.VisualStudio.LanguageServices.ProjectSystem Imports Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim Imports Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim.Interop Imports Microsoft.VisualStudio.Shell.Interop @@ -23,16 +21,12 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic Public Function CreateProject(wszName As String, punkProject As Object, pProjHier As IVsHierarchy, pVbCompilerHost As IVbCompilerHost) As IVbCompilerProject Implements IVbCompiler.CreateProject Dim hostDiagnosticUpdateSource = ComponentModel.GetService(Of HostDiagnosticUpdateSource)() - Dim projectTracker = Workspace.GetProjectTrackerAndInitializeIfNecessary(Me) - Return New VisualBasicProject( - projectTracker, wszName, pVbCompilerHost, pProjHier, Me, - Function(id) New ProjectExternalErrorReporter(id, "BC", serviceProvider:=Me), - Workspace, + ComponentModel.GetService(Of IThreadingContext), hostDiagnosticUpdateSource, commandLineParserServiceOpt:=Workspace.Services.GetLanguageServices(LanguageNames.VisualBasic).GetService(Of ICommandLineParserService)) End Function diff --git a/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.IVbEntryPointProvider.vb b/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.IVbEntryPointProvider.vb index b52141f37810a9c5869d82cbcbce3d1094b733df..26593f53dd54b460d38d148e3cf50cae791d664a 100644 --- a/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.IVbEntryPointProvider.vb +++ b/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.IVbEntryPointProvider.vb @@ -1,7 +1,7 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. Imports System.Runtime.InteropServices -Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +Imports System.Threading Imports Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim Imports Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim.Interop Imports Microsoft.VisualStudio.Shell.Interop @@ -15,18 +15,15 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic bstrList() As String, ByVal pcActualItems As IntPtr) As Integer Implements IVBEntryPointProvider.GetFormEntryPointsList - Dim visualStudioWorkspace = ComponentModel.GetService(Of VisualStudioWorkspaceImpl)() + Dim workspace = ComponentModel.GetService(Of VisualStudioWorkspace)() Dim hierarchy = CType(pHierarchy, IVsHierarchy) - Dim projects = visualStudioWorkspace.CurrentSolution.ProjectIds - For Each project In projects - Dim hostProject = visualStudioWorkspace.GetHostProject(project) - If hostProject IsNot Nothing AndAlso hostProject.Hierarchy Is hierarchy Then - Dim vbProject = TryCast(hostProject, VisualBasicProject) + For Each projectId In workspace.CurrentSolution.ProjectIds + Dim projectHierarchy = workspace.GetHierarchy(projectId) + If hierarchy Is projectHierarchy Then + Dim compilation = workspace.CurrentSolution.GetProject(projectId).GetCompilationAsync(CancellationToken.None).WaitAndGetResult(CancellationToken.None) - If vbProject IsNot Nothing Then - vbProject.GetEntryPointsWorker(cItems, bstrList, pcActualItems, findFormsOnly:=True) - End If + VisualBasicProject.GetEntryPointsWorker(compilation, cItems, bstrList, pcActualItems, findFormsOnly:=True) End If Next diff --git a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/TempPECompiler.TempPEProject.vb b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/TempPECompiler.TempPEProject.vb index 2566991f7cef626e57576f3516cf5948812c52e4..cfcddf0f2e803243de983c6c2ec8fcf5753972ed 100644 --- a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/TempPECompiler.TempPEProject.vb +++ b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/TempPECompiler.TempPEProject.vb @@ -23,7 +23,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim Private _parseOptions As VisualBasicParseOptions Private _compilationOptions As VisualBasicCompilationOptions Private _outputPath As String - Private _runtimeLibraries As List(Of String) + Private _runtimeLibraries As ImmutableArray(Of String) Public Sub New(tempPECompiler As TempPECompiler, compilerHost As IVbCompilerHost) _tempPECompiler = tempPECompiler @@ -197,16 +197,13 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim End Sub Public Sub SetCompilerOptions(ByRef pCompilerOptions As VBCompilerOptions) Implements IVbCompilerProject.SetCompilerOptions - _runtimeLibraries = VisualBasicProjectOptionsHelper.GetRuntimeLibraries(_compilerHost, pCompilerOptions) - _outputPath = VisualBasicProjectOptionsHelper.GetOutputPath(pCompilerOptions) - _parseOptions = VisualBasicProjectOptionsHelper.CreateParseOptions(Nothing, pCompilerOptions) - _compilationOptions = VisualBasicProjectOptionsHelper.CreateCompilationOptions(baseCompilationOptionsOpt:=Nothing, - newParseOptions:=_parseOptions, - compilerOptions:=pCompilerOptions, - compilerHost:=_compilerHost, - globalImports:=Array.Empty(Of GlobalImport)(), - projectDirectoryOpt:=Nothing, - ruleSetOpt:=Nothing) + _runtimeLibraries = VisualBasicProject.OptionsProcessor.GetRuntimeLibraries(_compilerHost, pCompilerOptions) + _outputPath = PathUtilities.CombinePathsUnchecked(pCompilerOptions.wszOutputPath, pCompilerOptions.wszExeName) + _parseOptions = VisualBasicProject.OptionsProcessor.ApplyVisualBasicParseOptionsFromCompilerOptions(VisualBasicParseOptions.Default, pCompilerOptions) + + ' Note that we pass a "default" compilation options with DLL set as output kind; the Apply method will figure out what the right one is and fix it up + _compilationOptions = VisualBasicProject.OptionsProcessor.ApplyCompilationOptionsFromVBCompilerOptions( + New VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary, parseOptions:=_parseOptions), pCompilerOptions) End Sub Public Sub SetModuleAssemblyName(wszName As String) Implements IVbCompilerProject.SetModuleAssemblyName diff --git a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicCodeModelInstanceFactory.vb b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicCodeModelInstanceFactory.vb new file mode 100644 index 0000000000000000000000000000000000000000..e43df3886add9c392698b611873089a32bfd8d88 --- /dev/null +++ b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicCodeModelInstanceFactory.vb @@ -0,0 +1,62 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel +Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +Imports Microsoft.VisualStudio.Shell.Interop + +Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim + Partial Friend NotInheritable Class VisualBasicProject + Private NotInheritable Class VisualBasicCodeModelInstanceFactory + Implements ICodeModelInstanceFactory + + Private ReadOnly _project As VisualBasicProject + + Public Sub New(project As VisualBasicProject) + _project = project + End Sub + + Public Function TryCreateFileCodeModelThroughProjectSystem(filePath As String) As EnvDTE.FileCodeModel Implements ICodeModelInstanceFactory.TryCreateFileCodeModelThroughProjectSystem + ' In the Dev11 VB code base, FileCodeModels were created eagerly when a VB SourceFile was created. + ' In Roslyn, we'll take the same lazy approach that the Dev11 C# language service does. + ' + ' Essentially, the C# project system has two methods that the C# language service calls to ask the + ' project system to create a FileCodeModel for a specific file name: + ' + ' * HRESULT CCSharpBuildMgrSite::CanCreateFileCodeModel(PCWSTR pszFileName, BOOL *pRetVal); + ' * HRESULT CCSharpBuildMgrSite::CreateFileCodeModel(PCWSTR pszFileName, REFIID riid, void **ppObj)' + ' + ' If the project system can create a FileCodeModel it calls back into ICSharpProjectSite::CreateFileCodeModel + ' with the correct "parent" object. + ' + ' Because the VB project system lacks these hooks, we simulate the same operations that those hooks perform. + Dim document = _project.Workspace.CurrentSolution.GetDocumentIdsWithFilePath(filePath).FirstOrDefault(Function(d) d.ProjectId Is _project.VisualStudioProject.Id) + + If document Is Nothing Then + Throw New ArgumentException(NameOf(filePath)) + End If + + Dim itemId = _project.Hierarchy.TryGetItemId(filePath) + If itemId = VSConstants.VSITEMID.Nil Then + Throw New ArgumentException(NameOf(filePath)) + End If + + Dim pvar As Object = Nothing + If ErrorHandler.Failed(_project.Hierarchy.GetProperty(itemId, __VSHPROPID.VSHPROPID_ExtObject, pvar)) Then + Throw New ArgumentException(NameOf(filePath)) + End If + + Dim projectItem = TryCast(pvar, EnvDTE.ProjectItem) + If projectItem Is Nothing Then + Throw New ArgumentException(NameOf(filePath)) + End If + + Dim fileCodeModel As EnvDTE.FileCodeModel = Nothing + If ErrorHandler.Failed(_project.CreateFileCodeModel(projectItem.ContainingProject, projectItem, fileCodeModel)) Then + Throw New ArgumentException(NameOf(filePath)) + End If + + Return fileCodeModel + End Function + End Class + End Class +End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.OptionsProcessor.vb b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.OptionsProcessor.vb new file mode 100644 index 0000000000000000000000000000000000000000..4773e90884bbbce344e40f46ee9c28ba7c286dc0 --- /dev/null +++ b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.OptionsProcessor.vb @@ -0,0 +1,321 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Collections.Immutable +Imports System.Collections.ObjectModel +Imports System.IO +Imports System.Runtime.InteropServices +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Host +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +Imports Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim.Interop + +Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim + ''' + ''' Helper to convert a legacy VBCompilerOptions into the new Roslyn CompilerOptions and ParseOptions. + ''' + ''' + Partial Friend NotInheritable Class VisualBasicProject + Friend NotInheritable Class OptionsProcessor + Inherits VisualStudioProjectOptionsProcessor + + Private _rawOptions As VBCompilerOptions + Private ReadOnly _imports As New List(Of GlobalImport) + + ''' + ''' Maps a string to the parsed conditional compilation symbols. + ''' It is expected that most projects in a solution will have similar (if not identical) + ''' sets of conditional compilation symbols. We expect the total set of these to be small, which is why we never evict anything from this cache. + ''' + Private Shared s_conditionalCompilationSymbolsCache As Dictionary(Of KeyValuePair(Of String, OutputKind), ImmutableArray(Of KeyValuePair(Of String, Object))) = + New Dictionary(Of KeyValuePair(Of String, OutputKind), ImmutableArray(Of KeyValuePair(Of String, Object))) + + ''' + ''' Maps a string to the related . Since many projects in a solution + ''' will have similar (if not identical) sets of imports, there are performance benefits to + ''' caching these rather than parsing them anew for each project. It is expected that the total + ''' number of imports will be rather small, which is why we never evict anything from this cache. + ''' + Private Shared s_importsCache As Dictionary(Of String, GlobalImport) = New Dictionary(Of String, GlobalImport) + + Public Sub New(project As VisualStudioProject, workspaceServices As HostWorkspaceServices) + MyBase.New(project, workspaceServices) + End Sub + + Public Sub SetNewRawOptions(ByRef rawOptions As VBCompilerOptions) + _rawOptions = rawOptions + UpdateProjectForNewHostValues() + End Sub + + Protected Overrides Function ComputeCompilationOptionsWithHostValues(compilationOptions As CompilationOptions, ruleSetFileOpt As IRuleSetFile) As CompilationOptions + Return ApplyCompilationOptionsFromVBCompilerOptions(compilationOptions, _rawOptions, ruleSetFileOpt) _ + .WithGlobalImports(_imports) + End Function + + Public Shared Function ApplyCompilationOptionsFromVBCompilerOptions(compilationOptions As CompilationOptions, compilerOptions As VBCompilerOptions, Optional ruleSetFileOpt As IRuleSetFile = Nothing) As VisualBasicCompilationOptions + Dim platform As Platform + If Not System.Enum.TryParse(compilerOptions.wszPlatformType, ignoreCase:=True, result:=platform) Then + platform = Platform.AnyCpu + End If + + Dim ruleSetFileGeneralDiagnosticOption As ReportDiagnostic? = Nothing + Dim ruleSetFileSpecificDiagnosticOptions As IDictionary(Of String, ReportDiagnostic) = Nothing + + If ruleSetFileOpt IsNot Nothing Then + ruleSetFileGeneralDiagnosticOption = ruleSetFileOpt.GetGeneralDiagnosticOption() + ruleSetFileSpecificDiagnosticOptions = ruleSetFileOpt.GetSpecificDiagnosticOptions() + End If + + Dim generalDiagnosticOption As ReportDiagnostic = DetermineGeneralDiagnosticOption(compilerOptions.WarningLevel, ruleSetFileGeneralDiagnosticOption) + Dim specificDiagnosticOptions As IReadOnlyDictionary(Of String, ReportDiagnostic) = DetermineSpecificDiagnosticOptions(compilerOptions, ruleSetFileSpecificDiagnosticOptions) + + Dim visualBasicCompilationOptions = DirectCast(compilationOptions, VisualBasicCompilationOptions) + Dim visualBasicParseOptions = ApplyVisualBasicParseOptionsFromCompilerOptions(visualBasicCompilationOptions.ParseOptions, compilerOptions) + + Return visualBasicCompilationOptions _ + .WithOverflowChecks(Not compilerOptions.bRemoveIntChecks) _ + .WithCryptoKeyContainer(compilerOptions.wszStrongNameContainer) _ + .WithCryptoKeyFile(compilerOptions.wszStrongNameKeyFile) _ + .WithDelaySign(If(compilerOptions.bDelaySign, CType(True, Boolean?), Nothing)) _ + .WithEmbedVbCoreRuntime(compilerOptions.vbRuntimeKind = VBRuntimeKind.EmbeddedRuntime) _ + .WithGeneralDiagnosticOption(generalDiagnosticOption) _ + .WithMainTypeName(If(compilerOptions.wszStartup <> String.Empty, compilerOptions.wszStartup, Nothing)) _ + .WithOptionExplicit(Not compilerOptions.bOptionExplicitOff) _ + .WithOptionInfer(Not compilerOptions.bOptionInferOff) _ + .WithOptionStrict(If(compilerOptions.bOptionStrictOff, OptionStrict.Custom, OptionStrict.On)) _ + .WithOptionCompareText(compilerOptions.bOptionCompareText) _ + .WithOptimizationLevel(If(compilerOptions.bOptimize, OptimizationLevel.Release, OptimizationLevel.Debug)) _ + .WithOutputKind(GetOutputKind(compilerOptions)) _ + .WithPlatform(platform) _ + .WithRootNamespace(If(compilerOptions.wszDefaultNamespace, String.Empty)) _ + .WithParseOptions(DirectCast(visualBasicParseOptions, VisualBasicParseOptions)) _ + .WithSpecificDiagnosticOptions(specificDiagnosticOptions) + End Function + + Public Sub AddImport(wszImport As String) + ' Add the import to the list. The legacy language services didn't do any sort of + ' checking to see if the import is already added. Instead, they'd just have two entries + ' in the list. This is OK because the UI in Project Property Pages disallows users from + ' adding multiple entries. Hence the potential first-chance exception here is not a + ' problem, it should in theory never happen. + + Try + Dim import As GlobalImport = Nothing + If Not s_importsCache.TryGetValue(wszImport, import) Then + import = GlobalImport.Parse(wszImport) + s_importsCache(wszImport) = import + End If + + _imports.Add(import) + Catch ex As ArgumentException + 'TODO: report error + End Try + + UpdateProjectForNewHostValues() + End Sub + + Private Shared Function GetOutputKind(ByRef compilerOptions As VBCompilerOptions) As OutputKind + Select Case compilerOptions.OutputType + Case VBCompilerOutputTypes.OUTPUT_ConsoleEXE + Return OutputKind.ConsoleApplication + Case VBCompilerOutputTypes.OUTPUT_Library, VBCompilerOutputTypes.OUTPUT_None + Return OutputKind.DynamicallyLinkedLibrary + Case VBCompilerOutputTypes.OUTPUT_Module + Return OutputKind.NetModule + Case VBCompilerOutputTypes.OUTPUT_WindowsEXE + Return OutputKind.WindowsApplication + Case VBCompilerOutputTypes.OUTPUT_AppContainerEXE + Return OutputKind.WindowsRuntimeApplication + Case VBCompilerOutputTypes.OUTPUT_WinMDObj + Return OutputKind.WindowsRuntimeMetadata + Case Else + Return Nothing + End Select + End Function + + Public Function GetRuntimeLibraries(compilerHost As IVbCompilerHost) As ImmutableArray(Of String) + Return GetRuntimeLibraries(compilerHost, _rawOptions) + End Function + + Public Shared Function GetRuntimeLibraries(compilerHost As IVbCompilerHost, ByRef compilerOptions As VBCompilerOptions) As ImmutableArray(Of String) + ' GetSDKPath can return E_NOTIMPL if there is no SDK path at all + Dim sdkPath As String = Nothing + Dim sdkPathHResult = compilerHost.GetSdkPath(sdkPath) + + If sdkPathHResult = VSConstants.E_NOTIMPL Then + sdkPath = Nothing + Else + Marshal.ThrowExceptionForHR(sdkPathHResult, New IntPtr(-1)) + End If + + Dim runtimes = ImmutableArray.CreateBuilder(Of String) + Select Case compilerOptions.vbRuntimeKind + Case VBRuntimeKind.DefaultRuntime + If sdkPath IsNot Nothing Then + runtimes.Add(PathUtilities.CombinePathsUnchecked(sdkPath, "Microsoft.VisualBasic.dll")) + End If + + Case VBRuntimeKind.SpecifiedRuntime + If compilerOptions.wszSpecifiedVBRuntime IsNot Nothing Then + ' If they specified a fully qualified file, use it + If File.Exists(compilerOptions.wszSpecifiedVBRuntime) Then + runtimes.Add(compilerOptions.wszSpecifiedVBRuntime) + ElseIf sdkPath IsNot Nothing Then + ' If it's just a filename, try to find it in the SDK path. + If compilerOptions.wszSpecifiedVBRuntime = PathUtilities.GetFileName(compilerOptions.wszSpecifiedVBRuntime) Then + Dim runtimePath = PathUtilities.CombinePathsUnchecked(sdkPath, compilerOptions.wszSpecifiedVBRuntime) + If File.Exists(runtimePath) Then + runtimes.Add(runtimePath) + End If + End If + End If + End If + End Select + + If sdkPath IsNot Nothing Then + If Not compilerOptions.bNoStandardLibs Then + runtimes.Add(PathUtilities.CombinePathsUnchecked(sdkPath, "System.dll")) + End If + + runtimes.Add(PathUtilities.CombinePathsUnchecked(sdkPath, "mscorlib.dll")) + End If + + Return runtimes.ToImmutable() + End Function + + Friend Sub DeleteImport(wszImport As String) + Dim index = _imports.FindIndex(Function(import) import.Clause.ToFullString() = wszImport) + If index >= 0 Then + _imports.RemoveAt(index) + UpdateProjectForNewHostValues() + End If + End Sub + + Friend Sub DeleteAllImports() + _imports.Clear() + UpdateProjectForNewHostValues() + End Sub + + Protected Overrides Function ComputeParseOptionsWithHostValues(parseOptions As ParseOptions) As ParseOptions + Dim visualBasicParseOptions = DirectCast(parseOptions, VisualBasicParseOptions) + Return ApplyVisualBasicParseOptionsFromCompilerOptions(visualBasicParseOptions, _rawOptions) + End Function + + Friend Shared Function ApplyVisualBasicParseOptionsFromCompilerOptions(parseOptions As VisualBasicParseOptions, ByRef compilerOptions As VBCompilerOptions) As VisualBasicParseOptions + parseOptions = parseOptions.WithPreprocessorSymbols( + GetConditionalCompilationSymbols(GetOutputKind(compilerOptions), If(compilerOptions.wszCondComp, ""))) + + ' For language versions after VB 15, we expect the version to be passed from MSBuild to the IDE + ' via command-line arguments (`ICompilerOptionsHostObject.SetCompilerOptions`) + ' instead of using `IVbcHostObject3.SetLanguageVersion`. Thus, if we already got a value, then we're good + If parseOptions.LanguageVersion <= LanguageVersion.VisualBasic15 Then + parseOptions = parseOptions.WithLanguageVersion(compilerOptions.langVersion) + End If + + Return parseOptions _ + .WithDocumentationMode(If(Not String.IsNullOrEmpty(compilerOptions.wszXMLDocName), DocumentationMode.Diagnose, DocumentationMode.Parse)) + End Function + + Private Shared Function GetConditionalCompilationSymbols(kind As OutputKind, str As String) As ImmutableArray(Of KeyValuePair(Of String, Object)) + Debug.Assert(str IsNot Nothing) + Dim key = KeyValuePairUtil.Create(str, kind) + + Dim result As ImmutableArray(Of KeyValuePair(Of String, Object)) = Nothing + If s_conditionalCompilationSymbolsCache.TryGetValue(key, result) Then + Return result + End If + + Dim errors As IEnumerable(Of Diagnostic) = Nothing + Dim defines = VisualBasicCommandLineParser.ParseConditionalCompilationSymbols(str, errors) + ' ignore errors + + Return AddPredefinedPreprocessorSymbols(kind, defines.AsImmutableOrEmpty()) + End Function + + Private Shared Iterator Function ParseWarningCodes(warnings As String) As IEnumerable(Of String) + If warnings IsNot Nothing Then + For Each warning In warnings.Split(New String() {" ", ",", ";"}, StringSplitOptions.RemoveEmptyEntries) + Dim warningId As Integer + If Integer.TryParse(warning, warningId) Then + Yield "BC" + warning + Else + Yield warning + End If + Next + End If + End Function + + Private Shared Function DetermineGeneralDiagnosticOption(level As WarningLevel, ruleSetGeneralDiagnosticOption As ReportDiagnostic?) As ReportDiagnostic + 'If no option was supplied in the project file, but there is one in the ruleset file, use that one. + If level = WarningLevel.WARN_Regular AndAlso + ruleSetGeneralDiagnosticOption.HasValue Then + + Return ruleSetGeneralDiagnosticOption.Value + End If + + Return ConvertWarningLevel(level) + End Function + + Private Shared Function DetermineSpecificDiagnosticOptions(options As VBCompilerOptions, ruleSetSpecificDiagnosticOptions As IDictionary(Of String, ReportDiagnostic)) As IReadOnlyDictionary(Of String, ReportDiagnostic) + If ruleSetSpecificDiagnosticOptions Is Nothing Then + ruleSetSpecificDiagnosticOptions = New Dictionary(Of String, ReportDiagnostic) + End If + + ' Start with the rule set options + Dim diagnosticOptions = New Dictionary(Of String, ReportDiagnostic)(ruleSetSpecificDiagnosticOptions) + + ' Update the specific options based on the general settings + If options.WarningLevel = WarningLevel.WARN_AsError Then + For Each pair In ruleSetSpecificDiagnosticOptions + If pair.Value = ReportDiagnostic.Warn Then + diagnosticOptions(pair.Key) = ReportDiagnostic.Error + End If + Next + ElseIf options.WarningLevel = WarningLevel.WARN_None Then + + For Each pair In ruleSetSpecificDiagnosticOptions + If pair.Value <> ReportDiagnostic.Error Then + diagnosticOptions(pair.Key) = ReportDiagnostic.Suppress + End If + Next + End If + + ' Update the specific options based on the specific settings + For Each diagnosticID In ParseWarningCodes(options.wszWarningsAsErrors) + diagnosticOptions(diagnosticID) = ReportDiagnostic.Error + Next + + For Each diagnosticID In ParseWarningCodes(options.wszWarningsNotAsErrors) + Dim ruleSetOption As ReportDiagnostic + If ruleSetSpecificDiagnosticOptions.TryGetValue(diagnosticID, ruleSetOption) Then + diagnosticOptions(diagnosticID) = ruleSetOption + Else + diagnosticOptions(diagnosticID) = ReportDiagnostic.Default + End If + Next + + For Each diagnosticID In ParseWarningCodes(options.wszDisabledWarnings) + diagnosticOptions(diagnosticID) = ReportDiagnostic.Suppress + Next + + Return New ReadOnlyDictionary(Of String, ReportDiagnostic)(diagnosticOptions) + End Function + + Private Shared Function ConvertWarningLevel(level As WarningLevel) As ReportDiagnostic + Select Case level + Case WarningLevel.WARN_None + Return ReportDiagnostic.Suppress + + Case WarningLevel.WARN_Regular + Return ReportDiagnostic.Default + + Case WarningLevel.WARN_AsError + Return ReportDiagnostic.Error + + Case Else + Throw ExceptionUtilities.UnexpectedValue(level) + End Select + End Function + End Class + End Class +End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.vb b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.vb index e323ce5e39c121913967f4a9882e6548a1a37148..eebf93e485bb1a0f6c0cccaced4b4bf2e2c6d3c8 100644 --- a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.vb +++ b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.vb @@ -1,20 +1,22 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +Imports System.Collections.Immutable +Imports System.IO Imports System.Runtime.InteropServices Imports System.Runtime.InteropServices.ComTypes Imports System.Threading Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities Imports Microsoft.CodeAnalysis.ErrorReporting Imports Microsoft.CodeAnalysis.Host Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.VisualStudio.ComponentModelHost Imports Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Legacy Imports Microsoft.VisualStudio.LanguageServices.Implementation.TaskList Imports Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim.Interop Imports Microsoft.VisualStudio.Shell.Interop -Imports Microsoft.VisualStudio.Text -Imports Microsoft.VisualStudio.TextManager.Interop Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim Partial Friend NotInheritable Class VisualBasicProject @@ -22,39 +24,43 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim Implements IVbCompilerProject Private ReadOnly _compilerHost As IVbCompilerHost - Private ReadOnly _imports As New List(Of GlobalImport) - Private _rawOptions As VBCompilerOptions - Private ReadOnly _explicitlyAddedDefaultReferences As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase) - Private _lastOutputPath As String - Private _runtimeLibraries As IList(Of String) = SpecializedCollections.EmptyList(Of String) + + Private _runtimeLibraries As ImmutableArray(Of String) = ImmutableArray(Of String).Empty ''' - ''' Maps a string to the related . Since many projects in a solution - ''' will have similar (if not identical) sets of imports, there are performance benefits to - ''' caching these rather than parsing them anew for each project. It is expected that the total - ''' number of imports will be rather small, which is why we never evict anything from this cache. + ''' To support the old contract of VB runtimes, we must ourselves add additional references beyond what the + ''' project system tells us. If the project system _also_ tells us about those, we put the in here so we can + ''' record that and make removal later work properly. ''' - Private Shared s_importsCache As Dictionary(Of String, GlobalImport) = New Dictionary(Of String, GlobalImport) + Private ReadOnly _explicitlyAddedRuntimeLibraries As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase) - Friend Sub New(projectTracker As VisualStudioProjectTracker, - ProjectSystemName As String, + Friend Sub New(projectSystemName As String, compilerHost As IVbCompilerHost, hierarchy As IVsHierarchy, serviceProvider As IServiceProvider, - Optional reportExternalErrorCreatorOpt As Func(Of ProjectId, IVsReportExternalErrors) = Nothing, - Optional visualStudioWorkspaceOpt As VisualStudioWorkspaceImpl = Nothing, + threadingContext As IThreadingContext, Optional hostDiagnosticUpdateSourceOpt As HostDiagnosticUpdateSource = Nothing, Optional commandLineParserServiceOpt As ICommandLineParserService = Nothing) - MyBase.New(projectTracker, reportExternalErrorCreatorOpt, ProjectSystemName, hierarchy, LanguageNames.VisualBasic, - serviceProvider, visualStudioWorkspaceOpt, hostDiagnosticUpdateSourceOpt, commandLineParserServiceOpt) + MyBase.New(projectSystemName, hierarchy, LanguageNames.VisualBasic, + serviceProvider, threadingContext, "VB", hostDiagnosticUpdateSourceOpt, commandLineParserServiceOpt) _compilerHost = compilerHost - projectTracker.AddProject(Me) + Dim componentModel = DirectCast(serviceProvider.GetService(GetType(SComponentModel)), IComponentModel) - ProjectCodeModel = New ProjectCodeModel(projectTracker.ThreadingContext, Me.Id, New VisualBasicCodeModelInstanceFactory(Me), visualStudioWorkspaceOpt, serviceProvider) + ProjectCodeModel = componentModel.GetService(Of IProjectCodeModelFactory).CreateProjectCodeModel(VisualStudioProject.Id, New VisualBasicCodeModelInstanceFactory(Me)) + VisualStudioProjectOptionsProcessor = New OptionsProcessor(VisualStudioProject, Workspace.Services) End Sub + Private Shadows Property VisualStudioProjectOptionsProcessor As OptionsProcessor + Get + Return DirectCast(MyBase.VisualStudioProjectOptionsProcessor, OptionsProcessor) + End Get + Set(value As OptionsProcessor) + MyBase.VisualStudioProjectOptionsProcessor = value + End Set + End Property + Public Sub AddApplicationObjectVariable(wszClassName As String, wszMemberName As String) Implements IVbCompilerProject.AddApplicationObjectVariable Throw New NotImplementedException() End Sub @@ -64,94 +70,51 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim End Sub Public Function AddEmbeddedMetaDataReference(wszFileName As String) As Integer Implements IVbCompilerProject.AddEmbeddedMetaDataReference - Try - Return AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(wszFileName, New MetadataReferenceProperties(embedInteropTypes:=True)) - Catch e As Exception When FilterException(e) - Throw ExceptionUtilities.Unreachable - End Try + VisualStudioProject.AddMetadataReference(wszFileName, New MetadataReferenceProperties(embedInteropTypes:=True)) + Return VSConstants.S_OK End Function Public Overloads Function AddMetaDataReference(wszFileName As String, bAssembly As Boolean) As Integer Implements IVbCompilerProject.AddMetaDataReference - Try - ' If this is a reference already added due to it being a standard reference, just record the add - If _runtimeLibraries.Contains(wszFileName, StringComparer.OrdinalIgnoreCase) Then - _explicitlyAddedDefaultReferences.Add(wszFileName) - Return VSConstants.S_OK - End If - - Return AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(wszFileName, New MetadataReferenceProperties()) - Catch e As Exception When FilterException(e) - Throw ExceptionUtilities.Unreachable - End Try + ' If this is a reference already added due to it being a standard reference, just record the add + If _runtimeLibraries.Contains(wszFileName, StringComparer.OrdinalIgnoreCase) Then + _explicitlyAddedRuntimeLibraries.Add(wszFileName) + Return VSConstants.S_OK + Else + VisualStudioProject.AddMetadataReference(wszFileName, MetadataReferenceProperties.Assembly) + Return VSConstants.S_OK + End If End Function Public Sub AddEmbeddedProjectReference(pReferencedCompilerProject As IVbCompilerProject) Implements IVbCompilerProject.AddEmbeddedProjectReference - Try - Dim project = TryCast(pReferencedCompilerProject, VisualBasicProject) + Dim referencedProject = TryCast(pReferencedCompilerProject, VisualBasicProject) - If project Is Nothing Then - ' Hmm, we got a project which isn't from ourselves. That's somewhat odd, and we really can't do anything - ' with it. - Throw New ArgumentException("Unknown type of IVbCompilerProject.", NameOf(pReferencedCompilerProject)) - End If + If referencedProject Is Nothing Then + ' Hmm, we got a project which isn't from ourselves. That's somewhat odd, and we really can't do anything + ' with it. + Throw New ArgumentException("Unknown type of IVbCompilerProject.", NameOf(pReferencedCompilerProject)) + End If - MyBase.AddProjectReference(New ProjectReference(project.Id, embedInteropTypes:=True)) - Catch e As Exception When FilterException(e) - Throw ExceptionUtilities.Unreachable - End Try + VisualStudioProject.AddProjectReference(New ProjectReference(referencedProject.VisualStudioProject.Id, embedInteropTypes:=True)) End Sub Public Shadows Sub AddFile(wszFileName As String, itemid As UInteger, fAddDuringOpen As Boolean) Implements IVbCompilerProject.AddFile - Try - ' We trust the project system to only tell us about files that we can use. - Dim canUseTextBuffer As Func(Of ITextBuffer, Boolean) = Function(t) True - - MyBase.AddFile(wszFileName, SourceCodeKind.Regular) - Catch e As Exception When FilterException(e) - Throw ExceptionUtilities.Unreachable - End Try + MyBase.AddFile(wszFileName, SourceCodeKind.Regular) End Sub Public Sub AddImport(wszImport As String) Implements IVbCompilerProject.AddImport - Try - ' Add the import to the list. The legacy language services didn't do any sort of - ' checking to see if the import is already added. Instead, they'd just have two entries - ' in the list. This is OK because the UI in Project Property Pages disallows users from - ' adding multiple entries. Hence the potential first-chance exception here is not a - ' problem, it should in theory never happen. - - Try - Dim import As GlobalImport = Nothing - If Not s_importsCache.TryGetValue(wszImport, import) Then - import = GlobalImport.Parse(wszImport) - s_importsCache(wszImport) = import - End If - - _imports.Add(import) - Catch ex As ArgumentException - 'TODO: report error - End Try - - PushUpdatedGlobalImportsToWorkspace() - Catch e As Exception When FilterException(e) - Throw ExceptionUtilities.Unreachable - End Try + VisualStudioProjectOptionsProcessor.AddImport(wszImport) End Sub Public Shadows Sub AddProjectReference(pReferencedCompilerProject As IVbCompilerProject) Implements IVbCompilerProject.AddProjectReference - Try - Dim project = TryCast(pReferencedCompilerProject, VisualBasicProject) + Dim referencedProject = TryCast(pReferencedCompilerProject, VisualBasicProject) - If project Is Nothing Then - ' Hmm, we got a project which isn't from ourselves. That's somewhat odd, and we really can't do anything - ' with it. - Throw New ArgumentException("Unknown type of IVbCompilerProject.", NameOf(pReferencedCompilerProject)) - End If + If referencedProject Is Nothing Then + ' Hmm, we got a project which isn't from ourselves. That's somewhat odd, and we really can't do anything + ' with it. + Throw New ArgumentException("Unknown type of IVbCompilerProject.", NameOf(pReferencedCompilerProject)) + End If - MyBase.AddProjectReference(New ProjectReference(project.Id)) - Catch e As Exception When FilterException(e) - Throw ExceptionUtilities.Unreachable - End Try + VisualStudioProject.AddProjectReference(New ProjectReference(referencedProject.VisualStudioProject.Id)) End Sub Public Sub AddResourceReference(wszFileName As String, wszName As String, fPublic As Boolean, fEmbed As Boolean) Implements IVbCompilerProject.AddResourceReference @@ -180,9 +143,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim pIVbBuildStatusCallback.ProjectBound() End If - Return 0 + Return VSConstants.S_OK Catch e As Exception When FatalError.Report(e) - Return 0 + Return VSConstants.S_OK End Try End Function @@ -215,12 +178,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim End Function Public Sub DeleteAllImports() Implements IVbCompilerProject.DeleteAllImports - Try - _imports.Clear() - PushUpdatedGlobalImportsToWorkspace() - Catch e As Exception When FilterException(e) - Throw ExceptionUtilities.Unreachable - End Try + VisualStudioProjectOptionsProcessor.DeleteAllImports() End Sub Public Sub DeleteAllResourceReferences() Implements IVbCompilerProject.DeleteAllResourceReferences @@ -228,23 +186,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim End Sub Public Sub DeleteImport(wszImport As String) Implements IVbCompilerProject.DeleteImport - Try - Dim index = _imports.FindIndex(Function(import) import.Clause.ToFullString() = wszImport) - If index >= 0 Then - _imports.RemoveAt(index) - PushUpdatedGlobalImportsToWorkspace() - End If - Catch e As Exception When FilterException(e) - Throw ExceptionUtilities.Unreachable - End Try - End Sub - - Public Overrides Sub Disconnect() Implements IVbCompilerProject.Disconnect - Try - MyBase.Disconnect() - Catch e As Exception When FilterException(e) - Throw ExceptionUtilities.Unreachable - End Try + VisualStudioProjectOptionsProcessor.DeleteImport(wszImport) End Sub Public Function ENCRebuild(in_pProgram As Object, ByRef out_ppUpdate As Object) As Integer Implements IVbCompilerProject.ENCRebuild @@ -256,22 +198,17 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim End Function Public Sub GetEntryPointsList(cItems As Integer, strList() As String, ByVal pcActualItems As IntPtr) Implements IVbCompilerProject.GetEntryPointsList - Try - Dim project = Workspace.CurrentSolution.GetProject(Id) - Dim compilation = project.GetCompilationAsync(CancellationToken.None).WaitAndGetResult(CancellationToken.None) + Dim project = Workspace.CurrentSolution.GetProject(VisualStudioProject.Id) + Dim compilation = project.GetCompilationAsync(CancellationToken.None).WaitAndGetResult(CancellationToken.None) - GetEntryPointsWorker(cItems, strList, pcActualItems, findFormsOnly:=False) - Catch e As Exception When FilterException(e) - Throw ExceptionUtilities.Unreachable - End Try + GetEntryPointsWorker(compilation, cItems, strList, pcActualItems, findFormsOnly:=False) End Sub - Public Sub GetEntryPointsWorker(cItems As Integer, + Public Shared Sub GetEntryPointsWorker(compilation As Compilation, + cItems As Integer, strList() As String, ByVal pcActualItems As IntPtr, findFormsOnly As Boolean) - Dim project = Workspace.CurrentSolution.GetProject(Id) - Dim compilation = project.GetCompilationAsync(CancellationToken.None).WaitAndGetResult(CancellationToken.None) ' If called with cItems = 0 and pcActualItems != NULL, GetEntryPointsList returns in pcActualItems the number of items available. Dim entryPoints = EntryPointFinder.FindEntryPoints(compilation.Assembly.GlobalNamespace, findFormsOnly:=findFormsOnly) @@ -317,11 +254,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim End Sub Public Shadows Sub RemoveFile(wszFileName As String, itemid As UInteger) Implements IVbCompilerProject.RemoveFile - Try - MyBase.RemoveFile(wszFileName) - Catch e As Exception When FilterException(e) - Throw ExceptionUtilities.Unreachable - End Try + MyBase.RemoveFile(wszFileName) End Sub Public Sub RemoveFileByName(wszPath As String) Implements IVbCompilerProject.RemoveFileByName @@ -331,38 +264,25 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim Public Shadows Sub RemoveMetaDataReference(wszFileName As String) Implements IVbCompilerProject.RemoveMetaDataReference wszFileName = FileUtilities.NormalizeAbsolutePath(wszFileName) - Try - ' If this is a reference which was explicitly added and is also a runtime library, leave it - If _explicitlyAddedDefaultReferences.Remove(wszFileName) Then - Return - End If + ' If this is a reference which was explicitly added and is also a runtime library, leave it + If _explicitlyAddedRuntimeLibraries.Remove(wszFileName) Then + Return + End If - MyBase.RemoveMetadataReference(wszFileName) - Catch e As Exception When FilterException(e) - Throw ExceptionUtilities.Unreachable - End Try + VisualStudioProject.RemoveMetadataReference(wszFileName, VisualStudioProject.GetPropertiesForMetadataReference(wszFileName).Single()) End Sub Public Shadows Sub RemoveProjectReference(pReferencedCompilerProject As IVbCompilerProject) Implements IVbCompilerProject.RemoveProjectReference - Try - Dim project = TryCast(pReferencedCompilerProject, VisualBasicProject) - - If project Is Nothing Then - ' Hmm, we got a project which isn't from ourselves. That's somewhat odd, and we really can't do anything - ' with it. - Throw New ArgumentException("Unknown type of IVbCompilerProject.", NameOf(pReferencedCompilerProject)) - End If - - If Not Me.CurrentProjectReferencesContains(project.Id) Then - Throw New ArgumentException("Project reference to remove is not referenced by this project.", NameOf(pReferencedCompilerProject)) - End If + Dim referencedProject = TryCast(pReferencedCompilerProject, VisualBasicProject) - Dim projectReference = GetCurrentProjectReferences().Single(Function(r) r.ProjectId Is project.Id) + If referencedProject Is Nothing Then + ' Hmm, we got a project which isn't from ourselves. That's somewhat odd, and we really can't do anything + ' with it. + Throw New ArgumentException("Unknown type of IVbCompilerProject.", NameOf(pReferencedCompilerProject)) + End If - MyBase.RemoveProjectReference(projectReference) - Catch e As Exception When FilterException(e) - Throw ExceptionUtilities.Unreachable - End Try + Dim projectReference = VisualStudioProject.GetProjectReferences().Single(Function(p) p.ProjectId = referencedProject.VisualStudioProject.Id) + VisualStudioProject.RemoveProjectReference(projectReference) End Sub Public Sub RenameDefaultNamespace(bstrDefaultNamespace As String) Implements IVbCompilerProject.RenameDefaultNamespace @@ -370,13 +290,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim End Sub Public Sub RenameFile(wszOldFileName As String, wszNewFileName As String, itemid As UInteger) Implements IVbCompilerProject.RenameFile - Try - ' We treat the rename as a removal of the old file and the addition of a new one. - RemoveFile(wszOldFileName, itemid) - AddFile(wszNewFileName, itemid, fAddDuringOpen:=False) - Catch e As Exception When FilterException(e) - Throw ExceptionUtilities.Unreachable - End Try + ' We treat the rename as a removal of the old file and the addition of a new one. + RemoveFile(wszOldFileName, itemid) + AddFile(wszNewFileName, itemid, fAddDuringOpen:=False) End Sub Public Sub RenameProject(wszNewProjectName As String) Implements IVbCompilerProject.RenameProject @@ -394,72 +310,38 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim End Sub Public Sub SetCompilerOptions(ByRef pCompilerOptions As VBCompilerOptions) Implements IVbCompilerProject.SetCompilerOptions - _rawOptions = pCompilerOptions - - Try - UpdateOptions() - Catch e As Exception When FilterException(e) - Throw ExceptionUtilities.Unreachable - End Try - End Sub - - Protected Overrides Function CreateCompilationOptions(commandLineArguments As CommandLineArguments, newParseOptions As ParseOptions) As CompilationOptions - Dim baseCompilationOptions = DirectCast(MyBase.CreateCompilationOptions(commandLineArguments, newParseOptions), VisualBasicCompilationOptions) - Dim vbParseOptions = DirectCast(newParseOptions, VisualBasicParseOptions) - Return VisualBasicProjectOptionsHelper.CreateCompilationOptions(baseCompilationOptions, vbParseOptions, _rawOptions, _compilerHost, _imports, ContainingDirectoryPathOpt, RuleSetFile?.Target) - End Function - - Protected Overrides Function CreateParseOptions(commandLineArguments As CommandLineArguments) As ParseOptions - Dim baseParseOptions = DirectCast(MyBase.CreateParseOptions(commandLineArguments), VisualBasicParseOptions) - - Dim resultParseOptions = VisualBasicProjectOptionsHelper.CreateParseOptions(baseParseOptions, _rawOptions) - - Dim commandLineOptions = DirectCast(commandLineArguments.ParseOptions, VisualBasicParseOptions) - If commandLineOptions.LanguageVersion > LanguageVersion.VisualBasic15 Then - ' For language versions after VB 15, we expect the version to be passed from MSBuild to the IDE - ' via command-line arguments (`ICompilerOptionsHostObject.SetCompilerOptions`) - ' instead of using `IVbcHostObject3.SetLanguageVersion` - resultParseOptions = resultParseOptions.WithLanguageVersion(commandLineOptions.LanguageVersion) - End If - - Return resultParseOptions - End Function - - Private Shadows Sub UpdateOptions() - MyBase.UpdateOptions() + Dim oldRuntimeLibraries = _runtimeLibraries + VisualStudioProjectOptionsProcessor.SetNewRawOptions(pCompilerOptions) - ' NOTE: _NOT_ using OrdinalIgnoreCase, even though this is a path. If the user - ' changes the casing in options, we want that to be reflected in the binary we - ' produce, etc. - Dim outputPath = VisualBasicProjectOptionsHelper.GetOutputPath(_rawOptions) - If _lastOutputPath Is Nothing OrElse Not outputPath.Equals(_lastOutputPath, StringComparison.Ordinal) Then - SetOutputPathAndRelatedData(outputPath) - _lastOutputPath = outputPath + If Not String.IsNullOrEmpty(pCompilerOptions.wszExeName) Then + VisualStudioProject.AssemblyName = Path.GetFileNameWithoutExtension(pCompilerOptions.wszExeName) End If - ' Push down the new runtime libraries - Dim newRuntimeLibraries = VisualBasicProjectOptionsHelper.GetRuntimeLibraries(_compilerHost, _rawOptions) - If Not newRuntimeLibraries.SequenceEqual(_runtimeLibraries, StringComparer.Ordinal) Then - For Each oldRuntimeLibrary In _runtimeLibraries - If Not _explicitlyAddedDefaultReferences.Contains(oldRuntimeLibrary) Then - MyBase.RemoveMetadataReference(oldRuntimeLibrary) - End If - Next - - _explicitlyAddedDefaultReferences.Clear() - - For Each newRuntimeLibrary In newRuntimeLibraries - newRuntimeLibrary = FileUtilities.NormalizeAbsolutePath(newRuntimeLibrary) - - ' If we already reference this, just skip it - If HasMetadataReference(newRuntimeLibrary) Then - _explicitlyAddedDefaultReferences.Add(newRuntimeLibrary) - Else - MyBase.AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(newRuntimeLibrary, MetadataReferenceProperties.Assembly) - End If - Next - - _runtimeLibraries = newRuntimeLibraries + _runtimeLibraries = VisualStudioProjectOptionsProcessor.GetRuntimeLibraries(_compilerHost) + + If Not _runtimeLibraries.SequenceEqual(oldRuntimeLibraries, StringComparer.Ordinal) Then + Using batchScope = VisualStudioProject.CreateBatchScope() + ' To keep things simple, we'll just remove everything and add everything back in + For Each oldRuntimeLibrary In oldRuntimeLibraries + ' If this one was added explicitly in addition to our computation, we don't have to remove it + If _explicitlyAddedRuntimeLibraries.Contains(oldRuntimeLibrary) Then + _explicitlyAddedRuntimeLibraries.Remove(oldRuntimeLibrary) + Else + VisualStudioProject.RemoveMetadataReference(oldRuntimeLibrary, MetadataReferenceProperties.Assembly) + End If + Next + + For Each newRuntimeLibrary In _runtimeLibraries + newRuntimeLibrary = FileUtilities.NormalizeAbsolutePath(newRuntimeLibrary) + + ' If we already reference this, just skip it + If VisualStudioProject.ContainsMetadataReference(newRuntimeLibrary, MetadataReferenceProperties.Assembly) Then + _explicitlyAddedRuntimeLibraries.Add(newRuntimeLibrary) + Else + VisualStudioProject.AddMetadataReference(newRuntimeLibrary, MetadataReferenceProperties.Assembly) + End If + Next + End Using End If End Sub @@ -488,14 +370,15 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim End Sub Public Sub StartEdit() Implements IVbCompilerProject.StartEdit - ' These are called by third parties during batch edit scenarios. Historically, this would stop the - ' background compiler so we wouldn't repeatedly decompile/recompile. For Roslyn, we have nothing to - ' currently do here. If we have some special "batch" edits we can do to the Workspace API, then we could - ' consider taking advantage of them here. + ' Since Roslyn's creation this method has not been implemented, because we didn't have a good batching concept in the project system shim code. + ' We now have that (with VisualStudioProject.CreateBatchScope), but unfortunately clients are not very well behaved. The native language service + ' relies on the old behavior, which was calling VisualBasicProject.StartBackgroundCompiler/VisualBasicProject.StopBackgroundCompiler and this + ' method here all increment the same global counter in the end: it was OK to call StopBackgroundCompiler to stop it but FinishEdit() to restart it. + ' Rather than trying to make this all work again, we'll leave this unimplemented still until we have evidence that this will help, and time to do it. End Sub Public Sub FinishEdit() Implements IVbCompilerProject.FinishEdit - ' Called by third parties to finish batch edit scenarios. See comments in StartEdit for details. + ' See comment in StartEdit for why this isn't implemented. End Sub Public Sub SuspendPostedNotifications() Implements IVbCompilerProject.SuspendPostedNotifications @@ -512,15 +395,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim Throw New NotSupportedException() End Sub - Private Sub PushUpdatedGlobalImportsToWorkspace() - ' We'll just use the last converted options with the global imports changed. If we don't - ' have any last options, then we won't push anything down at all. We'll call - ' SetCompilationOptions later once we get the call through - ' IVbCompiler.SetCompilerOptions - Dim lastCompilationOptions = TryCast(CurrentCompilationOptions, VisualBasicCompilationOptions) - If lastCompilationOptions IsNot Nothing Then - SetOptions(lastCompilationOptions.WithGlobalImports(_imports), CurrentParseOptions) - End If + Public Shadows Sub Disconnect() Implements IVbCompilerProject.Disconnect + MyBase.Disconnect() End Sub End Class End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProjectOptionsHelper.vb b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProjectOptionsHelper.vb deleted file mode 100644 index 99212dde0cbc49069b8deb1e549f91ce8dc45091..0000000000000000000000000000000000000000 --- a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProjectOptionsHelper.vb +++ /dev/null @@ -1,278 +0,0 @@ -' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -Imports System.Collections.Immutable -Imports System.Collections.ObjectModel -Imports System.IO -Imports System.Runtime.InteropServices -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.VisualBasic -Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -Imports Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim.Interop - -Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim - ''' - ''' Helper to convert a legacy VBCompilerOptions into the new Roslyn CompilerOptions and ParseOptions. - ''' - ''' - Friend NotInheritable Class VisualBasicProjectOptionsHelper - - ''' - ''' Maps a string to the parsed conditional compilation symbols. - ''' It is expected that most projects in a solution will have similar (if not identical) - ''' sets of conditional compilation symbols. From a performance perspective, it makes sense - ''' to cache these rather than reparse them every time we create a new - ''' instance. We also expect the total set of these to be small, which is why we never evict anything from this cache. - ''' - Private Shared s_conditionalCompilationSymbolsCache As Dictionary(Of KeyValuePair(Of String, OutputKind), ImmutableArray(Of KeyValuePair(Of String, Object))) = - New Dictionary(Of KeyValuePair(Of String, OutputKind), ImmutableArray(Of KeyValuePair(Of String, Object))) - - Private Shared ReadOnly s_EmptyCommandLineArguments As VisualBasicCommandLineArguments = VisualBasicCommandLineParser.Default.Parse(SpecializedCollections.EmptyEnumerable(Of String)(), baseDirectory:="", sdkDirectory:=Nothing) - - Public Shared Function CreateCompilationOptions(baseCompilationOptionsOpt As VisualBasicCompilationOptions, - newParseOptions As VisualBasicParseOptions, - compilerOptions As VBCompilerOptions, - compilerHost As IVbCompilerHost, - globalImports As IEnumerable(Of GlobalImport), - projectDirectoryOpt As String, - ruleSetOpt As IRuleSetFile) As VisualBasicCompilationOptions - Dim platform As Platform - If Not System.Enum.TryParse(compilerOptions.wszPlatformType, ignoreCase:=True, result:=platform) Then - platform = Platform.AnyCpu - End If - - Dim ruleSetFileGeneralDiagnosticOption As ReportDiagnostic? = Nothing - Dim ruleSetFileSpecificDiagnosticOptions As IDictionary(Of String, ReportDiagnostic) = Nothing - - If ruleSetOpt IsNot Nothing Then - ruleSetFileGeneralDiagnosticOption = ruleSetOpt.GetGeneralDiagnosticOption() - ruleSetFileSpecificDiagnosticOptions = ruleSetOpt.GetSpecificDiagnosticOptions() - End If - - Dim generalDiagnosticOption As ReportDiagnostic = DetermineGeneralDiagnosticOption(compilerOptions.WarningLevel, ruleSetFileGeneralDiagnosticOption) - Dim specificDiagnosticOptions As IReadOnlyDictionary(Of String, ReportDiagnostic) = DetermineSpecificDiagnosticOptions(compilerOptions, ruleSetFileSpecificDiagnosticOptions) - Dim outputKind = GetOutputKind(compilerOptions) - - If baseCompilationOptionsOpt Is Nothing Then - baseCompilationOptionsOpt = New VisualBasicCompilationOptions(outputKind) _ - .WithConcurrentBuild(False) _ - .WithXmlReferenceResolver(New XmlFileResolver(projectDirectoryOpt)) _ - .WithSourceReferenceResolver(New SourceFileResolver(Array.Empty(Of String), projectDirectoryOpt)) _ - .WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default) _ - .WithStrongNameProvider(New DesktopStrongNameProvider(ImmutableArray(Of String).Empty)) - End If - - Return baseCompilationOptionsOpt.WithOverflowChecks(Not compilerOptions.bRemoveIntChecks) _ - .WithCryptoKeyContainer(compilerOptions.wszStrongNameContainer) _ - .WithCryptoKeyFile(compilerOptions.wszStrongNameKeyFile) _ - .WithDelaySign(If(compilerOptions.bDelaySign, CType(True, Boolean?), Nothing)) _ - .WithEmbedVbCoreRuntime(compilerOptions.vbRuntimeKind = VBRuntimeKind.EmbeddedRuntime) _ - .WithGeneralDiagnosticOption(generalDiagnosticOption) _ - .WithGlobalImports(globalImports) _ - .WithMainTypeName(If(compilerOptions.wszStartup <> String.Empty, compilerOptions.wszStartup, Nothing)) _ - .WithOptionExplicit(Not compilerOptions.bOptionExplicitOff) _ - .WithOptionInfer(Not compilerOptions.bOptionInferOff) _ - .WithOptionStrict(If(compilerOptions.bOptionStrictOff, OptionStrict.Custom, OptionStrict.On)) _ - .WithOptionCompareText(compilerOptions.bOptionCompareText) _ - .WithOptimizationLevel(If(compilerOptions.bOptimize, OptimizationLevel.Release, OptimizationLevel.Debug)) _ - .WithOutputKind(outputKind) _ - .WithPlatform(platform) _ - .WithRootNamespace(If(compilerOptions.wszDefaultNamespace, String.Empty)) _ - .WithSpecificDiagnosticOptions(specificDiagnosticOptions) _ - .WithParseOptions(newParseOptions) - End Function - - Private Shared Function GetOutputKind(options As VBCompilerOptions) As OutputKind - Select Case options.OutputType - Case VBCompilerOutputTypes.OUTPUT_ConsoleEXE - Return OutputKind.ConsoleApplication - Case VBCompilerOutputTypes.OUTPUT_Library, VBCompilerOutputTypes.OUTPUT_None - Return OutputKind.DynamicallyLinkedLibrary - Case VBCompilerOutputTypes.OUTPUT_Module - Return OutputKind.NetModule - Case VBCompilerOutputTypes.OUTPUT_WindowsEXE - Return OutputKind.WindowsApplication - Case VBCompilerOutputTypes.OUTPUT_AppContainerEXE - Return OutputKind.WindowsRuntimeApplication - Case VBCompilerOutputTypes.OUTPUT_WinMDObj - Return OutputKind.WindowsRuntimeMetadata - Case Else - Return Nothing - End Select - End Function - - Public Shared Function GetRuntimeLibraries(compilerHost As IVbCompilerHost, options As VBCompilerOptions) As List(Of String) - ' GetSDKPath can return E_NOTIMPL if there is no SDK path at all - Dim sdkPath As String = Nothing - Dim sdkPathHResult = compilerHost.GetSdkPath(sdkPath) - - If sdkPathHResult = VSConstants.E_NOTIMPL Then - sdkPath = Nothing - Else - Marshal.ThrowExceptionForHR(sdkPathHResult, New IntPtr(-1)) - End If - - Dim runtimes = New List(Of String) - Select Case options.vbRuntimeKind - Case VBRuntimeKind.DefaultRuntime - If sdkPath IsNot Nothing Then - runtimes.Add(PathUtilities.CombinePathsUnchecked(sdkPath, "Microsoft.VisualBasic.dll")) - End If - - Case VBRuntimeKind.SpecifiedRuntime - If options.wszSpecifiedVBRuntime IsNot Nothing Then - ' If they specified a fully qualified file, use it - If File.Exists(options.wszSpecifiedVBRuntime) Then - runtimes.Add(options.wszSpecifiedVBRuntime) - ElseIf sdkPath IsNot Nothing Then - ' If it's just a filename, try to find it in the SDK path. - If options.wszSpecifiedVBRuntime = PathUtilities.GetFileName(options.wszSpecifiedVBRuntime) Then - Dim runtimePath = PathUtilities.CombinePathsUnchecked(sdkPath, options.wszSpecifiedVBRuntime) - If File.Exists(runtimePath) Then - runtimes.Add(runtimePath) - End If - End If - End If - End If - End Select - - If sdkPath IsNot Nothing Then - If Not options.bNoStandardLibs Then - runtimes.Add(PathUtilities.CombinePathsUnchecked(sdkPath, "System.dll")) - End If - - runtimes.Add(PathUtilities.CombinePathsUnchecked(sdkPath, "mscorlib.dll")) - End If - - Return runtimes - End Function - - Public Shared Function GetOutputPath(compilerOptions As VBCompilerOptions) As String - If compilerOptions.wszOutputPath IsNot Nothing AndAlso compilerOptions.wszExeName IsNot Nothing Then - Return PathUtilities.CombinePathsUnchecked(compilerOptions.wszOutputPath, compilerOptions.wszExeName) - End If - - Return String.Empty - End Function - Public Shared Function CreateParseOptions(baseParseOptionsOpt As VisualBasicParseOptions, compilerOptions As VBCompilerOptions) As VisualBasicParseOptions - Dim outputKind = GetOutputKind(compilerOptions) - Dim conditionalCompilationSymbols = GetConditionalCompilationSymbols(outputKind, If(compilerOptions.wszCondComp, "")) - - ' The project system may pass us zero to mean "default". Old project system binaries (prior to mid-September 2014) - ' would also use other constants that we just got rid of. This check can be replaced with an explicit check for just - ' zero in October 2014 or later. - If compilerOptions.langVersion < LanguageVersion.VisualBasic9 Then - compilerOptions.langVersion = VisualBasicParseOptions.Default.LanguageVersion - End If - - baseParseOptionsOpt = If(baseParseOptionsOpt, New VisualBasicParseOptions()) - Return baseParseOptionsOpt.WithLanguageVersion(compilerOptions.langVersion) _ - .WithPreprocessorSymbols(conditionalCompilationSymbols) _ - .WithDocumentationMode(If(Not String.IsNullOrEmpty(compilerOptions.wszXMLDocName), DocumentationMode.Diagnose, DocumentationMode.Parse)) - End Function - - Private Shared Function GetConditionalCompilationSymbols(kind As OutputKind, str As String) As ImmutableArray(Of KeyValuePair(Of String, Object)) - Debug.Assert(str IsNot Nothing) - Dim key = KeyValuePairUtil.Create(str, kind) - - Dim result As ImmutableArray(Of KeyValuePair(Of String, Object)) = Nothing - If s_conditionalCompilationSymbolsCache.TryGetValue(key, result) Then - Return result - End If - - Dim errors As IEnumerable(Of Diagnostic) = Nothing - Dim defines = VisualBasicCommandLineParser.ParseConditionalCompilationSymbols(str, errors) - ' ignore errors - - Return AddPredefinedPreprocessorSymbols(kind, defines.AsImmutableOrEmpty()) - End Function - - Private Shared Iterator Function ParseWarningCodes(warnings As String) As IEnumerable(Of String) - If warnings IsNot Nothing Then - For Each warning In warnings.Split(New String() {" ", ",", ";"}, StringSplitOptions.RemoveEmptyEntries) - Dim warningId As Integer - If Integer.TryParse(warning, warningId) Then - Yield "BC" + warning - Else - Yield warning - End If - Next - End If - End Function - - Private Shared Function DetermineGeneralDiagnosticOption(level As WarningLevel, ruleSetGeneralDiagnosticOption As ReportDiagnostic?) As ReportDiagnostic - 'If no option was supplied in the project file, but there is one in the ruleset file, use that one. - If level = WarningLevel.WARN_Regular AndAlso - ruleSetGeneralDiagnosticOption.HasValue Then - - Return ruleSetGeneralDiagnosticOption.Value - End If - - Return ConvertWarningLevel(level) - End Function - - Private Shared Function DetermineSpecificDiagnosticOptions(options As VBCompilerOptions, ruleSetSpecificDiagnosticOptions As IDictionary(Of String, ReportDiagnostic)) As IReadOnlyDictionary(Of String, ReportDiagnostic) - If ruleSetSpecificDiagnosticOptions Is Nothing Then - ruleSetSpecificDiagnosticOptions = New Dictionary(Of String, ReportDiagnostic) - End If - - ' Start with the rule set options - Dim diagnosticOptions = New Dictionary(Of String, ReportDiagnostic)(ruleSetSpecificDiagnosticOptions) - - ' Update the specific options based on the general settings - If options.WarningLevel = WarningLevel.WARN_AsError Then - For Each pair In ruleSetSpecificDiagnosticOptions - If pair.Value = ReportDiagnostic.Warn Then - diagnosticOptions(pair.Key) = ReportDiagnostic.Error - End If - Next - ElseIf options.WarningLevel = WarningLevel.WARN_None Then - - For Each pair In ruleSetSpecificDiagnosticOptions - If pair.Value <> ReportDiagnostic.Error Then - diagnosticOptions(pair.Key) = ReportDiagnostic.Suppress - End If - Next - End If - - ' Update the specific options based on the specific settings - For Each diagnosticID In ParseWarningCodes(options.wszWarningsAsErrors) - diagnosticOptions(diagnosticID) = ReportDiagnostic.Error - Next - - For Each diagnosticID In ParseWarningCodes(options.wszWarningsNotAsErrors) - Dim ruleSetOption As ReportDiagnostic - If ruleSetSpecificDiagnosticOptions.TryGetValue(diagnosticID, ruleSetOption) Then - diagnosticOptions(diagnosticID) = ruleSetOption - Else - diagnosticOptions(diagnosticID) = ReportDiagnostic.Default - End If - Next - - For Each diagnosticID In ParseWarningCodes(options.wszDisabledWarnings) - diagnosticOptions(diagnosticID) = ReportDiagnostic.Suppress - Next - - Return New ReadOnlyDictionary(Of String, ReportDiagnostic)(diagnosticOptions) - End Function - - Private Shared Function ConvertWarningLevel(level As WarningLevel) As ReportDiagnostic - Select Case level - Case WarningLevel.WARN_None - Return ReportDiagnostic.Suppress - - Case WarningLevel.WARN_Regular - Return ReportDiagnostic.Default - - Case WarningLevel.WARN_AsError - Return ReportDiagnostic.Error - - Case Else - Throw ExceptionUtilities.UnexpectedValue(level) - End Select - End Function - - Private Shared Function GetArrayEntry(Of T As Structure)(array As IntPtr, index As Integer) As T - Return DirectCast(Marshal.PtrToStructure(array + index * Marshal.SizeOf(GetType(T)), GetType(T)), T) - End Function - End Class -End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/Venus/VisualBasicContainedLanguage.vb b/src/VisualStudio/VisualBasic/Impl/Venus/VisualBasicContainedLanguage.vb index bd5434cb417363032e6ea1706b9a1e6932b0dcd2..d053c2bc506bd3a56873300041847cfcce8b4a0a 100644 --- a/src/VisualStudio/VisualBasic/Impl/Venus/VisualBasicContainedLanguage.vb +++ b/src/VisualStudio/VisualBasic/Impl/Venus/VisualBasicContainedLanguage.vb @@ -23,12 +23,12 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Venus Public Sub New(bufferCoordinator As IVsTextBufferCoordinator, componentModel As IComponentModel, - project As AbstractProject, + project As VisualStudioProject, hierarchy As IVsHierarchy, itemid As UInteger, languageService As VisualBasicLanguageService, sourceCodeKind As SourceCodeKind) - MyBase.New(bufferCoordinator, componentModel, project, hierarchy, itemid, languageService, sourceCodeKind, VisualBasicHelperFormattingRule.Instance) + MyBase.New(bufferCoordinator, componentModel, project, hierarchy, itemid, languageService, VisualBasicHelperFormattingRule.Instance) End Sub Public Function AddStaticEventBinding(pszClassName As String, diff --git a/src/VisualStudio/Xaml/Impl/Implementation/XamlProject.cs b/src/VisualStudio/Xaml/Impl/Implementation/XamlProject.cs deleted file mode 100644 index fcb91f78f091dfadc96f233e51370a528f221a3a..0000000000000000000000000000000000000000 --- a/src/VisualStudio/Xaml/Impl/Implementation/XamlProject.cs +++ /dev/null @@ -1,42 +0,0 @@ -// 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 System; -using System.Diagnostics; -using Microsoft.CodeAnalysis.Editor.Xaml; -using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; -using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Legacy; -using Microsoft.VisualStudio.Shell.Interop; - -namespace Microsoft.VisualStudio.LanguageServices.Xaml -{ - [DebuggerDisplay("{GetDebuggerDisplay(),nq}")] - internal class XamlProject : AbstractLegacyProject - { - public XamlProject(VisualStudioProjectTracker projectTracker, IVsHierarchy hierarchy, IServiceProvider serviceProvider, VisualStudioWorkspaceImpl visualStudioWorkspace) : - base( - projectTracker, - reportExternalErrorCreatorOpt: null, - projectSystemName: $"{XamlProject.GetProjectName(hierarchy)}-{nameof(XamlProject)}", - hierarchy: hierarchy, - language: StringConstants.XamlLanguageName, - serviceProvider: serviceProvider, - visualStudioWorkspaceOpt: visualStudioWorkspace, - hostDiagnosticUpdateSourceOpt: null) - { - projectTracker.AddProject(this); - - // Make sure the solution crawler is running in this workspace - visualStudioWorkspace.StartSolutionCrawler(); - } - - private static string GetProjectName(IVsHierarchy hierarchy) - { - return hierarchy.TryGetName(out var name) ? name : null; - } - - private string GetDebuggerDisplay() - { - return $"{this.DisplayName}"; - } - } -} diff --git a/src/VisualStudio/Xaml/Impl/Implementation/XamlTextViewCreationListener.cs b/src/VisualStudio/Xaml/Impl/Implementation/XamlTextViewCreationListener.cs index b3be131cd2b2798c508276375cb09b967b347373..aba4027e6146a3a651a4b7f66d1c79d7adc78abf 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/XamlTextViewCreationListener.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/XamlTextViewCreationListener.cs @@ -1,6 +1,7 @@ // 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 System; +using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel.Composition; using System.Diagnostics; @@ -27,21 +28,16 @@ namespace Microsoft.VisualStudio.LanguageServices.Xaml internal sealed partial class XamlTextViewCreationListener : IVsTextViewCreationListener { private readonly System.IServiceProvider _serviceProvider; + private readonly VisualStudioProjectFactory _visualStudioProjectFactory; private readonly VisualStudioWorkspaceImpl _vsWorkspace; - private readonly ICommandHandlerServiceFactory _commandHandlerService; private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactory; private readonly Lazy _rdt; private readonly IVsSolution _vsSolution; private uint? _solutionEventsCookie; private uint? _rdtEventsCookie; + private readonly Dictionary _xamlProjects = new Dictionary(); - internal ICommandHandlerServiceFactory CommandHandlerServiceFactory - { - get - { - return _commandHandlerService; - } - } + internal ICommandHandlerServiceFactory CommandHandlerServiceFactory { get; } [ImportingConstructor] public XamlTextViewCreationListener( @@ -49,12 +45,14 @@ internal ICommandHandlerServiceFactory CommandHandlerServiceFactory ICommandHandlerServiceFactory commandHandlerServiceFactory, IVsEditorAdaptersFactoryService editorAdaptersFactoryService, IXamlDocumentAnalyzerService analyzerService, - VisualStudioWorkspaceImpl vsWorkspace) + VisualStudioWorkspaceImpl vsWorkspace, + VisualStudioProjectFactory visualStudioProjectFactory) { _serviceProvider = services; - _commandHandlerService = commandHandlerServiceFactory; + CommandHandlerServiceFactory = commandHandlerServiceFactory; _editorAdaptersFactory = editorAdaptersFactoryService; _vsWorkspace = vsWorkspace; + _visualStudioProjectFactory = visualStudioProjectFactory; _rdt = new Lazy(() => new RunningDocumentTable(_serviceProvider)); _vsSolution = (IVsSolution)_serviceProvider.GetService(typeof(SVsSolution)); @@ -100,25 +98,19 @@ public void VsTextViewCreated(IVsTextView vsTextView) return; } - AbstractProject project = GetXamlProject(hierarchy); - if (project == null) + VisualStudioProject project; + + if (!_xamlProjects.TryGetValue(hierarchy, out project)) { - project = new XamlProject( - _vsWorkspace.GetProjectTrackerAndInitializeIfNecessary(_serviceProvider), - hierarchy, - _serviceProvider, - _vsWorkspace); + string name; + hierarchy.TryGetName(out name); + project = _visualStudioProjectFactory.CreateAndAddToWorkspace(name + "-XamlProject", StringConstants.XamlLanguageName); + _xamlProjects.Add(hierarchy, project); } - IVisualStudioHostDocument vsDocument = project.GetCurrentDocumentFromPath(filePath); - if (vsDocument == null) + if (!project.ContainsSourceFile(filePath)) { - if (!TryCreateXamlDocument(project, filePath, out vsDocument)) - { - return; - } - - project.AddDocument(vsDocument, isCurrentContext: true, hookupHandlers: true); + project.AddSourceFile(filePath); } AttachRunningDocTableEvents(); @@ -136,28 +128,13 @@ private void AttachRunningDocTableEvents() } } - private AbstractProject GetXamlProject(IVsHierarchy hierarchy) - { - var projects = _vsWorkspace.DeferredState?.ProjectTracker.ImmutableProjects ?? ImmutableArray.Empty; - - return projects.FirstOrDefault(p => p.Language == StringConstants.XamlLanguageName && p.Hierarchy == hierarchy); - } - - private bool TryCreateXamlDocument(AbstractProject project, string filePath, out IVisualStudioHostDocument vsDocument) - { - // We already have an AbstractProject, so the workspace has been created - vsDocument = _vsWorkspace.DeferredState.ProjectTracker.DocumentProvider.TryGetDocumentForFile( - project, filePath, SourceCodeKind.Regular, - tb => tb.ContentType.IsOfType(ContentTypeNames.XamlContentType), - ImmutableArray.Empty); - - return vsDocument != null; - } - private void OnProjectClosing(IVsHierarchy hierarchy) { - AbstractProject project = GetXamlProject(hierarchy); - project?.Disconnect(); + if (_xamlProjects.TryGetValue(hierarchy, out VisualStudioProject project)) + { + project.RemoveFromWorkspace(); + _xamlProjects.Remove(hierarchy); + } } private void OnDocumentMonikerChanged(IVsHierarchy hierarchy, string oldMoniker, string newMoniker) @@ -172,47 +149,31 @@ private void OnDocumentMonikerChanged(IVsHierarchy hierarchy, string oldMoniker, } // If the moniker change only involves a non-XAML project then ignore it. - AbstractProject project = GetXamlProject(hierarchy); - if (project == null) + if (!_xamlProjects.TryGetValue(hierarchy, out VisualStudioProject project)) { return; } // Managed languages rely on the msbuild host object to add and remove documents during rename. // For XAML we have to do that ourselves. - IVisualStudioHostDocument oldDocument = project.GetCurrentDocumentFromPath(oldMoniker); - if (oldDocument != null) + if (project.ContainsSourceFile(oldMoniker)) { - project.RemoveDocument(oldDocument); + project.RemoveSourceFile(oldMoniker); } - IVisualStudioHostDocument newDocument = project.GetCurrentDocumentFromPath(newMoniker); - Debug.Assert(newDocument == null, "Why does the renamed document already exist in the project?"); - if (newDocument == null) - { - if (TryCreateXamlDocument(project, newMoniker, out newDocument)) - { - project.AddDocument(newDocument, isCurrentContext: true, hookupHandlers: true); - } - } + project.AddSourceFile(newMoniker); } private void OnDocumentClosed(uint docCookie) { RunningDocumentInfo info = _rdt.Value.GetDocumentInfo(docCookie); - AbstractProject project = GetXamlProject(info.Hierarchy); - if (project == null) + if (info.Hierarchy != null && _xamlProjects.TryGetValue(info.Hierarchy, out VisualStudioProject project)) { - return; - } - - IVisualStudioHostDocument document = project.GetCurrentDocumentFromPath(info.Moniker); - if (document == null) - { - return; + if (project.ContainsSourceFile(info.Moniker)) + { + project.RemoveSourceFile(info.Moniker); + } } - - project.RemoveDocument(document); } } } diff --git a/src/Workspaces/Core/Portable/Utilities/IDictionaryExtensions.cs b/src/Workspaces/Core/Portable/Utilities/IDictionaryExtensions.cs index 6c92140d1ae869b2afc4353a71d07920769a078c..325411207a5ae09d363820f760148b5d0bd4bd9e 100644 --- a/src/Workspaces/Core/Portable/Utilities/IDictionaryExtensions.cs +++ b/src/Workspaces/Core/Portable/Utilities/IDictionaryExtensions.cs @@ -41,5 +41,19 @@ internal static class IDictionaryExtensions collection.Add(value); } + + public static void MultiRemove(this IDictionary dictionary, TKey key, TValue value) + where TCollection : ICollection + { + if (dictionary.TryGetValue(key, out var collection)) + { + collection.Remove(value); + + if (collection.Count == 0) + { + dictionary.Remove(key); + } + } + } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index 2b0ce3f236031af9f6918c34e440263725ca18ae..7f025665bc253290f5de5b3b74868a2d90e06518 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -282,7 +282,7 @@ protected virtual void ClearProjectData(ProjectId projectId) /// Override this method if you want to do additional work when a document is removed. /// Call the base method at the end of your method. /// - protected virtual void ClearDocumentData(DocumentId documentId) + protected internal virtual void ClearDocumentData(DocumentId documentId) { this.ClearOpenDocument(documentId); } @@ -735,7 +735,6 @@ protected internal void OnDocumentRemoved(DocumentId documentId) protected virtual void CheckDocumentCanBeRemoved(DocumentId documentId) { - CheckDocumentIsClosed(documentId); } /// diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs b/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs index 12133ec9fadad79663325937396e87abe29af6fc..e248986242fd7bd6def101ec0cf99d72571ed6b1 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs @@ -322,7 +322,7 @@ internal virtual void SetDocumentContext(DocumentId documentId) /// /// /// - protected void OnDocumentContextUpdated(DocumentId documentId) + protected internal void OnDocumentContextUpdated(DocumentId documentId) { // TODO: remove linear search @@ -341,7 +341,7 @@ protected void OnDocumentContextUpdated(DocumentId documentId) /// /// /// - internal void OnDocumentContextUpdated(DocumentId documentId, SourceTextContainer container) + private void OnDocumentContextUpdated(DocumentId documentId, SourceTextContainer container) { if (_isProjectUnloading.Value) { @@ -358,7 +358,7 @@ internal void OnDocumentContextUpdated(DocumentId documentId, SourceTextContaine } } - internal void OnDocumentContextUpdated_NoSerializationLock(DocumentId documentId, SourceTextContainer container) + private void OnDocumentContextUpdated_NoSerializationLock(DocumentId documentId, SourceTextContainer container) { DocumentId oldActiveContextDocumentId; diff --git a/src/Workspaces/Remote/Core/Services/SolutionCreator.cs b/src/Workspaces/Remote/Core/Services/SolutionCreator.cs index dd9371f2b2ededc2ad00b90bcaa57fec2a403e58..b03f46ceff40fb5eed7fd419f1167a135944d521 100644 --- a/src/Workspaces/Remote/Core/Services/SolutionCreator.cs +++ b/src/Workspaces/Remote/Core/Services/SolutionCreator.cs @@ -621,9 +621,6 @@ private ImmutableArray GetStrongNameKeyPaths(ProjectInfo.ProjectAttribut private async Task ValidateChecksumAsync(Checksum givenSolutionChecksum, Solution solution) { - // have this to avoid error on async - await Task.CompletedTask.ConfigureAwait(false); - #if DEBUG var currentSolutionChecksum = await solution.State.GetChecksumAsync(_cancellationToken).ConfigureAwait(false); @@ -632,8 +629,6 @@ private async Task ValidateChecksumAsync(Checksum givenSolutionChecksum, Solutio return; } - Debug.Assert(false, "checksum not same"); - var map = await solution.GetAssetMapAsync(_cancellationToken).ConfigureAwait(false); await RemoveDuplicateChecksumsAsync(givenSolutionChecksum, map).ConfigureAwait(false); @@ -649,9 +644,13 @@ private async Task ValidateChecksumAsync(Checksum givenSolutionChecksum, Solutio } Logger.Log(FunctionId.SolutionCreator_AssetDifferences, sb.ToString()); -#endif - return; + Debug.Fail("Differences detected in solution checksum: " + sb.ToString()); +#else + + // have this to avoid error on async + await Task.CompletedTask.ConfigureAwait(false); +#endif } private async Task RemoveDuplicateChecksumsAsync(Checksum givenSolutionChecksum, Dictionary map)