mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-07-10 01:41:16 +00:00
Add tests for drop move events
This commit is contained in:
parent
4c484ca1e8
commit
54eab02709
3 changed files with 104 additions and 17 deletions
|
|
@ -110,19 +110,28 @@ func run_loop(opts *Options, drop_dests map[string]drop_dest, drag_sources map[s
|
|||
const move_on_drop = 2
|
||||
|
||||
var copy_button_region, move_button_region button_region
|
||||
var offered_mimes_buf strings.Builder
|
||||
|
||||
on_drop_move := func(cell_x, cell_y int, offered_mimes string) (needs_rerender bool) {
|
||||
on_drop_move := func(cell_x, cell_y int, has_more bool, offered_mimes string) (needs_rerender bool) {
|
||||
prev_status := drop_status
|
||||
drop_status.cell_x, drop_status.cell_y = cell_x, cell_y
|
||||
if offered_mimes != "" {
|
||||
offered_mimes_buf.WriteString(offered_mimes)
|
||||
if has_more {
|
||||
return
|
||||
}
|
||||
offered_mimes := offered_mimes_buf.String()
|
||||
drop_status.offered_mimes = strings.Fields(offered_mimes)
|
||||
drop_status.accepted_mimes = make([]string, 0, len(drop_status.offered_mimes))
|
||||
seen := utils.NewSet[string](len(drop_status.offered_mimes))
|
||||
for _, x := range drop_status.offered_mimes {
|
||||
if _, found := drop_dests[x]; found {
|
||||
if _, found := drop_dests[x]; found && !seen.Has(x) {
|
||||
drop_status.accepted_mimes = append(drop_status.accepted_mimes, x)
|
||||
seen.Add(x)
|
||||
}
|
||||
}
|
||||
}
|
||||
offered_mimes_buf.Reset()
|
||||
if copy_button_region.has(cell_x, cell_y) {
|
||||
drop_status.action = copy_on_drop
|
||||
} else if move_button_region.has(cell_x, cell_y) {
|
||||
|
|
@ -139,10 +148,13 @@ func run_loop(opts *Options, drop_dests map[string]drop_dest, drag_sources map[s
|
|||
}
|
||||
}
|
||||
drop_status.in_window = cell_x > -1 && cell_y > -1
|
||||
if !drop_status.in_window {
|
||||
drop_status.offered_mimes = nil
|
||||
}
|
||||
mimes_changed := !slices.Equal(prev_status.accepted_mimes, drop_status.accepted_mimes)
|
||||
needs_rerender = prev_status.action != drop_status.action || mimes_changed
|
||||
if needs_rerender {
|
||||
c := DC{Type: 'm'}
|
||||
c := DC{Type: 'm', Operation: drop_status.action}
|
||||
if drop_status.action != 0 && len(drop_status.accepted_mimes) > 0 {
|
||||
c.Payload = utils.UnsafeStringToBytes(strings.Join(drop_status.accepted_mimes, " "))
|
||||
}
|
||||
|
|
@ -152,12 +164,13 @@ func run_loop(opts *Options, drop_dests map[string]drop_dest, drag_sources map[s
|
|||
return
|
||||
}
|
||||
|
||||
render_screen := func() error {
|
||||
render_screen := func() error { // {{{
|
||||
if !in_test_mode {
|
||||
lp.StartAtomicUpdate()
|
||||
defer lp.EndAtomicUpdate()
|
||||
}
|
||||
lp.ClearScreen()
|
||||
copy_button_region, move_button_region = button_region{}, button_region{}
|
||||
if drag_started {
|
||||
lp.Println("Dragging data...")
|
||||
return nil
|
||||
|
|
@ -196,7 +209,7 @@ func run_loop(opts *Options, drop_dests map[string]drop_dest, drag_sources map[s
|
|||
}
|
||||
frame_width, padding_width := 4, 8
|
||||
text_width := len("copymove")
|
||||
scale := 4
|
||||
scale := 5
|
||||
for scale > 1 && frame_width+padding_width+text_width*scale > int(sz.WidthCells) {
|
||||
scale--
|
||||
}
|
||||
|
|
@ -241,16 +254,17 @@ func run_loop(opts *Options, drop_dests map[string]drop_dest, drag_sources map[s
|
|||
lp.Printf("\x1b[%dm", fg)
|
||||
}
|
||||
render_box(1, "Copy", ©_button_region)
|
||||
lp.QueueWriteString("\x1b[m")
|
||||
lp.QueueWriteString("\x1b[39m")
|
||||
box_width := 6 + len("move")*scale
|
||||
if drop_status.action == move_on_drop {
|
||||
lp.Printf("\x1b[%dm", fg)
|
||||
}
|
||||
render_box(1+int(sz.WidthCells)-box_width, "Move", &move_button_region)
|
||||
lp.QueueWriteString("\x1b[m")
|
||||
lp.QueueWriteString("\x1b[39m")
|
||||
_ = in_test_mode
|
||||
return nil
|
||||
}
|
||||
} // }}}
|
||||
|
||||
lp.OnInitialize = func() (string, error) {
|
||||
lp.AllowLineWrapping(false)
|
||||
lp.SetCursorVisible(false)
|
||||
|
|
@ -319,6 +333,14 @@ func run_loop(opts *Options, drop_dests map[string]drop_dest, drag_sources map[s
|
|||
case "SETUP":
|
||||
in_test_mode = true
|
||||
lp.NoRoundtripToTerminalOnExit()
|
||||
case "GEOMETRY":
|
||||
send_test_response(fmt.Sprintf("GEOMETRY:%d:%d:%d:%d:%d:%d:%d:%d", copy_button_region.left, copy_button_region.top, copy_button_region.width, copy_button_region.height, move_button_region.left, move_button_region.top, move_button_region.width, move_button_region.height))
|
||||
case "DROP_MIMES":
|
||||
if drop_status.offered_mimes != nil {
|
||||
send_test_response(strings.Join(drop_status.offered_mimes, " "))
|
||||
} else {
|
||||
send_test_response("")
|
||||
}
|
||||
default:
|
||||
send_test_response("UNKNOWN TEST COMMAND: " + string(cmd.Payload))
|
||||
}
|
||||
|
|
@ -328,7 +350,7 @@ func run_loop(opts *Options, drop_dests map[string]drop_dest, drag_sources map[s
|
|||
if cmd.Payload != nil {
|
||||
payload = utils.UnsafeBytesToString(cmd.Payload)
|
||||
}
|
||||
if on_drop_move(cmd.X, cmd.Y, payload) {
|
||||
if on_drop_move(cmd.X, cmd.Y, cmd.Has_more, payload) {
|
||||
render_screen()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2196,6 +2196,13 @@ dnd_test_probe_state(PyObject *self UNUSED, PyObject *args) {
|
|||
if (strcmp(q, "drag_is_remote_client") == 0) {
|
||||
return Py_NewRef(w->drag_source.is_remote_client ? Py_True : Py_False);
|
||||
}
|
||||
if (strcmp(q, "drop_action") == 0) {
|
||||
return PyLong_FromLong((long)w->drop.accepted_operation);
|
||||
}
|
||||
if (strcmp(q, "drop_mimes") == 0) {
|
||||
if (w->drop.accepted_mimes == NULL) return PyUnicode_FromString("");
|
||||
return PyUnicode_FromStringAndSize(w->drop.accepted_mimes, w->drop.accepted_mimes_sz);
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
#!/usr/bin/env python
|
||||
# License: GPLv3 Copyright: 2026, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from base64 import standard_b64encode
|
||||
|
||||
from kitty.constants import kitten_exe
|
||||
from kitty.fast_data_types import (
|
||||
DND_CODE,
|
||||
GLFW_DRAG_OPERATION_COPY,
|
||||
GLFW_DRAG_OPERATION_MOVE,
|
||||
dnd_set_test_write_func,
|
||||
dnd_test_cleanup_fake_window,
|
||||
dnd_test_create_fake_window,
|
||||
|
|
@ -33,11 +36,16 @@ class TestDnDKitten(BaseTest):
|
|||
capture.os_window_id = os_window_id
|
||||
self.capture = capture
|
||||
self.test_dir = self.enterContext(tempfile.TemporaryDirectory())
|
||||
self.kitten_wd = os.path.join(self.test_dir, 'kitten')
|
||||
os.mkdir(self.kitten_wd)
|
||||
self.src_data_dir = os.path.join(self.test_dir, 'src')
|
||||
os.mkdir(self.src_data_dir)
|
||||
self.messages_from_kitten = ''
|
||||
self.set_options({'tab_bar_style': 'hidden'})
|
||||
|
||||
def send_dnd_command_to_kitten(self, payload=b'', as_base64=False, flush=False, **metadata):
|
||||
def send_dnd_command_to_kitten(self, payload=b'', as_base64=False, flush=False, t='T', **metadata):
|
||||
header = f'\x1b]{DND_CODE};'
|
||||
metadata['t'] = t
|
||||
for k, v in metadata.items():
|
||||
header = header + f'{k}={v}:'
|
||||
self.pty.write_to_child(header.encode())
|
||||
|
|
@ -62,7 +70,7 @@ class TestDnDKitten(BaseTest):
|
|||
cmd = [kitten_exe(), 'dnd']
|
||||
if remote_client:
|
||||
cmd.append('--machine-id=remote-client-for-test')
|
||||
self.pty = self.enterContext(PTY(argv=cmd, rows=25, columns=80, window_id=self.capture.window_id))
|
||||
self.pty = self.enterContext(PTY(argv=cmd, cwd=self.kitten_wd, rows=25, columns=80, window_id=self.capture.window_id))
|
||||
self.pty.callbacks.printbuf = self
|
||||
self.screen = self.pty.screen
|
||||
self.pty.wait_till(lambda: bool(self.pty.callbacks.titlebuf))
|
||||
|
|
@ -70,7 +78,23 @@ class TestDnDKitten(BaseTest):
|
|||
self.assertEqual(remote_client, self.probe_state('drop_is_remote_client'))
|
||||
if self.probe_state('drag_can_offer'):
|
||||
self.assertEqual(remote_client, self.probe_state('drag_is_remote_client'))
|
||||
self.send_dnd_command_to_kitten('SETUP', t='T')
|
||||
self.send_dnd_command_to_kitten('SETUP')
|
||||
|
||||
def get_button_geometry(self, are_present: bool = True):
|
||||
self.send_dnd_command_to_kitten('GEOMETRY')
|
||||
self.pty.wait_till(lambda: bool(self.messages_from_kitten))
|
||||
self.assertTrue(self.messages_from_kitten.startswith('GEOMETRY') and self.messages_from_kitten.endswith('\n'),
|
||||
f'Unexpected messages from kitten: {self.messages_from_kitten!r}')
|
||||
q, self.messages_from_kitten = self.messages_from_kitten.rstrip(), ''
|
||||
parts = tuple(map(int, q.split(':')[1:]))
|
||||
copy, move = parts[:4], parts[4:]
|
||||
if are_present:
|
||||
self.assertGreater(copy[2], 4)
|
||||
self.assertGreater(move[2], 4)
|
||||
else:
|
||||
self.assertEqual(copy, (0,0,0,0))
|
||||
self.assertEqual(move, (0,0,0,0))
|
||||
return copy, move
|
||||
|
||||
def append(self, text):
|
||||
self.messages_from_kitten += text
|
||||
|
|
@ -79,12 +103,21 @@ class TestDnDKitten(BaseTest):
|
|||
q = '\n'.join(responses)
|
||||
def wait_till():
|
||||
return q == self.messages_from_kitten.strip()
|
||||
self.pty.wait_till(wait_till, timeout, lambda: f'Responses so far: {self.messages_from_kitten!r}')
|
||||
self.messages_from_kitten = ''
|
||||
try:
|
||||
self.pty.wait_till(wait_till, timeout, lambda: f'Responses so far: {self.messages_from_kitten!r}')
|
||||
finally:
|
||||
self.messages_from_kitten = ''
|
||||
|
||||
def wait_for_state(self, q, expected, timeout=10):
|
||||
self.pty.wait_till(lambda: self.probe_state(q) == expected, timeout, lambda: f'{q}: {self.probe_state(q)!r}')
|
||||
|
||||
def probe_state(self, which: str):
|
||||
return dnd_test_probe_state(self.capture.window_id, which)
|
||||
|
||||
def roundtrip(self):
|
||||
self.send_dnd_command_to_kitten('PING')
|
||||
self.wait_for_responses('PONG')
|
||||
|
||||
def tearDown(self):
|
||||
dnd_set_test_write_func(None)
|
||||
dnd_test_cleanup_fake_window(self.capture.os_window_id)
|
||||
|
|
@ -93,6 +126,31 @@ class TestDnDKitten(BaseTest):
|
|||
self.pty = None
|
||||
|
||||
def test_dnd_kitten_drop(self):
|
||||
for remote_client in (False, True):
|
||||
with self.subTest(remote_client=remote_client):
|
||||
self.finish_setup(remote_client=remote_client)
|
||||
self.finish_setup(remote_client=False)
|
||||
self.dnd_kitten_drop(False)
|
||||
|
||||
def test_dnd_kitten_drop_remote(self):
|
||||
self.finish_setup(remote_client=True)
|
||||
self.dnd_kitten_drop(True)
|
||||
|
||||
def dnd_kitten_drop(self, remote_client):
|
||||
copy, move = self.get_button_geometry()
|
||||
all_mimes = 'text/uri-list a/b c/d'
|
||||
for b, expected in ((copy, GLFW_DRAG_OPERATION_COPY), (move, GLFW_DRAG_OPERATION_MOVE)):
|
||||
self.send_dnd_command_to_kitten(all_mimes, t='m', x=str(b[0] + 1), y=str(b[1] + 1))
|
||||
self.wait_for_state('drop_action', expected)
|
||||
self.assertEqual('text/uri-list', self.probe_state('drop_mimes').rstrip('\x00'))
|
||||
self.send_dnd_command_to_kitten('DROP_MIMES')
|
||||
self.wait_for_responses(all_mimes)
|
||||
self.send_dnd_command_to_kitten(t='m', x='-1', y='-1')
|
||||
self.send_dnd_command_to_kitten('DROP_MIMES')
|
||||
self.wait_for_responses('')
|
||||
large_mimes = (all_mimes + ' ') * 300
|
||||
self.assertGreater(len(large_mimes), 4096)
|
||||
self.send_dnd_command_to_kitten(all_mimes, t='m', x=str(copy[0] + 1), y=str(copy[1] + 1))
|
||||
self.wait_for_state('drop_action', GLFW_DRAG_OPERATION_COPY)
|
||||
self.send_dnd_command_to_kitten('DROP_MIMES')
|
||||
self.wait_for_responses(all_mimes)
|
||||
self.send_dnd_command_to_kitten(t='m', x='-1', y='-1')
|
||||
self.send_dnd_command_to_kitten('DROP_MIMES')
|
||||
self.wait_for_responses('')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue