可以通过定义要为其提供完成的标识符,然后触发完成会话来实现基于语言的语句完成。 可以在语言服务的上下文中定义语句完成,定义自己的文件扩展名和内容类型,然后仅显示该类型的完成。 或者,可以触发现有内容类型(例如“纯文本”)的完成。 本演练演示如何为“纯文本”内容类型(文本文件的内容类型)触发语句完成。 “text”内容类型是所有其他内容类型的上级,包括代码和 XML 文件。
语句完成通常通过键入某些字符(例如,键入标识符(如“using”)的开头来触发。 通常通过按 空格键、 Tab 或 Enter 键提交所选内容来消除它。 通过使用键击( IOleCommandTarget 接口)的命令处理程序和实现 IVsTextViewCreationListener 接口的处理程序提供程序,可以实现在键入字符时触发的 IntelliSense 功能。 若要创建完成源(即参与完成的标识符列表),请实现 ICompletionSource 接口和完成源提供程序( ICompletionSourceProvider 接口)。 提供程序是托管扩展性框架(MEF)组件部件。 它们负责导出源和控制器类以及导入服务和中转站(例如 ITextStructureNavigatorSelectorService,在文本缓冲区中启用导航)以及 ICompletionBroker触发完成会话的导航。
本演练演示如何为硬编码的标识符集实现语句完成。 在完整的实现中,语言服务和语言文档负责提供该内容。
创建 MEF 项目
创建 MEF 项目
创建 C# VSIX 项目。 (在 “新建项目 ”对话框,选择 Visual C# /扩展性,然后选择 VSIX Project。)将解决方案 CompletionTest
命名为 。
向项目添加编辑器分类器项模板。 有关详细信息,请参阅使用编辑器项模板创建扩展。
删除现有的类文件。
将以下引用添加到项目,并确保 CopyLocal 设置为 false
:
Microsoft.VisualStudio.Editor
Microsoft.VisualStudio.Language.Intellisense
Microsoft.VisualStudio.OLE.Interop
Microsoft.VisualStudio.Shell.15.0
Microsoft.VisualStudio.Shell.Immutable.10.0
Microsoft.VisualStudio.TextManager.Interop
实现完成源
完成源负责收集标识符集,并在用户键入完成触发器(例如标识符的第一个字母)时将内容添加到完成窗口。 在此示例中,标识符及其说明在方法中 AugmentCompletionSession 硬编码。 在大多数实际用途中,你将使用语言分析器获取令牌以填充完成列表。
实现完成源
添加一个类文件并将其命名为 TestCompletionSource
。
添加以下导入:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Operations;
using Microsoft.VisualStudio.Utilities;
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.ComponentModel.Composition
Imports Microsoft.VisualStudio.Language.Intellisense
Imports Microsoft.VisualStudio.Text
Imports Microsoft.VisualStudio.Text.Operations
Imports Microsoft.VisualStudio.Utilities
修改其实现的类声明TestCompletionSource
ICompletionSource:
internal class TestCompletionSource : ICompletionSource
Friend Class TestCompletionSource
Implements ICompletionSource
为源提供程序、文本缓冲区和对象列表 Completion 添加专用字段(对应于将参与完成会话的标识符):
private TestCompletionSourceProvider m_sourceProvider;
private ITextBuffer m_textBuffer;
private List<Completion> m_compList;
Private m_sourceProvider As TestCompletionSourceProvider
Private m_textBuffer As ITextBuffer
Private m_compList As List(Of Completion)
添加设置源提供程序和缓冲区的构造函数。 类 TestCompletionSourceProvider
在后续步骤中定义:
public TestCompletionSource(TestCompletionSourceProvider sourceProvider, ITextBuffer textBuffer)
{
m_sourceProvider = sourceProvider;
m_textBuffer = textBuffer;
}
Public Sub New(ByVal sourceProvider As TestCompletionSourceProvider, ByVal textBuffer As ITextBuffer)
m_sourceProvider = sourceProvider
m_textBuffer = textBuffer
End Sub
AugmentCompletionSession通过添加包含要在上下文中提供的完成的完成集来实现该方法。 每个完成集都包含一组 Completion 完成,对应于完成窗口的选项卡。 (在 Visual Basic 项目中,“完成”窗口选项卡命名 为通用 和 全部。该方法 FindTokenSpanAtPosition
在下一步中定义。
void ICompletionSource.AugmentCompletionSession(ICompletionSession session, IList<CompletionSet> completionSets)
{
List<string> strList = new List<string>();
strList.Add("addition");
strList.Add("adaptation");
strList.Add("subtraction");
strList.Add("summation");
m_compList = new List<Completion>();
foreach (string str in strList)
m_compList.Add(new Completion(str, str, str, null, null));
completionSets.Add(new CompletionSet(
"Tokens", //the non-localized title of the tab
"Tokens", //the display title of the tab
FindTokenSpanAtPosition(session.GetTriggerPoint(m_textBuffer),
session),
m_compList,
null));
}
Private Sub AugmentCompletionSession(ByVal session As ICompletionSession, ByVal completionSets As IList(Of CompletionSet)) Implements ICompletionSource.AugmentCompletionSession
Dim strList As New List(Of String)()
strList.Add("addition")
strList.Add("adaptation")
strList.Add("subtraction")
strList.Add("summation")
m_compList = New List(Of Completion)()
For Each str As String In strList
m_compList.Add(New Completion(str, str, str, Nothing, Nothing))
Next str
completionSets.Add(New CompletionSet(
"Tokens",
"Tokens",
FindTokenSpanAtPosition(session.GetTriggerPoint(m_textBuffer),
session),
m_compList,
Nothing))
End Sub
以下方法用于从光标的位置查找当前单词:
private ITrackingSpan FindTokenSpanAtPosition(ITrackingPoint point, ICompletionSession session)
{
SnapshotPoint currentPoint = (session.TextView.Caret.Position.BufferPosition) - 1;
ITextStructureNavigator navigator = m_sourceProvider.NavigatorService.GetTextStructureNavigator(m_textBuffer);
TextExtent extent = navigator.GetExtentOfWord(currentPoint);
return currentPoint.Snapshot.CreateTrackingSpan(extent.Span, SpanTrackingMode.EdgeInclusive);
}
Private Function FindTokenSpanAtPosition(ByVal point As ITrackingPoint, ByVal session As ICompletionSession) As ITrackingSpan
Dim currentPoint As SnapshotPoint = (session.TextView.Caret.Position.BufferPosition) - 1
Dim navigator As ITextStructureNavigator = m_sourceProvider.NavigatorService.GetTextStructureNavigator(m_textBuffer)
Dim extent As TextExtent = navigator.GetExtentOfWord(currentPoint)
Return currentPoint.Snapshot.CreateTrackingSpan(extent.Span, SpanTrackingMode.EdgeInclusive)
End Function
Dispose()
实现方法:
private bool m_isDisposed;
public void Dispose()
{
if (!m_isDisposed)
{
GC.SuppressFinalize(this);
m_isDisposed = true;
}
}
Private m_isDisposed As Boolean
Public Sub Dispose() Implements IDisposable.Dispose
If Not m_isDisposed Then
GC.SuppressFinalize(Me)
m_isDisposed = True
End If
End Sub
实现完成源提供程序
完成源提供程序是实例化完成源的 MEF 组件部件。
实现完成源提供程序
添加一个名为 TestCompletionSourceProvider
实现的 ICompletionSourceProvider类。 使用 ContentTypeAttribute “纯文本”和 NameAttribute “测试完成”导出此类。
[Export(typeof(ICompletionSourceProvider))]
[ContentType("plaintext")]
[Name("token completion")]
internal class TestCompletionSourceProvider : ICompletionSourceProvider
<Export(GetType(ICompletionSourceProvider)), ContentType("plaintext"), Name("token completion")>
Friend Class TestCompletionSourceProvider
Implements ICompletionSourceProvider
导入一个 ITextStructureNavigatorSelectorService在完成源中查找当前单词的单词。
[Import]
internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
<Import()>
Friend Property NavigatorService() As ITextStructureNavigatorSelectorService
TryCreateCompletionSource实现实例化完成源的方法。
public ICompletionSource TryCreateCompletionSource(ITextBuffer textBuffer)
{
return new TestCompletionSource(this, textBuffer);
}
Public Function TryCreateCompletionSource(ByVal textBuffer As ITextBuffer) As ICompletionSource Implements ICompletionSourceProvider.TryCreateCompletionSource
Return New TestCompletionSource(Me, textBuffer)
End Function
实现完成命令处理程序提供程序
完成命令处理程序提供程序派生自一个 IVsTextViewCreationListener,该提供程序侦听文本视图创建事件,并将视图从 IVsTextView一个视图转换为 Visual Studio shell ITextView的命令链。 由于此类是 MEF 导出,因此还可以使用它导入命令处理程序本身所需的服务。
实现完成命令处理程序提供程序
添加名为 TestCompletionCommandHandler
.. 的文件。
添加以下 using 指令:
using System;
using System.ComponentModel.Composition;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Utilities;
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.ComponentModel.Composition
Imports System.Runtime.InteropServices
Imports Microsoft.VisualStudio
Imports Microsoft.VisualStudio.Editor
Imports Microsoft.VisualStudio.Language.Intellisense
Imports Microsoft.VisualStudio.OLE.Interop
Imports Microsoft.VisualStudio.Shell
Imports Microsoft.VisualStudio.Text
Imports Microsoft.VisualStudio.Text.Editor
Imports Microsoft.VisualStudio.TextManager.Interop
Imports Microsoft.VisualStudio.Utilities
添加一个名为 TestCompletionHandlerProvider
实现的 IVsTextViewCreationListener类。 使用NameAttribute“令牌完成处理程序”、“纯文本”和 ContentTypeAttribute/> 的 EditableTextViewRoleAttribute/> 导出此类。
[Export(typeof(IVsTextViewCreationListener))]
[Name("token completion handler")]
[ContentType("plaintext")]
[TextViewRole(PredefinedTextViewRoles.Editable)]
internal class TestCompletionHandlerProvider : IVsTextViewCreationListener
<Export(GetType(IVsTextViewCreationListener))>
<Name("token completion handler")>
<ContentType("plaintext")> <TextViewRole(PredefinedTextViewRoles.Editable)>
Friend Class TestCompletionHandlerProvider
Implements IVsTextViewCreationListener
导入启用 IVsEditorAdaptersFactoryService从 a 到 a IVsTextView ITextView、a ICompletionBroker和 SVsServiceProvider 启用对标准 Visual Studio 服务的访问的转换。
[Import]
internal IVsEditorAdaptersFactoryService AdapterService = null;
[Import]
internal ICompletionBroker CompletionBroker { get; set; }
[Import]
internal SVsServiceProvider ServiceProvider { get; set; }
<Import()>
Friend AdapterService As IVsEditorAdaptersFactoryService = Nothing
<Import()>
Friend Property CompletionBroker() As ICompletionBroker
<Import()>
Friend Property ServiceProvider() As SVsServiceProvider
VsTextViewCreated实现实例化命令处理程序的方法。
public void VsTextViewCreated(IVsTextView textViewAdapter)
{
ITextView textView = AdapterService.GetWpfTextView(textViewAdapter);
if (textView == null)
return;
Func<TestCompletionCommandHandler> createCommandHandler = delegate() { return new TestCompletionCommandHandler(textViewAdapter, textView, this); };
textView.Properties.GetOrCreateSingletonProperty(createCommandHandler);
}
Public Sub VsTextViewCreated(ByVal textViewAdapter As IVsTextView) Implements IVsTextViewCreationListener.VsTextViewCreated
Dim textView As ITextView = AdapterService.GetWpfTextView(textViewAdapter)
If textView Is Nothing Then
Return
End If
Dim createCommandHandler As Func(Of TestCompletionCommandHandler) = Function() New TestCompletionCommandHandler(textViewAdapter, textView, Me)
textView.Properties.GetOrCreateSingletonProperty(createCommandHandler)
End Sub
实现完成命令处理程序
由于语句完成由击键触发,因此必须实现 IOleCommandTarget 接口来接收和处理触发、提交和消除完成会话的击键。
实现完成命令处理程序
添加一个名为 TestCompletionCommandHandler
实现的 IOleCommandTarget类:
internal class TestCompletionCommandHandler : IOleCommandTarget
Friend Class TestCompletionCommandHandler
Implements IOleCommandTarget
为下一个命令处理程序(向其传递命令)、文本视图、命令处理程序提供程序(允许访问各种服务)和完成会话添加专用字段:
private IOleCommandTarget m_nextCommandHandler;
private ITextView m_textView;
private TestCompletionHandlerProvider m_provider;
private ICompletionSession m_session;
Private m_nextCommandHandler As IOleCommandTarget
Private m_textView As ITextView
Private m_provider As TestCompletionHandlerProvider
Private m_session As ICompletionSession
添加一个构造函数,用于设置文本视图和提供程序字段,并将命令添加到命令链:
internal TestCompletionCommandHandler(IVsTextView textViewAdapter, ITextView textView, TestCompletionHandlerProvider provider)
{
this.m_textView = textView;
this.m_provider = provider;
//add the command to the command chain
textViewAdapter.AddCommandFilter(this, out m_nextCommandHandler);
}
Friend Sub New(ByVal textViewAdapter As IVsTextView, ByVal textView As ITextView, ByVal provider As TestCompletionHandlerProvider)
Me.m_textView = textView
Me.m_provider = provider
'add the command to the command chain
textViewAdapter.AddCommandFilter(Me, m_nextCommandHandler)
End Sub
QueryStatus通过传递命令来实现该方法:
public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
{
return m_nextCommandHandler.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
}
Public Function QueryStatus(ByRef pguidCmdGroup As Guid, ByVal cCmds As UInteger, ByVal prgCmds() As OLECMD, ByVal pCmdText As IntPtr) As Integer Implements IOleCommandTarget.QueryStatus
Return m_nextCommandHandler.QueryStatus(pguidCmdGroup, cCmds, prgCmds, pCmdText)
End Function
实现 Exec 方法。 此方法收到击键时,必须执行以下操作之一:
允许将字符写入缓冲区,然后触发或筛选完成。 (打印字符执行此操作。
提交完成,但不允许将字符写入缓冲区。 (空格, 选项卡,并在 显示完成会话时输入 执行此操作。
允许将命令传递给下一个处理程序。 (所有其他命令。
由于此方法可能显示 UI,因此调用 IsInAutomationFunction 以确保它在自动化上下文中未调用:
public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
{
if (VsShellUtilities.IsInAutomationFunction(m_provider.ServiceProvider))
{
return m_nextCommandHandler.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
}
//make a copy of this so we can look at it after forwarding some commands
uint commandID = nCmdID;
char typedChar = char.MinValue;
//make sure the input is a char before getting it
if (pguidCmdGroup == VSConstants.VSStd2K && nCmdID == (uint)VSConstants.VSStd2KCmdID.TYPECHAR)
{
typedChar = (char)(ushort)Marshal.GetObjectForNativeVariant(pvaIn);
}
//check for a commit character
if (nCmdID == (uint)VSConstants.VSStd2KCmdID.RETURN
|| nCmdID == (uint)VSConstants.VSStd2KCmdID.TAB
|| (char.IsWhiteSpace(typedChar) || char.IsPunctuation(typedChar)))
{
//check for a selection
if (m_session != null && !m_session.IsDismissed)
{
//if the selection is fully selected, commit the current session
if (m_session.SelectedCompletionSet.SelectionStatus.IsSelected)
{
m_session.Commit();
//also, don't add the character to the buffer
return VSConstants.S_OK;
}
else
{
//if there is no selection, dismiss the session
m_session.Dismiss();
}
}
}
//pass along the command so the char is added to the buffer
int retVal = m_nextCommandHandler.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
bool handled = false;
if (!typedChar.Equals(char.MinValue) && char.IsLetterOrDigit(typedChar))
{
if (m_session == null || m_session.IsDismissed) // If there is no active session, bring up completion
{
this.TriggerCompletion();
m_session.Filter();
}
else //the completion session is already active, so just filter
{
m_session.Filter();
}
handled = true;
}
else if (commandID == (uint)VSConstants.VSStd2KCmdID.BACKSPACE //redo the filter if there is a deletion
|| commandID == (uint)VSConstants.VSStd2KCmdID.DELETE)
{
if (m_session != null && !m_session.IsDismissed)
m_session.Filter();
handled = true;
}
if (handled) return VSConstants.S_OK;
return retVal;
}
Public Function Exec(ByRef pguidCmdGroup As Guid, ByVal nCmdID As UInteger, ByVal nCmdexecopt As UInteger, ByVal pvaIn As IntPtr, ByVal pvaOut As IntPtr) As Integer Implements IOleCommandTarget.Exec
If VsShellUtilities.IsInAutomationFunction(m_provider.ServiceProvider) Then
Return m_nextCommandHandler.Exec(pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut)
End If
'make a copy of this so we can look at it after forwarding some commands
Dim commandID As UInteger = nCmdID
Dim typedChar As Char = Char.MinValue
'make sure the input is a char before getting it
If pguidCmdGroup = VSConstants.VSStd2K AndAlso nCmdID = CUInt(VSConstants.VSStd2KCmdID.TYPECHAR) Then
typedChar = CChar(ChrW(CUShort(Marshal.GetObjectForNativeVariant(pvaIn))))
End If
'check for a commit character
If nCmdID = CUInt(VSConstants.VSStd2KCmdID.RETURN) OrElse nCmdID = CUInt(VSConstants.VSStd2KCmdID.TAB) OrElse (Char.IsWhiteSpace(typedChar) OrElse Char.IsPunctuation(typedChar)) Then
'check for a selection
If m_session IsNot Nothing AndAlso (Not m_session.IsDismissed) Then
'if the selection is fully selected, commit the current session
If m_session.SelectedCompletionSet.SelectionStatus.IsSelected Then
m_session.Commit()
'also, don't add the character to the buffer
Return VSConstants.S_OK
Else
'if there is no selection, dismiss the session
m_session.Dismiss()
End If
End If
End If
'pass along the command so the char is added to the buffer
Dim retVal As Integer = m_nextCommandHandler.Exec(pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut)
Dim handled As Boolean = False
If (Not typedChar.Equals(Char.MinValue)) AndAlso Char.IsLetterOrDigit(typedChar) Then
If m_session Is Nothing OrElse m_session.IsDismissed Then ' If there is no active session, bring up completion
Me.TriggerCompletion()
m_session.Filter()
Else 'the completion session is already active, so just filter
m_session.Filter()
End If
handled = True
ElseIf commandID = CUInt(VSConstants.VSStd2KCmdID.BACKSPACE) OrElse commandID = CUInt(VSConstants.VSStd2KCmdID.DELETE) Then 'redo the filter if there is a deletion
If m_session IsNot Nothing AndAlso (Not m_session.IsDismissed) Then
m_session.Filter()
End If
handled = True
End If
If handled Then
Return VSConstants.S_OK
End If
Return retVal
End Function
此代码是触发完成会话的私有方法:
private bool TriggerCompletion()
{
//the caret must be in a non-projection ___location
SnapshotPoint? caretPoint =
m_textView.Caret.Position.Point.GetPoint(
textBuffer => (!textBuffer.ContentType.IsOfType("projection")), PositionAffinity.Predecessor);
if (!caretPoint.HasValue)
{
return false;
}
m_session = m_provider.CompletionBroker.CreateCompletionSession
(m_textView,
caretPoint.Value.Snapshot.CreateTrackingPoint(caretPoint.Value.Position, PointTrackingMode.Positive),
true);
//subscribe to the Dismissed event on the session
m_session.Dismissed += this.OnSessionDismissed;
m_session.Start();
return true;
}
Private Function TriggerCompletion() As Boolean
'the caret must be in a non-projection ___location
Dim caretPoint As SnapshotPoint? = m_textView.Caret.Position.Point.GetPoint(Function(textBuffer) ((Not textBuffer.ContentType.IsOfType("projection"))), PositionAffinity.Predecessor)
If Not caretPoint.HasValue Then
Return False
End If
m_session = m_provider.CompletionBroker.CreateCompletionSession(m_textView, caretPoint.Value.Snapshot.CreateTrackingPoint(caretPoint.Value.Position, PointTrackingMode.Positive), True)
'subscribe to the Dismissed event on the session
AddHandler m_session.Dismissed, AddressOf OnSessionDismissed
m_session.Start()
Return True
End Function
下一个示例是取消订阅 Dismissed 事件的专用方法:
private void OnSessionDismissed(object sender, EventArgs e)
{
m_session.Dismissed -= this.OnSessionDismissed;
m_session = null;
}
Private Sub OnSessionDismissed(ByVal sender As Object, ByVal e As EventArgs)
RemoveHandler m_session.Dismissed, AddressOf OnSessionDismissed
m_session = Nothing
End Sub
生成并测试代码
若要测试此代码,请生成 CompletionTest 解决方案并在实验实例中运行它。
生成并测试 CompletionTest 解决方案
生成解决方案。
在调试器中运行此项目时,将启动 Visual Studio 的第二个实例。
创建文本文件并键入一些包含单词“add”的文本。
键入第一个“a”,然后键入“d”时,应显示包含“添加”和“适应”的列表。 请注意,已选择添加。 键入另一个“d”时,列表应仅包含“添加”,现在已选中。 可以通过按 空格键、 Tab 键或 Enter 键提交“添加”,也可以键入 Esc 或任何其他键来消除列表。
相关内容