VSX:如何重用现有的 XML 编辑器来处理转换为 XML 的二进制文件?
VSX: How can I reuse the existing XML editor to handle binary files converted to XML?
我正在尝试为 VS2017(在 C# 中)创建一个扩展 VSPackage,它将二进制数据转换为 XML,在默认的 VS XML 编辑器和 XML 中打开它语言服务,然后在保存时将其转换回二进制。
但是,我无法列出为此需要哪些步骤。在编辑器工厂新建编辑器时暂时想到了以下几点:
- 创建新的文本缓冲区
- 用转换后的 XML 数据喂它
- 创建核心编辑器
- 用文本缓冲区喂它
现在我的尝试是这样的:
private MyPackage _package; // Filled via constructor
private IServiceProvider _serviceProvider; // Filled via SetSite
public int CreateEditorInstance(uint grfCreateDoc, string pszMkDocument, string pszPhysicalView,
IVsHierarchy pvHier, uint itemid, IntPtr punkDocDataExisting, out IntPtr ppunkDocView,
out IntPtr ppunkDocData, out string pbstrEditorCaption, out Guid pguidCmdUI, out int pgrfCDW)
{
// Initialize and validate parameters.
ppunkDocView = IntPtr.Zero;
ppunkDocData = IntPtr.Zero;
pbstrEditorCaption = String.Empty;
pguidCmdUI = Guid.Empty;
pgrfCDW = 0;
VSConstants.CEF createDocFlags = (VSConstants.CEF)grfCreateDoc;
if (!createDocFlags.HasFlag(VSConstants.CEF.OpenFile) && !createDocFlags.HasFlag(VSConstants.CEF.Silent))
return VSConstants.E_INVALIDARG;
if (punkDocDataExisting != IntPtr.Zero)
return VSConstants.VS_E_INCOMPATIBLEDOCDATA;
// Create a sited IVsTextBuffer storing the converted data with the XML data and language service set.
IVsTextLines textLines = _package.CreateComInstance<VsTextBufferClass, IVsTextLines>();
SiteObject(textLines);
string xmlText = BinaryXmlData.GetXmlString(pszMkDocument);
textLines.InitializeContent(xmlText, xmlText.Length);
ErrorHandler.ThrowOnFailure(textLines.SetLanguageServiceID(ref Guids.XmlLanguageServiceGuid));
// Instantiate a sited IVsCodeWindow and feed it with the text buffer.
IVsCodeWindow codeWindow = _package.CreateComInstance<VsCodeWindowClass, IVsCodeWindow>();
SiteObject(codeWindow);
codeWindow.SetBuffer(textLines);
// Return the created instances to the caller.
ppunkDocView = Marshal.GetIUnknownForObject(codeWindow);
ppunkDocData = Marshal.GetIUnknownForObject(textLines);
return VSConstants.S_OK;
}
private void SiteObject(object obj)
{
(obj as IObjectWithSite)?.SetSite(_serviceProvider);
}
// --- CreateComInstance is a method on my package ----
internal TInterface CreateComInstance<TClass, TInterface>()
{
Guid guidT = typeof(TClass).GUID;
Guid guidInterface = typeof(TInterface).GUID;
TInterface instance = (TInterface)CreateInstance(ref guidT, ref guidInterface, typeof(TInterface));
if (instance == null)
throw new COMException($"Could not instantiate {typeof(TClass).Name} / {typeof(TInterface).Name}.");
return instance;
}
当我试图用我的编辑器明确打开一个文件时,它指出“无法用所选编辑器打开该文件。请选择另一个编辑器。”消息没有对我来说没有意义,我尝试用 XML 编辑器打开 XML 数据,但它仍然试图用二进制数据打开文本编辑器。
我被困在这里了,我已经尽我所能为它提供转换后的数据。显然这种方式不对。
- 我如何在中间添加步骤来获取二进制数据,快速将其转换为 XML,然后将其提供给 XML 编辑器?
- 当 XML 编辑器保存文件时,我如何将其存储为二进制文件?
- 是否可以为此重用 XML 编辑器和语言服务?
如果这些问题需要冗长的回答,我很抱歉;如果我能指出正确的方向或一些已经开源的扩展做类似的事情(在 VS 代码编辑器中显示它之前转换文件数据),我会很高兴。
一般的想法是让 Xml 编辑器做它通常做的事情:打开一个 文档。
对于你的情况,如果我理解正确的话,你没有实体的 Xml 文档,所以你必须创建一个。文档是在 Visual Studio 的 Running Object Table.
中注册的东西(它不一定是物理文件)
获得文档后,您只需请求 shell 即可将其打开。您可以再次使用 ROT 来处理 BeforeSave and AfterSave events。这是一些应该完成所有这些的示例代码:
public int CreateEditorInstance(uint grfCreateDoc, string pszMkDocument, string pszPhysicalView, IVsHierarchy pvHier, uint itemid, IntPtr punkDocDataExisting, out IntPtr ppunkDocView, out IntPtr ppunkDocData, out string pbstrEditorCaption, out Guid pguidCmdUI, out int pgrfCDW)
{
ppunkDocView = IntPtr.Zero;
ppunkDocData = IntPtr.Zero;
pbstrEditorCaption = null;
pguidCmdUI = Guid.Empty;
pgrfCDW = 0;
// create your virtual Xml buffer
var data = Package.CreateComInstance<VsTextBufferClass, IVsTextLines>();
SiteObject(data);
// this is where you're supposed to build your virtual Xml content from your binary data
string myXml = "<root>blah</root>";
data.InitializeContent(myXml, myXml.Length);
var dataPtr = Marshal.GetIUnknownForObject(data);
// build a document and register it in the Running Object Table
// this document has no hierarchy (it will be handled by the 'Miscellaneous Files' fallback project)
var rotFlags = _VSRDTFLAGS.RDT_ReadLock | _VSRDTFLAGS.RDT_VirtualDocument;
// come up with a moniker (which will be used as the caption also by the Xml editor)
// Note I presume the moniker is a file path, wich may not always be ok depending on your context
var virtualMk = Path.ChangeExtension(pszMkDocument, ".xml");
var rot = (IVsRunningDocumentTable)_sp.GetService(typeof(SVsRunningDocumentTable));
int hr = rot.RegisterAndLockDocument((uint)rotFlags, virtualMk, null, VSConstants.VSITEMID_NIL, dataPtr, out uint docCookie);
if (hr != 0)
return hr;
try
{
// ask Visual Studio to open that document
var opener = (IVsUIShellOpenDocument)_sp.GetService(typeof(SVsUIShellOpenDocument));
var view = VSConstants.LOGVIEWID_Primary;
opener.OpenDocumentViaProject(virtualMk, ref view,
out Microsoft.VisualStudio.OLE.Interop.IServiceProvider psp,
out IVsUIHierarchy uiHier,
out uint id,
out IVsWindowFrame frame);
if (frame != null)
{
// Hmm.. the dirty bit (the star after the caption) is not updated by the Xml Editor...
// If you close the document (or close VS), it does update it, but it does not react when we type in the editor.
// This is unexpected, so, let's do the "dirty" work ourselves
// hook on text line events from the buffer
var textLineEvents = new TextLineEvents((IConnectionPointContainer)data);
// we want to know when to unadvise, to hook frame events too
((IVsWindowFrame2)frame).Advise(textLineEvents, out uint frameCookie);
textLineEvents.LineTextChanged += (sender, e) =>
{
// get the dirty bit and override the frame's dirty state
((IVsPersistDocData)data).IsDocDataDirty(out int dirty);
frame.SetProperty((int)__VSFPROPID2.VSFPROPID_OverrideDirtyState, dirty != 0 ? true : false);
};
// now handle save events using the rot
var docEventHandler = new RotDocumentEvents(docCookie);
docEventHandler.Saving += (sender, e) =>
{
// this is where you can get the content of the data and save your binary data back
// you can use Saved or Saving
};
docEventHandler.Saved += (sender, e) =>
{
// manual reset of dirty bit...
frame.SetProperty((int)__VSFPROPID2.VSFPROPID_OverrideDirtyState, false);
};
rot.AdviseRunningDocTableEvents(docEventHandler, out uint rootCookie);
frame.Show();
}
}
finally
{
rot.UnlockDocument((uint)_VSRDTFLAGS.RDT_ReadLock, docCookie);
}
return VSConstants.S_OK;
}
private class TextLineEvents : IVsTextLinesEvents, IVsWindowFrameNotify, IVsWindowFrameNotify2
{
public event EventHandler LineTextChanged;
private uint _cookie;
private IConnectionPoint _cp;
public TextLineEvents(IConnectionPointContainer cpc)
{
var textLineEventsGuid = typeof(IVsTextLinesEvents).GUID;
cpc.FindConnectionPoint(ref textLineEventsGuid, out _cp);
_cp.Advise(this, out _cookie);
}
public void OnChangeLineText(TextLineChange[] pTextLineChange, int fLast) => LineTextChanged?.Invoke(this, EventArgs.Empty);
public int OnClose(ref uint pgrfSaveOptions)
{
_cp.Unadvise(_cookie);
return VSConstants.S_OK;
}
public void OnChangeLineAttributes(int iFirstLine, int iLastLine) { }
public int OnShow(int fShow) => VSConstants.S_OK;
public int OnMove() => VSConstants.S_OK;
public int OnSize() => VSConstants.S_OK;
public int OnDockableChange(int fDockable) => VSConstants.S_OK;
}
private class RotDocumentEvents : IVsRunningDocTableEvents3
{
public event EventHandler Saved;
public event EventHandler Saving;
public RotDocumentEvents(uint docCookie)
{
DocCookie = docCookie;
}
public uint DocCookie { get; }
public int OnBeforeSave(uint docCookie)
{
if (docCookie == DocCookie)
{
Saving?.Invoke(this, EventArgs.Empty);
}
return VSConstants.S_OK;
}
public int OnAfterSave(uint docCookie)
{
if (docCookie == DocCookie)
{
Saved?.Invoke(this, EventArgs.Empty);
}
return VSConstants.S_OK;
}
public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) => VSConstants.S_OK;
public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) => VSConstants.S_OK;
public int OnAfterAttributeChange(uint docCookie, uint grfAttribs) => VSConstants.S_OK;
public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame) => VSConstants.S_OK;
public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame) => VSConstants.S_OK;
public int OnAfterAttributeChangeEx(uint docCookie, uint grfAttribs, IVsHierarchy pHierOld, uint itemidOld, string pszMkDocumentOld, IVsHierarchy pHierNew, uint itemidNew, string pszMkDocumentNew) => VSConstants.S_OK;
}
我正在尝试为 VS2017(在 C# 中)创建一个扩展 VSPackage,它将二进制数据转换为 XML,在默认的 VS XML 编辑器和 XML 中打开它语言服务,然后在保存时将其转换回二进制。
但是,我无法列出为此需要哪些步骤。在编辑器工厂新建编辑器时暂时想到了以下几点:
- 创建新的文本缓冲区
- 用转换后的 XML 数据喂它
- 创建核心编辑器
- 用文本缓冲区喂它
现在我的尝试是这样的:
private MyPackage _package; // Filled via constructor
private IServiceProvider _serviceProvider; // Filled via SetSite
public int CreateEditorInstance(uint grfCreateDoc, string pszMkDocument, string pszPhysicalView,
IVsHierarchy pvHier, uint itemid, IntPtr punkDocDataExisting, out IntPtr ppunkDocView,
out IntPtr ppunkDocData, out string pbstrEditorCaption, out Guid pguidCmdUI, out int pgrfCDW)
{
// Initialize and validate parameters.
ppunkDocView = IntPtr.Zero;
ppunkDocData = IntPtr.Zero;
pbstrEditorCaption = String.Empty;
pguidCmdUI = Guid.Empty;
pgrfCDW = 0;
VSConstants.CEF createDocFlags = (VSConstants.CEF)grfCreateDoc;
if (!createDocFlags.HasFlag(VSConstants.CEF.OpenFile) && !createDocFlags.HasFlag(VSConstants.CEF.Silent))
return VSConstants.E_INVALIDARG;
if (punkDocDataExisting != IntPtr.Zero)
return VSConstants.VS_E_INCOMPATIBLEDOCDATA;
// Create a sited IVsTextBuffer storing the converted data with the XML data and language service set.
IVsTextLines textLines = _package.CreateComInstance<VsTextBufferClass, IVsTextLines>();
SiteObject(textLines);
string xmlText = BinaryXmlData.GetXmlString(pszMkDocument);
textLines.InitializeContent(xmlText, xmlText.Length);
ErrorHandler.ThrowOnFailure(textLines.SetLanguageServiceID(ref Guids.XmlLanguageServiceGuid));
// Instantiate a sited IVsCodeWindow and feed it with the text buffer.
IVsCodeWindow codeWindow = _package.CreateComInstance<VsCodeWindowClass, IVsCodeWindow>();
SiteObject(codeWindow);
codeWindow.SetBuffer(textLines);
// Return the created instances to the caller.
ppunkDocView = Marshal.GetIUnknownForObject(codeWindow);
ppunkDocData = Marshal.GetIUnknownForObject(textLines);
return VSConstants.S_OK;
}
private void SiteObject(object obj)
{
(obj as IObjectWithSite)?.SetSite(_serviceProvider);
}
// --- CreateComInstance is a method on my package ----
internal TInterface CreateComInstance<TClass, TInterface>()
{
Guid guidT = typeof(TClass).GUID;
Guid guidInterface = typeof(TInterface).GUID;
TInterface instance = (TInterface)CreateInstance(ref guidT, ref guidInterface, typeof(TInterface));
if (instance == null)
throw new COMException($"Could not instantiate {typeof(TClass).Name} / {typeof(TInterface).Name}.");
return instance;
}
当我试图用我的编辑器明确打开一个文件时,它指出“无法用所选编辑器打开该文件。请选择另一个编辑器。”消息没有对我来说没有意义,我尝试用 XML 编辑器打开 XML 数据,但它仍然试图用二进制数据打开文本编辑器。
我被困在这里了,我已经尽我所能为它提供转换后的数据。显然这种方式不对。
- 我如何在中间添加步骤来获取二进制数据,快速将其转换为 XML,然后将其提供给 XML 编辑器?
- 当 XML 编辑器保存文件时,我如何将其存储为二进制文件?
- 是否可以为此重用 XML 编辑器和语言服务?
如果这些问题需要冗长的回答,我很抱歉;如果我能指出正确的方向或一些已经开源的扩展做类似的事情(在 VS 代码编辑器中显示它之前转换文件数据),我会很高兴。
一般的想法是让 Xml 编辑器做它通常做的事情:打开一个 文档。
对于你的情况,如果我理解正确的话,你没有实体的 Xml 文档,所以你必须创建一个。文档是在 Visual Studio 的 Running Object Table.
中注册的东西(它不一定是物理文件)获得文档后,您只需请求 shell 即可将其打开。您可以再次使用 ROT 来处理 BeforeSave and AfterSave events。这是一些应该完成所有这些的示例代码:
public int CreateEditorInstance(uint grfCreateDoc, string pszMkDocument, string pszPhysicalView, IVsHierarchy pvHier, uint itemid, IntPtr punkDocDataExisting, out IntPtr ppunkDocView, out IntPtr ppunkDocData, out string pbstrEditorCaption, out Guid pguidCmdUI, out int pgrfCDW)
{
ppunkDocView = IntPtr.Zero;
ppunkDocData = IntPtr.Zero;
pbstrEditorCaption = null;
pguidCmdUI = Guid.Empty;
pgrfCDW = 0;
// create your virtual Xml buffer
var data = Package.CreateComInstance<VsTextBufferClass, IVsTextLines>();
SiteObject(data);
// this is where you're supposed to build your virtual Xml content from your binary data
string myXml = "<root>blah</root>";
data.InitializeContent(myXml, myXml.Length);
var dataPtr = Marshal.GetIUnknownForObject(data);
// build a document and register it in the Running Object Table
// this document has no hierarchy (it will be handled by the 'Miscellaneous Files' fallback project)
var rotFlags = _VSRDTFLAGS.RDT_ReadLock | _VSRDTFLAGS.RDT_VirtualDocument;
// come up with a moniker (which will be used as the caption also by the Xml editor)
// Note I presume the moniker is a file path, wich may not always be ok depending on your context
var virtualMk = Path.ChangeExtension(pszMkDocument, ".xml");
var rot = (IVsRunningDocumentTable)_sp.GetService(typeof(SVsRunningDocumentTable));
int hr = rot.RegisterAndLockDocument((uint)rotFlags, virtualMk, null, VSConstants.VSITEMID_NIL, dataPtr, out uint docCookie);
if (hr != 0)
return hr;
try
{
// ask Visual Studio to open that document
var opener = (IVsUIShellOpenDocument)_sp.GetService(typeof(SVsUIShellOpenDocument));
var view = VSConstants.LOGVIEWID_Primary;
opener.OpenDocumentViaProject(virtualMk, ref view,
out Microsoft.VisualStudio.OLE.Interop.IServiceProvider psp,
out IVsUIHierarchy uiHier,
out uint id,
out IVsWindowFrame frame);
if (frame != null)
{
// Hmm.. the dirty bit (the star after the caption) is not updated by the Xml Editor...
// If you close the document (or close VS), it does update it, but it does not react when we type in the editor.
// This is unexpected, so, let's do the "dirty" work ourselves
// hook on text line events from the buffer
var textLineEvents = new TextLineEvents((IConnectionPointContainer)data);
// we want to know when to unadvise, to hook frame events too
((IVsWindowFrame2)frame).Advise(textLineEvents, out uint frameCookie);
textLineEvents.LineTextChanged += (sender, e) =>
{
// get the dirty bit and override the frame's dirty state
((IVsPersistDocData)data).IsDocDataDirty(out int dirty);
frame.SetProperty((int)__VSFPROPID2.VSFPROPID_OverrideDirtyState, dirty != 0 ? true : false);
};
// now handle save events using the rot
var docEventHandler = new RotDocumentEvents(docCookie);
docEventHandler.Saving += (sender, e) =>
{
// this is where you can get the content of the data and save your binary data back
// you can use Saved or Saving
};
docEventHandler.Saved += (sender, e) =>
{
// manual reset of dirty bit...
frame.SetProperty((int)__VSFPROPID2.VSFPROPID_OverrideDirtyState, false);
};
rot.AdviseRunningDocTableEvents(docEventHandler, out uint rootCookie);
frame.Show();
}
}
finally
{
rot.UnlockDocument((uint)_VSRDTFLAGS.RDT_ReadLock, docCookie);
}
return VSConstants.S_OK;
}
private class TextLineEvents : IVsTextLinesEvents, IVsWindowFrameNotify, IVsWindowFrameNotify2
{
public event EventHandler LineTextChanged;
private uint _cookie;
private IConnectionPoint _cp;
public TextLineEvents(IConnectionPointContainer cpc)
{
var textLineEventsGuid = typeof(IVsTextLinesEvents).GUID;
cpc.FindConnectionPoint(ref textLineEventsGuid, out _cp);
_cp.Advise(this, out _cookie);
}
public void OnChangeLineText(TextLineChange[] pTextLineChange, int fLast) => LineTextChanged?.Invoke(this, EventArgs.Empty);
public int OnClose(ref uint pgrfSaveOptions)
{
_cp.Unadvise(_cookie);
return VSConstants.S_OK;
}
public void OnChangeLineAttributes(int iFirstLine, int iLastLine) { }
public int OnShow(int fShow) => VSConstants.S_OK;
public int OnMove() => VSConstants.S_OK;
public int OnSize() => VSConstants.S_OK;
public int OnDockableChange(int fDockable) => VSConstants.S_OK;
}
private class RotDocumentEvents : IVsRunningDocTableEvents3
{
public event EventHandler Saved;
public event EventHandler Saving;
public RotDocumentEvents(uint docCookie)
{
DocCookie = docCookie;
}
public uint DocCookie { get; }
public int OnBeforeSave(uint docCookie)
{
if (docCookie == DocCookie)
{
Saving?.Invoke(this, EventArgs.Empty);
}
return VSConstants.S_OK;
}
public int OnAfterSave(uint docCookie)
{
if (docCookie == DocCookie)
{
Saved?.Invoke(this, EventArgs.Empty);
}
return VSConstants.S_OK;
}
public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) => VSConstants.S_OK;
public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) => VSConstants.S_OK;
public int OnAfterAttributeChange(uint docCookie, uint grfAttribs) => VSConstants.S_OK;
public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame) => VSConstants.S_OK;
public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame) => VSConstants.S_OK;
public int OnAfterAttributeChangeEx(uint docCookie, uint grfAttribs, IVsHierarchy pHierOld, uint itemidOld, string pszMkDocumentOld, IVsHierarchy pHierNew, uint itemidNew, string pszMkDocumentNew) => VSConstants.S_OK;
}