railmux 0.4.1.dev3__tar.gz → 0.4.1.dev5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {railmux-0.4.1.dev3/src/railmux.egg-info → railmux-0.4.1.dev5}/PKG-INFO +1 -1
- railmux-0.4.1.dev5/src/railmux/__init__.py +1 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/tmux_ctl.py +33 -4
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/ui/app.py +34 -8
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/windows_attach_relay.py +26 -5
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5/src/railmux.egg-info}/PKG-INFO +1 -1
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_fullscreen_binding.py +22 -1
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_layout_profile.py +1 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_tmux_ctl.py +13 -1
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_tmux_integration.py +21 -9
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_tmux_status.py +28 -3
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_windows_attach_relay.py +114 -0
- railmux-0.4.1.dev3/src/railmux/__init__.py +0 -1
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/LICENSE +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/MANIFEST.in +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/README.md +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/pyproject.toml +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/setup.cfg +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/__main__.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/atomic_file.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/background_index.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/cli.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/codex_index.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/config.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/config_cli.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/diagnostic_contract.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/diagnostics.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/discovery.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/display_transport.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/entrypoint.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/fast_display_client.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/fast_display_history.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/fast_display_input.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/fast_display_protocol.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/fast_display_server.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/favorites.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/fuzzy.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/help_workspace.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/launcher.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/legacy_sessions.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/local_clipboard.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/local_open.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/models.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/modes.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/mouse_manager.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/orphan_marker.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/pane_surface.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/path_codec.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/preview_pager.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/project_favorites.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/provider_paths.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/release_version.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/remote_config.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/renames.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/restart_state.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/runtime_config.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/scroll_agent.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/scroll_manager.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/selection_isolation.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/self_update.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/session_cache.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/session_index.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/session_lease.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/setting_contracts.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/settings.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/ssh_args.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/ssh_compat.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/ssh_display_diagnostics.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/ssh_doctor.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/ssh_preflight.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/system_deps.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/terminal_screen.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/terminal_status.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/tmux_binding_manager.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/tmux_capabilities.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/tmux_health.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/tmux_server.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/tool_panes.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/transcript.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/ui/__init__.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/ui/_widgets.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/ui/keymap.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/ui/modals.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/ui/projects_pane.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/ui/running_pane.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/ui/sessions_pane.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/ui/sidebar.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/ui/statusbar.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/ui/workspace.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/windows_bootstrap.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/windows_install_log.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/windows_msys2.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/windows_pacman.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/windows_paths.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/windows_tmux_lifecycle.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux/windows_ui_transition.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux.egg-info/SOURCES.txt +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux.egg-info/dependency_links.txt +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux.egg-info/entry_points.txt +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux.egg-info/requires.txt +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/src/railmux.egg-info/top_level.txt +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/app_test_harness.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_agent_workspace.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_app_codex_placeholder.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_app_quit.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_app_restart_restore.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_app_running_recovery.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_atomic_file.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_background_index.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_cli.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_code_map.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_codex_delete.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_codex_index.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_codex_lineage.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_codex_transcript.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_config.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_config_cli.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_denested_transport_benchmark.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_diagnostics.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_discovery.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_display_transport.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_fast_display_bootstrap.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_fast_display_history.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_fast_display_protocol.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_fast_display_selection.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_fast_display_server.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_fast_display_terminal.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_focus_highlight.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_fuzzy.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_help_agent.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_help_workspace.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_history_cleanup.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_keymap.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_launcher.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_legacy_sessions.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_local_clipboard.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_local_open.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_modals.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_mode_selection.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_modes.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_mouse_manager.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_new_project.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_options.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_orphan_recovery.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_pane_surface.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_path_codec.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_preview_pager.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_project_favorites.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_projects_pane.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_provider_paths.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_release_notes.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_release_version.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_release_workflow.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_remote_config.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_renames.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_restart_state.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_running_pane.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_running_sort.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_runtime_config.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_scroll_agent.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_scroll_manager.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_selection_isolation.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_self_update.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_session_cache.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_session_index.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_session_lease.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_sessions_pane.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_setting_contracts.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_settings.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_sidebar.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_ssh_args.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_ssh_compat.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_ssh_display_benchmark.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_ssh_display_diagnostics.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_ssh_doctor.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_status.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_statusbar.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_system_deps.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_terminal_size.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_terminal_status.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_termux_local_touch.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_tmux_binding_manager.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_tmux_capabilities.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_tmux_health.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_tmux_server.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_tool_panes.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_transcript.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_utf8_state.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_web_demo_recorder.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_wheel_smoke.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_widgets.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_windows_bootstrap.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_windows_install_log.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_windows_msys2.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_windows_pacman.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_windows_paths.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_windows_synchronized_screen.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_windows_tmux_lifecycle.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_windows_ui_transition.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_windows_wrapper_contract.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tests/test_yolo_prompt.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tools/denested_transport_benchmark.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tools/release_notes.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tools/wheel_smoke.py +0 -0
- {railmux-0.4.1.dev3 → railmux-0.4.1.dev5}/tools/windows_archive_smoke.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.1.dev5"
|
|
@@ -2046,6 +2046,9 @@ _ROOT_TERMUX_TAP_MARKER = "railmux-termux-tap-v1"
|
|
|
2046
2046
|
STATUS_ACTION_MODE = "railmux-mode"
|
|
2047
2047
|
STATUS_ACTION_LAYOUT = "railmux-layout"
|
|
2048
2048
|
STATUS_ACTION_COPY = "railmux-copy"
|
|
2049
|
+
STATUS_ACTION_PAGE_SIDEBAR = "railmux-page-r"
|
|
2050
|
+
STATUS_ACTION_PAGE_PRIMARY = "railmux-page-1"
|
|
2051
|
+
STATUS_ACTION_PAGE_SECONDARY = "railmux-page-2"
|
|
2049
2052
|
_ROOT_STATUS_ACTION_KEYS = {
|
|
2050
2053
|
STATUS_ACTION_MODE: "MouseDown1Control0",
|
|
2051
2054
|
STATUS_ACTION_LAYOUT: "MouseDown1Control1",
|
|
@@ -2059,6 +2062,9 @@ _STATUS_ACTIONS = frozenset({
|
|
|
2059
2062
|
STATUS_ACTION_MODE,
|
|
2060
2063
|
STATUS_ACTION_LAYOUT,
|
|
2061
2064
|
STATUS_ACTION_COPY,
|
|
2065
|
+
STATUS_ACTION_PAGE_SIDEBAR,
|
|
2066
|
+
STATUS_ACTION_PAGE_PRIMARY,
|
|
2067
|
+
STATUS_ACTION_PAGE_SECONDARY,
|
|
2062
2068
|
})
|
|
2063
2069
|
_PREFIX_TARGET_KEY = "Tab"
|
|
2064
2070
|
_PREFIX_TARGET_MARKER = "railmux-target-toggle-v1"
|
|
@@ -2085,6 +2091,15 @@ RAILMUX_HISTORY_RESTORE_KEYS = {
|
|
|
2085
2091
|
"primary": ("F23", "\x1b[42~"),
|
|
2086
2092
|
"secondary": ("F24", "\x1b[43~"),
|
|
2087
2093
|
}
|
|
2094
|
+
# Compact status clicks must reach the controller before pane focus changes so
|
|
2095
|
+
# it can park the visible provider, switch the zoomed page, and resume the new
|
|
2096
|
+
# provider as one transaction. tmux names keys only through F12, therefore
|
|
2097
|
+
# these private routes use canonical xterm sequences in literal mode.
|
|
2098
|
+
RAILMUX_COMPACT_PAGE_KEYS = {
|
|
2099
|
+
STATUS_ACTION_PAGE_SIDEBAR: ("F13", "\x1b[25~"),
|
|
2100
|
+
STATUS_ACTION_PAGE_PRIMARY: ("F14", "\x1b[26~"),
|
|
2101
|
+
STATUS_ACTION_PAGE_SECONDARY: ("F15", "\x1b[28~"),
|
|
2102
|
+
}
|
|
2088
2103
|
RAILMUX_SELECTION_KEY_OPTION = "@railmux_selection_key"
|
|
2089
2104
|
RAILMUX_SELECTION_PEER_OPTION = "@railmux_selection_peer"
|
|
2090
2105
|
RAILMUX_SELECTION_FROZEN_OPTION = "@railmux_selection_frozen_by"
|
|
@@ -2124,7 +2139,9 @@ def status_pane_range(pane_id: str, content: str) -> str:
|
|
|
2124
2139
|
# A pane range sets ``mouse_status_range`` to only the literal ``pane``;
|
|
2125
2140
|
# ``=`` still resolves to the pane geometrically below the status cell,
|
|
2126
2141
|
# not to X. A user range preserves the strict pane ID as its argument, so
|
|
2127
|
-
# the managed binding can select
|
|
2142
|
+
# the managed binding can select a pane-targeted status control explicitly.
|
|
2143
|
+
# Compact page navigation uses controller actions instead so parking,
|
|
2144
|
+
# resuming, and zooming remain one ordered transaction.
|
|
2128
2145
|
#
|
|
2129
2146
|
# Status strings also pass through tmux's strftime expansion. Escape the
|
|
2130
2147
|
# pane-ID percent sign so IDs such as ``%76`` are not parsed as padding
|
|
@@ -2149,8 +2166,8 @@ def status_action_range(action: str, content: str) -> str:
|
|
|
2149
2166
|
# keys avoids routing fixed actions through the generic user-range
|
|
2150
2167
|
# dispatcher and provides deterministic bindings on the 3.7b version
|
|
2151
2168
|
# bundled by the managed Windows runtime.
|
|
2152
|
-
|
|
2153
|
-
|
|
2169
|
+
key = _ROOT_STATUS_ACTION_KEYS.get(action)
|
|
2170
|
+
if tmux_version() >= (3, 7) and key is not None:
|
|
2154
2171
|
index = key.removeprefix("MouseDown1Control")
|
|
2155
2172
|
return f"#[range=control|{index}]{content}#[norange]"
|
|
2156
2173
|
return f"#[range=user|{action}]{content}#[norange]"
|
|
@@ -3079,7 +3096,7 @@ def set_root_status_click_forwarding(
|
|
|
3079
3096
|
marker = f"{_ROOT_STATUS_CLICK_MARKER}-{token}"
|
|
3080
3097
|
condition = (
|
|
3081
3098
|
"#{&&:"
|
|
3082
|
-
"#{m/r:^(%[0-9]+|railmux-(mode|layout|copy))$,"
|
|
3099
|
+
"#{m/r:^(%[0-9]+|railmux-(mode|layout|copy|page-[r12]))$,"
|
|
3083
3100
|
"#{mouse_status_range}},"
|
|
3084
3101
|
"#{!=:#{@railmux_controller_pane},},"
|
|
3085
3102
|
f"#{{==:{marker},{marker}}}}}"
|
|
@@ -3097,6 +3114,18 @@ def set_root_status_click_forwarding(
|
|
|
3097
3114
|
f'\'#{{{RAILMUX_CONTROLLER_OPTION}}}\' F7 ;; '
|
|
3098
3115
|
f'{STATUS_ACTION_COPY}) {_tmux_shell_executable()} send-keys -t '
|
|
3099
3116
|
f'\'#{{{RAILMUX_CONTROLLER_OPTION}}}\' F6 ;; '
|
|
3117
|
+
f'{STATUS_ACTION_PAGE_SIDEBAR}) '
|
|
3118
|
+
f'{_tmux_shell_executable()} send-keys -l -t '
|
|
3119
|
+
f'\'#{{{RAILMUX_CONTROLLER_OPTION}}}\' -- '
|
|
3120
|
+
f'{shlex.quote(RAILMUX_COMPACT_PAGE_KEYS[STATUS_ACTION_PAGE_SIDEBAR][1])} ;; '
|
|
3121
|
+
f'{STATUS_ACTION_PAGE_PRIMARY}) '
|
|
3122
|
+
f'{_tmux_shell_executable()} send-keys -l -t '
|
|
3123
|
+
f'\'#{{{RAILMUX_CONTROLLER_OPTION}}}\' -- '
|
|
3124
|
+
f'{shlex.quote(RAILMUX_COMPACT_PAGE_KEYS[STATUS_ACTION_PAGE_PRIMARY][1])} ;; '
|
|
3125
|
+
f'{STATUS_ACTION_PAGE_SECONDARY}) '
|
|
3126
|
+
f'{_tmux_shell_executable()} send-keys -l -t '
|
|
3127
|
+
f'\'#{{{RAILMUX_CONTROLLER_OPTION}}}\' -- '
|
|
3128
|
+
f'{shlex.quote(RAILMUX_COMPACT_PAGE_KEYS[STATUS_ACTION_PAGE_SECONDARY][1])} ;; '
|
|
3100
3129
|
'esac"'
|
|
3101
3130
|
)
|
|
3102
3131
|
subprocess.check_call(
|
|
@@ -305,7 +305,7 @@ def _compact_tmux_status_left(
|
|
|
305
305
|
page: WorkspacePage,
|
|
306
306
|
panes: tuple[str | None, str | None, str | None],
|
|
307
307
|
width: int,
|
|
308
|
-
|
|
308
|
+
page_range_wrapper: Callable[[str, str], str] | None = None,
|
|
309
309
|
action_range_wrapper: Callable[[str, str], str] | None = None,
|
|
310
310
|
layout_indicator: str | None = None,
|
|
311
311
|
) -> tuple[str, int]:
|
|
@@ -332,19 +332,26 @@ def _compact_tmux_status_left(
|
|
|
332
332
|
WorkspacePage.PRIMARY,
|
|
333
333
|
WorkspacePage.SECONDARY,
|
|
334
334
|
)
|
|
335
|
+
page_actions = (
|
|
336
|
+
tmux_ctl.STATUS_ACTION_PAGE_SIDEBAR,
|
|
337
|
+
tmux_ctl.STATUS_ACTION_PAGE_PRIMARY,
|
|
338
|
+
tmux_ctl.STATUS_ACTION_PAGE_SECONDARY,
|
|
339
|
+
)
|
|
335
340
|
fg_inactive = "colour231" if error else "colour0"
|
|
336
341
|
fg_active = "colour220" if error else "colour231"
|
|
337
342
|
rendered: list[str] = []
|
|
338
343
|
visible = 0
|
|
339
|
-
for candidate, pane_id, label in zip(
|
|
344
|
+
for candidate, action, pane_id, label in zip(
|
|
345
|
+
pages, page_actions, panes, labels,
|
|
346
|
+
):
|
|
340
347
|
# Keep all three positions stable even before F8 creates Pane 2. A
|
|
341
348
|
# missing pane has no tmux range, so it is visible but not clickable.
|
|
342
349
|
content = (
|
|
343
350
|
f"#[fg={fg_active if candidate is page else fg_inactive}]"
|
|
344
351
|
f"[{label}]"
|
|
345
352
|
)
|
|
346
|
-
if
|
|
347
|
-
content =
|
|
353
|
+
if page_range_wrapper is not None and pane_id is not None:
|
|
354
|
+
content = page_range_wrapper(action, content)
|
|
348
355
|
rendered.append(content)
|
|
349
356
|
visible += len(label) + 2
|
|
350
357
|
mode_content = mode
|
|
@@ -3418,6 +3425,7 @@ class App:
|
|
|
3418
3425
|
for changed in reversed(parked):
|
|
3419
3426
|
self._resume_compact_slot(changed)
|
|
3420
3427
|
return False
|
|
3428
|
+
resume_required = bool(desired is not None and desired.display_parked)
|
|
3421
3429
|
if desired is not None and not self._resume_compact_slot(desired):
|
|
3422
3430
|
fallback_page = workspace.compact_page
|
|
3423
3431
|
fallback_pane = self._pane_for_workspace_page(fallback_page)
|
|
@@ -3435,6 +3443,14 @@ class App:
|
|
|
3435
3443
|
pane_id = self._pane_for_workspace_page(page)
|
|
3436
3444
|
if pane_id is None or not tmux_ctl.pane_alive(pane_id):
|
|
3437
3445
|
return False
|
|
3446
|
+
# swap-pane unzooms a window when the active compact placeholder moves
|
|
3447
|
+
# back to the provider's home window. Re-establish zoom against the
|
|
3448
|
+
# real pane that is now visible before committing the logical page.
|
|
3449
|
+
if resume_required and not self._zoom_pane(pane_id):
|
|
3450
|
+
if announce:
|
|
3451
|
+
self._set_status(
|
|
3452
|
+
"That compact page could not fill the screen.", "warn")
|
|
3453
|
+
return False
|
|
3438
3454
|
|
|
3439
3455
|
workspace.compact_page = page
|
|
3440
3456
|
if page is WorkspacePage.PRIMARY:
|
|
@@ -8332,6 +8348,17 @@ class App:
|
|
|
8332
8348
|
if key == COMPACT_RESIZE_KEY.lower():
|
|
8333
8349
|
self._handle_remote_compact_prepare()
|
|
8334
8350
|
return
|
|
8351
|
+
compact_pages = {
|
|
8352
|
+
"f13": WorkspacePage.SIDEBAR,
|
|
8353
|
+
"f14": WorkspacePage.PRIMARY,
|
|
8354
|
+
"f15": WorkspacePage.SECONDARY,
|
|
8355
|
+
}
|
|
8356
|
+
if key in compact_pages:
|
|
8357
|
+
if (self._agent_workspace().presentation
|
|
8358
|
+
is WorkspacePresentation.COMPACT):
|
|
8359
|
+
self._select_workspace_page(
|
|
8360
|
+
compact_pages[key], announce=True)
|
|
8361
|
+
return
|
|
8335
8362
|
for slot_key, (restore_key, _sequence) in (
|
|
8336
8363
|
tmux_ctl.RAILMUX_HISTORY_RESTORE_KEYS.items()):
|
|
8337
8364
|
if key == restore_key.lower():
|
|
@@ -11785,16 +11812,15 @@ class App:
|
|
|
11785
11812
|
is WorkspacePresentation.COMPACT):
|
|
11786
11813
|
width = (getattr(self, "_last_workspace_size", None) or (80, 24))[0]
|
|
11787
11814
|
manager = getattr(self, "_tmux_binding_manager", None)
|
|
11788
|
-
pane_range_helper = getattr(tmux_ctl, "status_pane_range", None)
|
|
11789
11815
|
action_range_helper = getattr(
|
|
11790
11816
|
tmux_ctl, "status_action_range", None)
|
|
11791
11817
|
wrap_pane = None
|
|
11792
11818
|
wrap_action = None
|
|
11793
11819
|
if (getattr(manager, "status_navigation_available", False)
|
|
11794
|
-
is True and callable(
|
|
11795
|
-
def wrap_pane(
|
|
11820
|
+
is True and callable(action_range_helper)):
|
|
11821
|
+
def wrap_pane(action: str, content: str) -> str:
|
|
11796
11822
|
try:
|
|
11797
|
-
return
|
|
11823
|
+
return action_range_helper(action, content)
|
|
11798
11824
|
except (TypeError, ValueError):
|
|
11799
11825
|
return content
|
|
11800
11826
|
if (getattr(manager, "status_navigation_available", False)
|
|
@@ -320,6 +320,22 @@ class _SemanticTerminalRenderer:
|
|
|
320
320
|
and self._presentation_serial >= self._presentation_required_serial
|
|
321
321
|
)
|
|
322
322
|
|
|
323
|
+
@property
|
|
324
|
+
def pointer_routing_stable(self) -> bool:
|
|
325
|
+
"""Whether the last physical screen still has trusted pointer geometry.
|
|
326
|
+
|
|
327
|
+
An ordinary provider repaint can keep one terminal write in flight for
|
|
328
|
+
long enough to overlap several Windows wheel reports. That does not
|
|
329
|
+
invalidate the already visible pane layout. Explicit topology
|
|
330
|
+
transitions call :meth:`require_fresh_presentation` and remain
|
|
331
|
+
fail-closed until the matching newer frame is physically visible.
|
|
332
|
+
"""
|
|
333
|
+
self._promote_presented_screen()
|
|
334
|
+
return bool(
|
|
335
|
+
self._screen is not None
|
|
336
|
+
and self._presentation_serial >= self._presentation_required_serial
|
|
337
|
+
)
|
|
338
|
+
|
|
323
339
|
def require_fresh_presentation(self) -> None:
|
|
324
340
|
"""Fail pointer input closed until one newer frame is displayed."""
|
|
325
341
|
required = self._presentation_serial + 1
|
|
@@ -1815,14 +1831,19 @@ class LocalPtyClient:
|
|
|
1815
1831
|
part,
|
|
1816
1832
|
logical_height=logical_height,
|
|
1817
1833
|
)
|
|
1818
|
-
|
|
1834
|
+
routing_stable = self._renderer.presentation_stable or (
|
|
1835
|
+
event.wheel_direction != 0
|
|
1836
|
+
and self._renderer.pointer_routing_stable
|
|
1837
|
+
)
|
|
1838
|
+
if not routing_stable:
|
|
1819
1839
|
if not self._selection.capturing and self._selection.cancel():
|
|
1820
1840
|
self._renderer.set_selection(self._selection.segments())
|
|
1821
1841
|
# A queued frame can already represent a new tmux pane topology
|
|
1822
|
-
# while the user still sees the prior one.
|
|
1823
|
-
#
|
|
1824
|
-
#
|
|
1825
|
-
# immutable visible snapshot and therefore
|
|
1842
|
+
# while the user still sees the prior one. Explicitly untrusted
|
|
1843
|
+
# geometry stays fail-closed; an ordinary content-only write may
|
|
1844
|
+
# still route wheel input against the last physical screen. A
|
|
1845
|
+
# capture already owns an immutable visible snapshot and therefore
|
|
1846
|
+
# keeps the whole gesture.
|
|
1826
1847
|
if not self._selection.capturing:
|
|
1827
1848
|
return
|
|
1828
1849
|
if self._selection.capturing:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Tests for F8/F9 dispatch and managed binding lifecycle."""
|
|
2
2
|
import threading
|
|
3
|
-
from unittest.mock import MagicMock
|
|
3
|
+
from unittest.mock import MagicMock, call
|
|
4
4
|
|
|
5
5
|
from railmux import tmux_server
|
|
6
6
|
from railmux.config import Config
|
|
@@ -847,6 +847,27 @@ def test_compact_page_switch_retargets_and_rezooms_without_losing_layout(
|
|
|
847
847
|
False, force_border=True)
|
|
848
848
|
|
|
849
849
|
|
|
850
|
+
def test_private_compact_page_keys_delegate_only_in_compact_presentation():
|
|
851
|
+
app = _bare_app()
|
|
852
|
+
app._select_workspace_page = MagicMock(return_value=True)
|
|
853
|
+
|
|
854
|
+
app._agent_workspace().presentation = WorkspacePresentation.COMPACT
|
|
855
|
+
app._on_input("f14")
|
|
856
|
+
app._on_input("f15")
|
|
857
|
+
app._on_input("f13")
|
|
858
|
+
|
|
859
|
+
assert app._select_workspace_page.call_args_list == [
|
|
860
|
+
call(WorkspacePage.PRIMARY, announce=True),
|
|
861
|
+
call(WorkspacePage.SECONDARY, announce=True),
|
|
862
|
+
call(WorkspacePage.SIDEBAR, announce=True),
|
|
863
|
+
]
|
|
864
|
+
|
|
865
|
+
app._select_workspace_page.reset_mock()
|
|
866
|
+
app._agent_workspace().presentation = WorkspacePresentation.WIDE
|
|
867
|
+
app._on_input("f14")
|
|
868
|
+
app._select_workspace_page.assert_not_called()
|
|
869
|
+
|
|
870
|
+
|
|
850
871
|
def test_full_sidebar_modal_restores_preexisting_agent_zoom(monkeypatch):
|
|
851
872
|
app = _bare_app(_railmux_pane_id="%1")
|
|
852
873
|
app._show_overlay = MagicMock()
|
|
@@ -219,6 +219,7 @@ def test_compact_page_switch_parks_old_agent_before_resuming_target(
|
|
|
219
219
|
|
|
220
220
|
transport.park.assert_called_once_with(workspace.primary)
|
|
221
221
|
transport.resume.assert_called_once_with(workspace.secondary)
|
|
222
|
+
assert app._zoom_pane.call_args_list == [call("%30"), call("%3")]
|
|
222
223
|
assert workspace.compact_page.value == "secondary"
|
|
223
224
|
|
|
224
225
|
|
|
@@ -970,6 +970,9 @@ def test_status_action_range_requires_tmux_34_and_known_action():
|
|
|
970
970
|
tmux_ctl.STATUS_ACTION_MODE,
|
|
971
971
|
tmux_ctl.STATUS_ACTION_LAYOUT,
|
|
972
972
|
tmux_ctl.STATUS_ACTION_COPY,
|
|
973
|
+
tmux_ctl.STATUS_ACTION_PAGE_SIDEBAR,
|
|
974
|
+
tmux_ctl.STATUS_ACTION_PAGE_PRIMARY,
|
|
975
|
+
tmux_ctl.STATUS_ACTION_PAGE_SECONDARY,
|
|
973
976
|
)
|
|
974
977
|
)
|
|
975
978
|
with patch.object(tmux_ctl, "tmux_version", return_value=(3, 3)):
|
|
@@ -985,6 +988,10 @@ def test_status_action_range_requires_tmux_34_and_known_action():
|
|
|
985
988
|
tmux_ctl.STATUS_ACTION_LAYOUT, "◨") == (
|
|
986
989
|
"#[range=control|1]◨#[norange]"
|
|
987
990
|
)
|
|
991
|
+
assert tmux_ctl.status_action_range(
|
|
992
|
+
tmux_ctl.STATUS_ACTION_PAGE_PRIMARY, "[1]") == (
|
|
993
|
+
"#[range=user|railmux-page-1][1]#[norange]"
|
|
994
|
+
)
|
|
988
995
|
try:
|
|
989
996
|
tmux_ctl.status_action_range("railmux-arbitrary", "unsafe")
|
|
990
997
|
except ValueError:
|
|
@@ -1013,7 +1020,7 @@ def test_root_status_click_scopes_ranges_and_dispatches_actions():
|
|
|
1013
1020
|
assert "-t" not in argv[5:8]
|
|
1014
1021
|
assert "mouse_status_range" in argv[7]
|
|
1015
1022
|
assert "%[0-9]+" in argv[7]
|
|
1016
|
-
assert "railmux-(mode|layout|copy)" in argv[7]
|
|
1023
|
+
assert "railmux-(mode|layout|copy|page-[r12])" in argv[7]
|
|
1017
1024
|
assert tmux_ctl.RAILMUX_CONTROLLER_OPTION in argv[7]
|
|
1018
1025
|
assert "railmux-status-pane-v1-owner123" in argv[7]
|
|
1019
1026
|
assert "tmux select-pane -Z -t '#{mouse_status_range}'" in argv[8]
|
|
@@ -1023,6 +1030,11 @@ def test_root_status_click_scopes_ranges_and_dispatches_actions():
|
|
|
1023
1030
|
assert "' F7 ;;" in argv[8]
|
|
1024
1031
|
assert "railmux-copy) tmux send-keys" in argv[8]
|
|
1025
1032
|
assert "' F6 ;;" in argv[8]
|
|
1033
|
+
assert "railmux-page-r) tmux send-keys -l" in argv[8]
|
|
1034
|
+
assert "railmux-page-1) tmux send-keys -l" in argv[8]
|
|
1035
|
+
assert "railmux-page-2) tmux send-keys -l" in argv[8]
|
|
1036
|
+
for _key, sequence in tmux_ctl.RAILMUX_COMPACT_PAGE_KEYS.values():
|
|
1037
|
+
assert sequence in argv[8]
|
|
1026
1038
|
assert argv[-1] == "select-window -t ="
|
|
1027
1039
|
|
|
1028
1040
|
|
|
@@ -3517,18 +3517,20 @@ def test_real_tmux_status_pane_range_selects_and_keeps_zoom(
|
|
|
3517
3517
|
# status range beneath the pointer.
|
|
3518
3518
|
os.write(master_fd, release)
|
|
3519
3519
|
|
|
3520
|
-
# Exercise
|
|
3521
|
-
#
|
|
3522
|
-
#
|
|
3520
|
+
# Exercise the compact page action used by the product UI. A raw-mode
|
|
3521
|
+
# reader in the controller records its private sequence; the click
|
|
3522
|
+
# must not select/unzoom a pane before the controller performs the
|
|
3523
|
+
# complete park/zoom/resume transaction.
|
|
3523
3524
|
ready_path = tmp_path / "status-action-ready"
|
|
3524
3525
|
key_path = tmp_path / "status-action-key"
|
|
3525
3526
|
reader = (
|
|
3526
|
-
"import os,termios,pathlib;"
|
|
3527
|
+
"import os,termios,pathlib,time;"
|
|
3527
3528
|
"a=termios.tcgetattr(0);"
|
|
3528
3529
|
"a[3]&=~(termios.ICANON|termios.ECHO);"
|
|
3529
3530
|
"termios.tcsetattr(0,termios.TCSANOW,a);"
|
|
3530
3531
|
f"pathlib.Path({str(ready_path)!r}).touch();"
|
|
3531
|
-
f"pathlib.Path({str(key_path)!r}).write_bytes(os.read(0,32))"
|
|
3532
|
+
f"pathlib.Path({str(key_path)!r}).write_bytes(os.read(0,32));"
|
|
3533
|
+
"time.sleep(10)"
|
|
3532
3534
|
)
|
|
3533
3535
|
subprocess.run(
|
|
3534
3536
|
[
|
|
@@ -3542,7 +3544,7 @@ def test_real_tmux_status_pane_range_selects_and_keeps_zoom(
|
|
|
3542
3544
|
[
|
|
3543
3545
|
"tmux", "set-option", "-t", display_session, "status-left",
|
|
3544
3546
|
tmux_ctl.status_action_range(
|
|
3545
|
-
tmux_ctl.
|
|
3547
|
+
tmux_ctl.STATUS_ACTION_PAGE_PRIMARY, "[1]"),
|
|
3546
3548
|
],
|
|
3547
3549
|
check=True,
|
|
3548
3550
|
)
|
|
@@ -3553,7 +3555,7 @@ def test_real_tmux_status_pane_range_selects_and_keeps_zoom(
|
|
|
3553
3555
|
action_painted = bytearray()
|
|
3554
3556
|
action_deadline = time.monotonic() + 3.0
|
|
3555
3557
|
while (time.monotonic() < action_deadline
|
|
3556
|
-
and b"[
|
|
3558
|
+
and b"[1]" not in action_painted):
|
|
3557
3559
|
readable, _, _ = select.select([master_fd], [], [], 0.1)
|
|
3558
3560
|
if not readable:
|
|
3559
3561
|
continue
|
|
@@ -3561,13 +3563,23 @@ def test_real_tmux_status_pane_range_selects_and_keeps_zoom(
|
|
|
3561
3563
|
action_painted.extend(os.read(master_fd, 65536))
|
|
3562
3564
|
except BlockingIOError:
|
|
3563
3565
|
pass
|
|
3564
|
-
assert b"[
|
|
3566
|
+
assert b"[1]" in action_painted
|
|
3565
3567
|
# Keep this a second ordinary click rather than tmux coalescing the two
|
|
3566
3568
|
# synthetic presses into DoubleClick1Status (a different key binding).
|
|
3567
3569
|
time.sleep(0.6)
|
|
3568
3570
|
os.write(master_fd, click)
|
|
3569
3571
|
assert _wait_until(key_path.exists)
|
|
3570
|
-
assert key_path.read_bytes()
|
|
3572
|
+
assert key_path.read_bytes() == (
|
|
3573
|
+
tmux_ctl.RAILMUX_COMPACT_PAGE_KEYS[
|
|
3574
|
+
tmux_ctl.STATUS_ACTION_PAGE_PRIMARY
|
|
3575
|
+
][1].encode()
|
|
3576
|
+
)
|
|
3577
|
+
assert tmux_ctl.active_pane_id(owner_pane) == other_pane
|
|
3578
|
+
assert subprocess.check_output(
|
|
3579
|
+
["tmux", "display-message", "-p", "-t", other_pane,
|
|
3580
|
+
"#{window_zoomed_flag}"],
|
|
3581
|
+
text=True,
|
|
3582
|
+
).strip() == "1"
|
|
3571
3583
|
finally:
|
|
3572
3584
|
if client.poll() is None:
|
|
3573
3585
|
client.kill()
|
|
@@ -367,6 +367,31 @@ def test_compact_status_left_wraps_mode_and_layout_as_actions():
|
|
|
367
367
|
assert visible == len("[R][1][2] CC · ◨ ")
|
|
368
368
|
|
|
369
369
|
|
|
370
|
+
def test_compact_status_left_routes_available_pages_through_controller_actions():
|
|
371
|
+
pages = []
|
|
372
|
+
|
|
373
|
+
def wrap_page(action, content):
|
|
374
|
+
pages.append((action, content))
|
|
375
|
+
return f"<{action}>{content}</{action}>"
|
|
376
|
+
|
|
377
|
+
value, _visible = _compact_tmux_status_left(
|
|
378
|
+
False,
|
|
379
|
+
"Codex",
|
|
380
|
+
WorkspacePage.PRIMARY,
|
|
381
|
+
("%1", "%2", None),
|
|
382
|
+
40,
|
|
383
|
+
page_range_wrapper=wrap_page,
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
assert pages == [
|
|
387
|
+
("railmux-page-r", "#[fg=colour0][R]"),
|
|
388
|
+
("railmux-page-1", "#[fg=colour231][1]"),
|
|
389
|
+
]
|
|
390
|
+
assert "<railmux-page-r>" in value
|
|
391
|
+
assert "<railmux-page-1>" in value
|
|
392
|
+
assert "<railmux-page-2>" not in value
|
|
393
|
+
|
|
394
|
+
|
|
370
395
|
def test_apply_bar_uses_dynamic_compact_left_length(monkeypatch):
|
|
371
396
|
run = MagicMock()
|
|
372
397
|
monkeypatch.setattr("subprocess.run", run)
|
|
@@ -413,9 +438,9 @@ def test_apply_compact_bar_makes_pages_mode_and_layout_clickable(monkeypatch):
|
|
|
413
438
|
app._apply_tmux_bar(error=False)
|
|
414
439
|
|
|
415
440
|
left = _style_calls(run, "status-left")[-1]
|
|
416
|
-
assert "#[range=user
|
|
417
|
-
assert "#[range=user
|
|
418
|
-
assert "#[range=user
|
|
441
|
+
assert "#[range=user|railmux-page-r]" in left
|
|
442
|
+
assert "#[range=user|railmux-page-1]" in left
|
|
443
|
+
assert "#[range=user|railmux-page-2]" in left
|
|
419
444
|
assert "#[range=user|railmux-mode]CC#[norange]" in left
|
|
420
445
|
assert "#[range=user|railmux-layout]◨#[norange]" in left
|
|
421
446
|
visible = len("[R][1][2] CC · ◨ ")
|
|
@@ -430,6 +430,114 @@ def test_local_windows_wheel_uses_shared_managed_history_without_tmux_copy_mode(
|
|
|
430
430
|
child_socket.close()
|
|
431
431
|
|
|
432
432
|
|
|
433
|
+
def test_local_windows_real_sgr_wheel_survives_busy_content_paint():
|
|
434
|
+
proxy_socket, child_socket = socket.socketpair()
|
|
435
|
+
client = windows_attach_relay.LocalPtyClient.__new__(
|
|
436
|
+
windows_attach_relay.LocalPtyClient
|
|
437
|
+
)
|
|
438
|
+
client.master_fd = proxy_socket.detach()
|
|
439
|
+
client._session_id = "$4"
|
|
440
|
+
client._selection = LocalTextSelection()
|
|
441
|
+
client._routes = ()
|
|
442
|
+
client._routes_checked_at = time.monotonic()
|
|
443
|
+
client._path_prompt = None
|
|
444
|
+
client._path_prompt_mouse_button = None
|
|
445
|
+
_set_local_history_state(client)
|
|
446
|
+
_install_local_history_snapshot(
|
|
447
|
+
client,
|
|
448
|
+
HistorySnapshot(
|
|
449
|
+
0,
|
|
450
|
+
"%8",
|
|
451
|
+
0,
|
|
452
|
+
0,
|
|
453
|
+
20,
|
|
454
|
+
2,
|
|
455
|
+
lines=tuple(f"row-{index}".encode() for index in range(8)),
|
|
456
|
+
),
|
|
457
|
+
)
|
|
458
|
+
renderer = MagicMock()
|
|
459
|
+
renderer.presentation_stable = False
|
|
460
|
+
renderer.pointer_routing_stable = True
|
|
461
|
+
renderer.screen = SimpleNamespace(
|
|
462
|
+
width=20,
|
|
463
|
+
height=3,
|
|
464
|
+
cursor_x=1,
|
|
465
|
+
cursor_y=0,
|
|
466
|
+
rows=(b"live-0", b"live-1", b"status"),
|
|
467
|
+
)
|
|
468
|
+
renderer.surface.translate_mouse_event.side_effect = lambda event, **_kwargs: event
|
|
469
|
+
client._renderer = renderer
|
|
470
|
+
decoder = windows_attach_relay.TerminalInputDecoder()
|
|
471
|
+
child_socket.setblocking(False)
|
|
472
|
+
try:
|
|
473
|
+
parts = decoder.feed(b"\033[<64;2;1M")
|
|
474
|
+
assert len(parts) == 1
|
|
475
|
+
client._forward_input_part(parts[0])
|
|
476
|
+
|
|
477
|
+
assert client._history.active
|
|
478
|
+
renderer.set_history_overlays.assert_called_with(
|
|
479
|
+
client._history.overlays()
|
|
480
|
+
)
|
|
481
|
+
with pytest.raises(BlockingIOError):
|
|
482
|
+
child_socket.recv(64)
|
|
483
|
+
finally:
|
|
484
|
+
os.close(client.master_fd)
|
|
485
|
+
child_socket.close()
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def test_local_windows_wheel_stays_fail_closed_during_topology_transition():
|
|
489
|
+
proxy_socket, child_socket = socket.socketpair()
|
|
490
|
+
client = windows_attach_relay.LocalPtyClient.__new__(
|
|
491
|
+
windows_attach_relay.LocalPtyClient
|
|
492
|
+
)
|
|
493
|
+
client.master_fd = proxy_socket.detach()
|
|
494
|
+
client._session_id = "$4"
|
|
495
|
+
client._selection = LocalTextSelection()
|
|
496
|
+
client._routes = ()
|
|
497
|
+
client._routes_checked_at = time.monotonic()
|
|
498
|
+
client._path_prompt = None
|
|
499
|
+
client._path_prompt_mouse_button = None
|
|
500
|
+
_set_local_history_state(client)
|
|
501
|
+
_install_local_history_snapshot(
|
|
502
|
+
client,
|
|
503
|
+
HistorySnapshot(
|
|
504
|
+
0,
|
|
505
|
+
"%8",
|
|
506
|
+
0,
|
|
507
|
+
0,
|
|
508
|
+
20,
|
|
509
|
+
2,
|
|
510
|
+
lines=tuple(f"row-{index}".encode() for index in range(8)),
|
|
511
|
+
),
|
|
512
|
+
)
|
|
513
|
+
renderer = MagicMock()
|
|
514
|
+
renderer.presentation_stable = False
|
|
515
|
+
renderer.pointer_routing_stable = False
|
|
516
|
+
renderer.screen = SimpleNamespace(
|
|
517
|
+
width=20,
|
|
518
|
+
height=3,
|
|
519
|
+
cursor_x=1,
|
|
520
|
+
cursor_y=0,
|
|
521
|
+
rows=(b"live-0", b"live-1", b"status"),
|
|
522
|
+
)
|
|
523
|
+
renderer.surface.translate_mouse_event.side_effect = lambda event, **_kwargs: event
|
|
524
|
+
client._renderer = renderer
|
|
525
|
+
decoder = windows_attach_relay.TerminalInputDecoder()
|
|
526
|
+
child_socket.setblocking(False)
|
|
527
|
+
try:
|
|
528
|
+
parts = decoder.feed(b"\033[<64;2;1M")
|
|
529
|
+
assert len(parts) == 1
|
|
530
|
+
client._forward_input_part(parts[0])
|
|
531
|
+
|
|
532
|
+
assert not client._history.active
|
|
533
|
+
renderer.set_history_overlays.assert_not_called()
|
|
534
|
+
with pytest.raises(BlockingIOError):
|
|
535
|
+
child_socket.recv(64)
|
|
536
|
+
finally:
|
|
537
|
+
os.close(client.master_fd)
|
|
538
|
+
child_socket.close()
|
|
539
|
+
|
|
540
|
+
|
|
433
541
|
def test_local_windows_sidebar_wheel_stays_tmux_owned():
|
|
434
542
|
proxy_socket, child_socket = socket.socketpair()
|
|
435
543
|
client = windows_attach_relay.LocalPtyClient.__new__(
|
|
@@ -1199,18 +1307,24 @@ def test_semantic_renderer_hit_testing_waits_for_physical_frame(monkeypatch):
|
|
|
1199
1307
|
assert renderer.screen is old_screen
|
|
1200
1308
|
assert renderer.presentation_serial == old_serial
|
|
1201
1309
|
assert renderer.presentation_stable is False
|
|
1310
|
+
# Ordinary content backpressure does not invalidate the pane geometry
|
|
1311
|
+
# of the frame that is still physically visible.
|
|
1312
|
+
assert renderer.pointer_routing_stable is True
|
|
1202
1313
|
|
|
1203
1314
|
# A topology mutation requested while this already-queued frame is
|
|
1204
1315
|
# writing must wait for one still-newer authoritative frame.
|
|
1205
1316
|
renderer.require_fresh_presentation()
|
|
1317
|
+
assert renderer.pointer_routing_stable is False
|
|
1206
1318
|
release.set()
|
|
1207
1319
|
assert renderer.writer.wait_idle(1.0)
|
|
1208
1320
|
assert renderer.presentation_stable is False
|
|
1321
|
+
assert renderer.pointer_routing_stable is False
|
|
1209
1322
|
assert renderer.presentation_serial == old_serial + 1
|
|
1210
1323
|
renderer.feed(b"\033[H\033[2KNEW-TOPOLOGY-VISIBLE")
|
|
1211
1324
|
renderer.paint_due(3.0, force=True)
|
|
1212
1325
|
assert renderer.writer.wait_idle(1.0)
|
|
1213
1326
|
assert renderer.presentation_stable is True
|
|
1327
|
+
assert renderer.pointer_routing_stable is True
|
|
1214
1328
|
assert b"NEW-TOPOLOGY-VISIBLE" in renderer.screen.rows[0]
|
|
1215
1329
|
finally:
|
|
1216
1330
|
release.set()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.1.dev3"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|