コマンドプロンプトでカーソルが消えたとき自動復帰させる方法を思いついた

:::note
本記事では `CMD.EXE` のことを**コマンドプロンプト**と呼び，コマンド入力待ちの文字列のことは単に**プロンプト文字列**と呼び分けています。
:::

# はじめに

**Windows 10** のいつ頃からでしょうか，コマンドプロンプト内でカーソルが消えてしまうという現象が起こるようになりました。同様な事例は他のユーザーからも報告[^1]されており，消えたカーソルの復旧方法[^2]も編み出されてはいますが，それは **PowerShell** を利用するというもので，**Windows** に標準で添付されているとはいえ起動の重さから常用が躊躇われ，筆者は下記のようなバッチファイルを作成して愛用していました。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
title {
  roundCorner 16
  padding 0 16 16 16
  backgroundColor #202020
  fontColor #e3e3e3
  fontStyle normal
  horizontalAlignment left
  fontName monospaced
  fontSize 14
}
</style>
sprite TITLE <svg width="640" height="28">
<path d="M0 0 v28 a4 4 0 0 0 4 4 h116 a4 4 0 0 0 4 -4 v-28" fill="#606060">
<text x="8" y="24" font-size="16" fill="white">CURSOR.CMD</text>
</svg>
title
<$TITLE>

@echo off
echo <color:yellow>^[</color>[0m<color:yellow>^[</color>[?25h
end title
@enduml
```

これは **Windows 10** の途中から使えるようになった **VT100** 互換のエスケープシーケンスを用いたカーソルの復旧方法です。カーソルがもしも見えなくなった場合，自作の `cursor` コマンドを実行するだけでよいのですが，先日，もっと良い方法を思いつきました。

# 最初に結論を書く

それはプロンプト文字列にカーソル復帰のエスケープシーケンスを仕込ませるというものです。まず，下記のようなバッチファイル `PROFILE.CMD` を作成します。

```:PROFILE.CMD
@echo off
prompt $p$g$e[0m$e[?25h
```

筆者はさらにデスクトップ上に `CMD.EXE` のショートカットを置き，起動オプション `/K` で上記の `PROFILE.CMD` を指定するようにしています。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
title {
  fontStyle normal
  fontSize 16
}
</style>
title
<img:https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3490655/0fd933c3-e19b-4292-94c1-6ffa73463ef4.png>

図１　コマンドプロンプトのショートカットの設定画面
end title
@enduml
```

上記のショートカットを用いてコマンドプロンプトを起動すれば，カーソルが消えたり，あるいは文字の色が変わってしまった場合でも，プロンプト文字列が再表示された時点で元の状態に自動復帰します。

<!--

# 応用編

プロンプト文字列の色を変えることもできます。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
title {
  roundCorner 16
  padding 0 16 16 16
  backgroundColor #202020
  fontColor #e3e3e3
  fontStyle normal
  horizontalAlignment left
  fontName monospaced
  fontSize 14
}
</style>
sprite TITLE <svg width="640" height="28">
<path d="M0 0 v28 a4 4 0 0 0 4 4 h246 a4 4 0 0 0 4 -4 v-28" fill="#606060">
<text x="8" y="24" font-size="16" fill="white">応用例）プロンプトの色を変える</text>
</svg>
sprite CURSOR <svg width="9" height="14">
<path d="M0 10 v4 h9 v-4" fill="e3e3e3">
</svg>
title
<$TITLE>

prompt $e[42m$p$g$e[0m$e[?25h

<back:#13A10e>C:\Qiita></back>cd
C:\Qiita

<back:#13A10e>C:\Qiita></back><$CURSOR>
end title
@enduml
```

-->

# 参考資料

プロンプト文字列をカスタマイズできる `PROMPT` コマンドのヘルプメッセージを以下に示します。
※**Windows 10 22H2** のものです。

```:PROMPT コマンドのヘルプメッセージ
c:\>prompt /?
cmd.exe のコマンド プロンプトを変更します。

PROMPT [文字列]

  文字列    新しいコマンド プロンプトを指定します。

PROMPT には通常の文字と次に示す特殊コードを使用できます:

  $A   & (アンパサンド)
  $B   | (パイプ)
  $C   ( (左かっこ)
  $D   現在の日付
  $E   エスケープ コード (ASCII コードの 27)
  $F   ) (右かっこ)
  $G   > (不等号 (より大))
  $H   バックスペース (直前の文字を削除します)
  $L   < (不等号 (より小))
  $N   現在のドライブ
  $P   現在のドライブとパス
  $Q   = (等号)
  $S     (空白)
  $T   現在の時刻
  $V   Windows のバージョン番号
  $_   キャリッジ リターンとラインフィード
  $$   $ (ドル記号)

コマンド拡張機能を有効にすると、PROMPT コマンドは、次の追加の書式
文字をサポートするようになります:

  $+   PUSHD ディレクトリ スタックの深さに応じて、0 個以上のプラス
       記号 (+) を指定します。1 個のプラス記号が、プッシュされた
       1 レベルを表します。

  $M   現在のドライブ文字に関連付けられているリモート名を表示します。
       現在のドライブがネットワーク ドライブでない場合は、空の文字列
       を表示します。
```

本記事で用いたエスケープシーケンスを以下に示します。エスケープシーケンスについての詳しい情報は Microsoft 公式サイト[^3]を参照下さい。

<table>
<caption>表１　カーソルの可視性</caption>
<thead>
<tr><th>シーケンス</th>
    <th>コード</th>
    <th>説明</th>
    <th>動作</th></tr>
</thead>
<tbody>
<tr></tr>
<tr><td><code>ESC [ ? 25 h</code></td>
    <td><b>DECTCEM</b></td>
    <td>テキストカーソル<br>有効化モード表示</td>
    <td>カーソルを表示します</td></tr>
<tr></tr>
<tr><td><code>ESC [ ? 25 l</code></td>
    <td><b>DECTCEM</b></td>
    <td>テキストカーソル<br>有効化モード非表示</td>
    <td>カーソルを非表示にします</td></tr>
</tbody>
</table>

<table>
<caption>表２　テキストの書式設定</caption>
<thead>
<tr><th>シーケンス</th>
    <th>コード</th>
    <th>説明</th>
    <th>動作</th></tr>
</thead>
<tbody>
<tr></tr>
<tr><td><code>ESC [ 0 m</code></td>
    <td><b>SGR</b></td>
    <td>既定値</td>
    <td>すべての属性<sup>※</sup>を変更前の<br>既定の状態に戻します</td></tr>
<!--
<tr></tr>
<tr><td><code>ESC [ 42 m</code></td>
    <td><b>SGR</b></td>
    <td>背景緑</td>
    <td>非ボールドの明るい緑を<br>背景に適用します</td></tr>
-->
</tbody>
</table>

※ここでいう「すべての属性」とはあくまでテキストの前景色や背景色に関わる属性のことです。カーソルの可視性の属性は含まれません。

# まとめ

プロンプト文字列にカーソル復帰のエスケープシーケンスを仕込ませて以来，カーソルが消える事象は一度も起こっていません。

# 謝辞

下記サイトの情報がヒントになりました。バッチファイルの冒頭部分でいきなり `Prompt;$H` と実行しており，この `$H` って何だろう（バックスペースの意味）とリファレンスを調べたのがきっかけです。エスケープコード `$E` を出力できるのならエスケープシーケンスを使ってプロンプト文字列の色を変えられますし，今回のようにカーソル表示のオン／オフも可能です。

https://qiita.com/mpyw/items/2e4bf35044a407be536d

<!--
参考文献
-->

[^1]: [Windows10のコマンドプロンプトでカーソルが非表示になる件 - Qiita](https://qiita.com/F_murasaki/items/23da7c4e8fbc9d861875)

[^2]: [Windows10のコマンドプロンプトでカーソルが消える問題 - Qiita](https://qiita.com/yu1c1yu1c1/items/463ce889e3c2275831a0)

[^3]: [コンソールの仮想ターミナル シーケンス - learn.microsoft.com](https://learn.microsoft.com/ja-jp/windows/console/console-virtual-terminal-sequences)