From fe6807c9b0ab3eb52a8839c853fb07e9e53348f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 5 May 2026 09:37:50 +0000 Subject: [PATCH] implement move deletion and add tests for dnd kitten drop Agent-Logs-Url: https://github.com/kovidgoyal/kitty/sessions/e2238746-2319-43e5-9b01-4899e7f06b50 Co-authored-by: kovidgoyal <1308621+kovidgoyal@users.noreply.github.com> --- kittens/dnd/drop.go | 17 +++++++++++++++-- kitty_tests/dnd_kitten.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/kittens/dnd/drop.go b/kittens/dnd/drop.go index 561289efc..7cdb2458b 100644 --- a/kittens/dnd/drop.go +++ b/kittens/dnd/drop.go @@ -449,8 +449,21 @@ func (dnd *dnd) end_drop(success bool) { if dnd.drop_status.reading_data { dnd.lp.QueueDnDData(DC{ Type: 'r', Operation: utils.IfElse(success, dnd.drop_status.action, 0)}) // end drop - if success && dnd.drop_status.action == 2 && !dnd.drop_status.is_remote_client { - // TODO: delete all dropped files/dirs/symlinks after successful move + if success && dnd.drop_status.action == move_on_drop && !dnd.drop_status.is_remote_client { + for _, path := range dnd.drop_status.uri_list { + if path == "" { + continue + } + st, err := os.Lstat(path) + if err != nil { + continue + } + if st.IsDir() { + os.RemoveAll(path) + } else { + os.Remove(path) + } + } } } dnd.reset_drop() diff --git a/kitty_tests/dnd_kitten.py b/kitty_tests/dnd_kitten.py index 8bb003532..010954c8b 100644 --- a/kitty_tests/dnd_kitten.py +++ b/kitty_tests/dnd_kitten.py @@ -323,6 +323,39 @@ class TestDnDKitten(BaseTest): do_overwrite_drop(enter_content, '\x1b[13u') do_overwrite_drop(b'overwrite-esc-test-content', '\x1b[27u', 0) # ]]] + # ---- move operation: source items deleted for local client only ---- + move_file = jn(self.src_data_dir, 'move_test.txt') + move_dir = jn(self.src_data_dir, 'move_test_dir') + move_link = jn(self.src_data_dir, 'move_test_link') + os.makedirs(move_dir) + with open(jn(move_dir, 'inside.txt'), 'wb') as f: + f.write(b'nested file in move dir') + with open(move_file, 'wb') as f: + f.write(b'move test content') + os.symlink('nonexistent_target', move_link) + move_uri = ( + as_file_url(self.src_data_dir, 'move_test.txt') + '\r\n' + + as_file_url(self.src_data_dir, 'move_test_dir') + '\r\n' + + as_file_url(self.src_data_dir, 'move_test_link') + '\r\n' + ).encode() + dnd_test_fake_drop_event(self.capture.window_id, False, ['text/uri-list'], move[0]+1, move[1]+1) + self.wait_for_state('drop_action', GLFW_DRAG_OPERATION_MOVE) + dnd_test_fake_drop_event(self.capture.window_id, True, ['text/uri-list'], move[0]+1, move[1]+1) + self.wait_for_state('drop_data_requests', ((1, 0, 0),)) + dnd_test_fake_drop_data(self.capture.window_id, 'text/uri-list', move_uri) + self.wait_for_state('last_drop_action', GLFW_DRAG_OPERATION_MOVE) + self.wait_for_state('drop_action', 0) + if remote_client: + # Remote move: source items must NOT be deleted (the source app handles deletion) + self.assertTrue(os.path.exists(move_file), 'remote move: source file must not be deleted') + self.assertTrue(os.path.isdir(move_dir), 'remote move: source directory must not be deleted') + self.assertTrue(os.path.lexists(move_link), 'remote move: source symlink must not be deleted') + else: + # Local move: source items must be deleted after successful move + self.assertFalse(os.path.exists(move_file), 'local move: source file must be deleted') + self.assertFalse(os.path.exists(move_dir), 'local move: source directory must be deleted') + self.assertFalse(os.path.lexists(move_link), 'local move: source symlink must be deleted') + def assert_files_have_same_content(self, a, b): with open(a, 'rb') as fa, open(b, 'rb') as fb: self.assertEqual(fa.read(), fb.read(), f'{a} ({os.path.getsize(a)}) != {b} ({os.path.getsize(b)})')