Visual Studioの「出力 (Output)」ウィンドウは、ビルドログやデバッグログが増えるほど読みづらくなります。VS拡張で行間を広げたり詰めたりして可読性を上げたいのに、ILineTransformSourceProviderがOutputでは反応しない…そんな時の原因と実装の勘所、そして「行を消す」要件の代替策までをまとめます。
やりたいこと:Outputウィンドウの行の高さ(行間)をVS拡張でコントロールしたい
Visual Studio拡張(VSIX)で、WPFエディタの行レイアウトを調整できる拡張ポイントがILineTransformSourceProviderです。通常のエディタ(.cs / .cpp / .h など)では、各行に対して LineTransform を返すことで、次のようなカスタマイズができます。
- 行の上下に余白を追加して、ログやコメントを読みやすくする
- 縦方向の拡大率(verticalScale)を変えて、行高をまとめて調整する
- 特定パターンの行だけ余白を増やし、区切りを分かりやすくする(例:エラー行、見出し行)
ところが「出力 (Output)」ウィンドウだけは、同じコードを入れても Create が呼ばれず、行高が変わらないケースがよくあります。
現象:ContentType("output") を付けても Create が呼ばれない
典型的な症状は次のとおりです。
[ContentType("output")]を付けたILineTransformSourceProviderがOutputでは動かない- 一方で、.h / .cpp / .cs など通常のエディタビューでは期待どおり動く
- 同じOutput向けでも、
IViewTaggerProviderはContentTypeだけで動くことがあり、余計に混乱する
この差は、拡張ポイントの「適用条件」が原因です。ILineTransformSourceProvider は ContentTypeだけでなく TextViewRole の一致も重要で、Outputウィンドウはふつうのドキュメントエディタと役割(Role)が異なります。
結論:Outputに行変換を適用するには TextViewRole(Interactive) が必須
Outputウィンドウに対して行の高さを変えるなら、プロバイダに次の属性を付けるのがポイントです。
[TextViewRole(PredefinedTextViewRoles.Interactive)](これが無いとOutputでは作成されない)[ContentType("Output")](まずは基底のOutputを狙う)
また、OutputのContentType名は環境・ペイン構成によって差が出る場合があるため、最初は「実値ログ」で確認するのが安全です(後述)。
最小実装:まずは「呼ばれる」状態を作る
最初は判定ロジックを入れず、固定値で行間を変えて「Createが呼ばれる」ことを確認するのが近道です。
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Formatting;
using Microsoft.VisualStudio.Utilities;
[Export(typeof(ILineTransformSourceProvider))]
[TextViewRole(PredefinedTextViewRoles.Interactive)] // ← Outputにはこれが必須
[ContentType("Output")] // まずは基底のOutputを狙う
internal sealed class OutputTransformSourceProvider
: ILineTransformSourceProvider, ILineTransformSource
{
public ILineTransformSource Create(IWpfTextView view)
{
// どのContentTypeで起動しているか確認したい場合:
// System.Diagnostics.Debug.WriteLine(view.TextDataModel.ContentType.TypeName);
return this;
}
public LineTransform GetLineTransform(ITextViewLine line, double y, ViewRelativePosition pos)
=> new LineTransform(topSpace: 10, bottomSpace: 10, verticalScale: 1.0);
}
これでOutputウィンドウの各行に上下余白が入り、見た目の行高が変わります。反映されない場合は、後述のトラブルシュート表を確認してください。
なぜInteractiveが必要?TextViewRoleで「対象ビュー」が絞り込まれる
Visual Studioのエディタ拡張は、MEF(Managed Extensibility Framework)のエクスポートとして登録し、
- そのビューがどんな種類のテキストか(ContentType)
- そのビューがどんな役割か(TextViewRole)
の条件に合致したときだけ有効になります。Outputウィンドウは「ユーザーがマウス/キーボードで操作できる対話的ビュー(Interactive)」として扱われるため、PredefinedTextViewRoles.Interactive を明示しないと一致しません。通常のエディタビューでよく使う Document だけでは対象外になります。
| TextViewRole(例) | 主なイメージ | どんなときに使う? |
|---|---|---|
Document | ファイルを開いた通常のエディタ | .cs / .cpp / .h など「ドキュメント編集」前提の拡張 |
Interactive | ユーザーが操作できる対話的ビュー | Outputなど「対話ビュー」に効かせたい拡張 |
PreviewTextView など | プレビュー用途の埋め込みビュー | Peek/差分/プレビューなど、限定された場面向け |
ContentTypeは「Output」を起点に、必要ならサブタイプも追加する
OutputのContentTypeはペインによって細分化されることがあります。まずは "Output" を指定して動作確認し、必要に応じて次のようなサブタイプを追加で狙います(複数付与が可能です)。
[ContentType("Output")]
// 必要に応じて追加
[ContentType("BuildOutput")]
[ContentType("BuildOrderOutput")]
[ContentType("DebugOutput")]
[ContentType("TestsOutput")]
「どれを付けるべきか迷う」場合は、実際に起動時の view.TextDataModel.ContentType.TypeName をログに出して、手元の環境での実値を優先してください。
| 狙いたい範囲 | 推奨ContentType | 使いどころ |
|---|---|---|
| まず動かしたい | Output | Outputウィンドウ全般をまず対象にする |
| ビルド出力中心 | BuildOutput, BuildOrderOutput | ビルドログを読みやすくしたい |
| デバッグ出力中心 | DebugOutput | Debug.WriteLine等のログに最適化したい |
| テスト出力中心 | TestsOutput | テストの標準出力・失敗ログを見やすくしたい |
LineTransformの設計:余白とverticalScaleで「行高」を作る
LineTransform は主に3つの軸で行の見た目を変えます。
- topSpace:文字の上に追加する余白
- bottomSpace:文字の下に追加する余白
- verticalScale:文字部分の縦スケール(余白にはかからない)
感覚的には「上下余白で行間を作り、verticalScaleで文字の背を伸ばす/縮める」という理解が分かりやすいです。おすすめの調整手順は、まず top/bottom を調整して読みやすい行間を作り、最後にverticalScaleで詰め具合を微調整することです。
| 目的 | 設定例 | 見た目の変化 |
|---|---|---|
| 行間を少し広げる | top=2, bottom=2, scale=1.0 | 自然に読みやすくなる。日本語ログでも効果が出やすい |
| 区切りを強調する | top=8, bottom=8, scale=1.0 | ブロック感が出る。大量ログでは広げすぎ注意 |
| 文字だけ少し小さく | top=0, bottom=0, scale=0.9 | 行数を稼げるが、視認性とのバランスが必要 |
| 行間+文字サイズも調整 | top=3, bottom=3, scale=0.95 | 詰めても読みやすくしたい時の落とし所 |
特定の行だけ行間を変える(例:エラー行だけ強調)
「普段は詰めて表示し、エラーや区切りだけ広げたい」場合は、GetLineTransform 内で表示行のテキストを見て分岐します。ただし、行ごとに文字列解析をすると重くなりやすいので、最初は軽い条件(IndexOf など)から始めるのが安全です。
public LineTransform GetLineTransform(ITextViewLine line, double y, ViewRelativePosition pos)
{
// SnapshotSpan からテキストを取得(多用しすぎると重くなるので注意)
var text = line.Extent.GetText();
// 例:error を含む行だけ上下余白を増やす
if (text.IndexOf("error", StringComparison.OrdinalIgnoreCase) >= 0)
return new LineTransform(6, 6, 1.0);
return new LineTransform(1, 1, 1.0);
}
大量ログでのパフォーマンスが気になる場合は、「別の場所で解析して結果をキャッシュし、GetLineTransformでは参照だけにする」方針に寄せると安定します。
ContentTypeの確認方法:実値をログで拾う
「自分の環境では何のContentTypeで動いているのか」を確かめるのが最短ルートです。Create の中で次を出力すると、Outputペインごとの差が見つけやすくなります。
public ILineTransformSource Create(IWpfTextView view)
{
System.Diagnostics.Debug.WriteLine(view.TextDataModel.ContentType.TypeName);
return this;
}
開発中ならデバッグ出力で十分です。配布版で調査したいなら、ActivityLogへの出力など運用に適した経路を使ってください。
実装パターン:ビュー単位で状態を持ち、トグル運用に強くする
「行間を固定で足す」だけなら最小実装で十分ですが、実運用では次のニーズが出がちです。
- ホットキーで行間を切り替えたい(広い / 狭い / 標準)
- Outputウィンドウが複数開いても破綻しないようにしたい
その場合は、ビューごとにILineTransformSourceインスタンスを作り、view.Propertiesに保持すると管理が楽です。さらに「切り替えたのに反映が遅い」体感を減らすため、軽い再レイアウトのトリガーも入れておくと扱いやすくなります。
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Formatting;
using Microsoft.VisualStudio.Utilities;
[Export(typeof(ILineTransformSourceProvider))]
[TextViewRole(PredefinedTextViewRoles.Interactive)]
[ContentType("Output")]
internal sealed class OutputTransformSourceProvider : ILineTransformSourceProvider
{
public ILineTransformSource Create(IWpfTextView view)
=> view.Properties.GetOrCreateSingletonProperty(() => new OutputLineTransformSource(view));
}
internal sealed class OutputLineTransformSource : ILineTransformSource
{
private readonly IWpfTextView _view;
private double _top = 2;
private double _bottom = 2;
private double _scale = 1.0;
public OutputLineTransformSource(IWpfTextView view) => _view = view;
public LineTransform GetLineTransform(ITextViewLine line, double y, ViewRelativePosition pos)
=> new LineTransform(_top, _bottom, _scale);
// 例:外部コマンドから呼んでモードを切り替える想定
public void SetModeCompact()
{
_top = 0;
_bottom = 0;
_scale = 0.95;
// 即時反映させたい場合の「再レイアウト刺激」
_view.DisplayTextLineContainingBufferPosition(
_view.Caret.Position.BufferPosition, 0.0, ViewRelativePosition.Top);
}
}
トラブルシュート:Createが呼ばれない/反応しない時の早見表
| 症状 | ありがちな原因 | 対処 |
|---|---|---|
| OutputだけCreateが呼ばれない | TextViewRoleがDocument前提になっている | [TextViewRole(PredefinedTextViewRoles.Interactive)]を付与して再確認 |
| Createが呼ばれたり呼ばれなかったり | 狙っているOutputペインのContentTypeが違う | view.TextDataModel.ContentType.TypeNameを出力し、実値に合わせてContentTypeを追加 |
| 行間が変わった気がしない | 差が小さい/verticalScaleだけ調整している | まずtop/bottomを大きめにして効果を目視し、その後に微調整 |
| スクロールが重い | GetLineTransformで重い処理(正規表現、長い文字列処理) | 判定を軽量化し、必要なら解析結果をキャッシュして参照だけにする |
重要:LineTransform では「行を削除」「完全に非表示」はできない
LineTransform は「行の位置と縦スケール」を調整するための仕組みで、行そのものを消すAPIではありません。極端に小さなフォントサイズやscaleを指定しても、レイアウト上の枠が残り、結果として大量の“隠したい行”が存在すると縦スペースを食い続けます。
つまり、要件が「行間調整」なら ILineTransformSourceProvider が適切ですが、要件が「特定行を畳む/消す」なら別アプローチが必要です。
「行を消したい/畳みたい」場合の代替策
Outputウィンドウで任意行を“本当に”折りたたむ公開APIは期待しにくいため、要件に合わせて実現方法を切り替えます。
見た目だけ隠す(近似策)
- 行変換で
top/bottomを最小にし、verticalScaleを小さめにする - 分類(Classification)タグを付け、書式マップで「背景と同系色」に寄せて目立たなくする
この方法は簡単ですが、縦スペースをゼロにはできません。「目障りを減らす」用途と割り切るのがコツです。
投影バッファ(ProjectionBuffer)で“必要な行だけ”の独自ビューを作る
Outputの内容を監視して、必要な行だけを投影したテキストビュー(IWpfTextView)を自作ツールウィンドウとして表示する方法です。自由度が高く、次のような操作も実装できます。
- 正規表現でフィルタして「出したい行だけ」表示
- カテゴリごとの折りたたみ(例:同一テストケースのログをまとめる)
- 行クリックで関連ファイルやエラー位置へジャンプ
一方で、監視・同期・パフォーマンス・UI一式が必要になり、実装コストは高めです。「どうしても折りたたみたい」要件の本命として検討するとよいでしょう。
自前ログビューアに切り替える(リダイレクト)
ビルド/テスト/デバッグの出力をファイル・パイプなどへリダイレクトし、別のビューアで閲覧する方法です。VSに閉じた体験からは離れますが、ログ閲覧の自由度は最大です。チーム開発で「ログフォーマットを揃えて解析したい」場合にも向きます。
| 目的 | おすすめ手段 | 強み | 弱み |
|---|---|---|---|
| 行間を広げ/詰めたい | ILineTransformSourceProvider | 実装が軽い、Outputにも適用できる | 行の削除・折りたたみはできない |
| 目立たなくしたい | 行変換+分類で色を寄せる | 実装が比較的簡単 | スペースは残る |
| 必要な行だけ見たい | ProjectionBufferで独自ビュー | フィルタ/折りたたみ/ジャンプなど自由度が高い | 実装コストが高い |
| 高度に分析したい | ログを外部へリダイレクト | 検索・可視化・共有が強い | VS内の一体感は下がる |
パフォーマンスの注意:Outputは“行数が増えていく”のが前提
Outputはスクロールや追記が頻繁で、行数も簡単に数万行に達します。GetLineTransform は表示行ごとに呼ばれるため、ここで重い処理(正規表現、文字列生成、外部I/Oなど)を行うとスクロールがカクつきます。
- 行の判定が必要なら、可能な限り「O(1)で終わる判定」に寄せる(状態フラグ、範囲キャッシュなど)
- “行テキスト全体”の解析が必要なら、別の場所で解析して結果をキャッシュし、
GetLineTransform側は参照だけにする - とりあえずは「全行同じTransform」で動かし、必要になってから条件分岐を足す
FAQ:Outputウィンドウの行間調整でよくある疑問
Outputのどのペインにも効きますか?
ContentType("Output") を指定すると、基本的にはOutput系のビュー全体が対象になります。特定ペインだけに限定したいなら、ContentTypeをサブタイプで絞る、またはCreate内で ContentType.TypeName を見て条件分岐するのが現実的です。
IViewTaggerProvider は動くのに、なぜ行変換だけ動かなかったの?
拡張ポイントごとに「一致条件」が違うためです。タグ付け系はContentType中心で動作するケースが多い一方、行のフォーマットに影響する拡張はRoleを含めた条件が要求され、OutputではInteractive指定が抜けていると適用されにくくなります。
verticalScaleを0.1みたいに極端にすると行が消えますか?
見た目は小さくできますが、レイアウト上の枠が残るため「行を完全に消す」用途には向きません。大量に“隠し行”があるなら、独自ビュー(ProjectionBuffer)などの方式を検討してください。
折りたたみ(アウトライン)で隠せませんか?
アウトライン機構は主にDocumentロールのテキストエディタ向けです。Outputは同じテキストビュー技術を使っていても、設計思想が異なるため、期待どおりに適用できないことが多いです。
まとめ:Outputの行高調整は「Interactiveロール」を押さえれば実現できる
- Outputで
ILineTransformSourceProviderを動かすには[TextViewRole(PredefinedTextViewRoles.Interactive)]が必須 - ContentTypeはまず
Outputを起点にし、必要ならBuildOutput/DebugOutput/TestsOutputなどを追加 LineTransformは余白と縦スケールの調整で、行の削除・折りたたみはできない- 「消す/畳む」が目的なら、見た目だけの近似策か、ProjectionBufferで独自ビューを作るのが現実的
まずは最小実装でCreateが呼ばれる状態を作り、ContentTypeの実値をログで確認しながら対象範囲を絞る、という順番で進めると迷いが減ります。

コメント