From f0c546208da2a185e8b733129391011a73995fea Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 17 May 2017 08:55:57 +0530 Subject: [PATCH] Implement OSC codes for changing selection colors --- kitty/char_grid.py | 63 +++++++++++++++++++++++++++------------------- kitty/data-types.h | 1 + kitty/parser.c | 4 +++ kitty/screen.c | 4 +++ kitty/window.py | 14 ++++++++--- 5 files changed, 57 insertions(+), 29 deletions(-) diff --git a/kitty/char_grid.py b/kitty/char_grid.py index 958cba974..c5867b913 100644 --- a/kitty/char_grid.py +++ b/kitty/char_grid.py @@ -4,6 +4,7 @@ import re import sys +from enum import Enum from collections import namedtuple from ctypes import addressof, memmove, sizeof from threading import Lock @@ -26,6 +27,11 @@ Cursor = namedtuple('Cursor', 'x y shape blink') + +class DynamicColor(Enum): + default_fg, default_bg, cursor_color, highlight_fg, highlight_bg = range(1, 6) + + if DATA_CELL_SIZE % 3: raise ValueError('Incorrect data cell size, must be a multiple of 3') @@ -252,17 +258,13 @@ def __init__(self, screen, opts): self.color_profile.update_ansi_color_table(build_ansi_color_table(opts)) self.screen = screen self.opts = opts - self.original_bg = opts.background - self.original_fg = opts.foreground - self.default_bg = color_as_int(self.original_bg) - self.default_fg = color_as_int(self.original_fg) + self.original_bg = self.default_bg = color_as_int(opts.background) + self.original_fg = self.default_fg = color_as_int(opts.foreground) self.dpix, self.dpiy = get_logical_dpi() self.opts = opts self.default_cursor = self.current_cursor = Cursor(0, 0, opts.cursor_shape, opts.cursor_blink_interval > 0) self.opts = opts - self.original_bg = opts.background - self.original_fg = opts.foreground - self.selection_foreground, self.selection_background = map(color_as_int, (opts.selection_foreground, opts.selection_background)) + self.highlight_fg, self.highlight_bg = map(color_as_int, (opts.selection_foreground, opts.selection_background)) self.sprite_map_type = self.main_sprite_map = self.scroll_sprite_map = self.render_buf = None def escape(chars): @@ -290,25 +292,30 @@ def resize(self, window_geometry): def change_colors(self, changes): dirtied = False + + def item(raw): + if raw is None: + return True, None + val = to_color(raw) + if val is None: + return False, None + return True, color_as_int(val) + for which, val in changes.items(): - if which in ('fg', 'bg'): - if not val: - setattr(self, 'default_' + which, color_as_int(getattr(self, 'original_' + which))) - dirtied = True - else: - val = to_color(val) - if val is not None: - setattr(self, 'default_' + which, color_as_int(val)) - dirtied = True - elif which == 'cc': - if not val: - self.screen.cursor.color = 0 - dirtied = True - else: - val = to_color(val) - if val is not None: - self.screen.cursor.color = (color_as_int(val) << 8) | 1 - dirtied = True + valid, val = item(val) + if not valid: + continue + dirtied = True + if which is DynamicColor.default_fg: + self.default_fg = self.original_fg if val is None else val + elif which is DynamicColor.default_bg: + self.default_bg = self.original_bg if val is None else val + elif which is DynamicColor.cursor_color: + self.screen.cursor.color = 0 if val is None else (val << 8) | 1 + elif which is DynamicColor.highlight_fg: + self.screen.highlight_fg = 0 if val is None else (val << 8) | 1 + elif which is DynamicColor.highlight_bg: + self.screen.highlight_bg = 0 if val is None else (val << 8) | 1 if dirtied: self.screen.mark_as_dirty() @@ -474,7 +481,11 @@ def prepare_for_render(self, sprites): buf = self.selection_buf if self.render_buf_is_dirty or sel != self.last_rendered_selection: memmove(buf, self.render_buf, sizeof(type(buf))) - self.screen.apply_selection(addressof(buf), start[0], start[1], end[0], end[1], self.selection_foreground, self.selection_background) + fg = self.screen.highlight_fg + fg = fg >> 8 if fg & 1 else self.highlight_fg + bg = self.screen.highlight_bg + bg = bg >> 8 if bg & 1 else self.highlight_bg + self.screen.apply_selection(addressof(buf), start[0], start[1], end[0], end[1], fg, bg) if self.render_buf_is_dirty or self.last_rendered_selection != sel: sprites.set_sprite_map(self.buffer_id, buf) self.render_buf_is_dirty = False diff --git a/kitty/data-types.h b/kitty/data-types.h index 43f628bf3..0427e2b9f 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -289,6 +289,7 @@ typedef struct { unsigned int parser_state, parser_text_start, parser_buf_pos; bool parser_has_pending_text; uint8_t read_buf[READ_BUF_SZ]; + uint32_t highlight_fg, highlight_bg; } Screen; PyTypeObject Screen_Type; diff --git a/kitty/parser.c b/kitty/parser.c index 5d43c3f5b..c9a9016f1 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -296,9 +296,13 @@ dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { case 10: case 11: case 12: + case 17: + case 19: case 110: case 111: case 112: + case 117: + case 119: SET_COLOR(set_dynamic_color); break; default: diff --git a/kitty/screen.c b/kitty/screen.c index ce56172cb..bd8f23691 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -44,6 +44,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { self->columns = columns; self->lines = lines; self->modes = empty_modes; self->margin_top = 0; self->margin_bottom = self->lines - 1; + self->highlight_fg = 0; self->highlight_bg = 0; RESET_CHARSETS; self->callbacks = callbacks; Py_INCREF(callbacks); self->cursor = alloc_cursor(); @@ -68,6 +69,7 @@ screen_reset(Screen *self) { if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self); linebuf_clear(self->linebuf, ' '); self->modes = empty_modes; + self->highlight_fg = 0; self->highlight_bg = 0; RESET_CHARSETS; self->margin_top = 0; self->margin_bottom = self->lines - 1; screen_normal_keypad_mode(self); @@ -1304,6 +1306,8 @@ static PyMemberDef members[] = { {"columns", T_UINT, offsetof(Screen, columns), READONLY, "columns"}, {"margin_top", T_UINT, offsetof(Screen, margin_top), READONLY, "margin_top"}, {"margin_bottom", T_UINT, offsetof(Screen, margin_bottom), READONLY, "margin_bottom"}, + {"highlight_fg", T_ULONG, offsetof(Screen, highlight_fg), 0, "highlight_fg"}, + {"highlight_bg", T_ULONG, offsetof(Screen, highlight_bg), 0, "highlight_bg"}, {NULL} }; diff --git a/kitty/window.py b/kitty/window.py index cab58c2d4..92947378d 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -8,7 +8,7 @@ from functools import partial from time import monotonic -from .char_grid import CharGrid +from .char_grid import CharGrid, DynamicColor from .constants import wakeup, get_boss, appname, WindowGeometry, is_key_pressed, mouse_button_pressed, cell_size from .fast_data_types import ( BRACKETED_PASTE_START, BRACKETED_PASTE_END, Screen, read_bytes_dump, @@ -22,6 +22,15 @@ from .terminfo import get_capabilities from .utils import sanitize_title, get_primary_selection, parse_color_set, safe_print +DYNAMIC_COLOR_CODES = { + 10: DynamicColor.default_fg, + 11: DynamicColor.default_bg, + 12: DynamicColor.cursor_color, + 17: DynamicColor.highlight_bg, + 19: DynamicColor.highlight_fg, +} +DYNAMIC_COLOR_CODES.update({k+100: v for k, v in DYNAMIC_COLOR_CODES.items()}) + class Window: @@ -149,12 +158,11 @@ def icon_changed(self, new_icon): pass # TODO: Implement this def set_dynamic_color(self, code, value): - wmap = {10: 'fg', 11: 'bg', 12: 'cc', 110: 'fg', 111: 'bg', 112: 'cc'} if isinstance(value, bytes): value = value.decode('utf-8') color_changes = {} for val in value.split(';'): - w = wmap.get(code) + w = DYNAMIC_COLOR_CODES.get(code) if w is not None: if code >= 110: val = None