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>`.
This commit is contained in:
parent
d846400279
commit
50e422659b
|
@ -6,8 +6,8 @@ Usual keys are written using their ascii character, including capital
|
|||
keys. Non printable keys use an alternate name, written between *<*
|
||||
and *>*, such as *<esc>* or *<del>*. Modified keys are written between
|
||||
*<* and *>* as well, with the modifier specified as either *c* for
|
||||
Control, or *a* for Alt, followed by a *-* and the key (either its
|
||||
name or ascii character), for example *<c-x>*, *<a-space>*, *<c-a-w>*.
|
||||
Control, *a* for Alt, or *s* for Shift, followed by a *-* and the key (either
|
||||
its name or ascii character), for example *<c-x>*, *<a-space>*, *<c-a-w>*.
|
||||
|
||||
In order to bind some keys to arbitrary ones, refer to <<mapping#,`:doc mapping`>>
|
||||
|
||||
|
@ -95,18 +95,20 @@ it when pasting text.
|
|||
== Movement
|
||||
|
||||
'word' is a sequence of alphanumeric characters or underscore, and 'WORD'
|
||||
is a sequence of non whitespace characters
|
||||
is a sequence of non whitespace characters. Generally, a movement on it own
|
||||
will move the selection to cover the text moved over, while holding down
|
||||
the Shift modifier and moving will extend the selection instead.
|
||||
|
||||
*h*::
|
||||
*h*, *<left>*::
|
||||
select the character on the left of selection end
|
||||
|
||||
*j*::
|
||||
*j*, *<down>*::
|
||||
select the character below the selection end
|
||||
|
||||
*k*::
|
||||
*k*, *<up>*::
|
||||
select the character above the selection end
|
||||
|
||||
*l*::
|
||||
*l*, *<right>*::
|
||||
select the character on the right of selection end
|
||||
|
||||
*w*::
|
||||
|
@ -134,16 +136,10 @@ is a sequence of non whitespace characters
|
|||
select to matching character, see the `matching_pairs` option
|
||||
in <<options#,`:doc options`>>
|
||||
|
||||
*M*::
|
||||
extend selection to matching character
|
||||
|
||||
*x*::
|
||||
select line on which selection end lies (or next line when end lies
|
||||
on an end-of-line)
|
||||
|
||||
*X*::
|
||||
similar to *x*, except the current selection is extended
|
||||
|
||||
*<a-x>*::
|
||||
expand selections to contain full lines (including end-of-lines)
|
||||
|
||||
|
@ -154,10 +150,10 @@ is a sequence of non whitespace characters
|
|||
*%*::
|
||||
select whole buffer
|
||||
|
||||
*<a-h>*::
|
||||
*<a-h>*, *<home>*::
|
||||
select to line begin
|
||||
|
||||
*<a-l>*::
|
||||
*<a-l>*, *<end>*::
|
||||
select to line end
|
||||
|
||||
*/*::
|
||||
|
@ -696,7 +692,7 @@ The following keys are recognized by this mode to help edition.
|
|||
*<tab>*::
|
||||
select next completion candidate
|
||||
|
||||
*<backtab>*::
|
||||
*<s-tab>*::
|
||||
select previous completion candidate
|
||||
|
||||
*<c-r>*::
|
||||
|
|
|
@ -65,15 +65,20 @@ be used:
|
|||
Keys can also be wrapped in angle-brackets for consistency
|
||||
with the non-alphabetic keys below.
|
||||
|
||||
*X*, *<X>*::
|
||||
Holding down Shift while pressing the *x* key.
|
||||
|
||||
*<c-x>*::
|
||||
Holding down Control while pressing the *x* key.
|
||||
|
||||
*<a-x>*::
|
||||
Holding down Alt while pressing the *x* key.
|
||||
|
||||
*<s-x>*, *X*, *<X>*, *<s-X>*::
|
||||
Holding down Shift while pressing the *x* key.
|
||||
*<s-x>*, *<s-X>* and *<X>* are treated as the same key. The *s-* modifier
|
||||
only works with ASCII letters and cannot be used with other printable keys
|
||||
(non-ASCII letters, digits, punctuation) because their shift behaviour
|
||||
depends on your keyboard layout. The *s-* modifier _can_ be used with
|
||||
special keys like *<up>* and *<tab>*.
|
||||
|
||||
*<c-a-x>*::
|
||||
Holding down Control and Alt while pressing the *x* key.
|
||||
|
||||
|
@ -92,9 +97,6 @@ be used:
|
|||
*<tab>*::
|
||||
The Tab key.
|
||||
|
||||
*<backtab>*::
|
||||
The reverse-tab key. This is Shift-Tab on most keyboards.
|
||||
|
||||
*<backspace>*::
|
||||
The Backspace (delete to the left) key.
|
||||
|
||||
|
@ -110,3 +112,8 @@ be used:
|
|||
|
||||
*<f1>*, *<f2>*, ...*<f12>*::
|
||||
Function keys.
|
||||
|
||||
NOTE: Although Kakoune allows many key combinations to be mapped, not every
|
||||
possible combination can be triggered. For example, due to limitations in
|
||||
the way terminals handle control characters, mappings like *<c-s-a>* are
|
||||
unlikely to work in Kakoune's terminal UI.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
73
src/keys.cc
73
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,15 +105,16 @@ 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 '{}'",
|
||||
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 '{}'",
|
||||
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>"));
|
||||
}};
|
||||
|
||||
}
|
||||
|
|
23
src/keys.hh
23
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)); }
|
||||
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>>} },
|
||||
|
|
Loading…
Reference in New Issue
Block a user