ターミナル用のページャーを作成しました。

2026/04/16 7:27

ターミナル用のページャーを作成しました。

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

著者は、ターミナルユーザーインターフェース(TUI)構築の中核エンジンとなる再利用可能な Go のビューポートコンポーネントを開発した。このモジュール化されたシステムは、Kubernetes ログを表示するための kl や、Nomad を表示するための wander、および

$PAGER
環境変数を尊重して複数ページの内容を処理するdaily utility loreといったツールを稼働させている。アーキテクチャは、リサイズ、スクロール、検索(ショートカット
/
r
i
に正規表現対応付き)、水平方向のパニング、アイテム選択を含む必須機能をサポートしている。システムは、文字の折り返しおよびセル幅の計算(バイトをグリフに正確にマッピング)を行う
Item
インターフェース、表示向けの
Viewport
、検索機能向けの
FilterableViewport
の 3 つの主要モジュールを通じて、複数行および動的コンテンツを管理する。
MultiItem
のような高度な変種は、行番号などの動的プレフィックスをサポートしており、実装では特殊文字および絵文字の堅牢な描画が保証されている。開発者は、これらのユーティリティをテストまたは実行するために Go または Docker を使用でき、堅牢なコマンドラインアプリケーションの作成を容易にしている。今後の作業には、libghosttyとの統合およびエコシステムのさらなる改善が焦点となる。

本文

【TL;DR】私は、k8s ログ向けの

kl
や Nomad 向けの
wander
のようなターミナルアプリケーション(TUI)を開発しています。TUI コア機能には、アプリケーションマニフェストやログなど大規模なテキストブロックとの対話が含まれます。私のプロジェクトでは、テキストナビゲーション用の再利用可能な「Viewport(視窗)」コンポーネントを Go で作成しました。ターミナルページャーは、マルチページのテキストと対話的にナビゲートできるプログラムです。この Viewport コンポーネントを利用して作成した
lore
は、現在私が毎日使用している愛用ターミナルページャーとなりました。今回は、Viewport 内で実装したかった機能に加え、それを具体化するための学びやデザイン上の決断について詳述します。

ターミナルのページングとは

コマンドの実行と並んで、ターミナルはテキストを表示・ナビゲートするための場としても利用されます。

❯ cat file.txt
I love terminals!

ターミナルは等幅フォントによる格子構造を持っています。サイズは行数と列数以降で定義され、テキストはこのグリッドに合わせて表示されます。

❯ cat ~/chessboard.txt
    a  b  c  d  e  f  g  h 
  8  R  N  B  Q  K  B  N  R 
  7  P  P  P  P  P  P  P  P 
  6                         
  5                         
  4                         
  3                         
  2  P  P  P  P  P  P  P  P 
  1  R  N  B  Q  K  B  N  R 
    a  b  c  d  e  f  g  h

※補足:ターミナルでのテキスト装飾

ターミナルでは ANSI エスケープコードを使ってテキストを装飾できます。

❯ echo "default, \x1b[31mred text\x1b[0m, default"
default, red text, default

上記のチェス盤パターンはこの方式で実現されています:

chessboard.txt
は、ターミナル上で灰色のチェックボードとして表示されるよう ANSI エスケープコードを埋め込んだテキストファイルです。

開発者は頻繁に大量のテキストをターミナルで確認します(スクリプトや CLI の冗長な出力、git の差分、アプリケーションログ、man ページ、README、データベースクエリの結果〔例:psql/sqlite〕、エージェント型 AI ツールの出力など)。しかし、出力が 1 つのターミナル画面分の高さより短い場合は、特別なページャーを使わずに標準出力に直接印刷されます。その後、マウスでスクロールしたり、ターミナルエミュレータの標準機能(例:iTerm2 の cmd+f)で検索を行ったりします。あるいは、tmux などのマルチプレクサーを使って、独自のキーバインディングによる検索やログ取り戻しを行うかもしれません。しかし、テキストが複数のページにわたる場合、プログラムは対話的なナビゲーションのためにページャーを使用するのが一般的です。プログラムは

PAGER
環境変数をチェックし、設定されていればそのプログラムでテキストを表示します(標準出力にダンプする代わりに)。
PAGER
はパイプ入力を受け付けるか(
command | mypager
)、ファイル引数を受け付けるか(
mypager myfile.txt
)を示すプログラムを指しています。

※補足:プログラムが $PAGER を使う仕組み

git や man などのプログラムは、内部ロジックで

