File: EditorConfigSettings\SettingsEditorPane.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_ozsccwvc_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.ComponentModel.Design;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.EditorConfigSettings;
using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data;
using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.Internal.VisualStudio.Shell.TableControl;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.LanguageServices.EditorConfigSettings.Analyzers.View;
using Microsoft.VisualStudio.LanguageServices.EditorConfigSettings.Analyzers.ViewModel;
using Microsoft.VisualStudio.LanguageServices.EditorConfigSettings.CodeStyle.View;
using Microsoft.VisualStudio.LanguageServices.EditorConfigSettings.CodeStyle.ViewModel;
using Microsoft.VisualStudio.LanguageServices.EditorConfigSettings.NamingStyle.View;
using Microsoft.VisualStudio.LanguageServices.EditorConfigSettings.NamingStyle.ViewModel;
using Microsoft.VisualStudio.LanguageServices.EditorConfigSettings.Whitespace.View;
using Microsoft.VisualStudio.LanguageServices.EditorConfigSettings.Whitespace.ViewModel;
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 static Microsoft.VisualStudio.VSConstants;
 
namespace Microsoft.VisualStudio.LanguageServices.EditorConfigSettings;
 
internal sealed partial class SettingsEditorPane : WindowPane, IOleComponent, IVsDeferredDocView, IVsLinkedUndoClient
{
    private readonly IVsEditorAdaptersFactoryService _vsEditorAdaptersFactoryService;
    private readonly IThreadingContext _threadingContext;
    private readonly ISettingsAggregator _settingsDataProviderService;
    private readonly IWpfTableControlProvider _controlProvider;
    private readonly ITableManagerProvider _tableMangerProvider;
    private readonly string _fileName;
    private readonly IVsTextLines _textBuffer;
    private readonly Workspace _workspace;
    private uint _componentId;
    private IOleUndoManager? _undoManager;
    private SettingsEditorControl? _control;
 
    public SettingsEditorPane(IVsEditorAdaptersFactoryService vsEditorAdaptersFactoryService,
                              IThreadingContext threadingContext,
                              ISettingsAggregator settingsDataProviderService,
                              IWpfTableControlProvider controlProvider,
                              ITableManagerProvider tableMangerProvider,
                              string fileName,
                              IVsTextLines textBuffer,
                              Workspace workspace)
        : base(null)
    {
        _vsEditorAdaptersFactoryService = vsEditorAdaptersFactoryService;
        _threadingContext = threadingContext;
        _settingsDataProviderService = settingsDataProviderService;
        _controlProvider = controlProvider;
        _tableMangerProvider = tableMangerProvider;
        _fileName = fileName;
        _textBuffer = textBuffer;
        _workspace = workspace;
    }
 
