提交 a59f56e2 编写于 作者: J Jason Malinowski

Merge remote-tracking branch 'dotnet/microupdate-3.2' into microupdate

......@@ -3,9 +3,10 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Linq;
using System.Text;
using System.Xml.Linq;
string usage = @"usage: BuildNuGets.csx <binaries-dir> <build-version> <output-directory>";
......@@ -27,6 +28,7 @@ string ScriptRoot([CallerFilePath]string path = "") => Path.GetDirectoryName(pat
// utilities will consider the '\"' as an escape sequence for the end quote
var BinDir = Path.GetFullPath(Args[0]).TrimEnd('\\');
var BuildVersion = Args[1].Trim();
var BuildingReleaseNugets = IsReleaseVersion(BuildVersion);
var NuspecDirPath = Path.Combine(SolutionRoot, "src/NuGet");
var OutDir = Path.GetFullPath(Args[2]).TrimEnd('\\');
......@@ -110,10 +112,10 @@ string[] TestPackageNames = {
// the following packages will only be publised on myget not on nuget:
var PreReleaseOnlyPackages = new HashSet<string>
{
"Microsoft.CodeAnalysis.EditorFeatures",
"Microsoft.CodeAnalysis.VisualBasic.Scripting",
"Microsoft.Net.Compilers.netcore",
"Microsoft.Net.CSharp.Interactive.netcore",
"Microsoft.CodeAnalysis.Test.Resources.Proprietary",
};
// Create an empty directory to be used in NuGet pack
......@@ -121,11 +123,10 @@ var emptyDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
var dirInfo = Directory.CreateDirectory(emptyDir);
File.Create(Path.Combine(emptyDir, "_._")).Close();
int PackFiles(string[] packageNames, string licenseUrl)
int PackFiles(string[] nuspecFiles, string licenseUrl)
{
int exit = 0;
foreach (var file in packageNames.Select(f => Path.Combine(NuspecDirPath, f + ".nuspec")))
foreach (var file in nuspecFiles)
{
var nugetArgs = $@"pack {file} " +
$"-BasePath \"{BinDir}\" " +
......@@ -148,15 +149,37 @@ int PackFiles(string[] packageNames, string licenseUrl)
p.StartInfo.FileName = nugetExePath;
p.StartInfo.Arguments = nugetArgs;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardError = true;
Console.WriteLine($"{Environment.NewLine}Running: nuget.exe {nugetArgs}");
Console.WriteLine($"Running: nuget.exe {nugetArgs}");
p.Start();
p.WaitForExit();
if ((exit = p.ExitCode) != 0)
var currentExit = p.ExitCode;
if (currentExit != 0)
{
break;
var stdErr = p.StandardError.ReadToEnd();
string message;
if (BuildingReleaseNugets && stdErr.Contains("A stable release of a package should not have on a prerelease dependency."))
{
// If we are building release nugets and if any packages have dependencies on prerelease packages
// then we want to ignore the error and allow the build to succeed.
currentExit = 0;
message = $"{Environment.NewLine}{file}: {stdErr}";
}
else
{
message = $"{Environment.NewLine}{file}: error: {stdErr}";
}
Console.WriteLine(message);
File.AppendAllText(ErrorLogFile, message);
}
// We want to try and generate all nugets and log any errors encountered along the way.
// We also want to fail the build in case of all encountered errors except the prerelease package dependency error above.
exit = (exit == 0) ? currentExit : exit;
}
return exit;
......@@ -167,16 +190,21 @@ XElement MakePackageElement(string packageName, string version)
return new XElement("package", new XAttribute("id", packageName), new XAttribute("version", version));
}
IEnumerable<XElement> MakeRoslynPackageElements(bool isRelease)
string[] GetRoslynPackageNames()
{
var packageNames = RedistPackageNames.Concat(NonRedistPackageNames);
var packageNames = RedistPackageNames.Concat(NonRedistPackageNames).Concat(TestPackageNames);
if (isRelease)
if (BuildingReleaseNugets)
{
packageNames = packageNames.Where(pn => !PreReleaseOnlyPackages.Contains(pn));
}
return packageNames.Select(packageName => MakePackageElement(packageName, BuildVersion));
return packageNames.ToArray();
}
IEnumerable<XElement> MakeRoslynPackageElements(string[] roslynPackageNames)
{
return roslynPackageNames.Select(packageName => MakePackageElement(packageName, BuildVersion));
}
void GeneratePublishingConfig(string fileName, IEnumerable<XElement> packages)
......@@ -186,44 +214,50 @@ void GeneratePublishingConfig(string fileName, IEnumerable<XElement> packages)
}
// Currently we publish some of the Roslyn dependencies. Remove this once they are moved to a separate repo.
IEnumerable<XElement> MakePackageElementsForPublishedDependencies(bool isRelease)
IEnumerable<XElement> MakePackageElementsForPublishedDependencies()
{
if (MicrosoftDiaSymReaderVersion != null && isRelease == IsReleaseVersion(MicrosoftDiaSymReaderVersion))
if (MicrosoftDiaSymReaderVersion != null && BuildingReleaseNugets == IsReleaseVersion(MicrosoftDiaSymReaderVersion))
{
yield return MakePackageElement("Microsoft.DiaSymReader", MicrosoftDiaSymReaderVersion);
}
if (MicrosoftDiaSymReaderPortablePdbVersion != null && isRelease == IsReleaseVersion(MicrosoftDiaSymReaderPortablePdbVersion))
if (MicrosoftDiaSymReaderPortablePdbVersion != null && BuildingReleaseNugets == IsReleaseVersion(MicrosoftDiaSymReaderPortablePdbVersion))
{
yield return MakePackageElement("Microsoft.DiaSymReader.PortablePdb", MicrosoftDiaSymReaderPortablePdbVersion);
}
}
void GeneratePublishingConfig()
void GeneratePublishingConfig(string[] roslynPackageNames)
{
if (IsReleaseVersion(BuildVersion))
var packages = MakeRoslynPackageElements(roslynPackageNames).Concat(MakePackageElementsForPublishedDependencies());
if (BuildingReleaseNugets)
{
// nuget:
var packages = MakeRoslynPackageElements(isRelease: true).Concat(MakePackageElementsForPublishedDependencies(isRelease: true));
GeneratePublishingConfig("nuget_org-packages.config", packages);
}
else
{
// myget:
var packages = MakeRoslynPackageElements(isRelease: false).Concat(MakePackageElementsForPublishedDependencies(isRelease: false));
GeneratePublishingConfig("myget_org-packages.config", packages);
}
}
bool IsReleaseVersion(string version) => !version.Contains('-');
Directory.CreateDirectory(OutDir);
var ErrorLogFile = Path.Combine(OutDir, "skipped_packages.txt");
try
{
if (File.Exists(ErrorLogFile)) File.Delete(ErrorLogFile);
}
catch
{
// Ignore errors
}
GeneratePublishingConfig();
int exit = PackFiles(RedistPackageNames, LicenseUrlRedist);
if (exit == 0) exit = PackFiles(NonRedistPackageNames, LicenseUrlNonRedist);
if (exit == 0) exit = PackFiles(TestPackageNames, LicenseUrlTest);
var roslynPackageNames = GetRoslynPackageNames();
GeneratePublishingConfig(roslynPackageNames);
string[] roslynNuspecFiles = roslynPackageNames.Select(f => Path.Combine(NuspecDirPath, f + ".nuspec")).ToArray();
int exit = PackFiles(roslynNuspecFiles, LicenseUrlRedist);
try
{
......
......@@ -71,10 +71,8 @@
$(BuildNumberSuffix.Split('.')[1].PadLeft(2,'0'))
</BuildNumberPart2>
<!-- Only set when building RTM with no dependencies on pre-release packages
<NuGetReleaseVersion>$(RoslynSemanticVersion)</NuGetReleaseVersion>
-->
<NuGetPreReleaseVersion>1.3.2-beta1</NuGetPreReleaseVersion>
<NuGetReleaseVersion>1.3.3</NuGetReleaseVersion>
<NuGetPreReleaseVersion>1.3.3-beta1</NuGetPreReleaseVersion>
<NuGetPerBuildPreReleaseVersion Condition="'$(BuildNumberSuffix)' != ''">$(NuGetPreReleaseVersion)-$(BuildNumberPart1.Trim())-$(BuildNumberPart2.Trim())</NuGetPerBuildPreReleaseVersion>
</PropertyGroup>
......
无法预览此类型文件
......@@ -174,12 +174,12 @@ private void SetGlobalErrorIfTrue(bool arg)
if (filterOpt == null)
{
WarnUnusedFields(compilation, diagnostics, cancellationToken);
}
MethodSymbol entryPoint = GetEntryPoint(compilation, moduleBeingBuiltOpt, hasDeclarationErrors, diagnostics, cancellationToken);
if (moduleBeingBuiltOpt != null && entryPoint != null && compilation.Options.OutputKind.IsApplication())
{
moduleBeingBuiltOpt.SetPEEntryPoint(entryPoint, diagnostics);
MethodSymbol entryPoint = GetEntryPoint(compilation, moduleBeingBuiltOpt, hasDeclarationErrors, diagnostics, cancellationToken);
if (moduleBeingBuiltOpt != null && entryPoint != null && compilation.Options.OutputKind.IsApplication())
{
moduleBeingBuiltOpt.SetPEEntryPoint(entryPoint, diagnostics);
}
}
}
......
......@@ -1424,5 +1424,25 @@ public static int Main()
Diagnostic(ErrorCode.ERR_NoEntryPoint)
);
}
[Fact, WorkItem(12113, "https://github.com/dotnet/roslyn/issues/12113")]
public void LazyEntryPoint()
{
string source = @"
class Program
{
public static void Main() {}
public static void Main(string[] args) {}
}
";
var compilation = CreateCompilationWithMscorlib(source, options: TestOptions.DebugExe);
var model = compilation.GetSemanticModel(compilation.SyntaxTrees[0]);
Assert.Empty(model.GetDiagnostics());
compilation.VerifyDiagnostics(
// (4,24): error CS0017: Program has more than one entry point defined. Compile with /main to specify the type that contains the entry point.
// public static void Main() {}
Diagnostic(ErrorCode.ERR_MultipleEntryPoints, "Main").WithLocation(4, 24)
);
}
}
}
......@@ -235,6 +235,8 @@ public async Task<GeneratedCodeAnalysisFlags> GetGeneratedCodeAnalysisFlagsAsync
});
// Subscribe for exceptions from lazily evaluated localizable strings in the descriptors.
// REVIEW: find out better way to handle these exception handlers. right now, it can leak
// so easily unless ClearAnalyzerState is called from host properly
foreach (var descriptor in supportedDiagnostics)
{
descriptor.Title.OnException += handler;
......@@ -257,6 +259,7 @@ public async Task<GeneratedCodeAnalysisFlags> GetGeneratedCodeAnalysisFlagsAsync
Tuple<ImmutableArray<DiagnosticDescriptor>, EventHandler<Exception>> value;
lock (_descriptorMap)
{
// REVIEW: how one knows exception handler is same as the one cached?
if (_descriptorMap.TryGetValue(analyzer, out value))
{
return value.Item1;
......
......@@ -14,6 +14,7 @@
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
using Microsoft.CodeAnalysis.Editor.Implementation.Preview;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings
{
......@@ -106,11 +107,11 @@ public async Task TestPickTheRightPreview_NoPreference()
var previewObjects = await previews.GetPreviewsAsync();
var preview = previewObjects[0];
Assert.NotNull(preview);
Assert.True(preview is IWpfDifferenceViewer);
var diffView = preview as IWpfDifferenceViewer;
var text = diffView.RightView.TextBuffer.AsTextContainer().CurrentText.ToString();
Assert.True(preview is DifferenceViewerPreview);
var diffView = preview as DifferenceViewerPreview;
var text = diffView.Viewer.RightView.TextBuffer.AsTextContainer().CurrentText.ToString();
Assert.Equal(ChangedDocumentText, text);
diffView.Close();
diffView.Dispose();
// Then comes the removed metadata reference.
preview = previewObjects[1];
......
......@@ -279,6 +279,7 @@
<Compile Include="Implementation\Interactive\IAbstractResetInteractiveCommand.cs" />
<Compile Include="Implementation\Outlining\AbstractSyntaxOutliner.cs" />
<Compile Include="Implementation\Outlining\InvalidOutliningRegionException.cs" />
<Compile Include="Implementation\Preview\DifferenceViewerPreview.cs" />
<Compile Include="Implementation\Suggestions\FixMultipleOccurrencesService.cs" />
<Compile Include="Implementation\Suggestions\FixMultipleSuggestedAction.cs" />
<Compile Include="Implementation\GoToImplementation\GoToImplementationCommandHandler.cs" />
......@@ -799,4 +800,4 @@
<ImportGroup Label="Targets">
<Import Project="..\..\..\build\Targets\VSL.Imports.targets" />
</ImportGroup>
</Project>
</Project>
\ No newline at end of file
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.VisualStudio.Text.Differencing;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editor.Implementation.Preview
{
internal class DifferenceViewerPreview : IDisposable
{
private IWpfDifferenceViewer _viewer;
public DifferenceViewerPreview(IWpfDifferenceViewer viewer)
{
Contract.ThrowIfNull(viewer);
_viewer = viewer;
}
public IWpfDifferenceViewer Viewer => _viewer;
public void Dispose()
{
GC.SuppressFinalize(this);
if (_viewer != null && !_viewer.IsClosed)
{
_viewer.Close();
}
_viewer = null;
}
~DifferenceViewerPreview()
{
// make sure we are not leaking diff viewer
// we can't close the view from finalizer thread since it must be same
// thread (owner thread) this UI is created.
if (Environment.HasShutdownStarted)
{
return;
}
FatalError.ReportWithoutCrash(new Exception($"Dispose is not called how? viewer state : {_viewer.IsClosed}"));
}
}
}
\ No newline at end of file
......@@ -623,7 +623,7 @@ private ITextBuffer CreateNewPlainTextBuffer(TextDocument document, Cancellation
rightWorkspace.EnableDiagnostic();
}
return diffViewer;
return new DifferenceViewerPreview(diffViewer);
}
private List<LineSpan> CreateLineSpans(ITextSnapshot textSnapshot, NormalizedSpanCollection allSpans, CancellationToken cancellationToken)
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
......@@ -52,25 +53,35 @@ public async Task<IReadOnlyList<object>> GetPreviewsAsync(DocumentId preferredDo
var result = new List<object>();
var gotRichPreview = false;
foreach (var previewItem in _previews)
try
{
cancellationToken.ThrowIfCancellationRequested();
if (previewItem.Text != null)
foreach (var previewItem in _previews)
{
result.Add(previewItem.Text);
}
else if (!gotRichPreview)
{
var preview = await previewItem.LazyPreview(cancellationToken).ConfigureAwait(true);
if (preview != null)
cancellationToken.ThrowIfCancellationRequested();
if (previewItem.Text != null)
{
result.Add(previewItem.Text);
}
else if (!gotRichPreview)
{
result.Add(preview);
gotRichPreview = true;
var preview = await previewItem.LazyPreview(cancellationToken).ConfigureAwait(true);
if (preview != null)
{
result.Add(preview);
gotRichPreview = true;
}
}
}
}
return result.Count == 0 ? null : result;
return result.Count == 0 ? null : result;
}
catch (OperationCanceledException)
{
// make sure we dispose all disposable preview objects before
// we let control to exit this method
result.OfType<IDisposable>().Do(d => d.Dispose());
throw;
}
}
/// <summary>Merge two different previews into one final preview result. The final preview will
......
......@@ -236,11 +236,19 @@ public virtual async Task<object> GetPreviewAsync(CancellationToken cancellation
// Light bulb will always invoke this function on the UI thread.
AssertIsForeground();
var previewPaneService = Workspace.Services.GetService<IPreviewPaneService>();
if (previewPaneService == null)
{
return null;
}
// after this point, this method should only return at GetPreviewPane. otherwise, DifferenceViewer will leak
// since there is no one to close the viewer
var preferredDocumentId = Workspace.GetDocumentIdInCurrentContext(SubjectBuffer.AsTextContainer());
var preferredProjectId = preferredDocumentId?.ProjectId;
var extensionManager = this.Workspace.Services.GetService<IExtensionManager>();
var previewContent = await extensionManager.PerformFunctionAsync(Provider, async () =>
var previewContents = await extensionManager.PerformFunctionAsync(Provider, async () =>
{
// We need to stay on UI thread after GetPreviewResultAsync() so that TakeNextPreviewAsync()
// below can execute on UI thread. We use ConfigureAwait(true) to stay on the UI thread.
......@@ -259,14 +267,6 @@ public virtual async Task<object> GetPreviewAsync(CancellationToken cancellation
// GetPreviewPane() below needs to run on UI thread. We use ConfigureAwait(true) to stay on the UI thread.
}, defaultValue: null).ConfigureAwait(true);
var previewPaneService = Workspace.Services.GetService<IPreviewPaneService>();
if (previewPaneService == null)
{
return null;
}
cancellationToken.ThrowIfCancellationRequested();
// GetPreviewPane() needs to run on the UI thread.
AssertIsForeground();
......@@ -274,7 +274,7 @@ public virtual async Task<object> GetPreviewAsync(CancellationToken cancellation
string projectType;
Workspace.GetLanguageAndProjectType(preferredProjectId, out language, out projectType);
return previewPaneService.GetPreviewPane(GetDiagnostic(), language, projectType, previewContent);
return previewPaneService.GetPreviewPane(GetDiagnostic(), language, projectType, previewContents);
}
protected virtual DiagnosticData GetDiagnostic()
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Immutable;
using System.Composition;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editor.Shared.Preview
{
[ExportWorkspaceService(typeof(ISolutionCrawlerRegistrationService), WorkspaceKind.Preview), Shared]
internal class PreviewSolutionCrawlerRegistrationService : ISolutionCrawlerRegistrationService
[ExportWorkspaceServiceFactory(typeof(ISolutionCrawlerRegistrationService), WorkspaceKind.Preview), Shared]
internal class PreviewSolutionCrawlerRegistrationServiceFactory : IWorkspaceServiceFactory
{
private static readonly ConditionalWeakTable<Workspace, CancellationTokenSource> s_cancellationTokens =
new ConditionalWeakTable<Workspace, CancellationTokenSource>();
private readonly IIncrementalAnalyzerProvider _provider;
private readonly DiagnosticAnalyzerService _analyzerService;
[ImportingConstructor]
public PreviewSolutionCrawlerRegistrationService(IDiagnosticAnalyzerService diagnosticService)
public PreviewSolutionCrawlerRegistrationServiceFactory(IDiagnosticAnalyzerService analyzerService)
{
// this service is directly tied to DiagnosticAnalyzerService and
// depends on its implementation.
_analyzerService = analyzerService as DiagnosticAnalyzerService;
Contract.ThrowIfNull(_analyzerService);
}
public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
{
_provider = diagnosticService as IIncrementalAnalyzerProvider;
Contract.ThrowIfNull(_provider);
// to make life time management easier, just create new sevice per new workspace
return new Service(this, workspaceServices.Workspace);
}
public async void Register(Workspace workspace)
// internal for testing
internal class Service : ISolutionCrawlerRegistrationService
{
try
private readonly PreviewSolutionCrawlerRegistrationServiceFactory _owner;
private readonly Workspace _workspace;
private readonly CancellationTokenSource _source;
// since we now have one service for each one specific instance of workspace,
// we can have states for this specific workspace.
private Task _analyzeTask;
[ImportingConstructor]
public Service(PreviewSolutionCrawlerRegistrationServiceFactory owner, Workspace workspace)
{
_owner = owner;
_workspace = workspace;
_source = new CancellationTokenSource();
}
public void Register(Workspace workspace)
{
// given workspace must be owner of this workspace service
Contract.ThrowIfFalse(workspace == _workspace);
// this can't be called twice
Contract.ThrowIfFalse(_analyzeTask == null);
_analyzeTask = AnalyzeAsync();
}
private async Task AnalyzeAsync()
{
var workerBackOffTimeSpanInMS = workspace.Options.GetOption(InternalSolutionCrawlerOptions.PreviewBackOffTimeSpanInMS);
var workerBackOffTimeSpanInMS = _workspace.Options.GetOption(InternalSolutionCrawlerOptions.PreviewBackOffTimeSpanInMS);
var diagnosticAnalyzer = _owner._analyzerService.CreateIncrementalAnalyzer(_workspace);
var analyzer = _provider.CreateIncrementalAnalyzer(workspace);
var source = s_cancellationTokens.GetValue(workspace, _ => new CancellationTokenSource());
var solution = _workspace.CurrentSolution;
var documentIds = _workspace.GetOpenDocumentIds().ToImmutableArray();
var solution = workspace.CurrentSolution;
foreach (var documentId in workspace.GetOpenDocumentIds())
try
{
var document = solution.GetDocument(documentId);
if (document == null)
foreach (var documentId in documentIds)
{
continue;
}
var document = solution.GetDocument(documentId);
if (document == null)
{
continue;
}
// delay analyzing
await Task.Delay(workerBackOffTimeSpanInMS).ConfigureAwait(false);
// delay analyzing
await Task.Delay(workerBackOffTimeSpanInMS, _source.Token).ConfigureAwait(false);
// do actual analysis
await analyzer.AnalyzeSyntaxAsync(document, source.Token).ConfigureAwait(false);
await analyzer.AnalyzeDocumentAsync(document, bodyOpt: null, cancellationToken: source.Token).ConfigureAwait(false);
// do actual analysis
await diagnosticAnalyzer.AnalyzeSyntaxAsync(document, _source.Token).ConfigureAwait(false);
await diagnosticAnalyzer.AnalyzeDocumentAsync(document, bodyOpt: null, cancellationToken: _source.Token).ConfigureAwait(false);
// don't call project one.
// don't call project one.
}
}
catch (OperationCanceledException)
{
// do nothing
}
}
catch (OperationCanceledException)
{
// do nothing
}
}
public void Unregister(Workspace workspace, bool blockingShutdown = false)
{
CancellationTokenSource source;
if (s_cancellationTokens.TryGetValue(workspace, out source))
public async void Unregister(Workspace workspace, bool blockingShutdown = false)
{
source.Cancel();
Contract.ThrowIfFalse(workspace == _workspace);
_source.Cancel();
// wait for analyzer work to be finished
await _analyzeTask.ConfigureAwait(false);
// ask it to reset its stages for the given workspace
_owner._analyzerService.ShutdownAnalyzerFrom(_workspace);
}
}
}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
......@@ -8,14 +7,10 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.Editor.Implementation.Preview;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.UnitTests;
using Microsoft.VisualStudio.Text.Differencing;
using Microsoft.VisualStudio.Text.Editor;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
......@@ -93,10 +88,10 @@ private static async Task VerifyPreviewContents(TestWorkspace workspace, string
{
var editHandler = workspace.ExportProvider.GetExportedValue<ICodeActionEditHandlerService>();
var content = (await editHandler.GetPreviews(workspace, operations, CancellationToken.None).GetPreviewsAsync())[0];
var diffView = content as IWpfDifferenceViewer;
Assert.NotNull(diffView);
var previewContents = diffView.RightView.TextBuffer.AsTextContainer().CurrentText.ToString();
diffView.Close();
var diffView = content as DifferenceViewerPreview;
Assert.NotNull(diffView.Viewer);
var previewContents = diffView.Viewer.RightView.TextBuffer.AsTextContainer().CurrentText.ToString();
diffView.Dispose();
Assert.Equal(expectedPreviewContents, previewContents);
}
......
......@@ -23,6 +23,7 @@
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
using Microsoft.CodeAnalysis.Editor.Implementation.Preview;
namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics
{
......@@ -374,9 +375,9 @@ protected async Task TestEquivalenceKeyAsync(string initialMarkup, string equiva
{
// If there is just one document change then we expect the preview to be a WpfTextView
var content = (await editHandler.GetPreviews(workspace, operations, CancellationToken.None).GetPreviewsAsync())[0];
var diffView = content as IWpfDifferenceViewer;
Assert.NotNull(diffView);
diffView.Close();
var diffView = content as DifferenceViewerPreview;
Assert.NotNull(diffView.Viewer);
diffView.Dispose();
}
else
{
......@@ -390,11 +391,11 @@ protected async Task TestEquivalenceKeyAsync(string initialMarkup, string equiva
{
if (preview != null)
{
var diffView = preview as IWpfDifferenceViewer;
if (diffView != null)
var diffView = preview as DifferenceViewerPreview;
if (diffView?.Viewer != null)
{
hasPreview = true;
diffView.Close();
diffView.Dispose();
break;
}
}
......
......@@ -222,6 +222,7 @@
<Compile Include="Outlining\AbstractSyntaxOutlinerTests.cs" />
<Compile Include="Outlining\AbstractSyntaxTriviaOutlinerTests.cs" />
<Compile Include="Outlining\OutliningServiceTests.cs" />
<Compile Include="Preview\MockPreviewPaneService.cs" />
<Compile Include="Preview\TestOnly_CompilerDiagnosticAnalyzerProviderService.cs" />
<Compile Include="DocCommentFormatting\DocCommentFormattingTests.cs" />
<Compile Include="DocumentationComments\AbstractDocumentationCommentTests.cs" />
......@@ -340,4 +341,4 @@
<Import Project="..\..\..\build\Targets\VSL.Imports.targets" />
<Import Project="..\..\..\build\Targets\Roslyn.Toolsets.Xunit.targets" />
</ImportGroup>
</Project>
</Project>
\ No newline at end of file
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Composition;
using System.Linq;
using System.Windows.Controls;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editor.UnitTests.Preview
{
[ExportWorkspaceService(typeof(IPreviewPaneService), ServiceLayer.Host), Shared]
internal class MockPreviewPaneService : IPreviewPaneService
{
public object GetPreviewPane(DiagnosticData diagnostic, string language, string projectType, IReadOnlyList<object> previewContents)
{
var contents = previewContents ?? SpecializedCollections.EmptyEnumerable<object>();
foreach (var content in contents.OfType<IDisposable>())
{
content.Dispose();
}
// test only mock object
return new Grid();
}
}
}
......@@ -26,6 +26,7 @@
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
using Microsoft.CodeAnalysis.Editor.Implementation.Preview;
namespace Microsoft.CodeAnalysis.Editor.UnitTests.Preview
{
......@@ -135,7 +136,7 @@ public void TestPreviewServices()
using (var previewWorkspace = new PreviewWorkspace(MefV1HostServices.Create(TestExportProvider.ExportProviderWithCSharpAndVisualBasic.AsExportProvider())))
{
var service = previewWorkspace.Services.GetService<ISolutionCrawlerRegistrationService>();
Assert.True(service is PreviewSolutionCrawlerRegistrationService);
Assert.True(service is PreviewSolutionCrawlerRegistrationServiceFactory.Service);
var persistentService = previewWorkspace.Services.GetService<IPersistentStorageService>();
Assert.NotNull(persistentService);
......@@ -230,48 +231,49 @@ public async Task TestPreviewDiagnosticTaggerInPreviewPane()
var newDocument = oldDocument.WithText(oldText.WithChanges(new TextChange(new TextSpan(0, oldText.Length), "class C { }")));
// create a diff view
WpfTestCase.RequireWpfFact($"{nameof(TestPreviewDiagnosticTaggerInPreviewPane)} creates a {nameof(IWpfDifferenceViewer)}");
WpfTestCase.RequireWpfFact($"{nameof(TestPreviewDiagnosticTaggerInPreviewPane)} creates a {nameof(DifferenceViewerPreview)}");
var previewFactoryService = workspace.ExportProvider.GetExportedValue<IPreviewFactoryService>();
var diffView = (IWpfDifferenceViewer)(await previewFactoryService.CreateChangedDocumentPreviewViewAsync(oldDocument, newDocument, CancellationToken.None));
var foregroundService = workspace.GetService<IForegroundNotificationService>();
var optionsService = workspace.Services.GetService<IOptionService>();
using (var diffView = (DifferenceViewerPreview)(await previewFactoryService.CreateChangedDocumentPreviewViewAsync(oldDocument, newDocument, CancellationToken.None)))
{
var foregroundService = workspace.GetService<IForegroundNotificationService>();
var optionsService = workspace.Services.GetService<IOptionService>();
var waiter = new ErrorSquiggleWaiter();
var listeners = AsynchronousOperationListener.CreateListeners(FeatureAttribute.ErrorSquiggles, waiter);
var waiter = new ErrorSquiggleWaiter();
var listeners = AsynchronousOperationListener.CreateListeners(FeatureAttribute.ErrorSquiggles, waiter);
// set up tagger for both buffers
var leftBuffer = diffView.LeftView.BufferGraph.GetTextBuffers(t => t.ContentType.IsOfType(ContentTypeNames.CSharpContentType)).First();
var leftProvider = new DiagnosticsSquiggleTaggerProvider(optionsService, diagnosticService, foregroundService, listeners);
var leftTagger = leftProvider.CreateTagger<IErrorTag>(leftBuffer);
using (var leftDisposable = leftTagger as IDisposable)
{
var rightBuffer = diffView.RightView.BufferGraph.GetTextBuffers(t => t.ContentType.IsOfType(ContentTypeNames.CSharpContentType)).First();
var rightProvider = new DiagnosticsSquiggleTaggerProvider(optionsService, diagnosticService, foregroundService, listeners);
var rightTagger = rightProvider.CreateTagger<IErrorTag>(rightBuffer);
using (var rightDisposable = rightTagger as IDisposable)
// set up tagger for both buffers
var leftBuffer = diffView.Viewer.LeftView.BufferGraph.GetTextBuffers(t => t.ContentType.IsOfType(ContentTypeNames.CSharpContentType)).First();
var leftProvider = new DiagnosticsSquiggleTaggerProvider(optionsService, diagnosticService, foregroundService, listeners);
var leftTagger = leftProvider.CreateTagger<IErrorTag>(leftBuffer);
using (var leftDisposable = leftTagger as IDisposable)
{
// wait up to 20 seconds for diagnostics
taskSource.Task.Wait(20000);
if (!taskSource.Task.IsCompleted)
var rightBuffer = diffView.Viewer.RightView.BufferGraph.GetTextBuffers(t => t.ContentType.IsOfType(ContentTypeNames.CSharpContentType)).First();
var rightProvider = new DiagnosticsSquiggleTaggerProvider(optionsService, diagnosticService, foregroundService, listeners);
var rightTagger = rightProvider.CreateTagger<IErrorTag>(rightBuffer);
using (var rightDisposable = rightTagger as IDisposable)
{
// something is wrong
FatalError.Report(new System.Exception("not finished after 20 seconds"));
// wait up to 20 seconds for diagnostics
taskSource.Task.Wait(20000);
if (!taskSource.Task.IsCompleted)
{
// something is wrong
FatalError.Report(new System.Exception("not finished after 20 seconds"));
}
// wait taggers
await waiter.CreateWaitTask();
// check left buffer
var leftSnapshot = leftBuffer.CurrentSnapshot;
var leftSpans = leftTagger.GetTags(leftSnapshot.GetSnapshotSpanCollection()).ToList();
Assert.Equal(1, leftSpans.Count);
// check right buffer
var rightSnapshot = rightBuffer.CurrentSnapshot;
var rightSpans = rightTagger.GetTags(rightSnapshot.GetSnapshotSpanCollection()).ToList();
Assert.Equal(0, rightSpans.Count);
}
// wait taggers
await waiter.CreateWaitTask();
// check left buffer
var leftSnapshot = leftBuffer.CurrentSnapshot;
var leftSpans = leftTagger.GetTags(leftSnapshot.GetSnapshotSpanCollection()).ToList();
Assert.Equal(1, leftSpans.Count);
// check right buffer
var rightSnapshot = rightBuffer.CurrentSnapshot;
var rightSpans = rightTagger.GetTags(rightSnapshot.GetSnapshotSpanCollection()).ToList();
Assert.Equal(0, rightSpans.Count);
}
}
}
......
......@@ -5,6 +5,7 @@ Imports System.Threading.Tasks
Imports System.Windows.Controls
Imports Microsoft.CodeAnalysis.CodeActions
Imports Microsoft.CodeAnalysis.CodeRefactorings
Imports Microsoft.CodeAnalysis.Editor.Implementation.Preview
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.VisualStudio.Text.Differencing
......@@ -87,11 +88,11 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeRefactorings
Dim previewObjects = Await previews.GetPreviewsAsync()
Dim preview = previewObjects(0)
Assert.NotNull(preview)
Assert.True(TypeOf preview Is IWpfDifferenceViewer)
Dim diffView = DirectCast(preview, IWpfDifferenceViewer)
Dim text = diffView.RightView.TextBuffer.AsTextContainer().CurrentText.ToString()
Assert.True(TypeOf preview Is DifferenceViewerPreview)
Dim diffView = DirectCast(preview, DifferenceViewerPreview)
Dim text = diffView.Viewer.RightView.TextBuffer.AsTextContainer().CurrentText.ToString()
Assert.Equal(s_changedDocumentText, text)
diffView.Close()
diffView.Dispose()
' Then comes the removed metadata reference.
preview = previewObjects(1)
......
......@@ -219,10 +219,13 @@ public virtual bool NeedsReanalysisOnOptionChanged(object sender, OptionChangedE
return false;
}
public virtual void LogAnalyzerCountSummary()
public virtual void Shutdown()
{
// virtual for v1 that got deleted in master
}
public abstract void LogAnalyzerCountSummary();
// internal for testing purposes.
internal Action<Exception, DiagnosticAnalyzer, Diagnostic> GetOnAnalyzerException(ProjectId projectId)
{
......
......@@ -46,6 +46,16 @@ private BaseDiagnosticIncrementalAnalyzer GetOrCreateIncrementalAnalyzer(Workspa
return _map.GetValue(workspace, _createIncrementalAnalyzer);
}
public void ShutdownAnalyzerFrom(Workspace workspace)
{
// this should be only called once analyzer associated with the workspace is done.
BaseDiagnosticIncrementalAnalyzer analyzer;
if (_map.TryGetValue(workspace, out analyzer))
{
analyzer.Shutdown();
}
}
private BaseDiagnosticIncrementalAnalyzer CreateIncrementalAnalyzerCallback(Workspace workspace)
{
// subscribe to active context changed event for new workspace
......@@ -188,6 +198,11 @@ public override Task SynchronizeWithBuildAsync(Workspace workspace, ImmutableDic
}
#endregion
public override void Shutdown()
{
Analyzer.Shutdown();
}
public override void LogAnalyzerCountSummary()
{
Analyzer.LogAnalyzerCountSummary();
......
......@@ -24,7 +24,7 @@ internal partial class DiagnosticService : IDiagnosticService
private readonly SimpleTaskQueue _eventQueue;
private readonly object _gate;
private readonly Dictionary<IDiagnosticUpdateSource, Dictionary<object, Data>> _map;
private readonly Dictionary<IDiagnosticUpdateSource, Dictionary<Workspace, Dictionary<object, Data>>> _map;
[ImportingConstructor]
public DiagnosticService([ImportMany] IEnumerable<Lazy<IAsynchronousOperationListener, FeatureMetadata>> asyncListeners) : this()
......@@ -39,7 +39,7 @@ public DiagnosticService([ImportMany] IEnumerable<Lazy<IAsynchronousOperationLis
_listener = new AggregateAsynchronousOperationListener(asyncListeners, FeatureAttribute.DiagnosticService);
_gate = new object();
_map = new Dictionary<IDiagnosticUpdateSource, Dictionary<object, Data>>();
_map = new Dictionary<IDiagnosticUpdateSource, Dictionary<Workspace, Dictionary<object, Data>>>();
}
public event EventHandler<DiagnosticsUpdatedArgs> DiagnosticsUpdated
......@@ -114,12 +114,28 @@ private bool UpdateDataMap(IDiagnosticUpdateSource source, DiagnosticsUpdatedArg
return false;
}
var diagnosticDataMap = _map.GetOrAdd(source, _ => new Dictionary<object, Data>());
// 2 different workspaces (ex, PreviewWorkspaces) can return same Args.Id, we need to
// distinguish them. so we separate diagnostics per workspace map.
var workspaceMap = _map.GetOrAdd(source, _ => new Dictionary<Workspace, Dictionary<object, Data>>());
if (args.Diagnostics.Length == 0 && !workspaceMap.ContainsKey(args.Workspace))
{
// no new diagnostic, and we don't have workspace for it.
return false;
}
var diagnosticDataMap = workspaceMap.GetOrAdd(args.Workspace, _ => new Dictionary<object, Data>());
diagnosticDataMap.Remove(args.Id);
if (diagnosticDataMap.Count == 0 && args.Diagnostics.Length == 0)
{
_map.Remove(source);
workspaceMap.Remove(args.Workspace);
if (workspaceMap.Count == 0)
{
_map.Remove(source);
}
return true;
}
......@@ -258,10 +274,14 @@ public IEnumerable<UpdatedEventArgs> GetDiagnosticsUpdatedEventArgs(Workspace wo
private void AppendMatchingData(
IDiagnosticUpdateSource source, Workspace workspace, ProjectId projectId, DocumentId documentId, object id, List<Data> list)
{
Contract.ThrowIfNull(workspace);
lock (_gate)
{
Dictionary<object, Data> current;
if (!_map.TryGetValue(source, out current))
Dictionary<Workspace, Dictionary<object, Data>> workspaceMap;
if (!_map.TryGetValue(source, out workspaceMap) ||
!workspaceMap.TryGetValue(workspace, out current))
{
return;
}
......@@ -279,9 +299,9 @@ public IEnumerable<UpdatedEventArgs> GetDiagnosticsUpdatedEventArgs(Workspace wo
foreach (var data in current.Values)
{
if (TryAddData(documentId, data, d => d.DocumentId, list) ||
TryAddData(projectId, data, d => d.ProjectId, list) ||
TryAddData(workspace, data, d => d.Workspace, list))
if (TryAddData(workspace, documentId, data, d => d.DocumentId, list) ||
TryAddData(workspace, projectId, data, d => d.ProjectId, list) ||
TryAddData(workspace, workspace, data, d => d.Workspace, list))
{
continue;
}
......@@ -289,13 +309,19 @@ public IEnumerable<UpdatedEventArgs> GetDiagnosticsUpdatedEventArgs(Workspace wo
}
}
private bool TryAddData<T>(T key, Data data, Func<Data, T> keyGetter, List<Data> result) where T : class
private bool TryAddData<T>(Workspace workspace, T key, Data data, Func<Data, T> keyGetter, List<Data> result) where T : class
{
if (key == null)
{
return false;
}
// make sure data is from same workspace. project/documentId can be shared between 2 different workspace
if (workspace != data.Workspace)
{
return false;
}
if (key == keyGetter(data))
{
result.Add(data);
......@@ -314,15 +340,15 @@ private void AssertIfNull(ImmutableArray<DiagnosticData> diagnostics)
}
[Conditional("DEBUG")]
private void AssertIfNull(DiagnosticData diagnostic)
private void AssertIfNull<T>(T obj) where T : class
{
if (diagnostic == null)
if (obj == null)
{
Contract.Requires(false, "who returns invalid data?");
}
}
private struct Data : IEquatable<Data>
private struct Data
{
public readonly Workspace Workspace;
public readonly ProjectId ProjectId;
......@@ -343,27 +369,6 @@ public Data(UpdatedEventArgs args, ImmutableArray<DiagnosticData> diagnostics)
this.Id = args.Id;
this.Diagnostics = diagnostics;
}
public bool Equals(Data other)
{
return this.Workspace == other.Workspace &&
this.ProjectId == other.ProjectId &&
this.DocumentId == other.DocumentId &&
this.Id == other.Id;
}
public override bool Equals(object obj)
{
return (obj is Data) && Equals((Data)obj);
}
public override int GetHashCode()
{
return Hash.Combine(Workspace,
Hash.Combine(ProjectId,
Hash.Combine(DocumentId,
Hash.Combine(Id, 1))));
}
}
}
}
......@@ -28,6 +28,12 @@ public ProjectStates(StateManager owner)
_stateMap = new ConcurrentDictionary<ProjectId, Entry>(concurrencyLevel: 2, capacity: 10);
}
public IEnumerable<StateSet> GetStateSets()
{
// return existing state sets
return _stateMap.Values.SelectMany(e => e.AnalyzerMap.Values).ToImmutableArray();
}
public IEnumerable<StateSet> GetStateSets(ProjectId projectId)
{
var map = GetCachedAnalyzerMap(projectId);
......
......@@ -47,6 +47,15 @@ public IEnumerable<DiagnosticAnalyzer> GetOrCreateAnalyzers(Project project)
return _hostStates.GetAnalyzers(project.Language).Concat(_projectStates.GetOrCreateAnalyzers(project));
}
/// <summary>
/// Return all <see cref="StateSet"/>.
/// This will never create new <see cref="StateSet"/> but will return ones already created.
/// </summary>
public IEnumerable<StateSet> GetStateSets()
{
return _hostStates.GetStateSets().Concat(_projectStates.GetStateSets());
}
/// <summary>
/// Return <see cref="StateSet"/>s for the given <see cref="ProjectId"/>.
/// This will never create new <see cref="StateSet"/> but will return ones already created.
......
......@@ -83,6 +83,34 @@ public bool ContainsAnyDocumentOrProjectDiagnostics(ProjectId projectId)
return !projectState.IsEmpty();
}
public IEnumerable<ProjectId> GetProjectsWithDiagnostics()
{
// quick bail out
if (_activeFileStates.IsEmpty && _projectStates.IsEmpty)
{
return SpecializedCollections.EmptyEnumerable<ProjectId>();
}
if (_activeFileStates.Count == 1 && _projectStates.IsEmpty)
{
// see whether we actually have diagnostics
var kv = _activeFileStates.First();
if (kv.Value.IsEmpty)
{
return SpecializedCollections.EmptyEnumerable<ProjectId>();
}
// we do have diagnostics
return SpecializedCollections.SingletonEnumerable(kv.Key.ProjectId);
}
return new HashSet<ProjectId>(
_activeFileStates.Where(kv => !kv.Value.IsEmpty)
.Select(kv => kv.Key.ProjectId)
.Concat(_projectStates.Where(kv => !kv.Value.IsEmpty())
.Select(kv => kv.Key)));
}
public IEnumerable<DocumentId> GetDocumentsWithDiagnostics(ProjectId projectId)
{
HashSet<DocumentId> set = null;
......
......@@ -7,6 +7,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics.Log;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
......@@ -95,6 +96,24 @@ private void OnProjectAnalyzerReferenceChanged(object sender, ProjectAnalyzerRef
ClearAllDiagnostics(stateSets, project.Id);
}
public override void Shutdown()
{
var stateSets = _stateManager.GetStateSets();
Owner.RaiseBulkDiagnosticsUpdated(raiseEvents =>
{
var handleActiveFile = true;
foreach (var stateSet in stateSets)
{
var projectIds = stateSet.GetProjectsWithDiagnostics();
foreach (var projectId in projectIds)
{
RaiseProjectDiagnosticsRemoved(stateSet, projectId, stateSet.GetDocumentsWithDiagnostics(projectId), handleActiveFile, raiseEvents);
}
}
});
}
private void ClearAllDiagnostics(ImmutableArray<StateSet> stateSets, ProjectId projectId)
{
Owner.RaiseBulkDiagnosticsUpdated(raiseEvents =>
......
......@@ -14,7 +14,6 @@
namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2
{
// TODO: make it to use cache
internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer
{
public override Task AnalyzeSyntaxAsync(Document document, CancellationToken cancellationToken)
......
......@@ -166,9 +166,12 @@ public bool IsAnalyzerSuppressed(DiagnosticAnalyzer analyzer, Project project)
return false;
}
// don't capture project
var projectId = project.Id;
// Skip telemetry logging for supported diagnostics, as that can cause an infinite loop.
Action<Exception, DiagnosticAnalyzer, Diagnostic> onAnalyzerException = (ex, a, diagnostic) =>
AnalyzerHelper.OnAnalyzerException_NoTelemetryLogging(ex, a, diagnostic, _hostDiagnosticUpdateSource, project.Id);
AnalyzerHelper.OnAnalyzerException_NoTelemetryLogging(ex, a, diagnostic, _hostDiagnosticUpdateSource, projectId);
return CompilationWithAnalyzers.IsDiagnosticAnalyzerSuppressed(analyzer, options, onAnalyzerException);
}
......
......@@ -9,8 +9,8 @@
using System.Windows.Navigation;
using Microsoft.CodeAnalysis.Diagnostics.Log;
using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities;
using Microsoft.VisualStudio.Text.Differencing;
using Roslyn.Utilities;
using Microsoft.CodeAnalysis.Editor.Implementation.Preview;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.PreviewPane
{
......@@ -26,7 +26,7 @@ internal partial class PreviewPane : UserControl, IDisposable
private bool _isExpanded;
private double _heightForThreeLineTitle;
private IWpfDifferenceViewer _previewDiffViewer;
private DifferenceViewerPreview _differenceViewerPreview;
public PreviewPane(
Image severityIcon,
......@@ -108,40 +108,87 @@ private FrameworkElement CreatePreviewElement(IReadOnlyList<object> previewItems
var grid = new Grid();
for (var i = 0; i < previewItems.Count; i++)
foreach (var previewItem in previewItems)
{
var previewItem = previewItems[i];
var previewElement = GetPreviewElement(previewItem);
FrameworkElement previewElement = null;
if (previewItem is IWpfDifferenceViewer)
// no preview element
if (previewElement == null)
{
_previewDiffViewer = (IWpfDifferenceViewer)previewItem;
previewElement = _previewDiffViewer.VisualElement;
PreviewDockPanel.Background = _previewDiffViewer.InlineView.Background;
continue;
}
else if (previewItem is string)
// the very first preview
if (grid.RowDefinitions.Count == 0)
{
previewElement = GetPreviewForString((string)previewItem);
grid.RowDefinitions.Add(new RowDefinition());
}
else if (previewItem is FrameworkElement)
else
{
previewElement = (FrameworkElement)previewItem;
grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
}
var rowDefinition = i == 0 ? new RowDefinition() : new RowDefinition() { Height = GridLength.Auto };
grid.RowDefinitions.Add(rowDefinition);
// set row position of the element
Grid.SetRow(previewElement, grid.RowDefinitions.Count - 1);
Grid.SetRow(previewElement, grid.RowDefinitions.IndexOf(rowDefinition));
// add the element to the grid
grid.Children.Add(previewElement);
if (i == 0)
// set width of the grid same as the first element
if (grid.Children.Count == 1)
{
grid.Width = previewElement.Width;
}
}
var preview = grid.Children.Count == 0 ? (FrameworkElement)grid.Children[0] : grid;
return preview;
if (grid.Children.Count == 0)
{
// no preview
return null;
}
// if there is only 1 item, just take preview element as it is without grid
if (grid.Children.Count == 1)
{
var preview = grid.Children[0];
// we need to take it out from visual tree
grid.Children.Clear();
return (FrameworkElement)preview;
}
return grid;
}
private FrameworkElement GetPreviewElement(object previewItem)
{
if (previewItem is DifferenceViewerPreview)
{
// Contract is there should be only 1 diff viewer, otherwise we leak.
Contract.ThrowIfFalse(_differenceViewerPreview == null);
// cache the diff viewer so that we can close it when panel goes away.
// this is a bit wierd since we are mutating state here.
_differenceViewerPreview = (DifferenceViewerPreview)previewItem;
PreviewDockPanel.Background = _differenceViewerPreview.Viewer.InlineView.Background;
var previewElement = _differenceViewerPreview.Viewer.VisualElement;
return previewElement;
}
if (previewItem is string)
{
return GetPreviewForString((string)previewItem);
}
if (previewItem is FrameworkElement)
{
return (FrameworkElement)previewItem;
}
// preview item we don't know how to show to users
return null;
}
private void InitializeHyperlinks(Uri helpLink, string helpLinkToolTipText)
......@@ -235,28 +282,12 @@ private bool HasDescription
}
}
#region IDisposable Implementation
private bool _disposedValue = false;
// VS editor will call Dispose at which point we should Close() the embedded IWpfDifferenceViewer.
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing && (_previewDiffViewer != null) && !_previewDiffViewer.IsClosed)
{
_previewDiffViewer.Close();
}
}
_disposedValue = true;
}
void IDisposable.Dispose()
{
Dispose(true);
// VS editor will call Dispose at which point we should Close() the embedded IWpfDifferenceViewer.
_differenceViewerPreview?.Dispose();
_differenceViewerPreview = null;
}
#endregion
private void LearnMoreHyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册