逐步解說: 顯示陳述式完成
如需 Visual Studio 2017 的最新文件請參閱 Visual Studio 2017 文件。
您可以定義您要提供完成的識別項,然後觸發完成的工作階段,以實作語言為基礎的陳述式完成。 您可以語言服務內容中定義陳述式完成、 定義您自己的檔案名稱副檔名和內容類型,然後顯示完成只是該型別,或者完成現有的內容類型的觸發程序 — 例如,「 純文字 」。 本逐步解說示範如何觸發陳述式完成 「 純文字 」 內容類型,也就是文字檔案的內容類型。 「 文字 」 內容類型是所有其他內容類型,包括程式碼和 XML 檔案的上階。
陳述式完成通常會觸發輸入特定的字元 — 例如,藉由輸入的識別碼,例如 「 使用 」 的開頭。 它通常被按下空格鍵、 Tab 或 Enter 鍵,確認選取項目。 您可以實作 IntelliSense 功能所觸發的輸入字元的按鍵輸入使用的命令處理常式 ( IOleCommandTarget介面) 和處理常式提供者實作IVsTextViewCreationListener介面。 若要建立完成來源時,這是參與完成的識別項的清單,實作ICompletionSource介面和完成來源提供者 ( ICompletionSourceProvider介面)。 提供者是 Managed Extensibility Framework (MEF) 元件組件。 他們要負責匯出的來源和控制器類別和匯入服務和代理程式 — 例如, ITextStructureNavigatorSelectorService,可讓文字緩衝區中,瀏覽和ICompletionBroker,而觸發完成的工作階段。
本逐步解說示範如何實作硬式編碼的組的識別項的陳述式完成。 在完整的實作中,語言服務和語言文件有責任提供該內容。
必要條件
啟動 Visual Studio 2015 中,您未安裝 Visual Studio SDK 從 「 下載中心 」。 它是 Visual Studio 安裝程式的選用功能。 您也可以在稍後安裝 VS SDK。 如需詳細資訊,請參閱安裝 Visual Studio SDK。
建立 MEF 專案
建立 MEF 專案
建立 C# VSIX 專案。 (在新的專案對話方塊中,選取Visual C# / 擴充性,然後VSIX 專案。)將方案命名為
CompletionTest
。將編輯器分類項目範本加入至專案。 如需詳細資訊,請參閱編輯器項目範本以建立副檔名為。
刪除現有類別檔案。
下列參考加入至專案,並確定CopyLocal設為
false
:Microsoft.VisualStudio.Editor
Microsoft.VisualStudio.Language.Intellisense
Microsoft.VisualStudio.OLE.Interop
Microsoft.VisualStudio.Shell.14.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
加入私用欄位的來源提供者、 文字緩衝區,以及一份完成物件 (對應至要參與完成的工作階段識別項)︰
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您想要提供的內容中加入一個完成集合,包含所完成的方法。 每個完成集包含一組完成完成項數目,而對應的索引標籤的 [完成] 視窗。 (在 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 「 純文字 」 和TextViewRoleAttribute的編輯。[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,可讓從轉換IVsTextView至ITextView、 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方法。 當這個方法會接收按鍵動作時,它必須執行這些動作的其中一個動作︰
允許寫入緩衝區,並接著觸發程序或篩選完成的字元。 (列印的字元進行。)
認可完成,但不是允許寫入緩衝區的字元。 (空格、 定位及 Enter 時執行此動作會顯示完成的工作階段。)
允許命令傳遞至下一個處理常式。 (所有其他命令)。
因為這個方法可能會顯示 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 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 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 鍵或其他索引鍵的清單。