    protected override void Initialize()
    {
        base.Initialize();
 
        // Create and initialize the editor
        if (_componentId == default && this.TryGetService<SOleComponentManager, IOleComponentManager>(_threadingContext.JoinableTaskFactory, out var componentManager))
        {
            var componentRegistrationInfo = new[]
            {
                new OLECRINFO
                {
                    cbSize = (uint)Marshal.SizeOf(typeof(OLECRINFO)),
                    grfcrf = (uint)_OLECRF.olecrfNeedIdleTime | (uint)_OLECRF.olecrfNeedPeriodicIdleTime,
                    grfcadvf = (uint)_OLECADVF.olecadvfModal | (uint)_OLECADVF.olecadvfRedrawOff | (uint)_OLECADVF.olecadvfWarningsOff,
                    uIdleTimeInterval = 100
                }
            };
 
            var hr = componentManager.FRegisterComponent(this, componentRegistrationInfo, out _componentId);
            _ = ErrorHandler.Succeeded(hr);
        }
 
        if (this.TryGetService<SOleUndoManager, IOleUndoManager>(_threadingContext.JoinableTaskFactory, out _undoManager))
        {
            var linkCapableUndoMgr = (IVsLinkCapableUndoManager)_undoManager;
            if (linkCapableUndoMgr is not null)
            {
                _ = linkCapableUndoMgr.AdviseLinkedUndoClient(this);
            }
        }
 
        var whitespaceView = GetWhitespaceView();
        var codeStyleView = GetCodeStyleView();
        var namingStyleView = GetNamingStyleView();
        var analyzerView = GetAnalyzerView();
 
        _control = new SettingsEditorControl(
             whitespaceView,
             codeStyleView,
             namingStyleView,
             analyzerView,
             _workspace,
             _fileName,
             _threadingContext,
             _vsEditorAdaptersFactoryService,
             _textBuffer);
 
        RegisterForSearch(_control);
 
        Content = _control;
 
        RegisterIndependentView(true);
        if (this.TryGetService<IMenuCommandService>(_threadingContext.JoinableTaskFactory, out var menuCommandService))
        {
            AddCommand(menuCommandService, GUID_VSStandardCommandSet97, (int)VSStd97CmdID.NewWindow,
                            new EventHandler(OnNewWindow), new EventHandler(OnQueryNewWindow));
            AddCommand(menuCommandService, GUID_VSStandardCommandSet97, (int)VSStd97CmdID.ViewCode,
                            new EventHandler(OnViewCode), new EventHandler(OnQueryViewCode));
        }
 
        return;
 
        ISettingsEditorView GetWhitespaceView()
        {
            return GetView<Setting>(
                static (dataProvider, controlProvider, tableMangerProvider) => new WhitespaceViewModel(dataProvider, controlProvider, tableMangerProvider),
                static viewModel => new WhitespaceSettingsView(viewModel));
        }
 
        ISettingsEditorView GetCodeStyleView()
        {
            return GetView<CodeStyleSetting>(
                static (dataProvider, controlProvider, tableMangerProvider) => new CodeStyleSettingsViewModel(dataProvider, controlProvider, tableMangerProvider),
                static viewModel => new CodeStyleSettingsView(viewModel));
        }
 
        ISettingsEditorView GetNamingStyleView()
        {
            return GetView<NamingStyleSetting>(
                static (dataProvider, controlProvider, tableMangerProvider) => new NamingStyleSettingsViewModel(dataProvider, controlProvider, tableMangerProvider),
                static viewModel => new NamingStyleSettingsView(viewModel));
        }
 
        ISettingsEditorView GetAnalyzerView()
        {
            return GetView<AnalyzerSetting>(
                static (dataProvider, controlProvider, tableMangerProvider) => new AnalyzerSettingsViewModel(dataProvider, controlProvider, tableMangerProvider),
                static viewModel => new AnalyzerSettingsView(viewModel));
        }
 
        ISettingsEditorView GetView<TData>(
            Func<ISettingsProvider<TData>, IWpfTableControlProvider, ITableManagerProvider, IWpfSettingsEditorViewModel> createViewModel,
            Func<IWpfSettingsEditorViewModel, ISettingsEditorView> createView)
        {
            var dataProvider = GetDataProvider<TData>();
            Assumes.NotNull(dataProvider);
            var viewModel = createViewModel(dataProvider, _controlProvider, _tableMangerProvider);
            var view = createView(viewModel);
            return view;
        }
 
        void RegisterForSearch(SettingsEditorControl control)
        {
            var windowSearchHostFactory = this.GetService<SVsWindowSearchHostFactory, IVsWindowSearchHostFactory>(_threadingContext.JoinableTaskFactory);
            var minWidth = (int)control.SearchControlParent.MinWidth;
            var maxWidth = (int)control.SearchControlParent.MaxWidth;
            var searchHandler = new SearchHandler(_threadingContext, minWidth, maxWidth, control.GetTableControls());
            var windowSearchHost = windowSearchHostFactory.CreateWindowSearchHost(control.SearchControlParent);
            windowSearchHost.SetupSearch(searchHandler);
            windowSearchHost.IsVisible = true;
        }
 
        ISettingsProvider<TData>? GetDataProvider<TData>() => _settingsDataProviderService.GetSettingsProvider<TData>(_fileName);
    }
 
    private void OnQueryNewWindow(object sender, EventArgs e)
    {
        var command = (OleMenuCommand)sender;
        command.Enabled = true;
    }
 
    private void OnNewWindow(object sender, EventArgs e)
    {
        NewWindow();
    }
 
    private void OnQueryViewCode(object sender, EventArgs e)
    {
        var command = (OleMenuCommand)sender;
        command.Enabled = true;
    }
 
    private void OnViewCode(object sender, EventArgs e)
    {
        ViewCode();
    }
 
    private void NewWindow()
    {
        if (this.TryGetService<SVsUIShellOpenDocument, IVsUIShellOpenDocument>(_threadingContext.JoinableTaskFactory, out var uishellOpenDocument) &&
            this.TryGetService<SVsWindowFrame, IVsWindowFrame>(_threadingContext.JoinableTaskFactory, out var windowFrameOrig))
        {
            var logicalView = Guid.Empty;
            var hr = uishellOpenDocument.OpenCopyOfStandardEditor(windowFrameOrig, ref logicalView, out var windowFrameNew);
            if (windowFrameNew != null)
            {
                hr = windowFrameNew.Show();
            }
 
            _ = ErrorHandler.ThrowOnFailure(hr);
        }
    }
 
    private void ViewCode()
    {
        var sourceCodeTextEditorGuid = VsEditorFactoryGuid.TextEditor_guid;
 
        // Open the referenced document using our editor.
        VsShellUtilities.OpenDocumentWithSpecificEditor(this, _fileName,
            sourceCodeTextEditorGuid, LOGVIEWID_Primary, out _, out _, out var frame);
        _ = ErrorHandler.ThrowOnFailure(frame.Show());
    }
 
