Merge reviewed bug fixes and first contributions (#337)
Integrate reviewed PRs #299, #321, #331, #314 and #303 after the combined native matrix passed.
This commit is contained in:
24 files changed
+1125
-65
No files matched your search
@@ -1,8 +1,10 @@
|
||||
# 这些现有夹具共享真实设置文件;进程内 Mutex 在 nextest 多进程模型下不再互斥。
|
||||
# 只还原该夹具组的串行合同,其余测试继续使用 runner 的并行度。
|
||||
# 入组判据只有一个:测试会让产品把设置写回真实设置路径。Ctrl+滚轮夹具同样如此
|
||||
# (`gpui_shell::settings_fixture` 里那把进程内锁在 nextest 下不跨进程互斥)。
|
||||
[test-groups]
|
||||
theme-studio = { max-threads = 1 }
|
||||
|
||||
[[profile.default.overrides]]
|
||||
filter = 'test(gpui_shell::settings_pane::theme_studio_tests::)'
|
||||
filter = 'test(gpui_shell::settings_pane::theme_studio_tests::) or test(ctrl_wheel_font_zoom_toggle_gates_zoom_and_terminal_scroll) or test(ctrl_wheel_font_zoom_setting_is_searchable_and_has_a_visible_switch)'
|
||||
test-group = 'theme-studio'
|
||||
@@ -117,7 +117,9 @@
|
||||
"loading": "Enumerating system fonts...",
|
||||
"no_matches": "No matching fonts",
|
||||
"expand": "Show font list",
|
||||
"collapse": "Hide font list"
|
||||
"collapse": "Hide font list",
|
||||
"ctrl_wheel_zoom": "Ctrl+wheel font zoom",
|
||||
"ctrl_wheel_zoom_description": "Hold Ctrl and scroll over the terminal to change its font size. Turned off, the Ctrl+wheel gesture is ignored completely: it neither resizes text nor scrolls the terminal. Ctrl+=, Ctrl+- and Ctrl+0 still work."
|
||||
},
|
||||
"keymap": {
|
||||
"conflict": "{combo} is assigned to both \"{first}\" and \"{second}\"; only the earlier \"{first}\" action will run.",
|
||||
|
||||
+819
-6
@@ -1,11 +1,24 @@
|
||||
{
|
||||
"notification": {
|
||||
"bell": "ターミナルのベル",
|
||||
"turn_failed_reason": "リクエストに失敗しました: {error}",
|
||||
"turn_failed": "リクエストに失敗しました。詳細はターミナルを確認してください。",
|
||||
"turn_incomplete": "応答が完了していません。続行する前にターミナルを確認してください。",
|
||||
"turn_ended": "ターンが終了しました。結果はターミナルで確認してください。"
|
||||
},
|
||||
"language": {
|
||||
"system": "システムに従う",
|
||||
"self_name": "日本語",
|
||||
"system": "システムに従う",
|
||||
"zh_cn": "简体中文",
|
||||
"en_us": "English"
|
||||
},
|
||||
"settings": {
|
||||
"scrolling": {
|
||||
"history": "スクロールバックの行数",
|
||||
"history_description": "ターミナルごとに保持する履歴の行数です。新しいターミナルにのみ適用されます。",
|
||||
"speed": "スクロール速度",
|
||||
"speed_description": "マウスホイールの倍率です。すぐに反映され、トラックパッドのスクロールには影響しません。"
|
||||
},
|
||||
"sidebar": {
|
||||
"application": "アプリケーション",
|
||||
"appearance": "外観",
|
||||
@@ -16,7 +29,23 @@
|
||||
"interaction": "操作",
|
||||
"keymap": "キー割り当て",
|
||||
"advanced": "詳細設定",
|
||||
"backup": "バックアップ"
|
||||
"backup": "バックアップ",
|
||||
"agents": "エージェント"
|
||||
},
|
||||
"notifications": {
|
||||
"duration": "通知の表示時間",
|
||||
"duration_description": "アプリ内通知を閉じるタイミングを選択するか、手動で閉じるまで表示したままにします。",
|
||||
"duration_details": "新しく表示または更新されたアプリ内カードに適用されます。既定値: 短いトーストは 5 秒、バナーは 90 秒、更新通知は閉じるまで表示。常時表示モードを含め、最新の 3 件のカードのみ保持されます。カードを閉じたり置き換えたりしても、確認への応答やタスク状態の変更は行われません。システム通知には影響しません。",
|
||||
"duration_default": "既定値を使用",
|
||||
"duration_five": "5 秒",
|
||||
"duration_ten": "10 秒",
|
||||
"duration_thirty": "30 秒",
|
||||
"duration_ninety": "90 秒",
|
||||
"duration_persistent": "閉じるまで表示",
|
||||
"ai_messages": "AI メッセージのトースト",
|
||||
"ai_messages_description": "右下に AI のトーストを表示します。システム通知には影響しません。",
|
||||
"ai_messages_details": "オフにすると、表示中のアプリ内 AI トーストを閉じ、新しい完了・確認トーストを表示しなくなります。Windows 通知を含むシステム通知とタブの状態表示は変わりません。ターミナルの出力やターミナル内の確認要求は引き続き利用できます。",
|
||||
"save_failed": "通知設定を保存できませんでした: {error}"
|
||||
},
|
||||
"network": {
|
||||
"title": "ネットワークプロキシ",
|
||||
@@ -29,6 +58,14 @@
|
||||
"address": {
|
||||
"label": "プロキシアドレス"
|
||||
},
|
||||
"status": {
|
||||
"idle": "現在の設定でネットワークにアクセスできるかテストします",
|
||||
"running": "現在の設定でネットワークアクセスをテスト中…",
|
||||
"success": "{route} 経由でネットワークに接続しました({elapsed_ms} ms)",
|
||||
"failed": "テストに失敗しました: {error}",
|
||||
"unexpected_end": "ネットワークテストが予期せず終了しました。もう一度お試しください",
|
||||
"start_failed": "ネットワークテストを開始できませんでした: {error}"
|
||||
},
|
||||
"action": {
|
||||
"test": "ネットワークをテスト",
|
||||
"testing": "テスト中…"
|
||||
@@ -36,12 +73,139 @@
|
||||
"route": {
|
||||
"direct": "直接接続",
|
||||
"direct_address": "直接アドレス",
|
||||
"custom_command": "カスタムコマンド"
|
||||
"custom_command": "カスタムコマンド",
|
||||
"proxy_server": "プロキシ {server}",
|
||||
"ssh_jump": "SSH {target}"
|
||||
},
|
||||
"failure": {
|
||||
"load_settings": "プロキシ設定を読み込めませんでした: {error}",
|
||||
"invalid_settings": "プロキシ設定が無効です: {error}",
|
||||
"timeout": "ネットワークテストが {seconds} 秒でタイムアウトしました",
|
||||
"send_request": "テストリクエストを送信できませんでした: {error}",
|
||||
"read_response": "テストの応答を読み取れませんでした: {error}",
|
||||
"invalid_http_status_line": "テストサイトが有効な HTTP ステータス行を返しませんでした: {line}",
|
||||
"http_status": "テストサイトが HTTP {status} を返しました",
|
||||
"proxy_server": "プロキシ {server} 経由でネットワークに接続できませんでした: {error}",
|
||||
"custom_command": "カスタムプロキシコマンドが失敗しました: {error}",
|
||||
"jump_task": "SSH 踏み台ホストを準備できませんでした: {error}",
|
||||
"jump_resolve": "SSH 踏み台ホスト {target} を解決できませんでした: {error}",
|
||||
"jump_connect": "SSH 踏み台ホスト {target} に接続できませんでした: {error}",
|
||||
"jump_channel": "SSH 踏み台ホスト {target} 経由で外部チャネルを開けませんでした: {error}",
|
||||
"direct": "テストサイトに直接接続できませんでした: {error}"
|
||||
}
|
||||
},
|
||||
"appearance": {
|
||||
"background_color_description": "テーマで指定されたターミナルの背景色を上書きします。OSC 11 でアプリケーションに通知される色も変わります。TUI はこの値でライト/ダークモードを判断します。",
|
||||
"background_cover_title": "背景画像をウィンドウコントロールの下まで広げますか?",
|
||||
"background_cover_description": "画像がタイトルバー、ウィンドウボタン、タブ、SSH サイドバーの下まで広がります。コントラストの低い画像ではコントロールが見えにくくなる場合があります。インターフェイスには最低限の不透明度が保たれます。"
|
||||
},
|
||||
"font": {
|
||||
"ligatures": "合字",
|
||||
"ligatures_description": "フォントが対応している場合、-> や != などの文字を合字で表示します。開いているターミナルにすぐ適用されます。",
|
||||
"ligatures_on": "オン",
|
||||
"ligatures_off": "オフ",
|
||||
"ligatures_theme": "テーマに従う",
|
||||
"ui_family": "UI フォント",
|
||||
"ui_family_description": "インストール済みのフォント名を入力します。空欄の場合は既定のフォントを使用します。Enter キーを押すか入力欄から離れると適用されます。",
|
||||
"ui_size": "UI フォントサイズ",
|
||||
"ui_size_description": "ターミナルの文字とは別に、UI の文字とコンポーネントのサイズを変更します。",
|
||||
"english": "英語ターミナルフォント",
|
||||
"chinese": "中国語ターミナルフォント",
|
||||
"chinese_description": "中国語などの全角文字に使用します。フォント名を入力し、複数ある場合はカンマで区切ります。空欄の場合は Maple Mono NF CN を使用します。Enter キーを押すか入力欄から離れると適用されます。",
|
||||
"fallback_description": "最初のフォントが優先されます。不足しているグリフは残りのフォントで順に補われ、最終的には内蔵の Maple が使用されます。",
|
||||
"group_description": "最初のフォントがメインで、残りはフォールバックとして順に使用されます。",
|
||||
"loading": "システムフォントを読み込み中…",
|
||||
"no_matches": "一致するフォントがありません"
|
||||
"no_matches": "一致するフォントがありません",
|
||||
"expand": "フォント一覧を表示",
|
||||
"collapse": "フォント一覧を隠す"
|
||||
},
|
||||
"keymap": {
|
||||
"conflict": "{combo} が「{first}」と「{second}」の両方に割り当てられています。先にある「{first}」のみ実行されます。",
|
||||
"help": "ショートカットをクリックして新しい組み合わせを押します。Backspace でクリア、Esc でキャンセルします。行のボタンでクリアまたは既定値に戻せます。",
|
||||
"clear": "ショートカットをクリア(ターミナルに渡す)",
|
||||
"restore": "このアクションの既定のショートカットに戻す"
|
||||
},
|
||||
"input": {
|
||||
"backup_secret": "資格情報はシステムの資格情報マネージャーにのみ保存されます"
|
||||
},
|
||||
"ssh": {
|
||||
"default_keys": "指定しない場合は、IdentityFile と既定の id_* 秘密鍵を使用します。",
|
||||
"address_hint": "user@host に対応しています。ssh://host:2222 を貼り付けることもできます",
|
||||
"auth_auto_description": "利用可能な秘密鍵を順に試し、失敗した場合はパスワードを求めます。ほとんどの場合はこれで十分です。",
|
||||
"auth_interactive_description": "資格情報を保存せず、接続時にターミナルで入力を求めます(2 段階認証にも対応)。",
|
||||
"save_password": "Windows 資格情報マネージャーに保存",
|
||||
"hide_config_host": "非表示(config のホストは削除できません)",
|
||||
"empty_hint": "「+ ホストを追加」を使うか、~/.ssh/config でエイリアスを管理します",
|
||||
"shared_data_hint": "旧シェルと同じデータを使用します。config のエイリアスは削除すると非表示になり、復元できます。"
|
||||
},
|
||||
"quick_terminal": {
|
||||
"mode": "クイックターミナルの動作",
|
||||
"description": "専用ウィンドウはサイズを記憶します。既存のウィンドウを表示してもタブは作成されません。",
|
||||
"dedicated": "専用ウィンドウ",
|
||||
"existing": "既存のウィンドウを表示"
|
||||
},
|
||||
"search_empty": "一致する設定がありません",
|
||||
"mouse": {
|
||||
"focus_follows_mouse": "マウスにフォーカスを追従",
|
||||
"focus_follows_mouse_description": "ポインターを重ねたターミナルペインにフォーカスします。既定ではオフです。"
|
||||
},
|
||||
"panes": {
|
||||
"dim_inactive": "非アクティブなペインを暗くする",
|
||||
"dim_inactive_description": "ほかの分割ペインを暗くして、アクティブなペインを見分けやすくします。"
|
||||
},
|
||||
"save_failed": "設定を保存できませんでした: {error}",
|
||||
"reset_title": "既定の設定に戻しますか?",
|
||||
"reset_description": "設定とキーボードショートカットをリセットします。SSH ホスト、資格情報、履歴は保持され、現在の設定はバックアップされます。続行しますか?",
|
||||
"startup": {
|
||||
"launch_at_login": "ログイン時に起動",
|
||||
"launch_at_login_help": "Windows へのサインイン時に Pebrel を起動します。インストーラーと同じスタートアップショートカットを使用します。",
|
||||
"silent_start": "バックグラウンドで起動",
|
||||
"silent_start_help": "ウィンドウを表示せずにバックグラウンドで起動します。システムトレイが有効になり、アイコンをクリックすると再び開きます。トレイが有効な間に適用されます(ディレクトリを明示的に開く場合を除く)。",
|
||||
"save_failed": "起動設定を変更できませんでした: {error}"
|
||||
},
|
||||
"agents": {
|
||||
"title": "コーディングエージェント",
|
||||
"description": "エージェントの動作状況、完了、承認リクエストを Pebrel に表示します。Claude Code と Codex は既定で有効です。",
|
||||
"refresh": "更新",
|
||||
"platform_unsupported": "フックの自動インストールは現在 Windows でのみ利用できます。",
|
||||
"checking": "インストール済みのエージェントとフックを確認中…",
|
||||
"applying": "適用中…",
|
||||
"installed": "フックはインストール済み",
|
||||
"off": "フックは未インストール",
|
||||
"not_detected": "未インストール",
|
||||
"config_detected": "エージェントの構成を検出しました",
|
||||
"unsupported": "フック連携はまだ利用できません",
|
||||
"unavailable": "利用不可",
|
||||
"needs_attention": "要確認",
|
||||
"restart": "変更はエージェントの再起動後に反映されます。ここでは、このコンピューターにインストールされたエージェントを管理します。",
|
||||
"enabled": "フックをインストールしました。反映するにはエージェントを再起動してください。",
|
||||
"disabled": "フックを削除しました。反映するにはエージェントを再起動してください。",
|
||||
"failed": "フックを更新できませんでした: {error}",
|
||||
"helper_missing": "フックのヘルパーが見つかりません。フックを有効にするには Pebrel のインストールを修復してください。",
|
||||
"needs_repair": "フックはインストール済み · 修復が必要",
|
||||
"initialize": "このエージェントを一度実行して構成を作成してから、更新してください。",
|
||||
"config_path": "構成: {path}"
|
||||
}
|
||||
},
|
||||
"provider": {
|
||||
"test": {
|
||||
"success": "接続に成功しました(HTTP {status})",
|
||||
"invalid_endpoint": "エンドポイントは http:// または https:// で始まる必要があります",
|
||||
"missing_model": "先に既定のモデルを入力してください",
|
||||
"missing_api_key": "先に API キーを保存してください",
|
||||
"credential_read_failed": "資格情報マネージャーから API キーを読み取れませんでした",
|
||||
"invalid_credential_encoding": "資格情報マネージャー内の API キーのエンコードが無効です",
|
||||
"timeout": "接続がタイムアウトしました",
|
||||
"host_not_found": "サーバーが見つかりません",
|
||||
"connection_failed": "サーバーに接続できませんでした",
|
||||
"io_error": "ネットワークエラー: {kind}",
|
||||
"tls_failed": "TLS ハンドシェイクに失敗しました",
|
||||
"request_failed": "リクエストに失敗しました",
|
||||
"auth_failed": "認証に失敗しました(HTTP {status})。API キーを確認してください",
|
||||
"endpoint_not_found": "エンドポイントが見つかりません(HTTP {status})。API エンドポイントを確認してください",
|
||||
"rate_limited": "レート制限中です(HTTP {status})。接続はできますが、現在クォータを確認できません",
|
||||
"http_status": "サービスが HTTP {status} を返しました",
|
||||
"start_failed": "プロバイダーのテストを開始できませんでした: {error}"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -107,7 +271,6 @@
|
||||
"new_window": "新しいウィンドウ",
|
||||
"close_tab": "タブを閉じる",
|
||||
"duplicate_tab": "タブを複製",
|
||||
"copy_working_directory": "作業ディレクトリをコピー",
|
||||
"terminal": "ターミナル",
|
||||
"connect": "接続",
|
||||
"disconnect": "切断",
|
||||
@@ -175,9 +338,607 @@
|
||||
"action": "アクション",
|
||||
"update_notice": "Pebrel v{latest} を利用できます(現在 v{current})",
|
||||
"copied_lines": "{lines} 行をクリップボードにコピーしました",
|
||||
"copy_working_directory": "作業ディレクトリをコピー",
|
||||
"cwd_copied": "作業ディレクトリをコピーしました",
|
||||
"paste_lines": "{lines} 行を貼り付けますか?",
|
||||
"sent_to": "{target} に送信しました"
|
||||
"drop_paths_invalid": "これらのパスを貼り付けられませんでした。特殊文字を確認するか、ドロップするファイル数を減らしてください。",
|
||||
"drop_paths_busy": "前回ドロップしたパスをまだ変換中です。",
|
||||
"drop_paths_expired": "アクティブな入力が変わったため、パスの貼り付けをキャンセルしました。",
|
||||
"drop_paths_failed": "この WSL セッション用にパスを変換できませんでした。もう一度お試しください。",
|
||||
"drop_paths_remote": "SSH ではローカルパスを直接使用できません。アップロードするには、ファイルをリモートのファイルツリーにドロップしてください。",
|
||||
"image_paste_failed": "画像を貼り付けられませんでした: {error}",
|
||||
"image_paste_busy": "画像の貼り付けがまだ処理中です。",
|
||||
"image_paste_expired": "アクティブな入力が変わったため、画像の貼り付けをキャンセルしました。",
|
||||
"image_paste_limit": "このタブの一時画像が上限に達しました。",
|
||||
"image_paste_disconnected": "画像を貼り付ける前に SSH セッションに接続してください。",
|
||||
"sent_to": "{target} に送信しました",
|
||||
"yes": "はい",
|
||||
"no": "いいえ",
|
||||
"confirmation_expired": "確認リクエストが変更されたか、すでに終了しています。",
|
||||
"link_missing_cwd": "作業ディレクトリがないため相対パスを開けません: {path}",
|
||||
"link_file_not_found": "ファイルが存在しません: {path}",
|
||||
"link_open_failed": "ファイルを開けませんでした: {error}",
|
||||
"link_unrecognized": "認識できないリンク形式です: {target}",
|
||||
"link_open_url_failed": "リンクを開けませんでした: {error}"
|
||||
},
|
||||
"editor": {
|
||||
"bold_selection": "選択範囲を太字(Ctrl+B / ⌘B)",
|
||||
"italic_selection": "選択範囲を斜体(Ctrl+I / ⌘I)",
|
||||
"release_to_close_sidebar": "離すとサイドバーを閉じます",
|
||||
"document_actions": "ドキュメントの操作",
|
||||
"source": "ソース",
|
||||
"edit_in_place": "編集",
|
||||
"finish_editing": "完了",
|
||||
"finish_editing_shortcut": "編集を終了(Ctrl+Enter / ⌘Enter)",
|
||||
"edit_block": "この段落を編集",
|
||||
"right_sidebar": "右サイドバーの切り替え",
|
||||
"loading": "ファイルを読み込み中…",
|
||||
"save": "保存",
|
||||
"saving": "保存中…",
|
||||
"saved": "保存しました",
|
||||
"unsaved": "未保存",
|
||||
"edit": "編集",
|
||||
"preview": "プレビュー",
|
||||
"reading": "閲覧",
|
||||
"statistics": "ドキュメントの統計",
|
||||
"properties": "ファイルのプロパティ",
|
||||
"details_toggle": "ドキュメントサイドバーの切り替え",
|
||||
"details_close": "ドキュメントサイドバーを隠す",
|
||||
"fullscreen": "全画面表示",
|
||||
"exit_fullscreen": "全画面表示を終了",
|
||||
"focus_reading": "集中モード",
|
||||
"exit_focus_reading": "集中モードを終了",
|
||||
"search_code_language": "言語を検索…",
|
||||
"copy_code": "コードをコピー",
|
||||
"copy_math_latex": "LaTeX ソースをコピー",
|
||||
"copy_math_image": "数式画像をコピー",
|
||||
"math_image_unavailable": "数式画像は、レンダリングが完了するまで、またはフォールバックテキストが必要になるまで利用できません",
|
||||
"math_latex_copied": "LaTeX ソースをコピーしました",
|
||||
"math_image_copied": "数式画像をコピーしました",
|
||||
"markdown_document": "Markdown ドキュメント",
|
||||
"outline": "アウトライン",
|
||||
"no_headings": "このドキュメントにはまだ見出しがありません",
|
||||
"copy_heading": "見出しをコピー",
|
||||
"reload": "ファイルを再読み込み",
|
||||
"copy_path": "ファイルパスをコピー",
|
||||
"read_only": "読み取り専用ファイル",
|
||||
"truncated": "ファイルが 8 MB を超えています。先頭部分のみ表示され、編集と保存はできません。",
|
||||
"encoding": "このファイルは有効な UTF-8 テキストではありません。プレビューのみ可能です。",
|
||||
"conflict": "ディスク上のファイルが変更されました。編集内容は保持されています。再読み込みする前に、必要な変更をコピーしてください。",
|
||||
"read_failed": "ファイルを読み込めませんでした: {error}",
|
||||
"save_failed": "ファイルを保存できませんでした: {error}",
|
||||
"discard_title": "未保存の変更を破棄しますか?",
|
||||
"close_title": "このファイルの変更を保存しますか?",
|
||||
"discard": "変更を破棄",
|
||||
"cancel": "キャンセル",
|
||||
"lines": "{count} 行",
|
||||
"save_shortcut": "保存(Ctrl+S / ⌘S)",
|
||||
"info": "ファイル情報",
|
||||
"type": "種類",
|
||||
"size": "ファイルサイズ",
|
||||
"modified": "更新日時",
|
||||
"characters": "文字数",
|
||||
"line_count": "行数",
|
||||
"encoding_label": "エンコード",
|
||||
"line_ending": "改行コード",
|
||||
"status": "状態",
|
||||
"copy_name": "ファイル名をコピー",
|
||||
"reveal": "ファイルマネージャーで表示",
|
||||
"open_external": "既定のアプリで開く",
|
||||
"preview_limited": "この大きなドキュメントでは、リッチプレビューが短縮表示されています。読み込まれたソースを読むには「編集」に切り替えてください。"
|
||||
},
|
||||
"commands": {
|
||||
"add_command": "新しいコマンド",
|
||||
"root": "すべてのコマンド",
|
||||
"back_to_root": "すべてのコマンドに戻る",
|
||||
"group_count": "{count} 個のコマンド",
|
||||
"add_group": "新しいグループ",
|
||||
"group_name": "グループ名",
|
||||
"builtin_group": "組み込みコマンド",
|
||||
"ungrouped": "グループなし",
|
||||
"remove_from_group": "グループから削除",
|
||||
"move_to_group": "グループに移動",
|
||||
"delete_group": "グループを削除(コマンドは残す)",
|
||||
"group_save_failed": "コマンドグループを保存できませんでした",
|
||||
"builtin": {
|
||||
"python_install_windows": "Python 3.14 をインストール · Windows",
|
||||
"python_install_mac": "Python をインストール · macOS",
|
||||
"python_install_ubuntu": "Python をインストール · Ubuntu/Debian",
|
||||
"python_install_fedora": "Python をインストール · Fedora",
|
||||
"git_install_windows": "Git をインストール · Windows",
|
||||
"git_install_mac": "Git をインストール · macOS",
|
||||
"git_install_ubuntu": "Git をインストール · Ubuntu/Debian",
|
||||
"git_install_fedora": "Git をインストール · Fedora",
|
||||
"docker_install_windows": "Docker Desktop をインストール · Windows",
|
||||
"docker_install_mac": "Docker Desktop をインストール · macOS",
|
||||
"docker_install_ubuntu": "Docker をインストール · Ubuntu 24.04",
|
||||
"docker_install_fedora": "Docker をインストール · Fedora",
|
||||
"docker_ps": "Docker · すべてのコンテナーを一覧表示",
|
||||
"docker_images": "Docker · イメージを一覧表示",
|
||||
"docker_build": "Docker · プロジェクトのイメージをビルド",
|
||||
"docker_run": "Docker · ポートを公開して実行",
|
||||
"docker_logs": "Docker · コンテナーのログを追跡",
|
||||
"docker_exec": "Docker · コンテナーのシェルを開く",
|
||||
"compose_up": "Docker Compose · 開発サービスを起動",
|
||||
"compose_logs": "Docker Compose · サービスのログを追跡",
|
||||
"compose_down": "Docker Compose · サービスを停止(ボリュームは保持)",
|
||||
"conda_install_windows": "Miniconda をインストール · Windows",
|
||||
"conda_install_mac": "Miniconda をインストール · macOS",
|
||||
"conda_install_linux": "Miniconda をインストール · Linux",
|
||||
"conda_list": "Conda · 環境を一覧表示",
|
||||
"conda_create": "Conda · Python 環境を作成",
|
||||
"conda_activate": "Conda · 環境をアクティブ化",
|
||||
"conda_deactivate": "Conda · 環境を非アクティブ化",
|
||||
"conda_install": "Conda · データサイエンス用パッケージをインストール",
|
||||
"conda_export": "Conda · 環境の依存関係をエクスポート",
|
||||
"conda_restore": "Conda · ファイルから環境を作成",
|
||||
"python_venv": "Python · 仮想環境を作成",
|
||||
"python_activate_windows": "Python · 仮想環境をアクティブ化 · PowerShell",
|
||||
"python_activate_posix": "Python · 仮想環境をアクティブ化 · Bash/Zsh",
|
||||
"pip_install": "Python · プロジェクトの依存関係をインストール",
|
||||
"uv_sync": "uv · プロジェクト環境を同期",
|
||||
"npm_install": "npm · ロックされた依存関係をインストール",
|
||||
"npm_dev": "npm · 開発サーバーを起動",
|
||||
"pnpm_install": "pnpm · 依存関係をインストール",
|
||||
"git_status": "Git · 作業ツリーを確認",
|
||||
"git_diff": "Git · ステージされていない変更を確認",
|
||||
"git_log": "Git · コミットグラフを表示",
|
||||
"git_branch": "Git · 機能ブランチを作成",
|
||||
"cargo_test": "Rust · プロジェクトのテストを実行",
|
||||
"cargo_check": "Rust · すべてのターゲットをチェック",
|
||||
"ssh_key": "SSH · Ed25519 キーを作成"
|
||||
},
|
||||
"builtin_label": "組み込み",
|
||||
"save_copy": "コピーを保存"
|
||||
},
|
||||
"files": {
|
||||
"path_unavailable": "パスが存在しません",
|
||||
"delete_failed": "削除できませんでした: {error}",
|
||||
"ignore_add": ".gitignore に追加",
|
||||
"ignore_remove": ".gitignore から削除",
|
||||
"ignore_added": ".gitignore に追加しました: {entry}",
|
||||
"ignore_removed": "無視を解除しました: {entry}",
|
||||
"ignore_present": "すでに無視されています: {entry}",
|
||||
"ignore_visible": "無視されていません: {entry}",
|
||||
"ignore_failed": "Git の無視ルールを変更できませんでした: {error}",
|
||||
"preview_loading": "プレビューを準備中…",
|
||||
"preview_unavailable": "このファイルはプレビューできません",
|
||||
"preview_pages": "1 / {pages} ページ",
|
||||
"open_terminal_here": "ここでターミナルを開く",
|
||||
"tree": {
|
||||
"open_in_explorer": "エクスプローラーで開く",
|
||||
"follow_refresh": "現在のターミナルに追従して更新(Alt+R)"
|
||||
},
|
||||
"copy_linux_path": "Linux パスをコピー",
|
||||
"reveal_in_explorer": "エクスプローラーで表示",
|
||||
"copy_path": "パスをコピー",
|
||||
"empty": {
|
||||
"reading_title": "ディレクトリを読み込み中",
|
||||
"reading_body": "現在の作業ディレクトリを列挙しています。",
|
||||
"reading_hint": "しばらく待つか、右上の [追従] をもう一度クリックしてください。",
|
||||
"read_failed_title": "ディレクトリを読み込めませんでした",
|
||||
"read_failed_body": "ディレクトリを完全には読み込めませんでした。アクセス権を確認するか、もう一度お試しください。",
|
||||
"read_failed_hint": "右上の [追従] をもう一度クリックするか、別のディレクトリを選択してください。",
|
||||
"no_directory_title": "表示するディレクトリがありません",
|
||||
"no_directory_body": "現在のターミナルはまだ作業ディレクトリを報告していません。",
|
||||
"no_directory_hint": "ターミナルでディレクトリに移動してから、右上の [追従] をクリックしてください。",
|
||||
"directory_empty_title": "このディレクトリは空です",
|
||||
"directory_empty_body": "現在の作業ディレクトリに表示するファイルがありません。",
|
||||
"directory_empty_hint": "ターミナルでファイルを作成するか、別のディレクトリを選択してください。"
|
||||
},
|
||||
"delete": {
|
||||
"title": "{name} を削除しますか?",
|
||||
"folder_body": "フォルダーとその中身はすべてごみ箱に移動されます。",
|
||||
"file_body": "ファイルはごみ箱に移動されます。"
|
||||
}
|
||||
},
|
||||
"recipe": {
|
||||
"title": "レイアウトレシピ",
|
||||
"name": "レイアウト名",
|
||||
"save": "レイアウトを保存",
|
||||
"tab": "現在のタブ",
|
||||
"window": "現在のウィンドウ",
|
||||
"empty": "保存済みのレイアウトはまだありません。タブとペインを配置してから、レイアウトに名前を付けてください。",
|
||||
"description": "タブ、分割比率、作業ディレクトリを保存します。復元時は新しいウィンドウにターミナルを作成します。",
|
||||
"open": "復元",
|
||||
"delete": "削除",
|
||||
"loading": "レイアウトを読み込み中…",
|
||||
"saved": "レイアウトを保存しました",
|
||||
"failed": "操作に失敗しました: {error}",
|
||||
"delete_title": "このレイアウトレシピを削除しますか?",
|
||||
"open_failed": "レイアウトを復元できませんでした",
|
||||
"count": "{tabs} 個のタブ · {panes} 個のペイン",
|
||||
"save_window": "現在のウィンドウのレイアウトを保存…",
|
||||
"open_library": "レイアウトレシピを開く…"
|
||||
},
|
||||
"chemistry": {
|
||||
"loading": "分子構造を描画中…",
|
||||
"invalid": "この SMILES は描画できません。ソースは保持されます。"
|
||||
},
|
||||
"markdown": {
|
||||
"plain_text": "プレーンテキスト",
|
||||
"choose_language": "このブロックの強調表示言語を選択"
|
||||
},
|
||||
"transfer": {
|
||||
"not_ready": "リモートディレクトリの準備ができていません",
|
||||
"target_changed": "転送先が変更されました。目的のディレクトリで転送をやり直してください。",
|
||||
"busy": "すでに転送が進行中です。完了を待つか、先にキャンセルしてください。",
|
||||
"checking": "転送先を確認中…",
|
||||
"downloaded": "ダウンロードが完了しました",
|
||||
"cancelled": "転送をキャンセルしました",
|
||||
"failed": "転送に失敗しました",
|
||||
"upload_hint": "ここにファイルをドロップしてアップロード",
|
||||
"download_hint": "エクスプローラーのフォルダーにドラッグしてダウンロード",
|
||||
"conflict_title": "同じ名前の項目がすでに存在します",
|
||||
"conflict_body": "転送先に同じ名前の項目が {count} 個あります。",
|
||||
"incompatible": "一部の項目はファイルとフォルダーの種類が一致しないため、上書きできません。",
|
||||
"symlink_warning": "シンボリックリンクを上書きすると、リンク先が直接変更されます。失敗またはキャンセルすると、内容が部分的に残る場合があります。",
|
||||
"overwrite": "上書き",
|
||||
"skip": "スキップ",
|
||||
"keep_both": "両方を保持",
|
||||
"cancel": "キャンセル",
|
||||
"cancelling": "操作をキャンセル中…",
|
||||
"busy_at": "{host} ではすでに転送が実行中です",
|
||||
"destination": "転送先: {destination}",
|
||||
"copied_item": "リモート項目をコピーしました: {name}",
|
||||
"choose_files": "アップロードするファイルを選択",
|
||||
"choose_upload_directory": "アップロードするフォルダーを選択",
|
||||
"choose_download_directory": "ダウンロード先フォルダーを選択",
|
||||
"drag_hint": "ここにファイルをドロップしてアップロード、項目をエクスプローラーにドラッグしてダウンロードできます。"
|
||||
},
|
||||
"hosts": {
|
||||
"search": "名前、アドレス、グループ、タグ、メモを検索",
|
||||
"group": "グループ",
|
||||
"tags": "タグ",
|
||||
"notes": "メモ",
|
||||
"tags_hint": "タグはコンマで区切ります",
|
||||
"all_groups": "すべてのグループ",
|
||||
"ungrouped": "グループなし",
|
||||
"all": "すべてのホスト",
|
||||
"managed": "管理対象ホスト",
|
||||
"recent": "最近の接続",
|
||||
"no_matches": "一致するホストがありません",
|
||||
"import": "CSV をインポート",
|
||||
"export": "CSV をエクスポート",
|
||||
"import_preview": "{added} 件のホストを追加し、既存の {skipped} 件のホストを保持します。既存の資格情報と接続設定は維持されます。一致する非表示のホストは再表示されます。",
|
||||
"invalid": "ホスト情報が無効です: {error}",
|
||||
"processing": "ホスト情報を処理中…",
|
||||
"reloaded": "ホスト情報を再読み込みしました",
|
||||
"exported": "ホスト情報を資格情報なしでエクスポートしました",
|
||||
"imported": "{added} 件のホストを追加し、既存の {skipped} 件をスキップしました",
|
||||
"imported_partial": "{added} 件のホストを追加しましたが、表示状態を更新できませんでした。インポートを再試行してください: {error}",
|
||||
"title": "SSH ホスト",
|
||||
"subtitle": "保存したホストの整理、サインイン方法の管理、ターミナルの起動を行います。",
|
||||
"add": "ホストを追加",
|
||||
"connect": "接続",
|
||||
"exchange": "インポート / エクスポート",
|
||||
"pinned": "ピン留め",
|
||||
"config_empty": "保存したホストと合わせて ~/.ssh/config のエイリアスを使用できます。",
|
||||
"config_shared": "~/.ssh/config の {count} 件のエイリアスを保存したホストと共有しています。",
|
||||
"reload": "再読み込み",
|
||||
"auth_auto": "自動",
|
||||
"auth_password": "パスワード",
|
||||
"auth_key": "秘密鍵",
|
||||
"auth_interactive": "対話型サインイン"
|
||||
},
|
||||
"vcs": {
|
||||
"changes": "変更",
|
||||
"staged": "ステージ済み",
|
||||
"merge_conflicts": "マージの競合",
|
||||
"no_conflicts": "未解決の競合はありません",
|
||||
"no_changes": "変更はありません",
|
||||
"no_history": "コミットはまだありません",
|
||||
"no_branch": "(ブランチなし)",
|
||||
"confirm_discard": "破棄しますか?",
|
||||
"discard": "変更を破棄",
|
||||
"stage": "ステージ",
|
||||
"unstage": "ステージ解除",
|
||||
"resolve_in_merge_editor": "3 ウェイマージエディターで解決",
|
||||
"commit_changes": "コミット / 変更",
|
||||
"graph": "グラフ",
|
||||
"history_graph": "コミットグラフ",
|
||||
"conflicts": "競合",
|
||||
"resolve_conflicts": "競合を解決",
|
||||
"commit": "コミット",
|
||||
"commit_placeholder": "コミットメッセージ…",
|
||||
"stage_all": "すべてステージ",
|
||||
"pull": "プル",
|
||||
"push": "プッシュ",
|
||||
"push_ahead": "プッシュ ↑{count}",
|
||||
"follow_directory": "ターミナルのディレクトリに追従",
|
||||
"not_repository": "現在のディレクトリは Git/SVN リポジトリ内にありません",
|
||||
"refresh_status": "{vcs} の状態を更新",
|
||||
"close_status": "{vcs} の状態を閉じる",
|
||||
"toggle_git": "Git の状態(Ctrl+Shift+G)",
|
||||
"time_now": "たった今",
|
||||
"time_minute": "1 分前",
|
||||
"time_minutes": "{count} 分前",
|
||||
"time_hour": "1 時間前",
|
||||
"time_hours": "{count} 時間前",
|
||||
"time_day": "1 日前",
|
||||
"time_days": "{count} 日前",
|
||||
"time_month": "1 か月前",
|
||||
"time_months": "{count} か月前",
|
||||
"time_year": "1 年前",
|
||||
"time_years": "{count} 年前",
|
||||
"directory_unavailable": "選択したディレクトリは使用できません",
|
||||
"following_directory": "選択したディレクトリは使用できないため、現在のディレクトリに追従しています",
|
||||
"command_failed": "{program} が失敗しました",
|
||||
"task_start_failed": "バージョン管理タスクを開始できませんでした: {error}"
|
||||
},
|
||||
"launcher": {
|
||||
"open_admin": "管理者として開く",
|
||||
"set_default": "既定のシェルに設定",
|
||||
"current_default": "現在の既定のシェル",
|
||||
"connect": "接続",
|
||||
"edit": "編集",
|
||||
"delete": "削除",
|
||||
"default_saved": "{shell} を既定のシェルに設定しました。",
|
||||
"admin_started": "新しい管理者の Pebrel ウィンドウで {shell} を開くよう要求しました。",
|
||||
"admin_cancelled": "管理者としての起動をキャンセルしました。",
|
||||
"admin_failed": "管理者ターミナルを開けませんでした: {error}"
|
||||
},
|
||||
"theme_transfer": {
|
||||
"import_title": "テーマをインポート",
|
||||
"export_title": "テーマをエクスポート",
|
||||
"import_description": "テーマファイルを選択してプレビューし、テンプレートとして使用します。",
|
||||
"export_description": "形式を選択し、含めることができる設定を確認します。",
|
||||
"cancel": "キャンセル",
|
||||
"close": "閉じる",
|
||||
"candidates": "テーマ",
|
||||
"diagnostics": "ファイルの詳細",
|
||||
"completed": "完了",
|
||||
"error": "要対応",
|
||||
"export": "エクスポート",
|
||||
"exported": "エクスポート先",
|
||||
"inspecting": "テーマファイルを読み込み中…",
|
||||
"losses": "この形式で保持されない設定",
|
||||
"no_candidates": "インポートできるテーマが見つかりませんでした。",
|
||||
"output_format": "形式",
|
||||
"picking": "ファイルを選択",
|
||||
"preparing": "エクスポートを準備中…",
|
||||
"preview": "プレビュー",
|
||||
"template_ready": "テンプレートをエディターに読み込みました",
|
||||
"use_template": "テンプレートとして使用",
|
||||
"warnings": "インポートに関する注意",
|
||||
"writing": "ファイルを書き込み中…"
|
||||
},
|
||||
"theme_editor": {
|
||||
"title": "テーマのカスタマイズ",
|
||||
"description": "既存のテーマをもとに、自分好みの配色に調整できます。",
|
||||
"template": "テンプレート",
|
||||
"name": "テーマ名",
|
||||
"background": "背景",
|
||||
"foreground": "文字色",
|
||||
"accent": "アクセント",
|
||||
"cursor": "カーソルの色",
|
||||
"font_family": "フォント",
|
||||
"font_size": "フォントサイズ",
|
||||
"line_height": "行の高さ",
|
||||
"opacity": "不透明度",
|
||||
"advanced": "詳細設定",
|
||||
"reset": "変更をリセット",
|
||||
"back": "戻る",
|
||||
"save_only": "保存のみ",
|
||||
"apply": "保存して適用",
|
||||
"import": "インポート",
|
||||
"export": "エクスポート",
|
||||
"common": "よく使う設定",
|
||||
"preview_terminal": "ターミナル",
|
||||
"preview_interface": "インターフェイス",
|
||||
"preview_ansi": "ANSI カラー",
|
||||
"invalid_value": "強調表示された色または数値を確認してください。",
|
||||
"new_name": "{name} のコピー",
|
||||
"discard_title": "保存されていない変更を破棄しますか?",
|
||||
"discard_body": "下書きはまだ保存されていません。",
|
||||
"discard": "変更を破棄",
|
||||
"keep_editing": "編集を続ける",
|
||||
"loading": "テーマを読み込み中…",
|
||||
"saving": "保存中…",
|
||||
"library_error": "テーマライブラリを読み込めませんでした: {error}",
|
||||
"save_error": "テーマを保存できませんでした: {error}",
|
||||
"use_template": "テンプレートとして使用",
|
||||
"saved": "カスタムテーマに保存しました",
|
||||
"preview_only": "変更は適用するまでこのプレビューにのみ反映されます。",
|
||||
"advanced_hide": "詳細設定を隠す",
|
||||
"ansi": "ANSI 16 色",
|
||||
"selection": "選択範囲とカーソルの文字",
|
||||
"selection_background": "選択範囲の背景",
|
||||
"selection_foreground": "選択範囲の文字",
|
||||
"cursor_text": "カーソルの文字",
|
||||
"inherit": "空欄にすると自動の色を使用します",
|
||||
"layout": "パネルのレイアウト",
|
||||
"radius": "角の丸み",
|
||||
"gutter": "間隔",
|
||||
"shadow": "影",
|
||||
"divider": "区切り線",
|
||||
"invalid_color": "色が無効です",
|
||||
"invalid_number": "数値が無効です",
|
||||
"change_template_title": "テンプレートを変更しますか?",
|
||||
"change_template_body": "テンプレートを変更すると、保存されていない変更は破棄されます。",
|
||||
"change_template": "テンプレートを変更",
|
||||
"current_setting": "現在の設定",
|
||||
"live_preview": "ライブプレビュー",
|
||||
"selected_line": "選択中のテキスト行",
|
||||
"based_on": "{name} をもとにしています。元のテーマは変更されません。",
|
||||
"editing_status": "テーマの下書き",
|
||||
"editing_unchanged": "現在の設定から変更はありません",
|
||||
"editing_changed": "プレビューには保存されていない変更が含まれています",
|
||||
"reset_short": "リセット"
|
||||
},
|
||||
"theme_picker": {
|
||||
"title": "テーマを選択",
|
||||
"all": "すべて",
|
||||
"light": "ライト",
|
||||
"dark": "ダーク",
|
||||
"custom": "カスタム",
|
||||
"customize": "テーマのカスタマイズ",
|
||||
"text_original": "テーマの文字色",
|
||||
"text_cool": "寒色系の文字色",
|
||||
"text_warm": "暖色系の文字色",
|
||||
"text_custom": "文字色を選択",
|
||||
"discard_body": "外観の変更はまだ適用されていません。",
|
||||
"apply": "このテーマを使用",
|
||||
"cancel": "キャンセル",
|
||||
"color_title": "文字色",
|
||||
"hex_color": "16 進数カラー",
|
||||
"invalid_color": "#aabbcc のような形式で色を入力してください。",
|
||||
"preview_hint": "文字色はこのプレビューに反映されます。ターミナルで使うには適用してください。",
|
||||
"loading": "テーマを読み込み中…",
|
||||
"empty": "保存したテーマがここに表示されます。",
|
||||
"save_error": "テーマを適用できませんでした: {error}",
|
||||
"manual_hint": "適用するとこれらの色が使われ、「システムに従う」はオフになります。",
|
||||
"preview_ready": "次のアイデアの準備ができました。",
|
||||
"apply_icon": "このアイコンを使用",
|
||||
"icon_title": "アプリアイコンを選択",
|
||||
"description": "心地よい配色を見つけましょう。プレビューしてから適用します。",
|
||||
"icon_description": "同じアイコンを、好きな色で。",
|
||||
"selected": "選択済み",
|
||||
"text_suggestion": "色を入力するか候補から選択",
|
||||
"color_hint": "プレビューはリアルタイムに更新されます。ターミナルで使うにはテーマを適用してください。",
|
||||
"saturation_value": "彩度と明度",
|
||||
"hue": "色相",
|
||||
"theme_count": "{count} 個のテーマ",
|
||||
"icon_count": "{count} 色",
|
||||
"neutral": "ニュートラル",
|
||||
"blue": "ブルー",
|
||||
"violet": "バイオレット",
|
||||
"green": "グリーン"
|
||||
},
|
||||
"wallpaper": {
|
||||
"load_failed": "背景画像を読み込めませんでした。読み取り可能な PNG、JPEG、WebP、GIF、BMP 画像を選択してください。",
|
||||
"too_large": "背景画像がメモリ制限を超えています。より小さい画像を選択してください。"
|
||||
},
|
||||
"update": {
|
||||
"downloading_hint": "このデバイス用の更新パッケージをダウンロードして検証しています。",
|
||||
"no_verified_package": "このリリースには、このデバイス用の検証済み更新パッケージがありません。Releases ページを開いて手動でダウンロードしてください。",
|
||||
"check_fallback_failed": "GitHub API へのアクセスが制限されており(HTTP {status})、公式の Releases ページにも接続できませんでした: {error}。後で再試行するか、Releases ページを開いてください。",
|
||||
"check_unrecognized_release": "公式の Releases ページでサポート対象の安定版を特定できませんでした。後で再試行するか、Releases ページを開いてください。",
|
||||
"installation_failed": "更新のインストールに失敗しました",
|
||||
"installation_failed_hint": "更新は完了しませんでした。元のセッションのスナップショットは保持されています。エラーを確認し、パッケージを再検証してから、もう一度インストールを確定してください。",
|
||||
"recheck_package": "パッケージを再検証",
|
||||
"view_details": "詳細を表示",
|
||||
"download": {
|
||||
"dns": "更新サーバーまたはプロキシのアドレスを解決できませんでした。ネットワークとプロキシの設定を確認してから再試行してください。",
|
||||
"tls": "更新のための安全な接続を確立できませんでした。システムの時刻と、プロキシが TLS を傍受していないかを確認してください。",
|
||||
"refused": "更新の接続が拒否されました。ネットワークを確認してください。プロキシを使用している場合は、設定したポートで実行されていることを確認してください。",
|
||||
"timeout": "更新のダウンロードがタイムアウトしました。ネットワークとプロキシの接続を確認してから再試行してください。",
|
||||
"interrupted": "更新のダウンロードが中断されました。接続を確認してから、もう一度ダウンロードしてください。",
|
||||
"proxy": "更新用プロキシ経由で接続できませんでした。アドレス、プロトコル、認証情報を確認してから再試行してください。",
|
||||
"http": "更新サーバーが HTTP {status} を返しました。後で再試行するか、Releases ページからインストーラーをダウンロードしてください。",
|
||||
"network": "更新をダウンロードできませんでした。ネットワークとプロキシの設定を確認するか、Releases ページを使用してください。"
|
||||
},
|
||||
"auto_download": "更新をバックグラウンドでダウンロード",
|
||||
"stop_download": "ダウンロードを停止",
|
||||
"skip_version": "このバージョンをスキップ",
|
||||
"restart_install": "再起動してインストール",
|
||||
"install_next_launch": "次回起動時にインストール",
|
||||
"ready_hint": "更新の準備ができました。Pebrel はワークスペースを保存して終了し、インストール後に再び開きます。",
|
||||
"later": "後で",
|
||||
"prepare_failed": "更新を準備できませんでした: {error}。Pebrel は開いたままになります。",
|
||||
"draft_changed": "終了の準備中にドキュメントが変更されました。最新の変更を保存または破棄してから再試行してください。",
|
||||
"in_progress": "このインストールは更新中です。更新が完了するまでお待ちください。Pebrel は自動的に再び開きます。"
|
||||
},
|
||||
"files.search_failed": "検索を完了できません",
|
||||
"files.search_running": "検索中…",
|
||||
"files.search_limited": "{count} 件の結果を表示中 · さらに表示するには検索を絞り込んでください",
|
||||
"files.search_count": "{count} 件の結果",
|
||||
"files.search_retry": "ディレクトリと検索語句を確認するか、検索オプションをオフにして再試行してください。",
|
||||
"files.search_progress": "一致するファイルは検索の進行に合わせて表示されます。",
|
||||
"files.search_continue": "入力を続けるか、ディレクトリを切り替えられます。",
|
||||
"files.search_incomplete": "検索は完了していません",
|
||||
"files.search_limit_reason": "検索が上限に達したか、一部のディレクトリを読み取れませんでした。",
|
||||
"files.search_narrow": "より小さいディレクトリを選択するか、より具体的な検索語句を入力してください。",
|
||||
"files.search_empty": "一致するファイルがありません",
|
||||
"files.search_empty_reason": "現在のディレクトリには、この検索に一致するファイルやフォルダーがありません。",
|
||||
"session": {
|
||||
"choose_conversation": "会話を選択…",
|
||||
"exit_incomplete_title": "一部の AI セッションを識別できていません。閉じますか?",
|
||||
"exit_incomplete_body": "閉じる前に、ウィンドウのレイアウトと判明している会話 ID が保存されます。実行中のコマンドは停止します。ID をまだ取得できていない会話は、次回起動時に手動で選択が必要になる場合があります。",
|
||||
"exit_anyway": "取得済みの状態を保存して閉じる",
|
||||
"save_failed": "ワークスペースを保存できませんでした。アプリケーションは開いたままになります。空き容量とフォルダーのアクセス許可を確認してから再試行してください。",
|
||||
"identity_pending": "実行中の AI セッションの一部が、復元用の ID をまだ報告していません。起動が完了してから再試行してください。",
|
||||
"restore_failed": "この AI 会話を復元できませんでした。保存された復元先は保持されています。タブメニューの [セッションの復元を再試行] を使用するか、セッション履歴から探してください。",
|
||||
"restore_choose_codex": "保存された Codex の会話は利用できません。Codex で続行する会話を選択してください。",
|
||||
"restore_ambiguous": "この会話に一致するネイティブのセッションファイルが複数あります。保存された復元先は保持されています。セッション履歴から正しい会話を選択してください。",
|
||||
"retry": "セッションの復元を再試行"
|
||||
},
|
||||
"ssh.agent.unavailable": "検出タイムアウト内に利用可能なシステム SSH エージェントがありませんでした。",
|
||||
"ssh.agent.empty": "SSH エージェントから ID が返されませんでした。",
|
||||
"ssh.agent.rejected": "SSH エージェントの ID が拒否されました(試行 {attempted} 件、返却 {available} 件)。",
|
||||
"ssh.agent.partial": "SSH エージェントの ID は受け入れられましたが、別の認証要素が必要です(試行 {attempted} 件、返却 {available} 件)。",
|
||||
"ssh.agent.fatal": "SSH エージェントの認証または署名に失敗したため、この接続は破棄されました。エージェントを確認してから再接続してください。{detail}",
|
||||
"cloud": {
|
||||
"title": "クラウドバックアップ",
|
||||
"snapshots": "バックアップ履歴",
|
||||
"settings": "クラウドストレージ",
|
||||
"configure": "クラウドストレージを構成",
|
||||
"scope": "バックアップの内容",
|
||||
"provider": "ストレージプロバイダー",
|
||||
"off": "未構成",
|
||||
"folder": "ローカル / ネットワークフォルダー",
|
||||
"nutstore": "Nutstore",
|
||||
"webdav": "WebDAV",
|
||||
"s3": "S3 互換ストレージ",
|
||||
"sftp": "SSH ファイルストレージ",
|
||||
"checking": "確認中…",
|
||||
"unreachable": "利用不可",
|
||||
"reachable": "アクセス可能",
|
||||
"not_checked": "未確認",
|
||||
"retention": "最新の {count} 件のバックアップを保持",
|
||||
"refresh": "バックアップを更新",
|
||||
"empty": "バックアップはまだありません",
|
||||
"load_history": "更新するとバックアップ履歴を読み込みます",
|
||||
"restore": "復元",
|
||||
"restore_warning": "バックアップに含まれるローカルの設定とデータが置き換えられます。続行しますか?",
|
||||
"restore_latest": "最新のバックアップを復元",
|
||||
"backup_now": "今すぐバックアップ",
|
||||
"encrypted_archive": "暗号化された完全バックアップ",
|
||||
"latest": "最新のバックアップ",
|
||||
"export": "バックアップファイルをエクスポート",
|
||||
"import": "バックアップファイルをインポート",
|
||||
"folder_path": "フォルダーのパス",
|
||||
"address": "ストレージのアドレス",
|
||||
"username": "アカウント",
|
||||
"s3_address": "サービスエンドポイント",
|
||||
"region": "リージョン",
|
||||
"bucket": "バケット",
|
||||
"access_key": "アクセスキー ID",
|
||||
"ssh_host": "保存済みの SSH ホスト",
|
||||
"remote_path": "リモートフォルダー",
|
||||
"credential": "パスワード / シークレットキー",
|
||||
"credential_set": "安全に保存済み",
|
||||
"credential_needed": "未保存",
|
||||
"store_credential": "資格情報を保存",
|
||||
"saved": "保存しました",
|
||||
"save_failed": "保存に失敗しました",
|
||||
"saving": "保存中…",
|
||||
"auto_save": "変更は自動的に保存されます",
|
||||
"test": "接続を確認",
|
||||
"retry_save": "保存を再試行",
|
||||
"passphrase": "バックアップのパスワード",
|
||||
"passphrase_hint": "最低 8 文字。このセッションでのみ使用されます。復元時は同じパスワードを使用してください。",
|
||||
"scope_hint": "SSH 秘密鍵は含まれません。履歴には機密情報が含まれる場合があります。",
|
||||
"appearance": "外観",
|
||||
"terminal": "ターミナルの設定",
|
||||
"hosts": "SSH ホスト",
|
||||
"sync": "同期の設定",
|
||||
"assistant": "アシスタントの設定",
|
||||
"sessions": "ワークスペースのセッション",
|
||||
"directories": "ディレクトリ履歴",
|
||||
"commands": "コマンド履歴",
|
||||
"fonts": "カスタムフォント"
|
||||
},
|
||||
"ai_hooks": {
|
||||
"announced": "AI のターン通知を接続しました(Claude / Codex / Kimi / Pi / opencode)。取り消すには: pebrel setup-ai --remove"
|
||||
},
|
||||
"notification.command_finished": "コマンドが完了しました({seconds} 秒)",
|
||||
"notification.command_failed": "コマンドが失敗しました(終了コード {code}、{seconds} 秒)",
|
||||
"files.path_edit": "ディレクトリのパスを編集",
|
||||
"files.path_placeholder": "絶対パスまたは相対パス",
|
||||
"files.path_choose": "ディレクトリを選択",
|
||||
"files.path_open": "ディレクトリを開く",
|
||||
"files.path_failed": "このディレクトリを開けませんでした: {error}",
|
||||
"tab_menu": {
|
||||
"fork_ai_session": "AI セッションを分岐",
|
||||
"move_to_new_window": "新しいウィンドウに移動",
|
||||
@@ -187,5 +948,57 @@
|
||||
"move_left": "左に移動",
|
||||
"move_right": "右に移動",
|
||||
"tab_color": "タブの色"
|
||||
},
|
||||
"chrome": {
|
||||
"new_terminal_ctrl_shift_t": "新しいターミナル(Ctrl+Shift+T)",
|
||||
"more": "その他",
|
||||
"scroll_tabs_left": "タブを左にスクロール",
|
||||
"scroll_tabs_right": "タブを右にスクロール",
|
||||
"select_terminal": "ターミナルを選択",
|
||||
"command_list": "コマンド一覧",
|
||||
"toggle_sidebar": "サイドバーの表示/非表示(Ctrl+Shift+B)",
|
||||
"settings_shortcut": "設定(Ctrl+,)"
|
||||
},
|
||||
"workspace": {
|
||||
"pane": {
|
||||
"stop_broadcast_input": "入力のブロードキャストを停止",
|
||||
"broadcast_input_tooltip": "このタブのすべてのペインに入力をブロードキャスト",
|
||||
"close": "このペインを閉じる",
|
||||
"extract_release": "離すと新しいタブとして切り離します",
|
||||
"extract_drag_hint": "ターミナルの外にドラッグして切り離す",
|
||||
"broadcast_enabled": "このタブの {count} 個のペインに入力をブロードキャスト中",
|
||||
"restore_layout": "ペインのレイアウトを元に戻す(Ctrl+Shift+Enter)",
|
||||
"zoom": "このペインを最大化(Ctrl+Shift+Enter)"
|
||||
},
|
||||
"close": {
|
||||
"running_process": "{process} はまだ実行中です。閉じると停止します。",
|
||||
"running_window_process": "{process} はまだ実行中です。ウィンドウを閉じると停止します。"
|
||||
},
|
||||
"close_window": {
|
||||
"title": "ウィンドウを閉じますか?"
|
||||
},
|
||||
"close_pane": {
|
||||
"title": "このペインを閉じますか?"
|
||||
},
|
||||
"close_tab": {
|
||||
"title": "このタブを閉じますか?"
|
||||
}
|
||||
},
|
||||
"remote": {
|
||||
"locating": "リモートディレクトリを特定中…",
|
||||
"parent": "1 つ上の階層へ",
|
||||
"reread": "再読み込み",
|
||||
"upload_files": "ファイルをアップロード",
|
||||
"upload_folder": "フォルダーをアップロード",
|
||||
"download": "このコンピューターにダウンロード",
|
||||
"copy_item": "リモート項目をコピー",
|
||||
"paste_here": "現在のリモートディレクトリに貼り付け",
|
||||
"skip_unchanged": "未変更をスキップ",
|
||||
"cancel": "転送をキャンセル",
|
||||
"preparing": "転送を準備中…",
|
||||
"reading": "リモートディレクトリを読み込み中…",
|
||||
"empty": "このディレクトリは空です。",
|
||||
"error_retry": "{error}(右上の [再読み込み] をクリックして再試行)",
|
||||
"busy_other": "別のホストが転送中です: {destination}"
|
||||
}
|
||||
}
|
||||
@@ -117,7 +117,9 @@
|
||||
"loading": "正在枚举系统字体…",
|
||||
"no_matches": "没有匹配的字体",
|
||||
"expand": "展开字体列表",
|
||||
"collapse": "收起字体列表"
|
||||
"collapse": "收起字体列表",
|
||||
"ctrl_wheel_zoom": "Ctrl+滚轮调整字号",
|
||||
"ctrl_wheel_zoom_description": "按住 Ctrl 在终端上滚动即可调整字号。关闭后该手势被完全忽略:既不改变字号,也不滚动终端;Ctrl+=、Ctrl+-、Ctrl+0 快捷键不受影响。"
|
||||
},
|
||||
"keymap": {
|
||||
"conflict": "{combo} 同时绑定了「{first}」与「{second}」——只有排前面的「{first}」会触发",
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::*;
|
||||
|
||||
#[test]
|
||||
fn non_successful_turns_do_not_emit_a_completion_notification() {
|
||||
for reason in ["error", "aborted", "length", "toolUse"] {
|
||||
for reason in ["error", "aborted", "length"] {
|
||||
let payload = serde_json::json!({"kind":"done", "stop_reason":reason});
|
||||
let event = parse_remote_envelope(
|
||||
format!("nebula-hook/1 source=pi\n{payload}").as_bytes(),
|
||||
@@ -25,6 +25,17 @@ fn non_successful_turns_do_not_emit_a_completion_notification() {
|
||||
)
|
||||
.unwrap();
|
||||
assert!(crate::notify::Notification::from_ai_hook(&event, None, false,).is_some());
|
||||
// Host-finished turns (plan menu, completed goal) end on a tool call; they
|
||||
// must notify like an ordinary completion, not as a failure.
|
||||
let event = parse_remote_envelope(
|
||||
b"nebula-hook/1 source=pi
|
||||
{\"kind\":\"done\",\"stop_reason\":\"toolUse\"}",
|
||||
Some(1),
|
||||
)
|
||||
.unwrap();
|
||||
let notification = crate::notify::Notification::from_ai_hook(&event, None, false)
|
||||
.expect("host-finished turns notify like ordinary completions");
|
||||
assert!(!notification.is_failure(), "a toolUse finish is not a failure");
|
||||
}
|
||||
#[test]
|
||||
fn pi_result_metadata_distinguishes_legacy_unknown_and_background_work() {
|
||||
@@ -36,7 +47,7 @@ fn pi_result_metadata_distinguishes_legacy_unknown_and_background_work() {
|
||||
(",\"stop_reason\":\"error\"", AiTurnOutcome::Failed, AgentStatus::Idle, true),
|
||||
(",\"stop_reason\":\"aborted\"", AiTurnOutcome::Cancelled, AgentStatus::Idle, false),
|
||||
(",\"stop_reason\":\"length\"", AiTurnOutcome::Incomplete, AgentStatus::Idle, true),
|
||||
(",\"stop_reason\":\"toolUse\"", AiTurnOutcome::Incomplete, AgentStatus::Idle, true),
|
||||
(",\"stop_reason\":\"toolUse\"", AiTurnOutcome::Succeeded, AgentStatus::Done, true),
|
||||
(",\"stop_reason\":\"future\"", AiTurnOutcome::Unknown, AgentStatus::Idle, true),
|
||||
(",\"stop_reason\":null", AiTurnOutcome::Unknown, AgentStatus::Idle, true),
|
||||
(",\"stop_reason\":42", AiTurnOutcome::Unknown, AgentStatus::Idle, true),
|
||||
|
||||
@@ -235,7 +235,11 @@ pub(super) fn parse_envelope(bytes: &[u8]) -> Option<AiHookEvent> {
|
||||
Some("stop") => AiTurnOutcome::Succeeded,
|
||||
Some("error") => AiTurnOutcome::Failed,
|
||||
Some("aborted") => AiTurnOutcome::Cancelled,
|
||||
Some("length" | "toolUse") => AiTurnOutcome::Incomplete,
|
||||
Some("length") => AiTurnOutcome::Incomplete,
|
||||
// pi ends a turn with `toolUse` when the host or an extension closes it
|
||||
// right after a tool result (the plan menu, a completed goal). That is a
|
||||
// finished turn, not a truncated one, so it must not raise a failure.
|
||||
Some("toolUse") => AiTurnOutcome::Succeeded,
|
||||
None if !payload.as_object()?.contains_key("stop_reason") => AiTurnOutcome::Unspecified,
|
||||
_ => AiTurnOutcome::Unknown,
|
||||
}
|
||||
|
||||
@@ -54,6 +54,8 @@ pub struct Settings {
|
||||
/// 配置文件的基准字号,不含设置页/Ctrl+滚轮持久化的终端缩放。
|
||||
/// 启动窗口按它定形,和旧壳的 `window_size` 契约一致。
|
||||
pub base_font_size_px: f32,
|
||||
/// Ctrl+滚轮缩放终端字号。默认开启;关闭时终端消费整个手势而不缩放。
|
||||
pub ctrl_wheel_font_zoom: bool,
|
||||
pub ui_font_size_px: f32,
|
||||
pub(crate) ui_font_family: Option<String>,
|
||||
pub(crate) ui_font_size_override: Option<f32>,
|
||||
@@ -122,6 +124,10 @@ pub(crate) fn panel_resize(cx: &App) -> bool {
|
||||
cx.try_global::<Settings>().is_some_and(|settings| settings.panel_resize)
|
||||
}
|
||||
|
||||
pub(crate) fn ctrl_wheel_font_zoom(cx: &App) -> bool {
|
||||
cx.try_global::<Settings>().is_none_or(|settings| settings.ctrl_wheel_font_zoom)
|
||||
}
|
||||
|
||||
pub(crate) fn ai_toasts_enabled(cx: &App) -> bool {
|
||||
cx.try_global::<Settings>().is_none_or(|settings| settings.ai_toasts)
|
||||
}
|
||||
@@ -262,6 +268,7 @@ impl Settings {
|
||||
.ligatures
|
||||
.enabled(resolved_theme.typography().map(|typography| typography.ligatures)),
|
||||
base_font_size_px,
|
||||
ctrl_wheel_font_zoom: runtime.ctrl_wheel_font_zoom,
|
||||
ui_font_size_px: runtime.ui_font_size_px.unwrap_or(base_font_size_px),
|
||||
ui_font_family: runtime.ui_font_family.clone(),
|
||||
ui_font_size_override: runtime.ui_font_size_px,
|
||||
@@ -817,6 +824,20 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpui-test-support")]
|
||||
#[gpui::test]
|
||||
fn ctrl_wheel_font_zoom_reads_current_settings(cx: &mut gpui::TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
assert!(super::ctrl_wheel_font_zoom(cx));
|
||||
for zoom_enabled in [false, true] {
|
||||
let mut runtime = RuntimeSettings::from_raw(&RawSettings::default());
|
||||
runtime.ctrl_wheel_font_zoom = zoom_enabled;
|
||||
cx.set_global(Settings::load_with_runtime(ThemeName::Nebula, runtime));
|
||||
assert_eq!(super::ctrl_wheel_font_zoom(cx), zoom_enabled);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn system_theme_owns_terminal_background_while_following_system() {
|
||||
let custom = Some([0x0f, 0x11, 0x1a]);
|
||||
|
||||
@@ -37,6 +37,8 @@ pub mod network_settings;
|
||||
pub mod prelude;
|
||||
mod scientific_render;
|
||||
pub mod session_restore;
|
||||
#[cfg(all(test, feature = "gpui-test-support"))]
|
||||
pub(crate) mod settings_fixture;
|
||||
pub mod settings_pane;
|
||||
pub mod ssh_hosts;
|
||||
pub mod ssh_settings;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
//! Shared fixture for tests that read or write the real settings path.
|
||||
//!
|
||||
//! These rendered fixtures share the real settings path and theme library.
|
||||
//! Readers must hold the same guard as Save/Apply tests so their before/after
|
||||
//! snapshots cannot observe another fixture's writes or restoration cleanup.
|
||||
//! Only this fixture group is serialized; the rest of the native suite stays parallel.
|
||||
//!
|
||||
//! Under nextest the in-process mutex is not enough on its own: each test runs in
|
||||
//! its own process, so `.config/nextest.toml` repeats the same group as a serial
|
||||
//! test-group. Both halves must stay in sync when a fixture joins the group.
|
||||
|
||||
/// Serializes every fixture that touches the real settings path.
|
||||
pub(crate) fn lock_theme_studio() -> std::sync::MutexGuard<'static, ()> {
|
||||
static FIXTURES: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||
FIXTURES.lock().unwrap_or_else(|error| error.into_inner())
|
||||
}
|
||||
|
||||
/// Restores the settings file's original bytes on drop, removing it when it did not exist.
|
||||
///
|
||||
/// Any test that lets the product persist settings must hold [`lock_theme_studio`]
|
||||
/// for as long as this guard lives.
|
||||
pub(crate) struct SettingsBytesGuard {
|
||||
path: std::path::PathBuf,
|
||||
bytes: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl SettingsBytesGuard {
|
||||
pub(crate) fn capture() -> Self {
|
||||
let path = nebula_settings::settings_path();
|
||||
Self { bytes: std::fs::read(&path).ok(), path }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SettingsBytesGuard {
|
||||
fn drop(&mut self) {
|
||||
match &self.bytes {
|
||||
Some(bytes) => {
|
||||
let _ = std::fs::write(&self.path, bytes);
|
||||
},
|
||||
None => {
|
||||
let _ = std::fs::remove_file(&self.path);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -745,6 +745,7 @@ impl SettingsPane {
|
||||
"powerline" => flag!(powerline),
|
||||
"ghost" => flag!(ghost),
|
||||
"ai_toasts" => flag!(ai_toasts),
|
||||
"ctrl_wheel_font_zoom" => flag!(ctrl_wheel_font_zoom),
|
||||
"notification_duration" => pick!(notification_duration),
|
||||
"cjk_bold_regular" => flag!(cjk_bold_regular),
|
||||
"fetch" => flag!(fetch),
|
||||
|
||||
@@ -126,6 +126,13 @@ impl SettingsPane {
|
||||
),
|
||||
)
|
||||
.child(self.font_size_row(false, cx))
|
||||
.child(self.switch_row(
|
||||
"ctrl_wheel_font_zoom",
|
||||
language.text(crate::i18n::Message::SettingsFontCtrlWheelZoom),
|
||||
language.text(crate::i18n::Message::SettingsFontCtrlWheelZoomDescription),
|
||||
self.runtime.ctrl_wheel_font_zoom,
|
||||
cx,
|
||||
))
|
||||
.child(self.select_row(
|
||||
"ligatures",
|
||||
language.text(crate::i18n::Message::SettingsFontLigatures),
|
||||
|
||||
@@ -28,7 +28,7 @@ pub(super) const SECTION_IDS: [&str; 11] = [
|
||||
/// section that owns the control instead of merely filtering the current page.
|
||||
pub(super) const SECTION_SEARCH_TERMS: [&str; 11] = [
|
||||
"application app 应用 update 更新 version 版本 github support 支持",
|
||||
"appearance ligatures 连字 外观 theme 主题 custom 自定义 template 模板 import 导入 export 导出 font 字体 opacity 透明度 background 背景 cursor 光标 icon 图标 dim inactive panes 调暗非活动窗格 分屏变暗 scrollback scrolling speed history 回滚 滚动 速度 历史",
|
||||
"appearance ligatures 连字 外观 theme 主题 custom 自定义 template 模板 import 导入 export 导出 font 字体 opacity 透明度 background 背景 cursor 光标 icon 图标 dim inactive panes 调暗非活动窗格 分屏变暗 scrollback scrolling speed history 回滚 滚动 速度 历史 滚轮 ctrl wheel zoom 缩放",
|
||||
"profiles 配置文件 shell terminal 终端 completion 补全 startup 启动 ai message notifications toast alerts bell 提醒 通知 弹窗 消息 右下角 ai消息通知 ai 消息通知 ai消息弹窗 ai 消息弹窗 铃声 duration timeout persistent auto-dismiss 时长 秒 常驻 自动关闭",
|
||||
"providers provider ai 供应商 模型 api",
|
||||
"ssh host 主机 remote 远程 connection 连接",
|
||||
|
||||
@@ -204,4 +204,13 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ctrl_wheel_zoom_search_opens_the_terminal_appearance_controls() {
|
||||
for query in ["滚轮", "ctrl wheel", "zoom"] {
|
||||
for language in [crate::display::UiLanguage::ZhCn, crate::display::UiLanguage::EnUs] {
|
||||
assert_eq!(matching_sections(query, language), vec![1], "{query}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,6 +125,66 @@ fn ai_toast_setting_is_searchable_and_has_a_visible_switch(cx: &mut gpui::TestAp
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpui-test-support")]
|
||||
#[gpui::test]
|
||||
fn ctrl_wheel_font_zoom_setting_is_searchable_and_has_a_visible_switch(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
use crate::gpui_shell::settings_fixture::{SettingsBytesGuard, lock_theme_studio};
|
||||
|
||||
// 点击开关会写 `ctrl_wheel_font_zoom=`:与 theme studio 夹具同一把锁,
|
||||
// 并原样恢复用户的设置文件。
|
||||
let _fixture_guard = lock_theme_studio();
|
||||
let _guard = SettingsBytesGuard::capture();
|
||||
cx.update(|cx| {
|
||||
gpui_component::init(cx);
|
||||
let mut settings = crate::gpui_shell::config::Settings::load(ThemeName::Nord);
|
||||
settings.ctrl_wheel_font_zoom = true;
|
||||
cx.set_global(settings);
|
||||
});
|
||||
let mut pane_out = None;
|
||||
let (_, cx) = cx.add_window_view(|window, cx| {
|
||||
let view = cx.new(|cx| SettingsPane::new(window, cx));
|
||||
view.update(cx, |pane, _| {
|
||||
pane.runtime = RuntimeSettings::from_raw(&nebula_settings::RawSettings::default());
|
||||
});
|
||||
pane_out = Some(view.clone());
|
||||
gpui_component::Root::new(view, window, cx)
|
||||
});
|
||||
let pane = pane_out.unwrap();
|
||||
cx.simulate_resize(gpui::size(px(1280.0), px(1600.0)));
|
||||
cx.update(|window, cx| {
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.settings_search_input
|
||||
.update(cx, |input, cx| input.replace_all("滚轮", window, cx));
|
||||
});
|
||||
});
|
||||
cx.run_until_parked();
|
||||
cx.update(|window, cx| {
|
||||
let _ = window.draw(cx);
|
||||
});
|
||||
assert_eq!(pane.read_with(cx, |pane, _| pane.active_section), 1);
|
||||
// 未改动时开关跟随出厂默认(开启),重置按钮回写 "1"。
|
||||
assert_eq!(
|
||||
pane.read_with(cx, |pane, _| pane.setting_override("ctrl_wheel_font_zoom")),
|
||||
Some((false, "1".to_owned()))
|
||||
);
|
||||
let bounds =
|
||||
cx.debug_bounds("nebula-switch-ctrl_wheel_font_zoom").expect("Ctrl+滚轮 switch 已渲染");
|
||||
assert!(bounds.size.width > px(0.0) && bounds.size.height > px(0.0));
|
||||
assert!(bounds.origin.y >= px(0.0) && bounds.bottom() <= px(1600.0));
|
||||
|
||||
// 点击必须真的落到运行时字段上:关闭后该键变脏,重置目标仍是 "1"。
|
||||
cx.simulate_click(bounds.center(), gpui::Modifiers::default());
|
||||
cx.run_until_parked();
|
||||
assert!(!pane.read_with(cx, |pane, _| pane.runtime.ctrl_wheel_font_zoom));
|
||||
assert_eq!(
|
||||
pane.read_with(cx, |pane, _| pane.setting_override("ctrl_wheel_font_zoom")),
|
||||
Some((true, "1".to_owned()))
|
||||
);
|
||||
cx.update(|_, cx| assert!(!crate::gpui_shell::config::ctrl_wheel_font_zoom(cx)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_nav_visibility_keeps_stable_routes_and_hides_backup() {
|
||||
let visibility: Vec<_> = (0..SECTION_IDS.len()).map(is_nav_section_visible).collect();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
//! global palette so a test cannot pass by only exercising a state callback.
|
||||
|
||||
use super::*;
|
||||
use crate::gpui_shell::settings_fixture::{SettingsBytesGuard, lock_theme_studio};
|
||||
use crate::theme_library::{ThemeDocument, ThemeFormat, ThemeLibraryStore};
|
||||
use gpui::{Modifiers, TestAppContext, VisualTestContext, size};
|
||||
use gpui_component::Root;
|
||||
@@ -13,15 +14,6 @@ use nebula_settings::{RawSettings, RuntimeSettings, ThemeDefinition, ThemeName};
|
||||
const TEST_SETTINGS: &str =
|
||||
"theme=Nord\nfollow_system_theme=0\napp_icon=graphite-violet\nfont_size=15\n";
|
||||
|
||||
// These rendered fixtures share the real settings path and theme library.
|
||||
// Readers must hold the same guard as Save/Apply tests so their before/after
|
||||
// snapshots cannot observe another fixture's writes or restoration cleanup.
|
||||
// Only this fixture group is serialized; the rest of the native suite stays parallel.
|
||||
fn lock_theme_studio() -> std::sync::MutexGuard<'static, ()> {
|
||||
static FIXTURES: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||
FIXTURES.lock().unwrap_or_else(|error| error.into_inner())
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct RuntimeSnapshot {
|
||||
theme: ThemeName,
|
||||
@@ -290,31 +282,6 @@ fn custom_theme_documents() -> Vec<ThemeDocument> {
|
||||
ThemeLibraryStore::default().list().expect("list native theme library").custom
|
||||
}
|
||||
|
||||
struct SettingsBytesGuard {
|
||||
path: std::path::PathBuf,
|
||||
bytes: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl SettingsBytesGuard {
|
||||
fn capture() -> Self {
|
||||
let path = nebula_settings::settings_path();
|
||||
Self { bytes: std::fs::read(&path).ok(), path }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SettingsBytesGuard {
|
||||
fn drop(&mut self) {
|
||||
match &self.bytes {
|
||||
Some(bytes) => {
|
||||
let _ = std::fs::write(&self.path, bytes);
|
||||
},
|
||||
None => {
|
||||
let _ = std::fs::remove_file(&self.path);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct CustomThemeCleanup {
|
||||
ids: Vec<String>,
|
||||
|
||||
@@ -595,11 +595,17 @@ mod tests {
|
||||
assert_eq!(encode(&keystroke("a"), &mode), None);
|
||||
assert_eq!(encode(&keystroke("1"), &mode), None);
|
||||
assert_eq!(encode(&keystroke("space"), &mode), None);
|
||||
assert_eq!(encode(&keystroke("/"), &mode), None);
|
||||
// Ctrl+C 仍走记录,不能为了 IME 把快捷键也放掉。
|
||||
let mut ctrl_c = keystroke("c");
|
||||
ctrl_c.modifiers.control = true;
|
||||
assert!(encode(&ctrl_c, &mode).is_some());
|
||||
assert!(encode(&keystroke("escape"), &mode).is_some());
|
||||
|
||||
use windows_sys::Win32::UI::Input::KeyboardAndMouse::VkKeyScanW;
|
||||
let slash = Keystroke::parse("ctrl-/").unwrap();
|
||||
let vk = unsafe { VkKeyScanW(b'/' as u16) } as u16 & 0xff;
|
||||
assert!(encode(&slash, &mode).unwrap().starts_with(format!("\x1b[{vk};").as_bytes()));
|
||||
}
|
||||
|
||||
/// 普通空格与字母使用同一文本输入路径:英语布局最终提交 `" "`,IME
|
||||
|
||||
@@ -13,7 +13,7 @@ pub(super) fn win32_encodes_keystroke(ks: &Keystroke) -> bool {
|
||||
return true;
|
||||
}
|
||||
let key = ks.key.as_str();
|
||||
if key == "space" {
|
||||
if key == "space" || key == "/" {
|
||||
return false;
|
||||
}
|
||||
let mut chars = key.chars();
|
||||
@@ -23,15 +23,14 @@ pub(super) fn win32_encodes_keystroke(ks: &Keystroke) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// GPUI 键名 → Win32 虚拟键码。
|
||||
/// GPUI 键名 → Win32 虚拟键码及布局所需的修饰键。
|
||||
///
|
||||
/// 旧壳从 winit fork 的 `RawKeyEventInfo` 直接拿到系统报的 VK;GPUI 的
|
||||
/// `Keystroke` 只有键名,所以这里按名字反查。表只覆盖**编码器会处理的键**
|
||||
/// (控制键、方向、功能键)——可打印字符在 GPUI 走 IME 管道,不经这里。
|
||||
pub(super) fn virtual_key_of(key: &str) -> Option<u16> {
|
||||
/// `Keystroke` 只有键名,所以这里按名字反查;普通文本仍走 IME 管道。
|
||||
pub(super) fn virtual_key_of(key: &str) -> Option<(u16, u16)> {
|
||||
use windows_sys::Win32::UI::Input::KeyboardAndMouse::{
|
||||
VK_BACK, VK_DELETE, VK_DOWN, VK_END, VK_ESCAPE, VK_F1, VK_HOME, VK_INSERT, VK_LEFT,
|
||||
VK_NEXT, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SPACE, VK_TAB, VK_UP,
|
||||
VK_NEXT, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SPACE, VK_TAB, VK_UP, VkKeyScanW,
|
||||
};
|
||||
|
||||
let vk = match key {
|
||||
@@ -50,6 +49,11 @@ pub(super) fn virtual_key_of(key: &str) -> Option<u16> {
|
||||
"delete" => VK_DELETE,
|
||||
"pageup" => VK_PRIOR,
|
||||
"pagedown" => VK_NEXT,
|
||||
"/" => {
|
||||
// GPUI 将布局所需的 Shift 合入键名,不能写死美式布局的 VK_OEM_2。
|
||||
let mapping = unsafe { VkKeyScanW(b'/' as u16) };
|
||||
return (mapping != -1).then_some((mapping as u16 & 0xff, mapping as u16 >> 8));
|
||||
},
|
||||
// F1..F24 在 VK 表里连号。
|
||||
key if key.starts_with('f') => {
|
||||
let index: u16 = key[1..].parse().ok()?;
|
||||
@@ -68,7 +72,7 @@ pub(super) fn virtual_key_of(key: &str) -> Option<u16> {
|
||||
c.to_ascii_uppercase() as u16
|
||||
},
|
||||
};
|
||||
Some(vk)
|
||||
Some((vk, 0))
|
||||
}
|
||||
|
||||
/// 控制键必须携带真实 `KEY_EVENT_RECORD` 的字符值(Esc=0x1B、Enter=0x0D、
|
||||
@@ -140,17 +144,17 @@ pub(super) fn win32_input_record(ks: &Keystroke, key_down: bool) -> Option<Vec<u
|
||||
const LEFT_ALT_PRESSED: u32 = 0x0002;
|
||||
const LEFT_CTRL_PRESSED: u32 = 0x0008;
|
||||
|
||||
let vk = virtual_key_of(ks.key.as_str())?;
|
||||
let (vk, layout_modifiers) = virtual_key_of(ks.key.as_str())?;
|
||||
// 扫描码问系统要,不硬编码:非 US 布局与笔记本键盘上这张表并不通用。
|
||||
let scan_code = unsafe { MapVirtualKeyW(u32::from(vk), MAPVK_VK_TO_VSC) };
|
||||
let mut control_key_state = 0u32;
|
||||
if ks.modifiers.shift {
|
||||
if ks.modifiers.shift || layout_modifiers & 1 != 0 {
|
||||
control_key_state |= SHIFT_PRESSED;
|
||||
}
|
||||
if ks.modifiers.alt {
|
||||
if ks.modifiers.alt || layout_modifiers & 4 != 0 {
|
||||
control_key_state |= LEFT_ALT_PRESSED;
|
||||
}
|
||||
if ks.modifiers.control {
|
||||
if ks.modifiers.control || layout_modifiers & 2 != 0 {
|
||||
control_key_state |= LEFT_CTRL_PRESSED;
|
||||
}
|
||||
let key_down = u8::from(key_down);
|
||||
|
||||
@@ -860,9 +860,12 @@ impl TerminalView {
|
||||
// 旧壳 `mouse_wheel_input`:Ctrl+滚轮先于一切滚动消费者,一步 1
|
||||
// 逻辑像素,钳在 4–64。设置页步进会写盘;这里同样写 `font_size=`,
|
||||
// 让下次启动跟上(旧壳只在其它设置落盘时顺便带走当前字号)。
|
||||
// 开关关闭时手势仍被整体消费:既不缩放,也不回落给下面的滚动逻辑。
|
||||
if event.modifiers.control && !event.modifiers.alt && delta_y != 0.0 {
|
||||
if crate::gpui_shell::config::ctrl_wheel_font_zoom(cx) {
|
||||
let step = if delta_y > 0.0 { 1.0 } else { -1.0 };
|
||||
self.zoom_font_size(step, cx);
|
||||
}
|
||||
cx.stop_propagation();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -326,3 +326,70 @@ fn explicit_link_gesture_respects_disabled_hints_and_required_modifiers(cx: &mut
|
||||
window.simulate_mouse_up(position, MouseButton::Left, modifiers);
|
||||
assert_eq!(clipboard(&mut window).as_deref(), Some("https://example.com"));
|
||||
}
|
||||
|
||||
/// 真实终端元素 + 可回滚的历史,滚轮手势才有可观察的落点。
|
||||
fn terminal_with_history(cx: &mut TestAppContext) -> (Entity<TerminalView>, VisualTestContext) {
|
||||
let (probe, mut cx) = open(cx);
|
||||
let terminal = probe.read_with(&cx, |probe, _| probe.terminal.clone());
|
||||
terminal.update(&mut cx, |view, cx| {
|
||||
let (session, _receiver) = session::test_session();
|
||||
view.session = Some(session);
|
||||
let mut history = Vec::new();
|
||||
for line in 0..200 {
|
||||
history.extend_from_slice(format!("history-{line}\r\n").as_bytes());
|
||||
}
|
||||
super::super::startup_tests::feed(view, &history);
|
||||
cx.notify();
|
||||
});
|
||||
draw(&mut cx);
|
||||
(terminal, cx)
|
||||
}
|
||||
|
||||
/// 走真实命中区域派发滚轮:`control` 决定是否按 Ctrl+滚轮解释。
|
||||
fn wheel_over_terminal(cx: &mut VisualTestContext, delta_y: f32, control: bool) {
|
||||
let position = cx.debug_bounds("mouse-terminal").unwrap().center();
|
||||
cx.simulate_mouse_move(position, None, Modifiers::default());
|
||||
cx.simulate_event(gpui::ScrollWheelEvent {
|
||||
position,
|
||||
delta: gpui::ScrollDelta::Pixels(point(px(0.0), px(delta_y))),
|
||||
modifiers: Modifiers { control, ..Modifiers::default() },
|
||||
touch_phase: gpui::TouchPhase::Moved,
|
||||
});
|
||||
cx.run_until_parked();
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn ctrl_wheel_font_zoom_toggle_gates_zoom_and_terminal_scroll(cx: &mut TestAppContext) {
|
||||
use crate::gpui_shell::settings_fixture::{SettingsBytesGuard, lock_theme_studio};
|
||||
|
||||
// 缩放会写 `font_size=`:与 theme studio 夹具同一把锁,并原样恢复用户的设置文件。
|
||||
let _fixture_guard = lock_theme_studio();
|
||||
let _guard = SettingsBytesGuard::capture();
|
||||
let (terminal, mut cx) = terminal_with_history(cx);
|
||||
// 从中间字号起步,让 ±1 步远离 4–64 的钳位边界。
|
||||
cx.update(|_, cx| {
|
||||
let settings = cx.global_mut::<Settings>();
|
||||
settings.font_size_px = 15.0;
|
||||
settings.ctrl_wheel_font_zoom = true;
|
||||
});
|
||||
|
||||
// 开启(默认):Ctrl+滚轮仍然放大字号,且不移动终端回滚位置。
|
||||
wheel_over_terminal(&mut cx, 60.0, true);
|
||||
let zoomed = terminal.read_with(&cx, |view, _| view.font_size);
|
||||
assert!(zoomed > px(15.0), "Ctrl+滚轮应放大字号,实际 {zoomed:?}");
|
||||
assert_eq!(terminal.read_with(&cx, |view, _| view.scroll_state().0), 0);
|
||||
|
||||
// 关闭:放大方向的手势既不改变字号,也不被当成普通滚动消费掉。
|
||||
cx.update(|_, cx| cx.global_mut::<Settings>().ctrl_wheel_font_zoom = false);
|
||||
wheel_over_terminal(&mut cx, 60.0, true);
|
||||
assert_eq!(terminal.read_with(&cx, |view, _| view.font_size), zoomed);
|
||||
assert_eq!(terminal.read_with(&cx, |view, _| view.scroll_state().0), 0);
|
||||
// 缩小方向同样被整体吞掉。
|
||||
wheel_over_terminal(&mut cx, -60.0, true);
|
||||
assert_eq!(terminal.read_with(&cx, |view, _| view.font_size), zoomed);
|
||||
assert_eq!(terminal.read_with(&cx, |view, _| view.scroll_state().0), 0);
|
||||
|
||||
// 关闭开关只影响 Ctrl+滚轮:普通滚轮照旧滚动。
|
||||
wheel_over_terminal(&mut cx, 60.0, false);
|
||||
assert!(terminal.read_with(&cx, |view, _| view.scroll_state().0) > 0);
|
||||
}
|
||||
@@ -346,8 +346,8 @@ impl TerminalView {
|
||||
bell_flash_epoch: 0,
|
||||
};
|
||||
// 出生即把亮暗种进 Term:`Term::color_scheme_dark` 的默认值是「暗」,
|
||||
// 浅色主题下启动的 pane 如果不种,第一个 DECSET 2031 的订阅方会拿到
|
||||
// 一个错的初值,而且在用户下一次改主题之前都纠不回来。
|
||||
// 浅色主题下启动的 pane 如果不种,第一次真实的亮暗翻转会被
|
||||
// `set_color_scheme` 的同值去重吞掉,订阅方漏收第一条通知。
|
||||
if let Some(session) = &view.session {
|
||||
session.term.lock().set_color_scheme(view.palette.is_dark());
|
||||
}
|
||||
|
||||
@@ -696,7 +696,6 @@ mod delivery_tests {
|
||||
("unknown", Some(AiTurnOutcome::Unknown)),
|
||||
("future", Some(AiTurnOutcome::Unknown)),
|
||||
("length", Some(AiTurnOutcome::Incomplete)),
|
||||
("toolUse", Some(AiTurnOutcome::Incomplete)),
|
||||
] {
|
||||
let event = pi_result(Some(reason), None);
|
||||
let notification = Notification::from_ai_hook(&event, None, false);
|
||||
@@ -708,7 +707,7 @@ mod delivery_tests {
|
||||
result => panic!("unexpected {reason} result: {result:?}"),
|
||||
}
|
||||
}
|
||||
for reason in [None, Some("stop")] {
|
||||
for reason in [None, Some("stop"), Some("toolUse")] {
|
||||
let mut payload = serde_json::json!({"kind": "done"});
|
||||
if let Some(reason) = reason {
|
||||
payload["stop_reason"] = reason.into();
|
||||
@@ -716,7 +715,10 @@ mod delivery_tests {
|
||||
let wire = format!("nebula-hook/1 source=pi\n{payload}");
|
||||
let event = crate::ai_hook::parse_remote_envelope(wire.as_bytes(), Some(1)).unwrap();
|
||||
let note = Notification::from_ai_hook(&event, None, false).unwrap();
|
||||
assert_eq!(matches!(note, Notification::AiTurn { .. }), reason == Some("stop"));
|
||||
assert_eq!(
|
||||
matches!(note, Notification::AiTurn { .. }),
|
||||
matches!(reason, Some("stop") | Some("toolUse"))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -977,6 +977,9 @@ pub struct RuntimeSettings {
|
||||
/// **逻辑像素**(旧壳写盘语义:设置页 spinner 与 Ctrl+滚轮缩放持久化时
|
||||
/// 已除以 scale factor)。`None` = 跟随 nebula.toml 的 `font.size`(pt)。
|
||||
pub font_size_px: Option<f32>,
|
||||
/// Ctrl+滚轮缩放终端字号。默认开启以保留既有行为;关闭时该手势被整体
|
||||
/// 消费:既不缩放,也不回落成普通滚动。不影响普通滚轮与键盘字号快捷键。
|
||||
pub ctrl_wheel_font_zoom: bool,
|
||||
/// Enabled by default; explicit theme mode follows the selected theme.
|
||||
pub ligatures: Ligatures,
|
||||
pub cursor_shape: Option<CursorShapeName>,
|
||||
@@ -1149,6 +1152,7 @@ impl RuntimeSettings {
|
||||
ui_font_family: raw.value("ui_font_family").map(str::to_owned),
|
||||
ui_font_size_px: raw.f32("ui_font_size").map(|size| size.clamp(10.0, 24.0)),
|
||||
font_size_px: raw.f32("font_size").map(|size| size.clamp(4.0, 96.0)),
|
||||
ctrl_wheel_font_zoom: raw.bool_on("ctrl_wheel_font_zoom").unwrap_or(true),
|
||||
ligatures: raw
|
||||
.value("ligatures")
|
||||
.and_then(Ligatures::from_settings)
|
||||
@@ -1613,6 +1617,22 @@ mod tests {
|
||||
assert_eq!(enabled.matches("ai_toasts=").count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ctrl_wheel_font_zoom_defaults_on_and_accepts_all_boolean_spellings() {
|
||||
assert!(RuntimeSettings::from_raw(&RawSettings::default()).ctrl_wheel_font_zoom);
|
||||
for value in ["", "invalid", "1", "true", "YES", "On"] {
|
||||
let raw = RawSettings::from_text(&format!("ctrl_wheel_font_zoom={value}\n"));
|
||||
assert!(RuntimeSettings::from_raw(&raw).ctrl_wheel_font_zoom, "{value:?}");
|
||||
}
|
||||
for value in ["0", "false", "NO", "Off"] {
|
||||
let raw = RawSettings::from_text(&format!("ctrl_wheel_font_zoom={value}\n"));
|
||||
let settings = RuntimeSettings::from_raw(&raw);
|
||||
assert!(!settings.ctrl_wheel_font_zoom, "{value:?}");
|
||||
assert_eq!(settings.bell, BellModeName::Both);
|
||||
assert!(settings.font_size_px.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bell_mode_parses_known_values_and_rejects_junk() {
|
||||
assert_eq!(BellModeName::from_settings("none"), Some(BellModeName::None));
|
||||
|
||||
@@ -16,6 +16,7 @@ const RESET_KEYS: &[&str] = &[
|
||||
"ui_font_family",
|
||||
"ui_font_size",
|
||||
"font_size",
|
||||
"ctrl_wheel_font_zoom",
|
||||
"ligatures",
|
||||
"cursor_shape",
|
||||
"cursor_blink",
|
||||
@@ -169,6 +170,15 @@ mod tests {
|
||||
assert!(RuntimeSettings::from_raw(&RawSettings::from_text(&restored)).ai_toasts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resetting_preferences_reenables_ctrl_wheel_font_zoom_without_erasing_other_data() {
|
||||
let original = "ctrl_wheel_font_zoom=0\ncustom_data=keep\n";
|
||||
assert!(!RuntimeSettings::from_raw(&RawSettings::from_text(original)).ctrl_wheel_font_zoom);
|
||||
let restored = default_settings_text(original);
|
||||
assert_eq!(restored, "custom_data=keep\n");
|
||||
assert!(RuntimeSettings::from_raw(&RawSettings::from_text(&restored)).ctrl_wheel_font_zoom);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resetting_preferences_restores_the_default_notification_duration() {
|
||||
let original = "notification_duration=persistent\ncustom_data=keep\n";
|
||||
|
||||
@@ -174,13 +174,15 @@ class NativeSuiteTests(unittest.TestCase):
|
||||
release = (root / ".github/workflows/release.yml").read_text(encoding="utf-8")
|
||||
self.assertIn("run: python scripts/ci_native_tests.py\n", release)
|
||||
|
||||
def test_nextest_preserves_only_the_existing_shared_theme_fixture_mutex(self):
|
||||
def test_nextest_serializes_only_fixtures_that_write_the_real_settings_file(self):
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
config = tomllib.loads((root / ".config/nextest.toml").read_text(encoding="utf-8"))
|
||||
self.assertEqual(config["test-groups"], {"theme-studio": {"max-threads": 1}})
|
||||
self.assertEqual(config["profile"]["default"], {
|
||||
"overrides": [{
|
||||
"filter": "test(gpui_shell::settings_pane::theme_studio_tests::)",
|
||||
"filter": "test(gpui_shell::settings_pane::theme_studio_tests::)"
|
||||
" or test(ctrl_wheel_font_zoom_toggle_gates_zoom_and_terminal_scroll)"
|
||||
" or test(ctrl_wheel_font_zoom_setting_is_searchable_and_has_a_visible_switch)",
|
||||
"test-group": "theme-studio",
|
||||
}],
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user