|
// 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;
}
|