diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Language/Util/Directory.Build.props | 8 | ||||
-rw-r--r-- | src/Language/Util/LanguageUtil/IntellisenseSourceCache.cs | 117 | ||||
-rw-r--r-- | src/Language/Util/LanguageUtil/IntellisenseUtilities.cs | 100 |
3 files changed, 225 insertions, 0 deletions
diff --git a/src/Language/Util/Directory.Build.props b/src/Language/Util/Directory.Build.props new file mode 100644 index 0000000..800fb86 --- /dev/null +++ b/src/Language/Util/Directory.Build.props @@ -0,0 +1,8 @@ +<Project> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory).., Directory.Build.props))\Directory.Build.props" Condition="'$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory).., Directory.Build.props))' != ''" /> + + <PropertyGroup> + <NonShipping>true</NonShipping> + <IsPackable>false</IsPackable> + </PropertyGroup> +</Project> diff --git a/src/Language/Util/LanguageUtil/IntellisenseSourceCache.cs b/src/Language/Util/LanguageUtil/IntellisenseSourceCache.cs new file mode 100644 index 0000000..d55a153 --- /dev/null +++ b/src/Language/Util/LanguageUtil/IntellisenseSourceCache.cs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft Corporation +// All rights reserved + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Linq; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Utilities; + +namespace Microsoft.VisualStudio.Language.Utilities +{ + internal static class IntellisenseSourceCache + { + // Collections given are stored directly in the cache and should not be modified. + public static IEnumerable<TSource> GetSources<TSource>(ITextView textView, ITextBuffer buffer, Func<ITextBuffer, IReadOnlyCollection<TSource>> sourceCreator) where TSource : IDisposable + { + var viewCache = textView.Properties.GetOrCreateSingletonProperty(() => new ViewSourceCache<TSource>(textView, sourceCreator)); + return viewCache.GetSources(new ITextBuffer[] { buffer }); + } + + // Collections given are stored directly in the cache and should not be modified. + public static IEnumerable<TSource> GetSources<TSource>( + ITextView textView, + IReadOnlyCollection<ITextBuffer> buffers, + Func<ITextBuffer, IReadOnlyCollection<TSource>> sourceCreator) where TSource : IDisposable + { + var viewCache = textView.Properties.GetOrCreateSingletonProperty(() => new ViewSourceCache<TSource>(textView, sourceCreator)); + return viewCache.GetSources(buffers); + } + + private class ViewSourceCache<TSource> where TSource : IDisposable + { + private readonly ITextView _textView; + private readonly Func<ITextBuffer, IReadOnlyCollection<TSource>> _sourceCreator; + private readonly HybridDictionary _bufferCache = new HybridDictionary(); + + public ViewSourceCache(ITextView textView, Func<ITextBuffer, IReadOnlyCollection<TSource>> sourceCreator) + { + _textView = textView; + _sourceCreator = sourceCreator; + + _textView.Closed += (sender, args) => this.HandleViewClosed(); + } + + public IEnumerable<TSource> GetSources(IEnumerable<ITextBuffer> buffers) + { + if (_textView.IsClosed) + { + Debug.Fail("Intellisense is trying to operate after its view has been closed."); + return Enumerable.Empty<TSource>(); + } + + // For each buffer that should be involved, either return the cached source for that buffer, or create a source and + // cache it for later use. + IList<TSource> sources = new FrugalList<TSource>(); + foreach (var buffer in buffers) + { + var bufferSources = (IReadOnlyCollection<TSource>) _bufferCache[buffer]; + + if (bufferSources == null) + { + bufferSources = _sourceCreator(buffer); + _bufferCache.Add(buffer, bufferSources); + + buffer.ContentTypeChanged += OnContentTypeChanged; + } + + foreach (var source in bufferSources) + { + sources.Add(source); + } + } + + return sources; + } + + private void OnContentTypeChanged(object sender, ContentTypeChangedEventArgs e) + { + var buffer = (ITextBuffer)sender; + buffer.ContentTypeChanged -= OnContentTypeChanged; + + var bufferSources = (IEnumerable<TSource>)_bufferCache[buffer]; + + if (bufferSources != null) + { + foreach (TSource source in bufferSources) + { + source.Dispose(); + } + } + + _bufferCache.Remove(buffer); + } + + public void HandleViewClosed() + { + foreach (ITextBuffer buffer in _bufferCache.Keys) + { + buffer.ContentTypeChanged -= OnContentTypeChanged; + } + + foreach (IEnumerable<TSource> sourcesList in _bufferCache.Values) + { + foreach (var source in sourcesList) + { + source.Dispose(); + } + } + + _bufferCache.Clear(); + } + } + } +} diff --git a/src/Language/Util/LanguageUtil/IntellisenseUtilities.cs b/src/Language/Util/LanguageUtil/IntellisenseUtilities.cs new file mode 100644 index 0000000..574ac2e --- /dev/null +++ b/src/Language/Util/LanguageUtil/IntellisenseUtilities.cs @@ -0,0 +1,100 @@ +namespace Microsoft.VisualStudio.Language.Utilities +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.VisualStudio.Text; + using Microsoft.VisualStudio.Text.Editor; + using Microsoft.VisualStudio.Threading; + + internal static class IntellisenseUtilities + { + internal static void ThrowIfNotOnMainThread(JoinableTaskContext joinableTaskContext) + { + if (!joinableTaskContext.IsOnMainThread) + { + throw new InvalidOperationException("Operation must occur on main thread."); + } + } + + internal static ITrackingSpan GetEncapsulatingSpan(ITextView textView, ITrackingSpan span1, ITrackingSpan span2) + { + // If either of the spans is null, return the other one. If they're both null, we'll end up returning null + // (as it should be). + if (span1 == null) + { + return MapTrackingSpanToBuffer(textView, span2); + } + + if (span2 == null) + { + return MapTrackingSpanToBuffer(textView, span1); + } + + var surfaceSpans = new List<SnapshotSpan>(); + surfaceSpans.AddRange(MapTrackingSpanToSpansOnBuffer(textView, span1)); + surfaceSpans.AddRange(MapTrackingSpanToSpansOnBuffer(textView, span2)); + + ITrackingSpan encapsulatingSpan = null; + foreach (var span in surfaceSpans) + { + encapsulatingSpan = GetEncapsulatingSpan( + encapsulatingSpan, + span.Snapshot.CreateTrackingSpan(span, span1.TrackingMode)); + } + + return encapsulatingSpan; + } + + private static ITrackingSpan GetEncapsulatingSpan(ITrackingSpan span1, ITrackingSpan span2) + { + // If either of the spans is null, return the other one. If they're both null, we'll end up returning null + // (as it should be). + if (span1 == null) + { + return span2; + } + + if (span2 == null) + { + return span1; + } + + // Use the other overload if you need mapping between buffers. + if (span1.TextBuffer != span2.TextBuffer) + { + throw new ArgumentException("Spans must be from the same textBuffer"); + } + + var snapshot = span1.TextBuffer.CurrentSnapshot; + var snapSpan1 = span1.GetSpan(snapshot); + var snapSpan2 = span2.GetSpan(snapshot); + + return snapshot.CreateTrackingSpan( + Span.FromBounds( + Math.Min(snapSpan1.Start.Position, snapSpan2.Start.Position), + Math.Max(snapSpan1.End.Position, snapSpan2.End.Position)), + span1.TrackingMode); + } + + private static ITrackingSpan MapTrackingSpanToBuffer(ITextView textView, ITrackingSpan trackingSpan) + { + if (trackingSpan == null) + { + return null; + } + + var spans = MapTrackingSpanToSpansOnBuffer(textView, trackingSpan); + Debug.Assert(spans.Count == 1); + return spans[0].Snapshot.CreateTrackingSpan(spans[0], trackingSpan.TrackingMode); + } + + private static NormalizedSnapshotSpanCollection MapTrackingSpanToSpansOnBuffer(ITextView textView, ITrackingSpan trackingSpan) + { + return textView.BufferGraph.MapUpToBuffer( + trackingSpan.GetSpan(trackingSpan.TextBuffer.CurrentSnapshot), + trackingSpan.TrackingMode, + textView.TextBuffer); + } + } +} |