    protected override void OnClose()
    {
        // unhook from Undo related services
        if (_undoManager != null)
        {
            var linkCapableUndoMgr = (IVsLinkCapableUndoManager)_undoManager;
            if (linkCapableUndoMgr != null)
            {
                _ = linkCapableUndoMgr.UnadviseLinkedUndoClient();
            }
 
            // Throw away the undo stack etc.
            // It is important to "zombify" the undo manager when the owning object is shutting down.
            // This is done by calling IVsLifetimeControlledObject.SeverReferencesToOwner on the undoManager.
            // This call will clear the undo and redo stacks. This is particularly important to do if
            // your undo units hold references back to your object. It is also important if you use
            // "mdtStrict" linked undo transactions as this sample does (see IVsLinkedUndoTransactionManager). 
            // When one object involved in linked undo transactions clears its undo/redo stacks, then 
            // the stacks of the other documents involved in the linked transaction will also be cleared. 
            var lco = (IVsLifetimeControlledObject)_undoManager;
            _ = lco.SeverReferencesToOwner();
            _undoManager = null;
        }
 
        if (this.TryGetService<SOleComponentManager, IOleComponentManager>(_threadingContext.JoinableTaskFactory, out var componentManager))
        {
            _ = componentManager.FRevokeComponent(_componentId);
        }
 
        _control?.OnClose();
 
        Dispose(true);
 
        base.OnClose();
    }
 
    public int FDoIdle(uint grfidlef)
    {
        _control?.SynchronizeSettings();
 
        return S_OK;
    }
 
    /// <summary>
    /// Registers an independent view with the IVsTextManager so that it knows
    /// the user is working with a view over the text buffer. This will trigger
    /// the text buffer to prompt the user whether to reload the file if it is
    /// edited outside of the environment.
    /// </summary>
    /// <param name="subscribe">True to subscribe, false to unsubscribe</param>
    private void RegisterIndependentView(bool subscribe)
    {
        if (this.TryGetService<SVsTextManager, IVsTextManager>(_threadingContext.JoinableTaskFactory, out var textManager))
        {
            _ = subscribe
                ? textManager.RegisterIndependentView(this, _textBuffer)
                : textManager.UnregisterIndependentView(this, _textBuffer);
        }
    }
 
    /// <summary>
    /// Helper function used to add commands using IMenuCommandService
    /// </summary>
    /// <param name="menuCommandService"> The IMenuCommandService interface.</param>
    /// <param name="menuGroup"> This guid represents the menu group of the command.</param>
    /// <param name="cmdID"> The command ID of the command.</param>
    /// <param name="commandEvent"> An EventHandler which will be called whenever the command is invoked.</param>
    /// <param name="queryEvent"> An EventHandler which will be called whenever we want to query the status of
    /// the command.  If null is passed in here then no EventHandler will be added.</param>
    private static void AddCommand(IMenuCommandService menuCommandService,
                                   Guid menuGroup,
                                   int cmdID,
                                   EventHandler commandEvent,
                                   EventHandler queryEvent)
    {
        // Create the OleMenuCommand from the menu group, command ID, and command event
        var menuCommandID = new CommandID(menuGroup, cmdID);
        var command = new OleMenuCommand(commandEvent, menuCommandID);
 
        // Add an event handler to BeforeQueryStatus if one was passed in
        if (null != queryEvent)
        {
            command.BeforeQueryStatus += queryEvent;
        }
 
        // Add the command using our IMenuCommandService instance
        menuCommandService.AddCommand(command);
    }
 
    public int get_DocView(out IntPtr ppUnkDocView)
    {
        ppUnkDocView = Marshal.GetIUnknownForObject(this);
        return S_OK;
    }
 
    public int get_CmdUIGuid(out Guid pGuidCmdId)
    {
        pGuidCmdId = SettingsEditorFactory.SettingsEditorFactoryGuid;
        return S_OK;
    }
 
    public int FReserved1(uint dwReserved, uint message, IntPtr wParam, IntPtr lParam) => S_OK;
    public int FPreTranslateMessage(MSG[] pMsg) => S_OK;
    public void OnEnterState(uint uStateID, int fEnter) { }
    public void OnAppActivate(int fActive, uint dwOtherThreadID) { }
    public void OnLoseActivation() { }
    public void OnActivationChange(IOleComponent pic, int fSameComponent, OLECRINFO[] pcrinfo, int fHostIsActivating, OLECHOSTINFO[] pchostinfo, uint dwReserved) { }
    public int FContinueMessageLoop(uint uReason, IntPtr pvLoopData, MSG[] pMsgPeeked) => S_OK;
    public int FQueryTerminate(int fPromptUser) => 1; //true
    public void Terminate() { }
    public IntPtr HwndGetWindow(uint dwWhich, uint dwReserved) => IntPtr.Zero;
    public int OnInterveningUnitBlockingLinkedUndo() => E_FAIL;
}