diff --git a/NuGet.config b/NuGet.config
index 3cd2f6f4e6a03..8c879770d5df4 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -8,6 +8,7 @@
+
diff --git a/Roslyn.sln b/Roslyn.sln
index 303f630b773cb..75e9c99cdc91e 100644
--- a/Roslyn.sln
+++ b/Roslyn.sln
@@ -487,6 +487,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Rebu
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Rebuild.UnitTests", "src\Compilers\Core\RebuildTest\Microsoft.CodeAnalysis.Rebuild.UnitTests.csproj", "{21B49277-E55A-45EF-8818-744BCD6CB732}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.ExternalAccess.DotNetWatch", "src\Tools\ExternalAccess\DotNetWatch\Microsoft.CodeAnalysis.ExternalAccess.DotNetWatch.csproj", "{9DF89AA1-142B-4D83-A9E7-F57BD1DDA333}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.ExternalAccess.Razor.UnitTests", "src\Tools\ExternalAccess\RazorTest\Microsoft.CodeAnalysis.ExternalAccess.Razor.UnitTests.csproj", "{BB987FFC-B758-4F73-96A3-923DE8DCFF1A}"
EndProject
Global
@@ -1268,6 +1270,10 @@ Global
{21B49277-E55A-45EF-8818-744BCD6CB732}.Debug|Any CPU.Build.0 = Debug|Any CPU
{21B49277-E55A-45EF-8818-744BCD6CB732}.Release|Any CPU.ActiveCfg = Release|Any CPU
{21B49277-E55A-45EF-8818-744BCD6CB732}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9DF89AA1-142B-4D83-A9E7-F57BD1DDA333}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9DF89AA1-142B-4D83-A9E7-F57BD1DDA333}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9DF89AA1-142B-4D83-A9E7-F57BD1DDA333}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9DF89AA1-142B-4D83-A9E7-F57BD1DDA333}.Release|Any CPU.Build.0 = Release|Any CPU
{BB987FFC-B758-4F73-96A3-923DE8DCFF1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BB987FFC-B758-4F73-96A3-923DE8DCFF1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BB987FFC-B758-4F73-96A3-923DE8DCFF1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -1494,6 +1500,7 @@ Global
{0C2E1633-1462-4712-88F4-A0C945BAD3A8} = {C2D1346B-9665-4150-B644-075CF1636BAA}
{B7D29559-4360-434A-B9B9-2C0612287999} = {A41D1B99-F489-4C43-BBDF-96D61B19A6B9}
{21B49277-E55A-45EF-8818-744BCD6CB732} = {A41D1B99-F489-4C43-BBDF-96D61B19A6B9}
+ {9DF89AA1-142B-4D83-A9E7-F57BD1DDA333} = {8977A560-45C2-4EC2-A849-97335B382C74}
{BB987FFC-B758-4F73-96A3-923DE8DCFF1A} = {8977A560-45C2-4EC2-A849-97335B382C74}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
diff --git a/eng/Versions.props b/eng/Versions.props
index e83865d958f7c..85a993e433bd0 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -31,11 +31,12 @@
6.0.0-preview1.21054.10
1.0.1-beta1.20623.3
3.9.0
- 16.9.220
+ 16.10.44
+ 17.0.32-g9d6b2c6f26
5.0.0-alpha1.19409.1
5.0.0-preview.1.20112.8
- 16.10.161
- 16.9.30921.310
+ 17.0.20-g6553c6c46e
+ 17.0.0-preview-2-31226-057
16.5.0
- 16.9.63
+ 16.10.17-alpha
12.0.2
- 2.7.67
+ 2.8.21
true
@@ -84,6 +84,7 @@
+
diff --git a/src/Test/Diagnostics/Roslyn.Hosting.Diagnostics.csproj b/src/Test/Diagnostics/Roslyn.Hosting.Diagnostics.csproj
index e04898d4fdd84..19069cb566061 100644
--- a/src/Test/Diagnostics/Roslyn.Hosting.Diagnostics.csproj
+++ b/src/Test/Diagnostics/Roslyn.Hosting.Diagnostics.csproj
@@ -16,6 +16,7 @@
+
diff --git a/src/Tools/BuildBoss/ProjectUtil.cs b/src/Tools/BuildBoss/ProjectUtil.cs
index 9fe1fbf0a5ef5..7b12c46c1b4e7 100644
--- a/src/Tools/BuildBoss/ProjectUtil.cs
+++ b/src/Tools/BuildBoss/ProjectUtil.cs
@@ -148,7 +148,7 @@ internal List GetPackageReferences()
internal PackageReference GetPackageReference(XElement element)
{
- var name = element.Attribute("Include")?.Value ?? "";
+ var name = element.Attribute("Include")?.Value ?? element.Attribute("Update")?.Value ?? "";
var version = element.Attribute("Version");
if (version != null)
{
diff --git a/src/Tools/ExternalAccess/DotNetWatch/DotNetWatchEditAndContinueWorkspaceService.cs b/src/Tools/ExternalAccess/DotNetWatch/DotNetWatchEditAndContinueWorkspaceService.cs
new file mode 100644
index 0000000000000..0878dd96e2e2c
--- /dev/null
+++ b/src/Tools/ExternalAccess/DotNetWatch/DotNetWatchEditAndContinueWorkspaceService.cs
@@ -0,0 +1,66 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Immutable;
+using System.Composition;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.EditAndContinue;
+using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Host.Mef;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.ExternalAccess.DotNetWatch
+{
+ internal sealed class DotNetWatchEditAndContinueWorkspaceService : IWorkspaceService
+ {
+ [ExportWorkspaceServiceFactory(typeof(DotNetWatchEditAndContinueWorkspaceService)), Shared]
+ private sealed class Factory : IWorkspaceServiceFactory
+ {
+ [ImportingConstructor]
+ [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
+ public Factory()
+ {
+ }
+
+ [Obsolete(MefConstruction.FactoryMethodMessage, error: true)]
+ public IWorkspaceService? CreateService(HostWorkspaceServices workspaceServices)
+ {
+ return new DotNetWatchEditAndContinueWorkspaceService(workspaceServices.GetRequiredService());
+ }
+ }
+
+ private readonly SolutionActiveStatementSpanProvider _nullSolutionActiveStatementSpanProvider = (_, _) => new(ImmutableArray.Empty);
+ private readonly IEditAndContinueWorkspaceService _workspaceService;
+
+ public DotNetWatchEditAndContinueWorkspaceService(IEditAndContinueWorkspaceService workspaceService)
+ {
+ _workspaceService = workspaceService;
+ }
+
+ public ValueTask OnSourceFileUpdatedAsync(Document document, CancellationToken cancellationToken)
+ => _workspaceService.OnSourceFileUpdatedAsync(document, cancellationToken);
+
+ public void CommitSolutionUpdate() => _workspaceService.CommitSolutionUpdate(out _);
+
+ public void DiscardSolutionUpdate() => _workspaceService.DiscardSolutionUpdate();
+
+ public void EndDebuggingSession() => _workspaceService.EndDebuggingSession(out _);
+
+ public void StartDebuggingSession(Solution solution)
+ => _workspaceService.StartDebuggingSessionAsync(solution, StubManagedEditAndContinueDebuggerService.Instance, captureMatchingDocuments: false, CancellationToken.None).GetAwaiter().GetResult();
+
+ public void StartEditSession() { }
+
+ public void EndEditSession() { }
+
+ public async ValueTask EmitSolutionUpdateAsync(Solution solution, CancellationToken cancellationToken)
+ {
+ var results = await _workspaceService.EmitSolutionUpdateAsync(solution, _nullSolutionActiveStatementSpanProvider, cancellationToken).ConfigureAwait(false);
+
+ return new DotNetWatchManagedModuleUpdatesWrapper(in results.ModuleUpdates);
+ }
+ }
+}
diff --git a/src/Tools/ExternalAccess/DotNetWatch/DotNetWatchManagedModuleUpdateStatus.cs b/src/Tools/ExternalAccess/DotNetWatch/DotNetWatchManagedModuleUpdateStatus.cs
new file mode 100644
index 0000000000000..4327246dea486
--- /dev/null
+++ b/src/Tools/ExternalAccess/DotNetWatch/DotNetWatchManagedModuleUpdateStatus.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue;
+
+namespace Microsoft.CodeAnalysis.ExternalAccess.DotNetWatch
+{
+ internal enum DotNetWatchManagedModuleUpdateStatus
+ {
+ None = ManagedModuleUpdateStatus.None,
+ Ready = ManagedModuleUpdateStatus.Ready,
+ Blocked = ManagedModuleUpdateStatus.Blocked,
+ }
+}
diff --git a/src/Tools/ExternalAccess/DotNetWatch/DotNetWatchManagedModuleUpdateWrapper.cs b/src/Tools/ExternalAccess/DotNetWatch/DotNetWatchManagedModuleUpdateWrapper.cs
new file mode 100644
index 0000000000000..c6b1d0312cbe8
--- /dev/null
+++ b/src/Tools/ExternalAccess/DotNetWatch/DotNetWatchManagedModuleUpdateWrapper.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Immutable;
+using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue;
+
+namespace Microsoft.CodeAnalysis.ExternalAccess.DotNetWatch
+{
+ internal readonly struct DotNetWatchManagedModuleUpdateWrapper
+ {
+ private readonly ManagedModuleUpdate _instance;
+
+ internal DotNetWatchManagedModuleUpdateWrapper(in ManagedModuleUpdate instance)
+ {
+ _instance = instance;
+ }
+
+ public Guid Module => _instance.Module;
+ public ImmutableArray ILDelta => _instance.ILDelta;
+ public ImmutableArray MetadataDelta => _instance.MetadataDelta;
+ public ImmutableArray PdbDelta => _instance.PdbDelta;
+ public ImmutableArray UpdatedMethods => _instance.UpdatedMethods;
+ }
+}
diff --git a/src/Tools/ExternalAccess/DotNetWatch/DotNetWatchManagedModuleUpdatesWrapper.cs b/src/Tools/ExternalAccess/DotNetWatch/DotNetWatchManagedModuleUpdatesWrapper.cs
new file mode 100644
index 0000000000000..59a89b59b281c
--- /dev/null
+++ b/src/Tools/ExternalAccess/DotNetWatch/DotNetWatchManagedModuleUpdatesWrapper.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Immutable;
+using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue;
+
+namespace Microsoft.CodeAnalysis.ExternalAccess.DotNetWatch
+{
+ internal struct DotNetWatchManagedModuleUpdatesWrapper
+ {
+ private readonly ManagedModuleUpdates _instance;
+ private ImmutableArray _lazyUpdates;
+
+ internal DotNetWatchManagedModuleUpdatesWrapper(in ManagedModuleUpdates instance)
+ {
+ _instance = instance;
+ _lazyUpdates = default;
+ }
+
+ public readonly DotNetWatchManagedModuleUpdateStatus Status => (DotNetWatchManagedModuleUpdateStatus)_instance.Status;
+
+ public ImmutableArray Updates
+ {
+ get
+ {
+ if (_lazyUpdates is { IsDefault: false } updates)
+ return updates;
+
+ updates = _instance.Updates.SelectAsArray(update => new DotNetWatchManagedModuleUpdateWrapper(in update));
+ ImmutableInterlocked.InterlockedInitialize(ref _lazyUpdates, updates);
+ return _lazyUpdates;
+ }
+ }
+ }
+}
diff --git a/src/Tools/ExternalAccess/DotNetWatch/Microsoft.CodeAnalysis.ExternalAccess.DotNetWatch.csproj b/src/Tools/ExternalAccess/DotNetWatch/Microsoft.CodeAnalysis.ExternalAccess.DotNetWatch.csproj
new file mode 100644
index 0000000000000..72486e2ab332e
--- /dev/null
+++ b/src/Tools/ExternalAccess/DotNetWatch/Microsoft.CodeAnalysis.ExternalAccess.DotNetWatch.csproj
@@ -0,0 +1,35 @@
+
+
+
+
+ Library
+ Microsoft.CodeAnalysis.ExternalAccess.DotNetWatch
+ netcoreapp3.1
+
+
+ true
+ Microsoft.CodeAnalysis.ExternalAccess.DotNetWatch
+
+ A supporting package for dotnet-watch:
+ https://github.com/dotnet/sdk/tree/master/src/BuiltInTools/dotnet-watch
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Tools/ExternalAccess/DotNetWatch/PublicAPI.Shipped.txt b/src/Tools/ExternalAccess/DotNetWatch/PublicAPI.Shipped.txt
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/src/Tools/ExternalAccess/DotNetWatch/PublicAPI.Unshipped.txt b/src/Tools/ExternalAccess/DotNetWatch/PublicAPI.Unshipped.txt
new file mode 100644
index 0000000000000..8b137891791fe
--- /dev/null
+++ b/src/Tools/ExternalAccess/DotNetWatch/PublicAPI.Unshipped.txt
@@ -0,0 +1 @@
+
diff --git a/src/Tools/ExternalAccess/DotNetWatch/StubManagedEditAndContinueDebuggerService.cs b/src/Tools/ExternalAccess/DotNetWatch/StubManagedEditAndContinueDebuggerService.cs
new file mode 100644
index 0000000000000..d33bfd55a062e
--- /dev/null
+++ b/src/Tools/ExternalAccess/DotNetWatch/StubManagedEditAndContinueDebuggerService.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Immutable;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue;
+
+namespace Microsoft.CodeAnalysis.ExternalAccess.DotNetWatch
+{
+ internal class StubManagedEditAndContinueDebuggerService : IManagedEditAndContinueDebuggerService
+ {
+ public static readonly StubManagedEditAndContinueDebuggerService Instance = new();
+
+ private StubManagedEditAndContinueDebuggerService() { }
+
+ public Task> GetActiveStatementsAsync(CancellationToken cancellationToken)
+ => Task.FromResult(ImmutableArray.Empty);
+
+ public Task GetAvailabilityAsync(Guid moduleVersionId, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(new ManagedEditAndContinueAvailability(ManagedEditAndContinueAvailabilityStatus.Available));
+ }
+
+ public Task> GetCapabilitiesAsync(CancellationToken cancellationToken)
+ {
+ return Task.FromResult(ImmutableArray.Create("Baseline", "AddDefinitionToExistingType", "NewTypeDefinition"));
+ }
+
+ public Task PrepareModuleForUpdateAsync(Guid moduleVersionId, CancellationToken cancellationToken)
+ {
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/Tools/ExternalAccess/FSharpTest/Microsoft.CodeAnalysis.ExternalAccess.FSharp.UnitTests.csproj b/src/Tools/ExternalAccess/FSharpTest/Microsoft.CodeAnalysis.ExternalAccess.FSharp.UnitTests.csproj
index 57fe5be60b1b7..8a9087e9226cc 100644
--- a/src/Tools/ExternalAccess/FSharpTest/Microsoft.CodeAnalysis.ExternalAccess.FSharp.UnitTests.csproj
+++ b/src/Tools/ExternalAccess/FSharpTest/Microsoft.CodeAnalysis.ExternalAccess.FSharp.UnitTests.csproj
@@ -30,6 +30,7 @@
+
diff --git a/src/Tools/ExternalAccess/Xamarin.Remote/Microsoft.CodeAnalysis.ExternalAccess.Xamarin.Remote.csproj b/src/Tools/ExternalAccess/Xamarin.Remote/Microsoft.CodeAnalysis.ExternalAccess.Xamarin.Remote.csproj
index 64ab7a07a2702..99b4f1aafe5ae 100644
--- a/src/Tools/ExternalAccess/Xamarin.Remote/Microsoft.CodeAnalysis.ExternalAccess.Xamarin.Remote.csproj
+++ b/src/Tools/ExternalAccess/Xamarin.Remote/Microsoft.CodeAnalysis.ExternalAccess.Xamarin.Remote.csproj
@@ -25,6 +25,7 @@
+
diff --git a/src/Tools/IdeBenchmarks/IdeBenchmarks.csproj b/src/Tools/IdeBenchmarks/IdeBenchmarks.csproj
index b065f2f981f14..b6ab0e38e59d0 100644
--- a/src/Tools/IdeBenchmarks/IdeBenchmarks.csproj
+++ b/src/Tools/IdeBenchmarks/IdeBenchmarks.csproj
@@ -14,6 +14,7 @@
+
diff --git a/src/Tools/IdeBenchmarks/SQLitePersistentStorageBenchmark.cs b/src/Tools/IdeBenchmarks/SQLitePersistentStorageBenchmark.cs
index 6d3bccd1c90d5..6eba6f1291013 100644
--- a/src/Tools/IdeBenchmarks/SQLitePersistentStorageBenchmark.cs
+++ b/src/Tools/IdeBenchmarks/SQLitePersistentStorageBenchmark.cs
@@ -64,17 +64,14 @@ public void GlobalSetup()
// Explicitly choose the sqlite db to test.
_workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options
- .WithChangedOption(StorageOptions.Database, StorageDatabase.SQLite)));
+ .WithChangedOption(StorageOptions.Database, StorageDatabase.SQLite)
+ .WithChangedOption(StorageOptions.DatabaseMustSucceed, true)));
var connectionPoolService = _workspace.ExportProvider.GetExportedValue();
- _storageService = new SQLitePersistentStorageService(connectionPoolService, new LocationService());
+ _storageService = new SQLitePersistentStorageService(_workspace.Options, connectionPoolService, new LocationService());
var solution = _workspace.CurrentSolution;
_storage = _storageService.GetStorageWorkerAsync(_workspace, SolutionKey.ToSolutionKey(solution), solution, CancellationToken.None).AsTask().GetAwaiter().GetResult();
- if (_storage == NoOpPersistentStorage.Instance)
- {
- throw new InvalidOperationException("We didn't properly get the sqlite storage instance.");
- }
Console.WriteLine("Storage type: " + _storage.GetType());
_document = _workspace.CurrentSolution.Projects.Single().Documents.Single();
diff --git a/src/Tools/IdeCoreBenchmarks/CloudCache/IdeCoreBenchmarksCloudCacheServiceProvider.cs b/src/Tools/IdeCoreBenchmarks/CloudCache/IdeCoreBenchmarksCloudCacheServiceProvider.cs
new file mode 100644
index 0000000000000..d088da456b528
--- /dev/null
+++ b/src/Tools/IdeCoreBenchmarks/CloudCache/IdeCoreBenchmarksCloudCacheServiceProvider.cs
@@ -0,0 +1,41 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Composition;
+using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Host.Mef;
+using Microsoft.CodeAnalysis.Storage;
+using Microsoft.CodeAnalysis.Storage.CloudCache;
+using Microsoft.CodeAnalysis.UnitTests.WorkspaceServices.Mocks;
+
+namespace CloudCache
+{
+ [ExportWorkspaceService(typeof(ICloudCacheStorageServiceFactory), ServiceLayer.Host), Shared]
+ internal class IdeCoreBenchmarksCloudCacheServiceProvider : ICloudCacheStorageServiceFactory
+ {
+ [ImportingConstructor]
+ [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
+ public IdeCoreBenchmarksCloudCacheServiceProvider()
+ {
+ Console.WriteLine($"Instantiated {nameof(IdeCoreBenchmarksCloudCacheServiceProvider)}");
+ }
+
+ public AbstractPersistentStorageService Create(IPersistentStorageLocationService locationService)
+ {
+ return new MockCloudCachePersistentStorageService(
+ locationService, @"C:\github\roslyn", cs =>
+ {
+ if (cs is IAsyncDisposable asyncDisposable)
+ {
+ asyncDisposable.DisposeAsync().AsTask().Wait();
+ }
+ else if (cs is IDisposable disposable)
+ {
+ disposable.Dispose();
+ }
+ });
+ }
+ }
+}
diff --git a/src/Tools/IdeCoreBenchmarks/FindReferencesBenchmarks.cs b/src/Tools/IdeCoreBenchmarks/FindReferencesBenchmarks.cs
index 3e87232092900..cc87d359b680b 100644
--- a/src/Tools/IdeCoreBenchmarks/FindReferencesBenchmarks.cs
+++ b/src/Tools/IdeCoreBenchmarks/FindReferencesBenchmarks.cs
@@ -5,16 +5,20 @@
#nullable disable
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
+using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using AnalyzerRunner;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;
+using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.MSBuild;
using Microsoft.CodeAnalysis.Storage;
@@ -23,77 +27,86 @@ namespace IdeCoreBenchmarks
[MemoryDiagnoser]
public class FindReferencesBenchmarks
{
- private readonly string _solutionPath;
-
- private MSBuildWorkspace _workspace;
-
- public FindReferencesBenchmarks()
- {
- var roslynRoot = Environment.GetEnvironmentVariable(Program.RoslynRootPathEnvVariableName);
- _solutionPath = Path.Combine(roslynRoot, @"C:\github\roslyn\Roslyn.sln");
-
- if (!File.Exists(_solutionPath))
- throw new ArgumentException("Couldn't find Roslyn.sln");
-
- Console.Write("Found roslyn.sln: " + Process.GetCurrentProcess().Id);
- }
-
- [GlobalSetup]
- public void Setup()
- {
- _workspace = AnalyzerRunnerHelper.CreateWorkspace();
- if (_workspace == null)
- throw new ArgumentException("Couldn't create workspace");
-
- _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options
- .WithChangedOption(StorageOptions.Database, StorageDatabase.SQLite)));
-
- Console.WriteLine("Opening roslyn. Attach to: " + Process.GetCurrentProcess().Id);
-
- var start = DateTime.Now;
- _ = _workspace.OpenSolutionAsync(_solutionPath, progress: null, CancellationToken.None).Result;
- Console.WriteLine("Finished opening roslyn: " + (DateTime.Now - start));
-
- // Force a storage instance to be created. This makes it simple to go examine it prior to any operations we
- // perform, including seeing how big the initial string table is.
- var storageService = _workspace.Services.GetService();
- if (storageService == null)
- throw new ArgumentException("Couldn't get storage service");
-
- using var storage = storageService.GetStorageAsync(_workspace.CurrentSolution, CancellationToken.None).AsTask().GetAwaiter().GetResult();
- }
-
- [GlobalCleanup]
- public void Cleanup()
- {
- _workspace?.Dispose();
- _workspace = null;
- }
-
[Benchmark]
public async Task RunFindReferences()
{
- var solution = _workspace.CurrentSolution;
-
- // There might be multiple projects with this name. That's ok. FAR goes and finds all the linked-projects
- // anyways to perform the search on all the equivalent symbols from them. So the end perf cost is the
- // same.
- var project = solution.Projects.First(p => p.AssemblyName == "Microsoft.CodeAnalysis.CSharp");
-
- var start = DateTime.Now;
- var compilation = await project.GetCompilationAsync();
- Console.WriteLine("Time to get first compilation: " + (DateTime.Now - start));
- var type = compilation.GetTypeByMetadataName("Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax.LanguageParser");
- if (type == null)
- throw new Exception("Couldn't find type");
-
- start = DateTime.Now;
- var references = await SymbolFinder.FindReferencesAsync(type, solution);
- Console.WriteLine("Time to find-refs: " + (DateTime.Now - start));
- var refList = references.ToList();
- Console.WriteLine($"References count: {refList.Count}");
- var locations = refList.SelectMany(r => r.Locations).ToList();
- Console.WriteLine($"Locations count: {locations.Count}");
+ try
+ {
+ // QueryVisualStudioInstances returns Visual Studio installations on .NET Framework, and .NET Core SDK
+ // installations on .NET Core. We use the one with the most recent version.
+ var msBuildInstance = MSBuildLocator.QueryVisualStudioInstances().OrderByDescending(x => x.Version).First();
+
+ MSBuildLocator.RegisterInstance(msBuildInstance);
+
+ var roslynRoot = Environment.GetEnvironmentVariable(Program.RoslynRootPathEnvVariableName);
+ var solutionPath = Path.Combine(roslynRoot, @"C:\github\roslyn\Compilers.sln");
+
+ if (!File.Exists(solutionPath))
+ throw new ArgumentException("Couldn't find Compilers.sln");
+
+ Console.Write("Found Compilers.sln: " + Process.GetCurrentProcess().Id);
+
+ var assemblies = MSBuildMefHostServices.DefaultAssemblies
+ .Add(typeof(AnalyzerRunnerHelper).Assembly)
+ .Add(typeof(FindReferencesBenchmarks).Assembly);
+ var services = MefHostServices.Create(assemblies);
+
+ var workspace = MSBuildWorkspace.Create(new Dictionary
+ {
+ // Use the latest language version to force the full set of available analyzers to run on the project.
+ { "LangVersion", "9.0" },
+ }, services);
+
+ if (workspace == null)
+ throw new ArgumentException("Couldn't create workspace");
+
+ workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(workspace.Options
+ .WithChangedOption(StorageOptions.Database, StorageDatabase.SQLite)
+ .WithChangedOption(StorageOptions.DatabaseMustSucceed, true)));
+
+ Console.WriteLine("Opening roslyn. Attach to: " + Process.GetCurrentProcess().Id);
+
+ var start = DateTime.Now;
+ var solution = workspace.OpenSolutionAsync(solutionPath, progress: null, CancellationToken.None).Result;
+ Console.WriteLine("Finished opening roslyn: " + (DateTime.Now - start));
+
+ // Force a storage instance to be created. This makes it simple to go examine it prior to any operations we
+ // perform, including seeing how big the initial string table is.
+ var storageService = workspace.Services.GetService();
+ if (storageService == null)
+ throw new ArgumentException("Couldn't get storage service");
+
+ using (var storage = await storageService.GetStorageAsync(workspace.CurrentSolution, CancellationToken.None))
+ {
+ Console.WriteLine();
+ }
+
+ // There might be multiple projects with this name. That's ok. FAR goes and finds all the linked-projects
+ // anyways to perform the search on all the equivalent symbols from them. So the end perf cost is the
+ // same.
+ var project = solution.Projects.First(p => p.AssemblyName == "Microsoft.CodeAnalysis");
+
+ start = DateTime.Now;
+ var compilation = await project.GetCompilationAsync();
+ Console.WriteLine("Time to get first compilation: " + (DateTime.Now - start));
+ var type = compilation.GetTypeByMetadataName("Microsoft.CodeAnalysis.SyntaxToken");
+ if (type == null)
+ throw new Exception("Couldn't find type");
+
+ Console.WriteLine("Starting find-refs");
+ start = DateTime.Now;
+ var references = await SymbolFinder.FindReferencesAsync(type, solution);
+ Console.WriteLine("Time to find-refs: " + (DateTime.Now - start));
+ var refList = references.ToList();
+ Console.WriteLine($"References count: {refList.Count}");
+ var locations = refList.SelectMany(r => r.Locations).ToList();
+ Console.WriteLine($"Locations count: {locations.Count}");
+ }
+ catch (ReflectionTypeLoadException ex)
+ {
+ foreach (var ex2 in ex.LoaderExceptions)
+ Console.WriteLine(ex2);
+ }
}
}
}
diff --git a/src/Tools/IdeCoreBenchmarks/IdeCoreBenchmarks.csproj b/src/Tools/IdeCoreBenchmarks/IdeCoreBenchmarks.csproj
index 759610bb4ce53..5ece62295b8fd 100644
--- a/src/Tools/IdeCoreBenchmarks/IdeCoreBenchmarks.csproj
+++ b/src/Tools/IdeCoreBenchmarks/IdeCoreBenchmarks.csproj
@@ -10,14 +10,36 @@
AnyCPU
true
+ 9
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs b/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs
index d61028cb6b9ff..164b23ee594c3 100644
--- a/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs
+++ b/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs
@@ -10,13 +10,16 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
+using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using AnalyzerRunner;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;
+using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.MSBuild;
using Microsoft.CodeAnalysis.NavigateTo;
using Microsoft.CodeAnalysis.Storage;
@@ -26,82 +29,95 @@ namespace IdeCoreBenchmarks
[MemoryDiagnoser]
public class NavigateToBenchmarks
{
- private readonly string _solutionPath;
+ [Benchmark]
+ public async Task RunNavigateTo()
+ {
+ try
+ {
+ // QueryVisualStudioInstances returns Visual Studio installations on .NET Framework, and .NET Core SDK
+ // installations on .NET Core. We use the one with the most recent version.
+ var msBuildInstance = MSBuildLocator.QueryVisualStudioInstances().OrderByDescending(x => x.Version).First();
- private MSBuildWorkspace _workspace;
+ MSBuildLocator.RegisterInstance(msBuildInstance);
- public NavigateToBenchmarks()
- {
- // var roslynRoot = Environment.GetEnvironmentVariable(Program.RoslynRootPathEnvVariableName);
- _solutionPath = @"C:\github\roslyn\Roslyn.sln";
+ var roslynRoot = Environment.GetEnvironmentVariable(Program.RoslynRootPathEnvVariableName);
+ var solutionPath = Path.Combine(roslynRoot, @"C:\github\roslyn\Roslyn.sln");
- if (!File.Exists(_solutionPath))
- throw new ArgumentException("Couldn't find Roslyn.sln");
+ if (!File.Exists(solutionPath))
+ throw new ArgumentException("Couldn't find Roslyn.sln");
- Console.Write("Found roslyn.sln");
- }
+ Console.Write("Found Roslyn.sln: " + Process.GetCurrentProcess().Id);
- [GlobalSetup]
- public void Setup()
- {
- _workspace = AnalyzerRunnerHelper.CreateWorkspace();
- if (_workspace == null)
- throw new ArgumentException("Couldn't create workspace");
+ var assemblies = MSBuildMefHostServices.DefaultAssemblies
+ .Add(typeof(AnalyzerRunnerHelper).Assembly)
+ .Add(typeof(FindReferencesBenchmarks).Assembly);
+ var services = MefHostServices.Create(assemblies);
- _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options
- .WithChangedOption(StorageOptions.Database, StorageDatabase.SQLite)));
+ var workspace = MSBuildWorkspace.Create(new Dictionary
+ {
+ // Use the latest language version to force the full set of available analyzers to run on the project.
+ { "LangVersion", "9.0" },
+ }, services);
- Console.WriteLine("Opening roslyn");
- var start = DateTime.Now;
- _ = _workspace.OpenSolutionAsync(_solutionPath, progress: null, CancellationToken.None).Result;
- Console.WriteLine("Finished opening roslyn: " + (DateTime.Now - start));
+ if (workspace == null)
+ throw new ArgumentException("Couldn't create workspace");
- var storageService = _workspace.Services.GetService();
- if (storageService == null)
- throw new ArgumentException("Couldn't get storage service");
+ workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(workspace.Options
+ .WithChangedOption(StorageOptions.Database, StorageDatabase.SQLite)
+ .WithChangedOption(StorageOptions.DatabaseMustSucceed, true)));
- // Force a storage instance to be created. This makes it simple to go examine it prior to any operations we
- // perform, including seeing how big the initial string table is.
- using var storage = storageService.GetStorageAsync(_workspace.CurrentSolution, CancellationToken.None).AsTask().GetAwaiter().GetResult();
- }
+ Console.WriteLine("Opening roslyn. Attach to: " + Process.GetCurrentProcess().Id);
- [GlobalCleanup]
- public void Cleanup()
- {
- _workspace?.Dispose();
- _workspace = null;
- }
+ var start = DateTime.Now;
+ var solution = workspace.OpenSolutionAsync(solutionPath, progress: null, CancellationToken.None).Result;
+ Console.WriteLine("Finished opening roslyn: " + (DateTime.Now - start));
- [Benchmark]
- public async Task RunNavigateTo()
- {
- var sw = new Stopwatch();
- sw.Start();
- var solution = _workspace.CurrentSolution;
- // Search each project with an independent threadpool task.
- var searchTasks = solution.Projects.Select(
- p => Task.Run(() => SearchAsync(p, priorityDocuments: ImmutableArray.Empty), CancellationToken.None)).ToArray();
-
- var result = await Task.WhenAll(searchTasks).ConfigureAwait(false);
- var sum = result.Sum();
- sw.Stop();
-
- Console.WriteLine($"Time: {sw.ElapsedMilliseconds}");
+ // Force a storage instance to be created. This makes it simple to go examine it prior to any operations we
+ // perform, including seeing how big the initial string table is.
+ var storageService = workspace.Services.GetService();
+ if (storageService == null)
+ throw new ArgumentException("Couldn't get storage service");
+
+ using (var storage = await storageService.GetStorageAsync(workspace.CurrentSolution, CancellationToken.None))
+ {
+ Console.WriteLine();
+ }
+
+ Console.WriteLine("Starting navigate to");
+
+ start = DateTime.Now;
+ // Search each project with an independent threadpool task.
+ var searchTasks = solution.Projects.Select(
+ p => Task.Run(() => SearchAsync(p, priorityDocuments: ImmutableArray.Empty), CancellationToken.None)).ToArray();
+
+ var result = await Task.WhenAll(searchTasks).ConfigureAwait(false);
+ var sum = result.Sum();
+
+ start = DateTime.Now;
+ Console.WriteLine("Num results: " + (DateTime.Now - start));
+ }
+ catch (ReflectionTypeLoadException ex)
+ {
+ foreach (var ex2 in ex.LoaderExceptions)
+ Console.WriteLine(ex2);
+ }
}
private async Task SearchAsync(Project project, ImmutableArray priorityDocuments)
{
var service = project.LanguageServices.GetService();
- var count = 0;
+ var results = new List();
await service.SearchProjectAsync(
project, priorityDocuments, "Document", service.KindsProvided,
r =>
{
- Interlocked.Increment(ref count);
+ lock (results)
+ results.Add(r);
+
return Task.CompletedTask;
}, isFullyLoaded: true, CancellationToken.None);
- return count;
+ return results.Count;
}
}
}
diff --git a/src/VisualStudio/CSharp/Impl/Microsoft.VisualStudio.LanguageServices.CSharp.csproj b/src/VisualStudio/CSharp/Impl/Microsoft.VisualStudio.LanguageServices.CSharp.csproj
index 3a8ea68c1b12b..a9c7bd5d5cd12 100644
--- a/src/VisualStudio/CSharp/Impl/Microsoft.VisualStudio.LanguageServices.CSharp.csproj
+++ b/src/VisualStudio/CSharp/Impl/Microsoft.VisualStudio.LanguageServices.CSharp.csproj
@@ -36,8 +36,6 @@
-
-
diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml
index 161d31aa425eb..92ec127b26c8d 100644
--- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml
+++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml
@@ -262,6 +262,13 @@
+
+
+
+
+
diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs
index 8cddfdfe5a172..21bd4256b6dfd 100644
--- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs
+++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs
@@ -126,6 +126,8 @@ public AdvancedOptionPageControl(OptionStore optionStore, IComponentModel compon
BindToOption(ShowHintsForVariablesWithInferredTypes, InlineHintsOptions.ForImplicitVariableTypes, LanguageNames.CSharp);
BindToOption(ShowHintsForLambdaParameterTypes, InlineHintsOptions.ForLambdaParameterTypes, LanguageNames.CSharp);
BindToOption(ShowHintsForImplicitObjectCreation, InlineHintsOptions.ForImplicitObjectCreation, LanguageNames.CSharp);
+
+ BindToOption(ShowInheritanceMargin, FeatureOnOffOptions.ShowInheritanceMargin, LanguageNames.CSharp);
}
// Since this dialog is constructed once for the lifetime of the application and VS Theme can be changed after the application has started,
diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs
index dfd48fad52ff5..635d0a0631d39 100644
--- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs
+++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs
@@ -273,5 +273,11 @@ public static string Enable_all_features_in_opened_files_from_source_generators_
public static string Option_Enable_file_logging_for_diagnostics
=> ServicesVSResources.Enable_file_logging_for_diagnostics;
+
+ public static string Show_inheritance_margin
+ => ServicesVSResources.Show_inheritance_margin;
+
+ public static string Inheritance_Margin_experimental
+ => ServicesVSResources.Inheritance_Margin_experimental;
}
}
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 fcdff9c634dcb..e818b1b37900a 100644
--- a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj
+++ b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj
@@ -49,9 +49,9 @@
-
-
+
+
diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs
index 4c54f7cca1358..09ffc4b65775b 100644
--- a/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs
+++ b/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs
@@ -12,9 +12,11 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
+using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.PersistentStorage;
using Microsoft.CodeAnalysis.Storage;
using Microsoft.CodeAnalysis.Test.Utilities;
+using Microsoft.VisualStudio.LanguageServices.UnitTests;
using Roslyn.Test.Utilities;
using Xunit;
@@ -74,6 +76,9 @@ protected AbstractPersistentStorageTests()
ThreadPool.SetMinThreads(Math.Max(workerThreads, NumThreads), completionPortThreads);
}
+ internal abstract AbstractPersistentStorageService GetStorageService(
+ OptionSet options, IMefHostExportProvider exportProvider, IPersistentStorageLocationService locationService, IPersistentStorageFaultInjector? faultInjector, string rootFolder);
+
public void Dispose()
{
// This should cause the service to release the cached connection it maintains for the primary workspace
@@ -248,24 +253,6 @@ public async Task PersistentService_Document_SimultaneousWrites()
Assert.True(value < NumThreads);
}
- private void DoSimultaneousWrites(Func write)
- {
- var barrier = new Barrier(NumThreads);
- var countdown = new CountdownEvent(NumThreads);
- for (var i = 0; i < NumThreads; i++)
- {
- ThreadPool.QueueUserWorkItem(s =>
- {
- var id = (int)s;
- barrier.SignalAndWait();
- write(id + "").Wait();
- countdown.Signal();
- }, i);
- }
-
- countdown.Wait();
- }
-
[Theory]
[CombinatorialData]
public async Task PersistentService_Solution_SimultaneousReads(Size size, bool withChecksum)
@@ -799,6 +786,26 @@ public void CacheDirectoryShouldNotBeAtRoot()
Assert.False(location?.StartsWith("/") ?? false);
}
+ [Theory]
+ [CombinatorialData]
+ public async Task PersistentService_ReadByteTwice(Size size, bool withChecksum)
+ {
+ var solution = CreateOrOpenSolution();
+ var streamName1 = "PersistentService_ReadByteTwice";
+
+ await using (var storage = await GetStorageAsync(solution))
+ {
+ Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum)));
+ }
+
+ await using (var storage = await GetStorageAsync(solution))
+ {
+ using var stream = await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum));
+ stream.ReadByte();
+ stream.ReadByte();
+ }
+ }
+
[PartNotDiscoverable]
[ExportWorkspaceService(typeof(IPersistentStorageLocationService), layer: ServiceLayer.Test), Shared]
private class TestPersistentStorageLocationService : DefaultPersistentStorageLocationService
@@ -843,13 +850,45 @@ private void DoSimultaneousReads(Func> read, string expectedValue)
Assert.Equal(new List(), exceptions);
}
+ private void DoSimultaneousWrites(Func write)
+ {
+ var barrier = new Barrier(NumThreads);
+ var countdown = new CountdownEvent(NumThreads);
+
+ var exceptions = new List();
+ for (var i = 0; i < NumThreads; i++)
+ {
+ ThreadPool.QueueUserWorkItem(s =>
+ {
+ var id = (int)s;
+ barrier.SignalAndWait();
+ try
+ {
+ write(id + "").Wait();
+ }
+ catch (Exception ex)
+ {
+ lock (exceptions)
+ {
+ exceptions.Add(ex);
+ }
+ }
+ countdown.Signal();
+ }, i);
+ }
+
+ countdown.Wait();
+
+ Assert.Empty(exceptions);
+ }
+
protected Solution CreateOrOpenSolution(bool nullPaths = false)
{
var solutionFile = _persistentFolder.CreateOrOpenFile("Solution1.sln").WriteAllText("");
var info = SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create(), solutionFile.Path);
- var workspace = new AdhocWorkspace();
+ var workspace = new AdhocWorkspace(VisualStudioTestCompositions.LanguageServices.GetHostServices());
workspace.AddSolution(info);
var solution = workspace.CurrentSolution;
@@ -876,13 +915,15 @@ internal async Task GetStorageAsync(
_storageService?.GetTestAccessor().Shutdown();
var locationService = new MockPersistentStorageLocationService(solution.Id, _persistentFolder.Path);
- _storageService = GetStorageService((IMefHostExportProvider)solution.Workspace.Services.HostServices, locationService, faultInjector);
+ _storageService = GetStorageService(
+ solution.Options, (IMefHostExportProvider)solution.Workspace.Services.HostServices,
+ locationService, faultInjector, _persistentFolder.Path);
var storage = await _storageService.GetStorageAsync(solution, checkBranchId: true, CancellationToken.None);
// If we're injecting faults, we expect things to be strange
if (faultInjector == null)
{
- Assert.NotEqual(NoOpPersistentStorage.Instance, storage);
+ Assert.NotEqual(NoOpPersistentStorage.TestAccessor.StorageInstance, storage);
}
return storage;
@@ -895,13 +936,14 @@ internal async Task GetStorageFromKeyAsync(
_storageService?.GetTestAccessor().Shutdown();
var locationService = new MockPersistentStorageLocationService(solutionKey.Id, _persistentFolder.Path);
- _storageService = GetStorageService((IMefHostExportProvider)workspace.Services.HostServices, locationService, faultInjector);
+ _storageService = GetStorageService(
+ workspace.Options, (IMefHostExportProvider)workspace.Services.HostServices, locationService, faultInjector, _persistentFolder.Path);
var storage = await _storageService.GetStorageAsync(workspace, solutionKey, checkBranchId: true, CancellationToken.None);
// If we're injecting faults, we expect things to be strange
if (faultInjector == null)
{
- Assert.NotEqual(NoOpPersistentStorage.Instance, storage);
+ Assert.NotEqual(NoOpPersistentStorage.TestAccessor.StorageInstance, storage);
}
return storage;
@@ -927,8 +969,6 @@ public MockPersistentStorageLocationService(SolutionId solutionId, string storag
=> solutionKey.Id == _solutionId ? _storageLocation : null;
}
- internal abstract AbstractPersistentStorageService GetStorageService(IMefHostExportProvider exportProvider, IPersistentStorageLocationService locationService, IPersistentStorageFaultInjector? faultInjector);
-
protected Stream EncodeString(string text)
{
var bytes = _encoding.GetBytes(text);
@@ -940,14 +980,9 @@ private string ReadStringToEnd(Stream stream)
{
using (stream)
{
- var bytes = new byte[stream.Length];
- var count = 0;
- while (count < stream.Length)
- {
- count = stream.Read(bytes, count, (int)stream.Length - count);
- }
-
- return _encoding.GetString(bytes);
+ using var memoryStream = new MemoryStream();
+ stream.CopyTo(memoryStream);
+ return _encoding.GetString(memoryStream.ToArray());
}
}
}
diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/CloudCachePersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/CloudCachePersistentStorageTests.cs
new file mode 100644
index 0000000000000..feb9fbd8b2fdd
--- /dev/null
+++ b/src/VisualStudio/CSharp/Test/PersistentStorage/CloudCachePersistentStorageTests.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Linq;
+using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
+using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Host.Mef;
+using Microsoft.CodeAnalysis.Options;
+using Microsoft.CodeAnalysis.Storage;
+using Microsoft.CodeAnalysis.UnitTests.WorkspaceServices.Mocks;
+
+namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices
+{
+ public class CloudCachePersistentStorageTests : AbstractPersistentStorageTests
+ {
+ internal override AbstractPersistentStorageService GetStorageService(
+ OptionSet options, IMefHostExportProvider exportProvider, IPersistentStorageLocationService locationService, IPersistentStorageFaultInjector? faultInjector, string relativePathBase)
+ {
+ var threadingContext = exportProvider.GetExports().Single().Value;
+ return new MockCloudCachePersistentStorageService(
+ locationService,
+ relativePathBase,
+ cs =>
+ {
+ if (cs is IAsyncDisposable asyncDisposable)
+ {
+ threadingContext.JoinableTaskFactory.Run(
+ () => asyncDisposable.DisposeAsync().AsTask());
+ }
+ else if (cs is IDisposable disposable)
+ {
+ disposable.Dispose();
+ }
+ });
+ }
+ }
+}
diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/AuthorizationServiceMock.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/AuthorizationServiceMock.cs
new file mode 100644
index 0000000000000..405d6af003f63
--- /dev/null
+++ b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/AuthorizationServiceMock.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// Copy of https://devdiv.visualstudio.com/DevDiv/_git/VS.CloudCache?path=%2Ftest%2FMicrosoft.VisualStudio.Cache.Tests%2FMocks&_a=contents&version=GBmain
+// Try to keep in sync and avoid unnecessary changes here.
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.ServiceHub.Framework.Services;
+
+#pragma warning disable CS0067 // events that are never used
+
+namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices.Mocks
+{
+ internal class AuthorizationServiceMock : IAuthorizationService
+ {
+ public event EventHandler? CredentialsChanged;
+
+ public event EventHandler? AuthorizationChanged;
+
+ internal bool Allow { get; set; } = true;
+
+ public ValueTask CheckAuthorizationAsync(ProtectedOperation operation, CancellationToken cancellationToken = default)
+ {
+ return new ValueTask(this.Allow);
+ }
+
+ public ValueTask> GetCredentialsAsync(CancellationToken cancellationToken = default)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/FileSystemServiceMock.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/FileSystemServiceMock.cs
new file mode 100644
index 0000000000000..58009779502fb
--- /dev/null
+++ b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/FileSystemServiceMock.cs
@@ -0,0 +1,89 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// Copy of https://devdiv.visualstudio.com/DevDiv/_git/VS.CloudCache?path=%2Ftest%2FMicrosoft.VisualStudio.Cache.Tests%2FMocks&_a=contents&version=GBmain
+// Try to keep in sync and avoid unnecessary changes here.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Pipelines;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.ServiceHub.Framework;
+using Microsoft.VisualStudio.RpcContracts.FileSystem;
+
+namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices.Mocks
+{
+ internal class FileSystemServiceMock : IFileSystem
+ {
+ public event EventHandler? DirectoryEntryChanged;
+
+ public event EventHandler? RootEntriesChanged;
+
+ public Task ConvertLocalFileNameToRemoteUriAsync(string fileName, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task ConvertLocalFileNameToRemoteUriAsync(string fileName, string remoteScheme, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task ConvertLocalUriToRemoteUriAsync(Uri localUri, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task ConvertLocalUriToRemoteUriAsync(Uri localUri, string remoteScheme, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task ConvertRemoteFileNameToRemoteUriAsync(string _1, CancellationToken _2) => throw new NotImplementedException();
+
+ public Task ConvertRemoteFileNameToRemoteUriAsync(string _1, string _2, CancellationToken _3) => throw new NotImplementedException();
+
+ public Task ConvertRemoteUriToLocalUriAsync(Uri remoteUri, CancellationToken cancellationToken) => Task.FromResult(remoteUri);
+
+ public Task ConvertRemoteUriToRemoteFileNameAsync(Uri _1, CancellationToken _2) => throw new NotImplementedException();
+
+ public Task CopyAsync(Uri sourceUri, Uri destinationUri, bool overwrite, IProgress? progress, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task CreateDirectoryAsync(Uri uri, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task DeleteAsync(Uri uri, bool recursive, IProgress? progress, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task DownloadFileAsync(Uri remoteUri, IProgress? progress, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public IAsyncEnumerable EnumerateDirectoriesAsync(Uri uri, string searchPattern, SearchOption searchOption, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public IAsyncEnumerable EnumerateDirectoryEntriesAsync(Uri uri, string searchPattern, SearchOption searchOption, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public IAsyncEnumerable EnumerateFilesAsync(Uri uri, string searchPattern, SearchOption searchOption, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task GetDefaultRemoteUriSchemeAsync(CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task GetDisplayInfoAsync(Uri uri, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task GetDisplayInfoAsync(string fileName, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task GetInfoAsync(Uri uri, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task GetMonikerForFileSystemProviderAsync(string scheme, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task GetMonikerForRemoteFileSystemProviderAsync(string scheme, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task> GetRootEntriesAsync(string scheme, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task> GetRootEntriesAsync(CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task> GetSupportedSchemesAsync(CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task MoveAsync(Uri oldUri, Uri newUri, bool overwrite, IProgress? progress, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task ReadFileAsync(Uri uri, PipeWriter writer, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public ValueTask UnwatchAsync(WatchResult watchResult, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public ValueTask WatchDirectoryAsync(Uri uri, bool recursive, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public ValueTask WatchFileAsync(Uri uri, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task WriteFileAsync(Uri uri, PipeReader reader, bool overwrite, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ protected virtual void OnDirectoryEntryChanged(DirectoryEntryChangedEventArgs args) => this.DirectoryEntryChanged?.Invoke(this, args);
+
+ protected virtual void OnRootEntriesChanged(RootEntriesChangedEventArgs args) => this.RootEntriesChanged?.Invoke(this, args);
+ }
+}
diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/MockCloudCachePersistentStorageService.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/MockCloudCachePersistentStorageService.cs
new file mode 100644
index 0000000000000..894eb5b9f9f26
--- /dev/null
+++ b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/MockCloudCachePersistentStorageService.cs
@@ -0,0 +1,61 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Host;
+using Microsoft.ServiceHub.Framework;
+using Microsoft.ServiceHub.Framework.Services;
+using Microsoft.VisualStudio;
+using Microsoft.VisualStudio.Cache;
+using Microsoft.VisualStudio.Cache.SQLite;
+using Microsoft.VisualStudio.LanguageServices.Storage;
+using Microsoft.VisualStudio.RpcContracts.Caching;
+
+namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices.Mocks
+{
+ internal class MockCloudCachePersistentStorageService : AbstractCloudCachePersistentStorageService
+ {
+ private readonly string _relativePathBase;
+ private readonly Action _disposeCacheService;
+
+ public MockCloudCachePersistentStorageService(
+ IPersistentStorageLocationService locationService,
+ string relativePathBase,
+ Action disposeCacheService)
+ : base(locationService)
+ {
+ _relativePathBase = relativePathBase;
+ _disposeCacheService = disposeCacheService;
+ }
+
+ protected override void DisposeCacheService(ICacheService cacheService)
+ => _disposeCacheService(cacheService);
+
+ protected override async ValueTask CreateCacheServiceAsync(CancellationToken cancellationToken)
+ {
+ // Directly access VS' CacheService through their library and not as a brokered service. Then create our
+ // wrapper CloudCacheService directly on that instance.
+ var authorizationServiceClient = new AuthorizationServiceClient(new AuthorizationServiceMock());
+ var solutionService = new SolutionServiceMock();
+ var fileSystem = new FileSystemServiceMock();
+ var serviceBroker = new ServiceBrokerMock()
+ {
+ BrokeredServices =
+ {
+ { VisualStudioServices.VS2019_10.SolutionService.Moniker, solutionService },
+ { VisualStudioServices.VS2019_10.FileSystem.Moniker, fileSystem },
+ { FrameworkServices.Authorization.Moniker, new AuthorizationServiceMock() },
+ },
+ };
+
+ var someContext = new CacheContext { RelativePathBase = _relativePathBase };
+ var pool = new SqliteConnectionPool();
+ var activeContext = await pool.ActivateContextAsync(someContext, default);
+ var cacheService = new CacheService(activeContext, serviceBroker, authorizationServiceClient, pool);
+ return cacheService;
+ }
+ }
+}
diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/OptionServiceMock.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/OptionServiceMock.cs
similarity index 97%
rename from src/VisualStudio/CSharp/Test/PersistentStorage/OptionServiceMock.cs
rename to src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/OptionServiceMock.cs
index 0af88b3beef93..caa140bd3db5b 100644
--- a/src/VisualStudio/CSharp/Test/PersistentStorage/OptionServiceMock.cs
+++ b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/OptionServiceMock.cs
@@ -12,7 +12,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Options;
-namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices
+namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices.Mocks
{
internal class OptionServiceMock : IOptionService
{
diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/ServiceBrokerMock.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/ServiceBrokerMock.cs
new file mode 100644
index 0000000000000..8a32ef1b221b9
--- /dev/null
+++ b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/ServiceBrokerMock.cs
@@ -0,0 +1,41 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// Copy of https://devdiv.visualstudio.com/DevDiv/_git/VS.CloudCache?path=%2Ftest%2FMicrosoft.VisualStudio.Cache.Tests%2FMocks&_a=contents&version=GBmain
+// Try to keep in sync and avoid unnecessary changes here.
+
+using System;
+using System.Collections.Generic;
+using System.IO.Pipelines;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.ServiceHub.Framework;
+
+namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices.Mocks
+{
+ internal class ServiceBrokerMock : IServiceBroker
+ {
+ public event EventHandler? AvailabilityChanged;
+
+ internal Dictionary BrokeredServices { get; } = new();
+
+ public ValueTask GetPipeAsync(ServiceMoniker serviceMoniker, ServiceActivationOptions options = default, CancellationToken cancellationToken = default)
+ {
+ throw new NotImplementedException();
+ }
+
+ public ValueTask GetProxyAsync(ServiceRpcDescriptor serviceDescriptor, ServiceActivationOptions options = default, CancellationToken cancellationToken = default)
+ where T : class
+ {
+ if (this.BrokeredServices.TryGetValue(serviceDescriptor.Moniker, out var service))
+ {
+ return new((T?)service);
+ }
+
+ return default;
+ }
+
+ internal void OnAvailabilityChanged(BrokeredServicesChangedEventArgs args) => this.AvailabilityChanged?.Invoke(this, args);
+ }
+}
diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/SolutionServiceMock.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/SolutionServiceMock.cs
new file mode 100644
index 0000000000000..0819ce75e36df
--- /dev/null
+++ b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/SolutionServiceMock.cs
@@ -0,0 +1,104 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// Copy of https://devdiv.visualstudio.com/DevDiv/_git/VS.CloudCache?path=%2Ftest%2FMicrosoft.VisualStudio.Cache.Tests%2FMocks&_a=contents&version=GBmain
+// Try to keep in sync and avoid unnecessary changes here.
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Threading.Tasks.Dataflow;
+using Microsoft.VisualStudio.RpcContracts.Solution;
+using Microsoft.VisualStudio.Threading;
+
+#pragma warning disable CS0067 // events that are never used
+
+namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices.Mocks
+{
+ internal class SolutionServiceMock : ISolutionService
+ {
+ private readonly BroadcastObservable openContainerObservable = new BroadcastObservable(new OpenCodeContainersState());
+
+ public event EventHandler? ProjectsLoaded;
+
+ public event EventHandler? ProjectsUnloaded;
+
+ public event EventHandler? ProjectLoadProgressChanged;
+
+ internal Uri? SolutionFilePath
+ {
+ get => this.openContainerObservable.Value.SolutionFilePath;
+ set => this.openContainerObservable.Value = this.openContainerObservable.Value with { SolutionFilePath = value };
+ }
+
+ public ValueTask AreProjectsLoadedAsync(Guid[] projectIds, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task SubscribeToOpenCodeContainersStateAsync(IObserver observer, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return Task.FromResult(this.openContainerObservable.Subscribe(observer));
+ }
+
+ public Task GetOpenCodeContainersStateAsync(CancellationToken cancellationToken) => Task.FromResult(this.openContainerObservable.Value);
+
+ public Task CloseSolutionAndFolderAsync(CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public ValueTask> GetPropertyValuesAsync(IReadOnlyList propertyIds, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public ValueTask> GetSolutionTelemetryContextPropertyValuesAsync(IReadOnlyList propertyNames, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public ValueTask LoadProjectsAsync(Guid[] projectIds, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public ValueTask LoadProjectsWithResultAsync(Guid[] projectIds, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public ValueTask RemoveProjectsAsync(IReadOnlyList projectIds, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task RequestProjectEventsAsync(CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public Task SaveSolutionFilterFileAsync(string filterFileDirectory, string filterFileName, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ public ValueTask UnloadProjectsAsync(Guid[] projectIds, ProjectUnloadReason unloadReason, CancellationToken cancellationToken) => throw new NotImplementedException();
+
+ internal void SimulateFolderChange(IReadOnlyList folderPaths) => this.openContainerObservable.Value = this.openContainerObservable.Value with { OpenFolderPaths = folderPaths };
+
+ private class BroadcastObservable : IObservable
+ {
+ private readonly BroadcastBlock sourceBlock = new(v => v);
+ private T value;
+
+ internal BroadcastObservable(T initialValue)
+ {
+ this.sourceBlock.Post(this.value = initialValue);
+ }
+
+ internal T Value
+ {
+ get => this.value;
+ set => this.sourceBlock.Post(this.value = value);
+ }
+
+ public IDisposable Subscribe(IObserver observer)
+ {
+ var actionBlock = new ActionBlock(observer.OnNext);
+ actionBlock.Completion.ContinueWith(
+ static (t, s) =>
+ {
+ var observer = (IObserver)s!;
+ if (t.Exception is object)
+ {
+ observer.OnError(t.Exception);
+ }
+ else
+ {
+ observer.OnCompleted();
+ }
+ },
+ observer,
+ TaskScheduler.Default).Forget();
+ return this.sourceBlock.LinkTo(actionBlock, new DataflowLinkOptions { PropagateCompletion = true });
+ }
+ }
+ }
+}
diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs
index f05948bfd630a..06976afc94576 100644
--- a/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs
+++ b/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs
@@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
+using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.SQLite.v2;
using Microsoft.CodeAnalysis.Storage;
using Xunit;
@@ -21,8 +22,8 @@ namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices
///
public class SQLiteV2PersistentStorageTests : AbstractPersistentStorageTests
{
- internal override AbstractPersistentStorageService GetStorageService(IMefHostExportProvider exportProvider, IPersistentStorageLocationService locationService, IPersistentStorageFaultInjector? faultInjector)
- => new SQLitePersistentStorageService(exportProvider.GetExports().Single().Value, locationService, faultInjector);
+ internal override AbstractPersistentStorageService GetStorageService(OptionSet options, IMefHostExportProvider exportProvider, IPersistentStorageLocationService locationService, IPersistentStorageFaultInjector? faultInjector, string relativePathBase)
+ => new SQLitePersistentStorageService(options, exportProvider.GetExports().Single().Value, locationService, faultInjector);
[Fact]
public async Task TestCrashInNewConnection()
diff --git a/src/VisualStudio/CodeLens/Microsoft.VisualStudio.LanguageServices.CodeLens.csproj b/src/VisualStudio/CodeLens/Microsoft.VisualStudio.LanguageServices.CodeLens.csproj
index 61d2679c5269c..70d0d189eb4cf 100644
--- a/src/VisualStudio/CodeLens/Microsoft.VisualStudio.LanguageServices.CodeLens.csproj
+++ b/src/VisualStudio/CodeLens/Microsoft.VisualStudio.LanguageServices.CodeLens.csproj
@@ -25,6 +25,7 @@
+
diff --git a/src/VisualStudio/Core/Def/Experimentation/IVsExperimentationService.cs b/src/VisualStudio/Core/Def/Experimentation/IVsExperimentationService.cs
deleted file mode 100644
index 239ff6ed51a44..0000000000000
--- a/src/VisualStudio/Core/Def/Experimentation/IVsExperimentationService.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-#nullable disable
-
-using System;
-using System.Runtime.InteropServices;
-
-namespace Microsoft.Internal.VisualStudio.Shell.Interop
-{
- [Guid("DFF66CB5-603C-4716-89BD-24BD0E8C172C")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- internal interface SVsExperimentationService
- {
- }
-}
diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactory.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactory.cs
new file mode 100644
index 0000000000000..2dc733097e183
--- /dev/null
+++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactory.cs
@@ -0,0 +1,55 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Windows;
+using Microsoft.CodeAnalysis.Editor.Host;
+using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
+using Microsoft.VisualStudio.Text.Classification;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Formatting;
+using Roslyn.Utilities;
+
+namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin
+{
+ internal sealed class InheritanceGlyphFactory : IGlyphFactory
+ {
+ private readonly IThreadingContext _threadingContext;
+ private readonly IStreamingFindUsagesPresenter _streamingFindUsagesPresenter;
+ private readonly ClassificationTypeMap _classificationTypeMap;
+ private readonly IClassificationFormatMap _classificationFormatMap;
+ private readonly IWaitIndicator _waitIndicator;
+
+ public InheritanceGlyphFactory(
+ IThreadingContext threadingContext,
+ IStreamingFindUsagesPresenter streamingFindUsagesPresenter,
+ ClassificationTypeMap classificationTypeMap,
+ IClassificationFormatMap classificationFormatMap,
+ IWaitIndicator waitIndicator)
+ {
+ _threadingContext = threadingContext;
+ _streamingFindUsagesPresenter = streamingFindUsagesPresenter;
+ _classificationTypeMap = classificationTypeMap;
+ _classificationFormatMap = classificationFormatMap;
+ _waitIndicator = waitIndicator;
+ }
+
+ public UIElement? GenerateGlyph(IWpfTextViewLine line, IGlyphTag tag)
+ {
+ if (tag is InheritanceMarginTag inheritanceMarginTag)
+ {
+ var membersOnLine = inheritanceMarginTag.MembersOnLine;
+ Contract.ThrowIfTrue(membersOnLine.IsEmpty);
+ return new MarginGlyph.InheritanceMargin(
+ _threadingContext,
+ _streamingFindUsagesPresenter,
+ _classificationTypeMap,
+ _classificationFormatMap,
+ _waitIndicator,
+ inheritanceMarginTag);
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactoryProvider.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactoryProvider.cs
new file mode 100644
index 0000000000000..d5b8d61a07ce0
--- /dev/null
+++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactoryProvider.cs
@@ -0,0 +1,58 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.ComponentModel.Composition;
+using Microsoft.CodeAnalysis.Editor;
+using Microsoft.CodeAnalysis.Editor.Host;
+using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
+using Microsoft.CodeAnalysis.Host.Mef;
+using Microsoft.VisualStudio.Text.Classification;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Tagging;
+using Microsoft.VisualStudio.Utilities;
+
+namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin
+{
+ [Export(typeof(IGlyphFactoryProvider))]
+ [Name(nameof(InheritanceGlyphFactoryProvider))]
+ [ContentType(ContentTypeNames.RoslynContentType)]
+ [TagType(typeof(InheritanceMarginTag))]
+ // This would ensure the margin is clickable.
+ [Order(After = "VsTextMarker")]
+ internal class InheritanceGlyphFactoryProvider : IGlyphFactoryProvider
+ {
+ private readonly IThreadingContext _threadingContext;
+ private readonly IStreamingFindUsagesPresenter _streamingFindUsagesPresenter;
+ private readonly ClassificationTypeMap _classificationTypeMap;
+ private readonly IClassificationFormatMapService _classificationFormatMapService;
+ private readonly IWaitIndicator _waitIndicator;
+
+ [ImportingConstructor]
+ [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
+ public InheritanceGlyphFactoryProvider(
+ IThreadingContext threadingContext,
+ IStreamingFindUsagesPresenter streamingFindUsagesPresenter,
+ ClassificationTypeMap classificationTypeMap,
+ IClassificationFormatMapService classificationFormatMapService,
+ IWaitIndicator waitIndicator)
+ {
+ _threadingContext = threadingContext;
+ _streamingFindUsagesPresenter = streamingFindUsagesPresenter;
+ _classificationTypeMap = classificationTypeMap;
+ _classificationFormatMapService = classificationFormatMapService;
+ _waitIndicator = waitIndicator;
+ }
+
+ public IGlyphFactory GetGlyphFactory(IWpfTextView view, IWpfTextViewMargin margin)
+ {
+ return new InheritanceGlyphFactory(
+ _threadingContext,
+ _streamingFindUsagesPresenter,
+ _classificationTypeMap,
+ _classificationFormatMapService.GetClassificationFormatMap("tooltip"),
+ _waitIndicator);
+ }
+ }
+}
diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginHelpers.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginHelpers.cs
new file mode 100644
index 0000000000000..34a15b3a44a30
--- /dev/null
+++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginHelpers.cs
@@ -0,0 +1,166 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.InheritanceMargin;
+using Microsoft.VisualStudio.Imaging;
+using Microsoft.VisualStudio.Imaging.Interop;
+using Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph;
+using Roslyn.Utilities;
+
+namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin
+{
+ internal static class InheritanceMarginHelpers
+ {
+ ///
+ /// Decide which moniker should be shown.
+ ///
+ public static ImageMoniker GetMoniker(InheritanceRelationship inheritanceRelationship)
+ {
+ // If there are multiple targets and we have the corresponding compound image, use it
+ if (inheritanceRelationship.HasFlag(InheritanceRelationship.ImplementingOverriding))
+ {
+ return KnownMonikers.ImplementingOverriding;
+ }
+
+ if (inheritanceRelationship.HasFlag(InheritanceRelationship.ImplementingOverridden))
+ {
+ return KnownMonikers.ImplementingOverridden;
+ }
+
+ // Otherwise, show the image based on this preference
+ if (inheritanceRelationship.HasFlag(InheritanceRelationship.Implemented))
+ {
+ return KnownMonikers.Implemented;
+ }
+
+ if (inheritanceRelationship.HasFlag(InheritanceRelationship.Implementing))
+ {
+ return KnownMonikers.Implementing;
+ }
+
+ if (inheritanceRelationship.HasFlag(InheritanceRelationship.Overridden))
+ {
+ return KnownMonikers.Overridden;
+ }
+
+ if (inheritanceRelationship.HasFlag(InheritanceRelationship.Overriding))
+ {
+ return KnownMonikers.Overriding;
+ }
+
+ // The relationship is None. Don't know what image should be shown, throws
+ throw ExceptionUtilities.UnexpectedValue(inheritanceRelationship);
+ }
+
+ ///
+ /// Create the view models for the inheritance targets of a single member.
+ /// There are two cases:
+ /// 1. If all the targets have the same inheritance relationship. It would be an array of TargetViewModel
+ /// e.g.
+ /// Target1ViewModel
+ /// Target2ViewModel
+ /// Target3ViewModel
+ ///
+ /// 2. If targets belongs to different inheritance group. It would be grouped.
+ /// e.g.
+ /// Header1ViewModel
+ /// Target1ViewModel
+ /// Target2ViewModel
+ /// Header2ViewModel
+ /// Target1ViewModel
+ /// Target2ViewModel
+ ///
+ public static ImmutableArray CreateMenuItemViewModelsForSingleMember(ImmutableArray targets)
+ {
+ var targetsByRelationship = targets.OrderBy(target => target.DisplayName).GroupBy(target => target.RelationToMember)
+ .ToImmutableDictionary(
+ keySelector: grouping => grouping.Key,
+ elementSelector: grouping => grouping);
+ if (targetsByRelationship.Count == 1)
+ {
+ // If all targets have one relationship.
+ // e.g. interface IBar { void Bar(); }
+ // class A : IBar { void Bar() {} }
+ // class B : IBar { void Bar() {} }
+ // for 'IBar', the margin would be I↓. So header is not needed.
+ var (_, targetItems) = targetsByRelationship.Single();
+ return targetItems.SelectAsArray(target => TargetMenuItemViewModel.Create(target, indent: false)).CastArray();
+ }
+ else
+ {
+ // Otherwise, it means these targets has different relationship,
+ // these targets would be shown in group, and a header should be shown as the first item to indicate the relationship to user.
+ return targetsByRelationship.SelectMany(kvp => CreateMenuItemsWithHeader(kvp.Key, kvp.Value)).ToImmutableArray();
+ }
+ }
+
+ ///
+ /// Create the view models for the inheritance targets of multiple members
+ /// There are two cases:
+ /// 1. If all the targets have the same inheritance relationship. It would have this structure:
+ /// e.g.
+ /// MemberViewModel1 -> Target1ViewModel
+ /// Target2ViewModel
+ /// MemberViewModel2 -> Target4ViewModel
+ /// Target5ViewModel
+ ///
+ /// 2. If targets belongs to different inheritance group. It would be grouped.
+ /// e.g.
+ /// MemberViewModel1 -> HeaderViewModel
+ /// Target1ViewModel
+ /// HeaderViewModel
+ /// Target2ViewModel
+ /// MemberViewModel2 -> HeaderViewModel
+ /// Target4ViewModel
+ /// HeaderViewModel
+ /// Target5ViewModel
+ ///
+ public static ImmutableArray CreateMenuItemViewModelsForMultipleMembers(ImmutableArray members)
+ {
+ Contract.ThrowIfTrue(members.Length <= 1);
+ // For multiple members, check if all the targets have the same inheritance relationship.
+ // If so, then don't add the header, because it is already indicated by the margin.
+ // Otherwise, add the Header.
+ var set = members
+ .SelectMany(member => member.TargetItems.Select(item => item.RelationToMember))
+ .ToImmutableHashSet();
+ if (set.Count == 1)
+ {
+ return members.SelectAsArray(MemberMenuItemViewModel.CreateWithNoHeaderInTargets).CastArray();
+ }
+ else
+ {
+ return members.SelectAsArray(MemberMenuItemViewModel.CreateWithHeaderInTargets).CastArray();
+ }
+ }
+
+ public static ImmutableArray CreateMenuItemsWithHeader(
+ InheritanceRelationship relationship,
+ IEnumerable targets)
+ {
+ using var _ = CodeAnalysis.PooledObjects.ArrayBuilder.GetInstance(out var builder);
+ var displayContent = relationship switch
+ {
+ InheritanceRelationship.Implemented => ServicesVSResources.Implemented_members,
+ InheritanceRelationship.Implementing => ServicesVSResources.Implementing_members,
+ InheritanceRelationship.Overriding => ServicesVSResources.Overriding_members,
+ InheritanceRelationship.Overridden => ServicesVSResources.Overridden_members,
+ _ => throw ExceptionUtilities.UnexpectedValue(relationship)
+ };
+
+ var headerViewModel = new HeaderMenuItemViewModel(displayContent, GetMoniker(relationship), displayContent);
+ builder.Add(headerViewModel);
+ foreach (var targetItem in targets)
+ {
+ builder.Add(TargetMenuItemViewModel.Create(targetItem, indent: true));
+ }
+
+ return builder.ToImmutable();
+ }
+ }
+}
diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginTag.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginTag.cs
new file mode 100644
index 0000000000000..3636b6baf7fb5
--- /dev/null
+++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginTag.cs
@@ -0,0 +1,67 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Immutable;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.InheritanceMargin;
+using Microsoft.VisualStudio.Imaging.Interop;
+using Microsoft.VisualStudio.Text.Editor;
+using Roslyn.Utilities;
+
+namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin
+{
+ internal class InheritanceMarginTag : IGlyphTag
+ {
+ ///
+ /// Margin moniker.
+ ///
+ public ImageMoniker Moniker { get; }
+
+ ///
+ /// Members needs to be shown on this line. There might be multiple members.
+ /// For example:
+ /// interface IBar { void Foo1(); void Foo2(); }
+ /// class Bar : IBar { void Foo1() { } void Foo2() { } }
+ ///
+ public readonly ImmutableArray MembersOnLine;
+
+ ///
+ /// Used for accessibility purpose.
+ ///
+ public readonly int LineNumber;
+
+ public readonly Workspace Workspace;
+
+ public InheritanceMarginTag(Workspace workspace, int lineNumber, ImmutableArray membersOnLine)
+ {
+ Contract.ThrowIfTrue(membersOnLine.IsEmpty);
+
+ Workspace = workspace;
+ LineNumber = lineNumber;
+ MembersOnLine = membersOnLine;
+ // The common case, one line has one member, avoid to use select & aggregate
+ if (membersOnLine.Length == 1)
+ {
+ var member = membersOnLine[0];
+ var targets = member.TargetItems;
+ var relationship = targets[0].RelationToMember;
+ foreach (var target in targets.Skip(1))
+ {
+ relationship |= target.RelationToMember;
+ }
+
+ Moniker = InheritanceMarginHelpers.GetMoniker(relationship);
+ }
+ else
+ {
+ // Multiple members on same line.
+ var aggregateRelationship = membersOnLine
+ .SelectMany(member => member.TargetItems.Select(target => target.RelationToMember))
+ .Aggregate((r1, r2) => r1 | r2);
+ Moniker = InheritanceMarginHelpers.GetMoniker(aggregateRelationship);
+ }
+ }
+ }
+}
diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginTaggerProvider.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginTaggerProvider.cs
new file mode 100644
index 0000000000000..965b5b77a8138
--- /dev/null
+++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginTaggerProvider.cs
@@ -0,0 +1,138 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.ComponentModel.Composition;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Editor;
+using Microsoft.CodeAnalysis.Editor.Implementation.Classification;
+using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
+using Microsoft.CodeAnalysis.Editor.Shared.Options;
+using Microsoft.CodeAnalysis.Editor.Shared.Tagging;
+using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
+using Microsoft.CodeAnalysis.Editor.Tagging;
+using Microsoft.CodeAnalysis.Host.Mef;
+using Microsoft.CodeAnalysis.InheritanceMargin;
+using Microsoft.CodeAnalysis.Shared.Extensions;
+using Microsoft.CodeAnalysis.Shared.TestHooks;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Tagging;
+using Microsoft.VisualStudio.Utilities;
+using Roslyn.Utilities;
+
+namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin
+{
+ [Export(typeof(IViewTaggerProvider))]
+ [TagType(typeof(InheritanceMarginTag))]
+ [ContentType(ContentTypeNames.RoslynContentType)]
+ [Name(nameof(InheritanceMarginTaggerProvider))]
+ internal sealed class InheritanceMarginTaggerProvider : AsynchronousViewTaggerProvider
+ {
+ [ImportingConstructor]
+ [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
+ public InheritanceMarginTaggerProvider(
+ IThreadingContext threadingContext,
+ IAsynchronousOperationListenerProvider listenerProvider,
+ IForegroundNotificationService notificationService) : base(
+ threadingContext,
+ listenerProvider.GetListener(FeatureAttribute.InheritanceMargin),
+ notificationService)
+ {
+ }
+
+ protected override TaggerDelay EventChangeDelay => TaggerDelay.OnIdle;
+
+ protected override ITaggerEventSource CreateEventSource(ITextView textViewOpt, ITextBuffer subjectBuffer)
+ // Because we use frozen-partial documents for semantic classification, we may end up with incomplete
+ // semantics (esp. during solution load). Because of this, we also register to hear when the full
+ // compilation is available so that reclassify and bring ourselves up to date.
+ => new CompilationAvailableTaggerEventSource(
+ subjectBuffer,
+ AsyncListener,
+ TaggerEventSources.OnWorkspaceChanged(subjectBuffer, AsyncListener),
+ TaggerEventSources.OnViewSpanChanged(ThreadingContext, textViewOpt),
+ TaggerEventSources.OnDocumentActiveContextChanged(subjectBuffer),
+ TaggerEventSources.OnOptionChanged(subjectBuffer, FeatureOnOffOptions.ShowInheritanceMargin));
+
+ protected override IEnumerable GetSpansToTag(ITextView textView, ITextBuffer subjectBuffer)
+ {
+ this.AssertIsForeground();
+
+ var visibleSpan = textView.GetVisibleLinesSpan(subjectBuffer, extraLines: 100);
+ if (visibleSpan == null)
+ {
+ return base.GetSpansToTag(textView, subjectBuffer);
+ }
+
+ return SpecializedCollections.SingletonEnumerable(visibleSpan.Value);
+ }
+
+ protected override async Task ProduceTagsAsync(
+ TaggerContext context,
+ DocumentSnapshotSpan spanToTag,
+ int? caretPosition)
+ {
+ var document = spanToTag.Document;
+ if (document == null)
+ {
+ return;
+ }
+
+ var cancellationToken = context.CancellationToken;
+
+ var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
+ var featureEnabled = options.GetOption(FeatureOnOffOptions.ShowInheritanceMargin);
+ if (!featureEnabled)
+ {
+ return;
+ }
+
+ // Use FrozenSemantics Version of document to get the semantics ready, therefore we could have faster
+ // response. (Since the full load might take a long time)
+ // We also subscribe to CompilationAvailableTaggerEventSource, so this will finally reach the correct state.
+ var inheritanceMarginInfoService = document.WithFrozenPartialSemantics(cancellationToken).GetLanguageService();
+ if (inheritanceMarginInfoService == null)
+ {
+ return;
+ }
+
+ var inheritanceMemberItems = await inheritanceMarginInfoService.GetInheritanceMemberItemsAsync(
+ document,
+ spanToTag.SnapshotSpan.Span.ToTextSpan(),
+ cancellationToken).ConfigureAwait(false);
+
+ if (inheritanceMemberItems.IsEmpty)
+ {
+ return;
+ }
+
+ // One line might have multiple members to show, so group them.
+ // For example:
+ // interface IBar { void Foo1(); void Foo2(); }
+ // class Bar : IBar { void Foo1() { } void Foo2() { } }
+ var lineToMembers = inheritanceMemberItems
+ .GroupBy(item => item.LineNumber);
+
+ var snapshot = spanToTag.SnapshotSpan.Snapshot;
+
+ foreach (var (lineNumber, membersOnTheLine) in lineToMembers)
+ {
+ var membersOnTheLineArray = membersOnTheLine.ToImmutableArray();
+
+ // One line should at least have one member on it.
+ Contract.ThrowIfTrue(membersOnTheLineArray.IsEmpty);
+
+ var line = snapshot.GetLineFromLineNumber(lineNumber);
+ // We only care about the line, so just tag the start.
+ context.AddTag(new TagSpan(
+ new SnapshotSpan(snapshot, line.Start, length: 0),
+ new InheritanceMarginTag(document.Project.Solution.Workspace, lineNumber, membersOnTheLineArray)));
+ }
+ }
+ }
+}
diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/HeaderMenuItemViewModel.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/HeaderMenuItemViewModel.cs
new file mode 100644
index 0000000000000..c1cc072ae084f
--- /dev/null
+++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/HeaderMenuItemViewModel.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.VisualStudio.Imaging.Interop;
+
+namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph
+{
+ ///
+ /// The view model used for the header of TargetMenuItemViewModel.
+ /// It is used when the context menu contains targets having multiple inheritance relationship.
+ /// In such case, this would be shown as a header for a group of targets.
+ /// e.g.
+ /// 'I↓ Implemented members'
+ /// Method 'Bar'
+ /// 'I↑ Implementing members'
+ /// Method 'Foo'
+ ///
+ internal class HeaderMenuItemViewModel : InheritanceMenuItemViewModel
+ {
+ public HeaderMenuItemViewModel(string displayContent, ImageMoniker imageMoniker, string automationName)
+ : base(displayContent, imageMoniker, automationName)
+ {
+ }
+ }
+}
diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceContextMenuItemViewModel.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceContextMenuItemViewModel.cs
new file mode 100644
index 0000000000000..a6b9f5e179d4c
--- /dev/null
+++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceContextMenuItemViewModel.cs
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.VisualStudio.Imaging.Interop;
+
+namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph
+{
+ internal abstract class InheritanceMenuItemViewModel
+ {
+ ///
+ /// Display content for the target.
+ ///
+ public string DisplayContent { get; }
+
+ ///
+ /// ImageMoniker shown in the menu.
+ ///
+ public ImageMoniker ImageMoniker { get; }
+
+ ///
+ /// AutomationName for the MenuItem.
+ ///
+ public string AutomationName { get; }
+
+ protected InheritanceMenuItemViewModel(string displayContent, ImageMoniker imageMoniker, string automationName)
+ {
+ ImageMoniker = imageMoniker;
+ DisplayContent = displayContent;
+ AutomationName = automationName;
+ }
+ }
+}
diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml
new file mode 100644
index 0000000000000..69c2ac3fed355
--- /dev/null
+++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml
@@ -0,0 +1,255 @@
+
\ No newline at end of file
diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml.cs
new file mode 100644
index 0000000000000..be1d27c76ab9e
--- /dev/null
+++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml.cs
@@ -0,0 +1,134 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Immutable;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Editor;
+using Microsoft.CodeAnalysis.Editor.GoToDefinition;
+using Microsoft.CodeAnalysis.Editor.Host;
+using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
+using Microsoft.CodeAnalysis.Internal.Log;
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.Text.Classification;
+
+namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph
+{
+ internal partial class InheritanceMargin
+ {
+ private readonly IThreadingContext _threadingContext;
+ private readonly IStreamingFindUsagesPresenter _streamingFindUsagesPresenter;
+ private readonly IWaitIndicator _waitIndicator;
+ private readonly Workspace _workspace;
+
+ public InheritanceMargin(
+ IThreadingContext threadingContext,
+ IStreamingFindUsagesPresenter streamingFindUsagesPresenter,
+ ClassificationTypeMap classificationTypeMap,
+ IClassificationFormatMap classificationFormatMap,
+ IWaitIndicator waitIndicator,
+ InheritanceMarginTag tag)
+ {
+ _threadingContext = threadingContext;
+ _streamingFindUsagesPresenter = streamingFindUsagesPresenter;
+ _workspace = tag.Workspace;
+ _waitIndicator = waitIndicator;
+ InitializeComponent();
+
+ var viewModel = InheritanceMarginViewModel.Create(classificationTypeMap, classificationFormatMap, tag);
+ DataContext = viewModel;
+ ContextMenu.DataContext = viewModel;
+ ToolTip = new ToolTip { Content = viewModel.ToolTipTextBlock, Style = (Style)FindResource("ToolTipStyle") };
+ }
+
+ private void InheritanceMargin_OnClick(object sender, RoutedEventArgs e)
+ {
+ if (this.ContextMenu != null)
+ {
+ this.ContextMenu.IsOpen = true;
+ e.Handled = true;
+ }
+ }
+
+ private void TargetMenuItem_OnClick(object sender, RoutedEventArgs e)
+ {
+ if (e.OriginalSource is MenuItem { DataContext: TargetMenuItemViewModel viewModel })
+ {
+ Logger.Log(FunctionId.InheritanceMargin_NavigateToTarget, KeyValueLogMessage.Create(LogType.UserAction));
+ _waitIndicator.Wait(
+ title: EditorFeaturesResources.Navigating,
+ message: string.Format(ServicesVSResources.Navigate_to_0, viewModel.DisplayContent),
+ allowCancel: true,
+ context => GoToDefinitionHelpers.TryGoToDefinition(
+ ImmutableArray.Create(viewModel.DefinitionItem),
+ _workspace,
+ string.Format(EditorFeaturesResources._0_declarations, viewModel.DisplayContent),
+ _threadingContext,
+ _streamingFindUsagesPresenter,
+ context.CancellationToken));
+ }
+ }
+
+ private void ChangeBorderToHoveringColor()
+ {
+ SetResourceReference(BackgroundProperty, VsBrushes.CommandBarMenuBackgroundGradientKey);
+ SetResourceReference(BorderBrushProperty, VsBrushes.CommandBarMenuBorderKey);
+ }
+
+ private void InheritanceMargin_OnMouseEnter(object sender, MouseEventArgs e)
+ {
+ ChangeBorderToHoveringColor();
+ }
+
+ private void InheritanceMargin_OnMouseLeave(object sender, MouseEventArgs e)
+ {
+ // If the context menu is open, then don't reset the color of the button because we need
+ // the margin looks like being pressed.
+ if (!ContextMenu.IsOpen)
+ {
+ ResetBorderToInitialColor();
+ }
+ }
+
+ private void ContextMenu_OnClose(object sender, RoutedEventArgs e)
+ {
+ ResetBorderToInitialColor();
+ }
+
+ private void ContextMenu_OnOpen(object sender, RoutedEventArgs e)
+ {
+ if (e.OriginalSource is ContextMenu { DataContext: InheritanceMarginViewModel inheritanceMarginViewModel }
+ && inheritanceMarginViewModel.MenuItemViewModels.Any(vm => vm is TargetMenuItemViewModel))
+ {
+ // We have two kinds of context menu. e.g.
+ // 1. [margin] -> Target1
+ // Target2
+ // Target3
+ //
+ // 2. [margin] -> method Bar -> Target1
+ // -> Target2
+ // -> method Foo -> Target3
+ // -> Target4
+ // If the first level of the context menu contains a TargetMenuItemViewModel, it means here it is case 1,
+ // user is viewing the targets menu.
+ Logger.Log(FunctionId.InheritanceMargin_TargetsMenuOpen, KeyValueLogMessage.Create(LogType.UserAction));
+ }
+ }
+
+ private void TargetsSubmenu_OnOpen(object sender, RoutedEventArgs e)
+ {
+ Logger.Log(FunctionId.InheritanceMargin_TargetsMenuOpen, KeyValueLogMessage.Create(LogType.UserAction));
+ }
+
+ private void ResetBorderToInitialColor()
+ {
+ this.Background = Brushes.Transparent;
+ this.BorderBrush = Brushes.Transparent;
+ }
+ }
+}
diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMarginViewModel.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMarginViewModel.cs
new file mode 100644
index 0000000000000..45bb4c41355a3
--- /dev/null
+++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMarginViewModel.cs
@@ -0,0 +1,93 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Immutable;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Documents;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
+using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
+using Microsoft.VisualStudio.Imaging.Interop;
+using Microsoft.VisualStudio.Text.Classification;
+
+namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph
+{
+ internal class InheritanceMarginViewModel
+ {
+ ///
+ /// ImageMoniker used for the margin.
+ ///
+ public ImageMoniker ImageMoniker { get; }
+
+ ///
+ /// Tooltip for the margin.
+ ///
+ public TextBlock ToolTipTextBlock { get; }
+
+ ///
+ /// Text used for automation.
+ ///
+ public string AutomationName { get; }
+
+ ///
+ /// ViewModels for the context menu items.
+ ///
+ public ImmutableArray MenuItemViewModels { get; }
+
+ // Internal for testing purpose
+ internal InheritanceMarginViewModel(
+ ImageMoniker imageMoniker,
+ TextBlock toolTipTextBlock,
+ string automationName,
+ ImmutableArray menuItemViewModels)
+ {
+ ImageMoniker = imageMoniker;
+ ToolTipTextBlock = toolTipTextBlock;
+ AutomationName = automationName;
+ MenuItemViewModels = menuItemViewModels;
+ }
+
+ public static InheritanceMarginViewModel Create(
+ ClassificationTypeMap classificationTypeMap,
+ IClassificationFormatMap classificationFormatMap,
+ InheritanceMarginTag tag)
+ {
+ var members = tag.MembersOnLine;
+ if (members.Length == 1)
+ {
+ var member = tag.MembersOnLine[0];
+
+ // Here we want to show a classified text with loc text,
+ // e.g. 'Bar' is inherited.
+ // But the classified text are inlines, so can't directly use string.format to generate the string
+ var inlines = member.DisplayTexts.ToInlines(classificationFormatMap, classificationTypeMap);
+ var startOfThePlaceholder = ServicesVSResources._0_is_inherited.IndexOf("{0}", StringComparison.Ordinal);
+ var prefixString = ServicesVSResources._0_is_inherited[..startOfThePlaceholder];
+ var suffixString = ServicesVSResources._0_is_inherited[(startOfThePlaceholder + "{0}".Length)..];
+ inlines.Insert(0, new Run(prefixString));
+ inlines.Add(new Run(suffixString));
+ var toolTipTextBlock = inlines.ToTextBlock(classificationFormatMap);
+ toolTipTextBlock.FlowDirection = FlowDirection.LeftToRight;
+
+ var automationName = string.Format(ServicesVSResources._0_is_inherited, member.DisplayTexts.JoinText());
+ var menuItemViewModels = InheritanceMarginHelpers.CreateMenuItemViewModelsForSingleMember(member.TargetItems);
+ return new InheritanceMarginViewModel(tag.Moniker, toolTipTextBlock, automationName, menuItemViewModels);
+ }
+ else
+ {
+ var textBlock = new TextBlock
+ {
+ Text = ServicesVSResources.Multiple_members_are_inherited
+ };
+
+ // Same automation name can't be set for control for accessibility purpose. So add the line number info.
+ var automationName = string.Format(ServicesVSResources.Multiple_members_are_inherited_on_line_0, tag.LineNumber);
+ var menuItemViewModels = InheritanceMarginHelpers.CreateMenuItemViewModelsForMultipleMembers(tag.MembersOnLine);
+ return new InheritanceMarginViewModel(tag.Moniker, textBlock, automationName, menuItemViewModels);
+ }
+ }
+ }
+}
diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/MemberMenuItemViewModel.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/MemberMenuItemViewModel.cs
new file mode 100644
index 0000000000000..b3c55621dda71
--- /dev/null
+++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/MemberMenuItemViewModel.cs
@@ -0,0 +1,73 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Immutable;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Editor.Wpf;
+using Microsoft.CodeAnalysis.InheritanceMargin;
+using Microsoft.VisualStudio.Imaging.Interop;
+using Roslyn.Utilities;
+
+namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph
+{
+ ///
+ /// View model used to display a member in MenuItem. Only used when there are multiple members on the same line.
+ /// e.g.
+ /// interface IBar
+ /// {
+ /// event EventHandler e1, e2
+ /// }
+ /// public class Bar : IBar
+ /// {
+ /// public event EventHandler e1, e2
+ /// }
+ /// And this view model is used to show the first level entry to let the user choose member.
+ ///
+ internal class MemberMenuItemViewModel : InheritanceMenuItemViewModel
+ {
+ ///
+ /// Inheritance Targets for this member.
+ ///
+ public ImmutableArray Targets { get; }
+
+ public MemberMenuItemViewModel(
+ string displayContent,
+ ImageMoniker imageMoniker,
+ string automationName,
+ ImmutableArray targets) : base(displayContent, imageMoniker, automationName)
+ {
+ Targets = targets;
+ }
+
+ public static MemberMenuItemViewModel CreateWithNoHeaderInTargets(InheritanceMarginItem member)
+ {
+ var displayName = member.DisplayTexts.JoinText();
+ return new MemberMenuItemViewModel(
+ displayName,
+ member.Glyph.GetImageMoniker(),
+ displayName,
+ member.TargetItems
+ .OrderBy(item => item.DisplayName)
+ .SelectAsArray(item => TargetMenuItemViewModel.Create(item, indent: false))
+ .CastArray());
+ }
+
+ public static MemberMenuItemViewModel CreateWithHeaderInTargets(InheritanceMarginItem member)
+ {
+ var displayName = member.DisplayTexts.JoinText();
+ var targetsByRelationship = member.TargetItems
+ .OrderBy(item => item.DisplayName)
+ .GroupBy(target => target.RelationToMember)
+ .SelectMany(grouping => InheritanceMarginHelpers.CreateMenuItemsWithHeader(grouping.Key, grouping))
+ .ToImmutableArray();
+
+ return new MemberMenuItemViewModel(
+ displayName,
+ member.Glyph.GetImageMoniker(),
+ displayName,
+ targetsByRelationship);
+ }
+ }
+}
diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/MenuItemContainerTemplateSelector.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/MenuItemContainerTemplateSelector.cs
new file mode 100644
index 0000000000000..ababfe920b829
--- /dev/null
+++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/MenuItemContainerTemplateSelector.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Windows;
+using System.Windows.Controls;
+using Roslyn.Utilities;
+
+namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph
+{
+ internal class MenuItemContainerTemplateSelector : ItemContainerTemplateSelector
+ {
+ // By default, ContextMenu would create same MenuItem for each ViewModel from ItemSource,
+ // this would override the default behavior, and let contextMenu create different MenuItem
+ // based on the ViewModel's type
+ public override DataTemplate SelectTemplate(object item, ItemsControl parentItemsControl)
+ {
+ if (item is HeaderMenuItemViewModel)
+ {
+ // Template for Header
+ return (DataTemplate)parentItemsControl.FindResource("HeaderMenuItemTemplate");
+ }
+
+ if (item is TargetMenuItemViewModel)
+ {
+ // Template for Target
+ return (DataTemplate)parentItemsControl.FindResource("TargetMenuItemTemplate");
+ }
+
+ if (item is MemberMenuItemViewModel)
+ {
+ // Template for member
+ return (DataTemplate)parentItemsControl.FindResource("MemberMenuItemTemplate");
+ }
+
+ throw ExceptionUtilities.Unreachable;
+ }
+ }
+}
diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/TargetMenuItemViewModel.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/TargetMenuItemViewModel.cs
new file mode 100644
index 0000000000000..d8b553509c76a
--- /dev/null
+++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/TargetMenuItemViewModel.cs
@@ -0,0 +1,68 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Windows;
+using Microsoft.CodeAnalysis.Editor.Wpf;
+using Microsoft.CodeAnalysis.FindUsages;
+using Microsoft.CodeAnalysis.InheritanceMargin;
+using Microsoft.VisualStudio.Imaging.Interop;
+
+namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph
+{
+ ///
+ /// View model used to show the MenuItem for inheritance target.
+ ///
+ internal class TargetMenuItemViewModel : InheritanceMenuItemViewModel
+ {
+ ///
+ /// The margin for the default case.
+ ///
+ private static readonly Thickness s_defaultMargin = new Thickness(4, 1, 4, 1);
+
+ ///
+ /// The margin used when this target item needs to be indented when the target is shown with the header.
+ /// e.g.
+ /// 'I↓ Implemented members'
+ /// Method 'Bar'
+ /// 'I↑ Implementing members'
+ /// Method 'Foo'
+ /// It is 22 because the default left margin is 4, and we want to keep the same indentation margin same as solution explorer, which is 18.
+ ///
+ private static readonly Thickness s_indentMargin = new Thickness(22, 1, 4, 1);
+
+ ///
+ /// DefinitionItem used for navigation.
+ ///
+ public DefinitionItem DefinitionItem { get; }
+
+ ///
+ /// Margin for the image moniker.
+ ///
+ public Thickness Margin { get; }
+
+ // Internal for testing purpose
+ internal TargetMenuItemViewModel(
+ string displayContent,
+ ImageMoniker imageMoniker,
+ string automationName,
+ DefinitionItem definitionItem,
+ Thickness margin) : base(displayContent, imageMoniker, automationName)
+ {
+ DefinitionItem = definitionItem;
+ Margin = margin;
+ }
+
+ public static TargetMenuItemViewModel Create(InheritanceTargetItem target, bool indent)
+ {
+ var displayContent = target.DisplayName;
+ var imageMoniker = target.Glyph.GetImageMoniker();
+ return new TargetMenuItemViewModel(
+ displayContent,
+ imageMoniker,
+ displayContent,
+ target.DefinitionItem,
+ indent ? s_indentMargin : s_defaultMargin);
+ }
+ }
+}
diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/AbstractInProcLanguageClient.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/AbstractInProcLanguageClient.cs
index d844c067f3f78..118837d209ffd 100644
--- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/AbstractInProcLanguageClient.cs
+++ b/src/VisualStudio/Core/Def/Implementation/LanguageClient/AbstractInProcLanguageClient.cs
@@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
+using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
@@ -17,6 +18,7 @@
using Microsoft.VisualStudio.LanguageServer.Client;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Microsoft.VisualStudio.LogHub;
+using Microsoft.VisualStudio.RpcContracts.Logging;
using Microsoft.VisualStudio.Shell.ServiceBroker;
using Microsoft.VisualStudio.Threading;
using Nerdbank.Streams;
@@ -195,7 +197,18 @@ internal static async Task CreateAsync(
var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream, jsonMessageFormatter));
var serverTypeName = languageClient.GetType().Name;
- var logger = await CreateLoggerAsync(asyncServiceProvider, serverTypeName, clientName, jsonRpc, cancellationToken).ConfigureAwait(false);
+
+ LogHubLspLogger? logger = null;
+ // In 16.10 preview 2 LogHub moved to MS.VS.Utilities and MS.VS.RpcContracts and the old assembly was removed.
+ // To allow LSP integration tests to run on 16.10 preview 1, we only setup the loghub
+ // logger if the MS.VS.Utilities assembly contains the LogHub types.
+ // FeatureFlags.IFeatureFlags is a known type in the MS.VS.Utilities assembly.
+ // Removal tracked by https://github.com/dotnet/roslyn/issues/52454
+ var traceConfigurationType = typeof(FeatureFlags.IFeatureFlags).Assembly.GetType("Microsoft.VisualStudio.LogHub.TraceConfiguration", throwOnError: false);
+ if (traceConfigurationType != null)
+ {
+ logger = await CreateLoggerAsync(asyncServiceProvider, serverTypeName, clientName, jsonRpc, cancellationToken).ConfigureAwait(false);
+ }
var server = languageClient.Create(
jsonRpc,
@@ -207,6 +220,10 @@ internal static async Task CreateAsync(
return server;
}
+ // Make sure this isn't inlined so these types are only loaded
+ // after the type check in CreateAsync.
+ // Removal tracked by https://github.com/dotnet/roslyn/issues/52454
+ [MethodImpl(MethodImplOptions.NoInlining)]
private static async Task CreateLoggerAsync(
VSShell.IAsyncServiceProvider? asyncServiceProvider,
string serverTypeName,
@@ -224,9 +241,8 @@ internal static async Task CreateAsync(
var service = serviceContainer.GetFullAccessServiceBroker();
var configuration = await TraceConfiguration.CreateTraceConfigurationInstanceAsync(service, cancellationToken).ConfigureAwait(false);
- var traceSource = await configuration.RegisterLogSourceAsync(logId, new LogHub.LoggerOptions(), cancellationToken).ConfigureAwait(false);
-
- traceSource.Switch.Level = SourceLevels.ActivityTracing | SourceLevels.Information;
+ var logOptions = new RpcContracts.Logging.LoggerOptions(new LoggingLevelSettings(SourceLevels.ActivityTracing | SourceLevels.Information));
+ var traceSource = await configuration.RegisterLogSourceAsync(logId, logOptions, cancellationToken).ConfigureAwait(false);
// Associate this trace source with the jsonrpc conduit. This ensures that we can associate logs we report
// with our callers and the operations they are performing.
diff --git a/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs b/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs
index 5f5fda2abed13..f0dc0fef9a964 100644
--- a/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs
+++ b/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs
@@ -537,7 +537,7 @@ private bool TryInsertArgumentCompletionSnippet(SnapshotSpan triggerSpan, Snapsh
var methodName = dataBufferSpan.GetText();
var snippet = CreateMethodCallSnippet(methodName, includeMethod: true, ImmutableArray.Empty, ImmutableDictionary.Empty);
- var doc = new DOMDocumentClass();
+ var doc = (DOMDocument)new DOMDocumentClass();
if (doc.loadXML(snippet.ToString(SaveOptions.OmitDuplicateNamespaces)))
{
if (expansion.InsertSpecificExpansion(doc, textSpan, this, LanguageServiceGuid, pszRelativePath: null, out _state._expansionSession) == VSConstants.S_OK)
@@ -899,7 +899,7 @@ public void MoveToSpecificMethod(IMethodSymbol method, CancellationToken cancell
}
var snippet = CreateMethodCallSnippet(method.Name, includeMethod: false, method.Parameters, newArguments);
- var doc = new DOMDocumentClass();
+ var doc = (DOMDocument)new DOMDocumentClass();
if (doc.loadXML(snippet.ToString(SaveOptions.OmitDuplicateNamespaces)))
{
if (expansion.InsertSpecificExpansion(doc, adjustedTextSpan, this, LanguageServiceGuid, pszRelativePath: null, out _state._expansionSession) == VSConstants.S_OK)
diff --git a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj
index 1eb93e319b038..421f12e0186dd 100644
--- a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj
+++ b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj
@@ -116,63 +116,47 @@
-
-
+
+
-
+
+
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
diff --git a/src/VisualStudio/Core/Def/ServicesVSResources.resx b/src/VisualStudio/Core/Def/ServicesVSResources.resx
index 83dc3efb00276..b1d6303ea7986 100644
--- a/src/VisualStudio/Core/Def/ServicesVSResources.resx
+++ b/src/VisualStudio/Core/Def/ServicesVSResources.resx
@@ -1659,6 +1659,12 @@ I agree to all of the foregoing:
This action cannot be undone. Do you wish to continue?
+
+ Show inheritance margin
+
+
+ Inheritance Margin (experimental)
+
Analyzers
@@ -1710,4 +1716,29 @@ I agree to all of the foregoing:
Search Settings
+
+ Multiple members are inherited
+
+
+ '{0}' is inherited
+
+
+ Navigate to '{0}'
+
+
+ Multiple members are inherited on line {0}
+ Line number info is needed for accessibility purpose.
+
+
+ Implemented members
+
+
+ Implementing members
+
+
+ Overriding members
+
+
+ Overridden members
+
\ No newline at end of file
diff --git a/src/VisualStudio/Core/Def/Storage/AbstractCloudCachePersistentStorageService.cs b/src/VisualStudio/Core/Def/Storage/AbstractCloudCachePersistentStorageService.cs
new file mode 100644
index 0000000000000..db39f0747d521
--- /dev/null
+++ b/src/VisualStudio/Core/Def/Storage/AbstractCloudCachePersistentStorageService.cs
@@ -0,0 +1,54 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.PersistentStorage;
+using Microsoft.CodeAnalysis.Storage;
+using Microsoft.VisualStudio.RpcContracts.Caching;
+using Roslyn.Utilities;
+
+namespace Microsoft.VisualStudio.LanguageServices.Storage
+{
+ internal abstract class AbstractCloudCachePersistentStorageService : AbstractPersistentStorageService
+ {
+ private const string StorageExtension = "CloudCache";
+
+ protected AbstractCloudCachePersistentStorageService(
+ IPersistentStorageLocationService locationService)
+ : base(locationService)
+ {
+ }
+
+ protected abstract void DisposeCacheService(ICacheService cacheService);
+ protected abstract ValueTask CreateCacheServiceAsync(CancellationToken cancellationToken);
+
+ protected sealed override string GetDatabaseFilePath(string workingFolderPath)
+ {
+ Contract.ThrowIfTrue(string.IsNullOrWhiteSpace(workingFolderPath));
+ return Path.Combine(workingFolderPath, StorageExtension);
+ }
+
+ protected sealed override bool ShouldDeleteDatabase(Exception exception)
+ {
+ // CloudCache owns the db, so we don't have to delete anything ourselves.
+ return false;
+ }
+
+ protected sealed override async ValueTask TryOpenDatabaseAsync(
+ SolutionKey solutionKey, string workingFolderPath, string databaseFilePath, CancellationToken cancellationToken)
+ {
+ var cacheService = await this.CreateCacheServiceAsync(cancellationToken).ConfigureAwait(false);
+ var relativePathBase = await cacheService.GetRelativePathBaseAsync(cancellationToken).ConfigureAwait(false);
+ if (string.IsNullOrEmpty(relativePathBase))
+ return null;
+
+ return new CloudCachePersistentStorage(
+ cacheService, solutionKey, workingFolderPath, relativePathBase, databaseFilePath, this.DisposeCacheService);
+ }
+ }
+}
diff --git a/src/VisualStudio/Core/Def/Storage/CloudCachePersistentStorage.cs b/src/VisualStudio/Core/Def/Storage/CloudCachePersistentStorage.cs
new file mode 100644
index 0000000000000..e91587331cae7
--- /dev/null
+++ b/src/VisualStudio/Core/Def/Storage/CloudCachePersistentStorage.cs
@@ -0,0 +1,225 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.IO;
+using System.IO.Pipelines;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.PersistentStorage;
+using Microsoft.CodeAnalysis.PooledObjects;
+using Microsoft.VisualStudio.RpcContracts.Caching;
+using Nerdbank.Streams;
+using Roslyn.Utilities;
+
+namespace Microsoft.VisualStudio.LanguageServices.Storage
+{
+ ///
+ /// Implementation of Roslyn's sitting on top of the platform's cloud storage
+ /// system.
+ ///
+ internal class CloudCachePersistentStorage : AbstractPersistentStorage
+ {
+ private static readonly ObjectPool s_byteArrayPool = new(() => new byte[Checksum.HashSize]);
+
+ ///
+ /// We do not need to store anything specific about the solution in this key as the platform cloud cache is
+ /// already keyed to the current solution. So this just allows us to store values considering that as the root.
+ ///
+ private static readonly CacheContainerKey s_solutionKey = new("Roslyn.Solution");
+
+ ///
+ /// Cache from project green nodes to the container keys we've computed for it (and the documents inside of it).
+ /// We can avoid computing these container keys when called repeatedly for the same projects/documents.
+ ///
+ private static readonly ConditionalWeakTable s_projectToContainerKeyCache = new();
+ private readonly ConditionalWeakTable.CreateValueCallback _projectToContainerKeyCacheCallback;
+
+ ///
+ /// Underlying cache service (owned by platform team) responsible for actual storage and retrieval of data.
+ ///
+ private readonly ICacheService _cacheService;
+ private readonly Action _disposeCacheService;
+
+ public CloudCachePersistentStorage(
+ ICacheService cacheService,
+ SolutionKey solutionKey,
+ string workingFolderPath,
+ string relativePathBase,
+ string databaseFilePath,
+ Action disposeCacheService)
+ : base(workingFolderPath, relativePathBase, databaseFilePath)
+ {
+ _cacheService = cacheService;
+ _disposeCacheService = disposeCacheService;
+ _projectToContainerKeyCacheCallback = ps => new ProjectContainerKeyCache(relativePathBase, ProjectKey.ToProjectKey(solutionKey, ps));
+ }
+
+ public sealed override void Dispose()
+ => _disposeCacheService(_cacheService);
+
+ public sealed override ValueTask DisposeAsync()
+ {
+ if (this._cacheService is IAsyncDisposable asyncDisposable)
+ {
+ return asyncDisposable.DisposeAsync();
+ }
+ else if (this._cacheService is IDisposable disposable)
+ {
+ disposable.Dispose();
+ return ValueTaskFactory.CompletedTask;
+ }
+
+ return ValueTaskFactory.CompletedTask;
+ }
+
+ ///
+ /// Maps our own roslyn key to the appropriate key to use for the cloud cache system. To avoid lots of
+ /// allocations we cache these (weakly) so if the same keys are used we can use the same platform keys.
+ ///
+ private CacheContainerKey? GetContainerKey(ProjectKey projectKey, Project? project)
+ {
+ return project != null
+ ? s_projectToContainerKeyCache.GetValue(project.State, _projectToContainerKeyCacheCallback).ProjectContainerKey
+ : ProjectContainerKeyCache.CreateProjectContainerKey(this.SolutionFilePath, projectKey);
+ }
+
+ ///
+ /// Maps our own roslyn key to the appropriate key to use for the cloud cache system. To avoid lots of
+ /// allocations we cache these (weakly) so if the same keys are used we can use the same platform keys.
+ ///
+ private CacheContainerKey? GetContainerKey(
+ DocumentKey documentKey, Document? document)
+ {
+ return document != null
+ ? s_projectToContainerKeyCache.GetValue(document.Project.State, _projectToContainerKeyCacheCallback).GetDocumentContainerKey(document.State)
+ : ProjectContainerKeyCache.CreateDocumentContainerKey(this.SolutionFilePath, documentKey);
+ }
+
+ public sealed override Task ChecksumMatchesAsync(string name, Checksum checksum, CancellationToken cancellationToken)
+ => ChecksumMatchesAsync(name, checksum, s_solutionKey, cancellationToken);
+
+ protected sealed override Task ChecksumMatchesAsync(ProjectKey projectKey, Project? project, string name, Checksum checksum, CancellationToken cancellationToken)
+ => ChecksumMatchesAsync(name, checksum, GetContainerKey(projectKey, project), cancellationToken);
+
+ protected sealed override Task ChecksumMatchesAsync(DocumentKey documentKey, Document? document, string name, Checksum checksum, CancellationToken cancellationToken)
+ => ChecksumMatchesAsync(name, checksum, GetContainerKey(documentKey, document), cancellationToken);
+
+ private async Task ChecksumMatchesAsync(string name, Checksum checksum, CacheContainerKey? containerKey, CancellationToken cancellationToken)
+ {
+ // If we failed to get a container key (for example, because the client is referencing a file not under the
+ // solution folder) then we can't proceed.
+ if (containerKey == null)
+ return false;
+
+ using var bytes = s_byteArrayPool.GetPooledObject();
+ checksum.WriteTo(bytes.Object);
+
+ return await _cacheService.CheckExistsAsync(new CacheItemKey(containerKey.Value, name) { Version = bytes.Object }, cancellationToken).ConfigureAwait(false);
+ }
+
+ public sealed override Task ReadStreamAsync(string name, Checksum? checksum, CancellationToken cancellationToken)
+ => ReadStreamAsync(name, checksum, s_solutionKey, cancellationToken);
+
+ protected sealed override Task ReadStreamAsync(ProjectKey projectKey, Project? project, string name, Checksum? checksum, CancellationToken cancellationToken)
+ => ReadStreamAsync(name, checksum, GetContainerKey(projectKey, project), cancellationToken);
+
+ protected sealed override Task ReadStreamAsync(DocumentKey documentKey, Document? document, string name, Checksum? checksum, CancellationToken cancellationToken)
+ => ReadStreamAsync(name, checksum, GetContainerKey(documentKey, document), cancellationToken);
+
+ private async Task ReadStreamAsync(string name, Checksum? checksum, CacheContainerKey? containerKey, CancellationToken cancellationToken)
+ {
+ // If we failed to get a container key (for example, because the client is referencing a file not under the
+ // solution folder) then we can't proceed.
+ if (containerKey == null)
+ return null;
+
+ if (checksum == null)
+ {
+ return await ReadStreamAsync(new CacheItemKey(containerKey.Value, name), cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ using var bytes = s_byteArrayPool.GetPooledObject();
+ checksum.WriteTo(bytes.Object);
+
+ return await ReadStreamAsync(new CacheItemKey(containerKey.Value, name) { Version = bytes.Object }, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private async Task ReadStreamAsync(CacheItemKey key, CancellationToken cancellationToken)
+ {
+ var pipe = new Pipe();
+ var result = await _cacheService.TryGetItemAsync(key, pipe.Writer, cancellationToken).ConfigureAwait(false);
+ if (!result)
+ return null;
+
+ // Clients will end up doing blocking reads on the synchronous stream we return from this. This can
+ // negatively impact our calls as that will cause sync blocking on the async work to fill the pipe. To
+ // alleviate that issue, we actually asynchronously read in the entire stream into memory inside the reader
+ // and then pass that out. This should not be a problem in practice as PipeReader internally intelligently
+ // uses and pools reasonable sized buffers, preventing us from exacerbating the GC or causing LOH
+ // allocations.
+ return await AsPrebufferedStreamAsync(pipe.Reader, cancellationToken).ConfigureAwait(false);
+ }
+
+ private static async Task AsPrebufferedStreamAsync(PipeReader pipeReader, CancellationToken cancellationToken = default)
+ {
+ while (true)
+ {
+ // Read and immediately report all bytes as "examined" so that the next ReadAsync call will block till more bytes come in.
+ // The goal here is to force the PipeReader to buffer everything internally (even if it were to exceed its natural writer threshold limit).
+ var readResult = await pipeReader.ReadAsync(cancellationToken).ConfigureAwait(false);
+ pipeReader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
+
+ if (readResult.IsCompleted)
+ {
+ // After having buffered and "examined" all the bytes, the stream returned from PipeReader.AsStream() would fail
+ // because it may not "examine" all bytes at once.
+ // Instead, we'll create our own Stream over just the buffer itself, and recycle the buffers when the stream is disposed
+ // the way the stream returned from PipeReader.AsStream() would have.
+ return new ReadOnlySequenceStream(readResult.Buffer, reader => ((PipeReader)reader!).Complete(), pipeReader);
+ }
+ }
+ }
+
+ public sealed override Task WriteStreamAsync(string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken)
+ => WriteStreamAsync(name, stream, checksum, s_solutionKey, cancellationToken);
+
+ protected sealed override Task WriteStreamAsync(ProjectKey projectKey, Project? project, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken)
+ => WriteStreamAsync(name, stream, checksum, GetContainerKey(projectKey, project), cancellationToken);
+
+ protected sealed override Task WriteStreamAsync(DocumentKey documentKey, Document? document, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken)
+ => WriteStreamAsync(name, stream, checksum, GetContainerKey(documentKey, document), cancellationToken);
+
+ private async Task WriteStreamAsync(string name, Stream stream, Checksum? checksum, CacheContainerKey? containerKey, CancellationToken cancellationToken)
+ {
+ // If we failed to get a container key (for example, because the client is referencing a file not under the
+ // solution folder) then we can't proceed.
+ if (containerKey == null)
+ return false;
+
+ if (checksum == null)
+ {
+ return await WriteStreamAsync(new CacheItemKey(containerKey.Value, name), stream, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ using var bytes = s_byteArrayPool.GetPooledObject();
+ checksum.WriteTo(bytes.Object);
+
+ return await WriteStreamAsync(new CacheItemKey(containerKey.Value, name) { Version = bytes.Object }, stream, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private async Task WriteStreamAsync(CacheItemKey key, Stream stream, CancellationToken cancellationToken)
+ {
+ await _cacheService.SetItemAsync(key, PipeReader.Create(stream), shareable: false, cancellationToken).ConfigureAwait(false);
+ return true;
+ }
+ }
+}
diff --git a/src/VisualStudio/Core/Def/Storage/Nerdbank/ReadOnlySequenceStream.cs b/src/VisualStudio/Core/Def/Storage/Nerdbank/ReadOnlySequenceStream.cs
new file mode 100644
index 0000000000000..ede5b8938ee2a
--- /dev/null
+++ b/src/VisualStudio/Core/Def/Storage/Nerdbank/ReadOnlySequenceStream.cs
@@ -0,0 +1,262 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// Copied from https://raw.githubusercontent.com/AArnott/Nerdbank.Streams/2b142fa6a38b15e4b06ecc53bf073aa49fd1de34/src/Nerdbank.Streams/ReadOnlySequenceStream.cs
+// Remove once we move to Nerdbank.Streams 2.7.62-alpha
+
+namespace Nerdbank.Streams
+{
+ using System;
+ using System.Buffers;
+ using System.IO;
+ using System.Runtime.InteropServices;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Microsoft;
+
+ internal class ReadOnlySequenceStream : Stream, IDisposableObservable
+ {
+ private static readonly Task TaskOfZero = Task.FromResult(0);
+
+ private readonly Action
-
-
-
+
+
-
@@ -66,7 +64,8 @@
-
+
+
diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/Microsoft.VisualStudio.LanguageServices.IntegrationTests.csproj b/src/VisualStudio/IntegrationTest/IntegrationTests/Microsoft.VisualStudio.LanguageServices.IntegrationTests.csproj
index 87bb4074494a9..edf25b4b891f7 100644
--- a/src/VisualStudio/IntegrationTest/IntegrationTests/Microsoft.VisualStudio.LanguageServices.IntegrationTests.csproj
+++ b/src/VisualStudio/IntegrationTest/IntegrationTests/Microsoft.VisualStudio.LanguageServices.IntegrationTests.csproj
@@ -45,8 +45,6 @@
-
-
-
+
\ No newline at end of file
diff --git a/src/VisualStudio/IntegrationTest/TestSetup/Microsoft.VisualStudio.IntegrationTest.Setup.csproj b/src/VisualStudio/IntegrationTest/TestSetup/Microsoft.VisualStudio.IntegrationTest.Setup.csproj
index c1f8b6dcec200..bd1ddf3aa572e 100644
--- a/src/VisualStudio/IntegrationTest/TestSetup/Microsoft.VisualStudio.IntegrationTest.Setup.csproj
+++ b/src/VisualStudio/IntegrationTest/TestSetup/Microsoft.VisualStudio.IntegrationTest.Setup.csproj
@@ -49,6 +49,7 @@
+
diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/SolutionExplorer_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/SolutionExplorer_InProc.cs
index d36c4ea0a618a..9e68733059678 100644
--- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/SolutionExplorer_InProc.cs
+++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/SolutionExplorer_InProc.cs
@@ -982,11 +982,11 @@ private EnvDTE.ProjectItem GetProjectItem(string projectName, string relativeFil
var fullFilePath = Path.Combine(projectPath, relativeFilePath);
var projectItems = project.ProjectItems.Cast();
- var document = projectItems.FirstOrDefault(d => d.FileNames[1].Equals(fullFilePath));
+ var document = projectItems.FirstOrDefault(d => d.get_FileNames(1).Equals(fullFilePath));
if (document == null)
{
- throw new InvalidOperationException($"File '{fullFilePath}' could not be found. Available files: {string.Join(", ", projectItems.Select(x => x.FileNames[1]))}.");
+ throw new InvalidOperationException($"File '{fullFilePath}' could not be found. Available files: {string.Join(", ", projectItems.Select(x => x.get_FileNames(1)))}.");
}
return document;
diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/StartPage_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/StartPage_InProc.cs
index ab351adf9ab43..74dd20d0afe09 100644
--- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/StartPage_InProc.cs
+++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/StartPage_InProc.cs
@@ -71,6 +71,6 @@ public bool CloseWindow()
}
private EnvDTE.Property GetProperty()
- => GetDTE().Properties["Environment", "Startup"].Item("OnStartUp");
+ => GetDTE().get_Properties("Environment", "Startup").Item("OnStartUp");
}
}
diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/Microsoft.VisualStudio.IntegrationTest.Utilities.csproj b/src/VisualStudio/IntegrationTest/TestUtilities/Microsoft.VisualStudio.IntegrationTest.Utilities.csproj
index 82e4dee9630b2..7c91f06deed4f 100644
--- a/src/VisualStudio/IntegrationTest/TestUtilities/Microsoft.VisualStudio.IntegrationTest.Utilities.csproj
+++ b/src/VisualStudio/IntegrationTest/TestUtilities/Microsoft.VisualStudio.IntegrationTest.Utilities.csproj
@@ -31,25 +31,19 @@
-
-
-
+
-
-
-
-
diff --git a/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspace.cs b/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspace.cs
index 829283c6afaeb..97b58e48a51ec 100644
--- a/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspace.cs
+++ b/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspace.cs
@@ -181,7 +181,9 @@ private async Task UpdatePathsToRemoteFilesAsync(CollaborationSession session)
{
// The local root is something like tmp\\xxx\\
// The external root should be tmp\\xxx\\~external, so replace the workspace name with ~external.
+#pragma warning disable CS8602 // Dereference of a possibly null reference. (Can localRoot be null here?)
var splitRoot = localRoot.TrimEnd('\\').Split('\\');
+#pragma warning restore CS8602 // Dereference of a possibly null reference.
splitRoot[splitRoot.Length - 1] = "~external";
var externalPath = string.Join("\\", splitRoot) + "\\";
@@ -388,7 +390,9 @@ public override void OpenDocument(DocumentId documentId, bool activate = true)
}
_threadingContext.JoinableTaskFactory.Run(async () =>
{
+#pragma warning disable CS8604 // Possible null reference argument. (Can ConvertLocalPathToSharedUri return null here?)
await _session.DownloadFileAsync(_session.ConvertLocalPathToSharedUri(doc.FilePath), CancellationToken.None).ConfigureAwait(true);
+#pragma warning restore CS8604 // Possible null reference argument.
});
var logicalView = Guid.Empty;
diff --git a/src/VisualStudio/LiveShare/Impl/Microsoft.VisualStudio.LanguageServices.LiveShare.csproj b/src/VisualStudio/LiveShare/Impl/Microsoft.VisualStudio.LanguageServices.LiveShare.csproj
index 4d2c8418a42c7..8d1c38c8bd5cf 100644
--- a/src/VisualStudio/LiveShare/Impl/Microsoft.VisualStudio.LanguageServices.LiveShare.csproj
+++ b/src/VisualStudio/LiveShare/Impl/Microsoft.VisualStudio.LanguageServices.LiveShare.csproj
@@ -22,6 +22,9 @@
+
+
+
diff --git a/src/VisualStudio/LiveShare/Test/Microsoft.VisualStudio.LanguageServices.LiveShare.UnitTests.csproj b/src/VisualStudio/LiveShare/Test/Microsoft.VisualStudio.LanguageServices.LiveShare.UnitTests.csproj
index dcf59acd6261c..47232bf6e17c5 100644
--- a/src/VisualStudio/LiveShare/Test/Microsoft.VisualStudio.LanguageServices.LiveShare.UnitTests.csproj
+++ b/src/VisualStudio/LiveShare/Test/Microsoft.VisualStudio.LanguageServices.LiveShare.UnitTests.csproj
@@ -8,8 +8,9 @@
UnitTest
+
+
-
diff --git a/src/VisualStudio/Setup.Dependencies/Roslyn.VisualStudio.Setup.Dependencies.csproj b/src/VisualStudio/Setup.Dependencies/Roslyn.VisualStudio.Setup.Dependencies.csproj
index 3c418f29027c4..16f3da046acaf 100644
--- a/src/VisualStudio/Setup.Dependencies/Roslyn.VisualStudio.Setup.Dependencies.csproj
+++ b/src/VisualStudio/Setup.Dependencies/Roslyn.VisualStudio.Setup.Dependencies.csproj
@@ -49,6 +49,7 @@
+
@@ -61,7 +62,6 @@
-