Port mouse selection handling to the new generic mouse actions framework

This commit is contained in:
Kovid Goyal 2021-05-10 21:33:13 +05:30
parent e944945b7a
commit 858dac5601
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
8 changed files with 124 additions and 74 deletions

View File

@ -259,19 +259,18 @@ Mouse features
------------------- -------------------
* You can hold down :kbd:`ctrl+shift` and click on a URL to open it in a * You can hold down :kbd:`ctrl+shift` and click on a URL to open it in a
browser (see also :opt:`open_url_modifiers`). browser.
* You can double click to select a word and then drag to select more words. * You can double click to select a word and then drag to select more words.
* You can triple click to select a line and then drag to select more lines. * You can triple click to select a line and then drag to select more lines.
* You can right click to extend a previous selection. * You can right click to extend a previous selection.
* You can hold down :kbd:`ctrl+alt` and drag with the mouse to select in * You can hold down :kbd:`ctrl+alt` and drag with the mouse to select in
columns (see also :opt:`rectangle_select_modifiers`). columns.
* Selecting text automatically copies it to the primary clipboard (on * Selecting text automatically copies it to the primary clipboard (on
platforms with a primary clipboard). platforms with a primary clipboard).
* You can middle click to paste from the primary clipboard (on platforms * You can middle click to paste from the primary clipboard (on platforms
with a primary clipboard). with a primary clipboard).
* You can select text with kitty even when a terminal program has grabbed * You can select text with kitty even when a terminal program has grabbed
the mouse by holding down the :kbd:`shift` key (see also the mouse by holding down the :kbd:`shift` key.
:opt:`terminal_select_modifiers`).
Font control Font control

View File

@ -326,6 +326,21 @@ def scroll_to_mark(func: str, rest: str) -> FuncArgsType:
return func, [parts[0] != 'next', max(0, min(int(parts[1]), 3))] return func, [parts[0] != 'next', max(0, min(int(parts[1]), 3))]
@func_with_args('mouse_selection')
def mouse_selection(func: str, rest: str) -> FuncArgsType:
cmap = getattr(mouse_selection, 'code_map', None)
if cmap is None:
cmap = {
'normal': defines.MOUSE_SELECTION_NORMAL,
'extend': defines.MOUSE_SELECTION_EXTEND,
'rectangle': defines.MOUSE_SELECTION_RECTANGLE,
'word': defines.MOUSE_SELECTION_WORD,
'line': defines.MOUSE_SELECTION_LINE,
}
setattr(mouse_selection, 'code_map', cmap)
return func, [cmap[rest]]
def parse_key_action(action: str) -> Optional[KeyAction]: def parse_key_action(action: str) -> Optional[KeyAction]:
parts = action.strip().split(maxsplit=1) parts = action.strip().split(maxsplit=1)
func = parts[0] func = parts[0]

View File

