入力候補を提供する識別子を定義し、入力候補セッションをトリガーすることによって、言語ベースの入力候補を実装できます。 言語サービスのコンテキストで入力候補を定義し、独自のファイル名の拡張子とコンテンツ タイプを定義して、その型の入力候補だけを表示することができます。 または、既存のコンテンツ タイプ ("plaintext" など) の入力候補をトリガーすることもできます。 このチュートリアルでは、テキスト ファイルのコンテンツ タイプである "plaintext" コンテンツ タイプに対して入力候補をトリガーする方法について説明します。 "text" コンテンツ タイプは、コード ファイルや XML ファイルなど、他のすべてのコンテンツ タイプの先祖です。
通常、入力候補は特定の文字を入力することによってトリガーされます。たとえば、"using" などの識別子の先頭を入力します。 通常、Space キー、Tab キー、または Enter キーを押して選択範囲をコミットすると、閉じます。 キーストローク (IOleCommandTarget インターフェイス) のコマンド ハンドラーと、IVsTextViewCreationListener インターフェイスを実装するハンドラー プロバイダーを使用して、文字を入力するときにトリガーされる IntelliSense 機能を実装できます。 入力候補に含まれる識別子の一覧である入力候補ソースを作成するには、ICompletionSource インターフェイスと入力候補ソース プロバイダー (ICompletionSourceProvider インターフェイス) を実装します。 プロバイダーは、Managed Extensibility Framework (MEF) コンポーネントのパーツです。 ソースとコントローラーのクラスをエクスポートし、サービスとブローカーをインポートする役割を担います。たとえば、テキスト バッファー内の移動を可能にする ITextStructureNavigatorSelectorService や、入力候補セッションをトリガーする ICompletionBroker などです。
このチュートリアルでは、ハードコーディングされた識別子のセットに対して入力候補を実装する方法について説明します。 完全な実装では、言語サービスと言語ドキュメントにそのコンテンツを提供する役割があります。
MEF プロジェクトを作成する
MEF プロジェクトを作成するには
C# VSIX プロジェクトを作成します。 ([新しいプロジェクト] ダイアログで、[Visual C#]、[拡張機能]、[VSIX プロジェクト] の順に選択します。) ソリューションに 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
ICompletionSource を実装するように、TestCompletionSource
のクラス宣言を変更します。
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 コンポーネントのパーツです。
入力候補ソース プロバイダーを実装するには
ICompletionSourceProvider を実装する、TestCompletionSourceProvider
という名前のクラスを追加します。 ContentTypeAttribute を "plaintext"、NameAttribute を "test completion" として、このクラスをエクスポートします。
[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 シェルのコマンド チェーンにコマンドを追加できるように、そのビューを 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
IVsTextViewCreationListener を実装する、TestCompletionHandlerProvider
という名前のクラスを追加します。 NameAttribute を "token completion handler"、ContentTypeAttribute を "plaintext"、および TextViewRoleAttribute を Editable として、このクラスをエクスポートします。
[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 インターフェイスを実装して、入力候補セッションをトリガー、コミット、および破棄するキーストロークを受信して処理する必要があります。
入力候補コマンド ハンドラーを実装するには
IOleCommandTarget を実装する、TestCompletionCommandHandler
という名前のクラスを追加します。
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 メソッドを実装します。 このメソッドがキーストロークを受け取ったとき、次のいずれかの操作を行う必要があります。
文字がバッファーに書き込まれることを許可し、その後、入力候補をトリガーまたはフィルター処理します。 (印刷文字はこれを行います。)
入力候補をコミットしますが、バッファーへの文字の書き込みは許可しません。 (入力候補セッションが表示されるとき、Space キー、Tab キー、または 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 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 の 2 つ目のインスタンスが起動されます。
テキスト ファイルを作成し、"add" という単語を含む何らかのテキストを入力します。
最初に "a"、次に "d" を入力すると、"addition" と "adaptation" を含む一覧が表示されます。 addition が選択されていることを確認します。 もう 1 回 "d" を入力すると、一覧に "addition" のみが表示されます。これは現在選択されています。 Space キー、Tab キー、または Enter キーを押して "addition" をコミットするか、Esc キーまたは他のキーを入力して一覧を閉じます。
関連するコンテンツ