File: EditorConfigSettings\SettingsEditorFactory.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_blq33qkg_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices)
// 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 System.Runtime.InteropServices;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.EditorConfigSettings;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.Internal.VisualStudio.Shell.TableControl;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell.TableManager;
using Microsoft.VisualStudio.TextManager.Interop;
using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
 
namespace Microsoft.VisualStudio.LanguageServices.EditorConfigSettings;
 
[Guid(SettingsEditorFactoryGuidString)]
internal sealed class SettingsEditorFactory() : IVsEditorFactory, IVsEditorFactory4
{
    private static SettingsEditorFactory? s_instance;
 
    public static readonly Guid SettingsEditorFactoryGuid = new(SettingsEditorFactoryGuidString);
    public const string SettingsEditorFactoryGuidString = "68b46364-d378-42f2-9e72-37d86c5f4468";
    public const string Extension = ".editorconfig";
 
    private ServiceProvider? _vsServiceProvider;
 
    public static SettingsEditorFactory GetInstance()
    {
        s_instance ??= new SettingsEditorFactory();
 
        return s_instance;
    }
 
    public int CreateEditorInstance(uint grfCreateDoc,
                                    string filePath,
                                    string pszPhysicalView,
                                    IVsHierarchy pvHier,
                                    uint itemid,
                                    IntPtr punkDocDataExisting,
                                    out IntPtr ppunkDocView,
                                    out IntPtr ppunkDocData,
                                    out string? pbstrEditorCaption,
                                    out Guid pguidCmdUI,
                                    out int pgrfCDW)
    {
        // Initialize to null
        ppunkDocView = IntPtr.Zero;
        ppunkDocData = IntPtr.Zero;
        pguidCmdUI = SettingsEditorFactoryGuid;
        pgrfCDW = 0;
        pbstrEditorCaption = null;
 
        Assumes.NotNull(_vsServiceProvider);
 
        var componentModel = (IComponentModel)_vsServiceProvider.GetService(typeof(SComponentModel));
        var workspace = componentModel.GetService<VisualStudioWorkspace>();
        if (!workspace.CurrentSolution.Projects.Any(p => p.Language is LanguageNames.CSharp or LanguageNames.VisualBasic))
        {
            // If there are no VB or C# projects loaded in the solution (so an editorconfig file in a C++ project) then we want their
            // editorfactory to present the file instead of use showing ours
            return VSConstants.VS_E_UNSUPPORTEDFORMAT;
        }
 
        if (!workspace.CurrentSolution.Projects.Any(p => p.AnalyzerConfigDocuments.Any(editorconfig => StringComparer.OrdinalIgnoreCase.Equals(editorconfig.FilePath, filePath))))
        {
            // If the user is simply opening an editorconfig file that does not apply to the current solution we just want to show the text view
            return VSConstants.VS_E_UNSUPPORTEDFORMAT;
        }
 
        // Validate inputs
        if ((grfCreateDoc & (VSConstants.CEF_OPENFILE | VSConstants.CEF_SILENT)) == 0)
        {
            return VSConstants.E_INVALIDARG;
        }
 
        var threadingContext = componentModel.GetService<IThreadingContext>();
        IVsTextLines? textBuffer = null;
        if (punkDocDataExisting == IntPtr.Zero)
        {
            if (_vsServiceProvider.TryGetService<SLocalRegistry, ILocalRegistry>(threadingContext.JoinableTaskFactory, out var localRegistry))
            {
                var textLinesGuid = typeof(IVsTextLines).GUID;
                _ = localRegistry.CreateInstance(typeof(VsTextBufferClass).GUID, null, ref textLinesGuid, 1 /*CLSCTX_INPROC_SERVER*/, out var ptr);
                try
                {
                    textBuffer = Marshal.GetObjectForIUnknown(ptr) as IVsTextLines;
                }
                finally
                {
                    _ = Marshal.Release(ptr); // Release RefCount from CreateInstance call
                }
 
                if (textBuffer is IObjectWithSite objectWithSite)
                {
                    var oleServiceProvider = _vsServiceProvider.GetService<IOleServiceProvider>(threadingContext.JoinableTaskFactory);
                    objectWithSite.SetSite(oleServiceProvider);
                }
            }
        }
        else
        {
            textBuffer = Marshal.GetObjectForIUnknown(punkDocDataExisting) as IVsTextLines;
            if (textBuffer == null)
            {
                return VSConstants.VS_E_INCOMPATIBLEDOCDATA;
            }
        }
 
        if (textBuffer is null)
        {
            throw new InvalidOperationException("unable to acquire text buffer");
        }
 
        var settingsDataProviderFactory = workspace.Services.GetRequiredService<ISettingsAggregator>();
        var tableManagerProvider = componentModel.GetService<ITableManagerProvider>();
        var controlProvider = componentModel.GetService<IWpfTableControlProvider>();
        var vsEditorAdaptersFactoryService = componentModel.GetService<IVsEditorAdaptersFactoryService>();
 
        // Create the editor
        var newEditor = new SettingsEditorPane(vsEditorAdaptersFactoryService,
                                               threadingContext,
                                               settingsDataProviderFactory,
                                               controlProvider,
                                               tableManagerProvider,
                                               filePath,
                                               textBuffer,
                                               workspace);
        ppunkDocView = Marshal.GetIUnknownForObject(newEditor);
        ppunkDocData = Marshal.GetIUnknownForObject(textBuffer);
        pbstrEditorCaption = "";
        return VSConstants.S_OK;
    }
 
    public int SetSite(IOleServiceProvider psp)
    {
        // We don't dispose this (as we aren't disposable), but that's fine as it's disposer only clears a field.
        _vsServiceProvider = new ServiceProvider(psp);
        return VSConstants.S_OK;
    }
 
    public int Close() => VSConstants.S_OK;
 
    public int MapLogicalView(ref Guid rguidLogicalView, out string? pbstrPhysicalView)
    {
        pbstrPhysicalView = null;    // initialize out parameter
 
        // we support only a single physical view
        if (VSConstants.LOGVIEWID_Primary == rguidLogicalView)
        {
            return VSConstants.S_OK;        // primary view uses NULL as pbstrPhysicalView
        }
        else
        {
            return VSConstants.E_NOTIMPL;   // you must return E_NOTIMPL for any unrecognized rguidLogicalView values
        }
    }
 
    public object? GetDocumentData(uint grfCreate, string pszMkDocument, IVsHierarchy pHier, uint itemid)
        => null;
 
    public object? GetDocumentView(uint grfCreate, string pszPhysicalView, IVsHierarchy pHier, IntPtr punkDocData, uint itemid)
        => null;
 
    public string? GetEditorCaption(string pszMkDocument, string pszPhysicalView, IVsHierarchy pHier, IntPtr punkDocData, out Guid pguidCmdUI)
        => throw new NotImplementedException();
 
    public bool ShouldDeferUntilIntellisenseIsReady(uint grfCreate, string pszMkDocument, string pszPhysicalView)
        => true;
}