Merge the char_grid and window modules
This commit is contained in:
parent
240c683504
commit
42329e5d46
@ -5,7 +5,6 @@
|
|||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
from weakref import WeakValueDictionary
|
from weakref import WeakValueDictionary
|
||||||
|
|
||||||
from .char_grid import load_shader_programs
|
|
||||||
from .config import MINIMUM_FONT_SIZE
|
from .config import MINIMUM_FONT_SIZE
|
||||||
from .constants import (
|
from .constants import (
|
||||||
MODIFIER_KEYS, cell_size, is_key_pressed, mouse_button_pressed,
|
MODIFIER_KEYS, cell_size, is_key_pressed, mouse_button_pressed,
|
||||||
@ -13,8 +12,8 @@ from .constants import (
|
|||||||
)
|
)
|
||||||
from .fast_data_types import (
|
from .fast_data_types import (
|
||||||
GLFW_MOUSE_BUTTON_1, GLFW_PRESS, GLFW_REPEAT, ChildMonitor,
|
GLFW_MOUSE_BUTTON_1, GLFW_PRESS, GLFW_REPEAT, ChildMonitor,
|
||||||
destroy_global_data, destroy_sprite_map,
|
destroy_global_data, destroy_sprite_map, glfw_post_empty_event,
|
||||||
glfw_post_empty_event, layout_sprite_map
|
layout_sprite_map
|
||||||
)
|
)
|
||||||
from .fonts.render import render_cell_wrapper, set_font_family
|
from .fonts.render import render_cell_wrapper, set_font_family
|
||||||
from .keys import (
|
from .keys import (
|
||||||
@ -23,6 +22,7 @@ from .keys import (
|
|||||||
from .session import create_session
|
from .session import create_session
|
||||||
from .tabs import SpecialWindow, TabManager
|
from .tabs import SpecialWindow, TabManager
|
||||||
from .utils import safe_print
|
from .utils import safe_print
|
||||||
|
from .window import load_shader_programs
|
||||||
|
|
||||||
|
|
||||||
class DumpCommands: # {{{
|
class DumpCommands: # {{{
|
||||||
|
|||||||
@ -1,152 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# vim:fileencoding=utf-8
|
|
||||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
|
||||||
|
|
||||||
import re
|
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
from .config import build_ansi_color_table
|
|
||||||
from .constants import ScreenGeometry, cell_size, viewport_size
|
|
||||||
from .fast_data_types import (
|
|
||||||
CELL_PROGRAM, CURSOR_PROGRAM, compile_program, init_cell_program,
|
|
||||||
init_cursor_program
|
|
||||||
)
|
|
||||||
from .rgb import to_color
|
|
||||||
from .utils import color_as_int, load_shaders, open_url, set_primary_selection
|
|
||||||
|
|
||||||
|
|
||||||
class DynamicColor(Enum):
|
|
||||||
default_fg, default_bg, cursor_color, highlight_fg, highlight_bg = range(1, 6)
|
|
||||||
|
|
||||||
|
|
||||||
def load_shader_programs():
|
|
||||||
compile_program(CELL_PROGRAM, *load_shaders('cell'))
|
|
||||||
init_cell_program()
|
|
||||||
compile_program(CURSOR_PROGRAM, *load_shaders('cursor'))
|
|
||||||
init_cursor_program()
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_gl_geometry(window_geometry, viewport_width, viewport_height, cell_width, cell_height):
|
|
||||||
dx, dy = 2 * cell_width / viewport_width, 2 * cell_height / viewport_height
|
|
||||||
xmargin = window_geometry.left / viewport_width
|
|
||||||
ymargin = window_geometry.top / viewport_height
|
|
||||||
xstart = -1 + 2 * xmargin
|
|
||||||
ystart = 1 - 2 * ymargin
|
|
||||||
return ScreenGeometry(xstart, ystart, window_geometry.xnum, window_geometry.ynum, dx, dy)
|
|
||||||
|
|
||||||
|
|
||||||
class CharGrid:
|
|
||||||
|
|
||||||
url_pat = re.compile('(?:http|https|file|ftp)://\S+', re.IGNORECASE)
|
|
||||||
|
|
||||||
def __init__(self, screen, opts):
|
|
||||||
self.screen_reversed = False
|
|
||||||
self.screen = screen
|
|
||||||
self.opts = opts
|
|
||||||
self.screen.color_profile.update_ansi_color_table(build_ansi_color_table(opts))
|
|
||||||
self.screen.color_profile.set_configured_colors(*map(color_as_int, (
|
|
||||||
opts.foreground, opts.background, opts.cursor, opts.selection_foreground, opts.selection_background)))
|
|
||||||
self.opts = opts
|
|
||||||
self.opts = opts
|
|
||||||
|
|
||||||
def update_position(self, window_geometry):
|
|
||||||
self.screen_geometry = sg = calculate_gl_geometry(window_geometry, viewport_size.width, viewport_size.height, cell_size.width, cell_size.height)
|
|
||||||
return sg
|
|
||||||
|
|
||||||
def change_colors(self, changes):
|
|
||||||
dirtied = False
|
|
||||||
|
|
||||||
def item(raw):
|
|
||||||
if raw is None:
|
|
||||||
return 0
|
|
||||||
val = to_color(raw)
|
|
||||||
return None if val is None else (color_as_int(val) << 8) | 2
|
|
||||||
|
|
||||||
for which, val in changes.items():
|
|
||||||
val = item(val)
|
|
||||||
if val is None:
|
|
||||||
continue
|
|
||||||
dirtied = True
|
|
||||||
setattr(self.screen.color_profile, which.name, val)
|
|
||||||
if dirtied:
|
|
||||||
self.screen.mark_as_dirty()
|
|
||||||
|
|
||||||
def cell_for_pos(self, x, y):
|
|
||||||
x, y = int(x // cell_size.width), int(y // cell_size.height)
|
|
||||||
if 0 <= x < self.screen.columns and 0 <= y < self.screen.lines:
|
|
||||||
return x, y
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
def update_drag(self, is_press, mx, my):
|
|
||||||
x, y = self.cell_for_pos(mx, my)
|
|
||||||
if x is None:
|
|
||||||
x = 0 if mx <= cell_size.width else self.screen.columns - 1
|
|
||||||
y = 0 if my <= cell_size.height else self.screen.lines - 1
|
|
||||||
ps = None
|
|
||||||
if is_press:
|
|
||||||
self.screen.start_selection(x, y)
|
|
||||||
elif self.screen.is_selection_in_progress():
|
|
||||||
ended = is_press is False
|
|
||||||
self.screen.update_selection(x, y, ended)
|
|
||||||
if ended:
|
|
||||||
ps = self.text_for_selection()
|
|
||||||
if ps and ps.strip():
|
|
||||||
set_primary_selection(ps)
|
|
||||||
|
|
||||||
def has_url_at(self, x, y):
|
|
||||||
x, y = self.cell_for_pos(x, y)
|
|
||||||
if x is not None:
|
|
||||||
l = self.screen.visual_line(y)
|
|
||||||
if l is not None:
|
|
||||||
text = str(l)
|
|
||||||
for m in self.url_pat.finditer(text):
|
|
||||||
if m.start() <= x < m.end():
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def click_url(self, x, y):
|
|
||||||
x, y = self.cell_for_pos(x, y)
|
|
||||||
if x is not None:
|
|
||||||
l = self.screen.visual_line(y)
|
|
||||||
if l is not None:
|
|
||||||
text = str(l)
|
|
||||||
for m in self.url_pat.finditer(text):
|
|
||||||
if m.start() <= x < m.end():
|
|
||||||
url = ''.join(l[i] for i in range(*m.span())).rstrip('.')
|
|
||||||
# Remove trailing "] and similar
|
|
||||||
url = re.sub(r'''["'][)}\]]$''', '', url)
|
|
||||||
# Remove closing trailing character if it is matched by it's
|
|
||||||
# corresponding opening character before the url
|
|
||||||
if m.start() > 0:
|
|
||||||
before = l[m.start() - 1]
|
|
||||||
closing = {'(': ')', '[': ']', '{': '}', '<': '>', '"': '"', "'": "'", '`': '`', '|': '|', ':': ':'}.get(before)
|
|
||||||
if closing is not None and url.endswith(closing):
|
|
||||||
url = url[:-1]
|
|
||||||
if url:
|
|
||||||
open_url(url, self.opts.open_url_with)
|
|
||||||
|
|
||||||
def multi_click(self, count, x, y):
|
|
||||||
x, y = self.cell_for_pos(x, y)
|
|
||||||
if x is not None:
|
|
||||||
line = self.screen.visual_line(y)
|
|
||||||
if line is not None and count in (2, 3):
|
|
||||||
if count == 2:
|
|
||||||
start_x, xlimit = self.screen.selection_range_for_word(x, y, self.opts.select_by_word_characters)
|
|
||||||
end_x = max(start_x, xlimit - 1)
|
|
||||||
elif count == 3:
|
|
||||||
start_x, xlimit = self.screen.selection_range_for_line(y)
|
|
||||||
end_x = max(start_x, xlimit - 1)
|
|
||||||
self.screen.start_selection(start_x, y)
|
|
||||||
self.screen.update_selection(end_x, y, True)
|
|
||||||
ps = self.text_for_selection()
|
|
||||||
if ps:
|
|
||||||
set_primary_selection(ps)
|
|
||||||
|
|
||||||
def get_scrollback_as_ansi(self):
|
|
||||||
ans = []
|
|
||||||
self.screen.historybuf.as_ansi(ans.append)
|
|
||||||
self.screen.linebuf.as_ansi(ans.append)
|
|
||||||
return ''.join(ans).encode('utf-8')
|
|
||||||
|
|
||||||
def text_for_selection(self):
|
|
||||||
return ''.join(self.screen.text_for_selection())
|
|
||||||
@ -7,7 +7,6 @@ from functools import partial
|
|||||||
from itertools import count
|
from itertools import count
|
||||||
|
|
||||||
from .borders import Borders
|
from .borders import Borders
|
||||||
from .char_grid import calculate_gl_geometry
|
|
||||||
from .child import Child
|
from .child import Child
|
||||||
from .config import build_ansi_color_table
|
from .config import build_ansi_color_table
|
||||||
from .constants import (
|
from .constants import (
|
||||||
@ -20,7 +19,7 @@ from .fast_data_types import (
|
|||||||
)
|
)
|
||||||
from .layout import Rect, all_layouts
|
from .layout import Rect, all_layouts
|
||||||
from .utils import color_as_int
|
from .utils import color_as_int
|
||||||
from .window import Window
|
from .window import Window, calculate_gl_geometry
|
||||||
|
|
||||||
TabbarData = namedtuple('TabbarData', 'title is_active is_last')
|
TabbarData = namedtuple('TabbarData', 'title is_active is_last')
|
||||||
borders = None
|
borders = None
|
||||||
|
|||||||
198
kitty/window.py
198
kitty/window.py
@ -2,30 +2,42 @@
|
|||||||
# vim:fileencoding=utf-8
|
# vim:fileencoding=utf-8
|
||||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import weakref
|
import weakref
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
from enum import Enum
|
||||||
from itertools import count
|
from itertools import count
|
||||||
from time import monotonic
|
from time import monotonic
|
||||||
|
|
||||||
from .char_grid import CharGrid, DynamicColor
|
from .config import build_ansi_color_table
|
||||||
from .constants import (
|
from .constants import (
|
||||||
WindowGeometry, appname, cell_size, get_boss, is_key_pressed,
|
ScreenGeometry, WindowGeometry, appname, cell_size, get_boss,
|
||||||
mouse_button_pressed, wakeup
|
is_key_pressed, mouse_button_pressed, viewport_size, wakeup
|
||||||
)
|
)
|
||||||
from .fast_data_types import (
|
from .fast_data_types import (
|
||||||
ANY_MODE, BRACKETED_PASTE_END, BRACKETED_PASTE_START, GLFW_KEY_DOWN,
|
ANY_MODE, BRACKETED_PASTE_END, BRACKETED_PASTE_START, CELL_PROGRAM,
|
||||||
GLFW_KEY_LEFT_SHIFT, GLFW_KEY_RIGHT_SHIFT, GLFW_KEY_UP, GLFW_MOD_SHIFT,
|
CURSOR_PROGRAM, GLFW_KEY_DOWN, GLFW_KEY_LEFT_SHIFT, GLFW_KEY_RIGHT_SHIFT,
|
||||||
GLFW_MOUSE_BUTTON_1, GLFW_MOUSE_BUTTON_4, GLFW_MOUSE_BUTTON_5,
|
GLFW_KEY_UP, GLFW_MOD_SHIFT, GLFW_MOUSE_BUTTON_1, GLFW_MOUSE_BUTTON_4,
|
||||||
GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS, GLFW_RELEASE, MOTION_MODE,
|
GLFW_MOUSE_BUTTON_5, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS, GLFW_RELEASE,
|
||||||
SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, Screen, create_cell_vao,
|
MOTION_MODE, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, Screen,
|
||||||
glfw_post_empty_event, remove_vao, set_window_render_data,
|
compile_program, create_cell_vao, glfw_post_empty_event, init_cell_program,
|
||||||
|
init_cursor_program, remove_vao, set_window_render_data,
|
||||||
update_window_title, update_window_visibility
|
update_window_title, update_window_visibility
|
||||||
)
|
)
|
||||||
from .keys import get_key_map
|
from .keys import get_key_map
|
||||||
from .mouse import DRAG, MOVE, PRESS, RELEASE, encode_mouse_event
|
from .mouse import DRAG, MOVE, PRESS, RELEASE, encode_mouse_event
|
||||||
|
from .rgb import to_color
|
||||||
from .terminfo import get_capabilities
|
from .terminfo import get_capabilities
|
||||||
from .utils import get_primary_selection, parse_color_set, sanitize_title
|
from .utils import (
|
||||||
|
color_as_int, get_primary_selection, load_shaders, open_url,
|
||||||
|
parse_color_set, sanitize_title, set_primary_selection
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DynamicColor(Enum):
|
||||||
|
default_fg, default_bg, cursor_color, highlight_fg, highlight_bg = range(1, 6)
|
||||||
|
|
||||||
|
|
||||||
DYNAMIC_COLOR_CODES = {
|
DYNAMIC_COLOR_CODES = {
|
||||||
10: DynamicColor.default_fg,
|
10: DynamicColor.default_fg,
|
||||||
@ -39,6 +51,22 @@ window_counter = count()
|
|||||||
next(window_counter)
|
next(window_counter)
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_gl_geometry(window_geometry, viewport_width, viewport_height, cell_width, cell_height):
|
||||||
|
dx, dy = 2 * cell_width / viewport_width, 2 * cell_height / viewport_height
|
||||||
|
xmargin = window_geometry.left / viewport_width
|
||||||
|
ymargin = window_geometry.top / viewport_height
|
||||||
|
xstart = -1 + 2 * xmargin
|
||||||
|
ystart = 1 - 2 * ymargin
|
||||||
|
return ScreenGeometry(xstart, ystart, window_geometry.xnum, window_geometry.ynum, dx, dy)
|
||||||
|
|
||||||
|
|
||||||
|
def load_shader_programs():
|
||||||
|
compile_program(CELL_PROGRAM, *load_shaders('cell'))
|
||||||
|
init_cell_program()
|
||||||
|
compile_program(CURSOR_PROGRAM, *load_shaders('cursor'))
|
||||||
|
init_cursor_program()
|
||||||
|
|
||||||
|
|
||||||
class Window:
|
class Window:
|
||||||
|
|
||||||
def __init__(self, tab, child, opts, args):
|
def __init__(self, tab, child, opts, args):
|
||||||
@ -56,7 +84,9 @@ class Window:
|
|||||||
self.is_visible_in_layout = True
|
self.is_visible_in_layout = True
|
||||||
self.child, self.opts = child, opts
|
self.child, self.opts = child, opts
|
||||||
self.screen = Screen(self, 24, 80, opts.scrollback_lines)
|
self.screen = Screen(self, 24, 80, opts.scrollback_lines)
|
||||||
self.char_grid = CharGrid(self.screen, opts)
|
self.screen.color_profile.update_ansi_color_table(build_ansi_color_table(opts))
|
||||||
|
self.screen.color_profile.set_configured_colors(*map(color_as_int, (
|
||||||
|
opts.foreground, opts.background, opts.cursor, opts.selection_foreground, opts.selection_background)))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Window(title={}, id={})'.format(self.title, self.id)
|
return 'Window(title={}, id={})'.format(self.title, self.id)
|
||||||
@ -73,6 +103,10 @@ class Window:
|
|||||||
self.screen.mark_as_dirty()
|
self.screen.mark_as_dirty()
|
||||||
wakeup()
|
wakeup()
|
||||||
|
|
||||||
|
def update_position(self, window_geometry):
|
||||||
|
self.screen_geometry = sg = calculate_gl_geometry(window_geometry, viewport_size.width, viewport_size.height, cell_size.width, cell_size.height)
|
||||||
|
return sg
|
||||||
|
|
||||||
def set_geometry(self, window_idx, new_geometry):
|
def set_geometry(self, window_idx, new_geometry):
|
||||||
if self.destroyed:
|
if self.destroyed:
|
||||||
return
|
return
|
||||||
@ -82,11 +116,11 @@ class Window:
|
|||||||
current_pty_size = (
|
current_pty_size = (
|
||||||
self.screen.lines, self.screen.columns,
|
self.screen.lines, self.screen.columns,
|
||||||
max(0, new_geometry.right - new_geometry.left), max(0, new_geometry.bottom - new_geometry.top))
|
max(0, new_geometry.right - new_geometry.left), max(0, new_geometry.bottom - new_geometry.top))
|
||||||
sg = self.char_grid.update_position(new_geometry)
|
sg = self.update_position(new_geometry)
|
||||||
self.needs_layout = False
|
self.needs_layout = False
|
||||||
boss.child_monitor.resize_pty(self.id, *current_pty_size)
|
boss.child_monitor.resize_pty(self.id, *current_pty_size)
|
||||||
else:
|
else:
|
||||||
sg = self.char_grid.update_position(new_geometry)
|
sg = self.update_position(new_geometry)
|
||||||
set_window_render_data(self.tab_id, window_idx, self.vao_id, sg.xstart, sg.ystart, sg.dx, sg.dy, self.screen)
|
set_window_render_data(self.tab_id, window_idx, self.vao_id, sg.xstart, sg.ystart, sg.dx, sg.dy, self.screen)
|
||||||
self.geometry = new_geometry
|
self.geometry = new_geometry
|
||||||
|
|
||||||
@ -105,8 +139,7 @@ class Window:
|
|||||||
boss = get_boss()
|
boss = get_boss()
|
||||||
self.screen.reset_callbacks()
|
self.screen.reset_callbacks()
|
||||||
boss.gui_close_window(self)
|
boss.gui_close_window(self)
|
||||||
self.screen = self.char_grid.screen = None
|
self.screen = None
|
||||||
self.char_grid = None
|
|
||||||
|
|
||||||
def write_to_child(self, data):
|
def write_to_child(self, data):
|
||||||
if data:
|
if data:
|
||||||
@ -115,6 +148,7 @@ class Window:
|
|||||||
else:
|
else:
|
||||||
print('Failed to write to child %d as it does not exist' % self.id, file=sys.stderr)
|
print('Failed to write to child %d as it does not exist' % self.id, file=sys.stderr)
|
||||||
|
|
||||||
|
# screen callbacks {{{
|
||||||
def bell(self):
|
def bell(self):
|
||||||
boss = get_boss()
|
boss = get_boss()
|
||||||
boss.request_attention()
|
boss.request_attention()
|
||||||
@ -143,6 +177,24 @@ class Window:
|
|||||||
def icon_changed(self, new_icon):
|
def icon_changed(self, new_icon):
|
||||||
pass # TODO: Implement this
|
pass # TODO: Implement this
|
||||||
|
|
||||||
|
def change_colors(self, changes):
|
||||||
|
dirtied = False
|
||||||
|
|
||||||
|
def item(raw):
|
||||||
|
if raw is None:
|
||||||
|
return 0
|
||||||
|
val = to_color(raw)
|
||||||
|
return None if val is None else (color_as_int(val) << 8) | 2
|
||||||
|
|
||||||
|
for which, val in changes.items():
|
||||||
|
val = item(val)
|
||||||
|
if val is None:
|
||||||
|
continue
|
||||||
|
dirtied = True
|
||||||
|
setattr(self.screen.color_profile, which.name, val)
|
||||||
|
if dirtied:
|
||||||
|
self.screen.mark_as_dirty()
|
||||||
|
|
||||||
def set_dynamic_color(self, code, value):
|
def set_dynamic_color(self, code, value):
|
||||||
if isinstance(value, bytes):
|
if isinstance(value, bytes):
|
||||||
value = value.decode('utf-8')
|
value = value.decode('utf-8')
|
||||||
@ -154,11 +206,11 @@ class Window:
|
|||||||
val = None
|
val = None
|
||||||
color_changes[w] = val
|
color_changes[w] = val
|
||||||
code += 1
|
code += 1
|
||||||
self.char_grid.change_colors(color_changes)
|
self.change_colors(color_changes)
|
||||||
glfw_post_empty_event()
|
glfw_post_empty_event()
|
||||||
|
|
||||||
def set_color_table_color(self, code, value):
|
def set_color_table_color(self, code, value):
|
||||||
cp = self.char_grid.screen.color_profile
|
cp = self.screen.color_profile
|
||||||
if code == 4:
|
if code == 4:
|
||||||
for c, val in parse_color_set(value):
|
for c, val in parse_color_set(value):
|
||||||
cp.set_color(c, val)
|
cp.set_color(c, val)
|
||||||
@ -181,14 +233,93 @@ class Window:
|
|||||||
def request_capabilities(self, q):
|
def request_capabilities(self, q):
|
||||||
self.write_to_child(get_capabilities(q))
|
self.write_to_child(get_capabilities(q))
|
||||||
|
|
||||||
|
def buf_toggled(self, is_main_linebuf):
|
||||||
|
self.screen.scroll(SCROLL_FULL, False)
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
# mouse handling {{{
|
||||||
|
def multi_click(self, count, x, y):
|
||||||
|
x, y = self.cell_for_pos(x, y)
|
||||||
|
if x is not None:
|
||||||
|
line = self.screen.visual_line(y)
|
||||||
|
if line is not None and count in (2, 3):
|
||||||
|
if count == 2:
|
||||||
|
start_x, xlimit = self.screen.selection_range_for_word(x, y, self.opts.select_by_word_characters)
|
||||||
|
end_x = max(start_x, xlimit - 1)
|
||||||
|
elif count == 3:
|
||||||
|
start_x, xlimit = self.screen.selection_range_for_line(y)
|
||||||
|
end_x = max(start_x, xlimit - 1)
|
||||||
|
self.screen.start_selection(start_x, y)
|
||||||
|
self.screen.update_selection(end_x, y, True)
|
||||||
|
ps = self.text_for_selection()
|
||||||
|
if ps:
|
||||||
|
set_primary_selection(ps)
|
||||||
|
|
||||||
|
def cell_for_pos(self, x, y):
|
||||||
|
x, y = int(x // cell_size.width), int(y // cell_size.height)
|
||||||
|
if 0 <= x < self.screen.columns and 0 <= y < self.screen.lines:
|
||||||
|
return x, y
|
||||||
|
return None, None
|
||||||
|
|
||||||
def dispatch_multi_click(self, x, y):
|
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:
|
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)
|
self.multi_click(3, x, y)
|
||||||
glfw_post_empty_event()
|
glfw_post_empty_event()
|
||||||
elif len(self.click_queue) > 1 and self.click_queue[-1] - self.click_queue[-2] <= self.opts.click_interval:
|
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)
|
self.multi_click(2, x, y)
|
||||||
glfw_post_empty_event()
|
glfw_post_empty_event()
|
||||||
|
|
||||||
|
def update_drag(self, is_press, mx, my):
|
||||||
|
x, y = self.cell_for_pos(mx, my)
|
||||||
|
if x is None:
|
||||||
|
x = 0 if mx <= cell_size.width else self.screen.columns - 1
|
||||||
|
y = 0 if my <= cell_size.height else self.screen.lines - 1
|
||||||
|
ps = None
|
||||||
|
if is_press:
|
||||||
|
self.screen.start_selection(x, y)
|
||||||
|
elif self.screen.is_selection_in_progress():
|
||||||
|
ended = is_press is False
|
||||||
|
self.screen.update_selection(x, y, ended)
|
||||||
|
if ended:
|
||||||
|
ps = self.text_for_selection()
|
||||||
|
if ps and ps.strip():
|
||||||
|
set_primary_selection(ps)
|
||||||
|
|
||||||
|
def has_url_at(self, x, y):
|
||||||
|
x, y = self.cell_for_pos(x, y)
|
||||||
|
if x is not None:
|
||||||
|
l = self.screen.visual_line(y)
|
||||||
|
if l is not None:
|
||||||
|
text = str(l)
|
||||||
|
for m in self.url_pat.finditer(text):
|
||||||
|
if m.start() <= x < m.end():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def click_url(self, x, y):
|
||||||
|
x, y = self.cell_for_pos(x, y)
|
||||||
|
if x is not None:
|
||||||
|
l = self.screen.visual_line(y)
|
||||||
|
if l is not None:
|
||||||
|
text = str(l)
|
||||||
|
for m in self.url_pat.finditer(text):
|
||||||
|
if m.start() <= x < m.end():
|
||||||
|
url = ''.join(l[i] for i in range(*m.span())).rstrip('.')
|
||||||
|
# Remove trailing "] and similar
|
||||||
|
url = re.sub(r'''["'][)}\]]$''', '', url)
|
||||||
|
# Remove closing trailing character if it is matched by it's
|
||||||
|
# corresponding opening character before the url
|
||||||
|
if m.start() > 0:
|
||||||
|
before = l[m.start() - 1]
|
||||||
|
closing = {'(': ')', '[': ']', '{': '}', '<': '>', '"': '"', "'": "'", '`': '`', '|': '|', ':': ':'}.get(before)
|
||||||
|
if closing is not None and url.endswith(closing):
|
||||||
|
url = url[:-1]
|
||||||
|
if url:
|
||||||
|
open_url(url, self.opts.open_url_with)
|
||||||
|
|
||||||
|
def text_for_selection(self):
|
||||||
|
return ''.join(self.screen.text_for_selection())
|
||||||
|
|
||||||
def on_mouse_button(self, button, action, mods):
|
def on_mouse_button(self, button, action, mods):
|
||||||
mode = self.screen.mouse_tracking_mode()
|
mode = self.screen.mouse_tracking_mode()
|
||||||
handle_event = mods == GLFW_MOD_SHIFT or mode == 0 or button == GLFW_MOUSE_BUTTON_MIDDLE or (
|
handle_event = mods == GLFW_MOD_SHIFT or mode == 0 or button == GLFW_MOUSE_BUTTON_MIDDLE or (
|
||||||
@ -196,17 +327,17 @@ class Window:
|
|||||||
x, y = self.last_mouse_cursor_pos
|
x, y = self.last_mouse_cursor_pos
|
||||||
if handle_event:
|
if handle_event:
|
||||||
if button == GLFW_MOUSE_BUTTON_1:
|
if button == GLFW_MOUSE_BUTTON_1:
|
||||||
self.char_grid.update_drag(action == GLFW_PRESS, x, y)
|
self.update_drag(action == GLFW_PRESS, x, y)
|
||||||
if action == GLFW_RELEASE:
|
if action == GLFW_RELEASE:
|
||||||
if mods == self.char_grid.opts.open_url_modifiers:
|
if mods == self.opts.open_url_modifiers:
|
||||||
self.char_grid.click_url(x, y)
|
self.click_url(x, y)
|
||||||
self.click_queue.append(monotonic())
|
self.click_queue.append(monotonic())
|
||||||
self.dispatch_multi_click(x, y)
|
self.dispatch_multi_click(x, y)
|
||||||
elif button == GLFW_MOUSE_BUTTON_MIDDLE:
|
elif button == GLFW_MOUSE_BUTTON_MIDDLE:
|
||||||
if action == GLFW_RELEASE:
|
if action == GLFW_RELEASE:
|
||||||
self.paste_from_selection()
|
self.paste_from_selection()
|
||||||
else:
|
else:
|
||||||
x, y = self.char_grid.cell_for_pos(x, y)
|
x, y = self.cell_for_pos(x, y)
|
||||||
if x is not None:
|
if x is not None:
|
||||||
ev = encode_mouse_event(mode, self.screen.mouse_tracking_protocol(),
|
ev = encode_mouse_event(mode, self.screen.mouse_tracking_protocol(),
|
||||||
button, PRESS if action == GLFW_PRESS else RELEASE, mods, x, y)
|
button, PRESS if action == GLFW_PRESS else RELEASE, mods, x, y)
|
||||||
@ -225,9 +356,9 @@ class Window:
|
|||||||
is_key_pressed[GLFW_KEY_LEFT_SHIFT] or is_key_pressed[GLFW_KEY_RIGHT_SHIFT])
|
is_key_pressed[GLFW_KEY_LEFT_SHIFT] or is_key_pressed[GLFW_KEY_RIGHT_SHIFT])
|
||||||
x, y = 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.last_mouse_cursor_pos = x, y
|
self.last_mouse_cursor_pos = x, y
|
||||||
get_boss().change_mouse_cursor(self.char_grid.has_url_at(x, y))
|
get_boss().change_mouse_cursor(self.has_url_at(x, y))
|
||||||
if send_event:
|
if send_event:
|
||||||
x, y = self.char_grid.cell_for_pos(x, y)
|
x, y = self.cell_for_pos(x, y)
|
||||||
if x is not None:
|
if x is not None:
|
||||||
ev = encode_mouse_event(mode, self.screen.mouse_tracking_protocol(),
|
ev = encode_mouse_event(mode, self.screen.mouse_tracking_protocol(),
|
||||||
button, action, 0, x, y)
|
button, action, 0, x, y)
|
||||||
@ -235,7 +366,7 @@ class Window:
|
|||||||
self.write_to_child(ev)
|
self.write_to_child(ev)
|
||||||
else:
|
else:
|
||||||
if self.screen.is_selection_in_progress():
|
if self.screen.is_selection_in_progress():
|
||||||
self.char_grid.update_drag(None, x, y)
|
self.update_drag(None, x, y)
|
||||||
margin = cell_size.height // 2
|
margin = cell_size.height // 2
|
||||||
if y <= margin or y >= self.geometry.bottom - margin:
|
if y <= margin or y >= self.geometry.bottom - margin:
|
||||||
get_boss().ui_timers.add(0.02, self.drag_scroll)
|
get_boss().ui_timers.add(0.02, self.drag_scroll)
|
||||||
@ -245,7 +376,7 @@ class Window:
|
|||||||
margin = cell_size.height // 2
|
margin = cell_size.height // 2
|
||||||
if y <= margin or y >= self.geometry.bottom - margin:
|
if y <= margin or y >= self.geometry.bottom - margin:
|
||||||
self.scroll_line_up() if y < 50 else self.scroll_line_down()
|
self.scroll_line_up() if y < 50 else self.scroll_line_down()
|
||||||
self.char_grid.update_drag(None, x, y)
|
self.update_drag(None, x, y)
|
||||||
return 0.02 # causes the timer to be re-added
|
return 0.02 # causes the timer to be re-added
|
||||||
|
|
||||||
def on_mouse_scroll(self, x, y):
|
def on_mouse_scroll(self, x, y):
|
||||||
@ -261,7 +392,7 @@ class Window:
|
|||||||
send_event = mode > 0
|
send_event = mode > 0
|
||||||
if send_event:
|
if send_event:
|
||||||
x, y = self.last_mouse_cursor_pos
|
x, y = self.last_mouse_cursor_pos
|
||||||
x, y = self.char_grid.cell_for_pos(x, y)
|
x, y = self.cell_for_pos(x, y)
|
||||||
if x is not None:
|
if x is not None:
|
||||||
ev = encode_mouse_event(mode, self.screen.mouse_tracking_protocol(),
|
ev = encode_mouse_event(mode, self.screen.mouse_tracking_protocol(),
|
||||||
GLFW_MOUSE_BUTTON_4 if upwards else GLFW_MOUSE_BUTTON_5, PRESS, 0, x, y)
|
GLFW_MOUSE_BUTTON_4 if upwards else GLFW_MOUSE_BUTTON_5, PRESS, 0, x, y)
|
||||||
@ -270,9 +401,7 @@ class Window:
|
|||||||
else:
|
else:
|
||||||
k = get_key_map(self.screen)[GLFW_KEY_UP if upwards else GLFW_KEY_DOWN]
|
k = get_key_map(self.screen)[GLFW_KEY_UP if upwards else GLFW_KEY_DOWN]
|
||||||
self.write_to_child(k * abs(s))
|
self.write_to_child(k * abs(s))
|
||||||
|
# }}}
|
||||||
def buf_toggled(self, is_main_linebuf):
|
|
||||||
self.screen.scroll(SCROLL_FULL, False)
|
|
||||||
|
|
||||||
def destroy(self):
|
def destroy(self):
|
||||||
if self.vao_id is not None:
|
if self.vao_id is not None:
|
||||||
@ -282,7 +411,10 @@ class Window:
|
|||||||
# actions {{{
|
# actions {{{
|
||||||
|
|
||||||
def show_scrollback(self):
|
def show_scrollback(self):
|
||||||
data = self.char_grid.get_scrollback_as_ansi()
|
data = []
|
||||||
|
self.screen.historybuf.as_ansi(data.append)
|
||||||
|
self.screen.linebuf.as_ansi(data.append)
|
||||||
|
data = ''.join(data).encode('utf-8')
|
||||||
get_boss().display_scrollback(data)
|
get_boss().display_scrollback(data)
|
||||||
|
|
||||||
def paste(self, text):
|
def paste(self, text):
|
||||||
@ -301,7 +433,7 @@ class Window:
|
|||||||
self.paste(text)
|
self.paste(text)
|
||||||
|
|
||||||
def copy_to_clipboard(self):
|
def copy_to_clipboard(self):
|
||||||
text = self.char_grid.text_for_selection()
|
text = self.text_for_selection()
|
||||||
if text:
|
if text:
|
||||||
get_boss().glfw_window.set_clipboard_string(text)
|
get_boss().glfw_window.set_clipboard_string(text)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user