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