mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-21 16:02:20 +00:00
feat(files): copy dropped files into the folder they were dropped on (#458)
* feat(files): copy dropped files into the folder they were dropped on The Files panel has only ever been a drag *source* — a row dragged into a terminal inserts its path. Nothing on the tree ever registered a drop, so a file dragged in from the desktop did nothing at all, not even a highlight. Closes #453. The drop is the whole gesture: files land where the cursor was, not somewhere a dialog asks about afterwards. A folder row takes them itself, a file row stands in for the folder holding it — "next to this one" — and the space the rows do not cover belongs to the top of the tree. The placeholder inside an empty folder takes a drop too; it is the only thing drawn there, and letting it fall through to the root would put files somewhere the cursor never was. A row under the cursor wins over the column, which is what gpui's innermost-first dispatch already does. The copy itself goes through the `Host` the tree is listing, so a remote workspace reads here and writes there. Locally it is `fs::copy`, which is what keeps the executable bit that `write_file` would drop; remotely the bytes ride one control frame, and a file too big for that is refused with the advice to use SFTP rather than half-sent. Names already taken are asked about before anything is written, and the answer governs the whole drop — a half-done copy would have to be undone to honour a "no". Replacing a folder replaces it rather than merging into it. A drag let go where it started is a miss, not an error, so it says nothing. * fix(sftp): list the directory again once an upload lands An upload is written to `<name>.tty7-upload-<hex>` and renamed into place at the very end. The browser listed the directory the moment the transfer was handed to the daemon, so it caught that temporary name — and nothing ever listed again, so a finished upload sat on screen as a file with a hash glued to its name until the directory was navigated by hand. The premature listing is gone, and the panel now remembers the job ids it started: once one stops running — done, failed, cancelled, or dropped off the job list entirely — the directory is listed once more. Two uploads in flight settle independently, so the second one finishing does not depend on the first. * docs(changelog): note the SFTP upload listing fix * ci(host-boundary): allow the source side of a file drop, and stop scanning two files as empty The Files panel now copies dropped files in, and what the desktop hands over is by construction a path on the desktop's own machine: reading it is a local read even when the tree being dropped on is remote. The destination side goes through `Host`, and the one `std::fs::copy` that touches a destination sits inside a branch already gated on `host.id().is_local()`. While adding that entry: `attr` starts unset, which awk reads as 0, so a file whose first line is `mod something` matched `attr == NR - 1` and cut its body at line 0. `head -n -1` then errored and the file was scanned as empty — `src/terminal/mod.rs` and `src/ui/tray/mod.rs` both open that way, and the guard had been blind to both. Neither contains a violation, so seeing them is free.
This commit is contained in:
@@ -84,6 +84,13 @@ src/terminal/search.rs|.is_absolute()
|
||||
# can be handed a path. Always `std::env::temp_dir()` on this machine.
|
||||
src/terminal/view.rs|std::fs::create_dir_all
|
||||
src/terminal/view.rs|std::fs::write
|
||||
|
||||
# The source side of a file drop. What the desktop hands over is by
|
||||
# construction a path on the desktop's own machine, so reading it is a local
|
||||
# read even when the tree being dropped on is remote — the destination side of
|
||||
# that copy goes through `Host`, and the one `std::fs::copy` that touches a
|
||||
# destination sits inside a branch already gated on `host.id().is_local()`.
|
||||
src/ui/file_copy.rs|std::fs::
|
||||
EOF
|
||||
)
|
||||
|
||||
@@ -91,7 +98,12 @@ EOF
|
||||
# preserved because only the tail is dropped.
|
||||
body_of() {
|
||||
local file=$1 cut
|
||||
# `attr` starts unset, which awk reads as 0 — so a file whose *first* line
|
||||
# is `mod something` used to match `attr == NR - 1` and cut the body at
|
||||
# line 0, leaving `head -n -1` to error out and the file to be scanned as
|
||||
# empty. Two files opened that way, and the guard was blind to both.
|
||||
cut=$(awk '
|
||||
BEGIN { attr = -1 }
|
||||
/^#\[cfg\(test\)\]$/ { attr = NR }
|
||||
/^mod [A-Za-z_]/ { if (attr == NR - 1) { print NR - 1; exit } }
|
||||
' "$file")
|
||||
|
||||
Reference in New Issue
Block a user