diff --git a/kitty/char_grid.py b/kitty/char_grid.py index 4a01ff5df..0dc7ffbed 100644 --- a/kitty/char_grid.py +++ b/kitty/char_grid.py @@ -2,6 +2,7 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2016, Kovid Goyal +import re from collections import namedtuple from ctypes import addressof, memmove, sizeof from threading import Lock @@ -264,22 +265,24 @@ class CharGrid: return int(x // cell_size.width), int(y // cell_size.height) def update_drag(self, is_press, x, y): - with self.buffer_lock: - x, y = self.cell_for_pos(x, y) - if is_press: - self.current_selection.start_x = self.current_selection.end_x = x - self.current_selection.start_y = self.current_selection.end_y = y - self.current_selection.start_scrolled_by = self.current_selection.end_scrolled_by = self.scrolled_by - self.current_selection.in_progress = True - elif self.current_selection.in_progress: - self.current_selection.end_x = x - self.current_selection.end_y = y - self.current_selection.end_scrolled_by = self.scrolled_by - if is_press is False: - self.current_selection.in_progress = False - text = self.text_for_selection() - if text and text.strip(): - set_primary_selection(text) + x, y = self.cell_for_pos(x, y) + if 0 <= x < self.screen.columns and 0 <= y < self.screen.lines: + ps = None + with self.buffer_lock: + if is_press: + self.current_selection.start_x = self.current_selection.end_x = x + self.current_selection.start_y = self.current_selection.end_y = y + self.current_selection.start_scrolled_by = self.current_selection.end_scrolled_by = self.scrolled_by + self.current_selection.in_progress = True + elif self.current_selection.in_progress: + self.current_selection.end_x = x + self.current_selection.end_y = y + self.current_selection.end_scrolled_by = self.scrolled_by + if is_press is False: + self.current_selection.in_progress = False + ps = self.text_for_selection() + if ps and ps.strip(): + set_primary_selection(ps) def screen_line(self, y): ' Return the Line object corresponding to the yth line on the rendered screen ' @@ -291,6 +294,39 @@ class CharGrid: else: return self.screen.line(y) + def multi_click(self, count, x, y): + x, y = self.cell_for_pos(x, y) + line = self.screen_line(y) + if line is not None and 0 <= x < self.screen.columns and count in (2, 3): + s = self.current_selection + s.start_scrolled_by = s.end_scrolled_by = self.scrolled_by + s.start_y = s.end_y = y + s.in_progress = False + if count == 3: + for i in range(self.screen.columns): + if line[i] != ' ': + s.start_x = i + break + else: + s.start_x = 0 + for i in range(self.screen.columns): + c = self.screen.columns - 1 - i + if line[c] != ' ': + s.end_x = c + break + else: + s.end_x = self.screen.columns - 1 + elif count == 2: + i = x + pat = re.compile(r'\w') + while i >= 0 and pat.match(line[i]) is not None: + i -= 1 + s.start_x = i if i == x else i + 1 + i = x + while i < self.screen.columns and pat.match(line[i]) is not None: + i += 1 + s.end_x = i if i == x else i - 1 + def text_for_selection(self, sel=None): start, end = (sel or self.current_selection).limits(self.scrolled_by) lines = [] diff --git a/kitty/config.py b/kitty/config.py index e3056de28..db85893a5 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -46,6 +46,7 @@ type_map = { 'repaint_delay': int, 'window_border_width': float, 'wheel_scroll_multiplier': float, + 'click_interval': float, } for name in 'foreground background cursor active_border_color inactive_border_color selection_foreground selection_background'.split(): diff --git a/kitty/kitty.conf b/kitty/kitty.conf index 7621a52bf..7075a72ce 100644 --- a/kitty/kitty.conf +++ b/kitty/kitty.conf @@ -33,6 +33,9 @@ scrollback_lines 2000 # Wheel scroll multiplier (modify the amount scrolled by the mouse wheel) wheel_scroll_multiplier 5.0 +# The interval between successive clicks to detect double/triple clicks (in seconds) +click_interval 0.5 + # Delay (in milliseconds) between screen updates. Decreasing it, increases fps # at the cost of more CPU usage. The default value yields ~50fps which is more # that sufficient for most uses. diff --git a/kitty/window.py b/kitty/window.py index 78e89d0ce..d41da9f7c 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -4,7 +4,9 @@ import os import weakref +from collections import deque from functools import partial +from time import monotonic import glfw import glfw_constants @@ -21,6 +23,7 @@ class Window: def __init__(self, tab, child, opts, args): self.tabref = weakref.ref(tab) + self.click_queue = deque(maxlen=3) self.geometry = WindowGeometry(0, 0, 0, 0, 0, 0) self.needs_layout = True self.title = appname @@ -113,11 +116,23 @@ class Window: def request_capabilities(self, q): self.write_to_child(get_capabilities(q)) + def dispatch_multi_click(self, x, y): + if len(self.click_queue) > 2 and self.click_queue[-1] - self.click_queue[-3] <= 2 * self.opts.click_interval: + self.char_grid.multi_click(3, x, y) + glfw.glfwPostEmptyEvent() + elif len(self.click_queue) > 1 and self.click_queue[-1] - self.click_queue[-2] <= self.opts.click_interval: + self.char_grid.multi_click(2, x, y) + glfw.glfwPostEmptyEvent() + def on_mouse_button(self, window, button, action, mods): ignore_mouse_mode = mods == glfw_constants.GLFW_MOD_SHIFT or not self.screen.mouse_button_tracking_enabled() if button == glfw_constants.GLFW_MOUSE_BUTTON_1 and ignore_mouse_mode: x, y = glfw.glfwGetCursorPos(window) - self.char_grid.update_drag(action == glfw_constants.GLFW_PRESS, max(0, x - self.geometry.left), max(0, y - self.geometry.top)) + x, y = max(0, x - self.geometry.left), max(0, y - self.geometry.top) + self.char_grid.update_drag(action == glfw_constants.GLFW_PRESS, x, y) + if action == glfw_constants.GLFW_RELEASE: + self.click_queue.append(monotonic()) + self.dispatch_multi_click(x, y) if action == glfw_constants.GLFW_RELEASE: if button == glfw_constants.GLFW_MOUSE_BUTTON_MIDDLE: self.paste_from_selection()