summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTim Allen <screwtape@froup.com>2018-03-15 23:02:27 +1100
committerTim Allen <screwtape@froup.com>2018-04-11 15:15:45 +1000
commit50e422659bb1533da2d5f04bbd17de062dcad84b (patch)
tree2c3c7fba8ec46a5491c0e27c6ea311952e2c0460 /src
parentd846400279a1831d3d29a4dd179fbf799b4ee541 (diff)
Add support for the shift modifier.
Because keyboard layouts vary, the shift-modifier `<s-…>` is only supported for special keys (like `<up>` and `<home>`) and for ASCII lowercase where we assume the shift-modifier just produces the matching uppercase character. Even that's not universally true, since in Turkish `i` and `I` are not an uppercase/lowercase pair, but Kakoune's default keyboard mappings already assume en-US mappings for mnemonic purposes. Mappings of the form `<s-x>` are normalized to `<X>` when `x` is an ASCII character. `<backtab>` is removed, since we can now say `<s-tab>`.
Diffstat (limited to 'src')
-rw-r--r--src/assert.hh8
-rw-r--r--src/input_handler.cc16
-rw-r--r--src/keys.cc77
-rw-r--r--src/keys.hh23
-rw-r--r--src/main.cc3
-rw-r--r--src/ncurses_ui.cc11
-rw-r--r--src/normal.cc7
7 files changed, 106 insertions, 39 deletions
diff --git a/src/assert.hh b/src/assert.hh
index 3d807ea6..ff642c9a 100644
--- a/src/assert.hh
+++ b/src/assert.hh
@@ -22,8 +22,16 @@ void on_assert_failed(const char* message);
on_assert_failed("assert failed \"" #__VA_ARGS__ \
"\" at " __FILE__ ":" TOSTRING(__LINE__)); \
} while (false)
+
+ #define kak_expect_throw(exception_type, ...) try {\
+ __VA_ARGS__; \
+ on_assert_failed("expression \"" #__VA_ARGS__ \
+ "\" did not throw \"" #exception_type \
+ "\" at " __FILE__ ":" TOSTRING(__LINE__)); \
+ } catch (exception_type &err) {}
#else
#define kak_assert(...) do { (void)sizeof(__VA_ARGS__); } while(false)
+ #define kak_expect_throw(_, ...) do { (void)sizeof(__VA_ARGS__); } while(false)
#endif
diff --git a/src/input_handler.cc b/src/input_handler.cc
index 2ee1d40d..1466ae02 100644
--- a/src/input_handler.cc
+++ b/src/input_handler.cc
@@ -461,15 +461,15 @@ public:
}
else if (key == ctrl('w'))
to_next_word_begin<Word>(m_cursor_pos, m_line);
- else if (key == ctrlalt('w'))
+ else if (key == ctrl(alt('w')))
to_next_word_begin<WORD>(m_cursor_pos, m_line);
else if (key == ctrl('b'))
to_prev_word_begin<Word>(m_cursor_pos, m_line);
- else if (key == ctrlalt('b'))
+ else if (key == ctrl(alt('b')))
to_prev_word_begin<WORD>(m_cursor_pos, m_line);
else if (key == ctrl('e'))
to_next_word_end<Word>(m_cursor_pos, m_line);
- else if (key == ctrlalt('e'))
+ else if (key == ctrl(alt('e')))
to_next_word_end<WORD>(m_cursor_pos, m_line);
else if (key == ctrl('k'))
m_line = m_line.substr(0_char, m_cursor_pos).str();
@@ -623,7 +623,7 @@ public:
it = std::find_if(m_choices.begin(), m_selected, match_filter);
select(it);
}
- else if (key == Key::Up or key == Key::BackTab or
+ else if (key == Key::Up or key == shift(Key::Tab) or
key == ctrl('p') or (not m_edit_filter and key == 'k'))
{
ChoiceList::const_reverse_iterator selected(m_selected+1);
@@ -837,9 +837,9 @@ public:
m_refresh_completion_pending = true;
}
}
- else if (key == Key::Tab or key == Key::BackTab) // tab completion
+ else if (key == Key::Tab or key == shift(Key::Tab)) // tab completion
{
- const bool reverse = (key == Key::BackTab);
+ const bool reverse = (key == shift(Key::Tab));
CandidateList& candidates = m_completions.candidates;
// first try, we need to ask our completer for completions
if (candidates.empty())
@@ -1568,8 +1568,10 @@ InputHandler::ScopedForceNormal::~ScopedForceNormal()
static bool is_valid(Key key)
{
+ constexpr Key::Modifiers valid_mods = (Key::Modifiers::Control | Key::Modifiers::Alt | Key::Modifiers::Shift);
+
return key != Key::Invalid and
- ((key.modifiers & ~Key::Modifiers::ControlAlt) or key.key <= 0x10FFFF);
+ ((key.modifiers & ~valid_mods) or key.key <= 0x10FFFF);
}
void InputHandler::handle_key(Key key)
diff --git a/src/keys.cc b/src/keys.cc
index 412b1c63..e4318707 100644
--- a/src/keys.cc
+++ b/src/keys.cc
@@ -11,6 +11,11 @@
namespace Kakoune
{
+struct key_parse_error : runtime_error
+{
+ using runtime_error::runtime_error;
+};
+
static Key canonicalize_ifn(Key key)
{
if (key.key > 0 and key.key < 27)
@@ -19,6 +24,22 @@ static Key canonicalize_ifn(Key key)
key.modifiers = Key::Modifiers::Control;
key.key = key.key - 1 + 'a';
}
+
+ if (key.modifiers & Key::Modifiers::Shift)
+ {
+ if (is_basic_alpha(key.key))
+ {
+ // Shift + ASCII letters is just the uppercase letter.
+ key.modifiers &= ~Key::Modifiers::Shift;
+ key.key = to_upper(key.key);
+ }
+ else if (key.key < 0xD800 || key.key > 0xDFFF)
+ {
+ // Shift + any other printable character is not allowed.
+ throw key_parse_error(format("Shift modifier only works on special keys and lowercase ASCII, not '{}'", key.key));
+ }
+ }
+
return key;
}
@@ -53,7 +74,6 @@ static constexpr KeyAndName keynamemap[] = {
{ "pagedown", Key::PageDown },
{ "home", Key::Home },
{ "end", Key::End },
- { "backtab", Key::BackTab },
{ "del", Key::Delete },
{ "plus", '+' },
{ "minus", '-' },
@@ -85,16 +105,17 @@ KeyList parse_keys(StringView str)
for (auto dash = find(desc, '-'); dash != desc.end(); dash = find(desc, '-'))
{
if (dash != desc.begin() + 1)
- throw runtime_error(format("unable to parse modifier in '{}'",
- full_desc));
+ throw key_parse_error(format("unable to parse modifier in '{}'",
+ full_desc));
switch(to_lower(desc[0_byte]))
{
case 'c': modifier |= Key::Modifiers::Control; break;
case 'a': modifier |= Key::Modifiers::Alt; break;
+ case 's': modifier |= Key::Modifiers::Shift; break;
default:
- throw runtime_error(format("unable to parse modifier in '{}'",
- full_desc));
+ throw key_parse_error(format("unable to parse modifier in '{}'",
+ full_desc));
}
desc = StringView{dash+1, desc.end()};
}
@@ -104,17 +125,17 @@ KeyList parse_keys(StringView str)
if (name_it != std::end(keynamemap))
result.push_back(canonicalize_ifn({ modifier, name_it->key }));
else if (desc.char_length() == 1)
- result.emplace_back(modifier, desc[0_char]);
+ result.push_back(canonicalize_ifn({ modifier, desc[0_char] }));
else if (to_lower(desc[0_byte]) == 'f' and desc.length() <= 3)
{
int val = str_to_int(desc.substr(1_byte));
if (val >= 1 and val <= 12)
result.emplace_back(modifier, Key::F1 + (val - 1));
else
- throw runtime_error("only F1 through F12 are supported");
+ throw key_parse_error(format("only F1 through F12 are supported, not '{}'", desc));
}
else
- throw runtime_error("unable to parse " +
+ throw key_parse_error("unable to parse " +
StringView{it.base(), end_it.base()+1});
it = end_it;
@@ -165,13 +186,10 @@ String key_to_str(Key key)
else
res = String{key.key};
- switch (key.modifiers)
- {
- case Key::Modifiers::Control: res = "c-" + res; named = true; break;
- case Key::Modifiers::Alt: res = "a-" + res; named = true; break;
- case Key::Modifiers::ControlAlt: res = "c-a-" + res; named = true; break;
- default: break;
- }
+ if (key.modifiers & Key::Modifiers::Shift) { res = "s-" + res; named = true; }
+ if (key.modifiers & Key::Modifiers::Alt) { res = "a-" + res; named = true; }
+ if (key.modifiers & Key::Modifiers::Control) { res = "c-" + res; named = true; }
+
if (named)
res = StringView{'<'} + res + StringView{'>'};
return res;
@@ -182,8 +200,10 @@ UnitTest test_keys{[]()
KeyList keys{
{ ' ' },
{ 'c' },
- { Key::Modifiers::Alt, 'j' },
- { Key::Modifiers::Control, 'r' }
+ { Key::Up },
+ alt('j'),
+ ctrl('r'),
+ shift(Key::Up),
};
String keys_as_str;
for (auto& key : keys)
@@ -191,7 +211,28 @@ UnitTest test_keys{[]()
auto parsed_keys = parse_keys(keys_as_str);
kak_assert(keys == parsed_keys);
kak_assert(ConstArrayView<Key>{parse_keys("a<c-a-b>c")} ==
- ConstArrayView<Key>{'a', {Key::Modifiers::ControlAlt, 'b'}, 'c'});
+ ConstArrayView<Key>{'a', ctrl(alt({'b'})), 'c'});
+
+ kak_assert(parse_keys("x") == KeyList{ {'x'} });
+ kak_assert(parse_keys("<x>") == KeyList{ {'x'} });
+ kak_assert(parse_keys("<s-x>") == KeyList{ {'X'} });
+ kak_assert(parse_keys("<s-X>") == KeyList{ {'X'} });
+ kak_assert(parse_keys("<X>") == KeyList{ {'X'} });
+ kak_assert(parse_keys("X") == KeyList{ {'X'} });
+ kak_assert(parse_keys("<s-up>") == KeyList{ shift({Key::Up}) });
+ kak_assert(parse_keys("<s-tab>") == KeyList{ shift({Key::Tab}) });
+
+ kak_assert(key_to_str(shift({Key::Tab})) == "<s-tab>");
+
+ kak_expect_throw(key_parse_error, parse_keys("<-x>"));
+ kak_expect_throw(key_parse_error, parse_keys("<xy-z>"));
+ kak_expect_throw(key_parse_error, parse_keys("<x-y>"));
+ kak_expect_throw(key_parse_error, parse_keys("<s-/>"));
+ kak_expect_throw(key_parse_error, parse_keys("<s-ë>"));
+ kak_expect_throw(key_parse_error, parse_keys("<s-lt>"));
+ kak_expect_throw(key_parse_error, parse_keys("<f99>"));
+ kak_expect_throw(key_parse_error, parse_keys("<backtab>"));
+ kak_expect_throw(key_parse_error, parse_keys("<invalidkey>"));
}};
}
diff --git a/src/keys.hh b/src/keys.hh
index e1e6ff35..9b632dcb 100644
--- a/src/keys.hh
+++ b/src/keys.hh
@@ -19,17 +19,17 @@ struct Key
None = 0,
Control = 1 << 0,
Alt = 1 << 1,
- ControlAlt = Control | Alt,
+ Shift = 1 << 2,
- MousePress = 1 << 2,
- MouseRelease = 1 << 3,
- MousePos = 1 << 4,
- MouseWheelDown = 1 << 5,
- MouseWheelUp = 1 << 6,
+ MousePress = 1 << 3,
+ MouseRelease = 1 << 4,
+ MousePos = 1 << 5,
+ MouseWheelDown = 1 << 6,
+ MouseWheelUp = 1 << 7,
MouseEvent = MousePress | MouseRelease | MousePos |
MouseWheelDown | MouseWheelUp,
- Resize = 1 << 7,
+ Resize = 1 << 8,
};
enum NamedKey : Codepoint
{
@@ -47,7 +47,6 @@ struct Key
Home,
End,
Tab,
- BackTab,
F1,
F2,
F3,
@@ -97,6 +96,10 @@ class StringView;
KeyList parse_keys(StringView str);
String key_to_str(Key key);
+constexpr Key shift(Key key)
+{
+ return { key.modifiers | Key::Modifiers::Shift, key.key };
+}
constexpr Key alt(Key key)
{
return { key.modifiers | Key::Modifiers::Alt, key.key };
@@ -105,10 +108,6 @@ constexpr Key ctrl(Key key)
{
return { key.modifiers | Key::Modifiers::Control, key.key };
}
-constexpr Key ctrlalt(Key key)
-{
- return { key.modifiers | Key::Modifiers::ControlAlt, key.key };
-}
constexpr Codepoint encode_coord(DisplayCoord coord) { return (Codepoint)(((int)coord.line << 16) | ((int)coord.column & 0x0000FFFF)); }
diff --git a/src/main.cc b/src/main.cc
index 53986525..e152fa94 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -53,7 +53,8 @@ static const char* startup_info =
" * 'x' will only jump to next line if full line is already selected\n"
" * WORD text object moved to <a-w> instead of W for consistency\n"
" * rotate main selection moved to ), rotate content to <a-)>, ( for backward\n"
-" * faces are now scoped, set-face command takes an additional scope parameter\n";
+" * faces are now scoped, set-face command takes an additional scope parameter\n"
+" * <backtab> key is gone, use <s-tab> instead\n";
struct startup_error : runtime_error
{
diff --git a/src/ncurses_ui.cc b/src/ncurses_ui.cc
index 42023ebb..e6863945 100644
--- a/src/ncurses_ui.cc
+++ b/src/ncurses_ui.cc
@@ -570,15 +570,24 @@ Optional<Key> NCursesUI::get_next_key()
{
case KEY_BACKSPACE: case 127: return {Key::Backspace};
case KEY_DC: return {Key::Delete};
+ case KEY_SDC: return shift(Key::Delete);
case KEY_UP: return {Key::Up};
+ case KEY_SR: return shift(Key::Up);
case KEY_DOWN: return {Key::Down};
+ case KEY_SF: return shift(Key::Down);
case KEY_LEFT: return {Key::Left};
+ case KEY_SLEFT: return shift(Key::Left);
case KEY_RIGHT: return {Key::Right};
+ case KEY_SRIGHT: return shift(Key::Right);
case KEY_PPAGE: return {Key::PageUp};
+ case KEY_SPREVIOUS: return shift(Key::PageUp);
case KEY_NPAGE: return {Key::PageDown};
+ case KEY_SNEXT: return shift(Key::PageDown);
case KEY_HOME: return {Key::Home};
+ case KEY_SHOME: return shift(Key::Home);
case KEY_END: return {Key::End};
- case KEY_BTAB: return {Key::BackTab};
+ case KEY_SEND: return shift(Key::End);
+ case KEY_BTAB: return shift(Key::Tab);
case KEY_RESIZE: return resize(m_dimensions);
}
diff --git a/src/normal.cc b/src/normal.cc
index 4662a23d..3d4a266a 100644
--- a/src/normal.cc
+++ b/src/normal.cc
@@ -2062,6 +2062,11 @@ static const HashMap<Key, NormalCmd, MemoryDomain::Undefined, KeymapBackend> key
{ {'K'}, {"extend up", move<LineCount, Backward, SelectMode::Extend>} },
{ {'L'}, {"extend right", move<CharCount, Forward, SelectMode::Extend>} },
+ { shift(Key::Left), {"extend left", move<CharCount, Backward, SelectMode::Extend>} },
+ { shift(Key::Down), {"extend down", move<LineCount, Forward, SelectMode::Extend>} },
+ { shift(Key::Up), {"extend up", move<LineCount, Backward, SelectMode::Extend>} },
+ { shift(Key::Right), {"extend right", move<CharCount, Forward, SelectMode::Extend>} },
+
{ {'t'}, {"select to next character", select_to_next_char<SelectFlags::None>} },
{ {'f'}, {"select to next character included", select_to_next_char<SelectFlags::Inclusive>} },
{ {'T'}, {"extend to next character", select_to_next_char<SelectFlags::Extend>} },
@@ -2140,9 +2145,11 @@ static const HashMap<Key, NormalCmd, MemoryDomain::Undefined, KeymapBackend> key
{ {alt('l')}, {"select to line end", repeated<select<SelectMode::Replace, select_to_line_end<false>>>} },
{ {Key::End}, {"select to line end", repeated<select<SelectMode::Replace, select_to_line_end<false>>>} },
{ {alt('L')}, {"extend to line end", repeated<select<SelectMode::Extend, select_to_line_end<false>>>} },
+ { shift(Key::End), {"extend to line end", repeated<select<SelectMode::Extend, select_to_line_end<false>>>} },
{ {alt('h')}, {"select to line begin", repeated<select<SelectMode::Replace, select_to_line_begin<false>>>} },
{ {Key::Home}, {"select to line begin", repeated<select<SelectMode::Replace, select_to_line_begin<false>>>} },
{ {alt('H')}, {"extend to line begin", repeated<select<SelectMode::Extend, select_to_line_begin<false>>>} },
+ { shift(Key::Home), {"extend to line begin", repeated<select<SelectMode::Extend, select_to_line_begin<false>>>} },
{ {'x'}, {"select line", repeated<select<SelectMode::Replace, select_line>>} },
{ {'X'}, {"extend line", repeated<select<SelectMode::Extend, select_line>>} },