198 lines
11 KiB
Markdown
198 lines
11 KiB
Markdown
# Terminal manager view abstraction — TECH
|
|
|
|
## Context
|
|
|
|
The local TTY terminal manager now has a reusable construction path for terminal frontends. The GUI remains the only frontend in this change, but the manager no longer needs to treat `TerminalView` as part of the object-safe manager contract.
|
|
|
|
The pre-change local manager was tightly coupled to `TerminalView`:
|
|
|
|
- [`app/src/terminal/writeable_pty/terminal_manager_util.rs:23 @ 0b2273da`](https://github.com/warpdotdev/warp/blob/0b2273da3443eacc8d78b748f37962427e968fe5/app/src/terminal/writeable_pty/terminal_manager_util.rs#L23) wired `PtyController` directly to `ViewHandle<TerminalView>` and matched `terminal::view::Event` variants for PTY writes, resize, command execution, and native completions.
|
|
- [`app/src/terminal/local_tty/terminal_manager.rs:139 @ 0b2273da`](https://github.com/warpdotdev/warp/blob/0b2273da3443eacc8d78b748f37962427e968fe5/app/src/terminal/local_tty/terminal_manager.rs#L139) stored `ViewHandle<TerminalView>` directly and called GUI-specific lifecycle hooks for shell startup, spawn failures, shell launch-data updates, and Unix terminal-attributes/password-prompt handling.
|
|
- [`app/src/terminal/terminal_manager.rs:24 @ 0b2273da`](https://github.com/warpdotdev/warp/blob/0b2273da3443eacc8d78b748f37962427e968fe5/app/src/terminal/terminal_manager.rs#L24) exposed `view() -> ViewHandle<TerminalView>` from the object-safe `TerminalManager` trait, so callers recovered the GUI view from the boxed manager.
|
|
|
|
The current design splits the terminal manager responsibilities into three layers:
|
|
|
|
1. `TerminalSurface` and `PtyIntent` define the narrow frontend-to-PTY boundary.
|
|
2. `TerminalManager<S>` owns local terminal model/session/controller/event-loop state for any terminal surface.
|
|
3. `TerminalManager<S>::create_model` is the generic model-producing construction path. The current GUI pane system calls it with `S = TerminalView` and a GUI surface setup callback.
|
|
|
|
## Proposed changes
|
|
|
|
### Terminal surface API
|
|
|
|
`app/src/terminal/writeable_pty/terminal_surface.rs` defines `PtyIntent`, `PtyIntentEvent`, and `TerminalSurface`.
|
|
|
|
`PtyIntent` is the only event vocabulary the generic PTY wiring understands. It is intentionally limited to PTY/session-driving actions: process control, byte writes, resize, command execution, and native shell completions. UI events, pane orchestration, remote-server choice UI, and shared-session protocol events remain on concrete surface event types.
|
|
|
|
`PtyIntentEvent` preserves the `From<&SurfaceEvent> for Option<PtyIntent>` projection pattern while hiding the higher-ranked bound behind a simple method:
|
|
|
|
```rust
|
|
pub(crate) trait PtyIntentEvent {
|
|
fn pty_intent(&self) -> Option<PtyIntent>;
|
|
}
|
|
|
|
impl<T> PtyIntentEvent for T
|
|
where
|
|
for<'a> Option<PtyIntent>: From<&'a T>,
|
|
{
|
|
fn pty_intent(&self) -> Option<PtyIntent> {
|
|
Option::<PtyIntent>::from(self)
|
|
}
|
|
}
|
|
```
|
|
|
|
`TerminalSurface` is the frontend contract consumed by `TerminalManager<S>`. It requires a surface event type that implements `PtyIntentEvent` and lifecycle hooks for shell startup, PTY spawn failure, launch-data updates, and Unix password-prompt polling.
|
|
|
|
`TerminalView` implements this contract and maps only the existing PTY-driving `terminal::view::Event` variants to `PtyIntent`:
|
|
|
|
- `CtrlD`
|
|
- `ShutdownPty`
|
|
- `WriteBytesToPty`
|
|
- `WriteAgentInputToPty`
|
|
- `Resize`
|
|
- `ExecuteCommand`
|
|
- `RunNativeShellCompletions`
|
|
|
|
Every other `TerminalView` event maps to `None`.
|
|
|
|
### View-agnostic terminal manager trait
|
|
|
|
`crate::terminal::TerminalManager` no longer exposes `view() -> ViewHandle<TerminalView>`. The object-safe trait now describes terminal model/session lifecycle:
|
|
|
|
- `model()`
|
|
- `on_view_detached(...)`
|
|
- `as_any()`
|
|
- `as_any_mut()`
|
|
|
|
Constructors that create a `TerminalView` return the view separately alongside the boxed manager. This keeps the boxed manager view-agnostic while preserving existing pane code that needs the `TerminalView` handle.
|
|
|
|
Current constructors that return `(manager, surface)`:
|
|
|
|
- `local_tty::TerminalManager<S>::create_model`, called by the GUI path as `TerminalManager::<TerminalView>::create_model`
|
|
- `remote_tty::TerminalManager::create_model`
|
|
- `MockTerminalManager::create_model`
|
|
- `shared_session::viewer::TerminalManager::new`
|
|
- `shared_session::viewer::TerminalManager::new_deferred`
|
|
|
|
### Generic local manager construction
|
|
|
|
`app/src/terminal/local_tty/terminal_manager.rs` defines the local manager as:
|
|
|
|
```rust
|
|
pub struct TerminalManager<S> {
|
|
view: ViewHandle<S>,
|
|
// model, controllers, event-loop state, and retained handles
|
|
}
|
|
```
|
|
|
|
`TerminalManager<S>::create_model(...)` is the canonical local terminal construction API. It owns the local terminal session construction order, creates the manager-owned channels/models/controllers internally, calls one surface setup callback that returns `(surface, post_wire)`, wires the surface to the `PtyController` through `wire_up_pty_controller_with_surface`, runs `post_wire`, boxes the manager as a WarpUI model, schedules shell determination, and returns `(manager_model, surface)`.
|
|
|
|
The surface factory callback receives only the components a surface needs:
|
|
|
|
- `TerminalModel`
|
|
- `Sessions`
|
|
- `ModelEventDispatcher`
|
|
- wakeup receiver
|
|
- inactive PTY reads receiver
|
|
- terminal colors
|
|
- current terminal size
|
|
|
|
The manager stores the remaining startup and lifetime components itself, including the event-loop sender/receiver, channel event proxy, `PtyController`, `RemoteServerController`, and shell-starter source. Shell determination later consumes that stored startup state from the manager after it has been registered as a WarpUI model.
|
|
|
|
The surface setup callback can return a deferred `post_wire` closure for surface-specific wiring. The constructor runs that closure after the PTY controller is wired and the manager has been assembled. This keeps ordering-sensitive post-wiring inside the manager-owned construction flow without requiring the generic manager core to import `TerminalView`.
|
|
|
|
For the GUI caller, `TerminalManager::<TerminalView>::create_model(...)` now:
|
|
|
|
1. Resolves GUI-specific restored blocks from explicit restored blocks and conversation restoration.
|
|
2. Uses a surface setup callback that creates `CurrentPrompt`, `PromptType`, and `TerminalView` from the manager-created surface components, then returns `(view, post_wire)`.
|
|
3. The returned `post_wire` closure appends the GUI restoration separator when needed, wires remote-server choice UI, and wires `TerminalView`-specific session sharing.
|
|
4. The generic constructor boxes the manager, schedules shell determination, and returns `(manager_model, terminal_view)`.
|
|
|
|
This keeps the end-to-end local terminal construction protocol in `TerminalManager<S>::create_model` while keeping GUI-specific work in the `TerminalView` surface setup callback. A future TUI surface can call the same function with a different surface setup callback.
|
|
|
|
### Generic PTY wiring
|
|
|
|
`wire_up_pty_controller_with_surface<T, S>(...)` replaces the direct `TerminalView` wiring. It subscribes to the surface event stream, calls `event.pty_intent()`, and handles only `PtyIntent` values.
|
|
|
|
The behavior of each intent matches the old `TerminalView` event match:
|
|
|
|
- raw bytes and agent bytes write to the PTY controller
|
|
- resize resizes the PTY
|
|
- command execution resolves shell type from `Sessions`, sets workflow state, writes the command, and updates command history when requested
|
|
- native completions call through to `PtyController::run_native_shell_completions`
|
|
- PTY disconnection exits the `TerminalModel`
|
|
|
|
`wire_up_remote_server_controller_with_view` remains GUI-specific because the remote-server install/skip choice is rendered as `TerminalView` rich content.
|
|
|
|
### TerminalView-specific session sharing boundary
|
|
|
|
Local session sharing remains GUI-specific in this change. The reusable lower layers stay unchanged:
|
|
|
|
- `shared_session::sharer::Network`
|
|
- ordered terminal event flow from `TerminalModel`
|
|
- existing shared-session handler helpers
|
|
|
|
The local sharer wiring is grouped behind `wire_up_terminal_view_session_sharing(...)`. That helper owns the current `TerminalView` adapter responsibilities:
|
|
|
|
- prompt updates
|
|
- presence selection
|
|
- LLM/input-mode/conversation broadcasts
|
|
- AgentView and active-agent registration
|
|
- CLI-agent session broadcasts
|
|
- local sharer `Network` setup
|
|
- network status UI reactions
|
|
|
|
`shared_session::manager::Manager` remains GUI-shaped and continues storing `TerminalView` handles. The sharing boundary is now easier to locate and refactor later without making this change TUI-sharing-ready.
|
|
|
|
### Unix password-prompt polling
|
|
|
|
The local manager still owns the Unix `termios` poller, but the surface decides whether polling is useful and how to react.
|
|
|
|
The poller now subscribes to `ModelEventDispatcher` rather than `TerminalView` events:
|
|
|
|
- `ModelEvent::AfterBlockStarted { is_for_in_band_command: false, .. }` records the active block index and starts polling only if `surface.should_poll_for_password_prompt(ctx)` returns true.
|
|
- `ModelEvent::BlockCompleted(completed)` stops polling and calls `surface.on_polled_block_completed(completed, ctx)`.
|
|
- `TerminalAttributesPollerEvent::TermiosQueryFinished` checks for ECHO off and ICANON on, calls `surface.on_possible_password_prompt(block_index, ctx)`, and stops polling after the first detected prompt.
|
|
|
|
For `TerminalView`, these hooks preserve existing password notification and SSH upload behavior.
|
|
|
|
## Testing and validation
|
|
|
|
Run formatting and compile checks:
|
|
|
|
- `./script/format`
|
|
- `cargo check -p warp`
|
|
- `cargo clippy -p warp --all-targets --tests -- -D warnings`
|
|
|
|
Run focused tests covering:
|
|
|
|
- terminal manager/view constructor paths
|
|
- PTY write and command execution events
|
|
- pane creation paths that now receive the `TerminalView` separately
|
|
- shared-session start/stop behavior
|
|
|
|
Validation performed during implementation:
|
|
|
|
- `./script/format`
|
|
- `cargo check -p warp`
|
|
|
|
## Parallelization
|
|
|
|
Do not parallelize implementation across child agents. The core changes touch the same local manager, object-safe trait, PTY wiring, and pane construction call sites; parallel work would create overlapping edits.
|
|
|
|
Validation can be performed separately after the code compiles.
|
|
|
|
## Risks and mitigations
|
|
|
|
- **Surface boundary grows too broad.** Keep `PtyIntent` limited to PTY/session-driving actions and leave pane, UI, remote-server choice, and shared-session protocol events on concrete surface event types.
|
|
- **Constructor churn breaks pane creation.** Return `TerminalView` explicitly from GUI constructors and update pane helpers at the same call sites that previously used `manager.view()`.
|
|
- **Shared-session behavior regresses.** Keep protocol/model layers unchanged and move the GUI-specific sharer setup as a group rather than rewriting it.
|
|
- **Password-prompt behavior changes.** Route the same termios detection through explicit `TerminalSurface` hooks and preserve one-notification-per-command behavior.
|
|
|
|
## Out of scope
|
|
|
|
- No TUI backend or `LaunchMode::Tui`.
|
|
- No terminal-history rendering, virtual list, or key-passthrough work.
|
|
- No generic shared-session frontend abstraction.
|
|
- No blanket object-safe trait implementation for every `TerminalManager<S>`; current detach behavior is still specific to the `TerminalView` manager.
|