Skip to content

非インタラクティブ画像表示モード(--static フラグ)の追加#21

Merged
flexphere merged 6 commits intomainfrom
feat/static-mode
Mar 25, 2026
Merged

非インタラクティブ画像表示モード(--static フラグ)の追加#21
flexphere merged 6 commits intomainfrom
feat/static-mode

Conversation

@flexphere
Copy link
Copy Markdown
Owner

概要

画像をターミナルに1回表示して即終了する --static フラグを追加。パイプラインやスクリプトでの利用、さっと画像を確認したいときに便利。

変更内容

  • cmd/gaze/main.go: --static フラグの追加と runStatic() 関数の実装。Bubbletea TUI を起動せず、Kitty Graphics Protocol で画像を表示して終了する
  • internal/adapter/tui/cellsize.go: queryCellSizeQueryCellSize に export。QueryTerminalSize 関数を追加(cols/rows 取得用)
  • internal/adapter/tui/cellsize_windows.go: 上記の Windows スタブ
  • internal/adapter/tui/model.go, update.go: export 後の関数名に追従

テスト計画

  • 既存テスト全パス (make ci)
  • 手動確認: gaze --static <image> で画像が表示され、プロセスが即終了すること
  • 手動確認: gaze <image> で従来通りインタラクティブモードが起動すること

備考

  • 終了時に画像の Clear は行わない(ターミナルに画像が残る仕様)
  • QueryTerminalSize は今後他の機能(tmux モード等)でも再利用可能

🤖 Generated with Claude Code

flexphere and others added 2 commits March 25, 2026 14:11
Export queryCellSize as QueryCellSize so it can be called from outside
the tui package. Add QueryTerminalSize to expose terminal column/row
counts via TIOCGWINSZ for use by the new static display mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a --static flag that displays the image to the terminal and exits
immediately without entering the interactive TUI. Useful for scripts,
pipelines, and quick image previews.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 25, 2026 05:12
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

ターミナルに画像を1回表示して即終了する非インタラクティブモード(--static)を追加し、あわせて TUI 側の端末サイズ取得ユーティリティを再利用可能にする PR です。

