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? disposeAction; + private readonly object? disposeActionArg; + + /// + /// A reusable task if two consecutive reads return the same number of bytes. + /// + private Task? lastReadTask; + + private readonly ReadOnlySequence readOnlySequence; + + private SequencePosition position; + + internal ReadOnlySequenceStream(ReadOnlySequence readOnlySequence, Action? disposeAction, object? disposeActionArg) + { + this.readOnlySequence = readOnlySequence; + this.disposeAction = disposeAction; + this.disposeActionArg = disposeActionArg; + this.position = readOnlySequence.Start; + } + + /// + public override bool CanRead => !this.IsDisposed; + + /// + public override bool CanSeek => !this.IsDisposed; + + /// + public override bool CanWrite => false; + + /// + public override long Length => this.ReturnOrThrowDisposed(this.readOnlySequence.Length); + + /// + public override long Position + { + get => this.readOnlySequence.Slice(0, this.position).Length; + set + { + Requires.Range(value >= 0, nameof(value)); + this.position = this.readOnlySequence.GetPosition(value, this.readOnlySequence.Start); + } + } + + /// + public bool IsDisposed { get; private set; } + + /// + public override void Flush() => this.ThrowDisposedOr(new NotSupportedException()); + + /// + public override Task FlushAsync(CancellationToken cancellationToken) => throw this.ThrowDisposedOr(new NotSupportedException()); + + /// + public override int Read(byte[] buffer, int offset, int count) + { + var remaining = this.readOnlySequence.Slice(this.position); + var toCopy = remaining.Slice(0, Math.Min(count, remaining.Length)); + this.position = toCopy.End; + toCopy.CopyTo(buffer.AsSpan(offset, count)); + return (int)toCopy.Length; + } + + /// + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var bytesRead = this.Read(buffer, offset, count); + if (bytesRead == 0) + { + return TaskOfZero; + } + + if (this.lastReadTask?.Result == bytesRead) + { + return this.lastReadTask; + } + else + { + return this.lastReadTask = Task.FromResult(bytesRead); + } + } + + /// + public override int ReadByte() + { + var remaining = this.readOnlySequence.Slice(this.position); + if (remaining.Length > 0) + { + var result = remaining.First.Span[0]; + this.position = this.readOnlySequence.GetPosition(1, this.position); + return result; + } + else + { + return -1; + } + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + Verify.NotDisposed(this); + + SequencePosition relativeTo; + switch (origin) + { + case SeekOrigin.Begin: + relativeTo = this.readOnlySequence.Start; + break; + case SeekOrigin.Current: + if (offset >= 0) + { + relativeTo = this.position; + } + else + { + relativeTo = this.readOnlySequence.Start; + offset += this.Position; + } + + break; + case SeekOrigin.End: + if (offset >= 0) + { + relativeTo = this.readOnlySequence.End; + } + else + { + relativeTo = this.readOnlySequence.Start; + offset += this.Position; + } + + break; + default: + throw new ArgumentOutOfRangeException(nameof(origin)); + } + + this.position = this.readOnlySequence.GetPosition(offset, relativeTo); + return this.Position; + } + + /// + public override void SetLength(long value) => this.ThrowDisposedOr(new NotSupportedException()); + + /// + public override void Write(byte[] buffer, int offset, int count) => this.ThrowDisposedOr(new NotSupportedException()); + + /// + public override void WriteByte(byte value) => this.ThrowDisposedOr(new NotSupportedException()); + + /// + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => throw this.ThrowDisposedOr(new NotSupportedException()); + + /// + public override async Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + foreach (var segment in this.readOnlySequence) + { + await WriteAsync(destination, segment, cancellationToken).ConfigureAwait(false); + } + } + + private static ValueTask WriteAsync(Stream stream, ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + Requires.NotNull(stream, nameof(stream)); + + if (MemoryMarshal.TryGetArray(buffer, out var array)) + { + return new ValueTask(stream.WriteAsync(array.Array!, array.Offset, array.Count, cancellationToken)); + } + else + { + var sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); + buffer.Span.CopyTo(sharedBuffer); + return new ValueTask(FinishWriteAsync(stream.WriteAsync(sharedBuffer, 0, buffer.Length, cancellationToken), sharedBuffer)); + } + + async Task FinishWriteAsync(Task writeTask, byte[] localBuffer) + { + try + { + await writeTask.ConfigureAwait(false); + } + finally + { + ArrayPool.Shared.Return(localBuffer); + } + } + } + +#if SPAN_BUILTIN + + /// + public override int Read(Span buffer) + { + ReadOnlySequence remaining = this.readOnlySequence.Slice(this.position); + ReadOnlySequence toCopy = remaining.Slice(0, Math.Min(buffer.Length, remaining.Length)); + this.position = toCopy.End; + toCopy.CopyTo(buffer); + return (int)toCopy.Length; + } + + /// + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new ValueTask(this.Read(buffer.Span)); + } + + /// + public override void Write(ReadOnlySpan buffer) => throw this.ThrowDisposedOr(new NotSupportedException()); + + /// + public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) => throw this.ThrowDisposedOr(new NotSupportedException()); + +#endif + + /// + protected override void Dispose(bool disposing) + { + if (!this.IsDisposed) + { + this.IsDisposed = true; + this.disposeAction?.Invoke(this.disposeActionArg); + base.Dispose(disposing); + } + } + + private T ReturnOrThrowDisposed(T value) + { + Verify.NotDisposed(this); + return value; + } + + private Exception ThrowDisposedOr(Exception ex) + { + Verify.NotDisposed(this); + throw ex; + } + } +} diff --git a/src/VisualStudio/Core/Def/Storage/ProjectContainerKeyCache.cs b/src/VisualStudio/Core/Def/Storage/ProjectContainerKeyCache.cs new file mode 100644 index 0000000000000..2d8bbc75d725d --- /dev/null +++ b/src/VisualStudio/Core/Def/Storage/ProjectContainerKeyCache.cs @@ -0,0 +1,110 @@ +// 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.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.PersistentStorage; +using Microsoft.VisualStudio.RpcContracts.Caching; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Storage +{ + /// + /// Cache of our own internal roslyn storage keys to the equivalent platform cloud cache keys. Cloud cache keys can + /// store a lot of date in them (like their 'dimensions' dictionary. We don't want to continually recreate these as + /// we read/write date to the db. + /// + internal class ProjectContainerKeyCache + { + private static readonly ImmutableSortedDictionary EmptyDimensions = ImmutableSortedDictionary.Create(StringComparer.Ordinal); + + /// + /// Container key explicitly for the project itself. + /// + public readonly CacheContainerKey? ProjectContainerKey; + + /// + /// Cache from document green nodes to the container keys we've computed for it. We can avoid computing these + /// container keys when called repeatedly for the same documents. + /// + /// + /// We can use a normal Dictionary here instead of a as + /// instances of are always owned in a context where the is alive. As that instance is alive, all s the project + /// points at will be held alive strongly too. + /// + private readonly Dictionary _documentToContainerKey = new(); + private readonly Func _documentToContainerKeyCallback; + + public ProjectContainerKeyCache(string relativePathBase, ProjectKey projectKey) + { + ProjectContainerKey = CreateProjectContainerKey(relativePathBase, projectKey); + + _documentToContainerKeyCallback = ds => CreateDocumentContainerKey(relativePathBase, DocumentKey.ToDocumentKey(projectKey, ds)); + } + + public CacheContainerKey? GetDocumentContainerKey(TextDocumentState state) + { + lock (_documentToContainerKey) + return _documentToContainerKey.GetOrAdd(state, _documentToContainerKeyCallback); + } + + public static CacheContainerKey? CreateProjectContainerKey( + string relativePathBase, ProjectKey projectKey) + { + // Creates a container key for this project. The container key is a mix of the project's name, relative + // file path (to the solution), and optional parse options. + + // If we don't have a valid solution path, we can't store anything. + if (string.IsNullOrEmpty(relativePathBase)) + return null; + + // We have to have a file path for this project + if (RoslynString.IsNullOrEmpty(projectKey.FilePath)) + return null; + + // The file path has to be relative to the base path the DB is associated with (either the solution-path or + // repo-path). + var relativePath = PathUtilities.GetRelativePath(relativePathBase, projectKey.FilePath!); + if (relativePath == projectKey.FilePath) + return null; + + var dimensions = EmptyDimensions + .Add($"{nameof(ProjectKey)}.{nameof(ProjectKey.Name)}", projectKey.Name) + .Add($"{nameof(ProjectKey)}.{nameof(ProjectKey.FilePath)}", relativePath) + .Add($"{nameof(ProjectKey)}.{nameof(ProjectKey.ParseOptionsChecksum)}", projectKey.ParseOptionsChecksum.ToString()); + + return new CacheContainerKey("Roslyn.Project", dimensions); + } + + public static CacheContainerKey? CreateDocumentContainerKey( + string relativePathBase, + DocumentKey documentKey) + { + // See if we can get a project key for this info. If not, we def can't get a doc key. + var projectContainerKey = CreateProjectContainerKey(relativePathBase, documentKey.Project); + if (projectContainerKey == null) + return null; + + // We have to have a file path for this document + if (string.IsNullOrEmpty(documentKey.FilePath)) + return null; + + // The file path has to be relative to the base path the DB is associated with (either the solution-path or + // repo-path). + var relativePath = PathUtilities.GetRelativePath(relativePathBase, documentKey.FilePath!); + if (relativePath == documentKey.FilePath) + return null; + + var dimensions = projectContainerKey.Value.Dimensions + .Add($"{nameof(DocumentKey)}.{nameof(DocumentKey.Name)}", documentKey.Name) + .Add($"{nameof(DocumentKey)}.{nameof(DocumentKey.FilePath)}", relativePath); + + return new CacheContainerKey("Roslyn.Document", dimensions); + } + } +} diff --git a/src/VisualStudio/Core/Def/Storage/VisualStudioCloudCacheStorageService.cs b/src/VisualStudio/Core/Def/Storage/VisualStudioCloudCacheStorageService.cs new file mode 100644 index 0000000000000..6ef9fa3714c54 --- /dev/null +++ b/src/VisualStudio/Core/Def/Storage/VisualStudioCloudCacheStorageService.cs @@ -0,0 +1,56 @@ +// 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.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host; +using Microsoft.VisualStudio.RpcContracts.Caching; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.ServiceBroker; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Storage +{ + internal class VisualStudioCloudCacheStorageService : AbstractCloudCachePersistentStorageService + { + private readonly IAsyncServiceProvider _serviceProvider; + private readonly IThreadingContext _threadingContext; + + public VisualStudioCloudCacheStorageService(IAsyncServiceProvider serviceProvider, IThreadingContext threadingContext, IPersistentStorageLocationService locationService) + : base(locationService) + { + _serviceProvider = serviceProvider; + _threadingContext = threadingContext; + } + + protected sealed override void DisposeCacheService(ICacheService cacheService) + { + if (cacheService is IAsyncDisposable asyncDisposable) + { + _threadingContext.JoinableTaskFactory.Run( + () => asyncDisposable.DisposeAsync().AsTask()); + } + else if (cacheService is IDisposable disposable) + { + disposable.Dispose(); + } + } + + protected sealed override async ValueTask CreateCacheServiceAsync(CancellationToken cancellationToken) + { + var serviceContainer = await _serviceProvider.GetServiceAsync().ConfigureAwait(false); + var serviceBroker = serviceContainer.GetFullAccessServiceBroker(); + +#pragma warning disable ISB001 // Dispose of proxies + // cache service will be disposed inside VisualStudioCloudCachePersistentStorage.Dispose + var cacheService = await serviceBroker.GetProxyAsync(VisualStudioServices.VS2019_10.CacheService, cancellationToken: cancellationToken).ConfigureAwait(false); +#pragma warning restore ISB001 // Dispose of proxies + + Contract.ThrowIfNull(cacheService); + return cacheService; + } + } +} diff --git a/src/VisualStudio/Core/Def/Storage/VisualStudioCloudCacheStorageServiceFactory.cs b/src/VisualStudio/Core/Def/Storage/VisualStudioCloudCacheStorageServiceFactory.cs new file mode 100644 index 0000000000000..da2e9afbae3de --- /dev/null +++ b/src/VisualStudio/Core/Def/Storage/VisualStudioCloudCacheStorageServiceFactory.cs @@ -0,0 +1,35 @@ +// 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.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Storage; +using Microsoft.CodeAnalysis.Storage.CloudCache; +using Microsoft.VisualStudio.Shell; + +namespace Microsoft.VisualStudio.LanguageServices.Storage +{ + [ExportWorkspaceService(typeof(ICloudCacheStorageServiceFactory), ServiceLayer.Host), Shared] + internal class VisualStudioCloudCacheStorageServiceFactory : ICloudCacheStorageServiceFactory + { + private readonly IAsyncServiceProvider _serviceProvider; + private readonly IThreadingContext _threadingContext; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public VisualStudioCloudCacheStorageServiceFactory( + IThreadingContext threadingContext, + SVsServiceProvider serviceProvider) + { + _threadingContext = threadingContext; + _serviceProvider = (IAsyncServiceProvider)serviceProvider; + } + + public AbstractPersistentStorageService Create(IPersistentStorageLocationService locationService) + => new VisualStudioCloudCacheStorageService(_serviceProvider, _threadingContext, locationService); + } +} diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf index 3ae7b11963116..1dc83dbc67874 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf @@ -372,6 +372,16 @@ Id + + Implemented members + Implemented members + + + + Implementing members + Implementing members + + In other operators V jiných operátorech @@ -397,6 +407,11 @@ Indexováno v úložišti + + Inheritance Margin (experimental) + Inheritance Margin (experimental) + + Inline Hints (experimental) Vložené nápovědy (experimentální) @@ -492,6 +507,16 @@ Přesunout do oboru názvů + + Multiple members are inherited + Multiple members are inherited + + + + Multiple members are inherited on line {0} + Multiple members are inherited on line {0} + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. Název koliduje s existujícím názvem typu. @@ -657,6 +682,11 @@ Pravidla pojmenování + + Navigate to '{0}' + Navigate to '{0}' + + Never if unnecessary Nikdy, pokud jsou nadbytečné @@ -717,6 +747,16 @@ Jiné + + Overridden members + Overridden members + + + + Overriding members + Overriding members + + Packages Packages @@ -987,6 +1027,11 @@ Zobrazit nápovědy pro proměnné s odvozenými typy + + Show inheritance margin + Show inheritance margin + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Některé barvy barevného schématu se přepsaly změnami na stránce možností Prostředí > Písma a barvy. Pokud chcete zrušit všechna přizpůsobení, vyberte na stránce Písma a barvy možnost Použít výchozí. @@ -1287,6 +1332,11 @@ Není platnou hodnotou. + + '{0}' is inherited + '{0}' is inherited + + '{0}' will be changed to abstract. {0} se změní na abstraktní. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf index 2155808978efb..3c9d60681517e 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf @@ -372,6 +372,16 @@ Id + + Implemented members + Implemented members + + + + Implementing members + Implementing members + + In other operators In anderen Operatoren @@ -397,6 +407,11 @@ In Repository indiziert + + Inheritance Margin (experimental) + Inheritance Margin (experimental) + + Inline Hints (experimental) Inlinehinweise (experimentell) @@ -492,6 +507,16 @@ In Namespace verschieben + + Multiple members are inherited + Multiple members are inherited + + + + Multiple members are inherited on line {0} + Multiple members are inherited on line {0} + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. Der Name verursacht einen Konflikt mit einem vorhandenen Typnamen. @@ -657,6 +682,11 @@ Benennungsregeln + + Navigate to '{0}' + Navigate to '{0}' + + Never if unnecessary Nie, wenn nicht erforderlich @@ -717,6 +747,16 @@ Andere + + Overridden members + Overridden members + + + + Overriding members + Overriding members + + Packages Packages @@ -987,6 +1027,11 @@ Hinweise für Variablen mit abgeleiteten Typen anzeigen + + Show inheritance margin + Show inheritance margin + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Einige Farbschemafarben werden durch Änderungen überschrieben, die auf der Optionsseite "Umgebung" > "Schriftarten und Farben" vorgenommen wurden. Wählen Sie auf der Seite "Schriftarten und Farben" die Option "Standardwerte verwenden" aus, um alle Anpassungen rückgängig zu machen. @@ -1287,6 +1332,11 @@ Kein gültiger Wert. + + '{0}' is inherited + '{0}' is inherited + + '{0}' will be changed to abstract. "{0}" wird in abstrakten Wert geändert. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf index 41acd11ecbadb..6a5548165a2d2 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf @@ -372,6 +372,16 @@ Id + + Implemented members + Implemented members + + + + Implementing members + Implementing members + + In other operators En otros operadores @@ -397,6 +407,11 @@ Indexado en el repositorio + + Inheritance Margin (experimental) + Inheritance Margin (experimental) + + Inline Hints (experimental) Sugerencias en línea (experimental) @@ -492,6 +507,16 @@ Mover a espacio de nombres + + Multiple members are inherited + Multiple members are inherited + + + + Multiple members are inherited on line {0} + Multiple members are inherited on line {0} + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. El nombre está en conflicto con un nombre de tipo que ya existía. @@ -657,6 +682,11 @@ Reglas de nomenclatura + + Navigate to '{0}' + Navigate to '{0}' + + Never if unnecessary Nunca si es innecesario @@ -717,6 +747,16 @@ Otros + + Overridden members + Overridden members + + + + Overriding members + Overriding members + + Packages Packages @@ -987,6 +1027,11 @@ Mostrar sugerencias para las variables con tipos inferidos + + Show inheritance margin + Show inheritance margin + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Algunos de los colores de la combinación se reemplazan por los cambios realizados en la página de opciones de Entorno > Fuentes y colores. Elija "Usar valores predeterminados" en la página Fuentes y colores para revertir todas las personalizaciones. @@ -1287,6 +1332,11 @@ No es un valor válido + + '{0}' is inherited + '{0}' is inherited + + '{0}' will be changed to abstract. "{0}" se cambiará a abstracto. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf index 7d56df997df62..3617dc386faf3 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf @@ -372,6 +372,16 @@ Id + + Implemented members + Implemented members + + + + Implementing members + Implementing members + + In other operators Dans les autres opérateurs @@ -397,6 +407,11 @@ Indexé dans le dépôt + + Inheritance Margin (experimental) + Inheritance Margin (experimental) + + Inline Hints (experimental) Indicateurs inline (expérimental) @@ -492,6 +507,16 @@ Déplacer vers un espace de noms + + Multiple members are inherited + Multiple members are inherited + + + + Multiple members are inherited on line {0} + Multiple members are inherited on line {0} + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. Le nom est en conflit avec un nom de type existant. @@ -657,6 +682,11 @@ Règles de nommage + + Navigate to '{0}' + Navigate to '{0}' + + Never if unnecessary Jamais si ce n'est pas nécessaire @@ -717,6 +747,16 @@ Autres + + Overridden members + Overridden members + + + + Overriding members + Overriding members + + Packages Packages @@ -987,6 +1027,11 @@ Afficher les indicateurs pour les variables ayant des types déduits + + Show inheritance margin + Show inheritance margin + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Certaines couleurs du modèle de couleurs sont substituées à la suite des changements apportés dans la page d'options Environnement > Polices et couleurs. Choisissez Utiliser les valeurs par défaut dans la page Polices et couleurs pour restaurer toutes les personnalisations. @@ -1287,6 +1332,11 @@ Valeur non valide + + '{0}' is inherited + '{0}' is inherited + + '{0}' will be changed to abstract. '{0}' va être changé en valeur abstraite. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf index 0c9dcd2b313e0..626705f24a7ba 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf @@ -372,6 +372,16 @@ Id + + Implemented members + Implemented members + + + + Implementing members + Implementing members + + In other operators In altri operatori @@ -397,6 +407,11 @@ Indicizzata nel repository + + Inheritance Margin (experimental) + Inheritance Margin (experimental) + + Inline Hints (experimental) Suggerimenti inline (sperimentale) @@ -492,6 +507,16 @@ Sposta nello spazio dei nomi + + Multiple members are inherited + Multiple members are inherited + + + + Multiple members are inherited on line {0} + Multiple members are inherited on line {0} + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. Il nome è in conflitto con un nome di tipo esistente. @@ -657,6 +682,11 @@ Regole di denominazione + + Navigate to '{0}' + Navigate to '{0}' + + Never if unnecessary Mai se non necessario @@ -717,6 +747,16 @@ Altri + + Overridden members + Overridden members + + + + Overriding members + Overriding members + + Packages Packages @@ -987,6 +1027,11 @@ Mostra suggerimenti per variabili con tipi dedotti + + Show inheritance margin + Show inheritance margin + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Alcuni colori della combinazione colori sono sostituiti dalle modifiche apportate nella pagina di opzioni Ambiente > Tipi di carattere e colori. Scegliere `Usa impostazioni predefinite` nella pagina Tipi di carattere e colori per ripristinare tutte le personalizzazioni. @@ -1287,6 +1332,11 @@ Valore non valido + + '{0}' is inherited + '{0}' is inherited + + '{0}' will be changed to abstract. '{0}' verrà modificato in astratto. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf index 9f403459d60de..d765678df13e8 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf @@ -372,6 +372,16 @@ Id + + Implemented members + Implemented members + + + + Implementing members + Implementing members + + In other operators その他の演算子内で @@ -397,6 +407,11 @@ リポジトリ内でインデックス付け + + Inheritance Margin (experimental) + Inheritance Margin (experimental) + + Inline Hints (experimental) インラインのヒント (試験段階) @@ -492,6 +507,16 @@ 名前空間に移動します + + Multiple members are inherited + Multiple members are inherited + + + + Multiple members are inherited on line {0} + Multiple members are inherited on line {0} + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. 名前が既存の型名と競合します。 @@ -657,6 +682,11 @@ 名前付けルール + + Navigate to '{0}' + Navigate to '{0}' + + Never if unnecessary 不必要なら保持しない @@ -717,6 +747,16 @@ その他 + + Overridden members + Overridden members + + + + Overriding members + Overriding members + + Packages Packages @@ -987,6 +1027,11 @@ 推論された型の変数のヒントを表示する + + Show inheritance margin + Show inheritance margin + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. 一部の配色パターンの色は、[環境] > [フォントおよび色] オプション ページで行われた変更によって上書きされます。[フォントおよび色] オプション ページで [既定値を使用] を選択すると、すべてのカスタマイズが元に戻ります。 @@ -1287,6 +1332,11 @@ 有効な値ではありません + + '{0}' is inherited + '{0}' is inherited + + '{0}' will be changed to abstract. '{0}' は抽象に変更されます。 diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf index 12c2d88dc8db2..b8fa6784d349b 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf @@ -372,6 +372,16 @@ Id + + Implemented members + Implemented members + + + + Implementing members + Implementing members + + In other operators 기타 연산자 @@ -397,6 +407,11 @@ 리포지토리에서 인덱싱됨 + + Inheritance Margin (experimental) + Inheritance Margin (experimental) + + Inline Hints (experimental) 인라인 힌트(실험적) @@ -492,6 +507,16 @@ 네임스페이스로 이동 + + Multiple members are inherited + Multiple members are inherited + + + + Multiple members are inherited on line {0} + Multiple members are inherited on line {0} + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. 이름이 기존 형식 이름과 충돌합니다. @@ -657,6 +682,11 @@ 명명 규칙 + + Navigate to '{0}' + Navigate to '{0}' + + Never if unnecessary 필요한 경우 사용 안 함 @@ -717,6 +747,16 @@ 기타 + + Overridden members + Overridden members + + + + Overriding members + Overriding members + + Packages Packages @@ -987,6 +1027,11 @@ 유추된 형식의 변수에 대한 힌트 표시 + + Show inheritance margin + Show inheritance margin + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. 색 구성표의 일부 색이 [환경] > [글꼴 및 색] 옵션 페이지에서 변경한 내용에 따라 재정의됩니다. 모든 사용자 지정을 되돌리려면 [글꼴 및 색] 페이지에서 '기본값 사용'을 선택하세요. @@ -1287,6 +1332,11 @@ 값이 잘못되었습니다. + + '{0}' is inherited + '{0}' is inherited + + '{0}' will be changed to abstract. '{0}'이(가) 추상으로 변경됩니다. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf index de49570730153..13e1cfe42d569 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf @@ -372,6 +372,16 @@ Id + + Implemented members + Implemented members + + + + Implementing members + Implementing members + + In other operators W innych operatorach @@ -397,6 +407,11 @@ Indeksowane w repozytorium + + Inheritance Margin (experimental) + Inheritance Margin (experimental) + + Inline Hints (experimental) Wskazówki w tekście (eksperymentalne) @@ -492,6 +507,16 @@ Przenieś do przestrzeni nazw + + Multiple members are inherited + Multiple members are inherited + + + + Multiple members are inherited on line {0} + Multiple members are inherited on line {0} + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. Wystąpił konflikt z nazwą istniejącego typu. @@ -657,6 +682,11 @@ Reguły nazewnictwa + + Navigate to '{0}' + Navigate to '{0}' + + Never if unnecessary Nigdy, jeśli niepotrzebne @@ -717,6 +747,16 @@ Inne + + Overridden members + Overridden members + + + + Overriding members + Overriding members + + Packages Packages @@ -987,6 +1027,11 @@ Pokaż wskazówki dla zmiennych z wnioskowanymi typami + + Show inheritance margin + Show inheritance margin + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Niektóre kolory w schemacie kolorów są przesłaniane przez zmiany wprowadzone na stronie opcji Środowisko > Czcionki i kolory. Wybierz pozycję „Użyj ustawień domyślnych” na stronie Czcionki i kolory, aby wycofać wszystkie dostosowania. @@ -1287,6 +1332,11 @@ Nieprawidłowa wartość + + '{0}' is inherited + '{0}' is inherited + + '{0}' will be changed to abstract. Element „{0}” zostanie zmieniony na abstrakcyjny. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf index a42dfc7faea74..9a4c1d98000d4 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf @@ -372,6 +372,16 @@ Id + + Implemented members + Implemented members + + + + Implementing members + Implementing members + + In other operators Em outros operadores @@ -397,6 +407,11 @@ Indexado no repositório + + Inheritance Margin (experimental) + Inheritance Margin (experimental) + + Inline Hints (experimental) Dicas Embutidas (experimental) @@ -492,6 +507,16 @@ Mover para o Namespace + + Multiple members are inherited + Multiple members are inherited + + + + Multiple members are inherited on line {0} + Multiple members are inherited on line {0} + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. O nome está em conflito com um nome de tipo existente. @@ -657,6 +682,11 @@ Regras de nomenclatura + + Navigate to '{0}' + Navigate to '{0}' + + Never if unnecessary Nunca se desnecessário @@ -717,6 +747,16 @@ Outros + + Overridden members + Overridden members + + + + Overriding members + Overriding members + + Packages Packages @@ -987,6 +1027,11 @@ Mostrar as dicas para as variáveis com tipos inferidos + + Show inheritance margin + Show inheritance margin + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Algumas cores do esquema de cores estão sendo substituídas pelas alterações feitas na página de Ambiente > Opções de Fontes e Cores. Escolha 'Usar Padrões' na página Fontes e Cores para reverter todas as personalizações. @@ -1287,6 +1332,11 @@ O valor não é válido + + '{0}' is inherited + '{0}' is inherited + + '{0}' will be changed to abstract. '{0}' será alterado para abstrato. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf index d31490962c834..20dacc85c6ef0 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf @@ -372,6 +372,16 @@ Id + + Implemented members + Implemented members + + + + Implementing members + Implementing members + + In other operators В других операторах @@ -397,6 +407,11 @@ Индексированный в репозитории + + Inheritance Margin (experimental) + Inheritance Margin (experimental) + + Inline Hints (experimental) Встроенные подсказки (экспериментальная функция) @@ -492,6 +507,16 @@ Переместить в пространство имен + + Multiple members are inherited + Multiple members are inherited + + + + Multiple members are inherited on line {0} + Multiple members are inherited on line {0} + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. Имя конфликтует с существующим именем типа. @@ -657,6 +682,11 @@ Правила именования + + Navigate to '{0}' + Navigate to '{0}' + + Never if unnecessary Никогда, если не требуется @@ -717,6 +747,16 @@ Другие + + Overridden members + Overridden members + + + + Overriding members + Overriding members + + Packages Packages @@ -987,6 +1027,11 @@ Отображать подсказки для переменных с выводимыми типами + + Show inheritance margin + Show inheritance margin + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Некоторые цвета цветовой схемы переопределяются изменениями, сделанными на странице "Среда" > "Шрифты и цвета". Выберите "Использовать значения по умолчанию" на странице "Шрифты и цвета", чтобы отменить все настройки. @@ -1287,6 +1332,11 @@ Недопустимое значение + + '{0}' is inherited + '{0}' is inherited + + '{0}' will be changed to abstract. Элемент "{0}" будет изменен на абстрактный. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf index 4cf6d06688bda..e9c8936b86795 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf @@ -372,6 +372,16 @@ Id + + Implemented members + Implemented members + + + + Implementing members + Implementing members + + In other operators Diğer işleçlerde @@ -397,6 +407,11 @@ Depo içinde dizini oluşturulmuş + + Inheritance Margin (experimental) + Inheritance Margin (experimental) + + Inline Hints (experimental) Satır İçi İpuçları (deneysel) @@ -492,6 +507,16 @@ Ad Alanına Taşı + + Multiple members are inherited + Multiple members are inherited + + + + Multiple members are inherited on line {0} + Multiple members are inherited on line {0} + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. Ad, mevcut bir tür adıyla çakışıyor. @@ -657,6 +682,11 @@ Adlandırma kuralları + + Navigate to '{0}' + Navigate to '{0}' + + Never if unnecessary Gereksizse hiçbir zaman @@ -717,6 +747,16 @@ Diğer + + Overridden members + Overridden members + + + + Overriding members + Overriding members + + Packages Packages @@ -987,6 +1027,11 @@ Çıkarsanan türlere sahip değişkenler için ipuçlarını göster + + Show inheritance margin + Show inheritance margin + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. Bazı renk düzeni renkleri, Ortam > Yazı Tipleri ve Renkler seçenek sayfasında yapılan değişiklikler tarafından geçersiz kılınıyor. Tüm özelleştirmeleri geri döndürmek için Yazı Tipleri ve Renkler sayfasında `Varsayılanları Kullan` seçeneğini belirleyin. @@ -1287,6 +1332,11 @@ Geçerli bir değer değil + + '{0}' is inherited + '{0}' is inherited + + '{0}' will be changed to abstract. '{0}' soyut olacak şekilde değiştirildi. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf index 3e029f8b2f630..91dcdc12c5904 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf @@ -372,6 +372,16 @@ Id + + Implemented members + Implemented members + + + + Implementing members + Implementing members + + In other operators 在其他运算符中 @@ -397,6 +407,11 @@ 已在存储库中编入索引 + + Inheritance Margin (experimental) + Inheritance Margin (experimental) + + Inline Hints (experimental) 内联提示(实验性) @@ -492,6 +507,16 @@ 移动到命名空间 + + Multiple members are inherited + Multiple members are inherited + + + + Multiple members are inherited on line {0} + Multiple members are inherited on line {0} + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. 名称与现有类型名称相冲突。 @@ -657,6 +682,11 @@ 命名规则 + + Navigate to '{0}' + Navigate to '{0}' + + Never if unnecessary 从不(若无必要) @@ -717,6 +747,16 @@ 其他 + + Overridden members + Overridden members + + + + Overriding members + Overriding members + + Packages Packages @@ -987,6 +1027,11 @@ 显示具有推断类型的变量的提示 + + Show inheritance margin + Show inheritance margin + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. 在“环境”>“字体和颜色”选项页中所做的更改将替代某些配色方案颜色。在“字体和颜色”页中选择“使用默认值”,还原所有自定义项。 @@ -1287,6 +1332,11 @@ 值无效 + + '{0}' is inherited + '{0}' is inherited + + '{0}' will be changed to abstract. “{0}”将更改为“抽象”。 diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf index 91c882e517e78..9068d0b8da32d 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf @@ -372,6 +372,16 @@ Id + + Implemented members + Implemented members + + + + Implementing members + Implementing members + + In other operators 其他運算子中 @@ -397,6 +407,11 @@ 已在存放庫中編制索引 + + Inheritance Margin (experimental) + Inheritance Margin (experimental) + + Inline Hints (experimental) 內嵌提示 (實驗性) @@ -492,6 +507,16 @@ 移到命名空間 + + Multiple members are inherited + Multiple members are inherited + + + + Multiple members are inherited on line {0} + Multiple members are inherited on line {0} + Line number info is needed for accessibility purpose. + Name conflicts with an existing type name. 名稱與現有類型名稱衝突。 @@ -657,6 +682,11 @@ 命名規則 + + Navigate to '{0}' + Navigate to '{0}' + + Never if unnecessary 不需要時一律不要 @@ -717,6 +747,16 @@ 其他 + + Overridden members + Overridden members + + + + Overriding members + Overriding members + + Packages Packages @@ -987,6 +1027,11 @@ 顯示有推斷類型之變數的提示 + + Show inheritance margin + Show inheritance margin + + Some color scheme colors are being overridden by changes made in the Environment > Fonts and Colors options page. Choose `Use Defaults` in the Fonts and Colors page to revert all customizations. [環境] > [字型和色彩選項] 頁面中所做的變更覆寫了某些色彩配置的色彩。請選擇 [字型和色彩] 頁面中的 [使用預設] 來還原所有自訂。 @@ -1287,6 +1332,11 @@ 值無效 + + '{0}' is inherited + '{0}' is inherited + + '{0}' will be changed to abstract. '{0}' 會變更為抽象。 diff --git a/src/VisualStudio/Core/Impl/CodeModel/ExternalElements/AbstractExternalCodeElement.cs b/src/VisualStudio/Core/Impl/CodeModel/ExternalElements/AbstractExternalCodeElement.cs index 32b69ac2df3c8..14f882703b53c 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/ExternalElements/AbstractExternalCodeElement.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/ExternalElements/AbstractExternalCodeElement.cs @@ -11,7 +11,6 @@ using System.Xml; using System.Xml.Linq; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel.Collections; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; diff --git a/src/VisualStudio/Core/Impl/Microsoft.VisualStudio.LanguageServices.Implementation.csproj b/src/VisualStudio/Core/Impl/Microsoft.VisualStudio.LanguageServices.Implementation.csproj index cde29808efc67..c4ed864ce58e3 100644 --- a/src/VisualStudio/Core/Impl/Microsoft.VisualStudio.LanguageServices.Implementation.csproj +++ b/src/VisualStudio/Core/Impl/Microsoft.VisualStudio.LanguageServices.Implementation.csproj @@ -37,9 +37,8 @@ - - + diff --git a/src/VisualStudio/Core/Test.Next/Roslyn.VisualStudio.Next.UnitTests.csproj b/src/VisualStudio/Core/Test.Next/Roslyn.VisualStudio.Next.UnitTests.csproj index cae53df8bb283..71349e579c5de 100644 --- a/src/VisualStudio/Core/Test.Next/Roslyn.VisualStudio.Next.UnitTests.csproj +++ b/src/VisualStudio/Core/Test.Next/Roslyn.VisualStudio.Next.UnitTests.csproj @@ -52,7 +52,8 @@ - + + diff --git a/src/VisualStudio/Core/Test/CodeModel/CSharp/CodeClassTests.vb b/src/VisualStudio/Core/Test/CodeModel/CSharp/CodeClassTests.vb index ca048a9757d1f..d7107e65bf376 100644 --- a/src/VisualStudio/Core/Test/CodeModel/CSharp/CodeClassTests.vb +++ b/src/VisualStudio/Core/Test/CodeModel/CSharp/CodeClassTests.vb @@ -1431,7 +1431,7 @@ static partial class C #Region "IsDerivedFrom tests" - + Public Sub TestIsDerivedFromObject_Explicit() Dim code = @@ -1441,7 +1441,7 @@ class $$C : object { } TestIsDerivedFrom(code, "System.Object", True) End Sub - + Public Sub TestIsDerivedFrom_ObjectImplicit() Dim code = @@ -1451,7 +1451,7 @@ class $$C { } TestIsDerivedFrom(code, "System.Object", True) End Sub - + Public Sub TestIsDerivedFrom_NotString() Dim code = @@ -1461,7 +1461,7 @@ class $$C { } TestIsDerivedFrom(code, "System.String", False) End Sub - + Public Sub TestIsDerivedFrom_NotNonexistent() Dim code = @@ -1471,7 +1471,7 @@ class $$C { } TestIsDerivedFrom(code, "System.ThisIsClearlyNotARealClassName", False) End Sub - + Public Sub TestIsDerivedFrom_UserClassInGlobalNamespace() Dim code = @@ -1482,7 +1482,7 @@ class $$C : B { } TestIsDerivedFrom(code, "B", True) End Sub - + Public Sub TestIsDerivedFrom_UserClassInSameNamespace() Dim code = @@ -1496,7 +1496,7 @@ namespace NS TestIsDerivedFrom(code, "NS.B", True) End Sub - + Public Sub TestIsDerivedFrom_UserClassInDifferentNamespace() Dim code = @@ -3879,7 +3879,7 @@ class C$$ TestElement(code, Sub(codeClass) For i = 1 To 100 - Dim variable = codeClass.AddVariable("x", "System.Int32") + Dim variable = codeClass.AddVariable("x", "System.Int32", , EnvDTE.vsCMAccess.vsCMAccessDefault) codeClass.RemoveMember(variable) Next End Sub) @@ -3898,7 +3898,7 @@ class C$$ TestElement(code, Sub(state, codeClass) For i = 1 To 100 - Dim variable = codeClass.AddVariable("x", "System.Int32") + Dim variable = codeClass.AddVariable("x", "System.Int32", , EnvDTE.vsCMAccess.vsCMAccessDefault) ' Now, delete the variable that we just added. Dim startPoint = variable.StartPoint diff --git a/src/VisualStudio/Core/Test/CodeModel/CSharp/FileCodeModelTests.vb b/src/VisualStudio/Core/Test/CodeModel/CSharp/FileCodeModelTests.vb index 17f61cc314b27..36936fc3a43db 100644 --- a/src/VisualStudio/Core/Test/CodeModel/CSharp/FileCodeModelTests.vb +++ b/src/VisualStudio/Core/Test/CodeModel/CSharp/FileCodeModelTests.vb @@ -1213,7 +1213,7 @@ class C Assert.Equal("C", classC.Name) fileCodeModel.BeginBatch() - Dim newFunction = classC.AddFunction("M", EnvDTE.vsCMFunction.vsCMFunctionFunction, Type:=EnvDTE.vsCMTypeRef.vsCMTypeRefVoid) + Dim newFunction = classC.AddFunction("M", EnvDTE.vsCMFunction.vsCMFunctionFunction, Type:=EnvDTE.vsCMTypeRef.vsCMTypeRefVoid, , EnvDTE.vsCMAccess.vsCMAccessDefault) Dim param1 = newFunction.AddParameter("x", EnvDTE.vsCMTypeRef.vsCMTypeRefInt, Position:=-1) Dim param2 = newFunction.AddParameter("y", EnvDTE.vsCMTypeRef.vsCMTypeRefInt, Position:=-1) fileCodeModel.EndBatch() diff --git a/src/VisualStudio/Core/Test/CodeModel/VisualBasic/CodeClassTests.vb b/src/VisualStudio/Core/Test/CodeModel/VisualBasic/CodeClassTests.vb index 6cd7b8bd91f7d..18a5933e2d654 100644 --- a/src/VisualStudio/Core/Test/CodeModel/VisualBasic/CodeClassTests.vb +++ b/src/VisualStudio/Core/Test/CodeModel/VisualBasic/CodeClassTests.vb @@ -3139,7 +3139,7 @@ End Class TestElement(code, Sub(codeClass) For i = 1 To 100 - Dim variable = codeClass.AddVariable("x", "System.Int32") + Dim variable = codeClass.AddVariable("x", "System.Int32", , EnvDTE.vsCMAccess.vsCMAccessDefault) codeClass.RemoveMember(variable) Next End Sub) @@ -3157,7 +3157,7 @@ End Class TestElement(code, Sub(state, codeClass) For i = 1 To 100 - Dim variable = codeClass.AddVariable("x", "System.Int32") + Dim variable = codeClass.AddVariable("x", "System.Int32", , EnvDTE.vsCMAccess.vsCMAccessDefault) ' Now, delete the variable that we just added. Dim startPoint = variable.StartPoint diff --git a/src/VisualStudio/Core/Test/InheritanceMargin/InheritanceMarginViewModelTests.vb b/src/VisualStudio/Core/Test/InheritanceMargin/InheritanceMarginViewModelTests.vb new file mode 100644 index 0000000000000..bd354581dfabb --- /dev/null +++ b/src/VisualStudio/Core/Test/InheritanceMargin/InheritanceMarginViewModelTests.vb @@ -0,0 +1,321 @@ +' 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. + +Imports System.Collections.Immutable +Imports System.Text +Imports System.Threading +Imports System.Windows +Imports System.Windows.Controls +Imports System.Windows.Documents +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities +Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces +Imports Microsoft.CodeAnalysis.InheritanceMargin +Imports Microsoft.CodeAnalysis.Shared.Extensions +Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Microsoft.VisualStudio.Imaging +Imports Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin +Imports Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph +Imports Microsoft.VisualStudio.Text.Classification +Imports Microsoft.VisualStudio.Threading +Imports Roslyn.Test.Utilities + +Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.InheritanceMargin + + + + Public Class InheritanceMarginViewModelTests + + Private Shared s_defaultMargin As Thickness = New Thickness(4, 1, 4, 1) + + Private Shared s_indentMargin As Thickness = New Thickness(20, 1, 4, 1) + + Private Shared Async Function VerifyAsync(markup As String, languageName As String, expectedViewModels As Dictionary(Of Integer, InheritanceMarginViewModel)) As Task + ' Add an lf before the document so that the line number starts + ' with 1, which meets the line number in the editor (but in fact all things start from 0) + Dim workspaceFile = + + CommonReferences="true"> + <%= vbLf %> + <%= markup.Replace(vbCrLf, vbLf) %> + + + + + Dim cancellationToken As CancellationToken = CancellationToken.None + Using workspace = TestWorkspace.Create(workspaceFile) + Dim testDocument = workspace.Documents.Single() + Dim document = workspace.CurrentSolution.GetDocument(testDocument.Id) + Dim service = document.GetRequiredLanguageService(Of IInheritanceMarginService) + + Dim classificationTypeMap = workspace.ExportProvider.GetExportedValue(Of ClassificationTypeMap) + Dim classificationFormatMap = workspace.ExportProvider.GetExportedValue(Of IClassificationFormatMapService) + + ' For these tests, we need to be on UI thread, so don't call ConfigureAwait(False) + Dim root = Await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(True) + Dim inheritanceItems = Await service.GetInheritanceMemberItemsAsync(document, root.FullSpan, cancellationToken).ConfigureAwait(True) + + Dim acutalLineToTagDictionary = inheritanceItems.GroupBy(Function(item) item.LineNumber) _ + .ToDictionary(Function(grouping) grouping.Key, + Function(grouping) + Dim lineNumber = grouping.Key + Dim items = grouping.Select(Function(g) g).ToImmutableArray() + Return New InheritanceMarginTag(workspace, lineNumber, items) + End Function) + Assert.Equal(expectedViewModels.Count, acutalLineToTagDictionary.Count) + + For Each kvp In expectedViewModels + Dim lineNumber = kvp.Key + Dim expectedViewModel = kvp.Value + Assert.True(acutalLineToTagDictionary.ContainsKey(lineNumber)) + + Dim acutalTag = acutalLineToTagDictionary(lineNumber) + Dim actualViewModel = InheritanceMarginViewModel.Create( + classificationTypeMap, classificationFormatMap.GetClassificationFormatMap("tooltip"), acutalTag) + + VerifyTwoViewModelAreSame(expectedViewModel, actualViewModel) + Next + + End Using + End Function + + Private Shared Sub VerifyTwoViewModelAreSame(expected As InheritanceMarginViewModel, acutal As InheritanceMarginViewModel) + Assert.Equal(expected.ImageMoniker, acutal.ImageMoniker) + Dim actualTextGetFromTextBlock = acutal.ToolTipTextBlock.Inlines _ + .OfType(Of Run).Select(Function(run) run.Text) _ + .Aggregate(Function(text1, text2) text1 + text2) + ' When the text block is created, a unicode 'left to right' would be inserted between the space. + ' Make sure it is removed. + Dim leftToRightMarker = Char.ConvertFromUtf32(&H200E) + Dim actualText = actualTextGetFromTextBlock.Replace(leftToRightMarker, String.Empty) + Assert.Equal(expected.ToolTipTextBlock.Text, actualText) + Assert.Equal(expected.AutomationName, acutal.AutomationName) + Assert.Equal(expected.MenuItemViewModels.Length, acutal.MenuItemViewModels.Length) + + For i = 0 To expected.MenuItemViewModels.Length - 1 + Dim expectedMenuItem = expected.MenuItemViewModels(i) + Dim actualMenuItem = acutal.MenuItemViewModels(i) + Next + + End Sub + + Private Shared Sub VerifyMenuItem(expected As InheritanceMenuItemViewModel, actual As InheritanceMenuItemViewModel) + Assert.Equal(expected.AutomationName, actual.AutomationName) + Assert.Equal(expected.DisplayContent, actual.DisplayContent) + Assert.Equal(expected.ImageMoniker, actual.ImageMoniker) + + Dim expectedTargetMenuItem = TryCast(expected, TargetMenuItemViewModel) + Dim acutalTargetMenuItem = TryCast(actual, TargetMenuItemViewModel) + If expectedTargetMenuItem IsNot Nothing AndAlso acutalTargetMenuItem IsNot Nothing Then + Assert.Equal(expectedTargetMenuItem.Margin, acutalTargetMenuItem.Margin) + Return + End If + + Dim expectedMemberMenuItem = TryCast(expected, MemberMenuItemViewModel) + Dim acutalMemberMenuItem = TryCast(actual, MemberMenuItemViewModel) + If expectedTargetMenuItem IsNot Nothing AndAlso acutalTargetMenuItem IsNot Nothing Then + Assert.Equal(expectedMemberMenuItem.Targets.Length, acutalMemberMenuItem.Targets.Length) + For i = 0 To expectedMemberMenuItem.Targets.Length + VerifyMenuItem(expectedMemberMenuItem.Targets(i), acutalMemberMenuItem.Targets(i)) + Next + Return + End If + + ' At this stage, both of the items should be header + Assert.True(TypeOf expected Is HeaderMenuItemViewModel) + Assert.True(TypeOf actual Is HeaderMenuItemViewModel) + End Sub + + Private Shared Function CreateTextBlock(text As String) As TextBlock + Return New TextBlock With { + .Text = text + } + End Function + + + Public Function TestClassImplementsInterfaceRelationship() As Task + Dim markup = " +public interface IBar +{ +} +public class Bar : IBar +{ +}" + Dim tooltipTextForIBar = String.Format(ServicesVSResources._0_is_inherited, "interface IBar") + Dim targetForIBar = ImmutableArray.Create( + New TargetMenuItemViewModel("Bar", KnownMonikers.ClassPublic, "Bar", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + + Dim tooltipTextForBar = String.Format(ServicesVSResources._0_is_inherited, "class Bar") + Dim targetForBar = ImmutableArray.Create( + New TargetMenuItemViewModel("IBar", KnownMonikers.InterfacePublic, "IBar", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + + Return VerifyAsync(markup, LanguageNames.CSharp, New Dictionary(Of Integer, InheritanceMarginViewModel) From { + {2, New InheritanceMarginViewModel( + KnownMonikers.Implemented, + CreateTextBlock(tooltipTextForIBar), + tooltipTextForIBar, + targetForIBar)}, + {5, New InheritanceMarginViewModel( + KnownMonikers.Implementing, + CreateTextBlock(tooltipTextForBar), + tooltipTextForBar, + targetForBar)}}) + End Function + + + Public Function TestClassOverridesAbstractClassRelationship() As Task + Dim markup = " +public abstract class AbsBar +{ + public abstract void Foo(); +} + +public class Bar : AbsBar +{ + public override void Foo(); +}" + + Dim tooltipTextForAbsBar = String.Format(ServicesVSResources._0_is_inherited, "class AbsBar") + Dim targetForAbsBar = ImmutableArray.Create( + New TargetMenuItemViewModel("Bar", KnownMonikers.Class, "Bar", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + + Dim tooltipTextForAbstractFoo = String.Format(ServicesVSResources._0_is_inherited, "abstract void AbsBar.Foo()") + Dim targetForAbsFoo = ImmutableArray.Create( + New TargetMenuItemViewModel("AbsBar.Foo()", KnownMonikers.MethodPublic, "AbsBar.Foo()", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + + Dim tooltipTextForBar = String.Format(ServicesVSResources._0_is_inherited, "class Bar") + Dim targetForBar = ImmutableArray.Create( + New TargetMenuItemViewModel("AbsBar", KnownMonikers.Class, "AbsBar", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + + Dim tooltipTextForOverrideFoo = String.Format(ServicesVSResources._0_is_inherited, "override void Bar.Foo()") + Dim targetForOverrideFoo = ImmutableArray.Create( + New TargetMenuItemViewModel("Bar.Foo()", KnownMonikers.MethodPublic, "Bar.Foo()", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + + Return VerifyAsync(markup, LanguageNames.CSharp, New Dictionary(Of Integer, InheritanceMarginViewModel) From { + {2, New InheritanceMarginViewModel( + KnownMonikers.Implemented, + CreateTextBlock(tooltipTextForAbsBar), + tooltipTextForAbsBar, + targetForAbsBar)}, + {4, New InheritanceMarginViewModel( + KnownMonikers.Overridden, + CreateTextBlock(tooltipTextForAbstractFoo), + tooltipTextForAbstractFoo, + targetForAbsFoo)}, + {7, New InheritanceMarginViewModel( + KnownMonikers.Implementing, + CreateTextBlock(tooltipTextForBar), + tooltipTextForBar, + targetForBar)}, + {9, New InheritanceMarginViewModel( + KnownMonikers.Overriding, + CreateTextBlock(tooltipTextForOverrideFoo), + tooltipTextForOverrideFoo, + targetForOverrideFoo)}}) + End Function + + + Public Function TestInterfaceImplementsInterfaceRelationship() As Task + Dim markup = " +public interface IBar1 { } +public interface IBar2 : IBar1 { } +public interface IBar3 : IBar2 { } +" + Dim tooltipTextForIBar1 = String.Format(ServicesVSResources._0_is_inherited, "interface IBar1") + Dim targetForIBar1 = ImmutableArray.Create( + New TargetMenuItemViewModel("IBar2", KnownMonikers.InterfacePublic, "IBar2", Nothing, s_defaultMargin), + New TargetMenuItemViewModel("IBar3", KnownMonikers.InterfacePublic, "IBar3", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + + Dim tooltipTextForIBar2 = String.Format(ServicesVSResources._0_is_inherited, "interface IBar2") + Dim targetForIBar2 = ImmutableArray.Create(Of InheritanceMenuItemViewModel)( + New HeaderMenuItemViewModel(ServicesVSResources.Implementing_members, KnownMonikers.Implementing, ServicesVSResources.Implementing_members), + New TargetMenuItemViewModel("IBar1", KnownMonikers.InterfacePublic, "IBar1", Nothing, s_indentMargin), + New HeaderMenuItemViewModel(ServicesVSResources.Implemented_members, KnownMonikers.Implemented, ServicesVSResources.Implemented_members), + New TargetMenuItemViewModel("IBar3", KnownMonikers.InterfacePublic, "IBar3", Nothing, s_indentMargin)) + + Dim tooltipTextForIBar3 = String.Format(ServicesVSResources._0_is_inherited, "interface IBar3") + Dim targetForIBar3 = ImmutableArray.Create( + New TargetMenuItemViewModel("IBar1", KnownMonikers.InterfacePublic, "IBar1", Nothing, s_defaultMargin), + New TargetMenuItemViewModel("IBar2", KnownMonikers.InterfacePublic, "IBar2", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + + Return VerifyAsync(markup, LanguageNames.CSharp, New Dictionary(Of Integer, InheritanceMarginViewModel) From { + {2, New InheritanceMarginViewModel( + KnownMonikers.Implemented, + CreateTextBlock(tooltipTextForIBar1), + tooltipTextForIBar1, + targetForIBar1)}, + {3, New InheritanceMarginViewModel( + KnownMonikers.Implemented, + CreateTextBlock(tooltipTextForIBar2), + tooltipTextForIBar2, + targetForIBar2)}, + {4, New InheritanceMarginViewModel( + KnownMonikers.Implementing, + CreateTextBlock(tooltipTextForIBar3), + tooltipTextForIBar3, + targetForIBar3)}}) + End Function + + + Public Function TestMutipleMemberOnSameline() As Task + Dim markup = " +using System; +interface IBar1 +{ + public event EventHandler e1, e2; +} + +public class BarSample : IBar1 +{ + public virtual event EventHandler e1, e2; +}" + + Dim tooltipTextForIBar1 = String.Format(ServicesVSResources._0_is_inherited, "interface IBar1") + Dim targetForIBar1 = ImmutableArray.Create( + New TargetMenuItemViewModel("BarSample", KnownMonikers.ClassPublic, "BarSample", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + + Dim tooltipTextForE1AndE2InInterface = ServicesVSResources.Multiple_members_are_inherited + Dim targetForE1AndE2InInterface = ImmutableArray.Create(Of InheritanceMenuItemViewModel)( + New MemberMenuItemViewModel("event EventHandler e1", KnownMonikers.EventPublic, "event EventHandler e1", ImmutableArray.Create( + New TargetMenuItemViewModel("BarSample.e1", KnownMonikers.EventPublic, "BarSample.e1", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel)), + New MemberMenuItemViewModel("event EventHandler e2", KnownMonikers.EventPublic, "event EventHandler e2", ImmutableArray.Create( + New TargetMenuItemViewModel("BarSample.e2", KnownMonikers.EventPublic, "BarSample.e2", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel))) _ + .CastArray(Of InheritanceMenuItemViewModel) + + Dim tooltipTextForBarSample = String.Format(ServicesVSResources._0_is_inherited, "class BarSample") + Dim targetForBarSample = ImmutableArray.Create( + New TargetMenuItemViewModel("IBar1", KnownMonikers.InterfacePublic, "IBar1", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel) + + Dim tooltipTextForE1AndE2InBarSample = ServicesVSResources.Multiple_members_are_inherited + Dim targetForE1AndE2InInBarSample = ImmutableArray.Create(Of InheritanceMenuItemViewModel)( + New MemberMenuItemViewModel("event EventHandler e1", KnownMonikers.EventPublic, "event EventHandler e1", ImmutableArray.Create( + New TargetMenuItemViewModel("IBar1.BarSample.e1", KnownMonikers.EventPublic, "IBar1.BarSample.e1", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel)), + New MemberMenuItemViewModel("event EventHandler e2", KnownMonikers.EventPublic, "event EventHandler e2", ImmutableArray.Create( + New TargetMenuItemViewModel("IBar1.BarSample.e2", KnownMonikers.EventPublic, "IBar1.BarSample.e2", Nothing, s_defaultMargin)).CastArray(Of InheritanceMenuItemViewModel))) _ + .CastArray(Of InheritanceMenuItemViewModel) + + Return VerifyAsync(markup, LanguageNames.CSharp, New Dictionary(Of Integer, InheritanceMarginViewModel) From { + {3, New InheritanceMarginViewModel( + KnownMonikers.Implemented, + CreateTextBlock(tooltipTextForIBar1), + tooltipTextForIBar1, + targetForIBar1)}, + {5, New InheritanceMarginViewModel( + KnownMonikers.Implemented, + CreateTextBlock(tooltipTextForE1AndE2InInterface), + String.Format(ServicesVSResources.Multiple_members_are_inherited_on_line_0, 5), + targetForE1AndE2InInterface)}, + {8, New InheritanceMarginViewModel( + KnownMonikers.Implementing, + CreateTextBlock(tooltipTextForBarSample), + tooltipTextForBarSample, + targetForBarSample)}, + {10, New InheritanceMarginViewModel( + KnownMonikers.Implementing, + CreateTextBlock(tooltipTextForE1AndE2InBarSample), + String.Format(ServicesVSResources.Multiple_members_are_inherited_on_line_0, 10), + targetForE1AndE2InInBarSample)}}) + End Function + End Class +End Namespace diff --git a/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj index 2a46a3b7a160c..553cd0d454330 100644 --- a/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj +++ b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj @@ -47,16 +47,14 @@ - - - + + - @@ -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 @@ -