완성을 제공하려는 식별자를 정의하고 완성 세션을 트리거하여, 언어 기반 문 완성을 구현할 수 있습니다. 언어 서비스의 컨텍스트에서 문 완성을 정의하고, 고유한 파일 이름 확장명 및 콘텐츠 형식을 정의한 다음, 해당 형식에 대한 완성만 표시할 수 있습니다. 또는 기존 콘텐츠 형식(예: "일반 텍스트")에 대한 완성을 트리거할 수 있습니다. 이 연습에서는 텍스트 파일의 콘텐츠 형식인 "일반 텍스트" 콘텐츠 형식에 대해 문 완성을 트리거하는 방법을 보여 줍니다. "텍스트" 콘텐츠 형식은 코드 및 XML 파일을 포함한 다른 모든 콘텐츠 형식의 상위 항목입니다.
완성 원본은 식별자 집합을 수집하고 사용자가 식별자의 첫 글자와 같은 완료 트리거를 입력할 때 완성 창에 콘텐츠를 추가하는 작업을 담당합니다. 이 예제에서는 식별자와 해당 설명이 AugmentCompletionSession 메서드에 하드 코딩됩니다. 대부분의 실제 사용에서는 언어의 파서를 사용하여 완성 목록을 채우는 토큰을 가져옵니다.
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;
Public Sub New(ByVal sourceProvider As TestCompletionSourceProvider, ByVal textBuffer As ITextBuffer)
m_sourceProvider = sourceProvider
m_textBuffer = textBuffer
End Sub
컨텍스트에서 제공하려는 완성이 포함된 완성 집합을 추가하여 AugmentCompletionSession 메서드를 구현합니다. 각 완성 집합은 Completion 완성의 세트를 포함하며 완성 창의 탭에 해당합니다. Visual Basic 프로젝트에서 완성 창 탭의 이름은 Common 및 All입니다. 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 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
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
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
완성 명령 처리기 공급자 구현
완성 명령 처리기 공급자는 텍스트 보기 만들기 이벤트를 수신 대기하고 Visual Studio 셸의 명령 체인에 명령을 추가할 수 있게 IVsTextView에서 뷰를 변환하는 IVsTextViewCreationListener에서 ITextView로 파생됩니다. 이 클래스는 MEF 내보내기이므로 명령 처리기 자체에 필요한 서비스를 가져오는 데 사용할 수도 있습니다.
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;
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 인터페이스를 구현해야 합니다.
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
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에서 이 작업을 수행합니다.)
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
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"를 입력하면 "addition" 및 "adaptation"이 포함된 목록이 표시됩니다. addition이 선택되어 있습니다. 다른 "d"를 입력하면 목록에 이제 선택된 "addition"만 포함됩니다. 스페이스바, 탭 또는 Enter 키를 눌러 "더하기"를 커밋하거나 Esc 또는 다른 키를 입력하여 목록을 해제할 수 있습니다.