railmux 0.4.1.dev1__tar.gz → 0.4.1.dev2__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.dev1/src/railmux.egg-info → railmux-0.4.1.dev2}/PKG-INFO +1 -1
- railmux-0.4.1.dev2/src/railmux/__init__.py +1 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/fast_display_input.py +173 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/windows_attach_relay.py +49 -5
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2/src/railmux.egg-info}/PKG-INFO +1 -1
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_fast_display_selection.py +112 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_windows_attach_relay.py +74 -0
- railmux-0.4.1.dev1/src/railmux/__init__.py +0 -1
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/LICENSE +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/MANIFEST.in +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/README.md +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/pyproject.toml +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/setup.cfg +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/__main__.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/atomic_file.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/background_index.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/cli.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/codex_index.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/config.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/config_cli.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/diagnostic_contract.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/diagnostics.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/discovery.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/display_transport.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/entrypoint.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/fast_display_client.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/fast_display_history.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/fast_display_protocol.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/fast_display_server.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/favorites.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/fuzzy.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/help_workspace.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/launcher.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/legacy_sessions.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/local_clipboard.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/local_open.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/models.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/modes.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/mouse_manager.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/orphan_marker.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/pane_surface.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/path_codec.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/preview_pager.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/project_favorites.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/provider_paths.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/release_version.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/remote_config.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/renames.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/restart_state.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/runtime_config.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/scroll_agent.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/scroll_manager.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/selection_isolation.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/self_update.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/session_cache.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/session_index.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/session_lease.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/setting_contracts.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/settings.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ssh_args.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ssh_compat.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ssh_display_diagnostics.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ssh_doctor.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ssh_preflight.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/system_deps.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/terminal_screen.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/terminal_status.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/tmux_binding_manager.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/tmux_capabilities.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/tmux_ctl.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/tmux_health.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/tmux_server.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/tool_panes.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/transcript.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/__init__.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/_widgets.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/app.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/keymap.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/modals.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/projects_pane.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/running_pane.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/sessions_pane.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/sidebar.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/statusbar.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/ui/workspace.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/windows_bootstrap.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/windows_install_log.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/windows_msys2.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/windows_pacman.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/windows_paths.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/windows_tmux_lifecycle.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux/windows_ui_transition.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux.egg-info/SOURCES.txt +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux.egg-info/dependency_links.txt +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux.egg-info/entry_points.txt +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux.egg-info/requires.txt +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/src/railmux.egg-info/top_level.txt +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/app_test_harness.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_agent_workspace.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_app_codex_placeholder.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_app_quit.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_app_restart_restore.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_app_running_recovery.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_atomic_file.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_background_index.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_cli.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_code_map.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_codex_delete.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_codex_index.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_codex_lineage.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_codex_transcript.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_config.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_config_cli.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_denested_transport_benchmark.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_diagnostics.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_discovery.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_display_transport.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_fast_display_bootstrap.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_fast_display_history.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_fast_display_protocol.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_fast_display_server.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_fast_display_terminal.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_focus_highlight.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_fullscreen_binding.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_fuzzy.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_help_agent.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_help_workspace.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_history_cleanup.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_keymap.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_launcher.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_layout_profile.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_legacy_sessions.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_local_clipboard.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_local_open.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_modals.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_mode_selection.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_modes.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_mouse_manager.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_new_project.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_options.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_orphan_recovery.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_pane_surface.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_path_codec.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_preview_pager.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_project_favorites.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_projects_pane.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_provider_paths.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_release_notes.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_release_version.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_release_workflow.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_remote_config.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_renames.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_restart_state.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_running_pane.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_running_sort.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_runtime_config.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_scroll_agent.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_scroll_manager.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_selection_isolation.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_self_update.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_session_cache.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_session_index.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_session_lease.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_sessions_pane.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_setting_contracts.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_settings.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_sidebar.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_ssh_args.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_ssh_compat.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_ssh_display_benchmark.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_ssh_display_diagnostics.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_ssh_doctor.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_status.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_statusbar.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_system_deps.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_terminal_size.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_terminal_status.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_termux_local_touch.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_tmux_binding_manager.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_tmux_capabilities.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_tmux_ctl.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_tmux_health.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_tmux_integration.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_tmux_server.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_tmux_status.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_tool_panes.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_transcript.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_utf8_state.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_web_demo_recorder.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_wheel_smoke.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_widgets.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_windows_bootstrap.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_windows_install_log.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_windows_msys2.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_windows_pacman.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_windows_paths.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_windows_synchronized_screen.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_windows_tmux_lifecycle.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_windows_ui_transition.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_windows_wrapper_contract.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tests/test_yolo_prompt.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tools/denested_transport_benchmark.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tools/release_notes.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tools/wheel_smoke.py +0 -0
- {railmux-0.4.1.dev1 → railmux-0.4.1.dev2}/tools/windows_archive_smoke.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.1.dev2"
|
|
@@ -550,6 +550,8 @@ _EMBEDDED_WINDOWS_PATH_RE = re.compile(
|
|
|
550
550
|
r"(?:^|(?<=[^A-Za-z0-9_.@+~\\/-]))"
|
|
551
551
|
r"(?:[A-Za-z]:[\\/]|\\\\[^\\/\s]+[\\/][^\\/\s]+(?:[\\/]|$))"
|
|
552
552
|
)
|
|
553
|
+
_TOOL_COMMAND_START_RE = re.compile(r"^\s*[•●]\s+Ran\s+")
|
|
554
|
+
_TOOL_COMMAND_CONTINUATION_RE = re.compile(r"^\s*│\s?")
|
|
553
555
|
|
|
554
556
|
|
|
555
557
|
def _clicked_character_index(
|
|
@@ -893,6 +895,169 @@ def _hard_wrapped_target(
|
|
|
893
895
|
return best
|
|
894
896
|
|
|
895
897
|
|
|
898
|
+
def _decorated_tool_target(
|
|
899
|
+
rows: tuple[tuple[str | None, ...], ...],
|
|
900
|
+
row: int,
|
|
901
|
+
column: int,
|
|
902
|
+
*,
|
|
903
|
+
route: HistorySnapshot,
|
|
904
|
+
) -> ClickTarget | None:
|
|
905
|
+
"""Join a token split across Codex's decorated ``Ran`` command rows."""
|
|
906
|
+
if not 0 <= row < len(rows) or route.width <= 0:
|
|
907
|
+
return None
|
|
908
|
+
|
|
909
|
+
# A continuation glyph alone is not authority: it can occur in ordinary
|
|
910
|
+
# program output. Walk back through one bounded chain and require Codex's
|
|
911
|
+
# explicit ``Ran`` header before interpreting the prefixes as decoration.
|
|
912
|
+
block_first = row
|
|
913
|
+
while (
|
|
914
|
+
block_first > 0
|
|
915
|
+
and row - block_first < 7
|
|
916
|
+
and _TOOL_COMMAND_CONTINUATION_RE.match(_row_text(rows[block_first]))
|
|
917
|
+
):
|
|
918
|
+
block_first -= 1
|
|
919
|
+
if _TOOL_COMMAND_START_RE.match(_row_text(rows[block_first])) is None:
|
|
920
|
+
return None
|
|
921
|
+
|
|
922
|
+
block_last = block_first
|
|
923
|
+
while (
|
|
924
|
+
block_last + 1 < len(rows)
|
|
925
|
+
and block_last - block_first < 7
|
|
926
|
+
and _TOOL_COMMAND_CONTINUATION_RE.match(_row_text(rows[block_last + 1]))
|
|
927
|
+
):
|
|
928
|
+
block_last += 1
|
|
929
|
+
if not block_first <= row <= block_last:
|
|
930
|
+
return None
|
|
931
|
+
|
|
932
|
+
best: ClickTarget | None = None
|
|
933
|
+
for first in range(block_first, row + 1):
|
|
934
|
+
first_text = _row_text(rows[first])
|
|
935
|
+
prefix = (
|
|
936
|
+
_TOOL_COMMAND_START_RE.match(first_text)
|
|
937
|
+
if first == block_first
|
|
938
|
+
else _TOOL_COMMAND_CONTINUATION_RE.match(first_text)
|
|
939
|
+
)
|
|
940
|
+
if prefix is None:
|
|
941
|
+
continue
|
|
942
|
+
tail = re.search(r"""[^\s<>"'`]+\s*$""", first_text[prefix.end() :])
|
|
943
|
+
if tail is None:
|
|
944
|
+
continue
|
|
945
|
+
fragment = tail.group(0).rstrip()
|
|
946
|
+
fragment_index = prefix.end() + tail.start()
|
|
947
|
+
fragment, fragment_index, _fragment_end = _trim_click_token(
|
|
948
|
+
fragment,
|
|
949
|
+
fragment_index,
|
|
950
|
+
fragment_index + len(fragment),
|
|
951
|
+
)
|
|
952
|
+
lower = fragment.lower()
|
|
953
|
+
is_url = lower.startswith(("http://", "https://"))
|
|
954
|
+
is_path = (
|
|
955
|
+
_path_parts(fragment) is not None
|
|
956
|
+
or fragment.startswith(("/", "./", "../", "~/"))
|
|
957
|
+
or _WINDOWS_ABSOLUTE_PATH_RE.match(fragment) is not None
|
|
958
|
+
)
|
|
959
|
+
if not (is_url or is_path):
|
|
960
|
+
continue
|
|
961
|
+
fragment_column = _display_column(first_text, fragment_index)
|
|
962
|
+
fragments: list[tuple[int, int, str]] = [(first, fragment_column, fragment)]
|
|
963
|
+
joined = fragment
|
|
964
|
+
previous_end = fragment_column + sum(
|
|
965
|
+
_display_width(character) for character in fragment
|
|
966
|
+
)
|
|
967
|
+
|
|
968
|
+
for following in range(first + 1, block_last + 1):
|
|
969
|
+
# The formatter may place a new shell token on the following
|
|
970
|
+
# decorated row. Only a fragment ending near the physical edge is
|
|
971
|
+
# proof that the token itself, rather than the command, wrapped.
|
|
972
|
+
if route.width - previous_end > 8:
|
|
973
|
+
break
|
|
974
|
+
following_text = _row_text(rows[following])
|
|
975
|
+
following_prefix = _TOOL_COMMAND_CONTINUATION_RE.match(following_text)
|
|
976
|
+
if following_prefix is None:
|
|
977
|
+
break
|
|
978
|
+
continuation = re.match(
|
|
979
|
+
r"""([^\s<>"'`]+)""",
|
|
980
|
+
following_text[following_prefix.end() :],
|
|
981
|
+
)
|
|
982
|
+
if continuation is None:
|
|
983
|
+
break
|
|
984
|
+
next_fragment = continuation.group(1)
|
|
985
|
+
next_index = following_prefix.end() + continuation.start()
|
|
986
|
+
next_fragment, next_index, _next_end = _trim_click_token(
|
|
987
|
+
next_fragment,
|
|
988
|
+
next_index,
|
|
989
|
+
next_index + len(next_fragment),
|
|
990
|
+
)
|
|
991
|
+
if not next_fragment:
|
|
992
|
+
break
|
|
993
|
+
if is_path and (
|
|
994
|
+
next_fragment[0] not in "._~" and not next_fragment[0].isalnum()
|
|
995
|
+
):
|
|
996
|
+
break
|
|
997
|
+
if is_url and (
|
|
998
|
+
next_fragment[0] not in "._~/%?&#"
|
|
999
|
+
and not next_fragment[0].isalnum()
|
|
1000
|
+
):
|
|
1001
|
+
break
|
|
1002
|
+
next_column = _display_column(following_text, next_index)
|
|
1003
|
+
fragments.append((following, next_column, next_fragment))
|
|
1004
|
+
joined += next_fragment
|
|
1005
|
+
previous_end = next_column + sum(
|
|
1006
|
+
_display_width(character) for character in next_fragment
|
|
1007
|
+
)
|
|
1008
|
+
|
|
1009
|
+
clicked = any(
|
|
1010
|
+
fragment_row == row
|
|
1011
|
+
and start_column
|
|
1012
|
+
<= column
|
|
1013
|
+
< start_column
|
|
1014
|
+
+ sum(_display_width(character) for character in fragment_text)
|
|
1015
|
+
for fragment_row, start_column, fragment_text in fragments
|
|
1016
|
+
)
|
|
1017
|
+
if not clicked:
|
|
1018
|
+
continue
|
|
1019
|
+
if is_url:
|
|
1020
|
+
value = _bounded_url_token(joined)
|
|
1021
|
+
parsed = urlsplit(value)
|
|
1022
|
+
if (
|
|
1023
|
+
parsed.scheme.lower() not in ("http", "https")
|
|
1024
|
+
or parsed.hostname is None
|
|
1025
|
+
or len(value.encode("utf-8")) > 8192
|
|
1026
|
+
):
|
|
1027
|
+
continue
|
|
1028
|
+
kind = "url"
|
|
1029
|
+
line = None
|
|
1030
|
+
target_column = None
|
|
1031
|
+
else:
|
|
1032
|
+
path = _path_parts(joined)
|
|
1033
|
+
if path is None:
|
|
1034
|
+
continue
|
|
1035
|
+
value, line, target_column = path
|
|
1036
|
+
kind = "path"
|
|
1037
|
+
segments = tuple(
|
|
1038
|
+
(
|
|
1039
|
+
route.y + fragment_row,
|
|
1040
|
+
route.x + start_column,
|
|
1041
|
+
fragment_text.encode("utf-8"),
|
|
1042
|
+
)
|
|
1043
|
+
for fragment_row, start_column, fragment_text in fragments
|
|
1044
|
+
)
|
|
1045
|
+
candidate = ClickTarget(
|
|
1046
|
+
kind,
|
|
1047
|
+
value,
|
|
1048
|
+
route.pane_id or "",
|
|
1049
|
+
line,
|
|
1050
|
+
target_column,
|
|
1051
|
+
highlight_row=segments[0][0],
|
|
1052
|
+
highlight_column=segments[0][1],
|
|
1053
|
+
highlight_text=segments[0][2],
|
|
1054
|
+
highlight_segments=segments,
|
|
1055
|
+
)
|
|
1056
|
+
if best is None or len(candidate.value) > len(best.value):
|
|
1057
|
+
best = candidate
|
|
1058
|
+
return best
|
|
1059
|
+
|
|
1060
|
+
|
|
896
1061
|
def _highlight_runs(
|
|
897
1062
|
target: ClickTarget,
|
|
898
1063
|
*,
|
|
@@ -963,6 +1128,14 @@ def _target_in_rows(
|
|
|
963
1128
|
"""Resolve a semantic target across one bounded visual-wrap chain."""
|
|
964
1129
|
if not 0 <= row < len(rows) or route.width <= 0:
|
|
965
1130
|
return None
|
|
1131
|
+
decorated = _decorated_tool_target(
|
|
1132
|
+
rows,
|
|
1133
|
+
row,
|
|
1134
|
+
column,
|
|
1135
|
+
route=route,
|
|
1136
|
+
)
|
|
1137
|
+
if decorated is not None:
|
|
1138
|
+
return decorated
|
|
966
1139
|
# A full terminal row followed by a non-indented row is an authoritative
|
|
967
1140
|
# visual soft-wrap chain. Do not let the more permissive agent hard-wrap
|
|
968
1141
|
# heuristic accept a shorter prefix first: on a three-row URL that made
|
|
@@ -1236,6 +1236,8 @@ class LocalPtyClient:
|
|
|
1236
1236
|
self._session_id = session_id
|
|
1237
1237
|
self._input_decoder = TerminalInputDecoder()
|
|
1238
1238
|
self._selection = LocalTextSelection()
|
|
1239
|
+
self._passthrough_left_gesture = False
|
|
1240
|
+
self._suppressed_left_gesture = False
|
|
1239
1241
|
self._routes: tuple[HistorySnapshot, ...] = ()
|
|
1240
1242
|
self._routes_checked_at = 0.0
|
|
1241
1243
|
self._routes_force_refresh = True
|
|
@@ -1508,15 +1510,50 @@ class LocalPtyClient:
|
|
|
1508
1510
|
part,
|
|
1509
1511
|
logical_height=logical_height,
|
|
1510
1512
|
)
|
|
1513
|
+
left_drag = (
|
|
1514
|
+
event.pressed
|
|
1515
|
+
and bool(event.button & 32)
|
|
1516
|
+
and event.button & 3 == 0
|
|
1517
|
+
and not event.button & 64
|
|
1518
|
+
)
|
|
1519
|
+
left_release = not event.pressed and event.button & 3 == 0
|
|
1520
|
+
plain_left_press = LocalTextSelection._is_plain_left_press(event)
|
|
1521
|
+
if getattr(self, "_suppressed_left_gesture", False):
|
|
1522
|
+
if left_drag or left_release:
|
|
1523
|
+
if left_release:
|
|
1524
|
+
self._suppressed_left_gesture = False
|
|
1525
|
+
return
|
|
1526
|
+
if plain_left_press:
|
|
1527
|
+
self._suppressed_left_gesture = False
|
|
1528
|
+
if getattr(self, "_passthrough_left_gesture", False):
|
|
1529
|
+
if left_drag or left_release:
|
|
1530
|
+
if left_release:
|
|
1531
|
+
self._passthrough_left_gesture = False
|
|
1532
|
+
_write_pty_input(self.master_fd, event.raw)
|
|
1533
|
+
return
|
|
1534
|
+
if plain_left_press:
|
|
1535
|
+
self._passthrough_left_gesture = False
|
|
1511
1536
|
if not self._renderer.presentation_stable:
|
|
1512
|
-
if self._selection.cancel():
|
|
1537
|
+
if not self._selection.capturing and self._selection.cancel():
|
|
1513
1538
|
self._renderer.set_selection(self._selection.segments())
|
|
1514
1539
|
# A queued frame can already represent a new tmux pane topology
|
|
1515
1540
|
# while the user still sees the prior one. Dropping only pointer
|
|
1516
|
-
# input during this bounded transition is safer than forwarding
|
|
1517
|
-
#
|
|
1518
|
-
|
|
1519
|
-
|
|
1541
|
+
# input during this bounded transition is safer than forwarding it
|
|
1542
|
+
# to Quit or stock tmux copy-mode. A capture already owns an
|
|
1543
|
+
# immutable visible snapshot and therefore keeps the whole gesture.
|
|
1544
|
+
if not self._selection.capturing:
|
|
1545
|
+
if plain_left_press or left_drag:
|
|
1546
|
+
self._suppressed_left_gesture = not left_release
|
|
1547
|
+
return
|
|
1548
|
+
if self._selection.capturing:
|
|
1549
|
+
# Once the newer frame is physically visible, revalidate the
|
|
1550
|
+
# captured route before consuming another event. During a queued
|
|
1551
|
+
# transition the old snapshot remains the visible authority.
|
|
1552
|
+
if self._renderer.presentation_stable:
|
|
1553
|
+
self._refresh_routes()
|
|
1554
|
+
source = None
|
|
1555
|
+
else:
|
|
1556
|
+
source = self._selection_source(event)
|
|
1520
1557
|
if event.is_hover_motion:
|
|
1521
1558
|
if self._selection.hover(event, source):
|
|
1522
1559
|
self._renderer.set_selection(self._selection.segments())
|
|
@@ -1533,6 +1570,13 @@ class LocalPtyClient:
|
|
|
1533
1570
|
if action.open_target is not None:
|
|
1534
1571
|
self._open_target(action.open_target)
|
|
1535
1572
|
if not action.handled:
|
|
1573
|
+
if plain_left_press:
|
|
1574
|
+
self._passthrough_left_gesture = True
|
|
1575
|
+
elif left_drag or left_release:
|
|
1576
|
+
# Never give tmux half a left-button gesture. An orphan means
|
|
1577
|
+
# its press was suppressed or a captured route was invalidated.
|
|
1578
|
+
self._suppressed_left_gesture = left_drag
|
|
1579
|
+
return
|
|
1536
1580
|
_write_pty_input(self.master_fd, event.raw)
|
|
1537
1581
|
|
|
1538
1582
|
def poll(self) -> int | None:
|
|
@@ -677,6 +677,118 @@ def test_local_text_selection_resolves_all_three_soft_wrapped_url_rows():
|
|
|
677
677
|
assert action.open_target.value == expected
|
|
678
678
|
|
|
679
679
|
|
|
680
|
+
def test_local_text_selection_joins_url_across_codex_ran_decoration():
|
|
681
|
+
first = "• Ran curl https://example.test/releases/rail"
|
|
682
|
+
second = " │ mux/0.4.1.dev1/index.html --head"
|
|
683
|
+
width = max(len(first), len(second))
|
|
684
|
+
route = HistorySnapshot(1, "%8", 3, 2, width, 2)
|
|
685
|
+
source = SelectionSource(
|
|
686
|
+
route,
|
|
687
|
+
tuple((line + " " * (width - len(line))).encode() for line in (first, second)),
|
|
688
|
+
0,
|
|
689
|
+
)
|
|
690
|
+
expected = "https://example.test/releases/railmux/0.4.1.dev1/index.html"
|
|
691
|
+
expected_segments = (
|
|
692
|
+
(2, 3 + first.index("https://"), b"https://example.test/releases/rail"),
|
|
693
|
+
(3, 3 + second.index("mux/"), b"mux/0.4.1.dev1/index.html"),
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
for screen_row, text, token in (
|
|
697
|
+
(3, first, "https://"),
|
|
698
|
+
(4, second, "mux/"),
|
|
699
|
+
):
|
|
700
|
+
selection = LocalTextSelection()
|
|
701
|
+
screen_column = 3 + text.index(token) + 2
|
|
702
|
+
assert selection.hover(
|
|
703
|
+
SgrMouseEvent(b"hover", 35, screen_column, screen_row, True),
|
|
704
|
+
source,
|
|
705
|
+
)
|
|
706
|
+
assert selection.segments() == expected_segments
|
|
707
|
+
selection.pointer_event(
|
|
708
|
+
SgrMouseEvent(b"down", 0, screen_column, screen_row, True),
|
|
709
|
+
source,
|
|
710
|
+
)
|
|
711
|
+
action = selection.pointer_event(
|
|
712
|
+
SgrMouseEvent(b"up", 0, screen_column, screen_row, False),
|
|
713
|
+
source,
|
|
714
|
+
)
|
|
715
|
+
assert action.open_target is not None
|
|
716
|
+
assert action.open_target.kind == "url"
|
|
717
|
+
assert action.open_target.value == expected
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
def test_local_text_selection_joins_path_across_codex_ran_decoration():
|
|
721
|
+
first = "• Ran /tmp/railmux-published-0.4.1.dev1-"
|
|
722
|
+
second = " │ 20260816/bin/pip --isolated"
|
|
723
|
+
width = max(len(first), len(second))
|
|
724
|
+
route = HistorySnapshot(1, "%8", 0, 0, width, 2)
|
|
725
|
+
source = SelectionSource(
|
|
726
|
+
route,
|
|
727
|
+
tuple((line + " " * (width - len(line))).encode() for line in (first, second)),
|
|
728
|
+
0,
|
|
729
|
+
)
|
|
730
|
+
selection = LocalTextSelection()
|
|
731
|
+
column = second.index("20260816") + 2
|
|
732
|
+
|
|
733
|
+
assert selection.hover(SgrMouseEvent(b"hover", 35, column, 2, True), source)
|
|
734
|
+
assert selection.segments() == (
|
|
735
|
+
(0, first.index("/tmp/"), b"/tmp/railmux-published-0.4.1.dev1-"),
|
|
736
|
+
(1, second.index("20260816"), b"20260816/bin/pip"),
|
|
737
|
+
)
|
|
738
|
+
selection.pointer_event(SgrMouseEvent(b"down", 0, column, 2, True), source)
|
|
739
|
+
action = selection.pointer_event(
|
|
740
|
+
SgrMouseEvent(b"up", 0, column, 2, False),
|
|
741
|
+
source,
|
|
742
|
+
)
|
|
743
|
+
assert action.open_target is not None
|
|
744
|
+
assert action.open_target.kind == "path"
|
|
745
|
+
assert action.open_target.value == (
|
|
746
|
+
"/tmp/railmux-published-0.4.1.dev1-20260816/bin/pip"
|
|
747
|
+
)
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
def test_local_text_selection_does_not_join_undecorated_box_drawing_rows():
|
|
751
|
+
first = "Output /home/user/project/"
|
|
752
|
+
second = " │ sibling/file.py"
|
|
753
|
+
width = max(len(first), len(second))
|
|
754
|
+
route = HistorySnapshot(1, "%8", 0, 0, width, 2)
|
|
755
|
+
source = SelectionSource(
|
|
756
|
+
route,
|
|
757
|
+
tuple((line + " " * (width - len(line))).encode() for line in (first, second)),
|
|
758
|
+
0,
|
|
759
|
+
)
|
|
760
|
+
selection = LocalTextSelection()
|
|
761
|
+
|
|
762
|
+
selection.pointer_event(SgrMouseEvent(b"down", 0, 12, 1, True), source)
|
|
763
|
+
action = selection.pointer_event(
|
|
764
|
+
SgrMouseEvent(b"up", 0, 12, 1, False),
|
|
765
|
+
source,
|
|
766
|
+
)
|
|
767
|
+
assert action.open_target is not None
|
|
768
|
+
assert action.open_target.value == "/home/user/project/"
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
def test_local_text_selection_does_not_join_next_decorated_shell_option():
|
|
772
|
+
first = "• Ran cat /home/user/project/"
|
|
773
|
+
second = " │ --verbose"
|
|
774
|
+
width = len(first)
|
|
775
|
+
route = HistorySnapshot(1, "%8", 0, 0, width, 2)
|
|
776
|
+
source = SelectionSource(
|
|
777
|
+
route,
|
|
778
|
+
tuple((line + " " * (width - len(line))).encode() for line in (first, second)),
|
|
779
|
+
0,
|
|
780
|
+
)
|
|
781
|
+
selection = LocalTextSelection()
|
|
782
|
+
|
|
783
|
+
selection.pointer_event(SgrMouseEvent(b"down", 0, 12, 1, True), source)
|
|
784
|
+
action = selection.pointer_event(
|
|
785
|
+
SgrMouseEvent(b"up", 0, 12, 1, False),
|
|
786
|
+
source,
|
|
787
|
+
)
|
|
788
|
+
assert action.open_target is not None
|
|
789
|
+
assert action.open_target.value == "/home/user/project/"
|
|
790
|
+
|
|
791
|
+
|
|
680
792
|
def test_local_text_selection_strips_label_before_absolute_path():
|
|
681
793
|
route = HistorySnapshot(1, "%8", 0, 0, 40, 1)
|
|
682
794
|
source = SelectionSource(
|
|
@@ -302,6 +302,41 @@ def test_local_semantic_drag_copies_without_forwarding_mouse(monkeypatch):
|
|
|
302
302
|
child_socket.close()
|
|
303
303
|
|
|
304
304
|
|
|
305
|
+
def test_local_semantic_drag_keeps_ownership_during_frame_transition():
|
|
306
|
+
proxy_socket, child_socket = socket.socketpair()
|
|
307
|
+
client = windows_attach_relay.LocalPtyClient.__new__(
|
|
308
|
+
windows_attach_relay.LocalPtyClient
|
|
309
|
+
)
|
|
310
|
+
client.master_fd = proxy_socket.detach()
|
|
311
|
+
client._session_id = "$4"
|
|
312
|
+
client._selection = LocalTextSelection()
|
|
313
|
+
client._routes = (HistorySnapshot(0, "%8", 0, 0, 20, 1),)
|
|
314
|
+
client._routes_checked_at = time.monotonic()
|
|
315
|
+
client._path_prompt = None
|
|
316
|
+
client._path_prompt_mouse_button = None
|
|
317
|
+
client._size = (20, 1)
|
|
318
|
+
renderer = MagicMock()
|
|
319
|
+
renderer.presentation_stable = True
|
|
320
|
+
renderer.screen = SimpleNamespace(
|
|
321
|
+
width=20, height=1, cursor_x=1, cursor_y=0, rows=(b"select me",)
|
|
322
|
+
)
|
|
323
|
+
renderer.surface.translate_mouse_event.side_effect = lambda event, **_kwargs: event
|
|
324
|
+
client._renderer = renderer
|
|
325
|
+
child_socket.setblocking(False)
|
|
326
|
+
try:
|
|
327
|
+
client._forward_input_part(SgrMouseEvent(b"down", 0, 1, 1, True))
|
|
328
|
+
renderer.presentation_stable = False
|
|
329
|
+
client._forward_input_part(SgrMouseEvent(b"drag", 32, 6, 1, True))
|
|
330
|
+
client._forward_input_part(SgrMouseEvent(b"up", 0, 6, 1, False))
|
|
331
|
+
|
|
332
|
+
renderer.copy_to_clipboard.assert_called_once_with(b"select")
|
|
333
|
+
with pytest.raises(BlockingIOError):
|
|
334
|
+
child_socket.recv(64)
|
|
335
|
+
finally:
|
|
336
|
+
os.close(client.master_fd)
|
|
337
|
+
child_socket.close()
|
|
338
|
+
|
|
339
|
+
|
|
305
340
|
def test_local_semantic_url_in_unfocused_agent_replays_focus_click():
|
|
306
341
|
client = windows_attach_relay.LocalPtyClient.__new__(
|
|
307
342
|
windows_attach_relay.LocalPtyClient
|
|
@@ -922,6 +957,9 @@ def test_local_pointer_is_dropped_while_visible_frame_is_transitioning():
|
|
|
922
957
|
child_socket.setblocking(False)
|
|
923
958
|
try:
|
|
924
959
|
client._forward_input_part(SgrMouseEvent(b"stale-click", 0, 4, 5, True))
|
|
960
|
+
renderer.presentation_stable = True
|
|
961
|
+
client._forward_input_part(SgrMouseEvent(b"orphan-drag", 32, 8, 5, True))
|
|
962
|
+
client._forward_input_part(SgrMouseEvent(b"orphan-up", 0, 8, 5, False))
|
|
925
963
|
with pytest.raises(BlockingIOError):
|
|
926
964
|
child_socket.recv(64)
|
|
927
965
|
finally:
|
|
@@ -929,6 +967,42 @@ def test_local_pointer_is_dropped_while_visible_frame_is_transitioning():
|
|
|
929
967
|
child_socket.close()
|
|
930
968
|
|
|
931
969
|
|
|
970
|
+
def test_local_left_gesture_outside_agent_route_remains_tmux_owned():
|
|
971
|
+
client = windows_attach_relay.LocalPtyClient.__new__(
|
|
972
|
+
windows_attach_relay.LocalPtyClient
|
|
973
|
+
)
|
|
974
|
+
proxy_socket, child_socket = socket.socketpair()
|
|
975
|
+
client.master_fd = proxy_socket.detach()
|
|
976
|
+
client._session_id = "$4"
|
|
977
|
+
client._selection = LocalTextSelection()
|
|
978
|
+
client._routes = (HistorySnapshot(0, "%8", 0, 0, 20, 1),)
|
|
979
|
+
client._routes_checked_at = time.monotonic()
|
|
980
|
+
client._path_prompt = None
|
|
981
|
+
client._path_prompt_mouse_button = None
|
|
982
|
+
client._size = (20, 2)
|
|
983
|
+
renderer = MagicMock()
|
|
984
|
+
renderer.presentation_stable = True
|
|
985
|
+
renderer.screen = SimpleNamespace(
|
|
986
|
+
width=20,
|
|
987
|
+
height=2,
|
|
988
|
+
cursor_x=1,
|
|
989
|
+
cursor_y=0,
|
|
990
|
+
rows=(b"agent", b"status"),
|
|
991
|
+
)
|
|
992
|
+
renderer.surface.translate_mouse_event.side_effect = lambda event, **_kwargs: event
|
|
993
|
+
client._renderer = renderer
|
|
994
|
+
child_socket.settimeout(0.5)
|
|
995
|
+
try:
|
|
996
|
+
client._forward_input_part(SgrMouseEvent(b"down", 0, 2, 2, True))
|
|
997
|
+
client._forward_input_part(SgrMouseEvent(b"drag", 32, 4, 2, True))
|
|
998
|
+
client._forward_input_part(SgrMouseEvent(b"up", 0, 4, 2, False))
|
|
999
|
+
|
|
1000
|
+
assert child_socket.recv(64) == b"downdragup"
|
|
1001
|
+
finally:
|
|
1002
|
+
os.close(client.master_fd)
|
|
1003
|
+
child_socket.close()
|
|
1004
|
+
|
|
1005
|
+
|
|
932
1006
|
def test_local_explicit_tmux_copy_mode_key_remains_byte_exact():
|
|
933
1007
|
client = windows_attach_relay.LocalPtyClient.__new__(
|
|
934
1008
|
windows_attach_relay.LocalPtyClient
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.1.dev1"
|
|
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
|
|
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
|
|
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
|