提交 a53928b4 编写于 作者: K Kevin Pilch 提交者: GitHub

Merge pull request #20685 from jasonmalinowski/ensure-projections-are-disconnected

Ensure we disconnect any projection buffers we create for views
......@@ -84,7 +84,7 @@ internal class SyntacticQuickInfoProvider : AbstractQuickInfoProvider
}
var span = new SnapshotSpan(textSnapshot, Span.FromBounds(spanStart, spanEnd));
return this.CreateElisionBufferDeferredContent(span);
return this.CreateProjectionBufferDeferredContent(span);
}
private static bool IsScopeBlock(SyntaxNode node)
......
......@@ -292,16 +292,9 @@ private IQuickInfoProvider CreateProvider(TestWorkspace workspace)
var state = await provider.GetItemAsync(document, position, cancellationToken: CancellationToken.None);
Assert.NotNull(state);
var viewHostingControl = (ViewHostingControl)((ElisionBufferDeferredContent)state.Content).Create();
try
{
var actualContent = viewHostingControl.ToString();
Assert.Equal(expectedContent, actualContent);
}
finally
{
viewHostingControl.TextView_TestOnly.Close();
}
var viewHostingControl = (ViewHostingControl)((ProjectionBufferDeferredContent)state.Content).Create();
var actualContent = viewHostingControl.GetText_TestOnly();
Assert.Equal(expectedContent, actualContent);
}
protected override Task TestInMethodAsync(string code, string expectedContent, string expectedDocumentationComment = null)
......
......@@ -573,7 +573,7 @@
<Compile Include="Implementation\Intellisense\QuickInfo\Controller_OnCaretPositionChanged.cs" />
<Compile Include="Implementation\Intellisense\QuickInfo\Controller_OnTextViewBufferPostChanged.cs" />
<Compile Include="Implementation\Intellisense\QuickInfo\DeferredContent\ClassifiableDeferredContent.cs" />
<Compile Include="Implementation\Intellisense\QuickInfo\DeferredContent\ElisionBufferDeferredContent.cs" />
<Compile Include="Implementation\Intellisense\QuickInfo\DeferredContent\ProjectionBufferDeferredContent.cs" />
<Compile Include="Implementation\Intellisense\QuickInfo\DeferredContent\QuickInfoDisplayDeferredContent.cs" />
<Compile Include="Implementation\Intellisense\QuickInfo\DeferredContent\SymbolGlyphDeferredContent.cs" />
<Compile Include="Implementation\Intellisense\IDocumentProvider.cs" />
......
......@@ -14,10 +14,10 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo
{
/// <summary>
/// Creates quick info content out of the span of an existing snapshot. The span will be
/// used to create an elision buffer out that will then be displayed in the quick info
/// used to create an projection buffer out that will then be displayed in the quick info
/// window.
/// </summary>
internal class ElisionBufferDeferredContent : IDeferredQuickInfoContent
internal class ProjectionBufferDeferredContent : IDeferredQuickInfoContent
{
private readonly SnapshotSpan _span;
private readonly IProjectionBufferFactoryService _projectionBufferFactoryService;
......@@ -26,7 +26,7 @@ internal class ElisionBufferDeferredContent : IDeferredQuickInfoContent
private readonly IContentType _contentType;
private readonly ITextViewRoleSet _roleSet;
public ElisionBufferDeferredContent(
public ProjectionBufferDeferredContent(
SnapshotSpan span,
IProjectionBufferFactoryService projectionBufferFactoryService,
IEditorOptionsFactoryService editorOptionsFactoryService,
......@@ -63,9 +63,9 @@ private IWpfTextView CreateView(ITextBuffer buffer)
return view;
}
private IElisionBuffer CreateBuffer()
private IProjectionBuffer CreateBuffer()
{
return _projectionBufferFactoryService.CreateElisionBufferWithoutIndentation(
return _projectionBufferFactoryService.CreateProjectionBufferWithoutIndentation(
_editorOptionsFactoryService.GlobalOptions, _contentType, _span);
}
}
......
......@@ -151,9 +151,9 @@ protected IDeferredQuickInfoContent CreateGlyphDeferredContent(ISymbol symbol)
return new DocumentationCommentDeferredContent(documentationComment, _typeMap);
}
protected IDeferredQuickInfoContent CreateElisionBufferDeferredContent(SnapshotSpan span)
protected IDeferredQuickInfoContent CreateProjectionBufferDeferredContent(SnapshotSpan span)
{
return new ElisionBufferDeferredContent(
return new ProjectionBufferDeferredContent(
span, _projectionBufferFactoryService, _editorOptionsFactoryService, _textEditorFactoryService);
}
}
......
......@@ -156,7 +156,7 @@ private Span TrimStartingNewlines(Span span)
private ITextBuffer CreateElisionBufferWithoutIndentation(
ITextBuffer dataBuffer, Span shortHintSpan)
{
return _projectionBufferFactoryService.CreateElisionBufferWithoutIndentation(
return _projectionBufferFactoryService.CreateProjectionBufferWithoutIndentation(
_editorOptionsFactoryService.GlobalOptions,
contentType: null,
exposedSpans: new SnapshotSpan(dataBuffer.CurrentSnapshot, shortHintSpan));
......
......@@ -30,19 +30,19 @@ internal static class IProjectionBufferFactoryServiceExtensions
[BaseDefinition("projection")]
public static readonly ContentTypeDefinition RoslynPreviewContentTypeDefinition;
public static IElisionBuffer CreateElisionBufferWithoutIndentation(
public static IProjectionBuffer CreateProjectionBufferWithoutIndentation(
this IProjectionBufferFactoryService factoryService,
IEditorOptions editorOptions,
IContentType contentType = null,
params SnapshotSpan[] exposedSpans)
{
return factoryService.CreateElisionBufferWithoutIndentation(
return factoryService.CreateProjectionBufferWithoutIndentation(
editorOptions,
contentType,
(IEnumerable<SnapshotSpan>)exposedSpans);
}
public static IElisionBuffer CreateElisionBufferWithoutIndentation(
public static IProjectionBuffer CreateProjectionBufferWithoutIndentation(
this IProjectionBufferFactoryService factoryService,
IEditorOptions editorOptions,
IContentType contentType,
......@@ -61,37 +61,55 @@ internal static class IProjectionBufferFactoryServiceExtensions
}
contentType = contentType ?? factoryService.ProjectionContentType;
var elisionBuffer = factoryService.CreateElisionBuffer(
null, spans, ElisionBufferOptions.None, contentType);
var projectionBuffer = factoryService.CreateProjectionBuffer(
projectionEditResolver: null,
sourceSpans: Array.Empty<object>(),
options: ProjectionBufferOptions.None,
contentType: contentType);
if (spans.Count > 0)
{
var snapshot = spans.First().Snapshot;
var buffer = snapshot.TextBuffer;
var finalSpans = new List<object>();
// We need to figure out the shorted indentation level of the exposed lines. We'll
// then remove that indentation from all lines.
var indentationColumn = DetermineIndentationColumn(editorOptions, spans);
var spansToElide = new List<Span>();
foreach (var span in spans)
{
var snapshot = span.Snapshot;
var startLineNumber = snapshot.GetLineNumberFromPosition(span.Start);
var endLineNumber = snapshot.GetLineNumberFromPosition(span.End);
for (var lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++)
{
// Compute the span clamped to this line
var line = snapshot.GetLineFromLineNumber(lineNumber);
var lineOffsetOfColumn = line.GetLineOffsetFromColumn(indentationColumn, editorOptions);
spansToElide.Add(Span.FromBounds(line.Start, line.Start + lineOffsetOfColumn));
var finalSpanStart = Math.Max(line.Start, span.Start);
var finalSpanEnd = Math.Min(line.EndIncludingLineBreak, span.End);
// We'll only offset if our span doesn't already start at the start of the line. See the similar exclusion in
// DetermineIndentationColumn that this matches.
if (line.Start == finalSpanStart)
{
finalSpanStart += line.GetLineOffsetFromColumn(indentationColumn, editorOptions);
// Paranoia: what if the indentation reversed our ordering?
if (finalSpanStart > finalSpanEnd)
{
finalSpanStart = finalSpanEnd;
}
}
// We don't expect edits to happen while this projection buffer is active. We'll choose EdgeExclusive so
// if they do we don't end up in any cases where there is overlapping source spans.
finalSpans.Add(snapshot.CreateTrackingSpan(Span.FromBounds(finalSpanStart, finalSpanEnd), SpanTrackingMode.EdgeExclusive));
}
}
elisionBuffer.ElideSpans(new NormalizedSpanCollection(spansToElide));
projectionBuffer.InsertSpans(0, finalSpans);
}
return elisionBuffer;
return projectionBuffer;
}
private static int DetermineIndentationColumn(
......
......@@ -6,6 +6,7 @@
using System.Windows.Media;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Projection;
namespace Microsoft.CodeAnalysis.Editor.Shared.Utilities
{
......@@ -14,6 +15,9 @@ internal class ViewHostingControl : ContentControl
private readonly Func<ITextBuffer, IWpfTextView> _createView;
private readonly Func<ITextBuffer> _createBuffer;
private ITextBuffer _createdTextBuffer;
private IWpfTextView _createdView;
public ViewHostingControl(
Func<ITextBuffer, IWpfTextView> createView,
Func<ITextBuffer> createBuffer)
......@@ -25,18 +29,30 @@ internal class ViewHostingControl : ContentControl
this.IsVisibleChanged += OnIsVisibleChanged;
}
private void EnsureBufferCreated()
{
if (_createdTextBuffer == null)
{
_createdTextBuffer = _createBuffer();
}
}
private void EnsureContentCreated()
{
if (this.Content == null)
{
EnsureBufferCreated();
_createdView = _createView(_createdTextBuffer);
this.Content = _createdView.VisualElement;
}
}
public ITextView TextView_TestOnly
{
get
{
var view = (IWpfTextView)this.Content;
if (view == null)
{
view = _createView(_createBuffer());
this.Content = view.VisualElement;
}
return view;
EnsureContentCreated();
return (ITextView)this.Content;
}
}
......@@ -45,26 +61,31 @@ private void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArg
var nowVisible = (bool)e.NewValue;
if (nowVisible)
{
if (this.Content == null)
{
this.Content = _createView(_createBuffer()).VisualElement;
}
EnsureContentCreated();
}
else
{
((ITextView)this.Content).Close();
this.Content = null;
_createdView.Close();
_createdView = null;
// If a projection buffer has a source span from another buffer, the projection buffer is held alive by the other buffer too.
// This means that a one-off projection buffer created for a tooltip would be kept alive as long as the underlying file
// is still open. Removing the source spans from the projection buffer ensures the projection buffer can be GC'ed.
if (_createdTextBuffer is IProjectionBuffer projectionBuffer)
{
projectionBuffer.DeleteSpans(0, projectionBuffer.CurrentSnapshot.SpanCount);
}
_createdTextBuffer = null;
}
}
public override string ToString()
public string GetText_TestOnly()
{
if (this.Content != null)
{
return ((ITextView)this.Content).TextBuffer.CurrentSnapshot.GetText();
}
return _createBuffer().CurrentSnapshot.GetText();
EnsureBufferCreated();
return _createdTextBuffer.CurrentSnapshot.GetText();
}
}
}
......@@ -27,7 +27,7 @@ public void TestCreateElisionBufferWithoutIndentation()
line 2
line 3", contentTypeRegistryService.GetContentType("text"));
var elisionBuffer = IProjectionBufferFactoryServiceExtensions.CreateElisionBufferWithoutIndentation(
var elisionBuffer = IProjectionBufferFactoryServiceExtensions.CreateProjectionBufferWithoutIndentation(
exportProvider.GetExportedValue<IProjectionBufferFactoryService>(),
exportProvider.GetExportedValue<IEditorOptionsFactoryService>().GlobalOptions,
contentType: null,
......
......@@ -155,7 +155,7 @@ End Sub
var tags = await GetTagsFromWorkspaceAsync(workspace);
var hints = tags.Select(x => x.CollapsedHintForm).Cast<ViewHostingControl>().ToArray();
Assert.Equal("Sub Main(args As String())\r\nEnd Sub", hints[1].ToString()); // method
Assert.Equal("Sub Main(args As String())\r\nEnd Sub", hints[1].GetText_TestOnly()); // method
hints.Do(v => v.TextView_TestOnly.Close());
}
}
......@@ -180,4 +180,4 @@ private static async Task<List<IOutliningRegionTag>> GetTagsFromWorkspaceAsync(T
return context.tagSpans.Select(x => x.Tag).ToList();
}
}
}
\ No newline at end of file
}
......@@ -140,7 +140,7 @@ private ContentControl CreateToolTipContent(ITextBuffer textBuffer)
PredefinedTextViewRoles.Document,
PredefinedTextViewRoles.Editable);
var content = new ElisionBufferDeferredContent(
var content = new ProjectionBufferDeferredContent(
snapshotSpan,
Presenter.ProjectionBufferFactoryService,
Presenter.EditorOptionsFactoryService,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册