我终于成功地将投影缓冲区嵌入到工具窗口中,并将它们连接到 C# 的语言服务。一个警告:这种方法仅适用于使用 Roslyn 的 Visual Studio。我已经发布了一个可供您使用的Github 项目以及随附的博客文章。
您的问题的答案很长,并且涉及到太多移动的部分,因此它不适合 StackOverflow 的问答风格。话虽如此,我将总结必要的步骤并包含一些相关代码。
以下示例创建由文件的前 100 个字符组成的文件的投影缓冲区。
我们首先IVsInvisibleEditor
为给定的文件路径创建一个并为其创建一个代码窗口。我们将此代码窗口的内容设置IVsTextLines
为IVsInvisibleEditor
.
"CustomProjectionRole"
然后我们在这个代码窗口的文本缓冲区上设置一个自定义角色。此角色允许我们通过导出的 MEF 自定义文本缓冲区ITextViewModelProvider
。
public IWpfTextViewHost CreateEditor(string filePath, int start = 0, int end = 100)
{
//IVsInvisibleEditors are in-memory represenations of typical Visual Studio editors.
//Language services, highlighting and error squiggles are hooked up to these editors
//for us once we convert them to WpfTextViews.
var invisibleEditor = GetInvisibleEditor(filePath);
var docDataPointer = IntPtr.Zero;
Guid guidIVsTextLines = typeof(IVsTextLines).GUID;
ErrorHandler.ThrowOnFailure(invisibleEditor.GetDocData(
fEnsureWritable: 1
, riid: ref guidIVsTextLines
, ppDocData: out docDataPointer));
IVsTextLines docData = (IVsTextLines)Marshal.GetObjectForIUnknown(docDataPointer);
//Create a code window adapter
var codeWindow = _editorAdapter.CreateVsCodeWindowAdapter(VisualStudioServices.OLEServiceProvider);
ErrorHandler.ThrowOnFailure(codeWindow.SetBuffer(docData));
//Get a text view for our editor which we will then use to get the WPF control for that editor.
IVsTextView textView;
ErrorHandler.ThrowOnFailure(codeWindow.GetPrimaryView(out textView));
//We add our own role to this text view. Later this will allow us to selectively modify
//this editor without getting in the way of Visual Studio's normal editors.
var roles = _editorFactoryService.DefaultRoles.Concat(new string[] { "CustomProjectionRole" });
var vsTextBuffer = docData as IVsTextBuffer;
var textBuffer = _editorAdapter.GetDataBuffer(vsTextBuffer);
textBuffer.Properties.AddProperty("StartPosition", start);
textBuffer.Properties.AddProperty("EndPosition", end);
var guid = VSConstants.VsTextBufferUserDataGuid.VsTextViewRoles_guid;
((IVsUserData)codeWindow).SetData(ref guid, _editorFactoryService.CreateTextViewRoleSet(roles).ToString());
_currentlyFocusedTextView = textView;
var textViewHost = _editorAdapter.GetWpfTextViewHost(textView);
return textViewHost;
}
我们现在创建一个IVsTextViewModelProvider
创建并返回一个ProjectionTextViewModel
. 这ProjectionTextViewModel
会在其可视缓冲区中保存一个投影缓冲区。这意味着当这个缓冲区被显示时,投影缓冲区就是所显示的。但是,后备数据缓冲区的语言服务运行正常。
[Export(typeof(ITextViewModelProvider)), ContentType("CSharp"), TextViewRole("CustomProjectionRole")]
internal class ProjectionTextViewModelProvider : ITextViewModelProvider
{
public ITextViewModel CreateTextViewModel(ITextDataModel dataModel, ITextViewRoleSet roles)
{
//Create a projection buffer based on the specified start and end position.
var projectionBuffer = CreateProjectionBuffer(dataModel);
//Display this projection buffer in the visual buffer, while still maintaining
//the full file buffer as the underlying data buffer.
var textViewModel = new ProjectionTextViewModel(dataModel, projectionBuffer);
return textViewModel;
}
public IProjectionBuffer CreateProjectionBuffer(ITextDataModel dataModel)
{
//retrieve start and end position that we saved in MyToolWindow.CreateEditor()
var startPosition = (int)dataModel.DataBuffer.Properties.GetProperty("StartPosition");
var endPosition = (int)dataModel.DataBuffer.Properties.GetProperty("EndPosition");
var length = endPosition - startPosition;
//Take a snapshot of the text within these indices.
var textSnapshot = dataModel.DataBuffer.CurrentSnapshot;
var trackingSpan = textSnapshot.CreateTrackingSpan(startPosition, length, SpanTrackingMode.EdgeExclusive);
//Create the actual projection buffer
var projectionBuffer = ProjectionBufferFactory.CreateProjectionBuffer(
null
, new List<object>() { trackingSpan }
, ProjectionBufferOptions.None
);
return projectionBuffer;
}
[Import]
public IProjectionBufferFactoryService ProjectionBufferFactory { get; set; }
}
希望这能让任何未来的访客有一个良好的开端。