Skip to content

Commit

Permalink
Not key following key in input expression matches when key is released
Browse files Browse the repository at this point in the history
  • Loading branch information
houmain committed Dec 29, 2023
1 parent afdc83f commit 9104337
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 11 deletions.
33 changes: 28 additions & 5 deletions src/config/ParseKeySequence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()(
Expand Down Expand Up @@ -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' };
Expand Down
55 changes: 54 additions & 1 deletion src/test/test0_ParseKeySequence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)"));
Expand Down
9 changes: 5 additions & 4 deletions src/test/test2_MatchKeySequence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand All @@ -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);
}

//--------------------------------------------------------------------
Expand Down
91 changes: 90 additions & 1 deletion src/test/test3_Stage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -869,7 +959,6 @@ TEST_CASE("Output on release toggle virtual", "[Stage]") {
REQUIRE(stage.is_clear());
}


//--------------------------------------------------------------------

TEST_CASE("Output on release with timeout", "[Stage]") {
Expand Down

0 comments on commit 9104337

Please sign in to comment.