Changes:

  • gaze --static <image> で Bubbletea を起動せずに Kitty Graphics Protocol で表示して終了する経路を追加
  • 端末セルサイズ取得を QueryCellSize として export し、端末の cols/rows を返す QueryTerminalSize を追加
  • 既存 TUI 実装を export 後の関数名に追従

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
cmd/gaze/main.go --static フラグ追加と runStatic() 実装(画像ロード→端末サイズ取得→表示→終了)
internal/adapter/tui/cellsize.go QueryCellSize export、QueryTerminalSize 追加(非 Windows)
internal/adapter/tui/cellsize_windows.go Windows 向けスタブ(QueryCellSize/QueryTerminalSize
internal/adapter/tui/model.go QueryCellSize に呼び出し変更
internal/adapter/tui/update.go QueryCellSize に呼び出し変更

cmd/gaze/main.go Outdated
// Query terminal dimensions
cols, rows := tui.QueryTerminalSize()
if cols <= 0 || rows <= 0 {
return fmt.Errorf("unable to determine terminal size")
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QueryTerminalSize() が失敗した場合のエラーメッセージが情報不足です。現状は unable to determine terminal size 固定で、--static が TTY を必要とすることや Windows では常に (0,0) になることが分かりません。失敗理由(TTY でない/未対応 OS など)が伝わる文言にするか、QueryTerminalSize 側で error を返して呼び出し元で %w できる形にすると原因特定が容易になります。

Suggested change
return fmt.Errorf("unable to determine terminal size")
return fmt.Errorf("unable to determine terminal size: --static mode requires a TTY; on some platforms (e.g., Windows) terminal size may be reported as 0x0")

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e825ed7 で修正 — TTY が必要である旨をエラーメッセージに追加しました

cmd/gaze/main.go Outdated
Comment on lines +70 to +77
vp := &domain.Viewport{
TermWidth: cols,
TermHeight: rows,
ImgWidth: img.Width,
ImgHeight: img.Height,
CellAspectRatio: cellH / cellW,
ZoomLevel: 1.0,
}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

domain.Viewport を struct literal で生成すると、NewViewport が設定する minZoom/maxZoom/zoomStep/panStep が 0 のままになります。現状の --static では主に FitToWindow() のみなので顕在化しにくいですが、将来的に static 表示でもズーム/パン等を追加した場合に Zoom() が常に 0 にクランプされるなど破綻し得ます。domain.NewViewport(...) を使って生成し、必要なフィールドは setter(SetTerminalSize/SetImageSize/SetCellAspectRatio)で埋める形に寄せるのが安全です。

Suggested change
vp := &domain.Viewport{
TermWidth: cols,
TermHeight: rows,
ImgWidth: img.Width,
ImgHeight: img.Height,
CellAspectRatio: cellH / cellW,
ZoomLevel: 1.0,
}
vp := domain.NewViewport()
vp.SetTerminalSize(cols, rows)
vp.SetImageSize(img.Width, img.Height)
vp.SetCellAspectRatio(cellH / cellW)

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e825ed7 で修正 — NewViewport(DefaultConfig().Viewport) + setter に変更しました

return fmt.Errorf("displaying image: %w", err)
}

fmt.Print(output)
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kittyRenderer.Display(vp) の出力は先頭に \x1b[H(カーソルを左上へ移動)を含むため、--static で alt-screen を使わずに即終了すると、シェルのプロンプトが左上に出て画像を上書きしやすいです。static モードでは表示前後でカーソル位置を save/restore する、表示後に最終行へカーソル移動して改行を出す、または static 用にカーソル移動を含まない表示シーケンスを生成する等の対応を検討してください。

Suggested change
fmt.Print(output)
// 画像表示後にカーソルをターミナル最終行の先頭へ移動し、改行を出力することで
// シェルのプロンプトが画像の左上を上書きしにくくする。
cursorToLastLine := fmt.Sprintf("\x1b[%d;1H\n", rows)
fmt.Print(output + cursorToLastLine)

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e825ed7 で修正 — \x1b[H を除去してインライン表示にし、表示後にカーソルを画像下端に移動するようにしました

ws, err := unix.IoctlGetWinsize(int(os.Stdout.Fd()), unix.TIOCGWINSZ)
if err != nil {
return 0, 0
}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QueryTerminalSize のコメントは「判定できない場合は (0, 0)」となっていますが、実装は ws.Col / ws.Row の片方だけが 0 の場合に (0, N) や (N, 0) を返し得ます(QueryCellSize では 0 チェックあり)。呼び出し側が片方だけを見落とすと不正なサイズを使ってしまうので、ws.Col == 0 || ws.Row == 0 の場合は (0,0) に正規化するか、コメントを実装に合わせてください。

Suggested change
}
}
if ws.Col == 0 || ws.Row == 0 {
return 0, 0
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2cf8694 で修正 — QueryCellSize と同様の ws.Col == 0 || ws.Row == 0 ガードを追加しました

flexphere and others added 3 commits March 25, 2026 14:21
Calculate display dimensions from the image's native pixel size and
cell pixel size so the image is shown at 1:1 resolution. When the
native width exceeds the terminal width, scale down proportionally
to fit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Normalize to (0, 0) when either ws.Col or ws.Row is zero, matching
the existing guard in QueryCellSize.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use NewViewport with DefaultConfig to inherit zoom/pan limits
- Improve error message to indicate TTY requirement
- Strip cursor-to-home escape so image displays inline at prompt
- Move cursor below image after display for clean shell prompt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 25, 2026 05:37
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

cmd/gaze/main.go Outdated
Comment on lines +65 to +68
cols, _ := tui.QueryTerminalSize()
if cols <= 0 {
return fmt.Errorf("determining terminal size: --static requires a TTY")
}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Windows では internal/adapter/tui/cellsize_windows.go の QueryTerminalSize() が常に (0,0) を返すため、cmd/gaze/main.go の --static は TTY 上でも必ず失敗します。その際のエラーメッセージが「TTY が必要」に固定されていると原因(未実装/未対応 OS)が誤解されやすいので、Windows では未対応である旨を明示するか、QueryTerminalSize を (cols, rows int, err error) にして未対応を error で返す形にすると切り分けしやすいです。

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gaze は Kitty Graphics Protocol 前提のため現時点で Windows は対象外です。将来的に Windows 対応する際に error 返しへの変更を検討します

Comment on lines +80 to +84
cfg := domain.DefaultConfig()
vp := domain.NewViewport(cfg.Viewport)
vp.SetCellAspectRatio(cellH / cellW)
vp.SetTerminalSize(displayCols, nativeRows)
vp.SetImageSize(img.Width, img.Height)
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vp.SetTerminalSize(displayCols, nativeRows) に nativeRows(画像サイズ由来)をそのまま渡すと、縦長画像で nativeRows が非常に大きくなり、kitty への r= 指定やカーソル移動量が実際のターミナル高さと大きく乖離します。QueryTerminalSize() で rows も取れているので、高さ方向も min(nativeRows, rows) のように上限を設けて実ターミナルサイズに合わせるのが安全です。

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0db8f37 で修正 — displayRows = min(nativeRows, rows) でターミナル高さを上限にしました

Comment on lines +14 to +16
// QueryTerminalSize returns (0, 0) on Windows as TIOCGWINSZ is not available.
func QueryTerminalSize() (cols, rows int) {
return 0, 0
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QueryTerminalSize() が Windows で常に (0,0) を返す実装だと、--static は実質的に Windows では利用不能になります。未対応方針であれば呼び出し側が判別できるように error を返す設計にするか、対応するなら golang.org/x/term.GetSize 等で cols/rows を取得する実装を検討してください。

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gaze は Kitty Graphics Protocol 前提のため現時点で Windows は対象外です

Limit nativeRows to the actual terminal height so very tall images
are scaled to fit on screen instead of being clipped by the terminal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Comment on lines +36 to +44
func QueryTerminalSize() (cols, rows int) {
ws, err := unix.IoctlGetWinsize(int(os.Stdout.Fd()), unix.TIOCGWINSZ)
if err != nil {
return 0, 0
}
if ws.Col == 0 || ws.Row == 0 {
return 0, 0
}
return int(ws.Col), int(ws.Row)
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QueryTerminalSizeos.Stdout の FD を前提にしているため、stdout がリダイレクト/パイプの場合にサイズ取得が必ず失敗します(ただし --static の用途としては gaze --static img | cat のようなパイプライン表示も考えられ、その場合でも制御 TTY からサイズを取れた方が扱いやすいです)。os.Stdin.Fd()/dev/tty を開いて ioctl する、または golang.org/x/term.GetSize を使って「実際に表示される TTY」から cols/rows を取得する実装にすると意図に合います。

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

妥当な改善点です。ただし --static で stdout をパイプする場合はエスケープシーケンス自体が無意味になるため、現時点では実用上の問題になりません。将来的に /dev/tty からの取得に変更することを検討します

Comment on lines +106 to +110
imgAspect := float64(img.Width) / float64(img.Height)
termAspect := float64(displayCols) / (float64(displayRows) * cellAspect)
dispRows := displayRows
if imgAspect > termAspect {
dispRows = int(math.Round(float64(displayCols) / imgAspect / cellAspect))
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dispRows の算出ロジックが KittyRenderer.Display() 内の表示サイズ計算(aspect ratio ベース)と重複しています。将来 Display() 側の計算式や丸めが変わると、--static のカーソル移動量だけがズレてプロンプト位置が崩れる可能性があるため、表示に使う displayRows を単一の関数(例: renderer 側で「表示 cols/rows を返す」API)から取得する形に寄せるのが安全です。

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

重複は認識しています。表示サイズを返す API を追加すると RendererPort インターフェースの変更が必要になるため、この PR のスコープ外とし将来のリファクタリング候補とします

@flexphere flexphere marked this pull request as ready for review March 25, 2026 05:57
@flexphere flexphere merged commit ec64ceb into main Mar 25, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants