From f7c1702965795e7166801847d253050f81e32c26 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Fri, 26 Mar 2021 15:17:41 +1100 Subject: [PATCH 1/2] src/ncurses_ui: move the parse_mask() helper outside parse_csi(). It's useful for parsing modifier masks in all kinds of sequences, not just CSI sequences. Also, since the modifier mask always has "1" as "no modifiers", do the subtraction inside parse_mask() instead of when calling it. --- src/ncurses_ui.cc | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/ncurses_ui.cc b/src/ncurses_ui.cc index 17006c13..9c5262c6 100644 --- a/src/ncurses_ui.cc +++ b/src/ncurses_ui.cc @@ -657,7 +657,19 @@ Optional NCursesUI::get_next_key() return Key{utf8::codepoint(CharIterator{c}, Sentinel{})}; }; - auto parse_csi = [this, &convert]() -> Optional { + auto parse_mask = [](int mask) { + mask = std::max(mask - 1, 0); + Key::Modifiers mod = Key::Modifiers::None; + if (mask & 1) + mod |= Key::Modifiers::Shift; + if (mask & 2) + mod |= Key::Modifiers::Alt; + if (mask & 4) + mod |= Key::Modifiers::Control; + return mod; + }; + + auto parse_csi = [this, &convert, &parse_mask]() -> Optional { auto next_char = [] { return get_char().value_or((unsigned char)0xff); }; int params[16] = {}; auto c = next_char(); @@ -679,17 +691,6 @@ Optional NCursesUI::get_next_key() if (c != '$' and (c < 0x40 or c > 0x7e)) return {}; - auto parse_mask = [](int mask) { - Key::Modifiers mod = Key::Modifiers::None; - if (mask & 1) - mod |= Key::Modifiers::Shift; - if (mask & 2) - mod |= Key::Modifiers::Alt; - if (mask & 4) - mod |= Key::Modifiers::Control; - return mod; - }; - auto mouse_button = [this](Key::Modifiers mod, Key::MouseButton button, Codepoint coord, bool release) { auto mask = 1 << (int)button; if (not release) @@ -710,7 +711,7 @@ Optional NCursesUI::get_next_key() (Codepoint)((down ? 1 : -1) * m_wheel_scroll_amount)}; }; - auto masked_key = [&](Codepoint key) { return Key{parse_mask(std::max(params[1] - 1, 0)), key}; }; + auto masked_key = [&](Codepoint key) { return Key{parse_mask(params[1]), key}; }; switch (c) { From 3aaf32f48f2cc76572888e90fb0a66a67643ff31 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Fri, 26 Mar 2021 15:43:42 +1100 Subject: [PATCH 2/2] src/ncurses_ui: Teach Kakoune about all the numeric keypad keys. Kakoune now knows about all the keypad keys listed in: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-VT220-Style-Function-Keys The VT220-style encodings are used to for modified numeric keys when NumLock is off. For example, consider the 8/Up key: | Modifiers | Sequence | Notes | |-----------------|-------------|-------------------| | Unmodified | CSI A | Ordinary up arrow | | Shift | SS3 2 x | Shift-8 | | NumLock | 8 | Ordinary 8 | | Shift + NumLock | CSI 1 ; 2 A | Shift-Up | Note that even though the terminal distinguishes between keypad and regular keys, Kakoune maps keypad keys onto regular keys - keypad Enter is still , it just supports more modifiers than the regular Enter key. --- src/ncurses_ui.cc | 52 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/src/ncurses_ui.cc b/src/ncurses_ui.cc index 9c5262c6..9f7b9011 100644 --- a/src/ncurses_ui.cc +++ b/src/ncurses_ui.cc @@ -726,6 +726,7 @@ Optional NCursesUI::get_next_key() case 'B': return masked_key(Key::Down); case 'C': return masked_key(Key::Right); case 'D': return masked_key(Key::Left); + case 'E': return masked_key('5'); // Numeric keypad 5 case 'F': return masked_key(Key::End); // PC/xterm style case 'H': return masked_key(Key::Home); // PC/xterm style case 'P': return masked_key(Key::F1); @@ -792,19 +793,46 @@ Optional NCursesUI::get_next_key() return {}; }; - auto parse_ss3 = []() -> Optional { - switch (get_char().value_or((unsigned char)0xff)) + auto parse_ss3 = [&parse_mask]() -> Optional { + int raw_mask = 0; + char code = '0'; + do { + raw_mask = raw_mask * 10 + (code - '0'); + code = get_char().value_or((unsigned char)0xff); + } while (code >= '0' and code <= '9'); + + switch (code) { - case 'A': return Key{Key::Up}; - case 'B': return Key{Key::Down}; - case 'C': return Key{Key::Right}; - case 'D': return Key{Key::Left}; - case 'F': return Key{Key::End}; - case 'H': return Key{Key::Home}; - case 'P': return Key{Key::F1}; - case 'Q': return Key{Key::F2}; - case 'R': return Key{Key::F3}; - case 'S': return Key{Key::F4}; + case ' ': return Key{parse_mask(raw_mask), ' '}; + case 'A': return Key{parse_mask(raw_mask), Key::Up}; + case 'B': return Key{parse_mask(raw_mask), Key::Down}; + case 'C': return Key{parse_mask(raw_mask), Key::Right}; + case 'D': return Key{parse_mask(raw_mask), Key::Left}; + case 'F': return Key{parse_mask(raw_mask), Key::End}; + case 'H': return Key{parse_mask(raw_mask), Key::Home}; + case 'I': return Key{parse_mask(raw_mask), Key::Tab}; + case 'M': return Key{parse_mask(raw_mask), Key::Return}; + case 'P': return Key{parse_mask(raw_mask), Key::F1}; + case 'Q': return Key{parse_mask(raw_mask), Key::F2}; + case 'R': return Key{parse_mask(raw_mask), Key::F3}; + case 'S': return Key{parse_mask(raw_mask), Key::F4}; + case 'X': return Key{parse_mask(raw_mask), '='}; + case 'j': return Key{parse_mask(raw_mask), '*'}; + case 'k': return Key{parse_mask(raw_mask), '+'}; + case 'l': return Key{parse_mask(raw_mask), ','}; + case 'm': return Key{parse_mask(raw_mask), '-'}; + case 'n': return Key{parse_mask(raw_mask), '.'}; + case 'o': return Key{parse_mask(raw_mask), '/'}; + case 'p': return Key{parse_mask(raw_mask), '0'}; + case 'q': return Key{parse_mask(raw_mask), '1'}; + case 'r': return Key{parse_mask(raw_mask), '2'}; + case 's': return Key{parse_mask(raw_mask), '3'}; + case 't': return Key{parse_mask(raw_mask), '4'}; + case 'u': return Key{parse_mask(raw_mask), '5'}; + case 'v': return Key{parse_mask(raw_mask), '6'}; + case 'w': return Key{parse_mask(raw_mask), '7'}; + case 'x': return Key{parse_mask(raw_mask), '8'}; + case 'y': return Key{parse_mask(raw_mask), '9'}; default: return {}; } };