私がAtCoderをRustで解く際、以下の記事で紹介されているcargo-competeを使っています。
導入方法・使い方などはREADMEなどを見てください。
この記事ではcargo-competeを使ったときに発生する、ちょっとした手間を省いて便利にする方法を紹介します。
VSCode + AtCoderのみで使えます
動機
1. コンテストを開くとき
cargo-competeで新しいコンテストを開いたとき、ディレクトリ変更したりrust-analyzerを再起動したりといった、面倒ではないが少し気になる程度の作業が必ずついてきます。
2. コードをテスト・提出するとき
コードをテスト・提出するcargo compete test/submit
コマンドですが、少し長めです。
ほんの少しの手間ですが、焦っているときにtypoを連続、結果時間を食ってしまうということがあります。
私だけでしょうか。
コンテストの結果にはほとんど影響しないようなものですが、このようなきっかけがあったためつくり始めました。
使用例
1. コンテストを開く時
このgifのように、コンテストを開くときについてくる作業をコマンド一個で自動的にしてくれます。
してくれることは以下の通りです。
- コンテストが開けるまで繰り返しリクエスト
- ディレクトリ変更
- rust-analyzer再起動
- エディターでa問題を開く
- カーソルを入力位置に合わせる
-
s a(改行)
をクリップボードにコピー - 今までの出力削除
- ブラウザでa問題を開く
2. コードをテスト・提出するとき
cargo compete test
→ t
cargo compete submit
→ s
それぞれ1文字になります。
後の章でさらに詳しく説明します。
手順
VSCodeの2つの設定を使います。
terminal.integrated.defaultProfile.windows
terminal.integrated.profiles.windows
ワークスペースの設定にすることで、他の場所では使えないようにしています。
コードを変更して動作確認をする際はシェルを再起動するのを忘れないようにしてください。
なお、ファイル・フォルダの名前は自由です。
1. ファイル作成
ワークスペースを作成
cargo-competeを使っているフォルダをワークスペースとして保存し、AtCoder.code-workspace
ができます。
プロファイルファイルを作成
次にAtCoderフォルダにprofile.ps1
をつくります。
次の画像のようなフォルダ構成になると思います。
2. ファイル編集
AtCoder.code-workspace
を編集
settings
の中身を追記してください。
PowerShell以外のシェルを使っていたり、違うバージョンのPowerShell(powershell.exe)を使っている方は適宜変更してください。
{
"folders": [
{
"path": "."
}
],
"settings": {
"terminal.integrated.defaultProfile.windows": "PowerShell for AtCoder",
"terminal.integrated.profiles.windows": {
"PowerShell for AtCoder": {
"path": "pwsh.exe",
"args": [
"-noexit",
"-file",
"C:/AtCoder/profile.ps1"
]
}
}
},
}
profile.ps1
を編集
下の章でそれぞれの関数の説明を読み、各自の環境に合わせてコードを変更してください。
コード
function T($Problem) {
cargo compete test $Problem
}
function S($Problem) {
cargo compete submit $Problem
}
function NewABC($ContestNumber) {
add-type -AssemblyName System.Windows.Forms
$ContestName = "abc$ContestNumber"
$strCmd = "cargo"
$arguments = "compete new $ContestName"
$Result = Invoke-ExternalCommand $strCmd $arguments
while (!($Result -match "Created")) {
Write-Host $Result.stderr
Start-Sleep -m 500
$Result = Invoke-ExternalCommand $strCmd $arguments
}
Write-Host $Result.stderr
Set-Location "$ContestName"
Set-Clipboard "rust-analyzer: restart server"
[System.Windows.Forms.SendKeys]::SendWait("+^p")
[System.Windows.Forms.SendKeys]::SendWait("^v")
[System.Windows.Forms.SendKeys]::SendWait("{Enter}")
Set-Clipboard "$ContestName/src/bin/a.rs"
[System.Windows.Forms.SendKeys]::SendWait("^p")
[System.Windows.Forms.SendKeys]::SendWait("^v")
Start-Sleep -m 500
[System.Windows.Forms.SendKeys]::SendWait("{Enter}")
Start-Sleep -m 500
# with Vim extension
[System.Windows.Forms.SendKeys]::SendWait("{ESC}5GA")
# without Vim extension
[System.Windows.Forms.SendKeys]::SendWait("^g")
[System.Windows.Forms.SendKeys]::SendWait("5")
[System.Windows.Forms.SendKeys]::SendWait("{Enter}")
[System.Windows.Forms.SendKeys]::SendWait("{End}")
Set-Clipboard "cargo s a
"
Clear-Host
$link = "https://atcoder.jp/contests/$ContestName/tasks/$ContestName" + "_a"
Start-Process $link
}
function Invoke-ExternalCommand([string]$commandPath, [string]$arguments) {
try {
# Creating process object.
$pinfo = New-Object System.Diagnostics.Process
# Setting process invocation parameters.
$pinfo.StartInfo.FileName = $commandPath
$pinfo.StartInfo.Arguments = $arguments
$pinfo.StartInfo.CreateNoWindow = $true
$pinfo.StartInfo.UseShellExecute = $false
$pinfo.StartInfo.RedirectStandardOutput = $true
$pinfo.StartInfo.RedirectStandardError = $true
# 非同期書き込み
# Creating string builders to store stdout and stderr.
$oStdOutBuilder = New-Object -TypeName System.Text.StringBuilder
$oStdErrBuilder = New-Object -TypeName System.Text.StringBuilder
# Adding event handlers for stdout and stderr.
$sScripBlock = {
if (! [String]::IsNullOrEmpty($EventArgs.Data)) {
$Event.MessageData.AppendLine($EventArgs.Data)
}
}
$oStdOutEvent = Register-ObjectEvent -InputObject $pinfo `
-Action $sScripBlock -EventName 'OutputDataReceived' `
-MessageData $oStdOutBuilder
$oStdErrEvent = Register-ObjectEvent -InputObject $pinfo `
-Action $sScripBlock -EventName 'ErrorDataReceived' `
-MessageData $oStdErrBuilder
# Starting process.
[Void]$pinfo.Start()
$pinfo.BeginOutputReadLine()
$pinfo.BeginErrorReadLine()
[Void]$pinfo.WaitForExit()
# Unregistering events to retrieve process output.
Unregister-Event -SourceIdentifier $oStdOutEvent.Name
Unregister-Event -SourceIdentifier $oStdErrEvent.Name
$Result = New-Object -TypeName PSObject -Property ([Ordered]@{
"ExitCode" = $pinfo.ExitCode;
"stdout" = $oStdOutBuilder.ToString().Trim();
"stderr" = $oStdErrBuilder.ToString().Trim()
})
return $Result
}
finally {
$pinfo.Dispose()
}
}
結果
こうすることで、AtCoderワークスペース内でターミナルを開いた際にPowerShell for AtCoder
プロファイルが開かれ、profile.ps1
の内容が読み込まれることで以下の関数が使えるようになります。
NewABC
T
S
- (
Invoke-ExternalCommands
)直接使うことはない
ちなみに関数の大文字小文字は区別されないようです。
関数の内容
NewABC
newabc (コンテストの番号)
newabc 300
新しいコンテストを開きます。
既に書きましたが、自動で以下のことをやってくれます。
- コンテストが開けるまで0.5秒間隔で繰り返しリクエスト
- ディレクトリ変更
- rust-analyzer再起動
- エディターでa問題を開く
- カーソルを入力位置に合わせる
-
s a(改行)
をクリップボードにコピー - 今までの出力削除
- ブラウザでa問題を開く
ARCに出る方はNewARC
をつくっても良いかもしれません。
説明
繰り返しリクエスト
9:00ちょうどにcargo compete new abc◯◯◯
を実行するとコンテストを開けないことがあったためこの機能をつくりました。
事前に関数を実行しておくと、テストケースがダウンロードされるまで0.5秒間隔で繰り返しコマンドを実行します。
該当コード
$ContestName = "abc$ContestNumber"
$strCmd = "cargo"
$arguments = "compete new $ContestName"
$Result = Invoke-ExternalCommand $strCmd $arguments
while (!($Result -match "Created")) {
Write-Host $Result.stderr
Start-Sleep -m 500
$Result = Invoke-ExternalCommand $strCmd $arguments
}
ディレクトリ変更
開いたコンテストのディレクトリに移動します。
該当コード
Set-Location "$ContestName"
rust-analyzer再起動
新しいパッケージを開いてもそのままではrust-analyzerによる補完が効きません。
そのためrust-analyzerのサーバーを再起動してパッケージを読み込ませます。
ctrl+shift+p
から実行しているため、ショートカットを変更している方はコードも変更してください。
該当コード
Set-Clipboard "rust-analyzer: restart server"
[System.Windows.Forms.SendKeys]::SendWait("+^p")
[System.Windows.Forms.SendKeys]::SendWait("^v")
[System.Windows.Forms.SendKeys]::SendWait("{Enter}")
エディターでa問題を開く
VSCodeの機能の「ファイルを名前で検索」を利用しています。
ショートカットをctrl+p
から変更している方はコードも変更してください。
またファイルを検索する時間とファイルを開く時間を取るため、500msのスリープを2回入れています。
該当コード
Set-Clipboard "$ContestName/src/bin/a.rs"
[System.Windows.Forms.SendKeys]::SendWait("^p")
[System.Windows.Forms.SendKeys]::SendWait("^v")
Start-Sleep -m 500
[System.Windows.Forms.SendKeys]::SendWait("{Enter}")
Start-Sleep -m 500
カーソルを合わせる
compete.toml
の[template]
の内容によります。
[template]
src = '''
use proconio::input;
fn main() {
input! {
}
}
'''
私はこの様になっているので、5行目の末尾にカーソルを合わせています。
Vimキーマップを使っていない場合はVSCodeの行を移動する機能を使うため、ショートカットをctrl+g
から変更している方はコードの方も変更してください。
該当コード
# with Vim extension
[System.Windows.Forms.SendKeys]::SendWait("{ESC}5GA")
# without Vim extension
[System.Windows.Forms.SendKeys]::SendWait("^g")
[System.Windows.Forms.SendKeys]::SendWait("5")
[System.Windows.Forms.SendKeys]::SendWait("{Enter}")
[System.Windows.Forms.SendKeys]::SendWait("{End}")
s a(改行)をコピー
a問題の解答を書いた後、ターミナルの上で右クリックするだけで提出が完了します。
これのおかげで1回Fastestを取れました。Firstではないですが。
該当コード
Set-Clipboard "s a
"
今までの出力削除
該当コード
Clear-Host
ブラウザでa問題を開く
details
$link = "https://atcoder.jp/contests/$ContestName/tasks/$ContestName" + "_a"
Start-Process $link
t
t (問題名)
t c
cargo compete test
と同じです。
s
s (問題名)
s d
cargo compete submit
と同じです。
注意点
NewABC
で繰り返しリクエストする場合、サーバーに負担をかけそうで心配です。
私はできるだけコンテスト開催直前で実行するようにしています。
また、スリープの時間もかなりギリギリなので間違った処理をし始めたらそこを見直してみてください。
t
とs
、a
からg
をしっかりと確認してください。
違う問題のコードを提出しているかもしれません(体験談)。
おまけ
profile.ps1
に以下のコードを追加すると、コード補完の無効化・起動時のバージョン表示の消去ができます。
AtCoderワークスペース以外でも適用したい場合はPowerShell自体のプロファイルを編集してください。
Set-PSReadLineOption -PredictionSource None
Clear-Host
おわりに
ここまで読んで頂きありがとうございます。
もともとPowerShell Profileを使って同じことをしていたのですが、関数のスコープが気になって調べてみたところ、ワークスペースを使えば実現できそうだったためやってみました。
これくらいなら自分でも記事をかけるだろうと思い、初めての記事作成にも挑戦しました。
最初は何を書けばいいかさっぱりでしたが、先人の知恵を借りつつ書き始めてしまえば何だかんだ3日くらいで出来てしまいました。
アウトプットをすること自体苦手なので、練習として継続していければと思っています。
至らない点があればぜひご指摘ください。
参考文献
Invoke-ExternalCommands
関数を使わせていただきました。
Start-Sleep
メソッドのドキュメント
SendWait
メソッドのドキュメント