Initial public release of Warp.

Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
David Stern
2026-04-28 08:43:33 -05:00
commit 0dbd3d567a
4982 changed files with 1431549 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
# PRODUCT.md — CODE-1779: Drag-and-drop file paths in WSL (and Git Bash)
Linear: https://linear.app/warpdotdev/issue/CODE-1779/windows-drag-and-drop-file-paths-in-wsl
Upstream: https://github.com/warpdotdev/warp/issues/6191
Figma: none provided (no visual design — the change is purely in which string lands in the input buffer)
## Summary
When a Warp tab is attached to a Unix-like shell on Windows — WSL, or MSYS2 / Git Bash — dragging a file or folder from Windows Explorer onto Warp should insert a path in that shell's native form, not a Windows-native path. For WSL that's `/mnt/c/Users/andy/Downloads`; for Git Bash that's `/c/Users/andy/Downloads`. WSL already works correctly on the terminal grid when a long-running command is active; this spec covers the input editor (broken for both WSL and Git Bash today) and adds matching behavior for Git Bash.
## Behavior
1. Dropping one or more files or folders from Windows Explorer onto the Warp input editor inserts each path in the active session's native form:
- **WSL session** — drive-letter paths are mapped under `/mnt/<drive>/…` with forward slashes.
- **MSYS2 / Git Bash session** — drive-letter paths are mapped under `/<drive>/…` with forward slashes (MSYS2's POSIX-style path convention, which Git Bash and the MSYS2 runtime translate automatically when invoking native Windows binaries).
- **All other sessions** — paths are inserted exactly as dropped (see invariant 5).
2. Conversion rules for a single dropped path:
- WSL session:
- `C:\Users\andy\file.txt` → `/mnt/c/Users/andy/file.txt`.
- `D:\Pictures\Screenshot 2025-05-14 155816.png` → `/mnt/d/Pictures/Screenshot 2025-05-14 155816.png` (spaces preserved; shell escaping then applies on top).
- `C:\` and `C:` both → `/mnt/c`.
- Uppercase drive letters are lowercased (`E:\foo` → `/mnt/e/foo`).
- UNC-style paths with no drive letter (`\\server\share\file`) have backslashes converted to forward slashes (`//server/share/file`); no further remapping is attempted.
- MSYS2 / Git Bash session:
- `C:\Users\andy\file.txt` → `/c/Users/andy/file.txt`.
- `D:\Pictures\Screenshot 2025-05-14 155816.png` → `/d/Pictures/Screenshot 2025-05-14 155816.png`.
- `C:\` and `C:` both → `/c`.
- Uppercase drive letters are lowercased (`E:\foo` → `/e/foo`).
- UNC-style paths with no drive letter (`\\server\share\file`) have backslashes converted to forward slashes (`//server/share/file`); no further remapping is attempted.
3. Dropping multiple paths in a single drop inserts each one individually, separated by a single space, with each path transformed per (2). A trailing space is appended to the inserted text so back-to-back drops don't concatenate tokens (unchanged from today).
4. Shell-specific escaping (quoting spaces, special characters) applies on top of the transformed path using the active session's shell family — identical to the non-WSL/non-MSYS2 behavior today.
5. When the active session is neither WSL nor MSYS2/Git Bash (local PowerShell, cmd, SSH into a remote host, Warpified remote, etc.), dropped paths are inserted exactly as they are today. No transformation happens.
6. Image auto-attachment (dragging an image file into Agent Mode / an empty buffer, which attaches it as AI image context) continues to use the original Windows-native path for filesystem reads, regardless of WSL / MSYS2 state. Transformed paths would not be readable from the Windows host.
7. If an image auto-attach fails (e.g. per-query or per-conversation image limit is exceeded) and the path is inserted as text as a fallback, that inserted text is the transformed path (per invariant 2) when the session is WSL or MSYS2 — matching invariant (1).
8. Parity with the terminal grid:
- WSL: dropping onto the grid already inserts WSL-style paths during a long-running command. The input editor must match, so the user sees the same text regardless of whether the drop target was the grid or the input editor.
- MSYS2 / Git Bash: the grid's long-running behavior (drop a path, get the Windows-native path written to the PTY without shell escaping, so native Windows executables receive the form they expect) is **intentional and unchanged** by this ticket. The input editor is a different context — the shell itself processes the text next, so MSYS2-style paths are the right default there. Grid and input-editor behavior therefore differ on MSYS2 by design; this is noted so it isn't flagged as a bug in review.
9. The transformation is driven by the *currently active block's session*. Switching blocks (and therefore sessions) between drops updates which rule applies on the next drop.
10. Non-regressions:
- Dropping into any non-terminal editor (notebooks, settings, themes, etc.) is unchanged — no path transformation is applied.
- Dropping into a non-WSL, non-MSYS2 terminal session (PowerShell, cmd, SSH, remote Warpified) is unchanged.
- The terminal-grid long-running-command code paths for both WSL and MSYS2 are unchanged.
- Dropping image-only content into the input in Agent Mode still attaches the images; nothing about attachment behavior changes.
- Pasting paths via clipboard is out of scope for this ticket and remains unchanged.
+106
View File
@@ -0,0 +1,106 @@
# TECH.md — CODE-1779: Drag-and-drop file paths in WSL (and Git Bash)
See `PRODUCT.md` for user-visible behavior.
## Context
There are two drop paths relevant to this ticket:
1. **Drop onto the terminal grid** (e.g. during a long-running command). Handled by `TerminalView::drag_and_drop_files` in `app/src/terminal/view.rs (23007-23085)`. This path already applies `warp_util::path::convert_windows_path_to_wsl` when `session.is_wsl()` before shell-escaping and writing to the PTY. For MSYS2 long-running it deliberately keeps Windows-native paths and skips shell escaping (so native Windows binaries receive the form they expect). **Not changing this path.**
2. **Drop onto the input editor** (typical case when there's no long-running command). The editor element's `drag_and_drop_file` in `app/src/editor/view/element.rs (663-681)` dispatches `EditorAction::DragAndDropFiles`, which routes to `EditorView::drag_and_drop_files` in `app/src/editor/view/mod.rs (7893-7914)`. That function emits `Event::DroppedImageFiles` for image paths (terminal input handles attachment) and, for everything else, calls `warpui::clipboard_utils::escaped_paths_str` followed by `self.user_insert`. **No path conversion happens — this is the bug for both WSL and MSYS2.**
Why the fix doesn't belong inside `EditorView`: `EditorView` is a general-purpose text editor used across dozens of surfaces (notebooks, settings, code editors, modals, etc. — see the many `EditorView::new(...)` / `single_line(...)` call sites). It already carries `shell_family` for escaping, which is a shell concern but not tied to any particular parent; WSL / MSYS2 path conversion is a terminal-session concern and must not leak into the editor.
Existing helpers:
- `warp_util::path::convert_windows_path_to_wsl` in `crates/warp_util/src/path.rs (673-691)`, with tests at `crates/warp_util/src/path_test.rs (629-649)`.
- **No equivalent exists yet for MSYS2.** We'll add `convert_windows_path_to_msys2` alongside it (same shape, `/<drive>/…` instead of `/mnt/<drive>/…`).
- `Session::is_wsl()` at `app/src/terminal/model/session.rs:972` and `Session::is_msys2()` at `app/src/terminal/model/session.rs:980` both already exist and return `false` on non-Windows platforms.
- `TerminalInput::active_session` is available in `app/src/terminal/input.rs:11011`.
Shell-family setup on the editor already happens in `TerminalInput::set_active_block_metadata` at `app/src/terminal/input.rs (13034-13065)`, which is called on every active-block (and therefore session) change. That's the natural place to install/clear the transformer too.
## Proposed changes
### 1. Add `convert_windows_path_to_msys2` to `warp_util::path`
In `crates/warp_util/src/path.rs`, alongside `convert_windows_path_to_wsl`, add:
```rust path=null start=null
/// Converts a Windows-native path string to an MSYS2 / Git Bash POSIX-style path.
///
/// Drive-letter paths (e.g. `C:\Users\aloke\file.txt`) are mapped to
/// `/<drive>/Users/aloke/file.txt`. Paths that don't start with a drive letter
/// are returned as-is with backslashes replaced by forward slashes.
pub fn convert_windows_path_to_msys2(windows_path: &str) -> String { /* ... */ }
```
Implementation mirrors `convert_windows_path_to_wsl` but emits `/<drive>` instead of `/mnt/<drive>`. Consider factoring both into a shared internal helper that takes a `&'static str` drive prefix (`"/mnt/"` vs `"/"`) to avoid duplication.
Add unit tests in `crates/warp_util/src/path_test.rs` matching PRODUCT.md invariant (2):
- `C:\Users\andy\file.txt` → `/c/Users/andy/file.txt`
- Spaces preserved
- `C:\` and `C:` both → `/c`
- Uppercase drive lowercased
- UNC fallback converts backslashes to slashes
### 2. Add a generic "path transformer" hook to `EditorView`
In `app/src/editor/view/mod.rs`:
- Add a public type alias `pub type PathTransformerFn = Rc<dyn Fn(&str) -> String>;` near the existing `CursorColorsFn` (line ~1587). `Rc` is already imported.
- Add `pub drag_drop_path_transformer: Option<PathTransformerFn>` to `EditorOptions` (default `None` in both `Default for EditorOptions` and `From<SingleLineEditorOptions> for EditorOptions`).
- Add a mirrored private field on `EditorView`, initialize it from `options.drag_drop_path_transformer` in `new_internal`, and add a `set_drag_drop_path_transformer` setter next to the existing `set_shell_family`. No public getter is needed: the only caller that would want to read the transformer back is the parent that installed it, and that parent already knows which transformation applies to the current session.
- In `EditorView::drag_and_drop_files`, after the image-files branch returns early, run each remaining (non-image) path through the transformer if present, then pass the transformed list — not `paths_as_strings` — into `escaped_paths_str`. Image paths stay untransformed because `Event::DroppedImageFiles` consumers (terminal input) need to read the original file from the host.
Design note: we deliberately picked a closure over an event/delegation flag. The editor already takes other domain-agnostic closures (`render_decorator_elements`, `cursor_colors_fn`, `keymap_context_modifier`), and a pure `Fn(&str) -> String` hook is the smallest, most focused extension point for what amounts to "rewrite each path string before inserting." It keeps image attachment, shell escaping, and insertion flow inside the editor unchanged, and it imposes no terminal vocabulary on the editor. A single closure type also naturally covers multiple concrete transformations (WSL, MSYS2, and anything else we add later).
### 3. Install the transformer from `TerminalInput`
In `app/src/terminal/input.rs`:
- In `set_active_block_metadata` (line ~13034), alongside the existing `editor.set_shell_family(...)` call, select a transformer based on the session and install or clear it:
- `session.is_wsl()` → `Rc::new(|p: &str| warp_util::path::convert_windows_path_to_wsl(p))`.
- `session.is_msys2()` → `Rc::new(|p: &str| warp_util::path::convert_windows_path_to_msys2(p))`.
- Otherwise → `None`.
Both session predicates return `false` off-Windows, so no `cfg!(windows)` gating is needed at the call site. Check `is_wsl()` before `is_msys2()` for clarity (they are mutually exclusive in practice, but the order documents priority).
- In the existing `EditorEvent::DroppedImageFiles` handler (line ~9493), when the image-attach fallback inserts paths as text, apply the same WSL / MSYS2 conversion to `image_filepaths` (branching on `session.is_wsl()` / `session.is_msys2()` just as step above does) before calling `escaped_paths_str`. This preserves PRODUCT.md invariant (7) for both WSL and MSYS2.
We rely on `set_active_block_metadata` being called whenever the active session changes; it already updates `shell_family` and `path_separators`, so the transformer follows the same lifecycle and always reflects the currently active session.
### 4. No changes needed elsewhere
- `TerminalView::drag_and_drop_files` (terminal grid path) already converts correctly for WSL, and deliberately does not convert for MSYS2 long-running commands. Per PRODUCT.md invariant (8), leave it alone.
- No other `EditorView::new(...)` call site sets the new option, so the default `None` transformer means identical behavior for notebooks, settings, etc.
## Testing and validation
Covers the invariants in `PRODUCT.md`.
- **Unit tests for `convert_windows_path_to_msys2` (invariant 2, MSYS2 cases).** In `crates/warp_util/src/path_test.rs`, mirror the existing `test_convert_windows_path_to_wsl` test with MSYS2-equivalent expectations (`/c/...`, `/d/...`, etc.).
- **Existing coverage for the WSL conversion itself (invariant 2, WSL cases).** `test_convert_windows_path_to_wsl` in `crates/warp_util/src/path_test.rs (629-649)` already verifies drive-letter lowercasing, spaces, UNC, and empty-suffix behavior. No new tests needed there.
- **Unit test — transformer wiring (invariants 1, 2, 5, 9).** In `app/src/editor/view/mod_test.rs`, add a test that:
- Creates an `EditorView` with `shell_family: Some(Posix)` and runs two scenarios:
1. `drag_drop_path_transformer: Some(Rc::new(|p| warp_util::path::convert_windows_path_to_wsl(p)))` — WSL behavior.
2. `drag_drop_path_transformer: Some(Rc::new(|p| warp_util::path::convert_windows_path_to_msys2(p)))` — MSYS2 behavior.
- Each scenario dispatches `EditorAction::DragAndDropFiles` with representative inputs (`C:\foo`, `D:\bar baz.txt`, a UNC path, and a path with no drive letter) and asserts the buffer contents match PRODUCT.md invariant (2) with shell escaping applied on top.
- A third scenario clears the transformer and asserts the original Windows paths are inserted verbatim (PRODUCT.md invariant 5).
- **Image-attach fallback (invariants 6, 7).** In `app/src/terminal/input_test.rs`, if there is already coverage for the `DroppedImageFiles` fallback path, extend it to assert that when a transformer is installed, the fallback text is the transformed paths. If no such test exists yet, add a focused one that mocks an over-limit scenario and inspects the buffer. Exercise both a WSL transformer and an MSYS2 transformer. Image attachment itself is unchanged and needs no new test.
- **Manual verification (invariants 1, 3, 8, 10).** On a Windows machine with both a WSL distro and Git Bash installed:
- Drag a file from Explorer into a WSL tab's input editor → buffer reads `/mnt/c/…` with shell escaping.
- Drag a file from Explorer into a Git Bash tab's input editor → buffer reads `/c/…` with shell escaping.
- Drag the same file onto a WSL tab while a long-running command is active → grid receives `/mnt/c/…` (regression check for existing behavior).
- Drag the same file onto a Git Bash tab while a long-running command is active → grid receives `C:\…` without shell escaping (regression check: deliberate existing behavior per PRODUCT.md invariant 8).
- Drag into a local PowerShell tab → buffer reads `C:\…` unchanged.
- Drag into a notebook editor → buffer reads `C:\…` unchanged.
- Drag an image file in Agent Mode with an empty buffer (WSL or Git Bash) → image attaches normally; no text inserted.
## Risks and mitigations
- **Stale transformer after session switch.** Because we install/clear the transformer in `set_active_block_metadata`, it always matches the active session. If that function is ever bypassed for a session change, the transformer could lag. Mitigation: piggy-back on the exact same call site as `set_shell_family`, which already has this assumption and has proven stable.
- **Cross-platform builds.** `convert_windows_path_to_wsl` is pure string manipulation and compiles on all platforms, as does `Session::is_wsl()` (returns `false` off-Windows). No `cfg!(windows)` gating needed at the call site, though in practice the transformer will only ever be non-`None` on Windows because no other platform has WSL sessions.