PAGER
が設定されているかをチェックし、条件付きで使用します。通常、標準出力が TTY(対話的なターミナルセッション)ではない場合はパイプをスキップして直接出力するため、
git diff | grep ...
のような非 TTY 環境では
PAGER
は意味を持ちません。一方、標準出力が TTY ならプログラムは
PAGER
を使うことを選択し、設定されたプログラムを子プロセスとして起動し、パイプでページャーのプロセス標準入力と親プロセスの標準出力をつなげます。Andrew Healey の「シェルの構築」に関する最近の記事には、このパイプやシェルが使用するシステムコールについて詳しく書かれていますのでおすすめです。

開発者のマシンにある多くのプログラムは、

PAGER
が設定されていなければ
less
をデフォルトのページャーとして使います。すべての出力をターミナルに直接ダンプさせたい場合は、
PAGER=cat
と設定します。他の選択肢には
bat
most
delta
があります。また、特定の出力用や
bat
内の装飾後のページング用の環境変数もあります(例:git の出力用に
GIT_PAGER
bat
内でのページング用に
BAT_PAGER
)。

最も一般的な

less
は、オプションと設定を有効活用すると非常に強力です。デフォルトではテキストは一度
less
を終了すれば失われますが、これは一般的に問題ありません。しかし
--no-init/-X
オプションを使うと、
less
から離れるまで表示されたテキスト履歴がターミナル出力に残ります。また
--ignore-case/-i
で検索を大文字小文字区別なく行うことができます。より詳しく学ぶには
less
のオプションや設定に関する記事を推奨します。

ターミナルアプリケーション(TUIs)

ターミナルアプリケーション、または TUI はネイティブデスクトップアプリやウェブサイトと似ていますが、ターミナル内で実行される点が異なります。TUI は通常、Alt スクリーンを使って全画面を一時的に占有し、タイトル、サイドバー、ヘルプテキスト、テキストビューポートなどのアプリケーションコンポーネントを表示します。テキストビューポートコンポーネントはターミナルページャーに似ていますが、画面の一部しか使用しません。TUI では他にも一般的なワークフローとして以下のものがあります:

  • 画面内のコンポーネント間でフォーカスを移動させる
  • 画面内のアイテムリストから選択する
  • ユーザー入力を取得する

TUI では、最小の編集単位はピクセルではなくターミナルグリッドセルです。これは良い制約となり、アプリケーションを「必要な情報のみを除去してヒラリキーボード操作で階層的にデータを観る仕組み」とし、「ウェブサイトやデスクトップアプリのように大きなスクロール可能な画面やボタン満載のツールバーを提供する」ものと引き締める効果があります。

例えば、私は Kubernetes ログを複数のクラスターとネームスペースで扱うために

kl
という TUI を構築しました。起動時に 2 つのテキストビューポートが表示されます:左側に構成されたクラスター、ネームスペース、ポッド、コンテナを示す Kubernetes エンティティ階層、右側には空の状態からログ表示です。

  • 最初は選択ビューポートにフォーカスを当て、ログを tail するための 1 つ以上のコンテナを選択します。
  • コンテナを選択し、そのログを表示します。

L
を押すと、選択ツリーを非表示にして全画面でログを表示します。そこから
/
キーで完全一致検索を実行できます(例:ログ内で "ERROR" を検索)。
x
を押すと一致部分の前後文を含めて表示するか、一致部分のみを表示かを切り替えます。
p
は JSON ログを整形してスペースとインデントを追加します。Enter で単一のログビューにズームします。
?
で利用可能なすべてのコマンドを表示し、Ctrl+C で退出します。これにより TUI がコンポーネントからなるキーボード主導のアプリケーションであることがわかります。TUI の最も重要なコンポーネントは往々にしてミニターミナルページャーそのものです。
kl
では選択ツリーとログビューの双方がミニターミナルページャーです。私はこの共有機能を抽出して「Viewport コンポーネント」として実装しました。

Viewport コンポーネント

Viewport は任意量のテキストを持つ柔軟にサイズ変更可能なボックスです。このテキストボックスはリサイズ可能でスクロール可能、現在の位置を示すパーセンテージインジケータを表示し、未折りたたみ時などに水平パン(左右スクロール)時に文字の折り返しを切り替えられます。検索機能により一致結果のナビゲーションが可能、アイテム選択を許可、ANSI エスケープコードによるテキスト装飾に対応、ユニコードをサポートし、大量のテキストでも一般的にパフォーマンスが良いです。この Viewport は Go で書かれており、Bubble Tea TUI フレームワークを使用したアプリケーションへの統合が容易です。

