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
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 triple click to select a line and then drag to select more lines.
* You can right click to extend a previous selection.
* 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
platforms with a primary clipboard).
* You can middle click to paste from the primary clipboard (on platforms
with a primary clipboard).
* You can select text with kitty even when a terminal program has grabbed
the mouse by holding down the :kbd:`shift` key (see also
:opt:`terminal_select_modifiers`).
the mouse by holding down the :kbd:`shift` key.
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))]
@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]:
parts = action.strip().split(maxsplit=1)
func = parts[0]

View File

@ -560,10 +560,6 @@ url_style_map = dict(
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=_('''
The program with which to open URLs that are clicked on.
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
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=_('''
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
@ -1357,6 +1346,17 @@ to force the choice.'''))
g('mousemap') # {{{
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') # {{{

View File

@ -10,6 +10,11 @@ from kitty.fonts.render import FontObject
from kitty.options_stub import Options
# 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
NO_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:
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
update_drag(bool from_button, Window *w, bool is_release, int modifiers, int button) {
update_drag(Window *w) {
Screen *screen = w->render_data.screen;
if (from_button) {
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) {
if (screen && 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);
}
set_mouse_cursor_when_dragging();
@ -246,7 +232,7 @@ do_drag_scroll(Window *w, bool upwards) {
Screen *screen = w->render_data.screen;
if (screen->linebuf == screen->main_linebuf) {
screen_history_scroll(screen, SCROLL_LINE, upwards);
update_drag(false, w, false, 0, -1);
update_drag(w);
if (mouse_cursor_shape != ARROW) {
mouse_cursor_shape = ARROW;
set_mouse_cursor(mouse_cursor_shape);
@ -276,7 +262,6 @@ extend_selection(Window *w, bool ended) {
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);
}
set_mouse_cursor_when_dragging();
}
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)) {
monotonic_t now = monotonic();
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;
}
}
@ -394,8 +379,7 @@ HANDLER(handle_move_event) {
bool in_tracking_mode = (
screen->modes.mouse_tracking_mode == ANY_MODE ||
(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 || has_terminal_select_modifiers;
bool handle_in_kitty = !in_tracking_mode || global_state.active_drag_in_window == w->id;
if (handle_in_kitty) {
handle_mouse_movement_in_kitty(w, button, mouse_cell_changed | cell_half_changed);
} 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
distance(double x1, double y1, double x2, double y2) {
return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
@ -602,6 +563,59 @@ enter_event() {
#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
mouse_event(int button, int modifiers, int action) {
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) {
w = window_for_id(global_state.active_drag_in_window);
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, DRAG);
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;
return true;
}

View File

@ -576,11 +576,6 @@ resolve_mods(int mods) {
return mods;
}
static int
convert_mods(PyObject *obj) {
return resolve_mods(PyLong_AsLong(obj));
}
static WindowTitleIn
window_title_in(PyObject *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(wheel_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(resize_debounce_time, parse_s_double_to_monotonic_t);
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;
}
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(remove_window)
THREE_ID(click_mouse_url)
@ -1186,6 +1189,7 @@ static PyMethodDef module_methods[] = {
MW(next_window_id, METH_NOARGS),
MW(set_options, METH_VARARGS),
MW(click_mouse_url, METH_VARARGS),
MW(mouse_selection, METH_VARARGS),
MW(set_in_sequence_mode, METH_O),
MW(resolve_key_mods, METH_VARARGS),
MW(handle_for_window_id, METH_VARARGS),

View File

@ -27,9 +27,6 @@ typedef struct {
CursorShape cursor_shape;
float cursor_beam_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 scrollback_pager_history_size;
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);
Window* window_for_window_id(id_type kitty_window_id);
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,
STRIKETHROUGH, TINT_PROGRAM, KeyEvent, Screen, add_timer, add_window,
cell_size_for_window, click_mouse_url, compile_program, encode_key_for_tty,
get_boss, get_clipboard_string, init_cell_program, pt_to_px,
set_clipboard_string, set_titlebar_color, set_window_padding,
get_boss, get_clipboard_string, init_cell_program, mouse_selection,
pt_to_px, set_clipboard_string, set_titlebar_color, set_window_padding,
set_window_render_data, update_window_title, update_window_visibility,
viewport_for_window
)
@ -297,6 +297,7 @@ class Window:
watchers: Optional[Watchers] = None
):
self.watchers = watchers or Watchers()
self.current_mouse_event_button = 0
self.prev_osc99_cmd = NotificationCommand()
self.action_on_close: 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:
ev = MouseEvent(**event)
self.current_mouse_event_button = ev.button
action = self.opts.mousemap.get(ev)
if action is None:
return False
@ -796,8 +798,11 @@ class Window:
# }}}
# 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)
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: