彷徨えるフジワラ

年がら年中さまよってます

color+pager 完全マスター

※ ページャ併用の有無によるモード切り替えに関する追記あり@2015-05-02

このエントリは、Mercurial Advent Calendar 2013 の22日目です。

id:tk0miya さんによる、17日目のエントリや、ターミナル判定の話との被り具合に一抹の不安を隠せませんが、一応別視点でまとめたエントリなので、気にしないことにします(笑)。

先日、以下のツイートに関連する、一連のやりとりがありました。

color エクステンションpager エクステンションの併用に関しては、私自身「ソースコード等を確認」⇒「忘れる」のループでしたし、Windows 環境は少々事情が入り組んでいるので、この機会にきっちりと調べてまとめておきましょう。

環境や使用するページャと、出力色付けの可否の関係は、以下の通りです。

環境 Windows UNIX
ページャ more.com サードパーティ製品 任意
pagerエクステンション併用 ×
ansiモード限定
明示的なページャ併用((コマンドラインにおける、パイプ "|" を使ったプロセス連携)) ×
ansiモード限定
--color true オプション必須

--color true オプション必須

表中の「ansiモード」と言うのは、"ANSI エスケープコードを用いた色付け" のことです。

Windows 環境では、基本的に「win32モード」で色付けを行いますので、Windows 環境で ansiモードによる色付けを行う際には、設定ファイルの [color] セクションで、明示的にモード設定しておいた方が良いでしょう。

[color]
mode = ansi

各モードに関する詳細は後述します。
Windows 環境で、cmd.exeWindows PowerShell の代わりに、ANSI エスケープコードに対応しているサードパーティ製ターミナルソフトを使用する場合は、ページャに more.com を使用していても、「ANSI エスケープ対応のサードパーティ製ページャ」を使用しているケースと同じ扱いになります(なる筈です、多分……)。

Windows 環境」での「サードパーティ製品」なページャや、「UNIX 系環境」のターミナルソフト/ページャは、ANSI エスケープコードに対応していることを前提にしています。

なお、pager エクステンションのオンラインヘルプでは、設定例として:

[pager]
pager = less -FRX

less コマンドに対して、ANSI エスケープコードを通すための -R オプションを指定しています。

しかし、文字コードに Shift-JIS (cp932) を使うケースでは、ANSI エスケープコード以外も通す -r を指定しないと、日本語文字列がうまく表示されない可能性がありますから注意してください(UTF-8 だと大丈夫っぽい)。

なお、現行の color エクステンションは、TERMINFO が利用できる環境以外では、ターミナルソフトが256色対応していても、8色 (black, red, green, yellow, blue, magenta, cyan, white) しか使用できません。

かなり強引な方法ではありますが、256色対応するための poor256color エクステンションを公開しましたので、色使いに拘りのある方はお試しください。

以下、技術的な話を交えた、詳細の説明になります。

色付けの実現方法

UNIX系環境でのテキスト表示の色付けは、先述した「ANSI エスケープコード」を用いるのが一般的です。

この手法では、通常の表示内容のテキストと同じバイト並びに、「ここから先は〜〜色で表示」といった制御情報を示す「エスケープコード」を混在させます。ANSI エスケープコードに対応しているターミナルソフトは、出力結果から制御情報を分離し、表示内容を描画すると同時に、対応区画への色付けといった処理を行います。

Mercurial の color エクステンションでは、これを「ansiモード」と呼んでいます。

その一方、Windows 環境での色付けは、以下のように表示内容と制御情報を個別に出力する必要があります。

  • 表示内容の出力は [http://msdn.microsoft.com/ja-jp/library/cc429856.aspx:title=WriteFile()](あるいは [http://msdn.microsoft.com/ja-jp/library/cc429845.aspx:title=WriteConsole()]?)
  • 色付け等の制御情報出力は [http://msdn.microsoft.com/ja-jp/library/cc429756.aspx:title=SetConsoleTextAttribute()]

Mercurial の color エクステンションでは、これを「win32モード」と呼んでいます。

Windows 環境で、パイプによってコマンド出力が他のプログラムにリダイレクトされている場合、SetConsoleTextAttribute() による色付けは効果を持ちません。

そのため、Mercurial の color エクステンションでは、以下の場合には色付け処理を行わないようになっています。

Windowsmore.com コマンドをページャとして利用した場合:

  • win32モード選択時は:
    • 明示的なリダイレクトの場合、color エクステンションが色付け処理を抑止してしまう
    • pager エクステンション併用でも、リダイレクト実施そのものが、色付け効果を無効化させてしまう
  • かといって、more.comANSIエスケープコードに対応していないので、ansiモードも選択できない

という具合に、色付けに関しては八方塞がりなことがわかります。

以上のことから、Windows環境上でページャを利用するには、以下の条件を満たす必要があるわけです。

  • ANSIエスケープコードに対応した、ページャ((ページャ自身が SetConsoleTextAttribute() を実施する筈))なり、ターミナルソフトを使用して、且つ
  • ansiモードを選択

ちなみに、Cygwinmore コマンドや less コマンドは、ansiモードで使用すれば、Windowshg コマンドとの組み合わせでも、出力の色付けが正常に機能します。

※ 2015-05-02 追記: ここから >>>>

2015-05-01 (現地時間) にリリースされた Mercurial 3.4 からは、ページャ併用の有無に応じて、別々のモードを指定できるようになりました。

例えば以下の設定により、サードパーティ製ページャを併用する際は ansi モードで、それ以外の場合は標準の win32 モードで色付けを行う、といったことが可能になります。

[color]
mode = win32
pagermode = ansi

これまでは、ページャプログラム向けに ansi モードに設定してしまうと、ページャを使用しないケースでは色付けが機能しない、という残念な状況だったのですが、[color] pagermode の導入により、かなり使い勝手が向上するのではないでしょうか?

※ 2015-05-02 追記: ここまで <<<<

リダイレクトの検出

色付けの要否を指定する --color オプションや、ページャ起動の要否を指定する --pager オプションのデフォルト値は、自動判定を意味する auto になっています。

「自動判定」が指定された場合、hg コマンドの標準出力がリダイレクトされているなら、color エクステンションによる色付けや、pager エクステンションによるページャ起動は抑止されます。

この挙動は、以下のような方針に基づいています。

リダイレクトされた標準出力は、grepsed といったフィルタコマンドによる、検索や加工の対象となりえる。
色付け*1をはじめとする余計な加工や、ページャによる出力の横取りは、フィルタコマンドの正常な動作を妨げてしまうかもしれない。

本エントリ冒頭に掲載した表の「明示的なページャ併用」のケースでは、hg コマンドのプロセスが起動された時点で、既に標準出力がリダイレクトされています。この状態で色付けの有無を自動判定した場合、当然、色付け不要と判定されます。

その結果、「明示的なページャ併用」と色付け処理を共存させるためには、「--color true オプション必須」となっているわけです。

その一方で、pager エクステンション経由でページャを使用する場合、pager エクステンションによるページャ起動は、以下の手順で実施されます。

  1. ページャ起動の要否を判定
  2. 内部で参照する「標準出力リダイレクトの有無」状態を、現状の判定結果で上書き
  3. 必要に応じてページャを起動

手順 (2) における設定上書きが、(3) におけるページャ起動の前の段階で実施されるため、内部的に保持される「標準出力リダイレクトの有無」値は、ページャ起動後も「hg コマンド起動直後時点」のものとなります。

つまり、既にページャが起動=標準出力がリダイレクトされていても、手順 (2) で設定された値を元に、color エクステンションは「標準出力はリダイレクトされていない」と判断します。

結果として、実際には標準出力のリダイレクトが行われているにも関わらず、--color true 等の指定がなくても、色付け処理が実施されるわけです。

ちなみに、リダイレクトの有無の検出は、ライブラリ関数 isatty(3) を使い、標準出力とターミナル(tty)との関連付けの有無を判定することで実現されます。

また、record エクステンション使用時等の、対話的入力を必要するケースでは、標準入力に対して isatty(3) 判定を実施することで、対話的入力の可否を判定しています。

エクステンションで追加される設定項目/オプション

ソースを追いかける際に、時々自分でも混乱するので(笑)、color および pager エクステンション有効化によって、追加される設定項目/オプションを改めて列挙しておきます。

color エクステンション:

  • [color] mode 設定は、色付け方式の指定
    自動選択(auto:デフォルト)以外に、win32ansiterminfo を指定可能
  • --color 種別 オプションは、色付け実施の有無
    自動選択(auto:デフォルト)以外に、真偽値を指定可能

pager エクステンション:

  • [pager] pager 設定は、ページャプログラムの指定
    無指定時(+ PAGER 環境変数も設定無し)はページャ使用無し
  • --pager 種別 オプションは、ページャ使用の有無
    自動選択(auto:デフォルト)以外に、真偽値を指定可能

--color--pager での指定値が auto や真偽値でない場合に、色付けのモードやページャプログラム指定とみなしてくれると、エイリアス定義等がやり易い((エイリアス定義では --config オプションが使えない))んですけどねぇ。

まぁ、従来不正値扱いされていた値に対して、新たな意味を持たせるのは、後方互換性を考えると、難しいところですけれど……

*1:ANSIエスケープコードの追加