#!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal from functools import partial from kitty.fast_data_types import ( GLFW_MOD_ALT, GLFW_MOD_CONTROL, GLFW_MOUSE_BUTTON_LEFT, GLFW_MOUSE_BUTTON_RIGHT, MOUSE_SELECTION_WORD_AND_LINE_FROM_POINT, create_mock_window, mock_mouse_selection, send_mock_mouse_event_to_window, ) from . import BaseTest def send_mouse_event( window, button=-1, modifiers=0, is_release=False, x=0.0, y=0, clear_click_queue=False, ): ix = int(x) in_left_half_of_cell = x - ix < 0.5 send_mock_mouse_event_to_window( window, button, modifiers, is_release, ix, y, clear_click_queue, in_left_half_of_cell ) class TestMouse(BaseTest): def test_mouse_selection(self): s = self.create_screen( options=dict( rectangle_select_modifiers=GLFW_MOD_ALT | GLFW_MOD_CONTROL ) ) w = create_mock_window(s) ev = partial(send_mouse_event, w) def mouse_selection(code: int) -> None: mock_mouse_selection(w, s.callbacks.current_mouse_button, code) s.callbacks.mouse_selection = mouse_selection def sel(): return ''.join(s.text_for_selection()) def init(): s.reset() s.draw('pqrst') s.draw('uvwxy') s.draw('ABCDE') s.draw('FGHIJ') s.draw('KLMNO') s.draw('12345') s.draw('67890') s.draw('abcde') s.draw('fghij') s.draw('klmno') def press(x=0, y=0, modifiers=0, button=GLFW_MOUSE_BUTTON_LEFT): ev(button, x=x, y=y, modifiers=modifiers) def release(x=0, y=0, button=GLFW_MOUSE_BUTTON_LEFT): ev( button, x=x, y=y, is_release=True, clear_click_queue=True ) def move(x=0, y=0, button=-1, q=None): ev(x=x, y=y, button=button) if q is not None: sl = sel() from kitty.window import as_text self.ae(sl, q, f'{sl!r} != {q!r} after movement to x={x} y={y}. Screen contents: {as_text(s)!r}') def multi_click(x=0, y=0, count=2, modifiers=0): clear_click_queue = True while count > 0: count -= 1 ev(GLFW_MOUSE_BUTTON_LEFT, x=x, y=y, modifiers=modifiers, clear_click_queue=clear_click_queue) clear_click_queue = False def scroll(x=0, y=0, up=True): move(x=x, y=y, button=-2 if up else -3) # Single line click, move, release test init() press() move(x=3.6, q='1234') release(x=3.6) self.ae(sel(), '1234') press(x=4), release(x=0.6) self.ae(sel(), '234') # multi line movement init() press(x=2, y=2) move(x=2, y=1, q='890ab') move(x=2.6, y=1, q='90ab') move(y=1, q='67890ab') move(x=4, y=1, q='0ab') move(x=4.6, y=1, q='ab') move(q='1234567890ab') move(x=2, y=3, q='cdefg') move(y=3, q='cde') move(x=0.6, y=3, q='cdef') move(x=2.6, y=3, q='cdefgh') move(x=4.6, y=3, q='cdefghij') # Single cell select init() press(), release(1) self.ae(sel(), '1') press(3), release(2) self.ae(sel(), '3') # Multi-line click release init() press(1, 1), release(3.6, 2) self.ae(sel(), '7890abcd') press(1.6, 1), release(3, 2) self.ae(sel(), '890abc') press(3.6, 4), release(2, 2) self.ae(sel(), 'cdefghijklmn') press(3, 4), release(2.6, 2) self.ae(sel(), 'defghijklm') # Word select with drag s.reset() s.draw('ab cd') s.draw(' f gh') s.draw(' stuv') s.draw('X Y') multi_click(x=1.4) self.ae(sel(), 'ab') move(2.6) self.ae(sel(), 'ab ') move(3.6) self.ae(sel(), 'ab cd') move(2.6) self.ae(sel(), 'ab ') release(3.6, 1) self.ae(sel(), 'ab cd f gh') multi_click(x=1, y=2) self.ae(sel(), 'stuvX') release() multi_click(x=3.6) self.ae(sel(), 'cd') move(0.2) release() self.ae(sel(), 'ab cd') multi_click(x=4.4) self.ae(sel(), 'cd') move(x=4.4, y=1) self.ae(sel(), 'cd f gh') move(x=4.4, y=0) self.ae(sel(), 'cd') release() multi_click(x=4.4, y=1) self.ae(sel(), 'gh') move(x=4.4, y=0) self.ae(sel(), 'cd f gh') move(x=4.4, y=1) self.ae(sel(), 'gh') release() multi_click(x=4.4) self.ae(sel(), 'cd') move() self.ae(sel(), 'ab cd') move(x=1, y=1) self.ae(sel(), 'ab cd f') move() self.ae(sel(), 'ab cd') release() multi_click(x=1.4) self.ae(sel(), 'ab') move(x=4.4) self.ae(sel(), 'ab cd') move(x=4.4, y=1) self.ae(sel(), 'ab cd f gh') move(x=4.4) self.ae(sel(), 'ab cd') # Line select with drag s.reset() s.draw('1 2 3') s.linefeed(), s.carriage_return() s.draw('4 5 6') s.linefeed(), s.carriage_return() s.draw('7 8 9X') multi_click(x=1, count=3) self.ae(sel(), str(s.line(0))) move(y=1) self.ae(sel(), '1 2 3\n4 5 6') move(y=2) self.ae(sel(), '1 2 3\n4 5 6\n7 8 9X') move(y=1) self.ae(sel(), '1 2 3\n4 5 6') move() self.ae(sel(), str(s.line(0))) release() multi_click(y=1, count=3) self.ae(sel(), '4 5 6') move(y=0) self.ae(sel(), '1 2 3\n4 5 6') move(y=1) self.ae(sel(), '4 5 6') move(y=2) self.ae(sel(), '4 5 6\n7 8 9X') release() s.reset() s.draw(' 123') s.linefeed(), s.carriage_return() s.draw(' 456') s.linefeed(), s.carriage_return() multi_click(x=1, count=3) self.ae(sel(), '123') move(x=2, y=1) self.ae(sel(), '123\n 456') release() press(x=2, y=1, button=GLFW_MOUSE_BUTTON_RIGHT) release(x=2, y=1, button=GLFW_MOUSE_BUTTON_RIGHT) self.ae(sel(), '123\n 456') press(button=GLFW_MOUSE_BUTTON_RIGHT) self.ae(sel(), ' 123\n 456') release(button=GLFW_MOUSE_BUTTON_RIGHT) # line select for wrapped lines in scrollback s.reset() s.draw('ABCDE12345') s.linefeed(), s.carriage_return() s.draw(('X' * s.columns) * (s.lines-1)) multi_click(x=1, count=3) self.ae(sel(), 'ABCDE12345') s.reset() s.draw('ABCDE12345') s.linefeed(), s.carriage_return() s.draw('678') s.linefeed(), s.carriage_return() s.draw(('X' * s.columns) * (s.lines-2)) multi_click(x=1, y=1, count=3) self.ae(sel(), '678') press(x=2, button=GLFW_MOUSE_BUTTON_RIGHT) release(x=2, button=GLFW_MOUSE_BUTTON_RIGHT) self.ae(sel(), 'ABCDE12345\n678') # line select for wrapped lines below viewport s.reset() s.scroll(100, False) # draw enough content to enable scrolling: fill lines-1 visual lines, then draw a long wrapped line s.draw(('X' * s.columns) * (s.lines - 1)) s.linefeed(), s.carriage_return() s.draw('ABCDE12345') # wraps to 2 visual lines, second one pushed below viewport # scroll up by 1 so top visual line of the wrapped text is visible but bottom wraps off-screen s.scroll(1, True) multi_click(x=1, y=s.lines - 1, count=3) self.ae(sel(), 'ABCDE12345') # extending selection to a line that wraps below viewport s.reset() s.scroll(100, False) s.draw(('X' * s.columns) * (s.lines - 2)) s.linefeed(), s.carriage_return() s.draw('678') s.linefeed(), s.carriage_return() s.draw('ABCDE12345') s.scroll(1, True) multi_click(x=1, y=3, count=3) self.ae(sel(), '678') press(x=2, y=s.lines - 1, button=GLFW_MOUSE_BUTTON_RIGHT) release(x=2, y=s.lines - 1, button=GLFW_MOUSE_BUTTON_RIGHT) self.ae(sel(), '678\nABCDE12345') s.scroll(100, False) # word select for wrapped words in scrollback s.reset() s.draw('abcde12345') s.linefeed(), s.carriage_return() s.draw(('X' * s.columns) * (s.lines-1)) multi_click(x=1) self.ae(sel(), 'abcde12345') # word select for wrapped words below viewport s.reset() s.scroll(100, False) s.draw(('X' * s.columns) * (s.lines - 1)) s.linefeed(), s.carriage_return() s.draw('abcde12345') s.scroll(1, True) multi_click(x=1, y=s.lines - 1) self.ae(sel(), 'abcde12345') s.scroll(100, False) # Rectangle select init() press(x=1, y=1, modifiers=GLFW_MOD_ALT | GLFW_MOD_CONTROL) move(x=3.6, y=3) self.ae(sel(), '789bcdghi') release(x=3, y=3) self.ae(sel(), '78bcgh') press(x=3.6, y=1, modifiers=GLFW_MOD_ALT | GLFW_MOD_CONTROL) self.ae(sel(), '') move(x=1, y=3) self.ae(sel(), '789bcdghi') release(x=1.6) self.ae(sel(), '3489') # scrolling init() press(x=1.6) scroll(x=1) self.ae(sel(), 'LMNO12') scroll(x=1) self.ae(sel(), 'GHIJKLMNO12') scroll(x=1, up=False) self.ae(sel(), 'LMNO12') scroll(x=2.6, up=False) self.ae(sel(), '3') release() # fractional scrolling init() s.fractional_scroll(-0.5) press() move(x=3.6, q='1234') release(x=3.6) self.ae(sel(), '1234') # extending selections init() press() move(x=3.6, q='1234') release(x=3.6) self.ae(sel(), '1234') press(x=1, y=1, button=GLFW_MOUSE_BUTTON_RIGHT) self.ae(sel(), '123456') move(x=2, y=1, q='1234567') release(x=3, y=1, button=GLFW_MOUSE_BUTTON_RIGHT) self.ae(sel(), '12345678') init() press(y=2) move(x=3.6, y=2, q='abcd') press(x=3, y=0, button=GLFW_MOUSE_BUTTON_RIGHT) self.ae(sel(), '4567890abcd') # blank line select s.reset() s.draw('abcde') s.linefeed(), s.carriage_return() s.linefeed(), s.carriage_return() s.draw('12345') press(x=0, y=0) move(x=2, y=2, q='abcde\n\n12') # Line from begin select (alt+triple click): selects from column 0 # (including any leading whitespace), unlike normal triple click which strips it s.reset() s.draw(' 123') s.linefeed(), s.carriage_return() s.draw(' 456') s.linefeed(), s.carriage_return() multi_click(x=1, count=3, modifiers=GLFW_MOD_ALT) self.ae(sel(), ' 123') multi_click(x=1, count=3) self.ae(sel(), '123') multi_click(x=1, count=3, modifiers=GLFW_MOD_ALT) move(y=1) self.ae(sel(), ' 123\n 456') release() # Line from point select (ctrl+alt+triple click): selects from click # position to end of line; cannot start before the first non-blank char s.reset() s.draw(' 123') s.linefeed(), s.carriage_return() s.draw(' 456') s.linefeed(), s.carriage_return() multi_click(x=2, count=3, modifiers=GLFW_MOD_ALT | GLFW_MOD_CONTROL) self.ae(sel(), '23') # click before first non-blank: selection starts at first non-blank multi_click(x=0, count=3, modifiers=GLFW_MOD_ALT | GLFW_MOD_CONTROL) self.ae(sel(), '123') # click past last non-blank char: no selection created multi_click(x=4, count=3, modifiers=GLFW_MOD_ALT | GLFW_MOD_CONTROL) self.ae(sel(), '') # drag to next line multi_click(x=2, count=3, modifiers=GLFW_MOD_ALT | GLFW_MOD_CONTROL) move(y=1) self.ae(sel(), '23\n 456') release() # Word and line from point: selects the word at the click point and # extends the selection to the end of the line sel_word_and_line = MOUSE_SELECTION_WORD_AND_LINE_FROM_POINT s.reset() s.draw('ab cd') press(x=0) mock_mouse_selection(w, GLFW_MOUSE_BUTTON_LEFT, sel_word_and_line) self.ae(sel(), 'ab cd') press(x=3) mock_mouse_selection(w, GLFW_MOUSE_BUTTON_LEFT, sel_word_and_line) self.ae(sel(), 'cd') # click on space: no word, selects from space to end of line press(x=2) mock_mouse_selection(w, GLFW_MOUSE_BUTTON_LEFT, sel_word_and_line) self.ae(sel(), ' cd') # click past last non-blank char: no selection created press(x=4) mock_mouse_selection(w, GLFW_MOUSE_BUTTON_LEFT, sel_word_and_line) self.ae(sel(), '') # Double-click on whitespace: no word found, no selection created s.reset() s.draw('ab cd') multi_click(x=2) self.ae(sel(), '') multi_click(x=2.4) self.ae(sel(), '')