この機能セットを実現するために、実装は以下の 3 つのモジュールで構成されています:

  • item: テキストをラップし、文字列幅、ターミナル幅、折りたたみ状態に応じて 1 つ以上の行を占用します。
  • viewport: アイテムを表示し、ナビゲーションを許可します。
  • filterable viewport: Viewport 機能に検索機能を追加します。

Go がインストールされている場合、これらの要素と Composition(合成)による Filterable Viewport を試す最速の方法は以下のコマンドを実行することです:

go run github.com/robinovitch61/viewport/examples/filterableviewport@latest

Go がない場合は Docker で実行できます:

docker run -ti golang:1.26-alpine \
  go run github.com/robinovitch61/viewport/examples/filterableviewport@latest

ユニコード対応

大半のユニコード文字はターミナルで 1 セルの幅を占用しますが、特殊なものは異なります。以下は

wcwidth
ライブラリを使って文字列のターミナル列幅をチェックするシェル関数です:

termwidth() {
  uv run --with wcwidth python -c \
  "import wcwidth; print(wcwidth.wcswidth('$1'))"
}

単純な文字列は期待通りのセル幅になります:

❯ termwidth 'a'
1
❯ termwidth '123'
3

しかし、ここはどうでしょうか:

❯ echo '\u2728'
✨
❯ termwidth '✨'
2

スパークルエモジ

(ユニコードコードポイント U+2728 SPARKLES)は 2 つのターミナルセル分の幅を占用します。ターミナルにおけるユニコードテキストでは、以下のような概念があります:

  • コードポイント: ユニコード標準に割り当てられた数字(例:U+2728)。
  • グラフイム: 人間の認識する単一文字(1 つ以上のコードポイント)、例:
  • バイトエンコーディング: コードポイントのバイト表現、UTF-8/16/32 に依存(例:
    は UTF-8 で 3 バイト
    0xE2 0x9C 0xA8
    )。
  • コードポイント幅: コードポイントが占用するターミナルセル数(0、1、または 2)、例:
    は 2。
  • グラフィムのターミナル幅は、その構成するコードポイントの合計で決定されます。

見た目だけで文字が何セルを占用するか推測するのは困難です:

❯ termwidth '全'
2
❯ termwidth '■'
1
❯ termwidth '﷽'
1

ユニコードでは単一幅の文字

é
も複数の方法で表現できます:単一の
é
コードポイントか、
e
と付随する結合アクセントコードポイント(例:U+0301)の組み合わせかのいずれかで。

  • é U+00E9 LATIN SMALL LETTER E WITH ACUTE
  • e U+0065 LATIN SMALL LETTER E + U+0301 COMBINING ACUTE ACCENT
❯ echo '\u00E9'
é
❯ termwidth '\u00E9'
1
❯ echo '\u0065\u0301'
é
❯ termwidth '\u0065\u0301'
1
❯ termwidth '\u0301'
0

Viewport でユニコードをサポートするためには、バイト列(Go では通常 UTF-8)からコードポイントへ、さらにグラフイムとその対応するターミナル幅へのマッピングを考慮する必要があります。

Item
インターフェースはこの実装を簡潔に扱います:

type Item interface {
    // Width はターミナルセルにおける総幅を返します
    Width() int

    // Take は startWidth から startWidth + takeWidth までの文字列を取得し、width はターミナルセル単位です
    Take(startWidth, takeWidth int) string
}

// NewItem は文字列から SingleItem を構築し、Item インターフェースを実装します
func NewItem(content string) SingleItem {
    // マップを構築:
    //   コードポイント -> バイトオフセット
    //   コードポイント -> 累積ターミナルセル幅
}

不変な文字列(例:Kubernetes ログ)に対して、到达するごとに一度

Item
オブジェクトを eagerly Instantiate(初期化)できます。これは内部にスパースなマップ(コードポイントからバイトオフセットとターミナルセル幅へ)を構築します。
MultiItem
も同じインターフェースを満たしますが、複数の個別のアイテムを超えて動作し、prefix の変更(例:行番号、タイムスタンプ、コンテナ名など)のために全体の
SingleItem
を再構築する必要なく効率的に動的 prefix 付けが可能です。多行コンテンツ(例:整形された JSON ログ)については、
MultiLineItem
が複数の改行を超えるアイテムをサポートします。
Item
アブストラクションは、 successive calls to
Take
でアイテムのベース内容を使い切るまでの巻き戻し、およびアイテムが展開されている時の効率的な左右パンをうまくサポートします。

検索とフィルタリング

私は完全一致検索

/
、正規表現検索
r
、大文字小文字区別なし検索
i
のための異なるキーボードショートカットを持つのが価値があると考えています。これはすべて「大文字小文字区別なしフラグ (
?i
) を自動で付与した単一の正規表現検索」として実装されています。これらの一般的な検索動作のそれぞれに個別の単一キーショートカットを割り当てることで、アプリは機敏に感じます。適用された検索はメモリ上のバッファに保存され、上下矢印キーでナビゲートできます。
Filterable Viewport
は、フィルタに基づいて現在表示されているセットからフィルタリングする全アイテムのシーケンスを維持します。
x
ショートカットは一致のみを表示するか、一致部分の前後文も含めて表示かを切り替えます。アイテムには複数の一致が存在し得、
n
/
N
で一致間をナビゲートして、フォーカスされた一致を画面に表示できます。

18,213 'Harry's across all 7 books

アイテム選択

いくつかのテキストビューでは、画面ごとの内容ページングで十分です。他のビューでは、表示可能なセットからアイテムを選択する必要があります。Viewport は这两种ケースの双方を扱い、選択を有効または無効にする設定を行います。例えば

kl
では、ログ上で Enter を押すとそのログの完全なページビュー(美しくフォーマットされたもの)へ移動でき、そこからスクロール、パン、検索、前後のログを一つずつ切り替えることができます。

アイテム選択をサポートするため、Viewport はオブジェクトタイプに対して汎用型化されています:

// New は汎用型 T の Objects を表示する新しい Viewport を作成します
func New[T Object](width, height int, opts ...Option[T]) (m *Model[T]) {
    ...
}

// GetSelectedItem は現在選択されているアイテムへのポインターを返します
func (m *Model[T]) GetSelectedItem() *T {
    if !m.selectionEnabled {
      return nil
    }
    return m.getSelectedItem()
}

呼び出し側は Enter キーイベントなどのキー入力を受け取り、フォーカスされた Viewport で

GetSelectedItem()
を呼び出して返されるオブジェクトに応答できます。

less
やその前に登場した
more
のように、
lore
もシンプルなターミナルページャーで、Viewport コンポーネントを頼りに全機能を実装しています。私が
kl
wander
といったアプリ内で「テキスト表示・ナビゲート」に合うミニターミナルページャーを構築している限り、その同じ機能をターミナル内のページングにも使えるでしょう!
lore
less
が持つ機能のサブセットのみをサポートしますが、私の日常活動に対してより直感的で有用な方式です。また、地から上へ理解することが価値があると感じています(バイトからターミナルビューへ)。さらに、私は実際に必要なターミナルページャーを理解して改善しながら続けています。

lore
のインストール手順はこちらから確認できます。私は定期的に
lore <some-file>
<command> | lore
を実行しています。また
~/.zshrc
export PAGER=lore
を設定しており、新しいプログラムが
less
を代わりに使ってくれるたびに嬉しく思います。このドメインは深いですね。
libghostty
などのパッケージの進展には興奮しています。これらは私が Go の Viewport で実装した多くの機能を Zig と C バインディングでカバーしています。コア Viewport 機能、
kl
、ターミナルページャー
lore
、そして今後の他の TUI を引き続き改善してまいります。

同じ日のほかのニュース

一覧に戻る →

2026/04/15 3:08

現在、サイバーセキュリティ分野において「証明作業」のような性質を帯びているようです。

## Japanese Translation: Anthropic の Mythos は、重要なソフトウェアメーカーがその能力に対してシステムを強化するまで非公開に保たれた高度な AI モデルであり、「低温くじ(low temperature lottery)」機構を通じてモデルが複雑な企業ネットワーク攻撃を実行できることを示している。この機構では、クリプトカレンシーのプルーフ・オブ・ワークのように、高額なトークン予算によってまれな成功が生じる。AI セキュリティ研究所(AISI)は、Mythos が 10 の試みのうち 3 でシミュレーションされた 32 ステップの企業ネットワーク攻撃を完了したことを確認したが、同様の条件下では Opus 4.6 と GPT-5.4 は失敗した。このタスクは「ラスト・ワンの」シミュレーションとして知られ、人間の完了には約 20 時間が必要と推定されている。特に注目すべきは、Mythos のパフォーマンスは、ランごとのトークン予算を 1 億トークンまで増加させた場合でも低下しなかったことだ。これは、1 つの試行あたり 12,500 ドル、完全な 10 回のラン基準テストスイート全体では 125,000 ドルのコストに対応している。この発見は、まれなセキュリティ監査から、市場価格で取引されるエクスプロイト価値に駆動される継続的なプロセスへの重要な移行を浮き彫りにしている。オープンソースソフトウェアは依然として重要であり、広く使用されているパッケージをクラッキングする方がワンオフの実装よりも攻撃者に高い投資対効果をもたらすためである。したがって、開発者は、専用ハードニングフェーズと常にレビューを行うことを含む新しい 3 フェージサイクルを採用することが推奨される。潜在的には Anthropic の新製品であるコードレビュー製品の活用も可能であり、その価格は 1 つのレビューあたり 15〜20 ドルである。企業は今や、資金が尽きるまで自律的なエクスプロイト特定を制限するトークン予算に対して大規模なリソースを割り当てるという現実と直面している。これとは対照的に、以前はまれで不整合だった慣行は存在しなかった。

2026/04/16 6:21

Excel 向けの ChatGPT

## Japanese Translation: Microsoft は、形式を整えたスプレッドシートや数式を用いた作成・更新を、ゼロから作業を開始する代わりに自然言語で行えるようにするための ChatGPT for Excel のベータ版を発売しました。このアドインは、ユーザーにデータの質問への明確な要約、タブ間での理解とデバッグ、パターン発見、そしてデータを実行可能な洞察への変換などを可能にし、かつ自らの行動を説明し、答えを特定のセルに関連付け、形式を整え、変更を行う前に許可を求めることができます。 このツールは ChatGPT Business、Enterprise、Edu、Teachers および K-12 ユーザー向けに世界中で提供されており、EU 外のプロとプラスユーザーにも利用可能です。インストールはホームからアドインを検索して「ChatGPT」を探し、Excel リボンに追加することで行うことができ、有効化には OpenAI アカウントを使用して ChatGPT Plus、Pro、Business、または Enterprise プランを持つ必要があります。 主要なユースケースとしては、アンケート分析、割引キャッシュフローモデル、ビジネス計画提案、財務諸表の数秒での分析などが挙げられます。将来のアップデートでは一般公開範囲の拡大、数理論理の改善、Slack、GitHub、Google Drive などの外部プラットフォームとのより深い連携が予定されています。この進化は、Excel 内での完全に対話的なデータ操作への重要なステップとなります。

2026/04/16 2:44

グーグルは私の望みに対し破約を果たし、今やICE(米国国土安全保障省移民・難民管理局)が私のデータを入手しました。

## 日本語翻訳: 2025 年 4 月、米国の法務・移民担当官(ICE)が、英国とトリニダード国籍を有し、親巴勒斯坦デモで保護された政治的言論を行った博士課程の候補者で元記者のアンドラ・トマス=ジョンソンに関するデータを求める行政命令状を発令しました。モモドゥ・タールのような友人らの事例とは異なり、当社は事前に通知され、利用者が要求に異議を唱える機会を得た上で処理が行われましたが、この命令状ではトマス=ジョンソンに警告なし、異議申立ての機会も提供せずに発令され、10 年間にわたる透明性の保証を実質的に破綻させました。同社は要求された利用者情報(IP アドレス、実在する住所、セッション時間など)を即時に提供しました。トマス=ジョンソンは既にナイアガラフォールズを経由してカナダへ渡っており、その時点で米国から離れていたにもかかわらずです。その後数週間後のスイス・ジュネーヴァにて、彼のデータが国土安全保障省に引き渡されたことが通知されました。エレクトロニックフロンティア財団(EFF)はこうした矛盾を指摘し、カリフォルニア州およびニューヨーク州の attorney general に提出した申し立てで、不正競争行為の調査を進めるよう Google に対して求めています。刑事告発を受けていないにもかかわらず、トマス=ジョンソンは今や旅行活動や報道活動に対する更なる監視のリスクに直面しており、企業責任、ユーザープライバシー、そしてテクノロジー企業が法的要求と侵入的な政府監視に対するユーザー権利の保護をどう均衡させるかという切迫した問いを提起しています。

ターミナル用のページャーを作成しました。 | そっか~ニュース