Visual Studio 出力(Output)ウィンドウの行の高さを変更する方法|ILineTransformSourceProviderとTextViewRole(Interactive)

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だけで動くことがあり、余計に混乱する

この差は、拡張ポイントの「適用条件」が原因です。ILineTransformSourceProviderContentTypeだけでなく 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使いどころ
まず動かしたいOutputOutputウィンドウ全般をまず対象にする
ビルド出力中心BuildOutput, BuildOrderOutputビルドログを読みやすくしたい
デバッグ出力中心DebugOutputDebug.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の実値をログで確認しながら対象範囲を絞る、という順番で進めると迷いが減ります。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次