Skip to content

Tab 補完時の文字重複問題(tmux/Zellij + starship) #43

@HagaSpa

Description

@HagaSpa

tmux/Zellij + starship 環境での Tab 補完時の文字重複問題

調査日: 2025-10-23
環境: macOS (Darwin 24.6.0), Ghostty, zsh 5.9, tmux 3.5a, starship

問題の概要

tmux または Zellij + starship プロンプト環境で Tab 補完を使用すると、入力した文字が残って補完結果と重複表示される問題が発生。

症状

# 操作
gi[Tab][Tab]

# 期待する表示
git

# 実際の表示
gigit  # 入力した "gi" が残って重複

発生環境

要素 状態
Ghostty 直接起動 + starship ✅ 問題なし
Ghostty + シンプルプロンプト + tmux ✅ 問題なし
Ghostty + starship + tmux ❌ 重複発生
Ghostty + starship + Zellij ❌ 重複発生

根本原因

starship プロンプトがターミナルマルチプレクサ(tmux/Zellij)環境で zsh にプロンプトの幅を誤認識させている

詳細な仕組み

  1. starship が生成するプロンプトの構造

    starship が生成するプロンプトには ANSI エスケープシーケンス(色やスタイル)が含まれる
    例: \033[1;32m~/workspaces/dotfiles\033[0m
    
    これらのエスケープシーケンスは画面上では幅を持たない(表示されない制御文字)
    
  2. 幅の誤計算

    実際のプロンプト幅: 30文字(見た目)
    zsh が認識する幅: 40文字(エスケープシーケンスを含めて計算)
    
    誤差: 10文字
    
  3. 補完時の画面描画で問題発生

    zsh は "gi" を消去して "git" を書き込もうとするが、
    消去位置が誤差分ずれているため、"gi" が残る
    
    結果: 画面には "gigit" と表示される
    
  4. マルチプレクサ環境でのみ発生する理由

    Ghostty 直接:
    starship → zsh → Ghostty
    (幅計算が正確)
    
    マルチプレクサ経由:
    starship → zsh → tmux/Zellij(仮想端末) → Ghostty
                        ↑
                      ここで幅計算が狂う
    

調査プロセス

1. 初期仮説と検証

仮説: zsh-autosuggestions が原因
結果: ❌ プラグインを無効化しても問題は継続

仮説: カスタム補完関数(_workspace_complete)が原因
結果: ❌ 標準コマンドでも同じ問題が発生

2. tmux 設定の調整

試した変更:

  • default-terminaltmux-256color に変更
  • terminal-overrides の調整

結果: ❌ 効果なし

3. zsh 補完設定の調整

試した変更:

zstyle ':completion:*' list-prompt ''
zstyle ':completion:*' select-prompt ''
setopt always_last_prompt

結果: ❌ 効果なし

4. 原因の特定

シンプルなプロンプトでテスト:

export PS1='%F{green}%~%f $ '

結果: ✅ 問題が完全に解消

starship プロンプトが原因と確定

5. Zellij での検証

tmux の代替として Zellij をテスト

結果: ❌ 同じ問題が発生

tmux 固有の問題ではなく、starship + マルチプレクサの組み合わせが原因と確定

試した解決策

1. Tab 押下時に行をクリア(部分的に成功)

if [[ -n "$TMUX" ]]; then
  _fix_completion_display() {
    # Clear the line using terminal escape sequences
    printf '\r\033[2K'
    # Redraw the prompt
    zle reset-prompt
    # Perform completion
    zle expand-or-complete
    # After completion, ensure clean redisplay
    zle redisplay
  }
  zle -N fix-completion-display _fix_completion_display
  bindkey '^I' fix-completion-display
fi

効果:

  • ✅ 1回目の Tab + 候補選択: 正しく動作(gigit
  • ❌ 2回目の Tab(補完候補リスト表示時): gigit と重複

説明:

  • printf '\r\033[2K': カーソルを行頭に移動(\r)し、行全体を消去(\033[2K
  • zle reset-prompt: プロンプトを再描画
  • zle expand-or-complete: 補完実行
  • zle redisplay: 画面全体を再描画

制限:

  • 2回目の Tab では、zsh が内部で候補リストを描画する際、再度幅計算の誤差が発生
  • 候補リスト描画のタイミングでは外部から介入できない

2. line-pre-redraw フックで継続的にクリア(失敗)

add-zle-hook-widget line-pre-redraw _tmux_completion_pre_redraw

結果: ❌ 画面が再描画されるたびに行をクリアしてしまい、候補選択後も継続的にクリアされてすべてが消える

3. プロンプト変更(根本的解決)

Powerlevel10k への変更を検討したが、starship を使い続けることを優先

最終的な対処法

現状維持 + 部分的な修正

.zshrc に以下を追加(tmux/Zellij 環境でのみ有効):

# Fix completion display issues in tmux
# Tab 1回 + 候補選択で正しく動作するようにする
if [[ -n "$TMUX" ]]; then
  _fix_completion_display() {
    # Clear the line using terminal escape sequences
    printf '\r\033[2K'
    # Redraw the prompt
    zle reset-prompt
    # Perform completion
    zle expand-or-complete
    # After completion, ensure clean redisplay
    zle redisplay
  }
  zle -N fix-completion-display _fix_completion_display
  bindkey '^I' fix-completion-display
fi

運用方針

  • ✅ 最も一般的な操作(1回の Tab で候補選択)は完璧に動作
  • ✅ セッション永続化を維持(tmux/Zellij 必須)
  • ✅ starship の美しいプロンプトを維持
  • ⚠️ 2回目の Tab(補完候補リスト表示時)の gigit 重複は許容

他のユーザーの対処法

Stack Overflow などで同様の報告が多数確認された:

一般的な原因

  1. プロンプトのエスケープシーケンス問題(最多)

    • 色やフォーマット用のエスケープシーケンスが %{%} で囲まれていない
    • zsh がプロンプトの幅を誤計算
  2. ロケール設定の不一致

    • ターミナルのエンコーディングと shell のロケール設定が合っていない
  3. Unicode 幅の計算問題

    • 絵文字や日本語などの全角文字の幅を誤認識

一般的な対処法

  1. 手動で再描画: Ctrl+L で画面をクリア(一時的対処)
  2. プロンプト変更: Powerlevel10k など互換性の高いプロンプトに変更
  3. マルチプレクサを使わない: セッション永続化を諦める(非推奨)

完全な解決策(未採用)

選択肢 1: Powerlevel10k への変更

git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ~/powerlevel10k
source ~/powerlevel10k/powerlevel10k.zsh-theme

メリット:

  • ✅ 問題が完全に解決
  • ✅ starship 並みに機能豊富
  • ✅ tmux/Zellij で動作実績あり

デメリット:

  • ⚠️ starship から乗り換える必要がある

選択肢 2: シンプルなプロンプト

export PS1='%F{green}%~%f %F{blue}$(git branch 2>/dev/null | grep "^\*" | cut -d" " -f2)%f $ '

メリット:

  • ✅ 問題が完全に解決
  • ✅ 軽量・高速

デメリット:

  • ⚠️ starship のような豊富な機能がない

技術的考察

なぜ完全な解決が難しいか

  1. starship 側の問題

    • starship が生成するプロンプト文字列の幅計算に問題がある可能性
    • ただし、多くの環境では正しく動作している
    • マルチプレクサとの組み合わせ特有の問題
  2. zsh の補完システム

    • 補完候補リスト表示は zsh の内部処理
    • 外部から完全に制御できない
    • フックポイントが限定的
  3. マルチプレクサの画面管理

    • tmux/Zellij の画面バッファ管理を外部から制御できない
    • エスケープシーケンスの解釈を変更できない

3つのシステムの相互作用

starship (プロンプト生成)
    ↓ ANSI エスケープシーケンスを含む文字列
zsh (画面描画計算)
    ↓ 幅計算が必要
tmux/Zellij (仮想端末・画面バッファ管理)
    ↓ エスケープシーケンスの再解釈
Ghostty (実際の表示)

この処理の連鎖で、幅計算の誤差が発生する。

参考情報

関連する Stack Overflow の質問

使用した ANSI エスケープシーケンス

\r          # Carriage Return(カーソルを行頭に移動)
\033[2K     # 行全体を消去
\033[0K     # カーソル位置から行末まで消去
\033[1K     # 行頭からカーソル位置まで消去
\033[2J     # 画面全体をクリア

zle (Zsh Line Editor) コマンド

zle reset-prompt          # プロンプトを再描画
zle expand-or-complete    # 補完実行
zle redisplay            # 画面全体を再描画
zle -N <name> <function> # カスタムウィジェットを登録
bindkey <key> <widget>   # キーバインドを設定

まとめ

  • 根本原因: starship プロンプトがマルチプレクサ環境で zsh に幅を誤認識させる
  • 完全な解決: Powerlevel10k などへのプロンプト変更が必要
  • 採用した対処: 1回目の Tab 補完を修正、2回目は許容
  • 運用方針: 実用上最も重要な操作は正しく動作するため、現状維持

この問題は starship、zsh、tmux/Zellij の3つのシステムが絡む複雑な問題であり、外部から完全に制御することは困難である。

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdocumentationImprovements or additions to documentation

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions