From 9104337036b8e7b3309885499f776f989b540751 Mon Sep 17 00:00:00 2001 From: houmain Date: Fri, 22 Dec 2023 08:05:46 +0100 Subject: [PATCH] Not key following key in input expression matches when key is released --- src/config/ParseKeySequence.cpp | 33 +++++++++-- src/test/test0_ParseKeySequence.cpp | 55 ++++++++++++++++- src/test/test2_MatchKeySequence.cpp | 9 +-- src/test/test3_Stage.cpp | 91 ++++++++++++++++++++++++++++- 4 files changed, 177 insertions(+), 11 deletions(-) diff --git a/src/config/ParseKeySequence.cpp b/src/config/ParseKeySequence.cpp index 0c0ec9e5..ba8bac07 100644 --- a/src/config/ParseKeySequence.cpp +++ b/src/config/ParseKeySequence.cpp @@ -27,13 +27,23 @@ namespace { default: return key; } } - + bool has_key_down(const KeySequence& sequence) { return std::count_if(sequence.rbegin(), sequence.rend(), [&](const KeyEvent& event) { return (event.state == KeyState::Down && event.key != Key::timeout); }); } + + bool ends_with_async_up(const KeySequence& sequence, Key key) { + return (!sequence.empty() && sequence.back() == KeyEvent{ key, KeyState::UpAsync }); + } + + bool is_pressed(const KeySequence& sequence, Key key) { + const auto it = std::find_if(sequence.rbegin(), sequence.rend(), + [&](const KeyEvent& event) { return (event.key == key); }); + return (it != sequence.rend() && it->state != KeyState::Up); + } } // namespace KeySequence ParseKeySequence::operator()( @@ -230,16 +240,29 @@ void ParseKeySequence::parse(It it, const It end) { continue; } - if (in_together_group || in_modified_group) + if (in_together_group) throw ParseError("Unexpected '!'"); - flush_key_buffer(false); - const auto key = read_key(&it, end); + + // prevent A{!A} but allow A{B !B} + if (m_is_input && in_modified_group) + if (!std::count(m_key_buffer.begin(), m_key_buffer.end(), key)) + throw ParseError("Key to up not in modifier group"); + + flush_key_buffer(m_is_input); + if (remove_from_keys_not_up(key)) add_key_to_sequence(key, KeyState::UpAsync); - add_key_to_sequence(key, KeyState::Not); + // in input sequences conditionally insert Up or Not + // depending on whether there was a Down + if (m_is_input && ends_with_async_up(m_sequence, key)) + m_sequence.back().state = KeyState::Up; + else if (m_is_input && is_pressed(m_sequence, key)) + add_key_to_sequence(key, KeyState::Up); + else + add_key_to_sequence(key, KeyState::Not); } else if (skip(&it, end, "'") || skip(&it, end, "\"")) { char quote[2] = { *std::prev(it), '\0' }; diff --git a/src/test/test0_ParseKeySequence.cpp b/src/test/test0_ParseKeySequence.cpp index d77d59a7..8e2aea3b 100644 --- a/src/test/test0_ParseKeySequence.cpp +++ b/src/test/test0_ParseKeySequence.cpp @@ -102,13 +102,66 @@ TEST_CASE("Input Expression", "[ParseKeySequence]") { })); // Not - CHECK(parse_input("A !A B") == (KeySequence{ + + CHECK(parse_input("A !B") == (KeySequence{ + KeyEvent(Key::A, KeyState::Down), + KeyEvent(Key::A, KeyState::UpAsync), + KeyEvent(Key::B, KeyState::Not), + })); + + // Not as Up + CHECK(parse_input("A !A B !B") == (KeySequence{ + KeyEvent(Key::A, KeyState::Down), + KeyEvent(Key::A, KeyState::Up), + KeyEvent(Key::B, KeyState::Down), + KeyEvent(Key::B, KeyState::Up), + })); + + CHECK(parse_input("A B !A !B") == (KeySequence{ KeyEvent(Key::A, KeyState::Down), KeyEvent(Key::A, KeyState::UpAsync), + KeyEvent(Key::B, KeyState::Down), + KeyEvent(Key::B, KeyState::UpAsync), + KeyEvent(Key::A, KeyState::Up), + KeyEvent(Key::B, KeyState::Up), + })); + + CHECK(parse_input("A !A B") == (KeySequence{ + KeyEvent(Key::A, KeyState::Down), + KeyEvent(Key::A, KeyState::Up), + KeyEvent(Key::B, KeyState::Down), + KeyEvent(Key::B, KeyState::UpAsync), + })); + + CHECK(parse_input("!A B A !A") == (KeySequence{ KeyEvent(Key::A, KeyState::Not), KeyEvent(Key::B, KeyState::Down), KeyEvent(Key::B, KeyState::UpAsync), + KeyEvent(Key::A, KeyState::Down), + KeyEvent(Key::A, KeyState::Up), + })); + + CHECK(parse_input("A{B !B}") == (KeySequence{ + KeyEvent(Key::A, KeyState::Down), + KeyEvent(Key::B, KeyState::Down), + KeyEvent(Key::B, KeyState::Up), + KeyEvent(Key::A, KeyState::UpAsync), })); + + CHECK(parse_input("A{B !B} !A") == (KeySequence{ + KeyEvent(Key::A, KeyState::Down), + KeyEvent(Key::B, KeyState::Down), + KeyEvent(Key::B, KeyState::Up), + KeyEvent(Key::A, KeyState::Up), + })); + + CHECK(parse_input("A{B} !A") == (KeySequence{ + KeyEvent(Key::A, KeyState::Down), + KeyEvent(Key::B, KeyState::Down), + KeyEvent(Key::B, KeyState::UpAsync), + KeyEvent(Key::A, KeyState::Up), + })); + CHECK_THROWS(parse_input("!")); CHECK_THROWS(parse_input("!A")); CHECK_THROWS(parse_input("!(A B)")); diff --git a/src/test/test2_MatchKeySequence.cpp b/src/test/test2_MatchKeySequence.cpp index daca31e9..30a2695e 100644 --- a/src/test/test2_MatchKeySequence.cpp +++ b/src/test/test2_MatchKeySequence.cpp @@ -129,6 +129,7 @@ TEST_CASE("Match Not", "[MatchKeySequence]") { CHECK(match(expr, parse_sequence("+A +B")) == MatchResult::no_match); CHECK(match(expr, parse_sequence("+A +B +C")) == MatchResult::no_match); CHECK(match(expr, parse_sequence("+B")) == MatchResult::might_match); + CHECK(match(expr, parse_sequence("+B +A")) == MatchResult::no_match); CHECK(match(expr, parse_sequence("+B +C")) == MatchResult::match); REQUIRE_NOTHROW(expr = parse_input("B C !A")); @@ -140,12 +141,12 @@ TEST_CASE("Match Not", "[MatchKeySequence]") { REQUIRE_NOTHROW(expr = parse_input("A !A B")); CHECK(match(expr, parse_sequence("+A")) == MatchResult::might_match); - CHECK(match(expr, parse_sequence("+A +B")) == MatchResult::match); + CHECK(match(expr, parse_sequence("+A +B")) == MatchResult::no_match); CHECK(match(expr, parse_sequence("+A +C")) == MatchResult::no_match); + CHECK(match(expr, parse_sequence("+A -A")) == MatchResult::might_match); + CHECK(match(expr, parse_sequence("+A -A +A")) == MatchResult::no_match); CHECK(match(expr, parse_sequence("+A -A +B")) == MatchResult::match); - CHECK(match(expr, parse_sequence("+A +B -A")) == MatchResult::match); - CHECK(match(expr, parse_sequence("+A +A +B")) == MatchResult::no_match); - CHECK(match(expr, parse_sequence("+A -A +A +B")) == MatchResult::no_match); + CHECK(match(expr, parse_sequence("+A -A +C")) == MatchResult::no_match); } //-------------------------------------------------------------------- diff --git a/src/test/test3_Stage.cpp b/src/test/test3_Stage.cpp index 2345a45a..7371bedc 100644 --- a/src/test/test3_Stage.cpp +++ b/src/test/test3_Stage.cpp @@ -350,6 +350,96 @@ TEST_CASE("Any matches any key", "[Stage]") { //-------------------------------------------------------------------- +TEST_CASE("Not in input", "[Stage]") { + auto config = R"( + !C A >> X + B !B >> Y + D !D E >> Z + )"; + Stage stage = create_stage(config); + + REQUIRE(apply_input(stage, "+A") == "+X"); + REQUIRE(apply_input(stage, "+A") == "+X"); + REQUIRE(apply_input(stage, "-A") == "-X"); + REQUIRE(stage.is_clear()); + + REQUIRE(apply_input(stage, "+C") == "+C"); + REQUIRE(apply_input(stage, "+A") == "+A"); + REQUIRE(apply_input(stage, "-A") == "-A"); + REQUIRE(apply_input(stage, "-C") == "-C"); + REQUIRE(stage.is_clear()); + + // output on release + REQUIRE(apply_input(stage, "+B") == ""); + REQUIRE(apply_input(stage, "+B") == ""); + REQUIRE(apply_input(stage, "-B") == "+Y -Y"); + REQUIRE(stage.is_clear()); + + REQUIRE(apply_input(stage, "+D") == ""); + REQUIRE(apply_input(stage, "-D") == ""); + REQUIRE(apply_input(stage, "+E") == "+Z"); + REQUIRE(apply_input(stage, "-E") == "-Z"); + REQUIRE(stage.is_clear()); + + REQUIRE(apply_input(stage, "+D") == ""); + REQUIRE(apply_input(stage, "+E") == "+D +E"); + REQUIRE(apply_input(stage, "-E") == "-E"); + REQUIRE(apply_input(stage, "-D") == "-D"); + REQUIRE(stage.is_clear()); + + REQUIRE(apply_input(stage, "+D") == ""); + REQUIRE(apply_input(stage, "+B") == "+D"); + REQUIRE(apply_input(stage, "+B") == ""); + REQUIRE(apply_input(stage, "-B") == "+Y -Y"); + REQUIRE(apply_input(stage, "-D") == "-D"); + REQUIRE(stage.is_clear()); +} + +//-------------------------------------------------------------------- + +TEST_CASE("Not in input with modifier group", "[Stage]") { + auto config = R"( + A{B !B} >> X + C{D} !C >> Y + )"; + Stage stage = create_stage(config); + + REQUIRE(apply_input(stage, "+A") == ""); + REQUIRE(apply_input(stage, "+B") == ""); + REQUIRE(apply_input(stage, "-B") == "+X -X"); + REQUIRE(apply_input(stage, "-A") == ""); + REQUIRE(stage.is_clear()); + + REQUIRE(apply_input(stage, "+A") == ""); + REQUIRE(apply_input(stage, "+B") == ""); + // this is how it currently is, +A +B -A may be better... + REQUIRE(apply_input(stage, "-A") == "+A -A +B"); + REQUIRE(apply_input(stage, "-B") == "-B"); + REQUIRE(stage.is_clear()); + + REQUIRE(apply_input(stage, "+C") == ""); + REQUIRE(apply_input(stage, "+D") == ""); + REQUIRE(apply_input(stage, "-C") == "+Y -Y"); + REQUIRE(apply_input(stage, "-D") == ""); + REQUIRE(stage.is_clear()); + + REQUIRE(apply_input(stage, "+C") == ""); + REQUIRE(apply_input(stage, "+D") == ""); + REQUIRE(apply_input(stage, "-D") == ""); + REQUIRE(apply_input(stage, "-C") == "+Y -Y"); + REQUIRE(stage.is_clear()); + + REQUIRE(apply_input(stage, "+C") == ""); + REQUIRE(apply_input(stage, "+D") == ""); + REQUIRE(apply_input(stage, "+E") == "+C +D +E"); + REQUIRE(apply_input(stage, "-C") == "-C"); + REQUIRE(apply_input(stage, "-E") == "-E"); + REQUIRE(apply_input(stage, "-D") == "-D"); + REQUIRE(stage.is_clear()); +} + +//-------------------------------------------------------------------- + TEST_CASE("Not in output", "[Stage]") { auto config = R"( Shift >> Shift @@ -869,7 +959,6 @@ TEST_CASE("Output on release toggle virtual", "[Stage]") { REQUIRE(stage.is_clear()); } - //-------------------------------------------------------------------- TEST_CASE("Output on release with timeout", "[Stage]") {