タブ補完の入力候補を動的に取得する
この記事は、以前の記事の続きとなります。
前回は、.NETのコンソールアプリケーションにタブ補完機能を追加するサンプルを紹介しました。
今回は、そのサンプルアプリを修正し、Web APIなどから動的に取得した値をコマンドパラメータの入力候補として利用できるようにします。
ソースコード
ソースコードは以下のリンクからご覧いただけます。
Googleサジェストから入力候補を動的に取得する
今回のサンプルでは、以下のようなコマンドを作成します。
- Google検索を行うコマンドを作ります
-
--query
パラメータに検索ワードを指定して検索します - 検索ワードには、入力した文字からGoogleのサジェストAPIを使って予測候補を提示します
–query
オプションの後に検索キーワードを入力します。その後、[Tab]キーまたは[Ctrl]+[Space]キーを押すと、コード補完が作動します。これにより、入力したキーワードに関連する候補が表示されます。
Googleコマンド
まず、Google検索を行うコマンドを実装します。指定した検索クエリを付けたGoogle検索のURLでブラウザを開くだけのシンプルなコマンドです。
partial class MyCommands
{
/// <summary>
/// 指定したクエリでGoogle検索ページを開きます
/// </summary>
/// <param name="query">検索クエリ</param>
[Command("google")]
public void GoogleSearch(string query)
{
//既定のブラウザURLを開く
var process = new Process()
{
StartInfo = new ProcessStartInfo()
{
FileName = "pwsh",
Arguments = $"-Command start https://www.google.com/search?q={System.Web.HttpUtility.UrlEncode(query)}"
}
};
process.Start();
}
}
ICommandCompletionItemの実装
次に、Googleコマンド用の補完候補を返すICommandCompletionItem
を実装したクラスを作成します。
/// <summary>
/// Googleコマンド用 ICommandCompletionItem の実装
/// </summary>
class GoogleCompletionItem : ICommandCompletionItem
{
private HttpClient _httpClient;
private static readonly IEnumerable<string> options = ["--query"];
public GoogleCompletionItem(HttpClient httpClient)
{
_httpClient = httpClient;
}
public string CommandName => "google";
public async Task<IEnumerable<string>> GetCompletionItemsAsync(string optionName, string wordToComplete)
{
if (optionName != "--query" || wordToComplete=="") { return []; }
var response = await _httpClient.GetAsync($"https://google.com/complete/search?q={System.Web.HttpUtility.UrlEncode(wordToComplete)}&output=toolbar");
using var stream = await response.Content.ReadAsStreamAsync();
var xml = XDocument.Load(stream);
return xml.Descendants("suggestion")
.Select(element => $"\"{(element.Attribute("data")?.Value ?? "")}\"");
}
public IEnumerable<string> GetAllOptions() => options;
public IEnumerable<string> GetOptions(string wordToComplete)
=> options.Where(o => o.Contains(wordToComplete, StringComparison.InvariantCultureIgnoreCase));
}
HttpClientの利用
GoogleサジェストへのリクエストにはHttpClient
を使用します。HttpClient
のインスタンスはコンストラクタで受け取ります。
メソッドの非同期化
GetCompletionItems
メソッドは、非同期メソッドGetCompletionItemsAsync
に変更されています。これはメソッドの中でHttpClient
の非同期メソッドを利用するためです。この非同期化の修正はMyCommands
クラスのComplete
メソッドまで伝播します。
interface ICommandCompletionItem
{
//...(省略)
//非同期対応
Task<IEnumerable<string>> GetCompletionItemsAsync(string optionName, string wordToComplete);
}
interface ICommandCompletionProvider
{
void Add(ICommandCompletionItem item);
//非同期対応
Task<IEnumerable<string>> CompleteAsync(string wordToComplete, string input, int cursorPosition);
}
partial class MyCommands
{
//非同期対応
public async Task Complete(string wordToComplete, string input, int cursorPosition)
{
var items = await CompletionProvider
.CompleteAsync(wordToComplete, input, cursorPosition);
foreach (var item in items)
{
Console.WriteLine(item);
}
}
}
HttpClient
をDIサービスに登録
使用するHttpClient
のインスタンスはDIを利用してIHttpClientFactory
経由で取得します。
AddHttpClient()
メソッドを利用するにはパッケージMicrosoft.Extensions.Http
を追加する必要があります。
using ConsoleAppFramework;
using Microsoft.Extensions.DependencyInjection;
//HttpClientをサービスに登録
var services = new ServiceCollection()
.AddHttpClient();
using var serviceProvider = services.BuildServiceProvider();
ConsoleApp.ServiceProvider = serviceProvider;
var app = ConsoleApp.Create();
app.Add<MyCommands>();
await app.RunAsync(args);
CommandCompletionProviderに登録
最後に、MyCommands
のコンストラクタでCompletionProvider
にGoogleCompletionItem
を登録します。
GoogleCompletionItem
を生成する際、コンストラクタにHttpClient
を渡します。
partial class MyCommands
{
private readonly CommandCompletionProvider CompletionProvider;
public MyCommands(HttpClient httpClient)//DIでHttpClientを注入
{
CompletionProvider =
[
//Google用CompletionItemの登録
new GoogleCompletionItem(httpClient),
new CommandCompletionItem("search")
{
{"--category", ["books","movies","music"]},
{"--sort", ["relevance","date","popularity"]},
{"--filter", ["free","paid","all"]}
},
//...(省略)
];
}
}
まとめ
今回の修正により、タブ補完の入力候補を動的に取得できるようになりました。この仕組みを利用すれば、より柔軟に入力候補を提供することが可能になります。
これにより、CLIツールの使いやすさと効率性が向上し、より多様なシチュエーションでの使用が可能になると思います。