LoginSignup
1
0

Terminal Profileでcargo-competeをもっと便利に

Last updated at Posted at 2024-02-24

私がAtCoderをRustで解く際、以下の記事で紹介されているcargo-competeを使っています。

導入方法・使い方などはREADMEなどを見てください。

この記事ではcargo-competeを使ったときに発生する、ちょっとした手間を省いて便利にする方法を紹介します。

VSCode + AtCoderのみで使えます

動機

1. コンテストを開くとき

cargo-competeで新しいコンテストを開いたとき、ディレクトリ変更したりrust-analyzerを再起動したりといった、面倒ではないが少し気になる程度の作業が必ずついてきます。

2. コードをテスト・提出するとき

コードをテスト・提出するcargo compete test/submitコマンドですが、少し長めです。
ほんの少しの手間ですが、焦っているときにtypoを連続、結果時間を食ってしまうということがあります。
私だけでしょうか。


コンテストの結果にはほとんど影響しないようなものですが、このようなきっかけがあったためつくり始めました。

使用例

1. コンテストを開く時

このgifのように、コンテストを開くときについてくる作業をコマンド一個で自動的にしてくれます。

newabc_example.gif

してくれることは以下の通りです。

  1. コンテストが開けるまで繰り返しリクエスト
  2. ディレクトリ変更
  3. rust-analyzer再起動
  4. エディターでa問題を開く
  5. カーソルを入力位置に合わせる
  6. s a(改行)をクリップボードにコピー
  7. 今までの出力削除
  8. ブラウザでa問題を開く

2. コードをテスト・提出するとき

cargo compete testt
cargo compete submits

それぞれ1文字になります。


後の章でさらに詳しく説明します。

手順

VSCodeの2つの設定を使います。
terminal.integrated.defaultProfile.windows
terminal.integrated.profiles.windows

ワークスペースの設定にすることで、他の場所では使えないようにしています。

コードを変更して動作確認をする際はシェルを再起動するのを忘れないようにしてください。

なお、ファイル・フォルダの名前は自由です。

1. ファイル作成

ワークスペースを作成

cargo-competeを使っているフォルダをワークスペースとして保存し、AtCoder.code-workspaceができます。

プロファイルファイルを作成

次にAtCoderフォルダにprofile.ps1をつくります。

次の画像のようなフォルダ構成になると思います。

image.png

2. ファイル編集

AtCoder.code-workspaceを編集

settingsの中身を追記してください。
PowerShell以外のシェルを使っていたり、違うバージョンのPowerShell(powershell.exe)を使っている方は適宜変更してください。

AtCoder.code-workspace
{
    "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を編集

下の章でそれぞれの関数の説明を読み、各自の環境に合わせてコードを変更してください。

コード
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

shell
newabc (コンテストの番号)
newabc 300

新しいコンテストを開きます。

既に書きましたが、自動で以下のことをやってくれます。

  1. コンテストが開けるまで0.5秒間隔で繰り返しリクエスト
  2. ディレクトリ変更
  3. rust-analyzer再起動
  4. エディターでa問題を開く
  5. カーソルを入力位置に合わせる
  6. s a(改行)をクリップボードにコピー
  7. 今までの出力削除
  8. ブラウザでa問題を開く

ARCに出る方はNewARCをつくっても良いかもしれません。

説明

繰り返しリクエスト

9:00ちょうどにcargo compete new abc◯◯◯を実行するとコンテストを開けないことがあったためこの機能をつくりました。
事前に関数を実行しておくと、テストケースがダウンロードされるまで0.5秒間隔で繰り返しコマンドを実行します。

該当コード
profile.ps1
$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
}

ディレクトリ変更

開いたコンテストのディレクトリに移動します。

該当コード
profile.ps1
Set-Location "$ContestName"

rust-analyzer再起動

新しいパッケージを開いてもそのままではrust-analyzerによる補完が効きません。
そのためrust-analyzerのサーバーを再起動してパッケージを読み込ませます。

ctrl+shift+pから実行しているため、ショートカットを変更している方はコードも変更してください。

該当コード
profile.ps1
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回入れています。

該当コード
profile.ps1
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]の内容によります。

compete.toml
[template]
src = '''
use proconio::input;

fn main() {
    input! {
        
    }
}
'''

私はこの様になっているので、5行目の末尾にカーソルを合わせています。

Vimキーマップを使っていない場合はVSCodeの行を移動する機能を使うため、ショートカットをctrl+gから変更している方はコードの方も変更してください。

該当コード
profile.ps1
# 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ではないですが。

該当コード
profile.ps1
Set-Clipboard "s a
"

今までの出力削除

該当コード
profile.ps1
Clear-Host

ブラウザでa問題を開く

details
profile.ps1
$link = "https://atcoder.jp/contests/$ContestName/tasks/$ContestName" + "_a"
Start-Process $link

t

shell
t (問題名)
t c

cargo compete testと同じです。

s

shell
s (問題名)
s d

cargo compete submitと同じです。

注意点

NewABCで繰り返しリクエストする場合、サーバーに負担をかけそうで心配です。
私はできるだけコンテスト開催直前で実行するようにしています。

また、スリープの時間もかなりギリギリなので間違った処理をし始めたらそこを見直してみてください。

tsaからgをしっかりと確認してください。
違う問題のコードを提出しているかもしれません(体験談)。

おまけ

profile.ps1に以下のコードを追加すると、コード補完の無効化・起動時のバージョン表示の消去ができます。
AtCoderワークスペース以外でも適用したい場合はPowerShell自体のプロファイルを編集してください。

profile.ps1
Set-PSReadLineOption -PredictionSource None
Clear-Host

おわりに

ここまで読んで頂きありがとうございます。

もともとPowerShell Profileを使って同じことをしていたのですが、関数のスコープが気になって調べてみたところ、ワークスペースを使えば実現できそうだったためやってみました。

これくらいなら自分でも記事をかけるだろうと思い、初めての記事作成にも挑戦しました。
最初は何を書けばいいかさっぱりでしたが、先人の知恵を借りつつ書き始めてしまえば何だかんだ3日くらいで出来てしまいました。

アウトプットをすること自体苦手なので、練習として継続していければと思っています。

至らない点があればぜひご指摘ください。

参考文献

Invoke-ExternalCommands関数を使わせていただきました。

Start-Sleepメソッドのドキュメント

SendWaitメソッドのドキュメント

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0