/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/VisualStudio/Core/Def/Snippets/AbstractSnippetInfoService.cs
254 строки
10 KB
Cyrus Najmabadi
Remove unnecessary nullable directives
25 мар 2025, 21:58
25 мар 2025, 21:58
2a3f854
Код
Авторство
О чём код?
// 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.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Snippets; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.TextManager.Interop; using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Snippets; /// <summary> /// This service is created on the UI thread during package initialization, but it must not /// block the initialization process. /// </summary> internal abstract class AbstractSnippetInfoService : ISnippetInfoService, IVsExpansionEvents { private readonly Guid _languageGuidForSnippets; private IVsExpansionManager? _expansionManager; /// <summary> /// Initialize these to empty values. When returning from <see cref="GetSnippetsIfAvailable "/> /// and <see cref="SnippetShortcutExists_NonBlocking"/>, we return the current set of known /// snippets rather than waiting for initial results. /// </summary> protected ImmutableArray<SnippetInfo> snippets = []; protected IImmutableSet<string> snippetShortcuts = ImmutableHashSet.Create<string>(); // Guard the snippets and snippetShortcut fields so that returned result sets are always // complete. protected object cacheGuard = new(); private readonly IAsynchronousOperationListener _waiter; private readonly IThreadingContext _threadingContext; public AbstractSnippetInfoService( IThreadingContext threadingContext, Shell.IAsyncServiceProvider serviceProvider, Guid languageGuidForSnippets, IAsynchronousOperationListenerProvider listenerProvider) { _waiter = listenerProvider.GetListener(FeatureAttribute.Snippets); _languageGuidForSnippets = languageGuidForSnippets; _threadingContext = threadingContext; _threadingContext.RunWithShutdownBlockAsync((_) => InitializeAndPopulateSnippetsCacheAsync(serviceProvider)); } private async Task InitializeAndPopulateSnippetsCacheAsync(Shell.IAsyncServiceProvider asyncServiceProvider) { await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); var textManager = (IVsTextManager2?)await asyncServiceProvider.GetServiceAsync(typeof(SVsTextManager)).ConfigureAwait(true); Assumes.Present(textManager); if (textManager.GetExpansionManager(out _expansionManager) == VSConstants.S_OK) { ComEventSink.Advise<IVsExpansionEvents>(_expansionManager, this); await PopulateSnippetCacheAsync().ConfigureAwait(false); } } public int OnAfterSnippetsUpdate() { _threadingContext.ThrowIfNotOnUIThread(); if (_expansionManager != null) { _threadingContext.RunWithShutdownBlockAsync((_) => PopulateSnippetCacheAsync()); } return VSConstants.S_OK; } public int OnAfterSnippetsKeyBindingChange([ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD")] uint dwCmdGuid, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD")] uint dwCmdId, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.BOOL")] int fBound) => VSConstants.S_OK; public IEnumerable<SnippetInfo> GetSnippetsIfAvailable() { // Immediately return the known set of snippets, even if we're still in the process // of calculating a more up-to-date list. lock (cacheGuard) { return snippets; } } public bool SnippetShortcutExists_NonBlocking(string? shortcut) { if (shortcut == null) { return false; } // Check against the known set of snippets, even if we're still in the process of // calculating a more up-to-date list. lock (cacheGuard) { return snippetShortcuts.Contains(shortcut); } } public virtual bool ShouldFormatSnippet(SnippetInfo snippetInfo) => false; private async Task PopulateSnippetCacheAsync() { using var token = _waiter.BeginAsyncOperation(GetType().Name + ".Start"); RoslynDebug.Assert(_expansionManager != null); // In Dev14 Update2+ the platform always provides an IExpansion Manager var expansionManager = (IExpansionManager)_expansionManager; // Call the asynchronous IExpansionManager API from a background thread await TaskScheduler.Default; var expansionEnumerator = await expansionManager.EnumerateExpansionsAsync( _languageGuidForSnippets, 0, // shortCutOnly [], // types 0, // countTypes 1, // includeNULLTypes 1 // includeDulicates: Allows snippets with the same title but different shortcuts ).ConfigureAwait(false); // The rest of the process requires being on the UI thread, see the explanation on // PopulateSnippetCacheFromExpansionEnumeration for details await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); PopulateSnippetCacheFromExpansionEnumeration(expansionEnumerator); } /// <remarks> /// This method must be called on the UI thread because it eventually calls into /// IVsExpansionEnumeration.Next, which must be called on the UI thread due to an issue /// with how the call is marshalled. /// /// The second parameter for IVsExpansionEnumeration.Next is defined like this: /// [ComAliasName("Microsoft.VisualStudio.TextManager.Interop.VsExpansion")] IntPtr[] rgelt /// /// We pass a pointer for rgelt that we expect to be populated as the result. This /// eventually calls into the native CExpansionEnumeratorShim::Next method, which has the /// same contract of expecting a non-null rgelt that it can drop expansion data into. When /// we call from the UI thread, this transition from managed code to the /// CExpansionEnumeratorShim goes smoothly and everything works. /// /// When we call from a background thread, the COM marshaller has to move execution to the /// UI thread, and as part of this process it uses the interface as defined in the idl to /// set up the appropriate arguments to pass. The same parameter from the idl is defined as /// [out, size_is(celt), length_is(*pceltFetched)] VsExpansion **rgelt /// /// Because rgelt is specified as an <c>out</c> parameter, the marshaller is discarding the /// pointer we passed and substituting the null reference. This then causes a null /// reference exception in the shim. Calling from the UI thread avoids this marshaller. /// </remarks> private void PopulateSnippetCacheFromExpansionEnumeration(IVsExpansionEnumeration expansionEnumerator) { _threadingContext.ThrowIfNotOnUIThread(); var updatedSnippets = ExtractSnippetInfo(expansionEnumerator); var updatedSnippetShortcuts = GetShortcutsHashFromSnippets(updatedSnippets); lock (cacheGuard) { snippets = updatedSnippets; snippetShortcuts = updatedSnippetShortcuts; } } private ImmutableArray<SnippetInfo> ExtractSnippetInfo(IVsExpansionEnumeration expansionEnumerator) { _threadingContext.ThrowIfNotOnUIThread(); var snippetListBuilder = ImmutableArray.CreateBuilder<SnippetInfo>(); var snippetInfo = new VsExpansion(); var pSnippetInfo = new IntPtr[1]; try { // Allocate enough memory for one VSExpansion structure. This memory is filled in by the Next method. pSnippetInfo[0] = Marshal.AllocCoTaskMem(Marshal.SizeOf(snippetInfo)); expansionEnumerator.GetCount(out var count); for (uint i = 0; i < count; i++) { expansionEnumerator.Next(1, pSnippetInfo, out var fetched); if (fetched > 0) { // Convert the returned blob of data into a structure that can be read in managed code. snippetInfo = ConvertToVsExpansionAndFree(pSnippetInfo[0]); if (!string.IsNullOrEmpty(snippetInfo.shortcut)) { snippetListBuilder.Add(new SnippetInfo(snippetInfo.shortcut, snippetInfo.title, snippetInfo.description, snippetInfo.path)); } } } } finally { Marshal.FreeCoTaskMem(pSnippetInfo[0]); } return snippetListBuilder.ToImmutableAndClear(); } protected static IImmutableSet<string> GetShortcutsHashFromSnippets(ImmutableArray<SnippetInfo> updatedSnippets) { return new HashSet<string>(updatedSnippets.Select(s => s.Shortcut), StringComparer.OrdinalIgnoreCase) .ToImmutableHashSet(StringComparer.OrdinalIgnoreCase); } private static VsExpansion ConvertToVsExpansionAndFree(IntPtr expansionPtr) { var buffer = (VsExpansionWithIntPtrs)Marshal.PtrToStructure(expansionPtr, typeof(VsExpansionWithIntPtrs)); var expansion = new VsExpansion(); ConvertToStringAndFree(ref buffer.DescriptionPtr, ref expansion.description); ConvertToStringAndFree(ref buffer.PathPtr, ref expansion.path); ConvertToStringAndFree(ref buffer.ShortcutPtr, ref expansion.shortcut); ConvertToStringAndFree(ref buffer.TitlePtr, ref expansion.title); return expansion; } private static void ConvertToStringAndFree(ref IntPtr ptr, ref string? str) { if (ptr != IntPtr.Zero) { str = Marshal.PtrToStringBSTR(ptr); Marshal.FreeBSTR(ptr); ptr = IntPtr.Zero; } } /// <summary> /// This structure is used to facilitate the interop calls with IVsExpansionEnumeration. /// </summary> [StructLayout(LayoutKind.Sequential)] private struct VsExpansionWithIntPtrs { public IntPtr PathPtr; public IntPtr TitlePtr; public IntPtr ShortcutPtr; public IntPtr DescriptionPtr; } }