@ -560,10 +560,6 @@ url_style_map = dict(
o('url_style', 'curly', option_type=url_style) o('url_style', 'curly', option_type=url_style)
o('open_url_modifiers', 'kitty_mod', option_type=to_modifiers, long_text=_('''
The modifier keys to press when clicking with the
mouse on URLs to open the URL'''))
o('open_url_with', 'default', option_type=to_cmdline, long_text=_(''' o('open_url_with', 'default', option_type=to_cmdline, long_text=_('''
The program with which to open URLs that are clicked on. The program with which to open URLs that are clicked on.
The special value :code:`default` means to use the The special value :code:`default` means to use the
@ -613,13 +609,6 @@ Remove spaces at the end of lines when copying to clipboard.
A value of :code:`smart` will do it when using normal selections, but not rectangle A value of :code:`smart` will do it when using normal selections, but not rectangle
selections. :code:`always` will always do it.''')) selections. :code:`always` will always do it.'''))
o('rectangle_select_modifiers', 'ctrl+alt', option_type=to_modifiers, long_text=_('''
The modifiers to use rectangular selection (i.e. to select text in a
rectangular block with the mouse)'''))
o('terminal_select_modifiers', 'shift', option_type=to_modifiers, long_text=_('''
The modifiers to override mouse selection even when a terminal application has grabbed the mouse'''))
o('select_by_word_characters', '@-./_~?&=%+#', long_text=_(''' o('select_by_word_characters', '@-./_~?&=%+#', long_text=_('''
Characters considered part of a word when double clicking. In addition to these characters Characters considered part of a word when double clicking. In addition to these characters
any character that is marked as an alphanumeric character in the unicode any character that is marked as an alphanumeric character in the unicode
@ -1357,6 +1346,17 @@ to force the choice.'''))
g('mousemap') # {{{ g('mousemap') # {{{
m('click_url', 'ctrl+shift+left', 'release', 'grabbed,ungrabbed', 'mouse_click_url', _('Click the link under the mouse cursor')) m('click_url', 'ctrl+shift+left', 'release', 'grabbed,ungrabbed', 'mouse_click_url', _('Click the link under the mouse cursor'))
m('paste_selection', 'middle', 'release', 'grabbed,ungrabbed', 'paste_selection', _('Paste from the primary selection'))
m('extend_selection', 'right', 'press', 'grabbed,ungrabbed', 'mouse_selection extend', _('Extend the current selection'))
for grabbed in (False, True):
modes = 'ungrabbed' + (',grabbed' if grabbed else '')
name_s = '_grabbed' if grabbed else ''
mods_p = 'shift+' if grabbed else ''
m('start_simple_selection' + name_s, mods_p + 'left', 'press', modes, 'mouse_selection normal', _('Start selecting text'))
m('start_rectangle_selection' + name_s, mods_p + 'ctrl+alt+left', 'press', modes, 'mouse_selection rectangle',
_('Start selecting text in a rectangle'))
m('select_word' + name_s, mods_p + 'left', 'doublepress', modes, 'mouse_selection word', _('Select a word'))
m('select_line' + name_s, mods_p + 'left', 'triplepress', modes, 'mouse_selection line', _('Select a line'))
# }}} # }}}
g('shortcuts') # {{{ g('shortcuts') # {{{

View File

@ -10,6 +10,11 @@ from kitty.fonts.render import FontObject
from kitty.options_stub import Options from kitty.options_stub import Options
# Constants {{{ # Constants {{{
MOUSE_SELECTION_LINE: int
MOUSE_SELECTION_EXTEND: int
MOUSE_SELECTION_NORMAL: int
MOUSE_SELECTION_WORD: int
MOUSE_SELECTION_RECTANGLE: int
KITTY_VCS_REV: str KITTY_VCS_REV: str
NO_CLOSE_REQUESTED: int NO_CLOSE_REQUESTED: int
IMPERATIVE_CLOSE_REQUESTED: int IMPERATIVE_CLOSE_REQUESTED: int
@ -1157,3 +1162,7 @@ def set_window_padding(os_window_id: int, tab_id: int, window_id: int, left: int
def click_mouse_url(os_window_id: int, tab_id: int, window_id: int) -> None: def click_mouse_url(os_window_id: int, tab_id: int, window_id: int) -> None:
pass pass
def mouse_selection(os_window_id: int, tab_id: int, window_id: int, code: int, button: int) -> None:
pass

View File

@ -219,23 +219,9 @@ set_mouse_cursor_when_dragging(void) {
} }
static inline void static inline void
update_drag(bool from_button, Window *w, bool is_release, int modifiers, int button) { update_drag(Window *w) {
Screen *screen = w->render_data.screen; Screen *screen = w->render_data.screen;
if (from_button) { if (screen && screen->selections.in_progress) {
if (is_release) {
global_state.active_drag_in_window = 0;
global_state.active_drag_button = -1;
w->last_drag_scroll_at = 0;
if (screen->selections.in_progress) {
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, true, false);
}
}
else {
global_state.active_drag_in_window = w->id;
global_state.active_drag_button = button;
screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, modifiers == (int)OPT(rectangle_select_modifiers) || modifiers == ((int)OPT(rectangle_select_modifiers) | (int)OPT(terminal_select_modifiers)), EXTEND_CELL);
}
} else if (screen->selections.in_progress) {
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, false); screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, false);
} }
set_mouse_cursor_when_dragging(); set_mouse_cursor_when_dragging();
@ -246,7 +232,7 @@ do_drag_scroll(Window *w, bool upwards) {
Screen *screen = w->render_data.screen; Screen *screen = w->render_data.screen;
if (screen->linebuf == screen->main_linebuf) { if (screen->linebuf == screen->main_linebuf) {
screen_history_scroll(screen, SCROLL_LINE, upwards); screen_history_scroll(screen, SCROLL_LINE, upwards);
update_drag(false, w, false, 0, -1); update_drag(w);
if (mouse_cursor_shape != ARROW) { if (mouse_cursor_shape != ARROW) {
mouse_cursor_shape = ARROW; mouse_cursor_shape = ARROW;
set_mouse_cursor(mouse_cursor_shape); set_mouse_cursor(mouse_cursor_shape);
@ -276,7 +262,6 @@ extend_selection(Window *w, bool ended) {
if (screen_has_selection(screen)) { if (screen_has_selection(screen)) {
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, ended, false); screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, ended, false);
} }
set_mouse_cursor_when_dragging();
} }
static inline void static inline void
@ -367,7 +352,7 @@ handle_mouse_movement_in_kitty(Window *w, int button, bool mouse_cell_changed) {
if (screen->selections.in_progress && (button == global_state.active_drag_button)) { if (screen->selections.in_progress && (button == global_state.active_drag_button)) {
monotonic_t now = monotonic(); monotonic_t now = monotonic();
if ((now - w->last_drag_scroll_at) >= ms_to_monotonic_t(20ll) || mouse_cell_changed) { if ((now - w->last_drag_scroll_at) >= ms_to_monotonic_t(20ll) || mouse_cell_changed) {
update_drag(false, w, false, 0, button); update_drag(w);
w->last_drag_scroll_at = now; w->last_drag_scroll_at = now;
} }
} }
@ -394,8 +379,7 @@ HANDLER(handle_move_event) {
bool in_tracking_mode = ( bool in_tracking_mode = (
screen->modes.mouse_tracking_mode == ANY_MODE || screen->modes.mouse_tracking_mode == ANY_MODE ||
(screen->modes.mouse_tracking_mode == MOTION_MODE && button >= 0)); (screen->modes.mouse_tracking_mode == MOTION_MODE && button >= 0));
bool has_terminal_select_modifiers = modifiers == (int)OPT(terminal_select_modifiers) || modifiers == ((int)OPT(rectangle_select_modifiers) | (int)OPT(terminal_select_modifiers)); bool handle_in_kitty = !in_tracking_mode || global_state.active_drag_in_window == w->id;
bool handle_in_kitty = !in_tracking_mode || has_terminal_select_modifiers;
if (handle_in_kitty) { if (handle_in_kitty) {
handle_mouse_movement_in_kitty(w, button, mouse_cell_changed | cell_half_changed); handle_mouse_movement_in_kitty(w, button, mouse_cell_changed | cell_half_changed);
} else { } else {
@ -405,29 +389,6 @@ HANDLER(handle_move_event) {
} }
} }
static inline void
multi_click(Window *w, unsigned int count) {
Screen *screen = w->render_data.screen;
index_type start, end;
SelectionExtendMode mode = EXTEND_CELL;
unsigned int y1, y2;
switch(count) {
case 2:
if (screen_selection_range_for_word(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, &y1, &y2, &start, &end, true)) mode = EXTEND_WORD;
break;
case 3:
if (screen_selection_range_for_line(screen, w->mouse_pos.cell_y, &start, &end)) mode = EXTEND_LINE;
break;
default:
break;
}
if (mode != EXTEND_CELL) {
screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, mode);
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, true);
}
set_mouse_cursor_when_dragging();
}
static inline double static inline double
distance(double x1, double y1, double x2, double y2) { distance(double x1, double y1, double x2, double y2) {
return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
@ -602,6 +563,59 @@ enter_event() {
#endif #endif
} }
static void
end_drag(Window *w) {
Screen *screen = w->render_data.screen;
global_state.active_drag_in_window = 0;
global_state.active_drag_button = -1;
w->last_drag_scroll_at = 0;
if (screen->selections.in_progress) {
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, true, false);
}
}
typedef enum MouseSelectionType {
MOUSE_SELECTION_NORMAL,
MOUSE_SELECTION_EXTEND,
MOUSE_SELECTION_RECTANGLE,
MOUSE_SELECTION_WORD,
MOUSE_SELECTION_LINE,
} MouseSelectionType;
void
mouse_selection(Window *w, int code, int button) {
global_state.active_drag_in_window = w->id;
global_state.active_drag_button = button;
Screen *screen = w->render_data.screen;
index_type start, end;
unsigned int y1, y2;
#define S(mode) {\
screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, mode); \
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, true); }
switch((MouseSelectionType)code) {
case MOUSE_SELECTION_NORMAL:
screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, EXTEND_CELL);
break;
case MOUSE_SELECTION_RECTANGLE:
screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, true, EXTEND_CELL);
break;
case MOUSE_SELECTION_WORD:
if (screen_selection_range_for_word(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, &y1, &y2, &start, &end, true)) S(EXTEND_WORD);
break;
case MOUSE_SELECTION_LINE:
if (screen_selection_range_for_line(screen, w->mouse_pos.cell_y, &start, &end)) S(EXTEND_LINE);
break;
case MOUSE_SELECTION_EXTEND:
extend_selection(w, false);
break;
}
set_mouse_cursor_when_dragging();
#undef S
}
void void
mouse_event(int button, int modifiers, int action) { mouse_event(int button, int modifiers, int action) {
MouseShape old_cursor = mouse_cursor_shape; MouseShape old_cursor = mouse_cursor_shape;
@ -626,7 +640,8 @@ mouse_event(int button, int modifiers, int action) {
else if (action == GLFW_RELEASE && button == global_state.active_drag_button) { else if (action == GLFW_RELEASE && button == global_state.active_drag_button) {
w = window_for_id(global_state.active_drag_in_window); w = window_for_id(global_state.active_drag_in_window);
if (w) { if (w) {
update_drag(true, w, true, modifiers, button); end_drag(w);
return;
} }
} }
} }
@ -801,6 +816,11 @@ init_mouse(PyObject *module) {
PyModule_AddIntMacro(module, RELEASE); PyModule_AddIntMacro(module, RELEASE);
PyModule_AddIntMacro(module, DRAG); PyModule_AddIntMacro(module, DRAG);
PyModule_AddIntMacro(module, MOVE); PyModule_AddIntMacro(module, MOVE);
PyModule_AddIntMacro(module, MOUSE_SELECTION_NORMAL);
PyModule_AddIntMacro(module, MOUSE_SELECTION_EXTEND);
PyModule_AddIntMacro(module, MOUSE_SELECTION_RECTANGLE);
PyModule_AddIntMacro(module, MOUSE_SELECTION_WORD);
PyModule_AddIntMacro(module, MOUSE_SELECTION_LINE);
if (PyModule_AddFunctions(module, module_methods) != 0) return false; if (PyModule_AddFunctions(module, module_methods) != 0) return false;
return true; return true;
} }

View File

@ -576,11 +576,6 @@ resolve_mods(int mods) {
return mods; return mods;
} }
static int
convert_mods(PyObject *obj) {
return resolve_mods(PyLong_AsLong(obj));
}
static WindowTitleIn static WindowTitleIn
window_title_in(PyObject *title_in) { window_title_in(PyObject *title_in) {
const char *in = PyUnicode_AsUTF8(title_in); const char *in = PyUnicode_AsUTF8(title_in);
@ -719,9 +714,6 @@ PYWRAP1(set_options) {
S(mouse_hide_wait, parse_s_double_to_monotonic_t); S(mouse_hide_wait, parse_s_double_to_monotonic_t);
S(wheel_scroll_multiplier, PyFloat_AsDouble); S(wheel_scroll_multiplier, PyFloat_AsDouble);
S(touch_scroll_multiplier, PyFloat_AsDouble); S(touch_scroll_multiplier, PyFloat_AsDouble);
S(open_url_modifiers, convert_mods);
S(rectangle_select_modifiers, convert_mods);
S(terminal_select_modifiers, convert_mods);
S(click_interval, parse_s_double_to_monotonic_t); S(click_interval, parse_s_double_to_monotonic_t);
S(resize_debounce_time, parse_s_double_to_monotonic_t); S(resize_debounce_time, parse_s_double_to_monotonic_t);
S(mark1_foreground, color_as_int); S(mark1_foreground, color_as_int);
@ -1163,6 +1155,17 @@ click_mouse_url(id_type os_window_id, id_type tab_id, id_type window_id) {
END_WITH_WINDOW; END_WITH_WINDOW;
} }
static PyObject*
pymouse_selection(PyObject *self UNUSED, PyObject *args) {
id_type os_window_id, tab_id, window_id;
int code, button;
PA("KKKii", &os_window_id, &tab_id, &window_id, &code, &button);
WITH_WINDOW(os_window_id, tab_id, window_id);
mouse_selection(window, code, button);
END_WITH_WINDOW;
Py_RETURN_NONE;
}
THREE_ID_OBJ(update_window_title) THREE_ID_OBJ(update_window_title)
THREE_ID(remove_window) THREE_ID(remove_window)
THREE_ID(click_mouse_url) THREE_ID(click_mouse_url)
@ -1186,6 +1189,7 @@ static PyMethodDef module_methods[] = {
MW(next_window_id, METH_NOARGS), MW(next_window_id, METH_NOARGS),
MW(set_options, METH_VARARGS), MW(set_options, METH_VARARGS),
MW(click_mouse_url, METH_VARARGS), MW(click_mouse_url, METH_VARARGS),
MW(mouse_selection, METH_VARARGS),
MW(set_in_sequence_mode, METH_O), MW(set_in_sequence_mode, METH_O),
MW(resolve_key_mods, METH_VARARGS), MW(resolve_key_mods, METH_VARARGS),
MW(handle_for_window_id, METH_VARARGS), MW(handle_for_window_id, METH_VARARGS),

View File

@ -27,9 +27,6 @@ typedef struct {
CursorShape cursor_shape; CursorShape cursor_shape;
float cursor_beam_thickness; float cursor_beam_thickness;
float cursor_underline_thickness; float cursor_underline_thickness;
unsigned int open_url_modifiers;
unsigned int rectangle_select_modifiers;
unsigned int terminal_select_modifiers;
unsigned int url_style; unsigned int url_style;
unsigned int scrollback_pager_history_size; unsigned int scrollback_pager_history_size;
bool scrollback_fill_enlarged_window; bool scrollback_fill_enlarged_window;
@ -293,3 +290,4 @@ void update_os_window_title(OSWindow *os_window);
void fake_scroll(Window *w, int amount, bool upwards); void fake_scroll(Window *w, int amount, bool upwards);
Window* window_for_window_id(id_type kitty_window_id); Window* window_for_window_id(id_type kitty_window_id);
void mouse_open_url(Window *w); void mouse_open_url(Window *w);
void mouse_selection(Window *w, int code, int button);

View File

@ -27,8 +27,8 @@ from .fast_data_types import (
MARK, MARK_MASK, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, MARK, MARK_MASK, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE,
STRIKETHROUGH, TINT_PROGRAM, KeyEvent, Screen, add_timer, add_window, STRIKETHROUGH, TINT_PROGRAM, KeyEvent, Screen, add_timer, add_window,
cell_size_for_window, click_mouse_url, compile_program, encode_key_for_tty, cell_size_for_window, click_mouse_url, compile_program, encode_key_for_tty,
get_boss, get_clipboard_string, init_cell_program, pt_to_px, get_boss, get_clipboard_string, init_cell_program, mouse_selection,
set_clipboard_string, set_titlebar_color, set_window_padding, pt_to_px, set_clipboard_string, set_titlebar_color, set_window_padding,
set_window_render_data, update_window_title, update_window_visibility, set_window_render_data, update_window_title, update_window_visibility,
viewport_for_window viewport_for_window
) )
@ -297,6 +297,7 @@ class Window:
watchers: Optional[Watchers] = None watchers: Optional[Watchers] = None
): ):
self.watchers = watchers or Watchers() self.watchers = watchers or Watchers()
self.current_mouse_event_button = 0
self.prev_osc99_cmd = NotificationCommand() self.prev_osc99_cmd = NotificationCommand()
self.action_on_close: Optional[Callable] = None self.action_on_close: Optional[Callable] = None
self.action_on_removal: Optional[Callable] = None self.action_on_removal: Optional[Callable] = None
@ -533,6 +534,7 @@ class Window:
def on_mouse_event(self, event: Dict[str, Any]) -> bool: def on_mouse_event(self, event: Dict[str, Any]) -> bool:
ev = MouseEvent(**event) ev = MouseEvent(**event)
self.current_mouse_event_button = ev.button
action = self.opts.mousemap.get(ev) action = self.opts.mousemap.get(ev)
if action is None: if action is None:
return False return False
@ -796,8 +798,11 @@ class Window:
# }}} # }}}
# mouse actions {{{ # mouse actions {{{
def mouse_click_url(self) -> bool: def mouse_click_url(self) -> None:
click_mouse_url(self.os_window_id, self.tab_id, self.id) click_mouse_url(self.os_window_id, self.tab_id, self.id)
def mouse_selection(self, code: int) -> None:
mouse_selection(self.os_window_id, self.tab_id, self.id, code, self.current_mouse_event_button)
# }}} # }}}
def text_for_selection(self) -> str: def text_for_selection(self) -> str: