|
// 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.Composition;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.EditorConfigSettings;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.Internal.VisualStudio.Shell.TableControl;
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;
[Export(typeof(SettingsEditorFactory))]
[Guid(SettingsEditorFactoryGuidString)]
internal sealed class SettingsEditorFactory : IVsEditorFactory, IVsEditorFactory4, IDisposable
{
public static readonly Guid SettingsEditorFactoryGuid = new(SettingsEditorFactoryGuidString);
public const string SettingsEditorFactoryGuidString = "68b46364-d378-42f2-9e72-37d86c5f4468";
public const string Extension = ".editorconfig";
private readonly ISettingsAggregator _settingsDataProviderFactory;
private readonly VisualStudioWorkspace _workspace;
private readonly IWpfTableControlProvider _controlProvider;
private readonly ITableManagerProvider _tableMangerProvider;
private readonly IVsEditorAdaptersFactoryService _vsEditorAdaptersFactoryService;
private readonly IThreadingContext _threadingContext;
private ServiceProvider? _vsServiceProvider;
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public SettingsEditorFactory(VisualStudioWorkspace workspace,
IWpfTableControlProvider controlProvider,
ITableManagerProvider tableMangerProvider,
IVsEditorAdaptersFactoryService vsEditorAdaptersFactoryService,
IThreadingContext threadingContext)
{
_settingsDataProviderFactory = workspace.Services.GetRequiredService<ISettingsAggregator>();
_workspace = workspace;
_controlProvider = controlProvider;
_tableMangerProvider = tableMangerProvider;
_vsEditorAdaptersFactoryService = vsEditorAdaptersFactoryService;
_threadingContext = threadingContext;
}
public void Dispose()
{
if (_vsServiceProvider is not null)
{
_vsServiceProvider.Dispose();
_vsServiceProvider = null;
}
}
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;
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;
}
IVsTextLines? textBuffer = null;
if (punkDocDataExisting == IntPtr.Zero)
{
Assumes.NotNull(_vsServiceProvider);
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");
}
// Create the editor
var newEditor = new SettingsEditorPane(_vsEditorAdaptersFactoryService,
_threadingContext,
_settingsDataProviderFactory,
_controlProvider,
_tableMangerProvider,
filePath,
textBuffer,
_workspace);
ppunkDocView = Marshal.GetIUnknownForObject(newEditor);
ppunkDocData = Marshal.GetIUnknownForObject(textBuffer);
pbstrEditorCaption = "";
return VSConstants.S_OK;
}
public int SetSite(IOleServiceProvider psp)
{
_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;
}
|